extracted all File components to apps/FileBrowser
This commit is contained in:
@@ -44,7 +44,7 @@ export function App() {
|
||||
<Route path="/chat/new" element={<Dashboard.SessionListPage isNew />} />
|
||||
<Route path="/chat/:sessionId" element={<Dashboard.SessionListPage />} />
|
||||
<Route path="/plans" element={<Dashboard.Plans />} />
|
||||
<Route path="/files" element={<Dashboard.FilesPage />} />
|
||||
<Route path="/files" element={<Dashboard.FilesScreen />} />
|
||||
|
||||
<Route path="/code-editor" element={<Dashboard.CodeEditor />} />
|
||||
<Route path="/skills" element={<Dashboard.Skills />} />
|
||||
|
||||
@@ -13,7 +13,7 @@ import { Card } from '@/components/Card';
|
||||
import { FrontmatterBlock } from '../CapabilityPage';
|
||||
import type { CapabilityDetail } from '../CapabilityPage';
|
||||
import type { AutomationSelection } from './AutomationRightPanel';
|
||||
import { TaskRunnerModal } from '../Files/Screen/TaskRunnerModal';
|
||||
import { TaskRunnerModal } from 'apps/FileBrowser';
|
||||
|
||||
type SelectOption = { value: string; label: string };
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import type { LayoutNode } from '@/components/Workspace';
|
||||
import { WorkspaceView } from '@/components/Workspace';
|
||||
import { useWorkspacesState } from '@/state/useWorkspacesState';
|
||||
import { appRegistry } from '../Workspaces/app-registry';
|
||||
import { defaultLayout } from './defaultLayout';
|
||||
|
||||
export const FilesScreen = () => {
|
||||
const workspace = useWorkspacesState<LayoutNode>('screens/files', defaultLayout);
|
||||
|
||||
return (
|
||||
<div className="h-full w-full">
|
||||
<WorkspaceView workspace={workspace} registry={appRegistry} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
import type { LayoutNode } from '@/components/Workspace';
|
||||
|
||||
export const defaultLayout: LayoutNode = {
|
||||
type: 'panel',
|
||||
id: 'files-main',
|
||||
appType: 'file-browser',
|
||||
};
|
||||
@@ -1,225 +1 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useSearchParams } from 'react-router';
|
||||
import type { LayoutNode, PanelComponents } from '@/components/Workspace';
|
||||
import { WorkspaceLayout } from '@/components/Workspace';
|
||||
import { FileViewerProvider, FileViewerHeader, FileViewerBody } from 'apps/FileViewer';
|
||||
import { useUserState } from '@/state/useUserState';
|
||||
import { appRegistry } from '../Workspaces/app-registry';
|
||||
import { Files as FilesInner } from './Screen';
|
||||
|
||||
export { FilesInner as Files };
|
||||
|
||||
const baseLayout: LayoutNode = {
|
||||
type: 'panel',
|
||||
id: 'files-main',
|
||||
appType: null,
|
||||
};
|
||||
|
||||
const viewerLayout: LayoutNode = {
|
||||
type: 'group',
|
||||
id: 'files-root',
|
||||
direction: 'horizontal',
|
||||
children: [
|
||||
{ node: { type: 'panel', id: 'files-main', appType: null }, size: 40 },
|
||||
{ node: { type: 'panel', id: 'files-viewer', appType: null }, size: 60 },
|
||||
],
|
||||
};
|
||||
|
||||
const viewerWithEphemeralLayout: LayoutNode = {
|
||||
type: 'group',
|
||||
id: 'files-root',
|
||||
direction: 'horizontal',
|
||||
children: [
|
||||
{ node: { type: 'panel', id: 'files-main', appType: null }, size: 40 },
|
||||
{
|
||||
node: {
|
||||
type: 'group',
|
||||
id: 'files-viewer-group',
|
||||
direction: 'vertical',
|
||||
children: [
|
||||
{ node: { type: 'panel', id: 'files-viewer', appType: null }, size: 50 },
|
||||
{ node: { type: 'panel', id: 'files-ephemeral', appType: null }, size: 50 },
|
||||
],
|
||||
},
|
||||
size: 60,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const viewerWithEphemeralSplitLayout: LayoutNode = {
|
||||
type: 'group',
|
||||
id: 'files-root',
|
||||
direction: 'horizontal',
|
||||
children: [
|
||||
{ node: { type: 'panel', id: 'files-main', appType: null }, size: 40 },
|
||||
{
|
||||
node: {
|
||||
type: 'group',
|
||||
id: 'files-viewer-group',
|
||||
direction: 'vertical',
|
||||
children: [
|
||||
{ node: { type: 'panel', id: 'files-viewer', appType: null }, size: 50 },
|
||||
{
|
||||
node: {
|
||||
type: 'group',
|
||||
id: 'files-ephemeral-group',
|
||||
direction: 'horizontal',
|
||||
children: [
|
||||
{ node: { type: 'panel', id: 'files-ephemeral', appType: null }, size: 50 },
|
||||
{ node: { type: 'panel', id: 'files-ephemeral2', appType: null }, size: 50 },
|
||||
],
|
||||
},
|
||||
size: 50,
|
||||
},
|
||||
],
|
||||
},
|
||||
size: 60,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const FilesFileViewerProvider = ({ children }: { children: ReactNode }) => {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const [homeRoot] = useUserState<string>('files/homeRoot', 'home');
|
||||
const viewPath = searchParams.get('view');
|
||||
const fileName = viewPath ? viewPath.split('/').pop()! : '';
|
||||
|
||||
const handleOpenFile = useCallback(
|
||||
(filePath: string, root: string) => {
|
||||
setSearchParams((prev) => {
|
||||
const next = new URLSearchParams(prev);
|
||||
next.set('ephemeral', filePath);
|
||||
next.set('ephemeralRoot', root);
|
||||
return next;
|
||||
});
|
||||
},
|
||||
[setSearchParams],
|
||||
);
|
||||
|
||||
if (!viewPath) return null;
|
||||
|
||||
return (
|
||||
<FileViewerProvider filePath={viewPath} fileName={fileName} root={homeRoot} onOpenFile={handleOpenFile}>
|
||||
{children}
|
||||
</FileViewerProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const EphemeralFileViewerProvider = ({ children }: { children: ReactNode }) => {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const ephemeralPath = searchParams.get('ephemeral');
|
||||
const ephemeralRoot = searchParams.get('ephemeralRoot') ?? 'home';
|
||||
const fileName = ephemeralPath ? ephemeralPath.split('/').pop()! : '';
|
||||
|
||||
const handleOpenFile = useCallback(
|
||||
(filePath: string, root: string) => {
|
||||
setSearchParams((prev) => {
|
||||
const next = new URLSearchParams(prev);
|
||||
next.set('ephemeral2', filePath);
|
||||
next.set('ephemeral2Root', root);
|
||||
next.set('ephemeral2Auto', '1');
|
||||
return next;
|
||||
});
|
||||
},
|
||||
[setSearchParams],
|
||||
);
|
||||
|
||||
if (!ephemeralPath) return null;
|
||||
|
||||
return (
|
||||
<FileViewerProvider filePath={ephemeralPath} fileName={fileName} root={ephemeralRoot} onOpenFile={handleOpenFile}>
|
||||
{children}
|
||||
</FileViewerProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const Ephemeral2FileViewerProvider = ({ children }: { children: ReactNode }) => {
|
||||
const [searchParams] = useSearchParams();
|
||||
const path = searchParams.get('ephemeral2');
|
||||
const root = searchParams.get('ephemeral2Root') ?? 'home';
|
||||
const autoPlay = searchParams.get('ephemeral2Auto') === '1';
|
||||
const fileName = path ? path.split('/').pop()! : '';
|
||||
|
||||
if (!path) return null;
|
||||
|
||||
return (
|
||||
<FileViewerProvider filePath={path} fileName={fileName} root={root} autoPlay={autoPlay}>
|
||||
{children}
|
||||
</FileViewerProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export const FilesPage = () => {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const viewPath = searchParams.get('view');
|
||||
const ephemeralPath = searchParams.get('ephemeral');
|
||||
const ephemeral2Path = searchParams.get('ephemeral2');
|
||||
|
||||
const layout = useMemo(
|
||||
() =>
|
||||
viewPath && ephemeralPath && ephemeral2Path
|
||||
? viewerWithEphemeralSplitLayout
|
||||
: viewPath && ephemeralPath
|
||||
? viewerWithEphemeralLayout
|
||||
: viewPath
|
||||
? viewerLayout
|
||||
: baseLayout,
|
||||
[viewPath, ephemeralPath, ephemeral2Path],
|
||||
);
|
||||
|
||||
const closeEphemeral = useCallback(() => {
|
||||
setSearchParams((prev) => {
|
||||
const next = new URLSearchParams(prev);
|
||||
next.delete('ephemeral');
|
||||
next.delete('ephemeralRoot');
|
||||
next.delete('ephemeral2');
|
||||
next.delete('ephemeral2Root');
|
||||
next.delete('ephemeral2Auto');
|
||||
return next;
|
||||
});
|
||||
}, [setSearchParams]);
|
||||
|
||||
const closeEphemeral2 = useCallback(() => {
|
||||
setSearchParams((prev) => {
|
||||
const next = new URLSearchParams(prev);
|
||||
next.delete('ephemeral2');
|
||||
next.delete('ephemeral2Root');
|
||||
next.delete('ephemeral2Auto');
|
||||
return next;
|
||||
});
|
||||
}, [setSearchParams]);
|
||||
|
||||
const panelComponents: PanelComponents = useMemo(
|
||||
() => ({
|
||||
'files-main': FilesInner,
|
||||
'files-viewer': {
|
||||
provider: FilesFileViewerProvider,
|
||||
header: FileViewerHeader,
|
||||
component: FileViewerBody,
|
||||
onClose: () => setSearchParams({}),
|
||||
},
|
||||
'files-ephemeral': {
|
||||
provider: EphemeralFileViewerProvider,
|
||||
header: FileViewerHeader,
|
||||
component: FileViewerBody,
|
||||
onClose: closeEphemeral,
|
||||
},
|
||||
'files-ephemeral2': {
|
||||
provider: Ephemeral2FileViewerProvider,
|
||||
header: FileViewerHeader,
|
||||
component: FileViewerBody,
|
||||
onClose: closeEphemeral2,
|
||||
},
|
||||
}),
|
||||
[setSearchParams, closeEphemeral, closeEphemeral2],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="h-full w-full pt-2">
|
||||
<WorkspaceLayout layout={layout} onLayoutChange={() => {}} registry={appRegistry} components={panelComponents} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { FileBrowser as FileBrowserApp } from './Widget';
|
||||
export * from './FilesScreen';
|
||||
|
||||
@@ -10,7 +10,7 @@ import { WorkspaceLayout, WorkspaceView, createDefaultLayout } from '@/component
|
||||
import type { LayoutNode, WorkspaceDefinition } from '@/components/Workspace';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { generateSlug, slugify } from 'helpers/slug';
|
||||
import { Files } from '../Files';
|
||||
import { Files } from 'apps/FileBrowser';
|
||||
import { appRegistry } from './app-registry';
|
||||
import { SELECTED_WORKSPACE_KEY, CREATING_WORKSPACE_KEY, EDITING_WORKSPACE_KEY, NEW_WS_NAME_KEY, NEW_WS_DESC_KEY, NEW_WS_TEMPLATE_KEY } from './constants';
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
import { MessageSquare, FolderOpen, History, Music, Code, TerminalSquare, Monitor, LayoutGrid, LayoutDashboard, Sparkles, Eye, FolderKanban, Columns2, PenLine } from 'lucide-react';
|
||||
import { MessageSquare, FolderOpen, History, Music, Code, TerminalSquare } from 'lucide-react';
|
||||
import { Monitor, LayoutGrid, LayoutDashboard, Sparkles, Eye, FolderKanban, Columns2, PenLine } from 'lucide-react';
|
||||
import { useAuth } from 'hooks/useAuth';
|
||||
import type { AppRegistry } from '@/components/Workspace';
|
||||
import { useWorkspace } from '@/components/Workspace';
|
||||
@@ -12,7 +13,7 @@ import { FileViewerProvider, FileViewerHeader, FileViewerBody } from 'apps/FileV
|
||||
import { EmbeddableChat, ChatLauncher, usePi } from 'apps/Chat';
|
||||
import { useVisiblePiModels } from '@/state/useModels';
|
||||
import { ChatHistoryApp as ChatHistory } from '../ChatHistory';
|
||||
import { Files } from '../Files';
|
||||
import { Files } from 'apps/FileBrowser';
|
||||
import { Catalog } from 'sounds';
|
||||
import { WorkspaceListApp } from './WorkspaceListApp';
|
||||
import { ProjectListApp } from '../Projects/ProjectListApp';
|
||||
|
||||
+2
-1
@@ -1,7 +1,8 @@
|
||||
import { useRef, useCallback, useMemo, useState, useEffect } from 'react';
|
||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||
import { ChevronUp, ChevronDown } from 'lucide-react';
|
||||
import type { DirEntry, TaskSummary } from 'apps/FileBrowser';
|
||||
import type { DirEntry } from './useFiles';
|
||||
import type { TaskSummary } from './useTasks';
|
||||
import { getFileType } from 'apps/FileViewer';
|
||||
import { FileItem } from './FileItem';
|
||||
|
||||
+2
-1
@@ -22,7 +22,8 @@ import {
|
||||
ContextMenuTrigger,
|
||||
} from '@/components/ui/context-menu';
|
||||
import { cardStyle } from '@/components/Card';
|
||||
import type { DirEntry, TaskSummary } from 'apps/FileBrowser';
|
||||
import type { DirEntry } from './useFiles';
|
||||
import type { TaskSummary } from './useTasks';
|
||||
import { getFileType } from 'apps/FileViewer';
|
||||
|
||||
export type FileItemProps = {
|
||||
+4
-1
@@ -24,7 +24,10 @@ import { getIcon } from 'material-file-icons';
|
||||
import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger } from '@/components/ui/context-menu';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { useFiles, type DirEntry, useTasks, type TaskSummary, Breadcrumb, Toolbar } from 'apps/FileBrowser';
|
||||
import { useFiles, type DirEntry } from './useFiles';
|
||||
import { useTasks, type TaskSummary } from './useTasks';
|
||||
import { Breadcrumb } from './Breadcrumb';
|
||||
import { Toolbar } from './Toolbar';
|
||||
import { useClient } from 'hooks/useClient';
|
||||
import { useUserState } from '@/state/useUserState';
|
||||
import { useAuth } from 'hooks/useAuth';
|
||||
+1
-1
@@ -7,7 +7,7 @@ import type { TaskInfo } from 'apps/Chat';
|
||||
import { usePi, EmbeddableChat } from 'apps/Chat';
|
||||
import { useVisiblePiModels } from '@/state/useModels';
|
||||
import { useSettings } from '@/state/useSettings';
|
||||
import type { TaskSummary } from 'apps/FileBrowser';
|
||||
import type { TaskSummary } from './useTasks';
|
||||
|
||||
const playDing = () => {
|
||||
const ctx = new AudioContext();
|
||||
+4
-3
@@ -2,9 +2,10 @@ import { useState, useEffect, useRef, useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { Folder, Pin, PinOff, Search, Clock, FolderOpen, Loader2, X } from 'lucide-react';
|
||||
import { getIcon } from 'material-file-icons';
|
||||
import { useFiles, type DirEntry, Breadcrumb } from 'apps/FileBrowser';
|
||||
import { useRecentFiles } from './state/useRecentFiles';
|
||||
import { usePinnedFiles } from './state/usePinnedFiles';
|
||||
import { useFiles, type DirEntry } from './useFiles';
|
||||
import { Breadcrumb } from './Breadcrumb';
|
||||
import { useRecentFiles } from './useRecentFiles';
|
||||
import { usePinnedFiles } from './usePinnedFiles';
|
||||
import { Widget } from 'widgets/Widget';
|
||||
|
||||
type Tab = 'browse' | 'recent' | 'pinned';
|
||||
@@ -2,3 +2,10 @@ export { Breadcrumb } from './Breadcrumb';
|
||||
export { Toolbar } from './Toolbar';
|
||||
export { useFiles, type DirEntry } from './useFiles';
|
||||
export { useTasks, type TaskSummary } from './useTasks';
|
||||
export { Files } from './Files';
|
||||
export { FileBrowser } from './Widget';
|
||||
export { FileGrid } from './FileGrid';
|
||||
export { FileItem, type FileItemProps } from './FileItem';
|
||||
export { TaskRunnerModal } from './TaskRunnerModal';
|
||||
export { useRecentFiles } from './useRecentFiles';
|
||||
export { usePinnedFiles } from './usePinnedFiles';
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "officerdev",
|
||||
"version": "0.0.1",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./*": "./src/*.ts"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import type { AppRegistry } from '@/components/Workspace';
|
||||
|
||||
export function useAppRegistry(): AppRegistry {
|
||||
return {};
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { useAppRegistry } from './appRegistry';
|
||||
@@ -0,0 +1 @@
|
||||
export * from './appRegistry';
|
||||
@@ -0,0 +1 @@
|
||||
export * from './hooks';
|
||||
@@ -0,0 +1,5 @@
|
||||
import type { AppRegistry } from '@/components/Workspace';
|
||||
|
||||
export function useAppRegistry(): AppRegistry {
|
||||
return {};
|
||||
}
|
||||
Reference in New Issue
Block a user