feat: Produce an archematics plugin that works in Firefox (#38)

With this loaded in under the Firefox debugger, one can see linked WRL files and Java Geometry Applets on arbitrary web pages.
This represents significant progress on #28, but getting more controls and getting it to work in other browsers is still on deck.

Reviewed-on: #38
Co-authored-by: Glen Whitney <glen@studioinfinity.org>
Co-committed-by: Glen Whitney <glen@studioinfinity.org>
This commit is contained in:
Glen Whitney 2023-10-05 06:19:11 +00:00 committed by Glen Whitney
parent b31c0671d2
commit e7361f94a7
43 changed files with 164418 additions and 102 deletions

View file

@ -1,18 +1,12 @@
import https://code.jquery.com/jquery-3.7.1.js
import type {AppletObject} from ./deps/geogebra/api.ts
import ./deps/jquery.js
import type {AppletObject} from ./deps/geotypes/api.ts
import {AppletDescription, AdapParams, params} from ./adapptypes.ts
colorsea from ./deps/colorsea.js
type AppletDescription
html: string
children: HTMLCollection
id: string
width: number
height: number
joyceApplets: AppletDescription[] := []
$('applet[code="Geometry"]').before (i, html) ->
id := `joyceApplet${i}`
joyceApplets.push { html, this.children, id,
joyceApplets.push { html, params(this.children), id,
width: parseInt(this.getAttribute('width') ?? '200'),
height: parseInt(this.getAttribute('height') ?? '200') }
`<div id="${id}"></div>`
@ -41,8 +35,8 @@ type Description
// with the otherName property:
type JoyceElements = Record<AnyName, Description>
jQuery.getScript 'https://www.geogebra.org/apps/deployggb.js', =>
for each jApp of joyceApplets
function postApplets(jApplets: AppletDescription[], codebase = '')
for each jApp of jApplets
params := {
appName: 'classic',
-enableRightClick,
@ -52,15 +46,36 @@ jQuery.getScript 'https://www.geogebra.org/apps/deployggb.js', =>
appletOnLoad: (api: AppletObject) =>
elements: JoyceElements := {}
backgroundRGB := [255, 255, 255] as RGB
for child of jApp.children
dispatchJcommand api, child, elements, backgroundRGB
for name, value in jApp.params
dispatchJcommand api, name, value, elements, backgroundRGB
api.setCoordSystem -10, 10 + jApp.width, -10, 10 + jApp.height
api.setAxesVisible false, false
api.setGridVisible false
} as const
geoApp := new GGBApplet params
if codebase then geoApp.setHTML5Codebase codebase
geoApp.inject jApp.id
adapParams: AdapParams :=
typeof GGBApplet is 'undefined'
? {loader: 'https://www.geogebra.org/apps/deployggb.js', joyceApplets: []}
: ((window as any).adapParams as AdapParams)
// Always use the final joyceApplets if there are any:
if joyceApplets.length
adapParams.joyceApplets = joyceApplets
if adapParams.joyceApplets.length
if adapParams.loader
jQuery.getScript adapParams.loader, =>
postApplets adapParams.joyceApplets
else
postApplets adapParams.joyceApplets, adapParams.codebase
/* That's all of the actions of this script. All of the remainder is
* implementation.
*/
type DimParts = [string[], string[], string[]] // Gives GeoNames
// or expressions for 0-, 1-, and 2-dimensional parts for coloring
@ -92,42 +107,39 @@ type XYZ = RGB
// present in that applet
function dispatchJcommand(
api: AppletObject,
param: Element,
name: string,
value: string,
elements: JoyceElements
backgroundRGB: RGB): void
val := param.getAttribute 'value'
unless val return
attr := param.getAttribute 'name'
switch attr
switch name
'background'
backgroundHex := `#${val}`
api.setGraphicsOptions 1, bgColor: backgroundHex
newback := colorsea(backgroundHex).rgb()
newback := joyce2rgb value, backgroundRGB
if adapParams.config?.commands
console.log 'Setting background to', value, 'interpreted as',
newback
for i of [0..2]
backgroundRGB[i] = newback[i]
api.setGraphicsOptions 1, bgColor: colorsea(backgroundRGB).hex()
'title'
if adapParams.config?.commands
console.log 'Setting title to', value
api.evalCommand `TitlePoint = Corner(1,1)
Text("${val}", TitlePoint + (2,5))`
Text("${value}", TitlePoint + (2,5))`
/e\[\d+\]/
num := parseInt(attr.slice(2))
{commands, callbacks, parts} := jToG val, elements, num, backgroundRGB
num := parseInt(name.slice(2))
{commands, callbacks, parts} :=
jToG value, elements, num, backgroundRGB
if commands.length
lastTried .= 0
if commands.filter((&)).every (cmd) =>
api.evalCommand(cmd) and ++lastTried
callbacks.forEach &(api, parts)
else console.log
else console.warn
`Geogebra command '${commands[lastTried]}'
(part of translation of '${val}')
(part of translation of '${value}')
failed.`
else console.log `Could not parse command '${val}'`
else console.log `Unkown param ${param}`
// function myListener(...args: unknown[]) {
// console.log 'In my listener with', args
// }
// window.myListener = myListener
else console.warn `Could not parse command '${value}'`
else console.warn `Unkown param ${name} = ${value}`
// Parses a Joyce element-creating command, extending the elements
// by side effect:
@ -137,9 +149,12 @@ function jToG(
index: number,
backgroundRGB: RGB): Commander
[jname, klass, method, data, ...colors] := jCom.split ';'
if adapParams.config?.commands
console.log 'Defining', jname, 'as a', klass, 'constructed by',
method, 'from', data, 'colored as', colors
cmdr .= freshCommander()
unless klass in classHandler
console.log `Unknown entity class ${klass}`
console.warn `Unknown entity class ${klass}`
return cmdr
assertJoyceClass klass // shouldn't need to do that :-/
name := if /^\p{L}\w*$/u.test jname then jname else geoname jname, elements
@ -151,7 +166,7 @@ function jToG(
(args.scalar ?= []).push scalar
continue
unless jdep in elements
console.log `Reference to unknown geometric entity ${jdep} in $jCom}`
console.warn `Reference to unknown geometric entity ${jdep} in $jCom}`
return cmdr
usesCaptions.push jdep
{klass: depKlass, otherName: depGeo, ends} := elements[jdep]
@ -175,7 +190,7 @@ function jToG(
else // we have to decorate
dimension .= cmdr.parts.findLastIndex .includes name
cmdr.callbacks.push (api: AppletObject, parts: DimParts) =>
trace := false // e.g., klass is 'polygon'
trace := adapParams.config?.color
// Operate in order faces, lines, point, caption so that
// we can adjust components after setting overall color, etc.
@ -202,7 +217,7 @@ function jToG(
// Lines default to black:
if invisible colors[2]
for each line of parts[1]
unless line in elements
if line is name or line not in elements
console.log 'Hiding line', line if trace
api.setVisible line, false
else
@ -213,11 +228,12 @@ function jToG(
api.setColor line, ...lineRGB
// Now color the points:
console.log 'Considering point colors for', name if trace
if invisible colors[1]
// Hide all the dim-0 elements that are not their own independent
// Hide all the dim-0 elements that are not distinct independent
// items:
for each point of parts[0]
unless point in elements
if point is name or point not in elements
console.log 'Hiding point', point if trace
api.setVisible point, false
else if dimension is 0 or colors[1] // Need to color the points
@ -335,7 +351,7 @@ function joyce2rgb(cname: string, backgroundRGB?: RGB): RGB
[H,S,B] := cname.split(',').map (s) => parseInt s
colorsea.hsv(H, S, B).rgb()
else
console.log 'Could not parse color:', cname
console.warn 'Could not parse color:', cname
[128, 128, 128]
function pointDefaultRGB(name: string, method: string): RGB
@ -485,8 +501,7 @@ classHandler: Record<JoyceClass, ClassHandler> :=
auxiliaries.push ...[1..4].map (n) => aux + n
unless madeSegment
commands.push `${name} = Segment(${ends[0]},${ends[1]})`
callbacks.push (api: AppletObject) =>
api.setLabelVisible name, true
callbacks.push (api: AppletObject) => api.setLabelVisible name, true
parts[0].push ...ends
circle: (name, method, args) =>
@ -522,9 +537,9 @@ classHandler: Record<JoyceClass, ClassHandler> :=
api.renameObject obj, newObj
switch api.getObjectType newObj
'segment'
parts[1].push newObj
moreParts[1].push newObj
'point'
parts[0].push newObj
moreParts[0].push newObj
api.setVisible newObj, false
/triangle|quadrilateral/
pt := args.subpoints
@ -539,9 +554,42 @@ classHandler: Record<JoyceClass, ClassHandler> :=
if obj is name continue
newObj := 'GeoAux' + index + obj
api.renameObject obj, newObj
parts[1].push newObj
moreParts[1].push newObj
sector: (name, method, args, index) =>
return := freshCommander()
return.value.ends = ['', '']
{commands, callbacks, parts, auxiliaries, ends} := return.value
aux := name + 'aUx'
parts[2].push name
switch method
'sector'
unless args.subpoints?.length is 3 return
parts[0].push ...args.subpoints
[center, end, start] := args.subpoints
ends[0] = start
ends[1] = end
parms := center + ', ' + start + ', ' + end
commands.push
`${name} = CircularSector(${parms})`
`${aux}1 = CircularArc(${parms})`
parts[1].push aux + 1
callbacks.push (api: AppletObject) =>
api.setLineThickness name, 1
// The rest of this function is a weird roundabout way to make
// the lines of the sector have zero opacity.
// I got it from
// https://www.reddit.com/r/geogebra/comments/12cbr85/setlineopacity_command/
// I don't really understand how/why it works, but it seems to
// So that's good enough for me
xml := api.getXML name
xml.replace(/opacity="\d+"/, 'opacity="0"')
api.evalXML(xml)
// This last step is especially confusing... I think
// evaluating the modified XML created a sort of second
// copy of the entity, and so we have to hide the original one
api.setVisible name, false
sector: (name, method, args) => freshCommander()
plane: (name, method, args) => freshCommander()
sphere: (name, method, args) => freshCommander()
polyhedron: (name, method, args) => freshCommander()