summaryrefslogtreecommitdiff
path: root/ui/lib/store/channels.svelte.js
diff options
context:
space:
mode:
authorKit La Touche <kit@transneptune.net>2025-02-16 22:00:57 -0500
committerKit La Touche <kit@transneptune.net>2025-02-20 21:53:25 -0500
commitdaaf37a1ed3760f03fceb1123ebe80de3a2f280c (patch)
treee823603aa1683a2c07fd08dde724780147822348 /ui/lib/store/channels.svelte.js
parent43af74832f9a2fa7f40dc71985eec9b0ada087dd (diff)
Separate channel metadata out into its own store
This is stored locally, and, while parallel to channel info, is not the same as. Eventually, this may hold info about moot/decayed channels, and grow unbounded. That'll need to be addressed.
Diffstat (limited to 'ui/lib/store/channels.svelte.js')
-rw-r--r--ui/lib/store/channels.svelte.js150
1 files changed, 84 insertions, 66 deletions
diff --git a/ui/lib/store/channels.svelte.js b/ui/lib/store/channels.svelte.js
index 49cc31c..86d924e 100644
--- a/ui/lib/store/channels.svelte.js
+++ b/ui/lib/store/channels.svelte.js
@@ -1,78 +1,28 @@
import { DateTime } from 'luxon';
-import { STORE_KEY_CHANNELS_DATA } from '$lib/constants';
-const EPOCH_STRING = '1970-01-01T00:00:00Z';
+import { get } from 'svelte/store'
+import { STORE_KEY_CHANNELS_DATA, EPOCH_STRING } from '$lib/constants';
+// # Why we don't have a Channel object
+//
// 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 }) {
- let lastReadAtParsed;
- if (Boolean(lastReadAt)) {
- if (typeof lastReadAt === "string") {
- lastReadAtParsed = DateTime.fromISO(lastReadAt);
- } else {
- lastReadAtParsed = lastReadAt;
- }
- } else {
- lastReadAtParsed = DateTime.fromISO(EPOCH_STRING);
- }
- return {
- id,
- name,
- lastReadAt: lastReadAtParsed,
- draft,
- scrollPosition
- };
-}
-
-function mergeLocalData(remoteData, currentData) {
- let currentDataObj = currentData.reduce(
- (acc, cur) => {
- acc[cur.id] = cur;
- return acc;
- },
- {}
- );
- const ret = remoteData.map(
- (ch) => {
- const newCh = makeChannelObject(ch);
- if (Boolean(currentDataObj[ch.id])) {
- newCh.lastReadAt = currentDataObj[ch.id].lastReadAt;
- }
- return newCh;
- }
- );
- return ret;
-}
export class Channels {
channels = $state([]);
- constructor({ channelsData }) {
- this.channels = channelsData.map(makeChannelObject);
- // On channel edits (inc 'last read' ones), write out to localstorage?
- }
-
- writeOutToLocalStorage() {
- localStorage.setItem(
- STORE_KEY_CHANNELS_DATA,
- JSON.stringify(this.channels)
- );
- }
-
- 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();
- }
+ constructor({ channelsMetaList }) {
+ // This is the state wrapper around the channels meta object. Dammit.
+ this.channelsMetaList = channelsMetaList;
}
getChannel(channelId) {
@@ -80,16 +30,17 @@ export class Channels {
}
setChannels(channels) {
- // This gets called, among other times, when the page is first loaded, with
- // server-sent data from the `boot` endpoint. That needs to be merged with
- // locally stored data!
- this.channels = mergeLocalData(channels, this.channels);
+ // 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;
}
@@ -113,3 +64,70 @@ 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
+ }, {});
+}