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",
|
||||
"build": "vite build",
|
||||
"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": {
|
||||
"@sveltejs/vite-plugin-svelte": "^2.0.3",
|
||||
|
@ -21,7 +22,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",
|
||||
|
|
|
@ -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==}
|
||||
|
|
10
src/app.css
10
src/app.css
|
@ -44,7 +44,6 @@ h1 {
|
|||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
button {
|
||||
|
@ -78,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;
|
||||
}
|
||||
|
|
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 { 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 };
|
||||
// }
|
||||
|
|
|
@ -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>
|
||||
|
|
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">
|
||||
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>
|
||||
|
|
|
@ -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.toString() || friendId.room}</strong>
|
||||
hizo
|
||||
<em>{thing.description}</em>
|
||||
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 { 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) }))
|
||||
);
|
||||
|
|
Loading…
Reference in a new issue