summaryrefslogtreecommitdiff
path: root/ui/lib/store/channels.svelte.js
blob: 9058d86e8626d8bf7fa35d6bb33bc68cb4012deb (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
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;
  }, {});
}