Combine app state into a single file

This commit is contained in:
Jonathan Staab 2023-04-12 14:28:13 -05:00
parent 76b3479b7d
commit c30b0d069e
32 changed files with 313 additions and 318 deletions

View File

@ -13,8 +13,21 @@ const Config = {
authHandler: null,
}
type StatsItem = {
error: string | null
timeouts: number
subsCount: number
eoseCount: number
eoseTimer: number
eventsCount: number
activeSubsCount: number
lastRequest: number
openedAt: number
closedAt: number
}
const Meta = {
stats: {},
stats: {} as Record<string, StatsItem>,
errors: {},
getStats(url) {
if (!this.stats[url]) {

View File

@ -1,24 +0,0 @@
import {pluck} from "ramda"
import {first} from "hurdak/lib/hurdak"
import {writable} from "svelte/store"
import pool from "src/agent/pool"
import {getUserRelays} from "src/agent/relays"
export const slowConnections = writable([])
setInterval(() => {
// Only notify about relays the user is actually subscribed to
const relayUrls = new Set(pluck("url", getUserRelays()))
// Prune connections we haven't used in a while
Object.entries(pool.Meta.stats)
.filter(([url, stats]) => stats.lastRequest < Date.now() - 60_000)
.forEach(([url, stats]) => pool.disconnect(url))
// Alert the user to any heinously slow connections
slowConnections.set(
Object.keys(pool.Meta.stats).filter(
url => relayUrls.has(url) && first(pool.getQuality(url)) < 0.3
)
)
}, 30_000)

View File

@ -1,71 +0,0 @@
import type {DisplayEvent} from "src/util/types"
import {omit, sortBy} from "ramda"
import {createMap} from "hurdak/lib/hurdak"
import {findReplyId} from "src/util/nostr"
import {getUserFollows} from "src/agent/social"
import {getUserReadRelays} from "src/agent/relays"
import network from "src/agent/network"
import keys from "src/agent/keys"
import listener from "src/app/listener"
import {modal, toast} from "src/partials/state"
export const loadAppData = async pubkey => {
if (getUserReadRelays().length > 0) {
// Start our listener, but don't wait for it
listener.listen(pubkey)
// Make sure the user and their network is loaded
await network.loadPeople([pubkey], {force: true})
await network.loadPeople(getUserFollows())
}
}
export const login = (method, key) => {
keys.login(method, key)
modal.set({type: "login/connect", noEscape: true})
}
export const mergeParents = (notes: Array<DisplayEvent>) => {
const notesById = createMap("id", notes) as Record<string, DisplayEvent>
const childIds = []
for (const note of Object.values(notesById)) {
const parentId = findReplyId(note)
if (parentId) {
childIds.push(note.id)
}
// Add the current note to its parents replies, but only if we found a parent
if (notesById[parentId]) {
notesById[parentId].replies = notesById[parentId].replies.concat([note])
}
}
return sortBy(e => -e.created_at, Object.values(omit(childIds, notesById)))
}
export const publishWithToast = (relays, thunk) =>
thunk.publish(relays, ({completed, succeeded, failed, timeouts, pending}) => {
let message = `Published to ${succeeded.size}/${relays.length} relays`
const extra = []
if (failed.size > 0) {
extra.push(`${failed.size} failed`)
}
if (timeouts.size > 0) {
extra.push(`${timeouts.size} timed out`)
}
if (pending.size > 0) {
extra.push(`${pending.size} pending`)
}
if (extra.length > 0) {
message += ` (${extra.join(", ")})`
}
toast.show("info", message, pending.size ? null : 5)
})

View File

@ -1,124 +0,0 @@
import {sortBy, max, find, pluck, slice, propEq} from "ramda"
import {derived} from "svelte/store"
import {doPipe} from "hurdak/lib/hurdak"
import {synced, now, timedelta} from "src/util/misc"
import {Tags, isNotification} from "src/util/nostr"
import {getUserReadRelays} from "src/agent/relays"
import {notifications, userEvents, contacts, rooms} from "src/agent/db"
import {watch} from "src/agent/db"
import network from "src/agent/network"
import user from "src/agent/user"
let listener
// State
export const lastChecked = synced("app/alerts/lastChecked", {})
export const newNotifications = derived(
[watch("notifications", t => pluck("created_at", t.all()).reduce(max, 0)), lastChecked],
([$lastNotification, $lastChecked]) => $lastNotification > ($lastChecked.notifications || 0)
)
export const newDirectMessages = derived(
[watch("contacts", t => t.all()), lastChecked],
([contacts, $lastChecked]) => Boolean(find(c => c.lastMessage > $lastChecked[c.pubkey], contacts))
)
export const newChatMessages = derived(
[watch("rooms", t => t.all()), lastChecked],
([rooms, $lastChecked]) => Boolean(find(c => c.lastMessage > $lastChecked[c.pubkey], rooms))
)
// Synchronization from events to state
const processNotifications = async (pubkey, events) => {
notifications.patch(events.filter(e => isNotification(e, pubkey)))
}
const processMessages = async (pubkey, events) => {
const messages = events.filter(propEq("kind", 4))
if (messages.length === 0) {
return
}
lastChecked.update($lastChecked => {
for (const message of messages) {
if (message.pubkey === pubkey) {
const recipient = Tags.from(message).type("p").values().first()
$lastChecked[recipient] = Math.max($lastChecked[recipient] || 0, message.created_at)
contacts.patch({pubkey: recipient, accepted: true})
} else {
const contact = contacts.get(message.pubkey)
const lastMessage = Math.max(contact?.lastMessage || 0, message.created_at)
contacts.patch({pubkey: message.pubkey, lastMessage})
}
}
return $lastChecked
})
}
const processChats = async (pubkey, events) => {
const messages = events.filter(propEq("kind", 42))
if (messages.length === 0) {
return
}
lastChecked.update($lastChecked => {
for (const message of messages) {
const id = Tags.from(message).type("e").values().first()
if (message.pubkey === pubkey) {
$lastChecked[id] = Math.max($lastChecked[id] || 0, message.created_at)
} else {
const room = rooms.get(id)
const lastMessage = Math.max(room?.lastMessage || 0, message.created_at)
rooms.patch({id, lastMessage})
}
}
return $lastChecked
})
}
const listen = async pubkey => {
// Include an offset so we don't miss notifications on one relay but not another
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),
pluck("id"),
])
if (listener) {
listener.unsub()
}
listener = await network.listen({
delay: 5000,
relays: getUserReadRelays(),
filter: [
{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},
],
onChunk: async events => {
events = user.applyMutes(events)
await network.loadPeople(pluck("pubkey", events))
await processNotifications(pubkey, events)
await processMessages(pubkey, events)
await processChats(pubkey, events)
},
})
}
export default {listen}

