// noinspection JSUnusedGlobalSymbols
import {MathCell, generateId, getVariable, checkLimits} from '@backstrap/mathcell';
import {Canvas} from './Canvas';
/**
* @typedef {Object} renderConfig
* @property {boolean} [axes]
* @property {boolean} [animate]
* @property {boolean} [animateOnInteraction]
* @property {boolean} [equalAspect]
* @property {boolean} [frame]
* @property {number} [cameraNear]
* @property {number} [cameraFar]
* @property {number} [decimals]
* @property {number} [suspendTimeout]
* @property {number} [xMin]
* @property {number} [yMin]
* @property {number} [zMin]
* @property {number} [xMax]
* @property {number} [yMax]
* @property {number} [zMax]
* @property {number[]} [aspectRatio]
* @property {number[]|string} [viewpoint]
* @property {number|string} [viewDistance]
* @property {number[]|number[][]} [clippingPlane]
* @property {string} [ambientLight]
* @property {string} [clearColor]
* @property {string[]} [axesLabels]
*/
/**
* @typedef {Object} baseConfig
* @property {string} type
* @property {string} name
* @property {string} [label]
* @property {number} [default]
*/
/**
* @typedef {baseConfig} buttonsConfig
* @property {number[]} [values]
*/
/**
* @typedef {baseConfig} sliderConfig
* @property {number} [min]
* @property {number} [max]
*/
/**
* @typedef {buttonsConfig|sliderConfig} inputConfig
*/
/**
* @typedef {inputConfig[]|inputConfig[][]} inputConfigSet
*/
/**
* @type {function(string): HTMLElement}
* @private
*/
const getElement = (id => document.getElementById(id));
/**
* Class representing a MathCell graphic.
*/
export class MathModel {
/**
* A MathCell rendering configuration
* @member {renderConfig}
*/
config = {};
/**
* A hierarchical array of input configurations
* @member {inputConfigSet}
*/
inputs = [];
/**
* The Canvas object on which we will draw our graphics
* @member {Canvas}
*/
canvas = new Canvas('', {});
// noinspection JSUnusedGlobalSymbols
/**
* Attempt to load all mathcells in a document
* @param {Object} classMap - an object which maps id's to Classes
* @param {Document} document - a DOM document (defaults to window.document)
*/
static loadMathCells(classMap, document = window.document) {
const win = document.defaultView || window;
// For input onchange events
win.checkLimits = checkLimits;
win.addEventListener('load', () => {
for (const cell of document.getElementsByClassName('mathcell')) {
/** @type {function(string=)} */
const Model = classMap[cell.id];
if (Model) {
new Model(cell.id).run(cell);
} else {
console.warn('Unknown model id "' + cell.id + '"');
}
}
});
}
/**
* If a subclass overrides beforeUpdate(), and it returns a truthy value,
* then the canvas' screen will be cleared before attempting to redraw the content.
* If beforeUpdate() returns a falsy value, the content redraw will happen synchronously instead.
*
* @param {Object} params - the parameter values from our configured inputs
*/
beforeUpdate(params) { }
/**
* @param {Object} params - the parameter values from our configured inputs
*/
update(params) { }
/**
* Build an interactive Math Cell in a DOM Element.
*
* @param {HTMLElement|string} element - A DOM Element, or the id of one.
*/
run(element) {
let id = element;
let first = true;
if (typeof id === 'string' || id instanceof String) {
element = getElement(id);
} else {
id = element.id || (element.id = generateId());
}
this.canvas = new Canvas(id, this.config);
MathCell(id, this.inputs);
element.update = (id => {
try {
const values = this.#getValues(id, this.inputs);
this.canvas.clear();
if (this.beforeUpdate(values)) {
// Draw a blank canvas.
first = first ? false : this.canvas.show()&&false;
// Allow any DOM changes in beforeUpdate() to take effect.
setTimeout(() => {
this.update(values);
this.canvas.show();
}, 0);
} else {
this.update(values);
this.canvas.show();
}
} catch (e) {
document.getElementById(id + 'output').innerHTML = e.toString();
throw e;
}
});
element.update(id);
}
/**
* Add some geometry to the current drawing
* @param {Geometry[]} objs
*/
add(...objs) {
objs.map(obj => this.canvas.add(obj));
return this;
}
/**
* Modify the rendering configuration
* @param {renderConfig} config - the parameters to be updated
*/
configure(config) {
this.canvas.configure(config);
return this;
}
/**
* Get the values from our configured inputs
* @param {string} id
* @param {inputConfigSet} inputs
* @returns {Object}
*/
#getValues(id, inputs) {
return inputs.reduce(
(values, input) => Object.assign(
values,
Array.isArray(input)
? this.#getValues(id, input)
: {[input.name]: getVariable(id, input.name)}
),
{}
);
}
}