Files
vscode-front-matter/src/dashboardWebView/components/Modals/MoveFileDialog.tsx

176 lines
5.9 KiB
TypeScript

import * as React from 'react';
import { useState, useMemo, useEffect } from 'react';
import { FolderIcon, ChevronRightIcon } from '@heroicons/react/24/solid';
import * as l10n from '@vscode/l10n';
import { LocalizationKey } from '../../../localization';
import { parseWinPath } from '../../../helpers/parseWinPath';
import { Page } from '../../models';
export interface IMoveFileDialogProps {
page: Page;
availableFolders: string[];
dismiss: () => void;
trigger: (destinationFolder: string) => void;
}
interface FolderNode {
name: string;
path: string;
children: FolderNode[];
level: number;
}
export const MoveFileDialog: React.FunctionComponent<IMoveFileDialogProps> = ({
page,
availableFolders,
dismiss,
trigger
}: React.PropsWithChildren<IMoveFileDialogProps>) => {
const [selectedFolder, setSelectedFolder] = useState<string>('');
const [expandedFolders, setExpandedFolders] = useState<Set<string>>(new Set());
// Build folder tree structure
const folderTree = useMemo(() => {
const root: FolderNode[] = [];
const folderMap = new Map<string, FolderNode>();
for (const folderPath of availableFolders) {
const normalized = parseWinPath(folderPath).replace(/^\/+|\/+$/g, '');
const parts = normalized.split('/').filter(Boolean);
let currentPath = '';
let currentLevel: FolderNode[] = root;
for (let i = 0; i < parts.length; i++) {
const part = parts[i];
const fullPath = currentPath ? `${currentPath}/${part}` : part;
if (!folderMap.has(fullPath)) {
const newNode: FolderNode = {
name: part,
path: fullPath,
children: [],
level: i
};
folderMap.set(fullPath, newNode);
currentLevel.push(newNode);
}
const node = folderMap.get(fullPath);
if (node) {
currentLevel = node.children;
}
currentPath = fullPath;
}
}
return root;
}, [availableFolders]);
const toggleFolder = (folderPath: string) => {
const newExpanded = new Set(expandedFolders);
if (newExpanded.has(folderPath)) {
newExpanded.delete(folderPath);
} else {
newExpanded.add(folderPath);
}
setExpandedFolders(newExpanded);
};
const renderFolderNode = (node: FolderNode): React.ReactNode => {
const isExpanded = expandedFolders.has(node.path);
const isSelected = selectedFolder === node.path;
const hasChildren = node.children.length > 0;
const paddingLeft = node.level * 20;
return (
<div key={node.path}>
<div
className={`flex items-center py-1 px-2 cursor-pointer hover:bg-[var(--vscode-list-hoverBackground)] rounded ${isSelected ? 'bg-[var(--vscode-list-activeSelectionBackground)]' : ''
}`}
style={{ paddingLeft: `${paddingLeft}px` }}
onClick={() => setSelectedFolder(node.path)}
>
{hasChildren && (
<button
onClick={(e) => {
e.stopPropagation();
toggleFolder(node.path);
}}
className="mr-1"
>
<ChevronRightIcon
className={`w-3 h-3 transform transition-transform ${isExpanded ? 'rotate-90' : ''
}`}
/>
</button>
)}
{!hasChildren && <span className="w-3 mr-1"></span>}
<FolderIcon className="w-4 h-4 mr-2 text-[var(--vscode-symbolIcon-folderForeground)]" />
<span className="text-sm text-[var(--vscode-editor-foreground)]">{node.name}</span>
</div>
{hasChildren && isExpanded && (
<div>
{node.children.map((child) => renderFolderNode(child))}
</div>
)}
</div>
);
};
const handleMove = () => {
if (selectedFolder) {
trigger(selectedFolder);
}
};
// Auto-expand folders by default (first level)
useEffect(() => {
const firstLevelFolders = folderTree.map(node => node.path);
setExpandedFolders(new Set(firstLevelFolders));
}, [folderTree]);
return (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black bg-opacity-50">
<div className="bg-[var(--vscode-editor-background)] border border-[var(--frontmatter-border)] rounded-lg shadow-xl max-w-2xl w-full max-h-[80vh] flex flex-col">
<div className="p-6 border-b border-[var(--frontmatter-border)]">
<h2 className="text-xl font-bold text-[var(--vscode-editor-foreground)]">
Move File
</h2>
<p className="mt-2 text-sm text-[var(--vscode-descriptionForeground)]">
Move <span className="font-medium">{page.title}</span> to a different folder
</p>
</div>
<div className="flex-1 overflow-y-auto p-6">
<div className="space-y-1">
{folderTree.length > 0 ? (
folderTree.map((node) => renderFolderNode(node))
) : (
<p className="text-sm text-[var(--vscode-descriptionForeground)]">
No folders available
</p>
)}
</div>
</div>
<div className="p-6 border-t border-[var(--frontmatter-border)] flex justify-end space-x-2">
<button
onClick={dismiss}
className="px-4 py-2 text-sm font-medium rounded text-[var(--vscode-button-foreground)] bg-[var(--vscode-button-secondaryBackground)] hover:bg-[var(--vscode-button-secondaryHoverBackground)]"
>
{l10n.t(LocalizationKey.commonCancel)}
</button>
<button
onClick={handleMove}
disabled={!selectedFolder}
className="px-4 py-2 text-sm font-medium rounded text-[var(--vscode-button-foreground)] bg-[var(--frontmatter-button-background)] hover:bg-[var(--vscode-button-hoverBackground)] disabled:opacity-50 disabled:cursor-not-allowed"
>
Move
</button>
</div>
</div>
</div>
);
};