View File

@ -1,66 +0,0 @@
import Bugsnag from "@bugsnag/js"
import {nip19} from "nostr-tools"
import {writable} from "svelte/store"
import {hash} from "src/util/misc"
import {warn} from "src/util/logger"
import user from "src/agent/user"
// Routing
export const routes = {
person: (pubkey, tab = "notes") => `/people/${nip19.npubEncode(pubkey)}/${tab}`,
}
// Menu
export const menuIsOpen = writable(false)
// Redact long strings, especially hex and bech32 keys which are 64 and 63
// characters long, respectively. Put the threshold a little lower in case
// someone accidentally enters a key with the last few digits missing
const redactErrorInfo = info =>
JSON.parse(JSON.stringify(info || null).replace(/\w{60}\w+/g, "[REDACTED]"))
// Wait for bugsnag to be started in main
setTimeout(() => {
Bugsnag.addOnError((event: any) => {
if (window.location.host.startsWith("localhost")) {
return false
}
if (!user.getSetting("reportAnalytics")) {
return false
}
// Redact individual properties since the event needs to be
// mutated, and we don't want to lose the prototype
event.context = redactErrorInfo(event.context)
event.request = redactErrorInfo(event.request)
event.exceptions = redactErrorInfo(event.exceptions)
event.breadcrumbs = redactErrorInfo(event.breadcrumbs)
event.setUser(session)
return true
})
})
const session = Math.random().toString().slice(2)
export const logUsage = async name => {
// Hash the user's pubkey so we can identify unique users without knowing
// anything about them
const pubkey = user.getPubkey()
const ident = pubkey ? hash(pubkey) : "unknown"
const {dufflepudUrl, reportAnalytics} = user.getSettings()
if (reportAnalytics) {
try {
await fetch(`${dufflepudUrl}/usage/${ident}/${session}/${name}`, {method: "post"})
} catch (e) {
if (!e.toString().includes("Failed to fetch")) {
warn(e)
}
}
}
}

