feat(workspace): move connection status indicator to top tabs header
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 57s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 57s
This commit is contained in:
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' ? (
|
||||
/* =========================================================================
|
||||
|
||||
Reference in new issue
Block a user