From 6f933ad3e12b9d712eb577064c51fb7b26574d61 Mon Sep 17 00:00:00 2001 From: hzrd149 Date: Mon, 3 Feb 2025 16:08:10 -0600 Subject: [PATCH] load timelines from cache relay remove process class --- pnpm-lock.yaml | 100 +++++------ src/classes/batch-identifier-loader.ts | 23 +-- src/classes/batch-relation-loader.ts | 23 +-- src/classes/notifications.ts | 167 ------------------ src/classes/persistent-subscription.ts | 19 +- src/classes/process.ts | 53 ------ src/classes/relay-pool.ts | 9 +- src/components/content/links/image.tsx | 25 ++- src/components/content/links/video.tsx | 4 +- src/components/layout/components/index.tsx | 5 +- .../navigation/app-favorite-button.tsx | 4 +- src/components/navigation/apps.ts | 3 +- .../people-list-selection.tsx | 2 +- src/hooks/use-forward-subscription.ts | 8 - src/hooks/use-timeline-loader.ts | 8 +- src/providers/global/index.tsx | 13 +- .../global/notifications-provider.tsx | 80 --------- src/providers/local/people-list-provider.tsx | 11 +- src/services/cache-relay.ts | 20 ++- src/services/dictionary.ts | 10 -- src/services/event-reactions.ts | 16 +- src/services/event-store.ts | 10 -- src/services/event-zaps.ts | 13 -- src/services/notifications.ts | 164 ++++++++++++++--- src/services/process-manager.ts | 47 ----- src/services/replaceable-loader.ts | 21 +-- src/services/rx-nostr.ts | 4 - src/services/single-event-loader.ts | 2 +- src/services/timeline-cache.ts | 6 +- src/services/user-sets-loader.ts | 2 +- .../{dms-card.tsx => messages-card.tsx} | 4 +- .../components/notifications-card.tsx | 36 +++- src/views/launchpad/index.tsx | 2 +- .../components/notification-item.tsx | 2 +- src/views/notifications/index.tsx | 52 ++++-- src/views/relays/webrtc/connect.tsx | 1 - src/views/task-manager/processes/index.tsx | 20 --- .../processes/process/process-icon.tsx | 10 -- .../processes/process/process-tree.tsx | 46 ----- .../task-manager/relays/inspect-relay.tsx | 2 - .../task-manager/relays/tabs/connections.tsx | 2 +- 41 files changed, 347 insertions(+), 702 deletions(-) delete mode 100644 src/classes/notifications.ts delete mode 100644 src/classes/process.ts delete mode 100644 src/providers/global/notifications-provider.tsx delete mode 100644 src/services/process-manager.ts rename src/views/launchpad/components/{dms-card.tsx => messages-card.tsx} (96%) delete mode 100644 src/views/task-manager/processes/index.tsx delete mode 100644 src/views/task-manager/processes/process/process-icon.tsx delete mode 100644 src/views/task-manager/processes/process/process-tree.tsx diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 963cbb2b5..bba0c3cd3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -104,28 +104,28 @@ importers: version: 0.7.2 applesauce-accounts: specifier: next - version: 0.0.0-next-20250203180810(typescript@5.7.3) + version: 0.0.0-next-20250203215539(typescript@5.7.3) applesauce-content: specifier: next - version: 0.0.0-next-20250203180810(typescript@5.7.3) + version: 0.0.0-next-20250203215539(typescript@5.7.3) applesauce-core: specifier: next - version: 0.0.0-next-20250203180810(typescript@5.7.3) + version: 0.0.0-next-20250203215539(typescript@5.7.3) applesauce-factory: specifier: next - version: 0.0.0-next-20250203180810(typescript@5.7.3) + version: 0.0.0-next-20250203215539(typescript@5.7.3) applesauce-loaders: specifier: next - version: 0.0.0-next-20250203180810(typescript@5.7.3) + version: 0.0.0-next-20250203215539(typescript@5.7.3) applesauce-net: specifier: ^0.10.0 version: 0.10.0(typescript@5.7.3) applesauce-react: specifier: next - version: 0.0.0-next-20250203180810(typescript@5.7.3) + version: 0.0.0-next-20250203215539(typescript@5.7.3) applesauce-signers: specifier: next - version: 0.0.0-next-20250203180810(typescript@5.7.3) + version: 0.0.0-next-20250203215539(typescript@5.7.3) bech32: specifier: ^2.0.0 version: 2.0.0 @@ -2192,32 +2192,32 @@ packages: engines: {node: '>=8.0.0'} hasBin: true - applesauce-accounts@0.0.0-next-20250203180810: - resolution: {integrity: sha512-yymm0dQgBJGgf3nlv1DC+AyxK+cvHLZczmRLyYW4k4MCNM7mO7qwnFGbMJJftDmVw5l8f9xBu0T/8r3IWsOxJw==} + applesauce-accounts@0.0.0-next-20250203215539: + resolution: {integrity: sha512-PuxwOkGC1wZrfVy585JTE6Eo7vnu9uN5Dt7rEb7PC/NFntv1RFSNfKOrXlEqzltiIo6StErN2ypFz2Tvtj20Eg==} - applesauce-content@0.0.0-next-20250203180810: - resolution: {integrity: sha512-7TpIqocvhn5g4M/Biua7AP3pWo4YtDa1C21uci/ElkbuqMNhVT37ASwttGM8GPcfLTxQ7uBkpPjOBTXm74D8Rw==} + applesauce-content@0.0.0-next-20250203215539: + resolution: {integrity: sha512-B7lNI/wtB1JZ20h6keFwMscP/ClVuyCC0e/fI4fhtv1RzPbr+GMrHD+liIJZnIqodptD/li3B8ixH41Ux20JlA==} - applesauce-core@0.0.0-next-20250203180810: - resolution: {integrity: sha512-BNXzw8ydbp4KiPfn7X5iCMaeaNlAW5tr3o6dWz9UwV7ItWieQLd17ySh0KPLdVbE+e42msBN7ByE7iNC0ItJOQ==} + applesauce-core@0.0.0-next-20250203215539: + resolution: {integrity: sha512-0Io5Vu1tXYm0ddtaK1DyboxrUblaKhEfYs/TIe9WhcGjBcHBNJgyqoAlZ9YNJ6TN6VX6CmyVAq779KH3IWtWgQ==} applesauce-core@0.10.0: resolution: {integrity: sha512-QMhUh4FIARcqY5soCB4Z8DIu+py0rYb28IgWT4gP9DLBGpDrY8lStXk7W1/46TLjEH97y0hbiXFK7kMCZ31oOQ==} - applesauce-factory@0.0.0-next-20250203180810: - resolution: {integrity: sha512-O4xMkwaOTX7t6JYaS78WTKzIRekCipCBnDeZpyYmLK+h4rQ+8aKd21VonV4hfh9A8dTeS28QNFsdDabbH+22Nw==} + applesauce-factory@0.0.0-next-20250203215539: + resolution: {integrity: sha512-1jMAzucp8as7bIyOHKwCHGQnkVuOmlaxoDohMth6nVhXclLBUp7pXzHDIwfqQndWWMCMq5TcPQAfYmlVsFQgAQ==} - applesauce-loaders@0.0.0-next-20250203180810: - resolution: {integrity: sha512-ZWU23UtUQPQdM26NFLUralB8M8IuxrJqYqCwA5j5pozi5EG49OxxdPaZuXn3u37VRX8mibQYRVQqR+rhPWEC6g==} + applesauce-loaders@0.0.0-next-20250203215539: + resolution: {integrity: sha512-ZhzJnnLLigJ/WGXtm3oeQSC3MwGpII92FmO0/Rbrd4vceOOx7b9hEbseDTtfYUtY8e/abahYuKqLH0d8QQFtiA==} applesauce-net@0.10.0: resolution: {integrity: sha512-ZsAs/MkeGHiPZ2/a8lwP8lx/Eh+5Dot0qG4BLTAqjg4emP/RsiqW+hyc6v6QcVbdvuR0+hP1gka3+wWtiy/cTA==} - applesauce-react@0.0.0-next-20250203180810: - resolution: {integrity: sha512-/LloC27jD9BieXmoTZs59SVbHw/vzjh/mh1jt98TQT8NcSh9zoPD1quR4a4BNFXUmeDDAxOi4VcrTOCVXAvweA==} + applesauce-react@0.0.0-next-20250203215539: + resolution: {integrity: sha512-KVizXnEkyjHJuVyl11oUArKAIk760U9olSH0hSSkhvPmvzmfUKFvNmugaIeyarr5FJA1qoebkrpsYzbIkvXnMA==} - applesauce-signers@0.0.0-next-20250203180810: - resolution: {integrity: sha512-PJBPVmLW+lDYQ2TYflJGr/ddmvDu0v19l6I9XBhHfmlS1ypAej9kk4c4X56gRzROeq2zgIs/SSi1+Jm454AqRw==} + applesauce-signers@0.0.0-next-20250203215539: + resolution: {integrity: sha512-o2xftPZuBDLWobAGPdCxWkAGY8F7NqmS85Dra8sVjSB8ZqFOIjII+2iBcqKAk5ytU23FAH3TzZXt1/11w0dGgQ==} arg@4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} @@ -5194,8 +5194,8 @@ packages: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - semver@7.7.0: - resolution: {integrity: sha512-DrfFnPzblFmNrIZzg5RzHegbiRWg7KMR7btwi2yjHwx06zsUbO5g613sVwEV7FTwmzJu+Io0lJe2GJ3LxqpvBQ==} + semver@7.7.1: + resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} engines: {node: '>=10'} hasBin: true @@ -6887,7 +6887,7 @@ snapshots: plist: 3.1.0 prompts: 2.4.2 rimraf: 4.4.1 - semver: 7.7.0 + semver: 7.7.1 tar: 6.2.1 tslib: 2.6.2 xml2js: 0.5.0 @@ -6909,7 +6909,7 @@ snapshots: plist: 3.1.0 prompts: 2.4.2 rimraf: 4.4.1 - semver: 7.7.0 + semver: 7.7.1 tar: 6.2.1 tslib: 2.8.1 xml2js: 0.5.0 @@ -7161,7 +7161,7 @@ snapshots: outdent: 0.5.0 prettier: 2.8.8 resolve-from: 5.0.0 - semver: 7.7.0 + semver: 7.7.1 '@changesets/assemble-release-plan@6.0.5': dependencies: @@ -7170,7 +7170,7 @@ snapshots: '@changesets/should-skip-package': 0.1.1 '@changesets/types': 6.0.0 '@manypkg/get-packages': 1.1.3 - semver: 7.7.0 + semver: 7.7.1 '@changesets/changelog-git@0.2.0': dependencies: @@ -7203,7 +7203,7 @@ snapshots: package-manager-detector: 0.2.9 picocolors: 1.1.1 resolve-from: 5.0.0 - semver: 7.7.0 + semver: 7.7.1 spawndamnit: 3.0.1 term-size: 2.2.1 @@ -7226,7 +7226,7 @@ snapshots: '@changesets/types': 6.0.0 '@manypkg/get-packages': 1.1.3 picocolors: 1.1.1 - semver: 7.7.0 + semver: 7.7.1 '@changesets/get-release-plan@4.0.6': dependencies: @@ -8423,10 +8423,10 @@ snapshots: dependencies: entities: 2.2.0 - applesauce-accounts@0.0.0-next-20250203180810(typescript@5.7.3): + applesauce-accounts@0.0.0-next-20250203215539(typescript@5.7.3): dependencies: '@noble/hashes': 1.7.1 - applesauce-signers: 0.0.0-next-20250203180810(typescript@5.7.3) + applesauce-signers: 0.0.0-next-20250203215539(typescript@5.7.3) nanoid: 5.0.9 nostr-tools: 2.10.4(typescript@5.7.3) rxjs: 7.8.1 @@ -8434,13 +8434,13 @@ snapshots: - supports-color - typescript - applesauce-content@0.0.0-next-20250203180810(typescript@5.7.3): + applesauce-content@0.0.0-next-20250203215539(typescript@5.7.3): dependencies: '@cashu/cashu-ts': 2.0.0-rc1 '@types/hast': 3.0.4 '@types/mdast': 4.0.4 '@types/unist': 3.0.3 - applesauce-core: 0.0.0-next-20250203180810(typescript@5.7.3) + applesauce-core: 0.0.0-next-20250203215539(typescript@5.7.3) mdast-util-find-and-replace: 3.0.2 nostr-tools: 2.10.4(typescript@5.7.3) remark: 15.0.1 @@ -8451,7 +8451,7 @@ snapshots: - supports-color - typescript - applesauce-core@0.0.0-next-20250203180810(typescript@5.7.3): + applesauce-core@0.0.0-next-20250203215539(typescript@5.7.3): dependencies: '@scure/base': 1.2.4 debug: 4.4.0 @@ -8479,19 +8479,19 @@ snapshots: - supports-color - typescript - applesauce-factory@0.0.0-next-20250203180810(typescript@5.7.3): + applesauce-factory@0.0.0-next-20250203215539(typescript@5.7.3): dependencies: - applesauce-content: 0.0.0-next-20250203180810(typescript@5.7.3) - applesauce-core: 0.0.0-next-20250203180810(typescript@5.7.3) + applesauce-content: 0.0.0-next-20250203215539(typescript@5.7.3) + applesauce-core: 0.0.0-next-20250203215539(typescript@5.7.3) nanoid: 5.0.9 nostr-tools: 2.10.4(typescript@5.7.3) transitivePeerDependencies: - supports-color - typescript - applesauce-loaders@0.0.0-next-20250203180810(typescript@5.7.3): + applesauce-loaders@0.0.0-next-20250203215539(typescript@5.7.3): dependencies: - applesauce-core: 0.0.0-next-20250203180810(typescript@5.7.3) + applesauce-core: 0.0.0-next-20250203215539(typescript@5.7.3) nanoid: 5.0.9 nostr-tools: 2.10.4(typescript@5.7.3) rx-nostr: 3.5.0 @@ -8510,12 +8510,12 @@ snapshots: - supports-color - typescript - applesauce-react@0.0.0-next-20250203180810(typescript@5.7.3): + applesauce-react@0.0.0-next-20250203215539(typescript@5.7.3): dependencies: - applesauce-accounts: 0.0.0-next-20250203180810(typescript@5.7.3) - applesauce-content: 0.0.0-next-20250203180810(typescript@5.7.3) - applesauce-core: 0.0.0-next-20250203180810(typescript@5.7.3) - applesauce-factory: 0.0.0-next-20250203180810(typescript@5.7.3) + applesauce-accounts: 0.0.0-next-20250203215539(typescript@5.7.3) + applesauce-content: 0.0.0-next-20250203215539(typescript@5.7.3) + applesauce-core: 0.0.0-next-20250203215539(typescript@5.7.3) + applesauce-factory: 0.0.0-next-20250203215539(typescript@5.7.3) nostr-tools: 2.10.4(typescript@5.7.3) react: 18.3.1 rxjs: 7.8.1 @@ -8523,12 +8523,12 @@ snapshots: - supports-color - typescript - applesauce-signers@0.0.0-next-20250203180810(typescript@5.7.3): + applesauce-signers@0.0.0-next-20250203215539(typescript@5.7.3): dependencies: '@noble/hashes': 1.7.1 '@noble/secp256k1': 1.7.1 '@scure/base': 1.2.4 - applesauce-core: 0.0.0-next-20250203180810(typescript@5.7.3) + applesauce-core: 0.0.0-next-20250203215539(typescript@5.7.3) debug: 4.4.0 nanoid: 5.0.9 nostr-tools: 2.10.4(typescript@5.7.3) @@ -11073,7 +11073,7 @@ snapshots: node-abi@3.74.0: dependencies: - semver: 7.7.0 + semver: 7.7.1 node-addon-api@6.1.0: {} @@ -11099,7 +11099,7 @@ snapshots: dependencies: hosted-git-info: 4.1.0 is-core-module: 2.16.1 - semver: 7.7.0 + semver: 7.7.1 validate-npm-package-license: 3.0.4 nostr-idb@2.2.0(typescript@5.7.3): @@ -11990,7 +11990,7 @@ snapshots: semver@6.3.1: {} - semver@7.7.0: {} + semver@7.7.1: {} send@0.19.0: dependencies: @@ -12057,7 +12057,7 @@ snapshots: detect-libc: 2.0.3 node-addon-api: 6.1.0 prebuild-install: 7.1.3 - semver: 7.7.0 + semver: 7.7.1 simple-get: 4.0.1 tar-fs: 3.0.8 tunnel-agent: 0.6.0 diff --git a/src/classes/batch-identifier-loader.ts b/src/classes/batch-identifier-loader.ts index 44e29f19a..ed6fa29a3 100644 --- a/src/classes/batch-identifier-loader.ts +++ b/src/classes/batch-identifier-loader.ts @@ -8,9 +8,6 @@ import { createDefer, Deferred } from "applesauce-core/promise"; import { Subject } from "rxjs"; import PersistentSubscription from "./persistent-subscription"; -import Process from "./process"; -import processManager from "../services/process-manager"; -import Dataflow04 from "../components/icons/dataflow-04"; import SuperMap from "./super-map"; /** Batches requests for events with #d tags from a single relay */ @@ -18,7 +15,6 @@ export default class BatchIdentifierLoader { store: EventStore; kinds: number[]; relay: AbstractRelay; - process: Process; /** list of identifiers that have been loaded */ requested = new Set(); @@ -37,20 +33,17 @@ export default class BatchIdentifierLoader { log: Debugger; + active = false; constructor(store: EventStore, relay: AbstractRelay, kinds: number[], log?: Debugger) { this.store = store; this.relay = relay; this.kinds = kinds; this.log = log || debug("BatchIdentifierLoader"); - this.process = new Process("BatchIdentifierLoader", this, [relay]); - this.process.icon = Dataflow04; - processManager.registerProcess(this.process); this.subscription = new PersistentSubscription(this.relay, { onevent: (event) => this.handleEvent(event), oneose: () => this.handleEOSE(), }); - this.process.addChild(this.subscription.process); } requestEvents(identifier: string): Promise> { @@ -74,9 +67,9 @@ export default class BatchIdentifierLoader { requestUpdate = _throttle( () => { // don't do anything if the subscription is already running - if (this.process.active) return; + if (this.active) return; - this.process.active = true; + this.active = true; this.update(); }, 500, @@ -106,7 +99,7 @@ export default class BatchIdentifierLoader { // reset this.pending.clear(); - this.process.active = false; + this.active = false; for (const identifier of this.changedIdentifiers) { this.onIdentifierUpdate.next(identifier); @@ -133,25 +126,23 @@ export default class BatchIdentifierLoader { } try { - this.process.active = true; + this.active = true; this.subscription.filters = []; if (dTags.length > 0) this.subscription.filters.push({ "#d": dTags, kinds: this.kinds }); await this.subscription.update(); } catch (error) { if (error instanceof Error) this.log(`Failed to update subscription`, error.message); - this.process.active = false; + this.active = false; } } else { this.log("Closing"); this.subscription.close(); - this.process.active = false; + this.active = false; } } destroy() { this.subscription.destroy(); - this.process.remove(); - processManager.unregisterProcess(this.process); } } diff --git a/src/classes/batch-relation-loader.ts b/src/classes/batch-relation-loader.ts index f82055caf..295d586b7 100644 --- a/src/classes/batch-relation-loader.ts +++ b/src/classes/batch-relation-loader.ts @@ -6,9 +6,6 @@ import { createDefer, Deferred } from "applesauce-core/promise"; import { Subject } from "rxjs"; import PersistentSubscription from "./persistent-subscription"; -import Process from "./process"; -import processManager from "../services/process-manager"; -import Dataflow04 from "../components/icons/dataflow-04"; import SuperMap from "./super-map"; import { eventStore } from "../services/event-store"; @@ -16,7 +13,6 @@ import { eventStore } from "../services/event-store"; export default class BatchRelationLoader { kinds: number[]; relay: AbstractRelay; - process: Process; requested = new Set(); /** event id / coordinate -> event id -> event */ @@ -34,19 +30,16 @@ export default class BatchRelationLoader { log: Debugger; + active = false; constructor(relay: AbstractRelay, kinds: number[], log?: Debugger) { this.relay = relay; this.kinds = kinds; this.log = log || debug("BatchRelationLoader"); - this.process = new Process("BatchRelationLoader", this, [relay]); - this.process.icon = Dataflow04; - processManager.registerProcess(this.process); this.subscription = new PersistentSubscription(this.relay, { onevent: (event) => this.handleEvent(event), oneose: () => this.handleEOSE(), }); - this.process.addChild(this.subscription.process); } requestEvents(uid: string): Promise> { @@ -70,9 +63,9 @@ export default class BatchRelationLoader { requestUpdate = _throttle( () => { // don't do anything if the subscription is already running - if (this.process.active) return; + if (this.active) return; - this.process.active = true; + this.active = true; this.update(); }, 500, @@ -106,7 +99,7 @@ export default class BatchRelationLoader { // reset this.pending.clear(); - this.process.active = false; + this.active = false; // do next request or close the subscription if (this.next.size > 0) this.requestUpdate(); @@ -132,7 +125,7 @@ export default class BatchRelationLoader { } try { - this.process.active = true; + this.active = true; this.subscription.filters = []; if (ids.length > 0) this.subscription.filters.push({ "#e": ids, kinds: this.kinds }); if (cords.length > 0) this.subscription.filters.push({ "#a": cords, kinds: this.kinds }); @@ -140,18 +133,16 @@ export default class BatchRelationLoader { await this.subscription.update(); } catch (error) { if (error instanceof Error) this.log(`Failed to update subscription`, error.message); - this.process.active = false; + this.active = false; } } else { this.log("Closing"); this.subscription.close(); - this.process.active = false; + this.active = false; } } destroy() { this.subscription.destroy(); - this.process.remove(); - processManager.unregisterProcess(this.process); } } diff --git a/src/classes/notifications.ts b/src/classes/notifications.ts deleted file mode 100644 index f2b576281..000000000 --- a/src/classes/notifications.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { NostrEvent, kinds, nip18, nip25 } from "nostr-tools"; -import { BehaviorSubject } from "rxjs"; -import { map, throttleTime } from "rxjs/operators"; -import { getZapPayment } from "applesauce-core/helpers"; - -import { getContentPointers, getThreadReferences, isReply, isRepost } from "../helpers/nostr/event"; -import singleEventLoader from "../services/single-event-loader"; -import { getPubkeysMentionedInContent } from "../helpers/nostr/post"; -import { TORRENT_COMMENT_KIND } from "../helpers/nostr/torrents"; -import { getPubkeysFromList } from "../helpers/nostr/lists"; -import { eventStore, queryStore } from "../services/event-store"; -import localSettings from "../services/local-settings"; - -export const NotificationTypeSymbol = Symbol("notificationType"); - -export enum NotificationType { - Reply = "reply", - Repost = "repost", - Zap = "zap", - Reaction = "reaction", - Mention = "mention", - Message = "message", - Quote = "quote", -} -export type CategorizedEvent = NostrEvent & { [NotificationTypeSymbol]?: NotificationType }; - -export default class AccountNotifications { - pubkey: string; - - timeline = new BehaviorSubject([]); - - constructor(pubkey: string) { - this.pubkey = pubkey; - - // subscribe to query store - queryStore - .timeline([ - { - "#p": [pubkey], - kinds: [ - kinds.ShortTextNote, - kinds.Repost, - kinds.GenericRepost, - kinds.Reaction, - kinds.Zap, - TORRENT_COMMENT_KIND, - kinds.LongFormArticle, - kinds.EncryptedDirectMessage, - 1111, //NIP-22 - ], - }, - ]) - .pipe( - throttleTime(100), - map((events) => events.map(this.handleEvent.bind(this)).filter(this.filterEvent.bind(this))), - ) - .subscribe((events) => this.timeline.next(events)); - } - - private categorizeEvent(event: NostrEvent): CategorizedEvent { - const e = event as CategorizedEvent; - - if (e[NotificationTypeSymbol]) return e; - - if (event.kind === kinds.Zap) { - e[NotificationTypeSymbol] = NotificationType.Zap; - } else if (event.kind === kinds.Reaction) { - e[NotificationTypeSymbol] = NotificationType.Reaction; - } else if (isRepost(event)) { - e[NotificationTypeSymbol] = NotificationType.Repost; - } else if (event.kind === kinds.EncryptedDirectMessage) { - e[NotificationTypeSymbol] = NotificationType.Message; - } else if ( - event.kind === kinds.ShortTextNote || - event.kind === TORRENT_COMMENT_KIND || - event.kind === kinds.LiveChatMessage || - event.kind === kinds.LongFormArticle - ) { - // is the pubkey mentioned in any way in the content - const isMentioned = getPubkeysMentionedInContent(event.content, true).includes(this.pubkey); - const isQuote = - event.tags.some((t) => t[0] === "q" && (t[1] === event.id || t[3] === this.pubkey)) || - getContentPointers(event.content).some( - (p) => (p.type === "nevent" && p.data.id === event.id) || (p.type === "note" && p.data === event.id), - ); - - if (isMentioned) e[NotificationTypeSymbol] = NotificationType.Mention; - else if (isQuote) e[NotificationTypeSymbol] = NotificationType.Quote; - else if (isReply(event)) e[NotificationTypeSymbol] = NotificationType.Reply; - } - return e; - } - - handleEvent(event: NostrEvent) { - const e = this.categorizeEvent(event); - - const loadEvent = (eventId: string, relays?: string[]) => { - singleEventLoader.next({ id: eventId, relays: [...localSettings.readRelays.value, ...(relays ?? [])] }); - }; - - // load event quotes - const quotes = event.tags.filter((t) => t[0] === "q" && t[1]); - for (const tag of quotes) { - loadEvent(tag[1], tag[2] ? [tag[2]] : undefined); - } - - // load reactions and replies - switch (e[NotificationTypeSymbol]) { - case NotificationType.Reply: - const refs = getThreadReferences(e); - if (refs.reply?.e?.id) loadEvent(refs.reply.e.id, refs.reply.e.relays); - break; - case NotificationType.Reaction: { - const pointer = nip25.getReactedEventPointer(e); - if (pointer?.id) loadEvent(pointer.id, pointer.relays); - break; - } - } - - return e; - } - - private filterEvent(event: CategorizedEvent) { - // ignore if muted - // TODO: this should be moved somewhere more performant - const muteList = eventStore.getReplaceable(kinds.Mutelist, this.pubkey); - const mutedPubkeys = muteList ? getPubkeysFromList(muteList).map((p) => p.pubkey) : []; - if (mutedPubkeys.includes(event.pubkey)) return false; - - // ignore if own - if (event.pubkey === this.pubkey) return false; - - const e = event as CategorizedEvent; - - switch (e[NotificationTypeSymbol]) { - case NotificationType.Reply: - const refs = getThreadReferences(e); - if (!refs.reply?.e?.id) return false; - if (refs.reply?.e?.author && refs.reply?.e?.author !== this.pubkey) return false; - const parent = eventStore.getEvent(refs.reply.e.id); - if (!parent || parent.pubkey !== this.pubkey) return false; - break; - case NotificationType.Mention: - break; - case NotificationType.Repost: { - const pointer = nip18.getRepostedEventPointer(e); - if (pointer?.author !== this.pubkey) return false; - break; - } - case NotificationType.Reaction: { - const pointer = nip25.getReactedEventPointer(e); - if (!pointer) return false; - if (pointer.author !== this.pubkey) return false; - if (pointer.kind === kinds.EncryptedDirectMessage) return false; - const parent = eventStore.getEvent(pointer.id); - if (parent && parent.kind === kinds.EncryptedDirectMessage) return false; - break; - } - case NotificationType.Zap: - const p = getZapPayment(e); - if (!p || p.amount === 0) return false; - break; - } - - return true; - } -} diff --git a/src/classes/persistent-subscription.ts b/src/classes/persistent-subscription.ts index 1948b1eb1..0288d0071 100644 --- a/src/classes/persistent-subscription.ts +++ b/src/classes/persistent-subscription.ts @@ -4,13 +4,9 @@ import { AbstractRelay, Subscription, SubscriptionParams } from "nostr-tools/abs import { isFilterEqual } from "applesauce-core/helpers"; import relayPoolService from "../services/relay-pool"; -import FilterFunnel01 from "../components/icons/filter-funnel-01"; -import processManager from "../services/process-manager"; -import Process from "./process"; export default class PersistentSubscription { id: string; - process: Process; relay: Relay; filters: Filter[]; connecting = false; @@ -24,10 +20,9 @@ export default class PersistentSubscription { return !this.subscription || this.subscription.closed; } + active = false; constructor(relay: AbstractRelay, params?: Partial) { this.id = nanoid(8); - this.process = new Process("PersistentSubscription", this, [relay]); - this.process.icon = FilterFunnel01; this.filters = []; this.params = { //@ts-expect-error @@ -36,8 +31,6 @@ export default class PersistentSubscription { }; this.relay = relay; - - processManager.registerProcess(this.process); } /** attempts to update the subscription */ @@ -45,12 +38,12 @@ export default class PersistentSubscription { if (!this.filters || this.filters.length === 0) throw new Error("Missing filters"); if (this.connecting) throw new Error("Cant update while connecting"); - this.process.active = true; + this.active = true; this.connecting = true; if ((await relayPoolService.waitForOpen(this.relay)) === false) { this.connecting = false; - this.process.active = false; + this.active = false; throw new Error("Failed to connect to relay"); } this.connecting = false; @@ -66,7 +59,7 @@ export default class PersistentSubscription { if (!this.closed) { relayPoolService.handleRelayNotice(this.relay, reason); - this.process.active = false; + this.active = false; } this.params.onclose?.(reason); }, @@ -80,14 +73,12 @@ export default class PersistentSubscription { } close() { if (this.subscription?.closed === false) this.subscription.close(); - this.process.active = false; + this.active = false; return this; } destroy() { this.close(); - this.process.remove(); - processManager.unregisterProcess(this.process); } } diff --git a/src/classes/process.ts b/src/classes/process.ts deleted file mode 100644 index 8ffef1e0a..000000000 --- a/src/classes/process.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { ComponentWithAs, IconProps } from "@chakra-ui/react"; -import { SimpleRelay } from "nostr-idb"; -import { AbstractRelay } from "nostr-tools/abstract-relay"; - -let lastId = 0; -export default class Process { - id = ++lastId; - type: string; - name?: string; - icon?: ComponentWithAs<"svg", IconProps>; - source: any; - - // if this process is running - active: boolean = false; - - // the relays this process is claiming - relays = new Set(); - - // the parent process - parent?: Process; - // any children this process has created - children = new Set(); - - constructor(type: string, source: any, relays?: Iterable) { - this.type = type; - this.source = source; - - this.relays = new Set(relays); - } - - static forkOrCreate(name: string, source: any, relays: Iterable, parent?: Process) { - return parent?.fork(name, source, relays) || new Process("BatchKindLoader", this, relays); - } - - addChild(child: Process) { - if (child === this) throw new Error("Process cant be a child of itself"); - this.children.add(child); - child.parent = this; - } - - fork(name: string, source: any, relays?: Iterable) { - const child = new Process(name, source, relays); - this.addChild(child); - return child; - } - - remove() { - if (!this.parent) return; - - this.parent.children.delete(this); - this.parent = undefined; - } -} diff --git a/src/classes/relay-pool.ts b/src/classes/relay-pool.ts index 4e729f5b6..0bf9f8959 100644 --- a/src/classes/relay-pool.ts +++ b/src/classes/relay-pool.ts @@ -7,7 +7,6 @@ import { logger } from "../helpers/debug"; import { safeRelayUrl, validateRelayURL } from "../helpers/relay"; import SuperMap from "./super-map"; import verifyEventMethod from "../services/verify-event"; -import processManager from "../services/process-manager"; import localSettings from "../services/local-settings"; export type Notice = { @@ -229,13 +228,7 @@ export default class RelayPool implements IConnectionPool { // don't disconnect from authenticated relays if (this.authenticated.get(relay).value) continue; - let disconnect = true; - for (const process of processManager.processes) { - if (process.active && process.relays.has(relay)) { - disconnect = false; - break; - } - } + let disconnect = false; if (disconnect) { this.log(`No active processes using ${relay.url}, disconnecting`); diff --git a/src/components/content/links/image.tsx b/src/components/content/links/image.tsx index b7cbd895c..d431292d6 100644 --- a/src/components/content/links/image.tsx +++ b/src/components/content/links/image.tsx @@ -95,21 +95,17 @@ export function EmbeddedImage({ src, event, imageProps, ...props }: EmbeddedImag if (ref.current) handleImageFallbacks(ref.current, getPubkeyMediaServers); }, []); - // NOTE: the parent
has display=block and and has inline-block - // this is so that the element can act like a block without being full width return ( -
- - - -
+ + + ); } @@ -184,7 +180,6 @@ export function renderImageUrl(match: URL) { label="Image" url={match} actions={hash ? : undefined} - hideOnDefaultOpen={!hash} > diff --git a/src/components/content/links/video.tsx b/src/components/content/links/video.tsx index b1be6e388..454d4bfc3 100644 --- a/src/components/content/links/video.tsx +++ b/src/components/content/links/video.tsx @@ -38,7 +38,7 @@ export function renderVideoUrl(match: URL) { if (!isVideoURL(match)) return null; return ( - + ); @@ -48,7 +48,7 @@ export function renderStreamUrl(match: URL) { if (!isStreamURL(match)) return null; return ( - + ); diff --git a/src/components/layout/components/index.tsx b/src/components/layout/components/index.tsx index c97a0e6a6..4304de515 100644 --- a/src/components/layout/components/index.tsx +++ b/src/components/layout/components/index.tsx @@ -7,7 +7,7 @@ import { QuestionIcon } from "@chakra-ui/icons"; import { LightningIcon, SettingsIcon } from "../../icons"; import Package from "../../icons/package"; import useRecentIds from "../../../hooks/use-recent-ids"; -import { defaultFavoriteApps, internalApps, internalTools } from "../../navigation/apps"; +import { defaultAnonFavoriteApps, defaultUserFavoriteApps, internalApps, internalTools } from "../../navigation/apps"; import NavItem from "./nav-item"; import Plus from "../../icons/plus"; import useFavoriteInternalIds from "../../../hooks/use-favorite-internal-ids"; @@ -15,7 +15,8 @@ import useFavoriteInternalIds from "../../../hooks/use-favorite-internal-ids"; export default function NavItems() { const account = useActiveAccount(); - const { ids: favorites = defaultFavoriteApps } = useFavoriteInternalIds("apps", "app"); + const defaultApps = account ? defaultUserFavoriteApps : defaultAnonFavoriteApps; + const { ids: favorites = defaultApps } = useFavoriteInternalIds("apps", "app"); const { recent } = useRecentIds("apps", 3); const favoriteApps = useMemo(() => { diff --git a/src/components/navigation/app-favorite-button.tsx b/src/components/navigation/app-favorite-button.tsx index 11a890a87..ea2900e38 100644 --- a/src/components/navigation/app-favorite-button.tsx +++ b/src/components/navigation/app-favorite-button.tsx @@ -4,7 +4,7 @@ import { kinds } from "nostr-tools"; import { useEventFactory } from "applesauce-react/hooks"; import { removeNameValueTag, addNameValueTag } from "applesauce-factory/operations"; -import { App, defaultFavoriteApps } from "./apps"; +import { App, defaultUserFavoriteApps } from "./apps"; import useFavoriteInternalIds from "../../hooks/use-favorite-internal-ids"; import { usePublishEvent } from "../../providers/global/publish-provider"; import { StarEmptyIcon, StarFullIcon } from "../icons"; @@ -22,7 +22,7 @@ export default function AppFavoriteButton({ const handleClick = async () => { const prev = favorites || { kind: kinds.Application, - tags: [["d", "nostrudel-favorite-apps"], ...defaultFavoriteApps.map((id) => ["app", id])], + tags: [["d", "nostrudel-favorite-apps"], ...defaultUserFavoriteApps.map((id) => ["app", id])], }; setLoading(true); diff --git a/src/components/navigation/apps.ts b/src/components/navigation/apps.ts index 045144dc5..a3e2e9ae2 100644 --- a/src/components/navigation/apps.ts +++ b/src/components/navigation/apps.ts @@ -260,6 +260,7 @@ export const externalTools: App[] = [ }, ]; -export const defaultFavoriteApps = ["launchpad", "notes", "discover", "notifications", "messages", "search"]; +export const defaultAnonFavoriteApps = ["notes", "discover", "search", "articles", "streams"]; +export const defaultUserFavoriteApps = ["launchpad", "notes", "discover", "notifications", "messages", "search"]; export const allApps = [...internalApps, ...internalTools, ...externalTools]; diff --git a/src/components/people-list-selection/people-list-selection.tsx b/src/components/people-list-selection/people-list-selection.tsx index 35e623691..7c9ac23a8 100644 --- a/src/components/people-list-selection/people-list-selection.tsx +++ b/src/components/people-list-selection/people-list-selection.tsx @@ -59,7 +59,7 @@ export default function PeopleListSelection({ const modal = useDisclosure(); const account = useActiveAccount(); const lists = useUserSets(account?.pubkey).filter((list) => list.kind === kinds.Followsets); - const { lists: favoriteLists } = useFavoriteLists(); + const { lists: favoriteLists } = useFavoriteLists(account?.pubkey); const { selected, setSelected, listEvent } = usePeopleListContext(); const searchDirectory = useObservable(userSearchDirectory); diff --git a/src/hooks/use-forward-subscription.ts b/src/hooks/use-forward-subscription.ts index aa40408f1..5d546d6ad 100644 --- a/src/hooks/use-forward-subscription.ts +++ b/src/hooks/use-forward-subscription.ts @@ -12,14 +12,6 @@ export default function useForwardSubscription(relays: string[], filters?: Filte const id = useMemo(() => nanoid(10), []); const rxReq = useMemo(() => createRxForwardReq(id), [id]); - // load from cache - // useEffect(() => { - // const sub = cacheRequest(Array.isArray(filters) ? filters : [filters]).subscribe({ - // next: (e) => eventStore.add(e), - // complete: () => sub.unsubscribe(), - // }); - // }, [hash(filters)]); - // attach to rxNostr const observable = useMemo(() => rxNostr.use(rxReq, { on: { relays } }), [rxReq, relays.join(",")]); diff --git a/src/hooks/use-timeline-loader.ts b/src/hooks/use-timeline-loader.ts index dd4297f41..ff977feac 100644 --- a/src/hooks/use-timeline-loader.ts +++ b/src/hooks/use-timeline-loader.ts @@ -3,6 +3,7 @@ import { useStoreQuery } from "applesauce-react/hooks"; import { useEventStore } from "applesauce-react/hooks/use-event-store"; import { Queries } from "applesauce-core"; import { Filter, NostrEvent } from "nostr-tools"; +import { useThrottle } from "react-use"; import sum from "hash-sum"; import timelineCacheService from "../services/timeline-cache"; @@ -35,16 +36,17 @@ export default function useTimelineLoader( return () => sub?.unsubscribe(); }, [eventStore, loader]); - let timeline = useStoreQuery(Queries.TimelineQuery, filters && [filters]) ?? []; + const timeline = useStoreQuery(Queries.TimelineQuery, filters && [filters]) ?? []; + let throttled = useThrottle(timeline, 50); // set event filter if (opts?.eventFilter) - timeline = timeline.filter((e) => { + throttled = throttled.filter((e) => { try { return opts.eventFilter && opts.eventFilter(e); } catch (error) {} return false; }); - return { loader, timeline }; + return { loader, timeline: throttled }; } diff --git a/src/providers/global/index.tsx b/src/providers/global/index.tsx index 8844a0a87..7c5178f73 100644 --- a/src/providers/global/index.tsx +++ b/src/providers/global/index.tsx @@ -5,7 +5,6 @@ import { AccountsProvider, QueryStoreProvider } from "applesauce-react/providers import { SigningProvider } from "./signing-provider"; import buildTheme from "../../theme"; import useAppSettings from "../../hooks/use-user-app-settings"; -import NotificationsProvider from "./notifications-provider"; import { UserEmojiProvider } from "./emoji-provider"; import BreakpointProvider from "./breakpoint-provider"; import PublishProvider from "./publish-provider"; @@ -33,13 +32,11 @@ export const GlobalProviders = ({ children }: { children: React.ReactNode }) => - - - - {children} - - - + + + {children} + + diff --git a/src/providers/global/notifications-provider.tsx b/src/providers/global/notifications-provider.tsx deleted file mode 100644 index 73193aca3..000000000 --- a/src/providers/global/notifications-provider.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { PropsWithChildren, createContext, useCallback, useContext, useEffect, useMemo, useState } from "react"; -import { kinds } from "nostr-tools"; -import { useActiveAccount } from "applesauce-react/hooks"; -import { TimelineLoader } from "applesauce-loaders"; - -import { useReadRelays } from "../../hooks/use-client-relays"; -import { NostrEvent } from "../../types/nostr-event"; -import useClientSideMuteFilter from "../../hooks/use-client-side-mute-filter"; -import useTimelineLoader from "../../hooks/use-timeline-loader"; -import { TORRENT_COMMENT_KIND } from "../../helpers/nostr/torrents"; -import { useUserInbox } from "../../hooks/use-user-mailboxes"; -import AccountNotifications from "../../classes/notifications"; -import { truncateId } from "../../helpers/string"; - -type NotificationTimelineContextType = { - timeline?: TimelineLoader; - notifications?: AccountNotifications; -}; -const NotificationTimelineContext = createContext(null); - -export function useNotifications() { - const ctx = useContext(NotificationTimelineContext); - if (!ctx) throw new Error("Missing notifications provider"); - return ctx; -} - -export default function NotificationsProvider({ children }: PropsWithChildren) { - const account = useActiveAccount(); - const inbox = useUserInbox(account?.pubkey); - const readRelays = useReadRelays(inbox); - - const [notifications, setNotifications] = useState(); - - const userMuteFilter = useClientSideMuteFilter(); - const eventFilter = useCallback( - (event: NostrEvent) => { - if (userMuteFilter(event)) return false; - return true; - }, - [userMuteFilter], - ); - - const { loader } = useTimelineLoader( - `${truncateId(account?.pubkey ?? "anon")}-notification`, - readRelays, - account?.pubkey - ? { - "#p": [account.pubkey], - kinds: [ - kinds.ShortTextNote, - kinds.Repost, - kinds.GenericRepost, - kinds.Reaction, - kinds.Zap, - TORRENT_COMMENT_KIND, - kinds.LongFormArticle, - ], - } - : undefined, - { eventFilter }, - ); - - useEffect(() => { - if (!account?.pubkey) return; - const n = new AccountNotifications(account.pubkey); - setNotifications(n); - if (import.meta.env.DEV) { - // @ts-expect-error debug - window.accountNotifications = n; - } - - return () => { - setNotifications(undefined); - }; - }, [account?.pubkey]); - - const context = useMemo(() => ({ timeline: loader, notifications }), [loader, notifications]); - - return {children}; -} diff --git a/src/providers/local/people-list-provider.tsx b/src/providers/local/people-list-provider.tsx index 326ce6547..9927d4b03 100644 --- a/src/providers/local/people-list-provider.tsx +++ b/src/providers/local/people-list-provider.tsx @@ -1,8 +1,8 @@ import { PropsWithChildren, createContext, useCallback, useContext, useMemo } from "react"; -import { Filter, kinds } from "nostr-tools"; +import { getProfilePointersFromList } from "applesauce-core/helpers"; import { useActiveAccount } from "applesauce-react/hooks"; +import { Filter, kinds } from "nostr-tools"; -import { getPubkeysFromList } from "../../helpers/nostr/lists"; import useReplaceableEvent from "../../hooks/use-replaceable-event"; import { NostrEvent } from "../../types/nostr-event"; import useRouteSearchValue from "../../hooks/use-route-search-value"; @@ -46,15 +46,16 @@ export function usePeopleListSelect(selected: ListId, onChange: (list: ListId) = const listId = useListCoordinate(selected); const listEvent = useReplaceableEvent(listId, [], true); - const people = listEvent && getPubkeysFromList(listEvent); + const people = useMemo(() => listEvent && getProfilePointersFromList(listEvent), [listEvent]); const filter = useMemo(() => { if (selected === "global") return {}; if (selected === "self") { if (account) return { authors: [account.pubkey] }; - else return {}; + else return undefined; } - if (!people) return undefined; + if (!people || people.length === 0) return undefined; + return { authors: people.map((p) => p.pubkey) }; }, [people, selected, account]); diff --git a/src/services/cache-relay.ts b/src/services/cache-relay.ts index 849aba62b..670cd1edf 100644 --- a/src/services/cache-relay.ts +++ b/src/services/cache-relay.ts @@ -1,4 +1,4 @@ -import { BehaviorSubject, distinctUntilChanged, pairwise } from "rxjs"; +import { BehaviorSubject, distinctUntilChanged, Observable, pairwise } from "rxjs"; import { CacheRelay, openDB } from "nostr-idb"; import { AbstractRelay } from "nostr-tools/abstract-relay"; import { fakeVerifyEvent, isFromCache } from "applesauce-core/helpers"; @@ -10,6 +10,7 @@ import WasmRelay from "./wasm-relay"; import MemoryRelay from "../classes/memory-relay"; import localSettings from "./local-settings"; import { eventStore } from "./event-store"; +import { Filter, NostrEvent } from "nostr-tools"; export const NOSTR_RELAY_TRAY_URL = "ws://localhost:4869/"; export async function checkNostrRelayTray() { @@ -114,6 +115,23 @@ cacheRelay$.pipe(pairwise()).subscribe(([prev, current]) => { } }); +// load events from cache relay +export function cacheRequest(filters: Filter[]) { + return new Observable((observer) => { + const relay = getCacheRelay(); + if (!relay) return observer.complete(); + + const sub = relay.subscribe(filters, { + onevent: (event) => observer.next(event), + oneose: () => { + sub.close(); + observer.complete(); + }, + onclose: () => observer.complete(), + }); + }); +} + /** set the cache relay URL and waits for it to connect */ export async function setCacheRelayURL(url: string) { return new Promise((res) => { diff --git a/src/services/dictionary.ts b/src/services/dictionary.ts index 274c6bfe3..a0d5153b5 100644 --- a/src/services/dictionary.ts +++ b/src/services/dictionary.ts @@ -5,18 +5,14 @@ import { BehaviorSubject } from "rxjs"; import { WIKI_PAGE_KIND } from "../helpers/nostr/wiki"; import { logger } from "../helpers/debug"; -import Process from "../classes/process"; import SuperMap from "../classes/super-map"; import BatchIdentifierLoader from "../classes/batch-identifier-loader"; -import BookOpen01 from "../components/icons/book-open-01"; -import processManager from "./process-manager"; import relayPoolService from "./relay-pool"; import { eventStore } from "./event-store"; import { getCacheRelay } from "./cache-relay"; class DictionaryService { log = logger.extend("DictionaryService"); - process: Process; store: EventStore; topics = new SuperMap>>( @@ -25,7 +21,6 @@ class DictionaryService { loaders = new SuperMap((relay) => { const loader = new BatchIdentifierLoader(this.store, relay, [WIKI_PAGE_KIND], this.log.extend(relay.url)); - this.process.addChild(loader.process); loader.onIdentifierUpdate.subscribe((identifier) => { this.updateSubject(identifier); }); @@ -34,11 +29,6 @@ class DictionaryService { constructor(store: EventStore) { this.store = store; - this.process = new Process("DictionaryService", this); - this.process.icon = BookOpen01; - this.process.active = true; - - processManager.registerProcess(this.process); } // merged results from all loaders for a single event diff --git a/src/services/event-reactions.ts b/src/services/event-reactions.ts index af144a66b..a190a53e9 100644 --- a/src/services/event-reactions.ts +++ b/src/services/event-reactions.ts @@ -4,32 +4,18 @@ import { AbstractRelay } from "nostr-tools/abstract-relay"; import SuperMap from "../classes/super-map"; import relayPoolService from "./relay-pool"; -import Process from "../classes/process"; -import { LightningIcon } from "../components/icons"; -import processManager from "./process-manager"; import BatchRelationLoader from "../classes/batch-relation-loader"; import { logger } from "../helpers/debug"; import { getCacheRelay } from "./cache-relay"; class EventReactionsService { log = logger.extend("EventReactionsService"); - process: Process; private loaded = new Map(); loaders = new SuperMap((relay) => { - const loader = new BatchRelationLoader(relay, [kinds.Reaction], this.log.extend(relay.url)); - this.process.addChild(loader.process); - return loader; + return new BatchRelationLoader(relay, [kinds.Reaction], this.log.extend(relay.url)); }); - constructor() { - this.process = new Process("EventReactionsService", this); - this.process.icon = LightningIcon; - this.process.active = true; - - processManager.registerProcess(this.process); - } - requestReactions(uid: string, urls: Iterable, alwaysRequest = false) { if (this.loaded.get(uid) && !alwaysRequest) return; diff --git a/src/services/event-store.ts b/src/services/event-store.ts index 94d1b6249..87031ac81 100644 --- a/src/services/event-store.ts +++ b/src/services/event-store.ts @@ -1,7 +1,4 @@ import { EventStore, QueryStore } from "applesauce-core"; -import { isFromCache } from "applesauce-core/helpers"; - -import { cacheRelay$ } from "./cache-relay"; export const eventStore = new EventStore(); export const queryStore = new QueryStore(eventStore); @@ -12,10 +9,3 @@ if (import.meta.env.DEV) { // @ts-expect-error debug window.queryStore = queryStore; } - -// save all events to cache relay -eventStore.database.inserted.subscribe((event) => { - if (!isFromCache(event) && cacheRelay$.value) { - cacheRelay$.value.publish(event); - } -}); diff --git a/src/services/event-zaps.ts b/src/services/event-zaps.ts index 62b915891..813320f5c 100644 --- a/src/services/event-zaps.ts +++ b/src/services/event-zaps.ts @@ -4,32 +4,19 @@ import { AbstractRelay } from "nostr-tools/abstract-relay"; import SuperMap from "../classes/super-map"; import relayPoolService from "./relay-pool"; -import Process from "../classes/process"; -import { LightningIcon } from "../components/icons"; -import processManager from "./process-manager"; import BatchRelationLoader from "../classes/batch-relation-loader"; import { logger } from "../helpers/debug"; import { getCacheRelay } from "./cache-relay"; class EventZapsService { log = logger.extend("EventZapsService"); - process: Process; private loaded = new Map(); loaders = new SuperMap((relay) => { const loader = new BatchRelationLoader(relay, [kinds.Zap], this.log.extend(relay.url)); - this.process.addChild(loader.process); return loader; }); - constructor() { - this.process = new Process("EventZapsService", this); - this.process.icon = LightningIcon; - this.process.active = true; - - processManager.registerProcess(this.process); - } - requestZaps(uid: string, urls: Iterable, alwaysRequest = true) { if (this.loaded.get(uid) && !alwaysRequest) return; diff --git a/src/services/notifications.ts b/src/services/notifications.ts index 0b0fa735b..9a9096668 100644 --- a/src/services/notifications.ts +++ b/src/services/notifications.ts @@ -1,13 +1,114 @@ -import { getEventPointerFromETag, getEventPointerFromQTag, processTags } from "applesauce-core/helpers"; -import { combineLatest, mergeMap, tap } from "rxjs"; -import { TimelineQuery } from "applesauce-core/queries"; -import { kinds, NostrEvent } from "nostr-tools"; +import { + COMMENT_KIND, + getEventPointerFromETag, + getEventPointerFromQTag, + getZapPayment, + Mutes, + processTags, +} from "applesauce-core/helpers"; +import { combineLatest, map, mergeMap, Observable, share, tap } from "rxjs"; +import { TimelineQuery, UserMuteQuery } from "applesauce-core/queries"; +import { kinds, nip18, nip25, NostrEvent } from "nostr-tools"; import localSettings from "./local-settings"; import singleEventLoader from "./single-event-loader"; -import { queryStore } from "./event-store"; +import { eventStore, queryStore } from "./event-store"; import { TORRENT_COMMENT_KIND } from "../helpers/nostr/torrents"; import accounts from "./accounts"; +import { getThreadReferences, isReply, isRepost } from "../helpers/nostr/event"; +import { getPubkeysMentionedInContent } from "../helpers/nostr/post"; +import { getContentPointers } from "applesauce-factory/helpers"; + +export const NotificationTypeSymbol = Symbol("notificationType"); + +export enum NotificationType { + Reply = "reply", + Repost = "repost", + Zap = "zap", + Reaction = "reaction", + Mention = "mention", + Message = "message", + Quote = "quote", +} +export type CategorizedEvent = NostrEvent & { [NotificationTypeSymbol]?: NotificationType }; + +function categorizeEvent(event: NostrEvent, pubkey?: string): CategorizedEvent { + const e = event as CategorizedEvent; + + if (e[NotificationTypeSymbol]) return e; + + if (event.kind === kinds.Zap) { + e[NotificationTypeSymbol] = NotificationType.Zap; + } else if (event.kind === kinds.Reaction) { + e[NotificationTypeSymbol] = NotificationType.Reaction; + } else if (isRepost(event)) { + e[NotificationTypeSymbol] = NotificationType.Repost; + } else if (event.kind === kinds.EncryptedDirectMessage) { + e[NotificationTypeSymbol] = NotificationType.Message; + } else if ( + event.kind === kinds.ShortTextNote || + event.kind === TORRENT_COMMENT_KIND || + event.kind === kinds.LiveChatMessage || + event.kind === kinds.LongFormArticle + ) { + // is the pubkey mentioned in any way in the content + const isMentioned = pubkey ? getPubkeysMentionedInContent(event.content, true).includes(pubkey) : false; + const isQuote = + event.tags.some((t) => t[0] === "q" && (t[1] === event.id || t[3] === pubkey)) || + getContentPointers(event.content).some( + (p) => (p.type === "nevent" && p.data.id === event.id) || (p.type === "note" && p.data === event.id), + ); + + if (isMentioned) e[NotificationTypeSymbol] = NotificationType.Mention; + else if (isQuote) e[NotificationTypeSymbol] = NotificationType.Quote; + else if (isReply(event)) e[NotificationTypeSymbol] = NotificationType.Reply; + } + return e; +} + +function filterEvents(events: CategorizedEvent[], pubkey: string, mute?: Mutes): CategorizedEvent[] { + return events.filter((event) => { + // ignore if muted + if (mute?.pubkeys.has(event.pubkey)) return false; + + // ignore if own + if (event.pubkey === pubkey) return false; + + const e = event as CategorizedEvent; + + switch (e[NotificationTypeSymbol]) { + case NotificationType.Reply: + const refs = getThreadReferences(e); + if (!refs.reply?.e?.id) return false; + if (refs.reply?.e?.author && refs.reply?.e?.author !== pubkey) return false; + const parent = eventStore.getEvent(refs.reply.e.id); + if (!parent || parent.pubkey !== pubkey) return false; + break; + case NotificationType.Mention: + break; + case NotificationType.Repost: { + const pointer = nip18.getRepostedEventPointer(e); + if (pointer?.author !== pubkey) return false; + break; + } + case NotificationType.Reaction: { + const pointer = nip25.getReactedEventPointer(e); + if (!pointer) return false; + if (pointer.author !== pubkey) return false; + if (pointer.kind === kinds.EncryptedDirectMessage) return false; + const parent = eventStore.getEvent(pointer.id); + if (parent && parent.kind === kinds.EncryptedDirectMessage) return false; + break; + } + case NotificationType.Zap: + const p = getZapPayment(e); + if (!p || p.amount === 0) return false; + break; + } + + return true; + }); +} async function handleTextNote(event: NostrEvent) { // request quotes @@ -43,10 +144,12 @@ async function handleShare(event: NostrEvent) { } } -const notifications = combineLatest([accounts.active$]).pipe( +const notifications$: Observable = combineLatest([accounts.active$]).pipe( mergeMap(([account]) => { - if (account) - return queryStore.createQuery(TimelineQuery, { + if (!account) return []; + + const timeline$ = queryStore + .createQuery(TimelineQuery, { "#p": [account.pubkey], kinds: [ kinds.ShortTextNote, @@ -57,23 +160,34 @@ const notifications = combineLatest([accounts.active$]).pipe( TORRENT_COMMENT_KIND, kinds.LongFormArticle, kinds.EncryptedDirectMessage, - 1111, //NIP-22 + COMMENT_KIND, ], - }); - else return []; - }), - tap((timeline) => { - // handle loading dependencies of each event - for (const event of timeline) { - switch (event.kind) { - case kinds.ShortTextNote: - handleTextNote(event); - break; - case kinds.Report: - case kinds.GenericRepost: - handleShare(event); - break; - } - } + }) + .pipe( + tap((timeline) => { + // handle loading dependencies of each event + for (const event of timeline) { + switch (event.kind) { + case kinds.ShortTextNote: + handleTextNote(event); + break; + case kinds.Report: + case kinds.GenericRepost: + handleShare(event); + break; + } + } + }), + map((timeline) => timeline.map((e) => categorizeEvent(e, account.pubkey))), + ); + + const mute$ = queryStore.createQuery(UserMuteQuery, account.pubkey); + + return combineLatest([timeline$, mute$]).pipe( + map(([timeline, mutes]) => filterEvents(timeline, account.pubkey, mutes)), + ); }), + share(), ); + +export default notifications$; diff --git a/src/services/process-manager.ts b/src/services/process-manager.ts deleted file mode 100644 index cf0e93998..000000000 --- a/src/services/process-manager.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { AbstractRelay } from "nostr-tools/abstract-relay"; -import Process from "../classes/process"; -import relayPoolService from "./relay-pool"; - -class ProcessManager { - processes = new Set(); - - registerProcess(process: Process) { - this.processes.add(process); - } - unregisterProcess(process: Process) { - this.processes.delete(process); - for (const child of process.children) { - this.unregisterProcess(child); - } - } - - getRootProcesses() { - return Array.from(this.processes).filter((process) => !process.parent); - } - getProcessRoot(process: Process): Process { - if (process.parent) return this.getProcessRoot(process.parent); - else return process; - } - getRootProcessesForRelay(relayOrUrl: string | URL | AbstractRelay) { - const relay = relayPoolService.getRelay(relayOrUrl); - if (!relay) return new Set(); - - const rootProcesses = new Set(); - for (const process of this.processes) { - if (process.relays.has(relay)) { - rootProcesses.add(this.getProcessRoot(process)); - } - } - - return rootProcesses; - } -} - -const processManager = new ProcessManager(); - -if (import.meta.env.DEV) { - // @ts-expect-error debug - window.processManager = processManager; -} - -export default processManager; diff --git a/src/services/replaceable-loader.ts b/src/services/replaceable-loader.ts index 21f4d742b..a26928e9d 100644 --- a/src/services/replaceable-loader.ts +++ b/src/services/replaceable-loader.ts @@ -1,12 +1,10 @@ -import { Filter, NostrEvent } from "nostr-tools"; import { ReplaceableLoader } from "applesauce-loaders/loaders"; -import { Observable } from "rxjs"; import { truncateId } from "../helpers/string"; import { eventStore } from "./event-store"; import rxNostr from "./rx-nostr"; import { COMMON_CONTACT_RELAYS } from "../const"; -import { getCacheRelay } from "./cache-relay"; +import { cacheRequest } from "./cache-relay"; export type RequestOptions = { /** Always request the event from the relays */ @@ -19,23 +17,6 @@ export function getHumanReadableCoordinate(kind: number, pubkey: string, d?: str return `${kind}:${truncateId(pubkey)}${d ? ":" + d : ""}`; } -// load events from cache relay -export function cacheRequest(filters: Filter[]) { - return new Observable((observer) => { - const relay = getCacheRelay(); - if (!relay) return observer.complete(); - - const sub = relay.subscribe(filters, { - onevent: (event) => observer.next(event), - oneose: () => { - sub.close(); - observer.complete(); - }, - onclose: () => observer.complete(), - }); - }); -} - const replaceableEventLoader = new ReplaceableLoader(rxNostr, { cacheRequest, lookupRelays: COMMON_CONTACT_RELAYS }); replaceableEventLoader.subscribe((packet) => eventStore.add(packet.event, packet.from)); diff --git a/src/services/rx-nostr.ts b/src/services/rx-nostr.ts index dcbfd3ae2..7919c7f63 100644 --- a/src/services/rx-nostr.ts +++ b/src/services/rx-nostr.ts @@ -3,15 +3,12 @@ import { BehaviorSubject, combineLatest } from "rxjs"; import { unixNow } from "applesauce-core/helpers"; import { nanoid } from "nanoid"; -import { logger } from "../helpers/debug"; import verifyEvent from "./verify-event"; import authenticationSigner from "./authentication-signer"; import localSettings from "./local-settings"; import { unique } from "../helpers/array"; -const log = logger.extend("rx-nostr"); - const rxNostr = createRxNostr({ verifier: async (event) => { try { @@ -40,7 +37,6 @@ rxNostr.createConnectionStateObservable().subscribe((packet) => { const url = new URL(packet.from).toString(); connections$.next({ ...connections$.value, [url]: packet.state }); - if (import.meta.env.DEV) log(packet.state, url); }); // capture all notices sent from relays diff --git a/src/services/single-event-loader.ts b/src/services/single-event-loader.ts index bc7e8bece..e67d7e52f 100644 --- a/src/services/single-event-loader.ts +++ b/src/services/single-event-loader.ts @@ -3,7 +3,7 @@ import { SingleEventLoader } from "applesauce-loaders"; import { eventStore } from "./event-store"; import rxNostr from "./rx-nostr"; -import { cacheRequest } from "./replaceable-loader"; +import { cacheRequest } from "./cache-relay"; const singleEventLoader = new SingleEventLoader(rxNostr, { cacheRequest }); diff --git a/src/services/timeline-cache.ts b/src/services/timeline-cache.ts index 704865566..8afbf46f2 100644 --- a/src/services/timeline-cache.ts +++ b/src/services/timeline-cache.ts @@ -3,6 +3,7 @@ import { TimelessFilter, TimelineLoader } from "applesauce-loaders"; import rxNostr from "./rx-nostr"; import { logger } from "../helpers/debug"; +import { cacheRequest } from "./cache-relay"; const MAX_CACHE = 30; const BATCH_LIMIT = 100; @@ -16,7 +17,10 @@ class TimelineCacheService { if (!timeline && relays.length > 0 && filters.length > 0) { this.log(`Creating ${key}`); - timeline = new TimelineLoader(rxNostr, TimelineLoader.simpleFilterMap(relays, filters), { limit: BATCH_LIMIT }); + timeline = new TimelineLoader(rxNostr, TimelineLoader.simpleFilterMap(relays, filters), { + limit: BATCH_LIMIT, + cacheRequest, + }); this.timelines.set(key, timeline); } diff --git a/src/services/user-sets-loader.ts b/src/services/user-sets-loader.ts index 619d2b560..e5e6cda5d 100644 --- a/src/services/user-sets-loader.ts +++ b/src/services/user-sets-loader.ts @@ -3,7 +3,7 @@ import { UserSetsLoader } from "applesauce-loaders"; import { eventStore } from "./event-store"; import rxNostr from "./rx-nostr"; -import { cacheRequest } from "./replaceable-loader"; +import { cacheRequest } from "./cache-relay"; const userSetsLoader = new UserSetsLoader(rxNostr, { cacheRequest }); diff --git a/src/views/launchpad/components/dms-card.tsx b/src/views/launchpad/components/messages-card.tsx similarity index 96% rename from src/views/launchpad/components/dms-card.tsx rename to src/views/launchpad/components/messages-card.tsx index c2aaa5192..15e1b2843 100644 --- a/src/views/launchpad/components/dms-card.tsx +++ b/src/views/launchpad/components/messages-card.tsx @@ -90,7 +90,7 @@ export default function DMsCard({ ...props }: Omit) { - + Messages @@ -103,7 +103,7 @@ export default function DMsCard({ ...props }: Omit) { {conversations.slice(0, 4).map((conversation) => ( ))} - diff --git a/src/views/launchpad/components/notifications-card.tsx b/src/views/launchpad/components/notifications-card.tsx index 39497c0ce..572eb7bc5 100644 --- a/src/views/launchpad/components/notifications-card.tsx +++ b/src/views/launchpad/components/notifications-card.tsx @@ -1,22 +1,44 @@ import { useCallback } from "react"; import { Button, Card, CardBody, CardHeader, CardProps, Heading, Link } from "@chakra-ui/react"; import { Link as RouterLink, useNavigate } from "react-router-dom"; -import { useObservable } from "applesauce-react/hooks"; -import { NostrEvent } from "nostr-tools"; -import { getEventUID } from "nostr-idb"; +import { useActiveAccount, useObservable } from "applesauce-react/hooks"; +import { getEventUID } from "applesauce-core/helpers"; +import { kinds, NostrEvent } from "nostr-tools"; import KeyboardShortcut from "../../../components/keyboard-shortcut"; -import { useNotifications } from "../../../providers/global/notifications-provider"; -import { NotificationType, NotificationTypeSymbol } from "../../../classes/notifications"; import NotificationItem from "../../notifications/components/notification-item"; import { ErrorBoundary } from "../../../components/error-boundary"; +import notifications$, { NotificationType, NotificationTypeSymbol } from "../../../services/notifications"; +import useForwardSubscription from "../../../hooks/use-forward-subscription"; +import useUserMailboxes from "../../../hooks/use-user-mailboxes"; +import { useReadRelays } from "../../../hooks/use-client-relays"; export default function NotificationsCard({ ...props }: Omit) { const navigate = useNavigate(); - const { notifications } = useNotifications(); + + const account = useActiveAccount(); + const mailboxes = useUserMailboxes(account?.pubkey); + const readRelays = useReadRelays(mailboxes?.inboxes); + useForwardSubscription( + readRelays, + account + ? { + "#p": [account.pubkey], + kinds: [ + kinds.ShortTextNote, + kinds.Repost, + kinds.GenericRepost, + kinds.Reaction, + kinds.Zap, + kinds.LongFormArticle, + kinds.EncryptedDirectMessage, + ], + } + : undefined, + ); const events = - useObservable(notifications?.timeline)?.filter( + useObservable(notifications$)?.filter( (event) => event[NotificationTypeSymbol] === NotificationType.Mention || event[NotificationTypeSymbol] === NotificationType.Reply || diff --git a/src/views/launchpad/index.tsx b/src/views/launchpad/index.tsx index 97910a268..47d2f481f 100644 --- a/src/views/launchpad/index.tsx +++ b/src/views/launchpad/index.tsx @@ -5,7 +5,7 @@ import RequireActiveAccount from "../../components/router/require-active-account import { ErrorBoundary } from "../../components/error-boundary"; import FeedsCard from "./components/feeds-card"; import SearchForm from "./components/search-form"; -import DMsCard from "./components/dms-card"; +import DMsCard from "./components/messages-card"; import NotificationsCard from "./components/notifications-card"; import ToolsCard from "./components/tools-card"; import StreamsCard from "./components/streams-card"; diff --git a/src/views/notifications/components/notification-item.tsx b/src/views/notifications/components/notification-item.tsx index 1ed211b1c..3f7438f92 100644 --- a/src/views/notifications/components/notification-item.tsx +++ b/src/views/notifications/components/notification-item.tsx @@ -5,7 +5,6 @@ import EmbeddedUnknown from "../../../components/embed-event/event-types/embedde import { ErrorBoundary } from "../../../components/error-boundary"; import { TrustProvider } from "../../../providers/local/trust-provider"; import { ChevronDownIcon, ChevronUpIcon } from "../../../components/icons"; -import { CategorizedEvent, NotificationType, NotificationTypeSymbol } from "../../../classes/notifications"; import useEventIntersectionRef from "../../../hooks/use-event-intersection-ref"; import { NostrEvent } from "nostr-tools"; import ReplyNotification from "./reply-notification"; @@ -16,6 +15,7 @@ import ZapNotification from "./zap-notificaiton"; import UnknownNotification from "./unknown-notification"; import MessageNotification from "./message-notification"; import QuoteNotification from "./quote-notification"; +import { CategorizedEvent, NotificationType, NotificationTypeSymbol } from "../../../services/notifications"; export const ExpandableToggleButton = ({ toggle, diff --git a/src/views/notifications/index.tsx b/src/views/notifications/index.tsx index 199329a8b..0bf952c88 100644 --- a/src/views/notifications/index.tsx +++ b/src/views/notifications/index.tsx @@ -1,22 +1,21 @@ import { memo, ReactNode, useCallback, useMemo } from "react"; -import { Button, ButtonGroup, Divider, Flex, Text } from "@chakra-ui/react"; -import { Link as RouterLink } from "react-router-dom"; +import { Button, Divider, Flex, Text } from "@chakra-ui/react"; import dayjs, { Dayjs } from "dayjs"; import { getEventUID } from "nostr-idb"; import { BehaviorSubject } from "rxjs"; -import { useObservable } from "applesauce-react/hooks"; +import { useActiveAccount, useObservable } from "applesauce-react/hooks"; +import { COMMENT_KIND } from "applesauce-core/helpers"; +import { kinds } from "nostr-tools"; import RequireActiveAccount from "../../components/router/require-active-account"; import IntersectionObserverProvider from "../../providers/local/intersection-observer"; import { useTimelineCurserIntersectionCallback } from "../../hooks/use-timeline-cursor-intersection-callback"; -import { useNotifications } from "../../providers/global/notifications-provider"; import PeopleListProvider, { usePeopleListContext } from "../../providers/local/people-list-provider"; import PeopleListSelection from "../../components/people-list-selection/people-list-selection"; import VerticalPageLayout from "../../components/vertical-page-layout"; import NotificationItem from "./components/notification-item"; import NotificationTypeToggles from "./notification-type-toggles"; import useLocalStorageDisclosure from "../../hooks/use-localstorage-disclosure"; -import { CategorizedEvent, NotificationType, NotificationTypeSymbol } from "../../classes/notifications"; import TimelineActionAndStatus from "../../components/timeline/timeline-action-and-status"; import FocusedContext from "./focused-context"; import readStatusService from "../../services/read-status"; @@ -25,6 +24,15 @@ import useNumberCache from "../../hooks/timeline/use-number-cache"; import { useTimelineDates } from "../../hooks/timeline/use-timeline-dates"; import useCacheEntryHeight from "../../hooks/timeline/use-cache-entry-height"; import useVimNavigation from "./use-vim-navigation"; +import useTimelineLoader from "../../hooks/use-timeline-loader"; +import { truncateId } from "../../helpers/string"; +import { useReadRelays } from "../../hooks/use-client-relays"; +import useUserMailboxes from "../../hooks/use-user-mailboxes"; +import notifications$, { + CategorizedEvent, + NotificationType, + NotificationTypeSymbol, +} from "../../services/notifications"; function TimeMarker({ date, ids }: { date: Dayjs; ids: string[] }) { const readAll = useCallback(() => { @@ -59,23 +67,22 @@ const NotificationsTimeline = memo( showReactions: boolean; showUnknown: boolean; }) => { - const { notifications } = useNotifications(); const { people } = usePeopleListContext(); const peoplePubkeys = useMemo(() => people?.map((p) => p.pubkey), [people]); - const events = useObservable(notifications?.timeline) ?? []; + const timeline = useObservable(notifications$) ?? []; const cacheKey = useTimelineLocationCacheKey(); const numberCache = useNumberCache(cacheKey); const minItems = Math.round(window.innerHeight / 48) * 2; - const dates = useTimelineDates(events, numberCache, minItems / 2, minItems); + const dates = useTimelineDates(timeline, numberCache, minItems / 2, minItems); // measure and cache the hight of every entry useCacheEntryHeight(numberCache.set); const filtered: CategorizedEvent[] = []; - for (const event of events) { + for (const event of timeline) { if (event.created_at < dates.cursor && filtered.length > minItems) continue; const type = event[NotificationTypeSymbol]; @@ -153,7 +160,28 @@ const NotificationsTimeline = memo( const cachedFocus = new BehaviorSubject(""); function NotificationsPage() { - const { timeline } = useNotifications(); + const account = useActiveAccount(); + + const mailboxes = useUserMailboxes(account?.pubkey); + const readRelays = useReadRelays(mailboxes?.inboxes); + const { loader } = useTimelineLoader( + `${truncateId(account?.pubkey ?? "anon")}-notification`, + readRelays, + account?.pubkey + ? { + "#p": [account.pubkey], + kinds: [ + kinds.ShortTextNote, + kinds.Repost, + kinds.GenericRepost, + kinds.Reaction, + kinds.Zap, + kinds.LongFormArticle, + COMMENT_KIND, + ], + } + : undefined, + ); // const { value: focused, setValue: setFocused } = useRouteStateValue("focused", ""); // const [focused, setFocused] = useState(""); @@ -168,7 +196,7 @@ function NotificationsPage() { const showReactions = useLocalStorageDisclosure("notifications-show-reactions", false); const showUnknown = useLocalStorageDisclosure("notifications-show-unknown", false); - const callback = useTimelineCurserIntersectionCallback(timeline); + const callback = useTimelineCurserIntersectionCallback(loader); return ( @@ -201,7 +229,7 @@ function NotificationsPage() { - + ); } diff --git a/src/views/relays/webrtc/connect.tsx b/src/views/relays/webrtc/connect.tsx index 741ef34b1..55ceba978 100644 --- a/src/views/relays/webrtc/connect.tsx +++ b/src/views/relays/webrtc/connect.tsx @@ -3,7 +3,6 @@ import { Alert, AlertIcon, Button, CloseButton, Flex, Heading, Input, Text, useI import { useForm } from "react-hook-form"; import { useObservable } from "applesauce-react/hooks"; -import BackButton from "../../../components/router/back-button"; import webRtcRelaysService from "../../../services/webrtc-relays"; import NostrWebRtcBroker from "../../../classes/webrtc/nostr-webrtc-broker"; import QRCodeScannerButton from "../../../components/qr-code/qr-code-scanner-button"; diff --git a/src/views/task-manager/processes/index.tsx b/src/views/task-manager/processes/index.tsx deleted file mode 100644 index 90be958f1..000000000 --- a/src/views/task-manager/processes/index.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { Flex, useInterval } from "@chakra-ui/react"; - -import ProcessBranch from "./process/process-tree"; -import processManager from "../../../services/process-manager"; -import useForceUpdate from "../../../hooks/use-force-update"; - -export default function TaskManagerProcesses() { - const update = useForceUpdate(); - useInterval(update, 500); - - const rootProcesses = processManager.getRootProcesses(); - - return ( - - {rootProcesses.map((process) => ( - - ))} - - ); -} diff --git a/src/views/task-manager/processes/process/process-icon.tsx b/src/views/task-manager/processes/process/process-icon.tsx deleted file mode 100644 index 37ecc5afe..000000000 --- a/src/views/task-manager/processes/process/process-icon.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { QuestionIcon } from "@chakra-ui/icons"; -import { ComponentWithAs, IconProps } from "@chakra-ui/react"; - -import Process from "../../../../classes/process"; - -export default function ProcessIcon({ process, ...props }: { process: Process } & IconProps) { - const IconComponent: ComponentWithAs<"svg", IconProps> = process.icon || QuestionIcon; - - return ; -} diff --git a/src/views/task-manager/processes/process/process-tree.tsx b/src/views/task-manager/processes/process/process-tree.tsx deleted file mode 100644 index d34d0936f..000000000 --- a/src/views/task-manager/processes/process/process-tree.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { Flex, Text, useDisclosure } from "@chakra-ui/react"; - -import ProcessIcon from "./process-icon"; -import Process from "../../../../classes/process"; -import ExpandButton from "../../../tools/event-console/expand-button"; - -export default function ProcessBranch({ - process, - level = 0, - filter, -}: { - process: Process; - level?: number; - filter?: (process: Process) => boolean; -}) { - const showChildren = useDisclosure({ defaultIsOpen: !!process.parent }); - - return ( - <> - - - - {process.type} - - - {process.id} - - {process.children.size > 0 && ( - - )} - - {process.name} - {process.relays.size > 1 - ? ` ${process.relays.size} relays` - : Array.from(process.relays) - .map((r) => r.url) - .join(", ")} - - - {showChildren.isOpen && - Array.from(process.children) - .filter((p) => (filter ? filter(p) : true)) - .map((child) => )} - - ); -} diff --git a/src/views/task-manager/relays/inspect-relay.tsx b/src/views/task-manager/relays/inspect-relay.tsx index 53e1c899b..c07575c50 100644 --- a/src/views/task-manager/relays/inspect-relay.tsx +++ b/src/views/task-manager/relays/inspect-relay.tsx @@ -4,7 +4,6 @@ import { Navigate, useParams } from "react-router-dom"; import VerticalPageLayout from "../../../components/vertical-page-layout"; import BackButton from "../../../components/router/back-button"; -import processManager from "../../../services/process-manager"; import { RelayAuthIconButton } from "../../../components/relays/relay-auth-icon-button"; import RelayStatusBadge from "../../../components/relays/relay-status"; import useRelayNotices from "../../../hooks/use-relay-notices"; @@ -14,7 +13,6 @@ export default function InspectRelayView() { const { relay } = useParams(); if (!relay) return ; - const rootProcesses = processManager.getRootProcessesForRelay(relay); const notices = useRelayNotices(relay); return ( diff --git a/src/views/task-manager/relays/tabs/connections.tsx b/src/views/task-manager/relays/tabs/connections.tsx index 6f4f8b1f7..3dd6fdb37 100644 --- a/src/views/task-manager/relays/tabs/connections.tsx +++ b/src/views/task-manager/relays/tabs/connections.tsx @@ -29,7 +29,7 @@ export default function RelayConnectionsTab() { {Object.entries(connections) - .sort((a, b) => getConnectionStateSort(a[1]) - getConnectionStateSort(b[1])) + .sort((a, b) => getConnectionStateSort(a[1]) - getConnectionStateSort(b[1]) || a[0].localeCompare(b[0])) .map(([relay]) => ( ))}