From d56ea36d4a406013ee024d4723ca04a81a93dd17 Mon Sep 17 00:00:00 2001
From: krmanik <12841290+krmanik@users.noreply.github.com>
Date: Thu, 9 Feb 2023 16:29:17 +0530
Subject: [PATCH] image occlusion project * basic shapes - rectangle -
ellipse - polygon * some other tools - undo & redo - group &
ungroup - zoom tools - copy
---
package.json | 2 +
ts/image-occlusion/MaskEditor.svelte | 96 +++++++
ts/image-occlusion/SideToolbar.svelte | 109 ++++++++
ts/image-occlusion/TopToolbar.svelte | 76 ++++++
ts/image-occlusion/generate.ts | 122 +++++++++
ts/image-occlusion/icons.ts | 21 ++
ts/image-occlusion/lib.ts | 35 +++
ts/image-occlusion/store.ts | 9 +
ts/image-occlusion/tools/index.ts | 8 +
ts/image-occlusion/tools/lib.ts | 149 +++++++++++
ts/image-occlusion/tools/more-tools.ts | 46 ++++
ts/image-occlusion/tools/tool-buttons.ts | 39 +++
ts/image-occlusion/tools/tool-ellipse.ts | 58 +++++
ts/image-occlusion/tools/tool-polygon.ts | 186 ++++++++++++++
ts/image-occlusion/tools/tool-rect.ts | 73 ++++++
ts/image-occlusion/tools/tool-undo-redo.ts | 45 ++++
ts/image-occlusion/tsconfig.json | 15 ++
yarn.lock | 281 ++++++++++++++++++++-
18 files changed, 1364 insertions(+), 6 deletions(-)
create mode 100644 ts/image-occlusion/MaskEditor.svelte
create mode 100644 ts/image-occlusion/SideToolbar.svelte
create mode 100644 ts/image-occlusion/TopToolbar.svelte
create mode 100644 ts/image-occlusion/generate.ts
create mode 100644 ts/image-occlusion/icons.ts
create mode 100644 ts/image-occlusion/lib.ts
create mode 100644 ts/image-occlusion/store.ts
create mode 100644 ts/image-occlusion/tools/index.ts
create mode 100644 ts/image-occlusion/tools/lib.ts
create mode 100644 ts/image-occlusion/tools/more-tools.ts
create mode 100644 ts/image-occlusion/tools/tool-buttons.ts
create mode 100644 ts/image-occlusion/tools/tool-ellipse.ts
create mode 100644 ts/image-occlusion/tools/tool-polygon.ts
create mode 100644 ts/image-occlusion/tools/tool-rect.ts
create mode 100644 ts/image-occlusion/tools/tool-undo-redo.ts
create mode 100644 ts/image-occlusion/tsconfig.json
diff --git a/package.json b/package.json
index 7833d1468cd..b9298e35f52 100644
--- a/package.json
+++ b/package.json
@@ -74,6 +74,7 @@
"codemirror": "^5.63.1",
"css-browser-selector": "^0.6.5",
"d3": "^7.0.0",
+ "fabric": "^5.3.0",
"fuse.js": "^6.6.2",
"gemoji": "^7.1.0",
"intl-pluralrules": "^1.2.2",
@@ -82,6 +83,7 @@
"lodash-es": "^4.17.21",
"marked": "^4.0.0",
"mathjax": "^3.1.2",
+ "panzoom": "^9.4.3",
"protobufjs": "^7"
},
"resolutions": {
diff --git a/ts/image-occlusion/MaskEditor.svelte b/ts/image-occlusion/MaskEditor.svelte
new file mode 100644
index 00000000000..d8c7bd91f6c
--- /dev/null
+++ b/ts/image-occlusion/MaskEditor.svelte
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+
diff --git a/ts/image-occlusion/SideToolbar.svelte b/ts/image-occlusion/SideToolbar.svelte
new file mode 100644
index 00000000000..4875e068c3c
--- /dev/null
+++ b/ts/image-occlusion/SideToolbar.svelte
@@ -0,0 +1,109 @@
+
+
+
+
+
+
+ {#each tools as tool}
+ {
+ setActive(tool.id);
+ // popup position for color dialog
+ toolTopPos = e.pageY - 60;
+ }}>{@html tool.icon}
+ {/each}
+
+
+
diff --git a/ts/image-occlusion/TopToolbar.svelte b/ts/image-occlusion/TopToolbar.svelte
new file mode 100644
index 00000000000..1a22087b32e
--- /dev/null
+++ b/ts/image-occlusion/TopToolbar.svelte
@@ -0,0 +1,76 @@
+
+
+
+
+ {#each undoRedoTools as undoRedoTool}
+ {
+ undoRedoTool.action(canvas);
+ }}
+ >
+ {@html undoRedoTool.icon}
+
+ {/each}
+
+ {#each zoomTools as zoomBottomTool}
+ {
+ zoomBottomTool.action(instance);
+ }}
+ >
+ {@html zoomBottomTool.icon}
+
+ {/each}
+
+ {#if activeTool === "cursor"}
+ {#each cursorTools as cursorBottomTool}
+ {
+ cursorBottomTool.action(canvas);
+ }}
+ >
+ {@html cursorBottomTool.icon}
+
+ {/each}
+ {/if}
+
+
+
diff --git a/ts/image-occlusion/generate.ts b/ts/image-occlusion/generate.ts
new file mode 100644
index 00000000000..6aed7aeff7a
--- /dev/null
+++ b/ts/image-occlusion/generate.ts
@@ -0,0 +1,122 @@
+// Copyright: Ankitects Pty Ltd and contributors
+// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
+
+import { get } from "svelte/store";
+
+import { addImageOcclusionNotes } from "./lib";
+import { noteFieldsData, tagsWritable } from "./store";
+
+const divData = [
+ "angle",
+ "endAngle",
+ "fill",
+ "fontFamily",
+ "fontSize",
+ "height",
+ "left",
+ "points",
+ "radius",
+ "rx",
+ "ry",
+ "startAngle",
+ "stroke",
+ "strokeWidth",
+ "text",
+ "top",
+ "type",
+ "width",
+];
+
+export function generate(): string {
+ const canvas = globalThis.canvas;
+ const canvasObjects = canvas.getObjects();
+ if (canvasObjects.length < 1) {
+ return "";
+ }
+
+ let occlusionNotes = "";
+ let clozeData = "";
+ let count = 0;
+
+ canvasObjects.forEach((object, index) => {
+ count++;
+ const obJson = object.toJSON();
+ if (obJson.type === "group") {
+ clozeData += getGroupCloze(object, index);
+ } else {
+ clozeData += getCloze(object, index, null);
+ }
+ });
+
+ occlusionNotes += clozeData;
+ console.log(occlusionNotes);
+ return occlusionNotes;
+}
+
+const getCloze = (object, index, relativePos) => {
+ const obJson = object.toJSON();
+ let clozeData = "";
+
+ Object.keys(obJson).forEach(function (key) {
+ if (divData.includes(key)) {
+ if (key === "points") {
+ const points = JSON.stringify(obJson[key]);
+ clozeData += `:${key}='${points}'`;
+ } else if (relativePos && key === "top") {
+ clozeData += `:top="${relativePos.top}" `;
+ } else if (relativePos && key === "left") {
+ clozeData += `:left="${relativePos.left}" `;
+ } else {
+ clozeData += `:${key}="${obJson[key]}"`;
+ }
+ }
+ });
+
+ clozeData = `{{c${index + 1}::image-occlusion:${clozeData}}}\n`;
+ return clozeData;
+};
+
+const getGroupCloze = (group, index) => {
+ let clozeData = "";
+ const objects = group._objects;
+
+ objects.forEach((object) => {
+ const { top, left } = getObjectPositionInGroup(group, object);
+ clozeData += getCloze(object, index, { top, left });
+ });
+
+ return clozeData;
+};
+
+const getObjectPositionInGroup = (group, object) => {
+ let left = object.left + group.left + group.width / 2;
+ let top = object.top + group.top + group.height / 2;
+ left = left.toFixed(2);
+ top = top.toFixed(2);
+ return { top, left };
+};
+
+const saveImageNotes = async function (
+ imagePath: string,
+ occlusions: string,
+ deckId: number,
+ count: number,
+) {
+ const fieldsData = get(noteFieldsData);
+ const tags = get(tagsWritable);
+ let header = fieldsData["header"];
+ let notes = fieldsData["notes"];
+
+ if (header === undefined) {
+ const textArea = document.getElementById("img-occ-html-header")! as HTMLTextAreaElement;
+ header = textArea.value;
+ }
+
+ if (notes === undefined) {
+ const textArea = document.getElementById("img-occ-html-notes")! as HTMLTextAreaElement;
+ notes = textArea.value;
+ }
+
+ await addImageOcclusionNotes(imagePath, deckId, occlusions, header, notes, tags);
+};
+
diff --git a/ts/image-occlusion/icons.ts b/ts/image-occlusion/icons.ts
new file mode 100644
index 00000000000..b72fba9a9c0
--- /dev/null
+++ b/ts/image-occlusion/icons.ts
@@ -0,0 +1,21 @@
+// Copyright: Ankitects Pty Ltd and contributors
+// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
+
+///
+
+export { default as mdiCopy } from "@mdi/svg/svg/content-copy.svg";
+export { default as mdiCursorDefaultOutline } from "@mdi/svg/svg/cursor-default-outline.svg";
+export { default as mdiDeleteOutline } from "@mdi/svg/svg/delete-outline.svg";
+export { default as mdiEllipseOutline } from "@mdi/svg/svg/ellipse-outline.svg";
+export { default as mdiFormatColorFill } from "@mdi/svg/svg/format-color-fill.svg";
+export { default as mdiGroup } from "@mdi/svg/svg/group.svg";
+export { default as mdiZoomReset } from "@mdi/svg/svg/magnify-expand.svg";
+export { default as mdiZoomOut } from "@mdi/svg/svg/magnify-minus-outline.svg";
+export { default as mdiZoomIn } from "@mdi/svg/svg/magnify-plus-outline.svg";
+export { default as mdiMagnifyScan } from "@mdi/svg/svg/magnify-scan.svg";
+export { default as mdiPalette } from "@mdi/svg/svg/palette.svg";
+export { default as mdiRectangleOutline } from "@mdi/svg/svg/rectangle-outline.svg";
+export { default as mdiRedo } from "@mdi/svg/svg/redo.svg";
+export { default as mdiUndo } from "@mdi/svg/svg/undo.svg";
+export { default as mdiUngroup } from "@mdi/svg/svg/ungroup.svg";
+export { default as mdiVectorPolygonVariant } from "@mdi/svg/svg/vector-polygon-variant.svg";
diff --git a/ts/image-occlusion/lib.ts b/ts/image-occlusion/lib.ts
new file mode 100644
index 00000000000..5810be0c133
--- /dev/null
+++ b/ts/image-occlusion/lib.ts
@@ -0,0 +1,35 @@
+// Copyright: Ankitects Pty Ltd and contributors
+// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
+
+import type { Collection } from "../lib/proto";
+import { ImageOcclusion, imageOcclusion } from "../lib/proto";
+
+export async function getImageClozeMetadata(
+ path: string,
+): Promise {
+ return imageOcclusion.getImageClozeMetadata(
+ ImageOcclusion.ImageClozeMetadataRequest.create({
+ path,
+ }),
+ );
+}
+
+export async function addImageOcclusionNotes(
+ imagePath: string,
+ deckId: number,
+ occlusions: string,
+ header: string,
+ notes: string,
+ tags: string[],
+): Promise {
+ return imageOcclusion.addImageOcclusionNotes(
+ ImageOcclusion.AddImageOcclusionNotesRequest.create({
+ imagePath,
+ deckId,
+ occlusions,
+ header,
+ notes,
+ tags,
+ }),
+ );
+}
diff --git a/ts/image-occlusion/store.ts b/ts/image-occlusion/store.ts
new file mode 100644
index 00000000000..d3f74693e76
--- /dev/null
+++ b/ts/image-occlusion/store.ts
@@ -0,0 +1,9 @@
+// Copyright: Ankitects Pty Ltd and contributors
+// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
+
+import { writable } from "svelte/store";
+
+export const active = writable("");
+export const noteFieldsData = writable({});
+export const zoomResetValue = writable(1);
+export const tagsWritable = writable([""]);
diff --git a/ts/image-occlusion/tools/index.ts b/ts/image-occlusion/tools/index.ts
new file mode 100644
index 00000000000..1a90ac44f09
--- /dev/null
+++ b/ts/image-occlusion/tools/index.ts
@@ -0,0 +1,8 @@
+// Copyright: Ankitects Pty Ltd and contributors
+// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
+
+import { drawEllipse } from "./tool-ellipse";
+import { drawPolygon } from "./tool-polygon";
+import { drawRectangle } from "./tool-rect";
+
+export { drawEllipse, drawPolygon, drawRectangle };
diff --git a/ts/image-occlusion/tools/lib.ts b/ts/image-occlusion/tools/lib.ts
new file mode 100644
index 00000000000..ae28ee7ed02
--- /dev/null
+++ b/ts/image-occlusion/tools/lib.ts
@@ -0,0 +1,149 @@
+// 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 type { PanZoom } from "panzoom";
+import { get } from "svelte/store";
+
+import { zoomResetValue } from "../store";
+
+let _clipboard;
+
+export const stopDraw = (canvas: fabric.Canvas): void => {
+ canvas.off("mouse:down");
+ canvas.off("mouse:up");
+ canvas.off("mouse:move");
+};
+
+export const enableSelectable = (canvas: fabric.Canvas, select: boolean): void => {
+ canvas.selection = select;
+ canvas.forEachObject(function(o) {
+ o.selectable = select;
+ });
+ canvas.renderAll();
+};
+
+export const deleteItem = (canvas: fabric.Canvas): void => {
+ const active = canvas.getActiveObject();
+ if (active) {
+ canvas.remove(active);
+ if (active.type == "activeSelection") {
+ active.getObjects().forEach((x) => canvas.remove(x));
+ canvas.discardActiveObject().renderAll();
+ }
+ }
+};
+
+export const duplicateItem = (canvas: fabric.Canvas): void => {
+ copyItem(canvas);
+ pasteItem(canvas);
+};
+
+export const groupShapes = (canvas: fabric.Canvas): void => {
+ if (
+ !canvas.getActiveObject()
+ || canvas.getActiveObject().type !== "activeSelection"
+ ) {
+ return;
+ }
+
+ canvas.getActiveObject().toGroup();
+ canvas.requestRenderAll();
+};
+
+export const unGroupShapes = (canvas: fabric.Canvas): void => {
+ if (
+ !canvas.getActiveObject()
+ || canvas.getActiveObject().type !== "group"
+ ) {
+ return;
+ }
+
+ const group = canvas.getActiveObject();
+ const items = group.getObjects();
+ group._restoreObjectsState();
+ canvas.remove(group);
+
+ items.forEach((item) => {
+ canvas.add(item);
+ });
+
+ canvas.requestRenderAll();
+};
+
+export const getQuestionMaskColor = (): string => {
+ return localStorage.getItem("ques-color")
+ ? localStorage.getItem("ques-color")!
+ : "#f06";
+};
+
+export const zoomIn = (instance: PanZoom): void => {
+ instance.smoothZoom(0, 0, 1.25);
+};
+
+export const zoomOut = (instance: PanZoom): void => {
+ instance.smoothZoom(0, 0, 0.5);
+};
+
+export const zoomReset = (instance: PanZoom): void => {
+ instance.moveTo(0, 0);
+ instance.zoomAbs(0, 0, get(zoomResetValue));
+};
+
+const copyItem = (canvas: fabric.Canvas): void => {
+ if (!canvas.getActiveObject()) {
+ return;
+ }
+
+ // clone what are you copying since you
+ // may want copy and paste on different moment.
+ // and you do not want the changes happened
+ // later to reflect on the copy.
+ canvas.getActiveObject().clone(function(cloned) {
+ _clipboard = cloned;
+ });
+};
+
+const pasteItem = (canvas: fabric.Canvas): void => {
+ // clone again, so you can do multiple copies.
+ _clipboard.clone(function(clonedObj) {
+ canvas.discardActiveObject();
+
+ clonedObj.set({
+ left: clonedObj.left + 10,
+ top: clonedObj.top + 10,
+ evented: true,
+ });
+
+ if (clonedObj.type === "activeSelection") {
+ // active selection needs a reference to the canvas.
+ clonedObj.canvas = canvas;
+ clonedObj.forEachObject(function(obj) {
+ canvas.add(obj);
+ });
+
+ // this should solve the unselectability
+ clonedObj.setCoords();
+ } else {
+ canvas.add(clonedObj);
+ }
+
+ _clipboard.top += 10;
+ _clipboard.left += 10;
+ canvas.setActiveObject(clonedObj);
+ canvas.requestRenderAll();
+ });
+};
+
+export const fillShapeColor = (canvas: fabric.Canvas, color: string): void => {
+ const active = canvas.getActiveObject();
+ if (active) {
+ if (active.type === "activeSelection") {
+ active.getObjects().forEach((x) => x.set({ fill: color }));
+ canvas.renderAll();
+ } else {
+ active.set({ fill: color });
+ canvas.renderAll();
+ }
+ }
+};
diff --git a/ts/image-occlusion/tools/more-tools.ts b/ts/image-occlusion/tools/more-tools.ts
new file mode 100644
index 00000000000..874796ac957
--- /dev/null
+++ b/ts/image-occlusion/tools/more-tools.ts
@@ -0,0 +1,46 @@
+// Copyright: Ankitects Pty Ltd and contributors
+// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
+
+import { mdiCopy, mdiDeleteOutline, mdiGroup, mdiUngroup, mdiZoomIn, mdiZoomOut, mdiZoomReset } from "../icons";
+import { deleteItem, duplicateItem, groupShapes, unGroupShapes, zoomIn, zoomOut, zoomReset } from "./lib";
+
+export const cursorTools = [
+ {
+ id: 1,
+ icon: mdiDeleteOutline,
+ action: deleteItem,
+ },
+ {
+ id: 2,
+ icon: mdiGroup,
+ action: groupShapes,
+ },
+ {
+ id: 3,
+ icon: mdiUngroup,
+ action: unGroupShapes,
+ },
+ {
+ id: 4,
+ icon: mdiCopy,
+ action: duplicateItem,
+ },
+];
+
+export const zoomTools = [
+ {
+ id: 1,
+ icon: mdiZoomOut,
+ action: zoomOut,
+ },
+ {
+ id: 2,
+ icon: mdiZoomReset,
+ action: zoomReset,
+ },
+ {
+ id: 3,
+ icon: mdiZoomIn,
+ action: zoomIn,
+ },
+];
diff --git a/ts/image-occlusion/tools/tool-buttons.ts b/ts/image-occlusion/tools/tool-buttons.ts
new file mode 100644
index 00000000000..a08715bd078
--- /dev/null
+++ b/ts/image-occlusion/tools/tool-buttons.ts
@@ -0,0 +1,39 @@
+// Copyright: Ankitects Pty Ltd and contributors
+// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
+
+import {
+ mdiCursorDefaultOutline,
+ mdiEllipseOutline,
+ mdiFormatColorFill,
+ mdiMagnifyScan,
+ mdiPalette,
+ mdiRectangleOutline,
+ mdiVectorPolygonVariant,
+} from "../icons";
+
+export const tools = [
+ {
+ id: "cursor",
+ icon: mdiCursorDefaultOutline,
+ },
+ {
+ id: "draw-rectangle",
+ icon: mdiRectangleOutline,
+ },
+ {
+ id: "draw-ellipse",
+ icon: mdiEllipseOutline,
+ },
+ {
+ id: "draw-polygon",
+ icon: mdiVectorPolygonVariant,
+ },
+ {
+ id: "shape-fill-color",
+ icon: mdiFormatColorFill,
+ },
+ {
+ id: "choose-color",
+ icon: mdiPalette,
+ },
+];
diff --git a/ts/image-occlusion/tools/tool-ellipse.ts b/ts/image-occlusion/tools/tool-ellipse.ts
new file mode 100644
index 00000000000..14b04dd6678
--- /dev/null
+++ b/ts/image-occlusion/tools/tool-ellipse.ts
@@ -0,0 +1,58 @@
+// Copyright: Ankitects Pty Ltd and contributors
+// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
+
+import { fabric } from "fabric";
+
+import { getQuestionMaskColor, stopDraw } from "./lib";
+
+export const drawEllipse = (canvas: fabric.Canvas): void => {
+ let ellipse, isDown, origX, origY;
+
+ stopDraw(canvas);
+
+ canvas.on("mouse:down", function(o) {
+ isDown = true;
+
+ const pointer = canvas.getPointer(o.e);
+ origX = pointer.x;
+ origY = pointer.y;
+
+ ellipse = new fabric.Ellipse({
+ left: pointer.x,
+ top: pointer.y,
+ rx: 1,
+ ry: 1,
+ fill: getQuestionMaskColor()!,
+ originX: "center",
+ originY: "center",
+ transparentCorners: false,
+ selectable: false,
+ });
+
+ canvas.add(ellipse);
+ });
+
+ canvas.on("mouse:move", function(o) {
+ if (!isDown) return;
+
+ const pointer = canvas.getPointer(o.e);
+ ellipse.set({
+ rx: Math.abs(origX - pointer.x),
+ ry: Math.abs(origY - pointer.y),
+ });
+
+ canvas.renderAll();
+ });
+
+ canvas.on("mouse:up", function(o) {
+ isDown = false;
+
+ const pointer = canvas.getPointer(o.e);
+ const rx = Math.abs(origX - pointer.x);
+ const ry = Math.abs(origY - pointer.y);
+ if (rx < 5 || ry < 5) {
+ canvas.remove(ellipse);
+ }
+ ellipse.setCoords();
+ });
+};
diff --git a/ts/image-occlusion/tools/tool-polygon.ts b/ts/image-occlusion/tools/tool-polygon.ts
new file mode 100644
index 00000000000..c1c8a97987c
--- /dev/null
+++ b/ts/image-occlusion/tools/tool-polygon.ts
@@ -0,0 +1,186 @@
+// Copyright: Ankitects Pty Ltd and contributors
+// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
+
+import { fabric } from "fabric";
+import type { PanZoom } from "panzoom";
+
+import { getQuestionMaskColor } from "./lib";
+
+let activeLine;
+let activeShape;
+let linesList: fabric.Line = [];
+let pointsList: fabric.Circle = [];
+let drawMode = false;
+let zoomValue = 1;
+let panzoomX = 1, panzoomY = 1;
+
+export const drawPolygon = (canvas: fabric.Canvas, panzoom: PanZoom): void => {
+ zoomValue = panzoom.getTransform().scale;
+ panzoomX = panzoom.getTransform().x;
+ panzoomY = panzoom.getTransform().y;
+
+ canvas.on("mouse:down", function(options) {
+ if (options.target && options.target.id === pointsList[0].id) {
+ generatePolygon(canvas, pointsList);
+ } else {
+ addPoint(canvas, options);
+ }
+ });
+
+ canvas.on("mouse:move", function(options) {
+ if (activeLine && activeLine.class === "line") {
+ const pointer = canvas.getPointer(options.e);
+ activeLine.set({
+ x2: pointer.x,
+ y2: pointer.y,
+ });
+
+ const points = activeShape.get("points");
+ points[pointsList.length] = {
+ x: pointer.x,
+ y: pointer.y,
+ };
+
+ activeShape.set({ points });
+ }
+ canvas.renderAll();
+ });
+};
+
+const toggleDrawPolygon = (canvas: fabric.Canvas): void => {
+ drawMode = !drawMode;
+ if (drawMode) {
+ activeLine = null;
+ activeShape = null;
+ linesList = [];
+ pointsList = [];
+ drawMode = false;
+ canvas.selection = true;
+ } else {
+ drawMode = true;
+ canvas.selection = false;
+ }
+};
+
+const addPoint = (canvas: fabric.Canvas, options): void => {
+ const point = new fabric.Circle({
+ radius: 5,
+ fill: "#ffffff",
+ stroke: "#333333",
+ strokeWidth: 0.5,
+ originX: "left",
+ originY: "top",
+ left: (options.e.layerX - panzoomX) / zoomValue,
+ top: (options.e.layerY - panzoomY) / zoomValue,
+ selectable: false,
+ hasBorders: false,
+ hasControls: false,
+ objectCaching: false,
+ });
+
+ if (pointsList.length === 0) {
+ point.set({
+ fill: "red",
+ });
+ }
+
+ const linePoints = [
+ (options.e.layerX - panzoomX) / zoomValue,
+ (options.e.layerY - panzoomY) / zoomValue,
+ (options.e.layerX - panzoomX) / zoomValue,
+ (options.e.layerY - panzoomY) / zoomValue,
+ ];
+
+ const line = new fabric.Line(linePoints, {
+ strokeWidth: 2,
+ fill: "#999999",
+ stroke: "#999999",
+ originX: "left",
+ originY: "top",
+ selectable: false,
+ hasBorders: false,
+ hasControls: false,
+ evented: false,
+ objectCaching: false,
+ });
+ line.class = "line";
+
+ if (activeShape) {
+ const pointer = canvas.getPointer(options.e);
+ const points = activeShape.get("points");
+ points.push({
+ x: pointer.x,
+ y: pointer.y,
+ });
+
+ const polygon = new fabric.Polygon(points, {
+ stroke: "#333333",
+ strokeWidth: 1,
+ fill: "#cccccc",
+ opacity: 0.3,
+ selectable: false,
+ hasBorders: false,
+ hasControls: false,
+ evented: false,
+ objectCaching: false,
+ });
+
+ canvas.remove(activeShape);
+ canvas.add(polygon);
+ activeShape = polygon;
+ canvas.renderAll();
+ } else {
+ const polyPoint = [{
+ x: (options.e.layerX - panzoomX) / zoomValue,
+ y: (options.e.layerY - panzoomY) / zoomValue,
+ }];
+
+ const polygon = new fabric.Polygon(polyPoint, {
+ stroke: "#333333",
+ strokeWidth: 1,
+ fill: "#cccccc",
+ opacity: 0.3,
+ selectable: false,
+ hasBorders: false,
+ hasControls: false,
+ evented: false,
+ objectCaching: false,
+ });
+
+ activeShape = polygon;
+ canvas.add(polygon);
+ }
+
+ activeLine = line;
+ pointsList.push(point);
+ linesList.push(line);
+
+ canvas.add(line);
+ canvas.add(point);
+};
+
+const generatePolygon = (canvas: fabric.Canvas, pointsList): void => {
+ const points: { x: number; y: number }[] = [];
+ pointsList.forEach((point) => {
+ points.push({
+ x: point.left,
+ y: point.top,
+ });
+ canvas.remove(point);
+ });
+
+ linesList.forEach((line) => {
+ canvas.remove(line);
+ });
+
+ canvas.remove(activeShape).remove(activeLine);
+
+ const polygon = new fabric.Polygon(points, {
+ fill: getQuestionMaskColor(),
+ objectCaching: false,
+ moveable: false,
+ });
+
+ canvas.add(polygon);
+ toggleDrawPolygon(canvas);
+};
diff --git a/ts/image-occlusion/tools/tool-rect.ts b/ts/image-occlusion/tools/tool-rect.ts
new file mode 100644
index 00000000000..e1c17b55f89
--- /dev/null
+++ b/ts/image-occlusion/tools/tool-rect.ts
@@ -0,0 +1,73 @@
+// Copyright: Ankitects Pty Ltd and contributors
+// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
+
+import { fabric } from "fabric";
+
+import { getQuestionMaskColor, stopDraw } from "./lib";
+
+export const drawRectangle = (canvas: fabric.Canvas): void => {
+ let rect, isDown, origX, origY;
+
+ stopDraw(canvas);
+
+ canvas.on("mouse:down", function(o) {
+ isDown = true;
+
+ const pointer = canvas.getPointer(o.e);
+ origX = pointer.x;
+ origY = pointer.y;
+
+ rect = new fabric.Rect({
+ left: origX,
+ top: origY,
+ originX: "left",
+ originY: "top",
+ width: pointer.x - origX,
+ height: pointer.y - origY,
+ angle: 0,
+ fill: getQuestionMaskColor()!,
+ transparentCorners: false,
+ selectable: false,
+ });
+
+ canvas.add(rect);
+ });
+
+ canvas.on("mouse:move", function(o) {
+ if (!isDown) return;
+ const pointer = canvas.getPointer(o.e);
+
+ if (origX > pointer.x) {
+ rect.set({
+ left: Math.abs(pointer.x),
+ });
+ }
+ if (origY > pointer.y) {
+ rect.set({
+ top: Math.abs(pointer.y),
+ });
+ }
+
+ rect.set({
+ width: Math.abs(origX - pointer.x),
+ });
+ rect.set({
+ height: Math.abs(origY - pointer.y),
+ });
+
+ canvas.renderAll();
+ });
+
+ canvas.on("mouse:up", function(o) {
+ isDown = false;
+
+ const pointer = canvas.getPointer(o.e);
+ const height = Math.abs(origY - pointer.y);
+ const width = Math.abs(origX - pointer.x);
+
+ if (height < 5 && width < 5) {
+ canvas.remove(rect);
+ }
+ rect.setCoords();
+ });
+};
diff --git a/ts/image-occlusion/tools/tool-undo-redo.ts b/ts/image-occlusion/tools/tool-undo-redo.ts
new file mode 100644
index 00000000000..3c2cd0b799c
--- /dev/null
+++ b/ts/image-occlusion/tools/tool-undo-redo.ts
@@ -0,0 +1,45 @@
+// 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 { mdiRedo, mdiUndo } from "../icons";
+
+let isRedoing = false;
+let stack: fabric.Object[] = [];
+
+export const undoRedoInit = (canvas: fabric.Canvas): void => {
+ canvas.on("object:added", function() {
+ if (!isRedoing) {
+ stack = [];
+ }
+ isRedoing = false;
+ });
+};
+
+export const undoAction = (canvas: fabric.Canvas): void => {
+ if (canvas._objects.length > 0) {
+ stack.push(canvas._objects.pop());
+ canvas.renderAll();
+ }
+};
+
+export const redoAction = (canvas: fabric.Canvas): void => {
+ if (stack.length > 0) {
+ isRedoing = true;
+ canvas.add(stack.pop());
+ }
+};
+
+export const undoRedoTools = [
+ {
+ id: 1,
+ icon: mdiUndo,
+ action: undoAction,
+ },
+ {
+ id: 2,
+ icon: mdiRedo,
+ action: redoAction,
+ },
+];
diff --git a/ts/image-occlusion/tsconfig.json b/ts/image-occlusion/tsconfig.json
new file mode 100644
index 00000000000..0ca399a1610
--- /dev/null
+++ b/ts/image-occlusion/tsconfig.json
@@ -0,0 +1,15 @@
+{
+ "extends": "../tsconfig.json",
+ "include": [
+ "*",
+ "tools/*"
+ ],
+ "references": [
+ { "path": "../lib" },
+ { "path": "../sveltelib" },
+ { "path": "../components" }
+ ],
+ "compilerOptions": {
+ "types": ["jest"]
+ }
+}
diff --git a/yarn.lock b/yarn.lock
index 07782b233fc..fcedb397da9 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -620,6 +620,21 @@
dependencies:
lodash "^4.17.21"
+"@mapbox/node-pre-gyp@^1.0.0":
+ version "1.0.10"
+ resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz#8e6735ccebbb1581e5a7e652244cadc8a844d03c"
+ integrity sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA==
+ dependencies:
+ detect-libc "^2.0.0"
+ https-proxy-agent "^5.0.0"
+ make-dir "^3.1.0"
+ node-fetch "^2.6.7"
+ nopt "^5.0.0"
+ npmlog "^5.0.1"
+ rimraf "^3.0.2"
+ semver "^7.3.5"
+ tar "^6.1.11"
+
"@mdi/svg@^7.0.96":
version "7.0.96"
resolved "https://registry.yarnpkg.com/@mdi/svg/-/svg-7.0.96.tgz#c7275a318da8594337243368c6b4dca6a90154f6"
@@ -1334,6 +1349,13 @@ ajv@^8.0.1:
require-from-string "^2.0.2"
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-colors@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
@@ -1378,6 +1400,19 @@ anymatch@^3.0.3, anymatch@~3.1.2:
normalize-path "^3.0.0"
picomatch "^2.0.4"
+"aproba@^1.0.3 || ^2.0.0":
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc"
+ integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==
+
+are-we-there-yet@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz#372e0e7bd279d8e94c653aaa1f67200884bf3e1c"
+ integrity sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==
+ dependencies:
+ delegates "^1.0.0"
+ readable-stream "^3.6.0"
+
argparse@^1.0.7:
version "1.0.10"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
@@ -1507,6 +1542,11 @@ 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"
@@ -1610,6 +1650,15 @@ caniuse-lite@^1.0.30001251, caniuse-lite@^1.0.30001317, caniuse-lite@^1.0.300014
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001431.tgz#e7c59bd1bc518fae03a4656be442ce6c4887a795"
integrity sha512-zBUoFU0ZcxpvSt9IU66dXVT/3ctO1cy4y9cscs1szkPlcWb6pasYM144GqrUygUbT+k7cmUCW61cvskjcv0enQ==
+canvas@^2.8.0:
+ version "2.11.0"
+ resolved "https://registry.yarnpkg.com/canvas/-/canvas-2.11.0.tgz#7f0c3e9ae94cf469269b5d3a7963a7f3a9936434"
+ integrity sha512-bdTjFexjKJEwtIo0oRx8eD4G2yWoUOXP9lj279jmQ2zMnTQhT8C3512OKz3s+ZOaQlLbE7TuVvRDYDB3Llyy5g==
+ dependencies:
+ "@mapbox/node-pre-gyp" "^1.0.0"
+ nan "^2.17.0"
+ simple-get "^3.0.3"
+
catharsis@^0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/catharsis/-/catharsis-0.9.0.tgz#40382a168be0e6da308c277d3a2b3eb40c7d2121"
@@ -1659,6 +1708,11 @@ character-entities@^2.0.2:
optionalDependencies:
fsevents "~2.3.2"
+chownr@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece"
+ integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==
+
ci-info@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46"
@@ -1722,6 +1776,11 @@ color-name@~1.1.4:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
+color-support@^1.1.2:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2"
+ integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==
+
combined-stream@^1.0.8:
version "1.0.8"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
@@ -1739,6 +1798,11 @@ concat-map@0.0.1:
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
+console-control-strings@^1.0.0, console-control-strings@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
+ integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==
+
convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369"
@@ -2084,6 +2148,13 @@ decimal.js@^10.3.1:
resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783"
integrity sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==
+decompress-response@^4.2.0:
+ version "4.2.1"
+ resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-4.2.1.tgz#414023cc7a302da25ce2ec82d0d5238ccafd8986"
+ integrity sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==
+ dependencies:
+ mimic-response "^2.0.0"
+
dedent-js@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/dedent-js/-/dedent-js-1.0.1.tgz#bee5fb7c9e727d85dffa24590d10ec1ab1255305"
@@ -2123,11 +2194,21 @@ delayed-stream@~1.0.0:
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
+delegates@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
+ integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==
+
detect-indent@^6.0.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.1.0.tgz#592485ebbbf6b3b1ab2be175c8393d04ca0d57e6"
integrity sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==
+detect-libc@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.1.tgz#e1897aa88fa6ad197862937fbc0441ef352ee0cd"
+ integrity sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==
+
detect-newline@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651"
@@ -2675,6 +2756,14 @@ expect@^28.0.0-alpha.7:
jest-matcher-utils "^28.0.0-alpha.7"
jest-message-util "^28.0.0-alpha.7"
+fabric@^5.3.0:
+ version "5.3.0"
+ resolved "https://registry.yarnpkg.com/fabric/-/fabric-5.3.0.tgz#199297b6409e3a6279c16c1166da2b2a9e3e8b9b"
+ integrity sha512-AVayKuzWoXM5cTn7iD3yNWBlfEa8r1tHaOe2g8NsZrmWKAHjryTxT/j6f9ncRfOWOF0I1Ci1AId3y78cC+GExQ==
+ optionalDependencies:
+ canvas "^2.8.0"
+ jsdom "^19.0.0"
+
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
@@ -2797,6 +2886,13 @@ fs-extra@^7.0.1:
jsonfile "^4.0.0"
universalify "^0.1.0"
+fs-minipass@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb"
+ integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==
+ dependencies:
+ minipass "^3.0.0"
+
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
@@ -2822,6 +2918,21 @@ fuse.js@^6.6.2:
resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-6.6.2.tgz#fe463fed4b98c0226ac3da2856a415576dc9a111"
integrity sha512-cJaJkxCCxC8qIIcPBF9yGxY0W/tVZS3uEISDxhYIdtk8OL93pe+6Zj7LjCqVV4dzbqcriOZ+kQ/NE4RXZHsIGA==
+gauge@^3.0.0:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/gauge/-/gauge-3.0.2.tgz#03bf4441c044383908bcfa0656ad91803259b395"
+ integrity sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==
+ dependencies:
+ aproba "^1.0.3 || ^2.0.0"
+ color-support "^1.1.2"
+ console-control-strings "^1.0.0"
+ has-unicode "^2.0.1"
+ object-assign "^4.1.1"
+ signal-exit "^3.0.0"
+ string-width "^4.2.3"
+ strip-ansi "^6.0.1"
+ wide-align "^1.1.2"
+
gemoji@^7.1.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/gemoji/-/gemoji-7.1.0.tgz#165403777681a9690d649aabd104da037bdd7739"
@@ -2960,6 +3071,11 @@ has-tostringtag@^1.0.0:
dependencies:
has-symbols "^1.0.2"
+has-unicode@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
+ integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==
+
has@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
@@ -3059,7 +3175,7 @@ inflight@^1.0.4:
once "^1.3.0"
wrappy "1"
-inherits@2:
+inherits@2, inherits@^2.0.3:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@@ -3941,7 +4057,7 @@ magic-string@^0.25.7:
dependencies:
sourcemap-codec "^1.4.8"
-make-dir@^3.0.0:
+make-dir@^3.0.0, make-dir@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==
@@ -4026,6 +4142,11 @@ mimic-fn@^2.1.0:
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
+mimic-response@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.1.0.tgz#d13763d35f613d09ec37ebb30bac0469c0ee8f43"
+ integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==
+
min-indent@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
@@ -4055,6 +4176,26 @@ minimist@^1.2.5, minimist@^1.2.6:
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
+minipass@^3.0.0:
+ version "3.3.6"
+ resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a"
+ integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==
+ dependencies:
+ yallist "^4.0.0"
+
+minipass@^4.0.0:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/minipass/-/minipass-4.0.3.tgz#00bfbaf1e16e35e804f4aa31a7c1f6b8d9f0ee72"
+ integrity sha512-OW2r4sQ0sI+z5ckEt5c1Tri4xTgZwYDxpE54eqWlQloQRoWtXjqt9udJ5Z4dSv7wK+nfFI7FRXyCpBSft+gpFw==
+
+minizlib@^2.1.1:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931"
+ integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==
+ dependencies:
+ minipass "^3.0.0"
+ yallist "^4.0.0"
+
mkdirp@^0.5.1:
version "0.5.6"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6"
@@ -4062,7 +4203,7 @@ mkdirp@^0.5.1:
dependencies:
minimist "^1.2.6"
-mkdirp@^1.0.4:
+mkdirp@^1.0.3, mkdirp@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
@@ -4087,11 +4228,21 @@ ms@^2.1.1:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
+nan@^2.17.0:
+ version "2.17.0"
+ resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb"
+ integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==
+
natural-compare@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=
+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==
+
nice-try@^1.0.4:
version "1.0.5"
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
@@ -4105,6 +4256,13 @@ no-case@^3.0.4:
lower-case "^2.0.2"
tslib "^2.0.3"
+node-fetch@^2.6.7:
+ version "2.6.9"
+ resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.9.tgz#7c7f744b5cc6eb5fd404e0c7a9fec630a55657e6"
+ integrity sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==
+ dependencies:
+ whatwg-url "^5.0.0"
+
node-int64@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
@@ -4149,11 +4307,26 @@ npm-run-path@^4.0.1:
dependencies:
path-key "^3.0.0"
+npmlog@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-5.0.1.tgz#f06678e80e29419ad67ab964e0fa69959c1eb8b0"
+ integrity sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==
+ dependencies:
+ are-we-there-yet "^2.0.0"
+ console-control-strings "^1.1.0"
+ gauge "^3.0.0"
+ set-blocking "^2.0.0"
+
nwsapi@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7"
integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==
+object-assign@^4.1.1:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
+ integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
+
object-inspect@^1.11.0, object-inspect@^1.9.0:
version "1.12.0"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.0.tgz#6e2c120e868fd1fd18cb4f18c31741d0d6e776f0"
@@ -4183,7 +4356,7 @@ object.values@^1.1.5:
define-properties "^1.1.3"
es-abstract "^1.19.1"
-once@^1.3.0:
+once@^1.3.0, once@^1.3.1:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==
@@ -4286,6 +4459,15 @@ 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"
@@ -4526,6 +4708,15 @@ read-package-json@^4.0.0:
normalize-package-data "^3.0.0"
npm-normalize-package-bin "^1.0.0"
+readable-stream@^3.6.0:
+ version "3.6.0"
+ resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
+ integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
+ dependencies:
+ inherits "^2.0.3"
+ string_decoder "^1.1.1"
+ util-deprecate "^1.0.1"
+
readdir-scoped-modules@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz#8d45407b4f870a0dcaebc0e28670d18e74514309"
@@ -4653,6 +4844,11 @@ safe-buffer@~5.1.1:
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
+safe-buffer@~5.2.0:
+ version "5.2.1"
+ resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
+ integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
+
"safer-buffer@>= 2.1.2 < 3.0.0":
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
@@ -4715,6 +4911,11 @@ semver@^7.1.2:
dependencies:
lru-cache "^6.0.0"
+set-blocking@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
+ integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==
+
shebang-command@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
@@ -4748,11 +4949,25 @@ side-channel@^1.0.4:
get-intrinsic "^1.0.2"
object-inspect "^1.9.0"
-signal-exit@^3.0.3, signal-exit@^3.0.7:
+signal-exit@^3.0.0, signal-exit@^3.0.3, signal-exit@^3.0.7:
version "3.0.7"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
+simple-concat@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f"
+ integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==
+
+simple-get@^3.0.3:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-3.1.1.tgz#cc7ba77cfbe761036fbfce3d021af25fc5584d55"
+ integrity sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==
+ dependencies:
+ decompress-response "^4.2.0"
+ once "^1.3.1"
+ simple-concat "^1.0.0"
+
sisteransi@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
@@ -4894,7 +5109,7 @@ string-length@^4.0.1:
char-regex "^1.0.2"
strip-ansi "^6.0.0"
-string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
+"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -4919,6 +5134,13 @@ string.prototype.trimstart@^1.0.4:
call-bind "^1.0.2"
define-properties "^1.1.3"
+string_decoder@^1.1.1:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
+ integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
+ dependencies:
+ safe-buffer "~5.2.0"
+
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
@@ -5048,6 +5270,18 @@ table@^6.0.9:
string-width "^4.2.3"
strip-ansi "^6.0.1"
+tar@^6.1.11:
+ version "6.1.13"
+ resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.13.tgz#46e22529000f612180601a6fe0680e7da508847b"
+ integrity sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw==
+ dependencies:
+ chownr "^2.0.0"
+ fs-minipass "^2.0.0"
+ minipass "^4.0.0"
+ minizlib "^2.1.1"
+ mkdirp "^1.0.3"
+ yallist "^4.0.0"
+
terminal-link@^2.0.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994"
@@ -5122,6 +5356,11 @@ tr46@^3.0.0:
dependencies:
punycode "^2.1.1"
+tr46@~0.0.3:
+ version "0.0.3"
+ resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
+ integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
+
treeify@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/treeify/-/treeify-1.1.0.tgz#4e31c6a463accd0943879f30667c4fdaff411bb8"
@@ -5246,6 +5485,11 @@ uri-js@^4.2.2:
dependencies:
punycode "^2.1.0"
+util-deprecate@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
+ integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
+
v8-compile-cache@^2.0.3:
version "2.3.0"
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"
@@ -5289,6 +5533,11 @@ walker@^1.0.7:
dependencies:
makeerror "1.0.12"
+webidl-conversions@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
+ integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==
+
webidl-conversions@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a"
@@ -5314,6 +5563,19 @@ whatwg-url@^10.0.0:
tr46 "^3.0.0"
webidl-conversions "^7.0.0"
+whatwg-url@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
+ integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==
+ dependencies:
+ tr46 "~0.0.3"
+ webidl-conversions "^3.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"
@@ -5339,6 +5601,13 @@ which@^2.0.1:
dependencies:
isexe "^2.0.0"
+wide-align@^1.1.2:
+ version "1.1.5"
+ resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3"
+ integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==
+ dependencies:
+ string-width "^1.0.2 || 2 || 3 || 4"
+
word-wrap@^1.2.3, word-wrap@~1.2.3:
version "1.2.3"
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"