Compare commits
5 commits
ec692186d5
...
69ca45ff48
Author | SHA1 | Date | |
---|---|---|---|
69ca45ff48 | |||
9a2dda49cb | |||
a8ad7054f1 | |||
7e51128145 | |||
ad74edb359 |
15 changed files with 318 additions and 99 deletions
|
@ -7,7 +7,8 @@
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"check": "svelte-check --tsconfig ./tsconfig.json"
|
"check": "svelte-check --tsconfig ./tsconfig.json",
|
||||||
|
"upload": "rsync --recursive --rsh='ssh -p2222' dist/ root@192.168.1.243:/data/beta_enprogreso_nulo_ar/"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sveltejs/vite-plugin-svelte": "^2.0.3",
|
"@sveltejs/vite-plugin-svelte": "^2.0.3",
|
||||||
|
@ -21,7 +22,10 @@
|
||||||
"vite-plugin-wasm": "^3.2.2"
|
"vite-plugin-wasm": "^3.2.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@syncedstore/core": "^0.5.1",
|
||||||
|
"@syncedstore/svelte": "^0.5.1",
|
||||||
"date-fns": "^2.30.0",
|
"date-fns": "^2.30.0",
|
||||||
|
"dayjs": "^1.11.7",
|
||||||
"nanoid": "^4.0.2",
|
"nanoid": "^4.0.2",
|
||||||
"svelt-yjs": "^1.1.0",
|
"svelt-yjs": "^1.1.0",
|
||||||
"y-indexeddb": "^9.0.10",
|
"y-indexeddb": "^9.0.10",
|
||||||
|
|
|
@ -1,9 +1,18 @@
|
||||||
lockfileVersion: '6.0'
|
lockfileVersion: '6.0'
|
||||||
|
|
||||||
dependencies:
|
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:
|
date-fns:
|
||||||
specifier: ^2.30.0
|
specifier: ^2.30.0
|
||||||
version: 2.30.0
|
version: 2.30.0
|
||||||
|
dayjs:
|
||||||
|
specifier: ^1.11.7
|
||||||
|
version: 1.11.7
|
||||||
nanoid:
|
nanoid:
|
||||||
specifier: ^4.0.2
|
specifier: ^4.0.2
|
||||||
version: 4.0.2
|
version: 4.0.2
|
||||||
|
@ -297,6 +306,10 @@ packages:
|
||||||
fastq: 1.15.0
|
fastq: 1.15.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@reactivedata/reactive@0.2.0:
|
||||||
|
resolution: {integrity: sha512-qWmVSmUrjKHBiD+ZS8TxNNzyk9J6rbcZSZJgcnKQpTLwORZoWuX5pYbl07aU0kzXGLaaVX2O/ZW+SZaBjRQW3g==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@rollup/plugin-virtual@3.0.1:
|
/@rollup/plugin-virtual@3.0.1:
|
||||||
resolution: {integrity: sha512-fK8O0IL5+q+GrsMLuACVNk2x21g3yaw+sG2qn16SnUd3IlBsQyvWxLMGHmCmXRMecPjGRSZ/1LmZB4rjQm68og==}
|
resolution: {integrity: sha512-fK8O0IL5+q+GrsMLuACVNk2x21g3yaw+sG2qn16SnUd3IlBsQyvWxLMGHmCmXRMecPjGRSZ/1LmZB4rjQm68og==}
|
||||||
engines: {node: '>=14.0.0'}
|
engines: {node: '>=14.0.0'}
|
||||||
|
@ -438,10 +451,57 @@ packages:
|
||||||
'@swc/core-win32-x64-msvc': 1.3.56
|
'@swc/core-win32-x64-msvc': 1.3.56
|
||||||
dev: true
|
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:
|
/@tsconfig/svelte@4.0.1:
|
||||||
resolution: {integrity: sha512-B+XlGpmuAQzJqDoBATNCvEPqQg0HkO7S8pM14QDI5NsmtymzRexQ1N+nX2H6RTtFbuFgaZD4I8AAi8voGg0GLg==}
|
resolution: {integrity: sha512-B+XlGpmuAQzJqDoBATNCvEPqQg0HkO7S8pM14QDI5NsmtymzRexQ1N+nX2H6RTtFbuFgaZD4I8AAi8voGg0GLg==}
|
||||||
dev: true
|
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:
|
/@types/pug@2.0.6:
|
||||||
resolution: {integrity: sha512-SnHmG9wN1UVmagJOnyo/qkk0Z7gejYxOYYmaAwr5u2yFYfsupN3sg10kyzN8Hep/2zbHxCnsumxOoRIRMBwKCg==}
|
resolution: {integrity: sha512-SnHmG9wN1UVmagJOnyo/qkk0Z7gejYxOYYmaAwr5u2yFYfsupN3sg10kyzN8Hep/2zbHxCnsumxOoRIRMBwKCg==}
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -530,6 +590,10 @@ packages:
|
||||||
'@babel/runtime': 7.21.5
|
'@babel/runtime': 7.21.5
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/dayjs@1.11.7:
|
||||||
|
resolution: {integrity: sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/debug@4.3.4:
|
/debug@4.3.4:
|
||||||
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
|
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
|
||||||
engines: {node: '>=6.0'}
|
engines: {node: '>=6.0'}
|
||||||
|
@ -1063,7 +1127,6 @@ packages:
|
||||||
/svelte@3.57.0:
|
/svelte@3.57.0:
|
||||||
resolution: {integrity: sha512-WMXEvF+RtAaclw0t3bPDTUe19pplMlfyKDsixbHQYgCWi9+O9VN0kXU1OppzrB9gPAvz4NALuoca2LfW2bOjTQ==}
|
resolution: {integrity: sha512-WMXEvF+RtAaclw0t3bPDTUe19pplMlfyKDsixbHQYgCWi9+O9VN0kXU1OppzrB9gPAvz4NALuoca2LfW2bOjTQ==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/to-regex-range@5.0.1:
|
/to-regex-range@5.0.1:
|
||||||
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
|
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
|
||||||
|
|
10
src/app.css
10
src/app.css
|
@ -44,7 +44,6 @@ h1 {
|
||||||
max-width: 1280px;
|
max-width: 1280px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
text-align: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
|
@ -78,3 +77,12 @@ button:focus-visible {
|
||||||
background-color: #f9f9f9;
|
background-color: #f9f9f9;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-btn {
|
||||||
|
line-height: 1;
|
||||||
|
padding: 0.2em;
|
||||||
|
}
|
||||||
|
.icon-btn > svg {
|
||||||
|
width: 1.5em;
|
||||||
|
height: 1.5em;
|
||||||
|
}
|
||||||
|
|
1
src/assets/eva-icons/edit-outline.svg
Normal file
1
src/assets/eva-icons/edit-outline.svg
Normal 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 |
1
src/assets/eva-icons/save-outline.svg
Normal file
1
src/assets/eva-icons/save-outline.svg
Normal 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 |
36
src/doc.ts
36
src/doc.ts
|
@ -1,16 +1,27 @@
|
||||||
import * as Y from "yjs";
|
import * as Y from "yjs";
|
||||||
import { WebrtcProvider } from "y-webrtc";
|
import { WebrtcProvider } from "y-webrtc";
|
||||||
import { IndexeddbPersistence } from "y-indexeddb";
|
import { IndexeddbPersistence } from "y-indexeddb";
|
||||||
|
import { syncedStore, getYjsDoc } from "@syncedstore/core";
|
||||||
|
import { svelteSyncedStore } from "@syncedstore/svelte";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
|
import type { MappedTypeDescription } from "@syncedstore/core/types/doc";
|
||||||
|
|
||||||
export type UserIdentifier = {
|
export type UserIdentifier = {
|
||||||
room: string;
|
room: string;
|
||||||
password: string;
|
password: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type SyncedStoreType = MappedTypeDescription<{
|
||||||
|
doing: Doing[];
|
||||||
|
done: Done[];
|
||||||
|
name: Y.Text;
|
||||||
|
}>;
|
||||||
|
|
||||||
export type UserY = {
|
export type UserY = {
|
||||||
ydoc: Y.Doc;
|
// ydoc: Y.Doc;
|
||||||
webrtcProvider: WebrtcProvider;
|
webrtcProvider: WebrtcProvider;
|
||||||
|
store: SyncedStoreType;
|
||||||
|
svelteStore: ReturnType<typeof svelteSyncedStore<SyncedStoreType>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function createUser(): UserIdentifier {
|
export function createUser(): UserIdentifier {
|
||||||
|
@ -35,7 +46,14 @@ const credsReq = fetch(
|
||||||
|
|
||||||
export function getUserY(world: UserIdentifier): UserY {
|
export function getUserY(world: UserIdentifier): UserY {
|
||||||
if (userYCache[world.room]) return userYCache[world.room];
|
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, {
|
const provider = new WebrtcProvider(world.room, ydoc, {
|
||||||
password: world.password,
|
password: world.password,
|
||||||
signaling: [
|
signaling: [
|
||||||
|
@ -59,7 +77,7 @@ export function getUserY(world: UserIdentifier): UserY {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const idbProvider = new IndexeddbPersistence(world.room, ydoc);
|
const idbProvider = new IndexeddbPersistence(world.room, ydoc);
|
||||||
const worldY = { ydoc, webrtcProvider: provider };
|
const worldY = { webrtcProvider: provider, store, svelteStore };
|
||||||
userYCache[world.room] = worldY;
|
userYCache[world.room] = worldY;
|
||||||
return worldY;
|
return worldY;
|
||||||
}
|
}
|
||||||
|
@ -77,9 +95,9 @@ export type Done = {
|
||||||
started: number;
|
started: number;
|
||||||
took: number;
|
took: number;
|
||||||
};
|
};
|
||||||
export function getData(userY: UserY) {
|
// export function getData(userY: UserY) {
|
||||||
const ydoing: Y.Array<Doing> = userY.ydoc.getArray("doing");
|
// const ydoing: Y.Array<Doing> = userY.ydoc.getArray("doing");
|
||||||
const ydone: Y.Array<Done> = userY.ydoc.getArray("done");
|
// const ydone: Y.Array<Done> = userY.ydoc.getArray("done");
|
||||||
const yname: Y.Text = userY.ydoc.getText("name");
|
// const yname: Y.Text = userY.ydoc.getText("name");
|
||||||
return { ydoing, ydone, yname };
|
// return { ydoing, ydone, yname };
|
||||||
}
|
// }
|
||||||
|
|
|
@ -1,11 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { derived, get } from "svelte/store";
|
import { getUserY, parseUser, type UserIdentifier } from "../doc";
|
||||||
import { getData, getUserY, parseUser, type UserIdentifier } from "../doc";
|
|
||||||
import friends, { friendsData } from "./stores/friends";
|
import friends, { friendsData } from "./stores/friends";
|
||||||
|
import Friend from "./Amigxs/Friend.svelte";
|
||||||
$: allData = derived($friendsData, (values) =>
|
|
||||||
values.map((v, i) => ({ ...v, id: get(friends)[i] }))
|
|
||||||
);
|
|
||||||
|
|
||||||
export let myUser: UserIdentifier;
|
export let myUser: UserIdentifier;
|
||||||
|
|
||||||
|
@ -25,24 +21,21 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
$: userY = getUserY(myUser);
|
$: userY = getUserY(myUser);
|
||||||
$: data = getData(userY);
|
$: userStore = userY.svelteStore;
|
||||||
const changeName = (name: string) => {
|
const changeName = (name: string) => {
|
||||||
// TODO: no ideal
|
// TODO: no ideal
|
||||||
data.yname.delete(0, data.yname.length);
|
$userStore.name.delete(0, $userStore.name.length);
|
||||||
data.yname.insert(0, name);
|
$userStore.name.insert(0, name);
|
||||||
};
|
};
|
||||||
|
|
||||||
$: id = `${myUser.room};${myUser.password}`;
|
$: id = `${myUser.room};${myUser.password}`;
|
||||||
const copiar = () => navigator.clipboard.writeText(id);
|
const copiar = () => navigator.clipboard.writeText(id);
|
||||||
|
|
||||||
const remove = (id: UserIdentifier) =>
|
|
||||||
($friends = $friends.filter((i) => i.room !== id.room));
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<button on:click={copiar}>copiar tu id</button>
|
<button on:click={copiar}>copiar tu id</button>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={data.yname.toString()}
|
value={$userStore.name.toString()}
|
||||||
on:input={(e) => changeName(e.currentTarget.value)}
|
on:input={(e) => changeName(e.currentTarget.value)}
|
||||||
placeholder="tu nombre"
|
placeholder="tu nombre"
|
||||||
/>
|
/>
|
||||||
|
@ -53,14 +46,7 @@
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
{#each $allData as friend}
|
{#each $friendsData as friend}
|
||||||
<li>
|
<li><Friend friend={friend.userY} id={friend.id} /></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}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
19
src/lib/Amigxs/Friend.svelte
Normal file
19
src/lib/Amigxs/Friend.svelte
Normal 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>
|
|
@ -1,37 +1,35 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import * as Y from "yjs";
|
import { getUserY, type UserIdentifier } from "../doc";
|
||||||
import { getData, getUserY, type UserIdentifier } from "../doc";
|
import { formatDuration, intervalToDuration } from "date-fns";
|
||||||
import { readableArray } from "svelt-yjs/src/main";
|
|
||||||
import {
|
|
||||||
formatDuration,
|
|
||||||
intervalToDuration,
|
|
||||||
formatDistance,
|
|
||||||
formatDistanceStrict,
|
|
||||||
} from "date-fns";
|
|
||||||
import timer from "./timer";
|
import timer from "./timer";
|
||||||
|
import PersonalDoneThing from "./PersonalDoneThing.svelte";
|
||||||
|
import { derived } from "svelte/store";
|
||||||
|
|
||||||
export let user: UserIdentifier;
|
export let user: UserIdentifier;
|
||||||
|
|
||||||
$: userY = getUserY(user);
|
$: userY = getUserY(user);
|
||||||
$: ({ ydoing, ydone } = getData(userY));
|
$: userStore = userY.svelteStore;
|
||||||
$: doing = readableArray(ydoing);
|
$: sortedDone = derived(userStore, ($user) =>
|
||||||
$: done = readableArray(ydone);
|
[...$user.done].sort((a, b) => b.started - a.started)
|
||||||
$: sortedDone = $done.sort((a, b) => b.started - a.started);
|
);
|
||||||
|
|
||||||
const start = (description: string) => {
|
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 finish = (index: number) => {
|
||||||
const thing = doing.y.get(index);
|
const thing = { ...$userStore.doing[index] };
|
||||||
doing.y.delete(index);
|
$userStore.doing.splice(index, 1);
|
||||||
done.y.push([{ ...thing, took: new Date().getTime() - thing.started }]);
|
$userStore.done.push({
|
||||||
|
...thing,
|
||||||
|
took: new Date().getTime() - thing.started,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const sTimer = timer(1000);
|
const sTimer = timer(1000);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $doing.length > 0}
|
{#if $userStore.doing.length > 0}
|
||||||
{#each $doing as doing, index}
|
{#each $userStore.doing as doing, index}
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<span>{doing.description} </span>
|
<span>{doing.description} </span>
|
||||||
|
@ -53,17 +51,12 @@
|
||||||
</form>
|
</form>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if $done.length > 0}
|
{#if $userStore.done.length > 0}
|
||||||
<h2>Ya hecho</h2>
|
<h2>Ya hecho</h2>
|
||||||
<ul>
|
<ul>
|
||||||
{#each sortedDone as thing}
|
{#each $sortedDone as thing}
|
||||||
<li>
|
<li>
|
||||||
{thing.description}
|
<PersonalDoneThing {thing} />
|
||||||
|
|
||||||
({formatDistance(
|
|
||||||
new Date(thing.took + thing.started),
|
|
||||||
new Date(thing.started)
|
|
||||||
)})
|
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -1,46 +1,36 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { derived, get } from "svelte/store";
|
|
||||||
import timer from "./timer";
|
import timer from "./timer";
|
||||||
import { formatDistance, intervalToDuration } from "date-fns";
|
import { formatDistance, intervalToDuration } from "date-fns";
|
||||||
import friends, { friendsData } from "./stores/friends";
|
|
||||||
import formatDistanceShort from "./helpers/formatDistanceShort";
|
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) =>
|
$: sortedDone = derived(
|
||||||
values.map((v, i) => ({ ...v, id: get(friends)[i] }))
|
$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);
|
const mTimer = timer(1000 * 60);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2>actualmente:</h2>
|
<h2>actualmente:</h2>
|
||||||
<ul>
|
<ul>
|
||||||
{#each $allData as data}
|
{#each $friendsData as friend}
|
||||||
{#if data.doing.length > 0}
|
<FriendDoing friend={friend.userY} id={friend.id} />
|
||||||
<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}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
<h2>previamente:</h2>
|
<h2>previamente:</h2>
|
||||||
<ul>
|
<ul>
|
||||||
{#each sortedDone as thing}
|
{#each $sortedDone as { thing, friend, id: friendId }}
|
||||||
<li>
|
<li>
|
||||||
<strong>{thing.friend.name || thing.friend.id.room}</strong>
|
<strong>{friend.name.toString() || friendId.room}</strong>
|
||||||
hizo
|
hizo
|
||||||
<em>{thing.description}</em>
|
<em>{thing.description}</em>
|
||||||
por
|
por
|
||||||
|
|
27
src/lib/Others/FriendDoing.svelte
Normal file
27
src/lib/Others/FriendDoing.svelte
Normal 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.toString() || 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}
|
38
src/lib/PersonalDoneThing.svelte
Normal file
38
src/lib/PersonalDoneThing.svelte
Normal 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}
|
19
src/lib/general-components/DateTimeInput.svelte
Normal file
19
src/lib/general-components/DateTimeInput.svelte
Normal 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} />
|
63
src/lib/general-components/DurationInput.svelte
Normal file
63
src/lib/general-components/DurationInput.svelte
Normal 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>
|
|
@ -1,8 +1,5 @@
|
||||||
import { derived, writable } from "svelte/store";
|
import { derived, writable } from "svelte/store";
|
||||||
import { readableArray } from "svelt-yjs/src/main";
|
import { getUserY, type UserIdentifier } from "../../doc";
|
||||||
import { getData, getUserY, type UserIdentifier } from "../../doc";
|
|
||||||
import { readableText } from "../helpers/readableText";
|
|
||||||
import { deriveObj } from "../helpers/recursiveReadable";
|
|
||||||
|
|
||||||
type Type = UserIdentifier[];
|
type Type = UserIdentifier[];
|
||||||
|
|
||||||
|
@ -14,13 +11,5 @@ store.subscribe(save);
|
||||||
export default store;
|
export default store;
|
||||||
|
|
||||||
export const friendsData = derived(store, ($friends) =>
|
export const friendsData = derived(store, ($friends) =>
|
||||||
$friends
|
$friends.map((id) => ({ id, userY: getUserY(id) }))
|
||||||
.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))
|
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in a new issue