From 9ad5ecb6f8c3df3f1d3c54969d9040c2226332cf Mon Sep 17 00:00:00 2001 From: Silberengel Date: Mon, 20 Oct 2025 21:20:58 +0200 Subject: [PATCH] Make the right-hand panel closeable. --- src/PageManager.tsx | 179 ++++++++++++++++++---- src/components/NoteCard/MainNoteCard.tsx | 6 +- src/constants.ts | 1 + src/layouts/SecondaryPageLayout/index.tsx | 34 ++-- src/pages/secondary/HomePage/index.tsx | 19 ++- src/pages/secondary/NotePage/index.tsx | 8 +- src/providers/UserPreferencesProvider.tsx | 14 +- src/services/local-storage.service.ts | 13 ++ 8 files changed, 215 insertions(+), 59 deletions(-) diff --git a/src/PageManager.tsx b/src/PageManager.tsx index 8aad816..fd538b6 100644 --- a/src/PageManager.tsx +++ b/src/PageManager.tsx @@ -1,9 +1,13 @@ import Sidebar from '@/components/Sidebar' +import { Button } from '@/components/ui/button' import { cn } from '@/lib/utils' +import { ChevronLeft } from 'lucide-react' import NoteListPage from '@/pages/primary/NoteListPage' import HomePage from '@/pages/secondary/HomePage' +import NotePage from '@/pages/secondary/NotePage' import { CurrentRelaysProvider } from '@/providers/CurrentRelaysProvider' import { NotificationProvider } from '@/providers/NotificationProvider' +import { UserPreferencesProvider, useUserPreferences } from '@/providers/UserPreferencesProvider' import { TPageRef } from '@/types' import { cloneElement, @@ -78,6 +82,10 @@ const PrimaryPageContext = createContext(undefi const SecondaryPageContext = createContext(undefined) +const PrimaryNoteViewContext = createContext<{ + setPrimaryNoteView: (view: ReactNode | null) => void +} | undefined>(undefined) + export function usePrimaryPage() { const context = useContext(PrimaryPageContext) if (!context) { @@ -94,6 +102,127 @@ export function useSecondaryPage() { return context } +export function usePrimaryNoteView() { + const context = useContext(PrimaryNoteViewContext) + if (!context) { + throw new Error('usePrimaryNoteView must be used within a PrimaryNoteViewContext.Provider') + } + return context +} + +// Custom hook for intelligent note navigation +export function useSmartNoteNavigation() { + const { hideRecommendedRelaysPanel } = useUserPreferences() + const { push: pushSecondary } = useSecondaryPage() + const { setPrimaryNoteView } = usePrimaryNoteView() + + const navigateToNote = (url: string) => { + if (hideRecommendedRelaysPanel) { + // When right panel is hidden, show note in primary area + // Extract note ID from URL (e.g., "/notes/note1..." -> "note1...") + const noteId = url.replace('/notes/', '') + setPrimaryNoteView() + } else { + // Normal behavior - use secondary navigation + pushSecondary(url) + } + } + + return { navigateToNote } +} + +function ConditionalHomePage() { + const { hideRecommendedRelaysPanel } = useUserPreferences() + + if (hideRecommendedRelaysPanel) { + return null + } + + return +} + +function MainContentArea({ + primaryPages, + currentPrimaryPage, + secondaryStack, + primaryNoteView, + setPrimaryNoteView +}: { + primaryPages: { name: TPrimaryPageName; element: ReactNode; props?: any }[] + currentPrimaryPage: TPrimaryPageName + secondaryStack: { index: number; component: ReactNode }[] + primaryNoteView: ReactNode | null + setPrimaryNoteView: (view: ReactNode | null) => void +}) { + const { hideRecommendedRelaysPanel } = useUserPreferences() + + // If recommended relays panel is hidden, use single column layout + // Otherwise use two-column grid layout + const gridClass = hideRecommendedRelaysPanel ? "grid-cols-1" : "grid-cols-2" + + return ( +
+
+ {hideRecommendedRelaysPanel && primaryNoteView ? ( + // Show note view with back button when right panel is hidden +
+
+
+ +
+
+
+ {primaryNoteView} +
+
+ ) : ( + // Show normal primary pages + primaryPages.map(({ name, element, props }) => ( +
+ {props ? cloneElement(element as React.ReactElement, props) : element} +
+ )) + )} +
+ {!hideRecommendedRelaysPanel && ( +
+ {secondaryStack.map((item, index) => ( +
+ {item.component} +
+ ))} +
+ +
+
+ )} +
+ ) +} + export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) { const [currentPrimaryPage, setCurrentPrimaryPage] = useState('home') const [primaryPages, setPrimaryPages] = useState< @@ -105,6 +234,7 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) { } ]) const [secondaryStack, setSecondaryStack] = useState([]) + const [primaryNoteView, setPrimaryNoteView] = useState(null) const { isSmallScreen } = useScreenSize() const ignorePopStateRef = useRef(false) @@ -310,6 +440,8 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) { > + + {!!secondaryStack.length && secondaryStack.map((item, index) => (
+ + @@ -359,6 +493,8 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) { > + +
-
-
- {primaryPages.map(({ name, element, props }) => ( -
- {props ? cloneElement(element as React.ReactElement, props) : element} -
- ))} -
-
- {secondaryStack.map((item, index) => ( -
- {item.component} -
- ))} -
- -
-
-
+
+ +
+
diff --git a/src/components/NoteCard/MainNoteCard.tsx b/src/components/NoteCard/MainNoteCard.tsx index c47eaaf..0177167 100644 --- a/src/components/NoteCard/MainNoteCard.tsx +++ b/src/components/NoteCard/MainNoteCard.tsx @@ -1,6 +1,6 @@ import { Separator } from '@/components/ui/separator' import { toNote } from '@/lib/link' -import { useSecondaryPage } from '@/PageManager' +import { useSmartNoteNavigation } from '@/PageManager' import { Event } from 'nostr-tools' import Collapsible from '../Collapsible' import Note from '../Note' @@ -20,14 +20,14 @@ export default function MainNoteCard({ embedded?: boolean originalNoteId?: string }) { - const { push } = useSecondaryPage() + const { navigateToNote } = useSmartNoteNavigation() return (
{ e.stopPropagation() - push(toNote(originalNoteId ?? event)) + navigateToNote(toNote(originalNoteId ?? event)) }} >
diff --git a/src/constants.ts b/src/constants.ts index c0825e8..003a93b 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -45,6 +45,7 @@ export const StorageKey = { NOTIFICATION_LIST_STYLE: 'notificationListStyle', MEDIA_AUTO_LOAD_POLICY: 'mediaAutoLoadPolicy', SHOWN_CREATE_WALLET_GUIDE_TOAST_PUBKEYS: 'shownCreateWalletGuideToastPubkeys', + HIDE_RECOMMENDED_RELAYS_PANEL: 'hideRecommendedRelaysPanel', MEDIA_UPLOAD_SERVICE: 'mediaUploadService', // deprecated HIDE_UNTRUSTED_EVENTS: 'hideUntrustedEvents', // deprecated ACCOUNT_RELAY_LIST_EVENT_MAP: 'accountRelayListEventMap', // deprecated diff --git a/src/layouts/SecondaryPageLayout/index.tsx b/src/layouts/SecondaryPageLayout/index.tsx index e707bc4..361a470 100644 --- a/src/layouts/SecondaryPageLayout/index.tsx +++ b/src/layouts/SecondaryPageLayout/index.tsx @@ -66,13 +66,15 @@ const SecondaryPageLayout = forwardRef( paddingBottom: 'calc(env(safe-area-inset-bottom) + 3rem)' }} > - + {title && ( + + )} {children}
{displayScrollToTopButton && } @@ -84,16 +86,18 @@ const SecondaryPageLayout = forwardRef( - + {title && ( + + )} {children}
diff --git a/src/pages/secondary/HomePage/index.tsx b/src/pages/secondary/HomePage/index.tsx index a01e0cc..030a88e 100644 --- a/src/pages/secondary/HomePage/index.tsx +++ b/src/pages/secondary/HomePage/index.tsx @@ -4,9 +4,10 @@ import { Button } from '@/components/ui/button' import { RECOMMENDED_RELAYS } from '@/constants' import SecondaryPageLayout from '@/layouts/SecondaryPageLayout' import { toRelay } from '@/lib/link' +import { useUserPreferences } from '@/providers/UserPreferencesProvider' import relayInfoService from '@/services/relay-info.service' import { TRelayInfo } from '@/types' -import { ArrowRight, Server } from 'lucide-react' +import { ArrowRight, Server, X } from 'lucide-react' import { forwardRef, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' @@ -14,6 +15,7 @@ const HomePage = forwardRef(({ index }: { index?: number }, ref) => { const { t } = useTranslation() const { navigate } = usePrimaryPage() const { push } = useSecondaryPage() + const { updateHideRecommendedRelaysPanel } = useUserPreferences() const [recommendedRelayInfos, setRecommendedRelayInfos] = useState([]) useEffect(() => { @@ -43,10 +45,21 @@ const HomePage = forwardRef(({ index }: { index?: number }, ref) => { ref={ref} index={index} title={ - <> +
{t('Recommended relays')}
- +
+ } + controls={ + } hideBackButton hideTitlebarBottomBorder diff --git a/src/pages/secondary/NotePage/index.tsx b/src/pages/secondary/NotePage/index.tsx index 3279209..8bbbaef 100644 --- a/src/pages/secondary/NotePage/index.tsx +++ b/src/pages/secondary/NotePage/index.tsx @@ -20,7 +20,7 @@ import { forwardRef, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import NotFound from './NotFound' -const NotePage = forwardRef(({ id, index }: { id?: string; index?: number }, ref) => { +const NotePage = forwardRef(({ id, index, hideTitlebar = false }: { id?: string; index?: number; hideTitlebar?: boolean }, ref) => { const { t } = useTranslation() const { event, isFetching } = useFetchEvent(id) const [externalEvent, setExternalEvent] = useState(undefined) @@ -37,7 +37,7 @@ const NotePage = forwardRef(({ id, index }: { id?: string; index?: number }, ref if (!event && isFetching) { return ( - +
@@ -64,14 +64,14 @@ const NotePage = forwardRef(({ id, index }: { id?: string; index?: number }, ref } if (!finalEvent) { return ( - + ) } return ( - +
{rootITag && } {rootEventId && rootEventId !== parentEventId && ( diff --git a/src/providers/UserPreferencesProvider.tsx b/src/providers/UserPreferencesProvider.tsx index ea554cc..9fbe673 100644 --- a/src/providers/UserPreferencesProvider.tsx +++ b/src/providers/UserPreferencesProvider.tsx @@ -5,6 +5,8 @@ import { createContext, useContext, useState } from 'react' type TUserPreferencesContext = { notificationListStyle: TNotificationStyle updateNotificationListStyle: (style: TNotificationStyle) => void + hideRecommendedRelaysPanel: boolean + updateHideRecommendedRelaysPanel: (hide: boolean) => void } const UserPreferencesContext = createContext(undefined) @@ -21,17 +23,27 @@ export function UserPreferencesProvider({ children }: { children: React.ReactNod const [notificationListStyle, setNotificationListStyle] = useState( storage.getNotificationListStyle() ) + const [hideRecommendedRelaysPanel, setHideRecommendedRelaysPanel] = useState( + storage.getHideRecommendedRelaysPanel() + ) const updateNotificationListStyle = (style: TNotificationStyle) => { setNotificationListStyle(style) storage.setNotificationListStyle(style) } + const updateHideRecommendedRelaysPanel = (hide: boolean) => { + setHideRecommendedRelaysPanel(hide) + storage.setHideRecommendedRelaysPanel(hide) + } + return ( {children} diff --git a/src/services/local-storage.service.ts b/src/services/local-storage.service.ts index f7d7f91..f9b5107 100644 --- a/src/services/local-storage.service.ts +++ b/src/services/local-storage.service.ts @@ -49,6 +49,7 @@ class LocalStorageService { private hideContentMentioningMutedUsers: boolean = false private notificationListStyle: TNotificationStyle = NOTIFICATION_LIST_STYLE.DETAILED private mediaAutoLoadPolicy: TMediaAutoLoadPolicy = MEDIA_AUTO_LOAD_POLICY.ALWAYS + private hideRecommendedRelaysPanel: boolean = false private shownCreateWalletGuideToastPubkeys: Set = new Set() constructor() { @@ -164,6 +165,9 @@ class LocalStorageService { this.dismissedTooManyRelaysAlert = window.localStorage.getItem(StorageKey.DISMISSED_TOO_MANY_RELAYS_ALERT) === 'true' + this.hideRecommendedRelaysPanel = + window.localStorage.getItem(StorageKey.HIDE_RECOMMENDED_RELAYS_PANEL) === 'true' + const showKindsStr = window.localStorage.getItem(StorageKey.SHOW_KINDS) if (!showKindsStr) { // Default: show all supported kinds except reposts @@ -456,6 +460,15 @@ class LocalStorageService { window.localStorage.setItem(StorageKey.DISMISSED_TOO_MANY_RELAYS_ALERT, dismissed.toString()) } + getHideRecommendedRelaysPanel() { + return this.hideRecommendedRelaysPanel + } + + setHideRecommendedRelaysPanel(hide: boolean) { + this.hideRecommendedRelaysPanel = hide + window.localStorage.setItem(StorageKey.HIDE_RECOMMENDED_RELAYS_PANEL, hide.toString()) + } + getShowKinds() { return this.showKinds }