Dev #7

Merged
sandrodach merged 33 commits from dev into main 2026-10-27 11:42:12 +01:00
2 changed files with 73 additions and 31 deletions
Showing only changes of commit 37948d3a6a - Show all commits

No files matched your search

@@ -5,7 +5,6 @@ import {
Trash2, Trash2,
Download, Download,
FileText, FileText,
Radio,
Loader2, Loader2,
ArrowLeft, ArrowLeft,
Hash, Hash,
@@ -18,7 +17,7 @@ import {
} from 'lucide-react'; } from 'lucide-react';
import { Document, DocumentBlock, User } from '../../lib/types'; import { Document, DocumentBlock, User } from '../../lib/types';
import { api } from '../../lib/api'; import { api } from '../../lib/api';
import { useDocumentSync, DocSyncEvent, RemoteCursor } from '../../lib/useDocumentSync'; import { useDocumentSync, DocSyncEvent, RemoteCursor, DocSyncStatus } from '../../lib/useDocumentSync';
import { OutlinerTree } from './OutlinerTree'; import { OutlinerTree } from './OutlinerTree';
export interface DocumentEditorProps { export interface DocumentEditorProps {
@@ -34,6 +33,7 @@ export interface DocumentEditorProps {
onSelectTag: (tag: string) => void; onSelectTag: (tag: string) => void;
onWikiLinkClick?: (pageTitle: string) => void; onWikiLinkClick?: (pageTitle: string) => void;
onBackMobile?: () => void; onBackMobile?: () => void;
onSyncStatusChange?: (status: DocSyncStatus) => void;
} }
const EMOJI_PALETTE = ['📝', '📄', '💡', '🚀', '📌', '📂', '🧠', '⚡', '🎯', '🛠️', '📊', '🔒']; const EMOJI_PALETTE = ['📝', '📄', '💡', '🚀', '📌', '📂', '🧠', '⚡', '🎯', '🛠️', '📊', '🔒'];
@@ -153,6 +153,7 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
onSelectTag, onSelectTag,
onWikiLinkClick, onWikiLinkClick,
onBackMobile, onBackMobile,
onSyncStatusChange,
}) => { }) => {
const [doc, setDoc] = useState<Document | null>(initialDoc || null); const [doc, setDoc] = useState<Document | null>(initialDoc || null);
const [title, setTitle] = useState<string>(initialDoc?.title || ''); const [title, setTitle] = useState<string>(initialDoc?.title || '');
@@ -344,6 +345,10 @@ export const DocumentEditor: React.FC<DocumentEditorProps> = ({
sendTitleUpdated, sendTitleUpdated,
} = useDocumentSync(docId, handleRemoteEvent, canEdit, currentUser); } = useDocumentSync(docId, handleRemoteEvent, canEdit, currentUser);
useEffect(() => {
onSyncStatusChange?.(syncStatus);
}, [syncStatus, onSyncStatusChange]);
// Fetch isolated document state on mount // Fetch isolated document state on mount
useEffect(() => { useEffect(() => {
let isCancelled = false; 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 <button
type="button" type="button"
onClick={handleTogglePin} onClick={handleTogglePin}
@@ -22,6 +22,7 @@ import {
PanelLeftClose, PanelLeftClose,
PanelLeftOpen, PanelLeftOpen,
ChevronLeft, ChevronLeft,
Radio,
} from 'lucide-react'; } from 'lucide-react';
import { import {
Document, Document,
@@ -34,7 +35,16 @@ import {
UnifiedShare, UnifiedShare,
User, User,
} from '../../lib/types'; } 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 { UnifiedShareModal } from '../../components/UnifiedShareModal';
import { useMobileActions } from '../../context/MobileActionContext'; import { useMobileActions } from '../../context/MobileActionContext';
import { DocumentEditor } from './DocumentEditor'; 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 // Drag and Drop state
const [draggingDocId, setDraggingDocId] = useState<string | null>(null); const [draggingDocId, setDraggingDocId] = useState<string | null>(null);
const [draggingFolderPath, setDraggingFolderPath] = 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>} {tagsSummary.length > 0 && <span className="ml-1 badge-neutral text-[10px]">{tagsSummary.length}</span>}
</button> </button>
</div> </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> </div>
{/* Main Workspace Split Pane */} {/* Main Workspace Split Pane */}
@@ -1336,6 +1400,7 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
onSelectTag={handleSelectTag} onSelectTag={handleSelectTag}
onWikiLinkClick={handleWikiLinkClick} onWikiLinkClick={handleWikiLinkClick}
onBackMobile={() => setMobileShowDetail(false)} onBackMobile={() => setMobileShowDetail(false)}
onSyncStatusChange={setActiveDocSyncStatus}
/> />
) : subView === 'tags' ? ( ) : subView === 'tags' ? (
/* ========================================================================= /* =========================================================================