radames commited on
Commit
5636b7a
1 Parent(s): d6e3c15

design improvments

Browse files
frontend/src/app.html CHANGED
@@ -1,13 +1,16 @@
1
  <!DOCTYPE html>
2
  <html lang="en">
3
- <head>
4
- <meta charset="utf-8" />
5
- <link rel="icon" href="%sveltekit.assets%/favicon.png" />
6
- <meta name="viewport" content="width=device-width" />
7
- %sveltekit.head%
8
- </head>
9
- <!-- <body class="dark:bg-[rgb(11,15,25)] bg-white dark:text-white text-black"> -->
10
- <body>
11
- <div>%sveltekit.body%</div>
12
- </body>
13
- </html>
 
 
 
 
1
  <!DOCTYPE html>
2
  <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="utf-8" />
6
+ <link rel="icon" href="%sveltekit.assets%/favicon.png" />
7
+ <meta name="viewport" content="width=device-width" />
8
+ %sveltekit.head%
9
+ </head>
10
+ <!-- <body class="dark:bg-[rgb(11,15,25)] bg-white dark:text-white text-black"> -->
11
+
12
+ <body>
13
+ <div>%sveltekit.body%</div>
14
+ </body>
15
+
16
+ </html>
frontend/src/lib/App.svelte CHANGED
@@ -8,12 +8,10 @@
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
-
15
  import { base64ToBlob, uploadImage } from '$lib/utils';
16
-
17
  import { nanoid } from 'nanoid';
18
 
