@@ -111,7 +92,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
right: 2px;
border: 1px solid var(--border);
overflow: auto;
- padding-bottom: 100px;
outline: none !important;
}
@@ -125,6 +105,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
height: 100%;
position: relative;
direction: ltr;
+ overflow: hidden;
}
#image {
diff --git a/ts/image-occlusion/Toolbar.svelte b/ts/image-occlusion/Toolbar.svelte
index c0f0cb9fb..3c50e26f0 100644
--- a/ts/image-occlusion/Toolbar.svelte
+++ b/ts/image-occlusion/Toolbar.svelte
@@ -28,11 +28,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
} from "./tools/more-tools";
import { toggleTranslucentKeyCombination } from "./tools/shortcuts";
import { tools } from "./tools/tool-buttons";
+ import { drawCursor } from "./tools/tool-cursor";
import { removeUnfinishedPolygon } from "./tools/tool-polygon";
import { undoRedoTools, undoStack } from "./tools/tool-undo-redo";
+ import { disableZoom, enableZoom } from "./tools/tool-zoom";
export let canvas;
- export let instance;
export let iconSize;
export let activeTool = "cursor";
let showAlignTools = false;
@@ -98,32 +99,37 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
});
window.addEventListener("keydown", (event) => {
if (event.key == "Control" && activeTool != "magnify") {
- instance.resume();
+ stopDraw(canvas);
+ enableZoom(canvas);
}
});
window.addEventListener("keyup", (event) => {
if (event.key == "Control" && activeTool != "magnify") {
- instance.pause();
+ disableFunctions();
}
});
window.addEventListener("wheel", () => {
if (clicked && move && wheel && !dbclicked) {
- enableMagnify();
+ stopDraw(canvas);
+ enableZoom(canvas);
}
});
});
// handle tool changes after initialization
- $: if (instance && canvas) {
+ $: if (canvas) {
disableFunctions();
enableSelectable(canvas, true);
// remove unfinished polygon when switching to other tools
removeUnfinishedPolygon(canvas);
switch (activeTool) {
+ case "cursor":
+ drawCursor(canvas);
+ break;
case "magnify":
+ enableZoom(canvas);
enableSelectable(canvas, false);
- instance.resume();
break;
case "draw-rectangle":
drawRectangle(canvas);
@@ -132,7 +138,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
drawEllipse(canvas);
break;
case "draw-polygon":
- drawPolygon(canvas, instance);
+ drawPolygon(canvas);
break;
case "draw-text":
drawText(canvas);
@@ -143,21 +149,14 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
}
const disableFunctions = () => {
- instance.pause();
stopDraw(canvas);
- canvas.selectionColor = "rgba(100, 100, 255, 0.3)";
+ disableZoom(canvas);
};
function changeOcclusionType(occlusionType: "all" | "one"): void {
$hideAllGuessOne = occlusionType === "all";
emitChangeSignal();
}
- const enableMagnify = () => {
- disableFunctions();
- enableSelectable(canvas, false);
- instance.resume();
- activeTool = "magnify";
- };
@@ -250,7 +249,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
{iconSize}
tooltip="{tool.tooltip()} ({getPlatformString(tool.shortcut)})"
on:click={() => {
- tool.action(instance);
+ tool.action(canvas);
}}
>
{@html tool.icon}
@@ -259,7 +258,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
{
- tool.action(instance);
+ tool.action(canvas);
}}
/>
{/if}
diff --git a/ts/image-occlusion/mask-editor.ts b/ts/image-occlusion/mask-editor.ts
index eb1521ba5..c0884bcf0 100644
--- a/ts/image-occlusion/mask-editor.ts
+++ b/ts/image-occlusion/mask-editor.ts
@@ -5,22 +5,16 @@ import { protoBase64 } from "@bufbuild/protobuf";
import { getImageForOcclusion, getImageOcclusionNote } from "@tslib/backend";
import * as tr from "@tslib/ftl";
import { fabric } from "fabric";
-import type { PanZoom } from "panzoom";
import { get } from "svelte/store";
import { optimumCssSizeForCanvas } from "./canvas-scale";
-import { notesDataStore, tagsWritable, zoomResetValue } from "./store";
+import { notesDataStore, tagsWritable } from "./store";
import Toast from "./Toast.svelte";
import { addShapesToCanvasFromCloze } from "./tools/add-from-cloze";
-import {
- enableSelectable,
- makeShapeRemainInCanvas,
- moveShapeToCanvasBoundaries,
- setCenterXForZoom,
- zoomReset,
-} from "./tools/lib";
+import { enableSelectable, makeShapeRemainInCanvas, moveShapeToCanvasBoundaries } from "./tools/lib";
import { modifiedPolygon } from "./tools/tool-polygon";
import { undoStack } from "./tools/tool-undo-redo";
+import { enablePinchZoom, onResize, setCanvasSize } from "./tools/tool-zoom";
import type { Size } from "./types";
export interface ImageLoadedEvent {
@@ -30,7 +24,6 @@ export interface ImageLoadedEvent {
export const setupMaskEditor = async (
path: string,
- instance: PanZoom,
onChange: () => void,
onImageLoaded: (event: ImageLoadedEvent) => void,
): Promise => {
@@ -42,13 +35,10 @@ export const setupMaskEditor = async (
image.src = getImageData(imageData.data!, path);
image.onload = function() {
const size = optimumCssSizeForCanvas({ width: image.width, height: image.height }, containerSize());
- canvas.setWidth(size.width);
- canvas.setHeight(size.height);
- image.height = size.height;
- image.width = size.width;
- setCanvasZoomRatio(canvas, instance);
- undoStack.reset();
+ setCanvasSize(canvas);
onImageLoaded({ path });
+ setupBoundingBox(canvas, size);
+ undoStack.reset();
};
return canvas;
@@ -56,7 +46,6 @@ export const setupMaskEditor = async (
export const setupMaskEditorForEdit = async (
noteId: number,
- instance: PanZoom,
onChange: () => void,
onImageLoaded: (event: ImageLoadedEvent) => void,
): Promise => {
@@ -78,22 +67,17 @@ export const setupMaskEditorForEdit = async (
// get image width and height
const image = document.getElementById("image") as HTMLImageElement;
- image.style.visibility = "hidden";
image.src = getImageData(clozeNote.imageData!, clozeNote.imageFileName!);
- image.onload = function() {
- const size = optimumCssSizeForCanvas({ width: image.width, height: image.height }, containerSize());
- canvas.setWidth(size.width);
- canvas.setHeight(size.height);
- image.height = size.height;
- image.width = size.width;
- setCanvasZoomRatio(canvas, instance);
- addShapesToCanvasFromCloze(canvas, clozeNote.occlusions);
+ image.onload = async function() {
+ const size = optimumCssSizeForCanvas({ width: image.width, height: image.height }, containerSize());
+ setCanvasSize(canvas);
+ const boundingBox = setupBoundingBox(canvas, size);
+ addShapesToCanvasFromCloze(canvas, boundingBox, clozeNote.occlusions);
enableSelectable(canvas, true);
addClozeNotesToTextEditor(clozeNote.header, clozeNote.backExtra, clozeNote.tags);
undoStack.reset();
window.requestAnimationFrame(() => {
- image.style.visibility = "visible";
onImageLoaded({ noteId: BigInt(noteId) });
});
};
@@ -114,13 +98,13 @@ function initCanvas(onChange: () => void): fabric.Canvas {
canvas.uniScaleKey = "none";
// disable rotation globally
delete fabric.Object.prototype.controls.mtr;
+ // disable object caching
+ fabric.Object.prototype.objectCaching = false;
// add a border to corner to handle blend of control
fabric.Object.prototype.transparentCorners = false;
fabric.Object.prototype.cornerStyle = "circle";
fabric.Object.prototype.cornerStrokeColor = "#000000";
fabric.Object.prototype.padding = 8;
- moveShapeToCanvasBoundaries(canvas);
- makeShapeRemainInCanvas(canvas);
canvas.on("object:modified", (evt) => {
if (evt.target instanceof fabric.Polygon) {
modifiedPolygon(canvas, evt.target);
@@ -129,10 +113,33 @@ function initCanvas(onChange: () => void): fabric.Canvas {
onChange();
});
canvas.on("object:removed", onChange);
- setCenterXForZoom(canvas);
return canvas;
}
+const setupBoundingBox = (canvas: fabric.Canvas, size: Size): fabric.Rect => {
+ const boundingBox = new fabric.Rect({
+ id: "boundingBox",
+ fill: "transparent",
+ width: size.width,
+ height: size.height,
+ hasBorders: false,
+ hasControls: false,
+ lockMovementX: true,
+ lockMovementY: true,
+ selectable: false,
+ evented: false,
+ stroke: "red",
+ });
+
+ canvas.add(boundingBox);
+ onResize(canvas);
+ makeShapeRemainInCanvas(canvas, boundingBox);
+ moveShapeToCanvasBoundaries(canvas, boundingBox);
+ // enable pinch zoom for mobile devices
+ enablePinchZoom(canvas);
+ return boundingBox;
+};
+
const getImageData = (imageData, path): string => {
const b64encoded = protoBase64.enc(imageData);
const extension = path.split(".").pop();
@@ -150,17 +157,6 @@ const getImageData = (imageData, path): string => {
return `data:image/${type};base64,${b64encoded}`;
};
-export const setCanvasZoomRatio = (
- canvas: fabric.Canvas,
- instance: PanZoom,
-): void => {
- const zoomRatioW = (innerWidth - 40) / canvas.width!;
- const zoomRatioH = (innerHeight - 100) / canvas.height!;
- const zoomRatio = zoomRatioW < zoomRatioH ? zoomRatioW : zoomRatioH;
- zoomResetValue.set(zoomRatio);
- zoomReset(instance);
-};
-
const addClozeNotesToTextEditor = (header: string, backExtra: string, tags: string[]) => {
const noteFieldsData: { id: string; title: string; divValue: string; textareaValue: string }[] = get(
notesDataStore,
@@ -195,16 +191,16 @@ export async function resetIOImage(path: string, onImageLoaded: (event: ImageLoa
image.src = getImageData(imageData.data!, path);
const canvas = globalThis.canvas;
- image.onload = function() {
+ image.onload = async function() {
const size = optimumCssSizeForCanvas(
{ width: image.naturalWidth, height: image.naturalHeight },
containerSize(),
);
- canvas.setWidth(size.width);
- canvas.setHeight(size.height);
- image.height = size.height;
image.width = size.width;
+ image.height = size.height;
+ setCanvasSize(canvas);
onImageLoaded({ path });
+ setupBoundingBox(canvas, size);
};
}
globalThis.resetIOImage = resetIOImage;
diff --git a/ts/image-occlusion/shapes/to-cloze.ts b/ts/image-occlusion/shapes/to-cloze.ts
index ac0aeaf08..8b0b53bf4 100644
--- a/ts/image-occlusion/shapes/to-cloze.ts
+++ b/ts/image-occlusion/shapes/to-cloze.ts
@@ -3,6 +3,7 @@
import type { Canvas, Object as FabricObject } from "fabric";
import { fabric } from "fabric";
+import { getBoundingBox } from "image-occlusion/tools/lib";
import { cloneDeep } from "lodash-es";
import type { Size } from "../types";
@@ -21,6 +22,14 @@ export function exportShapesToClozeDeletions(occludeInactive: boolean): {
let clozes = "";
let index = 0;
shapes.forEach((shapeOrShapes) => {
+ // shapes with width or height less than 5 are not valid
+ if (shapeOrShapes === null) {
+ return;
+ }
+ // if shape is Rect and fill is transparent, skip it
+ if (shapeOrShapes instanceof Rectangle && shapeOrShapes.fill === "transparent") {
+ return;
+ }
clozes += shapeOrShapesToCloze(shapeOrShapes, index, occludeInactive);
if (!(shapeOrShapes instanceof Text)) {
index++;
@@ -41,6 +50,7 @@ export function baseShapesFromFabric(): ShapeOrShapes[] {
? activeObject
: null;
const objects = canvas.getObjects() as FabricObject[];
+ const boundingBox = getBoundingBox();
return objects
.map((object) => {
// If the object is in the active selection containing multiple objects,
@@ -48,8 +58,11 @@ export function baseShapesFromFabric(): ShapeOrShapes[] {
const parent = selectionContainingMultipleObjects?.contains(object)
? selectionContainingMultipleObjects
: undefined;
+ if (object.width < 5 || object.height < 5) {
+ return null;
+ }
return fabricObjectToBaseShapeOrShapes(
- canvas,
+ boundingBox,
object,
parent,
);
@@ -107,6 +120,10 @@ function fabricObjectToBaseShapeOrShapes(
shape.top = newPosition.y;
}
+ if (size == undefined) {
+ size = { width: 0, height: 0 };
+ }
+
shape = shape.toNormal(size);
return shape;
}
diff --git a/ts/image-occlusion/store.ts b/ts/image-occlusion/store.ts
index ed867186b..5847250dd 100644
--- a/ts/image-occlusion/store.ts
+++ b/ts/image-occlusion/store.ts
@@ -5,16 +5,12 @@ import { writable } from "svelte/store";
// it stores note's data for generate.ts, when function generate() is called it will be used to generate the note
export const notesDataStore = writable({ id: "", title: "", divValue: "", textareaValue: "" }[0]);
-// it stores the value of zoom ratio for canvas
-export const zoomResetValue = writable(1);
// it stores the tags for the note in note editor
export const tagsWritable = writable([""]);
// it stores the visibility of mask editor
export const ioMaskEditorVisible = writable(true);
// it store hide all or hide one mode
export const hideAllGuessOne = writable(true);
-// store initial value of x for zoom reset
-export const zoomResetX = writable(0);
// ioImageLoadedStore is used to store the image loaded event
export const ioImageLoadedStore = writable(false);
// store opacity state of objects in canvas
diff --git a/ts/image-occlusion/tools/add-from-cloze.ts b/ts/image-occlusion/tools/add-from-cloze.ts
index b35603e62..ddd6c96fd 100644
--- a/ts/image-occlusion/tools/add-from-cloze.ts
+++ b/ts/image-occlusion/tools/add-from-cloze.ts
@@ -10,13 +10,14 @@ import { redraw } from "./lib";
export const addShapesToCanvasFromCloze = (
canvas: fabric.Canvas,
+ boundingBox: fabric.Rect,
occlusions: GetImageOcclusionNoteResponse_ImageOcclusion[],
): void => {
for (const shapeOrShapes of extractShapesFromClozedField(occlusions)) {
if (Array.isArray(shapeOrShapes)) {
- addShapeGroup(canvas, shapeOrShapes);
+ addShapeGroup(canvas, boundingBox, shapeOrShapes);
} else {
- addShape(canvas, shapeOrShapes);
+ addShape(canvas, boundingBox, shapeOrShapes);
}
}
redraw(canvas);
diff --git a/ts/image-occlusion/tools/api.ts b/ts/image-occlusion/tools/api.ts
index 737895f76..bc3942891 100644
--- a/ts/image-occlusion/tools/api.ts
+++ b/ts/image-occlusion/tools/api.ts
@@ -27,12 +27,12 @@ export class MaskEditorAPI {
this.canvas = canvas;
}
- addShape(shape: Shape): void {
- addShape(this.canvas, shape);
+ addShape(bounding, shape: Shape): void {
+ addShape(this.canvas, bounding, shape);
}
- addShapeGroup(shapes: Shape[]): void {
- addShapeGroup(this.canvas, shapes);
+ addShapeGroup(bounding, shapes: Shape[]): void {
+ addShapeGroup(this.canvas, bounding, shapes);
}
getClozes(occludeInactive: boolean): ClozeExportResult {
diff --git a/ts/image-occlusion/tools/from-shapes.ts b/ts/image-occlusion/tools/from-shapes.ts
index 132b3d180..61fa00c2f 100644
--- a/ts/image-occlusion/tools/from-shapes.ts
+++ b/ts/image-occlusion/tools/from-shapes.ts
@@ -8,9 +8,10 @@ import { addBorder, enableUniformScaling } from "./lib";
export const addShape = (
canvas: fabric.Canvas,
+ boundingBox: fabric.Rect,
shape: Shape,
): void => {
- const fabricShape = shape.toFabric(canvas);
+ const fabricShape = shape.toFabric(boundingBox);
addBorder(fabricShape);
if (fabricShape.type === "i-text") {
enableUniformScaling(canvas, fabricShape);
@@ -20,11 +21,12 @@ export const addShape = (
export const addShapeGroup = (
canvas: fabric.Canvas,
+ boundingBox: fabric.Rect,
shapes: Shape[],
): void => {
const group = new fabric.Group();
shapes.map((shape) => {
- const fabricShape = shape.toFabric(canvas);
+ const fabricShape = shape.toFabric(boundingBox);
addBorder(fabricShape);
group.addWithUpdate(fabricShape);
});
diff --git a/ts/image-occlusion/tools/lib.ts b/ts/image-occlusion/tools/lib.ts
index 968dbce6c..598eb1a24 100644
--- a/ts/image-occlusion/tools/lib.ts
+++ b/ts/image-occlusion/tools/lib.ts
@@ -2,10 +2,9 @@
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { fabric } from "fabric";
-import type { PanZoom } from "panzoom";
import { get } from "svelte/store";
-import { opacityStateStore, zoomResetValue, zoomResetX } from "../store";
+import { opacityStateStore } from "../store";
export const SHAPE_MASK_COLOR = "#ffeba2";
export const BORDER_COLOR = "#212121";
@@ -27,6 +26,9 @@ export const enableSelectable = (
): void => {
canvas.selection = select;
canvas.forEachObject(function(o) {
+ if (o.fill === "transparent") {
+ return;
+ }
o.selectable = select;
});
canvas.renderAll();
@@ -41,6 +43,7 @@ export const deleteItem = (canvas: fabric.Canvas): void => {
canvas.discardActiveObject().renderAll();
}
}
+ redraw(canvas);
};
export const duplicateItem = (canvas: fabric.Canvas): void => {
@@ -92,38 +95,6 @@ export const unGroupShapes = (canvas: fabric.Canvas): void => {
redraw(canvas);
};
-export const zoomIn = (instance: PanZoom): void => {
- const center = getCanvasCenter();
- instance.smoothZoom(center.x, center.y, 1.25);
-};
-
-export const zoomOut = (instance: PanZoom): void => {
- const center = getCanvasCenter();
- instance.smoothZoom(center.x, center.y, 0.8);
-};
-
-export const zoomReset = (instance: PanZoom): void => {
- setCenterXForZoom(globalThis.canvas);
- instance.moveTo(get(zoomResetX), 0);
- instance.smoothZoomAbs(get(zoomResetX), 0, get(zoomResetValue));
-};
-
-export const getCanvasCenter = () => {
- const canvas = globalThis.canvas.getElement();
- const rect = canvas.getBoundingClientRect();
- const centerX = rect.x + rect.width / 2;
- const centerY = rect.y + rect.height / 2;
- return { x: centerX, y: centerY };
-};
-
-export const setCenterXForZoom = (canvas: fabric.Canvas) => {
- const editor = document.querySelector(".editor-main")!;
- const editorWidth = editor.clientWidth;
- const canvasWidth = canvas.getElement().offsetWidth;
- const centerX = editorWidth / 2 - canvasWidth / 2;
- zoomResetX.set(centerX);
-};
-
const copyItem = (canvas: fabric.Canvas): void => {
if (!canvas.getActiveObject()) {
return;
@@ -184,26 +155,26 @@ export const makeMaskTransparent = (
canvas.renderAll();
};
-export const moveShapeToCanvasBoundaries = (canvas: fabric.Canvas): void => {
+export const moveShapeToCanvasBoundaries = (canvas: fabric.Canvas, boundingBox: fabric.Rect): void => {
canvas.on("object:modified", function(o) {
const activeObject = o.target;
if (!activeObject) {
return;
}
if (activeObject.type === "rect") {
- modifiedRectangle(canvas, activeObject);
+ modifiedRectangle(boundingBox, activeObject);
}
if (activeObject.type === "ellipse") {
- modifiedEllipse(canvas, activeObject);
+ modifiedEllipse(boundingBox, activeObject);
}
if (activeObject.type === "i-text") {
- modifiedText(canvas, activeObject);
+ modifiedText(boundingBox, activeObject);
}
});
};
const modifiedRectangle = (
- canvas: fabric.Canvas,
+ boundingBox: fabric.Rect,
object: fabric.Object,
): void => {
const newWidth = object.width * object.scaleX;
@@ -215,11 +186,11 @@ const modifiedRectangle = (
scaleX: 1,
scaleY: 1,
});
- setShapePosition(canvas, object);
+ setShapePosition(boundingBox, object);
};
const modifiedEllipse = (
- canvas: fabric.Canvas,
+ boundingBox: fabric.Rect,
object: fabric.Object,
): void => {
const newRx = object.rx * object.scaleX;
@@ -235,15 +206,15 @@ const modifiedEllipse = (
scaleX: 1,
scaleY: 1,
});
- setShapePosition(canvas, object);
+ setShapePosition(boundingBox, object);
};
-const modifiedText = (canvas: fabric.Canvas, object: fabric.Object): void => {
- setShapePosition(canvas, object);
+const modifiedText = (boundingBox: fabric.Rect, object: fabric.Object): void => {
+ setShapePosition(boundingBox, object);
};
const setShapePosition = (
- canvas: fabric.Canvas,
+ boundingBox: fabric.Rect,
object: fabric.Object,
): void => {
if (object.left < 0) {
@@ -252,11 +223,11 @@ const setShapePosition = (
if (object.top < 0) {
object.set({ top: 0 });
}
- if (object.left + object.width * object.scaleX + object.strokeWidth > canvas.width) {
- object.set({ left: canvas.width - object.width * object.scaleX });
+ if (object.left + object.width * object.scaleX + object.strokeWidth > boundingBox.width) {
+ object.set({ left: boundingBox.width - object.width * object.scaleX });
}
- if (object.top + object.height * object.scaleY + object.strokeWidth > canvas.height) {
- object.set({ top: canvas.height - object.height * object.scaleY });
+ if (object.top + object.height * object.scaleY + object.strokeWidth > boundingBox.height) {
+ object.set({ top: boundingBox.height - object.height * object.scaleY });
}
object.setCoords();
};
@@ -287,33 +258,25 @@ export const clear = (canvas: fabric.Canvas): void => {
canvas.clear();
};
-export const makeShapeRemainInCanvas = (canvas: fabric.Canvas) => {
+export const makeShapeRemainInCanvas = (canvas: fabric.Canvas, boundingBox: fabric.Rect) => {
canvas.on("object:moving", function(e) {
const obj = e.target;
- if (obj.getScaledHeight() > obj.canvas.height || obj.getScaledWidth() > obj.canvas.width) {
+ if (obj.getScaledHeight() > boundingBox.height || obj.getScaledWidth() > boundingBox.width) {
return;
}
obj.setCoords();
- if (obj.getBoundingRect().top < 0 || obj.getBoundingRect().left < 0) {
- obj.top = Math.max(obj.top, obj.top - obj.getBoundingRect().top);
- obj.left = Math.max(obj.left, obj.left - obj.getBoundingRect().left);
- }
+ const top = obj.top;
+ const left = obj.left;
- if (
- obj.getBoundingRect().top + obj.getBoundingRect().height > obj.canvas.height
- || obj.getBoundingRect().left + obj.getBoundingRect().width > obj.canvas.width
- ) {
- obj.top = Math.min(
- obj.top,
- obj.canvas.height - obj.getBoundingRect().height + obj.top - obj.getBoundingRect().top,
- );
- obj.left = Math.min(
- obj.left,
- obj.canvas.width - obj.getBoundingRect().width + obj.left - obj.getBoundingRect().left,
- );
- }
+ const topBound = boundingBox.top;
+ const bottomBound = topBound + boundingBox.height;
+ const leftBound = boundingBox.left;
+ const rightBound = leftBound + boundingBox.width;
+
+ obj.left = Math.min(Math.max(left, leftBound), rightBound - obj.width);
+ obj.top = Math.min(Math.max(top, topBound), bottomBound - obj.height);
});
};
@@ -325,3 +288,21 @@ export const selectAllShapes = (canvas: fabric.Canvas) => {
canvas.setActiveObject(sel);
redraw(canvas);
};
+
+export const isPointerInBoundingBox = (pointer): boolean => {
+ const boundingBox = getBoundingBox();
+ if (
+ pointer.x < boundingBox.left
+ || pointer.x > boundingBox.left + boundingBox.width
+ || pointer.y < boundingBox.top
+ || pointer.y > boundingBox.top + boundingBox.height
+ ) {
+ return false;
+ }
+ return true;
+};
+
+export const getBoundingBox = () => {
+ const canvas = globalThis.canvas;
+ return canvas.getObjects().find((obj) => obj.fill === "transparent");
+};
diff --git a/ts/image-occlusion/tools/more-tools.ts b/ts/image-occlusion/tools/more-tools.ts
index 8e78abb8b..b378ba0b7 100644
--- a/ts/image-occlusion/tools/more-tools.ts
+++ b/ts/image-occlusion/tools/more-tools.ts
@@ -19,16 +19,7 @@ import {
mdiZoomOut,
mdiZoomReset,
} from "../icons";
-import {
- deleteItem,
- duplicateItem,
- groupShapes,
- selectAllShapes,
- unGroupShapes,
- zoomIn,
- zoomOut,
- zoomReset,
-} from "./lib";
+import { deleteItem, duplicateItem, groupShapes, selectAllShapes, unGroupShapes } from "./lib";
import {
alignBottomKeyCombination,
alignHorizontalCenterKeyCombination,
@@ -53,6 +44,7 @@ import {
alignTop,
alignVerticalCenter,
} from "./tool-aligns";
+import { zoomIn, zoomOut, zoomReset } from "./tool-zoom";
export const groupUngroupTools = [
{
diff --git a/ts/image-occlusion/tools/tool-cursor.ts b/ts/image-occlusion/tools/tool-cursor.ts
new file mode 100644
index 000000000..4e9ebe17f
--- /dev/null
+++ b/ts/image-occlusion/tools/tool-cursor.ts
@@ -0,0 +1,24 @@
+// Copyright: Ankitects Pty Ltd and contributors
+// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
+
+import type { fabric } from "fabric";
+
+import { stopDraw } from "./lib";
+import { onPinchZoom } from "./tool-zoom";
+
+export const drawCursor = (canvas: fabric.Canvas): void => {
+ canvas.selectionColor = "rgba(100, 100, 255, 0.3)";
+ stopDraw(canvas);
+
+ canvas.on("mouse:down", function(o) {
+ if (o.target) {
+ return;
+ }
+ });
+
+ canvas.on("mouse:move", function(o) {
+ if (onPinchZoom(o)) {
+ return;
+ }
+ });
+};
diff --git a/ts/image-occlusion/tools/tool-ellipse.ts b/ts/image-occlusion/tools/tool-ellipse.ts
index 7d82608e2..d9bf11614 100644
--- a/ts/image-occlusion/tools/tool-ellipse.ts
+++ b/ts/image-occlusion/tools/tool-ellipse.ts
@@ -5,8 +5,9 @@ import { fabric } from "fabric";
import { opacityStateStore } from "image-occlusion/store";
import { get } from "svelte/store";
-import { BORDER_COLOR, SHAPE_MASK_COLOR, stopDraw } from "./lib";
+import { BORDER_COLOR, isPointerInBoundingBox, SHAPE_MASK_COLOR, stopDraw } from "./lib";
import { undoStack } from "./tool-undo-redo";
+import { onPinchZoom } from "./tool-zoom";
export const drawEllipse = (canvas: fabric.Canvas): void => {
canvas.selectionColor = "rgba(0, 0, 0, 0)";
@@ -24,6 +25,11 @@ export const drawEllipse = (canvas: fabric.Canvas): void => {
origX = pointer.x;
origY = pointer.y;
+ if (!isPointerInBoundingBox(pointer)) {
+ isDown = false;
+ return;
+ }
+
ellipse = new fabric.Ellipse({
id: "ellipse-" + new Date().getTime(),
left: origX,
@@ -45,6 +51,12 @@ export const drawEllipse = (canvas: fabric.Canvas): void => {
});
canvas.on("mouse:move", function(o) {
+ if (onPinchZoom(o)) {
+ canvas.remove(ellipse);
+ canvas.renderAll();
+ return;
+ }
+
if (!isDown) {
return;
}
diff --git a/ts/image-occlusion/tools/tool-polygon.ts b/ts/image-occlusion/tools/tool-polygon.ts
index 62c683f2c..fc6535195 100644
--- a/ts/image-occlusion/tools/tool-polygon.ts
+++ b/ts/image-occlusion/tools/tool-polygon.ts
@@ -3,20 +3,19 @@
import { fabric } from "fabric";
import { opacityStateStore } from "image-occlusion/store";
-import type { PanZoom } from "panzoom";
import { get } from "svelte/store";
-import { BORDER_COLOR, SHAPE_MASK_COLOR } from "./lib";
+import { BORDER_COLOR, isPointerInBoundingBox, SHAPE_MASK_COLOR } from "./lib";
import { undoStack } from "./tool-undo-redo";
+import { onPinchZoom } from "./tool-zoom";
let activeLine;
let activeShape;
let linesList: fabric.Line = [];
let pointsList: fabric.Circle = [];
let drawMode = false;
-let zoomValue = 1;
-export const drawPolygon = (canvas: fabric.Canvas, panzoom: PanZoom): void => {
+export const drawPolygon = (canvas: fabric.Canvas): void => {
// remove selectable for shapes
canvas.discardActiveObject();
canvas.forEachObject(function(o) {
@@ -29,7 +28,7 @@ export const drawPolygon = (canvas: fabric.Canvas, panzoom: PanZoom): void => {
if (options.target && options.target.id === pointsList[0].id) {
generatePolygon(canvas, pointsList);
} else {
- addPoint(canvas, options, panzoom);
+ addPoint(canvas, options);
}
} catch (e) {
// Cannot read properties of undefined (reading 'id')
@@ -37,6 +36,12 @@ export const drawPolygon = (canvas: fabric.Canvas, panzoom: PanZoom): void => {
});
canvas.on("mouse:move", function(options) {
+ // if pinch zoom is active, remove all points and lines
+ if (onPinchZoom(options)) {
+ removeUnfinishedPolygon(canvas);
+ return;
+ }
+
if (activeLine && activeLine.class === "line") {
const pointer = canvas.getPointer(options.e);
activeLine.set({
@@ -71,15 +76,14 @@ const toggleDrawPolygon = (canvas: fabric.Canvas): void => {
}
};
-const addPoint = (canvas: fabric.Canvas, options, panzoom): void => {
- zoomValue = panzoom.getTransform().scale;
+const addPoint = (canvas: fabric.Canvas, options): void => {
+ const pointer = canvas.getPointer(options.e);
+ const origX = pointer.x;
+ const origY = pointer.y;
- const canvasContainer = document.querySelector(".canvas-container")!.getBoundingClientRect()!;
- let clientX = options.e.touches ? options.e.touches[0].clientX : options.e.clientX;
- let clientY = options.e.touches ? options.e.touches[0].clientY : options.e.clientY;
-
- clientX = (clientX - canvasContainer.left) / zoomValue;
- clientY = (clientY - canvasContainer.top) / zoomValue;
+ if (!isPointerInBoundingBox(pointer)) {
+ return;
+ }
const point = new fabric.Circle({
radius: 5,
@@ -88,8 +92,8 @@ const addPoint = (canvas: fabric.Canvas, options, panzoom): void => {
strokeWidth: 0.5,
originX: "left",
originY: "top",
- left: clientX,
- top: clientY,
+ left: origX,
+ top: origY,
selectable: false,
hasBorders: false,
hasControls: false,
@@ -102,7 +106,7 @@ const addPoint = (canvas: fabric.Canvas, options, panzoom): void => {
});
}
- const linePoints = [clientX, clientY, clientX, clientY];
+ const linePoints = [origX, origY, origX, origY];
const line = new fabric.Line(linePoints, {
strokeWidth: 2,
@@ -143,7 +147,7 @@ const addPoint = (canvas: fabric.Canvas, options, panzoom): void => {
activeShape = polygon;
canvas.renderAll();
} else {
- const polyPoint = [{ x: clientX, y: clientY }];
+ const polyPoint = [{ x: origX, y: origY }];
const polygon = new fabric.Polygon(polyPoint, {
stroke: "#333333",
strokeWidth: 1,
@@ -166,6 +170,7 @@ const addPoint = (canvas: fabric.Canvas, options, panzoom): void => {
canvas.add(line);
canvas.add(point);
+ canvas.renderAll();
};
const generatePolygon = (canvas: fabric.Canvas, pointsList): void => {
diff --git a/ts/image-occlusion/tools/tool-rect.ts b/ts/image-occlusion/tools/tool-rect.ts
index eea6e6d37..02400be15 100644
--- a/ts/image-occlusion/tools/tool-rect.ts
+++ b/ts/image-occlusion/tools/tool-rect.ts
@@ -5,8 +5,9 @@ import { fabric } from "fabric";
import { opacityStateStore } from "image-occlusion/store";
import { get } from "svelte/store";
-import { BORDER_COLOR, SHAPE_MASK_COLOR, stopDraw } from "./lib";
+import { BORDER_COLOR, isPointerInBoundingBox, SHAPE_MASK_COLOR, stopDraw } from "./lib";
import { undoStack } from "./tool-undo-redo";
+import { onPinchZoom } from "./tool-zoom";
export const drawRectangle = (canvas: fabric.Canvas): void => {
canvas.selectionColor = "rgba(0, 0, 0, 0)";
@@ -24,6 +25,11 @@ export const drawRectangle = (canvas: fabric.Canvas): void => {
origX = pointer.x;
origY = pointer.y;
+ if (!isPointerInBoundingBox(pointer)) {
+ isDown = false;
+ return;
+ }
+
rect = new fabric.Rect({
id: "rect-" + new Date().getTime(),
left: origX,
@@ -46,6 +52,12 @@ export const drawRectangle = (canvas: fabric.Canvas): void => {
});
canvas.on("mouse:move", function(o) {
+ if (onPinchZoom(o)) {
+ canvas.remove(rect);
+ canvas.renderAll();
+ return;
+ }
+
if (!isDown) {
return;
}
diff --git a/ts/image-occlusion/tools/tool-text.ts b/ts/image-occlusion/tools/tool-text.ts
index bfdc8f8b7..11ac762ae 100644
--- a/ts/image-occlusion/tools/tool-text.ts
+++ b/ts/image-occlusion/tools/tool-text.ts
@@ -5,19 +5,34 @@ import { fabric } from "fabric";
import { opacityStateStore, textEditingState } from "image-occlusion/store";
import { get } from "svelte/store";
-import { enableUniformScaling, stopDraw, TEXT_BACKGROUND_COLOR, TEXT_FONT_FAMILY, TEXT_PADDING } from "./lib";
+import {
+ enableUniformScaling,
+ isPointerInBoundingBox,
+ stopDraw,
+ TEXT_BACKGROUND_COLOR,
+ TEXT_FONT_FAMILY,
+ TEXT_PADDING,
+} from "./lib";
import { undoStack } from "./tool-undo-redo";
+import { onPinchZoom } from "./tool-zoom";
export const drawText = (canvas: fabric.Canvas): void => {
canvas.selectionColor = "rgba(0, 0, 0, 0)";
stopDraw(canvas);
+ let text;
+
canvas.on("mouse:down", function(o) {
if (o.target) {
return;
}
const pointer = canvas.getPointer(o.e);
- const text = new fabric.IText("text", {
+
+ if (!isPointerInBoundingBox(pointer)) {
+ return;
+ }
+
+ text = new fabric.IText("text", {
id: "text-" + new Date().getTime(),
left: pointer.x,
top: pointer.y,
@@ -35,10 +50,17 @@ export const drawText = (canvas: fabric.Canvas): void => {
canvas.add(text);
canvas.setActiveObject(text);
undoStack.onObjectAdded(text.id);
- text.enterEditing();
text.selectAll();
});
+ canvas.on("mouse:move", function(o) {
+ if (onPinchZoom(o)) {
+ canvas.remove(text);
+ canvas.renderAll();
+ return;
+ }
+ });
+
canvas.on("text:editing:entered", function() {
textEditingState.set(true);
});
diff --git a/ts/image-occlusion/tools/tool-zoom.ts b/ts/image-occlusion/tools/tool-zoom.ts
new file mode 100644
index 000000000..1760f3d24
--- /dev/null
+++ b/ts/image-occlusion/tools/tool-zoom.ts
@@ -0,0 +1,212 @@
+// Copyright: Ankitects Pty Ltd and contributors
+// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
+
+// https://codepen.io/amsunny/pen/XWGLxye
+// canvas.viewportTransform = [ scaleX, skewX, skewY, scaleY, translateX, translateY ]
+
+import type { fabric } from "fabric";
+import Hammer from "hammerjs";
+
+import { getBoundingBox, redraw } from "./lib";
+
+let isDragging = false;
+
+const minScale = 0.5;
+const maxScale = 5;
+let zoomScale = 1;
+export let currentScale = 1;
+
+export const enableZoom = (canvas: fabric.Canvas) => {
+ canvas.on("mouse:wheel", onMouseWheel);
+ canvas.on("mouse:down", onMouseDown);
+ canvas.on("mouse:move", onMouseMove);
+ canvas.on("mouse:up", onMouseUp);
+};
+
+export const disableZoom = (canvas: fabric.Canvas) => {
+ canvas.off("mouse:wheel", onMouseWheel);
+ canvas.off("mouse:down", onMouseDown);
+ canvas.off("mouse:move", onMouseMove);
+ canvas.off("mouse:up", onMouseUp);
+};
+
+export const zoomIn = (canvas: fabric.Canvas): void => {
+ let zoom = canvas.getZoom();
+ zoom = Math.min(maxScale, zoom * 1.1);
+ canvas.zoomToPoint({ x: canvas.width / 2, y: canvas.height / 2 }, zoom);
+ constrainBoundsAroundBgImage(canvas);
+ redraw(canvas);
+};
+
+export const zoomOut = (canvas): void => {
+ let zoom = canvas.getZoom();
+ zoom = Math.max(minScale, zoom / 1.1);
+ canvas.zoomToPoint({ x: canvas.width / 2, y: canvas.height / 2 }, zoom / 1.1);
+ constrainBoundsAroundBgImage(canvas);
+ redraw(canvas);
+};
+
+export const zoomReset = (canvas: fabric.Canvas): void => {
+ canvas.zoomToPoint({ x: canvas.width / 2, y: canvas.height / 2 }, 1);
+ fitCanvasVptScale(canvas);
+ constrainBoundsAroundBgImage(canvas);
+};
+
+export const enablePinchZoom = (canvas: fabric.Canvas) => {
+ const hammer = new Hammer(canvas.upperCanvasEl);
+ hammer.get("pinch").set({ enable: true });
+ hammer.on("pinchin pinchout", ev => {
+ currentScale = Math.min(Math.max(minScale, ev.scale * zoomScale), maxScale);
+ canvas.zoomToPoint({ x: canvas.width / 2, y: canvas.height / 2 }, currentScale);
+ constrainBoundsAroundBgImage(canvas);
+ redraw(canvas);
+ });
+ hammer.on("pinchend pinchcancel", () => {
+ zoomScale = currentScale;
+ });
+};
+
+export const disablePinchZoom = (canvas: fabric.Canvas) => {
+ const hammer = new Hammer(canvas.upperCanvasEl);
+ hammer.get("pinch").set({ enable: false });
+ hammer.off("pinch pinchmove pinchend pinchcancel");
+};
+
+export const onResize = (canvas: fabric.Canvas) => {
+ setCanvasSize(canvas);
+ constrainBoundsAroundBgImage(canvas);
+ fitCanvasVptScale(canvas);
+};
+
+const onMouseWheel = (opt) => {
+ const canvas = globalThis.canvas;
+ const delta = opt.e.deltaY;
+ let zoom = canvas.getZoom();
+ zoom *= 0.999 ** delta;
+ zoom = Math.max(minScale, Math.min(zoom, maxScale));
+ canvas.zoomToPoint({ x: opt.pointer.x, y: opt.pointer.y }, zoom);
+ opt.e.preventDefault();
+ opt.e.stopPropagation();
+ constrainBoundsAroundBgImage(canvas);
+ redraw(canvas);
+};
+
+const onMouseDown = (opt) => {
+ isDragging = true;
+ const canvas = globalThis.canvas;
+ canvas.discardActiveObject();
+ const { e } = opt;
+ const clientX = e.type === "touchstart" ? e.touches[0].clientX : e.clientX;
+ const clientY = e.type === "touchstart" ? e.touches[0].clientY : e.clientY;
+ canvas.lastPosX = clientX;
+ canvas.lastPosY = clientY;
+ redraw(canvas);
+};
+
+export const onMouseMove = (opt) => {
+ const canvas = globalThis.canvas;
+ if (isDragging) {
+ canvas.discardActiveObject();
+ if (!canvas.viewportTransform) {
+ return;
+ }
+
+ // handle pinch zoom and pan for mobile devices
+ if (onPinchZoom(opt)) {
+ return;
+ }
+
+ onDrag(canvas, opt);
+ }
+};
+
+// initializes lastPosX and lastPosY because it is undefined in touchmove event
+document.addEventListener("touchstart", (e) => {
+ const canvas = globalThis.canvas;
+ canvas.lastPosX = e.touches[0].clientX;
+ canvas.lastPosY = e.touches[0].clientY;
+});
+
+export const onPinchZoom = (opt): boolean => {
+ const { e } = opt;
+ const canvas = globalThis.canvas;
+ if ((e.type === "touchmove") && (e.touches.length > 1)) {
+ onDrag(canvas, opt);
+ return true;
+ }
+ return false;
+};
+
+const onDrag = (canvas, opt) => {
+ const { e } = opt;
+ const clientX = e.type === "touchmove" ? e.touches[0].clientX : e.clientX;
+ const clientY = e.type === "touchmove" ? e.touches[0].clientY : e.clientY;
+ const vpt = canvas.viewportTransform;
+
+ vpt[4] += clientX - canvas.lastPosX;
+ vpt[5] += clientY - canvas.lastPosY;
+ canvas.lastPosX = clientX;
+ canvas.lastPosY = clientY;
+ constrainBoundsAroundBgImage(canvas);
+ redraw(canvas);
+};
+
+const onMouseUp = () => {
+ isDragging = false;
+ const canvas = globalThis.canvas;
+ canvas.setViewportTransform(canvas.viewportTransform);
+ constrainBoundsAroundBgImage(canvas);
+ redraw(canvas);
+};
+
+export const constrainBoundsAroundBgImage = (canvas: fabric.Canvas) => {
+ const boundingBox = getBoundingBox();
+ const ioImage = document.getElementById("image") as HTMLImageElement;
+
+ const width = boundingBox.width * canvas.getZoom();
+ const height = boundingBox.height * canvas.getZoom();
+
+ const left = canvas.viewportTransform[4];
+ const top = canvas.viewportTransform[5];
+
+ ioImage.width = width;
+ ioImage.height = height;
+ ioImage.style.left = `${left}px`;
+ ioImage.style.top = `${top}px`;
+};
+
+export const setCanvasSize = (canvas: fabric.Canvas) => {
+ canvas.setHeight(window.innerHeight - 76);
+ canvas.setWidth(window.innerWidth - 39);
+ redraw(canvas);
+};
+
+const fitCanvasVptScale = (canvas: fabric.Canvas) => {
+ const boundingBox = getBoundingBox();
+ const ratio = getScaleRatio(boundingBox);
+ const vpt = canvas.viewportTransform;
+
+ const boundingBoxWidth = boundingBox.width * canvas.getZoom();
+ const boundingBoxHeight = boundingBox.height * canvas.getZoom();
+ const center = canvas.getCenter();
+ const translateX = center.left - (boundingBoxWidth / 2);
+ const translateY = center.top - (boundingBoxHeight / 2);
+
+ vpt[0] = ratio;
+ vpt[3] = ratio;
+ vpt[4] = Math.max(1, translateX);
+ vpt[5] = Math.max(1, translateY);
+
+ canvas.setViewportTransform(canvas.viewportTransform);
+ constrainBoundsAroundBgImage(canvas);
+ redraw(canvas);
+};
+
+const getScaleRatio = (boundingBox: fabric.Rect) => {
+ const h1 = boundingBox.height;
+ const w1 = boundingBox.width;
+ const h2 = innerHeight - 79;
+ const w2 = innerWidth - 42;
+
+ return Math.min(w2 / w1, h2 / h1);
+};
diff --git a/yarn.lock b/yarn.lock
index 610901843..020748fcf 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1406,13 +1406,6 @@ ajv@^6.12.4:
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
-amator@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/amator/-/amator-1.1.0.tgz#08c6b60bc93aec2b61bbfc0c4d677d30323cc0f1"
- integrity sha512-V5+aH8pe+Z3u/UG3L3pG3BaFQGXAyXHVQDroRwjPHdh08bcUEchAVsU1MCuJSCaU5o60wTK6KaE6te5memzgYw==
- dependencies:
- bezier-easing "^2.0.3"
-
ansi-escapes@^4.2.1:
version "4.3.2"
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e"
@@ -1657,11 +1650,6 @@ balanced-match@^1.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
-bezier-easing@^2.0.3:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/bezier-easing/-/bezier-easing-2.1.0.tgz#c04dfe8b926d6ecaca1813d69ff179b7c2025d86"
- integrity sha512-gbIqZ/eslnUFC1tjEvtz0sgx+xTK20wDnYMIA27VA04R7w6xxXQPZDbibjA9DTWZRA2CXtwHykkVzlCaAJAZig==
-
binary-extensions@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
@@ -3038,6 +3026,11 @@ graphemer@^1.4.0:
resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6"
integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==
+hammerjs@^2.0.8:
+ version "2.0.8"
+ resolved "https://registry.yarnpkg.com/hammerjs/-/hammerjs-2.0.8.tgz#04ef77862cff2bb79d30f7692095930222bf60f1"
+ integrity sha512-tSQXBXS/MWQOn/RKckawJ61vvsDpCom87JgxiYdGwHdOa0ht0vzUWDlfioofFCRU0L+6NGDt6XzbgoJvZkMeRQ==
+
has-bigints@^1.0.1, has-bigints@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa"
@@ -4170,11 +4163,6 @@ natural-compare@^1.4.0:
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
-ngraph.events@^1.2.2:
- version "1.2.2"
- resolved "https://registry.yarnpkg.com/ngraph.events/-/ngraph.events-1.2.2.tgz#3ceb92d676a04a4e7ce60a09fa8e17a4f0346d7f"
- integrity sha512-JsUbEOzANskax+WSYiAPETemLWYXmixuPAlmZmhIbIj6FH/WDgEGCGnRwUQBK0GjOnVm8Ui+e5IJ+5VZ4e32eQ==
-
node-int64@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
@@ -4332,15 +4320,6 @@ p-try@^2.0.0:
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
-panzoom@^9.4.3:
- version "9.4.3"
- resolved "https://registry.yarnpkg.com/panzoom/-/panzoom-9.4.3.tgz#195c4031bb643f2e6c42f1de0ca87cc10e224042"
- integrity sha512-xaxCpElcRbQsUtIdwlrZA90P90+BHip4Vda2BC8MEb4tkI05PmR6cKECdqUCZ85ZvBHjpI9htJrZBxV5Gp/q/w==
- dependencies:
- amator "^1.1.0"
- ngraph.events "^1.2.2"
- wheel "^1.0.0"
-
parent-module@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
@@ -5389,11 +5368,6 @@ whatwg-url@^11.0.0:
tr46 "^3.0.0"
webidl-conversions "^7.0.0"
-wheel@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/wheel/-/wheel-1.0.0.tgz#6cf46e06a854181adb8649228077f8b0d5c574ce"
- integrity sha512-XiCMHibOiqalCQ+BaNSwRoZ9FDTAvOsXxGHXChBugewDj7HC8VBIER71dEOiRH1fSdLbRCQzngKTSiZ06ZQzeA==
-
which-boxed-primitive@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"