feat: improve all connection types

This commit is contained in:
LukeGus
2026-05-13 17:44:13 -05:00
parent 84a8dfa050
commit 5bf59f87c0
38 changed files with 2661 additions and 952 deletions
+35 -17
View File
@@ -562,8 +562,6 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
currentLoadingPathRef.current = resolvedPath;
setIsLoading(true);
setCreateIntent(null);
try {
const response = await listSSHFiles(sshSessionId, resolvedPath);
@@ -660,9 +658,17 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
return false;
} else if (initialLoadDoneRef.current) {
toast.error(
t("fileManager.failedToLoadDirectory") + ": " + errorMessage,
);
const isPermissionDenied =
httpStatus === 403 ||
errorMessage?.toLowerCase().includes("permission denied") ||
errorMessage?.toLowerCase().includes("eacces");
if (isPermissionDenied) {
toast.error(t("fileManager.permissionDenied"));
} else {
toast.error(
t("fileManager.failedToLoadDirectory") + ": " + errorMessage,
);
}
}
}
return false;
@@ -1079,14 +1085,12 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
t("fileManager.newFolderDefault"),
"directory",
);
const newCreateIntent = {
setCreateIntent({
id: Date.now().toString(),
type: "directory" as const,
defaultName,
currentName: defaultName,
};
setCreateIntent(newCreateIntent);
});
}
function handleCreateNewFile() {
@@ -1094,13 +1098,12 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
t("fileManager.newFileDefault"),
"file",
);
const newCreateIntent = {
setCreateIntent({
id: Date.now().toString(),
type: "file" as const,
defaultName,
currentName: defaultName,
};
setCreateIntent(newCreateIntent);
});
}
const handleSymlinkClick = async (file: FileItem) => {
@@ -2442,7 +2445,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
}
return (
<div className="h-full flex flex-col bg-background relative">
<div className="h-full flex flex-col bg-background relative overflow-hidden isolate">
<div
className="h-full w-full flex flex-col"
style={{
@@ -2617,16 +2620,21 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
<DropdownMenuContent
align="end"
className="w-44 rounded-none border-border bg-card"
onCloseAutoFocus={(e) => e.preventDefault()}
>
<DropdownMenuItem
onClick={handleCreateNewFolder}
onSelect={() => {
setTimeout(() => handleCreateNewFolder(), 0);
}}
className="rounded-none text-xs font-semibold gap-2 focus:bg-accent-brand/10 focus:text-accent-brand"
>
<FolderPlus className="size-4 text-accent-brand" />
{t("fileManager.newFolder")}
</DropdownMenuItem>
<DropdownMenuItem
onClick={handleCreateNewFile}
onSelect={() => {
setTimeout(() => handleCreateNewFile(), 0);
}}
className="rounded-none text-xs font-semibold gap-2 focus:bg-accent-brand/10 focus:text-accent-brand"
>
<FilePlus className="size-4 text-muted-foreground" />
@@ -2767,6 +2775,7 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
onSelectionChange={setSelection}
currentPath={currentPath}
isLoading={isLoading}
isConnected={!!sshSessionId}
onPathChange={navigateTo}
onRefresh={handleRefreshDirectory}
onUpload={handleFilesDropped}
@@ -2780,7 +2789,11 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
setSortOrder("asc");
}
}}
onDownload={(files) => files.forEach(handleDownloadFile)}
onDownload={(files) =>
files
.filter((f) => f.type === "file")
.forEach(handleDownloadFile)
}
onContextMenu={handleContextMenu}
viewMode={viewMode}
onRename={handleRenameConfirm}
@@ -2812,7 +2825,12 @@ function FileManagerContent({ initialHost, onClose }: FileManagerProps) {
onClose={() =>
setContextMenu((prev) => ({ ...prev, isVisible: false }))
}
onDownload={(files) => files.forEach(handleDownloadFile)}
onDownload={(files) =>
files
.filter((f) => f.type === "file")
.forEach(handleDownloadFile)
}
onPreview={handleFileOpen}
onRename={handleRenameFile}
onCopy={handleCopyFiles}
onCut={handleCutFiles}
@@ -536,7 +536,7 @@ export function FileManagerContextMenu({
ref={menuRef}
data-context-menu
className={cn(
"fixed bg-canvas border border-edge rounded-lg shadow-xl min-w-[180px] max-w-[250px] z-[99995] overflow-x-hidden overflow-y-auto",
"fixed bg-card border border-border rounded-none shadow-md min-w-[220px] max-w-[300px] z-[99995] overflow-x-hidden overflow-y-auto py-1",
)}
style={{
left: menuPosition.x,
@@ -549,7 +549,7 @@ export function FileManagerContextMenu({
return (
<div
key={`separator-${index}`}
className="border-t border-border"
className="my-1 border-t border-border"
/>
);
}
@@ -558,10 +558,10 @@ export function FileManagerContextMenu({
<button
key={index}
className={cn(
"w-full px-3 h-9 text-left text-[10px] font-bold uppercase tracking-widest flex items-center justify-between",
"hover:bg-accent-brand/10 hover:text-accent-brand transition-colors cursor-pointer rounded-none",
"w-full px-3 min-h-8 py-1.5 text-left text-xs font-semibold flex items-center justify-between gap-3 rounded-none transition-colors cursor-pointer",
"hover:bg-accent-brand/10 hover:text-accent-brand",
item.disabled &&
"opacity-50 cursor-not-allowed hover:bg-transparent hover:text-current",
"opacity-40 cursor-not-allowed hover:bg-transparent hover:text-current",
item.danger &&
"text-destructive hover:bg-destructive/10 hover:text-destructive",
)}
@@ -573,14 +573,14 @@ export function FileManagerContextMenu({
}}
disabled={item.disabled}
>
<div className="flex items-center gap-2.5 flex-1 min-w-0">
<div className="flex-shrink-0">{item.icon}</div>
<span className="flex-1 whitespace-normal break-words">
{item.label}
</span>
<div className="flex items-center gap-2 flex-1 min-w-0">
<div className="flex-shrink-0 text-muted-foreground">
{item.icon}
</div>
<span className="flex-1 leading-tight">{item.label}</span>
</div>
{item.shortcut && (
<div className="ml-2 flex-shrink-0">
<div className="ml-auto flex-shrink-0 opacity-50">
{renderShortcut(item.shortcut)}
</div>
)}
@@ -67,6 +67,7 @@ interface FileManagerGridProps {
onSelectionChange: (files: FileItem[]) => void;
currentPath: string;
isLoading?: boolean;
isConnected?: boolean;
onPathChange: (path: string) => void;
onRefresh: () => void;
onUpload?: (files: FileList) => void;
@@ -191,6 +192,7 @@ export function FileManagerGrid({
onSelectionChange,
currentPath,
isLoading,
isConnected,
onPathChange,
onRefresh,
onUpload,
@@ -525,20 +527,30 @@ export function FileManagerGrid({
e.stopPropagation();
}, []);
const handleMouseDown = useCallback((e: React.MouseEvent) => {
if (e.target === e.currentTarget && e.button === 0) {
e.preventDefault();
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
const startX = e.clientX - rect.left;
const startY = e.clientY - rect.top;
const handleMouseDown = useCallback(
(e: React.MouseEvent) => {
if (createIntent) {
const target = e.target as HTMLElement;
if (target.tagName !== "INPUT") {
e.preventDefault();
}
return;
}
if (e.target === e.currentTarget && e.button === 0) {
e.preventDefault();
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
const startX = e.clientX - rect.left;
const startY = e.clientY - rect.top;
setIsSelecting(true);
setSelectionStart({ x: startX, y: startY });
setSelectionRect({ x: startX, y: startY, width: 0, height: 0 });
setIsSelecting(true);
setSelectionStart({ x: startX, y: startY });
setSelectionRect({ x: startX, y: startY, width: 0, height: 0 });
setJustFinishedSelecting(false);
}
}, []);
setJustFinishedSelecting(false);
}
},
[createIntent],
);
const handleMouseMove = useCallback(
(e: React.MouseEvent) => {
@@ -695,7 +707,7 @@ export function FileManagerGrid({
const handleFileClick = (file: FileItem, event: React.MouseEvent) => {
event.stopPropagation();
if (gridRef.current) {
if (gridRef.current && !createIntent) {
gridRef.current.focus();
}
@@ -730,7 +742,7 @@ export function FileManagerGrid({
};
const handleGridClick = (event: React.MouseEvent) => {
if (gridRef.current) {
if (gridRef.current && !createIntent) {
gridRef.current.focus();
}
@@ -974,6 +986,7 @@ export function FileManagerGrid({
onBlur={handleEditConfirm}
className="max-w-[120px] min-w-[60px] w-fit border border-accent-brand/60 bg-card px-2 py-1 text-xs rounded-none outline-none focus:ring-1 focus:ring-accent-brand/50 text-center pointer-events-auto"
onClick={(e) => e.stopPropagation()}
onMouseDown={(e) => e.stopPropagation()}
/>
) : (
<p
@@ -1096,6 +1109,7 @@ export function FileManagerGrid({
onBlur={handleEditConfirm}
className="flex-1 min-w-0 max-w-[200px] border border-accent-brand/60 bg-card px-2 py-1 text-xs rounded-none outline-none focus:ring-1 focus:ring-accent-brand/50 pointer-events-auto"
onClick={(e) => e.stopPropagation()}
onMouseDown={(e) => e.stopPropagation()}
/>
) : (
<span
@@ -1227,7 +1241,10 @@ export function FileManagerGrid({
document.body,
)}
<SimpleLoader visible={isLoading} message={t("common.connecting")} />
<SimpleLoader
visible={!!isLoading && !!isConnected}
message={t("common.loading")}
/>
</div>
);
}
@@ -1244,24 +1261,49 @@ function CreateIntentGridItem({
const { t } = useTranslation();
const [inputName, setInputName] = useState(intent.currentName);
const inputRef = useRef<HTMLInputElement>(null);
const doneRef = useRef(false);
useEffect(() => {
inputRef.current?.focus();
inputRef.current?.select();
}, []);
const timer = setTimeout(() => {
if (inputRef.current) {
inputRef.current.focus();
inputRef.current.select();
}
}, 50);
return () => clearTimeout(timer);
}, [intent.id]);
const commit = useCallback(
(name: string) => {
if (doneRef.current) return;
doneRef.current = true;
if (name) {
onConfirm?.(name);
} else {
onCancel?.();
}
},
[onConfirm, onCancel],
);
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === "Enter") {
e.preventDefault();
onConfirm?.(inputName.trim());
commit(inputName.trim());
} else if (e.key === "Escape") {
e.preventDefault();
if (doneRef.current) return;
doneRef.current = true;
onCancel?.();
}
};
return (
<div className="group flex flex-col items-center p-3 rounded-none border-2 border-dashed border-accent-brand/60 bg-accent-brand/5">
<div
className="group flex flex-col items-center p-3 rounded-none border-2 border-dashed border-accent-brand/60 bg-accent-brand/5"
onClick={(e) => e.stopPropagation()}
onMouseDown={(e) => e.stopPropagation()}
>
<div className="mb-2">
{intent.type === "directory" ? (
<Folder className="size-10 text-accent-brand" />
@@ -1275,7 +1317,7 @@ function CreateIntentGridItem({
value={inputName}
onChange={(e) => setInputName(e.target.value)}
onKeyDown={handleKeyDown}
onBlur={() => onConfirm?.(inputName.trim())}
onBlur={() => commit(inputName.trim())}
className="w-full max-w-[120px] border border-accent-brand/60 bg-card px-2 py-1 text-xs text-center rounded-none outline-none focus:ring-1 focus:ring-accent-brand/50"
placeholder={
intent.type === "directory"
@@ -1299,24 +1341,49 @@ function CreateIntentListItem({
const { t } = useTranslation();
const [inputName, setInputName] = useState(intent.currentName);
const inputRef = useRef<HTMLInputElement>(null);
const doneRef = useRef(false);
useEffect(() => {
inputRef.current?.focus();
inputRef.current?.select();
}, []);
const timer = setTimeout(() => {
if (inputRef.current) {
inputRef.current.focus();
inputRef.current.select();
}
}, 50);
return () => clearTimeout(timer);
}, [intent.id]);
const commit = useCallback(
(name: string) => {
if (doneRef.current) return;
doneRef.current = true;
if (name) {
onConfirm?.(name);
} else {
onCancel?.();
}
},
[onConfirm, onCancel],
);
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === "Enter") {
e.preventDefault();
onConfirm?.(inputName.trim());
commit(inputName.trim());
} else if (e.key === "Escape") {
e.preventDefault();
if (doneRef.current) return;
doneRef.current = true;
onCancel?.();
}
};
return (
<div className="grid grid-cols-[1fr_120px_150px_80px_90px] gap-2 px-4 py-2 items-center border-b border-accent-brand/30 bg-accent-brand/5 rounded-none">
<div
className="grid grid-cols-[1fr_120px_150px_80px_90px] gap-2 px-4 py-2 items-center border-b border-accent-brand/30 bg-accent-brand/5 rounded-none"
onClick={(e) => e.stopPropagation()}
onMouseDown={(e) => e.stopPropagation()}
>
<div className="flex items-center gap-3">
<div className="shrink-0">
{intent.type === "directory" ? (
@@ -1331,7 +1398,7 @@ function CreateIntentListItem({
value={inputName}
onChange={(e) => setInputName(e.target.value)}
onKeyDown={handleKeyDown}
onBlur={() => onConfirm?.(inputName.trim())}
onBlur={() => commit(inputName.trim())}
className="flex-1 min-w-0 max-w-[200px] border border-accent-brand/60 bg-card px-2 py-1 text-xs rounded-none outline-none focus:ring-1 focus:ring-accent-brand/50"
placeholder={
intent.type === "directory"
@@ -53,14 +53,20 @@ export function DraggableWindow({
const [dragStart, setDragStart] = useState({ x: 0, y: 0 });
const [windowStart, setWindowStart] = useState({ x: 0, y: 0 });
const [sizeStart, setSizeStart] = useState({ width: 0, height: 0 });
const containerBoundsRef = useRef({ width: 0, height: 0 });
const windowRef = useRef<HTMLDivElement>(null);
const titleBarRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (targetSize && !isMaximized) {
const maxWidth = Math.min(window.innerWidth * 0.9, 1200);
const maxHeight = Math.min(window.innerHeight * 0.8, 800);
const container = windowRef.current?.offsetParent as HTMLElement | null;
const maxWidth = container
? Math.min(container.clientWidth * 0.9, 1200)
: Math.min(window.innerWidth * 0.9, 1200);
const maxHeight = container
? Math.min(container.clientHeight * 0.8, 800)
: Math.min(window.innerHeight * 0.8, 800);
let newWidth = Math.min(targetSize.width + 50, maxWidth);
let newHeight = Math.min(targetSize.height + 150, maxHeight);
@@ -80,8 +86,16 @@ export function DraggableWindow({
setSize({ width: newWidth, height: newHeight });
setPosition({
x: Math.max(0, (window.innerWidth - newWidth) / 2),
y: Math.max(0, (window.innerHeight - newHeight) / 2),
x: Math.max(
0,
(container ? container.clientWidth : window.innerWidth) / 2 -
newWidth / 2,
),
y: Math.max(
0,
(container ? container.clientHeight : window.innerHeight) / 2 -
newHeight / 2,
),
});
}
}, [targetSize, isMaximized, minWidth, minHeight]);
@@ -98,6 +112,13 @@ export function DraggableWindow({
setIsDragging(true);
setDragStart({ x: e.clientX, y: e.clientY });
setWindowStart({ x: position.x, y: position.y });
const container = windowRef.current?.offsetParent as HTMLElement | null;
containerBoundsRef.current = {
width: container ? container.clientWidth : window.innerWidth,
height: container ? container.clientHeight : window.innerHeight,
};
onFocus?.();
},
[isMaximized, position, onFocus],
@@ -112,50 +133,14 @@ export function DraggableWindow({
const newX = windowStart.x + deltaX;
const newY = windowStart.y + deltaY;
const windowElement = windowRef.current;
let positioningContainer = null;
let currentElement = windowElement?.parentElement;
while (currentElement && currentElement !== document.body) {
const computedStyle = window.getComputedStyle(currentElement);
const position = computedStyle.position;
const transform = computedStyle.transform;
if (
position === "relative" ||
position === "absolute" ||
position === "fixed" ||
transform !== "none"
) {
positioningContainer = currentElement;
break;
}
currentElement = currentElement.parentElement;
}
let maxX, maxY, minX, minY;
if (positioningContainer) {
const containerRect = positioningContainer.getBoundingClientRect();
maxX = containerRect.width - size.width;
maxY = containerRect.height - size.height;
minX = 0;
minY = 0;
} else {
maxX = window.innerWidth - size.width;
maxY = window.innerHeight - size.height;
minX = 0;
minY = 0;
}
const constrainedX = Math.max(minX, Math.min(maxX, newX));
const constrainedY = Math.max(minY, Math.min(maxY, newY));
const { width: containerW, height: containerH } =
containerBoundsRef.current;
const maxX = containerW - size.width;
const maxY = containerH - size.height;
setPosition({
x: constrainedX,
y: constrainedY,
x: Math.max(0, Math.min(maxX, newX)),
y: Math.max(49, Math.min(maxY, newY)),
});
}
@@ -194,8 +179,10 @@ export function DraggableWindow({
}
}
newX = Math.max(0, Math.min(window.innerWidth - newWidth, newX));
newY = Math.max(0, Math.min(window.innerHeight - newHeight, newY));
const { width: containerW, height: containerH } =
containerBoundsRef.current;
newX = Math.max(0, Math.min(containerW - newWidth, newX));
newY = Math.max(49, Math.min(containerH - newHeight, newY));
setSize({ width: newWidth, height: newHeight });
setPosition({ x: newX, y: newY });
@@ -213,7 +200,6 @@ export function DraggableWindow({
windowStart,
sizeStart,
size,
position,
minWidth,
minHeight,
resizeDirection,
@@ -238,6 +224,13 @@ export function DraggableWindow({
setDragStart({ x: e.clientX, y: e.clientY });
setWindowStart({ x: position.x, y: position.y });
setSizeStart({ width: size.width, height: size.height });
const container = windowRef.current?.offsetParent as HTMLElement | null;
containerBoundsRef.current = {
width: container ? container.clientWidth : window.innerWidth,
height: container ? container.clientHeight : window.innerHeight,
};
onFocus?.();
},
[isMaximized, position, size, onFocus],
@@ -4,8 +4,10 @@ import { AlertCircle, Download } from "lucide-react";
import { Button } from "@/components/button.tsx";
import { useTranslation } from "react-i18next";
import workerUrl from "pdfjs-dist/build/pdf.worker.min.mjs?url";
pdfjs.GlobalWorkerOptions.workerSrc = workerUrl;
pdfjs.GlobalWorkerOptions.workerSrc = new URL(
"pdfjs-dist/build/pdf.worker.min.mjs",
import.meta.url,
).toString();
interface PdfPreviewProps {
content: string;
@@ -168,35 +168,40 @@ export function PermissionsDialog({
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="w-[calc(100vw-2rem)] sm:max-w-md rounded-none border-border bg-card">
<DialogContent className="w-[calc(100vw-2rem)] sm:max-w-lg rounded-none border-border bg-card">
<DialogHeader>
<DialogTitle className="text-xs font-bold uppercase tracking-widest flex items-center gap-2">
<Lock className="size-4 text-accent-brand" />
{t("fileManager.changePermissions")}
</DialogTitle>
<DialogDescription className="text-[10px] font-bold uppercase tracking-tight text-muted-foreground font-mono">
<DialogDescription className="text-[10px] font-bold uppercase tracking-tight text-muted-foreground font-mono break-all">
{file.path}
</DialogDescription>
</DialogHeader>
<div className="py-3 flex flex-col gap-4">
<div className="grid grid-cols-4 gap-0 border border-border overflow-hidden">
<div className="px-3 py-2 bg-muted/50 border-b border-r border-border text-[10px] font-bold uppercase tracking-widest text-muted-foreground" />
{[
t("fileManager.read"),
t("fileManager.write"),
t("fileManager.execute"),
].map((h) => (
<div
key={h}
className="px-3 py-2 bg-muted/50 border-b border-r border-border last:border-r-0 text-[10px] font-bold uppercase tracking-widest text-muted-foreground text-center"
>
{h}
</div>
))}
<div className="border border-border overflow-hidden">
<div className="grid grid-cols-[1fr_64px_64px_64px] bg-muted/50 border-b border-border">
<div className="px-3 py-2 text-[10px] font-bold uppercase tracking-widest text-muted-foreground" />
{[
t("fileManager.read"),
t("fileManager.write"),
t("fileManager.execute"),
].map((h) => (
<div
key={h}
className="py-2 text-[10px] font-bold uppercase tracking-widest text-muted-foreground text-center border-l border-border"
>
{h}
</div>
))}
</div>
{rows.map((row, i) => (
<React.Fragment key={i}>
<div className="px-3 py-2.5 border-b border-r border-border last:border-b-0 text-xs font-semibold truncate">
<div
key={i}
className={`grid grid-cols-[1fr_64px_64px_64px] ${i < rows.length - 1 ? "border-b border-border" : ""}`}
>
<div className="px-3 py-3 text-xs font-semibold">
{row.label}
</div>
{[
@@ -206,29 +211,28 @@ export function PermissionsDialog({
].map((perm, j) => (
<div
key={j}
className="flex items-center justify-center border-b border-r border-border last:border-r-0 py-2.5"
className="flex items-center justify-center border-l border-border py-3"
>
<input
type="checkbox"
checked={perm.val}
onChange={(e) => perm.set(e.target.checked)}
className="accent-[var(--accent-brand)] size-3.5 cursor-pointer"
className="accent-[var(--accent-brand)] size-4 cursor-pointer"
/>
</div>
))}
</React.Fragment>
</div>
))}
</div>
<div className="flex items-center gap-3">
<span className="text-xs font-bold uppercase tracking-widest text-muted-foreground">
<span className="text-xs font-bold uppercase tracking-widest text-muted-foreground shrink-0">
{t("fileManager.octal")}
</span>
<Input
value={octal}
readOnly
className="w-20 rounded-none bg-muted/50 border-border text-xs font-mono text-center h-8"
maxLength={3}
/>
<span className="text-[10px] text-muted-foreground font-mono">
{t("fileManager.currentPermissions")}: {file.permissions || "—"}
@@ -4,20 +4,7 @@ import { Terminal } from "@/features/terminal/Terminal.tsx";
import { useWindowManager } from "./WindowManager.tsx";
import { useTranslation } from "react-i18next";
import { CommandHistoryProvider } from "@/features/terminal/command-history/CommandHistoryContext.tsx";
interface SSHHost {
id: number;
name: string;
ip: string;
port: number;
username: string;
password?: string;
key?: string;
keyPassword?: string;
authType: "password" | "key";
credentialId?: number;
userId?: number;
}
import type { SSHHost } from "@/types/index.ts";
interface TerminalWindowProps {
windowId: string;
@@ -114,8 +101,8 @@ export function TerminalWindow({
zIndex={currentWindow.zIndex}
>
<Terminal
ref={terminalRef}
hostConfig={hostConfig}
ref={terminalRef as any}
hostConfig={hostConfig as any}
isVisible={!currentWindow.isMinimized}
initialPath={initialPath}
executeCommand={executeCommand}
@@ -116,14 +116,19 @@ export function WindowManager({ children }: WindowManagerProps) {
return (
<WindowManagerContext.Provider value={contextValue}>
{children}
<div className="window-container">
{windows.map((window) => (
<div key={window.id}>
{typeof window.component === "function"
? window.component(window.id)
: window.component}
</div>
))}
<div
className="window-container absolute inset-0 pointer-events-none overflow-hidden"
style={{ zIndex: 1000 }}
>
<div className="relative w-full h-full pointer-events-none">
{windows.map((window) => (
<div key={window.id} className="pointer-events-auto">
{typeof window.component === "function"
? window.component(window.id)
: window.component}
</div>
))}
</div>
</div>
</WindowManagerContext.Provider>
);