slipshow
Version:
This is not another slide engine, but a slip engine.
1,561 lines (1,441 loc) • 85.7 kB
JavaScript
class SlipFigure extends HTMLImageElement {
// Pour spécifier les attributs qui, changés, appellent "attributeChangedCallback"
static get observedAttributes() {return ['figure-name']; };
constructor() {
// Toujours appeler "super" d'abord dans le constructeur
super();
if (typeof(this.internalStep) == "undefined") {
this.internalStep = 0;
}
this.img = [];
this.maxStep = 0;
this.figureName = this.getAttribute("figure-name");
this.promise = this.preloadImages(0).then(() => {
this.updateSRC();
});
}
preloadImage(i) {
return new Promise((resolve, reject) => {
this.img[i] = new Image();
this.img[i].onload = () => resolve();
this.img[i].onerror = reject;
this.img[i].src = this.getURL(i);
});
}
preloadImages(i) {
return new Promise((resolve, reject) => {
this.preloadImage(i).then(()=> {
this.preloadImages(i+1).then(() => { resolve(); });
}).catch(() => {
this.maxStep = i-1;
resolve();
});
});
}
connectedCallback() {
}
getURL(i) {
return "figures/"+this.figureName+"/"+this.figureName+"_"+i+".svg";
}
updateSRC() {
this.src = this.getURL(this.figureStep);
}
set figureStep(step) {
this.promise = this.promise.then(() => {
if(step > this.maxStep)
this.internalStep = this.maxStep;
else if (step < 0)
this.internalStep = 0;
else
this.internalStep=step;
this.updateSRC();
});
}
get figureStep() {
return this.internalStep;
}
attributeChangedCallback(name, oldValue, newValue) {
if(name == "figure-name") {
this.figureName = newValue;
this.promise = this.promise.then(() => {
this.preloadImages(0).then(() => {
this.updateSRC();
});
});
}
}
nextFigure() {
this.figuresStep++;
}
}
customElements.define('slip-figure', SlipFigure, { extends: "img" });
let myQueryAll = (root, selector, avoid) => {
avoid = avoid || "slip-slip";
if (!root.id)
root.id = '_' + Math.random().toString(36).substr(2, 15); let allElem = Array.from(root.querySelectorAll(selector));
let separatedSelector = selector.split(",").map(selec => "#"+root.id+" " + avoid + " " + selec).join();
let other = Array.from(root.querySelectorAll(separatedSelector));
// let other = Array.from(root.querySelectorAll("#"+root.id+" " + avoid + " " + separatedSelector));
return allElem.filter(value => !other.includes(value));
};
window.myQueryAll = myQueryAll;
function cloneNoSubslip (elem) {
let newElem = elem.cloneNode(false);
elem.childNodes.forEach((child) => {
if(child.tagName && child.tagName == "SLIP-SLIP"){
let placeholder = document.createElement(child.tagName);
let importantAttributes =["pause","step", "auto-enter", "immediate-enter"];
importantAttributes.forEach((attr) => {
if(child.hasAttribute(attr))
placeholder.setAttribute(attr, child.getAttribute(attr));
});
placeholder.classList.add("toReplace");
newElem.appendChild(placeholder);
}
else if(child.tagName && child.tagName == "CANVAS" && child.classList.contains("sketchpad")){
let placeholder = document.createElement(child.tagName);
placeholder.classList.add("toReplaceSketchpad");
newElem.appendChild(placeholder);
}
else
newElem.appendChild(cloneNoSubslip(child));
});
return newElem;
}
function replaceSubslips(clone, subslips, sketchpad, sketchpadHighlight) {
let placeholders = myQueryAll(clone, ".toReplace");
subslips.forEach((subslip, index) => {
let importantAttributes =["pause","step", "auto-enter", "immediate-enter"];
importantAttributes.forEach((attr) => {
if(placeholders[index].hasAttribute(attr))
subslip.setAttribute(attr, placeholders[index].getAttribute(attr));
});
placeholders[index].replaceWith(subslip);
});
let sketchPlaceholder = myQueryAll(clone, ".toReplaceSketchpad");
if(sketchPlaceholder[0])
sketchPlaceholder[0].replaceWith(sketchpad);
if(sketchPlaceholder[1])
sketchPlaceholder[1].replaceWith(sketchpadHighlight);
}
var IUtil = /*#__PURE__*/Object.freeze({
__proto__: null,
cloneNoSubslip: cloneNoSubslip,
myQueryAll: myQueryAll,
replaceSubslips: replaceSubslips
});
// import Hammer from 'hammerjs';
function IController (ng) {
let engine = ng;
this.getEngine = () => this.engine;
this.setEngine = (ng) => this.engine = ng;
let activated = true;
this.activate = () => {
activated = true;
};
this.deactivate = () => {
activated = false;
};
let left_keys = ["k"],
right_keys = ["m"],
up_keys = ["o"],
down_keys = ["l"],
rotate_keys = ["i"],
unrotate_keys = ["p"],
zoom_keys = ["z"],
unzoom_keys = ["Z"],
show_toc_keys = ["T"],
show_toc2_keys = ["t"],
next_keys = ["ArrowRight", "ArrowDown"],
previous_keys = ["ArrowLeft", "ArrowUp"],
refresh_keys = ["r"],
change_speed_keys = ["f"],
up_slip_keys = [],
draw_on_slip_keys = ["w"],
erase_on_slip_keys = ["W"],
highlight_on_slip_keys = ["h"],
erase_highlight_on_slip_keys = ["H"],
stop_writing_on_slip_keys = ["x"],
clear_annotations_keys = ["X"],
reload_canvas_keys = ["C"],
background_canvas_keys = ["#"];
// let mainSlip = mainS;
// this.getMainSlip = () => mainSlip;
// this.setMainSlip = (slip) => mainSlip = slip;
// let mc = new Hammer(document.body);
// mc.on("swipe", (ev) => {
// if (ev.direction == 2) {
// engine.next();
// }
// if (ev.direction == 4) {
// engine.previous();
// }
// });
let speedMove=1;
document.addEventListener("keypress", (ev) => {
if(change_speed_keys.includes(ev.key) && activated) { speedMove = (speedMove + 4)%30+1; }
if(refresh_keys.includes(ev.key) && activated) { engine.getCurrentSlip().refresh(); }
if(draw_on_slip_keys.includes(ev.key) && activated) { engine.setTool("drawing"); }
if(erase_on_slip_keys.includes(ev.key) && activated) { engine.setTool("drawing-erase"); }
if(highlight_on_slip_keys.includes(ev.key) && activated) { engine.setTool("highlighting"); }
if(erase_highlight_on_slip_keys.includes(ev.key) && activated) { engine.setTool("highlighting-erase"); }
if(stop_writing_on_slip_keys.includes(ev.key) && activated) { engine.setTool("no-tool"); }
if(clear_annotations_keys.includes(ev.key) && activated) { engine.setTool("clear-all"); }
if(reload_canvas_keys.includes(ev.key) && activated) { engine.reloadCanvas(); }
if(background_canvas_keys.includes(ev.key) && activated) {
document.querySelectorAll("slip-slip").forEach((slip) => {slip.style.zIndex = "-1";});
document.querySelectorAll(".background-canvas").forEach((canvas) => {canvas.style.zIndex = "1";});
}
});
document.addEventListener("keydown", (ev) => {
let openWindowHeight = engine.getOpenWindowHeight();
let openWindowWidth = engine.getOpenWindowWidth();
if(down_keys.includes(ev.key) && activated) { engine.moveWindowRelative( 0 , (speedMove)/openWindowHeight, 0, 0, 0.1); } // Bas
if(up_keys.includes(ev.key) && activated) { engine.moveWindowRelative( 0 , -(speedMove)/openWindowHeight, 0, 0, 0.1); } // Haut
if(left_keys.includes(ev.key) && activated) { engine.moveWindowRelative(-(speedMove)/openWindowWidth, 0 , 0, 0, 0.1); } // Gauche
if(right_keys.includes(ev.key) && activated) { engine.moveWindowRelative( (speedMove)/openWindowWidth, 0 , 0, 0, 0.1); } // Droite
if(rotate_keys.includes(ev.key) && activated) { engine.moveWindowRelative(0, 0, 0 , 1, 0.1); } // Rotate
if(unrotate_keys.includes(ev.key) && activated) { engine.moveWindowRelative(0, 0, 0 , -1, 0.1); } // Unrotate
if(zoom_keys.includes(ev.key) && activated) { engine.moveWindowRelative(0, 0, 0.01, 0, 0.1); } // Zoom
if(unzoom_keys.includes(ev.key) && activated) { engine.moveWindowRelative(0, 0, -0.01, 0, 0.1); } // Unzoom
if(show_toc_keys.includes(ev.key) && activated) {
engine.showToC();
// document.querySelector(".toc-slip").style.display = document.querySelector(".toc-slip").style.display == "none" ? "block" : "none";
}
if(show_toc2_keys.includes(ev.key) && activated) {
// engine.showToC();
document.querySelector(".toc-slip").style.display = document.querySelector(".toc-slip").style.display == "none" ? "block" : "none";
}
if(next_keys.includes(ev.key) && activated) {
// console.log(ev);
if(ev.shiftKey)
engine.nextSlip();
else
engine.next();
}
else if (previous_keys.includes(ev.key) && activated) {
if(ev.shiftKey)
engine.previousSlip();
else
engine.previous();
}
else if (up_slip_keys.includes(ev.key) && activated) {
engine.pop();
}
});
}
/* eslint-disable max-classes-per-file */
// make a class for Point
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
set(x, y) {
this.x = x;
this.y = y;
}
}
// make a class for the mouse data
class Mouse extends Point {
constructor() {
super(0, 0);
this.down = false;
this.previous = new Point(0, 0);
}
}
const floodFillInterval = 100;
const maxLineThickness = 50;
const minLineThickness = 1;
const lineThicknessRange = maxLineThickness - minLineThickness;
const thicknessIncrement = 0.5;
const minSmoothingFactor = 0.87;
const initialSmoothingFactor = 0.85;
const weightSpread = 10;
const initialThickness = 2;
class AtramentEventTarget {
constructor() {
this.eventListeners = new Map();
}
addEventListener(eventName, handler) {
const handlers = this.eventListeners.get(eventName) || new Set();
handlers.add(handler);
this.eventListeners.set(eventName, handlers);
}
removeEventListener(eventName, handler) {
const handlers = this.eventListeners.get(eventName);
if (!handlers) return;
handlers.delete(handler);
}
dispatchEvent(eventName, data) {
const handlers = this.eventListeners.get(eventName);
if (!handlers) return;
[...handlers].forEach((handler) => handler(data));
}
}
const lineDistance = (x1, y1, x2, y2) => {
// calculate euclidean distance between (x1, y1) and (x2, y2)
const xs = (x2 - x1) ** 2;
const ys = (y2 - y1) ** 2;
return Math.sqrt(xs + ys);
};
const hexToRgb = (hexColor) => {
// Since input type color provides hex and ImageData accepts RGB need to transform
const m = hexColor.match(/^#?([\da-f]{2})([\da-f]{2})([\da-f]{2})$/i);
return [
parseInt(m[1], 16),
parseInt(m[2], 16),
parseInt(m[3], 16),
];
};
const matchColor = (data, compR, compG, compB, compA) => (pixelPos) => {
// Pixel color equals comp color?
const r = data[pixelPos];
const g = data[pixelPos + 1];
const b = data[pixelPos + 2];
const a = data[pixelPos + 3];
return (r === compR && g === compG && b === compB && a === compA);
};
/* eslint-disable no-param-reassign */
const colorPixel = (data, fillR, fillG, fillB, startColor, alpha) => {
const matcher = matchColor(data, ...startColor);
return (pixelPos) => {
// Update fill color in matrix
data[pixelPos] = fillR;
data[pixelPos + 1] = fillG;
data[pixelPos + 2] = fillB;
data[pixelPos + 3] = alpha;
if (!matcher(pixelPos + 4)) {
data[pixelPos + 4] = data[pixelPos + 4] * 0.01 + fillR * 0.99;
data[pixelPos + 4 + 1] = data[pixelPos + 4 + 1] * 0.01 + fillG * 0.99;
data[pixelPos + 4 + 2] = data[pixelPos + 4 + 2] * 0.01 + fillB * 0.99;
data[pixelPos + 4 + 3] = data[pixelPos + 4 + 3] * 0.01 + alpha * 0.99;
}
if (!matcher(pixelPos - 4)) {
data[pixelPos - 4] = data[pixelPos - 4] * 0.01 + fillR * 0.99;
data[pixelPos - 4 + 1] = data[pixelPos - 4 + 1] * 0.01 + fillG * 0.99;
data[pixelPos - 4 + 2] = data[pixelPos - 4 + 2] * 0.01 + fillB * 0.99;
data[pixelPos - 4 + 3] = data[pixelPos - 4 + 3] * 0.01 + alpha * 0.99;
}
};
};
/* eslint-enable no-param-reassign */
const DrawingMode = {
DRAW: 'draw',
ERASE: 'erase',
FILL: 'fill',
DISABLED: 'disabled',
};
const PathDrawingModes = [DrawingMode.DRAW, DrawingMode.ERASE];
class Atrament extends AtramentEventTarget {
constructor(selector, config = {}) {
if (typeof window === 'undefined') {
throw new Error('Looks like we\'re not running in a browser');
}
super();
// get canvas element
if (selector instanceof window.Node && selector.tagName === 'CANVAS') this.canvas = selector;
else if (typeof selector === 'string') this.canvas = document.querySelector(selector);
else throw new Error(`can't look for canvas based on '${selector}'`);
if (!this.canvas) throw new Error('canvas not found');
// set external canvas params
this.canvas.width = config.width || this.canvas.width;
this.canvas.height = config.height || this.canvas.height;
// create a mouse object
this.mouse = new Mouse();
// mousemove handler
const mouseMove = (event) => {
if (event.cancelable) {
event.preventDefault();
}
const rect = this.canvas.getBoundingClientRect();
const position = (event.changedTouches && event.changedTouches[0]) || event;
let x = position.offsetX;
let y = position.offsetY;
if (typeof x === 'undefined') {
x = position.clientX - rect.left;
}
if (typeof y === 'undefined') {
y = position.clientY - rect.top;
}
const { mouse } = this;
// draw if we should draw
if (mouse.down && PathDrawingModes.includes(this.modeInternal)) {
const { x: newX, y: newY } = this.draw(x, y, mouse.previous.x, mouse.previous.y);
if (!this.dirty
&& this.modeInternal === DrawingMode.DRAW && (x !== mouse.x || y !== mouse.y)) {
this.dirty = true;
this.fireDirty();
}
mouse.set(x, y);
mouse.previous.set(newX, newY);
} else {
mouse.set(x, y);
}
};
// mousedown handler
const mouseDown = (event) => {
if (event.cancelable) {
event.preventDefault();
}
// update position just in case
mouseMove(event);
// if we are filling - fill and return
if (this.mode === DrawingMode.FILL) {
this.fill();
return;
}
// remember it
const { mouse } = this;
mouse.previous.set(mouse.x, mouse.y);
mouse.down = true;
this.beginStroke(mouse.previous.x, mouse.previous.y);
};
const mouseUp = (e) => {
if (this.mode === DrawingMode.FILL) {
return;
}
const { mouse } = this;
if (!mouse.down) {
return;
}
const position = (e.changedTouches && e.changedTouches[0]) || e;
const x = position.offsetX;
const y = position.offsetY;
mouse.down = false;
if (mouse.x === x && mouse.y === y && PathDrawingModes.includes(this.mode)) {
const { x: nx, y: ny } = this.draw(mouse.x, mouse.y, mouse.previous.x, mouse.previous.y);
mouse.previous.set(nx, ny);
}
this.endStroke(mouse.x, mouse.y);
};
// attach listeners
this.canvas.addEventListener('mousemove', mouseMove);
this.canvas.addEventListener('mousedown', mouseDown);
document.addEventListener('mouseup', mouseUp);
this.canvas.addEventListener('touchstart', mouseDown);
this.canvas.addEventListener('touchend', mouseUp);
this.canvas.addEventListener('touchmove', mouseMove);
// helper for destroying Atrament (removing event listeners)
this.destroy = () => {
this.clear();
this.canvas.removeEventListener('mousemove', mouseMove);
this.canvas.removeEventListener('mousedown', mouseDown);
document.removeEventListener('mouseup', mouseUp);
this.canvas.removeEventListener('touchstart', mouseDown);
this.canvas.removeEventListener('touchend', mouseUp);
this.canvas.removeEventListener('touchmove', mouseMove);
};
// set internal canvas params
this.context = this.canvas.getContext('2d');
this.context.globalCompositeOperation = 'source-over';
this.context.globalAlpha = 1;
this.context.strokeStyle = config.color || 'rgba(0,0,0,1)';
this.context.lineCap = 'round';
this.context.lineJoin = 'round';
this.context.translate(0.5, 0.5);
this.filling = false;
this.fillStack = [];
// set drawing params
this.recordStrokes = false;
this.strokeMemory = [];
this.smoothing = initialSmoothingFactor;
this.thickness = initialThickness;
this.targetThickness = this.thickness;
this.weightInternal = this.thickness;
this.maxWeight = this.thickness + weightSpread;
this.modeInternal = DrawingMode.DRAW;
this.adaptiveStroke = true;
// update from config object
['weight', 'smoothing', 'adaptiveStroke', 'mode']
.forEach((key) => {
if (config[key] !== undefined) {
this[key] = config[key];
}
});
}
/**
* Begins a stroke at a given position
*
* @param {number} x
* @param {number} y
*/
beginStroke(x, y) {
this.context.beginPath();
this.context.moveTo(x, y);
if (this.recordStrokes) {
this.strokeTimestamp = performance.now();
this.strokeMemory.push({
point: new Point(x, y),
time: performance.now() - this.strokeTimestamp,
});
}
this.dispatchEvent('strokestart', { x, y });
}
/**
* Ends a stroke at a given position
*
* @param {number} x
* @param {number} y
*/
endStroke(x, y) {
this.context.closePath();
if (this.recordStrokes) {
this.strokeMemory.push({
point: new Point(x, y),
time: performance.now() - this.strokeTimestamp,
});
}
this.dispatchEvent('strokeend', { x, y });
if (this.recordStrokes) {
this.dispatchEvent('strokerecorded', { stroke: this.currentStroke });
}
this.strokeMemory = [];
delete (this.strokeTimestamp);
}
/**
* Draws a smooth quadratic curve with adaptive stroke thickness
* between two points
*
* @param {number} x current X coordinate
* @param {number} y current Y coordinate
* @param {number} prevX previous X coordinate
* @param {number} prevY previous Y coordinate
*/
draw(x, y, prevX, prevY) {
if (this.recordStrokes) {
this.strokeMemory.push({
point: new Point(x, y),
time: performance.now() - this.strokeTimestamp,
});
this.dispatchEvent('pointdrawn', { stroke: this.currentStroke });
}
const { context } = this;
// calculate distance from previous point
const rawDist = lineDistance(x, y, prevX, prevY);
// now, here we scale the initial smoothing factor by the raw distance
// this means that when the mouse moves fast, there is more smoothing
// and when we're drawing small detailed stuff, we have more control
// also we hard clip at 1
const smoothingFactor = Math.min(
minSmoothingFactor,
this.smoothing + (rawDist - 60) / 3000,
);
// calculate processed coordinates
const procX = x - (x - prevX) * smoothingFactor;
const procY = y - (y - prevY) * smoothingFactor;
// recalculate distance from previous point, this time relative to the smoothed coords
const dist = lineDistance(procX, procY, prevX, prevY);
if (this.adaptiveStroke) {
// calculate target thickness based on the new distance
this.targetThickness = ((dist - minLineThickness) / lineThicknessRange)
* (this.maxWeight - this.weightInternal) + this.weightInternal;
// approach the target gradually
if (this.thickness > this.targetThickness) {
this.thickness -= thicknessIncrement;
} else if (this.thickness < this.targetThickness) {
this.thickness += thicknessIncrement;
}
// set line width
context.lineWidth = this.thickness;
} else {
// line width is equal to default weight
context.lineWidth = this.weightInternal;
}
// draw using quad interpolation
context.quadraticCurveTo(prevX, prevY, procX, procY);
context.stroke();
return { x: procX, y: procY };
}
get color() {
return this.context.strokeStyle;
}
set color(c) {
if (typeof c !== 'string') throw new Error('wrong argument type');
this.context.strokeStyle = c;
}
get weight() {
return this.weightInternal;
}
set weight(w) {
if (typeof w !== 'number') throw new Error('wrong argument type');
this.weightInternal = w;
this.thickness = w;
this.targetThickness = w;
this.maxWeight = w + weightSpread;
}
get mode() {
return this.modeInternal;
}
set mode(m) {
if (typeof m !== 'string') throw new Error('wrong argument type');
switch (m) {
case DrawingMode.ERASE:
this.modeInternal = DrawingMode.ERASE;
this.context.globalCompositeOperation = 'destination-out';
break;
case DrawingMode.FILL:
this.modeInternal = DrawingMode.FILL;
this.context.globalCompositeOperation = 'source-over';
break;
case DrawingMode.DISABLED:
this.modeInternal = DrawingMode.DISABLED;
break;
default:
this.modeInternal = DrawingMode.DRAW;
this.context.globalCompositeOperation = 'source-over';
break;
}
}
get currentStroke() {
return {
points: this.strokeMemory.slice(),
mode: this.mode,
weight: this.weight,
smoothing: this.smoothing,
color: this.color,
adaptiveStroke: this.adaptiveStroke,
};
}
isDirty() {
return !!this.dirty;
}
fireDirty() {
this.dispatchEvent('dirty');
}
clear() {
if (!this.isDirty) {
return;
}
this.dirty = false;
this.dispatchEvent('clean');
// make sure we're in the right compositing mode, and erase everything
if (this.modeInternal === DrawingMode.ERASE) {
this.modeInternal = DrawingMode.DRAW;
this.context.clearRect(-10, -10, this.canvas.width + 20, this.canvas.height + 20);
this.modeInternal = DrawingMode.ERASE;
} else {
this.context.clearRect(-10, -10, this.canvas.width + 20, this.canvas.height + 20);
}
}
toImage() {
return this.canvas.toDataURL();
}
fill() {
const { mouse } = this;
const { context } = this;
// converting to Array because Safari 9
const startColor = Array.from(context.getImageData(mouse.x, mouse.y, 1, 1).data);
if (!this.filling) {
const { x, y } = mouse;
this.dispatchEvent('fillstart', { x, y });
this.filling = true;
setTimeout(() => {
this.floodFill(mouse.x, mouse.y, startColor);
}, floodFillInterval);
} else {
this.fillStack.push([
mouse.x,
mouse.y,
startColor,
]);
}
}
floodFill(_startX, _startY, startColor) {
const { context } = this;
const startX = Math.floor(_startX);
const startY = Math.floor(_startY);
const canvasWidth = context.canvas.width;
const canvasHeight = context.canvas.height;
const pixelStack = [[startX, startY]];
// hex needs to be trasformed to rgb since colorLayer accepts RGB
const fillColor = hexToRgb(this.color);
// Need to save current context with colors, we will update it
const colorLayer = context.getImageData(0, 0, context.canvas.width, context.canvas.height);
const alpha = Math.min(context.globalAlpha * 10 * 255, 255);
const colorPixel$1 = colorPixel(colorLayer.data, ...fillColor, startColor, alpha);
const matchColor$1 = matchColor(colorLayer.data, ...startColor);
const matchFillColor = matchColor(colorLayer.data, ...[...fillColor, 255]);
// check if we're trying to fill with the same colour, if so, stop
if (matchFillColor((startY * context.canvas.width + startX) * 4)) {
this.filling = false;
this.dispatchEvent('fillend', {});
return;
}
while (pixelStack.length) {
const newPos = pixelStack.pop();
const x = newPos[0];
let y = newPos[1];
let pixelPos = (y * canvasWidth + x) * 4;
while (y-- >= 0 && matchColor$1(pixelPos)) {
pixelPos -= canvasWidth * 4;
}
pixelPos += canvasWidth * 4;
++y;
let reachLeft = false;
let reachRight = false;
while (y++ < canvasHeight - 1 && matchColor$1(pixelPos)) {
colorPixel$1(pixelPos);
if (x > 0) {
if (matchColor$1(pixelPos - 4)) {
if (!reachLeft) {
pixelStack.push([x - 1, y]);
reachLeft = true;
}
} else if (reachLeft) {
reachLeft = false;
}
}
if (x < canvasWidth - 1) {
if (matchColor$1(pixelPos + 4)) {
if (!reachRight) {
pixelStack.push([x + 1, y]);
reachRight = true;
}
} else if (reachRight) {
reachRight = false;
}
}
pixelPos += canvasWidth * 4;
}
}
// Update context with filled bucket!
context.putImageData(colorLayer, 0, 0);
if (this.fillStack.length) {
this.floodFill(...this.fillStack.shift());
} else {
this.filling = false;
this.dispatchEvent('fillend', {});
}
}
}
function Slip$1(name, fullName, actionL, ng, options) {
// ******************************
// Action List
// ******************************
this.generateActionList = function() {
let newActionList = [];
this.queryAll("slip-slip[enter-at]").forEach((slip) => {
newActionList[slip.getAttribute("enter-at")] = new Slip$1(slip, "", [], ng, {});
});
return newActionList;
};
this.addSubSlips = function() {
let newActionList = [];
this.queryAll("slip-slip[enter-at]").forEach((slip) => {
this.setNthAction(slip.getAttribute("enter-at"), new Slip$1(slip, "", [], ng, {}));
});
return newActionList;
};
let actionList = actionL;// || this.generateActionList();
this.setAction = (actionL) => {actionList = actionL;};
this.getActionList = () => {
let ret = [];
for(let i = 0;i <= this.getMaxNext(); i++) {
if(this.pauseSlipList[i] instanceof Slip$1)
ret[i] = this.pauseSlipList[i];
else if(typeof actionList[i] == "function" || actionList[i] instanceof Slip$1)
ret[i] = actionList[i];
else
ret[i] = () => {};
}
return ret;
};
this.setNthAction = (n,action) => {actionList[n] = action;};
this.getCurrentSubSlip = () => {
if(actionList[this.getActionIndex()] instanceof Slip$1)
return actionList[this.getActionIndex()];
if(this.pauseSlipList[this.getActionIndex()] instanceof Slip$1)
return this.pauseSlipList[this.getActionIndex()];
return false;
};
this.nextStageNeedGoto = () => {
if(actionList[this.getActionIndex()+1] instanceof Slip$1)
return false;
if(this.pauseSlipList[this.getActionIndex()+1] instanceof Slip$1)
return false;
if(this.getActionIndex() >= this.getMaxNext())
return false;
return true;
};
this.getSubSlipList = function () {
return this.getActionList().filter((action) => action instanceof Slip$1);
};
// ******************************
// Action Index
// ******************************
let actionIndex = -1;
this.setActionIndex = (actionI) => actionIndex = actionI;
this.getActionIndex = () => actionIndex;
this.getMaxNext = () => {
if(this.maxNext)
return this.maxNext;
let maxTemp = actionList.length;
["mk-visible-at",
"mk-hidden-at",
"mk-emphasize-at",
"mk-unemphasize-at",
"emphasize-at",
"chg-visib-at",
"up-at",
"down-at",
"center-at",
"static-at",
"exec-at",
"enter-at",
"focus-at",
"unfocus-at",
"figure-next-at",
"figure-previous-at",
].forEach((attr) => {
this.queryAll("*["+attr+"]").forEach((elem) => {
elem.getAttribute(attr).split(" ").forEach((strMax) => {
maxTemp = Math.max(Math.abs(parseInt(strMax)),maxTemp);
});
});
});
let sumArray = this.queryAll("[pause], [step], [auto-enter], [immediate-enter]").map((elem) => {
if(elem.hasAttribute("pause") && elem.getAttribute("pause") != "")
return parseInt(elem.getAttribute("pause"));
if(elem.hasAttribute("step") && elem.getAttribute("step") != "")
return parseInt(elem.getAttribute("step"));
return 1; });
maxTemp = Math.max(maxTemp, sumArray.reduce((a,b) => a+b, 0));
this.maxNext = maxTemp;
return maxTemp;
};
// ******************************
// Queries
// ******************************
this.queryAll = (quer) => {
return myQueryAll(this.element, quer);
// let allElem = Array.from(this.element.querySelectorAll(quer));
// let other = Array.from(this.element.querySelectorAll("#"+this.name+" slip "+quer));
// return allElem.filter(value => !other.includes(value));
};
this.query = (quer) => {
if(typeof quer != "string") return quer;
return this.queryAll(quer)[0];
};
this.findSubslipByID = (id) => {
let goodSubslip = this.getSubSlipList().find((subslip) => {
if(subslip.name == id)
return 1;
return subslip.findSubslipByID(id);
});
if(!goodSubslip)
return false;
if (goodSubslip.name == id)
return goodSubslip;
return goodSubslip.findSubslipByID(id);
};
// ******************************
// Coordinates
// ******************************
this.findSlipCoordinate = () => { // rename to getCoordInUniverse
let coord = engine.getCoordinateInUniverse(this.element);
coord.scale *= this.scale;
coord.y = coord.y + 0.5*coord.scale;
coord.x = coord.centerX;
return coord;
};
// ******************************
// Pause functions
// ******************************
this.updatePauseAncestors = () => {
this.queryAll(".pauseAncestor").forEach((elem) => {elem.classList.remove("pauseAncestor");});
let pause = this.query("[pause]");
while(pause && pause.tagName != "SLIP-SLIP") {
pause.classList.add("pauseAncestor");
pause = pause.parentElement;
} };
this.unpause = (pause) => {
if(pause.hasAttribute("static-at-unpause")) {
if(pause.getAttribute("static-at-unpause") == "")
this.makeStatic(pause);
else
pause.getAttribute("static-at-unpause").split(" ").map((strID) => {
this.makeStatic("#"+strID);
});
}
if(pause.hasAttribute("unstatic-at-unpause")) {
if(pause.getAttribute("unstatic-at-unpause") == "")
this.makeUnStatic(pause);
else
pause.getAttribute("unstatic-at-unpause").split(" ").map((strID) => {
this.makeUnStatic("#"+strID);
});
}
if(pause.hasAttribute("down-at-unpause")) {
if(pause.getAttribute("down-at-unpause") == "")
this.moveDownTo(pause, 1);
else
this.moveDownTo("#"+pause.getAttribute("down-at-unpause"), 1);
}
if(pause.hasAttribute("up-at-unpause")) {
if(pause.getAttribute("up-at-unpause") == "")
this.moveUpTo(pause, 1);
else
this.moveUpTo("#"+pause.getAttribute("up-at-unpause"), 1);
}
if(pause.hasAttribute("center-at-unpause")) {
if(pause.getAttribute("center-at-unpause") == "")
this.moveCenterTo(pause, 1);
else
this.moveCenterTo("#"+pause.getAttribute("center-at-unpause"), 1);
}
if(pause.hasAttribute("exec-at-unpause")) {
if(pause.getAttribute("exec-at-unpause") == "")
this.executeScript(pause);
else
pause.getAttribute("exec-at-unpause").split(" ").map((strID) => {
this.executeScript("#"+strID);
});
}
if(pause.hasAttribute("reveal-at-unpause")) {
if(pause.getAttribute("reveal-at-unpause") == "")
this.reveal(pause);
else
pause.getAttribute("reveal-at-unpause").split(" ").map((strID) => {
this.reveal("#"+strID);
});
}
if(pause.hasAttribute("hide-at-unpause")) {
if(pause.getAttribute("hide-at-unpause") == "")
this.hide(pause);
else
pause.getAttribute("hide-at-unpause").split(" ").map((strID) => {
this.hide("#"+strID);
});
}
if(pause.hasAttribute("figure-set-at-unpause")) {
let [figureID, figureStep] = pause.getAttribute("figure-set-at-unpause").split(" ");
this.query("#"+figureID).figureStep = figureStep;
}
if(pause.hasAttribute("figure-next-at-unpause")) {
pause.getAttribute("figure-next-at-unpause").split(" ").map((figureID) => {
this.query("#"+figureID).figureStep++;
});
}
if(pause.hasAttribute("figure-previous-at-unpause")) {
pause.getAttribute("figure-previous-at-unpause").split(" ").map((figureID) => {
this.query("#"+figureID).figureStep--;
});
}
if(pause.hasAttribute("focus-at-unpause")) {
if(pause.getAttribute("focus-at-unpause") == "")
this.focus(pause);
else
this.focus("#"+pause.getAttribute("focus-at-unpause"));
}
if(pause.hasAttribute("emph-at-unpause")) {
if(pause.getAttribute("emph-at-unpause") == "")
this.makeEmph(pause);
else
pause.getAttribute("emph-at-unpause").split(" ").map((strID) => {
this.makeEmph("#"+strID);
});
}
if(pause.hasAttribute("unemph-at-unpause")) {
if(pause.getAttribute("unemph-at-unpause") == "")
this.makeUnEmph(pause);
else
pause.getAttribute("unemph-at-unpause").split(" ").map((strID) => {
this.makeUnEmph("#"+strID);
});
}
if(pause.hasAttribute("unfocus-at-unpause")){
if(pause.getAttribute("unfocus-at-unpause") == "")
this.unfocus(pause);
else
this.unfocus("#"+pause.getAttribute("unfocus-at-unpause"));
}
};
this.incrPause = () => {
let pause = this.query("[pause], [auto-enter]:not([auto-enter=\"0\"]), [immediate-enter]:not([immediate-enter=\"0\"]), [step]");
// let pause = this.query("[pause]");
if(pause) {
if(pause.hasAttribute("step")) {
if(!pause.getAttribute("step"))
pause.setAttribute("step", 1);
let d = pause.getAttribute("step");
if (d <= 1){
pause.removeAttribute("step");
this.unpause(pause);
} else
pause.setAttribute("step", d-1);
}
if(pause.hasAttribute("auto-enter")) {
pause.setAttribute("auto-enter", 0);
this.unpause(pause);
}
if(pause.hasAttribute("immediate-enter")) {
pause.setAttribute("immediate-enter", 0);
this.unpause(pause);
}
if(pause.hasAttribute("pause")) {
if(!pause.getAttribute("pause"))
pause.setAttribute("pause", 1);
let d = pause.getAttribute("pause");
if (d <= 1){
pause.removeAttribute("pause");
this.unpause(pause);
} else
pause.setAttribute("pause", d-1);
this.updatePauseAncestors();
}
}
};
// ******************************
// Next functions
// ******************************
this.doAttributes = () => {
this.queryAll("*[mk-hidden-at]").forEach((elem) => {
let hiddenAt = elem.getAttribute("mk-hidden-at").split(" ").map((str) => parseInt(str));
if(hiddenAt.includes(actionIndex))
elem.style.opacity = "0";});
this.queryAll("*[mk-visible-at]").forEach((elem) => {
let visibleAt = elem.getAttribute("mk-visible-at").split(" ").map((str) => parseInt(str));
if(visibleAt.includes(actionIndex))
elem.style.opacity = "1";});
this.queryAll("*[mk-emphasize-at]").forEach((elem) => {
let emphAt = elem.getAttribute("mk-emphasize-at").split(" ").map((str) => parseInt(str));
if(emphAt.includes(actionIndex))
elem.classList.add("emphasize");});
this.queryAll("*[mk-unemphasize-at]").forEach((elem) => {
let unemphAt = elem.getAttribute("mk-unemphasize-at").split(" ").map((str) => parseInt(str));
if(unemphAt.includes(actionIndex))
elem.classList.remove("emphasize");});
this.queryAll("*[emphasize-at]").forEach((elem) => {
let emphAt = elem.getAttribute("emphasize-at").split(" ").map((str) => parseInt(str));
if(emphAt.includes(actionIndex))
elem.classList.add("emphasize");
else
elem.classList.remove("emphasize");
});
this.queryAll("*[chg-visib-at]").forEach((elem) => {
let visibAt = elem.getAttribute("chg-visib-at").split(" ").map((str) => parseInt(str));
if(visibAt.includes(actionIndex))
elem.style.opacity = "1";
if(visibAt.includes(-actionIndex))
elem.style.opacity = "0";
});
this.queryAll("*[static-at]").forEach((elem) => {
let staticAt = elem.getAttribute("static-at").split(" ").map((str) => parseInt(str));
if(actionIndex < 0) return;
if(staticAt.includes(-actionIndex)){
this.makeUnStatic(elem);
// elem.style.position = "absolute";
// elem.style.visibility = "hidden";
}
else if(staticAt.includes(actionIndex)) {
this.makeStatic(elem);
// elem.style.position = "static";
// elem.style.visibility = "visible";
}
});
this.queryAll("*[down-at]").forEach((elem) => {
let goDownTo = elem.getAttribute("down-at").split(" ").map((str) => parseInt(str));
if(goDownTo.includes(actionIndex))
this.moveDownTo(elem, 1);
});
this.queryAll("*[up-at]").forEach((elem) => {
let goTo = elem.getAttribute("up-at").split(" ").map((str) => parseInt(str));
if(goTo.includes(actionIndex))
this.moveUpTo(elem, 1);});
this.queryAll("*[center-at]").forEach((elem) => {
let goDownTo = elem.getAttribute("center-at").split(" ").map((str) => parseInt(str));
if(goDownTo.includes(actionIndex))
this.moveCenterTo(elem, 1);});
this.queryAll("*[focus-at]").forEach((elem) => {
let focus = elem.getAttribute("focus-at").split(" ").map((str) => parseInt(str));
if(focus.includes(actionIndex))
this.focus(elem, 1);});
this.queryAll("*[unfocus-at]").forEach((elem) => {
let focus = elem.getAttribute("unfocus-at").split(" ").map((str) => parseInt(str));
if(focus.includes(actionIndex))
this.unfocus(elem, 1);});
this.queryAll("*[exec-at]").forEach((elem) => {
let toExec = elem.getAttribute("exec-at").split(" ").map((str) => parseInt(str));
if(toExec.includes(actionIndex))
this.executeScript(elem);});
this.queryAll("*[figure-next-at]").forEach((elem) => {
let toFigureNext = elem.getAttribute("figure-next-at").split(" ").map((str) => parseInt(str));
if(toFigureNext.includes(actionIndex))
elem.figureStep++;});
this.queryAll("*[figure-previous-at]").forEach((elem) => {
let toFigureNext = elem.getAttribute("figure-previous-at").split(" ").map((str) => parseInt(str));
if(toFigureNext.includes(actionIndex))
elem.figureStep--;});
};
this.incrIndex = () => {
actionIndex = actionIndex+1;
this.doAttributes();
if(actionIndex>0)
this.incrPause();
this.updateToC();
};
this.next = function () {
if(actionIndex >= this.getMaxNext())
return false;
this.incrIndex();
if(typeof actionList[actionIndex] == "function") {
actionList[actionIndex](this);
}
if(actionList[actionIndex] instanceof Slip$1){
return actionList[actionIndex];
}
if(this.pauseSlipList[actionIndex] instanceof Slip$1)
return this.pauseSlipList[actionIndex];
// let nextSlip = this.query("[pause], [auto-enter]");
// if(nextSlip.hasAttribute("auto-enter"))
// return
return true;
};
this.previous = () => {
let savedActionIndex = this.getActionIndex();
this.currentDelay;
this.getEngine().setDoNotMove(true);
let savedClass = this.element.className;
this.doRefresh();
this.element.className = savedClass;
if(savedActionIndex == -1)
return false;
let toReturn;
while(this.getActionIndex()<savedActionIndex-1){
toReturn = this.next();
}
// if(!this.nextStageNeedGoto())
// this.getEngine().setDoNotMove(false);
// while(this.getActionIndex()<savedActionIndex-1)
// toReturn = this.next();
setTimeout(() => {
this.getEngine().setDoNotMove(false);
// this.getEngine().gotoSlip(this, {delay:savedDelay});
},0);
// this.getEngine().gotoSlip(this, {delay:savedDelay});
return toReturn;
// return this.next;
};
// ******************************
// ToC functions
// ******************************
this.setTocElem = (tocElem) => {this.tocElem = tocElem;};
this.updateToC = () => {
if(!this.tocElem)
return;
if(!this.ToCList)
this.ToCList = myQueryAll(this.tocElem, "li", "li");
let i;
for(i=0;i<this.getActionIndex(); i++) {
this.ToCList[i].classList.remove("before", "after", "current");
this.ToCList[i].classList.add("before");
}
if(i<=this.getActionIndex()) {
this.ToCList[i].classList.remove("before", "after", "current");
this.ToCList[i].classList.add("current");
i++;
}
for(i;i<=this.getMaxNext(); i++) {
this.ToCList[i].classList.remove("before", "after", "current");
this.ToCList[i].classList.add("after");
}
};
this.firstVisit = () => {
this.updateToC();
if(options.firstVisit)
options.firstVisit(this);
};
this.init = () => {
this.queryAll("*[chg-visib-at]").forEach((elem) => {
elem.style.opacity = "0";
});
// this.queryAll("*[static-at]").forEach((elem) => {
// elem.style.position = "absolute";
// elem.style.visibility = "hidden";
// });
// this.doAttributes();
this.updatePauseAncestors();
if(options.init)
options.init(this);
};
// ******************************
// Refreshes
// ******************************
this.refresh = () => {
if(actionList[actionIndex] instanceof Slip$1)
actionList[actionIndex].refresh();
else
this.doRefresh();
};
this.refreshAll = () => {
actionList.filter((elem) => elem instanceof Slip$1).forEach((subslip) => { subslip.refreshAll();});
this.pauseSlipList.filter((elem) => elem instanceof Slip$1).forEach((subslip) => { subslip.refreshAll();});
this.doRefresh();
};
this.doRefresh = () => {
this.setActionIndex(-1);
let subSlipList = myQueryAll(this.element, "slip-slip");
let clone = clonedElement.cloneNode(true);
replaceSubslips(clone, subSlipList, this.sketchpadCanvas, this.sketchpadCanvasHighlight);
this.element.replaceWith(clone);
this.element = clone;
this.init();
this.firstVisit();
delete(this.currentX);
delete(this.currentY);
delete(this.currentDelay);
this.getEngine().gotoSlip(this);
setTimeout(() => {
this.reObserve();
},0);
};
this.reObserve = () => {
let slipScaleContainer = this.element.firstChild;
if(slipScaleContainer && slipScaleContainer.classList && slipScaleContainer.classList.contains("slip-scale-container"))
this.resizeObserver.observe(slipScaleContainer);
this.getSubSlipList().forEach((slip) => {
slip.reObserve();
});
};
// ******************************
// Movement, execution and hide/show
// ******************************
this.makeUnStatic = (selector, delay, opacity) => {
let elem = this.query(selector);
// setTimeout(() => {
// elem.style.overflow = "hidden";
// setTimeout(() => {
// elem.style.transition = "height "+ (typeof(delay) == "undefined" ? "1s" : (delay+"s"));
// if(opacity)
// elem.style.transition += ", opacity "+ (typeof(delay) == "undefined" ? "1s" : (delay+"s"));
// elem.style.height = (elem.offsetHeight+"px");
// if(opacity)
// elem.style.opacity = "1";
// setTimeout(() => {
// if(opacity)
// elem.style.opacity = "0";
// elem.style.height = ("0px");}, 10);
// }, 0);
// },0);
elem.style.position = "absolute";
elem.style.visibility = "hidden";
};
this.makeStatic = (selector) => {
let elem = this.query(selector);
elem.style.position = "static";
elem.style.visibility = "visible";
};
this.makeEmph = (selector) => {
let elem = this.query(selector);
elem.classList.add("emphasize");
};
this.makeUnEmph = (selector) => {
let elem = this.query(selector);
elem.classList.remove("emphasize");
};
this.unfocus = (selector) => {
this.getEngine().gotoSlip(this, { delay: 1});
};
this.focus = (selector) => {
let elem = this.query(selector);
this.getEngine().moveToElement(elem, {});
};
this.executeScript = (selector) => {
let elem;
if(typeof selector == "string") elem = this.query(selector);
else elem = selector;
(new Function("slip",elem.innerHTML))(this);
};
this.moveUpTo = (selector, delay, offset) => {
setTimeout(() => {
let elem;
if(typeof selector == "string") elem = this.query(selector);
else elem = selector;
if (typeof offset == "undefined") offset = 0.0125;
let coord = this.findSlipCoordinate();
let d = ((elem.offsetTop)/1080-offset)*coord.scale;
this.moveWindow(coord.x, coord.y+d, coord.scale, this.rotate, delay);
// this.currentX = coord.x;
// this.currentY = coord.y+d;
// this.currentDelay = delay;
// engine.moveWindow(coord.x, coord.y+d, coord.scale, this.rotate, delay);
},0);
};
this.moveDownTo = (selector, delay, offset) => {
setTimeout(() => {
let elem;
if(typeof selector == "string") elem = this.query(selector);
else elem = selector;
if (typeof offset == "undefined") offset = 0.0125;
let coord = this.findSlipCoordinate();
let d = ((elem.offsetTop+elem.offsetHeight)/1080 - 1 + offset)*coord.scale;
this.moveWindow(coord.x, coord.y+d, coord.scale, this.rotate, delay);
// this.currentX = coord.x;
// this.currentY = coord.y+d;
// this.currentDelay = delay;
// engine.moveWindow(coord.x, coord.y+d, coord.scale, this.rotate, delay);
},0);
};
this.moveCenterTo = (selector, delay, offset) => {
setTimeout(() => {
let elem;
if(typeof selector == "string") elem = this.query(selector);
else elem = selector;
if (typeof offset == "undefined") offset = 0;
let coord = this.findSlipCoordinate();
let d = ((elem.offsetTop+elem.offsetHeight/2)/1080-1/2+offset)*coord.scale;
this.moveWindow(coord.x, coord.y+d, coord.scale, this.rotate, delay);
// this.currentX = coord.x;
// this.currentY = coord.y+d;
// this.currentDelay = delay;
// engine.moveWindow(coord.x, coord.y+d, coord.scale, this.rotate, delay);
},0);
};
this.restoreWindow = () => {
this.getEngine;
};
this.moveWindow = (x,y,scale,rotate, delay) => {
this.currentX = x;
this.currentY = y;
this.currentScale = scale;
this.currentDelay = delay;
// setTimeout(() => {
this.getEngine().moveWindow(x, y, scale, rotate, delay);
// }, 0);
};
this.reveal = (selector) => {
let elem;
if(typeof selector == "string") elem = this.query(selector);
else elem = selector;
elem.style.opacity = "1";
};
this.revealAll = (selector) => {
this.queryAll(selector).forEach((elem) => { elem.style.opacity = "1";});
};
this.hide = (selector) => {
this.query(selector).style.opacity = "0";
};
this.hideAll = (selector) => {
this.queryAll(selector).forEach((elem) => { elem.style.opacity = "0";});
};
// ******************************
// Function for writing and highlighting
// ******************************
this.tool = "no-tool";
this.getTool = (tool) => {return this.tool;};
this.setTool = (tool) => {
if(tool == "clear-all") {
this.sketchpad.clear();
this.sketchpadHighlight.clear();
return;
}
this.tool = tool;
this.element.classList.remove("drawing", "highlighting");
if(tool == "highlighting") {
this.element.classList.add("highlighting");
this.sketchpadHighlight.mode = "draw";
} else if(tool == "highlighting-erase") {
this.element.classList.add("highlighting");
this.sketchpadHighlight.mode = "erase";
} else if(tool == "drawing") {
this.element.classList.add("drawing");
this.sketchpad.mode = "draw";
this.sketchpad.weight = 1;
} else if(tool == "drawing-erase") {
this.element.classList.add("drawing");
this.sketchpad.weight = 20;
this.sketchpad.mode = "erase";
} else ;
};
this.color = "blue";
this.colorHighlight = "yellow";
this.getColor = () => {
if(["highlighting","highlighting-erase"].includes(this.getTool())) {
return this.colorHighlight;
}
else if(["drawing","drawing-erase"].includes(this.getTool())) {
return this.color;
}
return "no-color";
};
this.setColor = (color) => {
switch(this.getTool()) {
case("highlighting"):
case("highlighting-erase"):
this.colorHighlight = color;
this.sketchpadHighlight.color = color;
break;
case("drawing"):
case("drawing-erase"):
this.color = color;
this.sketchpad.color = color;
break;
}
};
this.lineWidth = "medium";
this.lineWidthErase = "medium";
this.lineWidthHighlight = "medium";
this.getLineWidth = () => {
if(["highlighting","highlighting-erase"].includes(this.getTool()))
return this.lineWidthHighlight;
else if(["drawing"].includes(this.getTool()))
return this.lineWidth;
else if(["drawing-erase"].includes(this.getTool()))
return this.lineWidthErase;
return "no-line-width";
};
this.setLineWidth = (lineWidth) => {
if(["highlighting","highlighting-erase"].includes(this.getTool())) {
this.lineWidthHighlight = lineWidth;
switch(lineWidth) {
case("small"):
this.sketchpadHighlight.weight = 10;
break;
case("medium"):
this.sketchpadHighlight.weight = 30;
break;
case("large"):
this.sketchpadHighlight.weight = 90;
break;
}
}
else if(["drawing"].includes(this.getTool())) {
this.lineWidth = lineWidth;
switch(lineWidth) {
case("small"):
this.sketchpad.weight = 0.25;
break;
case("medium"):
this.sketchpad.weight = 1;
break;
case("large"):
this.sketchpad.weight = 5;
break;
}
}
else if(["drawing-erase"].includes(this.getTool())) {
this.lineWidthErase = lineWidth;
switch(lineWidth) {
case("small"):
this.sketchpad.weight = 5;
break;
case("medium"):
this.sketchpad.weight = 20;
break;
case("large"):
this.sketchpad.weight = 80;
break;
}
}
};
// ******************************
// Initialisation of the object
// ******************************
// engine
let engine = ng;
this.getEngine = () => engine;
this.setEngine = (ng) => engine = ng;
// element
this.element =
typeof na