Spaces:
Runtime error
Runtime error
block painting on ongoing frames
Browse files
frontend/src/lib/App.svelte
CHANGED
|
@@ -5,7 +5,7 @@
|
|
| 5 |
import PaintCanvas from '$lib/PaintCanvas.svelte';
|
| 6 |
import Menu from '$lib/Menu.svelte';
|
| 7 |
import PromptModal from '$lib/PromptModal.svelte';
|
| 8 |
-
import { COLORS } from '$lib/constants';
|
| 9 |
import { PUBLIC_WS_INPAINTING } from '$env/static/public';
|
| 10 |
import type { PromptImgKey } from '$lib/types';
|
| 11 |
import { Status } from '$lib/types';
|
|
@@ -21,39 +21,72 @@
|
|
| 21 |
|
| 22 |
const myPresence = useMyPresence({ addToHistory: true });
|
| 23 |
const others = useOthers();
|
| 24 |
-
|
| 25 |
function getKey(position: { x: number; y: number }): PromptImgKey {
|
| 26 |
return `${position.x}_${position.y}`;
|
| 27 |
}
|
| 28 |
|
| 29 |
const promptImgStorage = useObject('promptImgStorage');
|
| 30 |
|
| 31 |
-
let showModal = false;
|
| 32 |
-
|
| 33 |
$: isLoading = $myPresence?.status === Status.loading || $isRenderingCanvas || false;
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
myPresence.update({
|
| 39 |
status: Status.prompting
|
| 40 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
}
|
| 42 |
}
|
| 43 |
-
function onClose() {
|
| 44 |
-
showModal = false;
|
| 45 |
-
}
|
| 46 |
|
| 47 |
function onPaint() {
|
| 48 |
-
generateImage();
|
| 49 |
showModal = false;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
}
|
| 51 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
async function generateImage() {
|
| 53 |
if (isLoading) return;
|
| 54 |
-
$loadingState = 'Pending';
|
| 55 |
const prompt = $myPresence.currentPrompt;
|
| 56 |
const position = $myPresence.frame;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
const imageKey = getKey(position);
|
| 58 |
const room = $selectedRoomID || 'default';
|
| 59 |
console.log('Generating...', prompt, position);
|
|
@@ -137,9 +170,7 @@
|
|
| 137 |
$promptImgStorage.set(imageKey, promptImgParams);
|
| 138 |
console.log(params.image.url);
|
| 139 |
$loadingState = data.success ? 'Complete' : 'Error';
|
| 140 |
-
|
| 141 |
-
$loadingState = '';
|
| 142 |
-
}, 2000);
|
| 143 |
myPresence.update({
|
| 144 |
status: Status.ready,
|
| 145 |
currentPrompt: ''
|
|
@@ -150,9 +181,7 @@
|
|
| 150 |
myPresence.update({
|
| 151 |
status: Status.ready
|
| 152 |
});
|
| 153 |
-
|
| 154 |
-
$loadingState = '';
|
| 155 |
-
}, 10000);
|
| 156 |
}
|
| 157 |
websocket.close();
|
| 158 |
return;
|
|
@@ -173,14 +202,16 @@
|
|
| 173 |
{$loadingState}
|
| 174 |
</div>
|
| 175 |
{#if showModal}
|
| 176 |
-
<PromptModal
|
|
|
|
|
|
|
|
|
|
|
|
|
| 177 |
{/if}
|
| 178 |
<div class="fixed top-0 left-0 z-0 w-screen h-screen min-h-[600px]">
|
| 179 |
<PaintCanvas />
|
| 180 |
|
| 181 |
<main class="z-10 relative">
|
| 182 |
-
<PaintFrame on:prompt={onPrompt} transform={$currZoomTransform} />
|
| 183 |
-
|
| 184 |
<!-- When others connected, iterate through others and show their cursors -->
|
| 185 |
{#if $others}
|
| 186 |
{#each [...$others] as { connectionId, presence } (connectionId)}
|
|
@@ -201,10 +232,11 @@
|
|
| 201 |
{/if}
|
| 202 |
{/each}
|
| 203 |
{/if}
|
|
|
|
| 204 |
</main>
|
| 205 |
</div>
|
| 206 |
<div class="fixed bottom-0 md:bottom-16 left-0 right-0 z-10 my-2">
|
| 207 |
-
<Menu on:
|
| 208 |
</div>
|
| 209 |
|
| 210 |
<style lang="postcss" scoped>
|
|
|
|
| 5 |
import PaintCanvas from '$lib/PaintCanvas.svelte';
|
| 6 |
import Menu from '$lib/Menu.svelte';
|
| 7 |
import PromptModal from '$lib/PromptModal.svelte';
|
| 8 |
+
import { COLORS, FRAME_SIZE } from '$lib/constants';
|
| 9 |
import { PUBLIC_WS_INPAINTING } from '$env/static/public';
|
| 10 |
import type { PromptImgKey } from '$lib/types';
|
| 11 |
import { Status } from '$lib/types';
|
|
|
|
| 21 |
|
| 22 |
const myPresence = useMyPresence({ addToHistory: true });
|
| 23 |
const others = useOthers();
|
| 24 |
+
let showModal = false;
|
| 25 |
function getKey(position: { x: number; y: number }): PromptImgKey {
|
| 26 |
return `${position.x}_${position.y}`;
|
| 27 |
}
|
| 28 |
|
| 29 |
const promptImgStorage = useObject('promptImgStorage');
|
| 30 |
|
|
|
|
|
|
|
| 31 |
$: isLoading = $myPresence?.status === Status.loading || $isRenderingCanvas || false;
|
| 32 |
+
function onShowModal(e: CustomEvent) {
|
| 33 |
+
if (isLoading) return;
|
| 34 |
+
showModal = e.detail.showModal;
|
| 35 |
+
if (showModal) {
|
| 36 |
myPresence.update({
|
| 37 |
status: Status.prompting
|
| 38 |
});
|
| 39 |
+
} else {
|
| 40 |
+
myPresence.update({
|
| 41 |
+
status: Status.ready
|
| 42 |
+
});
|
| 43 |
}
|
| 44 |
}
|
|
|
|
|
|
|
|
|
|
| 45 |
|
| 46 |
function onPaint() {
|
|
|
|
| 47 |
showModal = false;
|
| 48 |
+
generateImage();
|
| 49 |
+
}
|
| 50 |
+
function canPaint(position: { x: number; y: number }): boolean {
|
| 51 |
+
if (!$others) return true;
|
| 52 |
+
console.log('P', position);
|
| 53 |
+
let canPaint = true;
|
| 54 |
+
for (const { presence } of $others) {
|
| 55 |
+
if (
|
| 56 |
+
position.x < presence.frame.x + FRAME_SIZE &&
|
| 57 |
+
position.x + FRAME_SIZE > presence.frame.x &&
|
| 58 |
+
position.y < presence.frame.y + FRAME_SIZE &&
|
| 59 |
+
position.y + FRAME_SIZE > presence.frame.y
|
| 60 |
+
) {
|
| 61 |
+
// can paint if presence is only dragging
|
| 62 |
+
if (presence.status === Status.ready || presence.status === Status.dragging) {
|
| 63 |
+
canPaint = true;
|
| 64 |
+
}
|
| 65 |
+
canPaint = false;
|
| 66 |
+
break;
|
| 67 |
+
}
|
| 68 |
+
}
|
| 69 |
+
return canPaint;
|
| 70 |
}
|
| 71 |
|
| 72 |
+
function clearStateMsg(t = 5000) {
|
| 73 |
+
setTimeout(() => {
|
| 74 |
+
$loadingState = '';
|
| 75 |
+
}, t);
|
| 76 |
+
}
|
| 77 |
async function generateImage() {
|
| 78 |
if (isLoading) return;
|
|
|
|
| 79 |
const prompt = $myPresence.currentPrompt;
|
| 80 |
const position = $myPresence.frame;
|
| 81 |
+
$loadingState = 'Pending';
|
| 82 |
+
if (!canPaint(position)) {
|
| 83 |
+
$loadingState = 'Someone is already painting here';
|
| 84 |
+
myPresence.update({
|
| 85 |
+
status: Status.ready
|
| 86 |
+
});
|
| 87 |
+
clearStateMsg();
|
| 88 |
+
return;
|
| 89 |
+
}
|
| 90 |
const imageKey = getKey(position);
|
| 91 |
const room = $selectedRoomID || 'default';
|
| 92 |
console.log('Generating...', prompt, position);
|
|
|
|
| 170 |
$promptImgStorage.set(imageKey, promptImgParams);
|
| 171 |
console.log(params.image.url);
|
| 172 |
$loadingState = data.success ? 'Complete' : 'Error';
|
| 173 |
+
clearStateMsg();
|
|
|
|
|
|
|
| 174 |
myPresence.update({
|
| 175 |
status: Status.ready,
|
| 176 |
currentPrompt: ''
|
|
|
|
| 181 |
myPresence.update({
|
| 182 |
status: Status.ready
|
| 183 |
});
|
| 184 |
+
clearStateMsg(10000);
|
|
|
|
|
|
|
| 185 |
}
|
| 186 |
websocket.close();
|
| 187 |
return;
|
|
|
|
| 202 |
{$loadingState}
|
| 203 |
</div>
|
| 204 |
{#if showModal}
|
| 205 |
+
<PromptModal
|
| 206 |
+
on:paint={onPaint}
|
| 207 |
+
initPrompt={$myPresence?.currentPrompt}
|
| 208 |
+
on:showModal={onShowModal}
|
| 209 |
+
/>
|
| 210 |
{/if}
|
| 211 |
<div class="fixed top-0 left-0 z-0 w-screen h-screen min-h-[600px]">
|
| 212 |
<PaintCanvas />
|
| 213 |
|
| 214 |
<main class="z-10 relative">
|
|
|
|
|
|
|
| 215 |
<!-- When others connected, iterate through others and show their cursors -->
|
| 216 |
{#if $others}
|
| 217 |
{#each [...$others] as { connectionId, presence } (connectionId)}
|
|
|
|
| 232 |
{/if}
|
| 233 |
{/each}
|
| 234 |
{/if}
|
| 235 |
+
<PaintFrame transform={$currZoomTransform} on:showModal={onShowModal} />
|
| 236 |
</main>
|
| 237 |
</div>
|
| 238 |
<div class="fixed bottom-0 md:bottom-16 left-0 right-0 z-10 my-2">
|
| 239 |
+
<Menu {isLoading} on:showModal={onShowModal} />
|
| 240 |
</div>
|
| 241 |
|
| 242 |
<style lang="postcss" scoped>
|
frontend/src/lib/Menu.svelte
CHANGED
|
@@ -1,17 +1,21 @@
|
|
| 1 |
<script lang="ts">
|
| 2 |
-
import { createEventDispatcher } from 'svelte';
|
| 3 |
import RoomsSelector from '$lib/Buttons/RoomsSelector.svelte';
|
| 4 |
import AboutButton from '$lib/Buttons/AboutButton.svelte';
|
| 5 |
import { toggleAbout } from '$lib/store';
|
| 6 |
-
|
| 7 |
|
| 8 |
const dispatch = createEventDispatcher();
|
|
|
|
| 9 |
|
| 10 |
export let isLoading = false;
|
| 11 |
</script>
|
| 12 |
|
| 13 |
<svelte:window
|
| 14 |
-
on:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
/>
|
| 16 |
<div class="flex flex-col md:flex-row items-center justify-between px-4 md:px-12 gap-3 md:gap-0">
|
| 17 |
<AboutButton
|
|
@@ -21,7 +25,7 @@
|
|
| 21 |
/>
|
| 22 |
|
| 23 |
<button
|
| 24 |
-
on:click={() => dispatch('
|
| 25 |
title="Click to prompt, and paint. The generated image will show up in the frame."
|
| 26 |
disabled={isLoading}
|
| 27 |
class="{isLoading
|
|
@@ -48,7 +52,5 @@
|
|
| 48 |
Enter</span
|
| 49 |
></button
|
| 50 |
>
|
| 51 |
-
|
| 52 |
<RoomsSelector {isLoading} />
|
| 53 |
-
<!-- <PPButton {isLoading} on:click={() => dispatch('prompt')} /> -->
|
| 54 |
</div>
|
|
|
|
| 1 |
<script lang="ts">
|
|
|
|
| 2 |
import RoomsSelector from '$lib/Buttons/RoomsSelector.svelte';
|
| 3 |
import AboutButton from '$lib/Buttons/AboutButton.svelte';
|
| 4 |
import { toggleAbout } from '$lib/store';
|
| 5 |
+
import { createEventDispatcher } from 'svelte';
|
| 6 |
|
| 7 |
const dispatch = createEventDispatcher();
|
| 8 |
+
// const broadcast = useBroadcastEvent();
|
| 9 |
|
| 10 |
export let isLoading = false;
|
| 11 |
</script>
|
| 12 |
|
| 13 |
<svelte:window
|
| 14 |
+
on:keypress={(e) => {
|
| 15 |
+
if (e.key === 'Enter') {
|
| 16 |
+
dispatch('showModal', { showModal: true });
|
| 17 |
+
}
|
| 18 |
+
}}
|
| 19 |
/>
|
| 20 |
<div class="flex flex-col md:flex-row items-center justify-between px-4 md:px-12 gap-3 md:gap-0">
|
| 21 |
<AboutButton
|
|
|
|
| 25 |
/>
|
| 26 |
|
| 27 |
<button
|
| 28 |
+
on:click={() => dispatch('showModal', { showModal: true })}
|
| 29 |
title="Click to prompt, and paint. The generated image will show up in the frame."
|
| 30 |
disabled={isLoading}
|
| 31 |
class="{isLoading
|
|
|
|
| 52 |
Enter</span
|
| 53 |
></button
|
| 54 |
>
|
|
|
|
| 55 |
<RoomsSelector {isLoading} />
|
|
|
|
| 56 |
</div>
|
frontend/src/lib/PaintFrame.svelte
CHANGED
|
@@ -7,10 +7,11 @@
|
|
| 7 |
import { round } from '$lib/utils';
|
| 8 |
|
| 9 |
import type { ZoomTransform } from 'd3-zoom';
|
| 10 |
-
import { onMount
|
| 11 |
|
| 12 |
import { useMyPresence } from '$lib/liveblocks';
|
| 13 |
import { canvasEl, maskEl, loadingState, isRenderingCanvas } from '$lib/store';
|
|
|
|
| 14 |
|
| 15 |
import { Status } from './types';
|
| 16 |
const myPresence = useMyPresence({ addToHistory: true });
|
|
@@ -274,7 +275,7 @@
|
|
| 274 |
<div class="mx-4 flex flex-col gap-2">
|
| 275 |
<button
|
| 276 |
title="Click to prompt and paint"
|
| 277 |
-
on:click={() => dispatch('
|
| 278 |
class="w-10 h-10 bg-blue-600 hover:saturate-150 shadow-2xl shadow-blue-500 rounded-lg flex items-center justify-center text-3xl"
|
| 279 |
>
|
| 280 |
π
|
|
|
|
| 7 |
import { round } from '$lib/utils';
|
| 8 |
|
| 9 |
import type { ZoomTransform } from 'd3-zoom';
|
| 10 |
+
import { onMount } from 'svelte';
|
| 11 |
|
| 12 |
import { useMyPresence } from '$lib/liveblocks';
|
| 13 |
import { canvasEl, maskEl, loadingState, isRenderingCanvas } from '$lib/store';
|
| 14 |
+
import { createEventDispatcher } from 'svelte';
|
| 15 |
|
| 16 |
import { Status } from './types';
|
| 17 |
const myPresence = useMyPresence({ addToHistory: true });
|
|
|
|
| 275 |
<div class="mx-4 flex flex-col gap-2">
|
| 276 |
<button
|
| 277 |
title="Click to prompt and paint"
|
| 278 |
+
on:click={() => dispatch('showModal', { showModal: true })}
|
| 279 |
class="w-10 h-10 bg-blue-600 hover:saturate-150 shadow-2xl shadow-blue-500 rounded-lg flex items-center justify-center text-3xl"
|
| 280 |
>
|
| 281 |
π
|
frontend/src/lib/PromptModal.svelte
CHANGED
|
@@ -4,18 +4,18 @@
|
|
| 4 |
import { Status } from '$lib/types';
|
| 5 |
|
| 6 |
const dispatch = createEventDispatcher();
|
|
|
|
| 7 |
export let initPrompt = '';
|
|
|
|
| 8 |
let prompt: string;
|
| 9 |
let inputEl: HTMLInputElement;
|
| 10 |
let boxEl: HTMLDivElement;
|
| 11 |
-
const myPresence =
|
| 12 |
|
| 13 |
const onKeyup = (e: KeyboardEvent) => {
|
| 14 |
if (e.key === 'Escape') {
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
});
|
| 18 |
-
dispatch('close');
|
| 19 |
}
|
| 20 |
};
|
| 21 |
|
|
@@ -41,10 +41,13 @@
|
|
| 41 |
});
|
| 42 |
}, 100);
|
| 43 |
}
|
| 44 |
-
function onPrompt() {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
if (prompt.trim() !== '') {
|
| 46 |
dispatch('paint');
|
| 47 |
-
dispatch('close');
|
| 48 |
}
|
| 49 |
}
|
| 50 |
function onInput(event: Event) {
|
|
@@ -58,13 +61,13 @@
|
|
| 58 |
myPresence.update({
|
| 59 |
status: Status.ready
|
| 60 |
});
|
| 61 |
-
dispatch('
|
| 62 |
}
|
| 63 |
</script>
|
| 64 |
|
| 65 |
<form
|
| 66 |
class="fixed w-screen top-0 left-0 bottom-0 right-0 h-screen z-50 flex items-center justify-center bg-black bg-opacity-80"
|
| 67 |
-
on:submit
|
| 68 |
>
|
| 69 |
<div
|
| 70 |
class="flex bg-white overflow-hidden rounded-2xl w-full max-w-lg 2xl:max-w-xl"
|
|
|
|
| 4 |
import { Status } from '$lib/types';
|
| 5 |
|
| 6 |
const dispatch = createEventDispatcher();
|
| 7 |
+
|
| 8 |
export let initPrompt = '';
|
| 9 |
+
|
| 10 |
let prompt: string;
|
| 11 |
let inputEl: HTMLInputElement;
|
| 12 |
let boxEl: HTMLDivElement;
|
| 13 |
+
const myPresence = useMyPresence({ addToHistory: true });
|
| 14 |
|
| 15 |
const onKeyup = (e: KeyboardEvent) => {
|
| 16 |
if (e.key === 'Escape') {
|
| 17 |
+
dispatch('showModal', { showModal: false });
|
| 18 |
+
console.log('Escape');
|
|
|
|
|
|
|
| 19 |
}
|
| 20 |
};
|
| 21 |
|
|
|
|
| 41 |
});
|
| 42 |
}, 100);
|
| 43 |
}
|
| 44 |
+
function onPrompt(event: Event) {
|
| 45 |
+
event.stopPropagation();
|
| 46 |
+
event.preventDefault();
|
| 47 |
+
event.stopImmediatePropagation();
|
| 48 |
+
|
| 49 |
if (prompt.trim() !== '') {
|
| 50 |
dispatch('paint');
|
|
|
|
| 51 |
}
|
| 52 |
}
|
| 53 |
function onInput(event: Event) {
|
|
|
|
| 61 |
myPresence.update({
|
| 62 |
status: Status.ready
|
| 63 |
});
|
| 64 |
+
dispatch('showModal', { showModal: false });
|
| 65 |
}
|
| 66 |
</script>
|
| 67 |
|
| 68 |
<form
|
| 69 |
class="fixed w-screen top-0 left-0 bottom-0 right-0 h-screen z-50 flex items-center justify-center bg-black bg-opacity-80"
|
| 70 |
+
on:submit={onPrompt}
|
| 71 |
>
|
| 72 |
<div
|
| 73 |
class="flex bg-white overflow-hidden rounded-2xl w-full max-w-lg 2xl:max-w-xl"
|
frontend/src/lib/liveblocks/useRooms.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { writable, type Writable } from "svelte/store";
|
|
| 3 |
import type { RoomResponse } from '$lib/types';
|
| 4 |
import { PUBLIC_API_BASE } from '$env/static/public';
|
| 5 |
|
| 6 |
-
const INTERVAL =
|
| 7 |
|
| 8 |
export function useRooms(): Writable<RoomResponse[]> {
|
| 9 |
const roomsStorage = writable<RoomResponse[]>([]);
|
|
|
|
| 3 |
import type { RoomResponse } from '$lib/types';
|
| 4 |
import { PUBLIC_API_BASE } from '$env/static/public';
|
| 5 |
|
| 6 |
+
const INTERVAL = 10000
|
| 7 |
|
| 8 |
export function useRooms(): Writable<RoomResponse[]> {
|
| 9 |
const roomsStorage = writable<RoomResponse[]>([]);
|
frontend/src/lib/store.ts
CHANGED
|
@@ -7,4 +7,5 @@ export const canvasEl = writable<HTMLCanvasElement>();
|
|
| 7 |
export const maskEl = writable<HTMLCanvasElement>();
|
| 8 |
export const selectedRoomID = writable<string | null>();
|
| 9 |
export const toggleAbout = writable<boolean>(false);
|
| 10 |
-
export const isRenderingCanvas = writable<boolean>(true);
|
|
|
|
|
|
| 7 |
export const maskEl = writable<HTMLCanvasElement>();
|
| 8 |
export const selectedRoomID = writable<string | null>();
|
| 9 |
export const toggleAbout = writable<boolean>(false);
|
| 10 |
+
export const isRenderingCanvas = writable<boolean>(true);
|
| 11 |
+
export const showModal = writable<boolean>(false);
|
frontend/src/lib/types.ts
CHANGED
|
@@ -15,7 +15,7 @@ export type Presence = {
|
|
| 15 |
frame: {
|
| 16 |
x: number;
|
| 17 |
y: number;
|
| 18 |
-
}
|
| 19 |
status: Status;
|
| 20 |
currentPrompt: string
|
| 21 |
}
|
|
|
|
| 15 |
frame: {
|
| 16 |
x: number;
|
| 17 |
y: number;
|
| 18 |
+
};
|
| 19 |
status: Status;
|
| 20 |
currentPrompt: string
|
| 21 |
}
|