mirror of
https://github.com/ankitects/anki.git
synced 2025-09-24 16:56:36 -04:00
fix: ensure proper drawing of shape tools after pan/zoom (#3066)
* fix: ensure proper drawing of shape tools after pan/zoom * remove sticky bottom toolbar * make bounding-box not selectable after undo/redo * fix: resize issue, added option to pan using alt/shift + mouse wheel * drag with touchpad * use isDesktop and move globalThis to index.ts * gesture event not required, use preventDefault with passive false in wheel event * use shift in mac and ctrl in pc
This commit is contained in:
parent
6ff42155ad
commit
5eafd82521
7 changed files with 78 additions and 50 deletions
|
@ -6,19 +6,13 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
import * as tr from "@tslib/ftl";
|
||||
|
||||
import Container from "../components/Container.svelte";
|
||||
import { addOrUpdateNote } from "./add-or-update-note";
|
||||
import type { IOMode } from "./lib";
|
||||
import MasksEditor from "./MaskEditor.svelte";
|
||||
import Notes from "./Notes.svelte";
|
||||
import StickyFooter from "./StickyFooter.svelte";
|
||||
import { hideAllGuessOne, textEditingState } from "./store";
|
||||
import { textEditingState } from "./store";
|
||||
|
||||
export let mode: IOMode;
|
||||
|
||||
async function addNote(): Promise<void> {
|
||||
addOrUpdateNote(mode, $hideAllGuessOne);
|
||||
}
|
||||
|
||||
const items = [
|
||||
{ label: tr.notetypesOcclusionMask(), value: 1 },
|
||||
{ label: tr.notetypesOcclusionNote(), value: 2 },
|
||||
|
@ -51,8 +45,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
<div hidden={activeTabValue != 2}>
|
||||
<Notes />
|
||||
</div>
|
||||
|
||||
<StickyFooter {mode} {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 { directionKey } from "@tslib/context-keys";
|
||||
import * as tr from "@tslib/ftl";
|
||||
import { isApplePlatform } from "@tslib/platform";
|
||||
import { getPlatformString } from "@tslib/shortcuts";
|
||||
import DropdownItem from "components/DropdownItem.svelte";
|
||||
import IconButton from "components/IconButton.svelte";
|
||||
|
@ -31,7 +32,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
import { drawCursor } from "./tools/tool-cursor";
|
||||
import { removeUnfinishedPolygon } from "./tools/tool-polygon";
|
||||
import { undoRedoTools, undoStack } from "./tools/tool-undo-redo";
|
||||
import { disableZoom, enableZoom } from "./tools/tool-zoom";
|
||||
import { disableZoom, enableZoom, onWheelDrag } from "./tools/tool-zoom";
|
||||
|
||||
export let canvas;
|
||||
export let iconSize;
|
||||
|
@ -54,6 +55,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
let dbclicked = false;
|
||||
let move = false;
|
||||
let wheel = false;
|
||||
const controlKey = isApplePlatform() ? "Shift" : "Control";
|
||||
|
||||
onMount(() => {
|
||||
window.addEventListener("mousedown", (event) => {
|
||||
|
@ -82,7 +84,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
}
|
||||
});
|
||||
window.addEventListener("keyup", (event) => {
|
||||
if (event.key == "Control") {
|
||||
if (event.key === controlKey) {
|
||||
clicked = false;
|
||||
move = false;
|
||||
wheel = false;
|
||||
|
@ -90,34 +92,33 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
}
|
||||
});
|
||||
window.addEventListener("keydown", (event) => {
|
||||
if (event.key == "Control") {
|
||||
clicked = false;
|
||||
move = false;
|
||||
wheel = false;
|
||||
dbclicked = false;
|
||||
}
|
||||
});
|
||||
window.addEventListener("keydown", (event) => {
|
||||
if (event.key == "Control" && activeTool != "magnify") {
|
||||
if (event.key === controlKey) {
|
||||
stopDraw(canvas);
|
||||
enableZoom(canvas);
|
||||
}
|
||||
});
|
||||
window.addEventListener("keyup", (event) => {
|
||||
if (event.key == "Control" && activeTool != "magnify") {
|
||||
if (event.key === controlKey) {
|
||||
disableFunctions();
|
||||
handleToolChanges(activeTool);
|
||||
}
|
||||
});
|
||||
window.addEventListener("wheel", () => {
|
||||
if (clicked && move && wheel && !dbclicked) {
|
||||
stopDraw(canvas);
|
||||
enableZoom(canvas);
|
||||
}
|
||||
});
|
||||
window.addEventListener(
|
||||
"wheel",
|
||||
(event) => {
|
||||
event.preventDefault();
|
||||
|
||||
if (clicked && move && wheel && !dbclicked) {
|
||||
stopDraw(canvas);
|
||||
enableZoom(canvas);
|
||||
}
|
||||
onWheelDrag(canvas, event);
|
||||
},
|
||||
{ passive: false },
|
||||
);
|
||||
});
|
||||
|
||||
// handle tool changes after initialization
|
||||
$: if (canvas) {
|
||||
const handleToolChanges = (activeTool: string) => {
|
||||
disableFunctions();
|
||||
enableSelectable(canvas, true);
|
||||
// remove unfinished polygon when switching to other tools
|
||||
|
@ -127,10 +128,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
case "cursor":
|
||||
drawCursor(canvas);
|
||||
break;
|
||||
case "magnify":
|
||||
enableZoom(canvas);
|
||||
enableSelectable(canvas, false);
|
||||
break;
|
||||
case "draw-rectangle":
|
||||
drawRectangle(canvas);
|
||||
break;
|
||||
|
@ -146,6 +143,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
// handle tool changes after initialization
|
||||
$: if (canvas) {
|
||||
handleToolChanges(activeTool);
|
||||
}
|
||||
|
||||
const disableFunctions = () => {
|
||||
|
|
|
@ -4,10 +4,15 @@
|
|||
import "./image-occlusion-base.scss";
|
||||
|
||||
import { ModuleName, setupI18n } from "@tslib/i18n";
|
||||
import { get } from "svelte/store";
|
||||
|
||||
import { checkNightMode } from "../lib/nightmode";
|
||||
import { addOrUpdateNote } from "./add-or-update-note";
|
||||
import ImageOcclusionPage from "./ImageOcclusionPage.svelte";
|
||||
import type { IOMode } from "./lib";
|
||||
import { hideAllGuessOne } from "./store";
|
||||
|
||||
globalThis.anki = globalThis.anki || {};
|
||||
|
||||
const i18n = setupI18n({
|
||||
modules: [
|
||||
|
@ -25,6 +30,16 @@ export async function setupImageOcclusion(mode: IOMode, target = document.body):
|
|||
checkNightMode();
|
||||
await i18n;
|
||||
|
||||
async function addNote(): Promise<void> {
|
||||
addOrUpdateNote(mode, get(hideAllGuessOne));
|
||||
}
|
||||
|
||||
// for adding note from mobile devices
|
||||
globalThis.anki.imageOcclusion = {
|
||||
mode,
|
||||
addNote,
|
||||
};
|
||||
|
||||
return new ImageOcclusionPage({
|
||||
target: target,
|
||||
props: {
|
||||
|
|
|
@ -128,7 +128,6 @@ const setupBoundingBox = (canvas: fabric.Canvas, size: Size): fabric.Rect => {
|
|||
lockMovementY: true,
|
||||
selectable: false,
|
||||
evented: false,
|
||||
stroke: "red",
|
||||
});
|
||||
|
||||
canvas.add(boundingBox);
|
||||
|
|
|
@ -291,6 +291,8 @@ export const selectAllShapes = (canvas: fabric.Canvas) => {
|
|||
|
||||
export const isPointerInBoundingBox = (pointer): boolean => {
|
||||
const boundingBox = getBoundingBox();
|
||||
boundingBox.selectable = false;
|
||||
boundingBox.evented = false;
|
||||
if (
|
||||
pointer.x < boundingBox.left
|
||||
|| pointer.x > boundingBox.left + boundingBox.width
|
||||
|
|
|
@ -6,7 +6,6 @@ import * as tr from "@tslib/ftl";
|
|||
import {
|
||||
mdiCursorDefaultOutline,
|
||||
mdiEllipseOutline,
|
||||
mdiMagnifyScan,
|
||||
mdiRectangleOutline,
|
||||
mdiTextBox,
|
||||
mdiVectorPolygonVariant,
|
||||
|
@ -14,7 +13,6 @@ import {
|
|||
import {
|
||||
cursorKeyCombination,
|
||||
ellipseKeyCombination,
|
||||
magnifyKeyCombination,
|
||||
polygonKeyCombination,
|
||||
rectangleKeyCombination,
|
||||
textKeyCombination,
|
||||
|
@ -27,12 +25,6 @@ export const tools = [
|
|||
tooltip: tr.editingImageOcclusionSelectTool,
|
||||
shortcut: cursorKeyCombination,
|
||||
},
|
||||
{
|
||||
id: "magnify",
|
||||
icon: mdiMagnifyScan,
|
||||
tooltip: tr.editingImageOcclusionZoomTool,
|
||||
shortcut: magnifyKeyCombination,
|
||||
},
|
||||
{
|
||||
id: "draw-rectangle",
|
||||
icon: mdiRectangleOutline,
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
// https://codepen.io/amsunny/pen/XWGLxye
|
||||
// canvas.viewportTransform = [ scaleX, skewX, skewY, scaleY, translateX, translateY ]
|
||||
|
||||
import { isDesktop } from "@tslib/platform";
|
||||
import type { fabric } from "fabric";
|
||||
import Hammer from "hammerjs";
|
||||
|
||||
|
@ -47,9 +48,15 @@ export const zoomOut = (canvas): void => {
|
|||
};
|
||||
|
||||
export const zoomReset = (canvas: fabric.Canvas): void => {
|
||||
canvas.zoomToPoint({ x: canvas.width / 2, y: canvas.height / 2 }, 1);
|
||||
zoomResetInner(canvas);
|
||||
// reset again to update the viewportTransform
|
||||
zoomResetInner(canvas);
|
||||
};
|
||||
|
||||
const zoomResetInner = (canvas: fabric.Canvas): void => {
|
||||
fitCanvasVptScale(canvas);
|
||||
constrainBoundsAroundBgImage(canvas);
|
||||
const vpt = canvas.viewportTransform;
|
||||
canvas.zoomToPoint({ x: canvas.width / 2, y: canvas.height / 2 }, vpt[0]);
|
||||
};
|
||||
|
||||
export const enablePinchZoom = (canvas: fabric.Canvas) => {
|
||||
|
@ -74,8 +81,7 @@ export const disablePinchZoom = (canvas: fabric.Canvas) => {
|
|||
|
||||
export const onResize = (canvas: fabric.Canvas) => {
|
||||
setCanvasSize(canvas);
|
||||
constrainBoundsAroundBgImage(canvas);
|
||||
fitCanvasVptScale(canvas);
|
||||
zoomReset(canvas);
|
||||
};
|
||||
|
||||
const onMouseWheel = (opt) => {
|
||||
|
@ -151,6 +157,23 @@ const onDrag = (canvas, opt) => {
|
|||
redraw(canvas);
|
||||
};
|
||||
|
||||
export const onWheelDrag = (canvas: fabric.Canvas, event: WheelEvent) => {
|
||||
const deltaX = event.deltaX;
|
||||
const deltaY = event.deltaY;
|
||||
const vpt = canvas.viewportTransform;
|
||||
canvas.lastPosX = event.clientX;
|
||||
canvas.lastPosY = event.clientY;
|
||||
|
||||
vpt[4] -= deltaX;
|
||||
vpt[5] -= deltaY;
|
||||
|
||||
canvas.lastPosX -= deltaX;
|
||||
canvas.lastPosY -= deltaY;
|
||||
canvas.setViewportTransform(vpt);
|
||||
constrainBoundsAroundBgImage(canvas);
|
||||
redraw(canvas);
|
||||
};
|
||||
|
||||
const onMouseUp = () => {
|
||||
isDragging = false;
|
||||
const canvas = globalThis.canvas;
|
||||
|
@ -176,8 +199,11 @@ export const constrainBoundsAroundBgImage = (canvas: fabric.Canvas) => {
|
|||
};
|
||||
|
||||
export const setCanvasSize = (canvas: fabric.Canvas) => {
|
||||
canvas.setHeight(window.innerHeight - 76);
|
||||
canvas.setWidth(window.innerWidth - 39);
|
||||
const width = window.innerWidth - 39;
|
||||
let height = window.innerHeight;
|
||||
height = isDesktop() ? height - 76 : height - 46;
|
||||
canvas.setHeight(height);
|
||||
canvas.setWidth(width);
|
||||
redraw(canvas);
|
||||
};
|
||||
|
||||
|
@ -205,8 +231,8 @@ const fitCanvasVptScale = (canvas: fabric.Canvas) => {
|
|||
const getScaleRatio = (boundingBox: fabric.Rect) => {
|
||||
const h1 = boundingBox.height;
|
||||
const w1 = boundingBox.width;
|
||||
const h2 = innerHeight - 79;
|
||||
const w2 = innerWidth - 42;
|
||||
|
||||
let h2 = window.innerHeight;
|
||||
h2 = isDesktop() ? h2 - 79 : h2 - 48;
|
||||
return Math.min(w2 / w1, h2 / h1);
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue