anki/ts/image-occlusion/tools/tool-undo-redo.ts
Mani be1f889211
fixes: remove unfinished shapes, remove selectable and make shapes remain inside canvas (#2809)
* 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
2023-11-24 14:06:40 +10:00

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,
},
];