From 49cd05020c2c6448a6626aed39a2805c72283147 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 | 34 +- doc/python_api/static/js/version_switch.js | 578 ++++++++++--------- doc/python_api/templates/versions.html | 23 +- 3 files changed, 340 insertions(+), 295 deletions(-) diff --git a/doc/python_api/static/css/version_switch.css b/doc/python_api/static/css/version_switch.css index adb80b01c0a..28a73440c31 100644 --- a/doc/python_api/static/css/version_switch.css +++ b/doc/python_api/static/css/version_switch.css @@ -1,9 +1,9 @@ /* Override RTD theme */ .rst-versions { - display: none; border-top: 0px; overflow: visible; } + .version-btn.vdeact { cursor: default; color: dimgray; @@ -12,6 +12,7 @@ .version-btn.vdeact::after { content: ""; } + #versionwrap { display: flex; padding-top: 2px; @@ -19,6 +20,7 @@ justify-content: center; flex-wrap: wrap; } + .version-btn { display: inline-block; background-color: #272525; @@ -34,31 +36,39 @@ z-index: 400; transition: border-color 0.4s; } + .version-btn::after { - content:"\f0d8"; + content: "\f0d8"; display: inline; font: normal normal normal 16px/1 FontAwesome; color: #8d8c8c; vertical-align: top; padding-left: 0.5em; } + .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 gray; } + .version-btn.wait { cursor: wait; } + .version-btn.disabled { cursor: not-allowed; color: dimgray; } + .version-dialog { display: none; position: absolute; @@ -74,6 +84,7 @@ overflow-y: auto; cursor: default; } + .version-title { padding: 5px; color: black; @@ -82,6 +93,7 @@ background-color: #27ae60; border-bottom: solid 1.5px #444; } + .version-list { margin-bottom: 4px; text-align: center; @@ -89,7 +101,10 @@ border: solid 1px gray; 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%; @@ -99,21 +114,28 @@ padding: 4px 0px; color: #404040; } + .version-list li { background-color: #ede9e9; color: #404040; padding: 1px; } -.version-list li:hover, .version-list li a:focus { + +.version-list li:hover, +.version-list li a:focus { background-color: #b9cfda; } -.version-list li.selected, .version-list li.selected:hover { + +.version-list li.selected, +.version-list li.selected:hover { background-color: #8d8c8c; } + .version-list li.selected span { cursor: default; outline-color: red; } + .version-arrow { position: absolute; width: 8px; 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/versions.html b/doc/python_api/templates/versions.html index 482d4361207..ec3788f8bc5 100644 --- a/doc/python_api/templates/versions.html +++ b/doc/python_api/templates/versions.html @@ -1,19 +1,30 @@
-
+ + \ No newline at end of file