From 7ce1c4439a8ca944362a9bb4160bca33850880aa Mon Sep 17 00:00:00 2001 From: Aristotelis <5459332+glutanimate@users.noreply.github.com> Date: Fri, 13 Oct 2023 00:47:50 +0200 Subject: [PATCH] Propagate editor UI state transitions to add-ons (#2711) * Propagate editor UI state transitions to add-ons * Also set initial Python state to EditorState.INITIAL --------- Co-authored-by: Glutanimate --- qt/aqt/editor.py | 25 ++++++++++++++++++++ qt/tools/genhooks_gui.py | 10 ++++++++ ts/editor/NoteEditor.svelte | 46 +++++++++++++++++++++++++++++++++++++ ts/editor/types.ts | 8 +++++++ 4 files changed, 89 insertions(+) diff --git a/qt/aqt/editor.py b/qt/aqt/editor.py index 8a6bc8657..5087e1579 100644 --- a/qt/aqt/editor.py +++ b/qt/aqt/editor.py @@ -90,6 +90,18 @@ class EditorMode(Enum): BROWSER = 2 +class EditorState(Enum): + """ + Current input state of the editing UI. + """ + + INITIAL = -1 + FIELDS = 0 + IO_PICKER = 1 + IO_MASKS = 2 + IO_FIELDS = 3 + + class Editor: """The screen that embeds an editing widget should listen for changes via the `operation_did_execute` hook, and call set_note() when the editor needs @@ -124,6 +136,7 @@ class Editor: self.last_field_index: int | None = None # current card, for card layout self.card: Card | None = None + self.state: EditorState = EditorState.INITIAL self._init_links() self.setupOuter() self.add_webview() @@ -474,6 +487,12 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too collapsed = collapsed_string == "true" self.setTagsCollapsed(collapsed) + elif cmd.startswith("editorState"): + (_, new_state_id, old_state_id) = cmd.split(":", 2) + self.signal_state_change( + EditorState(int(new_state_id)), EditorState(int(old_state_id)) + ) + elif cmd in self._links: return self._links[cmd](self) @@ -483,6 +502,12 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too def mungeHTML(self, txt: str) -> str: return gui_hooks.editor_will_munge_html(txt, self) + def signal_state_change( + self, new_state: EditorState, old_state: EditorState + ) -> None: + self.state = new_state + gui_hooks.editor_state_did_change(self, new_state, old_state) + # Setting/unsetting the current note ###################################################################### diff --git a/qt/tools/genhooks_gui.py b/qt/tools/genhooks_gui.py index 6a4cb0567..6cdd35431 100644 --- a/qt/tools/genhooks_gui.py +++ b/qt/tools/genhooks_gui.py @@ -1152,6 +1152,16 @@ gui_hooks.webview_did_inject_style_into_page.append(mytest) or by a right-click paste. """, ), + Hook( + name="editor_state_did_change", + args=[ + "editor: aqt.editor.Editor", + "new_state: aqt.editor.EditorState", + "old_state: aqt.editor.EditorState", + ], + doc="""Called when the input state of the editor changes, e.g. when + switching to an image occlusion note type.""", + ), # Tag ################### Hook(name="tag_editor_did_process_key", args=["tag_edit: TagEdit", "evt: QEvent"]), diff --git a/ts/editor/NoteEditor.svelte b/ts/editor/NoteEditor.svelte index 153d827e7..7ae589082 100644 --- a/ts/editor/NoteEditor.svelte +++ b/ts/editor/NoteEditor.svelte @@ -69,6 +69,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import RichTextInput, { editingInputIsRichText } from "./rich-text-input"; import RichTextBadge from "./RichTextBadge.svelte"; import type { NotetypeIdAndModTime, SessionOptions } from "./types"; + import { EditorState } from "./types"; function quoteFontFamily(fontFamily: string): string { // generic families (e.g. sans-serif) must not be quoted @@ -496,6 +497,44 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html return false; } + // Signal editor UI state changes to add-ons + + let editorState: EditorState = EditorState.Initial; + let lastEditorState: EditorState = editorState; + + function getEditorState( + ioMaskEditorVisible: boolean, + isImageOcclusion: boolean, + isIOImageLoaded: boolean, + imageOcclusionMode: IOMode | undefined, + ): EditorState { + if (isImageOcclusion && ioMaskEditorVisible && !isIOImageLoaded) { + return EditorState.ImageOcclusionPicker; + } else if (imageOcclusionMode && ioMaskEditorVisible) { + return EditorState.ImageOcclusionMasks; + } else if (!ioMaskEditorVisible && isImageOcclusion) { + return EditorState.ImageOcclusionFields; + } + return EditorState.Fields; + } + + function signalEditorState(newState: EditorState) { + tick().then(() => { + globalThis.editorState = newState; + bridgeCommand(`editorState:${newState}:${lastEditorState}`); + lastEditorState = newState; + }); + } + + $: signalEditorState(editorState); + + $: editorState = getEditorState( + $ioMaskEditorVisible, + isImageOcclusion, + isIOImageLoaded, + imageOcclusionMode, + ); + onMount(() => { function wrap(before: string, after: string): void { if (!$focusedInput || !editingInputIsRichText($focusedInput)) { @@ -537,6 +576,13 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html ...oldEditorAdapter, }); + editorState = getEditorState( + $ioMaskEditorVisible, + isImageOcclusion, + isIOImageLoaded, + imageOcclusionMode, + ); + document.addEventListener("visibilitychange", saveOnPageHide); return () => document.removeEventListener("visibilitychange", saveOnPageHide); }); diff --git a/ts/editor/types.ts b/ts/editor/types.ts index 8989f6883..14cece175 100644 --- a/ts/editor/types.ts +++ b/ts/editor/types.ts @@ -19,3 +19,11 @@ export type NotetypeIdAndModTime = { id: number; modTime: number; }; + +export enum EditorState { + Initial = -1, + Fields = 0, + ImageOcclusionPicker = 1, + ImageOcclusionMasks = 2, + ImageOcclusionFields = 3, +}