mirror of
https://github.com/ankitects/anki.git
synced 2025-11-06 12:47:11 -05:00
This PR adds the "hide all but one" occlusion mode. An example use case is a note containing a collection of pairs of selection, where each selection is the prompt for the other in its pair. For example, given a table like | small | big | |-------+-----| | a | A | | b | B | | c | C | in each card, five letters are occluded, and one is shown. The user is prompted to state the occluded symbol that is adjacent to the shown symbol.
137 lines
4.4 KiB
TypeScript
137 lines
4.4 KiB
TypeScript
// Copyright: Ankitects Pty Ltd and contributors
|
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|
|
|
/* eslint
|
|
@typescript-eslint/no-explicit-any: "off",
|
|
*/
|
|
|
|
import type { GetImageOcclusionNoteResponse_ImageOcclusion } from "@generated/anki/image_occlusion_pb";
|
|
|
|
import type { Shape, ShapeOrShapes } from "./base";
|
|
import { Ellipse } from "./ellipse";
|
|
import { storedToAngle } from "./lib";
|
|
import { Point, Polygon } from "./polygon";
|
|
import { Rectangle } from "./rectangle";
|
|
import { Text } from "./text";
|
|
|
|
export function extractShapesFromClozedField(
|
|
occlusions: GetImageOcclusionNoteResponse_ImageOcclusion[],
|
|
): ShapeOrShapes[] {
|
|
const output: ShapeOrShapes[] = [];
|
|
for (const occlusion of occlusions) {
|
|
const group: Shape[] = [];
|
|
for (const shape of occlusion.shapes) {
|
|
if (isValidType(shape.shape)) {
|
|
const props: Record<string, any> = Object.fromEntries(
|
|
shape.properties.map(prop => [prop.name, prop.value]),
|
|
);
|
|
props.ordinal = occlusion.ordinal;
|
|
group.push(buildShape(shape.shape, props));
|
|
}
|
|
}
|
|
if (occlusion.ordinal === 0) {
|
|
output.push(...group);
|
|
} else if (group.length > 1) {
|
|
output.push(group);
|
|
} else {
|
|
output.push(group[0]);
|
|
}
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
/** Locate all cloze divs in the review screen for the given selector, and convert them into BaseShapes.
|
|
*/
|
|
export function extractShapesFromRenderedClozes(selector: string): Shape[] {
|
|
return Array.from(document.querySelectorAll(selector)).flatMap((cloze) => {
|
|
if (cloze instanceof HTMLDivElement) {
|
|
return extractShapeFromRenderedCloze(cloze) ?? [];
|
|
} else {
|
|
return [];
|
|
}
|
|
});
|
|
}
|
|
|
|
function extractShapeFromRenderedCloze(cloze: HTMLDivElement): Shape | null {
|
|
const type = cloze.dataset.shape!;
|
|
if (
|
|
type !== "rect"
|
|
&& type !== "ellipse"
|
|
&& type !== "polygon"
|
|
&& type !== "text"
|
|
) {
|
|
return null;
|
|
}
|
|
const props = {
|
|
occlusionMode: cloze.dataset.occludeinactive ? parseInt(cloze.dataset.occludeinactive) : undefined,
|
|
ordinal: parseInt(cloze.dataset.ordinal!),
|
|
left: cloze.dataset.left,
|
|
top: cloze.dataset.top,
|
|
width: cloze.dataset.width,
|
|
height: cloze.dataset.height,
|
|
rx: cloze.dataset.rx,
|
|
ry: cloze.dataset.ry,
|
|
points: cloze.dataset.points,
|
|
text: cloze.dataset.text,
|
|
scale: cloze.dataset.scale,
|
|
fs: cloze.dataset.fontSize,
|
|
angle: cloze.dataset.angle,
|
|
...(cloze.dataset.fill == null ? {} : { fill: cloze.dataset.fill }),
|
|
};
|
|
return buildShape(type, props);
|
|
}
|
|
|
|
type ShapeType = "rect" | "ellipse" | "polygon" | "text";
|
|
|
|
function isValidType(type: string): type is ShapeType {
|
|
return ["rect", "ellipse", "polygon", "text"].includes(type);
|
|
}
|
|
|
|
function buildShape(type: ShapeType, props: Record<string, any>): Shape {
|
|
props.left = parseFloat(
|
|
Number.isNaN(Number(props.left)) ? ".0000" : props.left,
|
|
);
|
|
props.top = parseFloat(
|
|
Number.isNaN(Number(props.top)) ? ".0000" : props.top,
|
|
);
|
|
props.angle = storedToAngle(props.angle) ?? 0;
|
|
switch (type) {
|
|
case "rect": {
|
|
return new Rectangle({
|
|
...props,
|
|
width: parseFloat(props.width),
|
|
height: parseFloat(props.height),
|
|
});
|
|
}
|
|
case "ellipse": {
|
|
return new Ellipse({
|
|
...props,
|
|
rx: parseFloat(props.rx),
|
|
ry: parseFloat(props.ry),
|
|
});
|
|
}
|
|
case "polygon": {
|
|
if (props.points !== "") {
|
|
props.points = props.points.split(" ").map((point) => {
|
|
const [x, y] = point.split(",");
|
|
return new Point({ x, y });
|
|
});
|
|
} else {
|
|
props.points = [new Point({ x: 0, y: 0 })];
|
|
}
|
|
return new Polygon(props);
|
|
}
|
|
case "text": {
|
|
const textProps: Record<string, any> = {
|
|
...props,
|
|
scaleX: parseFloat(props.scale),
|
|
scaleY: parseFloat(props.scale),
|
|
};
|
|
if (props.fs) {
|
|
textProps.fontSize = parseFloat(props.fs);
|
|
}
|
|
return new Text(textProps);
|
|
}
|
|
}
|
|
}
|