diff --git a/src/renderer/src/components/RelaySettings/RelayGroup.tsx b/src/renderer/src/components/RelaySettings/RelayGroup.tsx
index 40f3143a..0a27fa54 100644
--- a/src/renderer/src/components/RelaySettings/RelayGroup.tsx
+++ b/src/renderer/src/components/RelaySettings/RelayGroup.tsx
@@ -15,7 +15,9 @@ import { TRelayGroup } from './types'
export default function RelayGroup({ group }: { group: TRelayGroup }) {
const { expandedRelayGroup } = useRelaySettingsComponent()
- const { groupName, isActive, relayUrls } = group
+ const { temporaryRelayUrls } = useRelaySettings()
+ const { groupName, relayUrls } = group
+ const isActive = temporaryRelayUrls.length === 0 && group.isActive
return (
-
+ 0}
+ />
{relayUrls.length} relays
-
+
{expandedRelayGroup === groupName &&
}
@@ -38,20 +44,25 @@ export default function RelayGroup({ group }: { group: TRelayGroup }) {
)
}
-function RelayGroupActiveToggle({ groupName }: { groupName: string }) {
- const { relayGroups, switchRelayGroup } = useRelaySettings()
-
- const isActive = relayGroups.find((group) => group.groupName === groupName)?.isActive
- const hasRelayUrls = relayGroups.find((group) => group.groupName === groupName)?.relayUrls.length
+function RelayGroupActiveToggle({
+ groupName,
+ isActive,
+ canActive
+}: {
+ groupName: string
+ isActive: boolean
+ canActive: boolean
+}) {
+ const { switchRelayGroup } = useRelaySettings()
return isActive ? (
) : (
{
- if (hasRelayUrls) {
+ if (canActive) {
switchRelayGroup(groupName)
}
}}
@@ -68,6 +79,9 @@ function RelayGroupName({ groupName }: { groupName: string }) {
const hasRelayUrls = relayGroups.find((group) => group.groupName === groupName)?.relayUrls.length
const saveNewGroupName = () => {
+ if (relayGroups.find((group) => group.groupName === newGroupName)) {
+ return setNewNameError('already exists')
+ }
const errMsg = renameRelayGroup(groupName, newGroupName)
if (errMsg) {
setNewNameError(errMsg)
@@ -138,10 +152,9 @@ function RelayUrlsExpandToggle({
)
}
-function RelayGroupOptions({ groupName }: { groupName: string }) {
- const { relayGroups, deleteRelayGroup } = useRelaySettings()
+function RelayGroupOptions({ group }: { group: TRelayGroup }) {
+ const { deleteRelayGroup } = useRelaySettings()
const { setRenamingGroup } = useRelaySettingsComponent()
- const isActive = relayGroups.find((group) => group.groupName === groupName)?.isActive
return (
@@ -151,11 +164,21 @@ function RelayGroupOptions({ groupName }: { groupName: string }) {
- setRenamingGroup(groupName)}>Rename
+ setRenamingGroup(group.groupName)}>
+ Rename
+
+ {
+ navigator.clipboard.writeText(
+ `https://jumble.social/?${group.relayUrls.map((url) => 'r=' + url).join('&')}`
+ )
+ }}
+ >
+ Copy share link
+
deleteRelayGroup(groupName)}
+ onClick={() => deleteRelayGroup(group.groupName)}
>
Delete
diff --git a/src/renderer/src/components/RelaySettings/TemporaryRelayGroup.tsx b/src/renderer/src/components/RelaySettings/TemporaryRelayGroup.tsx
new file mode 100644
index 00000000..ba2530fb
--- /dev/null
+++ b/src/renderer/src/components/RelaySettings/TemporaryRelayGroup.tsx
@@ -0,0 +1,72 @@
+import { useRelaySettings } from '@renderer/providers/RelaySettingsProvider'
+import client from '@renderer/services/client.service'
+import { Save } from 'lucide-react'
+import { useEffect, useState } from 'react'
+import { Button } from '../ui/button'
+
+export default function TemporaryRelayGroup() {
+ const { temporaryRelayUrls, relayGroups, addRelayGroup, switchRelayGroup } = useRelaySettings()
+ const [relays, setRelays] = useState<
+ {
+ url: string
+ isConnected: boolean
+ }[]
+ >(temporaryRelayUrls.map((url) => ({ url, isConnected: false })))
+
+ useEffect(() => {
+ const interval = setInterval(() => {
+ const connectionStatusMap = client.listConnectionStatus()
+ setRelays((pre) => {
+ return pre.map((relay) => {
+ const isConnected = connectionStatusMap.get(relay.url) || false
+ return { ...relay, isConnected }
+ })
+ })
+ }, 1000)
+
+ return () => clearInterval(interval)
+ }, [])
+
+ useEffect(() => {
+ setRelays(temporaryRelayUrls.map((url) => ({ url, isConnected: false })))
+ }, [temporaryRelayUrls])
+
+ if (!relays.length) {
+ return null
+ }
+
+ const handleSave = () => {
+ const existingTemporaryIndexes = relayGroups
+ .filter((group) => /^Temporary \d+$/.test(group.groupName))
+ .map((group) => group.groupName.split(' ')[1])
+ .map(Number)
+ .filter((index) => !isNaN(index))
+ const nextIndex = Math.max(...existingTemporaryIndexes, 0) + 1
+ const groupName = `Temporary ${nextIndex}`
+ addRelayGroup(groupName, temporaryRelayUrls)
+ switchRelayGroup(groupName)
+ }
+
+ return (
+
+
+ {relays.map((relay, index) => (
+
+
+ {relay.isConnected ? (
+
●
+ ) : (
+
●
+ )}
+
{relay.url}
+
+
+ ))}
+
+ )
+}
diff --git a/src/renderer/src/components/RelaySettings/index.tsx b/src/renderer/src/components/RelaySettings/index.tsx
index f69600d7..6b583f5d 100644
--- a/src/renderer/src/components/RelaySettings/index.tsx
+++ b/src/renderer/src/components/RelaySettings/index.tsx
@@ -5,6 +5,7 @@ import { useRelaySettings } from '@renderer/providers/RelaySettingsProvider'
import { useEffect, useRef, useState } from 'react'
import { RelaySettingsComponentProvider } from './provider'
import RelayGroup from './RelayGroup'
+import TemporaryRelayGroup from './TemporaryRelayGroup'
export default function RelaySettings() {
const { relayGroups, addRelayGroup } = useRelaySettings()
@@ -19,6 +20,9 @@ export default function RelaySettings() {
}, [])
const saveRelayGroup = () => {
+ if (relayGroups.find((group) => group.groupName === newGroupName)) {
+ return setNewNameError('already exists')
+ }
const errMsg = addRelayGroup(newGroupName)
if (errMsg) {
return setNewNameError(errMsg)
@@ -43,6 +47,7 @@ export default function RelaySettings() {
Relay Settings
+
{relayGroups.map((group, index) => (
))}
@@ -63,7 +68,7 @@ export default function RelaySettings() {
onKeyDown={handleNewGroupNameKeyDown}
onBlur={saveRelayGroup}
/>
-
+
{newNameError && {newNameError}
}
diff --git a/src/renderer/src/layouts/PrimaryPageLayout/index.tsx b/src/renderer/src/layouts/PrimaryPageLayout/index.tsx
index 96d62ebc..eae817ef 100644
--- a/src/renderer/src/layouts/PrimaryPageLayout/index.tsx
+++ b/src/renderer/src/layouts/PrimaryPageLayout/index.tsx
@@ -10,7 +10,10 @@ import { forwardRef, useImperativeHandle, useRef } from 'react'
const PrimaryPageLayout = forwardRef(
(
- { children, titlebarContent }: { children: React.ReactNode; titlebarContent?: React.ReactNode },
+ {
+ children,
+ titlebarContent
+ }: { children?: React.ReactNode; titlebarContent?: React.ReactNode },
ref
) => {
const scrollAreaRef = useRef(null)
diff --git a/src/renderer/src/pages/primary/NoteListPage/index.tsx b/src/renderer/src/pages/primary/NoteListPage/index.tsx
index 33bbdb61..40ef6afa 100644
--- a/src/renderer/src/pages/primary/NoteListPage/index.tsx
+++ b/src/renderer/src/pages/primary/NoteListPage/index.tsx
@@ -1,10 +1,36 @@
import NoteList from '@renderer/components/NoteList'
+import RelaySettings from '@renderer/components/RelaySettings'
+import { Button } from '@renderer/components/ui/button'
+import { Popover, PopoverContent, PopoverTrigger } from '@renderer/components/ui/popover'
+import { ScrollArea } from '@renderer/components/ui/scroll-area'
import PrimaryPageLayout from '@renderer/layouts/PrimaryPageLayout'
import { useRelaySettings } from '@renderer/providers/RelaySettingsProvider'
export default function NoteListPage() {
const { relayUrls } = useRelaySettings()
- if (!relayUrls.length) return null
+
+ if (!relayUrls.length) {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+ }
return (
diff --git a/src/renderer/src/providers/RelaySettingsProvider.tsx b/src/renderer/src/providers/RelaySettingsProvider.tsx
index 8c44dec6..25073a21 100644
--- a/src/renderer/src/providers/RelaySettingsProvider.tsx
+++ b/src/renderer/src/providers/RelaySettingsProvider.tsx
@@ -1,14 +1,16 @@
import { TRelayGroup } from '@common/types'
+import { isWebsocketUrl, normalizeUrl } from '@renderer/lib/url'
import storage from '@renderer/services/storage.service'
import { createContext, useContext, useEffect, useState } from 'react'
type TRelaySettingsContext = {
relayGroups: TRelayGroup[]
+ temporaryRelayUrls: string[]
relayUrls: string[]
switchRelayGroup: (groupName: string) => void
renameRelayGroup: (oldGroupName: string, newGroupName: string) => string | null
deleteRelayGroup: (groupName: string) => void
- addRelayGroup: (groupName: string) => string | null
+ addRelayGroup: (groupName: string, relayUrls?: string[]) => string | null
updateRelayGroupRelayUrls: (groupName: string, relayUrls: string[]) => void
}
@@ -24,12 +26,31 @@ export const useRelaySettings = () => {
export function RelaySettingsProvider({ children }: { children: React.ReactNode }) {
const [relayGroups, setRelayGroups] = useState([])
+ const [temporaryRelayUrls, setTemporaryRelayUrls] = useState([])
const [relayUrls, setRelayUrls] = useState(
- relayGroups.find((group) => group.isActive)?.relayUrls ?? []
+ temporaryRelayUrls.length
+ ? temporaryRelayUrls
+ : (relayGroups.find((group) => group.isActive)?.relayUrls ?? [])
)
useEffect(() => {
const init = async () => {
+ const searchParams = new URLSearchParams(window.location.search)
+ const tempRelays = searchParams
+ .getAll('r')
+ .filter((url) => isWebsocketUrl(url))
+ .map((url) => normalizeUrl(url))
+ if (tempRelays.length) {
+ setTemporaryRelayUrls(tempRelays)
+ // remove relay urls from query string
+ searchParams.delete('r')
+ const newSearch = searchParams.toString()
+ window.history.replaceState(
+ {},
+ '',
+ `${window.location.pathname}${newSearch.length ? `?${newSearch}` : ''}`
+ )
+ }
const storedGroups = await storage.getRelayGroups()
setRelayGroups(storedGroups)
}
@@ -38,30 +59,39 @@ export function RelaySettingsProvider({ children }: { children: React.ReactNode
}, [])
useEffect(() => {
- setRelayUrls(relayGroups.find((group) => group.isActive)?.relayUrls ?? [])
- }, [relayGroups])
+ setRelayUrls(
+ temporaryRelayUrls.length
+ ? temporaryRelayUrls
+ : (relayGroups.find((group) => group.isActive)?.relayUrls ?? [])
+ )
+ }, [relayGroups, temporaryRelayUrls])
- const updateGroups = async (newGroups: TRelayGroup[]) => {
- setRelayGroups(newGroups)
+ const updateGroups = async (fn: (pre: TRelayGroup[]) => TRelayGroup[]) => {
+ let newGroups = relayGroups
+ setRelayGroups((pre) => {
+ newGroups = fn(pre)
+ return newGroups
+ })
await storage.setRelayGroups(newGroups)
}
const switchRelayGroup = (groupName: string) => {
- updateGroups(
- relayGroups.map((group) => ({
+ updateGroups((pre) =>
+ pre.map((group) => ({
...group,
isActive: group.groupName === groupName
}))
)
+ setTemporaryRelayUrls([])
}
const deleteRelayGroup = (groupName: string) => {
- updateGroups(relayGroups.filter((group) => group.groupName !== groupName || group.isActive))
+ updateGroups((pre) => pre.filter((group) => group.groupName !== groupName))
}
const updateRelayGroupRelayUrls = (groupName: string, relayUrls: string[]) => {
- updateGroups(
- relayGroups.map((group) => ({
+ updateGroups((pre) =>
+ pre.map((group) => ({
...group,
relayUrls: group.groupName === groupName ? relayUrls : group.relayUrls
}))
@@ -75,33 +105,38 @@ export function RelaySettingsProvider({ children }: { children: React.ReactNode
if (oldGroupName === newGroupName) {
return null
}
- if (relayGroups.some((group) => group.groupName === newGroupName)) {
- return 'already exists'
- }
- updateGroups(
- relayGroups.map((group) => ({
+ updateGroups((pre) => {
+ if (pre.some((group) => group.groupName === newGroupName)) {
+ return pre
+ }
+ return pre.map((group) => ({
...group,
groupName: group.groupName === oldGroupName ? newGroupName : group.groupName
}))
- )
+ })
return null
}
- const addRelayGroup = (groupName: string) => {
+ const addRelayGroup = (groupName: string, relayUrls: string[] = []) => {
if (groupName === '') {
return null
}
- if (relayGroups.some((group) => group.groupName === groupName)) {
- return 'already exists'
- }
- updateGroups([
- ...relayGroups,
- {
- groupName,
- relayUrls: [],
- isActive: false
+ const normalizedUrls = relayUrls
+ .filter((url) => isWebsocketUrl(url))
+ .map((url) => normalizeUrl(url))
+ updateGroups((pre) => {
+ if (pre.some((group) => group.groupName === groupName)) {
+ return pre
}
- ])
+ return [
+ ...pre,
+ {
+ groupName,
+ relayUrls: normalizedUrls,
+ isActive: false
+ }
+ ]
+ })
return null
}
@@ -109,6 +144,7 @@ export function RelaySettingsProvider({ children }: { children: React.ReactNode