feat: VWB panneau droit réorganisé en 3 onglets + galerie bibliothèque
- 3 onglets : Propriétés / Capture / Données - Panneau extensible 320px → 480px au clic - Galerie bibliothèque plein écran - Fix port détection UI : 5001 → 5002 - Boutons aide (?) et supprimer (×) toujours visibles sur les nœuds Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -14,19 +14,16 @@ import '@xyflow/react/dist/style.css';
|
|||||||
|
|
||||||
import * as api from './services/api';
|
import * as api from './services/api';
|
||||||
import type { AppState, Step, ActionType, Capture, ExecutionMode } from './types';
|
import type { AppState, Step, ActionType, Capture, ExecutionMode } from './types';
|
||||||
import { ACTIONS, EXECUTION_MODES } from './types';
|
// Types importés via les sous-composants
|
||||||
import StepNode from './components/StepNode';
|
import StepNode from './components/StepNode';
|
||||||
import ToolPalette from './components/ToolPalette';
|
import ToolPalette from './components/ToolPalette';
|
||||||
import PropertiesPanel from './components/PropertiesPanel';
|
|
||||||
import CapturePanel from './components/CapturePanel';
|
|
||||||
import WorkflowSelector from './components/WorkflowSelector';
|
import WorkflowSelector from './components/WorkflowSelector';
|
||||||
import WorkflowManagerModal from './components/WorkflowManagerModal';
|
import WorkflowManagerModal from './components/WorkflowManagerModal';
|
||||||
import ExecutionControls from './components/ExecutionControls';
|
import ExecutionControls from './components/ExecutionControls';
|
||||||
import ExecutionModeToggle from './components/ExecutionModeToggle';
|
import ExecutionModeToggle from './components/ExecutionModeToggle';
|
||||||
import ExecutionOverlay from './components/ExecutionOverlay';
|
import ExecutionOverlay from './components/ExecutionOverlay';
|
||||||
import VariableManager from './components/VariableManager';
|
|
||||||
import type { Variable } from './components/VariableManager';
|
import type { Variable } from './components/VariableManager';
|
||||||
import CaptureLibrary from './components/CaptureLibrary';
|
import RightPanel from './components/RightPanel';
|
||||||
import SelfHealingDialog from './components/SelfHealingDialog';
|
import SelfHealingDialog from './components/SelfHealingDialog';
|
||||||
import ConfidenceDashboard from './components/ConfidenceDashboard';
|
import ConfidenceDashboard from './components/ConfidenceDashboard';
|
||||||
import WorkflowValidation from './components/WorkflowValidation';
|
import WorkflowValidation from './components/WorkflowValidation';
|
||||||
@@ -494,14 +491,11 @@ function App() {
|
|||||||
)}
|
)}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
{/* Sidebar droite: Propriétés + Capture + Variables */}
|
{/* Sidebar droite: Panneau à onglets (Propriétés / Capture / Données) */}
|
||||||
<aside className="sidebar right">
|
<RightPanel
|
||||||
<PropertiesPanel
|
selectedStep={selectedStep || null}
|
||||||
step={selectedStep || null}
|
onUpdateStepParams={handleUpdateStepParams}
|
||||||
onUpdateParams={handleUpdateStepParams}
|
onDeleteStep={handleDeleteStep}
|
||||||
onDelete={handleDeleteStep}
|
|
||||||
/>
|
|
||||||
<CapturePanel
|
|
||||||
capture={capture}
|
capture={capture}
|
||||||
onCapture={handleCapture}
|
onCapture={handleCapture}
|
||||||
onSelectAnchor={handleSelectAnchor}
|
onSelectAnchor={handleSelectAnchor}
|
||||||
@@ -509,13 +503,8 @@ function App() {
|
|||||||
executionMode={executionMode}
|
executionMode={executionMode}
|
||||||
detectionZone={detectionZone}
|
detectionZone={detectionZone}
|
||||||
onSetDetectionZone={setDetectionZone}
|
onSetDetectionZone={setDetectionZone}
|
||||||
/>
|
|
||||||
<CaptureLibrary
|
|
||||||
currentCapture={currentCapture}
|
currentCapture={currentCapture}
|
||||||
onSelectCapture={handleSelectCaptureFromLibrary}
|
onSelectCaptureFromLibrary={handleSelectCaptureFromLibrary}
|
||||||
onCapture={handleCapture}
|
|
||||||
/>
|
|
||||||
<VariableManager
|
|
||||||
variables={variables}
|
variables={variables}
|
||||||
onVariableCreate={handleVariableCreate}
|
onVariableCreate={handleVariableCreate}
|
||||||
onVariableUpdate={handleVariableUpdate}
|
onVariableUpdate={handleVariableUpdate}
|
||||||
@@ -523,13 +512,6 @@ function App() {
|
|||||||
steps={appState?.workflow?.steps || []}
|
steps={appState?.workflow?.steps || []}
|
||||||
runtimeVariables={runtimeVariables}
|
runtimeVariables={runtimeVariables}
|
||||||
/>
|
/>
|
||||||
</aside>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Indicateur de mode flottant */}
|
|
||||||
<div className={`mode-indicator ${executionMode}`}>
|
|
||||||
<span>{EXECUTION_MODES[executionMode].icon}</span>
|
|
||||||
<span>Mode {EXECUTION_MODES[executionMode].label}</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Overlay de debug en temps réel */}
|
{/* Overlay de debug en temps réel */}
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ export default function CapturePanel({
|
|||||||
onSetDetectionZone
|
onSetDetectionZone
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const [isFullscreen, setIsFullscreen] = useState(false);
|
const [isFullscreen, setIsFullscreen] = useState(false);
|
||||||
|
const [showLibraryGallery, setShowLibraryGallery] = useState(false);
|
||||||
const [library, setLibrary] = useState<LibraryItem[]>([]);
|
const [library, setLibrary] = useState<LibraryItem[]>([]);
|
||||||
const [currentCapture, setCurrentCapture] = useState<Capture | null>(null);
|
const [currentCapture, setCurrentCapture] = useState<Capture | null>(null);
|
||||||
const [timerSeconds, setTimerSeconds] = useState(0);
|
const [timerSeconds, setTimerSeconds] = useState(0);
|
||||||
@@ -133,6 +134,7 @@ export default function CapturePanel({
|
|||||||
|
|
||||||
const handleLibrarySelect = (item: LibraryItem) => {
|
const handleLibrarySelect = (item: LibraryItem) => {
|
||||||
setCurrentCapture(item.capture);
|
setCurrentCapture(item.capture);
|
||||||
|
setIsFullscreen(true); // Ouvrir en plein écran pour sélectionner
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteLibraryItem = (id: string) => {
|
const handleDeleteLibraryItem = (id: string) => {
|
||||||
@@ -220,9 +222,12 @@ export default function CapturePanel({
|
|||||||
|
|
||||||
{/* Bibliothèque */}
|
{/* Bibliothèque */}
|
||||||
<div className="capture-library">
|
<div className="capture-library">
|
||||||
<h4>Bibliothèque ({library.length})</h4>
|
<h4 style={{ cursor: 'pointer' }} onClick={() => library.length > 0 && setShowLibraryGallery(true)}>
|
||||||
|
Bibliothèque ({library.length})
|
||||||
|
{library.length > 0 && <span style={{ fontSize: '11px', marginLeft: '8px', color: 'var(--primary)' }}>▸ Ouvrir</span>}
|
||||||
|
</h4>
|
||||||
<div className="library-grid">
|
<div className="library-grid">
|
||||||
{library.map(item => (
|
{library.slice(0, 4).map(item => (
|
||||||
<div key={item.id} className="library-item">
|
<div key={item.id} className="library-item">
|
||||||
<img
|
<img
|
||||||
src={`data:image/png;base64,${item.capture.screenshot_base64}`}
|
src={`data:image/png;base64,${item.capture.screenshot_base64}`}
|
||||||
@@ -237,9 +242,47 @@ export default function CapturePanel({
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
{library.length > 4 && (
|
||||||
|
<button
|
||||||
|
className="library-show-all"
|
||||||
|
onClick={() => setShowLibraryGallery(true)}
|
||||||
|
>
|
||||||
|
+{library.length - 4} autres
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Galerie plein écran de la bibliothèque */}
|
||||||
|
{showLibraryGallery && (
|
||||||
|
<div className="fullscreen-modal">
|
||||||
|
<div className="fullscreen-header">
|
||||||
|
<span>Bibliothèque — {library.length} captures — Cliquez pour sélectionner</span>
|
||||||
|
<button onClick={() => setShowLibraryGallery(false)}>Fermer (Échap)</button>
|
||||||
|
</div>
|
||||||
|
<div className="library-gallery-grid">
|
||||||
|
{library.map(item => (
|
||||||
|
<div
|
||||||
|
key={item.id}
|
||||||
|
className="library-gallery-item"
|
||||||
|
onClick={() => {
|
||||||
|
setShowLibraryGallery(false);
|
||||||
|
handleLibrarySelect(item);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={`data:image/png;base64,${item.capture.screenshot_base64}`}
|
||||||
|
alt="Capture"
|
||||||
|
/>
|
||||||
|
<div className="library-gallery-label">
|
||||||
|
{new Date(item.timestamp).toLocaleTimeString('fr-FR')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Modal plein écran */}
|
{/* Modal plein écran */}
|
||||||
{isFullscreen && currentCapture && (
|
{isFullscreen && currentCapture && (
|
||||||
<FullscreenSelector
|
<FullscreenSelector
|
||||||
|
|||||||
@@ -0,0 +1,201 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
/**
|
||||||
|
* Panneau droit à onglets pour le VWB
|
||||||
|
* 3 onglets : Propriétés, Capture, Données
|
||||||
|
* Panneau redimensionnable : 320px par défaut, 480px quand actif
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useState, useEffect, useRef, useCallback } from 'react';
|
||||||
|
import type { Step, Capture, ExecutionMode } from '../types';
|
||||||
|
import PropertiesPanel from './PropertiesPanel';
|
||||||
|
import CapturePanel from './CapturePanel';
|
||||||
|
import CaptureLibrary from './CaptureLibrary';
|
||||||
|
import VariableManager from './VariableManager';
|
||||||
|
import type { Variable } from './VariableManager';
|
||||||
|
|
||||||
|
type TabId = 'properties' | 'capture' | 'data';
|
||||||
|
|
||||||
|
interface Tab {
|
||||||
|
id: TabId;
|
||||||
|
label: string;
|
||||||
|
icon: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TABS: Tab[] = [
|
||||||
|
{ id: 'properties', label: 'Propriétés', icon: '⚙' },
|
||||||
|
{ id: 'capture', label: 'Capture', icon: '📷' },
|
||||||
|
{ id: 'data', label: 'Données', icon: '📊' },
|
||||||
|
];
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
// Propriétés
|
||||||
|
selectedStep: Step | null;
|
||||||
|
onUpdateStepParams: (id: string, params: Record<string, unknown>) => void;
|
||||||
|
onDeleteStep: (id: string) => void;
|
||||||
|
// Capture
|
||||||
|
capture: Capture | null;
|
||||||
|
onCapture: () => void;
|
||||||
|
onSelectAnchor: (bbox: { x: number; y: number; width: number; height: number }, screenshotBase64?: string) => void;
|
||||||
|
hasSelectedStep: boolean;
|
||||||
|
executionMode: ExecutionMode;
|
||||||
|
detectionZone: { x: number; y: number; width: number; height: number } | null;
|
||||||
|
onSetDetectionZone: (zone: { x: number; y: number; width: number; height: number } | null) => void;
|
||||||
|
// Bibliothèque
|
||||||
|
currentCapture: Capture | null;
|
||||||
|
onSelectCaptureFromLibrary: (capture: Capture) => void;
|
||||||
|
// Variables
|
||||||
|
variables: Variable[];
|
||||||
|
onVariableCreate: (data: Omit<Variable, 'id'>) => void;
|
||||||
|
onVariableUpdate: (id: string, data: Partial<Variable>) => void;
|
||||||
|
onVariableDelete: (id: string) => void;
|
||||||
|
steps: Step[];
|
||||||
|
runtimeVariables: Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function RightPanel({
|
||||||
|
selectedStep,
|
||||||
|
onUpdateStepParams,
|
||||||
|
onDeleteStep,
|
||||||
|
capture,
|
||||||
|
onCapture,
|
||||||
|
onSelectAnchor,
|
||||||
|
hasSelectedStep,
|
||||||
|
executionMode,
|
||||||
|
detectionZone,
|
||||||
|
onSetDetectionZone,
|
||||||
|
currentCapture,
|
||||||
|
onSelectCaptureFromLibrary,
|
||||||
|
variables,
|
||||||
|
onVariableCreate,
|
||||||
|
onVariableUpdate,
|
||||||
|
onVariableDelete,
|
||||||
|
steps,
|
||||||
|
runtimeVariables,
|
||||||
|
}: Props) {
|
||||||
|
const [activeTab, setActiveTab] = useState<TabId>('properties');
|
||||||
|
const [isExpanded, setIsExpanded] = useState(false);
|
||||||
|
const panelRef = useRef<HTMLElement>(null);
|
||||||
|
|
||||||
|
// Basculer sur l'onglet Propriétés quand une étape est sélectionnée
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedStep) {
|
||||||
|
setActiveTab('properties');
|
||||||
|
setIsExpanded(true);
|
||||||
|
}
|
||||||
|
}, [selectedStep?.id]);
|
||||||
|
|
||||||
|
// Basculer sur l'onglet Capture quand une nouvelle capture arrive
|
||||||
|
useEffect(() => {
|
||||||
|
if (capture) {
|
||||||
|
setActiveTab('capture');
|
||||||
|
setIsExpanded(true);
|
||||||
|
}
|
||||||
|
}, [capture]);
|
||||||
|
|
||||||
|
// Clic en dehors du panneau pour replier
|
||||||
|
const handleClickOutside = useCallback((e: MouseEvent) => {
|
||||||
|
if (panelRef.current && !panelRef.current.contains(e.target as Node)) {
|
||||||
|
setIsExpanded(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.addEventListener('mousedown', handleClickOutside);
|
||||||
|
return () => document.removeEventListener('mousedown', handleClickOutside);
|
||||||
|
}, [handleClickOutside]);
|
||||||
|
|
||||||
|
// Clic sur un onglet
|
||||||
|
const handleTabClick = (tabId: TabId) => {
|
||||||
|
if (activeTab === tabId && isExpanded) {
|
||||||
|
// Re-clic sur le même onglet : replier
|
||||||
|
setIsExpanded(false);
|
||||||
|
} else {
|
||||||
|
setActiveTab(tabId);
|
||||||
|
setIsExpanded(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Rendu du contenu de l'onglet actif
|
||||||
|
const renderTabContent = () => {
|
||||||
|
switch (activeTab) {
|
||||||
|
case 'properties':
|
||||||
|
return (
|
||||||
|
<div className="tab-content-inner">
|
||||||
|
<PropertiesPanel
|
||||||
|
step={selectedStep}
|
||||||
|
onUpdateParams={onUpdateStepParams}
|
||||||
|
onDelete={onDeleteStep}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'capture':
|
||||||
|
return (
|
||||||
|
<div className="tab-content-inner">
|
||||||
|
<CapturePanel
|
||||||
|
capture={capture}
|
||||||
|
onCapture={onCapture}
|
||||||
|
onSelectAnchor={onSelectAnchor}
|
||||||
|
hasSelectedStep={hasSelectedStep}
|
||||||
|
executionMode={executionMode}
|
||||||
|
detectionZone={detectionZone}
|
||||||
|
onSetDetectionZone={onSetDetectionZone}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
case 'data':
|
||||||
|
return (
|
||||||
|
<div className="tab-content-inner">
|
||||||
|
<VariableManager
|
||||||
|
variables={variables}
|
||||||
|
onVariableCreate={onVariableCreate}
|
||||||
|
onVariableUpdate={onVariableUpdate}
|
||||||
|
onVariableDelete={onVariableDelete}
|
||||||
|
steps={steps}
|
||||||
|
runtimeVariables={runtimeVariables}
|
||||||
|
/>
|
||||||
|
<CaptureLibrary
|
||||||
|
currentCapture={currentCapture}
|
||||||
|
onSelectCapture={onSelectCaptureFromLibrary}
|
||||||
|
onCapture={onCapture}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<aside
|
||||||
|
ref={panelRef}
|
||||||
|
className={`sidebar right right-panel-tabbed ${isExpanded ? 'expanded' : 'collapsed'}`}
|
||||||
|
onClick={() => !isExpanded && setIsExpanded(true)}
|
||||||
|
>
|
||||||
|
{/* Onglets en haut */}
|
||||||
|
<div className="right-panel-tabs">
|
||||||
|
{TABS.map((tab) => (
|
||||||
|
<button
|
||||||
|
key={tab.id}
|
||||||
|
className={`right-panel-tab ${activeTab === tab.id ? 'active' : ''}`}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleTabClick(tab.id);
|
||||||
|
}}
|
||||||
|
title={tab.label}
|
||||||
|
>
|
||||||
|
<span className="tab-icon">{tab.icon}</span>
|
||||||
|
<span className="tab-label">{tab.label}</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Contenu de l'onglet actif */}
|
||||||
|
<div className="right-panel-content">
|
||||||
|
{renderTabContent()}
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// @ts-nocheck
|
||||||
import { memo, useState, useEffect, useRef } from 'react';
|
import { memo, useState, useEffect, useRef } from 'react';
|
||||||
import { Handle, Position } from '@xyflow/react';
|
import { Handle, Position } from '@xyflow/react';
|
||||||
import type { Step } from '../types';
|
import type { Step } from '../types';
|
||||||
@@ -34,8 +35,8 @@ function StepNode({ data, selected }: StepNodeProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`step-node ${selected ? 'selected' : ''} ${isConditional ? 'conditional' : ''} ${isDataLoop ? 'data-loop' : ''} ${isImport ? 'data-import' : ''}`}>
|
<div className={`step-node ${selected ? 'selected' : ''} ${isConditional ? 'conditional' : ''} ${isDataLoop ? 'data-loop' : ''} ${isImport ? 'data-import' : ''}`}>
|
||||||
{/* Bouton aide (?) — toujours visible quand sélectionné */}
|
{/* Bouton aide (?) */}
|
||||||
{selected && action && (
|
{action && (
|
||||||
<button
|
<button
|
||||||
className="step-node-help"
|
className="step-node-help"
|
||||||
title="Documentation de l'outil"
|
title="Documentation de l'outil"
|
||||||
@@ -75,7 +76,7 @@ function StepNode({ data, selected }: StepNodeProps) {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Bouton supprimer */}
|
{/* Bouton supprimer */}
|
||||||
{selected && (
|
{(
|
||||||
<button
|
<button
|
||||||
className="step-node-delete"
|
className="step-node-delete"
|
||||||
title="Supprimer (Suppr)"
|
title="Supprimer (Suppr)"
|
||||||
@@ -94,6 +95,7 @@ function StepNode({ data, selected }: StepNodeProps) {
|
|||||||
position={Position.Top}
|
position={Position.Top}
|
||||||
id="top"
|
id="top"
|
||||||
className="handle-top"
|
className="handle-top"
|
||||||
|
isConnectable={true}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="step-node-header">
|
<div className="step-node-header">
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
* Service de détection UI (UI-DETR-1)
|
* Service de détection UI (UI-DETR-1)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Utilise l'hostname actuel pour permettre l'accès réseau
|
// VWB backend (port 5002) — contient le screen capturer et la détection UI
|
||||||
const API_BASE = `http://${window.location.hostname}:5001`;
|
const API_BASE = `http://${window.location.hostname}:5002`;
|
||||||
|
|
||||||
export interface UIElement {
|
export interface UIElement {
|
||||||
id: number;
|
id: number;
|
||||||
|
|||||||
@@ -96,6 +96,21 @@ body {
|
|||||||
border-left: 1px solid var(--border);
|
border-left: 1px solid var(--border);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Panneau droit à onglets — redimensionnement dynamique */
|
||||||
|
.sidebar.right.right-panel-tabbed {
|
||||||
|
width: 320px;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar.right.right-panel-tabbed.expanded {
|
||||||
|
width: 480px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar.right.right-panel-tabbed.collapsed {
|
||||||
|
width: 320px;
|
||||||
|
}
|
||||||
|
|
||||||
.sidebar h3, .sidebar h4 {
|
.sidebar h3, .sidebar h4 {
|
||||||
padding: 0.75rem 1rem;
|
padding: 0.75rem 1rem;
|
||||||
background: var(--bg-sidebar);
|
background: var(--bg-sidebar);
|
||||||
@@ -4225,3 +4240,148 @@ body {
|
|||||||
background: var(--bg-sidebar);
|
background: var(--bg-sidebar);
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Galerie bibliothèque plein écran */
|
||||||
|
.library-gallery-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||||
|
gap: 16px;
|
||||||
|
padding: 20px;
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: calc(100vh - 60px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-gallery-item {
|
||||||
|
cursor: pointer;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: border-color 0.2s, transform 0.2s;
|
||||||
|
background: var(--bg-paper);
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-gallery-item:hover {
|
||||||
|
border-color: var(--primary);
|
||||||
|
transform: scale(1.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-gallery-item img {
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-gallery-label {
|
||||||
|
padding: 8px 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
text-align: center;
|
||||||
|
background: var(--bg-default);
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-show-all {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: var(--bg-default);
|
||||||
|
border: 2px dashed var(--border);
|
||||||
|
border-radius: 6px;
|
||||||
|
color: var(--primary);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 13px;
|
||||||
|
min-height: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.library-show-all:hover {
|
||||||
|
background: var(--bg-paper);
|
||||||
|
border-color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
Panneau droit à onglets (RightPanel)
|
||||||
|
=========================================== */
|
||||||
|
|
||||||
|
/* Barre d'onglets en haut du panneau droit */
|
||||||
|
.right-panel-tabs {
|
||||||
|
display: flex;
|
||||||
|
background: var(--bg-sidebar);
|
||||||
|
border-bottom: 2px solid var(--border);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-panel-tab {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.35rem;
|
||||||
|
padding: 0.6rem 0.5rem;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
position: relative;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-panel-tab:hover {
|
||||||
|
color: var(--primary);
|
||||||
|
background: rgba(25, 118, 210, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-panel-tab.active {
|
||||||
|
color: var(--primary);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bordure inférieure colorée sur l'onglet actif */
|
||||||
|
.right-panel-tab.active::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: -2px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 2px;
|
||||||
|
background: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-panel-tab .tab-icon {
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-panel-tab .tab-label {
|
||||||
|
font-size: 0.78rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Zone de contenu scrollable */
|
||||||
|
.right-panel-content {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-content-inner {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ajustements des sous-composants dans le panneau à onglets */
|
||||||
|
.right-panel-tabbed .properties-panel {
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-panel-tabbed .capture-panel {
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-panel-tabbed .variable-manager {
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-panel-tabbed .capture-library {
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user