From 26c2512d61711e7ef97e81854e5108a24bb7507a Mon Sep 17 00:00:00 2001 From: Cody Tseng Date: Sat, 16 Nov 2024 15:44:37 +0800 Subject: [PATCH] feat: web (#6) --- README.md | 5 +- package-lock.json | 9 + package.json | 5 +- src/common/types.ts | 31 +++ src/main/services/storage.service.ts | 17 +- src/preload/index.d.ts | 33 ---- src/preload/index.ts | 13 +- src/renderer/index.html | 2 +- src/renderer/src/App.tsx | 13 +- src/renderer/src/PageManager.tsx | 177 +++++++++++------- .../components/AccountButton/LoginButton.tsx | 29 +++ .../AccountButton/ProfileButton.tsx | 70 +++++++ .../src/components/AccountButton/index.tsx | 17 ++ .../BackButton/index.tsx} | 0 .../src/components/Embedded/EmbeddedNote.tsx | 2 +- .../src/components/FollowButton/index.tsx | 54 ++++-- .../src/components/ImageGallery/index.tsx | 5 +- .../src/components/LoginDialog/index.tsx | 65 +++++++ src/renderer/src/components/Note/index.tsx | 2 +- .../components/NoteCard/RepostNoteCard.tsx | 3 + .../components/NoteCard/ShortTextNoteCard.tsx | 6 +- .../src/components/NoteList/index.tsx | 6 + .../src/components/NoteStats/LikeButton.tsx | 44 ++--- .../src/components/NoteStats/RepostButton.tsx | 44 ++--- .../PostButton/index.tsx} | 9 +- .../src/components/PostDialog/index.tsx | 75 ++++---- .../src/components/ProfileCard/index.tsx | 4 +- .../RefreshButton/index.tsx} | 9 +- .../RelaySettingsPopover/index.tsx} | 14 +- .../components/ScrollToTopButton/index.tsx | 2 +- src/renderer/src/components/Sidebar/index.tsx | 22 +++ .../ThemeToggle/index.tsx} | 0 .../src/components/Titlebar/index.tsx | 2 +- .../src/components/UserAvatar/index.tsx | 4 +- .../src/components/Username/index.tsx | 4 +- src/renderer/src/components/ui/button.tsx | 6 +- src/renderer/src/components/ui/card.tsx | 119 +++++------- src/renderer/src/components/ui/popover.tsx | 1 + src/renderer/src/env.d.ts | 11 ++ src/renderer/src/hooks/useFetchEventById.tsx | 31 +-- src/renderer/src/hooks/useFetchProfile.tsx | 20 +- .../PrimaryPageLayout/AccountButton.tsx | 114 ----------- .../src/layouts/PrimaryPageLayout/index.tsx | 20 +- .../src/layouts/SecondaryPageLayout/index.tsx | 15 +- src/renderer/src/lib/env.ts | 11 ++ src/renderer/src/lib/link.ts | 13 +- src/renderer/src/lib/platform.ts | 3 - .../secondary/FollowingListPage/index.tsx | 10 +- .../src/pages/secondary/HashtagPage/index.tsx | 17 +- .../{BlankPage => HomePage}/index.tsx | 2 +- .../src/pages/secondary/LoadingPage/index.tsx | 11 ++ .../pages/secondary/NotFoundPage/index.tsx | 19 ++ .../src/pages/secondary/NotePage/index.tsx | 24 ++- .../src/pages/secondary/ProfilePage/index.tsx | 12 +- src/renderer/src/providers/NostrProvider.tsx | 74 +++++++- src/renderer/src/providers/ThemeProvider.tsx | 77 ++++++-- src/renderer/src/routes.tsx | 21 +++ src/renderer/src/services/client.service.ts | 14 +- src/renderer/src/services/storage.service.ts | 39 +++- web.vite.config.ts | 17 ++ 60 files changed, 944 insertions(+), 554 deletions(-) delete mode 100644 src/preload/index.d.ts create mode 100644 src/renderer/src/components/AccountButton/LoginButton.tsx create mode 100644 src/renderer/src/components/AccountButton/ProfileButton.tsx create mode 100644 src/renderer/src/components/AccountButton/index.tsx rename src/renderer/src/{layouts/SecondaryPageLayout/BackButton.tsx => components/BackButton/index.tsx} (100%) create mode 100644 src/renderer/src/components/LoginDialog/index.tsx rename src/renderer/src/{layouts/PrimaryPageLayout/PostButton.tsx => components/PostButton/index.tsx} (52%) rename src/renderer/src/{layouts/PrimaryPageLayout/RefreshButton.tsx => components/RefreshButton/index.tsx} (50%) rename src/renderer/src/{layouts/PrimaryPageLayout/RelaySettingsPopover.tsx => components/RelaySettingsPopover/index.tsx} (62%) create mode 100644 src/renderer/src/components/Sidebar/index.tsx rename src/renderer/src/{layouts/SecondaryPageLayout/ThemeToggle.tsx => components/ThemeToggle/index.tsx} (100%) delete mode 100644 src/renderer/src/layouts/PrimaryPageLayout/AccountButton.tsx create mode 100644 src/renderer/src/lib/env.ts delete mode 100644 src/renderer/src/lib/platform.ts rename src/renderer/src/pages/secondary/{BlankPage => HomePage}/index.tsx (88%) create mode 100644 src/renderer/src/pages/secondary/LoadingPage/index.tsx create mode 100644 src/renderer/src/pages/secondary/NotFoundPage/index.tsx create mode 100644 src/renderer/src/routes.tsx create mode 100644 web.vite.config.ts diff --git a/README.md b/README.md index 7aabd5f..15f08f9 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ # jumble -Yet another Nostr desktop client - -> NOTE: Currently, only browsing is supported. Posting, liking, and reposting will be available soon. +Yet another Nostr client ## Features @@ -10,6 +8,7 @@ Yet another Nostr desktop client - **Relay-Friendly Design:** Minimized and simplified requests ensure efficient communication with relays - **Relay Groups:** Easily manage and switch between relay groups - **Clean Interface:** Enjoy a minimalist design and intuitive interactions +- **Cross-Platform:** Available on macOS, Windows, Linux, and web browsers ## Download diff --git a/package-lock.json b/package-lock.json index fec15c8..e094f8f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,6 +31,7 @@ "lru-cache": "^11.0.1", "lucide-react": "^0.453.0", "nostr-tools": "^2.9.1", + "path-to-regexp": "^8.2.0", "qrcode.react": "^4.1.0", "react-resizable-panels": "^2.1.5", "react-string-replace": "^1.1.1", @@ -7561,6 +7562,14 @@ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" }, + "node_modules/path-to-regexp": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "engines": { + "node": ">=16" + } + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", diff --git a/package.json b/package.json index b4d9c49..99752ea 100644 --- a/package.json +++ b/package.json @@ -18,12 +18,14 @@ "typecheck": "npm run typecheck:node && npm run typecheck:web", "start": "electron-vite preview", "dev": "electron-vite dev", + "dev:web": "vite --config web.vite.config.ts", "build": "npm run typecheck && electron-vite build", "postinstall": "electron-builder install-app-deps", "build:unpack": "npm run build && electron-builder --dir", "build:win": "npm run build && electron-builder --win -p never", "build:mac": "electron-vite build && electron-builder --mac -p never", - "build:linux": "electron-vite build && electron-builder --linux -p never" + "build:linux": "electron-vite build && electron-builder --linux -p never", + "build:web": "vite build --config web.vite.config.ts" }, "dependencies": { "@electron-toolkit/preload": "^3.0.1", @@ -47,6 +49,7 @@ "lru-cache": "^11.0.1", "lucide-react": "^0.453.0", "nostr-tools": "^2.9.1", + "path-to-regexp": "^8.2.0", "qrcode.react": "^4.1.0", "react-resizable-panels": "^2.1.5", "react-string-replace": "^1.1.1", diff --git a/src/common/types.ts b/src/common/types.ts index e91e368..ba27490 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -1,3 +1,4 @@ +import { ElectronAPI } from '@electron-toolkit/preload' import { Event } from 'nostr-tools' export type TRelayGroup = { @@ -15,3 +16,33 @@ export type TThemeSetting = 'light' | 'dark' | 'system' export type TTheme = 'light' | 'dark' export type TDraftEvent = Pick + +export type TElectronWindow = { + electron: ElectronAPI + api: { + system: { + isEncryptionAvailable: () => Promise + } + theme: { + onChange: (cb: (theme: TTheme) => void) => void + current: () => Promise + themeSetting: () => Promise + set: (themeSetting: TThemeSetting) => Promise + } + storage: { + getRelayGroups: () => Promise + setRelayGroups: (relayGroups: TRelayGroup[]) => Promise + } + nostr: { + login: (nsec: string) => Promise<{ + pubkey?: string + reason?: string + }> + logout: () => Promise + } + } + nostr: { + getPublicKey: () => Promise + signEvent: (draftEvent: TDraftEvent) => Promise + } +} diff --git a/src/main/services/storage.service.ts b/src/main/services/storage.service.ts index 6695233..cf8b105 100644 --- a/src/main/services/storage.service.ts +++ b/src/main/services/storage.service.ts @@ -17,21 +17,8 @@ export class StorageService { ) } - getRelayGroups(): TRelayGroup[] { - return ( - this.storage.get('relayGroups') ?? [ - { - groupName: 'Global', - relayUrls: [ - 'wss://relay.damus.io/', - 'wss://nos.lol/', - 'wss://nostr.mom/', - 'wss://relay.primal.net/' - ], - isActive: true - } - ] - ) + getRelayGroups(): TRelayGroup[] | null { + return this.storage.get('relayGroups') ?? null } setRelayGroups(relayGroups: TRelayGroup[]) { diff --git a/src/preload/index.d.ts b/src/preload/index.d.ts deleted file mode 100644 index 56a2751..0000000 --- a/src/preload/index.d.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { TDraftEvent, TRelayGroup, TTheme, TThemeSetting } from '@common/types' -import { ElectronAPI } from '@electron-toolkit/preload' -import { Event } from 'nostr-tools' - -declare global { - interface Window { - electron: ElectronAPI - api: { - system: { - isEncryptionAvailable: () => Promise - } - theme: { - onChange: (cb: (theme: TTheme) => void) => void - current: () => Promise - themeSetting: () => Promise - set: (themeSetting: TThemeSetting) => Promise - } - storage: { - getRelayGroups: () => Promise - setRelayGroups: (relayGroups: TRelayGroup[]) => Promise - } - nostr: { - login: (nsec: string) => Promise<{ - pubkey?: string - reason?: string - }> - logout: () => Promise - getPublicKey: () => Promise - signEvent: (draftEvent: TDraftEvent) => Promise - } - } - } -} diff --git a/src/preload/index.ts b/src/preload/index.ts index f03d918..86941b6 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -24,12 +24,16 @@ const api = { }, nostr: { login: (nsec: string) => ipcRenderer.invoke('nostr:login', nsec), - logout: () => ipcRenderer.invoke('nostr:logout'), - getPublicKey: () => ipcRenderer.invoke('nostr:getPublicKey'), - signEvent: (draftEvent: TDraftEvent) => ipcRenderer.invoke('nostr:signEvent', draftEvent) + logout: () => ipcRenderer.invoke('nostr:logout') } } +// NIP-07 +const nostr = { + getPublicKey: () => ipcRenderer.invoke('nostr:getPublicKey'), + signEvent: (draftEvent: TDraftEvent) => ipcRenderer.invoke('nostr:signEvent', draftEvent) +} + // Use `contextBridge` APIs to expose Electron APIs to // renderer only if context isolation is enabled, otherwise // just add to the DOM global. @@ -37,6 +41,7 @@ if (process.contextIsolated) { try { contextBridge.exposeInMainWorld('electron', electronAPI) contextBridge.exposeInMainWorld('api', api) + contextBridge.exposeInMainWorld('nostr', nostr) } catch (error) { console.error(error) } @@ -45,4 +50,6 @@ if (process.contextIsolated) { window.electron = electronAPI // @ts-ignore (define in dts) window.api = api + // @ts-ignore (define in dts) + window.nostr = nostr } diff --git a/src/renderer/index.html b/src/renderer/index.html index 027e508..a16dfda 100644 --- a/src/renderer/index.html +++ b/src/renderer/index.html @@ -2,7 +2,7 @@ - Electron + Jumble