View File

@ -18,9 +18,9 @@
import sync from "src/agent/sync"
import * as db from "src/agent/db"
import user from "src/agent/user"
import {loadAppData} from "src/app"
import {loadAppData} from "src/app2/state"
import {theme, getThemeVariables, modal, openModals} from "src/partials/state"
import {logUsage} from "src/app/ui"
import {logUsage} from "src/app2/state"
import SideNav from "src/app2/SideNav.svelte"
import Routes from "src/app2/Routes.svelte"
import Toast from "src/app2/Toast.svelte"

View File

@ -1,5 +1,5 @@
<script lang="ts">
import {menuIsOpen} from "src/app/ui"
import {menuIsOpen} from "src/app2/state"
import {modal} from "src/partials/state"
import Modal from "src/partials/Modal.svelte"
import Content from "src/partials/Content.svelte"

View File

@ -4,9 +4,9 @@
import {installPrompt} from "src/partials/state"
import user from "src/agent/user"
import pool from "src/agent/pool"
import {routes, menuIsOpen} from "src/app/ui"
import {newNotifications, newDirectMessages, newChatMessages} from "src/app/listener"
import {slowConnections} from "src/app/connection"
import {routes, menuIsOpen} from "src/app2/state"
import {newNotifications, newDirectMessages, newChatMessages} from "src/app2/state"
import {slowConnections} from "src/app2/state"
import PersonCircle from "src/app2/shared/PersonCircle.svelte"
const {profile, canPublish} = user

View File

@ -2,8 +2,8 @@
import {onMount} from "svelte"
import {theme} from "src/partials/state"
import Anchor from "src/partials/Anchor.svelte"
import {menuIsOpen} from "src/app/ui"
import {newNotifications} from "src/app/listener"
import {menuIsOpen} from "src/app2/state"
import {newNotifications} from "src/app2/state"
const toggleMenu = () => menuIsOpen.update(x => !x)
const toggleTheme = () => theme.update(t => (t === "dark" ? "light" : "dark"))

View File

@ -13,7 +13,7 @@
import Note from "src/app2/shared/Note.svelte"
import user from "src/agent/user"
import network from "src/agent/network"
import {mergeParents} from "src/app"
import {mergeParents} from "src/app2/state"
export let filter
export let relays = []

View File

@ -18,7 +18,7 @@
import {getRelaysForEventParent} from "src/agent/relays"
import {getPersonWithFallback} from "src/agent/db"
import {watch} from "src/agent/db"
import {routes} from "src/app/ui"
import {routes} from "src/app2/state"
import NoteContent from "src/app2/shared/NoteContent.svelte"
export let note

View File

@ -15,7 +15,7 @@
import user from "src/agent/user"
import network from "src/agent/network"
import {getPersonWithFallback} from "src/agent/db"
import {routes} from "src/app/ui"
import {routes} from "src/app2/state"
export let note
export let maxLength = 700

View File

@ -12,7 +12,7 @@
import {getEventPublishRelays} from "src/agent/relays"
import user from "src/agent/user"
import cmd from "src/agent/cmd"
import {publishWithToast} from "src/app"
import {publishWithToast} from "src/app2/state"
export let note
export let borderColor

View File

@ -2,7 +2,7 @@
import {Link} from "svelte-routing"
import {killEvent} from "src/util/html"
import {displayPerson} from "src/util/nostr"
import {routes} from "src/app/ui"
import {routes} from "src/app2/state"
import PersonCircle from "src/app2/shared/PersonCircle.svelte"
export let person

View File

@ -8,7 +8,7 @@
import PersonAbout from "src/app2/shared/PersonAbout.svelte"
import {getPubkeyWriteRelays, sampleRelays} from "src/agent/relays"
import user from "src/agent/user"
import {routes} from "src/app/ui"
import {routes} from "src/app2/state"
export let person

View File