19
  /**
@@ -28,8 +26,7 @@
28
  const initialPresence: Presence = {
29
  cursor: null,
30
  frame: null,
31
- isPrompting: false,
32
- isLoading: false,
33
  currentPrompt: ''
34
  };
35
  myPresence.update(initialPresence);
@@ -42,15 +39,17 @@
42
 
43
  let showModal = false;
44
 
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) {
 
 
51
  showModal = true;
52
  myPresence.update({
53
- isPrompting: true
54
  });
55
  }
56
  }
@@ -58,31 +57,12 @@
58
  showModal = false;
59
  }
60
 
61
- function onPrompt() {
62
- console.log('onPrompt');
63
  generateImage();
64
  showModal = false;
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
- }
86
  async function generateImage() {
87
  if (isLoading) return;
88
  $loadingState = 'Pending';
@@ -90,13 +70,14 @@
90
  const position = $myPresence.frame;
91
  console.log('Generating...', prompt, position);
92
  myPresence.update({
93
- isPrompting: true,
94
- isLoading: true
95
  });
96
  const sessionHash = crypto.randomUUID();
 
 
97
  const payload = {
98
  fn_index: 0,
99
- data: [getImageCrop(position), prompt, 0.75, 7.5, 40, 'patchmatch'],
100
  session_hash: sessionHash
101
  };
102
  console.log('payload', payload);
@@ -109,8 +90,7 @@
109
  if (!evt.wasClean) {
110
  $loadingState = 'Error';
111
  myPresence.update({
112
- isPrompting: false,
113
- isLoading: false
114
  });
115
  }
116
  };
@@ -127,8 +107,7 @@
127
  $loadingState = 'Queue full';
128
  websocket.close();
129
  myPresence.update({
130
- isPrompting: false,
131
- isLoading: false
132
  });
133
  return;
134
  case 'estimation':
@@ -167,8 +146,7 @@
167
  }
168
  websocket.close();
169
  myPresence.update({
170
- isPrompting: false,
171
- isLoading: false
172
  });
173
  return;
174
  case 'process_starts':
@@ -188,22 +166,18 @@
188
  {$loadingState}
189
  </div>
190
  {#if showModal}
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
198
- on:paintMode={onPaintMode}
199
- transform={$currZoomTransform}
200
- interactive={!isPrompting}
201
- />
202
 
203
  <!-- When others connected, iterate through others and show their cursors -->
204
  {#if $others}
205
  {#each [...$others] as { connectionId, presence } (connectionId)}
206
- {#if presence?.isPrompting && presence?.frame}
207
  <Frame
208
  color={COLORS[1 + (connectionId % (COLORS.length - 1))]}
209
  position={presence?.frame}
@@ -225,7 +199,7 @@
225
  </div>
226
 
227
  <div class="fixed bottom-0 left-0 right-0 z-10 my-2">
228
- <Menu on:paintMode={onPaintMode} />
229
  </div>
230
 
231
  <style lang="postcss" scoped>
 
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 { Status } from '$lib/types';
12
  import { loadingState, currZoomTransform, maskEl } from '$lib/store';
 
13
  import { useMyPresence, useObject, useOthers } from '$lib/liveblocks';
 
14
  import { base64ToBlob, uploadImage } from '$lib/utils';
 
15
  import { nanoid } from 'nanoid';
16
 
17
  /**
 
26
  const initialPresence: Presence = {
27
  cursor: null,
28
  frame: null,
29
+ status: Status.dragging,
 
30
  currentPrompt: ''
31
  };
32
  myPresence.update(initialPresence);
 
39
 
40
  let showModal = false;
41
 
42
+ $: isPrompting = $myPresence?.status === Status.prompting || false;
43
+ $: isLoading = $myPresence?.status === Status.loading || false;
44
 
45
+ $: {
46
+ console.log($myPresence.status);
47
+ }
48
+ function onPrompt() {
49
+ if (!isLoading) {
50
  showModal = true;
51
  myPresence.update({
52
+ status: Status.prompting
53
  });
54
  }
55
  }
 
57
  showModal = false;
58
  }
59
 
60
+ function onPaint() {
61
+ console.log('onPaint');
62
  generateImage();
63
  showModal = false;
64
  }
65
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  async function generateImage() {
67
  if (isLoading) return;
68
  $loadingState = 'Pending';
 
70
  const position = $myPresence.frame;
71
  console.log('Generating...', prompt, position);
72
  myPresence.update({
73
+ status: Status.loading
 
74
  });
75
  const sessionHash = crypto.randomUUID();
76
+ const base64Crop = $maskEl.toDataURL('image/png');
77
+
78
  const payload = {
79
  fn_index: 0,
80
+ data: [base64Crop, prompt, 0.75, 7.5, 40, 'patchmatch'],
81
  session_hash: sessionHash
82
  };
83
  console.log('payload', payload);
 
90
  if (!evt.wasClean) {
91
  $loadingState = 'Error';
92
  myPresence.update({
93
+ status: Status.ready
 
94
  });
95
  }
96
  };
 
107
  $loadingState = 'Queue full';
108
  websocket.close();
109
  myPresence.update({
110
+ status: Status.ready
 
111
  });
112
  return;
113
  case 'estimation':
 
146
  }
147
  websocket.close();
148
  myPresence.update({
149
+ status: Status.ready
 
150
  });
151
  return;
152
  case 'process_starts':
 
166
  {$loadingState}
167
  </div>
168
  {#if showModal}
169
+ <PromptModal on:paint={onPaint} on:close={onClose} />
170
  {/if}
171
  <div class="fixed top-0 left-0 z-0 w-screen h-screen">
172
  <PaintCanvas />
173
 
174
  <main class="z-10 relative">
175
+ <PaintFrame on:prompt={onPrompt} transform={$currZoomTransform} />
 
 
 
 
176
 
177
  <!-- When others connected, iterate through others and show their cursors -->
178
  {#if $others}
179
  {#each [...$others] as { connectionId, presence } (connectionId)}
180
+ {#if (presence?.status === Status.prompting || presence?.status === Status.masking) && presence?.frame}
181
  <Frame
182
  color={COLORS[1 + (connectionId % (COLORS.length - 1))]}
183
  position={presence?.frame}
 
199
  </div>
200
 
201
  <div class="fixed bottom-0 left-0 right-0 z-10 my-2">
202
+ <Menu on:prompt={onPrompt} />
203
  </div>
204
 
205
  <style lang="postcss" scoped>
frontend/src/lib/Frame.svelte CHANGED
@@ -33,7 +33,7 @@
33
  </div>
34
  {/if}
35
 
36
- <h2 class="text-lg">Click to paint</h2>
37
  <div class="absolute bottom-0 font-bold text-lg">{prompt}</div>
38
  </div>
39
  </div>
 
33
  </div>
34
  {/if}
35
 
36
+ <h2 class="text-lg"></h2>
37
  <div class="absolute bottom-0 font-bold text-lg">{prompt}</div>
38
  </div>
39
  </div>
frontend/src/lib/Icons/LoadingIcon.svelte CHANGED
@@ -1,12 +1,17 @@
1
  <svg
2
- xmlns="http://www.w3.org/2000/svg"
 
 
 
3
  fill="none"
4
- width="50"
5
- viewBox="0 0 24 24"
6
- class="animate-spin block w-full opacity-60"
7
  >
8
  <path
9
- fill="currentColor"
10
- d="M20 12a8 8 0 0 1-8 8v4a12 12 0 0 0 12-12h-4Zm-2-5.3a8 8 0 0 1 2 5.3h4c0-3-1.1-5.8-3-8l-3 2.7Z"
 
 
 
 
11
  />
12
  </svg>
 
1
  <svg
2
+ class="animate-spin opacity-60"
3
+ width="51"
4
+ height="51"
5
+ viewBox="0 0 21 21"
6
  fill="none"
7
+ xmlns="http://www.w3.org/2000/svg"
 
 
8
  >
9
  <path
10
+ d="M21 10.5C21 4.70101 16.299 0 10.5 0C4.70101 0 0 4.70101 0 10.5C0 16.299 4.70101 21 10.5 21C16.299 21 21 16.299 21 10.5Z"
11
+ fill="white"
12
+ />
13
+ <path
14
+ d="M10.5006 17C9.6233 17 8.77136 16.8286 7.97021 16.4896C7.19572 16.1621 6.50122 15.6924 5.90448 15.0957C5.30774 14.499 4.83797 13.8046 4.5104 13.0302C4.1714 12.2291 4 11.3772 4 10.5C4 10.2474 4.20441 10.043 4.45708 10.043C4.70974 10.043 4.91415 10.2474 4.91415 10.5C4.91415 11.2541 5.06143 11.9854 5.35345 12.6747C5.63532 13.3399 6.0378 13.9379 6.55074 14.4508C7.06368 14.9637 7.66169 15.3674 8.32698 15.6479C9.01514 15.9387 9.74646 16.0859 10.5006 16.0859C11.2548 16.0859 11.9861 15.9387 12.6756 15.6467C13.3409 15.3648 13.9389 14.9624 14.4518 14.4495C14.9647 13.9366 15.3685 13.3387 15.6491 12.6734C15.9398 11.9854 16.0871 11.2541 16.0871 10.5C16.0871 9.7459 15.9398 9.01465 15.6478 8.32529C15.3669 7.66166 14.9604 7.05857 14.4505 6.54922C13.9417 6.03876 13.3384 5.63215 12.6743 5.35205C11.9861 5.06133 11.2548 4.91406 10.5006 4.91406C10.248 4.91406 10.0436 4.70967 10.0436 4.45703C10.0436 4.20439 10.248 4 10.5006 4C11.378 4 12.2299 4.17139 13.0311 4.51035C13.8055 4.83789 14.5 5.30762 15.0968 5.9043C15.6935 6.50098 16.162 7.19668 16.4896 7.96982C16.8286 8.7709 17 9.62275 17 10.5C17 11.3772 16.8286 12.2291 16.4896 13.0302C16.1633 13.8046 15.6935 14.499 15.0968 15.0957C14.5 15.6924 13.8043 16.1608 13.0311 16.4884C12.2299 16.8286 11.378 17 10.5006 17Z"
15
+ fill="#1F2937"
16
  />
17
  </svg>
frontend/src/lib/Menu.svelte CHANGED
@@ -5,7 +5,7 @@
5
 
6
  const onKeyup = (e: KeyboardEvent) => {
7
  if (e.key === 'Enter') {
8
- dispatch('paintMode', { mode: 'paint' });
9
  }
10
  };
11
  onMount(() => {
@@ -17,19 +17,5 @@
17
  </script>
18
 
19
  <div class="grid grid-cols-1 gap-3 w-max mx-auto">
20
- <!-- <div class="flex items-center">
21
- <input
22
- id="showframes"
23
- type="checkbox"
24
- bind:checked={$showFrames}
25
- 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"
26
- />
27
- <label for="showframes" class="text-white dark:text-white cursor-pointer ml-2">
28
- Show Frames
29
- </label>
30
- </div> -->
31
- <!-- <button class="button" title="Move" on:click={() => dispatch('paintMode', { mode: 'move' })}>
32
- Move
33
- </button> -->
34
- <PPButton on:click={() => dispatch('paintMode', { mode: 'paint' })} />
35
  </div>
 
5
 
6
  const onKeyup = (e: KeyboardEvent) => {
7
  if (e.key === 'Enter') {
8
+ dispatch('prompt');
9
  }
10
  };
11
  onMount(() => {
 
17
  </script>
18
 
19
  <div class="grid grid-cols-1 gap-3 w-max mx-auto">
20
+ <PPButton on:click={() => dispatch('prompt')} />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  </div>
frontend/src/lib/PaintFrame.svelte CHANGED
@@ -16,13 +16,13 @@
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();
22
 
23
  export let transform: ZoomTransform;
24
  export let color = 'black';
25
- export let interactive = false;
26
 
27
  let maskCtx: CanvasRenderingContext2D;
28
 
@@ -35,7 +35,7 @@
35
  let dragEnabled = true;
36
  let isDragging = false;
37
  $: prompt = $myPresence?.currentPrompt;
38
- $: isLoading = $myPresence?.isLoading || false;
39
 
40
  $: coord = {
41
  x: transform.applyX(position.x),
@@ -52,25 +52,30 @@
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: {
@@ -87,21 +92,24 @@
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() {
@@ -151,25 +159,30 @@
151
  }
152
  function toggleDrag() {
153
  dragEnabled = true;
 
 
 
154
  }
155
- function toggleMask() {
156
  dragEnabled = false;
157
  cropCanvas(position);
 
 
 
 
 
 
 
158
  }
159
  </script>
160
 
161
  <div>
162
  <div
163
- class="absolute top-0 left-0"
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">
@@ -182,23 +195,23 @@
182
  </div>
183
  {/if}
184
 
185
- <h2 class="text-lg">Click to paint</h2>
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>
@@ -208,8 +221,7 @@
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
  />
@@ -217,6 +229,16 @@
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>
 
16
  import { loadingState, canvasEl, maskEl } from '$lib/store';
17
 
18
  import { toggle_class } from 'svelte/internal';
19
+ import { Status } from './types';
20
  const myPresence = useMyPresence();
21
 
22
  const dispatch = createEventDispatcher();
23
 
24
  export let transform: ZoomTransform;
25
  export let color = 'black';
 
26
 
27
  let maskCtx: CanvasRenderingContext2D;
28
 
 
35
  let dragEnabled = true;
36
  let isDragging = false;
37
  $: prompt = $myPresence?.currentPrompt;
38
+ $: isLoading = $myPresence?.status === Status.loading || false;
39
 
40
  $: coord = {
41
  x: transform.applyX(position.x),
 
52
  maskCtx.drawImage($canvasEl, cursor.x, cursor.y, 512, 512, 0, 0, 512, 512);
53
  maskCtx.restore();
54
  }
55
+ function drawLine(points: { x: number; y: number; lastx: number; lasty: number }) {
56
  maskCtx.save();
57
  maskCtx.globalCompositeOperation = 'destination-out';
58
  maskCtx.beginPath();
59
+ maskCtx.moveTo(points.lastx, points.lasty);
60
+ maskCtx.lineTo(points.x, points.y);
61
+ maskCtx.lineWidth = 50;
62
+ maskCtx.lineCap = 'round';
63
+ maskCtx.strokeStyle = 'black';
64
+ maskCtx.stroke();
65
  maskCtx.restore();
66
  }
 
67
  onMount(() => {
68
  maskCtx = $maskEl.getContext('2d') as CanvasRenderingContext2D;
69
 
70
  select(frameElement)
71
  .call(dragMoveHandler() as any)
72
  .call(cursorUpdate);
73
+ select($maskEl)
74
+ .call(maskingHandler() as any)
75
+ .call(cursorUpdate);
76
  });
77
 
78
+ function cursorUpdate(selection) {
79
  function handlePointerMove(event: PointerEvent) {
80
  myPresence.update({
81
  cursor: {
 
92
  return selection.on('pointermove', handlePointerMove).on('pointerleave', handlePointerLeave);
93
  }
94
  function maskingHandler() {
95
+ let lastx: number;
96
+ let lasty: number;
97
  function dragstarted(event: Event) {
98
  const x = event.x / transform.k;
99
  const y = event.y / transform.k;
100
+ lastx = x;
101
+ lasty = y;
102
  }
103
 
104
  function dragged(event: Event) {
105
  const x = event.x / transform.k;
106
  const y = event.y / transform.k;
107
+ drawLine({ x, y, lastx, lasty });
108
+ lastx = x;
109
+ lasty = y;
110
  }
111
 
112
+ function dragended(event: Event) {}
 
 
 
113
  return drag().on('start', dragstarted).on('drag', dragged).on('end', dragended);
114
  }
115
  function dragMoveHandler() {
 
159
  }
160
  function toggleDrag() {
161
  dragEnabled = true;
162
+ myPresence.update({
163
+ status: Status.dragging
164
+ });
165
  }
166
+ function toggleDrawMask() {
167
  dragEnabled = false;
168
  cropCanvas(position);
169
+ myPresence.update({
170
+ status: Status.masking
171
+ });
172
+ }
173
+
174
+ function cleanMask() {
175
+ cropCanvas(position);
176
  }
177
  </script>
178
 
179
  <div>
180
  <div
181
+ class="absolute top-0 left-0 pen"
182
+ style={`transform: translateX(${coord.x}px) translateY(${coord.y}px) scale(${transform.k}); transform-origin: 0 0;`}
183
  >
184
  <div class="frame">
185
+ <canvas class={dragEnabled ? '' : 'bg-white'} bind:this={$maskEl} width="512" height="512" />
 
 
 
 
 
186
  <div class="pointer-events-none touch-none">
187
  {#if $loadingState}
188
  <div class="col-span-2 row-start-1">
 
195
  </div>
196
  {/if}
197
 
198
+ <h2 class="text-lg" />
199
  <div class="absolute bottom-0 font-bold text-lg">{prompt}</div>
200
  </div>
201
  {#if !isDragging}
202
  <div class="absolute top-full ">
203
  <div class="py-2">
204
+ <PPButton on:click={() => dispatch('prompt')} />
205
  </div>
206
  </div>
207
  <div class="absolute left-full bottom-0">
208
  <div class="px-2">
209
  <DragButton isActive={dragEnabled} on:click={toggleDrag} />
210
  <div class="flex bg-white rounded-full mt-3">
211
+ <MaskButton isActive={!dragEnabled} on:click={toggleDrawMask} />
212
  {#if !dragEnabled}
213
  <span class="border-gray-800 border-opacity-50 border-r-2 my-2" />
214
+ <UndoButton on:click={cleanMask} />
215
  {/if}
216
  </div>
217
  </div>
 
221
  </div>
222
  <div
223
  bind:this={frameElement}
224
+ class="absolute top-0 left-0 w-[512px] h-[512px] hand
 
225
  {dragEnabled ? 'block' : 'hidden'}"
226
  style={`transform: translateX(${coord.x}px) translateY(${coord.y}px) scale(${transform.k}); transform-origin: 0 0;`}
227
  />
 
229
 
230
  <style lang="postcss" scoped>
231
  .frame {
232
+ @apply relative grid grid-cols-3 grid-rows-3 ring-8 ring-blue-500 w-[512px] h-[512px];
233
+ }
234
+ .hand {
235
+ cursor: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAAUCAYAAABvVQZ0AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAHSSURBVHgBzVQ9LENRFD4VozZWP4ku/jqJAYNoEQaWNpEwIYJFBINY6UCn6mKTKhYNSTuwVFBiaEgai9bP4CWKUftqv845vU+k7evr2C/5cu6799zvnHvOvQ+gUmEqNimEsKLZQ3YgA0j6TiNXeJPJlIZygEK1yFAmo4rj0Kkg0Jj4DyHyMxKyIt/I+zH5LJrau8V76lPMLa6KjU2vyKhZsbHl1YTX8/dX5X071eyPdX5xDRrr68BiNsNJ+AxsrS1sCf6DIEQub2hoNxJjxO7ivHnMNZqzzlHAIJBIvkBPV6cm7JC11RULWMw1LELRhwf6IPXxxSSRyMU1ztk5mKpmyX9aV0x2KUoitMHW1sxHjd3HWYQyGh7sY1+Z3ZTRMfcpCxLxHwZhZnIc63TEC3TU3iEXj2XdqGGOomKyBhxNq1fi6ZVF3J5tyK+rPGqHXmZX6OAgR61eVCc9UBDE332rzlu3uj0+WRs7GKGxoY5MWi8zZWZygp1KZUSg6yIR1RNzYQeV2/MQLC/MQqmM5HoYb8CDNl/w0GUTlpFLVDPfzi5myZ0DW3szX5Ex5whYLGYFp/pRTAEjyHcaFoX4RvqKPXRTOaJoHJDrmoKMlv0Lqhj8AlEeE/77ZUZMAAAAAElFTkSuQmCC')
236
+ 8 8,
237
+ pointer;
238
+ }
239
+ .pen {
240
+ cursor: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAExSURBVHgBnZJBjkRQEIZLt45YSIwTsLUQbuAqc4LpG3AUO9ueE5idpSFhSyKxsWCBDfKmyozZtcafVLz38r6/Sr0COCDGmEwBZ4TgB0bDfuUchvM8Z7ZtM1VVGa13m9BFuh1FEZNlmdERmfxJvryCi6JwNU2DOI4hCAJAkyVIHMe1mzCVStloS+F53lJJ0yytcA/BFKZprqXfT8FPM/u+r4ZhGJRl2ZyCkyRp0jRlfd8z13X3wyTs7oPg1YDeejeM4ghcN7quAz0ZgoC/AY7jAM/zrSRJb88M+Ov12s7zLN9ut+UAewDjOEJd18u3qqrN2eeHYfgWBMGmy1mWARnRetU0TZ9bBhecpnes4H+iVhj7AaIotoZh3DcNLMsq0MDquu6xHpKhoihf2A8LExRbBj/Uih2c7AwcBQAAAABJRU5ErkJggg==')
241
+ 8 8,
242
+ pointer;
243
  }
244
  </style>
frontend/src/lib/PromptModal.svelte CHANGED
@@ -1,6 +1,7 @@
1
  <script lang="ts">
2
  import { createEventDispatcher, onMount } from 'svelte';
3
  import { useMyPresence } from '$lib/liveblocks';
 
4
 
5
  const dispatch = createEventDispatcher();
6
  let prompt = '';
@@ -28,14 +29,14 @@
28
  prompt = newPrompt;
29
  myPresence.update({
30
  currentPrompt: prompt,
31
- isPrompting: true
32
  });
33
  }, 100);
34
  }
35
  function onPrompt() {
36
  if (prompt.trim() !== '') {
37
  console.log('Prompting with: ', prompt);
38
- dispatch('prompt');
39
  }
40
  }
41
  function onInput(event: Event) {
@@ -45,7 +46,7 @@
45
  function cancel() {
46
  myPresence.update({
47
  currentPrompt: '',
48
- isPrompting: false
49
  });
50
  dispatch('close');
51
  }
@@ -56,20 +57,25 @@
56
  on:submit|preventDefault={onPrompt}
57
  on:click={cancel}
58
  >
59
- <input
60
- bind:this={inputEl}
61
- on:click|stopPropagation
62
- on:input={onInput}
63
- class="input"
64
- placeholder="Type a prompt..."
65
- title="Input prompt to generate image and obtain palette"
66
- type="text"
67
- name="prompt"
68
- />
 
 
 
 
 
69
  </form>
70
 
71
  <style lang="postcss" scoped>
72
  .input {
73
- @apply w-full max-w-sm text-sm disabled:opacity-50 italic placeholder:text-white text-white placeholder:text-opacity-50 bg-slate-900 border-2 border-white rounded-2xl px-2 shadow-sm focus:outline-none focus:border-gray-400 focus:ring-1;
74
  }
75
  </style>
 
1
  <script lang="ts">
2
  import { createEventDispatcher, onMount } from 'svelte';
3
  import { useMyPresence } from '$lib/liveblocks';
4
+ import { Status } from '$lib/types';
5
 
6
  const dispatch = createEventDispatcher();
7
  let prompt = '';
 
29
  prompt = newPrompt;
30
  myPresence.update({
31
  currentPrompt: prompt,
32
+ status: Status.prompting
33
  });
34
  }, 100);
35
  }
36
  function onPrompt() {
37
  if (prompt.trim() !== '') {
38
  console.log('Prompting with: ', prompt);
39
+ dispatch('paint');
40
  }
41
  }
42
  function onInput(event: Event) {
 
46
  function cancel() {
47
  myPresence.update({
48
  currentPrompt: '',
49
+ status: Status.ready
50
  });
51
  dispatch('close');
52
  }
 
57
  on:submit|preventDefault={onPrompt}
58
  on:click={cancel}
59
  >
60
+ <div class="flex bg-white rounded-2xl px-2 w-full max-w-md">
61
+ <input
62
+ bind:this={inputEl}
63
+ on:click|stopPropagation
64
+ on:input={onInput}
65
+ class="input"
66
+ placeholder="Type a prompt..."
67
+ title="Input prompt to generate image and obtain palette"
68
+ type="text"
69
+ name="prompt"
70
+ />
71
+ <button on:click|preventDefault={onPrompt} class="font-mono border-l-2 pl-2" type="submit"
72
+ >Paint</button
73
+ >
74
+ </div>
75
  </form>
76
 
77
  <style lang="postcss" scoped>
78
  .input {
79
+ @apply flex-grow text-sm m-2 p-0 disabled:opacity-50 italic placeholder:text-black text-black placeholder:text-opacity-50 border-0 focus:outline-none focus:border-gray-400 focus:ring-1;
80
  }
81
  </style>
frontend/src/lib/types.ts CHANGED
@@ -1,3 +1,12 @@
 
 
 
 
 
 
 
 
 
1
  export type Presence = {
2
  cursor: {
3
  x: number;
@@ -7,8 +16,7 @@ export type Presence = {
7
  x: number;
8
  y: number;
9
  } | null;
10
- isPrompting: boolean;
11
- isLoading: boolean;
12
  currentPrompt: string
13
  }
14
 
@@ -26,3 +34,4 @@ export type PromptImgObject = {
26
  };
27
 
28
  export type PromptImgKey = string;
 
 
1
+ export enum Status {
2
+ ready = 'ready',
3
+ loading = 'loading',
4
+ prompting = 'prompting',
5
+ processing = 'processing',
6
+ dragging = 'dragging',
7
+ masking = 'masking',
8
+ }
9
+
10
  export type Presence = {
11
  cursor: {
12
  x: number;
 
16
  x: number;
17
  y: number;
18
  } | null;
19
+ status: Status;
 
20
  currentPrompt: string
21
  }
22
 
 
34
  };
35
 
36
  export type PromptImgKey = string;
37
+
frontend/src/routes/+page.svelte CHANGED
@@ -21,6 +21,8 @@
21
  let client: Client;
22
 
23
  onMount(() => {
 
 
24
  // Add random id to room param if not set, and return the id string
25
  // e.g. /?room=758df70b5e94c13289df6
26
  roomId = 'multiplayer-SD';
 
21
  let client: Client;
22
 
23
  onMount(() => {
24
+ document.addEventListener('wheel', (e) => e.preventDefault(), { passive: false });
25
+
26
  // Add random id to room param if not set, and return the id string
27
  // e.g. /?room=758df70b5e94c13289df6
28
  roomId = 'multiplayer-SD';