import { DateTime } from 'luxon'; import { STORE_KEY_CHANNELS_DATA } from '$lib/constants'; 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 }) { 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(); } } getChannel(channelId) { return this.channels.filter((ch) => ch.id === channelId)[0] || null; } 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); this.sort(); return this; } addChannel(id, name) { this.channels = [...this.channels, makeChannelObject({ id, name })]; this.sort(); return this; } deleteChannel(id) { const channelIndex = this.channels.map((e) => e.id).indexOf(id); if (channelIndex !== -1) { this.channels.splice(channelIndex, 1); } return this; } sort() { this.channels.sort((a, b) => { if (a.name < b.name) { return -1; } else if (a.name > b.name) { return 1; } return 0; }); } }