Workspaces and Terminals
This commit is contained in:
@@ -23,6 +23,7 @@ type FileGridProps = {
|
||||
onRenamingChange: (name: string | null) => void;
|
||||
getMatchingTasks: (fileName: string, entryType: 'file' | 'directory') => TaskSummary[];
|
||||
onRunTask: (task: TaskSummary, entry: DirEntry) => void;
|
||||
onCreateWorkspace: (entry: DirEntry) => void;
|
||||
scrollRef: React.RefObject<HTMLDivElement | null>;
|
||||
};
|
||||
|
||||
@@ -72,6 +73,7 @@ export const FileGrid = ({
|
||||
onRenamingChange,
|
||||
getMatchingTasks,
|
||||
onRunTask,
|
||||
onCreateWorkspace,
|
||||
scrollRef,
|
||||
}: FileGridProps) => {
|
||||
const lastClickedIdx = useRef<number>(-1);
|
||||
@@ -153,6 +155,7 @@ export const FileGrid = ({
|
||||
onRenamingChange={onRenamingChange}
|
||||
matchingTasks={getMatchingTasks(entry.name, entry.type)}
|
||||
onRunTask={onRunTask}
|
||||
onCreateWorkspace={onCreateWorkspace}
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import { Folder, Trash2, Pencil, MoreVertical, MessageSquare, Scissors, Copy, Check, Play, Download } from 'lucide-react';
|
||||
import { Folder, Trash2, Pencil, MoreVertical, MessageSquare, Scissors, Copy, Check, Play, Download, LayoutGrid } from 'lucide-react';
|
||||
import { getIcon } from 'material-file-icons';
|
||||
import {
|
||||
DropdownMenu,
|
||||
@@ -43,6 +43,7 @@ export type FileItemProps = {
|
||||
onRenamingChange: (name: string | null) => void;
|
||||
matchingTasks: TaskSummary[];
|
||||
onRunTask: (task: TaskSummary, entry: DirEntry) => void;
|
||||
onCreateWorkspace: (entry: DirEntry) => void;
|
||||
};
|
||||
|
||||
function formatSize(bytes: number): string {
|
||||
@@ -70,6 +71,7 @@ type MenuItemsProps = {
|
||||
onCopy: () => void;
|
||||
matchingTasks: TaskSummary[];
|
||||
onRunTask: (task: TaskSummary, entry: DirEntry) => void;
|
||||
onCreateWorkspace: (e: DirEntry) => void;
|
||||
};
|
||||
|
||||
const DropdownMenuItems = ({
|
||||
@@ -83,6 +85,7 @@ const DropdownMenuItems = ({
|
||||
onCopy,
|
||||
matchingTasks,
|
||||
onRunTask,
|
||||
onCreateWorkspace,
|
||||
}: MenuItemsProps) => (
|
||||
<>
|
||||
<DropdownMenuItem onClick={() => onChat(entry)} className="cursor-pointer">
|
||||
@@ -108,6 +111,15 @@ const DropdownMenuItems = ({
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuSub>
|
||||
)}
|
||||
{entry.type === 'directory' && (
|
||||
<>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={() => onCreateWorkspace(entry)} className="cursor-pointer">
|
||||
<LayoutGrid className="mr-2 h-4 w-4" />
|
||||
Create Workspace here
|
||||
</DropdownMenuItem>
|
||||
</>
|
||||
)}
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={onCut} className="cursor-pointer">
|
||||
<Scissors className="mr-2 h-4 w-4" />
|
||||
@@ -142,6 +154,7 @@ const ContextMenuItems = ({
|
||||
onCopy,
|
||||
matchingTasks,
|
||||
onRunTask,
|
||||
onCreateWorkspace,
|
||||
}: MenuItemsProps) => (
|
||||
<>
|
||||
<ContextMenuItem onClick={() => onChat(entry)} className="cursor-pointer">
|
||||
@@ -167,6 +180,15 @@ const ContextMenuItems = ({
|
||||
</ContextMenuSubContent>
|
||||
</ContextMenuSub>
|
||||
)}
|
||||
{entry.type === 'directory' && (
|
||||
<>
|
||||
<ContextMenuSeparator />
|
||||
<ContextMenuItem onClick={() => onCreateWorkspace(entry)} className="cursor-pointer">
|
||||
<LayoutGrid className="mr-2 h-4 w-4" />
|
||||
Create Workspace here
|
||||
</ContextMenuItem>
|
||||
</>
|
||||
)}
|
||||
<ContextMenuSeparator />
|
||||
<ContextMenuItem onClick={onCut} className="cursor-pointer">
|
||||
<Scissors className="mr-2 h-4 w-4" />
|
||||
@@ -300,6 +322,7 @@ export const FileItem = ({
|
||||
onRenamingChange,
|
||||
matchingTasks,
|
||||
onRunTask,
|
||||
onCreateWorkspace,
|
||||
}: FileItemProps) => {
|
||||
const [renaming, setRenaming] = useState(false);
|
||||
const clickTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
@@ -376,6 +399,7 @@ export const FileItem = ({
|
||||
onCopy,
|
||||
matchingTasks,
|
||||
onRunTask,
|
||||
onCreateWorkspace,
|
||||
};
|
||||
|
||||
const cutOpacity = isCut ? 'opacity-50' : '';
|
||||
|
||||
@@ -33,7 +33,11 @@ type ClipboardState = { paths: string[]; mode: 'copy' | 'cut' } | null;
|
||||
|
||||
type HomeRoot = 'home' | '~' | 'officer.dev';
|
||||
|
||||
export const Files = () => {
|
||||
type FilesProps = {
|
||||
basePath?: string;
|
||||
};
|
||||
|
||||
export const Files = ({ basePath = '/' }: FilesProps) => {
|
||||
const { user } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
@@ -86,6 +90,10 @@ export const Files = () => {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (basePath !== '/' && !currentPath.startsWith(basePath)) {
|
||||
setCurrentPath(basePath);
|
||||
return;
|
||||
}
|
||||
refresh();
|
||||
}, [currentPath, homeRoot]);
|
||||
|
||||
@@ -137,6 +145,10 @@ export const Files = () => {
|
||||
const selectedPaths = () => Array.from(selected).map(entryPath);
|
||||
|
||||
const handleNavigate = (path: string) => {
|
||||
if (basePath !== '/' && !path.startsWith(basePath)) {
|
||||
setCurrentPath(basePath);
|
||||
return;
|
||||
}
|
||||
setCurrentPath(path);
|
||||
};
|
||||
|
||||
@@ -262,6 +274,18 @@ export const Files = () => {
|
||||
setRunningTask({ task, entry });
|
||||
};
|
||||
|
||||
const handleCreateWorkspace = (entry: DirEntry) => {
|
||||
const folderPath = currentPath === '/' ? `/${entry.name}` : `${currentPath}/${entry.name}`;
|
||||
navigate(`/workspaces/new?name=${encodeURIComponent(entry.name)}&cwd=${encodeURIComponent(folderPath)}`);
|
||||
};
|
||||
|
||||
const handleCreateWorkspaceHere = () => {
|
||||
const dirName = currentPath === '/' ? '' : currentPath.split('/').pop()!;
|
||||
const params = new URLSearchParams({ cwd: currentPath });
|
||||
if (dirName) params.set('name', dirName);
|
||||
navigate(`/workspaces/new?${params}`);
|
||||
};
|
||||
|
||||
const handleGitClone = async () => {
|
||||
const url = cloneUrl.trim();
|
||||
if (!url) return;
|
||||
@@ -509,7 +533,7 @@ export const Files = () => {
|
||||
</div>
|
||||
|
||||
{/* Home dir selector (Super Admin only) */}
|
||||
{user?.role === 'Super Admin' && (
|
||||
{basePath === '/' && user?.role === 'Super Admin' && (
|
||||
<div className="shrink-0 border-b border-duck-dark/10 px-4 py-2">
|
||||
<RadioGroup
|
||||
value={homeRoot}
|
||||
@@ -539,7 +563,7 @@ export const Files = () => {
|
||||
|
||||
{/* Breadcrumb */}
|
||||
<div className="shrink-0 border-b border-duck-dark/10 px-4 py-2">
|
||||
<Breadcrumb path={currentPath} onNavigate={handleNavigate} />
|
||||
<Breadcrumb path={currentPath} onNavigate={handleNavigate} basePath={basePath} />
|
||||
</div>
|
||||
|
||||
{/* Upload progress */}
|
||||
@@ -625,6 +649,7 @@ export const Files = () => {
|
||||
onRenamingChange={setRenamingName}
|
||||
getMatchingTasks={getMatchingTasks}
|
||||
onRunTask={handleRunTask}
|
||||
onCreateWorkspace={handleCreateWorkspace}
|
||||
scrollRef={fileScrollRef}
|
||||
/>
|
||||
)}
|
||||
@@ -645,6 +670,10 @@ export const Files = () => {
|
||||
<FolderPlus className="mr-2 h-4 w-4" />
|
||||
New folder
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem onClick={handleCreateWorkspaceHere} className="cursor-pointer">
|
||||
<LayoutGrid className="mr-2 h-4 w-4" />
|
||||
Create Workspace here
|
||||
</ContextMenuItem>
|
||||
</ContextMenuContent>
|
||||
</ContextMenu>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user