From be1f889211b4cee5328298c3bf90db7ef809bd50 Mon Sep 17 00:00:00 2001 From: Mani <12841290+krmanik@users.noreply.github.com> Date: Fri, 24 Nov 2023 12:06:40 +0800 Subject: [PATCH] fixes: remove unfinished shapes, remove selectable and make shapes remain inside canvas (#2809) * remove unfinished polygon and remove selectable for shapes in polygon mode * make group and polygon position remain inside canvas area * click through transparent area in grouped object * add some shortcuts for basic usages * tools button icon in center & switch mode border * fix load svg image * basic rtl support, panzoom have issues in rtl mode * better zoom option both in ltr and rtl * handle zoom event in mask editor * add h button to handle toggle mask * add more mime type * use capital M (shift+m) for toggle mask * allow io shortcuts in mask editor only * make other shapes also remain in canvas bound area * better zoom implementation, zoom from center add zoom to resize event listener * add a border to corner to handle blend of control * add refresh button to go to selection menu * add tooltip to shortcuts and also add shortcut for other tools * make opacity remain in same state when toggled on * opacity for group/ungroup objects * update shortcuts implementation --- ftl/core/editing.ftl | 2 + proto/anki/image_occlusion.proto | 1 + rslib/src/image_occlusion/imagedata.rs | 6 + ts/editor/NoteEditor.svelte | 11 +- .../ImageOcclusionButton.svelte | 20 +- ts/editor/editor-toolbar/icons.ts | 1 + ts/image-occlusion/MaskEditor.svelte | 11 + ts/image-occlusion/Toolbar.svelte | 401 ++++++++++++------ ts/image-occlusion/mask-editor.ts | 40 +- ts/image-occlusion/review.ts | 6 + ts/image-occlusion/shapes/to-cloze.ts | 2 - ts/image-occlusion/store.ts | 6 + ts/image-occlusion/tools/lib.ts | 71 +++- ts/image-occlusion/tools/more-tools.ts | 28 ++ ts/image-occlusion/tools/shortcuts.ts | 25 ++ ts/image-occlusion/tools/tool-buttons.ts | 14 + ts/image-occlusion/tools/tool-ellipse.ts | 17 +- ts/image-occlusion/tools/tool-polygon.ts | 26 ++ ts/image-occlusion/tools/tool-rect.ts | 21 +- ts/image-occlusion/tools/tool-text.ts | 3 + ts/image-occlusion/tools/tool-undo-redo.ts | 3 + 21 files changed, 545 insertions(+), 170 deletions(-) create mode 100644 ts/image-occlusion/tools/shortcuts.ts diff --git a/ftl/core/editing.ftl b/ftl/core/editing.ftl index d2c5c46b9..8c33782e0 100644 --- a/ftl/core/editing.ftl +++ b/ftl/core/editing.ftl @@ -93,6 +93,8 @@ editing-image-occlusion-ellipse-tool = Ellipse editing-image-occlusion-polygon-tool = Polygon editing-image-occlusion-text-tool = Text editing-image-occlusion-toggle-mask-editor = Toggle Mask Editor +editing-image-occlusion-reset = Reset Image Occlusion +editing-image-occlusion-confirm-reset = Are you sure you want to reset this image occlusion? ## You don't need to translate these strings, as they will be replaced with different ones soon. diff --git a/proto/anki/image_occlusion.proto b/proto/anki/image_occlusion.proto index fdab4e139..01caf00ca 100644 --- a/proto/anki/image_occlusion.proto +++ b/proto/anki/image_occlusion.proto @@ -74,6 +74,7 @@ message GetImageOcclusionNoteResponse { string header = 3; string back_extra = 4; repeated string tags = 5; + string image_file_name = 6; } oneof value { diff --git a/rslib/src/image_occlusion/imagedata.rs b/rslib/src/image_occlusion/imagedata.rs index 17613344c..1e354c213 100644 --- a/rslib/src/image_occlusion/imagedata.rs +++ b/rslib/src/image_occlusion/imagedata.rs @@ -110,6 +110,12 @@ impl Collection { if self.is_image_file(&final_path)? { cloze_note.image_data = read_file(&final_path)?; + cloze_note.image_file_name = final_path + .file_name() + .or_not_found("expected filename")? + .to_str() + .unwrap() + .to_string(); } Ok(cloze_note) diff --git a/ts/editor/NoteEditor.svelte b/ts/editor/NoteEditor.svelte index 17ba6e437..4e7318fa7 100644 --- a/ts/editor/NoteEditor.svelte +++ b/ts/editor/NoteEditor.svelte @@ -388,13 +388,18 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html import ImageOcclusionPicker from "image-occlusion/ImageOcclusionPicker.svelte"; import type { IOMode } from "image-occlusion/lib"; import { exportShapesToClozeDeletions } from "image-occlusion/shapes/to-cloze"; - import { hideAllGuessOne, ioMaskEditorVisible } from "image-occlusion/store"; + import { + hideAllGuessOne, + ioImageLoadedStore, + ioMaskEditorVisible, + } from "image-occlusion/store"; import { mathjaxConfig } from "../editable/mathjax-element"; import CollapseLabel from "./CollapseLabel.svelte"; import * as oldEditorAdapter from "./old-editor-adapter"; - let isIOImageLoaded = false; + $: isIOImageLoaded = false; + $: ioImageLoadedStore.set(isIOImageLoaded); let imageOcclusionMode: IOMode | undefined; let ioFields = new ImageOcclusionFieldIndexes({}); @@ -456,6 +461,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html function resetIOImageLoaded() { isIOImageLoaded = false; globalThis.canvas.clear(); + globalThis.canvas = undefined; const page = document.querySelector(".image-occlusion"); if (page) { page.remove(); @@ -791,6 +797,7 @@ the AddCards dialog) should be implemented in the user of this component. } :global(.top-tool-bar-container .icon-button) { height: 36px !important; + line-height: 1; } :global(.image-occlusion .tool-bar-container) { top: unset !important; diff --git a/ts/editor/editor-toolbar/ImageOcclusionButton.svelte b/ts/editor/editor-toolbar/ImageOcclusionButton.svelte index 6f018fe91..72f80e89f 100644 --- a/ts/editor/editor-toolbar/ImageOcclusionButton.svelte +++ b/ts/editor/editor-toolbar/ImageOcclusionButton.svelte @@ -7,14 +7,14 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html 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 { ioImageLoadedStore, ioMaskEditorVisible } from "image-occlusion/store"; import ButtonGroupItem, { createProps, setSlotHostContext, updatePropsList, } from "../../components/ButtonGroupItem.svelte"; - import { mdiViewDashboard } from "./icons"; + import { mdiTableRefresh, mdiViewDashboard } from "./icons"; export let api = {}; @@ -39,6 +39,22 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html {@html mdiViewDashboard} + + { + if (confirm(tr.editingImageOcclusionConfirmReset())) { + globalThis.resetIOImageLoaded(); + } else { + return; + } + }} + tooltip={tr.editingImageOcclusionReset()} + > + {@html mdiTableRefresh} + + diff --git a/ts/editor/editor-toolbar/icons.ts b/ts/editor/editor-toolbar/icons.ts index 5c2749818..278c7412f 100644 --- a/ts/editor/editor-toolbar/icons.ts +++ b/ts/editor/editor-toolbar/icons.ts @@ -12,6 +12,7 @@ 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 paperclipIcon } from "@mdi/svg/svg/paperclip.svg"; export { default as mdiRefresh } from "@mdi/svg/svg/refresh.svg"; +export { default as mdiTableRefresh } from "@mdi/svg/svg/table-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 justifyFullIcon } from "bootstrap-icons/icons/justify.svg"; diff --git a/ts/image-occlusion/MaskEditor.svelte b/ts/image-occlusion/MaskEditor.svelte index 02dce95dc..866760a1e 100644 --- a/ts/image-occlusion/MaskEditor.svelte +++ b/ts/image-occlusion/MaskEditor.svelte @@ -26,6 +26,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html } from "./mask-editor"; import Toolbar from "./Toolbar.svelte"; import { MaskEditorAPI } from "./tools/api"; + import { setCenterXForZoom } from "./tools/lib"; export let mode: IOMode; const iconSize = 80; @@ -57,8 +58,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html minZoom: 0.1, zoomDoubleClickSpeed: 1, smoothScroll: false, + transformOrigin: { x: 0.5, y: 0.5 }, }); instance.pause(); + globalThis.panzoom = instance; if (mode.kind == "add") { setupMaskEditor(mode.imagePath, instance, onChange, onImageLoaded).then( @@ -78,12 +81,14 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html onMount(() => { window.addEventListener("resize", () => { setCanvasZoomRatio(canvas, instance); + setCenterXForZoom(canvas); }); }); onDestroy(() => { window.removeEventListener("resize", () => { setCanvasZoomRatio(canvas, instance); + setCenterXForZoom(canvas); }); }); @@ -109,10 +114,16 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html padding-bottom: 100px; } + :global([dir="rtl"]) .editor-main { + left: 2px; + right: 36px; + } + .editor-container { width: 100%; height: 100%; position: relative; + direction: ltr; } #image { diff --git a/ts/image-occlusion/Toolbar.svelte b/ts/image-occlusion/Toolbar.svelte index c945afdcf..7dd086d62 100644 --- a/ts/image-occlusion/Toolbar.svelte +++ b/ts/image-occlusion/Toolbar.svelte @@ -3,15 +3,20 @@ Copyright: Ankitects Pty Ltd and contributors License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -->
@@ -84,7 +165,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html { activeTool = tool.id; @@ -92,157 +173,215 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html > {@html tool.icon} + {#if $ioMaskEditorVisible} + { + activeTool = tool.id; + }} + /> + {/if} {/each}
-
- (showFloating = false)} - > - (showFloating = !showFloating)} +
+
+ (showFloating = false)} > - {@html $hideAllGuessOne ? mdiViewDashboard : mdiSquare} - - - - changeOcclusionType("all")} - > - {tr.notetypesHideAllGuessOne()} - - changeOcclusionType("one")} - > - {tr.notetypesHideOneGuessOne()} - - - - - -
- {#each undoRedoTools as tool} (showFloating = !showFloating)} > - {@html tool.icon} + {@html $hideAllGuessOne ? mdiViewDashboard : mdiSquare} - {/each} -
- -
- {#each zoomTools as tool} + + changeOcclusionType("all")} + > + {tr.notetypesHideAllGuessOne()} + + changeOcclusionType("one")} + > + {tr.notetypesHideOneGuessOne()} + + + + + +
+ {#each undoRedoTools as tool} + + {@html tool.icon} + + {#if $ioMaskEditorVisible} + + {/if} + {/each} +
+ + +
+ {#each zoomTools as tool} + { + tool.action(instance); + }} + > + {@html tool.icon} + + {#if $ioMaskEditorVisible} + { + tool.action(instance); + }} + /> + {/if} + {/each} +
+ +
+ { - tool.action(instance); + maksOpacity = !maksOpacity; + makeMaskTransparent(canvas, maksOpacity); }} > - {@html tool.icon} + {@html mdiEye} - {/each} -
+ {#if $ioMaskEditorVisible} + { + maksOpacity = !maksOpacity; + makeMaskTransparent(canvas, maksOpacity); + }} + /> + {/if} -
- - { - maksOpacity = !maksOpacity; - makeMaskTransparent(canvas, maksOpacity); - }} - > - {@html mdiEye} - + + {#each deleteDuplicateTools as tool} + { + tool.action(canvas); + }} + > + {@html tool.icon} + + {#if $ioMaskEditorVisible} + { + tool.action(canvas); + emitChangeSignal(); + }} + /> + {/if} + {/each} +
+ +
+ + {#each groupUngroupTools as tool} + { + tool.action(canvas); + emitChangeSignal(); + }} + > + {@html tool.icon} + + {#if $ioMaskEditorVisible} + { + tool.action(canvas); + emitChangeSignal(); + }} + /> + {/if} + {/each} - - {#each deleteDuplicateTools as tool} { - tool.action(canvas); + tooltip={tr.editingImageOcclusionAlignment()} + on:click={(e) => { + showAlignTools = !showAlignTools; + leftPos = e.pageX - 100; }} > - {@html tool.icon} + {@html mdiFormatAlignCenter} - {/each} +
-
- - {#each groupUngroupTools as tool} +
- -