diff --git a/doc/python_api/static/css/version_switch.css b/doc/python_api/static/css/version_switch.css index 80d5c2a52b0..b001a1a2ce7 100644 --- a/doc/python_api/static/css/version_switch.css +++ b/doc/python_api/static/css/version_switch.css @@ -31,23 +31,30 @@ cursor: pointer; z-index: 400; } + .version-btn-open::after { color: gray; } -.version-btn:hover, .version-btn:focus { + +.version-btn:hover, +.version-btn:focus { border-color: #525252; } + .version-btn-open { color: gray; border: solid 1px var(--color-sidebar-background-border); } + .version-btn.wait { cursor: wait; } + .version-btn.disabled { cursor: not-allowed; color: dimgray; } + .version-dialog { display: none; position: absolute; @@ -63,6 +70,7 @@ overflow-y: auto; cursor: default; } + .version-title { padding: 5px; color: var(--color-content-foreground); @@ -72,6 +80,7 @@ background-color: var(--color-brand-primary); border-bottom: solid 1.5px var(--color-sidebar-background-border); } + .version-list { padding-left: 0; margin-top: 0; @@ -80,7 +89,10 @@ border: solid 1px var(--color-sidebar-background-border); border-radius: 0px 0px 3px 3px; } -.version-list a, .version-list span, .version-list li { + +.version-list a, +.version-list span, +.version-list li { position: relative; display: block; font-size: 98%; @@ -90,6 +102,7 @@ padding: 4px 0px; color: var(--color-sidebar-link-text); } + .version-list li { background-color: var(--color-sidebar-background); color: var(--color-sidebar-link-text); diff --git a/doc/python_api/static/js/version_switch.js b/doc/python_api/static/js/version_switch.js index b2d25069fbe..c00253bfe73 100644 --- a/doc/python_api/static/js/version_switch.js +++ b/doc/python_api/static/js/version_switch.js @@ -1,63 +1,60 @@ -(function() { // switch: v1.2 +(function() { // switch: v1.4 "use strict"; var versionsFileUrl = "https://docs.blender.org/PROD/versions.json" var all_versions; -var Popover = function() { - function Popover(id) +class Popover { + constructor(id) { this.isOpen = false; this.type = (id === "version-popover"); - this.$btn = $('#' + id); - this.$dialog = this.$btn.next(); - this.$list = this.$dialog.children("ul"); + this.btn = document.querySelector('#' + id); + this.dialog = this.btn.nextElementSibling; + this.list = this.dialog.querySelector("ul"); this.sel = null; - this.beforeInit(); - } - - Popover.prototype = { - beforeInit : function() { - var that = this; - this.$btn.on("click", function(e) { + const that = this; + this.btnClickHandler = function(e) { + that.init(); + e.preventDefault(); + e.stopPropagation(); + }; + this.btnKeyHandler = function(e) { + if (that.btnKeyFilter(e)) { that.init(); e.preventDefault(); e.stopPropagation(); - }); - this.$btn.on("keydown", function(e) { - if (that.btnKeyFilter(e)) { - that.init(); - e.preventDefault(); - e.stopPropagation(); - } - }); - }, - init : function() { - this.$btn.off("click"); - this.$btn.off("keydown"); + } + }; + this.btn.addEventListener("click", this.btnClickHandler); + this.btn.addEventListener("keydown", this.btnKeyHandler); + } + init() + { + this.btn.removeEventListener("click", this.btnClickHandler); + this.btn.removeEventListener("keydown", this.btnKeyHandler); + + new Promise((resolve, reject) => { if (all_versions === undefined) { - this.$btn.addClass("wait"); - this.loadVL(this); + this.btn.classList.add("wait"); + fetch(versionsFileUrl) + .then((response) => response.json()) + .then((data) => { + all_versions = data; + resolve(); + }) + .catch(() => { + console.error("Version Switch Error: versions.json could not be loaded."); + this.btn.classList.remove("disabled"); + }); } else { - this.afterLoad(); + resolve(); } - }, - loadVL : function(that) { - $.getJSON(versionsFileUrl, function(data) { - all_versions = data; - that.afterLoad(); - return true; - }).fail(function() { - console.log("Version Switch Error: versions.json could not be loaded."); - that.$btn.addClass("disabled"); - return false; - }); - }, - afterLoad : function() { - var release = DOCUMENTATION_OPTIONS.VERSION; + }).then(() => { + let release = DOCUMENTATION_OPTIONS.VERSION; const m = release.match(/\d\.\d+/g); if (m) { release = m[0]; @@ -65,259 +62,274 @@ var Popover = function() { this.warnOld(release, all_versions); - var version = this.getNamed(release); - var list = this.buildList(version); + const version = this.getNamed(release); + this.buildList(version); - this.$list.children(":first-child").remove(); - this.$list.append(list); - var that = this; - this.$list.on("keydown", function(e) { + this.list.firstElementChild.remove(); + const that = this; + this.list.addEventListener("keydown", function(e) { that.keyMove(e); }); - this.$btn.removeClass("wait"); + this.btn.classList.remove("wait"); this.btnOpenHandler(); - this.$btn.on("mousedown", function(e) { + this.btn.addEventListener("mousedown", function(e) { that.btnOpenHandler(); e.preventDefault() }); - this.$btn.on("keydown", function(e) { + this.btn.addEventListener("keydown", function(e) { if (that.btnKeyFilter(e)) { that.btnOpenHandler(); } }); - }, - warnOld : function(release, all_versions) { - // Note this is effectively disabled now, two issues must fixed: - // * versions.js does not contain a current entry, because that leads to - // duplicate version numbers in the menu. These need to be deduplicated. - // * It only shows the warning after opening the menu to switch version - // when versions.js is loaded. This is too late to be useful. - var current = all_versions.current - if (!current) - { - // console.log("Version Switch Error: no 'current' in version.json."); - return; - } - const m = current.match(/\d\.\d+/g); - if (m) { - current = parseFloat(m[0]); - } - if (release < current) { - var currentURL = window.location.pathname.replace(release, current); - var warning = $('
' + - '

Note

' + - '

' + - 'You are not using the most up to date version of the documentation. ' + - ' is the newest version.' + - '

' + - '
'); - - warning.find('a').attr('href', currentURL).text(current); - - var body = $("div.body"); - if (!body.length) { - body = $("div.document"); - } - body.prepend(warning); - } - }, - buildList : function(v) { - var url = new URL(window.location.href); - let pathSplit = [ "", "api", v ]; - if (url.pathname.startsWith("/api/")) { - pathSplit.push(url.pathname.split('/').slice(3).join('/')); - } - else { - pathSplit.push(url.pathname.substring(1)); - } - if (this.type) { - var dyn = all_versions; - var cur = v; - } - var buf = []; - var that = this; - $.each(dyn, function(ix, title) { - buf.push("' + - title + ''); - } - else { - pathSplit[2 + that.type] = ix; - var href = new URL(url); - href.pathname = pathSplit.join('/'); - buf.push(' tabindex="-1" role="presentation">' + - title + ''); - } - }); - return buf.join(''); - }, - getNamed : function(v) { - $.each(all_versions, function(ix, title) { - if (ix === "master" || ix === "main" || ix === "latest") { - var m = title.match(/\d\.\d[\w\d\.]*/)[0]; - if (parseFloat(m) == v) { - v = ix; - return false; - } - } - }); - return v; - }, - dialogToggle : function(speed) { - var wasClose = !this.isOpen; - var that = this; - if (!this.isOpen) { - this.$btn.addClass("version-btn-open"); - this.$btn.attr("aria-pressed", true); - this.$dialog.attr("aria-hidden", false); - this.$dialog.fadeIn(speed, function() { - that.$btn.parent().on("focusout", function(e) { - that.focusoutHandler(); - e.stopImmediatePropagation(); - }) - that.$btn.parent().on("mouseleave", function(e) { - that.mouseoutHandler(); - e.stopImmediatePropagation(); - }); - }); - this.isOpen = true; - } - else { - this.$btn.removeClass("version-btn-open"); - this.$btn.attr("aria-pressed", false); - this.$dialog.attr("aria-hidden", true); - this.$btn.parent().off("focusout"); - this.$btn.parent().off("mouseleave"); - this.$dialog.fadeOut(speed, function() { - if (this.$sel) { - this.$sel.attr("tabindex", -1); - } - that.$btn.attr("tabindex", 0); - if (document.activeElement !== null && document.activeElement !== document && - document.activeElement !== document.body) { - that.$btn.focus(); - } - }); - this.isOpen = false; - } - - if (wasClose) { - if (this.$sel) { - this.$sel.attr("tabindex", -1); - } - if (document.activeElement !== null && document.activeElement !== document && - document.activeElement !== document.body) { - var $nw = this.listEnter(); - $nw.attr("tabindex", 0); - $nw.focus(); - this.$sel = $nw; - } - } - }, - btnOpenHandler : function() { - this.dialogToggle(300); - }, - focusoutHandler : function() { - var list = this.$list; - var that = this; - setTimeout(function() { - if (list.find(":focus").length === 0) { - that.dialogToggle(200); - } - }, 200); - }, - mouseoutHandler : function() { - this.dialogToggle(200); - }, - btnKeyFilter : function(e) { - if (e.ctrlKey || e.shiftKey) { - return false; - } - if (e.key === " " || e.key === "Enter" || (e.key === "ArrowDown" && e.altKey) || - e.key === "ArrowDown" || e.key === "ArrowUp") { - return true; - } - return false; - }, - keyMove : function(e) { - if (e.ctrlKey || e.shiftKey) { - return true; - } - var p = true; - var $nw = $(e.target); - switch (e.key) { - case "ArrowUp": - $nw = this.listPrev($nw); - break; - case "ArrowDown": - $nw = this.listNext($nw); - break; - case "Home": - $nw = this.listFirst(); - break; - case "End": - $nw = this.listLast(); - break; - case "Escape": - $nw = this.listExit(); - break; - case "ArrowLeft": - $nw = this.listExit(); - break; - case "ArrowRight": - $nw = this.listExit(); - break; - default: - p = false; - } - if (p) { - $nw.attr("tabindex", 0); - $nw.focus(); - if (this.$sel) { - this.$sel.attr("tabindex", -1); - } - this.$sel = $nw; - e.preventDefault(); - e.stopPropagation(); - } - }, - listPrev : function($nw) { - if ($nw.parent().prev().length !== 0) { - return $nw.parent().prev().children(":first-child"); - } - else { - return this.listLast(); - } - }, - listNext : function($nw) { - if ($nw.parent().next().length !== 0) { - return $nw.parent().next().children(":first-child"); - } - else { - return this.listFirst(); - } - }, - listFirst : function() { - return this.$list.children(":first-child").children(":first-child"); - }, - listLast : function() { - return this.$list.children(":last-child").children(":first-child"); - }, - listExit : function() { - this.mouseoutHandler(); - return this.$btn; - }, - listEnter : function() { - return this.$list.children(":first-child").children(":first-child"); + }); + } + warnOld(release, all_versions) + { + // Note this is effectively disabled now, two issues must fixed: + // * versions.js does not contain a current entry, because that leads to + // duplicate version numbers in the menu. These need to be deduplicated. + // * It only shows the warning after opening the menu to switch version + // when versions.js is loaded. This is too late to be useful. + let current = all_versions.current + if (!current) { + // console.log("Version Switch Error: no 'current' in version.json."); + return; } - }; - return Popover -}(); + const m = current.match(/\d\.\d+/g); + if (m) { + current = parseFloat(m[0]); + } + if (release < current) { + const currentURL = window.location.pathname.replace(release, current); + const warning = + document.querySelector("template#version-warning").firstElementChild.cloneNode(true); + const link = warning.querySelector('a'); + link.setAttribute('href', currentURL); + link.textContent = current; -$(document).ready(function() { - var lng_popover = new Popover("version-popover"); -}); + let body = document.querySelector("div.body"); + if (!body.length) { + body = document.querySelector("div.document"); + } + body.prepend(warning); + } + } + buildList(v) + { + const url = new URL(window.location.href); + let pathSplit = [ "", "api", v ]; + if (url.pathname.startsWith("/api/")) { + pathSplit.push(url.pathname.split('/').slice(4).join('/')); + } + else { + pathSplit.push(url.pathname.substring(1)); + } + let dyn, cur; + if (this.type) { + dyn = all_versions; + cur = v; + } + const that = this; + const template = document.querySelector("template#version-entry").content; + for (let [ix, title] of Object.entries(dyn)) { + let clone; + if (ix === cur) { + clone = template.querySelector("li.selected").cloneNode(true); + clone.querySelector("span").innerHTML = title; + } + else { + pathSplit[1 + that.type] = ix; + let href = new URL(url); + href.pathname = pathSplit.join('/'); + clone = template.firstElementChild.cloneNode(true); + const link = clone.querySelector("a"); + link.href = href; + link.innerHTML = title; + } + that.list.append(clone); + }; + return this.list; + } + getNamed(v) + { + for (let [ix, title] of Object.entries(all_versions)) { + if (ix === "master" || ix === "main" || ix === "latest") { + const m = title.match(/\d\.\d[\w\d\.]*/)[0]; + if (parseFloat(m) == v) { + v = ix; + return false; + } + } + }; + return v; + } + dialogToggle(speed) + { + const wasClose = !this.isOpen; + const that = this; + if (!this.isOpen) { + this.btn.classList.add("version-btn-open"); + this.btn.setAttribute("aria-pressed", true); + this.dialog.setAttribute("aria-hidden", false); + this.dialog.style.display = "block"; + this.dialog.animate({opacity : [ 0, 1 ], easing : [ 'ease-in', 'ease-out' ]}, speed) + .finished.then(() => { + this.focusoutHandlerPrime = function(e) { + that.focusoutHandler(); + e.stopImmediatePropagation(); + }; + this.mouseoutHandlerPrime = function(e) { + that.mouseoutHandler(); + e.stopImmediatePropagation(); + }; + this.btn.parentNode.addEventListener("focusout", this.focusoutHandlerPrime); + this.btn.parentNode.addEventListener("mouseleave", this.mouseoutHandlerPrime); + }); + this.isOpen = true; + } + else { + this.btn.classList.remove("version-btn-open"); + this.btn.setAttribute("aria-pressed", false); + this.dialog.setAttribute("aria-hidden", true); + this.btn.parentNode.removeEventListener("focusout", this.focusoutHandlerPrime); + this.btn.parentNode.removeEventListener("mouseleave", this.mouseoutHandlerPrime); + this.dialog.animate({opacity : [ 1, 0 ], easing : [ 'ease-in', 'ease-out' ]}, speed) + .finished.then(() => { + this.dialog.style.display = "none"; + if (this.sel) { + this.sel.setAttribute("tabindex", -1); + } + this.btn.setAttribute("tabindex", 0); + if (document.activeElement !== null && document.activeElement !== document && + document.activeElement !== document.body) + { + this.btn.focus(); + } + }); + this.isOpen = false; + } + + if (wasClose) { + if (this.sel) { + this.sel.setAttribute("tabindex", -1); + } + if (document.activeElement !== null && document.activeElement !== document && + document.activeElement !== document.body) + { + const nw = this.listEnter(); + nw.setAttribute("tabindex", 0); + nw.focus(); + this.sel = nw; + } + } + } + btnOpenHandler() + { + this.dialogToggle(300); + } + focusoutHandler() + { + const list = this.list; + const that = this; + setTimeout(function() { + if (!list.querySelector(":focus")) { + that.dialogToggle(200); + } + }, 200); + } + mouseoutHandler() + { + this.dialogToggle(200); + } + btnKeyFilter(e) + { + if (e.ctrlKey || e.shiftKey) { + return false; + } + if (e.key === " " || e.key === "Enter" || (e.key === "ArrowDown" && e.altKey) || + e.key === "ArrowDown" || e.key === "ArrowUp") + { + return true; + } + return false; + } + keyMove(e) + { + if (e.ctrlKey || e.shiftKey) { + return true; + } + let nw = e.target; + switch (e.key) { + case "ArrowUp": + nw = this.listPrev(nw); + break; + case "ArrowDown": + nw = this.listNext(nw); + break; + case "Home": + nw = this.listFirst(); + break; + case "End": + nw = this.listLast(); + break; + case "Escape": + nw = this.listExit(); + break; + case "ArrowLeft": + nw = this.listExit(); + break; + case "ArrowRight": + nw = this.listExit(); + break; + default: + return false; + } + nw.setAttribute("tabindex", 0); + nw.focus(); + if (this.sel) { + this.sel.setAttribute("tabindex", -1); + } + this.sel = nw; + e.preventDefault(); + e.stopPropagation(); + } + listPrev(nw) + { + if (nw.parentNode.previousElementSibling.length !== 0) { + return nw.parentNode.previousElementSibling.firstElementChild; + } + else { + return this.listLast(); + } + } + listNext(nw) + { + if (nw.parentNode.nextElementSibling.length !== 0) { + return nw.parentNode.nextElementSibling.firstElementChild; + } + else { + return this.listFirst(); + } + } + listFirst() + { + return this.list.firstElementChild.firstElementChild; + } + listLast() + { + return this.list.lastElementChild.firstElementChild; + } + listExit() + { + this.mouseoutHandler(); + return this.btn; + } + listEnter() + { + return this.list.firstElementChild.firstElementChild; + } +} + +document.addEventListener('DOMContentLoaded', () => { new Popover("version-popover"); }); })(); diff --git a/doc/python_api/templates/sidebar/variant-selector.html b/doc/python_api/templates/sidebar/variant-selector.html index 4eb56915f18..3bac5f95306 100644 --- a/doc/python_api/templates/sidebar/variant-selector.html +++ b/doc/python_api/templates/sidebar/variant-selector.html @@ -1,19 +1,21 @@