From be8f93b6b838e401c1548a5059137c9552adaf4a Mon Sep 17 00:00:00 2001 From: krmanik <12841290+krmanik@users.noreply.github.com> Date: Fri, 9 Jun 2023 13:13:43 +0800 Subject: [PATCH] setup mask editor in note editor - add image on mask button click (only one time) - show hide add button for io on notetype change --- ftl/core/notetypes.ftl | 2 + pylib/anki/collection.py | 4 + pylib/anki/models.py | 1 + qt/aqt/addcards.py | 42 +- qt/aqt/editor.py | 63 +++ ts/editor/NoteEditor.svelte | 358 +++++++++++------- ts/editor/base.ts | 2 + ts/editor/editor-toolbar/EditorToolbar.svelte | 5 + .../ImageOcclusionButton.svelte | 57 +++ ts/editor/tsconfig.json | 3 +- ts/image-occlusion/Notes.svelte | 2 +- ts/image-occlusion/TopToolbar.svelte | 3 +- ts/image-occlusion/store.ts | 2 + 13 files changed, 407 insertions(+), 137 deletions(-) create mode 100644 ts/editor/editor-toolbar/ImageOcclusionButton.svelte diff --git a/ftl/core/notetypes.ftl b/ftl/core/notetypes.ftl index 59036f9f1f8..1e92bcda4c8 100644 --- a/ftl/core/notetypes.ftl +++ b/ftl/core/notetypes.ftl @@ -51,3 +51,5 @@ notetypes-error-generating-cloze = An error occurred when generating an image oc 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? notetype-error-no-image-to-show = No image to show. +notetype-create-edit-io-message = Tap the two squares to create or edit an occlusion. +notetype-io-switch-type = Tap 'Type: Image Occlusion' to switch to normal text/media cards. diff --git a/pylib/anki/collection.py b/pylib/anki/collection.py index 4b924fe176c..da9bf813417 100644 --- a/pylib/anki/collection.py +++ b/pylib/anki/collection.py @@ -466,6 +466,10 @@ def import_json_string(self, json: str) -> ImportLogWithChanges: def get_image_for_occlusion(self, path: str | None) -> GetImageForOcclusionResponse: return self._backend.get_image_for_occlusion(path=path) + def add_image_occlusion_notetype(self) -> None: + "Add notetype if missing." + self._backend.add_image_occlusion_notetype() + def add_image_occlusion_note( self, notetype_id: int, diff --git a/pylib/anki/models.py b/pylib/anki/models.py index 7fe27dba379..fe9c9b5f27f 100644 --- a/pylib/anki/models.py +++ b/pylib/anki/models.py @@ -27,6 +27,7 @@ NotetypeNames = notetypes_pb2.NotetypeNames ChangeNotetypeInfo = notetypes_pb2.ChangeNotetypeInfo ChangeNotetypeRequest = notetypes_pb2.ChangeNotetypeRequest +StockNotetype = notetypes_pb2.StockNotetype # legacy types NotetypeDict = dict[str, Any] diff --git a/qt/aqt/addcards.py b/qt/aqt/addcards.py index ef8a1150060..96e4ec5903b 100644 --- a/qt/aqt/addcards.py +++ b/qt/aqt/addcards.py @@ -10,7 +10,7 @@ from anki._legacy import deprecated from anki.collection import OpChanges, SearchNode from anki.decks import DeckId -from anki.models import NotetypeId +from anki.models import NotetypeId, StockNotetype from anki.notes import Note, NoteFieldsCheckResult, NoteId from anki.utils import html_to_text_line, is_mac from aqt import AnkiQt, gui_hooks @@ -48,13 +48,16 @@ def __init__(self, mw: AnkiQt) -> None: self.setMinimumWidth(400) self.setup_choosers() self.setupEditor() - self.setupButtons() add_close_shortcut(self) self._load_new_note() + self.setupButtons() self.history: list[NoteId] = [] self._last_added_note: Optional[Note] = None gui_hooks.operation_did_execute.append(self.on_operation_did_execute) restoreGeom(self, "add") + self.col.add_image_occlusion_notetype() + # hide io buttons for note type other than image occlusion + self.show_hide_add_buttons() gui_hooks.add_cards_did_init(self) self.show() @@ -112,6 +115,17 @@ def setupButtons(self) -> None: self.compat_add_shorcut = QShortcut(QKeySequence("Ctrl+Enter"), self) qconnect(self.compat_add_shorcut.activated, self.addButton.click) self.addButton.setToolTip(shortcut(tr.adding_add_shortcut_ctrlandenter())) + + # add io hide all button + self.addButtonHideAll = bb.addButton(tr.notetypes_hide_all_guess_one(), ar) + qconnect(self.addButtonHideAll.clicked, self.add_io_hide_all_note) + self.addButtonHideAll.setShortcut(QKeySequence("Ctrl+Return+A")) + self.addButtonHideAll.setVisible(False) + # add io hide one button + self.addButtonHideOne = bb.addButton(tr.notetypes_hide_one_guess_one(), ar) + qconnect(self.addButtonHideOne.clicked, self.add_io_hide_one_note) + self.addButtonHideOne.setShortcut(QKeySequence("Ctrl+Return+O")) + # close self.closeButton = QPushButton(tr.actions_close()) self.closeButton.setAutoDefault(False) @@ -133,6 +147,19 @@ def setupButtons(self) -> None: b.setEnabled(False) self.historyButton = b + def show_hide_add_buttons(self) -> None: + if ( + self.editor.note.note_type()["originalStockKind"] + == StockNotetype.OriginalStockKind.ORIGINAL_STOCK_KIND_IMAGE_OCCLUSION + ): + self.addButton.setVisible(False) + self.addButtonHideAll.setVisible(True) + self.addButtonHideOne.setVisible(True) + else: + self.addButton.setVisible(True) + self.addButtonHideAll.setVisible(False) + self.addButtonHideOne.setVisible(False) + def setAndFocusNote(self, note: Note) -> None: self.editor.set_note(note, focusTo=0) @@ -192,6 +219,9 @@ def on_notetype_change(self, notetype_id: NotetypeId) -> None: old_note.note_type(), new_note.note_type() ) + # update buttons for image occlusion on note type change + self.show_hide_add_buttons() + def _load_new_note(self, sticky_fields_from: Optional[Note] = None) -> None: note = self._new_note() if old_note := sticky_fields_from: @@ -348,6 +378,14 @@ def doClose() -> None: self.ifCanClose(doClose) + def add_io_hide_all_note(self) -> None: + self.editor.web.eval("setOcclusionField(true)") + self.add_current_note() + + def add_io_hide_one_note(self) -> None: + self.editor.web.eval("setOcclusionField(false)") + self.add_current_note() + # legacy aliases @property diff --git a/qt/aqt/editor.py b/qt/aqt/editor.py index 3eed03c64e2..2b3d5be2328 100644 --- a/qt/aqt/editor.py +++ b/qt/aqt/editor.py @@ -31,6 +31,7 @@ from anki.consts import MODEL_CLOZE from anki.hooks import runFilter from anki.httpclient import HttpClient +from anki.models import StockNotetype from anki.notes import Note, NoteFieldsCheckResult from anki.utils import checksum, is_lin, is_win, namedtmp from aqt import AnkiQt, colors, gui_hooks @@ -475,6 +476,11 @@ def onBridgeCmd(self, cmd: str) -> Any: elif cmd in self._links: return self._links[cmd](self) + elif cmd.startswith("toggleMaskEditor"): + (_, show_str) = cmd.split(":", 1) + show = show_str == "true" + self.onToggleMaskEditor(show) + else: print("uncaught cmd", cmd) @@ -548,6 +554,7 @@ def oncallback(arg: Any) -> None: setShrinkImages({json.dumps(self.mw.col.get_config("shrinkEditorImages", True))}); setCloseHTMLTags({json.dumps(self.mw.col.get_config("closeHTMLTags", True))}); triggerChanges(); + setOriginalStockKind({json.dumps(self.note.note_type()["originalStockKind"])}); """ if self.addMode: @@ -1177,6 +1184,45 @@ def toggleCloseHTMLTags(self) -> None: def setTagsCollapsed(self, collapsed: bool) -> None: aqt.mw.pm.set_tags_collapsed(self.editorMode, collapsed) + def onAddImageForOcclusion(self) -> None: + """Show a file selection screen, then get selected image path.""" + extension_filter = " ".join( + f"*.{extension}" for extension in sorted(itertools.chain(pics)) + ) + filter = f"{tr.editing_media()} ({extension_filter})" + + def accept(file: str) -> None: + try: + html = self._addMedia(file) + options = {"kind": "add", "imagePath": file, "notetypeId": 0} + # pass both html and options + options = {"html": html, "mode": options} + self.web.eval(f"setupMaskEditor({options})") + except Exception as e: + showWarning(str(e)) + return + + if self.addMode: + file = getFile( + parent=self.widget, + title=tr.editing_add_media(), + cb=cast(Callable[[Any], None], accept), + filter=filter, + key="media", + ) + else: + options = {"kind": "edit", "noteId": self.note.id} + options = {"mode": options} + self.web.eval(f"setupMaskEditor({options})") + + self.parentWindow.activateWindow() + + def onToggleMaskEditor(self, show) -> None: + if show: + self.web.eval("toggleMaskEditor(true)") + else: + self.web.eval("toggleMaskEditor(false)") + # Links from HTML ###################################################################### @@ -1206,6 +1252,8 @@ def _init_links(self) -> None: toggleMathjax=Editor.toggleMathjax, toggleShrinkImages=Editor.toggleShrinkImages, toggleCloseHTMLTags=Editor.toggleCloseHTMLTags, + addImageForOcclusion=Editor.onAddImageForOcclusion, + toggleMaskEditor=Editor.onToggleMaskEditor, ) @@ -1463,4 +1511,19 @@ def set_cloze_button(editor: Editor) -> None: ) +def set_image_occlusion_button(editor: Editor) -> None: + action = ( + "show" + if editor.note.note_type()["originalStockKind"] + == StockNotetype.OriginalStockKind.ORIGINAL_STOCK_KIND_IMAGE_OCCLUSION + else "hide" + ) + editor.web.eval( + 'require("anki/ui").loaded.then(() =>' + f'require("anki/NoteEditor").instances[0].toolbar.toolbar.{action}("image-occlusion-button")' + "); " + ) + + gui_hooks.editor_did_load_note.append(set_cloze_button) +gui_hooks.editor_did_load_note.append(set_image_occlusion_button) diff --git a/ts/editor/NoteEditor.svelte b/ts/editor/NoteEditor.svelte index a9b78624c05..6388e7261f5 100644 --- a/ts/editor/NoteEditor.svelte +++ b/ts/editor/NoteEditor.svelte @@ -42,6 +42,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + + + + + { + bridgeCommand(`toggleMaskEditor:${false}`); + }} + > + {tr.notetypesOcclusionNote()} + + + + + { + bridgeCommand(`toggleMaskEditor:${true}`); + // run this bridge when image not added + if (get(ioImageLoaded) === false) { + bridgeCommand("addImageForOcclusion"); + } + }} + > + {tr.notetypesOcclusionMask()} + + + + diff --git a/ts/editor/tsconfig.json b/ts/editor/tsconfig.json index e021013df1f..38bb3cccd86 100644 --- a/ts/editor/tsconfig.json +++ b/ts/editor/tsconfig.json @@ -7,7 +7,8 @@ "symbols-overlay/*", "plain-text-input/*", "rich-text-input/*", - "editor-toolbar/*" + "editor-toolbar/*", + "../image-occlusion/*" ], "references": [ { "path": "../components" }, diff --git a/ts/image-occlusion/Notes.svelte b/ts/image-occlusion/Notes.svelte index 9333348b12b..bbe8fe9ae26 100644 --- a/ts/image-occlusion/Notes.svelte +++ b/ts/image-occlusion/Notes.svelte @@ -85,7 +85,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html } .note-toolbar { - margin-left: 98px; + margin-left: 106px; margin-top: 2px; display: flex; overflow-x: auto; diff --git a/ts/image-occlusion/TopToolbar.svelte b/ts/image-occlusion/TopToolbar.svelte index 0461915eb69..cf89bc083e5 100644 --- a/ts/image-occlusion/TopToolbar.svelte +++ b/ts/image-occlusion/TopToolbar.svelte @@ -141,7 +141,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html display: flex; overflow-y: scroll; z-index: 99; - margin-left: 98px; + margin-left: 106px; margin-top: 2px; } @@ -171,6 +171,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html height: 32px; margin: unset; padding: 6px !important; + font-size: 16px !important; } .dropdown-content { diff --git a/ts/image-occlusion/store.ts b/ts/image-occlusion/store.ts index ce9b3be7d19..63111edab9e 100644 --- a/ts/image-occlusion/store.ts +++ b/ts/image-occlusion/store.ts @@ -9,3 +9,5 @@ export const notesDataStore = writable({ id: "", title: "", divValue: "", textar export const zoomResetValue = writable(1); // it stores the tags for the note in note editor export const tagsWritable = writable([""]); +// it stores the value of image loaded into mask editor or not +export const ioImageLoaded = writable(false);