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"