feat(workspace): move connection status indicator to top tabs header
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 57s

This commit is contained in:
sandrodach committed 2026-10-27 11:32:25 +01:00
1 parent 3d780856b0
commit 37948d3a6a
2 files changed
+73 -31

No files matched your search

@@ -5,7 +5,6 @@ import {
Trash2,
Download,
FileText,
Radio,
Loader2,
ArrowLeft,
Hash,
@@ -18,7 +17,7 @@ import {
} from 'lucide-react';
import { Document, DocumentBlock, User } from '../../lib/types';
import { api } from '../../lib/api';
import { useDocumentSync, DocSyncEvent, RemoteCursor } from '../../lib/useDocumentSync';
import { useDocumentSync, DocSyncEvent, RemoteCursor, DocSyncStatus } from '../../lib/useDocumentSync';
import { OutlinerTree } from './OutlinerTree';
export interface DocumentEditorProps {
@@ -34,6 +33,7 @@ export interface DocumentEditorProps {
onSelectTag: (tag: string) => void;
onWikiLinkClick?: (pageTitle: string) => void;
onBackMobile?: () => void;
onSyncStatusChange?: (status: DocSyncStatus) => void;
}
const EMOJI_PALETTE = ['📝', '📄', '💡', '🚀', '📌', '📂', '🧠', '⚡', '🎯', '🛠️', '📊', '🔒'];
@@ -153,6 +153,7 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
onSelectTag,
onWikiLinkClick,
onBackMobile,
onSyncStatusChange,
}) => {
const [doc, setDoc] = useState<Document | null>(initialDoc || null);
const [title, setTitle] = useState<string>(initialDoc?.title || '');
@@ -344,6 +345,10 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
sendTitleUpdated,
} = useDocumentSync(docId, handleRemoteEvent, canEdit, currentUser);
useEffect(() => {
onSyncStatusChange?.(syncStatus);
}, [syncStatus, onSyncStatusChange]);
// Fetch isolated document state on mount
useEffect(() => {
let isCancelled = false;
@@ -750,34 +755,6 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
);
})()}
{/* Live Sync Status (1:1 styling from SharedListsView) */}
<div
className={`h-8 w-8 flex items-center justify-center rounded-btn border transition-colors ${
syncStatus === 'connected'
? 'bg-emerald-500/10 border-emerald-500/30 text-emerald-500'
: syncStatus === 'connecting' || syncStatus === 'reconnecting'
? 'bg-amber-500/10 border-amber-500/30 text-amber-500'
: 'bg-canvas border-border text-muted'
}`}
title={
syncStatus === 'connected'
? 'Live synchronisiert (WebSocket online)'
: syncStatus === 'connecting' || syncStatus === 'reconnecting'
? 'Verbindung wird aufgebaut…'
: 'Offline'
}
>
<Radio
className={`w-3.5 h-3.5 ${
syncStatus === 'connected'
? 'animate-pulse'
: syncStatus === 'connecting' || syncStatus === 'reconnecting'
? 'animate-spin'
: ''
}`}
/>
</div>
<button
type="button"
onClick={handleTogglePin}
@@ -22,6 +22,7 @@ import {
PanelLeftClose,
PanelLeftOpen,
ChevronLeft,
Radio,
} from 'lucide-react';
import {
Document,
@@ -34,7 +35,16 @@ import {
UnifiedShare,
User,
} from '../../lib/types';
import { api, onWebSocketMessage, triggerHaptic } from '../../lib/api';
import {
api,
onWebSocketMessage,
triggerHaptic,
onRealtimeStatus,
isRealtimeConnected,
getRealtimeTransport,
RealtimeTransport,
} from '../../lib/api';
import { DocSyncStatus } from '../../lib/useDocumentSync';
import { UnifiedShareModal } from '../../components/UnifiedShareModal';
import { useMobileActions } from '../../context/MobileActionContext';
import { DocumentEditor } from './DocumentEditor';
@@ -289,6 +299,19 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
});
}, []);
// Realtime connection status for top header indicator (identical to SharedListsView)
const [realtimeConnected, setRealtimeConnected] = useState<boolean>(() => isRealtimeConnected());
const [realtimeTransport, setRealtimeTransport] = useState<RealtimeTransport>(() => getRealtimeTransport());
const [activeDocSyncStatus, setActiveDocSyncStatus] = useState<DocSyncStatus>('connected');
useEffect(() => {
const unsub = onRealtimeStatus((connected, transport) => {
setRealtimeConnected(connected);
setRealtimeTransport(transport);
});
return unsub;
}, []);
// Drag and Drop state
const [draggingDocId, setDraggingDocId] = useState<string | null>(null);
const [draggingFolderPath, setDraggingFolderPath] = useState<string | null>(null);
@@ -1096,6 +1119,47 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
{tagsSummary.length > 0 && <span className="ml-1 badge-neutral text-[10px]">{tagsSummary.length}</span>}
</button>
</div>
{/* Right Header Section: Live Sync Status (identical to SharedListsView) */}
<div className="flex items-center gap-2">
{(() => {
const isDocActive = subView === 'editor' && Boolean(selectedDocId);
const isConnected = isDocActive
? activeDocSyncStatus === 'connected'
: realtimeConnected;
const isConnecting = isDocActive && (activeDocSyncStatus === 'connecting' || activeDocSyncStatus === 'reconnecting');
const isSSE = !isDocActive && realtimeTransport === 'sse';
return (
<div
className={`h-8 w-8 flex items-center justify-center rounded-btn border transition-colors ${
isConnected
? isSSE
? 'bg-teal-500/10 border-teal-500/30 text-teal-400'
: 'bg-emerald-500/10 border-emerald-500/30 text-emerald-500'
: isConnecting
? 'bg-amber-500/10 border-amber-500/30 text-amber-500'
: 'bg-canvas border-border text-muted'
}`}
title={
isConnected
? isSSE
? 'Live synchronisiert (Server-Sent Events / Proxy-Stream)'
: 'Live synchronisiert (WebSocket online)'
: isConnecting
? 'Verbindung wird aufgebaut…'
: 'Offline'
}
>
<Radio
className={`w-3.5 h-3.5 ${
isConnected ? 'animate-pulse' : isConnecting ? 'animate-spin' : ''
}`}
/>
</div>
);
})()}
</div>
</div>
{/* Main Workspace Split Pane */}
@@ -1336,6 +1400,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
onSelectTag={handleSelectTag}
onWikiLinkClick={handleWikiLinkClick}
onBackMobile={() => setMobileShowDetail(false)}
onSyncStatusChange={setActiveDocSyncStatus}
/>
) : subView === 'tags' ? (
/* =========================================================================