Spaces:
Runtime error
Runtime error
use hooks
Browse files- Makefile +0 -2
- frontend/package.json +2 -1
- frontend/src/lib/App.svelte +49 -20
- frontend/src/lib/Canvas.svelte +38 -19
- frontend/src/lib/Frame.svelte +2 -1
- frontend/src/lib/liveblocks/LiveblocksProvider.svelte +19 -0
- frontend/src/lib/liveblocks/RoomProvider.svelte +30 -0
- frontend/src/lib/liveblocks/index.ts +18 -0
- frontend/src/lib/liveblocks/symbols.ts +3 -0
- frontend/src/lib/liveblocks/useBatch.ts +14 -0
- frontend/src/lib/liveblocks/useHistory.ts +12 -0
- frontend/src/lib/liveblocks/useList.ts +46 -0
- frontend/src/lib/liveblocks/useMyPresence.ts +42 -0
- frontend/src/lib/liveblocks/useObject.ts +43 -0
- frontend/src/lib/liveblocks/useOthers.ts +28 -0
- frontend/src/lib/liveblocks/useRedo.ts +12 -0
- frontend/src/lib/liveblocks/useRoom.ts +21 -0
- frontend/src/lib/liveblocks/useSelf.ts +32 -0
- frontend/src/lib/liveblocks/useStorage.ts +21 -0
- frontend/src/lib/liveblocks/useUndo.ts +12 -0
- frontend/src/lib/liveblocks/useUpdateMyPresence.ts +20 -0
- frontend/src/lib/store.ts +2 -42
- frontend/src/lib/types.ts +18 -2
- frontend/src/routes/+page.svelte +31 -32
Makefile
CHANGED
@@ -4,8 +4,6 @@ build-dev:
|
|
4 |
cd frontend && npm install && npm run build-dev && rm -rf ../static && cp -r build/ ../static/
|
5 |
run-front-dev:
|
6 |
cd frontend && npm install && npm run dev
|
7 |
-
run-dev:
|
8 |
-
rm -rf .data/ && FLASK_ENV=development python app.py
|
9 |
run-prod:
|
10 |
python app.py
|
11 |
build-all: run-prod
|
|
|
4 |
cd frontend && npm install && npm run build-dev && rm -rf ../static && cp -r build/ ../static/
|
5 |
run-front-dev:
|
6 |
cd frontend && npm install && npm run dev
|
|
|
|
|
7 |
run-prod:
|
8 |
python app.py
|
9 |
build-all: run-prod
|
frontend/package.json
CHANGED
@@ -42,6 +42,7 @@
|
|
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 |
}
|
47 |
}
|
|
|
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"
|
47 |
}
|
48 |
}
|
frontend/src/lib/App.svelte
CHANGED
@@ -8,29 +8,54 @@
|
|
8 |
import { COLORS, EMOJIS } from '$lib/constants';
|
9 |
import { PUBLIC_WS_INPAINTING } from '$env/static/public';
|
10 |
import { onMount } from 'svelte';
|
|
|
11 |
import {
|
12 |
isLoading,
|
13 |
loadingState,
|
14 |
currZoomTransform,
|
15 |
-
myPresence,
|
16 |
-
others,
|
17 |
isPrompting,
|
18 |
clickedPosition,
|
19 |
-
|
20 |
-
showFrames,
|
21 |
-
text2img
|
22 |
} from '$lib/store';
|
|
|
|
|
|
|
23 |
import { base64ToBlob, uploadImage } from '$lib/utils';
|
|
|
|
|
|
|
24 |
/**
|
25 |
* The main Liveblocks code for the example.
|
26 |
* Check in src/routes/index.svelte to see the setup code.
|
27 |
*/
|
28 |
|
29 |
-
|
|
|
30 |
|
31 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
32 |
|
33 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
34 |
|
35 |
async function onClose(e: CustomEvent) {
|
36 |
$isPrompting = false;
|
@@ -182,13 +207,15 @@
|
|
182 |
}
|
183 |
const imgBlob = await base64ToBlob(imgBase64);
|
184 |
const imgURL = await uploadImage(imgBlob, _prompt);
|
185 |
-
|
186 |
-
$imagesList.push({
|
187 |
prompt: _prompt,
|
188 |
imgURL: imgURL,
|
189 |
position: $clickedPosition,
|
190 |
-
date: new Date().getTime()
|
191 |
-
|
|
|
|
|
|
|
192 |
console.log(imgURL);
|
193 |
$loadingState = data.success ? 'Complete' : 'Error';
|
194 |
} catch (err) {
|
@@ -224,18 +251,19 @@
|
|
224 |
<Canvas bind:value={canvasEl} />
|
225 |
|
226 |
<main class="z-10 relative">
|
227 |
-
{#if
|
228 |
-
{#each
|
229 |
<Frame
|
230 |
color={COLORS[0]}
|
231 |
-
position={$imagesList.get(i).position}
|
232 |
transform={$currZoomTransform}
|
|
|
|
|
233 |
/>
|
234 |
{/each}
|
235 |
{/if}
|
236 |
-
{#if $clickedPosition}
|
237 |
<Frame color={COLORS[0]} position={$clickedPosition} transform={$currZoomTransform} />
|
238 |
-
{/if}
|
239 |
{#if $myPresence?.cursor}
|
240 |
<Frame color={COLORS[0]} position={$myPresence.cursor} transform={$currZoomTransform} />
|
241 |
<Cursor
|
@@ -247,19 +275,20 @@
|
|
247 |
{/if}
|
248 |
|
249 |
<!-- When others connected, iterate through others and show their cursors -->
|
250 |
-
{#if others}
|
251 |
{#each [...$others] as { connectionId, presence } (connectionId)}
|
252 |
{#if presence?.cursor}
|
253 |
<Frame
|
254 |
color={COLORS[1 + (connectionId % (COLORS.length - 1))]}
|
255 |
-
position={presence
|
|
|
256 |
transform={$currZoomTransform}
|
257 |
/>
|
258 |
|
259 |
<Cursor
|
260 |
emoji={EMOJIS[1 + (connectionId % (EMOJIS.length - 1))]}
|
261 |
color={COLORS[1 + (connectionId % (COLORS.length - 1))]}
|
262 |
-
position={presence
|
263 |
transform={$currZoomTransform}
|
264 |
/>
|
265 |
{/if}
|
|
|
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,
|
15 |
currZoomTransform,
|
|
|
|
|
16 |
isPrompting,
|
17 |
clickedPosition,
|
18 |
+
showFrames
|
|
|
|
|
19 |
} from '$lib/store';
|
20 |
+
|
21 |
+
import { useMyPresence, useObject, useOthers } from '$lib/liveblocks';
|
22 |
+
|
23 |
import { base64ToBlob, uploadImage } from '$lib/utils';
|
24 |
+
|
25 |
+
import { nanoid } from 'nanoid';
|
26 |
+
|
27 |
/**
|
28 |
* The main Liveblocks code for the example.
|
29 |
* Check in src/routes/index.svelte to see the setup code.
|
30 |
*/
|
31 |
|
32 |
+
const myPresence = useMyPresence();
|
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 |
+
function getKey({ position }: PromptImgObject): PromptImgKey {
|
43 |
+
return `${position.x}_${position.y}`;
|
44 |
+
}
|
45 |
|
46 |
+
const promptImgStorage = useObject('promptImgStorage');
|
47 |
+
|
48 |
+
function getpromptImgList(promptImgList: PromptImgObject[]): PromptImgObject[] {
|
49 |
+
if (promptImgList) {
|
50 |
+
const list: PromptImgObject[] = Object.values(promptImgList);
|
51 |
+
return list.sort((a, b) => a.date - b.date);
|
52 |
+
}
|
53 |
+
return [];
|
54 |
+
}
|
55 |
+
let promptImgList: PromptImgObject[] = [];
|
56 |
+
$: promptImgList = getpromptImgList($promptImgStorage?.toObject());
|
57 |
+
|
58 |
+
let canvasEl: HTMLCanvasElement;
|
59 |
|
60 |
async function onClose(e: CustomEvent) {
|
61 |
$isPrompting = false;
|
|
|
207 |
}
|
208 |
const imgBlob = await base64ToBlob(imgBase64);
|
209 |
const imgURL = await uploadImage(imgBlob, _prompt);
|
210 |
+
const promptImg = {
|
|
|
211 |
prompt: _prompt,
|
212 |
imgURL: imgURL,
|
213 |
position: $clickedPosition,
|
214 |
+
date: new Date().getTime(),
|
215 |
+
id: nanoid()
|
216 |
+
};
|
217 |
+
const key = getKey(promptImg);
|
218 |
+
$promptImgStorage.set(key, promptImg);
|
219 |
console.log(imgURL);
|
220 |
$loadingState = data.success ? 'Complete' : 'Error';
|
221 |
} catch (err) {
|
|
|
251 |
<Canvas bind:value={canvasEl} />
|
252 |
|
253 |
<main class="z-10 relative">
|
254 |
+
{#if promptImgList && $showFrames}
|
255 |
+
{#each promptImgList as promptImg, i}
|
256 |
<Frame
|
257 |
color={COLORS[0]}
|
|
|
258 |
transform={$currZoomTransform}
|
259 |
+
position={promptImg?.position}
|
260 |
+
prompt={promptImg?.prompt}
|
261 |
/>
|
262 |
{/each}
|
263 |
{/if}
|
264 |
+
<!-- {#if $clickedPosition}
|
265 |
<Frame color={COLORS[0]} position={$clickedPosition} transform={$currZoomTransform} />
|
266 |
+
{/if} -->
|
267 |
{#if $myPresence?.cursor}
|
268 |
<Frame color={COLORS[0]} position={$myPresence.cursor} transform={$currZoomTransform} />
|
269 |
<Cursor
|
|
|
275 |
{/if}
|
276 |
|
277 |
<!-- When others connected, iterate through others and show their cursors -->
|
278 |
+
{#if $others}
|
279 |
{#each [...$others] as { connectionId, presence } (connectionId)}
|
280 |
{#if presence?.cursor}
|
281 |
<Frame
|
282 |
color={COLORS[1 + (connectionId % (COLORS.length - 1))]}
|
283 |
+
position={presence?.cursor}
|
284 |
+
prompt={presence?.currentPrompt}
|
285 |
transform={$currZoomTransform}
|
286 |
/>
|
287 |
|
288 |
<Cursor
|
289 |
emoji={EMOJIS[1 + (connectionId % (EMOJIS.length - 1))]}
|
290 |
color={COLORS[1 + (connectionId % (COLORS.length - 1))]}
|
291 |
+
position={presence?.cursor}
|
292 |
transform={$currZoomTransform}
|
293 |
/>
|
294 |
{/if}
|
frontend/src/lib/Canvas.svelte
CHANGED
@@ -3,13 +3,13 @@
|
|
3 |
import { select } from 'd3-selection';
|
4 |
import { onMount } from 'svelte';
|
5 |
import { PUBLIC_UPLOADS } from '$env/static/public';
|
6 |
-
import {
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
|
14 |
const height = 512 * 5;
|
15 |
const width = 512 * 5;
|
@@ -21,8 +21,21 @@
|
|
21 |
let containerEl: HTMLDivElement;
|
22 |
let canvasCtx: CanvasRenderingContext2D;
|
23 |
|
24 |
-
|
25 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
26 |
}
|
27 |
|
28 |
onMount(() => {
|
@@ -58,23 +71,29 @@
|
|
58 |
canvasCtx.strokeRect(0, 0, width, height);
|
59 |
});
|
60 |
|
61 |
-
function renderImages(
|
62 |
-
const images = [...imagesList.toImmutable()].sort((a, b) => a.date - b.date);
|
63 |
Promise.all(
|
64 |
-
|
65 |
-
({ imgURL, position }) =>
|
66 |
new Promise((resolve) => {
|
67 |
const img = new Image();
|
68 |
img.crossOrigin = 'anonymous';
|
69 |
img.onload = () => {
|
70 |
-
|
|
|
|
|
|
|
|
|
|
|
71 |
};
|
72 |
const url = imgURL.split('/');
|
73 |
img.src = `${PUBLIC_UPLOADS}/${url.slice(3).join('/')}`;
|
74 |
})
|
75 |
)
|
76 |
).then((images) => {
|
77 |
-
images.forEach(({ img, position }) => {
|
|
|
|
|
78 |
canvasCtx.drawImage(img, position.x, position.y, img.width, img.height);
|
79 |
});
|
80 |
});
|
@@ -99,19 +118,19 @@
|
|
99 |
// const y = Math.round(event.layerY / grid) * grid; //round(Math.max(r, Math.min(512 * 5 - r, event.clientY)), 100);
|
100 |
// const x = round(Math.max(r, Math.min(512 * 5 - r, event.clientX)), grid);
|
101 |
// const y = round(Math.max(r, Math.min(512 * 5 - r, event.clientY)), grid);
|
102 |
-
|
103 |
cursor: {
|
104 |
x,
|
105 |
y
|
106 |
}
|
107 |
-
};
|
108 |
}
|
109 |
|
110 |
// When the pointer leaves the page, set cursor presence to null
|
111 |
function handlePointerLeave() {
|
112 |
-
|
113 |
cursor: null
|
114 |
-
};
|
115 |
}
|
116 |
</script>
|
117 |
|
|
|
3 |
import { select } from 'd3-selection';
|
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';
|
10 |
+
|
11 |
+
const myPresence = useMyPresence();
|
12 |
+
const promptImgStorage = useObject('promptImgStorage');
|
13 |
|
14 |
const height = 512 * 5;
|
15 |
const width = 512 * 5;
|
|
|
21 |
let containerEl: HTMLDivElement;
|
22 |
let canvasCtx: CanvasRenderingContext2D;
|
23 |
|
24 |
+
// keep track of images already rendered
|
25 |
+
const imagesOnCanvas = new Set();
|
26 |
+
|
27 |
+
function getpromptImgList(promptImgList: PromptImgObject[]): PromptImgObject[] {
|
28 |
+
if (promptImgList) {
|
29 |
+
const list: PromptImgObject[] = Object.values(promptImgList).sort((a, b) => a.date - b.date);
|
30 |
+
return list.filter(({ id }) => !imagesOnCanvas.has(id));
|
31 |
+
}
|
32 |
+
return [];
|
33 |
+
}
|
34 |
+
let promptImgList: PromptImgObject[] = [];
|
35 |
+
$: promptImgList = getpromptImgList($promptImgStorage?.toObject());
|
36 |
+
|
37 |
+
$: if (promptImgList) {
|
38 |
+
renderImages(promptImgList);
|
39 |
}
|
40 |
|
41 |
onMount(() => {
|
|
|
71 |
canvasCtx.strokeRect(0, 0, width, height);
|
72 |
});
|
73 |
|
74 |
+
function renderImages(promptImgList: PromptImgObject[]) {
|
|
|
75 |
Promise.all(
|
76 |
+
promptImgList.map(
|
77 |
+
({ imgURL, position, id }) =>
|
78 |
new Promise((resolve) => {
|
79 |
const img = new Image();
|
80 |
img.crossOrigin = 'anonymous';
|
81 |
img.onload = () => {
|
82 |
+
const res = { img, position, id } as {
|
83 |
+
img: HTMLImageElement;
|
84 |
+
position: { x: number; y: number };
|
85 |
+
id: string;
|
86 |
+
};
|
87 |
+
resolve(res);
|
88 |
};
|
89 |
const url = imgURL.split('/');
|
90 |
img.src = `${PUBLIC_UPLOADS}/${url.slice(3).join('/')}`;
|
91 |
})
|
92 |
)
|
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 |
});
|
99 |
});
|
|
|
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,
|
124 |
y
|
125 |
}
|
126 |
+
});
|
127 |
}
|
128 |
|
129 |
// When the pointer leaves the page, set cursor presence to null
|
130 |
function handlePointerLeave() {
|
131 |
+
myPresence.update({
|
132 |
cursor: null
|
133 |
+
});
|
134 |
}
|
135 |
</script>
|
136 |
|
frontend/src/lib/Frame.svelte
CHANGED
@@ -6,6 +6,7 @@
|
|
6 |
export let transform: ZoomTransform;
|
7 |
export let color = '';
|
8 |
export let position = { x: 0, y: 0 };
|
|
|
9 |
|
10 |
$: coord = {
|
11 |
x: transform.applyX(position.x),
|
@@ -24,7 +25,7 @@
|
|
24 |
<LoadingIcon />
|
25 |
<h2 class="text-lg">Click to paint</h2>
|
26 |
|
27 |
-
<div class="absolute bottom-0 font-bold">
|
28 |
</div>
|
29 |
|
30 |
<style lang="postcss" scoped>
|
|
|
6 |
export let transform: ZoomTransform;
|
7 |
export let color = '';
|
8 |
export let position = { x: 0, y: 0 };
|
9 |
+
export let prompt = '';
|
10 |
|
11 |
$: coord = {
|
12 |
x: transform.applyX(position.x),
|
|
|
25 |
<LoadingIcon />
|
26 |
<h2 class="text-lg">Click to paint</h2>
|
27 |
|
28 |
+
<div class="absolute bottom-0 font-bold">{prompt}}</div>
|
29 |
</div>
|
30 |
|
31 |
<style lang="postcss" scoped>
|
frontend/src/lib/liveblocks/LiveblocksProvider.svelte
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!--
|
2 |
+
Works similarly to `liveblocks-react` LiveblocksProvider
|
3 |
+
https://liveblocks.io/docs/api-reference/liveblocks-react#RoomProvider
|
4 |
+
-->
|
5 |
+
<script lang="ts">
|
6 |
+
import { clientSymbol } from "./symbols";
|
7 |
+
import type { Client } from "@liveblocks/client";
|
8 |
+
import { setContext } from "svelte";
|
9 |
+
|
10 |
+
export let client: Client;
|
11 |
+
|
12 |
+
if (!client) {
|
13 |
+
throw new Error("LiveblocksProvider requires a client");
|
14 |
+
}
|
15 |
+
|
16 |
+
setContext<Client>(clientSymbol, client);
|
17 |
+
</script>
|
18 |
+
|
19 |
+
<slot />
|
frontend/src/lib/liveblocks/RoomProvider.svelte
ADDED
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!--
|
2 |
+
Works similarly to `liveblocks-react` RoomProvider
|
3 |
+
https://liveblocks.io/docs/api-reference/liveblocks-react#RoomProvider
|
4 |
+
-->
|
5 |
+
<script lang="ts">
|
6 |
+
import { clientSymbol, roomSymbol } from "./symbols";
|
7 |
+
import type { Client, Room } from "@liveblocks/client";
|
8 |
+
import { getContext, onDestroy, setContext } from "svelte";
|
9 |
+
|
10 |
+
export let id: string;
|
11 |
+
export let defaultPresence = () => ({});
|
12 |
+
|
13 |
+
if (!id) {
|
14 |
+
throw new Error("RoomProvider requires an id");
|
15 |
+
}
|
16 |
+
|
17 |
+
const client = getContext<Client>(clientSymbol);
|
18 |
+
|
19 |
+
if (client) {
|
20 |
+
const room = client.enter(id, defaultPresence());
|
21 |
+
|
22 |
+
setContext<Room>(roomSymbol, room);
|
23 |
+
|
24 |
+
onDestroy(() => {
|
25 |
+
client.leave(id);
|
26 |
+
});
|
27 |
+
}
|
28 |
+
</script>
|
29 |
+
|
30 |
+
<slot />
|
frontend/src/lib/liveblocks/index.ts
ADDED
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
export * from "./symbols";
|
2 |
+
export * from "./useUpdateMyPresence";
|
3 |
+
export * from "./useMyPresence";
|
4 |
+
export * from "./useOthers";
|
5 |
+
export * from "./useObject";
|
6 |
+
export * from "./useList";
|
7 |
+
export * from "./useSelf";
|
8 |
+
export * from "./useRoom";
|
9 |
+
export * from "./useUndo";
|
10 |
+
export * from "./useRedo";
|
11 |
+
export * from "./useBatch";
|
12 |
+
export * from "./useHistory";
|
13 |
+
|
14 |
+
/**
|
15 |
+
* These components were built to (mostly) match the
|
16 |
+
* liveblocks-react library
|
17 |
+
* https://liveblocks.io/docs/api-reference/liveblocks-react
|
18 |
+
*/
|
frontend/src/lib/liveblocks/symbols.ts
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
// Symbols are used for context to avoid polluting the global scope
|
2 |
+
export const clientSymbol = Symbol();
|
3 |
+
export const roomSymbol = Symbol();
|
frontend/src/lib/liveblocks/useBatch.ts
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { useRoom } from "./useRoom";
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Works similarly to `liveblocks-react` useBatch
|
5 |
+
* https://liveblocks.io/docs/api-reference/liveblocks-react#useBatch
|
6 |
+
*
|
7 |
+
* const batch = useBatch()
|
8 |
+
* batch(() => {
|
9 |
+
* // ...
|
10 |
+
* })
|
11 |
+
*/
|
12 |
+
export function useBatch() {
|
13 |
+
return useRoom().batch;
|
14 |
+
}
|
frontend/src/lib/liveblocks/useHistory.ts
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/**
|
2 |
+
* Works similarly to `liveblocks-react` useHistory
|
3 |
+
* https://liveblocks.io/docs/api-reference/liveblocks-react#useHistory
|
4 |
+
*
|
5 |
+
* const history = useHistory()
|
6 |
+
* history.pause()
|
7 |
+
*/
|
8 |
+
import { useRoom } from "./useRoom";
|
9 |
+
|
10 |
+
export function useHistory() {
|
11 |
+
return useRoom().history;
|
12 |
+
}
|
frontend/src/lib/liveblocks/useList.ts
ADDED
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { LiveList } from "@liveblocks/client";
|
2 |
+
import { useStorage } from "./useStorage";
|
3 |
+
import { onDestroy } from "svelte";
|
4 |
+
import type { Writable } from "svelte/store";
|
5 |
+
import { writable } from "svelte/store";
|
6 |
+
import { useRoom } from "./useRoom";
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Works similarly to `liveblocks-react` useList
|
10 |
+
* https://liveblocks.io/docs/api-reference/liveblocks-react#useList
|
11 |
+
*
|
12 |
+
* The main difference is that it returns a Svelte store:
|
13 |
+
* const list = useList()
|
14 |
+
* $list.push([{ item: 1 }])
|
15 |
+
* console.log([...$list])
|
16 |
+
*/
|
17 |
+
export function useList<T>(
|
18 |
+
name: string,
|
19 |
+
initial?: any[]
|
20 |
+
): Writable<LiveList<T>> {
|
21 |
+
const room = useRoom();
|
22 |
+
const rootStore = useStorage();
|
23 |
+
const list = writable<LiveList<T>>();
|
24 |
+
let unsubscribe = () => {};
|
25 |
+
|
26 |
+
const unsubscribeRoot = rootStore.subscribe((root) => {
|
27 |
+
if (!root) {
|
28 |
+
return;
|
29 |
+
}
|
30 |
+
|
31 |
+
if (!root.get(name)) {
|
32 |
+
root.set(name, new LiveList<T>(initial));
|
33 |
+
}
|
34 |
+
|
35 |
+
list.set(root.get(name));
|
36 |
+
|
37 |
+
unsubscribe();
|
38 |
+
unsubscribe = room.subscribe(root.get(name) as LiveList<T>, (newList) => {
|
39 |
+
list.set(newList);
|
40 |
+
});
|
41 |
+
});
|
42 |
+
|
43 |
+
onDestroy(unsubscribeRoot);
|
44 |
+
|
45 |
+
return list;
|
46 |
+
}
|
frontend/src/lib/liveblocks/useMyPresence.ts
ADDED
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import type { Presence } from "@liveblocks/client";
|
2 |
+
import { onDestroy } from "svelte";
|
3 |
+
import { writable } from "svelte/store";
|
4 |
+
import { useRoom } from "./useRoom";
|
5 |
+
|
6 |
+
/**
|
7 |
+
* Works similarly to `liveblocks-react` useMyPresence
|
8 |
+
* https://liveblocks.io/docs/api-reference/liveblocks-react#useMyPresence
|
9 |
+
*
|
10 |
+
* The main difference is that it returns a custom Svelte store:
|
11 |
+
* const presence = useMyPresence()
|
12 |
+
* presence.update({ name: 'Chris })
|
13 |
+
* console.log($presence.name)
|
14 |
+
* <div>{$presence.count}</div>
|
15 |
+
*
|
16 |
+
* USAGE NOTE:
|
17 |
+
* This is a custom Svelte store, `set` does nothing, only `update`.
|
18 |
+
* `update` does NOT take a function like regular Svelte stores,
|
19 |
+
* it takes an object and works like `useUpdateMyPresence` in Liveblocks
|
20 |
+
*/
|
21 |
+
|
22 |
+
export function useMyPresence(): any {
|
23 |
+
const room = useRoom();
|
24 |
+
const { subscribe, set } = writable<Presence>();
|
25 |
+
|
26 |
+
function update(newPresence) {
|
27 |
+
room.updatePresence(newPresence);
|
28 |
+
}
|
29 |
+
|
30 |
+
const unsubscribePresence = room.subscribe("my-presence", (presence) => {
|
31 |
+
set(presence);
|
32 |
+
});
|
33 |
+
|
34 |
+
onDestroy(() => {
|
35 |
+
unsubscribePresence();
|
36 |
+
});
|
37 |
+
|
38 |
+
return {
|
39 |
+
subscribe,
|
40 |
+
update,
|
41 |
+
};
|
42 |
+
}
|
frontend/src/lib/liveblocks/useObject.ts
ADDED
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { LiveObject } from "@liveblocks/client";
|
2 |
+
import { useStorage } from "./useStorage";
|
3 |
+
import { onDestroy } from "svelte";
|
4 |
+
import type { Writable } from "svelte/store";
|
5 |
+
import { writable } from "svelte/store";
|
6 |
+
import { useRoom } from "./useRoom";
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Works similarly to `liveblocks-react` useObject
|
10 |
+
* https://liveblocks.io/docs/api-reference/liveblocks-react#useObject
|
11 |
+
*
|
12 |
+
* The main difference is that it returns a Svelte store:
|
13 |
+
* const obj = useObject()
|
14 |
+
* $obj.set('name', 'Chris')
|
15 |
+
* console.log($obj.get('name'))
|
16 |
+
*/
|
17 |
+
export function useObject(name: string, initial?: any): Writable<LiveObject> {
|
18 |
+
const room = useRoom();
|
19 |
+
const rootStore = useStorage();
|
20 |
+
const list = writable<LiveObject>();
|
21 |
+
let unsubscribe = () => {};
|
22 |
+
|
23 |
+
const unsubscribeRoot = rootStore.subscribe((root) => {
|
24 |
+
if (!root) {
|
25 |
+
return;
|
26 |
+
}
|
27 |
+
|
28 |
+
if (!root.get(name)) {
|
29 |
+
root.set(name, new LiveObject(initial));
|
30 |
+
}
|
31 |
+
|
32 |
+
list.set(root.get(name));
|
33 |
+
|
34 |
+
unsubscribe();
|
35 |
+
unsubscribe = room.subscribe(root.get(name) as LiveObject, (newObject) => {
|
36 |
+
list.set(newObject);
|
37 |
+
});
|
38 |
+
});
|
39 |
+
|
40 |
+
onDestroy(unsubscribeRoot);
|
41 |
+
|
42 |
+
return list;
|
43 |
+
}
|
frontend/src/lib/liveblocks/useOthers.ts
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import type { Others } from "@liveblocks/client";
|
2 |
+
import { onDestroy } from "svelte";
|
3 |
+
import type { Writable } from "svelte/store";
|
4 |
+
import { writable } from "svelte/store";
|
5 |
+
import { useRoom } from "./useRoom";
|
6 |
+
|
7 |
+
/**
|
8 |
+
* Works similarly to `liveblocks-react` useOthers
|
9 |
+
* https://liveblocks.io/docs/api-reference/liveblocks-react#useOthers
|
10 |
+
*
|
11 |
+
* The main difference is that it returns a Svelte store:
|
12 |
+
* const others = useOthers()
|
13 |
+
* console.log($others.value)
|
14 |
+
* {#each [...$others] as other}
|
15 |
+
* ...
|
16 |
+
*/
|
17 |
+
export function useOthers(): Writable<Others> {
|
18 |
+
const room = useRoom();
|
19 |
+
const others = writable<Others>();
|
20 |
+
|
21 |
+
const unsubscribe = room.subscribe("others", (newOthers) => {
|
22 |
+
others.set(newOthers);
|
23 |
+
});
|
24 |
+
|
25 |
+
onDestroy(unsubscribe);
|
26 |
+
|
27 |
+
return others;
|
28 |
+
}
|
frontend/src/lib/liveblocks/useRedo.ts
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/**
|
2 |
+
* Works similarly to `liveblocks-react` useRedo
|
3 |
+
* https://liveblocks.io/docs/api-reference/liveblocks-react#useRedo
|
4 |
+
*
|
5 |
+
* const redo = useRedo()
|
6 |
+
* redo()
|
7 |
+
*/
|
8 |
+
import { useRoom } from "./useRoom";
|
9 |
+
|
10 |
+
export function useRedo() {
|
11 |
+
return useRoom().history.redo;
|
12 |
+
}
|
frontend/src/lib/liveblocks/useRoom.ts
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { getContext } from "svelte";
|
2 |
+
import type { Room } from "@liveblocks/client";
|
3 |
+
import { roomSymbol } from "./symbols";
|
4 |
+
|
5 |
+
/**
|
6 |
+
* Works similarly to `liveblocks-react` useRoom
|
7 |
+
* https://liveblocks.io/docs/api-reference/liveblocks-react#useRoom
|
8 |
+
*
|
9 |
+
* This does NOT return a Svelte store, just the plain room object
|
10 |
+
* const room = useRoom()
|
11 |
+
* room.history.undo()
|
12 |
+
*/
|
13 |
+
export function useRoom(): Room {
|
14 |
+
const room = getContext<Room>(roomSymbol);
|
15 |
+
|
16 |
+
if (!room) {
|
17 |
+
throw new Error("Use RoomProvider as parent with id prop");
|
18 |
+
}
|
19 |
+
|
20 |
+
return room;
|
21 |
+
}
|
frontend/src/lib/liveblocks/useSelf.ts
ADDED
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { onDestroy } from "svelte";
|
2 |
+
import type { Writable } from "svelte/store";
|
3 |
+
import { writable } from "svelte/store";
|
4 |
+
import { useRoom } from "./useRoom";
|
5 |
+
|
6 |
+
/**
|
7 |
+
* Works similarly to `liveblocks-react` useSelf
|
8 |
+
* https://liveblocks.io/docs/api-reference/liveblocks-react#useSelf
|
9 |
+
*
|
10 |
+
* The main difference is that it returns a Svelte store:
|
11 |
+
* const self = useSelf()
|
12 |
+
* console.log($self.info.id)
|
13 |
+
* <div>{$self.info.name}</div>
|
14 |
+
*/
|
15 |
+
export function useSelf(): Writable<any> {
|
16 |
+
const room = useRoom();
|
17 |
+
const self = writable();
|
18 |
+
|
19 |
+
const unsubscribeConnection = room.subscribe("connection", () => {
|
20 |
+
self.set(room.getSelf());
|
21 |
+
});
|
22 |
+
const unsubscribe = room.subscribe("my-presence", () => {
|
23 |
+
self.set(room.getSelf());
|
24 |
+
});
|
25 |
+
|
26 |
+
onDestroy(() => {
|
27 |
+
unsubscribeConnection();
|
28 |
+
unsubscribe();
|
29 |
+
});
|
30 |
+
|
31 |
+
return self;
|
32 |
+
}
|
frontend/src/lib/liveblocks/useStorage.ts
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import type { LiveObject } from "@liveblocks/client";
|
2 |
+
import type { Writable } from "svelte/store";
|
3 |
+
import { writable } from "svelte/store";
|
4 |
+
import { useRoom } from "./useRoom";
|
5 |
+
|
6 |
+
/**
|
7 |
+
* No `liveblocks-react` public API equivalent, but useStorage is used internally
|
8 |
+
*/
|
9 |
+
export function useStorage(): Writable<LiveObject> {
|
10 |
+
const room = useRoom();
|
11 |
+
const rootStore = writable<LiveObject>();
|
12 |
+
|
13 |
+
async function fetchStorage() {
|
14 |
+
const { root }: { root: LiveObject } = await room!.getStorage();
|
15 |
+
rootStore.set(root);
|
16 |
+
}
|
17 |
+
|
18 |
+
fetchStorage();
|
19 |
+
|
20 |
+
return rootStore;
|
21 |
+
}
|
frontend/src/lib/liveblocks/useUndo.ts
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/**
|
2 |
+
* Works similarly to `liveblocks-react` useUndo
|
3 |
+
* https://liveblocks.io/docs/api-reference/liveblocks-react#useUndo
|
4 |
+
*
|
5 |
+
* const undo = useUndo()
|
6 |
+
* undo()
|
7 |
+
*/
|
8 |
+
import { useRoom } from "./useRoom";
|
9 |
+
|
10 |
+
export function useUndo() {
|
11 |
+
return useRoom().history.undo;
|
12 |
+
}
|
frontend/src/lib/liveblocks/useUpdateMyPresence.ts
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { useMyPresence } from "./useMyPresence";
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Works similarly to `liveblocks-react` useUpdateMyPresence
|
5 |
+
* https://liveblocks.io/docs/api-reference/liveblocks-react#useUpdateMyPresence
|
6 |
+
*
|
7 |
+
* const updateMyPresence = useUpdateMyPresence()
|
8 |
+
* updateMyPresence({ name: 'Chris' })
|
9 |
+
*
|
10 |
+
*
|
11 |
+
* Can also import useMyPresence instead and use .update() instead:
|
12 |
+
*
|
13 |
+
* const myPresence = useMyPresence()
|
14 |
+
* myPresence.update({ name: 'Chris' })
|
15 |
+
*/
|
16 |
+
|
17 |
+
export function useUpdateMyPresence(): (val: any) => void {
|
18 |
+
const presence = useMyPresence();
|
19 |
+
return (updatedPresence) => presence.update(updatedPresence);
|
20 |
+
}
|
frontend/src/lib/store.ts
CHANGED
@@ -3,6 +3,8 @@ import type { Room } from '@liveblocks/client';
|
|
3 |
|
4 |
import { type ZoomTransform, zoomIdentity } from 'd3-zoom';
|
5 |
|
|
|
|
|
6 |
export const loadingState = writable<string>('');
|
7 |
export const isLoading = writable<boolean>(false);
|
8 |
export const isPrompting = writable<boolean>(false);
|
@@ -12,45 +14,3 @@ export const text2img = writable<boolean>(false);
|
|
12 |
|
13 |
export const currZoomTransform = writable<ZoomTransform>(zoomIdentity);
|
14 |
|
15 |
-
export const myPresence = writable(null);
|
16 |
-
export const others = writable(null);
|
17 |
-
export const imagesList = writable(null);
|
18 |
-
|
19 |
-
export function createPresenceStore(room: Room) {
|
20 |
-
// Get initial values for presence and others
|
21 |
-
myPresence.set(room.getPresence());
|
22 |
-
others.set(room.getOthers());
|
23 |
-
|
24 |
-
const unsubscribeMyPresence = room.subscribe('my-presence', (presence) => {
|
25 |
-
myPresence.update((_) => presence);
|
26 |
-
});
|
27 |
-
|
28 |
-
const unsubscribeOthers = room.subscribe('others', (otherUsers) => {
|
29 |
-
others.update((_) => otherUsers);
|
30 |
-
});
|
31 |
-
|
32 |
-
myPresence.set = (presence) => {
|
33 |
-
room.updatePresence(presence);
|
34 |
-
return presence;
|
35 |
-
};
|
36 |
-
|
37 |
-
return () => {
|
38 |
-
unsubscribeMyPresence();
|
39 |
-
unsubscribeOthers();
|
40 |
-
};
|
41 |
-
}
|
42 |
-
|
43 |
-
export async function createStorageStore(room: Room) {
|
44 |
-
try {
|
45 |
-
const { root } = await room.getStorage();
|
46 |
-
|
47 |
-
const _imagesList = root.get('imagesList');
|
48 |
-
|
49 |
-
imagesList.set(_imagesList);
|
50 |
-
room.subscribe(_imagesList, () => {
|
51 |
-
imagesList.update((_) => _imagesList);
|
52 |
-
});
|
53 |
-
} catch (e) {
|
54 |
-
console.log(e);
|
55 |
-
}
|
56 |
-
}
|
|
|
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);
|
|
|
14 |
|
15 |
export const currZoomTransform = writable<ZoomTransform>(zoomIdentity);
|
16 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/src/lib/types.ts
CHANGED
@@ -1,4 +1,7 @@
|
|
1 |
-
|
|
|
|
|
|
|
2 |
cursor: {
|
3 |
x: number;
|
4 |
y: number;
|
@@ -10,4 +13,17 @@ export type Storage = {
|
|
10 |
// ...
|
11 |
};
|
12 |
|
13 |
-
export type User = string;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import type { JsonObject } from "@liveblocks/client";
|
2 |
+
|
3 |
+
|
4 |
+
export interface Presence extends JsonObject {
|
5 |
cursor: {
|
6 |
x: number;
|
7 |
y: number;
|
|
|
13 |
// ...
|
14 |
};
|
15 |
|
16 |
+
export type User = string;
|
17 |
+
|
18 |
+
export type PromptImgObject = {
|
19 |
+
prompt: string;
|
20 |
+
imgURL: string;
|
21 |
+
position: {
|
22 |
+
x: number;
|
23 |
+
y: number;
|
24 |
+
}
|
25 |
+
date: number;
|
26 |
+
id: string;
|
27 |
+
};
|
28 |
+
|
29 |
+
export type PromptImgKey = string;
|
frontend/src/routes/+page.svelte
CHANGED
@@ -1,47 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
<script lang="ts">
|
2 |
import { onMount } from 'svelte';
|
3 |
-
import {
|
4 |
-
import type { Client
|
5 |
-
import
|
6 |
-
|
7 |
import App from '$lib/App.svelte';
|
8 |
-
import type { Presence, Storage } from '$lib/types';
|
9 |
|
|
|
|
|
10 |
let client: Client;
|
11 |
-
let room: Room;
|
12 |
-
let roomId = 'multiplayer-SD';
|
13 |
|
14 |
onMount(() => {
|
|
|
|
|
|
|
|
|
|
|
15 |
client = createClient({
|
16 |
publicApiKey: 'pk_test_JlUZGH3kQmhmZQiqU2l8eIi5'
|
17 |
});
|
18 |
|
19 |
-
|
20 |
-
initialPresence: {
|
21 |
-
cursor: null
|
22 |
-
},
|
23 |
-
initialStorage: { imagesList: new LiveList() }
|
24 |
-
});
|
25 |
-
const unsubscribe = room.subscribe('error', (error) => {
|
26 |
-
console.error('error', error);
|
27 |
-
});
|
28 |
-
|
29 |
-
const unsubscribePresence = createPresenceStore(room);
|
30 |
-
createStorageStore(room);
|
31 |
-
return () => {
|
32 |
-
if (client && room) {
|
33 |
-
client.leave(roomId);
|
34 |
-
unsubscribePresence();
|
35 |
-
}
|
36 |
-
};
|
37 |
});
|
38 |
</script>
|
39 |
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
|
|
|
|
47 |
{/if}
|
|
|
1 |
+
<script context="module" lang="ts">
|
2 |
+
export const prerender = true;
|
3 |
+
</script>
|
4 |
+
|
5 |
+
<!--
|
6 |
+
The main code for this component is in src/PixelArtTogether.svelte
|
7 |
+
This file contains the Liveblocks providers, based on the
|
8 |
+
liveblocks-react library
|
9 |
+
https://liveblocks.io/docs/api-reference/liveblocks-react#LiveblocksProvider
|
10 |
+
-->
|
11 |
<script lang="ts">
|
12 |
import { onMount } from 'svelte';
|
13 |
+
import { createClient } from '@liveblocks/client';
|
14 |
+
import type { Client } from '@liveblocks/client';
|
15 |
+
import LiveblocksProvider from '$lib/liveblocks/LiveblocksProvider.svelte';
|
16 |
+
import RoomProvider from '$lib/liveblocks/RoomProvider.svelte';
|
17 |
import App from '$lib/App.svelte';
|
|
|
18 |
|
19 |
+
let roomId: string;
|
20 |
+
let loaded = false;
|
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';
|
27 |
+
|
28 |
+
// Connect to the authentication API for Liveblocks
|
29 |
client = createClient({
|
30 |
publicApiKey: 'pk_test_JlUZGH3kQmhmZQiqU2l8eIi5'
|
31 |
});
|
32 |
|
33 |
+
loaded = true;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
34 |
});
|
35 |
</script>
|
36 |
|
37 |
+
{#if loaded}
|
38 |
+
<!-- Provides Liveblocks hooks to children -->
|
39 |
+
<LiveblocksProvider {client}>
|
40 |
+
<!-- Create a room from id e.g. `sveltekit-pixel-art-758df70b5e94c13289df6` -->
|
41 |
+
<RoomProvider id={roomId}>
|
42 |
+
<!-- Main app component -->
|
43 |
+
<App />
|
44 |
+
</RoomProvider>
|
45 |
+
</LiveblocksProvider>
|
46 |
{/if}
|