mirror of
https://github.com/ankitects/anki.git
synced 2025-11-06 12:47:11 -05:00
Add "hide all but one" occlusion mode.
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.
This commit is contained in:
parent
dac26ce671
commit
33d1057a46
15 changed files with 133 additions and 73 deletions
|
|
@ -47,6 +47,7 @@ notetypes-toggle-masks = Toggle Masks
|
||||||
notetypes-image-occlusion-name = Image Occlusion
|
notetypes-image-occlusion-name = Image Occlusion
|
||||||
notetypes-hide-all-guess-one = Hide All, Guess One
|
notetypes-hide-all-guess-one = Hide All, Guess One
|
||||||
notetypes-hide-one-guess-one = Hide One, Guess One
|
notetypes-hide-one-guess-one = Hide One, Guess One
|
||||||
|
notetypes-hide-all-but-one = Hide All But One
|
||||||
notetypes-error-generating-cloze = An error occurred when generating an image occlusion note
|
notetypes-error-generating-cloze = An error occurred when generating an image occlusion note
|
||||||
notetypes-error-getting-imagecloze = An error occurred while fetching an image occlusion note
|
notetypes-error-getting-imagecloze = An error occurred while fetching an image occlusion note
|
||||||
notetypes-error-loading-image-occlusion = Error loading image occlusion. Is your Anki version up to date?
|
notetypes-error-loading-image-occlusion = Error loading image occlusion. Is your Anki version up to date?
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,12 @@ message GetImageOcclusionNoteResponse {
|
||||||
uint32 ordinal = 2;
|
uint32 ordinal = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum OcclusionMode {
|
||||||
|
HIDE_ONE = 0;
|
||||||
|
HIDE_ALL = 1;
|
||||||
|
HIDE_ALL_BUT_ONE = 2;
|
||||||
|
}
|
||||||
|
|
||||||
message ImageOcclusionNote {
|
message ImageOcclusionNote {
|
||||||
bytes image_data = 1;
|
bytes image_data = 1;
|
||||||
repeated ImageOcclusion occlusions = 2;
|
repeated ImageOcclusion occlusions = 2;
|
||||||
|
|
@ -76,7 +82,7 @@ message GetImageOcclusionNoteResponse {
|
||||||
string back_extra = 4;
|
string back_extra = 4;
|
||||||
repeated string tags = 5;
|
repeated string tags = 5;
|
||||||
string image_file_name = 6;
|
string image_file_name = 6;
|
||||||
bool occlude_inactive = 7;
|
OcclusionMode occlusion_mode = 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
oneof value {
|
oneof value {
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ use std::path::PathBuf;
|
||||||
use anki_io::metadata;
|
use anki_io::metadata;
|
||||||
use anki_io::read_file;
|
use anki_io::read_file;
|
||||||
use anki_proto::image_occlusion::get_image_occlusion_note_response::ImageOcclusionNote;
|
use anki_proto::image_occlusion::get_image_occlusion_note_response::ImageOcclusionNote;
|
||||||
|
use anki_proto::image_occlusion::get_image_occlusion_note_response::OcclusionMode;
|
||||||
use anki_proto::image_occlusion::get_image_occlusion_note_response::Value;
|
use anki_proto::image_occlusion::get_image_occlusion_note_response::Value;
|
||||||
use anki_proto::image_occlusion::AddImageOcclusionNoteRequest;
|
use anki_proto::image_occlusion::AddImageOcclusionNoteRequest;
|
||||||
use anki_proto::image_occlusion::GetImageForOcclusionResponse;
|
use anki_proto::image_occlusion::GetImageForOcclusionResponse;
|
||||||
|
|
@ -97,14 +98,22 @@ impl Collection {
|
||||||
let idxs = nt.get_io_field_indexes()?;
|
let idxs = nt.get_io_field_indexes()?;
|
||||||
|
|
||||||
cloze_note.occlusions = parse_image_occlusions(fields[idxs.occlusions as usize].as_str());
|
cloze_note.occlusions = parse_image_occlusions(fields[idxs.occlusions as usize].as_str());
|
||||||
cloze_note.occlude_inactive = cloze_note.occlusions.iter().any(|oc| {
|
cloze_note.occlusion_mode = cloze_note
|
||||||
oc.shapes.iter().any(|sh| {
|
.occlusions
|
||||||
|
.iter()
|
||||||
|
.find_map(|oc| {
|
||||||
|
oc.shapes.iter().find_map(|sh| {
|
||||||
sh.properties
|
sh.properties
|
||||||
.iter()
|
.iter()
|
||||||
.find(|p| p.name == "oi")
|
.find(|p| p.name == "oi")
|
||||||
.is_some_and(|p| p.value == "1")
|
.and_then(|p| match p.value.as_str() {
|
||||||
|
"1" => Some(OcclusionMode::HideAll as i32),
|
||||||
|
"2" => Some(OcclusionMode::HideAllButOne as i32),
|
||||||
|
_ => None,
|
||||||
})
|
})
|
||||||
});
|
})
|
||||||
|
})
|
||||||
|
.unwrap_or(OcclusionMode::HideOne as i32);
|
||||||
cloze_note.header.clone_from(&fields[idxs.header as usize]);
|
cloze_note.header.clone_from(&fields[idxs.header as usize]);
|
||||||
cloze_note
|
cloze_note
|
||||||
.back_extra
|
.back_extra
|
||||||
|
|
|
||||||
|
|
@ -423,9 +423,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import type { IOMode } from "../routes/image-occlusion/lib";
|
import type { IOMode } from "../routes/image-occlusion/lib";
|
||||||
import { exportShapesToClozeDeletions } from "../routes/image-occlusion/shapes/to-cloze";
|
import { exportShapesToClozeDeletions } from "../routes/image-occlusion/shapes/to-cloze";
|
||||||
import {
|
import {
|
||||||
hideAllGuessOne,
|
|
||||||
ioImageLoadedStore,
|
ioImageLoadedStore,
|
||||||
ioMaskEditorVisible,
|
ioMaskEditorVisible,
|
||||||
|
occlusionMode,
|
||||||
} from "../routes/image-occlusion/store";
|
} from "../routes/image-occlusion/store";
|
||||||
import CollapseLabel from "./CollapseLabel.svelte";
|
import CollapseLabel from "./CollapseLabel.svelte";
|
||||||
import * as oldEditorAdapter from "./old-editor-adapter";
|
import * as oldEditorAdapter from "./old-editor-adapter";
|
||||||
|
|
@ -477,7 +477,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
function saveOcclusions(): void {
|
function saveOcclusions(): void {
|
||||||
if (isImageOcclusion && globalThis.canvas) {
|
if (isImageOcclusion && globalThis.canvas) {
|
||||||
const occlusionsData = exportShapesToClozeDeletions($hideAllGuessOne);
|
const occlusionsData = exportShapesToClozeDeletions($occlusionMode);
|
||||||
fieldStores[ioFields.occlusions].set(occlusionsData.clozes);
|
fieldStores[ioFields.occlusions].set(occlusionsData.clozes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ import AlignVerticalCenter_ from "@mdi/svg/svg/align-vertical-center.svg?compone
|
||||||
import alignVerticalCenter_ from "@mdi/svg/svg/align-vertical-center.svg?url";
|
import alignVerticalCenter_ from "@mdi/svg/svg/align-vertical-center.svg?url";
|
||||||
import AlignVerticalTop_ from "@mdi/svg/svg/align-vertical-top.svg?component";
|
import AlignVerticalTop_ from "@mdi/svg/svg/align-vertical-top.svg?component";
|
||||||
import alignVerticalTop_ from "@mdi/svg/svg/align-vertical-top.svg?url";
|
import alignVerticalTop_ from "@mdi/svg/svg/align-vertical-top.svg?url";
|
||||||
|
import CheckboxBlankOutline_ from "@mdi/svg/svg/checkbox-blank-outline.svg?component";
|
||||||
|
import checkboxBlankOutline_ from "@mdi/svg/svg/checkbox-blank-outline.svg?url";
|
||||||
import CheckCircle_ from "@mdi/svg/svg/check-circle.svg?component";
|
import CheckCircle_ from "@mdi/svg/svg/check-circle.svg?component";
|
||||||
import checkCircle_ from "@mdi/svg/svg/check-circle.svg?url";
|
import checkCircle_ from "@mdi/svg/svg/check-circle.svg?url";
|
||||||
import ChevronDown_ from "@mdi/svg/svg/chevron-down.svg?component";
|
import ChevronDown_ from "@mdi/svg/svg/chevron-down.svg?component";
|
||||||
|
|
@ -251,6 +253,7 @@ export const underlineIcon = { url: underline_, component: Underline_ };
|
||||||
export const deleteIcon = { url: delete_, component: Delete_ };
|
export const deleteIcon = { url: delete_, component: Delete_ };
|
||||||
export const inlineIcon = { url: inline_, component: Inline_ };
|
export const inlineIcon = { url: inline_, component: Inline_ };
|
||||||
export const blockIcon = { url: block_, component: Block_ };
|
export const blockIcon = { url: block_, component: Block_ };
|
||||||
|
export const mdiCheckboxBlankOutline = { url: checkboxBlankOutline_, component: CheckboxBlankOutline_ };
|
||||||
export const mdiAlignHorizontalCenter = { url: alignHorizontalCenter_, component: AlignHorizontalCenter_ };
|
export const mdiAlignHorizontalCenter = { url: alignHorizontalCenter_, component: AlignHorizontalCenter_ };
|
||||||
export const mdiAlignHorizontalLeft = { url: alignHorizontalLeft_, component: AlignHorizontalLeft_ };
|
export const mdiAlignHorizontalLeft = { url: alignHorizontalLeft_, component: AlignHorizontalLeft_ };
|
||||||
export const mdiAlignHorizontalRight = { url: alignHorizontalRight_, component: AlignHorizontalRight_ };
|
export const mdiAlignHorizontalRight = { url: alignHorizontalRight_, component: AlignHorizontalRight_ };
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import Icon from "$lib/components/Icon.svelte";
|
import Icon from "$lib/components/Icon.svelte";
|
||||||
import IconButton from "$lib/components/IconButton.svelte";
|
import IconButton from "$lib/components/IconButton.svelte";
|
||||||
import {
|
import {
|
||||||
|
mdiCheckboxBlankOutline,
|
||||||
mdiEye,
|
mdiEye,
|
||||||
mdiFormatAlignCenter,
|
mdiFormatAlignCenter,
|
||||||
mdiSquare,
|
mdiSquare,
|
||||||
|
|
@ -26,11 +27,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import WithFloating from "$lib/components/WithFloating.svelte";
|
import WithFloating from "$lib/components/WithFloating.svelte";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
hideAllGuessOne,
|
|
||||||
ioMaskEditorVisible,
|
ioMaskEditorVisible,
|
||||||
textEditingState,
|
OcclusionMode,
|
||||||
saveNeededStore,
|
occlusionMode,
|
||||||
opacityStateStore,
|
opacityStateStore,
|
||||||
|
saveNeededStore,
|
||||||
|
textEditingState,
|
||||||
} from "./store";
|
} from "./store";
|
||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
import { drawEllipse, drawPolygon, drawRectangle, drawText } from "./tools/index";
|
import { drawEllipse, drawPolygon, drawRectangle, drawText } from "./tools/index";
|
||||||
|
|
@ -228,8 +230,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
disablePan(canvas);
|
disablePan(canvas);
|
||||||
};
|
};
|
||||||
|
|
||||||
function changeOcclusionType(occlusionType: "all" | "one"): void {
|
function changeOcclusionType(mode: OcclusionMode): void {
|
||||||
$hideAllGuessOne = occlusionType === "all";
|
$occlusionMode = mode;
|
||||||
saveNeededStore.set(true);
|
saveNeededStore.set(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -312,22 +314,34 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
{iconSize}
|
{iconSize}
|
||||||
on:click={() => (showFloating = !showFloating)}
|
on:click={() => (showFloating = !showFloating)}
|
||||||
>
|
>
|
||||||
<Icon icon={$hideAllGuessOne ? mdiViewDashboard : mdiSquare} />
|
<Icon
|
||||||
|
icon={$occlusionMode === OcclusionMode.HideAll
|
||||||
|
? mdiViewDashboard
|
||||||
|
: $occlusionMode === OcclusionMode.HideAllButOne
|
||||||
|
? mdiCheckboxBlankOutline
|
||||||
|
: mdiSquare}
|
||||||
|
/>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|
||||||
<Popover slot="floating">
|
<Popover slot="floating">
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
active={$hideAllGuessOne}
|
active={$occlusionMode === OcclusionMode.HideAll}
|
||||||
on:click={() => changeOcclusionType("all")}
|
on:click={() => changeOcclusionType(OcclusionMode.HideAll)}
|
||||||
>
|
>
|
||||||
<span>{tr.notetypesHideAllGuessOne()}</span>
|
<span>{tr.notetypesHideAllGuessOne()}</span>
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
active={!$hideAllGuessOne}
|
active={$occlusionMode === OcclusionMode.HideOne}
|
||||||
on:click={() => changeOcclusionType("one")}
|
on:click={() => changeOcclusionType(OcclusionMode.HideOne)}
|
||||||
>
|
>
|
||||||
<span>{tr.notetypesHideOneGuessOne()}</span>
|
<span>{tr.notetypesHideOneGuessOne()}</span>
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
|
<DropdownItem
|
||||||
|
active={$occlusionMode === OcclusionMode.HideAllButOne}
|
||||||
|
on:click={() => changeOcclusionType(OcclusionMode.HideAllButOne)}
|
||||||
|
>
|
||||||
|
<span>{tr.notetypesHideAllButOne()}</span>
|
||||||
|
</DropdownItem>
|
||||||
</Popover>
|
</Popover>
|
||||||
</WithFloating>
|
</WithFloating>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,11 @@ import { get } from "svelte/store";
|
||||||
|
|
||||||
import { addOrUpdateNote } from "../add-or-update-note.svelte";
|
import { addOrUpdateNote } from "../add-or-update-note.svelte";
|
||||||
import type { IOMode } from "../lib";
|
import type { IOMode } from "../lib";
|
||||||
import { hideAllGuessOne } from "../store";
|
import { occlusionMode } from "../store";
|
||||||
import type { PageLoad } from "./$types";
|
import type { PageLoad } from "./$types";
|
||||||
|
|
||||||
async function save(): Promise<void> {
|
async function save(): Promise<void> {
|
||||||
addOrUpdateNote(globalThis["anki"].imageOcclusion.mode, get(hideAllGuessOne));
|
addOrUpdateNote(globalThis["anki"].imageOcclusion.mode, get(occlusionMode));
|
||||||
}
|
}
|
||||||
|
|
||||||
export const load = (async ({ params }) => {
|
export const load = (async ({ params }) => {
|
||||||
|
|
|
||||||
|
|
@ -9,14 +9,14 @@ import { get } from "svelte/store";
|
||||||
import { mount } from "svelte";
|
import { mount } from "svelte";
|
||||||
import type { IOAddingMode, IOMode } from "./lib";
|
import type { IOAddingMode, IOMode } from "./lib";
|
||||||
import { exportShapesToClozeDeletions } from "./shapes/to-cloze";
|
import { exportShapesToClozeDeletions } from "./shapes/to-cloze";
|
||||||
import { notesDataStore, tagsWritable } from "./store";
|
import { notesDataStore, OcclusionMode, tagsWritable } from "./store";
|
||||||
import Toast from "./Toast.svelte";
|
import Toast from "./Toast.svelte";
|
||||||
|
|
||||||
export const addOrUpdateNote = async function(
|
export const addOrUpdateNote = async function(
|
||||||
mode: IOMode,
|
mode: IOMode,
|
||||||
occludeInactive: boolean,
|
occlusionMode: OcclusionMode,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { clozes: occlusionCloze, noteCount } = exportShapesToClozeDeletions(occludeInactive);
|
const { clozes: occlusionCloze, noteCount } = exportShapesToClozeDeletions(occlusionMode);
|
||||||
if (noteCount === 0) {
|
if (noteCount === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import { get } from "svelte/store";
|
||||||
import { addOrUpdateNote } from "./add-or-update-note.svelte";
|
import { addOrUpdateNote } from "./add-or-update-note.svelte";
|
||||||
import ImageOcclusionPage from "./ImageOcclusionPage.svelte";
|
import ImageOcclusionPage from "./ImageOcclusionPage.svelte";
|
||||||
import type { IOMode } from "./lib";
|
import type { IOMode } from "./lib";
|
||||||
import { hideAllGuessOne } from "./store";
|
import { occlusionMode } from "./store";
|
||||||
|
|
||||||
globalThis.anki = globalThis.anki || {};
|
globalThis.anki = globalThis.anki || {};
|
||||||
|
|
||||||
|
|
@ -31,7 +31,7 @@ export async function setupImageOcclusion(mode: IOMode, target = document.body):
|
||||||
await i18n;
|
await i18n;
|
||||||
|
|
||||||
async function addNote(): Promise<void> {
|
async function addNote(): Promise<void> {
|
||||||
addOrUpdateNote(mode, get(hideAllGuessOne));
|
addOrUpdateNote(mode, get(occlusionMode));
|
||||||
}
|
}
|
||||||
|
|
||||||
// for adding note from mobile devices
|
// for adding note from mobile devices
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,8 @@ import { get } from "svelte/store";
|
||||||
|
|
||||||
import { optimumCssSizeForCanvas } from "./canvas-scale";
|
import { optimumCssSizeForCanvas } from "./canvas-scale";
|
||||||
import {
|
import {
|
||||||
hideAllGuessOne,
|
|
||||||
notesDataStore,
|
notesDataStore,
|
||||||
|
occlusionMode,
|
||||||
opacityStateStore,
|
opacityStateStore,
|
||||||
saveNeededStore,
|
saveNeededStore,
|
||||||
tagsWritable,
|
tagsWritable,
|
||||||
|
|
@ -75,7 +75,7 @@ export const setupMaskEditorForEdit = async (
|
||||||
const clozeNote = clozeNoteResponse.value.value;
|
const clozeNote = clozeNoteResponse.value.value;
|
||||||
const canvas = initCanvas();
|
const canvas = initCanvas();
|
||||||
|
|
||||||
hideAllGuessOne.set(clozeNote.occludeInactive);
|
occlusionMode.set(clozeNote.occlusionMode);
|
||||||
|
|
||||||
// get image width and height
|
// get image width and height
|
||||||
const image = document.getElementById("image") as HTMLImageElement;
|
const image = document.getElementById("image") as HTMLImageElement;
|
||||||
|
|
|
||||||
|
|
@ -168,7 +168,7 @@ async function setupImageOcclusionInner(setupOptions?: SetupImageOcclusionOption
|
||||||
// setup button for toggle image occlusion
|
// setup button for toggle image occlusion
|
||||||
const button = document.getElementById("toggle");
|
const button = document.getElementById("toggle");
|
||||||
if (button) {
|
if (button) {
|
||||||
if (document.querySelector("[data-occludeinactive=\"1\"]")) {
|
if (document.querySelector("[data-occludeinactive=\"1\"], [data-occludeinactive=\"2\"]")) {
|
||||||
button.addEventListener("click", () => toggleMasks(setupOptions));
|
button.addEventListener("click", () => toggleMasks(setupOptions));
|
||||||
} else {
|
} else {
|
||||||
button.style.display = "none";
|
button.style.display = "none";
|
||||||
|
|
@ -202,6 +202,22 @@ function drawShapes(
|
||||||
properties = processed.properties;
|
properties = processed.properties;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determine occlusion mode from the first shape
|
||||||
|
const occlusionMode = activeShapes[0]?.occlusionMode ?? inactiveShapes[0]?.occlusionMode ?? 0;
|
||||||
|
|
||||||
|
// Mode 0 (HideOne): Draw active only (front), reveal answer with highlight (back)
|
||||||
|
// Mode 1 (HideAll): Draw both active and inactive (front & back)
|
||||||
|
// Mode 2 (HideAllButOne): Draw inactive only (front), draw nothing (back)
|
||||||
|
|
||||||
|
// Check if we're on the back side (highlightShapes only exist on back)
|
||||||
|
const isBackSide = highlightShapes.length > 0;
|
||||||
|
|
||||||
|
// For mode 2 on the back side, draw nothing (show full unoccluded image)
|
||||||
|
if (occlusionMode === 2 && isBackSide) {
|
||||||
|
// Don't draw any shapes on the back for "Hide All But One" mode
|
||||||
|
} else {
|
||||||
|
// Normal drawing logic for all other cases
|
||||||
|
if (occlusionMode !== 2) {
|
||||||
for (const shape of activeShapes) {
|
for (const shape of activeShapes) {
|
||||||
drawShape({
|
drawShape({
|
||||||
context,
|
context,
|
||||||
|
|
@ -212,7 +228,9 @@ function drawShapes(
|
||||||
strokeWidth: properties.activeBorder.width,
|
strokeWidth: properties.activeBorder.width,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
for (const shape of inactiveShapes.filter((s) => s.occludeInactive)) {
|
}
|
||||||
|
if (occlusionMode === 1 || occlusionMode === 2) {
|
||||||
|
for (const shape of inactiveShapes) {
|
||||||
drawShape({
|
drawShape({
|
||||||
context,
|
context,
|
||||||
size,
|
size,
|
||||||
|
|
@ -222,6 +240,7 @@ function drawShapes(
|
||||||
strokeWidth: properties.inActiveBorder.width,
|
strokeWidth: properties.inActiveBorder.width,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
for (const shape of highlightShapes) {
|
for (const shape of highlightShapes) {
|
||||||
drawShape({
|
drawShape({
|
||||||
context,
|
context,
|
||||||
|
|
@ -232,6 +251,7 @@ function drawShapes(
|
||||||
strokeWidth: properties.highlightShapeBorder.width,
|
strokeWidth: properties.highlightShapeBorder.width,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onDidDrawShapes?.({
|
onDidDrawShapes?.({
|
||||||
activeShapes,
|
activeShapes,
|
||||||
|
|
|
||||||
|
|
@ -20,23 +20,23 @@ export class Shape {
|
||||||
top: number;
|
top: number;
|
||||||
angle?: number; // polygons don't use it
|
angle?: number; // polygons don't use it
|
||||||
fill: string;
|
fill: string;
|
||||||
/** Whether occlusions from other cloze numbers should be shown on the
|
/** Occlusion mode: 0=HideOne, 1=HideAll, 2=HideAllButOne.
|
||||||
* question side. Used only in reviewer code.
|
* Used only in reviewer code.
|
||||||
*/
|
*/
|
||||||
occludeInactive?: boolean;
|
occlusionMode?: number;
|
||||||
/* Cloze ordinal */
|
/* Cloze ordinal */
|
||||||
ordinal: number | undefined;
|
ordinal: number | undefined;
|
||||||
id: string | undefined;
|
id: string | undefined;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
{ left = 0, top = 0, angle = 0, fill = SHAPE_MASK_COLOR, occludeInactive, ordinal = undefined }:
|
{ left = 0, top = 0, angle = 0, fill = SHAPE_MASK_COLOR, occlusionMode, ordinal = undefined }:
|
||||||
ConstructorParams<Shape> = {},
|
ConstructorParams<Shape> = {},
|
||||||
) {
|
) {
|
||||||
this.left = left;
|
this.left = left;
|
||||||
this.top = top;
|
this.top = top;
|
||||||
this.angle = angle;
|
this.angle = angle;
|
||||||
this.fill = fill;
|
this.fill = fill;
|
||||||
this.occludeInactive = occludeInactive;
|
this.occlusionMode = occlusionMode;
|
||||||
this.ordinal = ordinal;
|
this.ordinal = ordinal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ function extractShapeFromRenderedCloze(cloze: HTMLDivElement): Shape | null {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const props = {
|
const props = {
|
||||||
occludeInactive: cloze.dataset.occludeinactive === "1",
|
occlusionMode: cloze.dataset.occludeinactive ? parseInt(cloze.dataset.occludeinactive) : undefined,
|
||||||
ordinal: parseInt(cloze.dataset.ordinal!),
|
ordinal: parseInt(cloze.dataset.ordinal!),
|
||||||
left: cloze.dataset.left,
|
left: cloze.dataset.left,
|
||||||
top: cloze.dataset.top,
|
top: cloze.dataset.top,
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
import { fabric } from "fabric";
|
import { fabric } from "fabric";
|
||||||
import { cloneDeep } from "lodash-es";
|
import { cloneDeep } from "lodash-es";
|
||||||
|
|
||||||
|
import { OcclusionMode } from "../store";
|
||||||
import { getBoundingBoxSize } from "../tools/lib";
|
import { getBoundingBoxSize } from "../tools/lib";
|
||||||
import type { Size } from "../types";
|
import type { Size } from "../types";
|
||||||
import type { Shape, ShapeOrShapes } from "./base";
|
import type { Shape, ShapeOrShapes } from "./base";
|
||||||
|
|
@ -12,7 +13,7 @@ import { Polygon } from "./polygon";
|
||||||
import { Rectangle } from "./rectangle";
|
import { Rectangle } from "./rectangle";
|
||||||
import { Text } from "./text";
|
import { Text } from "./text";
|
||||||
|
|
||||||
export function exportShapesToClozeDeletions(occludeInactive: boolean): {
|
export function exportShapesToClozeDeletions(mode: OcclusionMode): {
|
||||||
clozes: string;
|
clozes: string;
|
||||||
noteCount: number;
|
noteCount: number;
|
||||||
} {
|
} {
|
||||||
|
|
@ -76,7 +77,7 @@ export function exportShapesToClozeDeletions(occludeInactive: boolean): {
|
||||||
clozes += shapeOrShapesToCloze(
|
clozes += shapeOrShapesToCloze(
|
||||||
shapeOrShapes,
|
shapeOrShapes,
|
||||||
ordinal,
|
ordinal,
|
||||||
occludeInactive,
|
mode,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!(shapeOrShapes instanceof Text)) {
|
if (!(shapeOrShapes instanceof Text)) {
|
||||||
|
|
@ -179,7 +180,7 @@ function fabricObjectToBaseShapeOrShapes(
|
||||||
function shapeOrShapesToCloze(
|
function shapeOrShapesToCloze(
|
||||||
shapeOrShapes: ShapeOrShapes,
|
shapeOrShapes: ShapeOrShapes,
|
||||||
ordinal: number,
|
ordinal: number,
|
||||||
occludeInactive: boolean,
|
mode: OcclusionMode,
|
||||||
): string {
|
): string {
|
||||||
let text = "";
|
let text = "";
|
||||||
function addKeyValue(key: string, value: string) {
|
function addKeyValue(key: string, value: string) {
|
||||||
|
|
@ -190,7 +191,7 @@ function shapeOrShapesToCloze(
|
||||||
let type: string;
|
let type: string;
|
||||||
if (Array.isArray(shapeOrShapes)) {
|
if (Array.isArray(shapeOrShapes)) {
|
||||||
return shapeOrShapes
|
return shapeOrShapes
|
||||||
.map((shape) => shapeOrShapesToCloze(shape, ordinal, occludeInactive))
|
.map((shape) => shapeOrShapesToCloze(shape, ordinal, mode))
|
||||||
.join("");
|
.join("");
|
||||||
} else if (shapeOrShapes instanceof Rectangle) {
|
} else if (shapeOrShapes instanceof Rectangle) {
|
||||||
type = "rect";
|
type = "rect";
|
||||||
|
|
@ -207,8 +208,8 @@ function shapeOrShapesToCloze(
|
||||||
for (const [key, value] of Object.entries(shapeOrShapes.toDataForCloze())) {
|
for (const [key, value] of Object.entries(shapeOrShapes.toDataForCloze())) {
|
||||||
addKeyValue(key, value);
|
addKeyValue(key, value);
|
||||||
}
|
}
|
||||||
if (occludeInactive) {
|
if (mode !== OcclusionMode.HideOne) {
|
||||||
addKeyValue("oi", "1");
|
addKeyValue("oi", mode.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
text = `{{c${ordinal}::image-occlusion:${type}${text}}}<br>`;
|
text = `{{c${ordinal}::image-occlusion:${type}${text}}}<br>`;
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,20 @@
|
||||||
|
|
||||||
import { writable } from "svelte/store";
|
import { writable } from "svelte/store";
|
||||||
|
|
||||||
|
export enum OcclusionMode {
|
||||||
|
HideOne = 0,
|
||||||
|
HideAll = 1,
|
||||||
|
HideAllButOne = 2,
|
||||||
|
}
|
||||||
|
|
||||||
// it stores note's data for generate.ts, when function generate() is called it will be used to generate the note
|
// 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]);
|
export const notesDataStore = writable({ id: "", title: "", divValue: "", textareaValue: "" }[0]);
|
||||||
// it stores the tags for the note in note editor
|
// it stores the tags for the note in note editor
|
||||||
export const tagsWritable = writable([""]);
|
export const tagsWritable = writable([""]);
|
||||||
// it stores the visibility of mask editor
|
// it stores the visibility of mask editor
|
||||||
export const ioMaskEditorVisible = writable(true);
|
export const ioMaskEditorVisible = writable(true);
|
||||||
// it store hide all or hide one mode
|
// it stores the occlusion mode (hide one, hide all, or hide all reveal one)
|
||||||
export const hideAllGuessOne = writable(true);
|
export const occlusionMode = writable(OcclusionMode.HideAll);
|
||||||
// ioImageLoadedStore is used to store the image loaded event
|
// ioImageLoadedStore is used to store the image loaded event
|
||||||
export const ioImageLoadedStore = writable(false);
|
export const ioImageLoadedStore = writable(false);
|
||||||
// store opacity state of objects in canvas
|
// store opacity state of objects in canvas
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue