radames commited on
Commit
be26971
1 Parent(s): 1158d44

drag frame

Browse files
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 { onMount } from 'svelte';
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
- myPresence.update({
37
- name: '',
38
  cursor: null,
39
  isPrompting: false,
 
 
40
  currentPrompt: ''
41
- });
42
- $: {
43
- console.log($others)
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 * 5;
15
- const width = 512 * 5;
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 / 1.5, 1])
45
  // .extent([
46
  // [0, 0],
47
  // [width, height]
48
  // ])
49
  .translateExtent([
50
- [-width * 0.1, -height * 0.1],
51
- [width * 1.1, height * 1.1]
52
  ])
53
  .tapDistance(10)
54
  .on('zoom', zoomed);
@@ -61,14 +62,11 @@
61
  console.log('clicked', $clickedPosition);
62
  return null;
63
  })
64
- // .call(zoomHandler.scaleTo as any, 1 / scale)
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 = Math.round($currZoomTransform.invertX(event.layerX) / grid) * grid;
116
- const y = Math.round($currZoomTransform.invertY(event.layerY) / grid) * grid;
117
- // const x = Math.round(event.layerX / grid) * grid; //round(Math.max(r, Math.min(512 * 5 - r, event.clientX)), 100);
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
- <div
32
- class="absolute right-0 text-4xl col-start-2 row-start-2"
33
- style={`text-shadow: 0px 5px 5px ${color}`}
34
- >
35
- {emoji}
36
- </div>
 
 
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, text2img } from '$lib/store';
 
 
 
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-black dark:text-white cursor-pointer ml-2"
14
- >Show Frames</label
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="Add Prompt"> Add Prompt </button>
29
- <button class="button-paint bg-violet-100 text-violet-900" title="New Paint Frame">
 
 
 
 
 
 
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
- export const text2img = writable<boolean>(false);
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
- import type { JsonObject } from "@liveblocks/client";
2
-
3
-
4
- export interface Presence extends JsonObject {
5
  cursor: {
6
  x: number;
7
  y: number;
8
  } | null;
9
- };
10
-
11
- export type Storage = {
12
- // animals: LiveList<string>,
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
+ }