mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 22:12:21 -04:00
image occlusion button in note editor (#2485)
* setup mask editor in note editor - add image on mask button click (only one time) - show hide add button for io on notetype change - hide field in io notetype - icon for toggle and replace image * add update io notes * Tidy up i/o notetype check and fix error - Make it a method on editor - Use .get(), because the setting doesn't exist on older notetypes - Pass the bool value into the ts code, instead of the enum * reset io page after adding * remove adjust function & add target for mask editor * handle browse mode & merged sidetoolbar and toptoolbar to toolbar * fix: shape, button click in browse, dropdown menu * add arrow to add button * store for handling visiblity of maskeditor - remove update button in edit mode, implement autoupdate * update var name * simplify store
This commit is contained in:
parent
0a418e0612
commit
135de7f9ed
23 changed files with 520 additions and 240 deletions
|
@ -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-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?
|
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-error-no-image-to-show = No image to show.
|
||||||
|
notetypes-no-occlusion-created = You must make at least one occlusion.
|
||||||
|
notetypes-io-select-image = Select Image
|
||||||
|
|
|
@ -466,6 +466,10 @@ class Collection(DeprecatedNamesMixin):
|
||||||
def get_image_for_occlusion(self, path: str | None) -> GetImageForOcclusionResponse:
|
def get_image_for_occlusion(self, path: str | None) -> GetImageForOcclusionResponse:
|
||||||
return self._backend.get_image_for_occlusion(path=path)
|
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(
|
def add_image_occlusion_note(
|
||||||
self,
|
self,
|
||||||
notetype_id: int,
|
notetype_id: int,
|
||||||
|
|
|
@ -27,6 +27,7 @@ NotetypeNameIdUseCount = notetypes_pb2.NotetypeNameIdUseCount
|
||||||
NotetypeNames = notetypes_pb2.NotetypeNames
|
NotetypeNames = notetypes_pb2.NotetypeNames
|
||||||
ChangeNotetypeInfo = notetypes_pb2.ChangeNotetypeInfo
|
ChangeNotetypeInfo = notetypes_pb2.ChangeNotetypeInfo
|
||||||
ChangeNotetypeRequest = notetypes_pb2.ChangeNotetypeRequest
|
ChangeNotetypeRequest = notetypes_pb2.ChangeNotetypeRequest
|
||||||
|
StockNotetype = notetypes_pb2.StockNotetype
|
||||||
|
|
||||||
# legacy types
|
# legacy types
|
||||||
NotetypeDict = dict[str, Any]
|
NotetypeDict = dict[str, Any]
|
||||||
|
|
|
@ -48,9 +48,10 @@ class AddCards(QMainWindow):
|
||||||
self.setMinimumWidth(400)
|
self.setMinimumWidth(400)
|
||||||
self.setup_choosers()
|
self.setup_choosers()
|
||||||
self.setupEditor()
|
self.setupEditor()
|
||||||
self.setupButtons()
|
|
||||||
add_close_shortcut(self)
|
add_close_shortcut(self)
|
||||||
self._load_new_note()
|
self._load_new_note()
|
||||||
|
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)
|
||||||
|
@ -112,6 +113,12 @@ class AddCards(QMainWindow):
|
||||||
self.compat_add_shorcut = QShortcut(QKeySequence("Ctrl+Enter"), self)
|
self.compat_add_shorcut = QShortcut(QKeySequence("Ctrl+Enter"), self)
|
||||||
qconnect(self.compat_add_shorcut.activated, self.addButton.click)
|
qconnect(self.compat_add_shorcut.activated, self.addButton.click)
|
||||||
self.addButton.setToolTip(shortcut(tr.adding_add_shortcut_ctrlandenter()))
|
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.setShortcut(QKeySequence("Ctrl+Shift+I"))
|
||||||
|
|
||||||
# close
|
# close
|
||||||
self.closeButton = QPushButton(tr.actions_close())
|
self.closeButton = QPushButton(tr.actions_close())
|
||||||
self.closeButton.setAutoDefault(False)
|
self.closeButton.setAutoDefault(False)
|
||||||
|
@ -133,6 +140,17 @@ class AddCards(QMainWindow):
|
||||||
b.setEnabled(False)
|
b.setEnabled(False)
|
||||||
self.historyButton = b
|
self.historyButton = b
|
||||||
|
|
||||||
|
# hide io buttons for note type other than image occlusion
|
||||||
|
self.show_hide_add_buttons()
|
||||||
|
|
||||||
|
def show_hide_add_buttons(self) -> None:
|
||||||
|
if self.editor.current_notetype_is_image_occlusion():
|
||||||
|
self.addButton.setVisible(False)
|
||||||
|
self.io_add_button.setVisible(True)
|
||||||
|
else:
|
||||||
|
self.addButton.setVisible(True)
|
||||||
|
self.io_add_button.setVisible(False)
|
||||||
|
|
||||||
def setAndFocusNote(self, note: Note) -> None:
|
def setAndFocusNote(self, note: Note) -> None:
|
||||||
self.editor.set_note(note, focusTo=0)
|
self.editor.set_note(note, focusTo=0)
|
||||||
|
|
||||||
|
@ -192,6 +210,9 @@ class AddCards(QMainWindow):
|
||||||
self, old_note.note_type(), new_note.note_type()
|
self, 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:
|
def _load_new_note(self, sticky_fields_from: Optional[Note] = None) -> None:
|
||||||
note = self._new_note()
|
note = self._new_note()
|
||||||
if old_note := sticky_fields_from:
|
if old_note := sticky_fields_from:
|
||||||
|
@ -283,6 +304,9 @@ class AddCards(QMainWindow):
|
||||||
# no problem, duplicate, and confirmed cloze cases
|
# no problem, duplicate, and confirmed cloze cases
|
||||||
problem = None
|
problem = None
|
||||||
if result == NoteFieldsCheckResult.EMPTY:
|
if result == NoteFieldsCheckResult.EMPTY:
|
||||||
|
if self.editor.current_notetype_is_image_occlusion():
|
||||||
|
problem = tr.notetypes_no_occlusion_created()
|
||||||
|
else:
|
||||||
problem = tr.adding_the_first_field_is_empty()
|
problem = tr.adding_the_first_field_is_empty()
|
||||||
elif result == NoteFieldsCheckResult.MISSING_CLOZE:
|
elif result == NoteFieldsCheckResult.MISSING_CLOZE:
|
||||||
if not askUser(tr.adding_you_have_a_cloze_deletion_note()):
|
if not askUser(tr.adding_you_have_a_cloze_deletion_note()):
|
||||||
|
@ -348,6 +372,24 @@ class AddCards(QMainWindow):
|
||||||
|
|
||||||
self.ifCanClose(doClose)
|
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)")
|
||||||
|
self.add_current_note()
|
||||||
|
self.editor.web.eval("resetIOImageLoaded()")
|
||||||
|
|
||||||
# legacy aliases
|
# legacy aliases
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
@ -21,9 +21,6 @@ class EditCurrent(QDialog):
|
||||||
disable_help_button(self)
|
disable_help_button(self)
|
||||||
self.setMinimumHeight(400)
|
self.setMinimumHeight(400)
|
||||||
self.setMinimumWidth(250)
|
self.setMinimumWidth(250)
|
||||||
self.form.buttonBox.button(QDialogButtonBox.StandardButton.Close).setShortcut(
|
|
||||||
QKeySequence("Ctrl+Return")
|
|
||||||
)
|
|
||||||
self.editor = aqt.editor.Editor(
|
self.editor = aqt.editor.Editor(
|
||||||
self.mw,
|
self.mw,
|
||||||
self.form.fieldsArea,
|
self.form.fieldsArea,
|
||||||
|
@ -33,6 +30,9 @@ class EditCurrent(QDialog):
|
||||||
self.editor.card = self.mw.reviewer.card
|
self.editor.card = self.mw.reviewer.card
|
||||||
self.editor.set_note(self.mw.reviewer.card.note(), focusTo=0)
|
self.editor.set_note(self.mw.reviewer.card.note(), focusTo=0)
|
||||||
restoreGeom(self, "editcurrent")
|
restoreGeom(self, "editcurrent")
|
||||||
|
self.form.buttonBox.button(QDialogButtonBox.StandardButton.Close).setShortcut(
|
||||||
|
QKeySequence("Ctrl+Return")
|
||||||
|
)
|
||||||
gui_hooks.operation_did_execute.append(self.on_operation_did_execute)
|
gui_hooks.operation_did_execute.append(self.on_operation_did_execute)
|
||||||
self.show()
|
self.show()
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,7 @@ 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.models import StockNotetype
|
||||||
from anki.notes import Note, NoteFieldsCheckResult
|
from anki.notes import Note, NoteFieldsCheckResult
|
||||||
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
|
||||||
|
@ -549,17 +550,33 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
|
||||||
setShrinkImages({json.dumps(self.mw.col.get_config("shrinkEditorImages", True))});
|
setShrinkImages({json.dumps(self.mw.col.get_config("shrinkEditorImages", True))});
|
||||||
setCloseHTMLTags({json.dumps(self.mw.col.get_config("closeHTMLTags", True))});
|
setCloseHTMLTags({json.dumps(self.mw.col.get_config("closeHTMLTags", True))});
|
||||||
triggerChanges();
|
triggerChanges();
|
||||||
|
setIsImageOcclusion({json.dumps(self.current_notetype_is_image_occlusion())});
|
||||||
|
setIsEditMode({json.dumps(self.editorMode != EditorMode.ADD_CARDS)});
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.addMode:
|
if self.addMode:
|
||||||
sticky = [field["sticky"] for field in self.note.note_type()["flds"]]
|
sticky = [field["sticky"] for field in self.note.note_type()["flds"]]
|
||||||
js += " setSticky(%s);" % json.dumps(sticky)
|
js += " setSticky(%s);" % json.dumps(sticky)
|
||||||
|
|
||||||
|
if (
|
||||||
|
self.editorMode != EditorMode.ADD_CARDS
|
||||||
|
and self.current_notetype_is_image_occlusion()
|
||||||
|
):
|
||||||
|
options = {"kind": "edit", "noteId": self.note.id}
|
||||||
|
options = {"mode": 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 bool(self.note) and (
|
||||||
|
self.note.note_type().get("originalStockKind", None)
|
||||||
|
== StockNotetype.OriginalStockKind.ORIGINAL_STOCK_KIND_IMAGE_OCCLUSION
|
||||||
|
)
|
||||||
|
|
||||||
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(
|
||||||
|
@ -1175,6 +1192,34 @@ 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)
|
||||||
|
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)})")
|
||||||
|
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
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
|
@ -1204,6 +1249,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,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1452,4 +1498,14 @@ def set_cloze_button(editor: Editor) -> None:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def set_image_occlusion_button(editor: Editor) -> None:
|
||||||
|
action = "show" if editor.current_notetype_is_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_cloze_button)
|
||||||
|
gui_hooks.editor_did_load_note.append(set_image_occlusion_button)
|
||||||
|
|
|
@ -15,6 +15,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
plainText: boolean;
|
plainText: boolean;
|
||||||
description: string;
|
description: string;
|
||||||
collapsed: boolean;
|
collapsed: boolean;
|
||||||
|
hidden: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EditorFieldAPI {
|
export interface EditorFieldAPI {
|
||||||
|
@ -87,7 +88,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
onDestroy(() => api?.destroy());
|
onDestroy(() => api?.destroy());
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="field-container" on:mouseenter on:mouseleave>
|
<div class="field-container" class:hide={field.hidden} on:mouseenter on:mouseleave>
|
||||||
<slot name="field-label" />
|
<slot name="field-label" />
|
||||||
|
|
||||||
<Collapsible collapse={collapsed} let:collapsed={hidden}>
|
<Collapsible collapse={collapsed} let:collapsed={hidden}>
|
||||||
|
@ -127,6 +128,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.field-container.hide {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.editor-field {
|
.editor-field {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
/* make room for thicker focus border */
|
/* make room for thicker focus border */
|
||||||
|
|
|
@ -237,6 +237,17 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
return noteId;
|
return noteId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let isImageOcclusion = false;
|
||||||
|
function setIsImageOcclusion(val: boolean) {
|
||||||
|
isImageOcclusion = val;
|
||||||
|
$ioMaskEditorVisible = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
let isEditMode = false;
|
||||||
|
function setIsEditMode(val: boolean) {
|
||||||
|
isEditMode = val;
|
||||||
|
}
|
||||||
|
|
||||||
let cols: ("dupe" | "")[] = [];
|
let cols: ("dupe" | "")[] = [];
|
||||||
export function setBackgrounds(cls: ("dupe" | "")[]): void {
|
export function setBackgrounds(cls: ("dupe" | "")[]): void {
|
||||||
cols = cls;
|
cols = cls;
|
||||||
|
@ -255,6 +266,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
fontSize: fonts[index][1],
|
fontSize: fonts[index][1],
|
||||||
direction: fonts[index][2] ? "rtl" : "ltr",
|
direction: fonts[index][2] ? "rtl" : "ltr",
|
||||||
collapsed: fieldsCollapsed[index],
|
collapsed: fieldsCollapsed[index],
|
||||||
|
hidden: hideFieldInOcclusionType(index),
|
||||||
})) as FieldData[];
|
})) as FieldData[];
|
||||||
|
|
||||||
function saveTags({ detail }: CustomEvent): void {
|
function saveTags({ detail }: CustomEvent): void {
|
||||||
|
@ -286,6 +298,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveNow(): void {
|
function saveNow(): void {
|
||||||
|
updateIONoteInEditMode();
|
||||||
closeMathjaxEditor?.();
|
closeMathjaxEditor?.();
|
||||||
$commitTagEdits();
|
$commitTagEdits();
|
||||||
saveFieldNow();
|
saveFieldNow();
|
||||||
|
@ -372,12 +385,68 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
}
|
}
|
||||||
|
|
||||||
import { wrapInternal } from "@tslib/wrap";
|
import { wrapInternal } from "@tslib/wrap";
|
||||||
|
import LabelButton from "components/LabelButton.svelte";
|
||||||
import Shortcut from "components/Shortcut.svelte";
|
import Shortcut from "components/Shortcut.svelte";
|
||||||
|
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 { mathjaxConfig } from "../editable/mathjax-element";
|
import { mathjaxConfig } from "../editable/mathjax-element";
|
||||||
import CollapseLabel from "./CollapseLabel.svelte";
|
import CollapseLabel from "./CollapseLabel.svelte";
|
||||||
import * as oldEditorAdapter from "./old-editor-adapter";
|
import * as oldEditorAdapter from "./old-editor-adapter";
|
||||||
|
|
||||||
|
let isIOImageLoaded = false;
|
||||||
|
let imageOcclusionMode: IOMode | undefined;
|
||||||
|
async function setupMaskEditor(options: { html: string; mode: IOMode }) {
|
||||||
|
imageOcclusionMode = options.mode;
|
||||||
|
if (options.mode.kind === "add") {
|
||||||
|
fieldStores[1].set(options.html);
|
||||||
|
}
|
||||||
|
isIOImageLoaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update cloze deletions and set occlusion fields, it call in saveNow to update cloze deletions
|
||||||
|
function updateIONoteInEditMode() {
|
||||||
|
if (isEditMode) {
|
||||||
|
const clozeNote = get(fieldStores[0]);
|
||||||
|
if (clozeNote.includes("oi=1")) {
|
||||||
|
setOcclusionField(true);
|
||||||
|
} else {
|
||||||
|
setOcclusionField(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset for new occlusion in add mode
|
||||||
|
function resetIOImageLoaded() {
|
||||||
|
isIOImageLoaded = false;
|
||||||
|
globalThis.canvas.clear();
|
||||||
|
const page = document.querySelector(".image-occlusion");
|
||||||
|
if (page) {
|
||||||
|
page.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
globalThis.resetIOImageLoaded = resetIOImageLoaded;
|
||||||
|
|
||||||
|
function setOcclusionField(occludeInactive: boolean) {
|
||||||
|
// set fields data for occlusion and image fields for io notes type
|
||||||
|
if (isImageOcclusion) {
|
||||||
|
const occlusionsData = exportShapesToClozeDeletions(occludeInactive);
|
||||||
|
fieldStores[0].set(occlusionsData.clozes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// hide first two fields for occlusion type, first contains occlusion data and second contains image
|
||||||
|
function hideFieldInOcclusionType(index: number) {
|
||||||
|
if (isImageOcclusion) {
|
||||||
|
if (index == 0 || index == 1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
function wrap(before: string, after: string): void {
|
function wrap(before: string, after: string): void {
|
||||||
if (!$focusedInput || !editingInputIsRichText($focusedInput)) {
|
if (!$focusedInput || !editingInputIsRichText($focusedInput)) {
|
||||||
|
@ -411,6 +480,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
setShrinkImages,
|
setShrinkImages,
|
||||||
setCloseHTMLTags,
|
setCloseHTMLTags,
|
||||||
triggerChanges,
|
triggerChanges,
|
||||||
|
setIsImageOcclusion,
|
||||||
|
setIsEditMode,
|
||||||
|
setupMaskEditor,
|
||||||
|
setOcclusionField,
|
||||||
...oldEditorAdapter,
|
...oldEditorAdapter,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -464,6 +537,26 @@ the AddCards dialog) should be implemented in the user of this component.
|
||||||
</Absolute>
|
</Absolute>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if imageOcclusionMode}
|
||||||
|
<div style="display: {$ioMaskEditorVisible ? 'block' : 'none'}">
|
||||||
|
<ImageOcclusionPage mode={imageOcclusionMode} />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $ioMaskEditorVisible && isImageOcclusion && !isIOImageLoaded}
|
||||||
|
<div id="io-select-image-div" style="padding-top: 60px; text-align: center;">
|
||||||
|
<LabelButton
|
||||||
|
--border-left-radius="5px"
|
||||||
|
--border-right-radius="5px"
|
||||||
|
class="io-select-image-btn"
|
||||||
|
on:click={() => bridgeCommand("addImageForOcclusion")}
|
||||||
|
>
|
||||||
|
{tr.notetypesIoSelectImage()}
|
||||||
|
</LabelButton>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if !$ioMaskEditorVisible}
|
||||||
<Fields>
|
<Fields>
|
||||||
{#each fieldsData as field, index}
|
{#each fieldsData as field, index}
|
||||||
{@const content = fieldStores[index]}
|
{@const content = fieldStores[index]}
|
||||||
|
@ -595,6 +688,7 @@ the AddCards dialog) should be implemented in the user of this component.
|
||||||
<Collapsible toggleDisplay collapse={$tagsCollapsed}>
|
<Collapsible toggleDisplay collapse={$tagsCollapsed}>
|
||||||
<TagEditor {tags} on:tagsupdate={saveTags} />
|
<TagEditor {tags} on:tagsupdate={saveTags} />
|
||||||
</Collapsible>
|
</Collapsible>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@ -603,4 +697,32 @@ the AddCards dialog) should be implemented in the user of this component.
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:global(.image-occlusion) {
|
||||||
|
position: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.image-occlusion .tab-buttons) {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.image-occlusion .top-tool-bar-container) {
|
||||||
|
margin-left: 28px !important;
|
||||||
|
}
|
||||||
|
:global(.top-tool-bar-container .icon-button) {
|
||||||
|
height: 36px !important;
|
||||||
|
}
|
||||||
|
:global(.image-occlusion .tool-bar-container) {
|
||||||
|
top: unset !important;
|
||||||
|
margin-top: 2px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.io-select-image-btn) {
|
||||||
|
margin: auto;
|
||||||
|
padding: 0px 8px 0px 8px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.image-occlusion .sticky-footer) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -38,6 +38,8 @@ export const editorModules = [
|
||||||
ModuleName.KEYBOARD,
|
ModuleName.KEYBOARD,
|
||||||
ModuleName.ACTIONS,
|
ModuleName.ACTIONS,
|
||||||
ModuleName.BROWSING,
|
ModuleName.BROWSING,
|
||||||
|
ModuleName.NOTETYPES,
|
||||||
|
ModuleName.IMPORTING,
|
||||||
];
|
];
|
||||||
|
|
||||||
export const components = {
|
export const components = {
|
||||||
|
|
|
@ -55,6 +55,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import DynamicallySlottable from "../../components/DynamicallySlottable.svelte";
|
import DynamicallySlottable from "../../components/DynamicallySlottable.svelte";
|
||||||
import Item from "../../components/Item.svelte";
|
import Item from "../../components/Item.svelte";
|
||||||
import BlockButtons from "./BlockButtons.svelte";
|
import BlockButtons from "./BlockButtons.svelte";
|
||||||
|
import ImageOcclusionButton from "./ImageOcclusionButton.svelte";
|
||||||
import InlineButtons from "./InlineButtons.svelte";
|
import InlineButtons from "./InlineButtons.svelte";
|
||||||
import NotetypeButtons from "./NotetypeButtons.svelte";
|
import NotetypeButtons from "./NotetypeButtons.svelte";
|
||||||
import OptionsButtons from "./OptionsButtons.svelte";
|
import OptionsButtons from "./OptionsButtons.svelte";
|
||||||
|
@ -120,6 +121,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
<Item id="cloze">
|
<Item id="cloze">
|
||||||
<RichTextClozeButtons />
|
<RichTextClozeButtons />
|
||||||
</Item>
|
</Item>
|
||||||
|
|
||||||
|
<Item id="image-occlusion-button">
|
||||||
|
<ImageOcclusionButton />
|
||||||
|
</Item>
|
||||||
</DynamicallySlottable>
|
</DynamicallySlottable>
|
||||||
</ButtonToolbar>
|
</ButtonToolbar>
|
||||||
</div>
|
</div>
|
||||||
|
|
48
ts/editor/editor-toolbar/ImageOcclusionButton.svelte
Normal file
48
ts/editor/editor-toolbar/ImageOcclusionButton.svelte
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
<!--
|
||||||
|
Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
-->
|
||||||
|
<script lang="ts">
|
||||||
|
import ButtonGroup from "components/ButtonGroup.svelte";
|
||||||
|
import DynamicallySlottable from "components/DynamicallySlottable.svelte";
|
||||||
|
import IconButton from "components/IconButton.svelte";
|
||||||
|
import { ioMaskEditorVisible } from "image-occlusion/store";
|
||||||
|
|
||||||
|
import ButtonGroupItem, {
|
||||||
|
createProps,
|
||||||
|
setSlotHostContext,
|
||||||
|
updatePropsList,
|
||||||
|
} from "../../components/ButtonGroupItem.svelte";
|
||||||
|
import { mdiViewDashboard } from "./icons";
|
||||||
|
|
||||||
|
export let api = {};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ButtonGroup>
|
||||||
|
<DynamicallySlottable
|
||||||
|
slotHost={ButtonGroupItem}
|
||||||
|
{createProps}
|
||||||
|
{updatePropsList}
|
||||||
|
{setSlotHostContext}
|
||||||
|
{api}
|
||||||
|
>
|
||||||
|
<ButtonGroupItem>
|
||||||
|
<IconButton
|
||||||
|
id="io-mask-btn"
|
||||||
|
class={$ioMaskEditorVisible ? "active-io-btn" : ""}
|
||||||
|
on:click={() => {
|
||||||
|
$ioMaskEditorVisible = !$ioMaskEditorVisible;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{@html mdiViewDashboard}
|
||||||
|
</IconButton>
|
||||||
|
</ButtonGroupItem>
|
||||||
|
</DynamicallySlottable>
|
||||||
|
</ButtonGroup>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
:global(.active-io-btn) {
|
||||||
|
background: var(--button-primary-bg) !important;
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -11,6 +11,8 @@ export { default as subscriptIcon } from "@mdi/svg/svg/format-subscript.svg";
|
||||||
export { default as superscriptIcon } from "@mdi/svg/svg/format-superscript.svg";
|
export { default as superscriptIcon } from "@mdi/svg/svg/format-superscript.svg";
|
||||||
export { default as functionIcon } from "@mdi/svg/svg/function-variant.svg";
|
export { default as functionIcon } from "@mdi/svg/svg/function-variant.svg";
|
||||||
export { default as paperclipIcon } from "@mdi/svg/svg/paperclip.svg";
|
export { default as paperclipIcon } from "@mdi/svg/svg/paperclip.svg";
|
||||||
|
export { default as mdiRefresh } from "@mdi/svg/svg/refresh.svg";
|
||||||
|
export { default as mdiViewDashboard } from "@mdi/svg/svg/view-dashboard.svg";
|
||||||
export { default as eraserIcon } from "bootstrap-icons/icons/eraser.svg";
|
export { default as eraserIcon } from "bootstrap-icons/icons/eraser.svg";
|
||||||
export { default as justifyFullIcon } from "bootstrap-icons/icons/justify.svg";
|
export { default as justifyFullIcon } from "bootstrap-icons/icons/justify.svg";
|
||||||
export { default as olIcon } from "bootstrap-icons/icons/list-ol.svg";
|
export { default as olIcon } from "bootstrap-icons/icons/list-ol.svg";
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
{ "path": "../sveltelib" },
|
{ "path": "../sveltelib" },
|
||||||
{ "path": "../editable" },
|
{ "path": "../editable" },
|
||||||
{ "path": "../html-filter" },
|
{ "path": "../html-filter" },
|
||||||
{ "path": "../tag-editor" }
|
{ "path": "../tag-editor" },
|
||||||
|
{ "path": "../image-occlusion" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,10 +8,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
import type { IOMode } from "./lib";
|
import type { IOMode } from "./lib";
|
||||||
import { setupMaskEditor, setupMaskEditorForEdit } from "./mask-editor";
|
import { setupMaskEditor, setupMaskEditorForEdit } from "./mask-editor";
|
||||||
import SideToolbar from "./SideToolbar.svelte";
|
import Toolbar from "./Toolbar.svelte";
|
||||||
|
|
||||||
export let mode: IOMode;
|
export let mode: IOMode;
|
||||||
|
const iconSize = 80;
|
||||||
let instance: PanZoom;
|
let instance: PanZoom;
|
||||||
let innerWidth = 0;
|
let innerWidth = 0;
|
||||||
const startingTool = mode.kind === "add" ? "draw-rectangle" : "cursor";
|
const startingTool = mode.kind === "add" ? "draw-rectangle" : "cursor";
|
||||||
|
@ -39,7 +39,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<SideToolbar {instance} {canvas} activeTool={startingTool} />
|
<Toolbar {canvas} {instance} {iconSize} activeTool={startingTool} />
|
||||||
<div class="editor-main" bind:clientWidth={innerWidth}>
|
<div class="editor-main" bind:clientWidth={innerWidth}>
|
||||||
<div class="editor-container" use:init>
|
<div class="editor-container" use:init>
|
||||||
<!-- svelte-ignore a11y-missing-attribute -->
|
<!-- svelte-ignore a11y-missing-attribute -->
|
||||||
|
|
|
@ -85,7 +85,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
}
|
}
|
||||||
|
|
||||||
.note-toolbar {
|
.note-toolbar {
|
||||||
margin-left: 98px;
|
margin-left: 106px;
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
display: flex;
|
display: flex;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
|
|
|
@ -1,99 +0,0 @@
|
||||||
<!--
|
|
||||||
Copyright: Ankitects Pty Ltd and contributors
|
|
||||||
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|
||||||
-->
|
|
||||||
<script lang="ts">
|
|
||||||
import IconButton from "../components/IconButton.svelte";
|
|
||||||
import { drawEllipse, drawPolygon, drawRectangle } from "./tools/index";
|
|
||||||
import { enableSelectable, stopDraw } from "./tools/lib";
|
|
||||||
import { tools } from "./tools/tool-buttons";
|
|
||||||
import TopToolbar from "./TopToolbar.svelte";
|
|
||||||
|
|
||||||
export let instance;
|
|
||||||
export let canvas;
|
|
||||||
|
|
||||||
const iconSize = 80;
|
|
||||||
|
|
||||||
export let activeTool = "cursor";
|
|
||||||
|
|
||||||
// handle tool changes after initialization
|
|
||||||
$: if (instance && canvas) {
|
|
||||||
disableFunctions();
|
|
||||||
enableSelectable(canvas, true);
|
|
||||||
|
|
||||||
switch (activeTool) {
|
|
||||||
case "magnify":
|
|
||||||
enableSelectable(canvas, false);
|
|
||||||
instance.resume();
|
|
||||||
break;
|
|
||||||
case "draw-rectangle":
|
|
||||||
drawRectangle(canvas);
|
|
||||||
break;
|
|
||||||
case "draw-ellipse":
|
|
||||||
drawEllipse(canvas);
|
|
||||||
break;
|
|
||||||
case "draw-polygon":
|
|
||||||
drawPolygon(canvas, instance);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const disableFunctions = () => {
|
|
||||||
instance.pause();
|
|
||||||
stopDraw(canvas);
|
|
||||||
canvas.selectionColor = "rgba(100, 100, 255, 0.3)";
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<TopToolbar {canvas} {instance} {iconSize} />
|
|
||||||
|
|
||||||
<div class="tool-bar-container">
|
|
||||||
{#each tools as tool}
|
|
||||||
<IconButton
|
|
||||||
class="tool-icon-button {activeTool == tool.id ? 'active-tool' : ''}"
|
|
||||||
{iconSize}
|
|
||||||
active={activeTool === tool.id}
|
|
||||||
on:click={() => {
|
|
||||||
activeTool = tool.id;
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{@html tool.icon}
|
|
||||||
</IconButton>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.tool-bar-container {
|
|
||||||
position: fixed;
|
|
||||||
top: 42px;
|
|
||||||
left: 2px;
|
|
||||||
height: 100%;
|
|
||||||
border-right: 1px solid var(--border);
|
|
||||||
overflow-y: auto;
|
|
||||||
width: 32px;
|
|
||||||
z-index: 99;
|
|
||||||
background: var(--canvas-elevated);
|
|
||||||
padding-bottom: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.tool-icon-button) {
|
|
||||||
border: unset;
|
|
||||||
display: block;
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
margin: unset;
|
|
||||||
padding: 6px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.active-tool) {
|
|
||||||
color: white !important;
|
|
||||||
background: var(--button-primary-bg) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar {
|
|
||||||
width: 0.2em !important;
|
|
||||||
height: 0.2em !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -5,18 +5,22 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
<script>
|
<script>
|
||||||
import IconButton from "../components/IconButton.svelte";
|
import IconButton from "../components/IconButton.svelte";
|
||||||
import { mdiEye, mdiFormatAlignCenter } from "./icons";
|
import { mdiEye, mdiFormatAlignCenter } from "./icons";
|
||||||
|
import { drawEllipse, drawPolygon, drawRectangle } from "./tools/index";
|
||||||
import { makeMaskTransparent } from "./tools/lib";
|
import { makeMaskTransparent } from "./tools/lib";
|
||||||
|
import { enableSelectable, stopDraw } from "./tools/lib";
|
||||||
import {
|
import {
|
||||||
alignTools,
|
alignTools,
|
||||||
deleteDuplicateTools,
|
deleteDuplicateTools,
|
||||||
groupUngroupTools,
|
groupUngroupTools,
|
||||||
zoomTools,
|
zoomTools,
|
||||||
} from "./tools/more-tools";
|
} from "./tools/more-tools";
|
||||||
|
import { tools } from "./tools/tool-buttons";
|
||||||
import { undoRedoTools } from "./tools/tool-undo-redo";
|
import { undoRedoTools } from "./tools/tool-undo-redo";
|
||||||
|
|
||||||
export let canvas;
|
export let canvas;
|
||||||
export let instance;
|
export let instance;
|
||||||
export let iconSize;
|
export let iconSize;
|
||||||
|
export let activeTool = "cursor";
|
||||||
let showAlignTools = false;
|
let showAlignTools = false;
|
||||||
let leftPos = 82;
|
let leftPos = 82;
|
||||||
let maksOpacity = false;
|
let maksOpacity = false;
|
||||||
|
@ -27,8 +31,53 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
showAlignTools = false;
|
showAlignTools = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// handle tool changes after initialization
|
||||||
|
$: if (instance && canvas) {
|
||||||
|
disableFunctions();
|
||||||
|
enableSelectable(canvas, true);
|
||||||
|
|
||||||
|
switch (activeTool) {
|
||||||
|
case "magnify":
|
||||||
|
enableSelectable(canvas, false);
|
||||||
|
instance.resume();
|
||||||
|
break;
|
||||||
|
case "draw-rectangle":
|
||||||
|
drawRectangle(canvas);
|
||||||
|
break;
|
||||||
|
case "draw-ellipse":
|
||||||
|
drawEllipse(canvas);
|
||||||
|
break;
|
||||||
|
case "draw-polygon":
|
||||||
|
drawPolygon(canvas, instance);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const disableFunctions = () => {
|
||||||
|
instance.pause();
|
||||||
|
stopDraw(canvas);
|
||||||
|
canvas.selectionColor = "rgba(100, 100, 255, 0.3)";
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<div class="tool-bar-container">
|
||||||
|
{#each tools as tool}
|
||||||
|
<IconButton
|
||||||
|
class="tool-icon-button {activeTool == tool.id ? 'active-tool' : ''}"
|
||||||
|
{iconSize}
|
||||||
|
active={activeTool === tool.id}
|
||||||
|
on:click={() => {
|
||||||
|
activeTool = tool.id;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{@html tool.icon}
|
||||||
|
</IconButton>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="top-tool-bar-container">
|
<div class="top-tool-bar-container">
|
||||||
<!-- undo & redo tools -->
|
<!-- undo & redo tools -->
|
||||||
<div class="undo-redo-button">
|
<div class="undo-redo-button">
|
||||||
|
@ -141,7 +190,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
display: flex;
|
display: flex;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
z-index: 99;
|
z-index: 99;
|
||||||
margin-left: 98px;
|
margin-left: 106px;
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,6 +220,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
height: 32px;
|
height: 32px;
|
||||||
margin: unset;
|
margin: unset;
|
||||||
padding: 6px !important;
|
padding: 6px !important;
|
||||||
|
font-size: 16px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-content {
|
.dropdown-content {
|
||||||
|
@ -189,4 +239,31 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
width: 0.1em !important;
|
width: 0.1em !important;
|
||||||
height: 0.1em !important;
|
height: 0.1em !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tool-bar-container {
|
||||||
|
position: fixed;
|
||||||
|
top: 42px;
|
||||||
|
left: 2px;
|
||||||
|
height: 100%;
|
||||||
|
border-right: 1px solid var(--border);
|
||||||
|
overflow-y: auto;
|
||||||
|
width: 32px;
|
||||||
|
z-index: 99;
|
||||||
|
background: var(--canvas-elevated);
|
||||||
|
padding-bottom: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.tool-icon-button) {
|
||||||
|
border: unset;
|
||||||
|
display: block;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
margin: unset;
|
||||||
|
padding: 6px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.active-tool) {
|
||||||
|
color: white !important;
|
||||||
|
background: var(--button-primary-bg) !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
|
@ -20,12 +20,12 @@ const i18n = setupI18n({
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function setupImageOcclusion(mode: IOMode): Promise<ImageOcclusionPage> {
|
export async function setupImageOcclusion(mode: IOMode, target = document.body): Promise<ImageOcclusionPage> {
|
||||||
checkNightMode();
|
checkNightMode();
|
||||||
await i18n;
|
await i18n;
|
||||||
|
|
||||||
return new ImageOcclusionPage({
|
return new ImageOcclusionPage({
|
||||||
target: document.body,
|
target: target,
|
||||||
props: {
|
props: {
|
||||||
mode,
|
mode,
|
||||||
},
|
},
|
||||||
|
|
|
@ -54,6 +54,7 @@ export const setupMaskEditorForEdit = async (noteId: number, instance: PanZoom):
|
||||||
|
|
||||||
// get image width and height
|
// get image width and height
|
||||||
const image = document.getElementById("image") as HTMLImageElement;
|
const image = document.getElementById("image") as HTMLImageElement;
|
||||||
|
image.style.visibility = "hidden";
|
||||||
image.src = getImageData(clozeNote.imageData!);
|
image.src = getImageData(clozeNote.imageData!);
|
||||||
image.onload = function() {
|
image.onload = function() {
|
||||||
const size = optimumCssSizeForCanvas({ width: image.width, height: image.height }, containerSize());
|
const size = optimumCssSizeForCanvas({ width: image.width, height: image.height }, containerSize());
|
||||||
|
@ -66,6 +67,9 @@ export const setupMaskEditorForEdit = async (noteId: number, instance: PanZoom):
|
||||||
addShapesToCanvasFromCloze(canvas, clozeNote.occlusions);
|
addShapesToCanvasFromCloze(canvas, clozeNote.occlusions);
|
||||||
enableSelectable(canvas, true);
|
enableSelectable(canvas, true);
|
||||||
addClozeNotesToTextEditor(clozeNote.header, clozeNote.backExtra, clozeNote.tags);
|
addClozeNotesToTextEditor(clozeNote.header, clozeNote.backExtra, clozeNote.tags);
|
||||||
|
window.requestAnimationFrame(() => {
|
||||||
|
image.style.visibility = "visible";
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return canvas;
|
return canvas;
|
||||||
|
|
|
@ -6,5 +6,8 @@
|
||||||
* for up to widths/heights of 10kpx.
|
* for up to widths/heights of 10kpx.
|
||||||
*/
|
*/
|
||||||
export function floatToDisplay(number: number): string {
|
export function floatToDisplay(number: number): string {
|
||||||
|
if (Number.isNaN(number) || number == 0) {
|
||||||
|
return ".0000";
|
||||||
|
}
|
||||||
return number.toFixed(4).replace(/^0+|0+$/g, "");
|
return number.toFixed(4).replace(/^0+|0+$/g, "");
|
||||||
}
|
}
|
||||||
|
|
|
@ -108,8 +108,8 @@ function extractShapeFromRenderedCloze(cloze: HTMLDivElement): Shape | null {
|
||||||
type ShapeType = "rect" | "ellipse" | "polygon";
|
type ShapeType = "rect" | "ellipse" | "polygon";
|
||||||
|
|
||||||
function buildShape(type: ShapeType, props: Record<string, any>): Shape {
|
function buildShape(type: ShapeType, props: Record<string, any>): Shape {
|
||||||
props.left = parseFloat(props.left);
|
props.left = parseFloat(Number.isNaN(Number(props.left)) ? ".0000" : props.left);
|
||||||
props.top = parseFloat(props.top);
|
props.top = parseFloat(Number.isNaN(Number(props.top)) ? ".0000" : props.top);
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "rect": {
|
case "rect": {
|
||||||
return new Rectangle({ ...props, width: parseFloat(props.width), height: parseFloat(props.height) });
|
return new Rectangle({ ...props, width: parseFloat(props.width), height: parseFloat(props.height) });
|
||||||
|
|
|
@ -81,6 +81,9 @@ function fabricObjectToBaseShapeOrShapes(
|
||||||
function shapeOrShapesToCloze(shapeOrShapes: ShapeOrShapes, index: number): string {
|
function shapeOrShapesToCloze(shapeOrShapes: ShapeOrShapes, index: number): string {
|
||||||
let text = "";
|
let text = "";
|
||||||
function addKeyValue(key: string, value: string) {
|
function addKeyValue(key: string, value: string) {
|
||||||
|
if (Number.isNaN(Number(value))) {
|
||||||
|
value = ".0000";
|
||||||
|
}
|
||||||
text += `:${key}=${value}`;
|
text += `:${key}=${value}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,3 +9,5 @@ export const notesDataStore = writable({ id: "", title: "", divValue: "", textar
|
||||||
export const zoomResetValue = writable(1);
|
export const zoomResetValue = writable(1);
|
||||||
// it stores the tags for the note in note editor
|
// it stores the tags for the note in note editor
|
||||||
export const tagsWritable = writable([""]);
|
export const tagsWritable = writable([""]);
|
||||||
|
// it stores the visibility of mask editor
|
||||||
|
export const ioMaskEditorVisible = writable(true);
|
||||||
|
|
Loading…
Reference in a new issue