diff options
Diffstat (limited to 'ui/lib/store/channels.svelte.js')
| -rw-r--r-- | ui/lib/store/channels.svelte.js | 150 |
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 + }, {}); +} |
