WorkspaceLayout in officerdev workspace

This commit is contained in:
2026-02-22 22:43:28 +00:00
parent 09cff3f763
commit ed0debfeac
51 changed files with 76 additions and 69 deletions
@@ -3,8 +3,8 @@ import { useQuery } from '@tanstack/react-query';
import { useClient } from 'hooks/useClient';
import { useAuth } from 'hooks/useAuth';
import { usePanelChannel } from 'hooks/usePanelChannel';
import type { LayoutNode, PanelComponents } from '@/components/Workspace';
import { WorkspaceLayout } from '@/components/Workspace';
import type { LayoutNode, PanelComponents } from 'officerdev';
import { WorkspaceLayout } from 'officerdev';
import { useUserState } from 'state/useUserState';
import { AutomationRightPanel } from './AutomationRightPanel';
@@ -1,4 +1,4 @@
import type { LayoutNode } from '@/components/Workspace';
import type { LayoutNode } from 'officerdev';
export const defaultLayout: LayoutNode = {
type: 'group',
@@ -1,11 +1,10 @@
import { useEffect } from 'react';
import { useParams } from 'react-router';
import { WorkspaceView } from '@/components/Workspace';
import type { LayoutNode } from '@/components/Workspace';
import type { LayoutNode, SelectedSession } from 'officerdev';
import { WorkspaceView } from 'officerdev';
import { useWorkspacesState } from 'state/useWorkspacesState';
import { usePanelChannel } from 'hooks/usePanelChannel';
import { useChatSessions } from 'state/useChatSessions';
import type { SelectedSession } from 'officerdev';
import { defaultLayout } from './defaultLayout';
export { ChatHistory as ChatHistoryApp } from './Widget';
@@ -1,7 +1,6 @@
import type { LayoutNode } from '@/components/Workspace';
import { WorkspaceView } from '@/components/Workspace';
import type { LayoutNode } from 'officerdev';
import { WorkspaceView, useFileViewerPanels } from 'officerdev';
import { useWorkspacesState } from 'state/useWorkspacesState';
import { useFileViewerPanels } from 'officerdev';
import { defaultLayout } from './defaultLayout';
export const FilesScreen = () => {
@@ -1,4 +1,4 @@
import type { LayoutNode } from '@/components/Workspace';
import type { LayoutNode } from 'officerdev';
export const defaultLayout: LayoutNode = {
type: 'panel',
@@ -1,5 +1,5 @@
import type { LayoutNode } from '@/components/Workspace';
import { WorkspaceView } from '@/components/Workspace';
import type { LayoutNode } from 'officerdev';
import { WorkspaceView } from 'officerdev';
import { useWorkspacesState } from 'state/useWorkspacesState';
import { defaultLayout } from './defaultLayout';
@@ -1,4 +1,4 @@
import type { LayoutNode } from '@/components/Workspace';
import type { LayoutNode } from 'officerdev';
export const defaultLayout: LayoutNode = {
type: 'group',
@@ -2,10 +2,9 @@ import { useEffect } from 'react';
import { useNavigate, useSearchParams } from 'react-router';
import { useGlobal } from 'hooks/useGlobal';
import { useWorkspacesState } from 'state/useWorkspacesState';
import { WorkspaceView } from '@/components/Workspace';
import type { LayoutNode, ProjectType } from '@/components/Workspace';
import { generateSlug } from 'helpers/slug';
import type { LayoutNode, ProjectType } from 'officerdev';
import {
WorkspaceView,
SELECTED_PROJECT,
CREATING_PROJECT,
EDITING_PROJECT,
@@ -16,6 +15,7 @@ import {
NEW_PROJ_HAS_BACKEND,
NEW_PROJ_HAS_AUTH,
} from 'officerdev';
import { generateSlug } from 'helpers/slug';
import { defaultLayout } from './defaultLayout';
export const ProjectListScreen = () => {
@@ -1,7 +1,7 @@
import { useParams, Navigate } from 'react-router';
import { useWorkspacesState } from 'state/useWorkspacesState';
import { WorkspaceView, createDefaultLayout } from '@/components/Workspace';
import type { LayoutNode, ProjectDefinition } from '@/components/Workspace';
import type { LayoutNode, ProjectDefinition } from 'officerdev';
import { WorkspaceView, createDefaultLayout } from 'officerdev';
export const ProjectScreen = () => {
const { id } = useParams<{ id: string }>();
@@ -1,4 +1,4 @@
import type { LayoutNode } from '@/components/Workspace';
import type { LayoutNode } from 'officerdev';
export const defaultLayout: LayoutNode = {
type: 'group',
@@ -1,7 +1,7 @@
import { useMemo } from 'react';
import { User, Lock, Globe, ListChecks } from 'lucide-react';
import type { LayoutNode, PanelComponents } from '@/components/Workspace';
import { WorkspaceLayout } from '@/components/Workspace';
import type { LayoutNode, PanelComponents } from 'officerdev';
import { WorkspaceLayout } from 'officerdev';
import { createSettingsPanelComponents, type SettingsSection } from '../SettingsPanel';
import { UserData } from './UserData';
@@ -2,11 +2,10 @@ import { useState, useEffect, useMemo } from 'react';
import { X } from 'lucide-react';
import { toast } from 'sonner';
import { useQueryClient } from '@tanstack/react-query';
import type { LayoutNode, PanelComponents } from '@/components/Workspace';
import { WorkspaceLayout } from '@/components/Workspace';
import type { LayoutNode, PanelComponents } from 'officerdev';
import { WorkspaceLayout, TerminalView, FileViewerView } from 'officerdev';
import { usePanelChannel } from 'hooks/usePanelChannel';
import { useClient } from 'hooks/useClient';
import { TerminalView, FileViewerView } from 'officerdev';
import { Resources } from './Resources';
import { ResourceSidebar } from './ResourceSidebar';
@@ -8,10 +8,9 @@ import { Textarea } from '@/components/ui/textarea';
import { Slider } from '@/components/ui/slider';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { useQueryClient } from '@tanstack/react-query';
import type { LayoutNode, PanelComponents } from '@/components/Workspace';
import { WorkspaceLayout } from '@/components/Workspace';
import type { LayoutNode, PanelComponents } from 'officerdev';
import { WorkspaceLayout, TerminalView } from 'officerdev';
import { usePanelChannel } from 'hooks/usePanelChannel';
import { TerminalView } from 'officerdev';
import { createSettingsPanelComponents, type SettingsSectionGroup } from './SettingsPanel';
import { useSettings } from 'state/useSettings';
@@ -1,5 +1,5 @@
import type { LayoutNode } from '@/components/Workspace';
import { WorkspaceView } from '@/components/Workspace';
import type { LayoutNode } from 'officerdev';
import { WorkspaceView } from 'officerdev';
import { useWorkspacesState } from 'state/useWorkspacesState';
import { defaultLayout } from './defaultLayout';
@@ -1,4 +1,4 @@
import type { LayoutNode } from '@/components/Workspace';
import type { LayoutNode } from 'officerdev';
export const defaultLayout: LayoutNode = {
type: 'panel',
@@ -1,7 +1,7 @@
import { useParams, Navigate } from 'react-router';
import { useWorkspacesState } from 'state/useWorkspacesState';
import { WorkspaceView, createDefaultLayout } from '@/components/Workspace';
import type { LayoutNode, WorkspaceDefinition } from '@/components/Workspace';
import type { LayoutNode, WorkspaceDefinition } from 'officerdev';
import { WorkspaceView, createDefaultLayout } from 'officerdev';
export const WorkspaceScreen = () => {
const { id } = useParams<{ id: string }>();
@@ -1,5 +1,5 @@
import type { LayoutNode } from '@/components/Workspace';
import { WorkspaceView } from '@/components/Workspace';
import type { LayoutNode } from 'officerdev';
import { WorkspaceView } from 'officerdev';
import { useWorkspacesState } from 'state/useWorkspacesState';
import { defaultLayout } from './defaultLayout';
@@ -1,4 +1,4 @@
import type { LayoutNode } from '@/components/Workspace';
import type { LayoutNode } from 'officerdev';
export const defaultLayout: LayoutNode = {
type: 'group',
-1
View File
@@ -5,7 +5,6 @@
"type": "module",
"exports": {
".": "./index.ts",
"./Workspace": "./Workspace/index.ts",
"./*": "./*.tsx",
"./ui/*": "./ui/*.tsx"
},
@@ -1,4 +1,4 @@
import type { AppRegistry, AppRegistryEntry } from '@/components/Workspace';
import type { AppRegistry, AppRegistryEntry } from '../../components/Workspace';
import { useGlobal } from 'hooks/useGlobal';
export type AppRegistryMeta = { key: string } & AppRegistryEntry;
@@ -2,6 +2,7 @@ import { useState } from 'react';
import { useLocation } from 'react-router';
import { Trash2, Home, Monitor } from 'lucide-react';
import { usePanelChannel } from 'hooks/usePanelChannel';
import { useAuth } from 'hooks/useAuth';
import { useChatSessions } from 'state/useChatSessions';
import { getHostHome } from 'state/useModels';
import { usePiChat, EmbeddableChat } from '../Chat';
@@ -126,15 +127,17 @@ function CwdToggle({ cwdMode, onChange }: { cwdMode: 'user' | 'host'; onChange:
);
}
function NewChat() {
function NewChat({ allowHostMode }: { allowHostMode?: boolean }) {
const location = useLocation();
const locationState = location.state as ChatLocationState;
const { user } = useAuth();
const showToggle = allowHostMode && user?.role === 'Super Admin';
const [cwdMode, setCwdMode] = useState<'user' | 'host'>('user');
const chat = usePiChat(undefined, locationState?.model);
const sandboxed = cwdMode === 'user';
const cwd = cwdMode === 'host' ? { path: getHostHome() } : locationState?.cwd;
const sandboxed = showToggle ? cwdMode === 'user' : true;
const cwd = !sandboxed ? { path: getHostHome() } : locationState?.cwd;
const initialMessage = locationState?.initialMessage
? {
@@ -153,7 +156,7 @@ function NewChat() {
isGenerating={chat.isGenerating}
onDelete={undefined}
/>
{!chat.hasStarted && <CwdToggle cwdMode={cwdMode} onChange={setCwdMode} />}
{!chat.hasStarted && showToggle && <CwdToggle cwdMode={cwdMode} onChange={setCwdMode} />}
<EmbeddableChat
chat={chat}
sessionId={undefined}
@@ -168,7 +171,11 @@ function NewChat() {
);
}
export const ChatDetailPanel = () => {
type ChatDetailPanelProps = {
allowHostMode?: boolean;
};
export const ChatDetailPanel = ({ allowHostMode }: ChatDetailPanelProps) => {
const [selected] = usePanelChannel<SelectedSession>(CHANNEL, null);
if (!selected) {
@@ -180,7 +187,7 @@ export const ChatDetailPanel = () => {
}
if (selected.id.startsWith('new')) {
return <NewChat key={selected.id} />;
return <NewChat key={selected.id} allowHostMode={allowHostMode} />;
}
return <SessionChat key={selected.id} sessionId={selected.id} model={selected.model} />;
@@ -20,7 +20,7 @@ export const appRegistryMetas: AppRegistryMeta[] = [
key: 'chat-detail',
name: 'Chat',
icon: MessageSquare,
component: ChatDetailPanel,
component: () => ChatDetailPanel({ allowHostMode: true }),
availableOnPanel: false,
},
];
@@ -1,5 +1,5 @@
import { Code } from 'lucide-react';
import { useWorkspace } from '@/components/Workspace';
import { useWorkspace } from '../../components/Workspace';
export const CodeEditorHeader = () => {
const { cwd } = useWorkspace();
@@ -1,4 +1,4 @@
import { useWorkspace } from '@/components/Workspace';
import { useWorkspace } from '../../../components/Workspace';
import { FileBrowserApp } from './FileBrowserApp';
const cwdToPath = (cwd: string) => (cwd === '~' ? '/' : cwd.slice(1));
@@ -1,5 +1,5 @@
import { createContext, useContext } from 'react';
import type { ProjectDefinition } from '@/components/Workspace';
import type { ProjectDefinition } from '../../components/Workspace';
export type PreviewContextValue = {
slug: string | null;
@@ -2,8 +2,8 @@ import { useState, useEffect, useCallback } from 'react';
import type { ReactNode } from 'react';
import { useClient } from 'hooks/useClient';
import { useAuth } from 'hooks/useAuth';
import { useWorkspace } from '@/components/Workspace';
import type { ProjectDefinition } from '@/components/Workspace';
import { useWorkspace } from '../../components/Workspace';
import type { ProjectDefinition } from '../../components/Workspace';
import { useWorkspacesState } from 'state/useWorkspacesState';
import { PreviewContext } from './PreviewContext';
@@ -6,7 +6,7 @@ import { useGlobal } from 'hooks/useGlobal';
import { useClient } from 'hooks/useClient';
import { generateSlug } from 'helpers/slug';
import { useWorkspacesState } from 'state/useWorkspacesState';
import type { ProjectDefinition, ProjectType } from '@/components/Workspace';
import type { ProjectDefinition, ProjectType } from '../../components/Workspace';
import {
AlertDialog,
AlertDialogAction,
@@ -5,8 +5,8 @@ import { useQueryClient } from '@tanstack/react-query';
import { useGlobal } from 'hooks/useGlobal';
import { useClient } from 'hooks/useClient';
import { useWorkspacesState } from 'state/useWorkspacesState';
import { WorkspaceLayout, WorkspaceView, createDefaultLayout } from '@/components/Workspace';
import type { LayoutNode, ProjectDefinition, ProjectType } from '@/components/Workspace';
import { WorkspaceLayout, WorkspaceView, createDefaultLayout } from '../../components/Workspace';
import type { LayoutNode, ProjectDefinition, ProjectType } from '../../components/Workspace';
import { Button } from '@/components/ui/button';
import { generateSlug, slugify } from 'helpers/slug';
import { useAppRegistry } from '../../AppRegistry';
@@ -1,5 +1,5 @@
import { useEffect, useRef } from 'react';
import { useWorkspace } from '@/components/Workspace';
import { useWorkspace } from '../../components/Workspace';
import { useWorkspacesState } from 'state/useWorkspacesState';
import { TerminalView } from './Terminal';
@@ -1,5 +1,5 @@
import { TerminalSquare, Monitor, Columns2, PenLine } from 'lucide-react';
import { useWorkspace } from '@/components/Workspace';
import { useWorkspace } from '../../components/Workspace';
import { useAuth } from 'hooks/useAuth';
import { useTerminalMode } from './useTerminalMode';
@@ -1,5 +1,5 @@
import { useEffect, useRef } from 'react';
import { useWorkspace } from '@/components/Workspace';
import { useWorkspace } from '../../components/Workspace';
import { useAuth } from 'hooks/useAuth';
import { useWorkspacesState } from 'state/useWorkspacesState';
import { TerminalView } from './Terminal';
@@ -1,5 +1,5 @@
import { useEffect, useRef } from 'react';
import { useWorkspace } from '@/components/Workspace';
import { useWorkspace } from '../../components/Workspace';
import { useWorkspacesState } from 'state/useWorkspacesState';
import { TerminalView } from './Terminal';
import { useTerminalMode } from './useTerminalMode';
@@ -7,7 +7,7 @@ import { useClient } from 'hooks/useClient';
import { generateSlug } from 'helpers/slug';
import { useUserState } from 'state/useUserState';
import { useWorkspacesState } from 'state/useWorkspacesState';
import type { WorkspaceDefinition } from '@/components/Workspace';
import type { WorkspaceDefinition } from '../../components/Workspace';
import {
AlertDialog,
AlertDialogAction,
@@ -6,8 +6,8 @@ import { useGlobal } from 'hooks/useGlobal';
import { useClient } from 'hooks/useClient';
import { useUserState } from 'state/useUserState';
import { useWorkspacesState } from 'state/useWorkspacesState';
import { WorkspaceLayout, WorkspaceView, createDefaultLayout } from '@/components/Workspace';
import type { LayoutNode, WorkspaceDefinition } from '@/components/Workspace';
import { WorkspaceLayout, WorkspaceView, createDefaultLayout } from '../../components/Workspace';
import type { LayoutNode, WorkspaceDefinition } from '../../components/Workspace';
import { Button } from '@/components/ui/button';
import { generateSlug, slugify } from 'helpers/slug';
import { FileBrowserApp } from '../FileBrowser';
@@ -4,7 +4,7 @@ import { createPortal } from 'react-dom';
import { ArrowLeftRight, X, Minus } from 'lucide-react';
import type { LayoutPanel, AppRegistry, PanelComponents, PanelComponentEntry } from './types';
import { useWorkspace } from './WorkspaceContext';
import { Card } from '../Card';
import { Card } from '@/components/Card';
import { AppPicker } from './AppPicker';
import {
ContextMenu,
@@ -12,7 +12,7 @@ import {
ContextMenuContent,
ContextMenuItem,
ContextMenuSeparator,
} from '../ui/context-menu';
} from '@/components/ui/context-menu';
type PanelSlotProps = {
panel: LayoutPanel;
@@ -3,7 +3,7 @@ import type { LayoutNode, AppRegistry, PanelComponents } from './types';
import { updateSizes } from './layout-utils';
import { WorkspaceProvider } from './WorkspaceContext';
import { WorkspaceRenderer } from './WorkspaceRenderer';
import { useAppRegistry } from 'officerdev';
import { useAppRegistry } from '../../AppRegistry/useAppRegistry';
type WorkspaceLayoutProps = {
layout: LayoutNode;
@@ -1,5 +1,5 @@
import { useCallback, useRef } from 'react';
import { ResizablePanel, ResizablePanelGroup, ResizableHandle } from '../ui/resizable';
import { ResizablePanel, ResizablePanelGroup, ResizableHandle } from '@/components/ui/resizable';
import type { LayoutNode, AppRegistry, PanelComponents } from './types';
import { countPanels } from './layout-utils';
import { PanelSlot } from './PanelSlot';
@@ -1,12 +1,12 @@
import { useState, useCallback, useEffect } from 'react';
import { flushSync } from 'react-dom';
import { ResizablePanel, ResizablePanelGroup, ResizableHandle } from '../ui/resizable';
import { ResizablePanel, ResizablePanelGroup, ResizableHandle } from '@/components/ui/resizable';
import type { LayoutNode, WorkspaceState, EphemeralPanels } from './types';
import type { DropPosition } from './layout-utils';
import { splitPanel, removePanel, setApp, updateSizes, swapPanels, movePanel, countPanels } from './layout-utils';
import { WorkspaceProvider } from './WorkspaceContext';
import { WorkspaceRenderer } from './WorkspaceRenderer';
import { useAppRegistry } from 'officerdev';
import { useAppRegistry } from '../../AppRegistry/useAppRegistry';
type WorkspaceViewProps = {
workspace: WorkspaceState;
@@ -1,4 +1,4 @@
import type { LayoutNode } from '@/components/Workspace';
import type { LayoutNode } from '../../components/Workspace';
export const singleViewerLayout: LayoutNode = {
type: 'panel',
@@ -1,5 +1,5 @@
import { useSearchParams } from 'react-router';
import type { EphemeralPanels } from '@/components/Workspace';
import type { EphemeralPanels } from '../../components/Workspace';
import { FileViewerHeader, FileViewerBody } from '../../apps/FileViewer';
import { singleViewerLayout, viewerWithEphemeralLayout, viewerWithEphemeralSplitLayout } from './layouts';
import { ViewerProvider, EphemeralProvider, Ephemeral2Provider } from './Providers';
+5
View File
@@ -16,3 +16,8 @@ export { TerminalView } from './apps/Terminal';
export type { TerminalViewProps } from './apps/Terminal';
export { WorkspaceListApp, WorkspacePreview, SELECTED_WORKSPACE_KEY, CREATING_WORKSPACE_KEY, EDITING_WORKSPACE_KEY, NEW_WS_NAME_KEY, NEW_WS_DESC_KEY, NEW_WS_TEMPLATE_KEY } from './apps/Workspaces';
export { ProjectListApp, ProjectPreview, SELECTED_PROJECT, CREATING_PROJECT, EDITING_PROJECT, NEW_PROJ_NAME, NEW_PROJ_DESC, NEW_PROJ_TEMPLATE, NEW_PROJ_TYPE, NEW_PROJ_HAS_BACKEND, NEW_PROJ_HAS_AUTH, NEW_PROJ_PREVIEW_LAYOUT } from './apps/Projects';
// Workspace
export { WorkspaceView, WorkspaceLayout, WorkspaceProvider, useWorkspace } from './components/Workspace';
export { createDefaultLayout, splitPanel, removePanel, setApp, updateSizes, swapPanels, movePanel, pruneEmptyPanels, countPanels, hasAnyApp } from './components/Workspace';
export type { LayoutNode, LayoutGroup, LayoutPanel, WorkspaceDefinition, WorkspaceState, ProjectType, ProjectDefinition, AppRegistry, AppRegistryEntry, PanelComponents, PanelComponentEntry, EphemeralPanels, DropPosition } from './components/Workspace';
@@ -1,4 +1,4 @@
import type { AppRegistry } from '@/components/Workspace';
import type { AppRegistry } from './components/Workspace';
export function useAppRegistry(): AppRegistry {
return {};
+1 -1
View File
@@ -1,5 +1,5 @@
import { Clock as ClockIcon, CloudSun, Timer, Target, StickyNote } from 'lucide-react';
import type { AppRegistryEntry } from '@/components/Workspace';
import type { AppRegistryEntry } from 'officerdev';
import { Clock } from './Clock/index';
import { Weather } from './Weather/index';
import { Pomodoro } from './Pomodoro/index';