Spaces:
Runtime error
Runtime error
drag frame
Browse files- frontend/package.json +2 -0
- frontend/src/lib/App.svelte +17 -14
- frontend/src/lib/Canvas.svelte +15 -23
- frontend/src/lib/Cursor.svelte +9 -7
- frontend/src/lib/Menu.svelte +15 -17
- frontend/src/lib/PaintFrame.svelte +75 -0
- frontend/src/lib/store.ts +1 -4
- frontend/src/lib/types.ts +6 -10
- frontend/src/lib/utils.ts +5 -1
frontend/package.json
CHANGED
@@ -17,6 +17,7 @@
|
|
17 |
"@tailwindcss/forms": "^0.5.3",
|
18 |
"@tailwindcss/line-clamp": "^0.4.2",
|
19 |
"@types/cookie": "^0.5.1",
|
|
|
20 |
"@types/d3-selection": "^3.0.3",
|
21 |
"@types/d3-zoom": "^3.0.1",
|
22 |
"@typescript-eslint/eslint-plugin": "^5.38.0",
|
@@ -41,6 +42,7 @@
|
|
41 |
"dependencies": {
|
42 |
"@fontsource/fira-mono": "^4.5.0",
|
43 |
"@liveblocks/client": "^0.18.2",
|
|
|
44 |
"d3-selection": "^3.0.0",
|
45 |
"d3-zoom": "^3.0.0",
|
46 |
"nanoid": "^4.0.0"
|
|
|
17 |
"@tailwindcss/forms": "^0.5.3",
|
18 |
"@tailwindcss/line-clamp": "^0.4.2",
|
19 |
"@types/cookie": "^0.5.1",
|
20 |
+
"@types/d3-drag": "^3.0.1",
|
21 |
"@types/d3-selection": "^3.0.3",
|
22 |
"@types/d3-zoom": "^3.0.1",
|
23 |
"@typescript-eslint/eslint-plugin": "^5.38.0",
|
|
|
42 |
"dependencies": {
|
43 |
"@fontsource/fira-mono": "^4.5.0",
|
44 |
"@liveblocks/client": "^0.18.2",
|
45 |
+
"d3-drag": "^3.0.0",
|
46 |
"d3-selection": "^3.0.0",
|
47 |
"d3-zoom": "^3.0.0",
|
48 |
"nanoid": "^4.0.0"
|
frontend/src/lib/App.svelte
CHANGED
@@ -1,14 +1,13 @@
|
|
1 |
<script lang="ts">
|
2 |
import Cursor from '$lib/Cursor.svelte';
|
3 |
import Frame from '$lib/Frame.svelte';
|
|
|
4 |
import Canvas from '$lib/Canvas.svelte';
|
5 |
import Menu from '$lib/Menu.svelte';
|
6 |
import PromptModal from '$lib/PromptModal.svelte';
|
7 |
-
import type { Room } from '@liveblocks/client';
|
8 |
import { COLORS, EMOJIS } from '$lib/constants';
|
9 |
import { PUBLIC_WS_INPAINTING } from '$env/static/public';
|
10 |
-
import {
|
11 |
-
import type { PromptImgObject, PromptImgKey } from '$lib/types';
|
12 |
import {
|
13 |
isLoading,
|
14 |
loadingState,
|
@@ -33,15 +32,15 @@
|
|
33 |
const others = useOthers();
|
34 |
|
35 |
// Set a default value for presence
|
36 |
-
|
37 |
-
name: '',
|
38 |
cursor: null,
|
39 |
isPrompting: false,
|
|
|
|
|
40 |
currentPrompt: ''
|
41 |
-
}
|
42 |
-
|
43 |
-
|
44 |
-
}
|
45 |
function getKey({ position }: PromptImgObject): PromptImgKey {
|
46 |
return `${position.x}_${position.y}`;
|
47 |
}
|
@@ -60,6 +59,9 @@
|
|
60 |
|
61 |
let canvasEl: HTMLCanvasElement;
|
62 |
|
|
|
|
|
|
|
63 |
async function onClose(e: CustomEvent) {
|
64 |
$isPrompting = false;
|
65 |
}
|
@@ -187,9 +189,11 @@
|
|
187 |
<PromptModal on:prompt={onPrompt} on:close={onClose} />
|
188 |
{/if}
|
189 |
<div class="fixed top-0 left-0 z-0 w-screen h-screen">
|
190 |
-
<Canvas bind:value={canvasEl}
|
191 |
|
192 |
<main class="z-10 relative">
|
|
|
|
|
193 |
{#if promptImgList && $showFrames}
|
194 |
{#each promptImgList as promptImg, i}
|
195 |
<Frame
|
@@ -204,13 +208,12 @@
|
|
204 |
<Frame color={COLORS[0]} position={$clickedPosition} transform={$currZoomTransform} />
|
205 |
{/if} -->
|
206 |
{#if $myPresence?.cursor}
|
207 |
-
<!-- <Frame color={COLORS[0]} position={$myPresence.cursor} transform={$currZoomTransform} />
|
208 |
<Cursor
|
209 |
-
emoji={EMOJIS[0]}
|
210 |
color={COLORS[0]}
|
211 |
position={$myPresence.cursor}
|
212 |
transform={$currZoomTransform}
|
213 |
-
/>
|
214 |
{/if}
|
215 |
|
216 |
<!-- When others connected, iterate through others and show their cursors -->
|
@@ -238,7 +241,7 @@
|
|
238 |
</div>
|
239 |
|
240 |
<div class="fixed bottom-0 left-0 right-0 z-10 my-2">
|
241 |
-
<Menu />
|
242 |
</div>
|
243 |
|
244 |
<style lang="postcss" scoped>
|
|
|
1 |
<script lang="ts">
|
2 |
import Cursor from '$lib/Cursor.svelte';
|
3 |
import Frame from '$lib/Frame.svelte';
|
4 |
+
import PaintFrame from '$lib/PaintFrame.svelte';
|
5 |
import Canvas from '$lib/Canvas.svelte';
|
6 |
import Menu from '$lib/Menu.svelte';
|
7 |
import PromptModal from '$lib/PromptModal.svelte';
|
|
|
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 {
|
12 |
isLoading,
|
13 |
loadingState,
|
|
|
32 |
const others = useOthers();
|
33 |
|
34 |
// Set a default value for presence
|
35 |
+
const initialPresence: Presence = {
|
|
|
36 |
cursor: null,
|
37 |
isPrompting: false,
|
38 |
+
isLoading: false,
|
39 |
+
isMoving: true,
|
40 |
currentPrompt: ''
|
41 |
+
};
|
42 |
+
myPresence.update(initialPresence);
|
43 |
+
|
|
|
44 |
function getKey({ position }: PromptImgObject): PromptImgKey {
|
45 |
return `${position.x}_${position.y}`;
|
46 |
}
|
|
|
59 |
|
60 |
let canvasEl: HTMLCanvasElement;
|
61 |
|
62 |
+
function onPaintMode(e: CustomEvent) {
|
63 |
+
const mode = e.detail.mode;
|
64 |
+
}
|
65 |
async function onClose(e: CustomEvent) {
|
66 |
$isPrompting = false;
|
67 |
}
|
|
|
189 |
<PromptModal on:prompt={onPrompt} on:close={onClose} />
|
190 |
{/if}
|
191 |
<div class="fixed top-0 left-0 z-0 w-screen h-screen">
|
192 |
+
<Canvas bind:value={canvasEl} />
|
193 |
|
194 |
<main class="z-10 relative">
|
195 |
+
<PaintFrame transform={$currZoomTransform} />
|
196 |
+
|
197 |
{#if promptImgList && $showFrames}
|
198 |
{#each promptImgList as promptImg, i}
|
199 |
<Frame
|
|
|
208 |
<Frame color={COLORS[0]} position={$clickedPosition} transform={$currZoomTransform} />
|
209 |
{/if} -->
|
210 |
{#if $myPresence?.cursor}
|
211 |
+
<!-- <Frame color={COLORS[0]} position={$myPresence.cursor} transform={$currZoomTransform} />
|
212 |
<Cursor
|
|
|
213 |
color={COLORS[0]}
|
214 |
position={$myPresence.cursor}
|
215 |
transform={$currZoomTransform}
|
216 |
+
/> -->
|
217 |
{/if}
|
218 |
|
219 |
<!-- When others connected, iterate through others and show their cursors -->
|
|
|
241 |
</div>
|
242 |
|
243 |
<div class="fixed bottom-0 left-0 right-0 z-10 my-2">
|
244 |
+
<Menu on:paintMode={onPaintMode} />
|
245 |
</div>
|
246 |
|
247 |
<style lang="postcss" scoped>
|
frontend/src/lib/Canvas.svelte
CHANGED
@@ -4,6 +4,7 @@
|
|
4 |
import { onMount } from 'svelte';
|
5 |
import { PUBLIC_UPLOADS } from '$env/static/public';
|
6 |
import { currZoomTransform, isPrompting, clickedPosition } from '$lib/store';
|
|
|
7 |
|
8 |
import { useMyPresence, useObject } from '$lib/liveblocks';
|
9 |
import type { PromptImgObject } from '$lib/types';
|
@@ -11,8 +12,8 @@
|
|
11 |
const myPresence = useMyPresence();
|
12 |
const promptImgStorage = useObject('promptImgStorage');
|
13 |
|
14 |
-
const height = 512 *
|
15 |
-
const width = 512 *
|
16 |
|
17 |
let canvasEl: HTMLCanvasElement;
|
18 |
export { canvasEl as value };
|
@@ -41,14 +42,14 @@
|
|
41 |
onMount(() => {
|
42 |
const scale = width / containerEl.clientWidth;
|
43 |
const zoomHandler = zoom()
|
44 |
-
.scaleExtent([1 / scale /
|
45 |
// .extent([
|
46 |
// [0, 0],
|
47 |
// [width, height]
|
48 |
// ])
|
49 |
.translateExtent([
|
50 |
-
[-width * 0.
|
51 |
-
[width * 1.
|
52 |
])
|
53 |
.tapDistance(10)
|
54 |
.on('zoom', zoomed);
|
@@ -61,14 +62,11 @@
|
|
61 |
console.log('clicked', $clickedPosition);
|
62 |
return null;
|
63 |
})
|
64 |
-
|
65 |
.on('pointermove', handlePointerMove)
|
66 |
.on('pointerleave', handlePointerLeave);
|
67 |
|
68 |
canvasCtx = canvasEl.getContext('2d') as CanvasRenderingContext2D;
|
69 |
-
canvasCtx.strokeStyle = 'blue';
|
70 |
-
canvasCtx.lineWidth = 10;
|
71 |
-
canvasCtx.strokeRect(0, 0, width, height);
|
72 |
});
|
73 |
|
74 |
function renderImages(promptImgList: PromptImgObject[]) {
|
@@ -84,6 +82,7 @@
|
|
84 |
position: { x: number; y: number };
|
85 |
id: string;
|
86 |
};
|
|
|
87 |
resolve(res);
|
88 |
};
|
89 |
const url = imgURL.split('/');
|
@@ -93,6 +92,7 @@
|
|
93 |
).then((images) => {
|
94 |
images.forEach(({ img, position, id }) => {
|
95 |
// keep track of images already rendered
|
|
|
96 |
imagesOnCanvas.add(id);
|
97 |
canvasCtx.drawImage(img, position.x, position.y, img.width, img.height);
|
98 |
});
|
@@ -103,21 +103,12 @@
|
|
103 |
canvasEl.style.transform = `translate(${transform.x}px, ${transform.y}px) scale(${transform.k})`;
|
104 |
}
|
105 |
|
106 |
-
const r = 8;
|
107 |
-
function round(p, n) {
|
108 |
-
return p % n < n / 2 ? p - (p % n) : p + n - (p % n);
|
109 |
-
}
|
110 |
-
const grid = 10;
|
111 |
-
|
112 |
// Update cursor presence to current pointer location
|
113 |
function handlePointerMove(event: PointerEvent) {
|
114 |
event.preventDefault();
|
115 |
-
const x =
|
116 |
-
const y =
|
117 |
-
|
118 |
-
// const y = Math.round(event.layerY / grid) * grid; //round(Math.max(r, Math.min(512 * 5 - r, event.clientY)), 100);
|
119 |
-
// const x = round(Math.max(r, Math.min(512 * 5 - r, event.clientX)), grid);
|
120 |
-
// const y = round(Math.max(r, Math.min(512 * 5 - r, event.clientY)), grid);
|
121 |
myPresence.update({
|
122 |
cursor: {
|
123 |
x,
|
@@ -134,8 +125,9 @@
|
|
134 |
}
|
135 |
</script>
|
136 |
|
137 |
-
<div bind:this={containerEl} class="absolute top-0 left-0 right-0 bottom-0 overflow-hidden z-0">
|
138 |
-
<canvas bind:this={canvasEl} {width} {height} class="absolute top-0 left-0" />
|
|
|
139 |
</div>
|
140 |
|
141 |
<style lang="postcss" scoped>
|
|
|
4 |
import { onMount } from 'svelte';
|
5 |
import { PUBLIC_UPLOADS } from '$env/static/public';
|
6 |
import { currZoomTransform, isPrompting, clickedPosition } from '$lib/store';
|
7 |
+
import { round } from '$lib/utils';
|
8 |
|
9 |
import { useMyPresence, useObject } from '$lib/liveblocks';
|
10 |
import type { PromptImgObject } from '$lib/types';
|
|
|
12 |
const myPresence = useMyPresence();
|
13 |
const promptImgStorage = useObject('promptImgStorage');
|
14 |
|
15 |
+
const height = 512 * 4;
|
16 |
+
const width = 512 * 4;
|
17 |
|
18 |
let canvasEl: HTMLCanvasElement;
|
19 |
export { canvasEl as value };
|
|
|
42 |
onMount(() => {
|
43 |
const scale = width / containerEl.clientWidth;
|
44 |
const zoomHandler = zoom()
|
45 |
+
.scaleExtent([1 / scale / 2, 1])
|
46 |
// .extent([
|
47 |
// [0, 0],
|
48 |
// [width, height]
|
49 |
// ])
|
50 |
.translateExtent([
|
51 |
+
[-width * 0.3, -height * 0.3],
|
52 |
+
[width * 1.3, height * 1.3]
|
53 |
])
|
54 |
.tapDistance(10)
|
55 |
.on('zoom', zoomed);
|
|
|
62 |
console.log('clicked', $clickedPosition);
|
63 |
return null;
|
64 |
})
|
65 |
+
.call(zoomHandler.scaleTo as any, 1 / scale / 1.5)
|
66 |
.on('pointermove', handlePointerMove)
|
67 |
.on('pointerleave', handlePointerLeave);
|
68 |
|
69 |
canvasCtx = canvasEl.getContext('2d') as CanvasRenderingContext2D;
|
|
|
|
|
|
|
70 |
});
|
71 |
|
72 |
function renderImages(promptImgList: PromptImgObject[]) {
|
|
|
82 |
position: { x: number; y: number };
|
83 |
id: string;
|
84 |
};
|
85 |
+
canvasCtx.drawImage(img, position.x, position.y, img.width, img.height);
|
86 |
resolve(res);
|
87 |
};
|
88 |
const url = imgURL.split('/');
|
|
|
92 |
).then((images) => {
|
93 |
images.forEach(({ img, position, id }) => {
|
94 |
// keep track of images already rendered
|
95 |
+
//re draw in order
|
96 |
imagesOnCanvas.add(id);
|
97 |
canvasCtx.drawImage(img, position.x, position.y, img.width, img.height);
|
98 |
});
|
|
|
103 |
canvasEl.style.transform = `translate(${transform.x}px, ${transform.y}px) scale(${transform.k})`;
|
104 |
}
|
105 |
|
|
|
|
|
|
|
|
|
|
|
|
|
106 |
// Update cursor presence to current pointer location
|
107 |
function handlePointerMove(event: PointerEvent) {
|
108 |
event.preventDefault();
|
109 |
+
const x = round($currZoomTransform.invertX(event.layerX));
|
110 |
+
const y = round($currZoomTransform.invertY(event.layerY));
|
111 |
+
|
|
|
|
|
|
|
112 |
myPresence.update({
|
113 |
cursor: {
|
114 |
x,
|
|
|
125 |
}
|
126 |
</script>
|
127 |
|
128 |
+
<div bind:this={containerEl} class="absolute top-0 left-0 right-0 bottom-0 overflow-hidden z-0 bg-gray-800">
|
129 |
+
<canvas bind:this={canvasEl} {width} {height} class="absolute top-0 left-0 bg-white" />
|
130 |
+
<slot />
|
131 |
</div>
|
132 |
|
133 |
<style lang="postcss" scoped>
|
frontend/src/lib/Cursor.svelte
CHANGED
@@ -3,7 +3,7 @@
|
|
3 |
|
4 |
export let transform: ZoomTransform;
|
5 |
export let color = '';
|
6 |
-
export let emoji
|
7 |
export let position = { x: 0, y: 0 };
|
8 |
|
9 |
$: coord = {
|
@@ -28,12 +28,14 @@
|
|
28 |
fill="#FFB800"
|
29 |
/>
|
30 |
</svg>
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
|
|
|
|
37 |
</div>
|
38 |
|
39 |
<style lang="postcss" scoped>
|
|
|
3 |
|
4 |
export let transform: ZoomTransform;
|
5 |
export let color = '';
|
6 |
+
export let emoji;
|
7 |
export let position = { x: 0, y: 0 };
|
8 |
|
9 |
$: coord = {
|
|
|
28 |
fill="#FFB800"
|
29 |
/>
|
30 |
</svg>
|
31 |
+
{#if emoji}
|
32 |
+
<div
|
33 |
+
class="absolute right-0 text-4xl col-start-2 row-start-2"
|
34 |
+
style={`text-shadow: 0px 5px 5px ${color}`}
|
35 |
+
>
|
36 |
+
{emoji}
|
37 |
+
</div>
|
38 |
+
{/if}
|
39 |
</div>
|
40 |
|
41 |
<style lang="postcss" scoped>
|
frontend/src/lib/Menu.svelte
CHANGED
@@ -1,5 +1,8 @@
|
|
1 |
<script lang="ts">
|
2 |
-
import { showFrames
|
|
|
|
|
|
|
3 |
</script>
|
4 |
|
5 |
<div class="grid grid-cols-4 gap-3 text-sm w-max mx-auto">
|
@@ -10,23 +13,18 @@
|
|
10 |
bind:checked={$showFrames}
|
11 |
class="w-4 h-4 text-blue-600 bg-gray-100 rounded border-gray-300 focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600 cursor-pointer"
|
12 |
/>
|
13 |
-
<label for="showframes" class="text-
|
14 |
-
|
15 |
-
>
|
16 |
-
</div>
|
17 |
-
<div class="flex items-center">
|
18 |
-
<input
|
19 |
-
id="txt2img"
|
20 |
-
type="checkbox"
|
21 |
-
bind:checked={$text2img}
|
22 |
-
class="w-4 h-4 text-blue-600 bg-gray-100 rounded border-gray-300 focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600 cursor-pointer"
|
23 |
-
/>
|
24 |
-
<label for="txt2img" class="text-black dark:text-white cursor-pointer ml-2"
|
25 |
-
>Text2Image</label
|
26 |
-
>
|
27 |
</div>
|
28 |
-
<button class="button" title="
|
29 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
30 |
<span
|
31 |
class="rounded-sm h-5 w-5 m-1 flex justify-center items-center border-2 border-dashed border-violet-700 mr-2"
|
32 |
>+</span
|
|
|
1 |
<script lang="ts">
|
2 |
+
import { showFrames } from '$lib/store';
|
3 |
+
import { createEventDispatcher } from 'svelte';
|
4 |
+
|
5 |
+
const dispatch = createEventDispatcher();
|
6 |
</script>
|
7 |
|
8 |
<div class="grid grid-cols-4 gap-3 text-sm w-max mx-auto">
|
|
|
13 |
bind:checked={$showFrames}
|
14 |
class="w-4 h-4 text-blue-600 bg-gray-100 rounded border-gray-300 focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600 cursor-pointer"
|
15 |
/>
|
16 |
+
<label for="showframes" class="text-white dark:text-white cursor-pointer ml-2">
|
17 |
+
Show Frames
|
18 |
+
</label>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
19 |
</div>
|
20 |
+
<button class="button" title="Move" on:click={() => dispatch('paintMode', { mode: 'move' })}>
|
21 |
+
Move
|
22 |
+
</button>
|
23 |
+
<button
|
24 |
+
class="button-paint bg-violet-100 text-violet-900"
|
25 |
+
title="New Paint Frame"
|
26 |
+
on:click={() => dispatch('paintMode', { mode: 'paint' })}
|
27 |
+
>
|
28 |
<span
|
29 |
class="rounded-sm h-5 w-5 m-1 flex justify-center items-center border-2 border-dashed border-violet-700 mr-2"
|
30 |
>+</span
|
frontend/src/lib/PaintFrame.svelte
ADDED
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import LoadingIcon from '$lib/LoadingIcon.svelte';
|
3 |
+
import { drag } from 'd3-drag';
|
4 |
+
import { select } from 'd3-selection';
|
5 |
+
import { round } from '$lib/utils';
|
6 |
+
|
7 |
+
import type { ZoomTransform } from 'd3-zoom';
|
8 |
+
import { onMount } from 'svelte';
|
9 |
+
|
10 |
+
export let transform: ZoomTransform;
|
11 |
+
export let color = '';
|
12 |
+
|
13 |
+
let position = {
|
14 |
+
x: transform.invertX(768),
|
15 |
+
y: transform.invertX(768)
|
16 |
+
};
|
17 |
+
export let prompt = '';
|
18 |
+
|
19 |
+
let frameElement: HTMLDivElement;
|
20 |
+
$: coord = {
|
21 |
+
x: transform.applyX(position.x),
|
22 |
+
y: transform.applyY(position.y)
|
23 |
+
};
|
24 |
+
|
25 |
+
onMount(() => {
|
26 |
+
function dragstarted(event, d) {
|
27 |
+
// d3.select(this).raise().attr('stroke', 'black');
|
28 |
+
}
|
29 |
+
|
30 |
+
function dragged(event: CustomEvent) {
|
31 |
+
console.log(event.sourceEvent.layerX);
|
32 |
+
const grid = 20;
|
33 |
+
const x = round(transform.invertX(event.x) - 512 / 2);
|
34 |
+
const y = round(transform.invertY(event.y) - 512 / 2);
|
35 |
+
position = {
|
36 |
+
x,
|
37 |
+
y
|
38 |
+
};
|
39 |
+
}
|
40 |
+
|
41 |
+
function dragended(event, d) {
|
42 |
+
// d3.select(this).attr('stroke', null);
|
43 |
+
}
|
44 |
+
|
45 |
+
select(frameElement).call(
|
46 |
+
drag().on('start', dragstarted).on('drag', dragged).on('end', dragended)
|
47 |
+
);
|
48 |
+
});
|
49 |
+
</script>
|
50 |
+
|
51 |
+
<div
|
52 |
+
bind:this={frameElement}
|
53 |
+
class="frame z-0 flex relative"
|
54 |
+
style={`transform: translateX(${coord.x}px) translateY(${coord.y}px) scale(${transform.k});
|
55 |
+
background-image: linear-gradient(${color}, rgba(255,255,255,0));
|
56 |
+
color: ${color};
|
57 |
+
`}
|
58 |
+
>
|
59 |
+
<div class="small-frame z-0 flex relative" />
|
60 |
+
<LoadingIcon />
|
61 |
+
<h2 class="text-lg">Click to paint</h2>
|
62 |
+
|
63 |
+
<div class="absolute bottom-0 font-bold">{prompt}}</div>
|
64 |
+
</div>
|
65 |
+
|
66 |
+
<style lang="postcss" scoped>
|
67 |
+
.frame {
|
68 |
+
@apply absolute top-0 left-0 border-2 border-spacing-3 border-sky-500 w-[512px] h-[512px];
|
69 |
+
transform-origin: 0 0;
|
70 |
+
}
|
71 |
+
.small-frame {
|
72 |
+
@apply pointer-events-none touch-none absolute top-1/2 left-1/2 border-2 border-spacing-3 border-sky-500 w-[256px] h-[256px];
|
73 |
+
transform: translateX(-50%) translateY(-50%);
|
74 |
+
}
|
75 |
+
</style>
|
frontend/src/lib/store.ts
CHANGED
@@ -1,16 +1,13 @@
|
|
1 |
import { writable } from 'svelte/store';
|
2 |
-
import type { Room } from '@liveblocks/client';
|
3 |
-
|
4 |
import { type ZoomTransform, zoomIdentity } from 'd3-zoom';
|
5 |
|
6 |
-
import type {Person } from "$lib/types"
|
7 |
|
8 |
export const loadingState = writable<string>('');
|
9 |
export const isLoading = writable<boolean>(false);
|
10 |
export const isPrompting = writable<boolean>(false);
|
11 |
export const clickedPosition = writable<{ x: number; y: number }>();
|
12 |
export const showFrames = writable<boolean>(false);
|
13 |
-
|
14 |
|
15 |
export const currZoomTransform = writable<ZoomTransform>(zoomIdentity);
|
16 |
|
|
|
1 |
import { writable } from 'svelte/store';
|
|
|
|
|
2 |
import { type ZoomTransform, zoomIdentity } from 'd3-zoom';
|
3 |
|
|
|
4 |
|
5 |
export const loadingState = writable<string>('');
|
6 |
export const isLoading = writable<boolean>(false);
|
7 |
export const isPrompting = writable<boolean>(false);
|
8 |
export const clickedPosition = writable<{ x: number; y: number }>();
|
9 |
export const showFrames = writable<boolean>(false);
|
10 |
+
|
11 |
|
12 |
export const currZoomTransform = writable<ZoomTransform>(zoomIdentity);
|
13 |
|
frontend/src/lib/types.ts
CHANGED
@@ -1,17 +1,13 @@
|
|
1 |
-
|
2 |
-
|
3 |
-
|
4 |
-
export interface Presence extends JsonObject {
|
5 |
cursor: {
|
6 |
x: number;
|
7 |
y: number;
|
8 |
} | null;
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
};
|
15 |
|
16 |
export type User = string;
|
17 |
|
|
|
1 |
+
export type Presence = {
|
|
|
|
|
|
|
2 |
cursor: {
|
3 |
x: number;
|
4 |
y: number;
|
5 |
} | null;
|
6 |
+
isPrompting: boolean;
|
7 |
+
isLoading: boolean;
|
8 |
+
isMoving: boolean;
|
9 |
+
currentPrompt: string
|
10 |
+
}
|
|
|
11 |
|
12 |
export type User = string;
|
13 |
|
frontend/src/lib/utils.ts
CHANGED
@@ -50,6 +50,10 @@ export async function uploadImage(imagBlob: Blob, prompt: string): string {
|
|
50 |
return url;
|
51 |
}
|
52 |
|
|
|
|
|
|
|
|
|
53 |
function slugify(text: string) {
|
54 |
if (!text) return '';
|
55 |
return text
|
@@ -60,4 +64,4 @@ function slugify(text: string) {
|
|
60 |
.replace(/\-\-+/g, '-')
|
61 |
.replace(/^-+/, '')
|
62 |
.replace(/-+$/, '');
|
63 |
-
}
|
|
|
50 |
return url;
|
51 |
}
|
52 |
|
53 |
+
export function round(pos: number, size = 32) {
|
54 |
+
return pos % size < size / 2 ? pos - (pos % size) : pos + size - (pos % size);
|
55 |
+
}
|
56 |
+
|
57 |
function slugify(text: string) {
|
58 |
if (!text) return '';
|
59 |
return text
|
|
|
64 |
.replace(/\-\-+/g, '-')
|
65 |
.replace(/^-+/, '')
|
66 |
.replace(/-+$/, '');
|
67 |
+
}
|