AAAAAAAAAAAAA SYNCEDSTORE

This commit is contained in:
Cat /dev/Nulo 2023-05-07 21:12:39 -03:00
parent ad74edb359
commit 7e51128145
15 changed files with 316 additions and 97 deletions

View file

@ -21,7 +21,10 @@
"vite-plugin-wasm": "^3.2.2"
},
"dependencies": {
"@syncedstore/core": "^0.5.1",
"@syncedstore/svelte": "^0.5.1",
"date-fns": "^2.30.0",
"dayjs": "^1.11.7",
"nanoid": "^4.0.2",
"svelt-yjs": "^1.1.0",
"y-indexeddb": "^9.0.10",

View file

@ -1,9 +1,18 @@
lockfileVersion: '6.0'
dependencies:
'@syncedstore/core':
specifier: ^0.5.1
version: 0.5.1(yjs@13.6.0)
'@syncedstore/svelte':
specifier: ^0.5.1
version: 0.5.1(@reactivedata/reactive@0.2.0)(@syncedstore/core@0.5.1)(svelte@3.57.0)
date-fns:
specifier: ^2.30.0
version: 2.30.0
dayjs:
specifier: ^1.11.7
version: 1.11.7
nanoid:
specifier: ^4.0.2
version: 4.0.2
@ -297,6 +306,10 @@ packages:
fastq: 1.15.0
dev: true
/@reactivedata/reactive@0.2.0:
resolution: {integrity: sha512-qWmVSmUrjKHBiD+ZS8TxNNzyk9J6rbcZSZJgcnKQpTLwORZoWuX5pYbl07aU0kzXGLaaVX2O/ZW+SZaBjRQW3g==}
dev: false
/@rollup/plugin-virtual@3.0.1:
resolution: {integrity: sha512-fK8O0IL5+q+GrsMLuACVNk2x21g3yaw+sG2qn16SnUd3IlBsQyvWxLMGHmCmXRMecPjGRSZ/1LmZB4rjQm68og==}
engines: {node: '>=14.0.0'}
@ -438,10 +451,57 @@ packages:
'@swc/core-win32-x64-msvc': 1.3.56
dev: true
/@syncedstore/core@0.5.1(yjs@13.6.0):
resolution: {integrity: sha512-YTd6s2lQrkOYOooQkjKxBi41/4HxORnjiXxBU+fk+Qb7oq9ZD5XVaFkt8mrJcw4NQ/MZK3aGkaphdvETFPc7Kw==}
peerDependencies:
yjs: ^13.5.13
dependencies:
'@reactivedata/reactive': 0.2.0
'@syncedstore/yjs-reactive-bindings': 0.5.1(yjs@13.6.0)
'@types/eslint': 6.8.0
yjs: 13.6.0
dev: false
/@syncedstore/svelte@0.5.1(@reactivedata/reactive@0.2.0)(@syncedstore/core@0.5.1)(svelte@3.57.0):
resolution: {integrity: sha512-9o1w5Pg6gkB41dBasef1sBFq5IvgrNzEO4IeBy8aVo1NtUWiv2h2Mp3q3FnpVpsdoDDvwwZ9m/ZTZ3B3Ie/Uaw==}
peerDependencies:
'@reactivedata/reactive': '*'
'@syncedstore/core': '*'
svelte: '*'
dependencies:
'@reactivedata/reactive': 0.2.0
'@syncedstore/core': 0.5.1(yjs@13.6.0)
svelte: 3.57.0
dev: false
/@syncedstore/yjs-reactive-bindings@0.5.1(yjs@13.6.0):
resolution: {integrity: sha512-M1/YrK0gAiQAhTCekXtp+qAq4DVUdPCsEdsJ2Tcqo176WHcsjr0yOqjAYrva1Phh4DPTVx1GPWTCUWpxUBR1ug==}
peerDependencies:
yjs: ^13.5.13
dependencies:
'@types/eslint': 6.8.0
yjs: 13.6.0
dev: false
/@tsconfig/svelte@4.0.1:
resolution: {integrity: sha512-B+XlGpmuAQzJqDoBATNCvEPqQg0HkO7S8pM14QDI5NsmtymzRexQ1N+nX2H6RTtFbuFgaZD4I8AAi8voGg0GLg==}
dev: true
/@types/eslint@6.8.0:
resolution: {integrity: sha512-hqzmggoxkOubpgTdcOltkfc5N8IftRJqU70d1jbOISjjZVPvjcr+CLi2CI70hx1SUIRkLgpglTy9w28nGe2Hsw==}
dependencies:
'@types/estree': 1.0.1
'@types/json-schema': 7.0.11
dev: false
/@types/estree@1.0.1:
resolution: {integrity: sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==}
dev: false
/@types/json-schema@7.0.11:
resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==}
dev: false
/@types/pug@2.0.6:
resolution: {integrity: sha512-SnHmG9wN1UVmagJOnyo/qkk0Z7gejYxOYYmaAwr5u2yFYfsupN3sg10kyzN8Hep/2zbHxCnsumxOoRIRMBwKCg==}
dev: true
@ -530,6 +590,10 @@ packages:
'@babel/runtime': 7.21.5
dev: false
/dayjs@1.11.7:
resolution: {integrity: sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==}
dev: false
/debug@4.3.4:
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
engines: {node: '>=6.0'}
@ -1063,7 +1127,6 @@ packages:
/svelte@3.57.0:
resolution: {integrity: sha512-WMXEvF+RtAaclw0t3bPDTUe19pplMlfyKDsixbHQYgCWi9+O9VN0kXU1OppzrB9gPAvz4NALuoca2LfW2bOjTQ==}
engines: {node: '>= 8'}
dev: true
/to-regex-range@5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}

