[IO API] Create Python entry points for creating and editing IO notes (#2598)

* Create python API for adding and editing IO notes

Also: Refactor IO-related methods, tweaking their naming and moving them to a continuous section

* Ensure editor is loaded before setupMaskEditor call

---------

Co-authored-by: Glutanimate <glutanimate@users.noreply.github.com>
This commit is contained in:
Aristotelis 2023-08-24 04:35:38 +02:00 committed by GitHub
parent 239e964c42
commit 594267149d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 102 additions and 42 deletions

View file

@ -51,6 +51,7 @@ class AddCards(QMainWindow):
add_close_shortcut(self)
self._load_new_note()
self.setupButtons()
self.col.add_image_occlusion_notetype()
self.history: list[NoteId] = []
self._last_added_note: Optional[Note] = None
gui_hooks.operation_did_execute.append(self.on_operation_did_execute)

View file

@ -31,7 +31,8 @@ from anki.collection import Config, SearchNode
from anki.consts import MODEL_CLOZE
from anki.hooks import runFilter
from anki.httpclient import HttpClient
from anki.notes import Note, NoteFieldsCheckResult
from anki.models import NotetypeId, StockNotetype
from anki.notes import Note, NoteFieldsCheckResult, NoteId
from anki.utils import checksum, is_lin, is_win, namedtmp
from aqt import AnkiQt, colors, gui_hooks
from aqt.operations import QueryOp
@ -561,18 +562,14 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
self.editorMode != EditorMode.ADD_CARDS
and self.current_notetype_is_image_occlusion()
):
mode = {"kind": "edit", "noteId": self.note.id}
options = {"mode": mode}
js += " setupMaskEditor(%s);" % json.dumps(options)
io_options = self._create_edit_io_options(note_id=self.note.id)
js += " setupMaskEditor(%s);" % json.dumps(io_options)
js = gui_hooks.editor_will_load_note(js, self.note, self)
self.web.evalWithCallback(
f'require("anki/ui").loaded.then(() => {{ {js} }})', oncallback
)
def current_notetype_is_image_occlusion(self) -> bool:
return False
def _save_current_note(self) -> None:
"Call after note is updated with data from webview."
update_note(parent=self.widget, note=self.note).run_in_background(
@ -982,6 +979,102 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
def onCutOrCopy(self) -> None:
self.web.user_cut_or_copied()
# Image occlusion
######################################################################
def current_notetype_is_image_occlusion(self) -> bool:
return bool(self.note) and (
self.note.note_type().get("originalStockKind", None)
== StockNotetype.OriginalStockKind.ORIGINAL_STOCK_KIND_IMAGE_OCCLUSION
)
def select_image_and_occlude(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:
if self.editorMode == EditorMode.ADD_CARDS:
self.setup_mask_editor_for_new_note(image_path=file, notetype_id=0)
else:
self.setup_mask_editor_for_existing_note(
note_id=self.note.id, image_path=file
)
except Exception as e:
showWarning(str(e))
return
file = getFile(
parent=self.widget,
title=tr.editing_add_media(),
cb=cast(Callable[[Any], None], accept),
filter=filter,
key="media",
)
self.parentWindow.activateWindow()
def setup_mask_editor_for_new_note(
self,
image_path: str,
notetype_id: NotetypeId | int = 0,
):
"""Set-up IO mask editor for adding new notes
Presupposes that active editor notetype is an image occlusion notetype
Args:
image_path: Absolute path to image.
notetype_id: ID of note type to use. Provided ID must belong to an
image occlusion notetype. Set this to 0 to auto-select the first
found image occlusion notetype in the user's collection.
"""
image_field_html = self._addMedia(image_path)
io_options = self._create_add_io_options(
image_path=image_path,
image_field_html=image_field_html,
notetype_id=notetype_id,
)
self._setup_mask_editor(io_options)
def setup_mask_editor_for_existing_note(
self, note_id: NoteId, image_path: str | None = None
):
"""Set-up IO mask editor for editing existing notes
Presupposes that active editor notetype is an image occlusion notetype
Args:
note_id: ID of note to edit.
image_path: (Optional) Absolute path to image that should replace current
image
"""
io_options = self._create_edit_io_options(note_id)
if image_path:
image_field_html = self._addMedia(image_path)
self.web.eval(f"resetIOImage({json.dumps(image_path)})")
self.web.eval(f"setImageField({json.dumps(image_field_html)})")
self._setup_mask_editor(io_options)
def _setup_mask_editor(self, io_options: dict):
self.web.eval(
'require("anki/ui").loaded.then(() =>'
f"setupMaskEditor({json.dumps(io_options)})"
"); "
)
@staticmethod
def _create_add_io_options(
image_path: str, image_field_html: str, notetype_id: NotetypeId | int = 0
) -> dict:
return {
"mode": {"kind": "add", "imagePath": image_path, "notetypeId": notetype_id},
"html": image_field_html,
}
@staticmethod
def _create_edit_io_options(note_id: NoteId) -> dict:
return {"mode": {"kind": "edit", "noteId": note_id}}
# Legacy editing routines
######################################################################
@ -1188,40 +1281,6 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
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)
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", "noteId": 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
file = getFile(
parent=self.widget,
title=tr.editing_add_media(),
cb=cast(Callable[[Any], None], accept),
filter=filter,
key="media",
)
self.parentWindow.activateWindow()
# Links from HTML
######################################################################
@ -1251,7 +1310,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
toggleMathjax=Editor.toggleMathjax,
toggleShrinkImages=Editor.toggleShrinkImages,
toggleCloseHTMLTags=Editor.toggleCloseHTMLTags,
addImageForOcclusion=Editor.onAddImageForOcclusion,
addImageForOcclusion=Editor.select_image_and_occlude,
)