summaryrefslogtreecommitdiff
path: root/ui/lib/state/local/channels.svelte.js
diff options
context:
space:
mode:
authorOwen Jacobson <owen@grimoire.ca>2025-03-23 14:33:07 -0400
committerOwen Jacobson <owen@grimoire.ca>2025-03-23 14:33:07 -0400
commit876472299d67f8fe3a789b7777b9d8ee44297b23 (patch)
treedb62f5d1e15d871f8a73ce20b40cd53053d12f85 /ui/lib/state/local/channels.svelte.js
parentfa0f653f141efee3f5a01e1fa696d29140ec12c2 (diff)
parentf788ea84e25a4f7216ca0604aeb216346403b6ef (diff)
Merge branch 'prop/restartable-state'
Diffstat (limited to 'ui/lib/state/local/channels.svelte.js')
-rw-r--r--ui/lib/state/local/channels.svelte.js118
1 files changed, 118 insertions, 0 deletions
diff --git a/ui/lib/state/local/channels.svelte.js b/ui/lib/state/local/channels.svelte.js
new file mode 100644
index 0000000..9040685
--- /dev/null
+++ b/ui/lib/state/local/channels.svelte.js
@@ -0,0 +1,118 @@
+import { DateTime } from 'luxon';
+import { SvelteMap } from 'svelte/reactivity';
+
+import * as iter from '$lib/iterator.js';
+
+export const STORE_KEY_CHANNELS_DATA = 'pilcrow:channelsData';
+
+class Channel {
+ draft = $state();
+ lastReadAt = $state(null);
+ scrollPosition = $state(null);
+
+ static fromStored({ draft, lastReadAt, scrollPosition }) {
+ return new Channel({
+ draft,
+ lastReadAt: lastReadAt == null ? null : DateTime.fromISO(lastReadAt),
+ scrollPosition
+ });
+ }
+
+ constructor({ draft = '', lastReadAt = null, scrollPosition = null } = {}) {
+ this.draft = draft;
+ this.lastReadAt = lastReadAt;
+ this.scrollPosition = scrollPosition;
+ }
+
+ toStored() {
+ const { draft, lastReadAt, scrollPosition } = this;
+ return {
+ draft,
+ lastReadAt: lastReadAt?.toISO(),
+ scrollPosition
+ };
+ }
+}
+
+export class Channels {
+ // Store channelId -> { draft = '', lastReadAt = null, scrollPosition = null }
+ all = $state();
+
+ static fromLocalStorage() {
+ const stored = localStorage.getItem(STORE_KEY_CHANNELS_DATA);
+ if (stored !== null) {
+ return Channels.fromStored(stored);
+ }
+ return Channels.empty();
+ }
+
+ static fromStored(stored) {
+ const loaded = Object.keys(stored).map((channelId) => [
+ channelId,
+ Channel.fromStored(stored[channelId])
+ ]);
+ const all = new SvelteMap(loaded);
+ return new Channels({ all });
+ }
+
+ static empty() {
+ return new Channels({ all: new SvelteMap() });
+ }
+
+ constructor({ all }) {
+ this.all = all;
+ }
+
+ channel(channelId) {
+ let channel = this.all.get(channelId);
+ if (channel === undefined) {
+ channel = new Channel();
+ this.all.set(channelId, channel);
+ }
+ return channel;
+ }
+
+ updateLastReadAt(channelId, at) {
+ const channel = this.channel(channelId);
+ // Do it this way, rather than with Math.max tricks, to avoid assignment
+ // when we don't need it, to minimize reactive changes:
+ if (channel.lastReadAt === null || at > channel.lastReadAt) {
+ channel.lastReadAt = at;
+ this.save();
+ }
+ }
+
+ retainChannels(channelIds) {
+ const retain = new Set(channelIds);
+ for (const channelId of Array.from(this.all.keys())) {
+ if (!retain.has(channelId)) {
+ this.all.delete(channelId);
+ }
+ }
+ this.save();
+ }
+
+ toStored() {
+ return iter.reduce(
+ this.all.entries(),
+ (stored, [channelId, channel]) => ({
+ ...stored,
+ [channelId]: channel.toStored()
+ }),
+ {}
+ );
+ }
+
+ save() {
+ let stored = this.toStored();
+ console.log(this, stored);
+ localStorage.setItem(STORE_KEY_CHANNELS_DATA, JSON.stringify(stored));
+ }
+}
+
+function objectMap(object, mapFn) {
+ return Object.keys(object).reduce((result, key) => {
+ result[key] = mapFn(object[key]);
+ return result;
+ }, {});
+}