Compare commits
5 commits
8fe21c8202
...
872268cb29
Author | SHA1 | Date | |
---|---|---|---|
872268cb29 | |||
8ead9e4a72 | |||
316388582f | |||
0615ca9aed | |||
898e988bae |
8 changed files with 158 additions and 77 deletions
48
README.md
48
README.md
|
@ -1,47 +1,5 @@
|
||||||
# Svelte + TS + Vite
|
# enprogreso
|
||||||
|
|
||||||
This template should help get you started developing with Svelte and TypeScript in Vite.
|
una plataforma para trackear el tiempo que [nos](https://sutty.coop.ar) lleva hacer cosas y al mismo tiempo socializar _que_ estamos haciendo.
|
||||||
|
|
||||||
## Recommended IDE Setup
|
trabajo en progreso :)
|
||||||
|
|
||||||
[VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode).
|
|
||||||
|
|
||||||
## Need an official Svelte framework?
|
|
||||||
|
|
||||||
Check out [SvelteKit](https://github.com/sveltejs/kit#readme), which is also powered by Vite. Deploy anywhere with its serverless-first approach and adapt to various platforms, with out of the box support for TypeScript, SCSS, and Less, and easily-added support for mdsvex, GraphQL, PostCSS, Tailwind CSS, and more.
|
|
||||||
|
|
||||||
## Technical considerations
|
|
||||||
|
|
||||||
**Why use this over SvelteKit?**
|
|
||||||
|
|
||||||
- It brings its own routing solution which might not be preferable for some users.
|
|
||||||
- It is first and foremost a framework that just happens to use Vite under the hood, not a Vite app.
|
|
||||||
|
|
||||||
This template contains as little as possible to get started with Vite + TypeScript + Svelte, while taking into account the developer experience with regards to HMR and intellisense. It demonstrates capabilities on par with the other `create-vite` templates and is a good starting point for beginners dipping their toes into a Vite + Svelte project.
|
|
||||||
|
|
||||||
Should you later need the extended capabilities and extensibility provided by SvelteKit, the template has been structured similarly to SvelteKit so that it is easy to migrate.
|
|
||||||
|
|
||||||
**Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or `tsconfig.json`?**
|
|
||||||
|
|
||||||
Setting `compilerOptions.types` shuts out all other types not explicitly listed in the configuration. Using triple-slash references keeps the default TypeScript setting of accepting type information from the entire workspace, while also adding `svelte` and `vite/client` type information.
|
|
||||||
|
|
||||||
**Why include `.vscode/extensions.json`?**
|
|
||||||
|
|
||||||
Other templates indirectly recommend extensions via the README, but this file allows VS Code to prompt the user to install the recommended extension upon opening the project.
|
|
||||||
|
|
||||||
**Why enable `allowJs` in the TS template?**
|
|
||||||
|
|
||||||
While `allowJs: false` would indeed prevent the use of `.js` files in the project, it does not prevent the use of JavaScript syntax in `.svelte` files. In addition, it would force `checkJs: false`, bringing the worst of both worlds: not being able to guarantee the entire codebase is TypeScript, and also having worse typechecking for the existing JavaScript. In addition, there are valid use cases in which a mixed codebase may be relevant.
|
|
||||||
|
|
||||||
**Why is HMR not preserving my local component state?**
|
|
||||||
|
|
||||||
HMR state preservation comes with a number of gotchas! It has been disabled by default in both `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read the details [here](https://github.com/rixo/svelte-hmr#svelte-hmr).
|
|
||||||
|
|
||||||
If you have state that's important to retain within a component, consider creating an external store which would not be replaced by HMR.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
// store.ts
|
|
||||||
// An extremely simple external store
|
|
||||||
import { writable } from 'svelte/store'
|
|
||||||
export default writable(0)
|
|
||||||
```
|
|
|
@ -1,5 +1,5 @@
|
||||||
:root {
|
:root {
|
||||||
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
font-family: sans-serif;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
|
||||||
|
|
|
@ -80,5 +80,6 @@ export type Done = {
|
||||||
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");
|
||||||
return { ydoing, ydone };
|
const yname: Y.Text = userY.ydoc.getText("name");
|
||||||
|
return { ydoing, ydone, yname };
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { parseUser, type UserIdentifier } from "../doc";
|
import { derived, get } from "svelte/store";
|
||||||
import friends from "./stores/friends";
|
import { getData, 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] }))
|
||||||
|
);
|
||||||
|
|
||||||
export let myUser: UserIdentifier;
|
export let myUser: UserIdentifier;
|
||||||
|
|
||||||
|
@ -11,15 +16,36 @@
|
||||||
) {
|
) {
|
||||||
const idString: string = event.currentTarget.idd.value;
|
const idString: string = event.currentTarget.idd.value;
|
||||||
const id = parseUser(idString);
|
const id = parseUser(idString);
|
||||||
|
if ($friends.find((i) => i.room === id.room)) {
|
||||||
|
alert("ya agregaste a este ser!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
$friends = [...$friends, id];
|
$friends = [...$friends, id];
|
||||||
event.currentTarget.idd.value = "";
|
event.currentTarget.idd.value = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: userY = getUserY(myUser);
|
||||||
|
$: data = getData(userY);
|
||||||
|
const changeName = (name: string) => {
|
||||||
|
// TODO: no ideal
|
||||||
|
data.yname.delete(0, data.yname.length);
|
||||||
|
data.yname.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
|
||||||
|
type="text"
|
||||||
|
value={data.yname.toString()}
|
||||||
|
on:input={(e) => changeName(e.currentTarget.value)}
|
||||||
|
placeholder="tu nombre"
|
||||||
|
/>
|
||||||
|
|
||||||
<form on:submit|preventDefault={add}>
|
<form on:submit|preventDefault={add}>
|
||||||
<input name="idd" type="text" placeholder="id" />
|
<input name="idd" type="text" placeholder="id" />
|
||||||
|
@ -27,7 +53,14 @@
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
{#each $friends as friend}
|
{#each $allData as friend}
|
||||||
<li>{friend.room}</li>
|
<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>
|
||||||
|
|
|
@ -1,37 +1,30 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { readableArray } from "svelt-yjs/src/main";
|
import { derived, get } from "svelte/store";
|
||||||
import { getData, getUserY, type Doing } from "../doc";
|
|
||||||
import { derived } from "svelte/store";
|
|
||||||
import timer from "./timer";
|
import timer from "./timer";
|
||||||
import { intervalToDuration } from "date-fns";
|
import { formatDistance, intervalToDuration } from "date-fns";
|
||||||
import friends from "./stores/friends";
|
import friends, { friendsData } from "./stores/friends";
|
||||||
import formatDistanceShort from "./helpers/formatDistanceShort";
|
import formatDistanceShort from "./helpers/formatDistanceShort";
|
||||||
|
|
||||||
$: users = $friends
|
$: allData = derived($friendsData, (values) =>
|
||||||
.map((id) => ({ id, ...getUserY(id) }))
|
values.map((v, i) => ({ ...v, id: get(friends)[i] }))
|
||||||
.map((y) => ({ ...y, ...getData(y) }));
|
|
||||||
|
|
||||||
$: allDoing =
|
|
||||||
users &&
|
|
||||||
derived(
|
|
||||||
users.map((y) => readableArray(y.ydoing)),
|
|
||||||
(doings, set: (value: { name: string; $d: Doing[] }[]) => void) => {
|
|
||||||
set(doings.map(($d, i) => ({ name: users[i].id.room, $d })));
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// $: done = readableArray(ydone);
|
// $: done = readableArray(ydone);
|
||||||
// $: sortedDone = $done.sort((a, b) => b.started - a.started);
|
$: sortedDone = $allData
|
||||||
|
.flatMap((d) => d.done.map((t) => ({ ...t, friend: d })))
|
||||||
|
.sort((a, b) => b.started - a.started);
|
||||||
const sTimer = timer(1000);
|
const sTimer = timer(1000);
|
||||||
|
const hTimer = timer(1000 * 60 * 60);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if allDoing}
|
<section>
|
||||||
|
<h2>actualmente:</h2>
|
||||||
<ul>
|
<ul>
|
||||||
{#each $allDoing as doing}
|
{#each $allData as data}
|
||||||
{#if doing.$d.length > 0}
|
{#if data.doing.length > 0}
|
||||||
<li>
|
<li>
|
||||||
<strong>{doing.name}</strong>
|
<strong>{data.name || data.id.room}</strong>
|
||||||
{#each doing.$d as d, i}
|
{#each data.doing as d, i}
|
||||||
{#if i !== 0} y {/if}
|
{#if i !== 0} y {/if}
|
||||||
{d.description}
|
{d.description}
|
||||||
hace
|
hace
|
||||||
|
@ -43,4 +36,30 @@
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
{/if}
|
<h2>previamente:</h2>
|
||||||
|
<ul>
|
||||||
|
{#each sortedDone as thing}
|
||||||
|
<li>
|
||||||
|
<strong>{thing.friend.name || thing.friend.id.room}</strong>
|
||||||
|
hizo
|
||||||
|
<em>{thing.description}</em>
|
||||||
|
por
|
||||||
|
{formatDistanceShort(
|
||||||
|
intervalToDuration({
|
||||||
|
start: new Date(thing.started),
|
||||||
|
end: new Date(thing.started + thing.took),
|
||||||
|
})
|
||||||
|
)}
|
||||||
|
hace
|
||||||
|
{formatDistance($hTimer, new Date(thing.started + thing.took))}
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
section {
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
21
src/lib/helpers/readableText.js
Normal file
21
src/lib/helpers/readableText.js
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import * as Y from "yjs";
|
||||||
|
|
||||||
|
/** @typedef {import('svelte/store').Readable<string> & { y: Y.Text }} YReadableText */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Y.Text} ytext
|
||||||
|
* @returns {YReadableText}
|
||||||
|
*/
|
||||||
|
export function readableText(ytext) {
|
||||||
|
return {
|
||||||
|
subscribe(run) {
|
||||||
|
const cb = () => run(ytext.toString());
|
||||||
|
cb();
|
||||||
|
ytext.observe(cb);
|
||||||
|
return () => {
|
||||||
|
ytext.unobserve(cb);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
y: ytext,
|
||||||
|
};
|
||||||
|
}
|
34
src/lib/helpers/recursiveReadable.js
Normal file
34
src/lib/helpers/recursiveReadable.js
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
// /**
|
||||||
|
// * @template Type
|
||||||
|
// * @typedef {{
|
||||||
|
// [Property in keyof Type]: Type[Property] extends import("svelte/store").Readable ? Type[Property]['subscribe']['arguments'] : Type[Property];
|
||||||
|
// }} Resolved */
|
||||||
|
|
||||||
|
import * as Y from "yjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {{
|
||||||
|
[key: string]: import("svelte/store").Readable
|
||||||
|
}} DataStores */
|
||||||
|
/**
|
||||||
|
* @template {DataStores} T
|
||||||
|
* @typedef {{
|
||||||
|
[Property in keyof T]: Parameters<Parameters<T[Property]['subscribe']>[0]>[0]
|
||||||
|
}} Data */
|
||||||
|
|
||||||
|
import { derived } from "svelte/store";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template {DataStores} T
|
||||||
|
* @param {T} thing
|
||||||
|
* @returns {import('svelte/store').Readable<Data<T>>}
|
||||||
|
*/
|
||||||
|
export function deriveObj(thing) {
|
||||||
|
return derived(Object.values(thing), (values) => {
|
||||||
|
const keys = Object.keys(thing);
|
||||||
|
// prettier-ignore
|
||||||
|
return /** @type {Data<T>} */(Object.fromEntries(
|
||||||
|
values.map(($v, i) => [keys[i], $v])
|
||||||
|
))
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,5 +1,8 @@
|
||||||
import { writable } from "svelte/store";
|
import { derived, writable } from "svelte/store";
|
||||||
import type { UserIdentifier } from "../../doc";
|
import { readableArray } from "svelt-yjs/src/main";
|
||||||
|
import { getData, getUserY, type UserIdentifier } from "../../doc";
|
||||||
|
import { readableText } from "../helpers/readableText";
|
||||||
|
import { deriveObj } from "../helpers/recursiveReadable";
|
||||||
|
|
||||||
type Type = UserIdentifier[];
|
type Type = UserIdentifier[];
|
||||||
|
|
||||||
|
@ -9,3 +12,15 @@ const save = (v: Type) => (localStorage.friends = JSON.stringify(v));
|
||||||
const store = writable(start);
|
const store = writable(start);
|
||||||
store.subscribe(save);
|
store.subscribe(save);
|
||||||
export default store;
|
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))
|
||||||
|
);
|
||||||
|
|
Loading…
Reference in a new issue