import { DateTime } from 'luxon'; 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) { // 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) { const newChannel = { id, name } this.channels = [...this.channels, newChannel]; get(this.channelsMetaList).initializeChannel(newChannel); 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; }); } } 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 }, {}); }