@webwriter/geogebra
Version:
Use the mathematics learning apps of GeoGebra for algebra and geometry.
417 lines (348 loc) • 16.7 kB
text/typescript
import {LitElement, html, css, PropertyValues} from "lit"
import {LitElementWw, option} from "@webwriter/lit"
import {customElement, property, query, queryAsync} from "lit/decorators.js"
export interface GeogebraAPI {
evalCommand(cmdString: string): boolean
evalLaTex(input: string): boolean
evalCommandGetLabels(cmdString: string): string
evalCommandCAS(text: string): string
insertEmbed(type: string, uri: string): void
deleteObject(objName: string): void
setAuxiliary(geo: any, status: boolean): void
setValue(objName: string, value: number): void
setTextValue(objName: string, value: string): void
setListValue(objName: string, i: number, value: number): void
setCoords(objName: string, x: number, y: number, z?: number): void
setCaption(objName: string, caption: string): void
setColor(objName: string, red: number, green: number, blue: number): void
setVisible(objName: string, visible: boolean): void
setLabelVisible(objName: string, visible: boolean): void
setLabelStyle(objName: string, style: 0 | 1 | 2 | 3): void
setFixed(objName: string, fixed: boolean, selectionAllowed: boolean): void
setTrace(objName: string, flag: boolean): void
renameObject(oldObjName: string, newObjName: string): boolean
setLayer(objName: string, layer: number): void
setLayerVisible(objName: string, visible: boolean): void
setLineStyle(objName: string, style: 0 | 1 | 2 | 3 | 4): void
setLineThickness(objName: string, thickness: -1 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13): void
setPointStyle(objName: string, size: -1 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9): void
setPointSize(objName: string, size: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9): void
setDisplayStyle(objName: string, style: string): void
setFilling(objName: string, filling: number): void
getPNGBase64(exportScale: number, transparent?: boolean, dpi?: number): string
exportSVG(filename: string): void
exportSVG(callback: CallableFunction): void
exportPDF(scale: number, filename: string, sliderLabel?: string): void
exportPDF(scale: number, callback: CallableFunction, sliderLabel?: string): void
getScreenshotBase64(callback: CallableFunction): void
writePNGtoFile(fileName: string, exportScale?: number, transparent?: boolean, dpi?: number): boolean
isIndependent(objName: string): boolean
isMovable(objName: string): boolean
showAllObjects(): void
registerEmbedResolver(type: string, callback: CallableFunction): void
setAnimating(objName: string, animate: boolean): void
setAnimationSpeed(objName: string, speed: number): void
startAnimation(): void
stopAnimation(): void
isAnimationRunning(): boolean
getXcoord(objName: string): number
getYcoord(objName: string): number
getZcoord(objName: string): number
getValue(objName: string): number
getListValue(objName: string, index: number): number
getColor(objName: string): string
getVisible(objName: string): boolean
getValueString(objName: string, useLocalizedInput?: boolean): string
getDefinitionString(objName: string): string
getCommandString(objName: string, useLocalizedInput?: boolean): string
getLaTeXString(objName: string, value: boolean): string
getObjectType(objName: string): string
exists(objName: string): boolean
isDefined(objName: string): boolean
getAllObjectNames(type?: string): string[]
getObjectNumber(): number
getCASObjectNumber(): number
getObjectName(i: number): string
getLayer(objName: string): string
getLineStyle(objName: string): 0 | 1 | 2 | 3 | 4
getLineThickness(objName: string): -1 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13
getPointStyle(objName: string): -1 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
getPointSize(objName: string): 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
getFilling(objName: string): number
getCaption(objName: string, substitutePlaceholders?: boolean): string
getLabelStyle(objName: string): 0 | 1 | 2 | 3
getLabelVisible(): boolean
isInteractive(objName: string): boolean
setMode(mode: number): void
getMode(): number
reset(): void
newConstruction(): void
refreshViews(): void
setOnTheFlyPointCreationActive(flag: boolean): void
setPointCapture(view: -1 | 1 | 2, mode: 0 | 1 | 2 | 3): void
setRounding(round: string): void
hideCursorWhenDragging(flag: boolean): void
setRepaintingActive(flag: boolean): void
setErrorDialogsActive(flag: boolean): void
setCoordSystem(xmin: number, xmax: number, ymin: number, ymax: number, zmin?: number, zmax?: number, yVertical?: boolean): void
setAxesVisible(xAxis: boolean, yAxis: boolean, zAxis?: boolean): void
setUndoPoint(): void
setAxisLabels(viewNumber: number, xAxis: string, yAxis: string, zAxis: string): void
setAxisSteps(viewNumber: number, xAxis: number, yAxis: number, zAxis: number): void
setAxisUnits(viewNumber: number, xAxis: string, yAxis: string, zAxis: string): void
setGridVisible(flag: boolean): void
setGridVisible(viewNumber: number, flag: boolean): void
getGridVisible(viewNumber: number): boolean
getPerspectiveXML(): string
undo(): void
redo(): void
showToolBar(show: boolean): void
setCustomToolBar(toolbar: string): void
addCustomTool(iconURL: string, name: string, category?: string, callback?: CallableFunction): void
showMenuBar(show: boolean): void
showAlgebraInput(show: boolean): void
showResetIcon(show: boolean): void
enableRightClick(enable: boolean): void
enableLabelDrags(enable: boolean): void
enableShiftDragZoom(enable: boolean): void
enableCAS(enable: boolean): void
enable3D(enable: boolean): void
setPerspective(perspective: string): void
setWidth(width: number): void
setHeight(height: number): void
setSize(width: number, height: number): void
recalculateEnvironments(): void
getEditorState(): any
setEditorState(state: any): void
getGraphicsOptions(viewId: number): any
setGraphicsOptions(viewID: number, options: any): void
setAlgebraOptions(options: any): void
registerAddListener(func: CallableFunction): void
unregisterAddListener(func: CallableFunction): void
registerRemoveListener(func: CallableFunction): void
unregisterRemoveListener(func: CallableFunction): void
registerUpdateListener(func: CallableFunction): void
unregisterUpdateListener(func: CallableFunction): void
registerClickListener(func: CallableFunction): void
unregisterClickListener(func: CallableFunction): void
registerObjectUpdateListener(objName: string, func: CallableFunction): void
unregisterObjectUpdateListener(objName: string): void
registerObjectClickListener(objName: string, func: CallableFunction): void
unregisterObjectClickListener(objName: string): void
registerRenameListener(func: CallableFunction): void
unregisterRenameListener(func: CallableFunction): void
registerClearListener(func: CallableFunction): void
unregisterClearListener(func: CallableFunction): void
registerStoreUndoListener(func: CallableFunction): void
unregisterStoreUndoListener(func: CallableFunction): void
registerClientListener(func: CallableFunction): void
unregisterClientListener(func: CallableFunction): void
evalXML(xmlString: string): void
setXML(xmlString: string): void
getXML(objName?: string): string
getAlgorithmXML(objName: string): string
getFileJSON(): any
setFileJSON(content: any): void
getBase64(callback?: CallableFunction): string
setBase64(value: string, callback?: CallableFunction): void
debug(str: string): void
getVersion(): string
remove(): void
}
export class GeogebraApp extends LitElementWw {
static styles = css`
iframe {
border: none;
width: 100%;
aspect-ratio: 16/9;
}
`
@property({type: String})
ggbStyles: string
@property({reflect: true})
type: "classic" | "geometry" | "3d" | "suite" | "evaluator" | "scientific" | "notes" = "classic"
@property({type: Number, reflect: true})
width: number = 800
@property({type: Number, reflect: true})
height: number = 450
@property({type: String, attribute: true, reflect: true}) // base64, material ID, file url
accessor src: string
@property({type: String, reflect: true})
borderColor: string = "gray"
@property({type: Number, reflect: true})
borderRadius: number = 1
@property({type: Boolean, reflect: true})
enableRightClick = true
@property({type: Boolean, reflect: true})
enableLabelDrags = true
@property({type: Boolean, reflect: true})
enableShiftDragZoom = true
@property({type: Boolean, reflect: true})
showZoomButtons = false
@property({type: Boolean, reflect: true})
errorDialogsActive = true
@property({type: Boolean, reflect: true})
showMenuBar = false
@property({type: Boolean, reflect: true})
showToolBar = false
@property({type: Boolean, reflect: true})
showToolBarHelp = false
@property({type: String, reflect: true})
customToolBar: string
@property({type: Boolean, reflect: true})
showAlgebraInput = true
@property({type: Boolean, reflect: true})
showResetIcon = false
@property({type: String, reflect: true})
language: String
@property({type: String, reflect: true})
country: String
@property({type: String, reflect: true})
appletId: String
@property({type: Boolean, reflect: true})
allowStyleBar = false
@property({type: Boolean, reflect: true})
randomize = true
@property({type: Number, reflect: true})
randomSeed: number
@property({type: Boolean, reflect: true})
useBrowserForJs = false
@property({type: Boolean, reflect: true})
showLogging = false
@property({type: Number, reflect: true})
capturingThreshold = 3
@property({type: Boolean, reflect: true})
enableFileFeatures = true
@property({type: Boolean, reflect: true})
enableUndoRedo = true
@property({type: String, reflect: true})
perspective: String
@property({type: Boolean, reflect: true})
enable3d: boolean
@property({type: Boolean, reflect: true})
enableCAS: boolean
@property({type: String, reflect: true})
algebraInputPosition: "algebra" | "top" | "bottom"
@property({type: Boolean, reflect: true})
preventFocus = false
@property({type: String, reflect: true})
scaleContainerClass: string
@property({type: Boolean, reflect: true})
autoHeight = true
@property({type: Boolean, reflect: true})
allowUpscale = false
@property({type: Boolean, reflect: true})
playButton = false
@property({type: Number, reflect: true})
scale = 1
@property({type: Boolean, reflect: true})
showAnimationButton = false
@property({type: Boolean, reflect: true})
showFullscreenButton = false
@property({type: Boolean, reflect: true})
showSuggestionButtons = false
@property({type: Boolean, reflect: true})
showStartTooltip = false
@property({type: String, reflect: true})
rounding: string
@property({type: Boolean, reflect: true})
buttonShadows = false
@property({type: Number, reflect: true})
buttonRounding: Number = 0.2
@property({type: String, reflect: true})
buttonBorderColor: string
@property({type: String, reflect: true})
editorBackgroundColor: string
@property({type: String, reflect: true})
editorForegroundColor: string
@property({type: Boolean, reflect: true})
textmode = false
@property({type: Boolean, reflect: true})
showKeyboardOnFocus: boolean
@property({type: String, reflect: true})
keyboardType: "scientific" | "normal" | "notes"
@property({type: Boolean, reflect: true})
transparentGraphics = false
@property({type: Boolean, reflect: true})
disabledJavaScript = false
@property({type: String, reflect: true})
detachedKeyboardParent: string
private get ggbProperties() {
const {appletId: id, type: appName, width, height, borderColor, borderRadius, enableLabelDrags, enableShiftDragZoom, showZoomButtons, errorDialogsActive, showMenuBar, showToolBar, showToolBarHelp, customToolBar, showAlgebraInput, showResetIcon, language, country, allowStyleBar, randomize, randomSeed, useBrowserForJs, showLogging, capturingThreshold, enableFileFeatures, perspective, enable3d, enableCAS, algebraInputPosition, preventFocus, scaleContainerClass, autoHeight, allowUpscale, playButton, scale, showAnimationButton, showFullscreenButton, showSuggestionButtons, showStartTooltip, rounding, buttonShadows, buttonRounding, buttonBorderColor, editorBackgroundColor, editorForegroundColor, textmode, showKeyboardOnFocus, keyboardType, transparentGraphics, disabledJavaScript, detachedKeyboardParent} = this
let filename: string, material_id: string, ggbBase64: string
if(this.src && this.src.startsWith("data:")) {
ggbBase64 = this.src.split(",").at(-1)
}
else if(this.src && this.src.startsWith("ggbid:")) {
material_id = this.src.slice("ggbid:".length)
}
else if(this.src) {
filename = this.src
}
return {id, appName, filename, material_id, ggbBase64, width, height, borderColor, borderRadius, enableLabelDrags, enableShiftDragZoom, showZoomButtons, errorDialogsActive, showMenuBar, showToolBar, showToolBarHelp, customToolBar, showAlgebraInput, showResetIcon, language: language ?? this.lang, country, allowStyleBar, randomize, randomSeed, useBrowserForJs, showLogging, capturingThreshold, enableFileFeatures, perspective, enable3d, enableCAS, algebraInputPosition, preventFocus, scaleContainerClass, autoHeight, allowUpscale, playButton, scale, showAnimationButton, showFullscreenButton, showSuggestionButtons, showStartTooltip, rounding, buttonShadows, buttonRounding, buttonBorderColor, editorBackgroundColor, editorForegroundColor, textmode, showKeyboardOnFocus, keyboardType, transparentGraphics, disabledJavaScript, detachedKeyboardParent}
}
updateSrc = () => {
if(!this.updatingGgb) {
this.src = `data:application/vnd.geogebra.file;base64,${this.api.getBase64()}`
}
}
connectedCallback(): void {
super.connectedCallback()
this.apiReady = new Promise(r => {
this.addEventListener("ggb-load", (e: any) => {
r(e.detail.api)
this.api = e.detail.api
this.api.registerAddListener(this.updateSrc)
this.api.registerClearListener(this.updateSrc)
this.api.registerUpdateListener(this.updateSrc)
this.api.registerRenameListener(this.updateSrc)
this.api.registerRemoveListener(this.updateSrc)
})
})
}
updatingGgb = false
protected updated(changed: PropertyValues): void {
if(changed.has("src") && this.src && this.src.split(",").at(-1) !== this?.api?.getBase64()) {
this.updatingGgb = true
this?.api?.setBase64(this.src.split(",").at(-1), () => this.updatingGgb = false)
}
else if(changed.has("src") && !this.src) {
this.updatingGgb = true
this?.api?.setBase64("", () => this.updatingGgb = false)
}
}
apiReady: Promise<GeogebraAPI>
api?: GeogebraAPI
initializeIFrame(e: Event) {
const iframe = e.target as HTMLIFrameElement
const doc = iframe.contentDocument
const win = iframe.contentWindow
doc.body.style.margin = "0"
if(this.ggbStyles) {
const style = doc.createElement("style")
style.textContent = this.ggbStyles
doc.head.append(style)
}
doc.body.addEventListener("focusin", () => this.dispatchEvent(new FocusEvent("focus", {bubbles: true, composed: true})))
const ggbEl = doc.createElement("div")
ggbEl.id = "ggb-element"
doc.body.append(ggbEl);
const deployScript = doc.createElement("script")
deployScript.src = "https://www.geogebra.org/apps/deployggb.js"
deployScript.addEventListener("load", () => {
const createScript = doc.createElement("script")
createScript.textContent = `
const applet = new GGBApplet({${JSON.stringify(this.ggbProperties).slice(1, -1)}, appletOnLoad: api => document.dispatchEvent(new CustomEvent("ggb-load", {bubbles: true, composed: true, detail: {api}}))})
applet.inject("ggb-element")
`
doc.addEventListener("ggb-load", (e: any) => this.dispatchEvent(new CustomEvent("ggb-load", {bubbles: true, composed: true, detail: e.detail})))
doc.body.append(createScript)
})
doc.body.append(deployScript)
}
render() {
return html`
<iframe id="ggb" @load=${this.initializeIFrame}></iframe>
`
}
}