mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 14:02:21 -04:00
Indicate current occlusion type in pop-up menu (#2760)
* Simplify handling of occlusion types in editor code - Unify updateIONoteInEditMode(), setOcclusionFieldInner() and setOcclusionField() into updateOcclusionsField() - Don't use `includeInactive` property of Shape class in editor code - Drop `isEditMode` + Update the occlusions field every time a mask or text is updated, not only in editing mode but also in adding mode, so that IO cards can be previewed correctly in the card layout screen * Indicate current occlusion type in pop-up menu https://forums.ankiweb.net/t/anki-23-10-beta-5-6/35677/46 * Fix a11y warnings in Toolbar.svelte * Drop `occludeInactive` parameter from `MaskEditorAPI.getShapes()`
This commit is contained in:
parent
9bbc6c9405
commit
a53806e24a
7 changed files with 51 additions and 98 deletions
|
@ -373,7 +373,7 @@ class AddCards(QMainWindow):
|
||||||
self.ifCanClose(doClose)
|
self.ifCanClose(doClose)
|
||||||
|
|
||||||
def add_io_note(self) -> None:
|
def add_io_note(self) -> None:
|
||||||
self.editor.web.eval("setOcclusionFieldInner()")
|
self.editor.web.eval("updateOcclusionsField();")
|
||||||
self.add_current_note()
|
self.add_current_note()
|
||||||
self.editor.web.eval("resetIOImageLoaded()")
|
self.editor.web.eval("resetIOImageLoaded()")
|
||||||
|
|
||||||
|
|
|
@ -586,7 +586,6 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
|
||||||
setCloseHTMLTags({json.dumps(self.mw.col.get_config("closeHTMLTags", True))});
|
setCloseHTMLTags({json.dumps(self.mw.col.get_config("closeHTMLTags", True))});
|
||||||
triggerChanges();
|
triggerChanges();
|
||||||
setIsImageOcclusion({json.dumps(self.current_notetype_is_image_occlusion())});
|
setIsImageOcclusion({json.dumps(self.current_notetype_is_image_occlusion())});
|
||||||
setIsEditMode({json.dumps(self.editorMode != EditorMode.ADD_CARDS)});
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.addMode:
|
if self.addMode:
|
||||||
|
|
|
@ -245,11 +245,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
$ioMaskEditorVisible = val;
|
$ioMaskEditorVisible = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
let isEditMode = false;
|
|
||||||
function setIsEditMode(val: boolean) {
|
|
||||||
isEditMode = val;
|
|
||||||
}
|
|
||||||
|
|
||||||
let cols: ("dupe" | "")[] = [];
|
let cols: ("dupe" | "")[] = [];
|
||||||
export function setBackgrounds(cls: ("dupe" | "")[]): void {
|
export function setBackgrounds(cls: ("dupe" | "")[]): void {
|
||||||
cols = cls;
|
cols = cls;
|
||||||
|
@ -450,26 +445,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
}
|
}
|
||||||
globalThis.setImageField = setImageField;
|
globalThis.setImageField = setImageField;
|
||||||
|
|
||||||
// update cloze deletions and set occlusion fields, it call in saveNow to update cloze deletions
|
function updateOcclusionsField(): void {
|
||||||
function updateIONoteInEditMode() {
|
|
||||||
if (isEditMode) {
|
|
||||||
const clozeNote = get(fieldStores[ioFields.occlusions]);
|
|
||||||
if (clozeNote.includes("oi=1")) {
|
|
||||||
setOcclusionField(true);
|
|
||||||
} else {
|
|
||||||
setOcclusionField(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setOcclusionFieldInner() {
|
|
||||||
if (isImageOcclusion) {
|
if (isImageOcclusion) {
|
||||||
const occlusionsData = exportShapesToClozeDeletions($hideAllGuessOne);
|
const occlusionsData = exportShapesToClozeDeletions($hideAllGuessOne);
|
||||||
fieldStores[ioFields.occlusions].set(occlusionsData.clozes);
|
fieldStores[ioFields.occlusions].set(occlusionsData.clozes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// global for calling this method in desktop note editor
|
|
||||||
globalThis.setOcclusionFieldInner = setOcclusionFieldInner;
|
|
||||||
|
|
||||||
// reset for new occlusion in add mode
|
// reset for new occlusion in add mode
|
||||||
function resetIOImageLoaded() {
|
function resetIOImageLoaded() {
|
||||||
|
@ -482,14 +463,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
}
|
}
|
||||||
globalThis.resetIOImageLoaded = resetIOImageLoaded;
|
globalThis.resetIOImageLoaded = resetIOImageLoaded;
|
||||||
|
|
||||||
function setOcclusionField(occludeInactive: boolean) {
|
|
||||||
// set fields data for occlusion and image fields for io notes type
|
|
||||||
if (isImageOcclusion) {
|
|
||||||
const occlusionsData = exportShapesToClozeDeletions(occludeInactive);
|
|
||||||
fieldStores[ioFields.occlusions].set(occlusionsData.clozes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** hide occlusions and image */
|
/** hide occlusions and image */
|
||||||
function hideFieldInOcclusionType(
|
function hideFieldInOcclusionType(
|
||||||
index: number,
|
index: number,
|
||||||
|
@ -583,10 +556,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
setCloseHTMLTags,
|
setCloseHTMLTags,
|
||||||
triggerChanges,
|
triggerChanges,
|
||||||
setIsImageOcclusion,
|
setIsImageOcclusion,
|
||||||
setIsEditMode,
|
|
||||||
setupMaskEditor,
|
setupMaskEditor,
|
||||||
setOcclusionField,
|
updateOcclusionsField,
|
||||||
setOcclusionFieldInner,
|
|
||||||
...oldEditorAdapter,
|
...oldEditorAdapter,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -651,7 +622,7 @@ the AddCards dialog) should be implemented in the user of this component.
|
||||||
<div style="display: {$ioMaskEditorVisible ? 'block' : 'none'}">
|
<div style="display: {$ioMaskEditorVisible ? 'block' : 'none'}">
|
||||||
<ImageOcclusionPage
|
<ImageOcclusionPage
|
||||||
mode={imageOcclusionMode}
|
mode={imageOcclusionMode}
|
||||||
on:change={updateIONoteInEditMode}
|
on:change={updateOcclusionsField}
|
||||||
on:image-loaded={onImageLoaded}
|
on:image-loaded={onImageLoaded}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
Copyright: Ankitects Pty Ltd and contributors
|
Copyright: Ankitects Pty Ltd and contributors
|
||||||
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
-->
|
-->
|
||||||
<script>
|
<script lang="ts">
|
||||||
import * as tr from "@tslib/ftl";
|
import * as tr from "@tslib/ftl";
|
||||||
import DropdownItem from "components/DropdownItem.svelte";
|
import DropdownItem from "components/DropdownItem.svelte";
|
||||||
import IconButton from "components/IconButton.svelte";
|
import IconButton from "components/IconButton.svelte";
|
||||||
|
@ -10,6 +10,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import WithFloating from "components/WithFloating.svelte";
|
import WithFloating from "components/WithFloating.svelte";
|
||||||
|
|
||||||
import { mdiEye, mdiFormatAlignCenter, mdiSquare, mdiViewDashboard } from "./icons";
|
import { mdiEye, mdiFormatAlignCenter, mdiSquare, mdiViewDashboard } from "./icons";
|
||||||
|
import { emitChangeSignal } from "./MaskEditor.svelte";
|
||||||
import { hideAllGuessOne } from "./store";
|
import { hideAllGuessOne } from "./store";
|
||||||
import { drawEllipse, drawPolygon, drawRectangle, drawText } from "./tools/index";
|
import { drawEllipse, drawPolygon, drawRectangle, drawText } from "./tools/index";
|
||||||
import { makeMaskTransparent } from "./tools/lib";
|
import { makeMaskTransparent } from "./tools/lib";
|
||||||
|
@ -72,16 +73,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
canvas.selectionColor = "rgba(100, 100, 255, 0.3)";
|
canvas.selectionColor = "rgba(100, 100, 255, 0.3)";
|
||||||
};
|
};
|
||||||
|
|
||||||
const setOcclusionFieldForDesktop = () => {
|
function changeOcclusionType(occlusionType: "all" | "one"): void {
|
||||||
const clist = document.body.classList;
|
$hideAllGuessOne = occlusionType === "all";
|
||||||
if (
|
emitChangeSignal();
|
||||||
clist.contains("isLin") ||
|
}
|
||||||
clist.contains("isMac") ||
|
|
||||||
clist.contains("isWin")
|
|
||||||
) {
|
|
||||||
globalThis.setOcclusionFieldInner();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="tool-bar-container">
|
<div class="tool-bar-container">
|
||||||
|
@ -100,46 +95,36 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="top-tool-bar-container">
|
<div class="top-tool-bar-container">
|
||||||
<div class="undo-redo-button" on:click={() => (showFloating = !showFloating)}>
|
<WithFloating
|
||||||
<WithFloating
|
show={showFloating}
|
||||||
show={showFloating}
|
closeOnInsideClick
|
||||||
closeOnInsideClick
|
inline
|
||||||
inline
|
on:close={() => (showFloating = false)}
|
||||||
style="line-height: unset !important"
|
>
|
||||||
on:close={() => (showFloating = false)}
|
<IconButton
|
||||||
|
class="top-tool-icon-button right-border-radius dropdown-tool-mode"
|
||||||
|
slot="reference"
|
||||||
|
{iconSize}
|
||||||
|
on:click={() => (showFloating = !showFloating)}
|
||||||
>
|
>
|
||||||
<IconButton
|
{@html $hideAllGuessOne ? mdiViewDashboard : mdiSquare}
|
||||||
class="top-tool-icon-button right-border-radius dropdown-tool-mode"
|
</IconButton>
|
||||||
slot="reference"
|
|
||||||
{iconSize}
|
|
||||||
>
|
|
||||||
{#if $hideAllGuessOne}
|
|
||||||
{@html mdiViewDashboard}
|
|
||||||
{:else}
|
|
||||||
{@html mdiSquare}
|
|
||||||
{/if}
|
|
||||||
</IconButton>
|
|
||||||
|
|
||||||
<Popover slot="floating" --popover-padding-inline="0">
|
<Popover slot="floating">
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
on:click={() => {
|
active={$hideAllGuessOne}
|
||||||
$hideAllGuessOne = true;
|
on:click={() => changeOcclusionType("all")}
|
||||||
setOcclusionFieldForDesktop();
|
>
|
||||||
}}
|
<span>{tr.notetypesHideAllGuessOne()}</span>
|
||||||
>
|
</DropdownItem>
|
||||||
<span>{tr.notetypesHideAllGuessOne()}</span>
|
<DropdownItem
|
||||||
</DropdownItem>
|
active={!$hideAllGuessOne}
|
||||||
<DropdownItem
|
on:click={() => changeOcclusionType("one")}
|
||||||
on:click={() => {
|
>
|
||||||
$hideAllGuessOne = false;
|
<span>{tr.notetypesHideOneGuessOne()}</span>
|
||||||
setOcclusionFieldForDesktop();
|
</DropdownItem>
|
||||||
}}
|
</Popover>
|
||||||
>
|
</WithFloating>
|
||||||
<span>{tr.notetypesHideOneGuessOne()}</span>
|
|
||||||
</DropdownItem>
|
|
||||||
</Popover>
|
|
||||||
</WithFloating>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- undo & redo tools -->
|
<!-- undo & redo tools -->
|
||||||
<div class="undo-redo-button">
|
<div class="undo-redo-button">
|
||||||
|
|
|
@ -20,15 +20,14 @@ export class Shape {
|
||||||
top: number;
|
top: number;
|
||||||
fill: string = SHAPE_MASK_COLOR;
|
fill: string = SHAPE_MASK_COLOR;
|
||||||
/** Whether occlusions from other cloze numbers should be shown on the
|
/** Whether occlusions from other cloze numbers should be shown on the
|
||||||
* question side.
|
* question side. Used only in reviewer code.
|
||||||
*/
|
*/
|
||||||
occludeInactive = false;
|
occludeInactive?: boolean;
|
||||||
/* Cloze ordinal */
|
/* Cloze ordinal */
|
||||||
ordinal = 0;
|
ordinal = 0;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
{ left = 0, top = 0, fill = SHAPE_MASK_COLOR, occludeInactive = false, ordinal = 0 }: ConstructorParams<Shape> =
|
{ left = 0, top = 0, fill = SHAPE_MASK_COLOR, occludeInactive, ordinal = 0 }: ConstructorParams<Shape> = {},
|
||||||
{},
|
|
||||||
) {
|
) {
|
||||||
this.left = left;
|
this.left = left;
|
||||||
this.top = top;
|
this.top = top;
|
||||||
|
@ -45,7 +44,6 @@ export class Shape {
|
||||||
left: floatToDisplay(this.left),
|
left: floatToDisplay(this.left),
|
||||||
top: floatToDisplay(this.top),
|
top: floatToDisplay(this.top),
|
||||||
...(this.fill === SHAPE_MASK_COLOR ? {} : { fill: this.fill }),
|
...(this.fill === SHAPE_MASK_COLOR ? {} : { fill: this.fill }),
|
||||||
...(this.occludeInactive ? { oi: "1" } : {}),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,12 +17,12 @@ export function exportShapesToClozeDeletions(occludeInactive: boolean): {
|
||||||
clozes: string;
|
clozes: string;
|
||||||
noteCount: number;
|
noteCount: number;
|
||||||
} {
|
} {
|
||||||
const shapes = baseShapesFromFabric(occludeInactive);
|
const shapes = baseShapesFromFabric();
|
||||||
|
|
||||||
let clozes = "";
|
let clozes = "";
|
||||||
let index = 0;
|
let index = 0;
|
||||||
shapes.forEach((shapeOrShapes) => {
|
shapes.forEach((shapeOrShapes) => {
|
||||||
clozes += shapeOrShapesToCloze(shapeOrShapes, index);
|
clozes += shapeOrShapesToCloze(shapeOrShapes, index, occludeInactive);
|
||||||
if (!(shapeOrShapes instanceof Text)) {
|
if (!(shapeOrShapes instanceof Text)) {
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ export function exportShapesToClozeDeletions(occludeInactive: boolean): {
|
||||||
/** Gather all Fabric shapes, and convert them into BaseShapes or
|
/** Gather all Fabric shapes, and convert them into BaseShapes or
|
||||||
* BaseShape[]s.
|
* BaseShape[]s.
|
||||||
*/
|
*/
|
||||||
export function baseShapesFromFabric(occludeInactive: boolean): ShapeOrShapes[] {
|
export function baseShapesFromFabric(): ShapeOrShapes[] {
|
||||||
const canvas = globalThis.canvas as Canvas;
|
const canvas = globalThis.canvas as Canvas;
|
||||||
makeMaskTransparent(canvas, false);
|
makeMaskTransparent(canvas, false);
|
||||||
const activeObject = canvas.getActiveObject();
|
const activeObject = canvas.getActiveObject();
|
||||||
|
@ -53,7 +53,6 @@ export function baseShapesFromFabric(occludeInactive: boolean): ShapeOrShapes[]
|
||||||
return fabricObjectToBaseShapeOrShapes(
|
return fabricObjectToBaseShapeOrShapes(
|
||||||
canvas,
|
canvas,
|
||||||
object,
|
object,
|
||||||
occludeInactive,
|
|
||||||
parent,
|
parent,
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
|
@ -64,7 +63,6 @@ export function baseShapesFromFabric(occludeInactive: boolean): ShapeOrShapes[]
|
||||||
function fabricObjectToBaseShapeOrShapes(
|
function fabricObjectToBaseShapeOrShapes(
|
||||||
size: Size,
|
size: Size,
|
||||||
object: FabricObject,
|
object: FabricObject,
|
||||||
occludeInactive: boolean,
|
|
||||||
parentObject?: FabricObject,
|
parentObject?: FabricObject,
|
||||||
): ShapeOrShapes | null {
|
): ShapeOrShapes | null {
|
||||||
let shape: Shape;
|
let shape: Shape;
|
||||||
|
@ -91,14 +89,12 @@ function fabricObjectToBaseShapeOrShapes(
|
||||||
return fabricObjectToBaseShapeOrShapes(
|
return fabricObjectToBaseShapeOrShapes(
|
||||||
size,
|
size,
|
||||||
child,
|
child,
|
||||||
occludeInactive,
|
|
||||||
object,
|
object,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
shape.occludeInactive = occludeInactive;
|
|
||||||
if (parentObject) {
|
if (parentObject) {
|
||||||
const newPosition = fabric.util.transformPoint(
|
const newPosition = fabric.util.transformPoint(
|
||||||
{ x: shape.left, y: shape.top },
|
{ x: shape.left, y: shape.top },
|
||||||
|
@ -117,6 +113,7 @@ function fabricObjectToBaseShapeOrShapes(
|
||||||
function shapeOrShapesToCloze(
|
function shapeOrShapesToCloze(
|
||||||
shapeOrShapes: ShapeOrShapes,
|
shapeOrShapes: ShapeOrShapes,
|
||||||
index: number,
|
index: number,
|
||||||
|
occludeInactive: boolean,
|
||||||
): string {
|
): string {
|
||||||
let text = "";
|
let text = "";
|
||||||
function addKeyValue(key: string, value: string) {
|
function addKeyValue(key: string, value: string) {
|
||||||
|
@ -127,7 +124,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, index))
|
.map((shape) => shapeOrShapesToCloze(shape, index, occludeInactive))
|
||||||
.join("");
|
.join("");
|
||||||
} else if (shapeOrShapes instanceof Rectangle) {
|
} else if (shapeOrShapes instanceof Rectangle) {
|
||||||
type = "rect";
|
type = "rect";
|
||||||
|
@ -144,6 +141,9 @@ 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) {
|
||||||
|
addKeyValue("oi", "1");
|
||||||
|
}
|
||||||
|
|
||||||
let ordinal: number;
|
let ordinal: number;
|
||||||
if (type === "text") {
|
if (type === "text") {
|
||||||
|
|
|
@ -40,8 +40,8 @@ export class MaskEditorAPI {
|
||||||
return { clozes, cardCount };
|
return { clozes, cardCount };
|
||||||
}
|
}
|
||||||
|
|
||||||
getShapes(occludeInactive: boolean): ShapeOrShapes[] {
|
getShapes(): ShapeOrShapes[] {
|
||||||
return baseShapesFromFabric(occludeInactive);
|
return baseShapesFromFabric();
|
||||||
}
|
}
|
||||||
|
|
||||||
redraw(): void {
|
redraw(): void {
|
||||||
|
|
Loading…
Reference in a new issue