summaryrefslogtreecommitdiff
path: root/ui/routes/(app)/+layout.svelte
blob: 1ba3fa9aeefed93d3f7ad0582eff58f766a08d12 (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
<script>
  import { getContext, onDestroy, onMount } from 'svelte';

  import { browser } from '$app/environment';
  import { goto, afterNavigate } from '$app/navigation';
  import { page } from '$app/state';

  import TinyGesture from 'tinygesture';

  import * as api from '$lib/apiServer.js';
  import ChannelList from '$lib/components/ChannelList.svelte';
  import CreateChannelForm from '$lib/components/CreateChannelForm.svelte';

  let gesture = null;

  const { data, children } = $props();
  const { session } = data;

  onMount(session.begin.bind(session));
  onDestroy(session.end.bind(session));

  let pageContext = getContext('page');
  let channel = $derived(page.params.channel);

  let rawChannels = $derived(session.channels);
  let rawChannelsMeta = $derived(session.local.all);
  let rawMessages = $derived(session.messages);

  function enrichChannels(channels, channelsMeta, messages) {
    const enrichedChannels = [];
    for (const ch of channels.values()) {
      const channelMessages = messages.filter((message) => message.channel === ch.id);
      const lastMessage = channelMessages.slice(-1)[0];
      const lastMessageAt = lastMessage?.at;
      const lastReadAt = channelsMeta.get(ch.id)?.lastReadAt;
      const hasUnreads = lastReadAt === undefined || lastMessageAt > lastReadAt;
      enrichedChannels.push({
        ...ch,
        hasUnreads
      });
    }
    return enrichedChannels;
  }

  const enrichedChannels = $derived(enrichChannels(rawChannels, rawChannelsMeta, rawMessages));

  function setUpGestures() {
    if (!browser) {
      // Meaningless if we're not in a browser, so...
      return;
    }
    gesture = new TinyGesture(window);
    gesture.on('swiperight', () => {
      pageContext.showMenu = true;
    });
    gesture.on('swipeleft', () => {
      pageContext.showMenu = false;
    });
  }

  onMount(setUpGestures);

  onDestroy(async () => {
    if (gesture !== null) {
      gesture.destroy();
    }
  });

  const STORE_KEY_LAST_ACTIVE = 'pilcrow:lastActiveChannel';

  function getLastActiveChannel() {
    return browser && JSON.parse(localStorage.getItem(STORE_KEY_LAST_ACTIVE));
  }

  function setLastActiveChannel(channelId) {
    browser && localStorage.setItem(STORE_KEY_LAST_ACTIVE, JSON.stringify(channelId));
  }

  afterNavigate(() => {
    const lastActiveChannel = getLastActiveChannel();
    const inRoot = page.url.pathname === '/';
    if (inRoot && lastActiveChannel) {
      goto(`/ch/${lastActiveChannel}`);
    } else if (channel) {
      setLastActiveChannel(channel || null);
    }
  });

  async function createChannel(name) {
    await api.createChannel(name);
  }
</script>

<svelte:head>
  <!-- TODO: unread count? -->
  <title>pilcrow</title>
</svelte:head>

<div id="interface">
  <nav id="sidebar" data-expanded={pageContext.showMenu}>
    <ChannelList active={channel} channels={enrichedChannels} />
    <div class="create-channel">
      <CreateChannelForm {createChannel} />
    </div>
  </nav>
  <main>
    {@render children?.()}
  </main>
</div>