mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 14:02:21 -04:00
[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:
parent
239e964c42
commit
594267149d
2 changed files with 102 additions and 42 deletions
|
@ -51,6 +51,7 @@ class AddCards(QMainWindow):
|
||||||
add_close_shortcut(self)
|
add_close_shortcut(self)
|
||||||
self._load_new_note()
|
self._load_new_note()
|
||||||
self.setupButtons()
|
self.setupButtons()
|
||||||
|
self.col.add_image_occlusion_notetype()
|
||||||
self.history: list[NoteId] = []
|
self.history: list[NoteId] = []
|
||||||
self._last_added_note: Optional[Note] = None
|
self._last_added_note: Optional[Note] = None
|
||||||
gui_hooks.operation_did_execute.append(self.on_operation_did_execute)
|
gui_hooks.operation_did_execute.append(self.on_operation_did_execute)
|
||||||
|
|
143
qt/aqt/editor.py
143
qt/aqt/editor.py
|
@ -31,7 +31,8 @@ from anki.collection import Config, SearchNode
|
||||||
from anki.consts import MODEL_CLOZE
|
from anki.consts import MODEL_CLOZE
|
||||||
from anki.hooks import runFilter
|
from anki.hooks import runFilter
|
||||||
from anki.httpclient import HttpClient
|
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 anki.utils import checksum, is_lin, is_win, namedtmp
|
||||||
from aqt import AnkiQt, colors, gui_hooks
|
from aqt import AnkiQt, colors, gui_hooks
|
||||||
from aqt.operations import QueryOp
|
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
|
self.editorMode != EditorMode.ADD_CARDS
|
||||||
and self.current_notetype_is_image_occlusion()
|
and self.current_notetype_is_image_occlusion()
|
||||||
):
|
):
|
||||||
mode = {"kind": "edit", "noteId": self.note.id}
|
io_options = self._create_edit_io_options(note_id=self.note.id)
|
||||||
options = {"mode": mode}
|
js += " setupMaskEditor(%s);" % json.dumps(io_options)
|
||||||
js += " setupMaskEditor(%s);" % json.dumps(options)
|
|
||||||
|
|
||||||
js = gui_hooks.editor_will_load_note(js, self.note, self)
|
js = gui_hooks.editor_will_load_note(js, self.note, self)
|
||||||
self.web.evalWithCallback(
|
self.web.evalWithCallback(
|
||||||
f'require("anki/ui").loaded.then(() => {{ {js} }})', oncallback
|
f'require("anki/ui").loaded.then(() => {{ {js} }})', oncallback
|
||||||
)
|
)
|
||||||
|
|
||||||
def current_notetype_is_image_occlusion(self) -> bool:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _save_current_note(self) -> None:
|
def _save_current_note(self) -> None:
|
||||||
"Call after note is updated with data from webview."
|
"Call after note is updated with data from webview."
|
||||||
update_note(parent=self.widget, note=self.note).run_in_background(
|
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:
|
def onCutOrCopy(self) -> None:
|
||||||
self.web.user_cut_or_copied()
|
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
|
# Legacy editing routines
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
|
@ -1188,40 +1281,6 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
|
||||||
def setTagsCollapsed(self, collapsed: bool) -> None:
|
def setTagsCollapsed(self, collapsed: bool) -> None:
|
||||||
aqt.mw.pm.set_tags_collapsed(self.editorMode, collapsed)
|
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
|
# Links from HTML
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
|
@ -1251,7 +1310,7 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
|
||||||
toggleMathjax=Editor.toggleMathjax,
|
toggleMathjax=Editor.toggleMathjax,
|
||||||
toggleShrinkImages=Editor.toggleShrinkImages,
|
toggleShrinkImages=Editor.toggleShrinkImages,
|
||||||
toggleCloseHTMLTags=Editor.toggleCloseHTMLTags,
|
toggleCloseHTMLTags=Editor.toggleCloseHTMLTags,
|
||||||
addImageForOcclusion=Editor.onAddImageForOcclusion,
|
addImageForOcclusion=Editor.select_image_and_occlude,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue