summaryrefslogtreecommitdiff
path: root/ui/lib/state/local/channels.svelte.js
blob: d86d028470103f05704c75f34670366c3e8944f3 (plain)
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
116
117
import { DateTime } from 'luxon';
import { SvelteMap } from 'svelte/reactivity';

import * as iter from '$lib/iterator.js';

export const STORE_KEY_CHANNELS_DATA = 'pilcrow:channelsData';

class Channel {
  draft = $state();
  lastReadAt = $state(null);
  scrollPosition = $state(null);

  static fromStored({ draft, lastReadAt, scrollPosition }) {
    return new Channel({
      draft,
      lastReadAt: lastReadAt == null ? null : DateTime.fromISO(lastReadAt),
      scrollPosition
    });
  }

  constructor({ draft = '', lastReadAt = null, scrollPosition = null } = {}) {
    this.draft = draft;
    this.lastReadAt = lastReadAt;
    this.scrollPosition = scrollPosition;
  }

  toStored() {
    const { draft, lastReadAt, scrollPosition } = this;
    return {
      draft,
      lastReadAt: lastReadAt?.toISO(),
      scrollPosition
    };
  }
}

export class Channels {
  // Store channelId -> { draft = '', lastReadAt = null, scrollPosition = null }
  all = $state();

  static fromLocalStorage() {
    const stored = localStorage.getItem(STORE_KEY_CHANNELS_DATA);
    if (stored !== null) {
      return Channels.fromStored(JSON.parse(stored));
    }
    return Channels.empty();
  }

  static fromStored(stored) {
    const loaded = Object.keys(stored).map((channelId) => [
      channelId,
      Channel.fromStored(stored[channelId])
    ]);
    const all = new SvelteMap(loaded);
    return new Channels({ all });
  }

  static empty() {
    return new Channels({ all: new SvelteMap() });
  }

  constructor({ all }) {
    this.all = all;
  }

  channel(channelId) {
    let channel = this.all.get(channelId);
    if (channel === undefined) {
      channel = new Channel();
      this.all.set(channelId, channel);
    }
    return channel;
  }

  updateLastReadAt(channelId, at) {
    const channel = this.channel(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 (channel.lastReadAt === null || at > channel.lastReadAt) {
      channel.lastReadAt = at;
      this.save();
    }
  }

  retainChannels(channelIds) {
    const retain = new Set(channelIds);
    for (const channelId of Array.from(this.all.keys())) {
      if (!retain.has(channelId)) {
        this.all.delete(channelId);
      }
    }
    this.save();
  }

  toStored() {
    return iter.reduce(
      this.all.entries(),
      (stored, [channelId, channel]) => ({
        ...stored,
        [channelId]: channel.toStored()
      }),
      {}
    );
  }

  save() {
    let stored = this.toStored();
    localStorage.setItem(STORE_KEY_CHANNELS_DATA, JSON.stringify(stored));
  }
}

function objectMap(object, mapFn) {
  return Object.keys(object).reduce((result, key) => {
    result[key] = mapFn(object[key]);
    return result;
  }, {});
}