diff --git a/.changeset/brown-bulldogs-play.md b/.changeset/brown-bulldogs-play.md new file mode 100644 index 000000000..cda82994b --- /dev/null +++ b/.changeset/brown-bulldogs-play.md @@ -0,0 +1,5 @@ +--- +"nostrudel": patch +--- + +Add logging to app setting services diff --git a/.changeset/soft-mails-fetch.md b/.changeset/soft-mails-fetch.md new file mode 100644 index 000000000..947a62711 --- /dev/null +++ b/.changeset/soft-mails-fetch.md @@ -0,0 +1,5 @@ +--- +"nostrudel": patch +--- + +Fix color theme picker diff --git a/package.json b/package.json index 5d59c1a63..c224e6798 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "bech32": "^2.0.0", "cheerio": "^1.0.0-rc.12", "dayjs": "^1.11.9", + "debug": "^4.3.4", "framer-motion": "^7.10.3", "hls.js": "^1.4.7", "idb": "^7.1.1", @@ -40,6 +41,7 @@ "devDependencies": { "@changesets/cli": "^2.26.2", "@testing-library/cypress": "^9.0.0", + "@types/debug": "^4.1.8", "@types/identicon.js": "^2.3.1", "@types/react": "^18.2.14", "@types/react-dom": "^18.2.6", diff --git a/src/classes/pubkey-event-requester.ts b/src/classes/pubkey-event-requester.ts index bc1df9489..6bc7f2f00 100644 --- a/src/classes/pubkey-event-requester.ts +++ b/src/classes/pubkey-event-requester.ts @@ -1,9 +1,11 @@ import dayjs from "dayjs"; +import debug, { Debugger } from "debug"; import { NostrSubscription } from "./nostr-subscription"; import { SuperMap } from "./super-map"; import { NostrEvent } from "../types/nostr-event"; import Subject from "./subject"; import { NostrQuery } from "../types/nostr-query"; +import { nameOrPubkey } from "../helpers/debug"; type pubkey = string; type relay = string; @@ -19,13 +21,17 @@ class PubkeyEventRequestSubscription { private requestedPubkeys = new Map(); - constructor(relay: string, kind: number, name?: string, dTag?: string) { + log: Debugger; + + constructor(relay: string, kind: number, name?: string, dTag?: string, log?: Debugger) { this.kind = kind; this.dTag = dTag; this.subscription = new NostrSubscription(relay, undefined, name); this.subscription.onEvent.subscribe(this.handleEvent.bind(this)); this.subscription.onEOSE.subscribe(this.handleEOSE.bind(this)); + + this.log = log || debug("misc"); } private handleEvent(event: NostrEvent) { @@ -41,6 +47,7 @@ class PubkeyEventRequestSubscription { const current = sub.value; if (!current || event.created_at > current.created_at) { + this.log(`Found newer event for ${nameOrPubkey(event.pubkey)}`); sub.next(event); } } @@ -57,6 +64,7 @@ class PubkeyEventRequestSubscription { const sub = this.subjects.get(pubkey); if (!sub.value) { + this.log(`Adding ${nameOrPubkey(pubkey)} to queue`); this.requestNext.add(pubkey); } @@ -79,6 +87,7 @@ class PubkeyEventRequestSubscription { if (dayjs(date).isBefore(timeout)) { this.requestedPubkeys.delete(pubkey); needsUpdate = true; + this.log(`Request for ${nameOrPubkey(pubkey)} expired`); } } @@ -87,7 +96,10 @@ class PubkeyEventRequestSubscription { if (this.requestedPubkeys.size > 0) { const query: NostrQuery = { authors: Array.from(this.requestedPubkeys.keys()), kinds: [this.kind] }; if (this.dTag) query["#d"] = [this.dTag]; + + this.log(`Updating query with ${query.authors?.length} pubkeys`); this.subscription.setQuery(query); + if (this.subscription.state !== NostrSubscription.OPEN) { this.subscription.open(); } @@ -105,13 +117,17 @@ export class PubkeyEventRequester { private subjects = new SuperMap>(() => new Subject()); private subscriptions = new SuperMap( - (relay) => new PubkeyEventRequestSubscription(relay, this.kind, this.name, this.dTag) + (relay) => new PubkeyEventRequestSubscription(relay, this.kind, this.name, this.dTag, this.log.extend(relay)) ); - constructor(kind: number, name?: string, dTag?: string) { + log: Debugger; + + constructor(kind: number, name?: string, dTag?: string, log?: Debugger) { this.kind = kind; this.name = name; this.dTag = dTag; + + this.log = log || debug("misc"); } getSubject(pubkey: string) { @@ -120,25 +136,29 @@ export class PubkeyEventRequester { handleEvent(event: NostrEvent) { if (event.kind !== this.kind) return; - const sub = this.subjects.get(event.pubkey); + const sub = this.subjects.get(event.pubkey); const current = sub.value; if (!current || event.created_at > current.created_at) { + this.log(`New event for ${nameOrPubkey(event.pubkey)}`); sub.next(event); } } - private connected = new WeakSet(); requestEvent(pubkey: string, relays: string[]) { + this.log(`Requesting event for ${nameOrPubkey(pubkey)}`); const sub = this.subjects.get(pubkey); for (const relay of relays) { const relaySub = this.subscriptions.get(relay).requestEvent(pubkey); - if (!this.connected.has(relaySub)) { - relaySub.subscribe((event) => event && this.handleEvent(event)); - this.connected.add(relaySub); - } + sub.connectWithHandler(relaySub, (event, next, current) => { + if (event.kind !== this.kind) return; + if (!current || event.created_at > current.created_at) { + this.log(`Event for ${nameOrPubkey(event.pubkey)} from connection`); + next(event); + } + }); } return sub; diff --git a/src/classes/pubkey-subject-cache.ts b/src/classes/pubkey-subject-cache.ts index 71d0579ab..9af28c8e4 100644 --- a/src/classes/pubkey-subject-cache.ts +++ b/src/classes/pubkey-subject-cache.ts @@ -1,5 +1,6 @@ import Subject from "./subject"; +/** @deprecated */ export class PubkeySubjectCache { subjects = new Map>(); relays = new Map>(); diff --git a/src/classes/subject.ts b/src/classes/subject.ts index 65b1e5774..064dd8d4d 100644 --- a/src/classes/subject.ts +++ b/src/classes/subject.ts @@ -19,6 +19,8 @@ export class Subject implements Connectable { } next(value: Value) { + if (this.value === value) return; + this.value = value; for (const [listener, ctx] of this.listeners) { if (ctx) listener.call(ctx, value); diff --git a/src/components/embed-types/common.tsx b/src/components/embed-types/common.tsx index acd0b98fe..8b5c008c7 100644 --- a/src/components/embed-types/common.tsx +++ b/src/components/embed-types/common.tsx @@ -1,5 +1,5 @@ import { Box, Image, ImageProps, Link, useDisclosure } from "@chakra-ui/react"; -import appSettings from "../../services/app-settings"; +import appSettings from "../../services/settings/app-settings"; import { ImageGalleryLink } from "../image-gallery"; import { useIsMobile } from "../../hooks/use-is-mobile"; import { useTrusted } from "../../providers/trust"; diff --git a/src/components/embed-types/reddit.tsx b/src/components/embed-types/reddit.tsx index 96190a019..1085623a2 100644 --- a/src/components/embed-types/reddit.tsx +++ b/src/components/embed-types/reddit.tsx @@ -1,5 +1,5 @@ import { replaceDomain } from "../../helpers/url"; -import appSettings from "../../services/app-settings"; +import appSettings from "../../services/settings/app-settings"; import { renderGenericUrl } from "./common"; // copied from https://github.com/SimonBrazell/privacy-redirect/blob/master/src/assets/javascripts/helpers/reddit.js diff --git a/src/components/embed-types/twitter.tsx b/src/components/embed-types/twitter.tsx index b474e06f5..d75a4f9e3 100644 --- a/src/components/embed-types/twitter.tsx +++ b/src/components/embed-types/twitter.tsx @@ -1,5 +1,5 @@ import { replaceDomain } from "../../helpers/url"; -import appSettings from "../../services/app-settings"; +import appSettings from "../../services/settings/app-settings"; import { renderOpenGraphUrl } from "./common"; // copied from https://github.com/SimonBrazell/privacy-redirect/blob/master/src/assets/javascripts/helpers/twitter.js diff --git a/src/components/embed-types/youtube.tsx b/src/components/embed-types/youtube.tsx index da82b3479..0316110c3 100644 --- a/src/components/embed-types/youtube.tsx +++ b/src/components/embed-types/youtube.tsx @@ -1,5 +1,5 @@ import { AspectRatio, list } from "@chakra-ui/react"; -import appSettings from "../../services/app-settings"; +import appSettings from "../../services/settings/app-settings"; import { renderOpenGraphUrl } from "./common"; import { replaceDomain } from "../../helpers/url"; diff --git a/src/components/note/embedded-note.tsx b/src/components/note/embedded-note.tsx index a00f0db68..104168582 100644 --- a/src/components/note/embedded-note.tsx +++ b/src/components/note/embedded-note.tsx @@ -7,7 +7,7 @@ import { UserAvatarLink } from "../user-avatar-link"; import { UserLink } from "../user-link"; import { UserDnsIdentityIcon } from "../user-dns-identity-icon"; import useSubject from "../../hooks/use-subject"; -import appSettings from "../../services/app-settings"; +import appSettings from "../../services/settings/app-settings"; import EventVerificationIcon from "../event-verification-icon"; import { TrustProvider } from "../../providers/trust"; import { NoteLink } from "../note-link"; diff --git a/src/components/note/index.tsx b/src/components/note/index.tsx index 8d0e74180..6e77d0eba 100644 --- a/src/components/note/index.tsx +++ b/src/components/note/index.tsx @@ -25,7 +25,7 @@ import ReactionButton from "./buttons/reaction-button"; import NoteZapButton from "./note-zap-button"; import { ExpandProvider } from "./expanded"; import useSubject from "../../hooks/use-subject"; -import appSettings from "../../services/app-settings"; +import appSettings from "../../services/settings/app-settings"; import EventVerificationIcon from "../event-verification-icon"; import { ReplyButton } from "./buttons/reply-button"; import { RepostButton } from "./buttons/repost-button"; diff --git a/src/components/user-avatar.tsx b/src/components/user-avatar.tsx index 0a49a2e95..e443cf7a0 100644 --- a/src/components/user-avatar.tsx +++ b/src/components/user-avatar.tsx @@ -4,7 +4,7 @@ import { useUserMetadata } from "../hooks/use-user-metadata"; import { useAsync } from "react-use"; import { getIdenticon } from "../helpers/identicon"; import { safeUrl } from "../helpers/parse"; -import appSettings from "../services/app-settings"; +import appSettings from "../services/settings/app-settings"; import useSubject from "../hooks/use-subject"; export const UserIdenticon = React.memo(({ pubkey }: { pubkey: string }) => { diff --git a/src/components/zap-modal.tsx b/src/components/zap-modal.tsx index 0b80da729..08eee1283 100644 --- a/src/components/zap-modal.tsx +++ b/src/components/zap-modal.tsx @@ -24,7 +24,7 @@ import { Kind } from "nostr-tools"; import clientRelaysService from "../services/client-relays"; import { getEventRelays } from "../services/event-relays"; import { useSigningContext } from "../providers/signing-provider"; -import appSettings from "../services/app-settings"; +import appSettings from "../services/settings/app-settings"; import useSubject from "../hooks/use-subject"; import useUserLNURLMetadata from "../hooks/use-user-lnurl-metadata"; import { requestZapInvoice } from "../helpers/zaps"; diff --git a/src/helpers/cors.ts b/src/helpers/cors.ts index 7108fd2b9..15488143e 100644 --- a/src/helpers/cors.ts +++ b/src/helpers/cors.ts @@ -1,4 +1,4 @@ -import appSettings from "../services/app-settings"; +import appSettings from "../services/settings/app-settings"; import { convertToUrl } from "./url"; const corsFailedHosts = new Set(); diff --git a/src/helpers/debug.ts b/src/helpers/debug.ts new file mode 100644 index 000000000..ed31ee0ac --- /dev/null +++ b/src/helpers/debug.ts @@ -0,0 +1,11 @@ +import debug from "debug"; +import userMetadataService from "../services/user-metadata"; + +export const logger = debug("noStrudel"); + +debug.enable("noStrudel:*"); + +export function nameOrPubkey(pubkey: string) { + const parsed = userMetadataService.getSubject(pubkey).value; + return parsed?.name || parsed?.display_name || pubkey; +} diff --git a/src/hooks/use-app-settings.ts b/src/hooks/use-app-settings.ts index 1dec16e0b..756a9acd0 100644 --- a/src/hooks/use-app-settings.ts +++ b/src/hooks/use-app-settings.ts @@ -1,8 +1,8 @@ import { useCallback } from "react"; -import appSettings, { replaceSettings } from "../services/app-settings"; +import appSettings, { replaceSettings } from "../services/settings/app-settings"; import useSubject from "./use-subject"; -import { AppSettings } from "../services/user-app-settings"; import { useToast } from "@chakra-ui/react"; +import { AppSettings } from "../services/settings/migrations"; export default function useAppSettings() { const settings = useSubject(appSettings); diff --git a/src/hooks/use-set-color-mode.ts b/src/hooks/use-set-color-mode.ts index 72c0d7bb4..2b0526f1f 100644 --- a/src/hooks/use-set-color-mode.ts +++ b/src/hooks/use-set-color-mode.ts @@ -1,6 +1,6 @@ import { useColorMode } from "@chakra-ui/react"; import useSubject from "./use-subject"; -import appSettings from "../services/app-settings"; +import appSettings from "../services/settings/app-settings"; import { useSearchParams } from "react-router-dom"; import { useEffect } from "react"; diff --git a/src/providers/invoice-modal.tsx b/src/providers/invoice-modal.tsx index 87f9f0783..40ee52133 100644 --- a/src/providers/invoice-modal.tsx +++ b/src/providers/invoice-modal.tsx @@ -1,7 +1,7 @@ import React, { useCallback, useContext, useState } from "react"; import InvoiceModal from "../components/invoice-modal"; import createDefer, { Deferred } from "../classes/deferred"; -import appSettings from "../services/app-settings"; +import appSettings from "../services/settings/app-settings"; export type InvoiceModalContext = { requestPay: (invoice: string) => Promise; diff --git a/src/services/account.ts b/src/services/account.ts index 40a49a2b7..83078322f 100644 --- a/src/services/account.ts +++ b/src/services/account.ts @@ -1,6 +1,6 @@ import { PersistentSubject } from "../classes/subject"; import db from "./db"; -import { AppSettings } from "./user-app-settings"; +import { AppSettings } from "./settings/migrations"; export type Account = { pubkey: string; diff --git a/src/services/app-settings.ts b/src/services/app-settings.ts deleted file mode 100644 index 0c59f62f7..000000000 --- a/src/services/app-settings.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { PersistentSubject } from "../classes/subject"; -import accountService from "./account"; -import userAppSettings, { AppSettings, defaultSettings } from "./user-app-settings"; -import clientRelaysService from "./client-relays"; -import signingService from "./signing"; -import { nostrPostAction } from "../classes/nostr-post-action"; - -export let appSettings = new PersistentSubject(defaultSettings); -export async function replaceSettings(newSettings: AppSettings) { - const account = accountService.current.value; - if (!account) return; - - if (account.readonly) { - accountService.updateAccountLocalSettings(account.pubkey, newSettings); - appSettings.next(newSettings); - } else { - const draft = userAppSettings.buildAppSettingsEvent(newSettings); - const event = await signingService.requestSignature(draft, account); - userAppSettings.receiveEvent(event); - await nostrPostAction(clientRelaysService.getWriteUrls(), event).onComplete; - } -} - -export async function loadSettings() { - const account = accountService.current.value; - if (!account) { - appSettings.next(defaultSettings); - return; - } - - appSettings.disconnectAll(); - - if (account.readonly) { - if (account.localSettings) { - appSettings.next(account.localSettings); - } - } else { - const subject = userAppSettings.requestAppSettings(account.pubkey, clientRelaysService.getReadUrls(), true); - appSettings.connect(subject); - } -} -accountService.current.subscribe(loadSettings); -clientRelaysService.relays.subscribe(loadSettings); - -export default appSettings; diff --git a/src/services/settings/app-settings.ts b/src/services/settings/app-settings.ts new file mode 100644 index 000000000..20e489dd3 --- /dev/null +++ b/src/services/settings/app-settings.ts @@ -0,0 +1,61 @@ +import { PersistentSubject } from "../../classes/subject"; +import accountService from "../account"; +import userAppSettings from "./user-app-settings"; +import clientRelaysService from "../client-relays"; +import signingService from "../signing"; +import { nostrPostAction } from "../../classes/nostr-post-action"; +import { AppSettings, defaultSettings } from "./migrations"; +import { logger } from "../../helpers/debug"; + +const log = logger.extend("AppSettings"); + +export let appSettings = new PersistentSubject(defaultSettings); +appSettings.subscribe((event) => { + log(`Changed`, event); +}); + +export async function replaceSettings(newSettings: AppSettings) { + const account = accountService.current.value; + if (!account) return; + + if (account.readonly) { + accountService.updateAccountLocalSettings(account.pubkey, newSettings); + appSettings.next(newSettings); + } else { + const draft = userAppSettings.buildAppSettingsEvent(newSettings); + const event = await signingService.requestSignature(draft, account); + userAppSettings.receiveEvent(event); + await nostrPostAction(clientRelaysService.getWriteUrls(), event).onComplete; + } +} + +accountService.current.subscribe(() => { + const account = accountService.current.value; + + if (!account) { + appSettings.next(defaultSettings); + return; + } + + appSettings.disconnectAll(); + + if (account.localSettings) { + appSettings.next(account.localSettings); + log("Loaded user settings from local storage"); + } + + const subject = userAppSettings.requestAppSettings(account.pubkey, clientRelaysService.getReadUrls(), true); + appSettings.next(defaultSettings); + appSettings.connect(subject); +}); + +clientRelaysService.relays.subscribe(() => { + // relays changed, look for settings again + const account = accountService.current.value; + + if (account) { + userAppSettings.requestAppSettings(account.pubkey, clientRelaysService.getReadUrls(), true); + } +}); + +export default appSettings; diff --git a/src/services/settings/migrations.ts b/src/services/settings/migrations.ts new file mode 100644 index 000000000..bc9c5d6bb --- /dev/null +++ b/src/services/settings/migrations.ts @@ -0,0 +1,67 @@ +import { ColorModeWithSystem } from "@chakra-ui/react"; +import { NostrEvent } from "../../types/nostr-event"; +import { safeJson } from "../../helpers/parse"; + +export type AppSettingsV0 = { + version: 0; + colorMode: ColorModeWithSystem; + blurImages: boolean; + autoShowMedia: boolean; + proxyUserMedia: boolean; + showReactions: boolean; + showSignatureVerification: boolean; + + autoPayWithWebLN: boolean; + customZapAmounts: string; + + primaryColor: string; + imageProxy: string; + corsProxy: string; + showContentWarning: boolean; + twitterRedirect?: string; + redditRedirect?: string; + youtubeRedirect?: string; +}; +export function isV0(settings: { version: number }): settings is AppSettingsV0 { + return settings.version === undefined || settings.version === 0; +} + +export type AppSettings = AppSettingsV0; + +export const defaultSettings: AppSettings = { + version: 0, + colorMode: "system", + blurImages: true, + autoShowMedia: true, + proxyUserMedia: false, + showReactions: true, + showSignatureVerification: false, + + autoPayWithWebLN: true, + customZapAmounts: "50,200,500,1000,2000,5000", + + primaryColor: "#8DB600", + imageProxy: "", + corsProxy: "", + showContentWarning: true, + twitterRedirect: undefined, + redditRedirect: undefined, + youtubeRedirect: undefined, +}; + +export function upgradeSettings(settings: { version: number }) { + if (isV0(settings)) return settings; + return null; +} + +export function parseAppSettings(event: NostrEvent): AppSettings { + const json = safeJson(event.content, {}); + const upgraded = upgradeSettings(json); + + return upgraded + ? { + ...defaultSettings, + ...upgraded, + } + : defaultSettings; +} diff --git a/src/services/settings/user-app-settings.ts b/src/services/settings/user-app-settings.ts new file mode 100644 index 000000000..3bc22c702 --- /dev/null +++ b/src/services/settings/user-app-settings.ts @@ -0,0 +1,66 @@ +import dayjs from "dayjs"; + +import { DraftNostrEvent, NostrEvent } from "../../types/nostr-event"; +import db from "../db"; +import { logger } from "../../helpers/debug"; + +import { SuperMap } from "../../classes/super-map"; +import { PersistentSubject } from "../../classes/subject"; +import { CachedPubkeyEventRequester } from "../../classes/cached-pubkey-event-requester"; +import { AppSettings, defaultSettings, parseAppSettings } from "./migrations"; + +const DTAG = "nostrudel-settings"; + +class UserAppSettings { + requester: CachedPubkeyEventRequester; + log = logger.extend("UserAppSettings"); + + constructor() { + this.requester = new CachedPubkeyEventRequester(30078, "user-app-data", DTAG, this.log.extend("requester")); + this.requester.readCache = (pubkey) => db.get("settings", pubkey); + this.requester.writeCache = (pubkey, event) => db.put("settings", event); + } + + private parsedSubjects = new SuperMap>( + (pubkey) => new PersistentSubject(defaultSettings) + ); + getSubject(pubkey: string) { + return this.parsedSubjects.get(pubkey); + } + requestAppSettings(pubkey: string, relays: string[], alwaysRequest = false) { + const sub = this.parsedSubjects.get(pubkey); + const requestSub = this.requester.requestEvent(pubkey, relays, alwaysRequest); + sub.connectWithHandler(requestSub, (event, next) => next(parseAppSettings(event))); + return sub; + } + + receiveEvent(event: NostrEvent) { + this.requester.handleEvent(event); + } + + update() { + this.requester.update(); + } + + buildAppSettingsEvent(settings: AppSettings): DraftNostrEvent { + return { + kind: 30078, + tags: [["d", DTAG]], + content: JSON.stringify(settings), + created_at: dayjs().unix(), + }; + } +} + +const userAppSettings = new UserAppSettings(); + +setInterval(() => { + userAppSettings.update(); +}, 1000 * 2); + +if (import.meta.env.DEV) { + // @ts-ignore + window.userAppSettings = userAppSettings; +} + +export default userAppSettings; diff --git a/src/services/user-app-settings.ts b/src/services/user-app-settings.ts deleted file mode 100644 index f3441956c..000000000 --- a/src/services/user-app-settings.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { CachedPubkeyEventRequester } from "../classes/cached-pubkey-event-requester"; -import { DraftNostrEvent, NostrEvent } from "../types/nostr-event"; -import { SuperMap } from "../classes/super-map"; -import { PersistentSubject } from "../classes/subject"; -import { safeJson } from "../helpers/parse"; -import dayjs from "dayjs"; -import { ColorMode } from "@chakra-ui/react"; -import db from "./db"; - -const DTAG = "nostrudel-settings"; - -export type AppSettings = { - colorMode: ColorMode; - blurImages: boolean; - autoShowMedia: boolean; - proxyUserMedia: boolean; - showReactions: boolean; - showSignatureVerification: boolean; - - autoPayWithWebLN: boolean; - customZapAmounts: string; - - primaryColor: string; - imageProxy: string; - corsProxy: string; - showContentWarning: boolean; - twitterRedirect?: string; - redditRedirect?: string; - youtubeRedirect?: string; -}; - -export const defaultSettings: AppSettings = { - colorMode: "light", - blurImages: true, - autoShowMedia: true, - proxyUserMedia: false, - showReactions: true, - showSignatureVerification: false, - - autoPayWithWebLN: true, - customZapAmounts: "50,200,500,1000,2000,5000", - - primaryColor: "#8DB600", - imageProxy: "", - corsProxy: "", - showContentWarning: true, - twitterRedirect: undefined, - redditRedirect: undefined, - youtubeRedirect: undefined, -}; - -function parseAppSettings(event: NostrEvent): AppSettings { - const json = safeJson(event.content, {}); - return { - ...defaultSettings, - ...json, - }; -} - -class UserAppSettings { - requester: CachedPubkeyEventRequester; - constructor() { - this.requester = new CachedPubkeyEventRequester(30078, "user-app-data", DTAG); - this.requester.readCache = (pubkey) => db.get("settings", pubkey); - this.requester.writeCache = (pubkey, event) => db.put("settings", event); - } - - private parsedSubjects = new SuperMap>( - () => new PersistentSubject(defaultSettings) - ); - getSubject(pubkey: string) { - return this.parsedSubjects.get(pubkey); - } - requestAppSettings(pubkey: string, relays: string[], alwaysRequest = false) { - const sub = this.parsedSubjects.get(pubkey); - const requestSub = this.requester.requestEvent(pubkey, relays, alwaysRequest); - sub.connectWithHandler(requestSub, (event, next) => next(parseAppSettings(event))); - return sub; - } - - receiveEvent(event: NostrEvent) { - this.requester.handleEvent(event); - } - - update() { - this.requester.update(); - } - - buildAppSettingsEvent(settings: AppSettings): DraftNostrEvent { - return { - kind: 30078, - tags: [["d", DTAG]], - content: JSON.stringify(settings), - created_at: dayjs().unix(), - }; - } -} - -const userAppSettings = new UserAppSettings(); - -setInterval(() => { - userAppSettings.update(); -}, 1000 * 2); - -if (import.meta.env.DEV) { - // @ts-ignore - window.userAppSettings = userAppSettings; -} - -export default userAppSettings; diff --git a/src/views/settings/display-settings.tsx b/src/views/settings/display-settings.tsx index 2e7d3ba6f..12653d947 100644 --- a/src/views/settings/display-settings.tsx +++ b/src/views/settings/display-settings.tsx @@ -11,8 +11,10 @@ import { AccordionIcon, FormHelperText, Input, + Stack, + Select, } from "@chakra-ui/react"; -import { AppSettings } from "../../services/user-app-settings"; +import { AppSettings } from "../../services/settings/migrations"; export default function DisplaySettings() { const { register } = useFormContext(); @@ -30,15 +32,14 @@ export default function DisplaySettings() { - - - Use dark theme - - - - - Enables hacker mode - + + Use dark theme + + diff --git a/src/views/settings/index.tsx b/src/views/settings/index.tsx index 3e3c97d9b..b3d444d6a 100644 --- a/src/views/settings/index.tsx +++ b/src/views/settings/index.tsx @@ -16,6 +16,9 @@ export default function SettingsView() { const form = useForm({ mode: "all", values: settings, + resetOptions: { + keepDirty: true, + }, }); const saveSettings = form.handleSubmit(async (values) => { @@ -48,7 +51,7 @@ export default function SettingsView() {