feat: Refactor fullscreen editor to use React portals with maximum z-index and improved layout constraints.
This commit is contained in:
parent
2a14e4cea2
commit
72e408a3aa
@ -300,9 +300,43 @@ body {
|
|||||||
bottom: 0 !important;
|
bottom: 0 !important;
|
||||||
width: 100vw !important;
|
width: 100vw !important;
|
||||||
height: 100vh !important;
|
height: 100vh !important;
|
||||||
z-index: 99999 !important;
|
min-width: 100vw !important;
|
||||||
|
min-height: 100vh !important;
|
||||||
|
max-width: 100vw !important;
|
||||||
|
max-height: 100vh !important;
|
||||||
|
z-index: 2147483647 !important; /* Maximum z-index value */
|
||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
border-radius: 0 !important;
|
border-radius: 0 !important;
|
||||||
|
border: none !important;
|
||||||
background: var(--color-surface-900) !important;
|
background: var(--color-surface-900) !important;
|
||||||
|
transform: none !important;
|
||||||
|
box-sizing: border-box !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Portal-based fullscreen editor - renders directly to body */
|
||||||
|
.fullscreen-editor-portal {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
z-index: 2147483647;
|
||||||
|
background: var(--color-surface-900);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure fullscreen content fills the space */
|
||||||
|
.fullscreen-overlay > *,
|
||||||
|
.fullscreen-editor-portal > * {
|
||||||
|
max-width: 100% !important;
|
||||||
|
width: 100% !important;
|
||||||
|
height: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen-overlay .cm-editor,
|
||||||
|
.fullscreen-editor-portal .cm-editor {
|
||||||
|
height: 100% !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { useEffect, useState, useCallback, useRef } from 'react';
|
import { useEffect, useState, useCallback, useRef } from 'react';
|
||||||
|
import { createPortal } from 'react-dom';
|
||||||
import CodeMirror from '@uiw/react-codemirror';
|
import CodeMirror from '@uiw/react-codemirror';
|
||||||
import { json } from '@codemirror/lang-json';
|
import { json } from '@codemirror/lang-json';
|
||||||
import { markdown } from '@codemirror/lang-markdown';
|
import { markdown } from '@codemirror/lang-markdown';
|
||||||
@ -10,6 +11,38 @@ import type {
|
|||||||
} from '../api/client';
|
} from '../api/client';
|
||||||
import { api } from '../api/client';
|
import { api } from '../api/client';
|
||||||
|
|
||||||
|
// ═════════════════════════════════════════════════════════════════════════════════
|
||||||
|
// Fullscreen Editor Component using Portal for true fullscreen
|
||||||
|
// ═════════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
interface FullscreenEditorProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
function FullscreenEditor({ isOpen, onClose, children }: FullscreenEditorProps) {
|
||||||
|
useEffect(() => {
|
||||||
|
if (isOpen) {
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
} else {
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
}
|
||||||
|
return () => {
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
};
|
||||||
|
}, [isOpen]);
|
||||||
|
|
||||||
|
if (!isOpen) return null;
|
||||||
|
|
||||||
|
return createPortal(
|
||||||
|
<div className="fullscreen-editor-portal">
|
||||||
|
{children}
|
||||||
|
</div>,
|
||||||
|
document.body
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// ═════════════════════════════════════════════════════════════════════════════════
|
// ═════════════════════════════════════════════════════════════════════════════════
|
||||||
// Tab definitions
|
// Tab definitions
|
||||||
// ═════════════════════════════════════════════════════════════════════════════════
|
// ═════════════════════════════════════════════════════════════════════════════════
|
||||||
@ -625,16 +658,14 @@ function AgentsTab() {
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Identity File Editor */}
|
{/* Identity File Editor - Inline (not fullscreen) */}
|
||||||
{activeFile?.agent === agent.name && (
|
{activeFile?.agent === agent.name && !isFullscreen && (
|
||||||
<div className={`border border-glass-border bg-[#282c34] overflow-hidden shadow-inner mt-4 animate-fade-in flex flex-col ${
|
<div className="border border-glass-border bg-[#282c34] overflow-hidden shadow-inner mt-4 animate-fade-in flex flex-col h-[500px] rounded-xl relative">
|
||||||
isFullscreen ? 'fullscreen-overlay' : 'h-[500px] rounded-xl relative'
|
<div className="flex items-center justify-between bg-surface-900/80 border-b border-glass-border shrink-0 px-3 py-2">
|
||||||
}`}>
|
|
||||||
<div className={`flex items-center justify-between bg-surface-900/80 border-b border-glass-border shrink-0 ${isFullscreen ? 'h-16 px-6 py-4' : 'px-3 py-2'}`}>
|
|
||||||
<span className="text-xs font-mono text-purple-300 truncate pr-4">workspace/{agent.name}/{activeFile.file}</span>
|
<span className="text-xs font-mono text-purple-300 truncate pr-4">workspace/{agent.name}/{activeFile.file}</span>
|
||||||
<div className="flex gap-2 shrink-0">
|
<div className="flex gap-2 shrink-0">
|
||||||
<button onClick={() => setIsFullscreen(!isFullscreen)} className="px-2 py-1 rounded text-[10px] text-gray-400 hover:bg-surface-700 hover:text-white transition-colors">
|
<button onClick={() => setIsFullscreen(true)} className="px-2 py-1 rounded text-[10px] text-gray-400 hover:bg-surface-700 hover:text-white transition-colors">
|
||||||
{isFullscreen ? '🗗 Réduire' : '⛶ Plein écran'}
|
⛶ Plein écran
|
||||||
</button>
|
</button>
|
||||||
<button onClick={() => { setActiveFile(null); setIsFullscreen(false); }} className="px-2 py-1 rounded text-[10px] text-gray-400 hover:bg-surface-700 hover:text-white transition-colors">
|
<button onClick={() => { setActiveFile(null); setIsFullscreen(false); }} className="px-2 py-1 rounded text-[10px] text-gray-400 hover:bg-surface-700 hover:text-white transition-colors">
|
||||||
Fermer
|
Fermer
|
||||||
@ -660,6 +691,43 @@ function AgentsTab() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Identity File Editor - Fullscreen via Portal */}
|
||||||
|
{activeFile?.agent === agent.name && isFullscreen && createPortal(
|
||||||
|
<div className="fullscreen-editor-portal">
|
||||||
|
<div className="h-full w-full glass-card flex flex-col overflow-hidden border border-glass-border shadow-[0_40px_80px_rgb(0,0,0,0.9)] min-h-0">
|
||||||
|
<div className="flex items-center justify-between bg-surface-900/80 border-b border-glass-border shrink-0 h-16 px-6 py-4">
|
||||||
|
<span className="text-sm font-mono text-purple-300 truncate pr-4">workspace/{agent.name}/{activeFile.file}</span>
|
||||||
|
<div className="flex gap-2 shrink-0">
|
||||||
|
<button onClick={() => setIsFullscreen(false)} className="px-3 py-1.5 rounded text-xs text-gray-400 hover:bg-surface-700 hover:text-white transition-colors">
|
||||||
|
🗗 Réduire
|
||||||
|
</button>
|
||||||
|
<button onClick={() => { setActiveFile(null); setIsFullscreen(false); }} className="px-3 py-1.5 rounded text-xs text-gray-400 hover:bg-surface-700 hover:text-white transition-colors">
|
||||||
|
Fermer
|
||||||
|
</button>
|
||||||
|
<button onClick={saveFile} disabled={saving} className="px-4 py-1.5 rounded bg-purple-600/80 hover:bg-purple-500 text-white text-xs font-medium transition-colors">
|
||||||
|
{saving ? '...' : 'Sauvegarder'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{fileLoading ? (
|
||||||
|
<div className="flex-1 flex items-center justify-center text-gray-500 text-sm animate-pulse">Chargement…</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex-1 overflow-auto bg-[#282c34] min-h-0">
|
||||||
|
<CodeMirror
|
||||||
|
value={fileContent}
|
||||||
|
height="100%"
|
||||||
|
theme={oneDark}
|
||||||
|
extensions={[markdown()]}
|
||||||
|
onChange={(val) => setFileContent(val)}
|
||||||
|
basicSetup={{ lineNumbers: true, foldGutter: false, highlightActiveLine: true, tabSize: 2 }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>,
|
||||||
|
document.body
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -1153,8 +1221,9 @@ function ConfigFileEditor({ onClose, onSave }: { onClose: () => void, onSave: ()
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return createPortal(
|
||||||
<div className="fullscreen-overlay glass-card flex flex-col overflow-hidden border border-glass-border shadow-[0_40px_80px_rgb(0,0,0,0.9)] backdrop-blur-xl animate-fade-in">
|
<div className="fullscreen-editor-portal">
|
||||||
|
<div className="h-full w-full glass-card flex flex-col overflow-hidden border border-glass-border shadow-[0_40px_80px_rgb(0,0,0,0.9)] backdrop-blur-xl animate-fade-in">
|
||||||
<div className="flex items-center justify-between px-6 py-4 bg-surface-800 border-b border-glass-border shrink-0 h-16">
|
<div className="flex items-center justify-between px-6 py-4 bg-surface-800 border-b border-glass-border shrink-0 h-16">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-amber-500/20 to-orange-600/10 border border-amber-500/20 flex items-center justify-center text-xl">
|
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-amber-500/20 to-orange-600/10 border border-amber-500/20 flex items-center justify-center text-xl">
|
||||||
@ -1209,6 +1278,8 @@ function ConfigFileEditor({ onClose, onSave }: { onClose: () => void, onSave: ()
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>,
|
||||||
|
document.body
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1278,6 +1349,89 @@ function FilesystemTab() {
|
|||||||
return [];
|
return [];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Fullscreen editor via portal
|
||||||
|
if (isFullscreen && selectedFile) {
|
||||||
|
return createPortal(
|
||||||
|
<div className="fullscreen-editor-portal">
|
||||||
|
<div className="h-full w-full glass-card flex flex-col overflow-hidden border border-glass-border shadow-[0_30px_60px_rgb(0,0,0,0.9)] min-h-0">
|
||||||
|
<div className="p-4 bg-surface-800 border-b border-glass-border flex items-center justify-between z-10 shrink-0 h-16 px-6">
|
||||||
|
<div className="min-w-0 pr-4">
|
||||||
|
<h3 className="text-white font-medium flex items-center gap-2 text-base truncate">
|
||||||
|
<span className="text-xl">📄</span> {selectedFile.path.split('/').pop()}
|
||||||
|
</h3>
|
||||||
|
<div className="flex items-center gap-3 mt-1 text-[11px] text-gray-400 font-mono">
|
||||||
|
<span className="truncate max-w-[200px] md:max-w-xs" title={selectedFile.path}>{selectedFile.path}</span>
|
||||||
|
<span className="opacity-50">•</span>
|
||||||
|
<span>{formatSize(selectedFile.size)}</span>
|
||||||
|
<span className="opacity-50">•</span>
|
||||||
|
<span className="uppercase text-purple-300 bg-purple-500/10 px-1.5 py-0.5 rounded">{selectedFile.language}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2 shrink-0">
|
||||||
|
<button
|
||||||
|
onClick={() => setIsFullscreen(false)}
|
||||||
|
className="px-3 py-1.5 rounded-lg bg-surface-700 text-gray-300 text-sm hover:bg-surface-600 transition-colors flex items-center justify-center font-bold"
|
||||||
|
title="Réduire"
|
||||||
|
>
|
||||||
|
🗗
|
||||||
|
</button>
|
||||||
|
{!selectedFile.binary && (
|
||||||
|
<>
|
||||||
|
{editMode ? (
|
||||||
|
<>
|
||||||
|
<button onClick={() => setEditMode(false)} disabled={saving} className="px-3.5 py-1.5 rounded-lg bg-surface-700 text-gray-300 text-sm hover:bg-surface-600 transition-colors">
|
||||||
|
Annuler
|
||||||
|
</button>
|
||||||
|
<button onClick={handleSave} disabled={saving} className={`px-4 py-1.5 rounded-lg bg-gradient-to-r from-emerald-600 to-teal-600 text-white text-sm font-medium hover:from-emerald-500 hover:to-teal-500 transition-all shadow-[0_0_12px_rgba(16,185,129,0.3)] flex items-center gap-2 ${saving ? 'opacity-50 cursor-not-allowed scale-95' : 'hover:scale-105'}`}>
|
||||||
|
{saving ? 'Sauvegarde…' : '💾 Sauvegarder'}
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<button onClick={() => { setEditMode(true); setEditContent(selectedFile.pretty || selectedFile.content || ''); }} className="px-4 py-1.5 rounded-lg bg-gradient-to-r from-purple-600 to-indigo-600 text-white text-sm font-medium hover:from-purple-500 hover:to-indigo-500 transition-all shadow-[0_0_12px_rgba(147,51,234,0.3)] hover:scale-105 flex items-center gap-2">
|
||||||
|
<span>✏️</span> Modifier
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex-1 overflow-auto bg-[#282c34] min-h-0">
|
||||||
|
{selectedFile.binary ? (
|
||||||
|
<div className="flex items-center justify-center text-gray-500 bg-surface-900/60 h-full">
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="text-5xl mb-3 opacity-30">📦</div>
|
||||||
|
<p className="tracking-wide">Fichier binaire. Aperçu non disponible.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : editMode ? (
|
||||||
|
<CodeMirror
|
||||||
|
value={editContent}
|
||||||
|
height="100%"
|
||||||
|
theme={oneDark}
|
||||||
|
extensions={getExtensions()}
|
||||||
|
onChange={(val) => setEditContent(val)}
|
||||||
|
basicSetup={{
|
||||||
|
lineNumbers: true,
|
||||||
|
foldGutter: true,
|
||||||
|
highlightActiveLine: true,
|
||||||
|
tabSize: 2,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="p-4">
|
||||||
|
<pre className="text-[13px] text-[#abb2bf] font-mono whitespace-pre-wrap leading-relaxed">
|
||||||
|
{selectedFile.pretty || selectedFile.content}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>,
|
||||||
|
document.body
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex gap-6 h-[calc(100vh-250px)] min-h-[600px]">
|
<div className="flex gap-6 h-[calc(100vh-250px)] min-h-[600px]">
|
||||||
{/* Sidebar Tree */}
|
{/* Sidebar Tree */}
|
||||||
@ -1303,11 +1457,9 @@ function FilesystemTab() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Editor Panel */}
|
{/* Editor Panel - hidden when in fullscreen */}
|
||||||
<div className={isFullscreen
|
{!isFullscreen && (
|
||||||
? "fullscreen-overlay glass-card flex flex-col overflow-hidden border border-glass-border shadow-[0_30px_60px_rgb(0,0,0,0.9)] min-h-0"
|
<div className="glass-card w-2/3 flex flex-col overflow-hidden border border-glass-border shadow-[0_8px_30px_rgb(0,0,0,0.5)] min-h-0">
|
||||||
: "glass-card w-2/3 flex flex-col overflow-hidden border border-glass-border shadow-[0_8px_30px_rgb(0,0,0,0.5)] min-h-0"
|
|
||||||
}>
|
|
||||||
{fileLoading ? (
|
{fileLoading ? (
|
||||||
<div className="flex-1 flex items-center justify-center bg-surface-900/40">
|
<div className="flex-1 flex items-center justify-center bg-surface-900/40">
|
||||||
<div className="text-gray-400 flex flex-col items-center gap-3">
|
<div className="text-gray-400 flex flex-col items-center gap-3">
|
||||||
@ -1323,7 +1475,7 @@ function FilesystemTab() {
|
|||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col h-full">
|
<div className="flex flex-col h-full">
|
||||||
{/* Editor Header */}
|
{/* Editor Header */}
|
||||||
<div className={`p-4 bg-surface-800 border-b border-glass-border flex items-center justify-between z-10 shrink-0 ${isFullscreen ? 'h-16 px-6' : ''}`}>
|
<div className="p-4 bg-surface-800 border-b border-glass-border flex items-center justify-between z-10 shrink-0">
|
||||||
<div className="min-w-0 pr-4">
|
<div className="min-w-0 pr-4">
|
||||||
<h3 className="text-white font-medium flex items-center gap-2 text-base truncate">
|
<h3 className="text-white font-medium flex items-center gap-2 text-base truncate">
|
||||||
<span className="text-xl">📄</span> {selectedFile.path.split('/').pop()}
|
<span className="text-xl">📄</span> {selectedFile.path.split('/').pop()}
|
||||||
@ -1338,11 +1490,11 @@ function FilesystemTab() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2 shrink-0">
|
<div className="flex gap-2 shrink-0">
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsFullscreen(!isFullscreen)}
|
onClick={() => setIsFullscreen(true)}
|
||||||
className="px-3 py-1.5 rounded-lg bg-surface-700 text-gray-300 text-sm hover:bg-surface-600 transition-colors flex items-center justify-center font-bold"
|
className="px-3 py-1.5 rounded-lg bg-surface-700 text-gray-300 text-sm hover:bg-surface-600 transition-colors flex items-center justify-center font-bold"
|
||||||
title={isFullscreen ? "Réduire" : "Plein écran"}
|
title="Plein écran"
|
||||||
>
|
>
|
||||||
{isFullscreen ? "🗗" : "⛶"}
|
⛶
|
||||||
</button>
|
</button>
|
||||||
{!selectedFile.binary && (
|
{!selectedFile.binary && (
|
||||||
<>
|
<>
|
||||||
@ -1399,6 +1551,7 @@ function FilesystemTab() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user