layer history and finally layer delete/merge
Signed-off-by: Victor Seiji Hariki <victorseijih@gmail.com>
This commit is contained in:
parent
0cc6f7660a
commit
62ddc38f01
6 changed files with 361 additions and 34 deletions
|
@ -13,6 +13,11 @@
|
||||||
mask-image: url("/res/icons/file-plus.svg");
|
mask-image: url("/res/icons/file-plus.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ui.icon > .icon-file-x {
|
||||||
|
-webkit-mask-image: url("/res/icons/file-x.svg");
|
||||||
|
mask-image: url("/res/icons/file-x.svg");
|
||||||
|
}
|
||||||
|
|
||||||
.ui.icon > .icon-chevron-down {
|
.ui.icon > .icon-chevron-down {
|
||||||
-webkit-mask-image: url("/res/icons/chevron-down.svg");
|
-webkit-mask-image: url("/res/icons/chevron-down.svg");
|
||||||
mask-image: url("/res/icons/chevron-down.svg");
|
mask-image: url("/res/icons/chevron-down.svg");
|
||||||
|
@ -22,3 +27,13 @@
|
||||||
-webkit-mask-image: url("/res/icons/chevron-up.svg");
|
-webkit-mask-image: url("/res/icons/chevron-up.svg");
|
||||||
mask-image: url("/res/icons/chevron-up.svg");
|
mask-image: url("/res/icons/chevron-up.svg");
|
||||||
}
|
}
|
||||||
|
.ui.icon > .icon-chevron-first {
|
||||||
|
-webkit-mask-image: url("/res/icons/chevron-first.svg");
|
||||||
|
mask-image: url("/res/icons/chevron-first.svg");
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.icon > .icon-chevron-flat-down {
|
||||||
|
-webkit-mask-image: url("/res/icons/chevron-first.svg");
|
||||||
|
mask-image: url("/res/icons/chevron-first.svg");
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
}
|
||||||
|
|
22
index.html
22
index.html
|
@ -216,7 +216,7 @@
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
title="Add Layer"
|
title="Add Layer"
|
||||||
onclick="uil.addLayer(null, 'New Layer')"
|
onclick="commands.runCommand('addLayer', 'Added Layer')"
|
||||||
class="ui icon button">
|
class="ui icon button">
|
||||||
<div class="icon-file-plus"></div>
|
<div class="icon-file-plus"></div>
|
||||||
</button>
|
</button>
|
||||||
|
@ -224,7 +224,7 @@
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
title="Move Layer Up"
|
title="Move Layer Up"
|
||||||
onclick="uil.moveLayerUp()"
|
onclick="commands.runCommand('moveLayer', 'Moved Layer Up',{delta: 1})"
|
||||||
class="ui icon button">
|
class="ui icon button">
|
||||||
<div class="icon-chevron-up"></div>
|
<div class="icon-chevron-up"></div>
|
||||||
</button>
|
</button>
|
||||||
|
@ -232,10 +232,26 @@
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
title="Move Layer Down"
|
title="Move Layer Down"
|
||||||
onclick="uil.moveLayerDown()"
|
onclick="commands.runCommand('moveLayer', 'Moved Layer Down', {delta: -1})"
|
||||||
class="ui icon button">
|
class="ui icon button">
|
||||||
<div class="icon-chevron-down"></div>
|
<div class="icon-chevron-down"></div>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
title="Merge Layer Down"
|
||||||
|
onclick="commands.runCommand('mergeLayer', 'Merged Layer Down')"
|
||||||
|
class="ui icon button">
|
||||||
|
<div class="icon-chevron-flat-down"></div>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
title="Move Layer Down"
|
||||||
|
onclick="commands.runCommand('deleteLayer', 'Deleted Layer')"
|
||||||
|
class="ui icon button">
|
||||||
|
<div class="icon-file-x"></div>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -4,9 +4,6 @@
|
||||||
|
|
||||||
const _commands_events = new Observer();
|
const _commands_events = new Observer();
|
||||||
|
|
||||||
/** CommandNonExistentError */
|
|
||||||
class CommandNonExistentError extends Error {}
|
|
||||||
|
|
||||||
/** Global Commands Object */
|
/** Global Commands Object */
|
||||||
const commands = makeReadOnly(
|
const commands = makeReadOnly(
|
||||||
{
|
{
|
||||||
|
@ -32,7 +29,14 @@ const commands = makeReadOnly(
|
||||||
*/
|
*/
|
||||||
async undo(n = 1) {
|
async undo(n = 1) {
|
||||||
for (var i = 0; i < n && this.current > -1; i++) {
|
for (var i = 0; i < n && this.current > -1; i++) {
|
||||||
|
try {
|
||||||
await this._history[this._current--].undo();
|
await this._history[this._current--].undo();
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("[commands] Failed to undo command");
|
||||||
|
console.warn(e);
|
||||||
|
this._current++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
|
@ -42,7 +46,14 @@ const commands = makeReadOnly(
|
||||||
*/
|
*/
|
||||||
async redo(n = 1) {
|
async redo(n = 1) {
|
||||||
for (var i = 0; i < n && this.current + 1 < this._history.length; i++) {
|
for (var i = 0; i < n && this.current + 1 < this._history.length; i++) {
|
||||||
|
try {
|
||||||
await this._history[++this._current].redo();
|
await this._history[++this._current].redo();
|
||||||
|
} catch {
|
||||||
|
console.warn("[commands] Failed to redo command");
|
||||||
|
console.warn(e);
|
||||||
|
this._current--;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -67,7 +78,7 @@ const commands = makeReadOnly(
|
||||||
* @returns {Command}
|
* @returns {Command}
|
||||||
*/
|
*/
|
||||||
createCommand(name, run, undo, redo = run) {
|
createCommand(name, run, undo, redo = run) {
|
||||||
const command = async function runWrapper(title, options) {
|
const command = async function runWrapper(title, options, extra) {
|
||||||
// Create copy of options and state object
|
// Create copy of options and state object
|
||||||
const copy = {};
|
const copy = {};
|
||||||
Object.assign(copy, options);
|
Object.assign(copy, options);
|
||||||
|
@ -93,11 +104,11 @@ const commands = makeReadOnly(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const undoWrapper = () => {
|
const undoWrapper = async () => {
|
||||||
console.debug(
|
console.debug(
|
||||||
`[commands] Undoing '${title}'[${name}], currently ${this._current}`
|
`[commands] Undoing '${title}'[${name}], currently ${this._current}`
|
||||||
);
|
);
|
||||||
undo(title, state);
|
await undo(title, state);
|
||||||
_commands_events.emit({
|
_commands_events.emit({
|
||||||
id: entry.id,
|
id: entry.id,
|
||||||
name,
|
name,
|
||||||
|
@ -106,11 +117,11 @@ const commands = makeReadOnly(
|
||||||
current: this._current,
|
current: this._current,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const redoWrapper = () => {
|
const redoWrapper = async () => {
|
||||||
console.debug(
|
console.debug(
|
||||||
`[commands] Redoing '${title}'[${name}], currently ${this._current}`
|
`[commands] Redoing '${title}'[${name}], currently ${this._current}`
|
||||||
);
|
);
|
||||||
redo(title, copy, state);
|
await redo(title, copy, state);
|
||||||
_commands_events.emit({
|
_commands_events.emit({
|
||||||
id: entry.id,
|
id: entry.id,
|
||||||
name,
|
name,
|
||||||
|
@ -120,6 +131,11 @@ const commands = makeReadOnly(
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
entry.undo = undoWrapper;
|
||||||
|
entry.redo = redoWrapper;
|
||||||
|
|
||||||
|
if (!extra.recordHistory) return entry;
|
||||||
|
|
||||||
// Add to history
|
// Add to history
|
||||||
if (commands._history.length > commands._current + 1) {
|
if (commands._history.length > commands._current + 1) {
|
||||||
commands._history.forEach((entry, index) => {
|
commands._history.forEach((entry, index) => {
|
||||||
|
@ -139,9 +155,6 @@ const commands = makeReadOnly(
|
||||||
commands._history.push(entry);
|
commands._history.push(entry);
|
||||||
commands._current++;
|
commands._current++;
|
||||||
|
|
||||||
entry.undo = undoWrapper;
|
|
||||||
entry.redo = redoWrapper;
|
|
||||||
|
|
||||||
_commands_events.emit({
|
_commands_events.emit({
|
||||||
id: entry.id,
|
id: entry.id,
|
||||||
name,
|
name,
|
||||||
|
@ -163,13 +176,16 @@ const commands = makeReadOnly(
|
||||||
* @param {string} name The name of the command to run
|
* @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 {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
|
* @param {any} options The options to be sent to the command to be run
|
||||||
|
* @return {Promise<{undo: () => void, redo: () => void}>} The command's return value
|
||||||
*/
|
*/
|
||||||
runCommand(name, title, options = null) {
|
async runCommand(name, title, options = null, extra = {}) {
|
||||||
|
defaultOpt(extra, {
|
||||||
|
recordHistory: true,
|
||||||
|
});
|
||||||
if (!this._types[name])
|
if (!this._types[name])
|
||||||
throw new CommandNonExistentError(
|
throw new ReferenceError(`[commands] Command '${name}' does not exist`);
|
||||||
`[commands] Command '${name}' does not exist`
|
|
||||||
);
|
return this._types[name](title, options, extra);
|
||||||
this._types[name](title, options);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"commands",
|
"commands",
|
||||||
|
|
|
@ -99,6 +99,32 @@ const uil = {
|
||||||
const actionArray = document.createElement("div");
|
const actionArray = document.createElement("div");
|
||||||
actionArray.classList.add("actions");
|
actionArray.classList.add("actions");
|
||||||
|
|
||||||
|
if (uiLayer.deletable) {
|
||||||
|
const deleteButton = document.createElement("button");
|
||||||
|
deleteButton.addEventListener(
|
||||||
|
"click",
|
||||||
|
(evn) => {
|
||||||
|
commands.runCommand("deleteLayer", "Deleted Layer", {
|
||||||
|
layer: uiLayer,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{passive: false}
|
||||||
|
);
|
||||||
|
|
||||||
|
deleteButton.addEventListener(
|
||||||
|
"dblclick",
|
||||||
|
(evn) => {
|
||||||
|
evn.stopPropagation();
|
||||||
|
},
|
||||||
|
{passive: false}
|
||||||
|
);
|
||||||
|
deleteButton.title = "Delete Layer";
|
||||||
|
deleteButton.appendChild(document.createElement("div"));
|
||||||
|
deleteButton.classList.add("delete-btn");
|
||||||
|
|
||||||
|
actionArray.appendChild(deleteButton);
|
||||||
|
}
|
||||||
|
|
||||||
const hideButton = document.createElement("button");
|
const hideButton = document.createElement("button");
|
||||||
hideButton.addEventListener(
|
hideButton.addEventListener(
|
||||||
"click",
|
"click",
|
||||||
|
@ -111,6 +137,13 @@ const uil = {
|
||||||
},
|
},
|
||||||
{passive: false}
|
{passive: false}
|
||||||
);
|
);
|
||||||
|
hideButton.addEventListener(
|
||||||
|
"dblclick",
|
||||||
|
(evn) => {
|
||||||
|
evn.stopPropagation();
|
||||||
|
},
|
||||||
|
{passive: false}
|
||||||
|
);
|
||||||
hideButton.title = "Hide/Unhide Layer";
|
hideButton.title = "Hide/Unhide Layer";
|
||||||
hideButton.appendChild(document.createElement("div"));
|
hideButton.appendChild(document.createElement("div"));
|
||||||
hideButton.classList.add("hide-btn");
|
hideButton.classList.add("hide-btn");
|
||||||
|
@ -121,13 +154,23 @@ const uil = {
|
||||||
if (layersEl.children[index])
|
if (layersEl.children[index])
|
||||||
layersEl.children[index].before(uiLayer.entry);
|
layersEl.children[index].before(uiLayer.entry);
|
||||||
else layersEl.appendChild(uiLayer.entry);
|
else layersEl.appendChild(uiLayer.entry);
|
||||||
}
|
} else if (!layersEl.querySelector(`#ui-layer-${uiLayer.id}`)) {
|
||||||
|
// If layer exists but is not on the DOM, add it back
|
||||||
|
if (index === 0) layersEl.children[0].before(uiLayer.entry);
|
||||||
|
else layersEl.children[index - 1].after(uiLayer.entry);
|
||||||
|
} else {
|
||||||
// If the layer already exists, just move it here
|
// If the layer already exists, just move it here
|
||||||
else {
|
|
||||||
layersEl.children[index].before(uiLayer.entry);
|
layersEl.children[index].before(uiLayer.entry);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Deletes layer if not in array
|
||||||
|
for (var i = 0; i < layersEl.children.length; i++) {
|
||||||
|
if (!copy.find((l) => layersEl.children[i].id === `ui-layer-${l.id}`)) {
|
||||||
|
layersEl.children[i].remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Synchronizes with the layer lib
|
// Synchronizes with the layer lib
|
||||||
this.layers.forEach((uiLayer, index) => {
|
this.layers.forEach((uiLayer, index) => {
|
||||||
if (index === 0) uiLayer.layer.moveAfter(bgLayer);
|
if (index === 0) uiLayer.layer.moveAfter(bgLayer);
|
||||||
|
@ -138,11 +181,13 @@ const uil = {
|
||||||
/**
|
/**
|
||||||
* Adds a user-manageable layer for image editing.
|
* Adds a user-manageable layer for image editing.
|
||||||
*
|
*
|
||||||
|
* Should not be called directly. Use the command instead.
|
||||||
|
*
|
||||||
* @param {string} group The group the layer belongs to. [does nothing for now]
|
* @param {string} group The group the layer belongs to. [does nothing for now]
|
||||||
* @param {string} name The name of the new layer.
|
* @param {string} name The name of the new layer.
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
addLayer(group, name) {
|
_addLayer(group, name) {
|
||||||
const layer = imageCollection.registerLayer(null, {
|
const layer = imageCollection.registerLayer(null, {
|
||||||
name,
|
name,
|
||||||
after:
|
after:
|
||||||
|
@ -180,12 +225,14 @@ const uil = {
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Moves a layer to a specified position
|
* Moves a layer to a specified position.
|
||||||
|
*
|
||||||
|
* Should not be called directly. Use the command instead.
|
||||||
*
|
*
|
||||||
* @param {UserLayer} layer Layer to move
|
* @param {UserLayer} layer Layer to move
|
||||||
* @param {number} position Position to move the layer to
|
* @param {number} position Position to move the layer to
|
||||||
*/
|
*/
|
||||||
moveLayerTo(layer, position) {
|
_moveLayerTo(layer, position) {
|
||||||
if (position < 0 || position >= this.layers.length)
|
if (position < 0 || position >= this.layers.length)
|
||||||
throw new RangeError("Position out of bounds");
|
throw new RangeError("Position out of bounds");
|
||||||
|
|
||||||
|
@ -203,27 +250,31 @@ const uil = {
|
||||||
throw new ReferenceError("Layer could not be found");
|
throw new ReferenceError("Layer could not be found");
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Moves a layer up a single position
|
* Moves a layer up a single position.
|
||||||
|
*
|
||||||
|
* Should not be called directly. Use the command instead.
|
||||||
*
|
*
|
||||||
* @param {UserLayer} [layer=uil.active] Layer to move
|
* @param {UserLayer} [layer=uil.active] Layer to move
|
||||||
*/
|
*/
|
||||||
moveLayerUp(layer = uil.active) {
|
_moveLayerUp(layer = uil.active) {
|
||||||
const index = this.layers.indexOf(layer);
|
const index = this.layers.indexOf(layer);
|
||||||
if (index === -1) throw new ReferenceError("Layer could not be found");
|
if (index === -1) throw new ReferenceError("Layer could not be found");
|
||||||
try {
|
try {
|
||||||
this.moveLayerTo(layer, index + 1);
|
this._moveLayerTo(layer, index + 1);
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Moves a layer down a single position
|
* Moves a layer down a single position.
|
||||||
|
*
|
||||||
|
* Should not be called directly. Use the command instead.
|
||||||
*
|
*
|
||||||
* @param {UserLayer} [layer=uil.active] Layer to move
|
* @param {UserLayer} [layer=uil.active] Layer to move
|
||||||
*/
|
*/
|
||||||
moveLayerDown(layer = uil.active) {
|
_moveLayerDown(layer = uil.active) {
|
||||||
const index = this.layers.indexOf(layer);
|
const index = this.layers.indexOf(layer);
|
||||||
if (index === -1) throw new ReferenceError("Layer could not be found");
|
if (index === -1) throw new ReferenceError("Layer could not be found");
|
||||||
try {
|
try {
|
||||||
this.moveLayerTo(layer, index - 1);
|
this._moveLayerTo(layer, index - 1);
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
|
@ -266,4 +317,221 @@ const uil = {
|
||||||
return canvas;
|
return canvas;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
uil.addLayer(null, "Default Image Layer");
|
|
||||||
|
/**
|
||||||
|
* Command for creating a new layer
|
||||||
|
*/
|
||||||
|
commands.createCommand(
|
||||||
|
"addLayer",
|
||||||
|
(title, opt, state) => {
|
||||||
|
const options = Object.assign({}, opt) || {};
|
||||||
|
defaultOpt(options, {
|
||||||
|
group: null,
|
||||||
|
name: "New Layer",
|
||||||
|
deletable: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!state.layer) {
|
||||||
|
const {group, name} = options;
|
||||||
|
|
||||||
|
const layer = imageCollection.registerLayer(null, {
|
||||||
|
name,
|
||||||
|
after:
|
||||||
|
(uil.layers.length > 0 && uil.layers[uil.layers.length - 1].layer) ||
|
||||||
|
bgLayer,
|
||||||
|
});
|
||||||
|
|
||||||
|
state.layer = {
|
||||||
|
id: layer.id,
|
||||||
|
group,
|
||||||
|
name,
|
||||||
|
deletable: options.deletable,
|
||||||
|
_hidden: false,
|
||||||
|
set hidden(v) {
|
||||||
|
if (v) {
|
||||||
|
uil._hidden = true;
|
||||||
|
uil.layer.hide(v);
|
||||||
|
} else {
|
||||||
|
uil._hidden = false;
|
||||||
|
uil.layer.unhide(v);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
get hidden() {
|
||||||
|
return uil._hidden;
|
||||||
|
},
|
||||||
|
entry: null,
|
||||||
|
layer,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
uil.layers.push(state.layer);
|
||||||
|
|
||||||
|
uil._syncLayers();
|
||||||
|
|
||||||
|
uil.active = state.layer;
|
||||||
|
},
|
||||||
|
(title, state) => {
|
||||||
|
const index = uil.layers.findIndex((v) => v === state.layer);
|
||||||
|
|
||||||
|
if (index === -1) throw new ReferenceError("Layer could not be found");
|
||||||
|
|
||||||
|
if (uil.active === state.layer)
|
||||||
|
uil.active = uil.layers[index + 1] || uil.layers[index - 1];
|
||||||
|
uil.layers.splice(index, 1);
|
||||||
|
uil._syncLayers();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Command for moving a layer to a position
|
||||||
|
*/
|
||||||
|
commands.createCommand(
|
||||||
|
"moveLayer",
|
||||||
|
(title, opt, state) => {
|
||||||
|
const options = opt || {};
|
||||||
|
defaultOpt(options, {
|
||||||
|
layer: null,
|
||||||
|
to: null,
|
||||||
|
delta: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!state.layer) {
|
||||||
|
if (options.to === null && options.delta === null)
|
||||||
|
throw new Error(
|
||||||
|
"[layers.moveLayer] Options must contain one of {to?, delta?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
const layer = options.layer || uil.active;
|
||||||
|
|
||||||
|
const index = uil.layers.indexOf(layer);
|
||||||
|
if (index === -1) throw new ReferenceError("Layer could not be found");
|
||||||
|
|
||||||
|
let position = options.to;
|
||||||
|
|
||||||
|
if (position === null) position = index + options.delta;
|
||||||
|
|
||||||
|
state.layer = layer;
|
||||||
|
state.oldposition = index;
|
||||||
|
state.position = position;
|
||||||
|
}
|
||||||
|
|
||||||
|
uil._moveLayerTo(state.layer, state.position);
|
||||||
|
},
|
||||||
|
(title, state) => {
|
||||||
|
uil._moveLayerTo(state.layer, state.oldposition);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Command for deleting a layer
|
||||||
|
*/
|
||||||
|
commands.createCommand(
|
||||||
|
"deleteLayer",
|
||||||
|
(title, opt, state) => {
|
||||||
|
const options = opt || {};
|
||||||
|
defaultOpt(options, {
|
||||||
|
layer: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!state.layer) {
|
||||||
|
const layer = options.layer || uil.active;
|
||||||
|
|
||||||
|
if (!layer.deletable)
|
||||||
|
throw new TypeError("[layer.deleteLayer] Layer is not deletable");
|
||||||
|
|
||||||
|
const index = uil.layers.indexOf(layer);
|
||||||
|
if (index === -1)
|
||||||
|
throw new ReferenceError(
|
||||||
|
"[layer.deleteLayer] Layer could not be found"
|
||||||
|
);
|
||||||
|
|
||||||
|
state.layer = layer;
|
||||||
|
state.position = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
uil.layers.splice(state.position, 1);
|
||||||
|
uil.active = uil.layers[state.position - 1] || uil.layers[state.position];
|
||||||
|
|
||||||
|
uil._syncLayers();
|
||||||
|
|
||||||
|
state.layer.layer.hide();
|
||||||
|
},
|
||||||
|
(title, state) => {
|
||||||
|
uil.layers.splice(state.position, 0, state.layer);
|
||||||
|
uil.active = state.layer;
|
||||||
|
|
||||||
|
uil._syncLayers();
|
||||||
|
|
||||||
|
state.layer.layer.unhide();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Command for merging a layer into the layer below it
|
||||||
|
*/
|
||||||
|
commands.createCommand(
|
||||||
|
"mergeLayer",
|
||||||
|
async (title, opt, state) => {
|
||||||
|
const options = opt || {};
|
||||||
|
defaultOpt(options, {
|
||||||
|
layerS: null,
|
||||||
|
layerD: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const layerS = options.layer || uil.active;
|
||||||
|
|
||||||
|
if (!layerS.deletable)
|
||||||
|
throw new TypeError(
|
||||||
|
"[layer.mergeLayer] Layer is a root layer and cannot be merged"
|
||||||
|
);
|
||||||
|
|
||||||
|
const index = uil.layers.indexOf(layerS);
|
||||||
|
if (index === -1)
|
||||||
|
throw new ReferenceError("[layer.mergeLayer] Layer could not be found");
|
||||||
|
|
||||||
|
if (index === 0 && !options.layerD)
|
||||||
|
throw new ReferenceError(
|
||||||
|
"[layer.mergeLayer] No layer below source layer exists"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Use layer under source layer to merge into if not given
|
||||||
|
const layerD = options.layerD || uil.layers[index - 1];
|
||||||
|
|
||||||
|
state.layerS = layerS;
|
||||||
|
state.layerD = layerD;
|
||||||
|
|
||||||
|
// REFERENCE: This is a great reference for metacommands (commands that use other commands)
|
||||||
|
// These commands should NOT record history as we are already executing a command
|
||||||
|
state.drawCommand = await commands.runCommand(
|
||||||
|
"drawImage",
|
||||||
|
"Merge Layer Draw",
|
||||||
|
{
|
||||||
|
image: state.layerS.layer.canvas,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
ctx: state.layerD.layer.ctx,
|
||||||
|
},
|
||||||
|
{recordHistory: false}
|
||||||
|
);
|
||||||
|
state.delCommand = await commands.runCommand(
|
||||||
|
"deleteLayer",
|
||||||
|
"Merge Layer Delete",
|
||||||
|
{layer: state.layerS},
|
||||||
|
{recordHistory: false}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
(title, state) => {
|
||||||
|
state.drawCommand.undo();
|
||||||
|
state.delCommand.undo();
|
||||||
|
},
|
||||||
|
(title, options, state) => {
|
||||||
|
state.drawCommand.redo();
|
||||||
|
state.delCommand.redo();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
commands.runCommand(
|
||||||
|
"addLayer",
|
||||||
|
"Initial Layer Creation",
|
||||||
|
{name: "Default Image Layer", deletable: false},
|
||||||
|
{recordHistory: false}
|
||||||
|
);
|
||||||
|
|
5
res/icons/chevron-first.svg
Normal file
5
res/icons/chevron-first.svg
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<polyline points="17 18 11 12 17 6"></polyline>
|
||||||
|
<path d="M7 6v12"></path>
|
||||||
|
|
||||||
|
</svg>
|
After Width: | Height: | Size: 267 B |
7
res/icons/file-x.svg
Normal file
7
res/icons/file-x.svg
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"></path>
|
||||||
|
<polyline points="14 2 14 8 20 8"></polyline>
|
||||||
|
<line x1="9.5" y1="12.5" x2="14.5" y2="17.5"></line>
|
||||||
|
<line x1="14.5" y1="12.5" x2="9.5" y2="17.5"></line>
|
||||||
|
|
||||||
|
</svg>
|
After Width: | Height: | Size: 437 B |
Loading…
Reference in a new issue