diff --git a/js/commands.d.js b/js/commands.d.js new file mode 100644 index 0000000..5ac16a8 --- /dev/null +++ b/js/commands.d.js @@ -0,0 +1,38 @@ +/** + * An object that represents an entry of the command in the history + * + * @typedef CommandEntry + * @property {string} id A unique ID generated for this entry + * @property {string} title The title passed to the command being run + * @property {() => void | Promise} undo A method to undo whatever the command did + * @property {() => void | Promise} redo A method to redo whatever undo did + * @property {{[key: string]: any}} state The state of the current command instance + */ + +/** + * A command, which is run, then returns a CommandEntry object that can be used to manually undo/redo it + * + * @callback Command + * @param {string} title The title passed to the command being run + * @param {*} options A options object for the command + * @returns {Promise} + */ + +/** + * A method for running a command (or redoing it) + * + * @callback CommandDoCallback + * @param {string} title The title passed to the command being run + * @param {*} options A options object for the command + * @param {{[key: string]: any}} state The state of the current command instance + * @returns {void | Promise} + */ + +/** + * A method for undoing a command + * + * @callback CommandUndoCallback + * @param {string} title The title passed to the command when it was run + * @param {{[key: string]: any}} state The state of the current command instance + * @returns {void | Promise} + */ diff --git a/js/commands.js b/js/commands.js index ed81c5b..68a7a84 100644 --- a/js/commands.js +++ b/js/commands.js @@ -7,6 +7,45 @@ const _commands_events = new Observer(); /** CommandNonExistentError */ class CommandNonExistentError extends Error {} +/** + * An object that represents an entry of the command in the history + * + * @typedef CommandEntry + * @property {string} id A unique ID generated for this entry + * @property {string} title The title passed to the command being run + * @property {() => void | Promise} undo A method to undo whatever the command did + * @property {() => void | Promise} redo A method to redo whatever undo did + * @property {{[key: string]: any}} state The state of the current command instance + */ + +/** + * A command, which is run, then returns a CommandEntry object that can be used to manually undo/redo it + * + * @callback Command + * @param {string} title The title passed to the command being run + * @param {*} options A options object for the command + * @returns {Promise} + */ + +/** + * A method for running a command (or redoing it) + * + * @callback CommandDoCallback + * @param {string} title The title passed to the command being run + * @param {*} options A options object for the command + * @param {{[key: string]: any}} state The state of the current command instance + * @returns {void | Promise} + */ + +/** + * A method for undoing a command + * + * @callback CommandUndoCallback + * @param {string} title The title passed to the command when it was run + * @param {{[key: string]: any}} state The state of the current command instance + * @returns {void | Promise} + */ + /** Global Commands Object */ const commands = makeReadOnly( { @@ -16,7 +55,11 @@ const commands = makeReadOnly( }, /** Current History Index (private) */ _current: -1, - /** Command History (private) */ + /** + * Command History (private) + * + * @type {CommandEntry[]} + */ _history: [], /** The types of commands we can run (private) */ _types: {}, @@ -24,21 +67,21 @@ const commands = makeReadOnly( /** * Undoes the last commands in the history * - * @param {number} n Number of actions to undo + * @param {number} [n] Number of actions to undo */ - undo(n = 1) { + async undo(n = 1) { for (var i = 0; i < n && this.current > -1; i++) { - this._history[this._current--].undo(); + await this._history[this._current--].undo(); } }, /** * Redoes the next commands in the history * - * @param {number} n Number of actions to redo + * @param {number} [n] Number of actions to redo */ - redo(n = 1) { + async redo(n = 1) { for (var i = 0; i < n && this.current + 1 < this._history.length; i++) { - this._history[++this._current].redo(); + await this._history[++this._current].redo(); } }, @@ -57,10 +100,10 @@ const commands = makeReadOnly( * The 'state' object will be passed to the 'undo' function as well. * * @param {string} name Command identifier (name) - * @param {(title: string, options: any, state: {[key: string]: any}) => void | Promise} run A method that performs the action for the first time - * @param {(title: string, state: {[key: string]: any}) => } undo A method that reverses what the run method did - * @param {(title: string, options: any, state: {[key: string]: any}) => void | Promise} redo A method that redoes the action after undone (default: run) - * @returns + * @param {CommandDoCallback} run A method that performs the action for the first time + * @param {CommandUndoCallback} undo A method that reverses what the run method did + * @param {CommandDoCallback} redo A method that redoes the action after undone (default: run) + * @returns {Command} */ createCommand(name, run, undo, redo = run) { const command = async function runWrapper(title, options) { @@ -69,6 +112,7 @@ const commands = makeReadOnly( Object.assign(copy, options); const state = {}; + /** @type {CommandEntry} */ const entry = { id: guid(), title, @@ -77,16 +121,21 @@ const commands = makeReadOnly( // Attempt to run command try { + console.debug(`[commands] Running '${title}'[${name}]`); await run(title, copy, state); } catch (e) { - console.warn(`Error while running command '${name}' with options:`); + console.warn( + `[commands] Error while running command '${name}' with options:` + ); console.warn(copy); console.warn(e); return; } const undoWrapper = () => { - console.debug(`Undoing ${name}, currently ${this._current}`); + console.debug( + `[commands] Undoing '${title}'[${name}], currently ${this._current}` + ); undo(title, state); _commands_events.emit({ id: entry.id, @@ -97,7 +146,9 @@ const commands = makeReadOnly( }); }; const redoWrapper = () => { - console.debug(`Redoing ${name}, currently ${this._current}`); + console.debug( + `[commands] Redoing '${title}'[${name}], currently ${this._current}` + ); redo(title, copy, state); _commands_events.emit({ id: entry.id, diff --git a/js/input.d.js b/js/input.d.js new file mode 100644 index 0000000..498bd8f --- /dev/null +++ b/js/input.d.js @@ -0,0 +1,120 @@ +/* Here are event types */ +/** + * A base event type for input handlers + * + * @typedef InputEvent + * @property {HTMLElement} target The target for the event + * @property {MouseEvent | KeyboardEvent} evn An input event + * @property {number} timestamp The time an event was emmited + */ + +/** + * A base event type for input + */ +// TODO: Implement event typing +/** + * An object for mouse event listeners + * + * @typedef OnClickEvent + */ + +/* Here are mouse context types */ +/** + * An object for mouse button event listeners. + * + * Drag events are use timing and radius to determine if they will be triggered + * Paint events are triggered on any mousedown, mousemove and mouseup circunstances + * + * @typedef MouseListenerBtnContext + * @property {Observer} onclick A click handler + * @property {Observer} ondclick A double click handler + * + * @property {Observer} ondragstart A drag start handler + * @property {Observer} ondrag A drag handler + * @property {Observer} ondragend A drag end handler + * + * @property {Observer} onpaintstart A paint start handler + * @property {Observer} onpaint A paint handler + * @property {Observer} onpaintend A paint end handler + */ + +/** + * An object for mouse event listeners + * + * @typedef MouseListenerContext + * @property {Observer} onmousemove A mouse move handler + * @property {Observer} onwheel A mouse wheel handler + * @property {MouseListenerBtnContext} btn Button handlers + */ + +/** + * This callback defines how event coordinateswill be transformed + * for this context. This function should set ctx.coords appropriately. + * + * + * @callback ContextMoveTransformer + * @param {MouseEvent} evn The mousemove event to be transformed + * @param {MouseContext} ctx The context object we are currently in + * @returns {void} + */ + +/** + * A context for handling mouse coordinates and events + * + * @typedef MouseContext + * @property {string} id A unique identifier + * @property {string} name The key name + * @property {ContextMoveTransformer} onmove The coordinate transform callback + * @property {?HTMLElement} target The target + */ + +/** + * An object for storing dragging information + * + * @typedef MouseCoordContextDragInfo + * @property {number} x X coordinate of drag start + * @property {number} y Y coordinate of drag start + * @property {HTMLElement} target Original element of drag + * @property {boolean} drag If we are in a drag + */ + +/** + * An object for storing mouse coordinates in a context + * + * @typedef MouseCoordContext + * @property {{[key: string]: MouseCoordContextDragInfo}} dragging Information about mouse button drags + * @property {{x: number, y: number}} prev Previous mouse position + * @property {{x: number, y: number}} pos Current mouse position + */ + +/* Here are keyboard-related types */ +/** + * Stores key states + * + * @typedef KeyboardKeyState + * @property {boolean} pressed If the key is currently pressed or not + * @property {boolean} held If the key is currently held or not + * @property {?number} _hold_to A timeout for detecting key holding status + */ + +/* Here are the shortcut types */ +/** + * Keyboard shortcut callback + * + * @callback KeyboardShortcutCallback + * @param {KeyboardEvent} evn The keyboard event that triggered this shorcut + * @returns {void} + */ + +/** + * Shortcut information + * + * @typedef KeyboardShortcut + * @property {string} id A unique identifier for this shortcut + * + * @property {boolean} ctrl Shortcut ctrl key state + * @property {boolean} alt Shortcut alt key state + * @property {boolean} shift Shortcut shift key state + * + * @property {KeyboardShortcutCallback} callback If the key is currently held or not + */ diff --git a/js/input.js b/js/input.js index 0492f9c..ed7ef4e 100644 --- a/js/input.js +++ b/js/input.js @@ -30,14 +30,42 @@ function _mouse_observers(name = "generic_mouse_observer_array") { ); } +/** Global Mouse Object */ const mouse = { + /** + * Array of context objects + * @type {MouseContext[]} + */ _contexts: [], + /** + * Timestamps of the button's last down event + * @type {Record<,number | null>} + */ buttons: {}, + /** + * Coordinate storage of mouse positions + * @type {{[ctxKey: string]: MouseCoordContext}} + */ coords: makeWriteOnce({}, "mouse.coords"), + /** + * Listener storage for event observers + * @type {{[ctxKey: string]: MouseListenerContext}} + */ listen: makeWriteOnce({}, "mouse.listen"), // Register Context + + /** + * Registers a new mouse context + * + * @param {string} name The key name of the context + * @param {ContextMoveTransformer} onmove The function to perform coordinate transform + * @param {object} options Extra options + * @param {HTMLElement} [options.target=null] Target filtering + * @param {Record} [options.buttons={0: "left", 1: "middle", 2: "right"}] Custom button mapping + * @returns {MouseContext} + */ registerContext: (name, onmove, options = {}) => { // Options defaultOpt(options, { @@ -46,6 +74,7 @@ const mouse = { }); // Context information + /** @type {MouseContext} */ const context = { id: guid(), name, @@ -79,8 +108,8 @@ const mouse = { Object.keys(options.buttons).forEach((index) => { const button = options.buttons[index]; mouse.coords[name].dragging[button] = null; - mouse.listen[name][button] = _mouse_observers( - `mouse.listen[${name}][${button}]` + mouse.listen[name].btn[button] = _mouse_observers( + `mouse.listen[${name}].btn[${button}]` ); }); @@ -105,7 +134,7 @@ window.onmousedown = (evn) => { // ondclick event mouse._contexts.forEach(({target, name, buttons}) => { if ((!target || target === evn.target) && buttons[evn.button]) - mouse.listen[name][buttons[evn.button]].ondclick.emit({ + mouse.listen[name].btn[buttons[evn.button]].ondclick.emit({ target: evn.target, buttonId: evn.button, x: mouse.coords[name].pos.x, @@ -131,7 +160,7 @@ window.onmousedown = (evn) => { !mouse.coords[name].dragging[key].drag && key ) { - mouse.listen[name][key].ondragstart.emit({ + mouse.listen[name].btn[key].ondragstart.emit({ target: evn.target, buttonId: evn.button, x: mouse.coords[name].pos.x, @@ -156,7 +185,7 @@ window.onmousedown = (evn) => { Object.assign(mouse.coords[name].dragging[key], mouse.coords[name].pos); // onpaintstart event - mouse.listen[name][key].onpaintstart.emit({ + mouse.listen[name].btn[key].onpaintstart.emit({ target: evn.target, buttonId: evn.button, x: mouse.coords[name].pos.x, @@ -192,7 +221,7 @@ window.onmouseup = (evn) => { time - mouse.buttons[evn.button] < inputConfig.clickTiming && dx * dx + dy * dy < inputConfig.clickRadius * inputConfig.clickRadius ) - mouse.listen[name][key].onclick.emit({ + mouse.listen[name].btn[key].onclick.emit({ target: evn.target, buttonId: evn.button, x: mouse.coords[name].pos.x, @@ -202,7 +231,7 @@ window.onmouseup = (evn) => { }); // onpaintend event - mouse.listen[name][key].onpaintend.emit({ + mouse.listen[name].btn[key].onpaintend.emit({ target: evn.target, initialTarget: mouse.coords[name].dragging[key].target, buttonId: evn.button, @@ -216,7 +245,7 @@ window.onmouseup = (evn) => { // ondragend event if (mouse.coords[name].dragging[key].drag) - mouse.listen[name][key].ondragend.emit({ + mouse.listen[name].btn[key].ondragend.emit({ target: evn.target, initialTarget: mouse.coords[name].dragging[key].target, buttonId: evn.button, @@ -270,7 +299,7 @@ window.onmousemove = (evn) => { dx * dx + dy * dy >= inputConfig.clickRadius * inputConfig.clickRadius ) { - mouse.listen[name][key].ondragstart.emit({ + mouse.listen[name].btn[key].ondragstart.emit({ target: evn.target, buttonId: evn.button, ix: mouse.coords[name].dragging[key].x, @@ -290,7 +319,7 @@ window.onmousemove = (evn) => { mouse.coords[name].dragging[key] && mouse.coords[name].dragging[key].drag ) - mouse.listen[name][key].ondrag.emit({ + mouse.listen[name].btn[key].ondrag.emit({ target: evn.target, initialTarget: mouse.coords[name].dragging[key].target, button: index, @@ -306,7 +335,7 @@ window.onmousemove = (evn) => { // onpaint event if (mouse.coords[name].dragging[key]) { - mouse.listen[name][key].onpaint.emit({ + mouse.listen[name].btn[key].onpaint.emit({ target: evn.target, initialTarget: mouse.coords[name].dragging[key].target, button: index, @@ -366,20 +395,51 @@ mouse.registerContext( /** * Keyboard input processing */ -// Base object generator functions - +/** Global Keyboard Object */ const keyboard = { + /** + * Stores the key states for all keys + * + * @type {Record} + */ keys: {}, + /** + * Checks if a key is pressed or not + * + * @param {string} code - The code of the key + * @returns {boolean} + */ isPressed(code) { - return this.keys[key].pressed; + return this.keys[code].pressed; }, + /** + * Checks if a key is held or not + * + * @param {string} code - The code of the key + * @returns {boolean} + */ isHeld(code) { - return !!this; + return this.keys[code].held; }, + /** + * Object storing shortcuts. Uses key as indexing for better performance. + * @type {Record} + */ shortcuts: {}, + /** + * Adds a shortcut listener + * + * @param {object} shortcut Shortcut information + * @param {boolean} [shortcut.ctrl=false] If control must be pressed + * @param {boolean} [shortcut.alt=false] If alt must be pressed + * @param {boolean} [shortcut.shift=false] If shift must be pressed + * @param {string} shortcut.key The key code (evn.code) for the key pressed + * @param {KeyboardShortcutCallback} callback Will be called on shortcut detection + * @returns + */ onShortcut(shortcut, callback) { /** * Adds a shortcut handler (shorcut must be in format: {ctrl?: bool, alt?: bool, shift?: bool, key: string (code)}) @@ -389,23 +449,32 @@ const keyboard = { this.shortcuts[shortcut.key] = []; this.shortcuts[shortcut.key].push({ - ctrl: shortcut.ctrl, - alt: shortcut.alt, - shift: shortcut.shift, + ctrl: !!shortcut.ctrl, + alt: !!shortcut.alt, + shift: !!shortcut.shift, id: guid(), callback, }); + + return callback; }, - deleteShortcut(id, key = null) { + /** + * Deletes a shortcut (disables callback) + * + * @param {string | KeyboardShortcutCallback} shortcut A shortcut ID or its callback + * @param {string} [key=null] If you know the key code, to avoid searching all shortcuts + * @returns + */ + deleteShortcut(shortcut, key = null) { if (key) { this.shortcuts[key] = this.shortcuts[key].filter( - (v) => v.id !== id && v.callback !== id + (v) => v.id !== shortcut && v.callback !== shortcut ); return; } this.shortcuts.keys().forEach((key) => { this.shortcuts[key] = this.shortcuts[key].filter( - (v) => v.id !== id && v.callback !== id + (v) => v.id !== shortcut && v.callback !== shortcut ); }); }, diff --git a/js/settingsbar.js b/js/settingsbar.js index a041c0a..f7848cd 100644 --- a/js/settingsbar.js +++ b/js/settingsbar.js @@ -6,7 +6,7 @@ function makeDraggable(element) { element.style.top = startbb.y + "px"; element.style.left = startbb.x + "px"; - mouse.listen.window.left.onpaintstart.on((evn) => { + mouse.listen.window.btn.left.onpaintstart.on((evn) => { if ( element.contains(evn.target) && evn.target.classList.contains("draggable") @@ -18,14 +18,14 @@ function makeDraggable(element) { } }); - mouse.listen.window.left.onpaint.on((evn) => { + mouse.listen.window.btn.left.onpaint.on((evn) => { if (dragging) { element.style.top = evn.y - offset.y + "px"; element.style.left = evn.x - offset.x + "px"; } }); - mouse.listen.window.left.onpaintend.on((evn) => { + mouse.listen.window.btn.left.onpaintend.on((evn) => { dragging = false; }); } @@ -143,13 +143,13 @@ function createSlider(name, wrapper, options = {}) { } }); - mouse.listen.window.left.onclick.on((evn) => { + mouse.listen.window.btn.left.onclick.on((evn) => { if (evn.target === overEl) { textEl.select(); } }); - mouse.listen.window.left.ondrag.on((evn) => { + mouse.listen.window.btn.left.ondrag.on((evn) => { if (evn.target === overEl) { setValue( Math.max( diff --git a/js/ui/tool/dream.js b/js/ui/tool/dream.js index a2999aa..3eb5a06 100644 --- a/js/ui/tool/dream.js +++ b/js/ui/tool/dream.js @@ -254,8 +254,8 @@ const dreamTool = () => // Start Listeners mouse.listen.canvas.onmousemove.on(state.mousemovecb); - mouse.listen.canvas.left.onclick.on(state.dreamcb); - mouse.listen.canvas.right.onclick.on(state.erasecb); + mouse.listen.canvas.btn.left.onclick.on(state.dreamcb); + mouse.listen.canvas.btn.right.onclick.on(state.erasecb); // Display Mask setMask(state.invertMask ? "hold" : "clear"); @@ -263,8 +263,8 @@ const dreamTool = () => (state, opt) => { // Clear Listeners mouse.listen.canvas.onmousemove.clear(state.mousemovecb); - mouse.listen.canvas.left.onclick.clear(state.dreamcb); - mouse.listen.canvas.right.onclick.clear(state.erasecb); + mouse.listen.canvas.btn.left.onclick.clear(state.dreamcb); + mouse.listen.canvas.btn.right.onclick.clear(state.erasecb); // Hide Mask setMask("none"); @@ -336,8 +336,8 @@ const img2imgTool = () => // Start Listeners mouse.listen.canvas.onmousemove.on(state.mousemovecb); - mouse.listen.canvas.left.onclick.on(state.dreamcb); - mouse.listen.canvas.right.onclick.on(state.erasecb); + mouse.listen.canvas.btn.left.onclick.on(state.dreamcb); + mouse.listen.canvas.btn.right.onclick.on(state.erasecb); // Display Mask setMask(state.invertMask ? "hold" : "clear"); @@ -345,8 +345,8 @@ const img2imgTool = () => (state, opt) => { // Clear Listeners mouse.listen.canvas.onmousemove.clear(state.mousemovecb); - mouse.listen.canvas.left.onclick.clear(state.dreamcb); - mouse.listen.canvas.right.onclick.clear(state.erasecb); + mouse.listen.canvas.btn.left.onclick.clear(state.dreamcb); + mouse.listen.canvas.btn.right.onclick.clear(state.erasecb); // Hide mask setMask("none"); diff --git a/js/ui/tool/maskbrush.js b/js/ui/tool/maskbrush.js index 275364c..bc4af36 100644 --- a/js/ui/tool/maskbrush.js +++ b/js/ui/tool/maskbrush.js @@ -74,10 +74,10 @@ const maskBrushTool = () => // Start Listeners mouse.listen.canvas.onmousemove.on(state.movecb); mouse.listen.canvas.onwheel.on(state.wheelcb); - mouse.listen.canvas.left.onpaintstart.on(state.drawcb); - mouse.listen.canvas.left.onpaint.on(state.drawcb); - mouse.listen.canvas.right.onpaintstart.on(state.erasecb); - mouse.listen.canvas.right.onpaint.on(state.erasecb); + mouse.listen.canvas.btn.left.onpaintstart.on(state.drawcb); + mouse.listen.canvas.btn.left.onpaint.on(state.drawcb); + mouse.listen.canvas.btn.right.onpaintstart.on(state.erasecb); + mouse.listen.canvas.btn.right.onpaint.on(state.erasecb); // Display Mask setMask("neutral"); @@ -86,10 +86,10 @@ const maskBrushTool = () => // Clear Listeners mouse.listen.canvas.onmousemove.clear(state.movecb); mouse.listen.canvas.onwheel.clear(state.wheelcb); - mouse.listen.canvas.left.onpaintstart.clear(state.drawcb); - mouse.listen.canvas.left.onpaint.clear(state.drawcb); - mouse.listen.canvas.right.onpaintstart.clear(state.erasecb); - mouse.listen.canvas.right.onpaint.clear(state.erasecb); + mouse.listen.canvas.btn.left.onpaintstart.clear(state.drawcb); + mouse.listen.canvas.btn.left.onpaint.clear(state.drawcb); + mouse.listen.canvas.btn.right.onpaintstart.clear(state.erasecb); + mouse.listen.canvas.btn.right.onpaint.clear(state.erasecb); // Hide Mask setMask("none"); diff --git a/js/ui/tool/select.js b/js/ui/tool/select.js index a8814af..3b19d40 100644 --- a/js/ui/tool/select.js +++ b/js/ui/tool/select.js @@ -9,12 +9,12 @@ const selectTransformTool = () => // Canvas left mouse handlers mouse.listen.canvas.onmousemove.on(state.movecb); - mouse.listen.canvas.left.onclick.on(state.clickcb); - mouse.listen.canvas.left.ondragstart.on(state.dragstartcb); - mouse.listen.canvas.left.ondragend.on(state.dragendcb); + mouse.listen.canvas.btn.left.onclick.on(state.clickcb); + mouse.listen.canvas.btn.left.ondragstart.on(state.dragstartcb); + mouse.listen.canvas.btn.left.ondragend.on(state.dragendcb); // Canvas right mouse handler - mouse.listen.canvas.right.onclick.on(state.cancelcb); + mouse.listen.canvas.btn.right.onclick.on(state.cancelcb); // Keyboard click handlers keyboard.listen.onkeyclick.on(state.keyclickcb); @@ -30,11 +30,11 @@ const selectTransformTool = () => (state, opt) => { // Clear all those listeners and shortcuts we set up mouse.listen.canvas.onmousemove.clear(state.movecb); - mouse.listen.canvas.left.onclick.clear(state.clickcb); - mouse.listen.canvas.left.ondragstart.clear(state.dragstartcb); - mouse.listen.canvas.left.ondragend.clear(state.dragendcb); + mouse.listen.canvas.btn.left.onclick.clear(state.clickcb); + mouse.listen.canvas.btn.left.ondragstart.clear(state.dragstartcb); + mouse.listen.canvas.btn.left.ondragend.clear(state.dragendcb); - mouse.listen.canvas.right.onclick.clear(state.cancelcb); + mouse.listen.canvas.btn.right.onclick.clear(state.cancelcb); keyboard.listen.onkeyclick.clear(state.keyclickcb); keyboard.listen.onkeydown.clear(state.keydowncb); diff --git a/js/ui/tool/stamp.js b/js/ui/tool/stamp.js index 4bf2fa3..0bb111a 100644 --- a/js/ui/tool/stamp.js +++ b/js/ui/tool/stamp.js @@ -9,8 +9,8 @@ const stampTool = () => // Start Listeners mouse.listen.canvas.onmousemove.on(state.movecb); - mouse.listen.canvas.left.onclick.on(state.drawcb); - mouse.listen.canvas.right.onclick.on(state.cancelcb); + mouse.listen.canvas.btn.left.onclick.on(state.drawcb); + mouse.listen.canvas.btn.right.onclick.on(state.cancelcb); // For calls from other tools to paste image if (opt && opt.image) { @@ -33,8 +33,8 @@ const stampTool = () => (state, opt) => { // Clear Listeners mouse.listen.canvas.onmousemove.clear(state.movecb); - mouse.listen.canvas.left.onclick.clear(state.drawcb); - mouse.listen.canvas.right.onclick.clear(state.cancelcb); + mouse.listen.canvas.btn.left.onclick.clear(state.drawcb); + mouse.listen.canvas.btn.right.onclick.clear(state.cancelcb); // Deselect state.selected = null; diff --git a/js/util.js b/js/util.js index 4d643fd..03f6a6d 100644 --- a/js/util.js +++ b/js/util.js @@ -1,18 +1,33 @@ /** - * Observer class + * Some type definitions before the actual code + */ +/** + * Represents a simple bounding box + * + * @typedef BoundingBox + * @type {Object} + * @property {number} x - Leftmost coordinate of the box + * @property {number} y - Topmost coordinate of the box + * @property {number} w - The bounding box Width + * @property {number} h - The bounding box Height + */ + +/** + * A simple implementation of the Observer programming pattern + * @template [T=any] Message type */ class Observer { /** * List of handlers - * @type {Set<(msg: any) => void | Promise>} + * @type {Set<(msg: T) => void | Promise>} */ _handlers = new Set(); /** * Adds a observer to the events * - * @param {(msg: any) => void | Promise} callback The function to run when receiving a message - * @returns {(msg:any) => void | Promise} The callback we received + * @param {(msg: T) => void | Promise} callback The function to run when receiving a message + * @returns {(msg:T) => void | Promise} The callback we received */ on(callback) { this._handlers.add(callback); @@ -21,16 +36,16 @@ class Observer { /** * Removes a observer * - * @param {(msg: any) => void | Promise} callback The function used to register the callback + * @param {(msg: T) => void | Promise} callback The function used to register the callback * @returns {boolean} Whether the handler existed */ clear(callback) { return this._handlers.delete(callback); } /** - * Send a message to all observers + * Sends a message to all observers * - * @param {any} msg The message to send to the observers + * @param {T} msg The message to send to the observers */ async emit(msg) { return Promise.all( @@ -49,7 +64,7 @@ class Observer { /** * Generates a simple UID in the format xxxx-xxxx-...-xxxx, with x being [0-9a-f] * - * @param {number} size Number of quartets of characters to generate + * @param {number} [size] Number of quartets of characters to generate * @returns {string} The new UID */ const guid = (size = 3) => { @@ -68,8 +83,10 @@ const guid = (size = 3) => { /** * Assigns defaults to an option object passed to the function. * - * @param {{[key: string]: any}} options Original options object - * @param {{[key: string]: any}} defaults Default values to assign + * @template T Object Type + * + * @param {T} options Original options object + * @param {T} defaults Default values to assign */ function defaultOpt(options, defaults) { Object.keys(defaults).forEach((key) => { @@ -108,8 +125,8 @@ class ProxyWriteOnceSetError extends Error {} * * @template T Object Type * @param {T} obj Object to be proxied - * @param {string} name Name for logging purposes - * @param {string[]} exceptions Parameters excepted from this restriction + * @param {string} [name] Name for logging purposes + * @param {string[]} [exceptions] Parameters excepted from this restriction * @returns {T} Proxied object, intercepting write attempts */ function makeWriteOnce(obj, name = "write-once object", exceptions = []) { @@ -154,12 +171,12 @@ function snap(i, scaled = true, gridSize = 64) { /** * Gets a bounding box centered on a given set of coordinates. Supports grid snapping * - * @param {number} cx x-coordinate of the center of the box - * @param {number} cy y-coordinate of the center of the box - * @param {number} w the width of the box - * @param {height} h the height of the box - * @param {number | null} gridSnap The size of the grid to snap to - * @returns {BoundingBox} A bounding box object centered at (cx, cy) + * @param {number} cx - x-coordinate of the center of the box + * @param {number} cy - y-coordinate of the center of the box + * @param {number} w - the width of the box + * @param {height} h - the height of the box + * @param {number | null} gridSnap - The size of the grid to snap to + * @returns {BoundingBox} - A bounding box object centered at (cx, cy) */ function getBoundingBox(cx, cy, w, h, gridSnap = null) { const offset = {x: 0, y: 0}; @@ -180,9 +197,6 @@ function getBoundingBox(cx, cy, w, h, gridSnap = null) { }; } -/** - * Triggers Canvas Download - */ /** * Crops a given canvas to content, returning a new canvas object with the content in it. * @@ -239,10 +253,10 @@ function cropCanvas(sourceCanvas) { /** * Downloads the content of a canvas to the disk, or opens it * - * @param {{cropToContent: boolean, canvas: HTMLCanvasElement, filename: string}} options A options array with the following:\ - * cropToContent: If we wish to crop to content first (default: true) - * canvas: The source canvas (default: imgCanvas) - * filename: The filename to save as (default: '[ISO date] [Hours] [Minutes] [Seconds] openOutpaint image.png').\ + * @param {Object} options - Optional Information + * @param {boolean} [options.cropToContent] - If we wish to crop to content first (default: true) + * @param {HTMLCanvasElement} [options.canvas] - The source canvas (default: imgCanvas) + * @param {string} [options.filename] - The filename to save as (default: '[ISO date] [Hours] [Minutes] [Seconds] openOutpaint image.png').\ * If null, opens image in new tab. */ function downloadCanvas(options = {}) {