View file

@ -77,3 +77,12 @@ button:focus-visible {
background-color: #f9f9f9;
}
}
.icon-btn {
line-height: 1;
padding: 0.2em;
}
.icon-btn > svg {
width: 1.5em;
height: 1.5em;
}

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><g data-name="Layer 2"><g data-name="edit"><rect width="24" height="24" opacity="0"/><path d="M19.4 7.34L16.66 4.6A2 2 0 0 0 14 4.53l-9 9a2 2 0 0 0-.57 1.21L4 18.91a1 1 0 0 0 .29.8A1 1 0 0 0 5 20h.09l4.17-.38a2 2 0 0 0 1.21-.57l9-9a1.92 1.92 0 0 0-.07-2.71zM9.08 17.62l-3 .28.27-3L12 9.32l2.7 2.7zM16 10.68L13.32 8l1.95-2L18 8.73z"/></g></g></svg>

After

Width:  |  Height:  |  Size: 427 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><g data-name="Layer 2"><g data-name="save"><rect width="24" height="24" opacity="0"/><path d="M20.12 8.71l-4.83-4.83A3 3 0 0 0 13.17 3H6a3 3 0 0 0-3 3v12a3 3 0 0 0 3 3h12a3 3 0 0 0 3-3v-7.17a3 3 0 0 0-.88-2.12zM10 19v-2h4v2zm9-1a1 1 0 0 1-1 1h-2v-3a1 1 0 0 0-1-1H9a1 1 0 0 0-1 1v3H6a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1h2v5a1 1 0 0 0 1 1h4a1 1 0 0 0 0-2h-3V5h3.17a1.05 1.05 0 0 1 .71.29l4.83 4.83a1 1 0 0 1 .29.71z"/></g></g></svg>

After

Width:  |  Height:  |  Size: 504 B

View file