@ -7,7 +7,7 @@
import {sampleRelays, getPubkeyWriteRelays} from "src/agent/relays"
import {getPersonWithFallback} from "src/agent/db"
import {watch} from "src/agent/db"
import {routes} from "src/app/ui"
import {routes} from "src/app2/state"
import PersonCircle from "src/app2/shared/PersonCircle.svelte"
import PersonAbout from "src/app2/shared/PersonAbout.svelte"

View File

@ -9,7 +9,7 @@
import Anchor from "src/partials/Anchor.svelte"
import pool from "src/agent/pool"
import user from "src/agent/user"
import {loadAppData} from "src/app"
import {loadAppData} from "src/app2/state"
export let relay
export let theme = "gray-8"

267
src/app2/state.ts Normal file
View File

@ -0,0 +1,267 @@
import type {DisplayEvent} from "src/util/types"
import Bugsnag from "@bugsnag/js"
import {nip19} from "nostr-tools"
import {derived} from "svelte/store"
import {writable} from "svelte/store"
import {omit, pluck, sortBy, max, find, slice, propEq} from "ramda"
import {createMap, doPipe, first} from "hurdak/lib/hurdak"
import {warn} from "src/util/logger"
import {hash} from "src/util/misc"
import {synced, now, timedelta} from "src/util/misc"
import {Tags, isNotification} from "src/util/nostr"
import {findReplyId} from "src/util/nostr"
import {modal, toast} from "src/partials/state"
import {notifications, watch, userEvents, contacts, rooms} from "src/agent/db"
import keys from "src/agent/keys"
import network from "src/agent/network"
import pool from "src/agent/pool"
import {getUserReadRelays, getUserRelays} from "src/agent/relays"
import {getUserFollows} from "src/agent/social"
import user from "src/agent/user"
// Routing
export const routes = {
person: (pubkey, tab = "notes") => `/people/${nip19.npubEncode(pubkey)}/${tab}`,
}
// Menu
export const menuIsOpen = writable(false)
// Redact long strings, especially hex and bech32 keys which are 64 and 63
// characters long, respectively. Put the threshold a little lower in case
// someone accidentally enters a key with the last few digits missing
const redactErrorInfo = info =>
JSON.parse(JSON.stringify(info || null).replace(/\w{60}\w+/g, "[REDACTED]"))
// Wait for bugsnag to be started in main
setTimeout(() => {
Bugsnag.addOnError((event: any) => {
if (window.location.host.startsWith("localhost")) {
return false
}
if (!user.getSetting("reportAnalytics")) {
return false
}
// Redact individual properties since the event needs to be
// mutated, and we don't want to lose the prototype
event.context = redactErrorInfo(event.context)
event.request = redactErrorInfo(event.request)
event.exceptions = redactErrorInfo(event.exceptions)
event.breadcrumbs = redactErrorInfo(event.breadcrumbs)
event.setUser(session)
return true
})
})
const session = Math.random().toString().slice(2)
export const logUsage = async name => {
// Hash the user's pubkey so we can identify unique users without knowing
// anything about them
const pubkey = user.getPubkey()
const ident = pubkey ? hash(pubkey) : "unknown"
const {dufflepudUrl, reportAnalytics} = user.getSettings()
if (reportAnalytics) {
try {
await fetch(`${dufflepudUrl}/usage/${ident}/${session}/${name}`, {method: "post"})
} catch (e) {
if (!e.toString().includes("Failed to fetch")) {
warn(e)
}
}
}
}
// State
export const lastChecked = synced("app/alerts/lastChecked", {})
export const newNotifications = derived(
[watch("notifications", t => pluck("created_at", t.all()).reduce(max, 0)), lastChecked],
([$lastNotification, $lastChecked]) => $lastNotification > ($lastChecked.notifications || 0)
)
export const newDirectMessages = derived(
[watch("contacts", t => t.all()), lastChecked],
([contacts, $lastChecked]) => Boolean(find(c => c.lastMessage > $lastChecked[c.pubkey], contacts))
)
export const newChatMessages = derived(
[watch("rooms", t => t.all()), lastChecked],
([rooms, $lastChecked]) => Boolean(find(c => c.lastMessage > $lastChecked[c.pubkey], rooms))
)
// Synchronization from events to state
const processNotifications = async (pubkey, events) => {
notifications.patch(events.filter(e => isNotification(e, pubkey)))
}
const processMessages = async (pubkey, events) => {
const messages = events.filter(propEq("kind", 4))
if (messages.length === 0) {
return
}
lastChecked.update($lastChecked => {
for (const message of messages) {
if (message.pubkey === pubkey) {
const recipient = Tags.from(message).type("p").values().first()
$lastChecked[recipient] = Math.max($lastChecked[recipient] || 0, message.created_at)
contacts.patch({pubkey: recipient, accepted: true})
} else {
const contact = contacts.get(message.pubkey)
const lastMessage = Math.max(contact?.lastMessage || 0, message.created_at)
contacts.patch({pubkey: message.pubkey, lastMessage})
}
}
return $lastChecked
})
}
const processChats = async (pubkey, events) => {
const messages = events.filter(propEq("kind", 42))
if (messages.length === 0) {
return
}
lastChecked.update($lastChecked => {
for (const message of messages) {
const id = Tags.from(message).type("e").values().first()
if (message.pubkey === pubkey) {
$lastChecked[id] = Math.max($lastChecked[id] || 0, message.created_at)
} else {
const room = rooms.get(id)
const lastMessage = Math.max(room?.lastMessage || 0, message.created_at)
rooms.patch({id, lastMessage})
}
}
return $lastChecked
})
}
export const listen = async pubkey => {
// Include an offset so we don't miss notifications on one relay but not another
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),
pluck("id"),
])
;(listen as any)._listener?.unsub()
;(listen as any)._listener = await network.listen({
delay: 5000,
relays: getUserReadRelays(),
filter: [
{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},
],
onChunk: async events => {
events = user.applyMutes(events)
await network.loadPeople(pluck("pubkey", events))
await processNotifications(pubkey, events)
await processMessages(pubkey, events)
await processChats(pubkey, events)
},
})
}
export const slowConnections = writable([])
setInterval(() => {
// Only notify about relays the user is actually subscribed to
const relayUrls = new Set(pluck("url", getUserRelays()))
// Prune connections we haven't used in a while
Object.entries(pool.Meta.stats)
.filter(([url, stats]) => stats.lastRequest < Date.now() - 60_000)
.forEach(([url, stats]) => pool.disconnect(url))
// Alert the user to any heinously slow connections
slowConnections.set(
Object.keys(pool.Meta.stats).filter(
url => relayUrls.has(url) && first(pool.getQuality(url)) < 0.3
)
)
}, 30_000)
export const loadAppData = async pubkey => {
if (getUserReadRelays().length > 0) {
// Start our listener, but don't wait for it
listen(pubkey)
// Make sure the user and their network is loaded
await network.loadPeople([pubkey], {force: true})
await network.loadPeople(getUserFollows())
}
}
export const login = (method, key) => {
keys.login(method, key)
modal.set({type: "login/connect", noEscape: true})
}
export const mergeParents = (notes: Array<DisplayEvent>) => {
const notesById = createMap("id", notes) as Record<string, DisplayEvent>
const childIds = []
for (const note of Object.values(notesById)) {
const parentId = findReplyId(note)
if (parentId) {
childIds.push(note.id)
}
// Add the current note to its parents replies, but only if we found a parent
if (notesById[parentId]) {
notesById[parentId].replies = notesById[parentId].replies.concat([note])
}
}
return sortBy(e => -e.created_at, Object.values(omit(childIds, notesById)))
}
export const publishWithToast = (relays, thunk) =>
thunk.publish(relays, ({completed, succeeded, failed, timeouts, pending}) => {
let message = `Published to ${succeeded.size}/${relays.length} relays`
const extra = []
if (failed.size > 0) {
extra.push(`${failed.size} failed`)
}
if (timeouts.size > 0) {
extra.push(`${timeouts.size} timed out`)
}
if (pending.size > 0) {
extra.push(`${pending.size} pending`)
}
if (extra.length > 0) {
message += ` (${extra.join(", ")})`
}
toast.show("info", message, pending.size ? null : 5)
})

