mirror of
https://github.com/ankitects/anki.git
synced 2025-09-25 09:16:38 -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 * as tr from "@tslib/ftl";
|
||||||
|
|
||||||
import Container from "../components/Container.svelte";
|
import Container from "../components/Container.svelte";
|
||||||
import { addOrUpdateNote } from "./add-or-update-note";
|
|
||||||
import type { IOMode } from "./lib";
|
import type { IOMode } from "./lib";
|
||||||
import MasksEditor from "./MaskEditor.svelte";
|
import MasksEditor from "./MaskEditor.svelte";
|
||||||
import Notes from "./Notes.svelte";
|
import Notes from "./Notes.svelte";
|
||||||
import StickyFooter from "./StickyFooter.svelte";
|
import { textEditingState } from "./store";
|
||||||
import { hideAllGuessOne, textEditingState } from "./store";
|
|
||||||
|
|
||||||
export let mode: IOMode;
|
export let mode: IOMode;
|
||||||
|
|
||||||
async function addNote(): Promise<void> {
|
|
||||||
addOrUpdateNote(mode, $hideAllGuessOne);
|
|
||||||
}
|
|
||||||
|
|
||||||
const items = [
|
const items = [
|
||||||
{ label: tr.notetypesOcclusionMask(), value: 1 },
|
{ label: tr.notetypesOcclusionMask(), value: 1 },
|
||||||
{ label: tr.notetypesOcclusionNote(), value: 2 },
|
{ 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}>
|
<div hidden={activeTabValue != 2}>
|
||||||
<Notes />
|
<Notes />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<StickyFooter {mode} {addNote} />
|
|
||||||
</Container>
|
</Container>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
|
@ -5,6 +5,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { directionKey } from "@tslib/context-keys";
|
import { directionKey } from "@tslib/context-keys";
|
||||||
import * as tr from "@tslib/ftl";
|
import * as tr from "@tslib/ftl";
|
||||||
|
import { isApplePlatform } from "@tslib/platform";
|
||||||
import { getPlatformString } from "@tslib/shortcuts";
|
import { getPlatformString } from "@tslib/shortcuts";
|
||||||
import DropdownItem from "components/DropdownItem.svelte";
|
import DropdownItem from "components/DropdownItem.svelte";
|
||||||
import IconButton from "components/IconButton.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 { drawCursor } from "./tools/tool-cursor";
|
||||||
import { removeUnfinishedPolygon } from "./tools/tool-polygon";
|
import { removeUnfinishedPolygon } from "./tools/tool-polygon";
|
||||||
import { undoRedoTools, undoStack } from "./tools/tool-undo-redo";
|
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 canvas;
|
||||||
export let iconSize;
|
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 dbclicked = false;
|
||||||
let move = false;
|
let move = false;
|
||||||
let wheel = false;
|
let wheel = false;
|
||||||
|
const controlKey = isApplePlatform() ? "Shift" : "Control";
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
window.addEventListener("mousedown", (event) => {
|
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) => {
|
window.addEventListener("keyup", (event) => {
|
||||||
if (event.key == "Control") {
|
if (event.key === controlKey) {
|
||||||
clicked = false;
|
clicked = false;
|
||||||
move = false;
|
move = false;
|
||||||
wheel = 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) => {
|
window.addEventListener("keydown", (event) => {
|
||||||
if (event.key == "Control") {
|
if (event.key === controlKey) {
|
||||||
clicked = false;
|
|
||||||
move = false;
|
|
||||||
wheel = false;
|
|
||||||
dbclicked = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
window.addEventListener("keydown", (event) => {
|
|
||||||
if (event.key == "Control" && activeTool != "magnify") {
|
|
||||||
stopDraw(canvas);
|
stopDraw(canvas);
|
||||||
enableZoom(canvas);
|
enableZoom(canvas);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
window.addEventListener("keyup", (event) => {
|
window.addEventListener("keyup", (event) => {
|
||||||
if (event.key == "Control" && activeTool != "magnify") {
|
if (event.key === controlKey) {
|
||||||
disableFunctions();
|
disableFunctions();
|
||||||
|
handleToolChanges(activeTool);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
window.addEventListener("wheel", () => {
|
window.addEventListener(
|
||||||
if (clicked && move && wheel && !dbclicked) {
|
"wheel",
|
||||||
stopDraw(canvas);
|
(event) => {
|
||||||
enableZoom(canvas);
|
event.preventDefault();
|
||||||
}
|
|
||||||
});
|
if (clicked && move && wheel && !dbclicked) {
|
||||||
|
stopDraw(canvas);
|
||||||
|
enableZoom(canvas);
|
||||||
|
}
|
||||||
|
onWheelDrag(canvas, event);
|
||||||
|
},
|
||||||
|
{ passive: false },
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// handle tool changes after initialization
|
const handleToolChanges = (activeTool: string) => {
|
||||||
$: if (canvas) {
|
|
||||||
disableFunctions();
|
disableFunctions();
|
||||||
enableSelectable(canvas, true);
|
enableSelectable(canvas, true);
|
||||||
// remove unfinished polygon when switching to other tools
|
// 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":
|
case "cursor":
|
||||||
drawCursor(canvas);
|
drawCursor(canvas);
|
||||||
break;
|
break;
|
||||||
case "magnify":
|
|
||||||
enableZoom(canvas);
|
|
||||||
enableSelectable(canvas, false);
|
|
||||||
break;
|
|
||||||
case "draw-rectangle":
|
case "draw-rectangle":
|
||||||
drawRectangle(canvas);
|
drawRectangle(canvas);
|
||||||
break;
|
break;
|
||||||
|
@ -146,6 +143,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// handle tool changes after initialization
|
||||||
|
$: if (canvas) {
|
||||||
|
handleToolChanges(activeTool);
|
||||||
}
|
}
|
||||||
|
|
||||||
const disableFunctions = () => {
|
const disableFunctions = () => {
|
||||||
|
|
|
@ -4,10 +4,15 @@
|
||||||
import "./image-occlusion-base.scss";
|
import "./image-occlusion-base.scss";
|
||||||
|
|
||||||
import { ModuleName, setupI18n } from "@tslib/i18n";
|
import { ModuleName, setupI18n } from "@tslib/i18n";
|
||||||
|
import { get } from "svelte/store";
|
||||||
|
|
||||||
import { checkNightMode } from "../lib/nightmode";
|
import { checkNightMode } from "../lib/nightmode";
|
||||||
|
import { addOrUpdateNote } from "./add-or-update-note";
|
||||||
import ImageOcclusionPage from "./ImageOcclusionPage.svelte";
|
import ImageOcclusionPage from "./ImageOcclusionPage.svelte";
|
||||||
import type { IOMode } from "./lib";
|
import type { IOMode } from "./lib";
|
||||||
|
import { hideAllGuessOne } from "./store";
|
||||||
|
|
||||||
|
globalThis.anki = globalThis.anki || {};
|
||||||
|
|
||||||
const i18n = setupI18n({
|
const i18n = setupI18n({
|
||||||
modules: [
|
modules: [
|
||||||
|
@ -25,6 +30,16 @@ export async function setupImageOcclusion(mode: IOMode, target = document.body):
|
||||||
checkNightMode();
|
checkNightMode();
|
||||||
await i18n;
|
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({
|
return new ImageOcclusionPage({
|
||||||
target: target,
|
target: target,
|
||||||
props: {
|
props: {
|
||||||
|
|
|
@ -128,7 +128,6 @@ const setupBoundingBox = (canvas: fabric.Canvas, size: Size): fabric.Rect => {
|
||||||
lockMovementY: true,
|
lockMovementY: true,
|
||||||
selectable: false,
|
selectable: false,
|
||||||
evented: false,
|
evented: false,
|
||||||
stroke: "red",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
canvas.add(boundingBox);
|
canvas.add(boundingBox);
|
||||||
|
|
|
@ -291,6 +291,8 @@ export const selectAllShapes = (canvas: fabric.Canvas) => {
|
||||||
|
|
||||||
export const isPointerInBoundingBox = (pointer): boolean => {
|
export const isPointerInBoundingBox = (pointer): boolean => {
|
||||||
const boundingBox = getBoundingBox();
|
const boundingBox = getBoundingBox();
|
||||||
|
boundingBox.selectable = false;
|
||||||
|
boundingBox.evented = false;
|
||||||
if (
|
if (
|
||||||
pointer.x < boundingBox.left
|
pointer.x < boundingBox.left
|
||||||
|| pointer.x > boundingBox.left + boundingBox.width
|
|| pointer.x > boundingBox.left + boundingBox.width
|
||||||
|
|
|
@ -6,7 +6,6 @@ import * as tr from "@tslib/ftl";
|
||||||
import {
|
import {
|
||||||
mdiCursorDefaultOutline,
|
mdiCursorDefaultOutline,
|
||||||
mdiEllipseOutline,
|
mdiEllipseOutline,
|
||||||
mdiMagnifyScan,
|
|
||||||
mdiRectangleOutline,
|
mdiRectangleOutline,
|
||||||
mdiTextBox,
|
mdiTextBox,
|
||||||
mdiVectorPolygonVariant,
|
mdiVectorPolygonVariant,
|
||||||
|
@ -14,7 +13,6 @@ import {
|
||||||
import {
|
import {
|
||||||
cursorKeyCombination,
|
cursorKeyCombination,
|
||||||
ellipseKeyCombination,
|
ellipseKeyCombination,
|
||||||
magnifyKeyCombination,
|
|
||||||
polygonKeyCombination,
|
polygonKeyCombination,
|
||||||
rectangleKeyCombination,
|
rectangleKeyCombination,
|
||||||
textKeyCombination,
|
textKeyCombination,
|
||||||
|
@ -27,12 +25,6 @@ export const tools = [
|
||||||
tooltip: tr.editingImageOcclusionSelectTool,
|
tooltip: tr.editingImageOcclusionSelectTool,
|
||||||
shortcut: cursorKeyCombination,
|
shortcut: cursorKeyCombination,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: "magnify",
|
|
||||||
icon: mdiMagnifyScan,
|
|
||||||
tooltip: tr.editingImageOcclusionZoomTool,
|
|
||||||
shortcut: magnifyKeyCombination,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: "draw-rectangle",
|
id: "draw-rectangle",
|
||||||
icon: mdiRectangleOutline,
|
icon: mdiRectangleOutline,
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
// https://codepen.io/amsunny/pen/XWGLxye
|
// https://codepen.io/amsunny/pen/XWGLxye
|
||||||
// canvas.viewportTransform = [ scaleX, skewX, skewY, scaleY, translateX, translateY ]
|
// canvas.viewportTransform = [ scaleX, skewX, skewY, scaleY, translateX, translateY ]
|
||||||
|
|
||||||
|
import { isDesktop } from "@tslib/platform";
|
||||||
import type { fabric } from "fabric";
|
import type { fabric } from "fabric";
|
||||||
import Hammer from "hammerjs";
|
import Hammer from "hammerjs";
|
||||||
|
|
||||||
|
@ -47,9 +48,15 @@ export const zoomOut = (canvas): void => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const zoomReset = (canvas: fabric.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);
|
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) => {
|
export const enablePinchZoom = (canvas: fabric.Canvas) => {
|
||||||
|
@ -74,8 +81,7 @@ export const disablePinchZoom = (canvas: fabric.Canvas) => {
|
||||||
|
|
||||||
export const onResize = (canvas: fabric.Canvas) => {
|
export const onResize = (canvas: fabric.Canvas) => {
|
||||||
setCanvasSize(canvas);
|
setCanvasSize(canvas);
|
||||||
constrainBoundsAroundBgImage(canvas);
|
zoomReset(canvas);
|
||||||
fitCanvasVptScale(canvas);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onMouseWheel = (opt) => {
|
const onMouseWheel = (opt) => {
|
||||||
|
@ -151,6 +157,23 @@ const onDrag = (canvas, opt) => {
|
||||||
redraw(canvas);
|
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 = () => {
|
const onMouseUp = () => {
|
||||||
isDragging = false;
|
isDragging = false;
|
||||||
const canvas = globalThis.canvas;
|
const canvas = globalThis.canvas;
|
||||||
|
@ -176,8 +199,11 @@ export const constrainBoundsAroundBgImage = (canvas: fabric.Canvas) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setCanvasSize = (canvas: fabric.Canvas) => {
|
export const setCanvasSize = (canvas: fabric.Canvas) => {
|
||||||
canvas.setHeight(window.innerHeight - 76);
|
const width = window.innerWidth - 39;
|
||||||
canvas.setWidth(window.innerWidth - 39);
|
let height = window.innerHeight;
|
||||||
|
height = isDesktop() ? height - 76 : height - 46;
|
||||||
|
canvas.setHeight(height);
|
||||||
|
canvas.setWidth(width);
|
||||||
redraw(canvas);
|
redraw(canvas);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -205,8 +231,8 @@ const fitCanvasVptScale = (canvas: fabric.Canvas) => {
|
||||||
const getScaleRatio = (boundingBox: fabric.Rect) => {
|
const getScaleRatio = (boundingBox: fabric.Rect) => {
|
||||||
const h1 = boundingBox.height;
|
const h1 = boundingBox.height;
|
||||||
const w1 = boundingBox.width;
|
const w1 = boundingBox.width;
|
||||||
const h2 = innerHeight - 79;
|
|
||||||
const w2 = innerWidth - 42;
|
const w2 = innerWidth - 42;
|
||||||
|
let h2 = window.innerHeight;
|
||||||
|
h2 = isDesktop() ? h2 - 79 : h2 - 48;
|
||||||
return Math.min(w2 / w1, h2 / h1);
|
return Math.min(w2 / w1, h2 / h1);
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue