1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
|
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;
});
}
}
|