added jsdoc to commands
Signed-off-by: Victor Seiji Hariki <victorseijih@gmail.com>
This commit is contained in:
parent
8d0b44e36b
commit
54c381de8e
6 changed files with 204 additions and 149 deletions
|
@ -241,7 +241,7 @@ div.prompt-wrapper > textarea:focus {
|
|||
border-bottom-right-radius: 5px;
|
||||
}
|
||||
|
||||
.button-array > .button.tool {
|
||||
.button.tool {
|
||||
background-color: rgb(0, 0, 50);
|
||||
color: rgb(255, 255, 255);
|
||||
cursor: pointer;
|
||||
|
@ -254,15 +254,15 @@ div.prompt-wrapper > textarea:focus {
|
|||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.button-array > .button.tool:disabled {
|
||||
.button.tool:disabled {
|
||||
background-color: #666 !important;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.button-array > .button.tool:hover {
|
||||
.button.tool:hover {
|
||||
background-color: rgb(30, 30, 80);
|
||||
}
|
||||
.button-array > .button.tool:active,
|
||||
.button.tool:active,
|
||||
.button.tool.active {
|
||||
background-color: rgb(60, 60, 130);
|
||||
}
|
||||
|
|
|
@ -284,7 +284,6 @@
|
|||
</div>
|
||||
|
||||
<!-- Base Libs -->
|
||||
<script src="js/error.js" type="text/javascript"></script>
|
||||
<script src="js/util.js" type="text/javascript"></script>
|
||||
<script src="js/input.js" type="text/javascript"></script>
|
||||
<script src="js/commands.js" type="text/javascript"></script>
|
||||
|
|
|
@ -4,24 +4,45 @@
|
|||
|
||||
const _commands_events = new Observer();
|
||||
|
||||
const commands = {
|
||||
current: -1,
|
||||
history: [],
|
||||
/** Global Commands Object */
|
||||
const commands = makeReadOnly(
|
||||
{
|
||||
/** Current History Index Reader */
|
||||
get current() {
|
||||
return this._current;
|
||||
},
|
||||
/** Current History Index (private) */
|
||||
_current: -1,
|
||||
/** Command History (private) */
|
||||
_history: [],
|
||||
/** The types of commands we can run (private) */
|
||||
_types: {},
|
||||
|
||||
/**
|
||||
* Undoes the last commands in the history
|
||||
*
|
||||
* @param {number} n Number of actions to undo
|
||||
*/
|
||||
undo(n = 1) {
|
||||
for (var i = 0; i < n && this.current > -1; i++) {
|
||||
this.history[this.current--].undo();
|
||||
this._history[this._current--].undo();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Redoes the next commands in the history
|
||||
*
|
||||
* @param {number} n Number of actions to redo
|
||||
*/
|
||||
redo(n = 1) {
|
||||
for (var i = 0; i < n && this.current + 1 < this.history.length; i++) {
|
||||
this.history[++this.current].redo();
|
||||
for (var i = 0; i < n && this.current + 1 < this._history.length; i++) {
|
||||
this._history[++this._current].redo();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* These are basic commands that can be done/undone
|
||||
* Creates a basic command, that can be done and undone
|
||||
*
|
||||
* They must contain a 'run' method that performs the action the first time,
|
||||
* They must contain a 'run' method that performs the action for the first time,
|
||||
* a 'undo' method that undoes that action and a 'redo' method that does the
|
||||
* action again, but without requiring parameters. 'redo' is by default the
|
||||
* same as 'run'.
|
||||
|
@ -31,9 +52,15 @@ const commands = {
|
|||
* can be used to store state for undoing things.
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
createCommand(name, run, undo, redo = run) {
|
||||
const command = function runWrapper(title, options) {
|
||||
const command = async function runWrapper(title, options) {
|
||||
// Create copy of options and state object
|
||||
const copy = {};
|
||||
Object.assign(copy, options);
|
||||
|
@ -47,7 +74,7 @@ const commands = {
|
|||
|
||||
// Attempt to run command
|
||||
try {
|
||||
run(title, copy, state);
|
||||
await run(title, copy, state);
|
||||
} catch (e) {
|
||||
console.warn(`Error while running command '${name}' with options:`);
|
||||
console.warn(copy);
|
||||
|
@ -56,46 +83,46 @@ const commands = {
|
|||
}
|
||||
|
||||
const undoWrapper = () => {
|
||||
console.debug(`Undoing ${name}, currently ${commands.current}`);
|
||||
console.debug(`Undoing ${name}, currently ${this._current}`);
|
||||
undo(title, state);
|
||||
_commands_events.emit({
|
||||
id: entry.id,
|
||||
name,
|
||||
action: "undo",
|
||||
state,
|
||||
current: commands.current,
|
||||
current: this._current,
|
||||
});
|
||||
};
|
||||
const redoWrapper = () => {
|
||||
console.debug(`Redoing ${name}, currently ${commands.current}`);
|
||||
console.debug(`Redoing ${name}, currently ${this._current}`);
|
||||
redo(title, copy, state);
|
||||
_commands_events.emit({
|
||||
id: entry.id,
|
||||
name,
|
||||
action: "redo",
|
||||
state,
|
||||
current: commands.current,
|
||||
current: this._current,
|
||||
});
|
||||
};
|
||||
|
||||
// Add to history
|
||||
if (commands.history.length > commands.current + 1) {
|
||||
commands.history.forEach((entry, index) => {
|
||||
if (index >= commands.current + 1)
|
||||
if (commands._history.length > commands._current + 1) {
|
||||
commands._history.forEach((entry, index) => {
|
||||
if (index >= commands._current + 1)
|
||||
_commands_events.emit({
|
||||
id: entry.id,
|
||||
name,
|
||||
action: "deleted",
|
||||
state,
|
||||
current: commands.current,
|
||||
current: this._current,
|
||||
});
|
||||
});
|
||||
|
||||
commands.history.splice(commands.current + 1);
|
||||
commands._history.splice(commands._current + 1);
|
||||
}
|
||||
|
||||
commands.history.push(entry);
|
||||
commands.current++;
|
||||
commands._history.push(entry);
|
||||
commands._current++;
|
||||
|
||||
entry.undo = undoWrapper;
|
||||
entry.redo = redoWrapper;
|
||||
|
@ -105,21 +132,30 @@ const commands = {
|
|||
name,
|
||||
action: "run",
|
||||
state,
|
||||
current: commands.current,
|
||||
current: commands._current,
|
||||
});
|
||||
|
||||
return entry;
|
||||
};
|
||||
|
||||
this.types[name] = command;
|
||||
this._types[name] = command;
|
||||
|
||||
return command;
|
||||
},
|
||||
runCommand(name, title, options) {
|
||||
this.types[name](title, options);
|
||||
/**
|
||||
* Runs a command
|
||||
*
|
||||
* @param {string} name The name of the command to run
|
||||
* @param {string} title The display name of the command on the history panel view
|
||||
* @param {any} options The options to be sent to the command to be run
|
||||
*/
|
||||
runCommand(name, title, options = null) {
|
||||
this._types[name](title, options);
|
||||
},
|
||||
types: {},
|
||||
};
|
||||
},
|
||||
"commands",
|
||||
["_current"]
|
||||
);
|
||||
|
||||
/**
|
||||
* Draw Image Command, used to draw a Image to a context
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
/**
|
||||
* This is a file to configure custom errors
|
||||
*/
|
||||
|
||||
/* Proxy Restriction Errors */
|
||||
class ProxyReadOnlySetError extends Error {}
|
||||
class ProxyWriteOnceSetError extends Error {}
|
|
@ -28,7 +28,7 @@
|
|||
if (message.action === "run") {
|
||||
Array.from(historyView.children).forEach((child) => {
|
||||
if (
|
||||
!commands.history.find((entry) => `hist-${entry.id}` === child.id)
|
||||
!commands._history.find((entry) => `hist-${entry.id}` === child.id)
|
||||
) {
|
||||
console.log("Removing " + child.id);
|
||||
historyView.removeChild(child);
|
||||
|
@ -36,7 +36,7 @@
|
|||
});
|
||||
}
|
||||
|
||||
commands.history.forEach((entry, index) => {
|
||||
commands._history.forEach((entry, index) => {
|
||||
if (!document.getElementById(`hist-${entry.id}`)) {
|
||||
console.log("Inserting " + entry.id);
|
||||
historyView.appendChild(
|
||||
|
|
71
js/util.js
71
js/util.js
|
@ -1,30 +1,50 @@
|
|||
/**
|
||||
* Implementation of a simple Oberver Pattern for custom event handling
|
||||
* Observer class
|
||||
*/
|
||||
function Observer() {
|
||||
this.handlers = new Set();
|
||||
}
|
||||
class Observer {
|
||||
/**
|
||||
* List of handlers
|
||||
* @type {Set<(msg: any) => void | Promise<void>>}
|
||||
*/
|
||||
_handlers = new Set();
|
||||
|
||||
Observer.prototype = {
|
||||
// Adds handler for this message
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
on(callback) {
|
||||
this.handlers.add(callback);
|
||||
this._handlers.add(callback);
|
||||
return callback;
|
||||
},
|
||||
}
|
||||
/**
|
||||
* Removes a observer
|
||||
*
|
||||
* @param {(msg: any) => void | Promise<void>} callback The function used to register the callback
|
||||
* @returns {boolean} Whether the handler existed
|
||||
*/
|
||||
clear(callback) {
|
||||
return this.handlers.delete(callback);
|
||||
},
|
||||
emit(msg) {
|
||||
this.handlers.forEach(async (handler) => {
|
||||
return this._handlers.delete(callback);
|
||||
}
|
||||
/**
|
||||
* Send a message to all observers
|
||||
*
|
||||
* @param {any} msg The message to send to the observers
|
||||
*/
|
||||
async emit(msg) {
|
||||
Promise.all(
|
||||
Array.from(this._handlers).map((handler) => async () => {
|
||||
try {
|
||||
await handler(msg);
|
||||
} catch (e) {
|
||||
console.warn("Observer failed to run handler");
|
||||
console.warn(e);
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a simple UID in the format xxxx-xxxx-...-xxxx, with x being [0-9a-f]
|
||||
|
@ -62,16 +82,21 @@ class ProxyReadOnlySetError extends Error {}
|
|||
/**
|
||||
* Makes a given object read-only; throws a ProxyReadOnlySetError exception if modification is attempted
|
||||
*
|
||||
* @param {any} obj Object to be proxied
|
||||
* @template T Object Type
|
||||
*
|
||||
* @param {T} obj Object to be proxied
|
||||
* @param {string} name Name for logging purposes
|
||||
* @returns {any} Proxied object, intercepting write attempts
|
||||
* @param {string[]} exceptions Parameters excepted from this restriction
|
||||
* @returns {T} Proxied object, intercepting write attempts
|
||||
*/
|
||||
function makeReadOnly(obj, name = "read-only object") {
|
||||
function makeReadOnly(obj, name = "read-only object", exceptions = []) {
|
||||
return new Proxy(obj, {
|
||||
set: (obj, prop, value) => {
|
||||
if (!exceptions.some((v) => v === prop))
|
||||
throw new ProxyReadOnlySetError(
|
||||
`Tried setting the '${prop}' property on '${name}'`
|
||||
);
|
||||
obj[prop] = value;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -81,14 +106,16 @@ class ProxyWriteOnceSetError extends Error {}
|
|||
/**
|
||||
* Makes a given object write-once; Attempts to overwrite an existing prop in the object will throw a ProxyWriteOnceSetError exception
|
||||
*
|
||||
* @param {any} obj Object to be proxied
|
||||
* @template T Object Type
|
||||
* @param {T} obj Object to be proxied
|
||||
* @param {string} name Name for logging purposes
|
||||
* @returns {any} Proxied object, intercepting write attempts
|
||||
* @param {string[]} exceptions Parameters excepted from this restriction
|
||||
* @returns {T} Proxied object, intercepting write attempts
|
||||
*/
|
||||
function makeWriteOnce(obj, name = "write-once object") {
|
||||
function makeWriteOnce(obj, name = "write-once object", exceptions = []) {
|
||||
return new Proxy(obj, {
|
||||
set: (obj, prop, value) => {
|
||||
if (obj[prop] !== undefined)
|
||||
if (obj[prop] !== undefined && !exceptions.some((v) => v === prop))
|
||||
throw new ProxyWriteOnceSetError(
|
||||
`Tried setting the '${prop}' property on '${name}' after it was already set`
|
||||
);
|
||||
|
|
Loading…
Reference in a new issue