Use better behavior on disconnected radio and allow deeplinking into settings. Closes #66.

This commit is contained in:
Jack Kingsman
2026-03-16 17:46:12 -07:00
parent ffb5fa51c1
commit b68bfc41d6
14 changed files with 359 additions and 56 deletions
+28 -5
View File
@@ -1,7 +1,8 @@
import { startTransition, useCallback, useState } from 'react';
import { startTransition, useCallback, useEffect, useRef, useState } from 'react';
import { getLocalLabel, type LocalLabel } from '../utils/localLabel';
import type { SettingsSection } from '../components/settings/settingsConstants';
import { parseHashSettingsSection, updateSettingsHash } from '../utils/urlHash';
interface UseAppShellResult {
showNewMessage: boolean;
@@ -23,25 +24,47 @@ interface UseAppShellResult {
}
export function useAppShell(): UseAppShellResult {
const initialSettingsSection = typeof window === 'undefined' ? null : parseHashSettingsSection();
const [showNewMessage, setShowNewMessage] = useState(false);
const [showSettings, setShowSettings] = useState(false);
const [settingsSection, setSettingsSection] = useState<SettingsSection>('radio');
const [showSettings, setShowSettings] = useState(() => initialSettingsSection !== null);
const [settingsSection, setSettingsSection] = useState<SettingsSection>(
() => initialSettingsSection ?? 'radio'
);
const [sidebarOpen, setSidebarOpen] = useState(false);
const [showCracker, setShowCracker] = useState(false);
const [crackerRunning, setCrackerRunning] = useState(false);
const [localLabel, setLocalLabel] = useState(getLocalLabel);
const previousHashRef = useRef('');
useEffect(() => {
if (showSettings) {
updateSettingsHash(settingsSection);
}
}, [settingsSection, showSettings]);
const handleCloseSettingsView = useCallback(() => {
if (typeof window !== 'undefined' && parseHashSettingsSection() !== null) {
window.history.replaceState(null, '', previousHashRef.current || window.location.pathname);
}
startTransition(() => setShowSettings(false));
setSidebarOpen(false);
}, []);
const handleToggleSettingsView = useCallback(() => {
if (showSettings) {
handleCloseSettingsView();
return;
}
if (typeof window !== 'undefined') {
previousHashRef.current =
parseHashSettingsSection() === null ? window.location.hash : previousHashRef.current;
}
startTransition(() => {
setShowSettings((prev) => !prev);
setShowSettings(true);
});
setSidebarOpen(false);
}, []);
}, [handleCloseSettingsView, showSettings]);
const handleOpenNewMessage = useCallback(() => {
setShowNewMessage(true);
+10 -5
View File
@@ -1,6 +1,7 @@
import { useState, useCallback, useEffect, useRef, type MutableRefObject } from 'react';
import {
parseHashConversation,
parseHashSettingsSection,
updateUrlHash,
resolveChannelFromHashToken,
resolveContactFromHashToken,
@@ -18,6 +19,7 @@ interface UseConversationRouterArgs {
channels: Channel[];
contacts: Contact[];
contactsLoaded: boolean;
suspendHashSync: boolean;
setSidebarOpen: (open: boolean) => void;
pendingDeleteFallbackRef: MutableRefObject<boolean>;
hasSetDefaultConversation: MutableRefObject<boolean>;
@@ -27,6 +29,7 @@ export function useConversationRouter({
channels,
contacts,
contactsLoaded,
suspendHashSync,
setSidebarOpen,
pendingDeleteFallbackRef,
hasSetDefaultConversation,
@@ -34,7 +37,9 @@ export function useConversationRouter({
const [activeConversation, setActiveConversationState] = useState<Conversation | null>(null);
const activeConversationRef = useRef<Conversation | null>(null);
const hashSyncEnabledRef = useRef(
typeof window !== 'undefined' ? window.location.hash.length > 0 : false
typeof window !== 'undefined'
? window.location.hash.length > 0 && parseHashSettingsSection() === null
: false
);
const setActiveConversation = useCallback((conv: Conversation | null) => {
@@ -58,7 +63,7 @@ export function useConversationRouter({
if (hasSetDefaultConversation.current || activeConversation) return;
if (channels.length === 0) return;
const hashConv = parseHashConversation();
const hashConv = parseHashSettingsSection() ? null : parseHashConversation();
// Handle non-data views immediately
if (hashConv?.type === 'raw') {
@@ -141,7 +146,7 @@ export function useConversationRouter({
useEffect(() => {
if (hasSetDefaultConversation.current || activeConversation) return;
const hashConv = parseHashConversation();
const hashConv = parseHashSettingsSection() ? null : parseHashConversation();
if (hashConv?.type === 'contact') {
if (!contactsLoaded) return;
@@ -203,14 +208,14 @@ export function useConversationRouter({
useEffect(() => {
activeConversationRef.current = activeConversation;
if (activeConversation) {
if (hashSyncEnabledRef.current) {
if (hashSyncEnabledRef.current && !suspendHashSync) {
updateUrlHash(activeConversation);
}
if (getReopenLastConversationEnabled() && activeConversation.type !== 'search') {
saveLastViewedConversation(activeConversation);
}
}
}, [activeConversation]);
}, [activeConversation, suspendHashSync]);
// If a delete action left us without an active conversation, recover to Public
useEffect(() => {