Spaces:
Runtime error
Runtime error
mask and canvas as global state
Browse files
frontend/src/lib/App.svelte
CHANGED
@@ -8,7 +8,7 @@
|
|
8 |
import { COLORS, EMOJIS } from '$lib/constants';
|
9 |
import { PUBLIC_WS_INPAINTING } from '$env/static/public';
|
10 |
import type { PromptImgObject, PromptImgKey, Presence } from '$lib/types';
|
11 |
-
import { loadingState, currZoomTransform } from '$lib/store';
|
12 |
|
13 |
import { useMyPresence, useObject, useOthers } from '$lib/liveblocks';
|
14 |
|
@@ -45,8 +45,6 @@
|
|
45 |
$: isPrompting = $myPresence?.isPrompting || false;
|
46 |
$: isLoading = $myPresence?.isLoading || false;
|
47 |
|
48 |
-
let canvasEl: HTMLCanvasElement;
|
49 |
-
|
50 |
function onPaintMode(e: CustomEvent) {
|
51 |
const mode = e.detail.mode;
|
52 |
if (mode == 'paint' && !isPrompting) {
|
@@ -67,21 +65,21 @@
|
|
67 |
}
|
68 |
|
69 |
function getImageCrop(cursor: { x: number; y: number }) {
|
70 |
-
const canvasCrop = document.createElement('canvas');
|
71 |
-
|
72 |
-
canvasCrop.width = 512;
|
73 |
-
canvasCrop.height = 512;
|
74 |
|
75 |
-
|
|
|
76 |
|
77 |
-
//
|
78 |
-
ctxCrop.save();
|
79 |
-
ctxCrop.clearRect(0, 0, 512, 512);
|
80 |
-
ctxCrop.globalCompositeOperation = 'source-over';
|
81 |
-
ctxCrop.drawImage(canvasEl, cursor.x, cursor.y, 512, 512, 0, 0, 512, 512);
|
82 |
-
ctxCrop.restore();
|
83 |
|
84 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
85 |
|
86 |
return base64Crop;
|
87 |
}
|
@@ -193,7 +191,7 @@
|
|
193 |
<PromptModal on:prompt={onPrompt} on:close={onClose} />
|
194 |
{/if}
|
195 |
<div class="fixed top-0 left-0 z-0 w-screen h-screen">
|
196 |
-
<PaintCanvas
|
197 |
|
198 |
<main class="z-10 relative">
|
199 |
<PaintFrame
|
|
|
8 |
import { COLORS, EMOJIS } from '$lib/constants';
|
9 |
import { PUBLIC_WS_INPAINTING } from '$env/static/public';
|
10 |
import type { PromptImgObject, PromptImgKey, Presence } from '$lib/types';
|
11 |
+
import { loadingState, currZoomTransform, maskEl } from '$lib/store';
|
12 |
|
13 |
import { useMyPresence, useObject, useOthers } from '$lib/liveblocks';
|
14 |
|
|
|
45 |
$: isPrompting = $myPresence?.isPrompting || false;
|
46 |
$: isLoading = $myPresence?.isLoading || false;
|
47 |
|
|
|
|
|
48 |
function onPaintMode(e: CustomEvent) {
|
49 |
const mode = e.detail.mode;
|
50 |
if (mode == 'paint' && !isPrompting) {
|
|
|
65 |
}
|
66 |
|
67 |
function getImageCrop(cursor: { x: number; y: number }) {
|
68 |
+
// const canvasCrop = document.createElement('canvas');
|
|
|
|
|
|
|
69 |
|
70 |
+
// canvasCrop.width = 512;
|
71 |
+
// canvasCrop.height = 512;
|
72 |
|
73 |
+
// const ctxCrop = canvasCrop.getContext('2d') as CanvasRenderingContext2D;
|
|
|
|
|
|
|
|
|
|
|
74 |
|
75 |
+
// // crop image from point canvas
|
76 |
+
// ctxCrop.save();
|
77 |
+
// ctxCrop.clearRect(0, 0, 512, 512);
|
78 |
+
// ctxCrop.globalCompositeOperation = 'source-over';
|
79 |
+
// ctxCrop.drawImage($maskEl, cursor.x, cursor.y, 512, 512, 0, 0, 512, 512);
|
80 |
+
// ctxCrop.restore();
|
81 |
+
|
82 |
+
const base64Crop = $maskEl.toDataURL('image/png');
|
83 |
|
84 |
return base64Crop;
|
85 |
}
|
|
|
191 |
<PromptModal on:prompt={onPrompt} on:close={onClose} />
|
192 |
{/if}
|
193 |
<div class="fixed top-0 left-0 z-0 w-screen h-screen">
|
194 |
+
<PaintCanvas />
|
195 |
|
196 |
<main class="z-10 relative">
|
197 |
<PaintFrame
|
frontend/src/lib/Buttons/UndoButton.svelte
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import Undo from '$lib/Icons/Undo.svelte';
|
3 |
+
export let isActive = false;
|
4 |
+
</script>
|
5 |
+
|
6 |
+
<button
|
7 |
+
on:click
|
8 |
+
class="bg-white rounded-full p-2 {isActive ? 'text-blue-700' : 'text-gray-800'}"
|
9 |
+
title="Clear Masking"
|
10 |
+
>
|
11 |
+
<Undo />
|
12 |
+
</button>
|
13 |
+
|
14 |
+
<style lang="postcss" scoped>
|
15 |
+
</style>
|
frontend/src/lib/Icons/Undo.svelte
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
export let classList = '';
|
3 |
+
</script>
|
4 |
+
|
5 |
+
<svg
|
6 |
+
class={classList}
|
7 |
+
width="20"
|
8 |
+
height="19"
|
9 |
+
viewBox="0 0 10 9"
|
10 |
+
fill="none"
|
11 |
+
xmlns="http://www.w3.org/2000/svg"
|
12 |
+
>
|
13 |
+
<g opacity="0.5">
|
14 |
+
<path
|
15 |
+
d="M6.33333 2.66667H2.27167L3.46733 1.47133L3 1L1 3L3 5L3.46733 4.52833L2.27267 3.33333H6.33333C6.86377 3.33333 7.37247 3.54405 7.74755 3.91912C8.12262 4.29419 8.33333 4.8029 8.33333 5.33333C8.33333 5.86377 8.12262 6.37247 7.74755 6.74755C7.37247 7.12262 6.86377 7.33333 6.33333 7.33333H3.66667V8H6.33333C7.04058 8 7.71885 7.71905 8.21895 7.21895C8.71905 6.71885 9 6.04058 9 5.33333C9 4.62609 8.71905 3.94781 8.21895 3.44772C7.71885 2.94762 7.04058 2.66667 6.33333 2.66667Z"
|
16 |
+
fill="black"
|
17 |
+
stroke="black"
|
18 |
+
stroke-width="0.5"
|
19 |
+
stroke-linejoin="round"
|
20 |
+
/>
|
21 |
+
</g>
|
22 |
+
</svg>
|
frontend/src/lib/PaintCanvas.svelte
CHANGED
@@ -3,7 +3,7 @@
|
|
3 |
import { select } from 'd3-selection';
|
4 |
import { onMount } from 'svelte';
|
5 |
import { PUBLIC_UPLOADS } from '$env/static/public';
|
6 |
-
import { currZoomTransform } from '$lib/store';
|
7 |
import { round } from '$lib/utils';
|
8 |
|
9 |
import { useMyPresence, useObject } from '$lib/liveblocks';
|
@@ -15,8 +15,6 @@
|
|
15 |
const height = 512 * 4;
|
16 |
const width = 512 * 4;
|
17 |
|
18 |
-
export let canvasEl: HTMLCanvasElement = document.createElement('canvas');
|
19 |
-
|
20 |
let containerEl: HTMLDivElement;
|
21 |
let canvasCtx: CanvasRenderingContext2D;
|
22 |
|
@@ -53,14 +51,14 @@
|
|
53 |
.tapDistance(10)
|
54 |
.on('zoom', zoomed);
|
55 |
|
56 |
-
const selection = select(canvasEl.parentElement)
|
57 |
.call(zoomHandler as any)
|
58 |
.call(zoomHandler.transform as any, zoomIdentity)
|
59 |
.call(zoomHandler.scaleTo as any, 1 / scale)
|
60 |
.on('pointermove', handlePointerMove)
|
61 |
.on('pointerleave', handlePointerLeave);
|
62 |
|
63 |
-
canvasCtx = canvasEl.getContext('2d') as CanvasRenderingContext2D;
|
64 |
function zoomReset() {
|
65 |
const scale =
|
66 |
(width + padding * 2) /
|
@@ -108,7 +106,7 @@
|
|
108 |
}
|
109 |
function zoomed(e: Event) {
|
110 |
const transform = ($currZoomTransform = e.transform);
|
111 |
-
canvasEl.style.transform = `translate(${transform.x}px, ${transform.y}px) scale(${transform.k})`;
|
112 |
}
|
113 |
|
114 |
// Update cursor presence to current pointer location
|
@@ -137,7 +135,7 @@
|
|
137 |
bind:this={containerEl}
|
138 |
class="absolute top-0 left-0 right-0 bottom-0 overflow-hidden z-0 bg-gray-800"
|
139 |
>
|
140 |
-
<canvas bind:this={canvasEl} {width} {height} class="absolute top-0 left-0 bg-white" />
|
141 |
<slot />
|
142 |
</div>
|
143 |
|
|
|
3 |
import { select } from 'd3-selection';
|
4 |
import { onMount } from 'svelte';
|
5 |
import { PUBLIC_UPLOADS } from '$env/static/public';
|
6 |
+
import { currZoomTransform, canvasEl } from '$lib/store';
|
7 |
import { round } from '$lib/utils';
|
8 |
|
9 |
import { useMyPresence, useObject } from '$lib/liveblocks';
|
|
|
15 |
const height = 512 * 4;
|
16 |
const width = 512 * 4;
|
17 |
|
|
|
|
|
18 |
let containerEl: HTMLDivElement;
|
19 |
let canvasCtx: CanvasRenderingContext2D;
|
20 |
|
|
|
51 |
.tapDistance(10)
|
52 |
.on('zoom', zoomed);
|
53 |
|
54 |
+
const selection = select($canvasEl.parentElement)
|
55 |
.call(zoomHandler as any)
|
56 |
.call(zoomHandler.transform as any, zoomIdentity)
|
57 |
.call(zoomHandler.scaleTo as any, 1 / scale)
|
58 |
.on('pointermove', handlePointerMove)
|
59 |
.on('pointerleave', handlePointerLeave);
|
60 |
|
61 |
+
canvasCtx = $canvasEl.getContext('2d') as CanvasRenderingContext2D;
|
62 |
function zoomReset() {
|
63 |
const scale =
|
64 |
(width + padding * 2) /
|
|
|
106 |
}
|
107 |
function zoomed(e: Event) {
|
108 |
const transform = ($currZoomTransform = e.transform);
|
109 |
+
$canvasEl.style.transform = `translate(${transform.x}px, ${transform.y}px) scale(${transform.k})`;
|
110 |
}
|
111 |
|
112 |
// Update cursor presence to current pointer location
|
|
|
135 |
bind:this={containerEl}
|
136 |
class="absolute top-0 left-0 right-0 bottom-0 overflow-hidden z-0 bg-gray-800"
|
137 |
>
|
138 |
+
<canvas bind:this={$canvasEl} {width} {height} class="absolute top-0 left-0 bg-white" />
|
139 |
<slot />
|
140 |
</div>
|
141 |
|
frontend/src/lib/PaintFrame.svelte
CHANGED
@@ -1,19 +1,21 @@
|
|
1 |
<script lang="ts">
|
2 |
-
import Frame from '$lib/Frame.svelte';
|
3 |
import PPButton from '$lib/Buttons/PPButton.svelte';
|
4 |
import DragButton from '$lib/Buttons/DragButton.svelte';
|
5 |
import MaskButton from '$lib/Buttons/MaskButton.svelte';
|
|
|
6 |
import LoadingIcon from '$lib/Icons/LoadingIcon.svelte';
|
7 |
|
8 |
import { drag } from 'd3-drag';
|
9 |
-
import { select } from 'd3-selection';
|
10 |
import { round } from '$lib/utils';
|
11 |
|
12 |
import type { ZoomTransform } from 'd3-zoom';
|
13 |
import { onMount, createEventDispatcher } from 'svelte';
|
14 |
|
15 |
import { useMyPresence } from '$lib/liveblocks';
|
16 |
-
import { loadingState } from '$lib/store';
|
|
|
|
|
17 |
const myPresence = useMyPresence();
|
18 |
|
19 |
const dispatch = createEventDispatcher();
|
@@ -22,6 +24,8 @@
|
|
22 |
export let color = 'black';
|
23 |
export let interactive = false;
|
24 |
|
|
|
|
|
25 |
let position = {
|
26 |
x: 768,
|
27 |
y: 768
|
@@ -40,7 +44,67 @@
|
|
40 |
|
41 |
let offsetX = 0;
|
42 |
let offsetY = 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
43 |
onMount(() => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
44 |
function dragstarted(event: Event) {
|
45 |
const rect = (event.sourceEvent.target as HTMLElement).getBoundingClientRect();
|
46 |
if (event.sourceEvent instanceof TouchEvent) {
|
@@ -66,6 +130,7 @@
|
|
66 |
y: transform.invertY(event.y)
|
67 |
}
|
68 |
});
|
|
|
69 |
}
|
70 |
|
71 |
function dragended(event: Event) {
|
@@ -73,6 +138,7 @@
|
|
73 |
|
74 |
const x = round(transform.invertX(event.x - offsetX));
|
75 |
const y = round(transform.invertY(event.y - offsetY));
|
|
|
76 |
|
77 |
myPresence.update({
|
78 |
frame: {
|
@@ -81,28 +147,14 @@
|
|
81 |
}
|
82 |
});
|
83 |
}
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
function handlePointerLeave() {
|
93 |
-
myPresence.update({
|
94 |
-
cursor: null
|
95 |
-
});
|
96 |
-
}
|
97 |
-
const dragHandler = drag().on('start', dragstarted).on('drag', dragged).on('end', dragended);
|
98 |
-
select(frameElement)
|
99 |
-
.call(dragHandler as any)
|
100 |
-
.on('pointermove', handlePointerMove)
|
101 |
-
.on('pointerleave', handlePointerLeave);
|
102 |
-
});
|
103 |
-
|
104 |
-
function DragMask() {
|
105 |
-
dragEnabled = !dragEnabled;
|
106 |
}
|
107 |
</script>
|
108 |
|
@@ -112,6 +164,12 @@
|
|
112 |
style={`transform: translateX(${coord.x}px) translateY(${coord.y}px) scale(${transform.k}); border-color: ${color}; transform-origin: 0 0;`}
|
113 |
>
|
114 |
<div class="frame">
|
|
|
|
|
|
|
|
|
|
|
|
|
115 |
<div class="pointer-events-none touch-none">
|
116 |
{#if $loadingState}
|
117 |
<div class="col-span-2 row-start-1">
|
@@ -128,15 +186,21 @@
|
|
128 |
<div class="absolute bottom-0 font-bold text-lg">{prompt}</div>
|
129 |
</div>
|
130 |
{#if !isDragging}
|
131 |
-
<div class="absolute top-full">
|
132 |
<div class="py-2">
|
133 |
<PPButton on:click={() => dispatch('paintMode', { mode: 'paint' })} />
|
134 |
</div>
|
135 |
</div>
|
136 |
<div class="absolute left-full bottom-0">
|
137 |
-
<div class="px-2
|
138 |
-
<DragButton isActive={dragEnabled} on:click={
|
139 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
140 |
</div>
|
141 |
</div>
|
142 |
{/if}
|
@@ -144,15 +208,15 @@
|
|
144 |
</div>
|
145 |
<div
|
146 |
bind:this={frameElement}
|
147 |
-
class="absolute top-0 left-0 w-[512px] h-[512px]
|
148 |
{isDragging ? 'cursor-grabbing' : 'cursor-grab'}
|
149 |
{dragEnabled ? 'block' : 'hidden'}"
|
150 |
-
style={`transform: translateX(${coord.x}px) translateY(${coord.y}px) scale(${transform.k});
|
151 |
/>
|
152 |
</div>
|
153 |
|
154 |
<style lang="postcss" scoped>
|
155 |
.frame {
|
156 |
-
@apply relative grid grid-cols-3 grid-rows-3
|
157 |
}
|
158 |
</style>
|
|
|
1 |
<script lang="ts">
|
|
|
2 |
import PPButton from '$lib/Buttons/PPButton.svelte';
|
3 |
import DragButton from '$lib/Buttons/DragButton.svelte';
|
4 |
import MaskButton from '$lib/Buttons/MaskButton.svelte';
|
5 |
+
import UndoButton from '$lib/Buttons/UndoButton.svelte';
|
6 |
import LoadingIcon from '$lib/Icons/LoadingIcon.svelte';
|
7 |
|
8 |
import { drag } from 'd3-drag';
|
9 |
+
import { select, type Selection } from 'd3-selection';
|
10 |
import { round } from '$lib/utils';
|
11 |
|
12 |
import type { ZoomTransform } from 'd3-zoom';
|
13 |
import { onMount, createEventDispatcher } from 'svelte';
|
14 |
|
15 |
import { useMyPresence } from '$lib/liveblocks';
|
16 |
+
import { loadingState, canvasEl, maskEl } from '$lib/store';
|
17 |
+
|
18 |
+
import { toggle_class } from 'svelte/internal';
|
19 |
const myPresence = useMyPresence();
|
20 |
|
21 |
const dispatch = createEventDispatcher();
|
|
|
24 |
export let color = 'black';
|
25 |
export let interactive = false;
|
26 |
|
27 |
+
let maskCtx: CanvasRenderingContext2D;
|
28 |
+
|
29 |
let position = {
|
30 |
x: 768,
|
31 |
y: 768
|
|
|
44 |
|
45 |
let offsetX = 0;
|
46 |
let offsetY = 0;
|
47 |
+
|
48 |
+
function cropCanvas(cursor: { x: number; y: number }) {
|
49 |
+
maskCtx.save();
|
50 |
+
maskCtx.clearRect(0, 0, 512, 512);
|
51 |
+
maskCtx.globalCompositeOperation = 'source-over';
|
52 |
+
maskCtx.drawImage($canvasEl, cursor.x, cursor.y, 512, 512, 0, 0, 512, 512);
|
53 |
+
maskCtx.restore();
|
54 |
+
}
|
55 |
+
function drawCircle(cursor: { x: number; y: number }) {
|
56 |
+
maskCtx.save();
|
57 |
+
maskCtx.globalCompositeOperation = 'destination-out';
|
58 |
+
maskCtx.beginPath();
|
59 |
+
maskCtx.arc(cursor.x, cursor.y, 20, 0, 2 * Math.PI);
|
60 |
+
maskCtx.fill();
|
61 |
+
maskCtx.restore();
|
62 |
+
}
|
63 |
+
|
64 |
onMount(() => {
|
65 |
+
maskCtx = $maskEl.getContext('2d') as CanvasRenderingContext2D;
|
66 |
+
|
67 |
+
select(frameElement)
|
68 |
+
.call(dragMoveHandler() as any)
|
69 |
+
.call(cursorUpdate);
|
70 |
+
select($maskEl).call(maskingHandler() as any);
|
71 |
+
});
|
72 |
+
|
73 |
+
function cursorUpdate(selection: Selection) {
|
74 |
+
function handlePointerMove(event: PointerEvent) {
|
75 |
+
myPresence.update({
|
76 |
+
cursor: {
|
77 |
+
x: transform.invertX(event.clientX),
|
78 |
+
y: transform.invertY(event.clientY)
|
79 |
+
}
|
80 |
+
});
|
81 |
+
}
|
82 |
+
function handlePointerLeave() {
|
83 |
+
myPresence.update({
|
84 |
+
cursor: null
|
85 |
+
});
|
86 |
+
}
|
87 |
+
return selection.on('pointermove', handlePointerMove).on('pointerleave', handlePointerLeave);
|
88 |
+
}
|
89 |
+
function maskingHandler() {
|
90 |
+
function dragstarted(event: Event) {
|
91 |
+
const x = event.x / transform.k;
|
92 |
+
const y = event.y / transform.k;
|
93 |
+
}
|
94 |
+
|
95 |
+
function dragged(event: Event) {
|
96 |
+
const x = event.x / transform.k;
|
97 |
+
const y = event.y / transform.k;
|
98 |
+
drawCircle({ x, y });
|
99 |
+
}
|
100 |
+
|
101 |
+
function dragended(event: Event) {
|
102 |
+
const x = event.x / transform.k;
|
103 |
+
const y = event.y / transform.k;
|
104 |
+
}
|
105 |
+
return drag().on('start', dragstarted).on('drag', dragged).on('end', dragended);
|
106 |
+
}
|
107 |
+
function dragMoveHandler() {
|
108 |
function dragstarted(event: Event) {
|
109 |
const rect = (event.sourceEvent.target as HTMLElement).getBoundingClientRect();
|
110 |
if (event.sourceEvent instanceof TouchEvent) {
|
|
|
130 |
y: transform.invertY(event.y)
|
131 |
}
|
132 |
});
|
133 |
+
cropCanvas({ x, y });
|
134 |
}
|
135 |
|
136 |
function dragended(event: Event) {
|
|
|
138 |
|
139 |
const x = round(transform.invertX(event.x - offsetX));
|
140 |
const y = round(transform.invertY(event.y - offsetY));
|
141 |
+
cropCanvas({ x, y });
|
142 |
|
143 |
myPresence.update({
|
144 |
frame: {
|
|
|
147 |
}
|
148 |
});
|
149 |
}
|
150 |
+
return drag().on('start', dragstarted).on('drag', dragged).on('end', dragended);
|
151 |
+
}
|
152 |
+
function toggleDrag() {
|
153 |
+
dragEnabled = true;
|
154 |
+
}
|
155 |
+
function toggleMask() {
|
156 |
+
dragEnabled = false;
|
157 |
+
cropCanvas(position);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
158 |
}
|
159 |
</script>
|
160 |
|
|
|
164 |
style={`transform: translateX(${coord.x}px) translateY(${coord.y}px) scale(${transform.k}); border-color: ${color}; transform-origin: 0 0;`}
|
165 |
>
|
166 |
<div class="frame">
|
167 |
+
<canvas
|
168 |
+
class={dragEnabled || isLoading ? '' : 'bg-white'}
|
169 |
+
bind:this={$maskEl}
|
170 |
+
width="512"
|
171 |
+
height="512"
|
172 |
+
/>
|
173 |
<div class="pointer-events-none touch-none">
|
174 |
{#if $loadingState}
|
175 |
<div class="col-span-2 row-start-1">
|
|
|
186 |
<div class="absolute bottom-0 font-bold text-lg">{prompt}</div>
|
187 |
</div>
|
188 |
{#if !isDragging}
|
189 |
+
<div class="absolute top-full ">
|
190 |
<div class="py-2">
|
191 |
<PPButton on:click={() => dispatch('paintMode', { mode: 'paint' })} />
|
192 |
</div>
|
193 |
</div>
|
194 |
<div class="absolute left-full bottom-0">
|
195 |
+
<div class="px-2">
|
196 |
+
<DragButton isActive={dragEnabled} on:click={toggleDrag} />
|
197 |
+
<div class="flex bg-white rounded-full mt-3">
|
198 |
+
<MaskButton isActive={!dragEnabled} on:click={toggleMask} />
|
199 |
+
{#if !dragEnabled}
|
200 |
+
<span class="border-gray-800 border-opacity-50 border-r-2 my-2" />
|
201 |
+
<UndoButton on:click={() => {}} />
|
202 |
+
{/if}
|
203 |
+
</div>
|
204 |
</div>
|
205 |
</div>
|
206 |
{/if}
|
|
|
208 |
</div>
|
209 |
<div
|
210 |
bind:this={frameElement}
|
211 |
+
class="absolute top-0 left-0 w-[512px] h-[512px] ring-2 ring-black
|
212 |
{isDragging ? 'cursor-grabbing' : 'cursor-grab'}
|
213 |
{dragEnabled ? 'block' : 'hidden'}"
|
214 |
+
style={`transform: translateX(${coord.x}px) translateY(${coord.y}px) scale(${transform.k}); transform-origin: 0 0;`}
|
215 |
/>
|
216 |
</div>
|
217 |
|
218 |
<style lang="postcss" scoped>
|
219 |
.frame {
|
220 |
+
@apply relative grid grid-cols-3 grid-rows-3 ring-2 ring-blue-500 w-[512px] h-[512px];
|
221 |
}
|
222 |
</style>
|
frontend/src/lib/store.ts
CHANGED
@@ -3,4 +3,5 @@ import { type ZoomTransform, zoomIdentity } from 'd3-zoom';
|
|
3 |
|
4 |
export const loadingState = writable<string>('');
|
5 |
export const currZoomTransform = writable<ZoomTransform>(zoomIdentity);
|
6 |
-
|
|
|
|
3 |
|
4 |
export const loadingState = writable<string>('');
|
5 |
export const currZoomTransform = writable<ZoomTransform>(zoomIdentity);
|
6 |
+
export const canvasEl = writable<HTMLCanvasElement>();
|
7 |
+
export const maskEl = writable<HTMLCanvasElement>();
|