View File

@ -13,7 +13,7 @@
import network from "src/agent/network"
import {watch} from "src/agent/db"
import cmd from "src/agent/cmd"
import {lastChecked} from "src/app/listener"
import {lastChecked} from "src/app2/state"
export let entity

View File

@ -11,7 +11,7 @@
import {getUserWriteRelays} from "src/agent/relays"
import {rooms} from "src/agent/db"
import cmd from "src/agent/cmd"
import {publishWithToast} from "src/app"
import {publishWithToast} from "src/app2/state"
export let room = {name: null, id: null, about: null, picture: null}

View File

@ -6,7 +6,7 @@
import Content from "src/partials/Content.svelte"
import Heading from "src/partials/Heading.svelte"
import user from "src/agent/user"
import {login} from "src/app"
import {login} from "src/app2/state"
const nip07 = "https://github.com/nostr-protocol/nips/blob/master/07.md"

View File

@ -17,7 +17,7 @@
import network from "src/agent/network"
import user from "src/agent/user"
import pool from "src/agent/pool"
import {loadAppData} from "src/app"
import {loadAppData} from "src/app2/state"
let modal = null
let customRelayUrl = null

View File

@ -6,7 +6,7 @@
import Content from "src/partials/Content.svelte"
import Heading from "src/partials/Heading.svelte"
import keys from "src/agent/keys"
import {login} from "src/app"
import {login} from "src/app2/state"
let nsec = ""
const nip07 = "https://github.com/nostr-protocol/nips/blob/master/07.md"

