be1f889211
* remove unfinished polygon and remove selectable for shapes in polygon mode * make group and polygon position remain inside canvas area * click through transparent area in grouped object * add some shortcuts for basic usages * tools button icon in center & switch mode border * fix load svg image * basic rtl support, panzoom have issues in rtl mode * better zoom option both in ltr and rtl * handle zoom event in mask editor * add h button to handle toggle mask * add more mime type * use capital M (shift+m) for toggle mask * allow io shortcuts in mask editor only * make other shapes also remain in canvas bound area * better zoom implementation, zoom from center add zoom to resize event listener * add a border to corner to handle blend of control * add refresh button to go to selection menu * add tooltip to shortcuts and also add shortcut for other tools * make opacity remain in same state when toggled on * opacity for group/ungroup objects * update shortcuts implementation
147 lines
3.6 KiB
TypeScript
147 lines
3.6 KiB
TypeScript
// Copyright: Ankitects Pty Ltd and contributors
|
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|
|
|
import * as tr from "@tslib/ftl";
|
|
import type fabric from "fabric";
|
|
import { writable } from "svelte/store";
|
|
|
|
import { mdiRedo, mdiUndo } from "../icons";
|
|
import { emitChangeSignal } from "../MaskEditor.svelte";
|
|
import { redoKeyCombination, undoKeyCombination } from "./shortcuts";
|
|
|
|
/**
|
|
* Undo redo for rectangle and ellipse handled here,
|
|
* view tool-polygon for handling undo redo in case of polygon
|
|
*/
|
|
|
|
type UndoState = {
|
|
undoable: boolean;
|
|
redoable: boolean;
|
|
};
|
|
|
|
const shapeType = ["rect", "ellipse", "i-text"];
|
|
|
|
const validShape = (shape: fabric.Object): boolean => {
|
|
if (shape.width <= 5 || shape.height <= 5) {
|
|
return false;
|
|
}
|
|
if (shapeType.indexOf(shape.type) === -1) {
|
|
return false;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
class UndoStack {
|
|
private stack: string[] = [];
|
|
private index = -1;
|
|
private canvas: fabric.Canvas | undefined;
|
|
private locked = false;
|
|
private shapeIds = new Set<string>();
|
|
/** used to make the toolbar buttons reactive */
|
|
private state = writable<UndoState>({ undoable: false, redoable: false });
|
|
subscribe: typeof this.state.subscribe;
|
|
|
|
constructor() {
|
|
// allows an instance of the class to act as a store
|
|
this.subscribe = this.state.subscribe;
|
|
}
|
|
|
|
setCanvas(canvas: fabric.Canvas): void {
|
|
this.canvas = canvas;
|
|
this.canvas.on("object:modified", (opts) => this.maybePush(opts));
|
|
this.canvas.on("object:removed", (opts) => this.maybePush(opts));
|
|
}
|
|
|
|
reset(): void {
|
|
this.shapeIds.clear();
|
|
this.stack.length = 0;
|
|
this.index = -1;
|
|
this.push();
|
|
this.updateState();
|
|
}
|
|
|
|
private canUndo(): boolean {
|
|
return this.index > 0;
|
|
}
|
|
|
|
private canRedo(): boolean {
|
|
return this.index < this.stack.length - 1;
|
|
}
|
|
|
|
private updateState(): void {
|
|
this.state.set({
|
|
undoable: this.canUndo(),
|
|
redoable: this.canRedo(),
|
|
});
|
|
}
|
|
|
|
private updateCanvas(): void {
|
|
this.locked = true;
|
|
this.canvas?.loadFromJSON(this.stack[this.index], () => {
|
|
this.canvas?.renderAll();
|
|
emitChangeSignal();
|
|
this.locked = false;
|
|
});
|
|
}
|
|
|
|
onObjectAdded(id: string): void {
|
|
if (!this.shapeIds.has(id)) {
|
|
this.push();
|
|
}
|
|
this.shapeIds.add(id);
|
|
emitChangeSignal();
|
|
}
|
|
|
|
onObjectModified(): void {
|
|
this.push();
|
|
}
|
|
|
|
private maybePush(opts): void {
|
|
if (!this.locked && validShape(opts.target as fabric.Object)) {
|
|
this.push();
|
|
}
|
|
}
|
|
|
|
private push(): void {
|
|
this.stack.length = this.index + 1;
|
|
this.stack.push(JSON.stringify(this.canvas));
|
|
this.index++;
|
|
this.updateState();
|
|
}
|
|
|
|
undo(): void {
|
|
if (this.canUndo()) {
|
|
this.index--;
|
|
this.updateState();
|
|
this.updateCanvas();
|
|
}
|
|
}
|
|
|
|
redo(): void {
|
|
if (this.canRedo()) {
|
|
this.index++;
|
|
this.updateState();
|
|
this.updateCanvas();
|
|
}
|
|
}
|
|
}
|
|
|
|
export const undoStack = new UndoStack();
|
|
|
|
export const undoRedoTools = [
|
|
{
|
|
name: "undo",
|
|
icon: mdiUndo,
|
|
action: () => undoStack.undo(),
|
|
tooltip: tr.undoUndo,
|
|
shortcut: undoKeyCombination,
|
|
},
|
|
{
|
|
name: "redo",
|
|
icon: mdiRedo,
|
|
action: () => undoStack.redo(),
|
|
tooltip: tr.undoRedo,
|
|
shortcut: redoKeyCombination,
|
|
},
|
|
];
|