summaryrefslogtreecommitdiff
path: root/ui/lib/store
diff options
context:
space:
mode:
authorOwen Jacobson <owen@grimoire.ca>2025-02-24 15:05:20 -0500
committerOwen Jacobson <owen@grimoire.ca>2025-02-24 15:05:20 -0500
commit8cdc6a686644fbf9de6e91ae622f47e23bf7bb23 (patch)
tree746d9830fb11522d9435be9aecc428e95fcfdb61 /ui/lib/store
parent099471c574f6dceeb45f8bb5dae1699a734cb084 (diff)
parentf2c415dd7eb1cb68e18e96dfd70460f8972ee9df (diff)
Merge branch 'prop/preserve-state'
Diffstat (limited to 'ui/lib/store')
-rw-r--r--ui/lib/store/channels.svelte.js98
1 files changed, 77 insertions, 21 deletions
diff --git a/ui/lib/store/channels.svelte.js b/ui/lib/store/channels.svelte.js
index c82f9aa..9058d86 100644
--- a/ui/lib/store/channels.svelte.js
+++ b/ui/lib/store/channels.svelte.js
@@ -1,39 +1,31 @@
import { DateTime } from 'luxon';
-const EPOCH_STRING = '1970-01-01T00:00:00Z';
-
-// For reasons unclear to me, a straight up class definition with a constructor
-// doesn't seem to work, reactively. So we resort to this.
-// Owen suggests that this sentence in the Svelte docs should make the reason
-// clear:
-// > If $state is used with an array or a simple object, the result is a deeply
-// > reactive state proxy.
-// Emphasis on "simple object".
-// --Kit
-function makeChannelObject({ id, name, draft = '', lastReadAt = null, scrollPosition = null }) {
- return {
- id,
- name,
- lastReadAt: lastReadAt || DateTime.fromISO(EPOCH_STRING),
- draft,
- scrollPosition
- };
-}
+import { get } from 'svelte/store';
+import { STORE_KEY_CHANNELS_DATA, EPOCH_STRING } from '$lib/constants';
export class Channels {
channels = $state([]);
+ constructor({ channelsMetaList }) {
+ // This is the state wrapper around the channels meta object. Dammit.
+ this.channelsMetaList = channelsMetaList;
+ }
+
getChannel(channelId) {
return this.channels.filter((ch) => ch.id === channelId)[0] || null;
}
setChannels(channels) {
- this.channels = channels.map(makeChannelObject);
+ // Because this is called at initialization, we need to initialize the matching meta:
+ get(this.channelsMetaList).ensureChannels(channels);
+ this.channels = channels;
this.sort();
return this;
}
addChannel(id, name) {
- this.channels = [...this.channels, makeChannelObject({ id, name })];
+ const newChannel = { id, name };
+ this.channels = [...this.channels, newChannel];
+ get(this.channelsMetaList).initializeChannel(newChannel);
this.sort();
return this;
}
@@ -57,3 +49,67 @@ export class Channels {
});
}
}
+
+export class ChannelsMeta {
+ // Store channelId -> { draft = '', lastReadAt = null, scrollPosition = null }
+ channelsMeta = $state({});
+
+ constructor({ channelsMetaData }) {
+ const channelsMeta = objectMap(channelsMetaData, (ch) => {
+ let lastReadAt = ch.lastReadAt;
+ if (typeof lastReadAt === 'string') {
+ lastReadAt = DateTime.fromISO(lastReadAt);
+ }
+ if (!Boolean(lastReadAt)) {
+ lastReadAt = DateTime.fromISO(EPOCH_STRING);
+ }
+ return {
+ ...ch,
+ lastReadAt
+ };
+ });
+ this.channelsMeta = channelsMeta;
+ }
+
+ writeOutToLocalStorage() {
+ localStorage.setItem(STORE_KEY_CHANNELS_DATA, JSON.stringify(this.channelsMeta));
+ }
+
+ updateLastReadAt(channelId, at) {
+ const channelObject = this.getChannel(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 (at > channelObject?.lastReadAt) {
+ channelObject.lastReadAt = at;
+ this.writeOutToLocalStorage();
+ }
+ }
+
+ ensureChannels(channelsList) {
+ channelsList.forEach(({ id }) => {
+ this.initializeChannel(id);
+ });
+ }
+
+ initializeChannel(channelId) {
+ if (!this.channelsMeta[channelId]) {
+ const channelData = {
+ lastReadAt: null,
+ draft: '',
+ scrollPosition: null
+ };
+ this.channelsMeta[channelId] = channelData;
+ }
+ }
+
+ getChannel(channelId) {
+ return this.channelsMeta[channelId] || null;
+ }
+}
+
+function objectMap(object, mapFn) {
+ return Object.keys(object).reduce((result, key) => {
+ result[key] = mapFn(object[key]);
+ return result;
+ }, {});
+}