View File

@ -6,7 +6,7 @@
import Heading from "src/partials/Heading.svelte"
import keys from "src/agent/keys"
import {toast} from "src/partials/state"
import {login} from "src/app"
import {login} from "src/app2/state"
let npub = ""

View File

@ -14,8 +14,8 @@
import keys from "src/agent/keys"
import user from "src/agent/user"
import cmd from "src/agent/cmd"
import {routes} from "src/app/ui"
import {lastChecked} from "src/app/listener"
import {routes} from "src/app2/state"
import {lastChecked} from "src/app2/state"
import PersonCircle from "src/app2/shared/PersonCircle.svelte"
import PersonAbout from "src/app2/shared/PersonAbout.svelte"

View File

@ -4,7 +4,7 @@
import {ellipsize} from "hurdak/lib/hurdak"
import {displayPerson} from "src/util/nostr"
import {getPersonWithFallback} from "src/agent/db"
import {lastChecked} from "src/app/listener"
import {lastChecked} from "src/app2/state"
import PersonCircle from "src/app2/shared/PersonCircle.svelte"
import Card from "src/partials/Card.svelte"

View File

@ -21,7 +21,7 @@
import cmd from "src/agent/cmd"
import user from "src/agent/user"
import {toast, modal} from "src/partials/state"
import {publishWithToast} from "src/app"
import {publishWithToast} from "src/app2/state"
export let pubkey = null
export let nevent = null

View File

@ -10,7 +10,7 @@
import {watch} from "src/agent/db"
import user from "src/agent/user"
import {userEvents} from "src/agent/db"
import {lastChecked} from "src/app/listener"
import {lastChecked} from "src/app2/state"
let limit = 0
let events = null

View File

@ -18,7 +18,7 @@
import pool from "src/agent/pool"
import user from "src/agent/user"
import keys from "src/agent/keys"
import {loadAppData} from "src/app"
import {loadAppData} from "src/app2/state"
import {modal} from "src/partials/state"
export let stage

View File

@ -21,7 +21,7 @@
import {sampleRelays, getPubkeyWriteRelays} from "src/agent/relays"
import network from "src/agent/network"
import {getPersonWithFallback, watch} from "src/agent/db"
import {routes} from "src/app/ui"
import {routes} from "src/app2/state"
import PersonCircle from "src/app2/shared/PersonCircle.svelte"
import PersonAbout from "src/app2/shared/PersonAbout.svelte"

View File

@ -6,7 +6,7 @@
import Button from "src/partials/Button.svelte"
import user from "src/agent/user"
import {toast, modal} from "src/partials/state"
import {loadAppData} from "src/app"
import {loadAppData} from "src/app2/state"
let url = $modal.url

View File

@ -12,8 +12,8 @@
import user from "src/agent/user"
import {getUserWriteRelays} from "src/agent/relays"
import cmd from "src/agent/cmd"
import {routes} from "src/app/ui"
import {publishWithToast} from "src/app"
import {routes} from "src/app2/state"
import {publishWithToast} from "src/app2/state"
let values = user.getProfile().kind0 || {}