From 429fea4abf360534676d708083312a06977938a6 Mon Sep 17 00:00:00 2001 From: Aaron Carlisle Date: Tue, 19 Mar 2024 20:51:30 -0400 Subject: [PATCH] Docs: Python API: Update the version switch to match the user manual Not quite a 1:1 match, some customizations have to be made for the API URL differences and the face that there are no translations. This also enables the version switch publicly now that this change fixes a few bugs. --- doc/python_api/static/css/version_switch.css | 17 +- doc/python_api/static/js/version_switch.js | 578 +++++++++--------- .../templates/sidebar/variant-selector.html | 12 +- 3 files changed, 317 insertions(+), 290 deletions(-) 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 @@