summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ui/lib/constants.js1
-rw-r--r--ui/lib/store.js10
-rw-r--r--ui/lib/store/channels.svelte.js150
-rw-r--r--ui/routes/(app)/+layout.svelte10
-rw-r--r--ui/routes/(app)/ch/[channel]/+page.svelte4
5 files changed, 102 insertions, 73 deletions
diff --git a/ui/lib/constants.js b/ui/lib/constants.js
index a707c4b..c001f6d 100644
--- a/ui/lib/constants.js
+++ b/ui/lib/constants.js
@@ -1 +1,2 @@
export const STORE_KEY_CHANNELS_DATA = 'pilcrow:channelsData';
+export const EPOCH_STRING = '1970-01-01T00:00:00Z';
diff --git a/ui/lib/store.js b/ui/lib/store.js
index f408c0c..508320f 100644
--- a/ui/lib/store.js
+++ b/ui/lib/store.js
@@ -1,13 +1,17 @@
import { writable } from 'svelte/store';
import { browser } from '$app/environment';
-import { Channels } from '$lib/store/channels.svelte.js';
+import { Channels, ChannelsMeta } from '$lib/store/channels.svelte.js';
import { Messages } from '$lib/store/messages.svelte.js';
import { Logins } from '$lib/store/logins';
+import { STORE_KEY_CHANNELS_DATA } from '$lib/constants';
// Get channelsList content from the local storage
-const channelsData = (browser && JSON.parse(localStorage.getItem('pilcrow:channelsData'))) || {};
+const channelsMetaData = (
+ browser && JSON.parse(localStorage.getItem(STORE_KEY_CHANNELS_DATA))
+) || {};
export const currentUser = writable(null);
export const logins = writable(new Logins());
-export const channelsList = writable(new Channels({ channelsData }));
+export const channelsMetaList = writable(new ChannelsMeta({ channelsMetaData }));
+export const channelsList = writable(new Channels({ channelsMetaList }));
export const messages = writable(new Messages());
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
+ }, {});
+}
diff --git a/ui/routes/(app)/+layout.svelte b/ui/routes/(app)/+layout.svelte
index cbfef54..9ade399 100644
--- a/ui/routes/(app)/+layout.svelte
+++ b/ui/routes/(app)/+layout.svelte
@@ -6,7 +6,7 @@
import TinyGesture from 'tinygesture';
import { boot, subscribeToEvents } from '$lib/apiServer';
- import { currentUser, logins, channelsList, messages } from '$lib/store';
+ import { currentUser, logins, channelsList, channelsMetaList, messages } from '$lib/store';
import ChannelList from '$lib/components/ChannelList.svelte';
import CreateChannelForm from '$lib/components/CreateChannelForm.svelte';
@@ -23,6 +23,10 @@
channelsList.subscribe((val) => {
rawChannels = val.channels;
});
+ let rawChannelsMeta;
+ channelsMetaList.subscribe((val) => {
+ rawChannelsMeta = val.channelsMeta;
+ });
let rawMessages;
messages.subscribe((val) => {
rawMessages = val;
@@ -30,7 +34,9 @@
let enrichedChannels = $derived.by(() => {
const channels = rawChannels;
+ const channelsMeta = rawChannelsMeta;
const messages = rawMessages;
+
const enrichedChannels = [];
if (channels && messages) {
for (let ch of channels) {
@@ -38,7 +44,7 @@
let lastRun = runs?.slice(-1)[0];
let lastMessage = lastRun?.messages.slice(-1)[0];
let lastMessageAt = lastMessage?.at;
- let hasUnreads = lastMessageAt > ch.lastReadAt;
+ let hasUnreads = lastMessageAt > channelsMeta[ch.id]?.lastReadAt;
enrichedChannels.push({
...ch,
hasUnreads
diff --git a/ui/routes/(app)/ch/[channel]/+page.svelte b/ui/routes/(app)/ch/[channel]/+page.svelte
index d64a8c9..7bd0e10 100644
--- a/ui/routes/(app)/ch/[channel]/+page.svelte
+++ b/ui/routes/(app)/ch/[channel]/+page.svelte
@@ -3,7 +3,7 @@
import { page } from '$app/stores';
import ActiveChannel from '$lib/components/ActiveChannel.svelte';
import MessageInput from '$lib/components/MessageInput.svelte';
- import { channelsList, messages } from '$lib/store';
+ import { channelsMetaList, messages } from '$lib/store';
let channel = $derived($page.params.channel);
let messageRuns = $derived($messages.inChannel(channel));
@@ -36,7 +36,7 @@
const lastInView = getLastVisibleMessage();
if (lastInView) {
const at = DateTime.fromISO(lastInView.dataset.at);
- $channelsList.updateLastReadAt(channel, at);
+ $channelsMetaList.updateLastReadAt(channel, at);
}
}