@ -1,16 +1,27 @@
import * as Y from "yjs";
import { WebrtcProvider } from "y-webrtc";
import { IndexeddbPersistence } from "y-indexeddb";
import { syncedStore, getYjsDoc } from "@syncedstore/core";
import { svelteSyncedStore } from "@syncedstore/svelte";
import { nanoid } from "nanoid";
import type { MappedTypeDescription } from "@syncedstore/core/types/doc";
export type UserIdentifier = {
room: string;
password: string;
};
type SyncedStoreType = MappedTypeDescription<{
doing: Doing[];
done: Done[];
name: Y.Text;
}>;
export type UserY = {
ydoc: Y.Doc;
// ydoc: Y.Doc;
webrtcProvider: WebrtcProvider;
store: SyncedStoreType;
svelteStore: ReturnType<typeof svelteSyncedStore<SyncedStoreType>>;
};
export function createUser(): UserIdentifier {
@ -35,7 +46,14 @@ const credsReq = fetch(
export function getUserY(world: UserIdentifier): UserY {
if (userYCache[world.room]) return userYCache[world.room];
const ydoc = new Y.Doc();
const store = syncedStore({
doing: [] as Doing[],
done: [] as Done[],
name: "text",
});
const svelteStore = svelteSyncedStore(store);
const ydoc = getYjsDoc(store);
const provider = new WebrtcProvider(world.room, ydoc, {
password: world.password,
signaling: [
@ -59,7 +77,7 @@ export function getUserY(world: UserIdentifier): UserY {
}
});
const idbProvider = new IndexeddbPersistence(world.room, ydoc);
const worldY = { ydoc, webrtcProvider: provider };
const worldY = { webrtcProvider: provider, store, svelteStore };
userYCache[world.room] = worldY;
return worldY;
}
@ -77,9 +95,9 @@ export type Done = {
started: number;
took: number;
};
export function getData(userY: UserY) {
const ydoing: Y.Array<Doing> = userY.ydoc.getArray("doing");
const ydone: Y.Array<Done> = userY.ydoc.getArray("done");
const yname: Y.Text = userY.ydoc.getText("name");
return { ydoing, ydone, yname };
}
// export function getData(userY: UserY) {
// const ydoing: Y.Array<Doing> = userY.ydoc.getArray("doing");
// const ydone: Y.Array<Done> = userY.ydoc.getArray("done");
// const yname: Y.Text = userY.ydoc.getText("name");
// return { ydoing, ydone, yname };
// }

View file

@ -1,11 +1,7 @@
<script lang="ts">
import { derived, get } from "svelte/store";
import { getData, getUserY, parseUser, type UserIdentifier } from "../doc";
import { getUserY, parseUser, type UserIdentifier } from "../doc";
import friends, { friendsData } from "./stores/friends";
$: allData = derived($friendsData, (values) =>
values.map((v, i) => ({ ...v, id: get(friends)[i] }))
);
import Friend from "./Amigxs/Friend.svelte";
export let myUser: UserIdentifier;
@ -25,24 +21,21 @@
}
$: userY = getUserY(myUser);
$: data = getData(userY);
$: userStore = userY.svelteStore;
const changeName = (name: string) => {
// TODO: no ideal
data.yname.delete(0, data.yname.length);
data.yname.insert(0, name);
$userStore.name.delete(0, $userStore.name.length);
$userStore.name.insert(0, name);
};
$: id = `${myUser.room};${myUser.password}`;
const copiar = () => navigator.clipboard.writeText(id);
const remove = (id: UserIdentifier) =>
($friends = $friends.filter((i) => i.room !== id.room));
</script>
<button on:click={copiar}>copiar tu id</button>
<input
type="text"
value={data.yname.toString()}
value={$userStore.name.toString()}
on:input={(e) => changeName(e.currentTarget.value)}
placeholder="tu nombre"
/>
@ -53,14 +46,7 @@
</form>
<ul>
{#each $allData as friend}
<li>
{#if friend.name}
{friend.name} <small>({friend.id.room})</small>
{:else}
{friend.id.room}
{/if}
<button on:click={() => remove(friend.id)}>❌</button>
</li>
{#each $friendsData as friend}
<li><Friend friend={friend.userY} id={friend.id} /></li>
{/each}
</ul>

View file

@ -0,0 +1,19 @@
<script lang="ts">
import type { UserIdentifier, UserY } from "../../doc";
import friends from "../stores/friends";
export let friend: UserY;
export let id: UserIdentifier;
$: friendStore = friend.svelteStore;
const remove = (id: UserIdentifier) =>
($friends = $friends.filter((i) => i.room !== id.room));
</script>
{#if $friendStore.name.toString()}
{$friendStore.name} <small>({id.room})</small>
{:else}
{id.room}
{/if}
<button on:click={() => remove(id)}>❌</button>

View file

@ -1,37 +1,35 @@
<script lang="ts">
import * as Y from "yjs";
import { getData, getUserY, type UserIdentifier } from "../doc";
import { readableArray } from "svelt-yjs/src/main";
import {
formatDuration,
intervalToDuration,
formatDistance,
formatDistanceStrict,
} from "date-fns";
import { getUserY, type UserIdentifier } from "../doc";
import { formatDuration, intervalToDuration } from "date-fns";
import timer from "./timer";
import PersonalDoneThing from "./PersonalDoneThing.svelte";
import { derived } from "svelte/store";
export let user: UserIdentifier;
$: userY = getUserY(user);
$: ({ ydoing, ydone } = getData(userY));
$: doing = readableArray(ydoing);
$: done = readableArray(ydone);
$: sortedDone = $done.sort((a, b) => b.started - a.started);
$: userStore = userY.svelteStore;
$: sortedDone = derived(userStore, ($user) =>
[...$user.done].sort((a, b) => b.started - a.started)
);
const start = (description: string) => {
doing.y.insert(0, [{ description, started: new Date().getTime() }]);
$userStore.doing.push({ description, started: new Date().getTime() });
};
const finish = (index: number) => {
const thing = doing.y.get(index);
doing.y.delete(index);
done.y.push([{ ...thing, took: new Date().getTime() - thing.started }]);
const thing = { ...$userStore.doing[index] };
$userStore.doing.splice(index, 1);
$userStore.done.push({
...thing,
took: new Date().getTime() - thing.started,
});
};
const sTimer = timer(1000);
</script>
{#if $doing.length > 0}
{#each $doing as doing, index}
{#if $userStore.doing.length > 0}
{#each $userStore.doing as doing, index}
<div>
<div>
<span>{doing.description} </span>
@ -53,17 +51,12 @@
</form>
{/if}
{#if $done.length > 0}
{#if $userStore.done.length > 0}
<h2>Ya hecho</h2>
<ul>
{#each sortedDone as thing}
{#each $sortedDone as thing}
<li>
{thing.description}
({formatDistance(
new Date(thing.took + thing.started),
new Date(thing.started)
)})
<PersonalDoneThing {thing} />
</li>
{/each}
</ul>

View file

@ -1,46 +1,36 @@
<script lang="ts">
import { derived, get } from "svelte/store";
import timer from "./timer";
import { formatDistance, intervalToDuration } from "date-fns";
import friends, { friendsData } from "./stores/friends";
import formatDistanceShort from "./helpers/formatDistanceShort";
import friends, { friendsData } from "./stores/friends";
import { derived, get } from "svelte/store";
import FriendDoing from "./Others/FriendDoing.svelte";
$: allData = derived($friendsData, (values) =>
values.map((v, i) => ({ ...v, id: get(friends)[i] }))
$: sortedDone = derived(
$friendsData.map((d) => d.userY.svelteStore),
($values) =>
$values.flatMap(($f, i) =>
$f.done
.map((thing) => ({ thing, friend: $f, id: get(friends)[i] }))
.sort((a, b) => b.thing.started - a.thing.started)
)
);
// $: done = readableArray(ydone);
$: sortedDone = $allData
.flatMap((d) => d.done.map((t) => ({ ...t, friend: d })))
.sort((a, b) => b.started - a.started);
const sTimer = timer(1000);
const mTimer = timer(1000 * 60);
</script>
<section>
<h2>actualmente:</h2>
<ul>
{#each $allData as data}
{#if data.doing.length > 0}
<li>
<strong>{data.name || data.id.room}</strong>
{#each data.doing as d, i}
{#if i !== 0} y {/if}
{d.description}
hace
{formatDistanceShort(
intervalToDuration({ start: $sTimer, end: new Date(d.started) })
)}
{/each}
</li>
{/if}
{#each $friendsData as friend}
<FriendDoing friend={friend.userY} id={friend.id} />
{/each}
</ul>
<h2>previamente:</h2>
<ul>
{#each sortedDone as thing}
{#each $sortedDone as { thing, friend, id: friendId }}
<li>
<strong>{thing.friend.name || thing.friend.id.room}</strong>
<strong>{friend.name || friendId.room}</strong>
hizo
<em>{thing.description}</em>
por

View file

@ -0,0 +1,27 @@
<script lang="ts">
import { intervalToDuration } from "date-fns";
import type { UserIdentifier, UserY } from "../../doc";
import formatDistanceShort from "../helpers/formatDistanceShort";
import timer from "../timer";
export let friend: UserY;
export let id: UserIdentifier;
$: friendStore = friend.svelteStore;
const sTimer = timer(1000);
</script>
{#if $friendStore.doing.length > 0}
<li>
<strong>{$friendStore.name || id.room}</strong>
{#each $friendStore.doing as d, i}
{#if i !== 0} y {/if}
{d.description}
hace
{formatDistanceShort(
intervalToDuration({ start: $sTimer, end: new Date(d.started) })
)}
{/each}
</li>
{/if}

View file

@ -0,0 +1,38 @@
<script>
import EditIcon from "./../assets/eva-icons/edit-outline.svg?raw";
import SaveIcon from "./../assets/eva-icons/save-outline.svg?raw";
import DurationInput from "./general-components/DurationInput.svelte";
import DateTimeInput from "./general-components/DateTimeInput.svelte";
import { formatDistance } from "date-fns";
/** @type {import("../doc").Done} */
export let thing;
let editing = false;
const finishEditing = () => {
editing = false;
};
</script>
{#if editing}
<input type="text" bind:value={thing.description} />
<br />
<label>Empezó </label><DateTimeInput bind:date={thing.started} />
<br />
<label>Duró </label><DurationInput bind:value={thing.took} />
<br />
<button class="icon-btn" on:click={finishEditing}>
{@html SaveIcon}
</button>
{:else}
{thing.description}
({formatDistance(
new Date(thing.took + thing.started),
new Date(thing.started)
)})
<button class="icon-btn" on:click={() => (editing = true)}>
{@html EditIcon}
</button>
{/if}

View file

@ -0,0 +1,19 @@
<script>
// https://svelte.dev/repl/dc963bbead384b69aad17824149d6d27?version=3.25.1
import dayjs from "dayjs";
export let date = new Date().getTime();
let internal;
const format = "YYYY-MM-DDTHH:mm";
const input = (/** @type {number} */ x) =>
(internal = dayjs(x).format(format));
const output = (/** @type {string} */ x) =>
(date = dayjs(x, format).toDate().getTime());
$: input(date);
$: output(internal);
</script>
<input type="datetime-local" bind:value={internal} />

View file

@ -0,0 +1,63 @@
<script>
import dayjs from "dayjs";
/** @type {number} */
export let value;
/** @type {number} */
let hours;
/** @type {number} */
let minutes;
/** @type {number} */
let seconds;
const input = (value) => {
// https://code-boxx.com/simple-javascript-stopwatch/ real
let remain = Math.floor(value / 1000);
hours = Math.floor(remain / (60 * 60));
remain -= hours * (60 * 60);
minutes = Math.floor(remain / 60);
remain -= minutes * 60;
seconds = remain;
};
$: input(value);
const output = () =>
(value = (seconds + minutes * 60 + hours * 60 * 60) * 1000);
</script>
<input
type="number"
min="0"
max="23"
placeholder="hs"
size="2"
bind:value={hours}
on:input={output}
/>
:
<input
type="number"
min="0"
max="59"
placeholder="min"
size="2"
bind:value={minutes}
on:input={output}
/>
:
<input
type="number"
min="0"
max="59"
placeholder="sec"
size="2"
bind:value={seconds}
on:input={output}
/>
<style>
input[type="number"] {
width: 3em;
}
</style>

View file

@ -1,8 +1,5 @@
import { derived, writable } from "svelte/store";
import { readableArray } from "svelt-yjs/src/main";
import { getData, getUserY, type UserIdentifier } from "../../doc";
import { readableText } from "../helpers/readableText";
import { deriveObj } from "../helpers/recursiveReadable";
import { getUserY, type UserIdentifier } from "../../doc";
type Type = UserIdentifier[];
@ -14,13 +11,5 @@ store.subscribe(save);
export default store;
export const friendsData = derived(store, ($friends) =>
$friends
.map((id) => ({ id, ...getUserY(id) }))
.map((y) => ({ ...y, ...getData(y) }))
.map((y) => ({
name: readableText(y.yname),
doing: readableArray(y.ydoing),
done: readableArray(y.ydone),
}))
.map((d) => deriveObj(d))
$friends.map((id) => ({ id, userY: getUserY(id) }))
);