input.js types and move type definitions
Moved type definitions to .d.js files to avoid clutter and network overhead. Added typing to input.js, but still no event typing Signed-off-by: Victor Seiji Hariki <victorseijih@gmail.com>
This commit is contained in:
parent
fdb93bad26
commit
83470ebba3
10 changed files with 385 additions and 93 deletions
38
js/commands.d.js
Normal file
38
js/commands.d.js
Normal file
|
@ -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<void>} undo A method to undo whatever the command did
|
||||
* @property {() => void | Promise<void>} 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<CommandEntry>}
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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<void>}
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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<void>}
|
||||
*/
|
|
@ -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<void>} undo A method to undo whatever the command did
|
||||
* @property {() => void | Promise<void>} 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<CommandEntry>}
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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<void>}
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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<void>}
|
||||
*/
|
||||
|
||||
/** 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<void>} 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<void>} 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,
|
||||
|
|
120
js/input.d.js
Normal file
120
js/input.d.js
Normal file
|
@ -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
|
||||
*/
|
111
js/input.js
111
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<number, string>} [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<string, KeyboardKeyState>}
|
||||
*/
|
||||
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<string, KeyboardShortcut[]>}
|
||||
*/
|
||||
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
|
||||
);
|
||||
});
|
||||
},
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
64
js/util.js
64
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<void>>}
|
||||
* @type {Set<(msg: T) => void | Promise<void>>}
|
||||
*/
|
||||
_handlers = new Set();
|
||||
|
||||
/**
|
||||
* Adds a observer to the events
|
||||
*
|
||||
* @param {(msg: any) => void | Promise<void>} callback The function to run when receiving a message
|
||||
* @returns {(msg:any) => void | Promise<void>} The callback we received
|
||||
* @param {(msg: T) => void | Promise<void>} callback The function to run when receiving a message
|
||||
* @returns {(msg:T) => void | Promise<void>} The callback we received
|
||||
*/
|
||||
on(callback) {
|
||||
this._handlers.add(callback);
|
||||
|
@ -21,16 +36,16 @@ class Observer {
|
|||
/**
|
||||
* Removes a observer
|
||||
*
|
||||
* @param {(msg: any) => void | Promise<void>} callback The function used to register the callback
|
||||
* @param {(msg: T) => void | Promise<void>} 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 = {}) {
|
||||
|
|
Loading…
Reference in a new issue