Spaces:
Runtime error
Runtime error
liveblocks
Browse files- frontend/package.json +2 -1
- frontend/src/lib/Canvas.svelte +103 -0
- frontend/src/lib/Cursor.svelte +40 -0
- frontend/src/lib/store.ts +14 -0
- frontend/src/lib/types.ts +13 -0
- frontend/src/lib/utils.ts +1 -1
- frontend/src/routes/+page.svelte +39 -0
frontend/package.json
CHANGED
|
@@ -36,6 +36,7 @@
|
|
| 36 |
},
|
| 37 |
"type": "module",
|
| 38 |
"dependencies": {
|
| 39 |
-
"@fontsource/fira-mono": "^4.5.0"
|
|
|
|
| 40 |
}
|
| 41 |
}
|
|
|
|
| 36 |
},
|
| 37 |
"type": "module",
|
| 38 |
"dependencies": {
|
| 39 |
+
"@fontsource/fira-mono": "^4.5.0",
|
| 40 |
+
"@liveblocks/client": "^0.18.2"
|
| 41 |
}
|
| 42 |
}
|
frontend/src/lib/Canvas.svelte
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<script lang="ts">
|
| 2 |
+
import Cursor from '$lib/Cursor.svelte';
|
| 3 |
+
import type { Room } from '@liveblocks/client';
|
| 4 |
+
import { onDestroy } from 'svelte';
|
| 5 |
+
/**
|
| 6 |
+
* The main Liveblocks code for the example.
|
| 7 |
+
* Check in src/routes/index.svelte to see the setup code.
|
| 8 |
+
*/
|
| 9 |
+
|
| 10 |
+
export let room: Room;
|
| 11 |
+
|
| 12 |
+
// Get initial values for presence and others
|
| 13 |
+
let myPresence = room.getPresence();
|
| 14 |
+
let others = room.getOthers();
|
| 15 |
+
|
| 16 |
+
// Subscribe to further changes
|
| 17 |
+
const unsubscribeMyPresence = room.subscribe('my-presence', (presence) => {
|
| 18 |
+
myPresence = presence;
|
| 19 |
+
});
|
| 20 |
+
|
| 21 |
+
const unsubscribeOthers = room.subscribe('others', (otherUsers) => {
|
| 22 |
+
others = otherUsers;
|
| 23 |
+
});
|
| 24 |
+
|
| 25 |
+
// Unsubscribe when unmounting
|
| 26 |
+
onDestroy(() => {
|
| 27 |
+
unsubscribeMyPresence();
|
| 28 |
+
unsubscribeOthers();
|
| 29 |
+
});
|
| 30 |
+
|
| 31 |
+
// Update cursor presence to current pointer location
|
| 32 |
+
function handlePointerMove(event: PointerEvent) {
|
| 33 |
+
event.preventDefault();
|
| 34 |
+
room.updatePresence({
|
| 35 |
+
cursor: {
|
| 36 |
+
x: Math.round(event.clientX),
|
| 37 |
+
y: Math.round(event.clientY)
|
| 38 |
+
}
|
| 39 |
+
});
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
// When the pointer leaves the page, set cursor presence to null
|
| 43 |
+
function handlePointerLeave() {
|
| 44 |
+
room.updatePresence({
|
| 45 |
+
cursor: null
|
| 46 |
+
});
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
const COLORS = [
|
| 50 |
+
'#E57373',
|
| 51 |
+
'#9575CD',
|
| 52 |
+
'#4FC3F7',
|
| 53 |
+
'#81C784',
|
| 54 |
+
'#FFF176',
|
| 55 |
+
'#FF8A65',
|
| 56 |
+
'#F06292',
|
| 57 |
+
'#7986CB'
|
| 58 |
+
];
|
| 59 |
+
</script>
|
| 60 |
+
|
| 61 |
+
<main on:pointerleave={handlePointerLeave} on:pointermove={handlePointerMove}>
|
| 62 |
+
<!-- Show the current user's cursor location -->
|
| 63 |
+
<div class="text">
|
| 64 |
+
{myPresence?.cursor
|
| 65 |
+
? `${myPresence.cursor.x} × ${myPresence.cursor.y}`
|
| 66 |
+
: 'Move your cursor to broadcast its position to other people in the room.'}
|
| 67 |
+
</div>
|
| 68 |
+
|
| 69 |
+
<!-- When others connected, iterate through others and show their cursors -->
|
| 70 |
+
{#if others}
|
| 71 |
+
{#each [...others] as { connectionId, presence } (connectionId)}
|
| 72 |
+
{#if presence?.cursor}
|
| 73 |
+
<Cursor
|
| 74 |
+
color={COLORS[connectionId % COLORS.length]}
|
| 75 |
+
x={presence.cursor.x}
|
| 76 |
+
y={presence.cursor.y}
|
| 77 |
+
/>
|
| 78 |
+
{/if}
|
| 79 |
+
{/each}
|
| 80 |
+
{/if}
|
| 81 |
+
</main>
|
| 82 |
+
|
| 83 |
+
<style lang="postcss" scoped>
|
| 84 |
+
main {
|
| 85 |
+
@apply fixed top-0 left-0 w-screen h-screen flex flex-col items-center justify-center touch-none bg-white;
|
| 86 |
+
/* position: absolute;
|
| 87 |
+
top: 0;
|
| 88 |
+
left: 0;
|
| 89 |
+
width: 100vw;
|
| 90 |
+
height: 100vh;
|
| 91 |
+
display: flex;
|
| 92 |
+
place-content: center;
|
| 93 |
+
place-items: center;
|
| 94 |
+
touch-action: none;
|
| 95 |
+
background-color: white; */
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
.text {
|
| 99 |
+
max-width: 380px;
|
| 100 |
+
margin: 0 16px;
|
| 101 |
+
text-align: center;
|
| 102 |
+
}
|
| 103 |
+
</style>
|
frontend/src/lib/Cursor.svelte
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<script lang="ts">
|
| 2 |
+
import { spring } from 'svelte/motion';
|
| 3 |
+
|
| 4 |
+
export let color = '';
|
| 5 |
+
export let x = 0;
|
| 6 |
+
export let y = 0;
|
| 7 |
+
|
| 8 |
+
// Spring animation for cursor
|
| 9 |
+
const coords = spring(
|
| 10 |
+
{ x, y },
|
| 11 |
+
{
|
| 12 |
+
stiffness: 0.07,
|
| 13 |
+
damping: 0.35
|
| 14 |
+
}
|
| 15 |
+
);
|
| 16 |
+
|
| 17 |
+
// Update spring when x and y change
|
| 18 |
+
$: coords.set({ x, y });
|
| 19 |
+
</script>
|
| 20 |
+
|
| 21 |
+
<svg
|
| 22 |
+
class="cursor"
|
| 23 |
+
fill="none"
|
| 24 |
+
height="36"
|
| 25 |
+
style={`transform: translateX(${$coords.x}px) translateY(${$coords.y}px)`}
|
| 26 |
+
viewBox="0 0 24 36"
|
| 27 |
+
width="24"
|
| 28 |
+
xmlns="http://www.w3.org/2000/svg"
|
| 29 |
+
>
|
| 30 |
+
<path
|
| 31 |
+
d="M5.65376 12.3673H5.46026L5.31717 12.4976L0.500002 16.8829L0.500002 1.19841L11.7841 12.3673H5.65376Z"
|
| 32 |
+
fill={color}
|
| 33 |
+
/>
|
| 34 |
+
</svg>
|
| 35 |
+
|
| 36 |
+
<style lang="postcss" scoped>
|
| 37 |
+
.cursor {
|
| 38 |
+
@apply absolute top-0 left-0;
|
| 39 |
+
}
|
| 40 |
+
</style>
|
frontend/src/lib/store.ts
CHANGED
|
@@ -1,3 +1,17 @@
|
|
| 1 |
import { writable } from 'svelte/store';
|
|
|
|
|
|
|
|
|
|
| 2 |
export const loadingState = writable<string>('');
|
| 3 |
export const isLoading = writable<boolean>(false);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import { writable } from 'svelte/store';
|
| 2 |
+
import type { User } from '$lib/types';
|
| 3 |
+
import { browser } from '$app/environment';
|
| 4 |
+
|
| 5 |
export const loadingState = writable<string>('');
|
| 6 |
export const isLoading = writable<boolean>(false);
|
| 7 |
+
|
| 8 |
+
const initialUser: User = crypto.randomUUID();
|
| 9 |
+
|
| 10 |
+
export const currentUser = writable<User>(
|
| 11 |
+
browser ? JSON.parse(localStorage['user'] || JSON.stringify(initialUser)) : initialUser
|
| 12 |
+
);
|
| 13 |
+
currentUser.subscribe((value) => {
|
| 14 |
+
if (browser) {
|
| 15 |
+
return (localStorage['user'] = JSON.stringify(value));
|
| 16 |
+
}
|
| 17 |
+
});
|
frontend/src/lib/types.ts
CHANGED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export type Presence = {
|
| 2 |
+
cursor: {
|
| 3 |
+
x: number;
|
| 4 |
+
y: number;
|
| 5 |
+
} | null;
|
| 6 |
+
};
|
| 7 |
+
|
| 8 |
+
export type Storage = {
|
| 9 |
+
// animals: LiveList<string>,
|
| 10 |
+
// ...
|
| 11 |
+
};
|
| 12 |
+
|
| 13 |
+
export type User = string;
|
frontend/src/lib/utils.ts
CHANGED
|
@@ -39,4 +39,4 @@ function slugify(text: string) {
|
|
| 39 |
.replace(/\-\-+/g, '-')
|
| 40 |
.replace(/^-+/, '')
|
| 41 |
.replace(/-+$/, '');
|
| 42 |
-
}
|
|
|
|
| 39 |
.replace(/\-\-+/g, '-')
|
| 40 |
.replace(/^-+/, '')
|
| 41 |
.replace(/-+$/, '');
|
| 42 |
+
}
|
frontend/src/routes/+page.svelte
CHANGED
|
@@ -1,11 +1,45 @@
|
|
| 1 |
<script lang="ts">
|
|
|
|
| 2 |
import { isLoading, loadingState } from '$lib/store';
|
| 3 |
import { PUBLIC_WS_ENDPOINT, PUBLIC_DEV_MODE } from '$env/static/public';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
|
| 5 |
const apiUrl =
|
| 6 |
PUBLIC_DEV_MODE === 'DEV'
|
| 7 |
? 'http://localhost:7860'
|
| 8 |
: '/embed/huggingface-projects/color-palette-generator-sd';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
</script>
|
| 10 |
|
| 11 |
<div class="max-w-screen-md mx-auto px-3 py-8 relative z-0">
|
|
@@ -26,6 +60,11 @@
|
|
| 26 |
</button>
|
| 27 |
</form>
|
| 28 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
</div>
|
| 30 |
|
| 31 |
<style lang="postcss" scoped>
|
|
|
|
| 1 |
<script lang="ts">
|
| 2 |
+
import { onMount } from 'svelte';
|
| 3 |
import { isLoading, loadingState } from '$lib/store';
|
| 4 |
import { PUBLIC_WS_ENDPOINT, PUBLIC_DEV_MODE } from '$env/static/public';
|
| 5 |
+
import type { Client, Room } from '@liveblocks/client';
|
| 6 |
+
import { createClient } from '@liveblocks/client';
|
| 7 |
+
import { currentUser } from '$lib/store';
|
| 8 |
+
|
| 9 |
+
import Canvas from '$lib/Canvas.svelte';
|
| 10 |
+
import type { Presence, Storage } from '$lib/types';
|
| 11 |
|
| 12 |
const apiUrl =
|
| 13 |
PUBLIC_DEV_MODE === 'DEV'
|
| 14 |
? 'http://localhost:7860'
|
| 15 |
: '/embed/huggingface-projects/color-palette-generator-sd';
|
| 16 |
+
|
| 17 |
+
let client: Client;
|
| 18 |
+
let room: Room;
|
| 19 |
+
let roomId = 'sveltekit-live-cursors';
|
| 20 |
+
|
| 21 |
+
$: {
|
| 22 |
+
console.log('whoami', $currentUser);
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
onMount(() => {
|
| 26 |
+
client = createClient({
|
| 27 |
+
publicApiKey: 'pk_live_6o9jIg1m7lFJp5kc7HgYgE3S'
|
| 28 |
+
});
|
| 29 |
+
|
| 30 |
+
room = client.enter<Presence, Storage /* UserMeta, RoomEvent */>(roomId, {
|
| 31 |
+
initialPresence: {
|
| 32 |
+
cursor: null
|
| 33 |
+
},
|
| 34 |
+
initialStorage: {}
|
| 35 |
+
});
|
| 36 |
+
console.log('room', room);
|
| 37 |
+
return () => {
|
| 38 |
+
if (client && room) {
|
| 39 |
+
client.leave(roomId);
|
| 40 |
+
}
|
| 41 |
+
};
|
| 42 |
+
});
|
| 43 |
</script>
|
| 44 |
|
| 45 |
<div class="max-w-screen-md mx-auto px-3 py-8 relative z-0">
|
|
|
|
| 60 |
</button>
|
| 61 |
</form>
|
| 62 |
</div>
|
| 63 |
+
<div class="relative">
|
| 64 |
+
{#if room}
|
| 65 |
+
<Canvas {room} />
|
| 66 |
+
{/if}
|
| 67 |
+
</div>
|
| 68 |
</div>
|
| 69 |
|
| 70 |
<style lang="postcss" scoped>
|