anki/ts/image-occlusion/shapes/to-cloze.ts
Mani 135de7f9ed
image occlusion button in note editor (#2485)
* setup mask editor in note editor
- add image on mask button click (only one time)
- show hide add button for io on notetype change
- hide field in io notetype
- icon for toggle
and replace image

* add update io notes

* Tidy up i/o notetype check and fix error

- Make it a method on editor
- Use .get(), because the setting doesn't exist on older notetypes
- Pass the bool value into the ts code, instead of the enum

* reset io page after adding

* remove adjust function & add target for mask editor

* handle browse mode & merged sidetoolbar and toptoolbar to toolbar

* fix: shape, button click in browse, dropdown menu

* add arrow to add button

* store for handling visiblity of maskeditor
- remove update  button in edit mode, implement autoupdate

* update var name

* simplify store
2023-07-27 22:45:49 +10:00

110 lines
3.6 KiB
TypeScript

// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import type { Canvas, Object as FabricObject } from "fabric";
import { makeMaskTransparent } from "../tools/lib";
import type { Size } from "../types";
import type { Shape, ShapeOrShapes } from "./base";
import { Ellipse } from "./ellipse";
import { Polygon } from "./polygon";
import { Rectangle } from "./rectangle";
export function exportShapesToClozeDeletions(occludeInactive: boolean): { clozes: string; noteCount: number } {
const shapes = baseShapesFromFabric(occludeInactive);
let clozes = "";
shapes.forEach((shapeOrShapes, index) => {
clozes += shapeOrShapesToCloze(shapeOrShapes, index);
});
return { clozes, noteCount: shapes.length };
}
/** Gather all Fabric shapes, and convert them into BaseShapes or
* BaseShape[]s.
*/
function baseShapesFromFabric(occludeInactive: boolean): ShapeOrShapes[] {
const canvas = globalThis.canvas as Canvas;
makeMaskTransparent(canvas, false);
const objects = canvas.getObjects() as FabricObject[];
return objects.map((object) => {
return fabricObjectToBaseShapeOrShapes(canvas, object, occludeInactive);
}).filter((o): o is ShapeOrShapes => o !== null);
}
interface TopAndLeftOffset {
top: number;
left: number;
}
/** Convert a single Fabric object/group to one or more BaseShapes. */
function fabricObjectToBaseShapeOrShapes(
size: Size,
object: FabricObject,
occludeInactive: boolean,
groupOffset: TopAndLeftOffset = { top: 0, left: 0 },
): ShapeOrShapes | null {
let shape: Shape;
switch (object.type) {
case "rect":
shape = new Rectangle(object);
break;
case "ellipse":
shape = new Ellipse(object);
break;
case "polygon":
shape = new Polygon(object);
break;
case "group":
// Positions inside a group are relative to the group, so we
// need to pass in an offset. We do not support nested groups.
groupOffset = {
left: object.left + object.width / 2,
top: object.top + object.height / 2,
};
return object._objects.map((obj) => {
return fabricObjectToBaseShapeOrShapes(size, obj, occludeInactive, groupOffset);
});
default:
return null;
}
shape.occludeInactive = occludeInactive;
shape.left += groupOffset.left;
shape.top += groupOffset.top;
shape.makeNormal(size);
return shape;
}
/** generate cloze data in form of
{{c1::image-occlusion:rect:top=.1:left=.23:width=.4:height=.5}} */
function shapeOrShapesToCloze(shapeOrShapes: ShapeOrShapes, index: number): string {
let text = "";
function addKeyValue(key: string, value: string) {
if (Number.isNaN(Number(value))) {
value = ".0000";
}
text += `:${key}=${value}`;
}
let type: string;
if (Array.isArray(shapeOrShapes)) {
return shapeOrShapes.map((shape) => shapeOrShapesToCloze(shape, index)).join("");
} else if (shapeOrShapes instanceof Rectangle) {
type = "rect";
} else if (shapeOrShapes instanceof Ellipse) {
type = "ellipse";
} else if (shapeOrShapes instanceof Polygon) {
type = "polygon";
} else {
throw new Error("Unknown shape type");
}
for (const [key, value] of Object.entries(shapeOrShapes.toDataForCloze())) {
addKeyValue(key, value);
}
text = `{{c${index + 1}::image-occlusion:${type}${text}}}<br>`;
return text;
}