From b9da61f99361a57fd7345e0822522ca15e3747c7 Mon Sep 17 00:00:00 2001 From: Mani <12841290+krmanik@users.noreply.github.com> Date: Mon, 31 Jul 2023 12:24:26 +0800 Subject: [PATCH] follow-up pr for io button in note editor (#2600) * follow-up pr for io button in note editor * Expose change event in Svelte instead of relying on timeout (dae) --- qt/aqt/addcards.py | 21 +--- qt/aqt/editor.py | 18 ++-- ts/editor/NoteEditor.svelte | 32 +++++- ts/image-occlusion/ImageOcclusionPage.svelte | 13 +-- ts/image-occlusion/MaskEditor.svelte | 11 +- ts/image-occlusion/StickyFooter.svelte | 15 +-- ts/image-occlusion/Toolbar.svelte | 104 ++++++++++++++++++- ts/image-occlusion/icons.ts | 4 + ts/image-occlusion/mask-editor.ts | 22 ++-- ts/image-occlusion/store.ts | 2 + 10 files changed, 186 insertions(+), 56 deletions(-) diff --git a/qt/aqt/addcards.py b/qt/aqt/addcards.py index 933ab8379..d39927cc2 100644 --- a/qt/aqt/addcards.py +++ b/qt/aqt/addcards.py @@ -115,8 +115,8 @@ class AddCards(QMainWindow): self.addButton.setToolTip(shortcut(tr.adding_add_shortcut_ctrlandenter())) # add io button - self.io_add_button = bb.addButton(f"{tr.actions_add()} {downArrow()}", ar) - qconnect(self.io_add_button.clicked, self.onAddIo) + self.io_add_button = bb.addButton(f"{tr.actions_add()}", ar) + qconnect(self.io_add_button.clicked, self.add_io_note) self.io_add_button.setShortcut(QKeySequence("Ctrl+Shift+I")) # close @@ -372,21 +372,8 @@ class AddCards(QMainWindow): self.ifCanClose(doClose) - def onAddIo(self) -> None: - m = QMenu(self) - a = m.addAction(tr.notetypes_hide_all_guess_one()) - qconnect(a.triggered, self.add_io_hide_all_note) - a = m.addAction(tr.notetypes_hide_one_guess_one()) - qconnect(a.triggered, self.add_io_hide_one_note) - m.popup(QCursor.pos()) - - def add_io_hide_all_note(self) -> None: - self.editor.web.eval("setOcclusionField(true)") - self.add_current_note() - self.editor.web.eval("resetIOImageLoaded()") - - def add_io_hide_one_note(self) -> None: - self.editor.web.eval("setOcclusionField(false)") + def add_io_note(self) -> None: + self.editor.web.eval("setOcclusionFieldInner()") self.add_current_note() self.editor.web.eval("resetIOImageLoaded()") diff --git a/qt/aqt/editor.py b/qt/aqt/editor.py index 732e0cfc8..502bb3bde 100644 --- a/qt/aqt/editor.py +++ b/qt/aqt/editor.py @@ -562,8 +562,8 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too self.editorMode != EditorMode.ADD_CARDS and self.current_notetype_is_image_occlusion() ): - options = {"kind": "edit", "noteId": self.note.id} - options = {"mode": options} + mode = {"kind": "edit", "noteId": self.note.id} + options = {"mode": mode} js += " setupMaskEditor(%s);" % json.dumps(options) js = gui_hooks.editor_will_load_note(js, self.note, self) @@ -1202,10 +1202,16 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too def accept(file: str) -> None: try: html = self._addMedia(file) - mode = {"kind": "add", "imagePath": file, "notetypeId": 0} - # pass both html and options - options = {"html": html, "mode": mode} - self.web.eval(f"setupMaskEditor({json.dumps(options)})") + if self.editorMode == EditorMode.ADD_CARDS: + mode = {"kind": "add", "imagePath": file, "notetypeId": 0} + options = {"html": html, "mode": mode} + self.web.eval(f"setupMaskEditor({json.dumps(options)})") + else: + mode = {"kind": "edit", "notetypeId": self.note.id} + options = {"html": html, "mode": mode} + self.web.eval(f"resetIOImage({json.dumps(file)})") + self.web.eval(f"setImageField({json.dumps(html)})") + self.web.eval(f"setupMaskEditor({json.dumps(options)})") except Exception as e: showWarning(str(e)) return diff --git a/ts/editor/NoteEditor.svelte b/ts/editor/NoteEditor.svelte index f17276ee3..04a14171c 100644 --- a/ts/editor/NoteEditor.svelte +++ b/ts/editor/NoteEditor.svelte @@ -298,10 +298,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html } function saveNow(): void { - updateIONoteInEditMode(); closeMathjaxEditor?.(); $commitTagEdits(); saveFieldNow(); + imageOcclusionMode = undefined; } export function saveOnPageHide() { @@ -390,7 +390,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import ImageOcclusionPage from "image-occlusion/ImageOcclusionPage.svelte"; import type { IOMode } from "image-occlusion/lib"; import { exportShapesToClozeDeletions } from "image-occlusion/shapes/to-cloze"; - import { ioMaskEditorVisible } from "image-occlusion/store"; + import { hideAllGuessOne, ioMaskEditorVisible } from "image-occlusion/store"; import { mathjaxConfig } from "../editable/mathjax-element"; import CollapseLabel from "./CollapseLabel.svelte"; @@ -402,10 +402,23 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html imageOcclusionMode = options.mode; if (options.mode.kind === "add") { fieldStores[1].set(options.html); + } else { + const clozeNote = get(fieldStores[0]); + if (clozeNote.includes("oi=1")) { + $hideAllGuessOne = true; + } else { + $hideAllGuessOne = false; + } } + isIOImageLoaded = true; } + function setImageField(html) { + fieldStores[1].set(html); + } + globalThis.setImageField = setImageField; + // update cloze deletions and set occlusion fields, it call in saveNow to update cloze deletions function updateIONoteInEditMode() { if (isEditMode) { @@ -418,6 +431,15 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html } } + function setOcclusionFieldInner() { + if (isImageOcclusion) { + const occlusionsData = exportShapesToClozeDeletions($hideAllGuessOne); + fieldStores[0].set(occlusionsData.clozes); + } + } + // global for calling this method in desktop note editor + globalThis.setOcclusionFieldInner = setOcclusionFieldInner; + // reset for new occlusion in add mode function resetIOImageLoaded() { isIOImageLoaded = false; @@ -484,6 +506,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html setIsEditMode, setupMaskEditor, setOcclusionField, + setOcclusionFieldInner, ...oldEditorAdapter, }); @@ -539,7 +562,10 @@ the AddCards dialog) should be implemented in the user of this component. {#if imageOcclusionMode}
- +
{/if} diff --git a/ts/image-occlusion/ImageOcclusionPage.svelte b/ts/image-occlusion/ImageOcclusionPage.svelte index 21a26a938..c285482b5 100644 --- a/ts/image-occlusion/ImageOcclusionPage.svelte +++ b/ts/image-occlusion/ImageOcclusionPage.svelte @@ -11,15 +11,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import MasksEditor from "./MaskEditor.svelte"; import Notes from "./Notes.svelte"; import StickyFooter from "./StickyFooter.svelte"; + import { hideAllGuessOne } from "./store"; export let mode: IOMode; - async function hideAllGuessOne(): Promise { - addOrUpdateNote(mode, true); - } - - async function hideOneGuessOne(): Promise { - addOrUpdateNote(mode, false); + async function addNote(): Promise { + addOrUpdateNote(mode, $hideAllGuessOne); } const items = [ @@ -45,14 +42,14 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html - + diff --git a/ts/image-occlusion/icons.ts b/ts/image-occlusion/icons.ts index 52456ac01..f9bbc917e 100644 --- a/ts/image-occlusion/icons.ts +++ b/ts/image-occlusion/icons.ts @@ -9,6 +9,7 @@ export { default as mdiAlignHorizontalRight } from "@mdi/svg/svg/align-horizonta 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 mdiChevronDown } from "@mdi/svg/svg/chevron-down.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"; @@ -27,7 +28,10 @@ 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 mdiRefresh } from "@mdi/svg/svg/refresh.svg"; +export { default as mdiSquare } from "@mdi/svg/svg/square.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"; +export { default as mdiViewDashboard } from "@mdi/svg/svg/view-dashboard.svg"; diff --git a/ts/image-occlusion/mask-editor.ts b/ts/image-occlusion/mask-editor.ts index ea9550fdd..4e54265fd 100644 --- a/ts/image-occlusion/mask-editor.ts +++ b/ts/image-occlusion/mask-editor.ts @@ -16,9 +16,13 @@ import { enableSelectable, moveShapeToCanvasBoundaries } from "./tools/lib"; import { undoRedoInit } from "./tools/tool-undo-redo"; import type { Size } from "./types"; -export const setupMaskEditor = async (path: string, instance: PanZoom): Promise => { +export const setupMaskEditor = async ( + path: string, + instance: PanZoom, + onChange: () => void, +): Promise => { const imageData = await getImageForOcclusion({ path }); - const canvas = initCanvas(); + const canvas = initCanvas(onChange); // get image width and height const image = document.getElementById("image") as HTMLImageElement; @@ -35,7 +39,11 @@ export const setupMaskEditor = async (path: string, instance: PanZoom): Promise< return canvas; }; -export const setupMaskEditorForEdit = async (noteId: number, instance: PanZoom): Promise => { +export const setupMaskEditorForEdit = async ( + noteId: number, + instance: PanZoom, + onChange: () => void, +): Promise => { const clozeNoteResponse = await getImageOcclusionNote({ noteId: BigInt(noteId) }); const kind = clozeNoteResponse.value?.case; if (!kind || kind === "error") { @@ -50,7 +58,7 @@ export const setupMaskEditorForEdit = async (noteId: number, instance: PanZoom): } const clozeNote = clozeNoteResponse.value.value; - const canvas = initCanvas(); + const canvas = initCanvas(onChange); // get image width and height const image = document.getElementById("image") as HTMLImageElement; @@ -75,7 +83,7 @@ export const setupMaskEditorForEdit = async (noteId: number, instance: PanZoom): return canvas; }; -const initCanvas = (): fabric.Canvas => { +function initCanvas(onChange: () => void): fabric.Canvas { const canvas = new fabric.Canvas("canvas"); tagsWritable.set([]); globalThis.canvas = canvas; @@ -84,8 +92,10 @@ const initCanvas = (): fabric.Canvas => { canvas.uniScaleKey = "none"; moveShapeToCanvasBoundaries(canvas); undoRedoInit(canvas); + canvas.on("object:modified", onChange); + canvas.on("object:removed", onChange); return canvas; -}; +} const getImageData = (imageData): string => { const b64encoded = protoBase64.enc(imageData); diff --git a/ts/image-occlusion/store.ts b/ts/image-occlusion/store.ts index bf56b688f..07b97899c 100644 --- a/ts/image-occlusion/store.ts +++ b/ts/image-occlusion/store.ts @@ -11,3 +11,5 @@ export const zoomResetValue = writable(1); export const tagsWritable = writable([""]); // it stores the visibility of mask editor export const ioMaskEditorVisible = writable(true); +// it store hide all or hide one mode +export const hideAllGuessOne = writable(true);