feat: Improve options page and add toggles for the two main features. (#68)

Resolves #43.

Should leave archematics ready to submit.

Reviewed-on: #68
Co-authored-by: Glen Whitney <glen@studioinfinity.org>
Co-committed-by: Glen Whitney <glen@studioinfinity.org>
This commit is contained in:
Glen Whitney 2024-02-20 04:14:44 +00:00 committed by Glen Whitney
parent b74921341c
commit 0567da019f
12 changed files with 215 additions and 142 deletions

View File

@ -1,10 +1,16 @@
{
"manifest_version": 3,
"name": "archematics",
"version": "0.1",
"description": "unearths mathematical treasures lost in the web.",
"version": "1.0",
"description": "unearths mathematical treasures lost in the web",
"icons": {
"48": "assets/archIcon48.png",
"128": "assets/archIcon128.png",
"256": "assets/archIcon256.png"
},
"author": "Glen Whitney <glen@archematics.app>",
"homepage_url": "https://archematics.app",
"content_scripts": [
{
"matches": ["*://*/*"],

View File

@ -6,7 +6,54 @@
</head>
<body>
<h3>Debugging</h3>
<h2>archematics</h2>
<p>This plug-in currently has two main capabilities that can be activated
independently:</p>
<h2>Embedded VRML/X3D display</h2>
<div style="float: right;margin: 1.5em;">
<label for="vrmlview">Enable</label>
<input type="checkbox" id="vrmlview"></div>
<div style="float: left;margin: 1em;">
<a href="http://www.wiley.com/legacy/compbooks/vrml2sbk/ch14/14fig04a.htm">
<img src="assets/vrmlExample.png" width="160px"></a></div>
<p>Scans web pages for links (in text or images) to VRML or X3D files
(and other file formats compatible with the
<a href="https://create3000.github.io/x_ite/tutorials/how-to-navigate-in-a-scene/">
X_ITE X3D Browser</a>, such as glTF, obj, and STL) and inserts
"eye-cons" (👁) next to each of them. When you click the eye, it embeds
a viewer and displays the model in 3D within the page. Right-click on an
active viewer for additional options. Click the eye again
to close the viewer. Supports multiple simultaneous viewers on the same
page.</p>
<p> Be sure to visit George Hart's
<a href="http://georgehart.com/virtual-polyhedra/vp.html">
Encyclopedia of Polyhedra</a> with this module enabled for a trove of
beautiful models to play with.</p>
<h2>JavaScript reinterpretation of Geometry Applets</h2>
<div style="float: right;margin: 1.5em;">
<label for="joyce">Enable</label><input type="checkbox" id="joyce">
</div>
<div style="float: left;margin: 1em;">
<a href="https://mathcs.clarku.edu/~djoyce/java/Geometry/eulerline.html">
<img src="assets/joyceExample.png" width="150px"></a></div>
<p>In the late 1990s, <a href="https://mathcs.clarku.edu/~djoyce/">
David Joyce</a> wrote a web applet in Java that allowed for interactive
draggable 2D and 3D geometry diagrams within a page. Nearly 30 years on,
Java is no longer supported for embedded applets by any major browser. This
module scans web pages for invocations of that Java applet, and translates
them into calls to the
<a href="https://wiki.geogebra.org/en/Reference:GeoGebra_Apps_Embedding">
GeoGebra JavaScript module</a>, bringing the diagrams back to life.</p>
<p>What's more, Joyce posted an
<a href="https://mathcs.clarku.edu/~djoyce/java/elements/elements.html">
on-line version</a> of the <strong>entire</strong>
Euclid's <em>Elements</em> with all of the traditional diagrams, and many
new ones of his own creation, rendered in fully interactive versions.
Be sure to give archematics a try on
<a href="https://mathcs.clarku.edu/~djoyce/java/elements/bookI/propI47.html">
his site</a>.</p>
<hr/>
<h3>Debugging-only options</h3>
<h4>Embedded VRML/X3D display</h4>
Write to the JavaScript console: <br/>
<label for="vrml97">Generated VRML97 specifications</label>

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -26,9 +26,9 @@ obs := new MutationObserver (mutationList) =>
width: parseInt(newNode.getAttribute('width') ?? '200'),
height: parseInt(newNode.getAttribute('height') ?? '200') }
config := childList: true, subtree: true
observerConfig := childList: true, subtree: true
obs.observe document.documentElement, config
obs.observe document.documentElement, observerConfig
function addScriptTag(url: string, params = '', module = false)
return new Promise (resolve, reject) =>
@ -41,6 +41,8 @@ function addScriptTag(url: string, params = '', module = false)
document.head.appendChild script
document.addEventListener "DOMContentLoaded", async =>
config := await browser.storage.local.get(flags) as ConfigType
unless config.joyce return
finalJoyceApplets := document.querySelectorAll "applet[code='Geometry']"
if finalJoyceApplets.length
joyceApplets = []
@ -58,7 +60,6 @@ document.addEventListener "DOMContentLoaded", async =>
width: parseInt(jApp.getAttribute('width') ?? '200'),
height: parseInt(jApp.getAttribute('height') ?? '200') }
if joyceApplets.length
config := await browser.storage.local.get(flags) as ConfigType
use3d .= false
for each jApp of joyceApplets
if contains3d jApp.params

View File

@ -1,7 +1,10 @@
// This file is a bit misnamed, as it has options for giveAwrl, too.
export const flags = [
'color', 'commands', 'showall', 'showaux', 'algebra', 'vrml97'] as const
'vrmlview', 'joyce',
// debugging
'color', 'commands', 'showall', 'showaux', 'algebra', 'vrml97'
] as const
export type FlagType = (typeof flags)[number]
export type ConfigType = Partial<Record<FlagType, boolean>>

View File

@ -1,12 +1,15 @@
import ./deps/jquery.js
{convert} from ./deps/vrml1to97/index.js
{ConfigType} from ./adapptypes.ts
import type {ConfigType} from ./adapptypes.ts
configPromise := browser.storage.local.get(['vrmlview', 'vrml97'])
knownExtensions := /[.](?:wrl|x3d|gltf|glb|obj|stl|ply)$/
certainlyHandled :=
knownExtensions.source.slice(0, -2).split('wrl|')[1].split '|'
function makeBrowser(url: string, width: string, height: string)
function makeBrowser(url: string, width: string, height: string,
config: ConfigType)
x_ite_rel := 'deps/x_ite/x_ite.mjs'
x_ite_url .= './' + x_ite_rel
unless typeof browser is 'undefined'
@ -43,141 +46,149 @@ function makeBrowser(url: string, width: string, height: string)
text .= await response.text()
if /#\s*VRML\s*V?1[.]/i.test text
text = convert text
maybeDebug text
maybeDebug text, config
browser3D.baseURL = url
scene := await browser3D.createX3DFromString text
browser3D.replaceWorld scene
{canvas, browser3D}
// Put eye icons after all of the eligible links
links := $('a').filter -> knownExtensions.test @.getAttribute('href') ?? ''
links.after ->
newSpan := $('<span>👁</span>')
newSpan.css 'overflow', 'visible'
floatLike := this.lastElementChild as HTMLElement
float .= ''
if floatLike
float = (floatLike.getAttribute('align') ?? '')
or floatLike.style.getPropertyValue 'float'
or window.getComputedStyle(floatLike).getPropertyValue 'float'
switch float
/left/i
float = 'left'
newSpan.css {float, position: 'relative'}
/right/i
float = 'right'
newSpan.css {float, position: 'relative'}
else float = ''
newSpan.hover
(-> $(@).css 'background-color', 'lightblue'),
(-> $(@).css 'background-color', 'inherit')
newSpan.on 'click', @,
(e) =>
eye := e.target
state .= eye.getAttribute 'data'
unless state
state = 'off'
url := e.data.getAttribute('href') ?? ''
overImg := floatLike and floatLike.tagName is 'IMG'
width := overImg ? ($(floatLike).width() + 'px') : '150px'
height := overImg ? ($(floatLike).height() + 'px') : '150px'
{canvas} := await makeBrowser url, width, height
if float
canvas.style.float = float
if overImg
canvas.style.position = 'absolute'
imgSty := window.getComputedStyle floatLike
canvas.style.marginTop = imgSty.getPropertyValue 'margin-top'
canvas.style.marginLeft = imgSty.getPropertyValue 'margin-left'
canvas.style.marginRight = imgSty.getPropertyValue 'margin-right'
if float is 'right'
canvas.style.left = $(eye).width() + 'px'
else if float is 'left'
canvas.style.right = $(eye).width() + 'px'
else canvas.style.left = floatLike.offsetLeft
$(eye).append canvas
if state is 'off'
eye.setAttribute 'data', 'on'
$(eye).css 'text-decoration', 'line-through 3px'
$(eye.lastElementChild as Element).show()
else
eye.setAttribute 'data', 'off'
$(eye).css 'text-decoration', 'none'
$(eye.lastElementChild as Element).hide()
configPromise.then((config) =>
// Put eye icons after all of the eligible links
links := $('a').filter -> knownExtensions.test @.getAttribute('href') ?? ''
links.after ->
unless config.vrmlview return ''
newSpan := $('<span>👁</span>')
newSpan.css 'overflow', 'visible'
floatLike := this.lastElementChild as HTMLElement
float .= ''
if floatLike
float = (floatLike.getAttribute('align') ?? '')
or floatLike.style.getPropertyValue 'float'
or window.getComputedStyle(floatLike).getPropertyValue 'float'
switch float
/left/i
float = 'left'
newSpan.css {float, position: 'relative'}
/right/i
float = 'right'
newSpan.css {float, position: 'relative'}
else float = ''
newSpan.hover
(-> $(@).css 'background-color', 'lightblue'),
(-> $(@).css 'background-color', 'inherit')
newSpan.on 'click', @,
(e) =>
eye := e.target
state .= eye.getAttribute 'data'
unless state
state = 'off'
url := e.data.getAttribute('href') ?? ''
overImg := floatLike and floatLike.tagName is 'IMG'
width := overImg ? ($(floatLike).width() + 'px') : '150px'
height := overImg ? ($(floatLike).height() + 'px') : '150px'
{canvas} := await makeBrowser url, width, height, config
if float
canvas.style.float = float
if overImg
canvas.style.position = 'absolute'
imgSty := window.getComputedStyle floatLike
canvas.style.marginTop =
imgSty.getPropertyValue 'margin-top'
canvas.style.marginLeft =
imgSty.getPropertyValue 'margin-left'
canvas.style.marginRight =
imgSty.getPropertyValue 'margin-right'
if float is 'right'
canvas.style.left = $(eye).width() + 'px'
else if float is 'left'
canvas.style.right = $(eye).width() + 'px'
else canvas.style.left = floatLike.offsetLeft
$(eye).append canvas
if state is 'off'
eye.setAttribute 'data', 'on'
$(eye).css 'text-decoration', 'line-through 3px'
$(eye.lastElementChild as Element).show()
else
eye.setAttribute 'data', 'off'
$(eye).css 'text-decoration', 'none'
$(eye.lastElementChild as Element).hide()
function maybeDebug(vrml: string)
config := await browser.storage.local.get(['vrml97']) as ConfigType
let conwayBrowser: any
madeConway .= false
// See if we are on George Hart's Conway-notation generator page
inputs := $('input[type="button"][value="Generate"][onclick="viewVRML()"]')
if config.vrmlview and inputs.length is 1
// Seems so, fix the generator
// Note that modifying the onclick prop is not the recommended way to
// change button click functionality, but we need to clear out the old
// behavior so I wasn't sure how else to do it
inputs.prop 'onclick', (i, val) => () =>
import(browser.runtime.getURL('conway.js')).then (conway) =>
notation := $('input[name="notation"]').val()
unless notation then return
vrml := conway.generateVRML notation.toString()
maybeDebug vrml, config
unless madeConway
{canvas, browser3D} :=
await makeBrowser '', '250px', '250px', config
conwayBrowser = browser3D
canvas.style.float = 'left'
canvas.style.marginRight = '1em'
$('form[name="input"]').first().before canvas
madeConway = true
scene := await conwayBrowser.createX3DFromString vrml
conwayBrowser.replaceWorld scene
// See if we are on George Hart's prism generator page
panelFrame := $('frame[name="panel"][src="prism-maker-subpanel.html"]')
if config.vrmlview and panelFrame.length is 1
// Seems so, fix the generator
panelFrame.on "load", =>
panelDoc := frames[1].document
vrmlDoc := frames[0].document
vrmlBody := $('body', vrmlDoc)
// Grab the initial text while it is still easy to get
textNode := vrmlBody.contents()[0]
initialVrml1: string := textNode.textContent or ''
// Now build up the vrml frame as we want it
viewerDiv := $('<div></div>')
$('head').after $('<body></body>')
$('body').append viewerDiv
// We are presuming here that the body just contains a single
// text node. That should stay true unless GWH changes the page.
initialVrml97 := convert initialVrml1
{canvas, browser3D: prismBrowser} :=
await makeBrowser '', '300px', '300px', config
viewerDiv.append canvas
initialScene := await prismBrowser.createX3DFromString initialVrml97
prismBrowser.replaceWorld initialScene
$(textNode).remove()
$('frame[name="vrml"]').remove()
// OK, finally have the layout cleaned up. Now we can set up our
// replacement generator:
prismBtn :=
$('input[type="button"][value="View"][onclick="ViewVRML()"]',
panelDoc)
unless prismBtn.length is 1 return
prismBtn.prop 'onclick', (i, val) => =>
import(browser.runtime.getURL('prism.js')).then (prism) =>
numerator := parseInt(
$('input[name="numerator"]', panelDoc).val() as string)
denominator := parseInt(
$('input[name="denominator"]', panelDoc).val() as string)
checks: boolean[] := []
$('input[name="what"]', panelDoc).each ->
checks.push (@ as HTMLInputElement).checked
return
{vrml, err} := prism.generateVRML numerator, denominator, checks
maybeDebug vrml, config
if err then alert err
if vrml
scene := await prismBrowser.createX3DFromString vrml
prismBrowser.replaceWorld scene
) // end of then for configPromise
function maybeDebug(vrml: string, config: ConfigType)
if config.vrml97 then console.log 'Generated VRML97', vrml
let conwayBrowser: any
madeConway .= false
// See if we are on George Hart's Conway-notation generator page
inputs := $('input[type="button"][value="Generate"][onclick="viewVRML()"]')
if inputs.length is 1
// Seems so, fix the generator
// Note that modifying the onclick prop is not the recommended way to
// change button click functionality, but we need to clear out the old
// behavior so I wasn't sure how else to do it
inputs.prop 'onclick', (i, val) => () =>
import(browser.runtime.getURL('conway.js')).then (conway) =>
notation := $('input[name="notation"]').val()
unless notation then return
vrml := conway.generateVRML notation.toString()
maybeDebug vrml
unless madeConway
{canvas, browser3D} := await makeBrowser '', '250px', '250px'
conwayBrowser = browser3D
canvas.style.float = 'left'
canvas.style.marginRight = '1em'
$('form[name="input"]').first().before canvas
madeConway = true
scene := await conwayBrowser.createX3DFromString vrml
conwayBrowser.replaceWorld scene
// See if we are on George Hart's prism generator page
panelFrame := $('frame[name="panel"][src="prism-maker-subpanel.html"]')
if panelFrame.length is 1
// Seems so, fix the generator
panelFrame.on "load", =>
panelDoc := frames[1].document
vrmlDoc := frames[0].document
vrmlBody := $('body', vrmlDoc)
// Grab the initial text while it is still easy to get
textNode := vrmlBody.contents()[0]
initialVrml1: string := textNode.textContent or ''
// Now build up the vrml frame as we want it
viewerDiv := $('<div></div>')
$('head').after $('<body></body>')
$('body').append viewerDiv
// We are presuming here that the body just contains a single
// text node. That should stay true unless GWH changes the page.
initialVrml97 := convert initialVrml1
{canvas, browser3D: prismBrowser} := await makeBrowser '', '300px', '300px'
viewerDiv.append canvas
initialScene := await prismBrowser.createX3DFromString initialVrml97
prismBrowser.replaceWorld initialScene
$(textNode).remove()
$('frame[name="vrml"]').remove()
// OK, finally have the layout cleaned up. Now we can set up our
// replacement generator:
prismBtn := $('input[type="button"][value="View"][onclick="ViewVRML()"]',
panelDoc)
unless prismBtn.length is 1 return
prismBtn.prop 'onclick', (i, val) => =>
import(browser.runtime.getURL('prism.js')).then (prism) =>
numerator :=
parseInt $('input[name="numerator"]', panelDoc).val() as string
denominator :=
parseInt $('input[name="denominator"]', panelDoc).val() as string
checks: boolean[] := []
$('input[name="what"]', panelDoc).each ->
checks.push (@ as HTMLInputElement).checked
return
{vrml, err} := prism.generateVRML numerator, denominator, checks
maybeDebug vrml
if err then alert err
if vrml
scene := await prismBrowser.createX3DFromString vrml
prismBrowser.replaceWorld scene

View File

@ -5,7 +5,7 @@ cache := await browser.storage.local.get flags
for each box of flags
checkbox := document.getElementById(box) as HTMLInputElement
unless checkbox then continue
checkbox.checked = cache[box] ?? false
checkbox.checked = cache[box] ?? (box is 'vrmlview' or box is 'joyce')
document.body.addEventListener 'click', (event) ->
elt := event.target as HTMLInputElement

View File

@ -25,4 +25,9 @@ cp public/js/options.js public/js/adapptypes.js public/js/conway.js $1
cp public/js/prism.js $1
cp node_modules/webextension-polyfill/dist/browser-polyfill.js $1
cp node_modules/@webcomponents/custom-elements/custom-elements.min.js $1
# Images etc
mkdir -p $1/assets
cp public/assets/arch*.png $1/assets
cp public/assets/*Example.png $1/assets
# Wrap it all up
zip -r $1 $1