import {html, css, PropertyValues, PropertyDeclaration} from "lit"
import {LitElementWw} from "@webwriter/lit"
import {property} from "lit/decorators.js"
import {keyed} from "lit/directives/keyed.js"
import "@shoelace-style/shoelace/dist/themes/light.css"
// @ts-ignore
import LOCALIZE from "../localization/generated"
import {localized, msg} from "@lit/localize"
import {geogebraAppStyles} from "./geogebra-app.styles"
import { booleanAttributeConverter } from "../utils/boolean-attribute-converter"
import { GeogebraOptions, type PropertyConfig } from "./geogebra-options"
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
}
/**
* @fires focus - Fired when the GeoGebra applet gains focus.
* @fires ggb-load - Fired when the GeoGebra API is ready. The event detail contains the API instance.
*/
@localized()
export class GeogebraApp extends LitElementWw {
protected localize = LOCALIZE
protected static get scopedElements() {
return {
"geogebra-options-panel": GeogebraOptions
}
}
static styles = geogebraAppStyles
/**
* Custom CSS styles to apply to the GeoGebra applet.
*/
@property({type: String})
accessor ggbStyles: string = css`
.GeoGebraFrame .toolbar .header-open-landscape .center, .GeoGebraFrame .toolbar .header-close-landscape .center {
top: 0 !important;
}
`.cssText
/**
* app name, default: "classic". "graphing" … GeoGebra Graphing Calculator "geometry" … GeoGebra Geometry "3d" … GeoGebra 3D Graphing Calculator "classic" … GeoGebra Classic "suite" … GeoGebra Calculator Suite "evaluator" … Equation Editor "scientific" …Scientific Calculator "notes" … GeoGebra Notes
*/
@property({type: String, attribute: true, reflect: true})
accessor type: "classic" | "geometry" | "3d" | "suite" | "evaluator" | "scientific" | "notes" = "classic"
/**
* Applet width
*/
@property({type: Number, attribute: true, reflect: true})
accessor width: number = 800
/**
* Applet height
*/
@property({type: Number, attribute: true, reflect: true})
accessor height: number = 450
/**
* Source of the GeoGebra file to load (base64-encoded string, material ID prefixed with "ggbid:", or file URL)
*/
@property({type: String, attribute: true, reflect: true})
accessor src: string
/**
* Color of the border line drawn around the applet panel (as hex rgb string). Default: gray
*/
@property({type: String, attribute: true, reflect: true})
accessor borderColor: string = "gray"
/**
* Size of applet's border radius in pixels.
*/
@property({type: Number, attribute: true, reflect: true})
accessor borderRadius: number = 1
/**
* States whether right clicks should be handled by the applet. Setting this parameter to "false" disables context menus, properties dialogs and right-click-zooming. Default: true. NB also enables/disables some keyboard shortcuts eg Delete and Ctrl + R (recompute all objects). Default: true
*/
@property({type: Boolean, attribute: true, reflect: true, converter: booleanAttributeConverter})
accessor enableRightClick = true
/**
* States whether labels can be dragged. Default: true
*/
@property({type: Boolean, attribute: true, reflect: true, converter: booleanAttributeConverter})
accessor enableLabelDrags = true
/**
* States whether the Graphics View(s) should be moveable using Shift mouse drag (or. Ctrl + mouse drag) or zoomable using Shift + mouse wheel (or Ctrl + mouse wheel). Setting this parameter to "false" disables moving and zooming of the drawing pad. Default: true
*/
@property({type: Boolean, attribute: true, reflect: true, converter: booleanAttributeConverter})
accessor enableShiftDragZoom = true
/**
* States whether the zoom in / zoom out / home buttons should be shown in the Graphics View or not. Default: false
*/
@property({type: Boolean, attribute: true, reflect: true, converter: booleanAttributeConverter})
accessor showZoomButtons = false
/**
* States whether error dialogs will be shown if an invalid input is entered (using the Input Bar or JavaScript) Default: true
*/
@property({type: Boolean, attribute: true, reflect: true, converter: booleanAttributeConverter})
accessor errorDialogsActive = true
/**
* States whether the menubar of GeoGebra should be shown in the applet. Default: false
*/
@property({type: Boolean, attribute: true, reflect: true, converter: booleanAttributeConverter})
accessor showMenuBar = false
/**
* States whether the toolbar with the construction mode buttons should be shown in the applet. Default: false
*/
@property({type: Boolean, attribute: true, reflect: true, converter: booleanAttributeConverter})
accessor showToolBar = true
/**
* States whether the toolbar help text right to the toolbar buttons should be shown in the applet
*/
@property({type: Boolean, attribute: true, reflect: true, converter: booleanAttributeConverter})
accessor showToolBarHelp = false
/**
* Sets the toolbar according to a custom toolbar string where the int values are Toolbar Mode Values, `,` adds a separator within a menu, `|` starts a new menu and `||` adds a separator in the toolbar before starting a new menu. This will override the saved toolbar from the .ggb file / base64 string. Custom tools are numbered 100 001, 100 002, etc
*/
@property({type: String, attribute: true, reflect: true})
accessor customToolBar: string
/**
* States whether the algebra input line (with input field, greek letters and command list) should be shown in the applet. Default: false
*/
@property({type: Boolean, attribute: true, reflect: true, converter: booleanAttributeConverter})
accessor showAlgebraInput = true
/**
* States whether a small icon (GeoGebra ellipse) should be shown in the upper right corner of the applet. Clicking on this icon resets the applet (i.e. it reloads the file given in the filename parameter). Default: false
*/
@property({type: Boolean, attribute: true, reflect: true, converter: booleanAttributeConverter})
accessor showResetIcon = false
/**
* ISO language string. GeoGebra tries to set your local language automatically (if it is available among the supported languages, of course). The default language for unsupported languages is English. If you want to specify a certain language manually, please use this parameter.
*/
@property({type: String, attribute: true, reflect: true})
accessor language: String
/**
* ISO country string. This parameter only makes sense if you use it together with the language parameter.
*/
@property({type: String, attribute: true, reflect: true})
accessor country: String
/**
* This parameter allows you to specify the argument passed to the JavaScript function ggbOnInit(), which is called once the applet is fully initialised. This is useful when you have multiple applets on a page.
*/
@property({type: String, attribute: true, reflect: true})
accessor appletId: String
/**
* Determines whether the Style Bar can be shown (or will be shown if just Graphics View 1 is showing). Default: false
*/
@property({type: Boolean, attribute: true, reflect: true, converter: booleanAttributeConverter})
accessor allowStyleBar = false
/**
* Determines whether random numbers should be randomized on file load. Useful when you want the app to reload in exactly the same state it was saved. Default: true
*/
@property({type: Boolean, attribute: true, reflect: true, converter: booleanAttributeConverter})
accessor randomize = true
/**
* Specify seed for random numbers. Note that if you save a state of the app after user interacted with it and try to reload that state with the same randomSeed, you may get a different result. Use randomize for those use-cases.
*/
@property({type: Number, attribute: true, reflect: true})
accessor randomSeed: number
/**
* When true, GeoGebra: runs ggbOnInit from HTML, ignores ggbOnInit from file, and ignores JS update scripts of objects in file. When false, GeoGebra: ignores ggbOnInit from HTML (use appletOnLoad parameter of GGBApplet instead), runs ggbOnInit from file, and runs JS update scripts of objects in file. Default: false
*/
@property({type: Boolean, attribute: true, reflect: true, converter: booleanAttributeConverter})
accessor useBrowserForJs = false
/**
* Determines whether logging is shown in the Browser's console. Default: false
*/
@property({type: Boolean, attribute: true, reflect: true, converter: booleanAttributeConverter})
accessor showLogging = false
/**
* Determines the sensitivity of object selection. The default value of 3 is usually fine to select and drag objects both with the mouse and touch. Use larger values (e.g. 4 or 5) to make it easier to select and drag objects. Default: 3
*/
@property({type: Number, attribute: true, reflect: true})
accessor capturingThreshold = 3
/**
* Determines whether file saving, file loading, sign in and Options > Save settings are enabled. This argument is ignored when menubar is not showing. Default: true
*/
@property({type: Boolean, attribute: true, reflect: true, converter: booleanAttributeConverter})
accessor enableFileFeatures = true
/**
* Determines whether Undo and Redo icons are shown. This argument is ignored when toolbar is not showing. Default: true
*/
@property({type: Boolean, attribute: true, reflect: true, converter: booleanAttributeConverter})
accessor enableUndoRedo = true
/**
* For values see SetPerspective_Command in the GeoGebra Manual. Just for a blank start ie shouldn't be used with material_id, filename or ggbBase64 parameters
*/
@property({type: String, attribute: true, reflect: true})
accessor perspective: String
/**
* Whether 3D should be enabled (for exam mode).
*/
@property({type: Boolean, attribute: true, reflect: true, converter: booleanAttributeConverter})
accessor enable3d: boolean
/**
* Whether CAS should be enabled (for exam mode).
*/
@property({type: Boolean, attribute: true, reflect: true, converter: booleanAttributeConverter})
accessor enableCAS: boolean
/**
* Determines whether input bar should be shown in algebra, on top of the applet or under the applet. When left empty (default), the position depends on file.
*/
@property({type: String, attribute: true, reflect: true})
accessor algebraInputPosition: "algebra" | "top" | "bottom"
/**
* When set to true, this prevents the applet from getting focus automatically at the start. Default: false
*/
@property({type: Boolean, attribute: true, reflect: true, converter: booleanAttributeConverter})
accessor preventFocus = false
/**
* Name of CSS class that is used as container; applet will scale to fit into the container.
*/
@property({type: String, attribute: true, reflect: true})
accessor scaleContainerClass: string
/**
* `true` to restrict the width of the applet and compute height automatically, add autoHeight: true. `false` if you want the applet to be restricted by both width and height of the container
*/
@property({type: Boolean, attribute: true, reflect: true, converter: booleanAttributeConverter})
accessor autoHeight = true
/**
* Determines whether automatic scaling may scale the applet up. Default: false
*/
@property({type: Boolean, attribute: true, reflect: true, converter: booleanAttributeConverter})
accessor allowUpscale = false
/**
* Determines whether a preview image and a play button should be rendered in place of the applet. Pushing the play button initializes the applet. Default: false
*/
@property({type: Boolean, attribute: true, reflect: true, converter: booleanAttributeConverter})
accessor playButton = false
/**
* Ratio by which the applet should be scaled (eg. 2 makes the applet 2 times bigger, including all texts and UI elements). Default: 1
*/
@property({type: Number, attribute: true, reflect: true})
accessor scale = 1
/**
* Whether animation button should be visible
*/
@property({type: Boolean, attribute: true, reflect: true, converter: booleanAttributeConverter})
accessor showAnimationButton = false
/**
* Whether fullscreen button should be visible
*/
@property({type: Boolean, attribute: true, reflect: true, converter: booleanAttributeConverter})
accessor showFullscreenButton = false
/**
* Whether suggestion buttons (special points, solve) in Algebra View should be visible
*/
@property({type: Boolean, attribute: true, reflect: true, converter: booleanAttributeConverter})
accessor showSuggestionButtons = false
/**
* Whether "Welcome" tooltip should be shown
*/
@property({type: Boolean, attribute: true, reflect: true, converter: booleanAttributeConverter})
accessor showStartTooltip = false
/**
* String composed of number of decimal places and flags — valid flags are "s" for significant digits and "r" for rational numbers. Hence "10" means 10 decimal places, "10s" stands for 10 significant digits.
*/
@property({type: String, attribute: true, reflect: true})
accessor rounding: string
/**
* Whether buttons should have shadow
*/
@property({type: Boolean, attribute: true, reflect: true, converter: booleanAttributeConverter})
accessor buttonShadows = false
/**
* Relative radius of button's rounded border. The border radius in pixels is buttonRounding * height /2, where height is the height of the button. Default: 0.2
*/
@property({type: Number, attribute: true, reflect: true})
accessor buttonRounding: Number = 0.2
/**
* Border color of buttons on the graphics view. Default is black, if the button background is white, otherwise one shade darker than the background color
*/
@property({type: String, attribute: true, reflect: true})
accessor buttonBorderColor: string
/**
* Background color of the evaluator app
*/
@property({type: String, attribute: true, reflect: true})
accessor editorBackgroundColor: string
/**
* Foreground (text) color of the equation editor (appname = "evaluator")
*/
@property({type: String, attribute: true, reflect: true})
accessor editorForegroundColor: string
/**
* Whether editor is in text mode or not (appname = "evaluator"). Default: false
*/
@property({type: Boolean, attribute: true, reflect: true, converter: booleanAttributeConverter})
accessor textmode = false
/**
* Whether to show keyboard when input is focused. When set to true, keyboard is always shown, for false it never appears, for auto it's shown unless closed by user. Default: true in evaluator app, auto in other apps
*/
@property({type: Boolean, attribute: true, reflect: true, converter: booleanAttributeConverter})
accessor showKeyboardOnFocus: boolean
/**
* Which keyboard is shown for equation editor (appname = "evaluator")
*/
@property({type: String, attribute: true, reflect: true})
accessor keyboardType: "scientific" | "normal" | "notes"
/**
* Whether the Graphics View and Graphics View 2 should be transparent
*/
@property({type: Boolean, attribute: true, reflect: true, converter: booleanAttributeConverter})
accessor transparentGraphics = false
/**
* Whether running JavaScript from material files is disabled or not.
*/
@property({type: Boolean, attribute: true, reflect: true, converter: booleanAttributeConverter})
accessor disabledJavaScript = false
/**
* When set, the keyboard should be attached to the first element in DOM that fits the selector.
*/
@property({type: String, attribute: true, reflect: true})
accessor detachedKeyboardParent: string
/**
* Used to force reset of the applet. Increment this property to reset the applet.
*/
@property({type: Number})
protected accessor resetKey: number = 0
// A map of parameter names to their corresponding update functions in the GeoGebra API
private paramUpdateMap: Map void>
private getConfigurableProperties() {
const properties: PropertyConfig[] = []
const ownProperties = (this.constructor as any).elementProperties as Map
const excludedProperties = new Set([
"contentEditable",
"lang",
"src",
"width",
"height",
"ggbStyles",
"resetKey"
])
ownProperties.forEach((config, propName) => {
if (excludedProperties.has(propName)) return
const currentValue = (this as any)[propName]
let propConfig: PropertyConfig = {
name: propName,
type: "string",
value: currentValue
}
if (config.type === Boolean || typeof currentValue === "boolean") {
propConfig.type = "boolean"
} else if (config.type === Number || typeof currentValue === "number") {
propConfig.type = "number"
} else if (config.type === String || typeof currentValue === "string") {
const selectProps: Record = {
"algebraInputPosition": ["algebra", "top", "bottom"],
"type": ["classic", "geometry", "3d", "suite", "evaluator", "scientific", "notes"],
"keyboardType": ["scientific", "normal", "notes"]
}
if (selectProps[propName]) {
propConfig.type = "select"
propConfig.options = selectProps[propName]
}
}
properties.push(propConfig)
})
return properties
}
private get ggbProperties() {
const {appletId: id, type: appName, width, height, borderColor, borderRadius, enableRightClick, 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, enableRightClick, 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}
}
private updateSrc = (a) => {
console.log("updateSrc called", a)
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)
this.paramUpdateMap = new Map void>([
["width", this.api.setWidth],
["height", this.api.setHeight],
["enableRightClick", this.api.enableRightClick],
["enableLabelDrags", this.api.enableLabelDrags],
["enableShiftDragZoom", this.api.enableShiftDragZoom],
["errorDialogsActive", this.api.setErrorDialogsActive],
// ["showMenuBar", this.api.showMenuBar], // disabled because showMenuBar(false) does not work
["showToolBar", this.api.showToolBar],
["customToolBar", this.api.setCustomToolBar],
["showAlgebraInput", this.api.showAlgebraInput],
["showResetIcon", this.api.showResetIcon],
["perspective", this.api.setPerspective],
["enable3d", this.api.enable3D],
["enableCAS", this.api.enableCAS],
["rounding", this.api.setRounding],
])
})
})
}
private handlePropertyChange(e: CustomEvent) {
const {name, value} = e.detail;
(this as any)[name] = value
}
private handleFileImport(e: CustomEvent) {
const { base64 } = e.detail
this.src = `data:application/vnd.geogebra.file;base64,${base64}`
}
private updatingGgb = false
protected updated(changed: PropertyValues): void {
console.log("GeogebraApp updated:", changed)
super.updated(changed)
if(!this.api) {
this.resetIFrame()
return
}
if (this.paramUpdateMap) {
let needFullReload = false
changed.forEach((_, key: PropertyKey) => {
const propName = key.toString()
console.log(`Property changed: ${propName}`)
if(propName === "src") {
return
}
if(this.paramUpdateMap.has(propName) && this.api) {
const updateFunc = this.paramUpdateMap.get(propName)
if(updateFunc) {
updateFunc.call(this.api, (this as any)[propName])
console.log(`Updated GeoGebra property ${propName} to value ${(this as any)[propName]}`)
}
} else {
console.log(`No update function found for property: ${propName}, reloading entire applet...`)
needFullReload = true
}
})
if(needFullReload) {
this.resetIFrame()
return
}
}
if(!changed.has("src") || !this.api) {
return
}
if(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(!this.src) {
this.updatingGgb = true
this?.api?.setBase64("", () => {
this.updatingGgb = false
})
}
}
private apiReady: Promise
private api?: GeogebraAPI
private initializeIFrame(iframe: HTMLIFrameElement) {
const doc = iframe.contentDocument
const win = iframe.contentWindow
if (doc.getElementById("ggb-element")) {
return
}
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)
}
private resetIFrame() {
if(!this.api) {
return
}
this.api = undefined
this.apiReady = new Promise(r => {
this.addEventListener("ggb-load", (e: any) => {
r(e.detail.api)
}, {once: true})
})
this.resetKey++
}
render() {
return html`
${keyed(this.resetKey, html``)}
`
}
}