mirror of
https://github.com/ankitects/anki.git
synced 2025-09-19 06:22:22 -04:00
follow-up pr for io button in note editor (#2600)
* follow-up pr for io button in note editor * Expose change event in Svelte instead of relying on timeout (dae)
This commit is contained in:
parent
a35c1a058d
commit
b9da61f993
10 changed files with 186 additions and 56 deletions
|
@ -115,8 +115,8 @@ class AddCards(QMainWindow):
|
|||
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 = bb.addButton(f"{tr.actions_add()}", ar)
|
||||
qconnect(self.io_add_button.clicked, self.add_io_note)
|
||||
self.io_add_button.setShortcut(QKeySequence("Ctrl+Shift+I"))
|
||||
|
||||
# close
|
||||
|
@ -372,21 +372,8 @@ class AddCards(QMainWindow):
|
|||
|
||||
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)")
|
||||
def add_io_note(self) -> None:
|
||||
self.editor.web.eval("setOcclusionFieldInner()")
|
||||
self.add_current_note()
|
||||
self.editor.web.eval("resetIOImageLoaded()")
|
||||
|
||||
|
|
|
@ -562,8 +562,8 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
|
|||
self.editorMode != EditorMode.ADD_CARDS
|
||||
and self.current_notetype_is_image_occlusion()
|
||||
):
|
||||
options = {"kind": "edit", "noteId": self.note.id}
|
||||
options = {"mode": options}
|
||||
mode = {"kind": "edit", "noteId": self.note.id}
|
||||
options = {"mode": mode}
|
||||
js += " setupMaskEditor(%s);" % json.dumps(options)
|
||||
|
||||
js = gui_hooks.editor_will_load_note(js, self.note, self)
|
||||
|
@ -1202,10 +1202,16 @@ require("anki/ui").loaded.then(() => require("anki/NoteEditor").instances[0].too
|
|||
def accept(file: str) -> None:
|
||||
try:
|
||||
html = self._addMedia(file)
|
||||
if self.editorMode == EditorMode.ADD_CARDS:
|
||||
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)})")
|
||||
else:
|
||||
mode = {"kind": "edit", "notetypeId": 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
|
||||
|
|
|
@ -298,10 +298,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
}
|
||||
|
||||
function saveNow(): void {
|
||||
updateIONoteInEditMode();
|
||||
closeMathjaxEditor?.();
|
||||
$commitTagEdits();
|
||||
saveFieldNow();
|
||||
imageOcclusionMode = undefined;
|
||||
}
|
||||
|
||||
export function saveOnPageHide() {
|
||||
|
@ -390,7 +390,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
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 { hideAllGuessOne, ioMaskEditorVisible } from "image-occlusion/store";
|
||||
|
||||
import { mathjaxConfig } from "../editable/mathjax-element";
|
||||
import CollapseLabel from "./CollapseLabel.svelte";
|
||||
|
@ -402,10 +402,23 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
imageOcclusionMode = options.mode;
|
||||
if (options.mode.kind === "add") {
|
||||
fieldStores[1].set(options.html);
|
||||
} else {
|
||||
const clozeNote = get(fieldStores[0]);
|
||||
if (clozeNote.includes("oi=1")) {
|
||||
$hideAllGuessOne = true;
|
||||
} else {
|
||||
$hideAllGuessOne = false;
|
||||
}
|
||||
}
|
||||
|
||||
isIOImageLoaded = true;
|
||||
}
|
||||
|
||||
function setImageField(html) {
|
||||
fieldStores[1].set(html);
|
||||
}
|
||||
globalThis.setImageField = setImageField;
|
||||
|
||||
// update cloze deletions and set occlusion fields, it call in saveNow to update cloze deletions
|
||||
function updateIONoteInEditMode() {
|
||||
if (isEditMode) {
|
||||
|
@ -418,6 +431,15 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
}
|
||||
}
|
||||
|
||||
function setOcclusionFieldInner() {
|
||||
if (isImageOcclusion) {
|
||||
const occlusionsData = exportShapesToClozeDeletions($hideAllGuessOne);
|
||||
fieldStores[0].set(occlusionsData.clozes);
|
||||
}
|
||||
}
|
||||
// global for calling this method in desktop note editor
|
||||
globalThis.setOcclusionFieldInner = setOcclusionFieldInner;
|
||||
|
||||
// reset for new occlusion in add mode
|
||||
function resetIOImageLoaded() {
|
||||
isIOImageLoaded = false;
|
||||
|
@ -484,6 +506,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
setIsEditMode,
|
||||
setupMaskEditor,
|
||||
setOcclusionField,
|
||||
setOcclusionFieldInner,
|
||||
...oldEditorAdapter,
|
||||
});
|
||||
|
||||
|
@ -539,7 +562,10 @@ the AddCards dialog) should be implemented in the user of this component.
|
|||
|
||||
{#if imageOcclusionMode}
|
||||
<div style="display: {$ioMaskEditorVisible ? 'block' : 'none'}">
|
||||
<ImageOcclusionPage mode={imageOcclusionMode} />
|
||||
<ImageOcclusionPage
|
||||
mode={imageOcclusionMode}
|
||||
on:change={updateIONoteInEditMode}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
|
|
@ -11,15 +11,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
import MasksEditor from "./MaskEditor.svelte";
|
||||
import Notes from "./Notes.svelte";
|
||||
import StickyFooter from "./StickyFooter.svelte";
|
||||
import { hideAllGuessOne } from "./store";
|
||||
|
||||
export let mode: IOMode;
|
||||
|
||||
async function hideAllGuessOne(): Promise<void> {
|
||||
addOrUpdateNote(mode, true);
|
||||
}
|
||||
|
||||
async function hideOneGuessOne(): Promise<void> {
|
||||
addOrUpdateNote(mode, false);
|
||||
async function addNote(): Promise<void> {
|
||||
addOrUpdateNote(mode, $hideAllGuessOne);
|
||||
}
|
||||
|
||||
const items = [
|
||||
|
@ -45,14 +42,14 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
</div>
|
||||
|
||||
<div hidden={activeTabValue != 1}>
|
||||
<MasksEditor {mode} />
|
||||
<MasksEditor {mode} on:change />
|
||||
</div>
|
||||
|
||||
<div hidden={activeTabValue != 2}>
|
||||
<Notes />
|
||||
</div>
|
||||
|
||||
<StickyFooter {hideAllGuessOne} {hideOneGuessOne} />
|
||||
<StickyFooter {addNote} />
|
||||
</Container>
|
||||
|
||||
<style lang="scss">
|
||||
|
|
|
@ -5,6 +5,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
<script lang="ts">
|
||||
import type { PanZoom } from "panzoom";
|
||||
import panzoom from "panzoom";
|
||||
import { createEventDispatcher } from "svelte";
|
||||
|
||||
import type { IOMode } from "./lib";
|
||||
import { setupMaskEditor, setupMaskEditorForEdit } from "./mask-editor";
|
||||
|
@ -17,6 +18,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
const startingTool = mode.kind === "add" ? "draw-rectangle" : "cursor";
|
||||
$: canvas = null;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
function onChange() {
|
||||
dispatch("change", { canvas });
|
||||
}
|
||||
|
||||
function init(node) {
|
||||
instance = panzoom(node, {
|
||||
bounds: true,
|
||||
|
@ -28,11 +35,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
instance.pause();
|
||||
|
||||
if (mode.kind == "add") {
|
||||
setupMaskEditor(mode.imagePath, instance).then((canvas1) => {
|
||||
setupMaskEditor(mode.imagePath, instance, onChange).then((canvas1) => {
|
||||
canvas = canvas1;
|
||||
});
|
||||
} else {
|
||||
setupMaskEditorForEdit(mode.noteId, instance).then((canvas1) => {
|
||||
setupMaskEditorForEdit(mode.noteId, instance, onChange).then((canvas1) => {
|
||||
canvas = canvas1;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -8,8 +8,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
import ButtonGroup from "../components/ButtonGroup.svelte";
|
||||
import LabelButton from "../components/LabelButton.svelte";
|
||||
|
||||
export let hideAllGuessOne: () => void;
|
||||
export let hideOneGuessOne: () => void;
|
||||
export let addNote: () => void;
|
||||
</script>
|
||||
|
||||
<div style:flex-grow="1" />
|
||||
|
@ -18,18 +17,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
<LabelButton
|
||||
--border-left-radius="5px"
|
||||
--border-right-radius="5px"
|
||||
on:click={hideAllGuessOne}
|
||||
on:click={addNote}
|
||||
class=" bottom-btn"
|
||||
>
|
||||
{tr.notetypesHideAllGuessOne()}
|
||||
</LabelButton>
|
||||
<LabelButton
|
||||
--border-left-radius="5px"
|
||||
--border-right-radius="5px"
|
||||
on:click={hideOneGuessOne}
|
||||
class=" bottom-btn"
|
||||
>
|
||||
{tr.notetypesHideOneGuessOne()}
|
||||
{tr.actionsAdd()}
|
||||
</LabelButton>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
|
|
|
@ -3,8 +3,23 @@ Copyright: Ankitects Pty Ltd and contributors
|
|||
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
-->
|
||||
<script>
|
||||
import IconButton from "../components/IconButton.svelte";
|
||||
import { mdiEye, mdiFormatAlignCenter } from "./icons";
|
||||
import { bridgeCommand } from "@tslib/bridgecommand";
|
||||
import * as tr from "@tslib/ftl";
|
||||
import DropdownItem from "components/DropdownItem.svelte";
|
||||
import IconButton from "components/IconButton.svelte";
|
||||
import Popover from "components/Popover.svelte";
|
||||
import WithFloating from "components/WithFloating.svelte";
|
||||
|
||||
import {
|
||||
mdiChevronDown,
|
||||
mdiEye,
|
||||
mdiFormatAlignCenter,
|
||||
mdiRefresh,
|
||||
mdiSquare,
|
||||
mdiViewDashboard,
|
||||
} from "./icons";
|
||||
import { setupMaskEditor } from "./mask-editor";
|
||||
import { hideAllGuessOne } from "./store";
|
||||
import { drawEllipse, drawPolygon, drawRectangle } from "./tools/index";
|
||||
import { makeMaskTransparent } from "./tools/lib";
|
||||
import { enableSelectable, stopDraw } from "./tools/lib";
|
||||
|
@ -24,6 +39,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
let showAlignTools = false;
|
||||
let leftPos = 82;
|
||||
let maksOpacity = false;
|
||||
let showFloating = false;
|
||||
|
||||
document.addEventListener("click", (event) => {
|
||||
const upperCanvas = document.querySelector(".upper-canvas");
|
||||
|
@ -61,6 +77,22 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
stopDraw(canvas);
|
||||
canvas.selectionColor = "rgba(100, 100, 255, 0.3)";
|
||||
};
|
||||
|
||||
const resetIOImage = (path) => {
|
||||
setupMaskEditor(path, instance);
|
||||
};
|
||||
globalThis.resetIOImage = resetIOImage;
|
||||
|
||||
const setOcclusionFieldForDesktop = () => {
|
||||
const clist = document.body.classList;
|
||||
if (
|
||||
clist.contains("isLin") ||
|
||||
clist.contains("isMac") ||
|
||||
clist.contains("isWin")
|
||||
) {
|
||||
globalThis.setOcclusionFieldInner();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="tool-bar-container">
|
||||
|
@ -79,6 +111,65 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
</div>
|
||||
|
||||
<div class="top-tool-bar-container">
|
||||
<div class="undo-redo-button" on:click={() => (showFloating = !showFloating)}>
|
||||
{#if $hideAllGuessOne}
|
||||
<IconButton class="top-tool-icon-button left-border-radius" {iconSize}>
|
||||
{@html mdiViewDashboard}
|
||||
</IconButton>
|
||||
{:else}
|
||||
<IconButton class="top-tool-icon-button left-border-radius" {iconSize}>
|
||||
{@html mdiSquare}
|
||||
</IconButton>
|
||||
{/if}
|
||||
|
||||
<WithFloating
|
||||
show={showFloating}
|
||||
closeOnInsideClick
|
||||
inline
|
||||
style="line-height: unset !important"
|
||||
on:close={() => (showFloating = false)}
|
||||
>
|
||||
<IconButton
|
||||
class="top-tool-icon-button right-border-radius dropdown-tool-mode"
|
||||
slot="reference"
|
||||
>
|
||||
{@html mdiChevronDown}
|
||||
</IconButton>
|
||||
|
||||
<Popover slot="floating" --popover-padding-inline="0">
|
||||
<DropdownItem
|
||||
on:click={() => {
|
||||
$hideAllGuessOne = true;
|
||||
setOcclusionFieldForDesktop();
|
||||
}}
|
||||
>
|
||||
<span>{tr.notetypesHideAllGuessOne()}</span>
|
||||
</DropdownItem>
|
||||
<DropdownItem
|
||||
on:click={() => {
|
||||
$hideAllGuessOne = false;
|
||||
setOcclusionFieldForDesktop();
|
||||
}}
|
||||
>
|
||||
<span>{tr.notetypesHideOneGuessOne()}</span>
|
||||
</DropdownItem>
|
||||
</Popover>
|
||||
</WithFloating>
|
||||
</div>
|
||||
|
||||
<!-- refresh for changing image -->
|
||||
<div class="undo-redo-button">
|
||||
<IconButton
|
||||
class="top-tool-icon-button icon-border-radius"
|
||||
{iconSize}
|
||||
on:click={() => {
|
||||
bridgeCommand("addImageForOcclusion");
|
||||
}}
|
||||
>
|
||||
{@html mdiRefresh}
|
||||
</IconButton>
|
||||
</div>
|
||||
|
||||
<!-- undo & redo tools -->
|
||||
<div class="undo-redo-button">
|
||||
{#each undoRedoTools as tool}
|
||||
|
@ -266,4 +357,13 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
color: white !important;
|
||||
background: var(--button-primary-bg) !important;
|
||||
}
|
||||
|
||||
:global(.icon-border-radius) {
|
||||
border-radius: 5px !important;
|
||||
}
|
||||
|
||||
:global(.dropdown-tool-mode) {
|
||||
height: 38px !important;
|
||||
display: inline;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -9,6 +9,7 @@ export { default as mdiAlignHorizontalRight } from "@mdi/svg/svg/align-horizonta
|
|||
export { default as mdiAlignVerticalBottom } from "@mdi/svg/svg/align-vertical-bottom.svg";
|
||||
export { default as mdiAlignVerticalCenter } from "@mdi/svg/svg/align-vertical-center.svg";
|
||||
export { default as mdiAlignVerticalTop } from "@mdi/svg/svg/align-vertical-top.svg";
|
||||
export { default as mdiChevronDown } from "@mdi/svg/svg/chevron-down.svg";
|
||||
export { default as mdiClose } from "@mdi/svg/svg/close.svg";
|
||||
export { default as mdiCodeTags } from "@mdi/svg/svg/code-tags.svg";
|
||||
export { default as mdiCopy } from "@mdi/svg/svg/content-copy.svg";
|
||||
|
@ -27,7 +28,10 @@ export { default as mdiZoomIn } from "@mdi/svg/svg/magnify-plus-outline.svg";
|
|||
export { default as mdiMagnifyScan } from "@mdi/svg/svg/magnify-scan.svg";
|
||||
export { default as mdiRectangleOutline } from "@mdi/svg/svg/rectangle-outline.svg";
|
||||
export { default as mdiRedo } from "@mdi/svg/svg/redo.svg";
|
||||
export { default as mdiRefresh } from "@mdi/svg/svg/refresh.svg";
|
||||
export { default as mdiSquare } from "@mdi/svg/svg/square.svg";
|
||||
export { default as mdiUndo } from "@mdi/svg/svg/undo.svg";
|
||||
export { default as mdiUnfoldMoreHorizontal } from "@mdi/svg/svg/unfold-more-horizontal.svg";
|
||||
export { default as mdiUngroup } from "@mdi/svg/svg/ungroup.svg";
|
||||
export { default as mdiVectorPolygonVariant } from "@mdi/svg/svg/vector-polygon-variant.svg";
|
||||
export { default as mdiViewDashboard } from "@mdi/svg/svg/view-dashboard.svg";
|
||||
|
|
|
@ -16,9 +16,13 @@ import { enableSelectable, moveShapeToCanvasBoundaries } from "./tools/lib";
|
|||
import { undoRedoInit } from "./tools/tool-undo-redo";
|
||||
import type { Size } from "./types";
|
||||
|
||||
export const setupMaskEditor = async (path: string, instance: PanZoom): Promise<fabric.Canvas> => {
|
||||
export const setupMaskEditor = async (
|
||||
path: string,
|
||||
instance: PanZoom,
|
||||
onChange: () => void,
|
||||
): Promise<fabric.Canvas> => {
|
||||
const imageData = await getImageForOcclusion({ path });
|
||||
const canvas = initCanvas();
|
||||
const canvas = initCanvas(onChange);
|
||||
|
||||
// get image width and height
|
||||
const image = document.getElementById("image") as HTMLImageElement;
|
||||
|
@ -35,7 +39,11 @@ export const setupMaskEditor = async (path: string, instance: PanZoom): Promise<
|
|||
return canvas;
|
||||
};
|
||||
|
||||
export const setupMaskEditorForEdit = async (noteId: number, instance: PanZoom): Promise<fabric.Canvas> => {
|
||||
export const setupMaskEditorForEdit = async (
|
||||
noteId: number,
|
||||
instance: PanZoom,
|
||||
onChange: () => void,
|
||||
): Promise<fabric.Canvas> => {
|
||||
const clozeNoteResponse = await getImageOcclusionNote({ noteId: BigInt(noteId) });
|
||||
const kind = clozeNoteResponse.value?.case;
|
||||
if (!kind || kind === "error") {
|
||||
|
@ -50,7 +58,7 @@ export const setupMaskEditorForEdit = async (noteId: number, instance: PanZoom):
|
|||
}
|
||||
|
||||
const clozeNote = clozeNoteResponse.value.value;
|
||||
const canvas = initCanvas();
|
||||
const canvas = initCanvas(onChange);
|
||||
|
||||
// get image width and height
|
||||
const image = document.getElementById("image") as HTMLImageElement;
|
||||
|
@ -75,7 +83,7 @@ export const setupMaskEditorForEdit = async (noteId: number, instance: PanZoom):
|
|||
return canvas;
|
||||
};
|
||||
|
||||
const initCanvas = (): fabric.Canvas => {
|
||||
function initCanvas(onChange: () => void): fabric.Canvas {
|
||||
const canvas = new fabric.Canvas("canvas");
|
||||
tagsWritable.set([]);
|
||||
globalThis.canvas = canvas;
|
||||
|
@ -84,8 +92,10 @@ const initCanvas = (): fabric.Canvas => {
|
|||
canvas.uniScaleKey = "none";
|
||||
moveShapeToCanvasBoundaries(canvas);
|
||||
undoRedoInit(canvas);
|
||||
canvas.on("object:modified", onChange);
|
||||
canvas.on("object:removed", onChange);
|
||||
return canvas;
|
||||
};
|
||||
}
|
||||
|
||||
const getImageData = (imageData): string => {
|
||||
const b64encoded = protoBase64.enc(imageData);
|
||||
|
|
|
@ -11,3 +11,5 @@ export const zoomResetValue = writable(1);
|
|||
export const tagsWritable = writable([""]);
|
||||
// it stores the visibility of mask editor
|
||||
export const ioMaskEditorVisible = writable(true);
|
||||
// it store hide all or hide one mode
|
||||
export const hideAllGuessOne = writable(true);
|
||||
|
|
Loading…
Reference in a new issue