diff --git a/blog/2020-08-28.md b/blog/2020-08-28.md deleted file mode 100644 index 323f39bd..00000000 --- a/blog/2020-08-28.md +++ /dev/null @@ -1,16 +0,0 @@ -# Rewriting the tech stack - -- started in 2011 as simple webpage with some Processing.js -- complete rewrite to React in 2016 -- web's caught up, and there is no reason to keep things React. This content should work even without JS. - - -- markdown - - extract latex - - build using actual xelatex - - replace with - - extract custom elements - - ammend HTML markup - - generate offline image -- compile markdown into a single HTML file using normal HTML templating -- localization diff --git a/docs/js/custom-element/api/graphics-api.js b/docs/js/custom-element/api/graphics-api.js index 1ecc7f6e..3437ed5a 100644 --- a/docs/js/custom-element/api/graphics-api.js +++ b/docs/js/custom-element/api/graphics-api.js @@ -1,5 +1,4 @@ import { enrich } from "../lib/enrich.js"; -import { create } from "../lib/create.js"; import { Bezier } from "./types/bezier.js"; import { BSpline } from "./types/bspline.js"; import { Vector } from "./types/vector.js"; @@ -7,6 +6,7 @@ import { Matrix } from "./types/matrix.js"; import { Shape } from "./util/shape.js"; import binomial from "./util/binomial.js"; import { BaseAPI } from "./base-api.js"; +import impartSliderLogic from "./impart-slider-logic.js"; const MOUSE_PRECISION_ZONE = 5; const TOUCH_PRECISION_ZONE = 30; @@ -182,154 +182,6 @@ class GraphicsAPI extends BaseAPI { this.line(0, 0, 0, this.height); } - /** - * Dynamically add a slider - */ - addSlider(classes, propname, min, max, step, value, transform) { - if (this.element) { - let slider = create(`input`); - slider.type = `range`; - slider.min = min; - slider.max = max; - slider.step = step; - slider.setAttribute(`value`, value); - slider.setAttribute(`class`, classes); - this.element.append(slider); - this.setSlider(slider, propname, value, transform); - } - } - - /** - * Update a slider with new min/max/step parameters, and value. - * - * @param {*} propname - * @param {*} min - * @param {*} max - * @param {*} step - * @param {*} value - */ - updateSlider(propname, min, max, step, value) { - let slider = this._sliders[propname]; - - if (!slider) { - throw new Error(`this.${propname} has no associated slider.`); - } - - slider.setAttribute(`min`, min); - slider.setAttribute(`max`, max); - slider.setAttribute(`step`, step); - slider.setAttribute(`value`, value); - slider.updateProperty(value); - } - - /** - * Set up a slider to control a named, numerical property in the sketch. - * - * @param {String} local query selector for the type=range element. - * @param {String} propname the name of the property to control. - * @param {float} initial the initial value for this property. - * @param {boolean} redraw whether or not to redraw after updating the value from the slider. - */ - setSlider(qs, propname, initial, transform) { - if (propname !== false && typeof this[propname] !== `undefined`) { - throw new Error(`this.${propname} already exists: cannot bind slider.`); - } - - this._sliders = this._sliders || {}; - - let propLabel = propname.replace(`!`, ``); - propname = propLabel === propname ? propname : false; - - let slider = typeof qs === `string` ? this.find(qs) : qs; - - // create a slider row in the table of sliders - let ui = (() => { - if (!this.element) { - return { update: (v) => {} }; - } - - let table = find(`table.slider-wrapper`); - - if (!table) { - table = slider.parentNode.querySelector(`table.slider-wrapper`); - if (!table) { - table = create(`table`); - table.classList.add(`slider-wrapper`); - slider.parentNode.replaceChild(table, slider); - } - } - - let tr = create(`tr`); - - let td = create(`td`); - let label = create(`label`); - label.classList.add(`slider-label`); - label.innerHTML = propLabel; - td.append(label); - tr.append(td); - - td = create(`td`); - slider.classList.add(`slider`); - this._sliders[propname] = slider; - td.append(slider); - tr.append(td); - - td = create(`td`); - let valueField = create(`label`); - valueField.classList.add(`slider-value`); - valueField.textContent; - td.append(valueField); - tr.append(td); - - table.append(tr); - return { update: (v) => (valueField.textContent = v) }; - })(); - - if (!slider) { - console.warn(`Warning: no slider found for query selector "${qs}"`); - if (propname) this[propname] = initial; - return undefined; - } - - let step = slider.getAttribute(`step`) || "1"; - let res = !step.includes(`.`) - ? 0 - : step.substring(step.indexOf(`.`) + 1).length; - - slider.updateProperty = (evt) => { - let value = parseFloat(slider.value); - ui.update(value.toFixed(res)); - try { - let checked = transform ? transform(value) ?? value : value; - if (propname) this[propname] = checked; - } catch (e) { - if (evt instanceof Event) { - evt.preventDefault(); - evt.stopPropagation(); - } - ui.update(e.value.toFixed(res)); - slider.value = e.value; - slider.setAttribute(`value`, e.value); - } - if (!this.redrawing) this.redraw(); - }; - - slider.value = initial; - slider.updateProperty({ target: { value: initial } }); - slider.listen(`input`, (evt) => slider.updateProperty(evt)); - - return slider; - } - - /** - * remove all sliders from this element - */ - removeSliders() { - this.findAll(`.slider-wrapper`).forEach((s) => { - s.parentNode.removeChild(s); - }); - } - /** * Convert the canvas to an image */ @@ -936,4 +788,12 @@ class GraphicsAPI extends BaseAPI { } } +/** + * Multiple inheritance is probably a good thing to not have in JS, + * but traits would have been nice, because a 1000+ LoC class is kind + * of silly, so to keep things easy to maintain, you end up writing + * custom trait-adding functions like this... + */ +impartSliderLogic(GraphicsAPI); + export { GraphicsAPI, Bezier, BSpline, Vector, Matrix, Shape }; diff --git a/docs/js/custom-element/api/impart-slider-logic.js b/docs/js/custom-element/api/impart-slider-logic.js new file mode 100644 index 00000000..b91820db --- /dev/null +++ b/docs/js/custom-element/api/impart-slider-logic.js @@ -0,0 +1,172 @@ +import { create } from "../lib/create.js"; + +export default function impartSliderLogic(GraphicsAPI) { + /** + * Dynamically add a slider + */ + GraphicsAPI.prototype.addSlider = function addSlider( + classes, + propname, + min, + max, + step, + value, + transform + ) { + if (this.element) { + let slider = create(`input`); + slider.type = `range`; + slider.min = min; + slider.max = max; + slider.step = step; + slider.setAttribute(`value`, value); + slider.setAttribute(`class`, classes); + this.element.append(slider); + this.setSlider(slider, propname, value, transform); + } + }; + + /** + * Update a slider with new min/max/step parameters, and value. + * + * @param {*} propname + * @param {*} min + * @param {*} max + * @param {*} step + * @param {*} value + */ + GraphicsAPI.prototype.updateSlider = function updateSlider( + propname, + min, + max, + step, + value + ) { + let slider = this._sliders[propname]; + + if (!slider) { + throw new Error(`this.${propname} has no associated slider.`); + } + + slider.setAttribute(`min`, min); + slider.setAttribute(`max`, max); + slider.setAttribute(`step`, step); + slider.setAttribute(`value`, value); + slider.updateProperty(value); + }; + + /** + * Set up a slider to control a named, numerical property in the sketch. + * + * @param {String} local query selector for the type=range element. + * @param {String} propname the name of the property to control. + * @param {float} initial the initial value for this property. + * @param {boolean} redraw whether or not to redraw after updating the value from the slider. + */ + GraphicsAPI.prototype.setSlider = function setSlider( + qs, + propname, + initial, + transform + ) { + if (propname !== false && typeof this[propname] !== `undefined`) { + throw new Error(`this.${propname} already exists: cannot bind slider.`); + } + + this._sliders = this._sliders || {}; + + let propLabel = propname.replace(`!`, ``); + propname = propLabel === propname ? propname : false; + + let slider = typeof qs === `string` ? this.find(qs) : qs; + + // create a slider row in the table of sliders + let ui = (() => { + if (!this.element) { + return { update: (v) => {} }; + } + + let table = find(`table.slider-wrapper`); + + if (!table) { + table = slider.parentNode.querySelector(`table.slider-wrapper`); + if (!table) { + table = create(`table`); + table.classList.add(`slider-wrapper`); + slider.parentNode.replaceChild(table, slider); + } + } + + let tr = create(`tr`); + + let td = create(`td`); + let label = create(`label`); + label.classList.add(`slider-label`); + label.innerHTML = propLabel; + td.append(label); + tr.append(td); + + td = create(`td`); + slider.classList.add(`slider`); + this._sliders[propname] = slider; + td.append(slider); + tr.append(td); + + td = create(`td`); + let valueField = create(`label`); + valueField.classList.add(`slider-value`); + valueField.textContent; + td.append(valueField); + tr.append(td); + + table.append(tr); + return { update: (v) => (valueField.textContent = v) }; + })(); + + if (!slider) { + console.warn(`Warning: no slider found for query selector "${qs}"`); + if (propname) this[propname] = initial; + return undefined; + } + + let step = slider.getAttribute(`step`) || "1"; + let res = !step.includes(`.`) + ? 0 + : step.substring(step.indexOf(`.`) + 1).length; + + slider.updateProperty = (evt) => { + let value = parseFloat(slider.value); + ui.update(value.toFixed(res)); + try { + let checked = transform ? transform(value) ?? value : value; + if (propname) this[propname] = checked; + } catch (e) { + if (evt instanceof Event) { + evt.preventDefault(); + evt.stopPropagation(); + } + ui.update(e.value.toFixed(res)); + slider.value = e.value; + slider.setAttribute(`value`, e.value); + } + if (!this.redrawing) this.redraw(); + }; + + slider.value = initial; + slider.updateProperty({ target: { value: initial } }); + slider.listen(`input`, (evt) => slider.updateProperty(evt)); + + return slider; + }; + + /** + * remove all sliders from this element + */ + GraphicsAPI.prototype.removeSliders = function removeSliders() { + this.findAll(`.slider-wrapper`).forEach((s) => { + s.parentNode.removeChild(s); + }); + }; + + return GraphicsAPI; +} diff --git a/docs/js/custom-element/graphics-element.js b/docs/js/custom-element/graphics-element.js index 7d666087..223b16e0 100644 --- a/docs/js/custom-element/graphics-element.js +++ b/docs/js/custom-element/graphics-element.js @@ -256,7 +256,15 @@ class GraphicsElement extends CustomElement { * Reload this graphics element the brute force way. */ async reset() { - this.outerHTML = this.originalHTML; + const parent = this.parentNode; + const offDOM = document.createElement(`div`); + offDOM.innerHTML = this.originalHTML; + const newElement = offDOM.querySelector(`graphics-element`); + offDOM.style.display = `none`; + document.body.append(offDOM); + newElement.addEventListener(`loaded`, () => + parent.replaceChild(newElement, this) + ); } /** @@ -284,6 +292,7 @@ class GraphicsElement extends CustomElement { // If we get here, there were no source code errors: undo the scheduled error print. clearTimeout(this.errorPrintTimeout); this.render(); + this.dispatchEvent(new CustomEvent(`loaded`)); } /** diff --git a/docs/js/site/referrer.js b/docs/js/site/referrer.js index 4d3bbdb0..cbf6f133 100644 --- a/docs/js/site/referrer.js +++ b/docs/js/site/referrer.js @@ -10,17 +10,15 @@ * grabs your document.referrer value, which (unless Do * Not Track is enabled) will contain the location of * the page you were on before you clicked a link to this - * page, and GETs that to my logger. That GET operation - * comes from your computer, so will have your IP as part - * of the HTTP headers. + * page, and GETs that to my logger. * - * And that's all I really care about, because I want to - * know how many people visit this page, and roughly where - * they're from (gasp! IPs can be turned into rough - * geographical location O_O). + * If you want to know what gets logged, have a look + * at the ./src/logger/logger.php file on github. * - * If you want to know what logger.php looks like, hit up - * github. It's in referrer/logger.php + * If that's too much effort: + * - the request URL + * - the referrer URL (if there is one) + * - the user agent * */ (function referrer(l) { @@ -32,20 +30,7 @@ if (loc.indexOf("localhost") !== -1) return; // right, continue var url = "https://pomax.nihongoresources.com/pages/bezierinfo/logger.php"; - var xhr = new XMLHttpRequest(); - xhr.open( - "GET", - url + - "?" + - "referrer=" + - encodeURIComponent(document.referrer) + - "&for=" + - page, - true - ); - try { - xhr.send(null); - } catch (e) { - /* you don't care about this error, and I can't see it, so why would we do anything with it? */ - } + fetch(`${url}?referrer=${encodeURIComponent(document.referrer)}&for=${page}`) + .then((_) => {}) + .catch((_) => {}); })(window.location.toString()); diff --git a/docs/news/2020-08-28.md b/docs/news/2020-08-28.md new file mode 100644 index 00000000..4eb517fb --- /dev/null +++ b/docs/news/2020-08-28.md @@ -0,0 +1,29 @@ +# Rewriting the tech stack + +- started in 2011 as simple webpage with some Processing.js +- complete rewrite to React in 2016 +- web's caught up, and there is no reason to keep things React. This content should work even without JS. + +- progressive enhancement: it should work, JS should make it work better. + - generate "a finished page" + - html + css + - load it with JS that "makes it better", not "makes it work" + +- start with markdown + - convert to HTML + - index.template.html + - nunjucks, like Django/Jinja/Mustache + - extract latex + - build using actual `xelatex` + - convert TeX to SVG + - xelatex, pdfcrop, pdf2svg, svgo + - replace with + - extract custom elements + - extract .scr, load in node, _actually run_ + - run single frame, export to `.png` image + - ammend HTML markup + - fill in missing width/height attributes + - add + - link in exported image +- each section into the larger HTML file (baesd on `toc` order) +- localization! diff --git a/docs/placeholder-style.css b/docs/placeholder-style.css index abbaa8a4..e7da1eff 100644 --- a/docs/placeholder-style.css +++ b/docs/placeholder-style.css @@ -24,7 +24,7 @@ margin: 0; --note-block-color: rgb(255, 255, 246); - --code-block-color: lightyellow; + --code-block-color: rgba(0,0,0,0.03); --heading-color: #cfe6ff; --heading-text: #333; } @@ -135,7 +135,7 @@ div.print { div.howtocode { position: relative; border: 1px solid black; - background: rgb(246, 255, 255); + background: #FCFCFD; margin: 1em; padding: 1em 1em 0 1em; } @@ -217,7 +217,7 @@ pre { code { - font-family: monospace; + font-family: Courier; } li code, p code { diff --git a/src/logger/logger.php b/src/logger/logger.php new file mode 100644 index 00000000..528c26a6 --- /dev/null +++ b/src/logger/logger.php @@ -0,0 +1,59 @@ +