+
{
+ field.textareaValue = field.divValue;
+ }}
+ contenteditable
+ />
+
+
+
+
+ {/each}
+
+
+
+
+
+
diff --git a/ts/image-occlusion/SideToolbar.svelte b/ts/image-occlusion/SideToolbar.svelte
new file mode 100644
index 000000000..3701e9d43
--- /dev/null
+++ b/ts/image-occlusion/SideToolbar.svelte
@@ -0,0 +1,97 @@
+
+
+
+
+
+
+ {#each tools as tool}
+ {
+ setActive(tool.id);
+ }}>{@html tool.icon}
+ {/each}
+
+
+
diff --git a/ts/image-occlusion/StickyFooter.svelte b/ts/image-occlusion/StickyFooter.svelte
new file mode 100644
index 000000000..be36d9416
--- /dev/null
+++ b/ts/image-occlusion/StickyFooter.svelte
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
diff --git a/ts/image-occlusion/Tags.svelte b/ts/image-occlusion/Tags.svelte
new file mode 100644
index 000000000..d7df5e585
--- /dev/null
+++ b/ts/image-occlusion/Tags.svelte
@@ -0,0 +1,28 @@
+
+
+
+
+ {
+ globalTags = detail.tags;
+ tagsWritable.set(globalTags);
+ }}
+ keyCombination={"Control+T"}
+ />
+
+
+
diff --git a/ts/image-occlusion/Toast.svelte b/ts/image-occlusion/Toast.svelte
new file mode 100644
index 000000000..df83be331
--- /dev/null
+++ b/ts/image-occlusion/Toast.svelte
@@ -0,0 +1,61 @@
+
+
+
+{#if showToast}
+
+
+ {message}
+
+ {@html mdiClose}
+
+
+{/if}
+
+
diff --git a/ts/image-occlusion/TopToolbar.svelte b/ts/image-occlusion/TopToolbar.svelte
new file mode 100644
index 000000000..5ef1613b5
--- /dev/null
+++ b/ts/image-occlusion/TopToolbar.svelte
@@ -0,0 +1,194 @@
+
+
+
+
+
+
+ {#each alignTools as alignTool}
+ {
+ alignTool.action(canvas);
+ }}
+ >
+ {@html alignTool.icon}
+
+ {/each}
+
+
+
diff --git a/ts/image-occlusion/generate.ts b/ts/image-occlusion/generate.ts
new file mode 100644
index 000000000..d9de97f34
--- /dev/null
+++ b/ts/image-occlusion/generate.ts
@@ -0,0 +1,166 @@
+// Copyright: Ankitects Pty Ltd and contributors
+// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
+
+import * as tr from "@tslib/ftl";
+import { fabric } from "fabric";
+import { get } from "svelte/store";
+
+import type { Collection } from "../lib/proto";
+import { addImageOcclusionNote, updateImageOcclusionNote } from "./lib";
+import { notesDataStore, tagsWritable } from "./store";
+import Toast from "./Toast.svelte";
+import { makeMaskTransparent } from "./tools/lib";
+
+const divData = [
+ "height",
+ "left",
+ "top",
+ "type",
+ "width",
+];
+
+// Defines the number of fraction digits to use when serializing object values
+fabric.Object.NUM_FRACTION_DIGITS = 2;
+
+export function generate(hideInactive: boolean): { occlusionCloze: string; noteCount: number } {
+ const canvas = globalThis.canvas;
+ const canvasObjects = canvas.getObjects();
+ if (canvasObjects.length < 1) {
+ return { occlusionCloze: "", noteCount: 0 };
+ }
+
+ let occlusionCloze = "";
+ let clozeData = "";
+ let noteCount = 0;
+
+ makeMaskTransparent(canvas, false);
+
+ canvasObjects.forEach((object, index) => {
+ const obJson = object.toJSON();
+ noteCount++;
+ if (obJson.type === "group") {
+ clozeData += getGroupCloze(object, index, hideInactive);
+ } else {
+ clozeData += getCloze(object, index, null, hideInactive);
+ }
+ });
+
+ occlusionCloze += clozeData;
+ return { occlusionCloze, noteCount };
+}
+
+const getCloze = (object, index, relativePos, hideInactive): string => {
+ const obJson = object.toJSON();
+ let clozeData = "";
+
+ // generate cloze data in form of
+ // {{c1::image-occlusion:rect:top=100:left=100:width=100:height=100}}
+ Object.keys(obJson).forEach(function(key) {
+ if (divData.includes(key)) {
+ if (key === "type") {
+ clozeData += `:${obJson[key]}`;
+
+ if (obJson[key] === "ellipse") {
+ clozeData += `:rx=${obJson.rx.toFixed(2)}:ry=${obJson.ry.toFixed(2)}`;
+ }
+
+ if (obJson[key] === "polygon") {
+ const points = obJson.points;
+ let pnts = "";
+ points.forEach((point: { x: number; y: number }) => {
+ pnts += point.x.toFixed(2) + "," + point.y.toFixed(2) + " ";
+ });
+ clozeData += `:points=${pnts.trim()}`;
+ }
+ } else if (relativePos && key === "top") {
+ clozeData += `:top=${relativePos.top}`;
+ } else if (relativePos && key === "left") {
+ clozeData += `:left=${relativePos.left}`;
+ } else {
+ clozeData += `:${key}=${obJson[key]}`;
+ }
+ }
+ });
+
+ clozeData += `:hideinactive=${hideInactive}`;
+ clozeData = `{{c${index + 1}::image-occlusion${clozeData}}}
`;
+ return clozeData;
+};
+
+const getGroupCloze = (group, index, hideInactive): string => {
+ let clozeData = "";
+ const objects = group._objects;
+
+ objects.forEach((object) => {
+ const { top, left } = getObjectPositionInGroup(group, object);
+ clozeData += getCloze(object, index, { top, left }, hideInactive);
+ });
+
+ return clozeData;
+};
+
+const getObjectPositionInGroup = (group, object): { top: number; left: number } => {
+ 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 };
+};
+
+export const saveImageNotes = async function(
+ imagePath: string,
+ noteId: number,
+ hideInactive: boolean,
+): Promise
{
+ const { occlusionCloze, noteCount } = generate(hideInactive);
+ if (noteCount === 0) {
+ return;
+ }
+
+ const fieldsData: { id: string; title: string; divValue: string; textareaValue: string }[] = get(notesDataStore);
+ const tags = get(tagsWritable);
+ let header = fieldsData[0].textareaValue;
+ let backExtra = fieldsData[1].textareaValue;
+
+ header = header ? `${header}
` : "";
+ backExtra = header ? `${backExtra}
` : "";
+
+ if (noteId) {
+ const result = await updateImageOcclusionNote(
+ noteId,
+ occlusionCloze,
+ header,
+ backExtra,
+ tags,
+ );
+ showResult(noteId, result, noteCount);
+ } else {
+ const result = await addImageOcclusionNote(
+ imagePath,
+ occlusionCloze,
+ header,
+ backExtra,
+ tags,
+ );
+ showResult(noteId, result, noteCount);
+ }
+};
+
+// show toast message
+const showResult = (noteId: number, result: Collection.OpChanges, count: number) => {
+ const toastComponent = new Toast({
+ target: document.body,
+ props: {
+ message: "",
+ type: "error",
+ },
+ });
+
+ if (result.note) {
+ const msg = noteId ? tr.browsingCardsUpdated({ count: count }) : tr.importingCardsAdded({ count: count });
+ toastComponent.$set({ message: msg, type: "success", showToast: true });
+ } else {
+ const msg = tr.notetypesErrorGeneratingCloze();
+ toastComponent.$set({ message: msg, showToast: true });
+ }
+};
diff --git a/ts/image-occlusion/icons.ts b/ts/image-occlusion/icons.ts
new file mode 100644
index 000000000..52456ac01
--- /dev/null
+++ b/ts/image-occlusion/icons.ts
@@ -0,0 +1,33 @@
+// Copyright: Ankitects Pty Ltd and contributors
+// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
+
+///
+
+export { default as mdiAlignHorizontalCenter } from "@mdi/svg/svg/align-horizontal-center.svg";
+export { default as mdiAlignHorizontalLeft } from "@mdi/svg/svg/align-horizontal-left.svg";
+export { default as mdiAlignHorizontalRight } from "@mdi/svg/svg/align-horizontal-right.svg";
+export { default as mdiAlignVerticalBottom } from "@mdi/svg/svg/align-vertical-bottom.svg";
+export { default as mdiAlignVerticalCenter } from "@mdi/svg/svg/align-vertical-center.svg";
+export { default as mdiAlignVerticalTop } from "@mdi/svg/svg/align-vertical-top.svg";
+export { default as mdiClose } from "@mdi/svg/svg/close.svg";
+export { default as mdiCodeTags } from "@mdi/svg/svg/code-tags.svg";
+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 mdiEye } from "@mdi/svg/svg/eye.svg";
+export { default as mdiFormatAlignCenter } from "@mdi/svg/svg/format-align-center.svg";
+export { default as mdiFormatBold } from "@mdi/svg/svg/format-bold.svg";
+export { default as mdiFormatItalic } from "@mdi/svg/svg/format-italic.svg";
+export { default as mdiFormatUnderline } from "@mdi/svg/svg/format-underline.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 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 mdiUnfoldMoreHorizontal } from "@mdi/svg/svg/unfold-more-horizontal.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/image-occlusion-base.scss b/ts/image-occlusion/image-occlusion-base.scss
new file mode 100644
index 000000000..900c430e4
--- /dev/null
+++ b/ts/image-occlusion/image-occlusion-base.scss
@@ -0,0 +1,19 @@
+@use "sass/vars";
+@use "sass/bootstrap-dark";
+
+@import "sass/base";
+
+@import "bootstrap/scss/alert";
+@import "bootstrap/scss/buttons";
+@import "bootstrap/scss/button-group";
+@import "bootstrap/scss/close";
+@import "bootstrap/scss/grid";
+@import "sass/bootstrap-forms";
+
+.night-mode {
+ @include bootstrap-dark.night-mode;
+}
+
+html {
+ overflow: hidden;
+}
diff --git a/ts/image-occlusion/index.ts b/ts/image-occlusion/index.ts
new file mode 100644
index 000000000..a178c97de
--- /dev/null
+++ b/ts/image-occlusion/index.ts
@@ -0,0 +1,56 @@
+// Copyright: Ankitects Pty Ltd and contributors
+// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
+
+import "./image-occlusion-base.scss";
+
+import { ModuleName, setupI18n } from "@tslib/i18n";
+
+import { checkNightMode } from "../lib/nightmode";
+import ImageOcclusionPage from "./ImageOcclusionPage.svelte";
+
+const i18n = setupI18n({
+ modules: [
+ ModuleName.IMPORTING,
+ ModuleName.DECKS,
+ ModuleName.EDITING,
+ ModuleName.NOTETYPES,
+ ModuleName.ACTIONS,
+ ModuleName.BROWSING,
+ ],
+});
+
+export async function setupImageOcclusion(path: string): Promise {
+ checkNightMode();
+ await i18n;
+
+ return new ImageOcclusionPage({
+ target: document.body,
+ props: {
+ path: path,
+ noteId: null,
+ },
+ });
+}
+
+export async function setupImageOcclusionForEdit(noteId: number): Promise {
+ checkNightMode();
+ await i18n;
+
+ return new ImageOcclusionPage({
+ target: document.body,
+ props: {
+ path: null,
+ noteId: noteId,
+ },
+ });
+}
+
+if (window.location.hash.startsWith("#test-")) {
+ const path = window.location.hash.replace("#test-", "");
+ setupImageOcclusion(path);
+}
+
+if (window.location.hash.startsWith("#testforedit-")) {
+ const noteId = parseInt(window.location.hash.replace("#testforedit-", ""));
+ setupImageOcclusionForEdit(noteId);
+}
diff --git a/ts/image-occlusion/lib.ts b/ts/image-occlusion/lib.ts
new file mode 100644
index 000000000..43e94123b
--- /dev/null
+++ b/ts/image-occlusion/lib.ts
@@ -0,0 +1,61 @@
+// 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 getImageForOcclusion(
+ path: string,
+): Promise {
+ return imageOcclusion.getImageForOcclusion(
+ ImageOcclusion.GetImageForOcclusionRequest.create({
+ path,
+ }),
+ );
+}
+
+export async function addImageOcclusionNote(
+ imagePath: string,
+ occlusions: string,
+ header: string,
+ backExtra: string,
+ tags: string[],
+): Promise {
+ return imageOcclusion.addImageOcclusionNote(
+ ImageOcclusion.AddImageOcclusionNoteRequest.create({
+ imagePath,
+ occlusions,
+ header,
+ backExtra,
+ tags,
+ }),
+ );
+}
+
+export async function getImageClozeNote(
+ noteId: number,
+): Promise {
+ return imageOcclusion.getImageClozeNote(
+ ImageOcclusion.GetImageOcclusionNoteRequest.create({
+ noteId,
+ }),
+ );
+}
+
+export async function updateImageOcclusionNote(
+ noteId: number,
+ occlusions: string,
+ header: string,
+ backExtra: string,
+ tags: string[],
+): Promise {
+ return imageOcclusion.updateImageOcclusionNote(
+ ImageOcclusion.UpdateImageOcclusionNoteRequest.create({
+ noteId,
+ occlusions,
+ header,
+ backExtra,
+ tags,
+ }),
+ );
+}
diff --git a/ts/image-occlusion/mask-editor.ts b/ts/image-occlusion/mask-editor.ts
new file mode 100644
index 000000000..30136f3a5
--- /dev/null
+++ b/ts/image-occlusion/mask-editor.ts
@@ -0,0 +1,143 @@
+// Copyright: Ankitects Pty Ltd and contributors
+// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
+
+import * as tr from "@tslib/ftl";
+import type { ImageOcclusion } from "@tslib/proto";
+import { fabric } from "fabric";
+import type { PanZoom } from "panzoom";
+import protobuf from "protobufjs";
+import { get } from "svelte/store";
+
+import { getImageClozeNote, getImageForOcclusion } from "./lib";
+import { notesDataStore, tagsWritable, zoomResetValue } from "./store";
+import Toast from "./Toast.svelte";
+import { enableSelectable, moveShapeToCanvasBoundaries } from "./tools/lib";
+import { generateShapeFromCloze } from "./tools/shape-generate";
+import { undoRedoInit } from "./tools/tool-undo-redo";
+
+export const setupMaskEditor = async (path: string, instance: PanZoom): Promise => {
+ const imageData = await getImageForOcclusion(path!);
+ const canvas = initCanvas();
+
+ // get image width and height
+ const image = document.getElementById("image") as HTMLImageElement;
+ image.src = getImageData(imageData.data!);
+ image.onload = function() {
+ const size = limitSize({ width: image.width, height: image.height });
+ canvas.setWidth(size.width);
+ canvas.setHeight(size.height);
+ image.height = size.height;
+ image.width = size.width;
+ setCanvasZoomRatio(canvas, instance);
+ };
+
+ return canvas;
+};
+
+export const setupMaskEditorForEdit = async (noteId: number, instance: PanZoom): Promise => {
+ const clozeNoteResponse: ImageOcclusion.ImageClozeNoteResponse = await getImageClozeNote(noteId);
+ if (clozeNoteResponse.error) {
+ new Toast({
+ target: document.body,
+ props: {
+ message: tr.notetypesErrorGettingImagecloze(),
+ type: "error",
+ },
+ }).$set({ showToast: true });
+ return;
+ }
+
+ const clozeNote = clozeNoteResponse.note!;
+ const canvas = initCanvas();
+
+ // get image width and height
+ const image = document.getElementById("image") as HTMLImageElement;
+ image.src = getImageData(clozeNote.imageData!);
+ image.onload = function() {
+ const size = limitSize({ width: image.width, height: image.height });
+ canvas.setWidth(size.width);
+ canvas.setHeight(size.height);
+ image.height = size.height;
+ image.width = size.width;
+
+ setCanvasZoomRatio(canvas, instance);
+ generateShapeFromCloze(canvas, clozeNote.occlusions);
+ enableSelectable(canvas, true);
+ addClozeNotesToTextEditor(clozeNote.header, clozeNote.backExtra, clozeNote.tags);
+ };
+
+ return canvas;
+};
+
+const initCanvas = (): fabric.Canvas => {
+ const canvas = new fabric.Canvas("canvas");
+ tagsWritable.set([]);
+ globalThis.canvas = canvas;
+ // enables uniform scaling by default without the need for the Shift key
+ canvas.uniformScaling = false;
+ canvas.uniScaleKey = "none";
+ moveShapeToCanvasBoundaries(canvas);
+ undoRedoInit(canvas);
+ return canvas;
+};
+
+const getImageData = (imageData): string => {
+ const b64encoded = protobuf.util.base64.encode(
+ imageData,
+ 0,
+ imageData.length,
+ );
+ return "data:image/png;base64," + b64encoded;
+};
+
+const setCanvasZoomRatio = (
+ canvas: fabric.Canvas,
+ instance: PanZoom,
+): void => {
+ const zoomRatioW = (innerWidth - 60) / canvas.width!;
+ const zoomRatioH = (innerHeight - 100) / canvas.height!;
+ const zoomRatio = zoomRatioW < zoomRatioH ? zoomRatioW : zoomRatioH;
+ zoomResetValue.set(zoomRatio);
+ instance.smoothZoom(0, 0, zoomRatio);
+};
+
+const addClozeNotesToTextEditor = (header: string, backExtra: string, tags: string[]) => {
+ const noteFieldsData: { id: string; title: string; divValue: string; textareaValue: string }[] = get(
+ notesDataStore,
+ );
+ noteFieldsData[0].divValue = header;
+ noteFieldsData[1].divValue = backExtra;
+ noteFieldsData[0].textareaValue = header;
+ noteFieldsData[1].textareaValue = backExtra;
+ tagsWritable.set(tags);
+
+ noteFieldsData.forEach((note) => {
+ const divId = `${note.id}--div`;
+ const textAreaId = `${note.id}--textarea`;
+ const divElement = document.getElementById(divId)!;
+ const textAreaElement = document.getElementById(textAreaId)! as HTMLTextAreaElement;
+ divElement.innerHTML = note.divValue;
+ textAreaElement.value = note.textareaValue;
+ });
+};
+
+/**
+ * Fix for safari browser,
+ * Canvas area exceeds the maximum limit (width * height > 16777216),
+ * Following function also added in reviewer ts,
+ * so update both, if it changes
+ */
+const limitSize = (size: { width: number; height: number }): { width: number; height: number; scalar: number } => {
+ const maximumPixels = 1000000;
+ const { width, height } = size;
+
+ const requiredPixels = width * height;
+ if (requiredPixels <= maximumPixels) return { width, height, scalar: 1 };
+
+ const scalar = Math.sqrt(maximumPixels) / Math.sqrt(requiredPixels);
+ return {
+ width: Math.floor(width * scalar),
+ height: Math.floor(height * scalar),
+ scalar: scalar,
+ };
+};
diff --git a/ts/image-occlusion/notes-toolbar/MoreTools.svelte b/ts/image-occlusion/notes-toolbar/MoreTools.svelte
new file mode 100644
index 000000000..ce1b56942
--- /dev/null
+++ b/ts/image-occlusion/notes-toolbar/MoreTools.svelte
@@ -0,0 +1,29 @@
+
+
+
+{#each moreTools as tool}
+ tool.action()}
+ tooltip={tool.title}>{@html tool.icon}
+{/each}
diff --git a/ts/image-occlusion/notes-toolbar/NotesToolbar.svelte b/ts/image-occlusion/notes-toolbar/NotesToolbar.svelte
new file mode 100644
index 000000000..e5fb82f0f
--- /dev/null
+++ b/ts/image-occlusion/notes-toolbar/NotesToolbar.svelte
@@ -0,0 +1,13 @@
+
+
+
+
+
diff --git a/ts/image-occlusion/notes-toolbar/TextFormatting.svelte b/ts/image-occlusion/notes-toolbar/TextFormatting.svelte
new file mode 100644
index 000000000..333db6d6b
--- /dev/null
+++ b/ts/image-occlusion/notes-toolbar/TextFormatting.svelte
@@ -0,0 +1,58 @@
+
+
+
+{#each textFormatting as tool}
+ {
+ // setActiveTool(tool);
+ textFormat(tool);
+ }}>{@html tool.icon}
+{/each}
+
+
diff --git a/ts/image-occlusion/notes-toolbar/index.ts b/ts/image-occlusion/notes-toolbar/index.ts
new file mode 100644
index 000000000..9a2d2696e
--- /dev/null
+++ b/ts/image-occlusion/notes-toolbar/index.ts
@@ -0,0 +1,6 @@
+// Copyright: Ankitects Pty Ltd and contributors
+// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
+
+import NotesToolbar from "./NotesToolbar.svelte";
+
+export default NotesToolbar;
diff --git a/ts/image-occlusion/notes-toolbar/lib.ts b/ts/image-occlusion/notes-toolbar/lib.ts
new file mode 100644
index 000000000..de30bdf5c
--- /dev/null
+++ b/ts/image-occlusion/notes-toolbar/lib.ts
@@ -0,0 +1,25 @@
+// Copyright: Ankitects Pty Ltd and contributors
+// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
+
+export const changePreviewHTMLView = (): void => {
+ const activeElement = document.activeElement!;
+ if (!activeElement || !activeElement.id.includes("--")) {
+ return;
+ }
+
+ const noteId = activeElement.id.split("--")[0];
+ const divId = `${noteId}--div`;
+ const textAreaId = `${noteId}--textarea`;
+ const divElement = document.getElementById(divId)!;
+ const textAreaElement = document.getElementById(textAreaId)! as HTMLTextAreaElement;
+
+ if (divElement.style.display == "none") {
+ divElement.style.display = "block";
+ textAreaElement.style.display = "none";
+ divElement.focus();
+ } else {
+ divElement.style.display = "none";
+ textAreaElement.style.display = "block";
+ textAreaElement.focus();
+ }
+};
diff --git a/ts/image-occlusion/store.ts b/ts/image-occlusion/store.ts
new file mode 100644
index 000000000..ce9b3be7d
--- /dev/null
+++ b/ts/image-occlusion/store.ts
@@ -0,0 +1,11 @@
+// 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";
+
+// it stores note's data for generate.ts, when function generate() is called it will be used to generate the note
+export const notesDataStore = writable({ id: "", title: "", divValue: "", textareaValue: "" }[0]);
+// it stores the value of zoom ratio for canvas
+export const zoomResetValue = writable(1);
+// it stores the tags for the note in note editor
+export const tagsWritable = writable([""]);
diff --git a/ts/image-occlusion/tools/index.ts b/ts/image-occlusion/tools/index.ts
new file mode 100644
index 000000000..1a90ac44f
--- /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 000000000..7b84d9484
--- /dev/null
+++ b/ts/image-occlusion/tools/lib.ts
@@ -0,0 +1,208 @@
+// 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";
+
+export const shapeMaskColor = "#ffeba2";
+export const borderColor = "#212121";
+
+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 => {
+ if (!canvas.getActiveObject()) {
+ return;
+ }
+ 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 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.smoothZoomAbs(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 makeMaskTransparent = (canvas: fabric.Canvas, opacity = false): void => {
+ const objects = canvas.getObjects();
+ objects.forEach((object) => {
+ object.set({
+ opacity: opacity ? 0.4 : 1,
+ transparentCorners: false,
+ });
+ });
+ canvas.renderAll();
+};
+
+export const moveShapeToCanvasBoundaries = (canvas: fabric.Canvas): void => {
+ canvas.on("object:modified", function(o) {
+ const activeObject = o.target;
+ if (!activeObject) {
+ return;
+ }
+ if (activeObject.type === "activeSelection" || activeObject.type === "rect") {
+ modifiedSelection(canvas, activeObject);
+ }
+ if (activeObject.type === "ellipse") {
+ modifiedEllipse(canvas, activeObject);
+ }
+ });
+};
+
+const modifiedSelection = (canvas: fabric.Canvas, object: fabric.Object): void => {
+ const newWidth = object.width * object.scaleX;
+ const newHeight = object.height * object.scaleY;
+
+ object.set({
+ width: newWidth,
+ height: newHeight,
+ scaleX: 1,
+ scaleY: 1,
+ });
+ setShapePosition(canvas, object);
+};
+
+const modifiedEllipse = (canvas: fabric.Canvas, object: fabric.Object): void => {
+ const newRx = object.rx * object.scaleX;
+ const newRy = object.ry * object.scaleY;
+ const newWidth = object.width * object.scaleX;
+ const newHeight = object.height * object.scaleY;
+
+ object.set({
+ rx: newRx,
+ ry: newRy,
+ width: newWidth,
+ height: newHeight,
+ scaleX: 1,
+ scaleY: 1,
+ });
+ setShapePosition(canvas, object);
+};
+
+const setShapePosition = (canvas: fabric.Canvas, object: fabric.Object): void => {
+ if (object.left < 0) {
+ object.set({ left: 0 });
+ }
+ if (object.top < 0) {
+ object.set({ top: 0 });
+ }
+ if (object.left + object.width + object.strokeWidth > canvas.width) {
+ object.set({ left: canvas.width - object.width });
+ }
+ if (object.top + object.height + object.strokeWidth > canvas.height) {
+ object.set({ top: canvas.height - object.height });
+ }
+ object.setCoords();
+};
diff --git a/ts/image-occlusion/tools/more-tools.ts b/ts/image-occlusion/tools/more-tools.ts
new file mode 100644
index 000000000..c54c82636
--- /dev/null
+++ b/ts/image-occlusion/tools/more-tools.ts
@@ -0,0 +1,104 @@
+// Copyright: Ankitects Pty Ltd and contributors
+// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
+
+import {
+ mdiAlignHorizontalCenter,
+ mdiAlignHorizontalLeft,
+ mdiAlignHorizontalRight,
+ mdiAlignVerticalBottom,
+ mdiAlignVerticalCenter,
+ mdiAlignVerticalTop,
+ mdiCopy,
+ mdiDeleteOutline,
+ mdiGroup,
+ mdiUngroup,
+ mdiZoomIn,
+ mdiZoomOut,
+ mdiZoomReset,
+} from "../icons";
+import { deleteItem, duplicateItem, groupShapes, unGroupShapes, zoomIn, zoomOut, zoomReset } from "./lib";
+import {
+ alignBottom,
+ alignHorizontalCenter,
+ alignLeft,
+ alignRight,
+ alignTop,
+ alignVerticalCenter,
+} from "./tool-aligns";
+
+export const groupUngroupTools = [
+ {
+ name: "group",
+ icon: mdiGroup,
+ action: groupShapes,
+ },
+ {
+ name: "ungroup",
+ icon: mdiUngroup,
+ action: unGroupShapes,
+ },
+];
+
+export const deleteDuplicateTools = [
+ {
+ name: "delete",
+ icon: mdiDeleteOutline,
+ action: deleteItem,
+ },
+ {
+ name: "duplicate",
+ icon: mdiCopy,
+ action: duplicateItem,
+ },
+];
+
+export const zoomTools = [
+ {
+ name: "zoomOut",
+ icon: mdiZoomOut,
+ action: zoomOut,
+ },
+ {
+ name: "zoomIn",
+ icon: mdiZoomIn,
+ action: zoomIn,
+ },
+ {
+ name: "zoomReset",
+ icon: mdiZoomReset,
+ action: zoomReset,
+ },
+];
+
+export const alignTools = [
+ {
+ id: 1,
+ icon: mdiAlignHorizontalLeft,
+ action: alignLeft,
+ },
+ {
+ id: 2,
+ icon: mdiAlignHorizontalCenter,
+ action: alignHorizontalCenter,
+ },
+ {
+ id: 3,
+ icon: mdiAlignHorizontalRight,
+ action: alignRight,
+ },
+ {
+ id: 4,
+ icon: mdiAlignVerticalTop,
+ action: alignTop,
+ },
+ {
+ id: 5,
+ icon: mdiAlignVerticalCenter,
+ action: alignVerticalCenter,
+ },
+ {
+ id: 6,
+ icon: mdiAlignVerticalBottom,
+ action: alignBottom,
+ },
+];
diff --git a/ts/image-occlusion/tools/shape-generate.ts b/ts/image-occlusion/tools/shape-generate.ts
new file mode 100644
index 000000000..ca80418ef
--- /dev/null
+++ b/ts/image-occlusion/tools/shape-generate.ts
@@ -0,0 +1,131 @@
+// 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 { shapeMaskColor } from "./lib";
+
+export const generateShapeFromCloze = (canvas: fabric.Canvas, clozeStr: string): void => {
+ // generate shapes from clozeStr similar to following
+ // {{c1::image-occlusion:rect:left=10.0:top=20:width=30:height=10:fill=#ffe34d}}
+
+ const regex = /{{(.*?)}}/g;
+ const clozeStrList: string[] = [];
+ let match: string[] | null;
+
+ while ((match = regex.exec(clozeStr)) !== null) {
+ clozeStrList.push(match[1]);
+ }
+
+ const clozeList = {};
+ for (const str of clozeStrList) {
+ const [prefix, value] = str.split("::image-occlusion:");
+ if (!clozeList[prefix]) {
+ clozeList[prefix] = [];
+ }
+ clozeList[prefix].push(value);
+ }
+
+ for (const index in clozeList) {
+ let shape: fabric.Group | fabric.Rect | fabric.Ellipse | fabric.Polygon;
+
+ if (clozeList[index].length > 1) {
+ const group = new fabric.Group();
+
+ clozeList[index].forEach((shape) => {
+ const parts = shape.split(":");
+ const objectType = parts[0];
+ const objectProperties = {
+ angle: "0",
+ left: "0",
+ top: "0",
+ width: "0",
+ height: "0",
+ fill: shapeMaskColor,
+ rx: "0",
+ ry: "0",
+ points: "",
+ };
+
+ for (let i = 1; i < parts.length; i++) {
+ const keyValue = parts[i].split("=");
+ const key = keyValue[0];
+ const value = keyValue[1];
+ objectProperties[key] = value;
+ }
+
+ shape = drawShapes(objectType, objectProperties);
+ group.addWithUpdate(shape);
+ });
+ canvas.add(group);
+ } else {
+ const cloze = clozeList[index][0];
+ const parts = cloze.split(":");
+ const objectType = parts[0];
+ const objectProperties = {
+ angle: "0",
+ left: "0",
+ top: "0",
+ width: "0",
+ height: "0",
+ fill: shapeMaskColor,
+ rx: "0",
+ ry: "0",
+ points: "",
+ };
+
+ for (let i = 1; i < parts.length; i++) {
+ const keyValue = parts[i].split("=");
+ const key = keyValue[0];
+ const value = keyValue[1];
+ objectProperties[key] = value;
+ }
+
+ shape = drawShapes(objectType, objectProperties);
+ canvas.add(shape);
+ }
+ }
+ canvas.requestRenderAll();
+};
+
+const drawShapes = (objectType, objectProperties) => {
+ switch (objectType) {
+ case "rect": {
+ const rect = new fabric.Rect({
+ left: parseFloat(objectProperties.left),
+ top: parseFloat(objectProperties.top),
+ width: parseFloat(objectProperties.width),
+ height: parseFloat(objectProperties.height),
+ fill: objectProperties.fill,
+ selectable: false,
+ });
+ return rect;
+ }
+
+ case "ellipse": {
+ const ellipse = new fabric.Ellipse({
+ left: parseFloat(objectProperties.left),
+ top: parseFloat(objectProperties.top),
+ rx: parseFloat(objectProperties.rx),
+ ry: parseFloat(objectProperties.ry),
+ fill: objectProperties.fill,
+ selectable: false,
+ });
+ return ellipse;
+ }
+
+ case "polygon": {
+ const points = objectProperties.points.split(" ");
+ const polygon = new fabric.Polygon(points, {
+ left: parseFloat(objectProperties.left),
+ top: parseFloat(objectProperties.top),
+ fill: objectProperties.fill,
+ selectable: false,
+ });
+ return polygon;
+ }
+
+ default:
+ break;
+ }
+};
diff --git a/ts/image-occlusion/tools/tool-aligns.ts b/ts/image-occlusion/tools/tool-aligns.ts
new file mode 100644
index 000000000..c8cb047f1
--- /dev/null
+++ b/ts/image-occlusion/tools/tool-aligns.ts
@@ -0,0 +1,210 @@
+// 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";
+
+export const alignLeft = (canvas: fabric.canvas): void => {
+ if (!canvas.getActiveObject()) {
+ return;
+ }
+
+ const activeObject = canvas.getActiveObject();
+ if (canvas.getActiveObject().type == "activeSelection") {
+ alignLeftGroup(canvas, activeObject);
+ } else {
+ activeObject.set({ left: 0 });
+ }
+
+ activeObject.setCoords();
+ canvas.renderAll();
+};
+
+export const alignHorizontalCenter = (canvas: fabric.Canvas): void => {
+ if (!canvas.getActiveObject()) {
+ return;
+ }
+
+ const activeObject = canvas.getActiveObject();
+ if (canvas.getActiveObject().type == "activeSelection") {
+ alignHorizontalCenterGroup(canvas, activeObject);
+ } else {
+ activeObject.set({ left: canvas.width / 2 - activeObject.width / 2 });
+ }
+
+ activeObject.setCoords();
+ canvas.renderAll();
+};
+
+export const alignRight = (canvas: fabric.Canvas): void => {
+ if (!canvas.getActiveObject()) {
+ return;
+ }
+
+ const activeObject = canvas.getActiveObject();
+ if (canvas.getActiveObject().type == "activeSelection") {
+ alignRightGroup(canvas, activeObject);
+ } else {
+ activeObject.set({ left: canvas.getWidth() - activeObject.width });
+ }
+
+ activeObject.setCoords();
+ canvas.renderAll();
+};
+
+export const alignTop = (canvas: fabric.Canvas): void => {
+ if (!canvas.getActiveObject()) {
+ return;
+ }
+
+ const activeObject = canvas.getActiveObject();
+ if (canvas.getActiveObject().type == "activeSelection") {
+ alignTopGroup(canvas, activeObject);
+ } else {
+ activeObject.set({ top: 0 });
+ }
+
+ activeObject.setCoords();
+ canvas.renderAll();
+};
+
+export const alignVerticalCenter = (canvas: fabric.Canvas): void => {
+ if (!canvas.getActiveObject()) {
+ return;
+ }
+
+ const activeObject = canvas.getActiveObject();
+ if (canvas.getActiveObject().type == "activeSelection") {
+ alignVerticalCenterGroup(canvas, activeObject);
+ } else {
+ activeObject.set({ top: canvas.getHeight() / 2 - activeObject.height / 2 });
+ }
+
+ activeObject.setCoords();
+ canvas.renderAll();
+};
+
+export const alignBottom = (canvas: fabric.Canvas): void => {
+ if (!canvas.getActiveObject()) {
+ return;
+ }
+
+ const activeObject = canvas.getActiveObject();
+ if (canvas.getActiveObject().type == "activeSelection") {
+ alignBottomGroup(canvas, activeObject);
+ } else {
+ activeObject.set({ top: canvas.height - activeObject.height });
+ }
+
+ activeObject.setCoords();
+ canvas.renderAll();
+};
+
+// group aligns
+
+const alignLeftGroup = (canvas: fabric.Canvas, group: fabric.Group | fabric.IObject) => {
+ const objects = group.getObjects();
+ let leftmostShape = objects[0];
+
+ for (let i = 1; i < objects.length; i++) {
+ if (objects[i].left < leftmostShape.left) {
+ leftmostShape = objects[i];
+ }
+ }
+
+ objects.forEach((object) => {
+ object.left = leftmostShape.left;
+ object.setCoords();
+ });
+};
+
+const alignRightGroup = (canvas: fabric.Canvas, group: fabric.Group | fabric.IObject): void => {
+ const objects = group.getObjects();
+ let rightmostShape = objects[0];
+
+ for (let i = 1; i < objects.length; i++) {
+ if (objects[i].left > rightmostShape.left) {
+ rightmostShape = objects[i];
+ }
+ }
+
+ objects.forEach((object) => {
+ object.left = rightmostShape.left + rightmostShape.width - object.width;
+ object.setCoords();
+ });
+};
+
+const alignTopGroup = (canvas: fabric.Canvas, group: fabric.Group | fabric.IObject): void => {
+ const objects = group.getObjects();
+ let topmostShape = objects[0];
+
+ for (let i = 1; i < objects.length; i++) {
+ if (objects[i].top < topmostShape.top) {
+ topmostShape = objects[i];
+ }
+ }
+
+ objects.forEach((object) => {
+ object.top = topmostShape.top;
+ object.setCoords();
+ });
+};
+
+const alignBottomGroup = (canvas: fabric.Canvas, group: fabric.Group | fabric.IObject): void => {
+ const objects = group.getObjects();
+ let bottommostShape = objects[0];
+
+ for (let i = 1; i < objects.length; i++) {
+ if (objects[i].top + objects[i].height > bottommostShape.top + bottommostShape.height) {
+ bottommostShape = objects[i];
+ }
+ }
+
+ objects.forEach(function(object) {
+ if (object !== bottommostShape) {
+ object.set({ top: bottommostShape.top + bottommostShape.height - object.height });
+ object.setCoords();
+ }
+ });
+};
+
+const alignHorizontalCenterGroup = (canvas: fabric.Canvas, group: fabric.Group | fabric.IObject) => {
+ const objects = group.getObjects();
+ let leftmostShape = objects[0];
+ let rightmostShape = objects[0];
+
+ for (let i = 1; i < objects.length; i++) {
+ if (objects[i].left < leftmostShape.left) {
+ leftmostShape = objects[i];
+ }
+ if (objects[i].left > rightmostShape.left) {
+ rightmostShape = objects[i];
+ }
+ }
+
+ const centerX = (leftmostShape.left + rightmostShape.left + rightmostShape.width) / 2;
+ objects.forEach((object) => {
+ object.left = centerX - object.width / 2;
+ object.setCoords();
+ });
+};
+
+const alignVerticalCenterGroup = (canvas: fabric.Canvas, group: fabric.Group | fabric.IObject) => {
+ const objects = group.getObjects();
+ let topmostShape = objects[0];
+ let bottommostShape = objects[0];
+
+ for (let i = 1; i < objects.length; i++) {
+ if (objects[i].top < topmostShape.top) {
+ topmostShape = objects[i];
+ }
+ if (objects[i].top > bottommostShape.top) {
+ bottommostShape = objects[i];
+ }
+ }
+
+ const centerY = (topmostShape.top + bottommostShape.top + bottommostShape.height) / 2;
+ objects.forEach((object) => {
+ object.top = centerY - object.height / 2;
+ object.setCoords();
+ });
+};
diff --git a/ts/image-occlusion/tools/tool-buttons.ts b/ts/image-occlusion/tools/tool-buttons.ts
new file mode 100644
index 000000000..e3386d525
--- /dev/null
+++ b/ts/image-occlusion/tools/tool-buttons.ts
@@ -0,0 +1,33 @@
+// Copyright: Ankitects Pty Ltd and contributors
+// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
+
+import {
+ mdiCursorDefaultOutline,
+ mdiEllipseOutline,
+ mdiMagnifyScan,
+ mdiRectangleOutline,
+ mdiVectorPolygonVariant,
+} from "../icons";
+
+export const tools = [
+ {
+ id: "cursor",
+ icon: mdiCursorDefaultOutline,
+ },
+ {
+ id: "magnify",
+ icon: mdiMagnifyScan,
+ },
+ {
+ id: "draw-rectangle",
+ icon: mdiRectangleOutline,
+ },
+ {
+ id: "draw-ellipse",
+ icon: mdiEllipseOutline,
+ },
+ {
+ id: "draw-polygon",
+ icon: mdiVectorPolygonVariant,
+ },
+];
diff --git a/ts/image-occlusion/tools/tool-ellipse.ts b/ts/image-occlusion/tools/tool-ellipse.ts
new file mode 100644
index 000000000..f3dd93673
--- /dev/null
+++ b/ts/image-occlusion/tools/tool-ellipse.ts
@@ -0,0 +1,121 @@
+// 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 { borderColor, shapeMaskColor, stopDraw } from "./lib";
+import { objectAdded } from "./tool-undo-redo";
+
+const addedEllipseIds: string[] = [];
+
+export const drawEllipse = (canvas: fabric.Canvas): void => {
+ canvas.selectionColor = "rgba(0, 0, 0, 0)";
+ let ellipse, isDown, origX, origY;
+
+ stopDraw(canvas);
+
+ canvas.on("mouse:down", function(o) {
+ if (o.target) {
+ return;
+ }
+ isDown = true;
+
+ const pointer = canvas.getPointer(o.e);
+ origX = pointer.x;
+ origY = pointer.y;
+
+ ellipse = new fabric.Ellipse({
+ id: "ellipse-" + new Date().getTime(),
+ left: origX,
+ top: origY,
+ originX: "left",
+ originY: "top",
+ rx: pointer.x - origX,
+ ry: pointer.y - origY,
+ fill: shapeMaskColor,
+ transparentCorners: false,
+ selectable: true,
+ stroke: borderColor,
+ strokeWidth: 1,
+ strokeUniform: true,
+ noScaleCache: false,
+ });
+ canvas.add(ellipse);
+ });
+
+ canvas.on("mouse:move", function(o) {
+ if (!isDown) return;
+
+ const pointer = canvas.getPointer(o.e);
+ let rx = Math.abs(origX - pointer.x) / 2;
+ let ry = Math.abs(origY - pointer.y) / 2;
+ const x = pointer.x;
+ const y = pointer.y;
+
+ if (rx > ellipse.strokeWidth) {
+ rx -= ellipse.strokeWidth / 2;
+ }
+ if (ry > ellipse.strokeWidth) {
+ ry -= ellipse.strokeWidth / 2;
+ }
+
+ if (x < origX) {
+ ellipse.set({ originX: "right" });
+ } else {
+ ellipse.set({ originX: "left" });
+ }
+
+ if (y < origY) {
+ ellipse.set({ originY: "bottom" });
+ } else {
+ ellipse.set({ originY: "top" });
+ }
+
+ // do not draw outside of canvas
+ if (x < ellipse.strokeWidth) {
+ rx = (origX + ellipse.strokeWidth + 0.5) / 2;
+ }
+ if (y < ellipse.strokeWidth) {
+ ry = (origY + ellipse.strokeWidth + 0.5) / 2;
+ }
+ if (x >= canvas.width - ellipse.strokeWidth) {
+ rx = (canvas.width - origX) / 2 - ellipse.strokeWidth + 0.5;
+ }
+ if (y > canvas.height - ellipse.strokeWidth) {
+ ry = (canvas.height - origY) / 2 - ellipse.strokeWidth + 0.5;
+ }
+
+ ellipse.set({ rx: rx, ry: ry });
+
+ canvas.renderAll();
+ });
+
+ canvas.on("mouse:up", function() {
+ isDown = false;
+ // probably changed from ellipse to rectangle
+ if (!ellipse) {
+ return;
+ }
+ if (ellipse.width < 5 || ellipse.height < 5) {
+ canvas.remove(ellipse);
+ return;
+ }
+
+ if (ellipse.originX === "right") {
+ ellipse.set({
+ originX: "left",
+ left: ellipse.left - ellipse.width + ellipse.strokeWidth,
+ });
+ }
+
+ if (ellipse.originY === "bottom") {
+ ellipse.set({
+ originY: "top",
+ top: ellipse.top - ellipse.height + ellipse.strokeWidth,
+ });
+ }
+
+ ellipse.setCoords();
+ objectAdded(canvas, addedEllipseIds, ellipse.id);
+ });
+};
diff --git a/ts/image-occlusion/tools/tool-polygon.ts b/ts/image-occlusion/tools/tool-polygon.ts
new file mode 100644
index 000000000..66e6fb415
--- /dev/null
+++ b/ts/image-occlusion/tools/tool-polygon.ts
@@ -0,0 +1,233 @@
+// 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 { borderColor, shapeMaskColor } from "./lib";
+import { objectAdded, saveCanvasState } from "./tool-undo-redo";
+
+let activeLine;
+let activeShape;
+let linesList: fabric.Line = [];
+let pointsList: fabric.Circle = [];
+let drawMode = false;
+let zoomValue = 1;
+let panzoomX = 1, panzoomY = 1;
+const addedPolygonIds: string[] = [];
+
+export const drawPolygon = (canvas: fabric.Canvas, panzoom: PanZoom): void => {
+ canvas.selectionColor = "rgba(0, 0, 0, 0)";
+ canvas.on("mouse:down", function(options) {
+ try {
+ if (options.target && options.target.id === pointsList[0].id) {
+ generatePolygon(canvas, pointsList);
+ } else {
+ addPoint(canvas, options, panzoom);
+ }
+ } catch (e) {
+ // Cannot read properties of undefined (reading 'id')
+ }
+ });
+
+ 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, panzoom): void => {
+ zoomValue = panzoom.getTransform().scale;
+ panzoomX = panzoom.getTransform().x;
+ panzoomY = panzoom.getTransform().y;
+
+ const canvasContainer = document.querySelector(".canvas-container")!.getBoundingClientRect()!;
+ let clientX = options.e.touches ? options.e.touches[0].clientX : options.e.clientX;
+ let clientY = options.e.touches ? options.e.touches[0].clientY : options.e.clientY;
+
+ clientX = (clientX - canvasContainer.left - panzoomX) / zoomValue;
+ clientY = (clientY - canvasContainer.top - panzoomY) / zoomValue;
+
+ const point = new fabric.Circle({
+ radius: 5,
+ fill: "#ffffff",
+ stroke: "#333333",
+ strokeWidth: 0.5,
+ originX: "left",
+ originY: "top",
+ left: clientX,
+ top: clientY,
+ selectable: false,
+ hasBorders: false,
+ hasControls: false,
+ objectCaching: false,
+ });
+
+ if (pointsList.length === 0) {
+ point.set({
+ fill: "red",
+ });
+ }
+
+ const linePoints = [clientX, clientY, clientX, clientY];
+
+ 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: clientX, y: clientY }];
+ 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, {
+ id: "polygon-" + new Date().getTime(),
+ fill: shapeMaskColor,
+ objectCaching: false,
+ stroke: borderColor,
+ strokeWidth: 1,
+ strokeUniform: true,
+ noScaleCache: false,
+ });
+ if (polygon.width > 5 && polygon.height > 5) {
+ canvas.add(polygon);
+ // view undo redo tools
+ objectAdded(canvas, addedPolygonIds, polygon.id);
+ }
+
+ polygon.on("modified", () => {
+ modifiedPolygon(canvas, polygon);
+ saveCanvasState(canvas);
+ });
+
+ toggleDrawPolygon(canvas);
+};
+
+const modifiedPolygon = (canvas: fabric.Canvas, polygon: fabric.Polygon): void => {
+ const matrix = polygon.calcTransformMatrix();
+ const transformedPoints = polygon.get("points")
+ .map(function(p) {
+ return new fabric.Point(p.x - polygon.pathOffset.x, p.y - polygon.pathOffset.y);
+ })
+ .map(function(p) {
+ return fabric.util.transformPoint(p, matrix);
+ });
+
+ const polygon1 = new fabric.Polygon(transformedPoints, {
+ id: polygon.id,
+ fill: shapeMaskColor,
+ objectCaching: false,
+ stroke: borderColor,
+ strokeWidth: 1,
+ strokeUniform: true,
+ noScaleCache: false,
+ });
+
+ polygon1.on("modified", () => {
+ modifiedPolygon(canvas, polygon1);
+ saveCanvasState(canvas);
+ });
+
+ canvas.remove(polygon);
+ canvas.add(polygon1);
+};
diff --git a/ts/image-occlusion/tools/tool-rect.ts b/ts/image-occlusion/tools/tool-rect.ts
new file mode 100644
index 000000000..7bcf54ab2
--- /dev/null
+++ b/ts/image-occlusion/tools/tool-rect.ts
@@ -0,0 +1,115 @@
+// 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 { borderColor, shapeMaskColor, stopDraw } from "./lib";
+import { objectAdded } from "./tool-undo-redo";
+
+const addedRectangleIds: string[] = [];
+
+export const drawRectangle = (canvas: fabric.Canvas): void => {
+ canvas.selectionColor = "rgba(0, 0, 0, 0)";
+ let rect, isDown, origX, origY;
+
+ stopDraw(canvas);
+
+ canvas.on("mouse:down", function(o) {
+ if (o.target) {
+ return;
+ }
+ isDown = true;
+
+ const pointer = canvas.getPointer(o.e);
+ origX = pointer.x;
+ origY = pointer.y;
+
+ rect = new fabric.Rect({
+ id: "rect-" + new Date().getTime(),
+ left: origX,
+ top: origY,
+ originX: "left",
+ originY: "top",
+ width: pointer.x - origX,
+ height: pointer.y - origY,
+ angle: 0,
+ fill: shapeMaskColor,
+ transparentCorners: false,
+ selectable: true,
+ stroke: borderColor,
+ strokeWidth: 1,
+ strokeUniform: true,
+ noScaleCache: false,
+ });
+ canvas.add(rect);
+ });
+
+ canvas.on("mouse:move", function(o) {
+ if (!isDown) return;
+ const pointer = canvas.getPointer(o.e);
+ let x = pointer.x;
+ let y = pointer.y;
+
+ if (x < origX) {
+ rect.set({ originX: "right" });
+ } else {
+ rect.set({ originX: "left" });
+ }
+
+ if (y < origY) {
+ rect.set({ originY: "bottom" });
+ } else {
+ rect.set({ originY: "top" });
+ }
+
+ // do not draw outside of canvas
+ if (x < rect.strokeWidth) {
+ x = -rect.strokeWidth + 0.5;
+ }
+ if (y < rect.strokeWidth) {
+ y = -rect.strokeWidth + 0.5;
+ }
+ if (x >= canvas.width - rect.strokeWidth) {
+ x = canvas.width - rect.strokeWidth + 0.5;
+ }
+ if (y >= canvas.height - rect.strokeWidth) {
+ y = canvas.height - rect.strokeWidth + 0.5;
+ }
+
+ rect.set({
+ width: Math.abs(x - rect.left),
+ height: Math.abs(y - rect.top),
+ });
+
+ canvas.renderAll();
+ });
+
+ canvas.on("mouse:up", function() {
+ isDown = false;
+ // probably changed from rectangle to ellipse
+ if (!rect) {
+ return;
+ }
+ if (rect.width < 5 || rect.height < 5) {
+ canvas.remove(rect);
+ return;
+ }
+
+ if (rect.originX === "right") {
+ rect.set({
+ originX: "left",
+ left: rect.left - rect.width + rect.strokeWidth,
+ });
+ }
+
+ if (rect.originY === "bottom") {
+ rect.set({
+ originY: "top",
+ top: rect.top - rect.height + rect.strokeWidth,
+ });
+ }
+
+ rect.setCoords();
+ objectAdded(canvas, addedRectangleIds, rect.id);
+ });
+};
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 000000000..78176e5bd
--- /dev/null
+++ b/ts/image-occlusion/tools/tool-undo-redo.ts
@@ -0,0 +1,90 @@
+// 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";
+
+/**
+ * Undo redo for rectangle and ellipse handled here,
+ * view tool-polygon for handling undo redo in case of polygon
+ */
+
+let lockHistory = false;
+const undoHistory: string[] = [];
+const redoHistory: string[] = [];
+
+const shapeType = ["rect", "ellipse"];
+
+export const undoRedoInit = (canvas: fabric.Canvas): void => {
+ undoHistory.push(JSON.stringify(canvas));
+
+ canvas.on("object:modified", function(o) {
+ if (lockHistory) return;
+ if (!validShape(o.target as fabric.Object)) return;
+ saveCanvasState(canvas);
+ });
+
+ canvas.on("object:removed", function(o) {
+ if (lockHistory) return;
+ if (!validShape(o.target as fabric.Object)) return;
+ saveCanvasState(canvas);
+ });
+};
+
+const validShape = (shape: fabric.Object): boolean => {
+ if (shape.width <= 5 || shape.height <= 5) return false;
+ if (shapeType.indexOf(shape.type) === -1) return false;
+ return true;
+};
+
+export const undoAction = (canvas: fabric.Canvas): void => {
+ if (undoHistory.length > 0) {
+ lockHistory = true;
+ if (undoHistory.length > 1) redoHistory.push(undoHistory.pop() as string);
+ const content = undoHistory[undoHistory.length - 1];
+ canvas.loadFromJSON(content, function() {
+ canvas.renderAll();
+ lockHistory = false;
+ });
+ }
+};
+
+export const redoAction = (canvas: fabric.Canvas): void => {
+ if (redoHistory.length > 0) {
+ lockHistory = true;
+ const content = redoHistory.pop() as string;
+ undoHistory.push(content);
+ canvas.loadFromJSON(content, function() {
+ canvas.renderAll();
+ lockHistory = false;
+ });
+ }
+};
+
+export const objectAdded = (canvas: fabric.Canvas, shapeIdList: string[], shapeId: string): void => {
+ if (shapeIdList.includes(shapeId)) {
+ return;
+ }
+
+ shapeIdList.push(shapeId);
+ saveCanvasState(canvas);
+};
+
+export const saveCanvasState = (canvas: fabric.Canvas): void => {
+ undoHistory.push(JSON.stringify(canvas));
+ redoHistory.length = 0;
+};
+
+export const undoRedoTools = [
+ {
+ name: "undo",
+ icon: mdiUndo,
+ action: undoAction,
+ },
+ {
+ name: "redo",
+ icon: mdiRedo,
+ action: redoAction,
+ },
+];
diff --git a/ts/image-occlusion/tsconfig.json b/ts/image-occlusion/tsconfig.json
new file mode 100644
index 000000000..d1761750c
--- /dev/null
+++ b/ts/image-occlusion/tsconfig.json
@@ -0,0 +1,17 @@
+{
+ "extends": "../tsconfig.json",
+ "include": [
+ "*",
+ "tools/*",
+ "notes-toolbar"
+ ],
+ "references": [
+ { "path": "../lib" },
+ { "path": "../sveltelib" },
+ { "path": "../components" },
+ { "path": "../tag-editor" }
+ ],
+ "compilerOptions": {
+ "types": ["jest"]
+ }
+}
diff --git a/ts/lib/proto.ts b/ts/lib/proto.ts
index 1b1f68105..734bb06c3 100644
--- a/ts/lib/proto.ts
+++ b/ts/lib/proto.ts
@@ -15,6 +15,7 @@ import DeckConfig = anki.deckconfig;
import Decks = anki.decks;
import Generic = anki.generic;
import I18n = anki.i18n;
+import ImageOcclusion = anki.image_occlusion;
import ImportExport = anki.import_export;
import Notes = anki.notes;
import Notetypes = anki.notetypes;
@@ -81,3 +82,6 @@ export const stats = Stats.StatsService.create(serviceCallback as RPCImpl);
export { Tags };
export const tags = Tags.TagsService.create(serviceCallback as RPCImpl);
+
+export { ImageOcclusion };
+export const imageOcclusion = ImageOcclusion.ImageOcclusionService.create(serviceCallback as RPCImpl);
diff --git a/ts/licenses.json b/ts/licenses.json
index 3f6302762..8987c18af 100644
--- a/ts/licenses.json
+++ b/ts/licenses.json
@@ -21,6 +21,14 @@
"path": "node_modules/@fluent/bundle",
"licenseFile": "node_modules/@fluent/bundle/README.md"
},
+ "@mapbox/node-pre-gyp@1.0.10": {
+ "licenses": "BSD-3-Clause",
+ "repository": "https://github.com/mapbox/node-pre-gyp",
+ "publisher": "Dane Springmeyer",
+ "email": "dane@mapbox.com",
+ "path": "node_modules/@mapbox/node-pre-gyp",
+ "licenseFile": "node_modules/@mapbox/node-pre-gyp/LICENSE"
+ },
"@mdi/svg@7.0.96": {
"licenses": "Apache-2.0",
"repository": "https://github.com/Templarian/MaterialDesign-SVG",
@@ -116,6 +124,15 @@
"path": "node_modules/@protobufjs/utf8",
"licenseFile": "node_modules/@protobufjs/utf8/LICENSE"
},
+ "@tootallnate/once@2.0.0": {
+ "licenses": "MIT",
+ "repository": "https://github.com/TooTallNate/once",
+ "publisher": "Nathan Rajlich",
+ "email": "nathan@tootallnate.net",
+ "url": "http://n8.io/",
+ "path": "node_modules/@tootallnate/once",
+ "licenseFile": "node_modules/@tootallnate/once/LICENSE"
+ },
"@types/lodash-es@4.17.6": {
"licenses": "MIT",
"repository": "https://github.com/DefinitelyTyped/DefinitelyTyped",
@@ -140,6 +157,112 @@
"path": "node_modules/protobufjs/node_modules/@types/node",
"licenseFile": "node_modules/protobufjs/node_modules/@types/node/LICENSE"
},
+ "abab@2.0.5": {
+ "licenses": "BSD-3-Clause",
+ "repository": "https://github.com/jsdom/abab",
+ "publisher": "Jeff Carpenter",
+ "email": "gcarpenterv@gmail.com",
+ "path": "node_modules/abab",
+ "licenseFile": "node_modules/abab/LICENSE.md"
+ },
+ "abbrev@1.1.1": {
+ "licenses": "ISC",
+ "repository": "https://github.com/isaacs/abbrev-js",
+ "publisher": "Isaac Z. Schlueter",
+ "email": "i@izs.me",
+ "path": "node_modules/abbrev",
+ "licenseFile": "node_modules/abbrev/LICENSE"
+ },
+ "acorn-globals@6.0.0": {
+ "licenses": "MIT",
+ "repository": "https://github.com/ForbesLindesay/acorn-globals",
+ "publisher": "ForbesLindesay",
+ "path": "node_modules/acorn-globals",
+ "licenseFile": "node_modules/acorn-globals/LICENSE"
+ },
+ "acorn-walk@7.2.0": {
+ "licenses": "MIT",
+ "repository": "https://github.com/acornjs/acorn",
+ "path": "node_modules/acorn-walk",
+ "licenseFile": "node_modules/acorn-walk/LICENSE"
+ },
+ "acorn@7.4.1": {
+ "licenses": "MIT",
+ "repository": "https://github.com/acornjs/acorn",
+ "path": "node_modules/acorn-globals/node_modules/acorn",
+ "licenseFile": "node_modules/acorn-globals/node_modules/acorn/LICENSE"
+ },
+ "acorn@8.7.0": {
+ "licenses": "MIT",
+ "repository": "https://github.com/acornjs/acorn",
+ "path": "node_modules/acorn",
+ "licenseFile": "node_modules/acorn/LICENSE"
+ },
+ "agent-base@6.0.2": {
+ "licenses": "MIT",
+ "repository": "https://github.com/TooTallNate/node-agent-base",
+ "publisher": "Nathan Rajlich",
+ "email": "nathan@tootallnate.net",
+ "url": "http://n8.io/",
+ "path": "node_modules/agent-base",
+ "licenseFile": "node_modules/agent-base/README.md"
+ },
+ "amator@1.1.0": {
+ "licenses": "MIT",
+ "repository": "https://github.com/anvaka/amator",
+ "publisher": "Andrei Kashcha",
+ "path": "node_modules/amator",
+ "licenseFile": "node_modules/amator/LICENSE"
+ },
+ "ansi-regex@5.0.1": {
+ "licenses": "MIT",
+ "repository": "https://github.com/chalk/ansi-regex",
+ "publisher": "Sindre Sorhus",
+ "email": "sindresorhus@gmail.com",
+ "url": "sindresorhus.com",
+ "path": "node_modules/ansi-regex",
+ "licenseFile": "node_modules/ansi-regex/license"
+ },
+ "aproba@2.0.0": {
+ "licenses": "ISC",
+ "repository": "https://github.com/iarna/aproba",
+ "publisher": "Rebecca Turner",
+ "email": "me@re-becca.org",
+ "path": "node_modules/aproba",
+ "licenseFile": "node_modules/aproba/LICENSE"
+ },
+ "are-we-there-yet@2.0.0": {
+ "licenses": "ISC",
+ "repository": "https://github.com/npm/are-we-there-yet",
+ "publisher": "GitHub Inc.",
+ "path": "node_modules/are-we-there-yet",
+ "licenseFile": "node_modules/are-we-there-yet/LICENSE.md"
+ },
+ "asynckit@0.4.0": {
+ "licenses": "MIT",
+ "repository": "https://github.com/alexindigo/asynckit",
+ "publisher": "Alex Indigo",
+ "email": "iam@alexindigo.com",
+ "path": "node_modules/asynckit",
+ "licenseFile": "node_modules/asynckit/LICENSE"
+ },
+ "balanced-match@1.0.2": {
+ "licenses": "MIT",
+ "repository": "https://github.com/juliangruber/balanced-match",
+ "publisher": "Julian Gruber",
+ "email": "mail@juliangruber.com",
+ "url": "http://juliangruber.com",
+ "path": "node_modules/balanced-match",
+ "licenseFile": "node_modules/balanced-match/LICENSE.md"
+ },
+ "bezier-easing@2.1.0": {
+ "licenses": "MIT",
+ "repository": "https://github.com/gre/bezier-easing",
+ "publisher": "Gaëtan Renaudeau",
+ "email": "renaudeau.gaetan@gmail.com",
+ "path": "node_modules/bezier-easing",
+ "licenseFile": "node_modules/bezier-easing/LICENSE"
+ },
"bootstrap-icons@1.8.1": {
"licenses": "MIT",
"repository": "https://github.com/twbs/icons",
@@ -155,6 +278,30 @@
"path": "node_modules/bootstrap",
"licenseFile": "node_modules/bootstrap/LICENSE"
},
+ "brace-expansion@1.1.11": {
+ "licenses": "MIT",
+ "repository": "https://github.com/juliangruber/brace-expansion",
+ "publisher": "Julian Gruber",
+ "email": "mail@juliangruber.com",
+ "url": "http://juliangruber.com",
+ "path": "node_modules/brace-expansion",
+ "licenseFile": "node_modules/brace-expansion/LICENSE"
+ },
+ "browser-process-hrtime@1.0.0": {
+ "licenses": "BSD-2-Clause",
+ "repository": "https://github.com/kumavis/browser-process-hrtime",
+ "publisher": "kumavis",
+ "path": "node_modules/browser-process-hrtime",
+ "licenseFile": "node_modules/browser-process-hrtime/LICENSE"
+ },
+ "canvas@2.11.0": {
+ "licenses": "MIT",
+ "repository": "https://github.com/Automattic/node-canvas",
+ "publisher": "TJ Holowaychuk",
+ "email": "tj@learnboost.com",
+ "path": "node_modules/canvas",
+ "licenseFile": "node_modules/canvas/Readme.md"
+ },
"character-entities@2.0.2": {
"licenses": "MIT",
"repository": "https://github.com/wooorm/character-entities",
@@ -164,6 +311,15 @@
"path": "node_modules/character-entities",
"licenseFile": "node_modules/character-entities/license"
},
+ "chownr@2.0.0": {
+ "licenses": "ISC",
+ "repository": "https://github.com/isaacs/chownr",
+ "publisher": "Isaac Z. Schlueter",
+ "email": "i@izs.me",
+ "url": "http://blog.izs.me/",
+ "path": "node_modules/chownr",
+ "licenseFile": "node_modules/chownr/LICENSE"
+ },
"codemirror@5.65.2": {
"licenses": "MIT",
"repository": "https://github.com/codemirror/CodeMirror",
@@ -173,6 +329,24 @@
"path": "node_modules/codemirror",
"licenseFile": "node_modules/codemirror/LICENSE"
},
+ "color-support@1.1.3": {
+ "licenses": "ISC",
+ "repository": "https://github.com/isaacs/color-support",
+ "publisher": "Isaac Z. Schlueter",
+ "email": "i@izs.me",
+ "url": "http://blog.izs.me/",
+ "path": "node_modules/color-support",
+ "licenseFile": "node_modules/color-support/LICENSE"
+ },
+ "combined-stream@1.0.8": {
+ "licenses": "MIT",
+ "repository": "https://github.com/felixge/node-combined-stream",
+ "publisher": "Felix Geisendörfer",
+ "email": "felix@debuggable.com",
+ "url": "http://debuggable.com/",
+ "path": "node_modules/combined-stream",
+ "licenseFile": "node_modules/combined-stream/License"
+ },
"commander@7.2.0": {
"licenses": "MIT",
"repository": "https://github.com/tj/commander.js",
@@ -181,6 +355,24 @@
"path": "node_modules/commander",
"licenseFile": "node_modules/commander/LICENSE"
},
+ "concat-map@0.0.1": {
+ "licenses": "MIT",
+ "repository": "https://github.com/substack/node-concat-map",
+ "publisher": "James Halliday",
+ "email": "mail@substack.net",
+ "url": "http://substack.net",
+ "path": "node_modules/concat-map",
+ "licenseFile": "node_modules/concat-map/LICENSE"
+ },
+ "console-control-strings@1.1.0": {
+ "licenses": "ISC",
+ "repository": "https://github.com/iarna/console-control-strings",
+ "publisher": "Rebecca Turner",
+ "email": "me@re-becca.org",
+ "url": "http://re-becca.org/",
+ "path": "node_modules/console-control-strings",
+ "licenseFile": "node_modules/console-control-strings/LICENSE"
+ },
"css-browser-selector@0.6.5": {
"licenses": "CC-BY-SA-2.5",
"repository": "https://github.com/verbatim/css_browser_selector",
@@ -188,6 +380,28 @@
"path": "node_modules/css-browser-selector",
"licenseFile": "node_modules/css-browser-selector/README.mkdn"
},
+ "cssom@0.3.8": {
+ "licenses": "MIT",
+ "repository": "https://github.com/NV/CSSOM",
+ "publisher": "Nikita Vasilyev",
+ "email": "me@elv1s.ru",
+ "path": "node_modules/cssstyle/node_modules/cssom",
+ "licenseFile": "node_modules/cssstyle/node_modules/cssom/LICENSE.txt"
+ },
+ "cssom@0.5.0": {
+ "licenses": "MIT",
+ "repository": "https://github.com/NV/CSSOM",
+ "publisher": "Nikita Vasilyev",
+ "email": "me@elv1s.ru",
+ "path": "node_modules/cssom",
+ "licenseFile": "node_modules/cssom/LICENSE.txt"
+ },
+ "cssstyle@2.3.0": {
+ "licenses": "MIT",
+ "repository": "https://github.com/jsdom/cssstyle",
+ "path": "node_modules/cssstyle",
+ "licenseFile": "node_modules/cssstyle/LICENSE"
+ },
"d3-array@3.2.0": {
"licenses": "ISC",
"repository": "https://github.com/d3/d3-array",
@@ -436,6 +650,49 @@
"path": "node_modules/d3",
"licenseFile": "node_modules/d3/LICENSE"
},
+ "data-urls@3.0.1": {
+ "licenses": "MIT",
+ "repository": "https://github.com/jsdom/data-urls",
+ "publisher": "Domenic Denicola",
+ "email": "d@domenic.me",
+ "url": "https://domenic.me/",
+ "path": "node_modules/data-urls",
+ "licenseFile": "node_modules/data-urls/LICENSE.txt"
+ },
+ "debug@4.3.4": {
+ "licenses": "MIT",
+ "repository": "https://github.com/debug-js/debug",
+ "publisher": "Josh Junon",
+ "email": "josh.junon@protonmail.com",
+ "path": "node_modules/debug",
+ "licenseFile": "node_modules/debug/LICENSE"
+ },
+ "decimal.js@10.3.1": {
+ "licenses": "MIT",
+ "repository": "https://github.com/MikeMcl/decimal.js",
+ "publisher": "Michael Mclaughlin",
+ "email": "M8ch88l@gmail.com",
+ "path": "node_modules/decimal.js",
+ "licenseFile": "node_modules/decimal.js/LICENCE.md"
+ },
+ "decompress-response@4.2.1": {
+ "licenses": "MIT",
+ "repository": "https://github.com/sindresorhus/decompress-response",
+ "publisher": "Sindre Sorhus",
+ "email": "sindresorhus@gmail.com",
+ "url": "sindresorhus.com",
+ "path": "node_modules/decompress-response",
+ "licenseFile": "node_modules/decompress-response/license"
+ },
+ "deep-is@0.1.4": {
+ "licenses": "MIT",
+ "repository": "https://github.com/thlorenz/deep-is",
+ "publisher": "Thorsten Lorenz",
+ "email": "thlorenz@gmx.de",
+ "url": "http://thlorenz.com",
+ "path": "node_modules/deep-is",
+ "licenseFile": "node_modules/deep-is/LICENSE"
+ },
"delaunator@5.0.0": {
"licenses": "ISC",
"repository": "https://github.com/mapbox/delaunator",
@@ -443,6 +700,116 @@
"path": "node_modules/delaunator",
"licenseFile": "node_modules/delaunator/LICENSE"
},
+ "delayed-stream@1.0.0": {
+ "licenses": "MIT",
+ "repository": "https://github.com/felixge/node-delayed-stream",
+ "publisher": "Felix Geisendörfer",
+ "email": "felix@debuggable.com",
+ "url": "http://debuggable.com/",
+ "path": "node_modules/delayed-stream",
+ "licenseFile": "node_modules/delayed-stream/License"
+ },
+ "delegates@1.0.0": {
+ "licenses": "MIT",
+ "repository": "https://github.com/visionmedia/node-delegates",
+ "path": "node_modules/delegates",
+ "licenseFile": "node_modules/delegates/License"
+ },
+ "detect-libc@2.0.1": {
+ "licenses": "Apache-2.0",
+ "repository": "https://github.com/lovell/detect-libc",
+ "publisher": "Lovell Fuller",
+ "email": "npm@lovell.info",
+ "path": "node_modules/detect-libc",
+ "licenseFile": "node_modules/detect-libc/LICENSE"
+ },
+ "domexception@4.0.0": {
+ "licenses": "MIT",
+ "repository": "https://github.com/jsdom/domexception",
+ "publisher": "Domenic Denicola",
+ "email": "d@domenic.me",
+ "url": "https://domenic.me/",
+ "path": "node_modules/domexception",
+ "licenseFile": "node_modules/domexception/LICENSE.txt"
+ },
+ "emoji-regex@8.0.0": {
+ "licenses": "MIT",
+ "repository": "https://github.com/mathiasbynens/emoji-regex",
+ "publisher": "Mathias Bynens",
+ "url": "https://mathiasbynens.be/",
+ "path": "node_modules/emoji-regex",
+ "licenseFile": "node_modules/emoji-regex/LICENSE-MIT.txt"
+ },
+ "escodegen@2.0.0": {
+ "licenses": "BSD-2-Clause",
+ "repository": "https://github.com/estools/escodegen",
+ "path": "node_modules/escodegen",
+ "licenseFile": "node_modules/escodegen/LICENSE.BSD"
+ },
+ "esprima@4.0.1": {
+ "licenses": "BSD-2-Clause",
+ "repository": "https://github.com/jquery/esprima",
+ "publisher": "Ariya Hidayat",
+ "email": "ariya.hidayat@gmail.com",
+ "path": "node_modules/esprima",
+ "licenseFile": "node_modules/esprima/LICENSE.BSD"
+ },
+ "estraverse@5.3.0": {
+ "licenses": "BSD-2-Clause",
+ "repository": "https://github.com/estools/estraverse",
+ "path": "node_modules/estraverse",
+ "licenseFile": "node_modules/estraverse/LICENSE.BSD"
+ },
+ "esutils@2.0.3": {
+ "licenses": "BSD-2-Clause",
+ "repository": "https://github.com/estools/esutils",
+ "path": "node_modules/esutils",
+ "licenseFile": "node_modules/esutils/LICENSE.BSD"
+ },
+ "fabric@5.3.0": {
+ "licenses": "MIT",
+ "repository": "https://github.com/fabricjs/fabric.js",
+ "publisher": "Juriy Zaytsev",
+ "email": "kangax@gmail.com",
+ "path": "node_modules/fabric",
+ "licenseFile": "node_modules/fabric/LICENSE"
+ },
+ "fast-levenshtein@2.0.6": {
+ "licenses": "MIT",
+ "repository": "https://github.com/hiddentao/fast-levenshtein",
+ "publisher": "Ramesh Nair",
+ "email": "ram@hiddentao.com",
+ "url": "http://www.hiddentao.com/",
+ "path": "node_modules/fast-levenshtein",
+ "licenseFile": "node_modules/fast-levenshtein/LICENSE.md"
+ },
+ "form-data@4.0.0": {
+ "licenses": "MIT",
+ "repository": "https://github.com/form-data/form-data",
+ "publisher": "Felix Geisendörfer",
+ "email": "felix@debuggable.com",
+ "url": "http://debuggable.com/",
+ "path": "node_modules/form-data",
+ "licenseFile": "node_modules/form-data/License"
+ },
+ "fs-minipass@2.1.0": {
+ "licenses": "ISC",
+ "repository": "https://github.com/npm/fs-minipass",
+ "publisher": "Isaac Z. Schlueter",
+ "email": "i@izs.me",
+ "url": "http://blog.izs.me/",
+ "path": "node_modules/fs-minipass",
+ "licenseFile": "node_modules/fs-minipass/LICENSE"
+ },
+ "fs.realpath@1.0.0": {
+ "licenses": "ISC",
+ "repository": "https://github.com/isaacs/fs.realpath",
+ "publisher": "Isaac Z. Schlueter",
+ "email": "i@izs.me",
+ "url": "http://blog.izs.me/",
+ "path": "node_modules/fs.realpath",
+ "licenseFile": "node_modules/fs.realpath/LICENSE"
+ },
"fuse.js@6.6.2": {
"licenses": "Apache-2.0",
"repository": "https://github.com/krisk/Fuse",
@@ -452,6 +819,14 @@
"path": "node_modules/fuse.js",
"licenseFile": "node_modules/fuse.js/LICENSE"
},
+ "gauge@3.0.2": {
+ "licenses": "ISC",
+ "repository": "https://github.com/iarna/gauge",
+ "publisher": "Rebecca Turner",
+ "email": "me@re-becca.org",
+ "path": "node_modules/gauge",
+ "licenseFile": "node_modules/gauge/LICENSE"
+ },
"gemoji@7.1.0": {
"licenses": "MIT",
"repository": "https://github.com/wooorm/gemoji",
@@ -461,6 +836,50 @@
"path": "node_modules/gemoji",
"licenseFile": "node_modules/gemoji/license"
},
+ "glob@7.2.0": {
+ "licenses": "ISC",
+ "repository": "https://github.com/isaacs/node-glob",
+ "publisher": "Isaac Z. Schlueter",
+ "email": "i@izs.me",
+ "url": "http://blog.izs.me/",
+ "path": "node_modules/glob",
+ "licenseFile": "node_modules/glob/LICENSE"
+ },
+ "has-unicode@2.0.1": {
+ "licenses": "ISC",
+ "repository": "https://github.com/iarna/has-unicode",
+ "publisher": "Rebecca Turner",
+ "email": "me@re-becca.org",
+ "path": "node_modules/has-unicode",
+ "licenseFile": "node_modules/has-unicode/LICENSE"
+ },
+ "html-encoding-sniffer@3.0.0": {
+ "licenses": "MIT",
+ "repository": "https://github.com/jsdom/html-encoding-sniffer",
+ "publisher": "Domenic Denicola",
+ "email": "d@domenic.me",
+ "url": "https://domenic.me/",
+ "path": "node_modules/html-encoding-sniffer",
+ "licenseFile": "node_modules/html-encoding-sniffer/LICENSE.txt"
+ },
+ "http-proxy-agent@5.0.0": {
+ "licenses": "MIT",
+ "repository": "https://github.com/TooTallNate/node-http-proxy-agent",
+ "publisher": "Nathan Rajlich",
+ "email": "nathan@tootallnate.net",
+ "url": "http://n8.io/",
+ "path": "node_modules/http-proxy-agent",
+ "licenseFile": "node_modules/http-proxy-agent/README.md"
+ },
+ "https-proxy-agent@5.0.0": {
+ "licenses": "MIT",
+ "repository": "https://github.com/TooTallNate/node-https-proxy-agent",
+ "publisher": "Nathan Rajlich",
+ "email": "nathan@tootallnate.net",
+ "url": "http://n8.io/",
+ "path": "node_modules/https-proxy-agent",
+ "licenseFile": "node_modules/https-proxy-agent/README.md"
+ },
"iconv-lite@0.6.3": {
"licenses": "MIT",
"repository": "https://github.com/ashtuchkin/iconv-lite",
@@ -469,6 +888,21 @@
"path": "node_modules/iconv-lite",
"licenseFile": "node_modules/iconv-lite/LICENSE"
},
+ "inflight@1.0.6": {
+ "licenses": "ISC",
+ "repository": "https://github.com/npm/inflight",
+ "publisher": "Isaac Z. Schlueter",
+ "email": "i@izs.me",
+ "url": "http://blog.izs.me/",
+ "path": "node_modules/inflight",
+ "licenseFile": "node_modules/inflight/LICENSE"
+ },
+ "inherits@2.0.4": {
+ "licenses": "ISC",
+ "repository": "https://github.com/isaacs/inherits",
+ "path": "node_modules/inherits",
+ "licenseFile": "node_modules/inherits/LICENSE"
+ },
"internmap@2.0.3": {
"licenses": "ISC",
"repository": "https://github.com/mbostock/internmap",
@@ -485,6 +919,23 @@
"path": "node_modules/intl-pluralrules",
"licenseFile": "node_modules/intl-pluralrules/LICENSE"
},
+ "is-fullwidth-code-point@3.0.0": {
+ "licenses": "MIT",
+ "repository": "https://github.com/sindresorhus/is-fullwidth-code-point",
+ "publisher": "Sindre Sorhus",
+ "email": "sindresorhus@gmail.com",
+ "url": "sindresorhus.com",
+ "path": "node_modules/is-fullwidth-code-point",
+ "licenseFile": "node_modules/is-fullwidth-code-point/license"
+ },
+ "is-potential-custom-element-name@1.0.1": {
+ "licenses": "MIT",
+ "repository": "https://github.com/mathiasbynens/is-potential-custom-element-name",
+ "publisher": "Mathias Bynens",
+ "url": "https://mathiasbynens.be/",
+ "path": "node_modules/is-potential-custom-element-name",
+ "licenseFile": "node_modules/is-potential-custom-element-name/LICENSE-MIT.txt"
+ },
"jquery-ui-dist@1.13.1": {
"licenses": "MIT",
"repository": "https://github.com/jquery/jquery-ui",
@@ -501,6 +952,20 @@
"path": "node_modules/jquery",
"licenseFile": "node_modules/jquery/LICENSE.txt"
},
+ "jsdom@19.0.0": {
+ "licenses": "MIT",
+ "repository": "https://github.com/jsdom/jsdom",
+ "path": "node_modules/jsdom",
+ "licenseFile": "node_modules/jsdom/LICENSE.txt"
+ },
+ "levn@0.3.0": {
+ "licenses": "MIT",
+ "repository": "https://github.com/gkz/levn",
+ "publisher": "George Zahariev",
+ "email": "z@georgezahariev.com",
+ "path": "node_modules/optionator/node_modules/levn",
+ "licenseFile": "node_modules/optionator/node_modules/levn/LICENSE"
+ },
"lodash-es@4.17.21": {
"licenses": "MIT",
"repository": "https://github.com/lodash/lodash",
@@ -517,6 +982,23 @@
"path": "node_modules/long",
"licenseFile": "node_modules/long/LICENSE"
},
+ "lru-cache@6.0.0": {
+ "licenses": "ISC",
+ "repository": "https://github.com/isaacs/node-lru-cache",
+ "publisher": "Isaac Z. Schlueter",
+ "email": "i@izs.me",
+ "path": "node_modules/lru-cache",
+ "licenseFile": "node_modules/lru-cache/LICENSE"
+ },
+ "make-dir@3.1.0": {
+ "licenses": "MIT",
+ "repository": "https://github.com/sindresorhus/make-dir",
+ "publisher": "Sindre Sorhus",
+ "email": "sindresorhus@gmail.com",
+ "url": "sindresorhus.com",
+ "path": "node_modules/make-dir",
+ "licenseFile": "node_modules/make-dir/license"
+ },
"marked@4.0.12": {
"licenses": "MIT",
"repository": "https://github.com/markedjs/marked",
@@ -530,6 +1012,181 @@
"path": "node_modules/mathjax",
"licenseFile": "node_modules/mathjax/LICENSE"
},
+ "mime-db@1.52.0": {
+ "licenses": "MIT",
+ "repository": "https://github.com/jshttp/mime-db",
+ "path": "node_modules/mime-db",
+ "licenseFile": "node_modules/mime-db/LICENSE"
+ },
+ "mime-types@2.1.35": {
+ "licenses": "MIT",
+ "repository": "https://github.com/jshttp/mime-types",
+ "path": "node_modules/mime-types",
+ "licenseFile": "node_modules/mime-types/LICENSE"
+ },
+ "mimic-response@2.1.0": {
+ "licenses": "MIT",
+ "repository": "https://github.com/sindresorhus/mimic-response",
+ "publisher": "Sindre Sorhus",
+ "email": "sindresorhus@gmail.com",
+ "url": "https://sindresorhus.com",
+ "path": "node_modules/mimic-response",
+ "licenseFile": "node_modules/mimic-response/license"
+ },
+ "minimatch@3.1.2": {
+ "licenses": "ISC",
+ "repository": "https://github.com/isaacs/minimatch",
+ "publisher": "Isaac Z. Schlueter",
+ "email": "i@izs.me",
+ "url": "http://blog.izs.me",
+ "path": "node_modules/minimatch",
+ "licenseFile": "node_modules/minimatch/LICENSE"
+ },
+ "minipass@3.3.6": {
+ "licenses": "ISC",
+ "repository": "https://github.com/isaacs/minipass",
+ "publisher": "Isaac Z. Schlueter",
+ "email": "i@izs.me",
+ "url": "http://blog.izs.me/",
+ "path": "node_modules/minipass",
+ "licenseFile": "node_modules/minipass/LICENSE"
+ },
+ "minipass@4.0.3": {
+ "licenses": "ISC",
+ "repository": "https://github.com/isaacs/minipass",
+ "publisher": "Isaac Z. Schlueter",
+ "email": "i@izs.me",
+ "url": "http://blog.izs.me/",
+ "path": "node_modules/tar/node_modules/minipass",
+ "licenseFile": "node_modules/tar/node_modules/minipass/LICENSE"
+ },
+ "minizlib@2.1.2": {
+ "licenses": "MIT",
+ "repository": "https://github.com/isaacs/minizlib",
+ "publisher": "Isaac Z. Schlueter",
+ "email": "i@izs.me",
+ "url": "http://blog.izs.me/",
+ "path": "node_modules/minizlib",
+ "licenseFile": "node_modules/minizlib/LICENSE"
+ },
+ "mkdirp@1.0.4": {
+ "licenses": "MIT",
+ "repository": "https://github.com/isaacs/node-mkdirp",
+ "path": "node_modules/mkdirp",
+ "licenseFile": "node_modules/mkdirp/LICENSE"
+ },
+ "ms@2.1.2": {
+ "licenses": "MIT",
+ "repository": "https://github.com/zeit/ms",
+ "path": "node_modules/ms",
+ "licenseFile": "node_modules/ms/license.md"
+ },
+ "nan@2.17.0": {
+ "licenses": "MIT",
+ "repository": "https://github.com/nodejs/nan",
+ "path": "node_modules/nan",
+ "licenseFile": "node_modules/nan/LICENSE.md"
+ },
+ "ngraph.events@1.2.2": {
+ "licenses": "BSD-3-Clause",
+ "repository": "https://github.com/anvaka/ngraph.events",
+ "publisher": "Andrei Kashcha",
+ "path": "node_modules/ngraph.events",
+ "licenseFile": "node_modules/ngraph.events/LICENSE"
+ },
+ "node-fetch@2.6.9": {
+ "licenses": "MIT",
+ "repository": "https://github.com/bitinn/node-fetch",
+ "publisher": "David Frank",
+ "path": "node_modules/node-fetch",
+ "licenseFile": "node_modules/node-fetch/LICENSE.md"
+ },
+ "nopt@5.0.0": {
+ "licenses": "ISC",
+ "repository": "https://github.com/npm/nopt",
+ "publisher": "Isaac Z. Schlueter",
+ "email": "i@izs.me",
+ "url": "http://blog.izs.me/",
+ "path": "node_modules/nopt",
+ "licenseFile": "node_modules/nopt/LICENSE"
+ },
+ "npmlog@5.0.1": {
+ "licenses": "ISC",
+ "repository": "https://github.com/npm/npmlog",
+ "publisher": "Isaac Z. Schlueter",
+ "email": "i@izs.me",
+ "url": "http://blog.izs.me/",
+ "path": "node_modules/npmlog",
+ "licenseFile": "node_modules/npmlog/LICENSE"
+ },
+ "nwsapi@2.2.0": {
+ "licenses": "MIT",
+ "repository": "https://github.com/dperini/nwsapi",
+ "publisher": "Diego Perini",
+ "email": "diego.perini@gmail.com",
+ "url": "http://www.iport.it/",
+ "path": "node_modules/nwsapi",
+ "licenseFile": "node_modules/nwsapi/LICENSE"
+ },
+ "object-assign@4.1.1": {
+ "licenses": "MIT",
+ "repository": "https://github.com/sindresorhus/object-assign",
+ "publisher": "Sindre Sorhus",
+ "email": "sindresorhus@gmail.com",
+ "url": "sindresorhus.com",
+ "path": "node_modules/object-assign",
+ "licenseFile": "node_modules/object-assign/license"
+ },
+ "once@1.4.0": {
+ "licenses": "ISC",
+ "repository": "https://github.com/isaacs/once",
+ "publisher": "Isaac Z. Schlueter",
+ "email": "i@izs.me",
+ "url": "http://blog.izs.me/",
+ "path": "node_modules/once",
+ "licenseFile": "node_modules/once/LICENSE"
+ },
+ "optionator@0.8.3": {
+ "licenses": "MIT",
+ "repository": "https://github.com/gkz/optionator",
+ "publisher": "George Zahariev",
+ "email": "z@georgezahariev.com",
+ "path": "node_modules/optionator",
+ "licenseFile": "node_modules/optionator/LICENSE"
+ },
+ "panzoom@9.4.3": {
+ "licenses": "MIT",
+ "repository": "https://github.com/anvaka/panzoom",
+ "publisher": "Andrei Kashcha",
+ "path": "node_modules/panzoom",
+ "licenseFile": "node_modules/panzoom/LICENSE"
+ },
+ "parse5@6.0.1": {
+ "licenses": "MIT",
+ "repository": "https://github.com/inikulin/parse5",
+ "publisher": "Ivan Nikulin",
+ "email": "ifaaan@gmail.com",
+ "url": "https://github.com/inikulin",
+ "path": "node_modules/parse5",
+ "licenseFile": "node_modules/parse5/LICENSE"
+ },
+ "path-is-absolute@1.0.1": {
+ "licenses": "MIT",
+ "repository": "https://github.com/sindresorhus/path-is-absolute",
+ "publisher": "Sindre Sorhus",
+ "email": "sindresorhus@gmail.com",
+ "url": "sindresorhus.com",
+ "path": "node_modules/path-is-absolute",
+ "licenseFile": "node_modules/path-is-absolute/license"
+ },
+ "prelude-ls@1.1.2": {
+ "licenses": "MIT",
+ "repository": "https://github.com/gkz/prelude-ls",
+ "publisher": "George Zahariev",
+ "email": "z@georgezahariev.com",
+ "path": "node_modules/prelude-ls",
+ "licenseFile": "node_modules/prelude-ls/LICENSE"
+ },
"protobufjs@7.2.1": {
"licenses": "BSD-3-Clause",
"repository": "https://github.com/protobufjs/protobuf.js",
@@ -538,6 +1195,38 @@
"path": "node_modules/protobufjs",
"licenseFile": "node_modules/protobufjs/LICENSE"
},
+ "psl@1.8.0": {
+ "licenses": "MIT",
+ "repository": "https://github.com/lupomontero/psl",
+ "publisher": "Lupo Montero",
+ "email": "lupomontero@gmail.com",
+ "url": "https://lupomontero.com/",
+ "path": "node_modules/psl",
+ "licenseFile": "node_modules/psl/LICENSE"
+ },
+ "punycode@2.1.1": {
+ "licenses": "MIT",
+ "repository": "https://github.com/bestiejs/punycode.js",
+ "publisher": "Mathias Bynens",
+ "url": "https://mathiasbynens.be/",
+ "path": "node_modules/punycode",
+ "licenseFile": "node_modules/punycode/LICENSE-MIT.txt"
+ },
+ "readable-stream@3.6.0": {
+ "licenses": "MIT",
+ "repository": "https://github.com/nodejs/readable-stream",
+ "path": "node_modules/readable-stream",
+ "licenseFile": "node_modules/readable-stream/LICENSE"
+ },
+ "rimraf@3.0.2": {
+ "licenses": "ISC",
+ "repository": "https://github.com/isaacs/rimraf",
+ "publisher": "Isaac Z. Schlueter",
+ "email": "i@izs.me",
+ "url": "http://blog.izs.me/",
+ "path": "node_modules/rimraf",
+ "licenseFile": "node_modules/rimraf/LICENSE"
+ },
"robust-predicates@3.0.1": {
"licenses": "Unlicense",
"repository": "https://github.com/mourner/robust-predicates",
@@ -553,6 +1242,15 @@
"path": "node_modules/rw",
"licenseFile": "node_modules/rw/LICENSE"
},
+ "safe-buffer@5.2.1": {
+ "licenses": "MIT",
+ "repository": "https://github.com/feross/safe-buffer",
+ "publisher": "Feross Aboukhadijeh",
+ "email": "feross@feross.org",
+ "url": "https://feross.org",
+ "path": "node_modules/string_decoder/node_modules/safe-buffer",
+ "licenseFile": "node_modules/string_decoder/node_modules/safe-buffer/LICENSE"
+ },
"safer-buffer@2.1.2": {
"licenses": "MIT",
"repository": "https://github.com/ChALkeR/safer-buffer",
@@ -561,6 +1259,289 @@
"url": "https://github.com/ChALkeR",
"path": "node_modules/safer-buffer",
"licenseFile": "node_modules/safer-buffer/LICENSE"
+ },
+ "saxes@5.0.1": {
+ "licenses": "ISC",
+ "repository": "https://github.com/lddubeau/saxes",
+ "publisher": "Louis-Dominique Dubeau",
+ "email": "ldd@lddubeau.com",
+ "path": "node_modules/saxes",
+ "licenseFile": "node_modules/saxes/README.md"
+ },
+ "semver@6.3.0": {
+ "licenses": "ISC",
+ "repository": "https://github.com/npm/node-semver",
+ "path": "node_modules/make-dir/node_modules/semver",
+ "licenseFile": "node_modules/make-dir/node_modules/semver/LICENSE"
+ },
+ "semver@7.3.5": {
+ "licenses": "ISC",
+ "repository": "https://github.com/npm/node-semver",
+ "path": "node_modules/semver",
+ "licenseFile": "node_modules/semver/LICENSE"
+ },
+ "set-blocking@2.0.0": {
+ "licenses": "ISC",
+ "repository": "https://github.com/yargs/set-blocking",
+ "publisher": "Ben Coe",
+ "email": "ben@npmjs.com",
+ "path": "node_modules/set-blocking",
+ "licenseFile": "node_modules/set-blocking/LICENSE.txt"
+ },
+ "signal-exit@3.0.7": {
+ "licenses": "ISC",
+ "repository": "https://github.com/tapjs/signal-exit",
+ "publisher": "Ben Coe",
+ "email": "ben@npmjs.com",
+ "path": "node_modules/signal-exit",
+ "licenseFile": "node_modules/signal-exit/LICENSE.txt"
+ },
+ "simple-concat@1.0.1": {
+ "licenses": "MIT",
+ "repository": "https://github.com/feross/simple-concat",
+ "publisher": "Feross Aboukhadijeh",
+ "email": "feross@feross.org",
+ "url": "https://feross.org",
+ "path": "node_modules/simple-concat",
+ "licenseFile": "node_modules/simple-concat/LICENSE"
+ },
+ "simple-get@3.1.1": {
+ "licenses": "MIT",
+ "repository": "https://github.com/feross/simple-get",
+ "publisher": "Feross Aboukhadijeh",
+ "email": "feross@feross.org",
+ "url": "http://feross.org/",
+ "path": "node_modules/simple-get",
+ "licenseFile": "node_modules/simple-get/LICENSE"
+ },
+ "source-map@0.6.1": {
+ "licenses": "BSD-3-Clause",
+ "repository": "https://github.com/mozilla/source-map",
+ "publisher": "Nick Fitzgerald",
+ "email": "nfitzgerald@mozilla.com",
+ "path": "node_modules/source-map",
+ "licenseFile": "node_modules/source-map/LICENSE"
+ },
+ "string-width@4.2.3": {
+ "licenses": "MIT",
+ "repository": "https://github.com/sindresorhus/string-width",
+ "publisher": "Sindre Sorhus",
+ "email": "sindresorhus@gmail.com",
+ "url": "sindresorhus.com",
+ "path": "node_modules/string-width",
+ "licenseFile": "node_modules/string-width/license"
+ },
+ "string_decoder@1.3.0": {
+ "licenses": "MIT",
+ "repository": "https://github.com/nodejs/string_decoder",
+ "path": "node_modules/string_decoder",
+ "licenseFile": "node_modules/string_decoder/LICENSE"
+ },
+ "strip-ansi@6.0.1": {
+ "licenses": "MIT",
+ "repository": "https://github.com/chalk/strip-ansi",
+ "publisher": "Sindre Sorhus",
+ "email": "sindresorhus@gmail.com",
+ "url": "sindresorhus.com",
+ "path": "node_modules/strip-ansi",
+ "licenseFile": "node_modules/strip-ansi/license"
+ },
+ "symbol-tree@3.2.4": {
+ "licenses": "MIT",
+ "repository": "https://github.com/jsdom/js-symbol-tree",
+ "publisher": "Joris van der Wel",
+ "email": "joris@jorisvanderwel.com",
+ "path": "node_modules/symbol-tree",
+ "licenseFile": "node_modules/symbol-tree/LICENSE"
+ },
+ "tar@6.1.13": {
+ "licenses": "ISC",
+ "repository": "https://github.com/npm/node-tar",
+ "publisher": "GitHub Inc.",
+ "path": "node_modules/tar",
+ "licenseFile": "node_modules/tar/LICENSE"
+ },
+ "tough-cookie@4.0.0": {
+ "licenses": "BSD-3-Clause",
+ "repository": "https://github.com/salesforce/tough-cookie",
+ "publisher": "Jeremy Stashewsky",
+ "email": "jstash@gmail.com",
+ "path": "node_modules/tough-cookie",
+ "licenseFile": "node_modules/tough-cookie/LICENSE"
+ },
+ "tr46@0.0.3": {
+ "licenses": "MIT",
+ "repository": "https://github.com/Sebmaster/tr46.js",
+ "publisher": "Sebastian Mayr",
+ "email": "npm@smayr.name",
+ "path": "node_modules/node-fetch/node_modules/tr46"
+ },
+ "tr46@3.0.0": {
+ "licenses": "MIT",
+ "repository": "https://github.com/jsdom/tr46",
+ "publisher": "Sebastian Mayr",
+ "email": "npm@smayr.name",
+ "path": "node_modules/tr46",
+ "licenseFile": "node_modules/tr46/LICENSE.md"
+ },
+ "type-check@0.3.2": {
+ "licenses": "MIT",
+ "repository": "https://github.com/gkz/type-check",
+ "publisher": "George Zahariev",
+ "email": "z@georgezahariev.com",
+ "path": "node_modules/type-check",
+ "licenseFile": "node_modules/type-check/LICENSE"
+ },
+ "universalify@0.1.2": {
+ "licenses": "MIT",
+ "repository": "https://github.com/RyanZim/universalify",
+ "publisher": "Ryan Zimmerman",
+ "email": "opensrc@ryanzim.com",
+ "path": "node_modules/universalify",
+ "licenseFile": "node_modules/universalify/LICENSE"
+ },
+ "util-deprecate@1.0.2": {
+ "licenses": "MIT",
+ "repository": "https://github.com/TooTallNate/util-deprecate",
+ "publisher": "Nathan Rajlich",
+ "email": "nathan@tootallnate.net",
+ "url": "http://n8.io/",
+ "path": "node_modules/util-deprecate",
+ "licenseFile": "node_modules/util-deprecate/LICENSE"
+ },
+ "w3c-hr-time@1.0.2": {
+ "licenses": "MIT",
+ "repository": "https://github.com/jsdom/w3c-hr-time",
+ "publisher": "Timothy Gu",
+ "email": "timothygu99@gmail.com",
+ "path": "node_modules/w3c-hr-time",
+ "licenseFile": "node_modules/w3c-hr-time/LICENSE.md"
+ },
+ "w3c-xmlserializer@3.0.0": {
+ "licenses": "MIT",
+ "repository": "https://github.com/jsdom/w3c-xmlserializer",
+ "path": "node_modules/w3c-xmlserializer",
+ "licenseFile": "node_modules/w3c-xmlserializer/LICENSE.md"
+ },
+ "webidl-conversions@3.0.1": {
+ "licenses": "BSD-2-Clause",
+ "repository": "https://github.com/jsdom/webidl-conversions",
+ "publisher": "Domenic Denicola",
+ "email": "d@domenic.me",
+ "url": "https://domenic.me/",
+ "path": "node_modules/node-fetch/node_modules/webidl-conversions",
+ "licenseFile": "node_modules/node-fetch/node_modules/webidl-conversions/LICENSE.md"
+ },
+ "webidl-conversions@7.0.0": {
+ "licenses": "BSD-2-Clause",
+ "repository": "https://github.com/jsdom/webidl-conversions",
+ "publisher": "Domenic Denicola",
+ "email": "d@domenic.me",
+ "url": "https://domenic.me/",
+ "path": "node_modules/webidl-conversions",
+ "licenseFile": "node_modules/webidl-conversions/LICENSE.md"
+ },
+ "whatwg-encoding@2.0.0": {
+ "licenses": "MIT",
+ "repository": "https://github.com/jsdom/whatwg-encoding",
+ "publisher": "Domenic Denicola",
+ "email": "d@domenic.me",
+ "url": "https://domenic.me/",
+ "path": "node_modules/whatwg-encoding",
+ "licenseFile": "node_modules/whatwg-encoding/LICENSE.txt"
+ },
+ "whatwg-mimetype@3.0.0": {
+ "licenses": "MIT",
+ "repository": "https://github.com/jsdom/whatwg-mimetype",
+ "publisher": "Domenic Denicola",
+ "email": "d@domenic.me",
+ "url": "https://domenic.me/",
+ "path": "node_modules/whatwg-mimetype",
+ "licenseFile": "node_modules/whatwg-mimetype/LICENSE.txt"
+ },
+ "whatwg-url@10.0.0": {
+ "licenses": "MIT",
+ "repository": "https://github.com/jsdom/whatwg-url",
+ "publisher": "Sebastian Mayr",
+ "email": "github@smayr.name",
+ "path": "node_modules/whatwg-url",
+ "licenseFile": "node_modules/whatwg-url/LICENSE.txt"
+ },
+ "whatwg-url@5.0.0": {
+ "licenses": "MIT",
+ "repository": "https://github.com/jsdom/whatwg-url",
+ "publisher": "Sebastian Mayr",
+ "email": "github@smayr.name",
+ "path": "node_modules/node-fetch/node_modules/whatwg-url",
+ "licenseFile": "node_modules/node-fetch/node_modules/whatwg-url/LICENSE.txt"
+ },
+ "wheel@1.0.0": {
+ "licenses": "MIT",
+ "repository": "https://github.com/anvaka/wheel",
+ "publisher": "Andrei Kashcha",
+ "path": "node_modules/wheel",
+ "licenseFile": "node_modules/wheel/LICENSE"
+ },
+ "wide-align@1.1.5": {
+ "licenses": "ISC",
+ "repository": "https://github.com/iarna/wide-align",
+ "publisher": "Rebecca Turner",
+ "email": "me@re-becca.org",
+ "url": "http://re-becca.org/",
+ "path": "node_modules/wide-align",
+ "licenseFile": "node_modules/wide-align/LICENSE"
+ },
+ "word-wrap@1.2.3": {
+ "licenses": "MIT",
+ "repository": "https://github.com/jonschlinkert/word-wrap",
+ "publisher": "Jon Schlinkert",
+ "url": "https://github.com/jonschlinkert",
+ "path": "node_modules/word-wrap",
+ "licenseFile": "node_modules/word-wrap/LICENSE"
+ },
+ "wrappy@1.0.2": {
+ "licenses": "ISC",
+ "repository": "https://github.com/npm/wrappy",
+ "publisher": "Isaac Z. Schlueter",
+ "email": "i@izs.me",
+ "url": "http://blog.izs.me/",
+ "path": "node_modules/wrappy",
+ "licenseFile": "node_modules/wrappy/LICENSE"
+ },
+ "ws@8.5.0": {
+ "licenses": "MIT",
+ "repository": "https://github.com/websockets/ws",
+ "publisher": "Einar Otto Stangvik",
+ "email": "einaros@gmail.com",
+ "url": "http://2x.io",
+ "path": "node_modules/ws",
+ "licenseFile": "node_modules/ws/LICENSE"
+ },
+ "xml-name-validator@4.0.0": {
+ "licenses": "Apache-2.0",
+ "repository": "https://github.com/jsdom/xml-name-validator",
+ "publisher": "Domenic Denicola",
+ "email": "d@domenic.me",
+ "url": "https://domenic.me/",
+ "path": "node_modules/xml-name-validator",
+ "licenseFile": "node_modules/xml-name-validator/LICENSE.txt"
+ },
+ "xmlchars@2.2.0": {
+ "licenses": "MIT",
+ "repository": "https://github.com/lddubeau/xmlchars",
+ "publisher": "Louis-Dominique Dubeau",
+ "email": "ldd@lddubeau.com",
+ "path": "node_modules/xmlchars",
+ "licenseFile": "node_modules/xmlchars/LICENSE"
+ },
+ "yallist@4.0.0": {
+ "licenses": "ISC",
+ "repository": "https://github.com/isaacs/yallist",
+ "publisher": "Isaac Z. Schlueter",
+ "email": "i@izs.me",
+ "url": "http://blog.izs.me/",
+ "path": "node_modules/yallist",
+ "licenseFile": "node_modules/yallist/LICENSE"
}
}
diff --git a/ts/reviewer/image_occlusion.ts b/ts/reviewer/image_occlusion.ts
new file mode 100644
index 000000000..dd38c5fac
--- /dev/null
+++ b/ts/reviewer/image_occlusion.ts
@@ -0,0 +1,194 @@
+// Copyright: Ankitects Pty Ltd and contributors
+// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
+
+window.addEventListener("load", () => {
+ window.addEventListener("resize", setupImageCloze);
+});
+
+export function setupImageCloze(): void {
+ const canvas: HTMLCanvasElement = document.querySelector("canvas")! as HTMLCanvasElement;
+ canvas.style.maxWidth = "100%";
+ canvas.style.maxHeight = "95vh";
+
+ const ctx: CanvasRenderingContext2D = canvas.getContext("2d")!;
+ const image = document.getElementById("img") as HTMLImageElement;
+ const size = limitSize({ width: image.naturalWidth, height: image.naturalHeight });
+ canvas.width = size.width;
+ canvas.height = size.height;
+
+ // set height for div container (used 'relative' in css)
+ const container = document.getElementById("container") as HTMLDivElement;
+ container.style.height = `${image.height}px`;
+
+ // setup button for toggle image occlusion
+ const button = document.getElementById("toggle");
+ if (button) {
+ button.addEventListener("click", toggleMasks);
+ }
+
+ drawShapes(ctx);
+}
+
+function drawShapes(ctx: CanvasRenderingContext2D): void {
+ const activeCloze = document.querySelectorAll(".cloze");
+ const inActiveCloze = document.querySelectorAll(".cloze-inactive");
+ const shapeProperty = getShapeProperty();
+
+ for (const clz of activeCloze) {
+ const cloze = ( clz);
+ const shape = cloze.dataset.shape!;
+ const fill = shapeProperty.activeShapeColor;
+ draw(ctx, cloze, shape, fill, shapeProperty.activeBorder);
+ }
+
+ for (const clz of inActiveCloze) {
+ const cloze = ( clz);
+ const shape = cloze.dataset.shape!;
+ const fill = shapeProperty.inActiveShapeColor;
+ const hideinactive = cloze.dataset.hideinactive == "true";
+ if (!hideinactive) {
+ draw(ctx, cloze, shape, fill, shapeProperty.inActiveBorder);
+ }
+ }
+}
+
+function draw(
+ ctx: CanvasRenderingContext2D,
+ cloze: HTMLDivElement,
+ shape: string,
+ color: string,
+ border: { width: number; color: string },
+): void {
+ ctx.fillStyle = color;
+
+ const posLeft = parseFloat(cloze.dataset.left!);
+ const posTop = parseFloat(cloze.dataset.top!);
+ const width = parseFloat(cloze.dataset.width!);
+ const height = parseFloat(cloze.dataset.height!);
+
+ switch (shape) {
+ case "rect":
+ {
+ ctx.strokeStyle = border.color;
+ ctx.lineWidth = border.width;
+ ctx.fillRect(posLeft, posTop, width, height);
+ ctx.strokeRect(posLeft, posTop, width, height);
+ }
+ break;
+
+ case "ellipse":
+ {
+ const rx = parseFloat(cloze.dataset.rx!);
+ const ry = parseFloat(cloze.dataset.ry!);
+ const newLeft = posLeft + rx;
+ const newTop = posTop + ry;
+ ctx.beginPath();
+ ctx.strokeStyle = border.color;
+ ctx.lineWidth = border.width;
+ ctx.ellipse(newLeft, newTop, rx, ry, 0, 0, Math.PI * 2, false);
+ ctx.closePath();
+ ctx.fill();
+ ctx.stroke();
+ }
+ break;
+
+ case "polygon":
+ {
+ const points = JSON.parse(cloze.dataset.points!);
+ ctx.beginPath();
+ ctx.strokeStyle = border.color;
+ ctx.lineWidth = border.width;
+ ctx.moveTo(points[0][0], points[0][1]);
+ for (let i = 1; i < points.length; i++) {
+ ctx.lineTo(points[i][0], points[i][1]);
+ }
+ ctx.closePath();
+ ctx.fill();
+ ctx.stroke();
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+// following function copy+pasted from mask-editor.ts,
+// so update both, if it changes
+function limitSize(size: { width: number; height: number }): { width: number; height: number; scalar: number } {
+ const maximumPixels = 1000000;
+ const { width, height } = size;
+
+ const requiredPixels = width * height;
+ if (requiredPixels <= maximumPixels) return { width, height, scalar: 1 };
+
+ const scalar = Math.sqrt(maximumPixels) / Math.sqrt(requiredPixels);
+ return {
+ width: Math.floor(width * scalar),
+ height: Math.floor(height * scalar),
+ scalar: scalar,
+ };
+}
+
+function getShapeProperty(): {
+ activeShapeColor: string;
+ inActiveShapeColor: string;
+ activeBorder: { width: number; color: string };
+ inActiveBorder: { width: number; color: string };
+} {
+ const canvas = document.getElementById("canvas");
+ const computedStyle = window.getComputedStyle(canvas!);
+ // it may throw error if the css variable is not defined
+ try {
+ // shape color
+ const activeShapeColor = computedStyle.getPropertyValue("--active-shape-color");
+ const inActiveShapeColor = computedStyle.getPropertyValue("--inactive-shape-color");
+ // inactive shape border
+ const inActiveShapeBorder = computedStyle.getPropertyValue("--inactive-shape-border");
+ const inActiveBorder = inActiveShapeBorder.split(" ").filter((x) => x);
+ const inActiveShapeBorderWidth = parseFloat(inActiveBorder[0]);
+ const inActiveShapeBorderColor = inActiveBorder[1];
+ // active shape border
+ const activeShapeBorder = computedStyle.getPropertyValue("--active-shape-border");
+ const activeBorder = activeShapeBorder.split(" ").filter((x) => x);
+ const activeShapeBorderWidth = parseFloat(activeBorder[0]);
+ const activeShapeBorderColor = activeBorder[1];
+
+ return {
+ activeShapeColor: activeShapeColor ? activeShapeColor : "#ff8e8e",
+ inActiveShapeColor: inActiveShapeColor ? inActiveShapeColor : "#ffeba2",
+ activeBorder: {
+ width: activeShapeBorderWidth ? activeShapeBorderWidth : 1,
+ color: activeShapeBorderColor ? activeShapeBorderColor : "#212121",
+ },
+ inActiveBorder: {
+ width: inActiveShapeBorderWidth ? inActiveShapeBorderWidth : 1,
+ color: inActiveShapeBorderColor ? inActiveShapeBorderColor : "#212121",
+ },
+ };
+ } catch {
+ // return default values
+ return {
+ activeShapeColor: "#ff8e8e",
+ inActiveShapeColor: "#ffeba2",
+ activeBorder: {
+ width: 1,
+ color: "#212121",
+ },
+ inActiveBorder: {
+ width: 1,
+ color: "#212121",
+ },
+ };
+ }
+}
+
+const toggleMasks = (): void => {
+ const canvas = document.getElementById("canvas") as HTMLCanvasElement;
+ const display = canvas.style.display;
+ if (display === "none") {
+ canvas.style.display = "unset";
+ } else {
+ canvas.style.display = "none";
+ }
+};
diff --git a/ts/reviewer/index.ts b/ts/reviewer/index.ts
index c382f5182..44fef54c4 100644
--- a/ts/reviewer/index.ts
+++ b/ts/reviewer/index.ts
@@ -10,9 +10,11 @@ import "css-browser-selector/css_browser_selector.min";
export { default as $, default as jQuery } from "jquery/dist/jquery";
import { mutateNextCardStates } from "./answering";
+import { setupImageCloze } from "./image_occlusion";
globalThis.anki = globalThis.anki || {};
globalThis.anki.mutateNextCardStates = mutateNextCardStates;
+globalThis.anki.setupImageCloze = setupImageCloze;
import { bridgeCommand } from "@tslib/bridgecommand";
diff --git a/ts/reviewer/reviewer_extras.ts b/ts/reviewer/reviewer_extras.ts
index f1c0d7a0c..98bab5b52 100644
--- a/ts/reviewer/reviewer_extras.ts
+++ b/ts/reviewer/reviewer_extras.ts
@@ -9,6 +9,8 @@
// When all clients are using reviewer.js directly, we can get rid of this.
import { mutateNextCardStates } from "./answering";
+import { setupImageCloze } from "./image_occlusion";
globalThis.anki = globalThis.anki || {};
globalThis.anki.mutateNextCardStates = mutateNextCardStates;
+globalThis.anki.setupImageCloze = setupImageCloze;
diff --git a/yarn.lock b/yarn.lock
index 07782b233..fcedb397d 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"