VirtualLabo / src /components /lab /WorkspaceManager.tsx
rinogeek's picture
Initial commit: Virtual Labo Chimique - Docker deployment
538d81e
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Plus, X, Star, Pencil, Check, Undo, Redo, Upload, Download, FileJson, FlaskConical } from "lucide-react";
import { cn } from "@/lib/utils";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, DropdownMenuSeparator, DropdownMenuLabel } from "@/components/ui/dropdown-menu";
import { ExperimentTemplate } from "@/data/experimentTemplates";
export interface Workspace {
id: string;
name: string;
canvasData: string | null;
isFavorite?: boolean;
history?: string[];
historyIndex?: number;
}
interface WorkspaceManagerProps {
workspaces: Workspace[];
activeWorkspaceId: string;
onWorkspaceChange: (id: string) => void;
onWorkspaceAdd: () => void;
onWorkspaceDelete: (id: string) => void;
onWorkspaceRename: (id: string, name: string) => void;
onWorkspaceFavorite: (id: string) => void;
onUndo?: () => void;
onRedo?: () => void;
canUndo?: boolean;
canRedo?: boolean;
onImport?: () => void;
onExport?: () => void;
onLoadTemplate?: (templateId: string) => void;
templates?: ExperimentTemplate[];
}
const WorkspaceManager = ({
workspaces,
activeWorkspaceId,
onWorkspaceChange,
onWorkspaceAdd,
onWorkspaceDelete,
onWorkspaceRename,
onWorkspaceFavorite,
onUndo,
onRedo,
canUndo = false,
canRedo = false,
onImport,
onExport,
onLoadTemplate,
templates = [],
}: WorkspaceManagerProps) => {
const [editingId, setEditingId] = useState<string | null>(null);
const [editingName, setEditingName] = useState("");
// Sort workspaces: favorites first, then by order
const sortedWorkspaces = [...workspaces].sort((a, b) => {
if (a.isFavorite && !b.isFavorite) return -1;
if (!a.isFavorite && b.isFavorite) return 1;
return 0;
});
const handleStartEditing = (e: React.MouseEvent, workspace: Workspace) => {
e.stopPropagation();
setEditingId(workspace.id);
setEditingName(workspace.name);
};
const handleFinishEditing = () => {
if (editingId && editingName.trim()) {
onWorkspaceRename(editingId, editingName.trim());
}
setEditingId(null);
setEditingName("");
};
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === "Enter") {
handleFinishEditing();
} else if (e.key === "Escape") {
setEditingId(null);
setEditingName("");
}
};
return (
<div className="flex items-center gap-2 bg-card border border-border rounded-lg p-2">
{/* Undo/Redo buttons */}
<div className="flex gap-1 flex-shrink-0">
<Button
onClick={onUndo}
size="sm"
variant="ghost"
disabled={!canUndo}
title="Annuler (Ctrl+Z)"
className="h-8 w-8 p-0"
>
<Undo className="w-4 h-4" />
</Button>
<Button
onClick={onRedo}
size="sm"
variant="ghost"
disabled={!canRedo}
title="Refaire (Ctrl+Y)"
className="h-8 w-8 p-0"
>
<Redo className="w-4 h-4" />
</Button>
</div>
<div className="flex-1 overflow-x-auto">
<div className="flex gap-1 min-w-max">
{sortedWorkspaces.map((workspace) => (
<div
key={workspace.id}
className={cn(
"flex items-center gap-1.5 px-3 py-1.5 rounded-md transition-all cursor-pointer group",
activeWorkspaceId === workspace.id
? "bg-primary text-primary-foreground"
: "bg-muted hover:bg-muted/80"
)}
onClick={() => onWorkspaceChange(workspace.id)}
>
{/* Favorite button */}
<button
onClick={(e) => {
e.stopPropagation();
onWorkspaceFavorite(workspace.id);
}}
className={cn(
"transition-opacity",
workspace.isFavorite ? "opacity-100" : "opacity-0 group-hover:opacity-60"
)}
title={workspace.isFavorite ? "Retirer des favoris" : "Ajouter aux favoris"}
>
<Star
className={cn(
"w-3.5 h-3.5",
workspace.isFavorite && "fill-current"
)}
/>
</button>
{/* Name or input for editing */}
{editingId === workspace.id ? (
<div className="flex items-center gap-1" onClick={(e) => e.stopPropagation()}>
<Input
value={editingName}
onChange={(e) => setEditingName(e.target.value)}
onKeyDown={handleKeyDown}
onBlur={handleFinishEditing}
className="h-6 w-32 text-sm px-2"
autoFocus
/>
<button
onClick={handleFinishEditing}
className="hover:bg-primary-foreground/20 rounded p-0.5"
>
<Check className="w-3 h-3" />
</button>
</div>
) : (
<>
<span className="text-sm font-medium">{workspace.name}</span>
{/* Edit button */}
<button
onClick={(e) => handleStartEditing(e, workspace)}
className={cn(
"opacity-0 group-hover:opacity-100 transition-opacity",
activeWorkspaceId === workspace.id && "hover:bg-primary-foreground/20 rounded p-0.5"
)}
title="Renommer"
>
<Pencil className="w-3 h-3" />
</button>
</>
)}
{/* Delete button */}
{workspaces.length > 1 && editingId !== workspace.id && (
<button
onClick={(e) => {
e.stopPropagation();
onWorkspaceDelete(workspace.id);
}}
className={cn(
"opacity-0 group-hover:opacity-100 transition-opacity",
activeWorkspaceId === workspace.id && "hover:bg-primary-foreground/20 rounded p-0.5"
)}
title="Supprimer"
>
<X className="w-3 h-3" />
</button>
)}
</div>
))}
</div>
</div>
{/* Action buttons */}
<div className="flex gap-1 flex-shrink-0">
{/* Templates dropdown */}
{templates.length > 0 && onLoadTemplate && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button size="sm" variant="outline" title="Charger un modèle">
<FlaskConical className="w-4 h-4 mr-1" />
Modèles
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-64">
<DropdownMenuLabel>Modèles d'expériences</DropdownMenuLabel>
<DropdownMenuSeparator />
{templates.map((template) => (
<DropdownMenuItem
key={template.id}
onClick={() => onLoadTemplate(template.id)}
className="flex flex-col items-start"
>
<span className="font-medium">{template.name}</span>
<span className="text-xs text-muted-foreground">{template.description}</span>
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
)}
{/* Import/Export buttons */}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button size="sm" variant="outline" title="Importer/Exporter">
<FileJson className="w-4 h-4 mr-1" />
Fichier
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{onImport && (
<DropdownMenuItem onClick={onImport}>
<Upload className="w-4 h-4 mr-2" />
Importer JSON
</DropdownMenuItem>
)}
{onExport && (
<DropdownMenuItem onClick={onExport}>
<Download className="w-4 h-4 mr-2" />
Exporter JSON
</DropdownMenuItem>
)}
</DropdownMenuContent>
</DropdownMenu>
<Button
onClick={onWorkspaceAdd}
size="sm"
variant="outline"
className="flex-shrink-0"
>
<Plus className="w-4 h-4 mr-1" />
Nouveau
</Button>
</div>
</div>
);
};
export default WorkspaceManager;