Remember which rooms the user has joined

This commit is contained in:
Jonathan Staab 2023-04-20 17:21:58 -05:00
parent 7916ed501c
commit 612a0d18df
8 changed files with 86 additions and 29 deletions

View File

@ -15,6 +15,7 @@ export const loki = new Loki("agent.db", {
autoload: true,
autosave: true,
autosaveInterval: 4000,
throttledSaves: true,
adapter: new Adapter(),
autoloadCallback: () => {
for (const table of Object.values(registry)) {

View File

@ -1,4 +1,4 @@
import {uniq, prop, reject, nth, uniqBy, objOf, pick, identity} from "ramda"
import {is, uniq, prop, reject, nth, uniqBy, objOf, pick, identity} from "ramda"
import {nip05} from "nostr-tools"
import {noop, ensurePlural, chunk} from "hurdak/lib/hurdak"
import {
@ -242,16 +242,30 @@ addHandler(
30078,
profileHandler("settings", async (e, p) => {
if (Tags.from(e).getMeta("d") === "coracle/settings/v1") {
return keys.decryptJson(e.content)
return {...p.settings, ...(await keys.decryptJson(e.content))}
}
})
)
addHandler(
30078,
profileHandler("lastChecked", async (e, p) => {
profileHandler("last_checked", async (e, p) => {
if (Tags.from(e).getMeta("d") === "coracle/last_checked/v1") {
return {...p.lastChecked, ...(await keys.decryptJson(e.content))}
return {...p.last_checked, ...(await keys.decryptJson(e.content))}
}
})
)
addHandler(
30078,
profileHandler("rooms_joined", async (e, p) => {
if (Tags.from(e).getMeta("d") === "coracle/rooms_joined/v1") {
const roomsJoined = await keys.decryptJson(e.content)
// Just a bug from when I was building the feature, remove someday
if (is(Array, roomsJoined)) {
return roomsJoined
}
}
})
)

View File

@ -3,6 +3,7 @@ import type {Readable} from "svelte/store"
import {
slice,
uniqBy,
without,
reject,
prop,
find,
@ -34,7 +35,8 @@ const profile = synced("agent/user/profile", {
dufflepudUrl: import.meta.env.VITE_DUFFLEPUD_URL,
multiplextrUrl: import.meta.env.VITE_MULTIPLEXTR_URL,
},
lastChecked: {},
rooms_joined: [],
last_checked: {},
petnames: [],
relays: [],
mutes: [],
@ -42,7 +44,8 @@ const profile = synced("agent/user/profile", {
})
const settings = derived(profile, prop("settings"))
const lastChecked = derived(profile, prop("lastChecked")) as Readable<Record<string, number>>
const roomsJoined = derived(profile, prop("rooms_joined")) as Readable<string>
const lastChecked = derived(profile, prop("last_checked")) as Readable<Record<string, number>>
const petnames = derived(profile, prop("petnames")) as Readable<Array<Array<string>>>
const relays = derived(profile, prop("relays")) as Readable<Array<Relay>>
const mutes = derived(profile, prop("mutes")) as Readable<Array<[string, string]>>
@ -93,6 +96,7 @@ export default {
// App data
lastChecked,
roomsJoined,
async setAppData(key, content) {
if (keys.canSign()) {
const d = `coracle/${key}`
@ -103,11 +107,29 @@ export default {
},
setLastChecked(k, v) {
profile.update($profile => {
const lastChecked = {...$profile.lastChecked, [k]: v}
const lastChecked = {...$profile.last_checked, [k]: v}
this.setAppData("last_checked/v1", lastChecked)
return {...$profile, lastChecked}
return {...$profile, last_checked: lastChecked}
})
},
joinRoom(id) {
profile.update($profile => {
const roomsJoined = $profile.rooms_joined.concat(id)
this.setAppData("rooms_joined/v1", roomsJoined)
return {...$profile, rooms_joined: roomsJoined}
})
},
leaveRoom(id) {
profile.update($profile => {
const roomsJoined = without([id], $profile.rooms_joined)
this.setAppData("rooms_joined/v1", roomsJoined)
return {...$profile, rooms_joined: roomsJoined}
})
},

View File

@ -102,7 +102,7 @@ export const newNotifications = derived(
)
export const hasNewMessages = ({lastReceived, lastSent}, lastChecked) =>
lastReceived > Math.max(lastSent, lastChecked || 0)
lastReceived > Math.max(lastSent || lastReceived, lastChecked || 0)
export const newDirectMessages = derived(
[watch("contacts", t => t.all()), user.lastChecked],
@ -111,9 +111,14 @@ export const newDirectMessages = derived(
)
export const newChatMessages = derived(
[watch("rooms", t => t.all()), user.lastChecked],
([rooms, $lastChecked]) =>
Boolean(find(r => hasNewMessages(r, $lastChecked[`chat/${r.id}`]), rooms))
[watch("rooms", t => t.all()), user.lastChecked, user.roomsJoined],
([rooms, $lastChecked, $roomsJoined]) =>
Boolean(
find(
r => $roomsJoined.includes(r.id) && hasNewMessages(r, $lastChecked[`chat/${r.id}`]),
rooms
)
)
)
// Synchronization from events to state
@ -159,10 +164,10 @@ const processChats = async (pubkey, events) => {
}
}
export const listen = async pubkey => {
// Include an offset so we don't miss notifications on one relay but not another
export const listen = async () => {
const pubkey = user.getPubkey()
const {roomsJoined} = user.getProfile()
const since = now() - timedelta(30, "days")
const roomIds = pluck("id", rooms.all({joined: true}))
const eventIds = doPipe(userEvents.all({kind: 1, created_at: {$gt: since}}), [
sortBy(e => -e.created_at),
slice(0, 256),
@ -177,7 +182,7 @@ export const listen = async pubkey => {
{kinds: [1, 4], authors: [pubkey], since},
{kinds: [1, 7, 4, 9735], "#p": [pubkey], since},
{kinds: [1, 7, 4, 9735], "#e": eventIds, since},
{kinds: [42], "#e": roomIds, since},
{kinds: [42], "#e": roomsJoined, since},
],
onChunk: async events => {
events = user.applyMutes(events)
@ -212,7 +217,7 @@ setInterval(() => {
export const loadAppData = async pubkey => {
if (getUserReadRelays().length > 0) {
// Start our listener, but don't wait for it
listen(pubkey)
listen()
// Make sure the user and their network is loaded
await network.loadPeople([pubkey], {force: true, kinds: userKinds})

View File

@ -9,7 +9,7 @@
import Button from "src/partials/Button.svelte"
import {toast, modal} from "src/partials/state"
import {getUserWriteRelays} from "src/agent/relays"
import {rooms} from "src/agent/db"
import user from "src/agent/user"
import cmd from "src/agent/cmd"
import {publishWithToast} from "src/app/state"
@ -45,7 +45,7 @@
const [event] = await publishWithToast(relays, cmd.createRoom(room))
// Auto join the room the user just created
rooms.patch({id: event.id, joined: true})
user.joinRoom(event.id)
}
modal.pop()

View File

@ -1,11 +1,14 @@
<script>
import {onMount} from "svelte"
import {derived} from "svelte/store"
import {partition} from "ramda"
import {fuzzy} from "src/util/misc"
import Input from "src/partials/Input.svelte"
import Content from "src/partials/Content.svelte"
import Anchor from "src/partials/Anchor.svelte"
import ChatListItem from "src/app/views/ChatListItem.svelte"
import {watch} from "src/agent/db"
import user from "src/agent/user"
import network from "src/agent/network"
import {getUserReadRelays} from "src/agent/relays"
import {modal} from "src/partials/state"
@ -14,10 +17,15 @@
let search
let results = []
const userRooms = watch("rooms", t => t.all({joined: true}))
const otherRooms = watch("rooms", t => t.all({joined: {$ne: true}}))
const {roomsJoined} = user
const rooms = derived([watch("rooms", t => t.all()), roomsJoined], ([_rooms, _joined]) => {
const ids = new Set(_joined)
const [joined, other] = partition(r => ids.has(r.id), _rooms)
$: search = fuzzy($otherRooms, {keys: ["name", "about"]})
return {joined, other}
})
$: search = fuzzy($rooms.other, {keys: ["name", "about"]})
$: results = search(q).slice(0, 50)
document.title = "Chat"
@ -44,7 +52,7 @@
<i class="fa-solid fa-plus" /> Create Room
</Anchor>
</div>
{#each $userRooms as room (room.id)}
{#each $rooms.joined as room (room.id)}
<ChatListItem {room} />
{:else}
<p class="text-center py-8">You haven't yet joined any rooms.</p>
@ -64,7 +72,7 @@
<ChatListItem {room} />
{/each}
<small class="text-center">
Showing {Math.min(50, $otherRooms.length)} of {$otherRooms.length} known rooms
Showing {Math.min(50, $rooms.other.length)} of {$rooms.other.length} known rooms
</small>
{:else}
<small class="text-center"> No matching rooms found </small>

View File

@ -4,13 +4,16 @@
import {fly} from "svelte/transition"
import {ellipsize} from "hurdak/lib/hurdak"
import Anchor from "src/partials/Anchor.svelte"
import {rooms} from "src/agent/db"
import user from "src/agent/user"
export let room
const {roomsJoined} = user
const enter = () => navigate(`/chat/${nip19.noteEncode(room.id)}`)
const join = () => rooms.patch({id: room.id, joined: true})
const leave = () => rooms.patch({id: room.id, joined: false})
const join = () => user.joinRoom(room.id)
const leave = () => user.leaveRoom(room.id)
$: joined = $roomsJoined.includes(room.id)
</script>
<button
@ -26,7 +29,7 @@
<i class="fa fa-lock-open text-gray-1" />
<h2 class="text-lg">{room.name || ""}</h2>
</div>
{#if room.joined}
{#if joined}
<Anchor type="button" preventDefault class="flex items-center gap-2" on:click={leave}>
<i class="fa fa-right-from-bracket" />
<span>Leave</span>

View File

@ -7,7 +7,11 @@ import {invoiceAmount} from "src/util/lightning"
export const personKinds = [0, 2, 3, 10001, 10002]
export const userKinds = personKinds.concat([10000, 30001, 30078])
export const appDataKeys = ["coracle/settings/v1", "coracle/last_checked/v1"]
export const appDataKeys = [
"coracle/settings/v1",
"coracle/last_checked/v1",
"coracle/rooms_joined/v1",
]
export class Tags {
tags: Array<any>