diff --git a/bun.lock b/bun.lock index 9a31afca..97946082 100644 --- a/bun.lock +++ b/bun.lock @@ -101,6 +101,7 @@ "shiki": "^3.22.0", "sonner": "^2.0.7", "sounds": "workspace:*", + "state": "workspace:*", "tailwind-merge": "^3.3.1", "tailwindcss-animate": "^1.0.7", "three": "^0.182.0", @@ -245,6 +246,7 @@ "version": "0.0.1", "dependencies": { "hooks": "workspace:*", + "state": "workspace:*", "widgets": "workspace:*", }, }, @@ -252,6 +254,14 @@ "name": "sounds", "version": "1.0.0", }, + "src/workspaces/state": { + "name": "state", + "version": "0.0.1", + "dependencies": { + "hooks": "workspace:*", + "officerdev": "workspace:*", + }, + }, "src/workspaces/types": { "name": "types", "version": "0.0.1", @@ -1989,6 +1999,8 @@ "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], + "state": ["state@workspace:src/workspaces/state"], + "state-local": ["state-local@1.0.7", "", {}, "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w=="], "stats-gl": ["stats-gl@2.4.2", "", { "dependencies": { "@types/three": "*", "three": "^0.170.0" } }, "sha512-g5O9B0hm9CvnM36+v7SFl39T7hmAlv541tU81ME8YeSb3i1CIP5/QdDeSB3A0la0bKNHpxpwxOVRo2wFTYEosQ=="], diff --git a/package.json b/package.json index 8d055704..7d3ddab7 100644 --- a/package.json +++ b/package.json @@ -119,6 +119,7 @@ "shiki": "^3.22.0", "sonner": "^2.0.7", "sounds": "workspace:*", + "state": "workspace:*", "tailwind-merge": "^3.3.1", "tailwindcss-animate": "^1.0.7", "three": "^0.182.0", diff --git a/src/apps/officer-web/App.tsx b/src/apps/officer-web/App.tsx index 5d6df1f7..78fc74e6 100644 --- a/src/apps/officer-web/App.tsx +++ b/src/apps/officer-web/App.tsx @@ -2,7 +2,7 @@ import { BrowserRouter, Routes, Route, Navigate } from 'react-router'; import * as Authentication from './Screens/Authentication'; import * as Dashboard from './Screens/Dashboard'; import { useAuth } from 'hooks/useAuth'; -import { useServerSettings } from '@/state/useServerSettings'; +import { useServerSettings } from 'state/useServerSettings'; import { useInitialData } from '@/state/useInitialData'; export function App() { @@ -51,9 +51,7 @@ export function App() { } /> } /> } /> - } /> - } /> - } /> + } /> } /> } /> } /> diff --git a/src/apps/officer-web/Screens/Authentication/LandingPage/LandingPage.tsx b/src/apps/officer-web/Screens/Authentication/LandingPage/LandingPage.tsx index f23e21e9..f127f72f 100644 --- a/src/apps/officer-web/Screens/Authentication/LandingPage/LandingPage.tsx +++ b/src/apps/officer-web/Screens/Authentication/LandingPage/LandingPage.tsx @@ -1,5 +1,5 @@ import { OfficerSvgLogo } from '@/components/Logos'; -import { useLandingPage } from '@/state/useLandingPage'; +import { useLandingPage } from 'state/useLandingPage'; import { Bootstrap } from './Bootstrap'; import { Login } from './Login'; diff --git a/src/apps/officer-web/Screens/Dashboard/Automation/index.tsx b/src/apps/officer-web/Screens/Dashboard/Automation/index.tsx index 2b76cca3..e267878c 100644 --- a/src/apps/officer-web/Screens/Dashboard/Automation/index.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Automation/index.tsx @@ -5,8 +5,8 @@ import { useAuth } from 'hooks/useAuth'; import { usePanelChannel } from 'hooks/usePanelChannel'; import type { LayoutNode, PanelComponents } from '@/components/Workspace'; import { WorkspaceLayout } from '@/components/Workspace'; -import { useUserState } from '@/state/useUserState'; -import { appRegistry } from '../Workspaces/app-registry'; +import { useUserState } from 'state/useUserState'; + import { AutomationRightPanel } from './AutomationRightPanel'; import type { AutomationSelection } from './AutomationRightPanel'; import { AutomationEditChat } from './AutomationEditChat'; @@ -98,7 +98,7 @@ export const Automation = () => { return (
- +
); }; diff --git a/src/apps/officer-web/Screens/Dashboard/ChatHistory/ChatDetailPanel.tsx b/src/apps/officer-web/Screens/Dashboard/ChatHistory/ChatDetailPanel.tsx index e0538ec3..fbe8ecb3 100644 --- a/src/apps/officer-web/Screens/Dashboard/ChatHistory/ChatDetailPanel.tsx +++ b/src/apps/officer-web/Screens/Dashboard/ChatHistory/ChatDetailPanel.tsx @@ -2,7 +2,7 @@ import { useEffect } from 'react'; import { useLocation } from 'react-router'; import { Trash2 } from 'lucide-react'; import { usePanelChannel } from 'hooks/usePanelChannel'; -import { useChatSessions } from '@/state/useChatSessions'; +import { useChatSessions } from 'state/useChatSessions'; import { usePiChat, EmbeddableChat } from 'officerdev'; export type SelectedSession = { diff --git a/src/apps/officer-web/Screens/Dashboard/ChatHistory/CreateGroupDialog.tsx b/src/apps/officer-web/Screens/Dashboard/ChatHistory/CreateGroupDialog.tsx index 5ae11530..be2349b5 100644 --- a/src/apps/officer-web/Screens/Dashboard/ChatHistory/CreateGroupDialog.tsx +++ b/src/apps/officer-web/Screens/Dashboard/ChatHistory/CreateGroupDialog.tsx @@ -1,6 +1,6 @@ import { useState } from 'react'; import { X } from 'lucide-react'; -import { useChatGroups } from '@/state/useChatGroups'; +import { useChatGroups } from 'state/useChatGroups'; type CreateGroupDialogProps = { onClose: () => void; diff --git a/src/apps/officer-web/Screens/Dashboard/ChatHistory/GroupContextMenu.tsx b/src/apps/officer-web/Screens/Dashboard/ChatHistory/GroupContextMenu.tsx index 688ed08b..e9be9384 100644 --- a/src/apps/officer-web/Screens/Dashboard/ChatHistory/GroupContextMenu.tsx +++ b/src/apps/officer-web/Screens/Dashboard/ChatHistory/GroupContextMenu.tsx @@ -1,6 +1,6 @@ import { useState } from 'react'; import { MoreVertical, Edit2, Trash2 } from 'lucide-react'; -import { useChatGroups } from '@/state/useChatGroups'; +import { useChatGroups } from 'state/useChatGroups'; import type { GroupEntry } from 'officerdev'; type GroupContextMenuProps = { diff --git a/src/apps/officer-web/Screens/Dashboard/ChatHistory/Screen.tsx b/src/apps/officer-web/Screens/Dashboard/ChatHistory/Screen.tsx index db5c7eb1..4caff5f9 100644 --- a/src/apps/officer-web/Screens/Dashboard/ChatHistory/Screen.tsx +++ b/src/apps/officer-web/Screens/Dashboard/ChatHistory/Screen.tsx @@ -1,8 +1,8 @@ import { useEffect, useRef, useCallback, useState } from 'react'; import { Plus, MessageSquare, Folder, ChevronRight, FolderPlus } from 'lucide-react'; import { usePanelChannel } from 'hooks/usePanelChannel'; -import { useChatSessions } from '@/state/useChatSessions'; -import { useChatGroups } from '@/state/useChatGroups'; +import { useChatSessions } from 'state/useChatSessions'; +import { useChatGroups } from 'state/useChatGroups'; import type { SelectedSession } from './ChatDetailPanel'; import { CreateGroupDialog } from './CreateGroupDialog'; import { GroupContextMenu } from './GroupContextMenu'; diff --git a/src/apps/officer-web/Screens/Dashboard/ChatHistory/SessionContextMenu.tsx b/src/apps/officer-web/Screens/Dashboard/ChatHistory/SessionContextMenu.tsx index c9161d0c..5665c2d5 100644 --- a/src/apps/officer-web/Screens/Dashboard/ChatHistory/SessionContextMenu.tsx +++ b/src/apps/officer-web/Screens/Dashboard/ChatHistory/SessionContextMenu.tsx @@ -1,7 +1,7 @@ import { useState } from 'react'; import { MoreVertical, FolderInput, Edit2, Trash2 } from 'lucide-react'; -import { useChatGroups } from '@/state/useChatGroups'; -import { useChatSessions } from '@/state/useChatSessions'; +import { useChatGroups } from 'state/useChatGroups'; +import { useChatSessions } from 'state/useChatSessions'; import type { SessionEntry } from 'officerdev'; type SessionContextMenuProps = { diff --git a/src/apps/officer-web/Screens/Dashboard/ChatHistory/Widget.tsx b/src/apps/officer-web/Screens/Dashboard/ChatHistory/Widget.tsx index 674977d2..ca45633c 100644 --- a/src/apps/officer-web/Screens/Dashboard/ChatHistory/Widget.tsx +++ b/src/apps/officer-web/Screens/Dashboard/ChatHistory/Widget.tsx @@ -1,7 +1,7 @@ import { Link } from 'react-router'; import { MessageSquare, Trash2 } from 'lucide-react'; import { Widget } from 'widgets/Widget'; -import { useChatSessions } from '@/state/useChatSessions'; +import { useChatSessions } from 'state/useChatSessions'; export const ChatHistory = () => { const { sessions, deleteSession } = useChatSessions(); diff --git a/src/apps/officer-web/Screens/Dashboard/ChatHistory/index.tsx b/src/apps/officer-web/Screens/Dashboard/ChatHistory/index.tsx index 3a99c5b3..e2222060 100644 --- a/src/apps/officer-web/Screens/Dashboard/ChatHistory/index.tsx +++ b/src/apps/officer-web/Screens/Dashboard/ChatHistory/index.tsx @@ -3,8 +3,8 @@ import { useParams } from 'react-router'; import type { LayoutNode, PanelComponents } from '@/components/Workspace'; import { WorkspaceLayout } from '@/components/Workspace'; import { usePanelChannel } from 'hooks/usePanelChannel'; -import { useChatSessions } from '@/state/useChatSessions'; -import { appRegistry } from '../Workspaces/app-registry'; +import { useChatSessions } from 'state/useChatSessions'; + import { SessionList } from './Screen'; import { ChatDetailPanel, type SelectedSession } from './ChatDetailPanel'; @@ -50,7 +50,7 @@ export const SessionListPage = ({ isNew }: SessionListPageProps) => { return (
- {}} registry={appRegistry} components={panelComponents} /> + {}} components={panelComponents} />
); }; diff --git a/src/apps/officer-web/Screens/Dashboard/ChatScreen/ChatScreen.tsx b/src/apps/officer-web/Screens/Dashboard/ChatScreen/ChatScreen.tsx index 5aeb7492..5ebf205e 100644 --- a/src/apps/officer-web/Screens/Dashboard/ChatScreen/ChatScreen.tsx +++ b/src/apps/officer-web/Screens/Dashboard/ChatScreen/ChatScreen.tsx @@ -1,6 +1,6 @@ import { useRef, useEffect, useState } from 'react'; import { useNavigate, useLocation } from 'react-router'; -import { useChatSessions } from '@/state/useChatSessions'; +import { useChatSessions } from 'state/useChatSessions'; import { SessionBar, EmbeddableChat, type UsePiChatType, type Attachment } from 'officerdev'; import { Card } from '@/components/Card'; diff --git a/src/apps/officer-web/Screens/Dashboard/Files/FilesScreen.tsx b/src/apps/officer-web/Screens/Dashboard/Files/FilesScreen.tsx index 5f740980..cc227cf1 100644 --- a/src/apps/officer-web/Screens/Dashboard/Files/FilesScreen.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Files/FilesScreen.tsx @@ -1,8 +1,7 @@ import type { LayoutNode } from '@/components/Workspace'; import { WorkspaceView } from '@/components/Workspace'; -import { useWorkspacesState } from '@/state/useWorkspacesState'; +import { useWorkspacesState } from 'state/useWorkspacesState'; import { useFileViewerPanels } from 'officerdev'; -import { appRegistry } from '../Workspaces/app-registry'; import { defaultLayout } from './defaultLayout'; export const FilesScreen = () => { @@ -11,7 +10,7 @@ export const FilesScreen = () => { return (
- +
); }; diff --git a/src/apps/officer-web/Screens/Dashboard/Files/defaultLayout.ts b/src/apps/officer-web/Screens/Dashboard/Files/defaultLayout.ts index a5dd3d4d..8981a693 100644 --- a/src/apps/officer-web/Screens/Dashboard/Files/defaultLayout.ts +++ b/src/apps/officer-web/Screens/Dashboard/Files/defaultLayout.ts @@ -3,5 +3,5 @@ import type { LayoutNode } from '@/components/Workspace'; export const defaultLayout: LayoutNode = { type: 'panel', id: 'files-main', - appType: 'file-browser', + appType: 'officerdev/file-browser', }; diff --git a/src/apps/officer-web/Screens/Dashboard/Home/HomeScreen.tsx b/src/apps/officer-web/Screens/Dashboard/Home/HomeScreen.tsx index 5cf67f49..9805d538 100644 --- a/src/apps/officer-web/Screens/Dashboard/Home/HomeScreen.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Home/HomeScreen.tsx @@ -1,6 +1,6 @@ +import type { LayoutNode } from '@/components/Workspace'; import { WorkspaceView } from '@/components/Workspace'; -import { useWorkspacesState } from '@/state/useWorkspacesState'; -import { appRegistry } from '../Workspaces/app-registry'; +import { useWorkspacesState } from 'state/useWorkspacesState'; import { defaultLayout } from './defaultLayout'; export const HomeScreen = () => { @@ -8,8 +8,7 @@ export const HomeScreen = () => { return (
- +
); }; - diff --git a/src/apps/officer-web/Screens/Dashboard/Layout/Dock.tsx b/src/apps/officer-web/Screens/Dashboard/Layout/Dock.tsx index cf94d577..f3c037b4 100644 --- a/src/apps/officer-web/Screens/Dashboard/Layout/Dock.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Layout/Dock.tsx @@ -120,7 +120,7 @@ export const dockItems: DockItem[] = [ { label: 'Plans', to: '/plans', icon: FileText, color: '#f472b6' }, { label: 'Automation', to: '/automation', icon: Bot, color: '#2dd4bf' }, { label: 'Logs', to: '/task-logs', icon: ScrollText, color: '#94a3b8' }, - { label: 'Terminal', to: '/terminal', icon: Monitor, color: '#f97316', role: 'Super Admin' }, + { label: 'Terminal', to: '/terminal', icon: Monitor, color: '#f97316' }, { label: 'Projects', to: '/projects', icon: FolderKanban, color: '#10b981' }, { label: 'Workspaces', to: '/workspaces', icon: LayoutGrid, color: '#8b5cf6' }, ]; diff --git a/src/apps/officer-web/Screens/Dashboard/Layout/Header/UserMenu.tsx b/src/apps/officer-web/Screens/Dashboard/Layout/Header/UserMenu.tsx index 51756f44..c08e011d 100644 --- a/src/apps/officer-web/Screens/Dashboard/Layout/Header/UserMenu.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Layout/Header/UserMenu.tsx @@ -5,7 +5,7 @@ import { User, LogOut, Settings, Package, Sun, Moon } from 'lucide-react'; import { useAuth } from 'hooks/useAuth'; import { useTranslation } from '@/lib/i18n'; import { useColorMode } from '@/components/ui/ThemeProvider'; -import { useSettings } from '@/state/useSettings'; +import { useSettings } from 'state/useSettings'; const { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } = Dropdown; diff --git a/src/apps/officer-web/Screens/Dashboard/OnboardingAdmin/index.tsx b/src/apps/officer-web/Screens/Dashboard/OnboardingAdmin/index.tsx index bd3e7781..d586469a 100644 --- a/src/apps/officer-web/Screens/Dashboard/OnboardingAdmin/index.tsx +++ b/src/apps/officer-web/Screens/Dashboard/OnboardingAdmin/index.tsx @@ -1,5 +1,5 @@ import { useState, useEffect } from 'react'; -import { useServerSettings } from '@/state/useServerSettings'; +import { useServerSettings } from 'state/useServerSettings'; import { ServerTypeCard } from './ServerTypeCard'; import { AIHarnessesCard } from './AIHarnessesCard'; diff --git a/src/apps/officer-web/Screens/Dashboard/Projects/ProjectListApp.tsx b/src/apps/officer-web/Screens/Dashboard/Projects/ProjectListApp.tsx index 955b4938..aa04981d 100644 --- a/src/apps/officer-web/Screens/Dashboard/Projects/ProjectListApp.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Projects/ProjectListApp.tsx @@ -5,7 +5,7 @@ import { useQueryClient } from '@tanstack/react-query'; import { useGlobal } from 'hooks/useGlobal'; import { useClient } from 'hooks/useClient'; import { generateSlug } from 'helpers/slug'; -import { useProjectsState } from '@/state/useProjectsState'; +import { useProjectsState } from 'state/useProjectsState'; import type { ProjectDefinition, ProjectType } from '@/components/Workspace'; import { AlertDialog, diff --git a/src/apps/officer-web/Screens/Dashboard/Projects/ProjectListScreen.tsx b/src/apps/officer-web/Screens/Dashboard/Projects/ProjectListScreen.tsx index b8c07988..a7aeb758 100644 --- a/src/apps/officer-web/Screens/Dashboard/Projects/ProjectListScreen.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Projects/ProjectListScreen.tsx @@ -4,12 +4,12 @@ import { FolderKanban, Plus, ArrowRight, Type, Rocket, FileText, Layout } from ' import { useQueryClient } from '@tanstack/react-query'; import { useGlobal } from 'hooks/useGlobal'; import { useClient } from 'hooks/useClient'; -import { useProjectsState } from '@/state/useProjectsState'; +import { useProjectsState } from 'state/useProjectsState'; 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 { appRegistry } from '../Workspaces/app-registry'; +import { useAppRegistry } from 'officerdev'; import { SELECTED_PROJECT, CREATING_PROJECT, @@ -482,7 +482,7 @@ const TemplatePreviewInner = ({ templateIdx }: { templateIdx: number }) => { queryClient.setQueryData(PREVIEW_LAYOUT_KEY, next); }; - return ; + return ; }; const TemplatePreviewPanel = () => { @@ -524,17 +524,18 @@ const newProjLayout: LayoutNode = { ], }; -const newProjRegistry = { - ...appRegistry, - 'new-proj-details': { name: 'Details', icon: Type, component: DetailsPanel }, - 'new-proj-placeholder': { name: 'Preview', icon: Layout, component: TemplatePreviewPanel }, - 'new-proj-template': { name: 'Template', icon: Layout, component: TemplatePanel }, - 'new-proj-create': { name: 'Create', icon: Rocket, component: CreatePanel }, -}; +const NewProjectForm = () => { + const { registry } = useAppRegistry(); + const newProjRegistry = { + ...registry, + 'new-proj-details': { name: 'Details', icon: Type, component: DetailsPanel }, + 'new-proj-placeholder': { name: 'Preview', icon: Layout, component: TemplatePreviewPanel }, + 'new-proj-template': { name: 'Template', icon: Layout, component: TemplatePanel }, + 'new-proj-create': { name: 'Create', icon: Rocket, component: CreatePanel }, + }; -const NewProjectForm = () => ( - {}} registry={newProjRegistry} /> -); + return {}} registry={newProjRegistry} />; +}; const ProjectPreviewEmpty = () => { const [creating, setCreating] = useGlobal(CREATING_PROJECT, false); @@ -576,7 +577,7 @@ const ProjectPreviewInner = ({ project }: { project: ProjectDefinition }) => { const [layout, setLayout] = useProjectsState(`proj-layout-${project.id}`, createDefaultLayout()); const workspace = { id: project.id, name: project.name, cwd: project.cwd }; - return ; + return ; }; const ProjectPreview = () => { @@ -599,14 +600,14 @@ const ProjectPreview = () => { ); }; -const projHomeRegistry = { - ...appRegistry, - 'project-list': { name: 'Projects', icon: FolderKanban, component: () => }, - 'project-preview': { name: 'Project Preview', icon: FolderKanban, component: ProjectPreview }, -}; - export const ProjectListScreen = () => { const [layout, setLayout, isLoaded] = useProjectsState('proj-layout-proj-homepage', defaultLayout); + const { registry } = useAppRegistry(); + const projHomeRegistry = { + ...registry, + 'project-list': { name: 'Projects', icon: FolderKanban, component: () => }, + 'project-preview': { name: 'Project Preview', icon: FolderKanban, component: ProjectPreview }, + }; if (!isLoaded) return null; diff --git a/src/apps/officer-web/Screens/Dashboard/Projects/ProjectScreen.tsx b/src/apps/officer-web/Screens/Dashboard/Projects/ProjectScreen.tsx index 821d74c9..e5900134 100644 --- a/src/apps/officer-web/Screens/Dashboard/Projects/ProjectScreen.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Projects/ProjectScreen.tsx @@ -1,8 +1,8 @@ import { useParams, Navigate } from 'react-router'; -import { useProjectsState } from '@/state/useProjectsState'; +import { useProjectsState } from 'state/useProjectsState'; import { WorkspaceView, createDefaultLayout } from '@/components/Workspace'; import type { LayoutNode, ProjectDefinition } from '@/components/Workspace'; -import { appRegistry } from '../Workspaces/app-registry'; + export const ProjectScreen = () => { const { id } = useParams<{ id: string }>(); @@ -25,7 +25,7 @@ const ProjectScreenInner = ({ project }: { project: ProjectDefinition }) => { workspace={workspace} layout={layout} onLayoutChange={setLayout} - registry={appRegistry} + /> ); diff --git a/src/apps/officer-web/Screens/Dashboard/Settings/ProfileSettings/Appearance.tsx b/src/apps/officer-web/Screens/Dashboard/Settings/ProfileSettings/Appearance.tsx index 61da9b0d..f07a63ce 100644 --- a/src/apps/officer-web/Screens/Dashboard/Settings/ProfileSettings/Appearance.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Settings/ProfileSettings/Appearance.tsx @@ -1,4 +1,4 @@ -import { useSettings } from '@/state/useSettings'; +import { useSettings } from 'state/useSettings'; import { themes } from 'themes'; export const Appearance = () => { diff --git a/src/apps/officer-web/Screens/Dashboard/Settings/ProfileSettings/Languages.tsx b/src/apps/officer-web/Screens/Dashboard/Settings/ProfileSettings/Languages.tsx index 14859ac1..876b3223 100644 --- a/src/apps/officer-web/Screens/Dashboard/Settings/ProfileSettings/Languages.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Settings/ProfileSettings/Languages.tsx @@ -1,7 +1,7 @@ import { useState } from 'react'; import { X } from 'lucide-react'; import { Label } from '@/components/ui/label'; -import { useSettings } from '@/state/useSettings'; +import { useSettings } from 'state/useSettings'; import { useTranslation } from '@/lib/i18n'; const LANGUAGES = [ diff --git a/src/apps/officer-web/Screens/Dashboard/Settings/ProfileSettings/TaskDefaults.tsx b/src/apps/officer-web/Screens/Dashboard/Settings/ProfileSettings/TaskDefaults.tsx index 0407de74..414b6180 100644 --- a/src/apps/officer-web/Screens/Dashboard/Settings/ProfileSettings/TaskDefaults.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Settings/ProfileSettings/TaskDefaults.tsx @@ -11,8 +11,8 @@ import { SelectTrigger, SelectValue, } from '@/components/ui/select'; -import { useSettings } from '@/state/useSettings'; -import { useVisiblePiModels, type ModelOption } from '@/state/useModels'; +import { useSettings } from 'state/useSettings'; +import { useVisiblePiModels, type ModelOption } from 'state/useModels'; export const TaskDefaults = () => { const { settings, saveSettings } = useSettings(); diff --git a/src/apps/officer-web/Screens/Dashboard/Settings/ProfileSettings/index.tsx b/src/apps/officer-web/Screens/Dashboard/Settings/ProfileSettings/index.tsx index 9bafe595..7ca4af21 100644 --- a/src/apps/officer-web/Screens/Dashboard/Settings/ProfileSettings/index.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Settings/ProfileSettings/index.tsx @@ -2,7 +2,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 { appRegistry } from '../../Workspaces/app-registry'; + import { createSettingsPanelComponents, type SettingsSection } from '../SettingsPanel'; import { UserData } from './UserData'; import { ChangePassword } from './ChangePassword'; @@ -46,7 +46,7 @@ export const ProfileSettings = () => { return (
- {}} registry={appRegistry} components={panelComponents} /> + {}} components={panelComponents} />
); }; diff --git a/src/apps/officer-web/Screens/Dashboard/Settings/ResourceSettings/ResourceSidebar.tsx b/src/apps/officer-web/Screens/Dashboard/Settings/ResourceSettings/ResourceSidebar.tsx index 730b7600..bd8e0b3b 100644 --- a/src/apps/officer-web/Screens/Dashboard/Settings/ResourceSettings/ResourceSidebar.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Settings/ResourceSettings/ResourceSidebar.tsx @@ -3,7 +3,7 @@ import { Circle, Server, Wrench } from 'lucide-react'; import { useGlobal } from 'hooks/useGlobal'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; -import { useResources, getResourceCategory, type Resource } from '@/state/useResources'; +import { useResources, getResourceCategory, type Resource } from 'state/useResources'; type ResourceItemProps = { resource: Resource; diff --git a/src/apps/officer-web/Screens/Dashboard/Settings/ResourceSettings/Resources.tsx b/src/apps/officer-web/Screens/Dashboard/Settings/ResourceSettings/Resources.tsx index 230f257b..1a84522c 100644 --- a/src/apps/officer-web/Screens/Dashboard/Settings/ResourceSettings/Resources.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Settings/ResourceSettings/Resources.tsx @@ -15,7 +15,7 @@ import { AlertDialogHeader, AlertDialogTitle, } from '@/components/ui/alert-dialog'; -import { useResources, getResourceCategory, type Resource, type PingResult } from '@/state/useResources'; +import { useResources, getResourceCategory, type Resource, type PingResult } from 'state/useResources'; import { RUN_COMMAND_CHANNEL, type RunCommandState } from './run-command-channel'; type ConnectionSectionProps = { diff --git a/src/apps/officer-web/Screens/Dashboard/Settings/ResourceSettings/index.tsx b/src/apps/officer-web/Screens/Dashboard/Settings/ResourceSettings/index.tsx index a70fc7ab..136bf401 100644 --- a/src/apps/officer-web/Screens/Dashboard/Settings/ResourceSettings/index.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Settings/ResourceSettings/index.tsx @@ -7,7 +7,7 @@ import { WorkspaceLayout } from '@/components/Workspace'; import { usePanelChannel } from 'hooks/usePanelChannel'; import { useClient } from 'hooks/useClient'; import { TerminalView, FileViewerView } from 'officerdev'; -import { appRegistry } from '../../Workspaces/app-registry'; + import { Resources } from './Resources'; import { ResourceSidebar } from './ResourceSidebar'; import { @@ -174,7 +174,7 @@ export const ResourceSettings = () => { return (
- {}} registry={appRegistry} components={panelComponents} /> + {}} components={panelComponents} />
); }; diff --git a/src/apps/officer-web/Screens/Dashboard/Settings/ServerSettings/PluginsSection.tsx b/src/apps/officer-web/Screens/Dashboard/Settings/ServerSettings/PluginsSection.tsx index 93300ac3..40dd3b1a 100644 --- a/src/apps/officer-web/Screens/Dashboard/Settings/ServerSettings/PluginsSection.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Settings/ServerSettings/PluginsSection.tsx @@ -1,7 +1,7 @@ import { useQuery } from '@tanstack/react-query'; import { Switch } from '@/components/ui/switch'; import { useClient } from 'hooks/useClient'; -import { useServerSettings } from '@/state/useServerSettings'; +import { useServerSettings } from 'state/useServerSettings'; type PluginInfo = { id: string; diff --git a/src/apps/officer-web/Screens/Dashboard/Settings/SystemSettings.tsx b/src/apps/officer-web/Screens/Dashboard/Settings/SystemSettings.tsx index eb02071f..23ecf09b 100644 --- a/src/apps/officer-web/Screens/Dashboard/Settings/SystemSettings.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Settings/SystemSettings.tsx @@ -12,12 +12,12 @@ import type { LayoutNode, PanelComponents } from '@/components/Workspace'; import { WorkspaceLayout } from '@/components/Workspace'; import { usePanelChannel } from 'hooks/usePanelChannel'; import { TerminalView } from 'officerdev'; -import { appRegistry } from '../Workspaces/app-registry'; + import { createSettingsPanelComponents, type SettingsSectionGroup } from './SettingsPanel'; -import { useSettings } from '@/state/useSettings'; -import { useUserState } from '@/state/useUserState'; -import { usePiModels, useVisiblePiModels, type ModelOption } from '@/state/useModels'; -import type { UserSettings } from '@/state/types/user-settings'; +import { useSettings } from 'state/useSettings'; +import { useUserState } from 'state/useUserState'; +import { usePiModels, useVisiblePiModels, type ModelOption } from 'state/useModels'; +import type { UserSettings } from 'state/useSettings'; import { AIHarnessesSection } from './ServerSettings/AIHarnessesSection'; import { RUN_COMMAND_CHANNEL, type RunCommandState } from './ServerSettings/run-command-channel'; @@ -158,7 +158,7 @@ export const SystemSettings = () => { return (
- {}} registry={appRegistry} components={panelComponents} /> + {}} components={panelComponents} />
); }; diff --git a/src/apps/officer-web/Screens/Dashboard/Terminal/TerminalScreen.tsx b/src/apps/officer-web/Screens/Dashboard/Terminal/TerminalScreen.tsx index 46d6e377..5dc563de 100644 --- a/src/apps/officer-web/Screens/Dashboard/Terminal/TerminalScreen.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Terminal/TerminalScreen.tsx @@ -1,20 +1,14 @@ -import { Navigate } from 'react-router'; -import { useAuth } from 'hooks/useAuth'; import type { LayoutNode } from '@/components/Workspace'; import { WorkspaceView } from '@/components/Workspace'; -import { useWorkspacesState } from '@/state/useWorkspacesState'; -import { appRegistry } from '../Workspaces/app-registry'; +import { useWorkspacesState } from 'state/useWorkspacesState'; import { defaultLayout } from './defaultLayout'; export const TerminalScreen = () => { - const { user } = useAuth(); const workspace = useWorkspacesState('screens/terminal', defaultLayout); - if (user?.role !== 'Super Admin') return ; - return (
- +
); }; diff --git a/src/apps/officer-web/Screens/Dashboard/Terminal/defaultLayout.ts b/src/apps/officer-web/Screens/Dashboard/Terminal/defaultLayout.ts index 2d4af981..1daea597 100644 --- a/src/apps/officer-web/Screens/Dashboard/Terminal/defaultLayout.ts +++ b/src/apps/officer-web/Screens/Dashboard/Terminal/defaultLayout.ts @@ -3,5 +3,5 @@ import type { LayoutNode } from '@/components/Workspace'; export const defaultLayout: LayoutNode = { type: 'panel', id: 'terminal-screen', - appType: 'terminal-host', + appType: 'officerdev/terminal', }; diff --git a/src/apps/officer-web/Screens/Dashboard/Workspaces/WorkspaceScreen.tsx b/src/apps/officer-web/Screens/Dashboard/Workspaces/WorkspaceScreen.tsx index 8c4bc729..98ffebbd 100644 --- a/src/apps/officer-web/Screens/Dashboard/Workspaces/WorkspaceScreen.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Workspaces/WorkspaceScreen.tsx @@ -1,26 +1,14 @@ -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 { appRegistry } from './app-registry'; +import type { LayoutNode } from '@/components/Workspace'; +import { WorkspaceView } from '@/components/Workspace'; +import { useWorkspacesState } from 'state/useWorkspacesState'; +import { defaultLayout } from './defaultLayout'; export const WorkspaceScreen = () => { - const { id } = useParams<{ id: string }>(); - const { value: workspaces, isLoaded } = useWorkspacesState('workspaces', []); - const workspace = workspaces.find((ws) => ws.id === id); - - if (!isLoaded) return null; - if (!workspace) return ; - - return ; -}; - -const WorkspaceScreenInner = ({ workspace }: { workspace: WorkspaceDefinition }) => { - const ws = useWorkspacesState(`ws-layout-${workspace.id}`, createDefaultLayout()); + const workspace = useWorkspacesState('screens/workspaces', defaultLayout); return (
- +
); }; diff --git a/src/apps/officer-web/Screens/Dashboard/Workspaces/app-registry.tsx b/src/apps/officer-web/Screens/Dashboard/Workspaces/app-registry.tsx deleted file mode 100644 index fec90aeb..00000000 --- a/src/apps/officer-web/Screens/Dashboard/Workspaces/app-registry.tsx +++ /dev/null @@ -1,250 +0,0 @@ -import { useState, useEffect, useRef } from 'react'; -import type { ReactNode } from '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'; -import { CodeEditorView, TerminalView, FileViewerProvider, FileViewerHeader, FileViewerBody, EmbeddableChat, ChatLauncher, usePiChat, FileBrowserApp } from 'officerdev'; -import { useWorkspacesState } from '@/state/useWorkspacesState'; -import { usePanelChannel } from 'hooks/usePanelChannel'; -import { useVisiblePiModels } from '@/state/useModels'; -import { ChatHistoryApp as ChatHistory } from '../ChatHistory'; -import { Catalog } from 'sounds'; -import { WorkspaceListApp } from './WorkspaceListApp'; -import { ProjectListApp } from '../Projects/ProjectListApp'; -import { widgetRegistry } from 'widgets/widget-registry'; -import { WidgetPanel } from 'widgets/WidgetPanel'; - -const ChatWidget = () => ; - -const ChatLauncherWidget = () => { - const pi = usePiChat(); - const models = useVisiblePiModels(); - console.log(models) - return ( - pi.sendPrompt(prompt, attachmentIds, images)} - /> - ); -}; - -const cwdToPath = (cwd: string) => (cwd === '~' ? '/' : cwd.slice(1)); - -const FileBrowserWrapper = () => { - const { cwd } = useWorkspace(); - const basePath = cwdToPath(cwd); - return ; -}; - -const CodeEditorWrapper = () => ; - -const TerminalHeader = () => { - const { cwd } = useWorkspace(); - return ( - <> - - Terminal - {cwd} - - ); -}; - -const HostTerminalHeader = () => { - const { cwd } = useWorkspace(); - return ( - <> - - Terminal - {cwd} - - ); -}; - -const CodeEditorHeader = () => { - const { cwd } = useWorkspace(); - return ( - <> - - Code Editor - {cwd} - - ); -}; - -const EMPTY_TERMINALS: Record = {}; - -const TerminalWrapper = ({ panelId }: { panelId: string }) => { - const { workspaceId, cwd } = useWorkspace(); - const stateKey = workspaceId ? `ws-terminals-${workspaceId}` : 'ws-terminals-default'; - const { value: terminals, setValue: setTerminals } = useWorkspacesState>(stateKey, EMPTY_TERMINALS); - const setTerminalsRef = useRef(setTerminals); - setTerminalsRef.current = setTerminals; - - const sessionId = terminals[panelId]; - - useEffect(() => { - if (!sessionId) { - setTerminals((prev) => ({ ...prev, [panelId]: crypto.randomUUID() })); - } - }, [panelId, sessionId, setTerminals]); - - useEffect(() => { - return () => { - setTerminalsRef.current((prev) => { - const { [panelId]: _, ...rest } = prev; - return rest; - }); - }; - }, [panelId]); - - if (!sessionId) return null; - - return ; -}; - -const HostTerminalWrapper = ({ panelId }: { panelId: string }) => { - const { user } = useAuth(); - const { workspaceId, cwd } = useWorkspace(); - const stateKey = workspaceId ? `ws-host-terminals-${workspaceId}` : 'ws-host-terminals-default'; - const { value: terminals, setValue: setTerminals } = useWorkspacesState>(stateKey, EMPTY_TERMINALS); - const setTerminalsRef = useRef(setTerminals); - setTerminalsRef.current = setTerminals; - - const sessionId = terminals[panelId]; - - useEffect(() => { - if (!sessionId) { - setTerminals((prev) => ({ ...prev, [panelId]: crypto.randomUUID() })); - } - }, [panelId, sessionId, setTerminals]); - - useEffect(() => { - return () => { - setTerminalsRef.current((prev) => { - const { [panelId]: _, ...rest } = prev; - return rest; - }); - }; - }, [panelId]); - - if (user?.role !== 'Super Admin') { - return ( -
- Host Terminal requires Super Admin permissions. -
- ); - } - - if (!sessionId) return null; - - return ; -}; - -const CommandTerminalWrapper = ({ panelId, command, statePrefix }: { panelId: string; command: string; statePrefix: string }) => { - const { workspaceId, cwd } = useWorkspace(); - const stateKey = workspaceId ? `ws-${statePrefix}-${workspaceId}` : `ws-${statePrefix}-default`; - const { value: terminals, setValue: setTerminals } = useWorkspacesState>(stateKey, EMPTY_TERMINALS); - const setTerminalsRef = useRef(setTerminals); - setTerminalsRef.current = setTerminals; - - const sessionId = terminals[panelId]; - - useEffect(() => { - if (!sessionId) { - setTerminals((prev) => ({ ...prev, [panelId]: crypto.randomUUID() })); - } - }, [panelId, sessionId, setTerminals]); - - useEffect(() => { - return () => { - setTerminalsRef.current((prev) => { - const { [panelId]: _, ...rest } = prev; - return rest; - }); - }; - }, [panelId]); - - if (!sessionId) return null; - - const fullCommand = cwd && cwd !== '~' ? `cd ${cwd} && ${command}` : command; - - return ; -}; - -const TmuxWrapper = ({ panelId }: { panelId: string }) => ( - -); - -const NvimWrapper = ({ panelId }: { panelId: string }) => ( - -); - -const TmuxHeader = () => { - const { cwd } = useWorkspace(); - return ( - <> - - Tmux - {cwd} - - ); -}; - -const NvimHeader = () => { - const { cwd } = useWorkspace(); - return ( - <> - - Neovim - {cwd} - - ); -}; - -type FileViewerChannelState = { - filePath: string; - fileName: string; - root?: string; -} | null; - -const FILE_VIEWER_CHANNEL = 'file-viewer'; - -const FileViewerWorkspaceProvider = ({ panelId, children }: { panelId: string; children: ReactNode }) => { - const [state] = usePanelChannel(`${FILE_VIEWER_CHANNEL}:${panelId}`, null); - - if (!state) { - return ( -
- No file selected -
- ); - } - - return ( - - {children} - - ); -}; - -export const appRegistry: AppRegistry = { - 'chat': { name: 'Chat', icon: MessageSquare, component: ChatWidget }, - 'file-browser': { name: 'File Browser', icon: FolderOpen, component: FileBrowserWrapper }, - 'chat-history': { name: 'Chat History', icon: History, component: () => , availableOnPanel: false }, - 'sound-library': { name: 'Sound Library', icon: Music, component: () => , availableOnPanel: false }, - 'code-editor': { name: 'Code Editor', icon: Code, component: CodeEditorWrapper, header: CodeEditorHeader }, - 'terminal': { name: 'Terminal', icon: TerminalSquare, component: TerminalWrapper, header: TerminalHeader }, - 'tmux': { name: 'Tmux', icon: Columns2, component: TmuxWrapper, header: TmuxHeader }, - 'nvim': { name: 'Neovim', icon: PenLine, component: NvimWrapper, header: NvimHeader }, - 'terminal-host': { name: 'Host Terminal', icon: Monitor, component: HostTerminalWrapper, header: HostTerminalHeader, availableOnPanel: false }, - 'workspace-list': { name: 'Workspaces', icon: LayoutGrid, component: () => , availableOnPanel: false }, - 'project-list': { name: 'Projects', icon: FolderKanban, component: () => , availableOnPanel: false }, - 'file-viewer': { name: 'File Viewer', icon: Eye, component: FileViewerBody, header: FileViewerHeader, provider: FileViewerWorkspaceProvider, availableOnPanel: false }, - 'chat-launcher': { name: 'Chat Launcher', icon: Sparkles, component: ChatLauncherWidget, fixedHeight: 180, availableOnPanel: false }, - 'widget-panel': { name: 'Widget Panel', icon: LayoutDashboard, component: WidgetPanel, transparent: true }, - ...widgetRegistry, -}; diff --git a/src/apps/officer-web/Screens/Dashboard/Workspaces/defaultLayout.ts b/src/apps/officer-web/Screens/Dashboard/Workspaces/defaultLayout.ts new file mode 100644 index 00000000..81614cbf --- /dev/null +++ b/src/apps/officer-web/Screens/Dashboard/Workspaces/defaultLayout.ts @@ -0,0 +1,11 @@ +import type { LayoutNode } from '@/components/Workspace'; + +export const defaultLayout: LayoutNode = { + type: 'group', + id: 'ws-home-root', + direction: 'horizontal', + children: [ + { node: { type: 'panel', id: 'ws-home-list', appType: 'workspace-list' }, size: 50 }, + { node: { type: 'panel', id: 'ws-home-right', appType: 'workspace-preview' }, size: 50 }, + ], +}; diff --git a/src/apps/officer-web/Screens/Dashboard/Workspaces/index.tsx b/src/apps/officer-web/Screens/Dashboard/Workspaces/index.tsx index 7fbf78c0..5868f861 100644 --- a/src/apps/officer-web/Screens/Dashboard/Workspaces/index.tsx +++ b/src/apps/officer-web/Screens/Dashboard/Workspaces/index.tsx @@ -1,2 +1 @@ -export { WorkspaceListScreen, NewWorkspaceRedirect } from './WorkspaceListScreen'; export { WorkspaceScreen } from './WorkspaceScreen'; diff --git a/src/apps/officer-web/frontend.tsx b/src/apps/officer-web/frontend.tsx index dfc68794..3eb7190a 100644 --- a/src/apps/officer-web/frontend.tsx +++ b/src/apps/officer-web/frontend.tsx @@ -1,6 +1,7 @@ import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; import { App } from './App'; +import { AppRegistry } from 'officerdev'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ColorModeProvider } from '@/components/ui/ThemeProvider'; import { I18nBridge } from '@/lib/I18nBridge'; @@ -39,6 +40,7 @@ const app = ( + diff --git a/src/apps/officer-web/lib/I18nBridge.tsx b/src/apps/officer-web/lib/I18nBridge.tsx index e344e7f4..22ffd3c2 100644 --- a/src/apps/officer-web/lib/I18nBridge.tsx +++ b/src/apps/officer-web/lib/I18nBridge.tsx @@ -1,7 +1,7 @@ import type { ReactNode } from 'react'; import type { TranslationMap } from 'i18n'; import { I18nProvider } from 'i18n'; -import { useSettings } from '@/state/useSettings'; +import { useSettings } from 'state/useSettings'; import en from '../locales/en.json'; import pt from '../locales/pt.json'; diff --git a/src/apps/officer-web/state/useInitialData.ts b/src/apps/officer-web/state/useInitialData.ts index 1b4509c9..2f879059 100644 --- a/src/apps/officer-web/state/useInitialData.ts +++ b/src/apps/officer-web/state/useInitialData.ts @@ -1,6 +1,6 @@ -import { useChatSessions } from '@/state/useChatSessions'; -import { usePlans } from './usePlans'; -import { useSettings } from './useSettings'; +import { useChatSessions } from 'state/useChatSessions'; +import { usePlans } from 'state/usePlans'; +import { useSettings } from 'state/useSettings'; import { useColorModeSync } from './useThemeSync'; export const useInitialData = () => { diff --git a/src/apps/officer-web/state/useThemeSync.ts b/src/apps/officer-web/state/useThemeSync.ts index 1a67b3ad..cf8bdf9c 100644 --- a/src/apps/officer-web/state/useThemeSync.ts +++ b/src/apps/officer-web/state/useThemeSync.ts @@ -1,6 +1,6 @@ import { useEffect } from 'react'; import { useColorMode } from '@/components/ui/ThemeProvider'; -import { useSettings } from './useSettings'; +import { useSettings } from 'state/useSettings'; export const useColorModeSync = () => { const { settings } = useSettings(); diff --git a/src/workspaces/components/Workspace/AppPicker.tsx b/src/workspaces/components/Workspace/AppPicker.tsx index 652fed9e..96311382 100644 --- a/src/workspaces/components/Workspace/AppPicker.tsx +++ b/src/workspaces/components/Workspace/AppPicker.tsx @@ -6,7 +6,7 @@ type AppPickerProps = { }; export const AppPicker = ({ registry, onSelect }: AppPickerProps) => { - const entries = Object.entries(registry).filter(([, entry]) => !entry.widget && entry.availableOnPanel !== false); + const entries = Object.entries(registry).filter(([, entry]) => entry.availableOnPanel !== false); return (
diff --git a/src/workspaces/components/Workspace/WorkspaceLayout.tsx b/src/workspaces/components/Workspace/WorkspaceLayout.tsx index f8a33b64..ae6c20b9 100644 --- a/src/workspaces/components/Workspace/WorkspaceLayout.tsx +++ b/src/workspaces/components/Workspace/WorkspaceLayout.tsx @@ -3,11 +3,12 @@ import type { LayoutNode, AppRegistry, PanelComponents } from './types'; import { updateSizes } from './layout-utils'; import { WorkspaceProvider } from './WorkspaceContext'; import { WorkspaceRenderer } from './WorkspaceRenderer'; +import { useAppRegistry } from 'officerdev'; type WorkspaceLayoutProps = { layout: LayoutNode; onLayoutChange: (layout: LayoutNode) => void; - registry: AppRegistry; + registry?: AppRegistry; components?: PanelComponents; workspaceId?: string; cwd?: string; @@ -15,7 +16,10 @@ type WorkspaceLayoutProps = { const noop = () => {}; -export const WorkspaceLayout = ({ layout, onLayoutChange, registry, components, workspaceId, cwd }: WorkspaceLayoutProps) => { +export const WorkspaceLayout = ({ layout, onLayoutChange, registry: registryProp, components, workspaceId, cwd }: WorkspaceLayoutProps) => { + const { registry: globalRegistry } = useAppRegistry(); + const registry = registryProp ?? globalRegistry; + const handleResized = useCallback( (groupId: string, sizes: number[]) => { onLayoutChange(updateSizes(layout, groupId, sizes)); diff --git a/src/workspaces/components/Workspace/WorkspaceView.tsx b/src/workspaces/components/Workspace/WorkspaceView.tsx index 1849a41a..7f83e926 100644 --- a/src/workspaces/components/Workspace/WorkspaceView.tsx +++ b/src/workspaces/components/Workspace/WorkspaceView.tsx @@ -1,25 +1,26 @@ import { useState, useCallback, useEffect } from 'react'; import { flushSync } from 'react-dom'; import { ResizablePanel, ResizablePanelGroup, ResizableHandle } from '../ui/resizable'; -import type { LayoutNode, AppRegistry, WorkspaceState, EphemeralPanels } from './types'; +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'; type WorkspaceViewProps = { workspace: WorkspaceState; - registry: AppRegistry; cwd?: string; ephemeral?: EphemeralPanels | null; }; const noop = () => {}; -export const WorkspaceView = ({ workspace, registry, cwd = '~', ephemeral }: WorkspaceViewProps) => { - if (!workspace.isLoaded) return null; +export const WorkspaceView = ({ workspace, cwd = '~', ephemeral }: WorkspaceViewProps) => { + const { registry } = useAppRegistry(); - const { value: layout, setValue: onLayoutChange } = workspace; + const layout = workspace.value; + const onLayoutChange = workspace.setValue; const [swapSourceId, setSwapSourceId] = useState(null); const [dragSourceId, setDragSourceId] = useState(null); const [maximizedPanelId, setMaximizedPanelId] = useState(null); @@ -105,6 +106,8 @@ export const WorkspaceView = ({ workspace, registry, cwd = '~', ephemeral }: Wor return () => window.removeEventListener('keydown', onKeyDown); }, [swapSourceId, dragSourceId, maximizedPanelId, setMaximizedAnimated]); + if (!workspace.isLoaded) return null; + const baseRenderer = ( ; transparent?: boolean; fixedHeight?: number; - widget?: boolean; availableOnPanel?: boolean; }; diff --git a/src/workspaces/officerdev/package.json b/src/workspaces/officerdev/package.json index 6d5b2b79..71019b0f 100644 --- a/src/workspaces/officerdev/package.json +++ b/src/workspaces/officerdev/package.json @@ -9,6 +9,7 @@ }, "dependencies": { "hooks": "workspace:*", + "state": "workspace:*", "widgets": "workspace:*" } } diff --git a/src/workspaces/officerdev/src/AppRegistry/AppRegistry.tsx b/src/workspaces/officerdev/src/AppRegistry/AppRegistry.tsx new file mode 100644 index 00000000..b5ae0b9d --- /dev/null +++ b/src/workspaces/officerdev/src/AppRegistry/AppRegistry.tsx @@ -0,0 +1,14 @@ +import { appRegistryMetas as fileBrowserMetas } from '../apps/FileBrowser'; +import { appRegistryMetas as terminalMetas } from '../apps/Terminal'; +import { appRegistryMetas as codeEditorMetas } from '../apps/CodeEditor'; +import { appRegistryMetas as chatMetas } from '../apps/Chat'; +import { appRegistryMetas as fileViewerMetas } from '../apps/FileViewer'; +import { appRegistryMetas as workspaceMetas } from '../apps/Workspaces'; +import { useAppRegistry } from './useAppRegistry'; + +const apps = [...fileBrowserMetas, ...terminalMetas, ...codeEditorMetas, ...chatMetas, ...fileViewerMetas, ...workspaceMetas]; + +export const AppRegistry = () => { + useAppRegistry(apps); + return null; +}; diff --git a/src/workspaces/officerdev/src/AppRegistry/index.tsx b/src/workspaces/officerdev/src/AppRegistry/index.tsx new file mode 100644 index 00000000..faf7550c --- /dev/null +++ b/src/workspaces/officerdev/src/AppRegistry/index.tsx @@ -0,0 +1,2 @@ +export * from './AppRegistry'; +export * from './useAppRegistry'; diff --git a/src/workspaces/officerdev/src/AppRegistry/useAppRegistry/index.ts b/src/workspaces/officerdev/src/AppRegistry/useAppRegistry/index.ts new file mode 100644 index 00000000..f5cbe731 --- /dev/null +++ b/src/workspaces/officerdev/src/AppRegistry/useAppRegistry/index.ts @@ -0,0 +1 @@ +export { useAppRegistry, type UseAppRegistryType, type AppRegistryMeta } from './useAppRegistry'; diff --git a/src/workspaces/officerdev/src/AppRegistry/useAppRegistry/useAppRegistry.ts b/src/workspaces/officerdev/src/AppRegistry/useAppRegistry/useAppRegistry.ts new file mode 100644 index 00000000..03659e25 --- /dev/null +++ b/src/workspaces/officerdev/src/AppRegistry/useAppRegistry/useAppRegistry.ts @@ -0,0 +1,19 @@ +import type { AppRegistry, AppRegistryEntry } from '@/components/Workspace'; +import { useGlobal } from 'hooks/useGlobal'; + +export type AppRegistryMeta = { key: string } & AppRegistryEntry; + +export function useAppRegistry(initialApps: AppRegistryMeta[] = []) { + const [registry, setRegistry] = useGlobal('APP_REGISTRY', () => metasToRegistry(initialApps)); + + const registerApp = (key: string, entry: AppRegistryEntry) => { + setRegistry((prev) => ({ ...prev, [key]: entry })); + }; + + return { registry, registerApp }; +} + +export type UseAppRegistryType = ReturnType; + +const metasToRegistry = (metas: AppRegistryMeta[]): AppRegistry => + Object.fromEntries(metas.map(({ key, ...entry }) => [key, entry])); diff --git a/src/workspaces/officerdev/src/apps/Chat/ChatList/index.tsx b/src/workspaces/officerdev/src/apps/Chat/ChatList/index.tsx index e2ddb60c..3a37270f 100644 --- a/src/workspaces/officerdev/src/apps/Chat/ChatList/index.tsx +++ b/src/workspaces/officerdev/src/apps/Chat/ChatList/index.tsx @@ -1,5 +1,5 @@ import { MessageSquare } from 'lucide-react'; -import { useChatSessions } from '@/state/useChatSessions'; +import { useChatSessions } from 'state/useChatSessions'; export const ChatList = () => { const { sessions } = useChatSessions(); diff --git a/src/workspaces/officerdev/src/apps/Chat/ChatPanelWrapper.tsx b/src/workspaces/officerdev/src/apps/Chat/ChatPanelWrapper.tsx new file mode 100644 index 00000000..99c06481 --- /dev/null +++ b/src/workspaces/officerdev/src/apps/Chat/ChatPanelWrapper.tsx @@ -0,0 +1,3 @@ +import { EmbeddableChat } from './EmbeddableChat'; + +export const ChatPanelWrapper = () => ; diff --git a/src/workspaces/officerdev/src/apps/Chat/EmbeddableChat/useEmbeddableChat.ts b/src/workspaces/officerdev/src/apps/Chat/EmbeddableChat/useEmbeddableChat.ts index 55531fde..5f306501 100644 --- a/src/workspaces/officerdev/src/apps/Chat/EmbeddableChat/useEmbeddableChat.ts +++ b/src/workspaces/officerdev/src/apps/Chat/EmbeddableChat/useEmbeddableChat.ts @@ -1,6 +1,6 @@ import type { KeyboardEvent } from 'react'; import { useState, useRef, useEffect } from 'react'; -import { useVisiblePiModels } from '@/state/useModels'; +import { useVisiblePiModels } from 'state/useModels'; import { usePiChat, type UsePiChatType } from '../../../hooks/usePiChat'; import { useAttachments } from '../useAttachments'; import { useSlashCommands } from '../useSlashCommands'; diff --git a/src/workspaces/officerdev/src/apps/Chat/components/ModelSelector.tsx b/src/workspaces/officerdev/src/apps/Chat/components/ModelSelector.tsx index 638f6692..cc8e1d15 100644 --- a/src/workspaces/officerdev/src/apps/Chat/components/ModelSelector.tsx +++ b/src/workspaces/officerdev/src/apps/Chat/components/ModelSelector.tsx @@ -1,5 +1,5 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; -import type { ModelOption } from '@/state/useModels'; +import type { ModelOption } from 'state/useModels'; import type { ChatMessage } from '../types'; const PROVIDER_DISPLAY: Record = { diff --git a/src/workspaces/officerdev/src/apps/Chat/index.ts b/src/workspaces/officerdev/src/apps/Chat/index.ts index adc04ec8..b0ab89b1 100644 --- a/src/workspaces/officerdev/src/apps/Chat/index.ts +++ b/src/workspaces/officerdev/src/apps/Chat/index.ts @@ -1,3 +1,7 @@ +import type { AppRegistryMeta } from '../../AppRegistry'; +import { MessageSquare } from 'lucide-react'; +import { ChatPanelWrapper } from './ChatPanelWrapper'; + export { MessageList } from './components/MessageList'; export { MessageBubble, StreamingBubble } from './components/MessageBubble'; export { ToolActivity } from './components/ToolActivity'; @@ -18,3 +22,12 @@ export { useAttachments, type UseAttachmentsType } from './useAttachments'; export { useAudioRecording, type UseAudioRecordingType } from './useAudioRecording'; export * from './types'; + +export const appRegistryMetas: AppRegistryMeta[] = [ + { + key: 'officerdev/chat', + name: 'Chat', + icon: MessageSquare, + component: ChatPanelWrapper, + }, +]; diff --git a/src/workspaces/officerdev/src/apps/CodeEditor/CodeEditorHeader.tsx b/src/workspaces/officerdev/src/apps/CodeEditor/CodeEditorHeader.tsx new file mode 100644 index 00000000..6c923540 --- /dev/null +++ b/src/workspaces/officerdev/src/apps/CodeEditor/CodeEditorHeader.tsx @@ -0,0 +1,13 @@ +import { Code } from 'lucide-react'; +import { useWorkspace } from '@/components/Workspace'; + +export const CodeEditorHeader = () => { + const { cwd } = useWorkspace(); + return ( + <> + + Code Editor + {cwd} + + ); +}; diff --git a/src/workspaces/officerdev/src/apps/CodeEditor/CodeEditorPanelWrapper.tsx b/src/workspaces/officerdev/src/apps/CodeEditor/CodeEditorPanelWrapper.tsx new file mode 100644 index 00000000..9013ca81 --- /dev/null +++ b/src/workspaces/officerdev/src/apps/CodeEditor/CodeEditorPanelWrapper.tsx @@ -0,0 +1,3 @@ +import { CodeEditorView } from './CodeEditor'; + +export const CodeEditorPanelWrapper = () => ; diff --git a/src/workspaces/officerdev/src/apps/CodeEditor/index.ts b/src/workspaces/officerdev/src/apps/CodeEditor/index.ts index c3ba9632..69ad01e4 100644 --- a/src/workspaces/officerdev/src/apps/CodeEditor/index.ts +++ b/src/workspaces/officerdev/src/apps/CodeEditor/index.ts @@ -1 +1,16 @@ +import type { AppRegistryMeta } from '../../AppRegistry'; +import { Code } from 'lucide-react'; +import { CodeEditorPanelWrapper } from './CodeEditorPanelWrapper'; +import { CodeEditorHeader } from './CodeEditorHeader'; + export { CodeEditorView } from './CodeEditor'; + +export const appRegistryMetas: AppRegistryMeta[] = [ + { + key: 'officerdev/code-editor', + name: 'Code Editor', + icon: Code, + component: CodeEditorPanelWrapper, + header: CodeEditorHeader, + }, +]; diff --git a/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/FileBrowserApp.tsx b/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/FileBrowserApp.tsx index 26115c57..87084bd3 100644 --- a/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/FileBrowserApp.tsx +++ b/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/FileBrowserApp.tsx @@ -13,24 +13,20 @@ type FileBrowserAppProps = { export const FileBrowserApp = ({ basePath = '/' }: FileBrowserAppProps) => { const fileBrowserManager = useFileBrowserApp(basePath); + const { handleNavigate } = fileBrowserManager; return ( -
- - - - - {/* Breadcrumb */} -
- -
- - - - - - - -
+
+ + + + + + + +
); }; + diff --git a/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/FileBrowserPanelWrapper.tsx b/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/FileBrowserPanelWrapper.tsx new file mode 100644 index 00000000..ffa9beb7 --- /dev/null +++ b/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/FileBrowserPanelWrapper.tsx @@ -0,0 +1,9 @@ +import { useWorkspace } from '@/components/Workspace'; +import { FileBrowserApp } from './FileBrowserApp'; + +const cwdToPath = (cwd: string) => (cwd === '~' ? '/' : cwd.slice(1)); + +export const FileBrowserPanelWrapper = () => { + const { cwd } = useWorkspace(); + return ; +}; diff --git a/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/components/Breadcrumb.tsx b/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/components/Breadcrumb.tsx index be85161c..1cb0724d 100644 --- a/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/components/Breadcrumb.tsx +++ b/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/components/Breadcrumb.tsx @@ -11,36 +11,38 @@ export const Breadcrumb = ({ path, onNavigate, basePath = '/' }: BreadcrumbProps const segments = relativePath.split('/').filter(Boolean); return ( - +
); }; diff --git a/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/components/TaskRunnerModal.tsx b/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/components/TaskRunnerModal.tsx index b1508e3e..d077937f 100644 --- a/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/components/TaskRunnerModal.tsx +++ b/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/components/TaskRunnerModal.tsx @@ -5,7 +5,7 @@ import * as DialogPrimitive from '@radix-ui/react-dialog'; import { cardStyle } from '@/components/Card'; import type { TaskInfo } from '../../../Chat'; import { usePiChat, EmbeddableChat } from '../../../Chat'; -import { useSettings } from '@/state/useSettings'; +import { useSettings } from 'state/useSettings'; import type { TaskSummary } from '../../useTasks'; const playDing = () => { diff --git a/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/index.ts b/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/index.ts index c9623415..ba21b449 100644 --- a/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/index.ts +++ b/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/index.ts @@ -1 +1,2 @@ export * from './FileBrowserApp'; +export * from './FileBrowserPanelWrapper'; diff --git a/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/useFileBrowserApp.ts b/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/useFileBrowserApp.ts index 02a117c1..0d3d83e2 100644 --- a/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/useFileBrowserApp.ts +++ b/src/workspaces/officerdev/src/apps/FileBrowser/FileBrowserApp/useFileBrowserApp.ts @@ -3,7 +3,7 @@ import { useSearchParams, useNavigate } from 'react-router'; import { toast } from 'sonner'; import { useFilesAPI, type DirEntry } from '../../../hooks/useFilesAPI'; import { useTasks, type TaskSummary } from '../useTasks'; -import { useUserState } from '@/state/useUserState'; +import { useUserState } from 'state/useUserState'; import { useAuth } from 'hooks/useAuth'; export const useFileBrowserApp = (basePath: string) => { diff --git a/src/workspaces/officerdev/src/apps/FileBrowser/index.ts b/src/workspaces/officerdev/src/apps/FileBrowser/index.ts index 7d67c3a2..b11a203b 100644 --- a/src/workspaces/officerdev/src/apps/FileBrowser/index.ts +++ b/src/workspaces/officerdev/src/apps/FileBrowser/index.ts @@ -1,7 +1,28 @@ +import type { AppRegistryMeta } from '../../AppRegistry'; +import { FolderOpen } from 'lucide-react'; +import { FileBrowserPanelWrapper } from './FileBrowserApp'; +import { FileBrowserWidget } from './FileBrowserWidget'; + export { useFilesAPI, type DirEntry } from '../../hooks/useFilesAPI'; export { useTasks, type TaskSummary } from './useTasks'; export { useRecentFiles } from './useRecentFiles'; export { usePinnedFiles } from './usePinnedFiles'; -export { FileBrowserApp } from './FileBrowserApp'; +export { FileBrowserApp, FileBrowserPanelWrapper } from './FileBrowserApp'; export { FileBrowserWidget } from './FileBrowserWidget'; export { TaskRunnerModal } from './FileBrowserApp/components/TaskRunnerModal'; + +export const appRegistryMetas: AppRegistryMeta[] = [ + { + key: 'officerdev/file-browser', + name: 'File Browser', + icon: FolderOpen, + component: FileBrowserPanelWrapper, + }, + { + key: 'officerdev/file-browser-widget', + name: 'File Browser', + icon: FolderOpen, + component: FileBrowserWidget, + availableOnPanel: false, + }, +]; diff --git a/src/workspaces/officerdev/src/apps/FileBrowser/usePinnedFiles.ts b/src/workspaces/officerdev/src/apps/FileBrowser/usePinnedFiles.ts index 01d741af..8434ff09 100644 --- a/src/workspaces/officerdev/src/apps/FileBrowser/usePinnedFiles.ts +++ b/src/workspaces/officerdev/src/apps/FileBrowser/usePinnedFiles.ts @@ -1,4 +1,4 @@ -import { useUserState } from '@/state/useUserState'; +import { useUserState } from 'state/useUserState'; type PinnedFile = { path: string; name: string; pinnedAt: number }; diff --git a/src/workspaces/officerdev/src/apps/FileBrowser/useRecentFiles.ts b/src/workspaces/officerdev/src/apps/FileBrowser/useRecentFiles.ts index 84b896f4..8c8dabf9 100644 --- a/src/workspaces/officerdev/src/apps/FileBrowser/useRecentFiles.ts +++ b/src/workspaces/officerdev/src/apps/FileBrowser/useRecentFiles.ts @@ -1,4 +1,4 @@ -import { useUserState } from '@/state/useUserState'; +import { useUserState } from 'state/useUserState'; type RecentFile = { path: string; name: string; openedAt: number }; diff --git a/src/workspaces/officerdev/src/apps/FileViewer/FileViewerPanelWrapper.tsx b/src/workspaces/officerdev/src/apps/FileViewer/FileViewerPanelWrapper.tsx new file mode 100644 index 00000000..d0c4649c --- /dev/null +++ b/src/workspaces/officerdev/src/apps/FileViewer/FileViewerPanelWrapper.tsx @@ -0,0 +1,29 @@ +import type { ReactNode } from 'react'; +import { usePanelChannel } from 'hooks/usePanelChannel'; +import { FileViewerProvider } from './FileViewerProvider'; + +type FileViewerChannelState = { + filePath: string; + fileName: string; + root?: string; +} | null; + +const FILE_VIEWER_CHANNEL = 'file-viewer'; + +export const FileViewerPanelProvider = ({ panelId, children }: { panelId: string; children: ReactNode }) => { + const [state] = usePanelChannel(`${FILE_VIEWER_CHANNEL}:${panelId}`, null); + + if (!state) { + return ( +
+ No file selected +
+ ); + } + + return ( + + {children} + + ); +}; diff --git a/src/workspaces/officerdev/src/apps/FileViewer/index.ts b/src/workspaces/officerdev/src/apps/FileViewer/index.ts index 0f8262f7..a84420eb 100644 --- a/src/workspaces/officerdev/src/apps/FileViewer/index.ts +++ b/src/workspaces/officerdev/src/apps/FileViewer/index.ts @@ -1,6 +1,25 @@ +import type { AppRegistryMeta } from '../../AppRegistry'; +import { Eye } from 'lucide-react'; +import { FileViewerBody } from './FileViewerBody'; +import { FileViewerHeader } from './FileViewerHeader'; +import { FileViewerPanelProvider } from './FileViewerPanelWrapper'; + export { FileViewerView } from './FileViewerView'; export { FileViewerProvider } from './FileViewerProvider'; export { FileViewerHeader } from './FileViewerHeader'; export { FileViewerBody } from './FileViewerBody'; +export { FileViewerPanelProvider } from './FileViewerPanelWrapper'; export { useFileViewer } from './FileViewerContext'; export { getFileType, getLang, getExt, getArchiveBaseName, ARCHIVE_EXTS, type FileType } from './file-types'; + +export const appRegistryMetas: AppRegistryMeta[] = [ + { + key: 'officerdev/file-viewer', + name: 'File Viewer', + icon: Eye, + component: FileViewerBody, + header: FileViewerHeader, + provider: FileViewerPanelProvider, + availableOnPanel: false, + }, +]; diff --git a/src/workspaces/officerdev/src/apps/Terminal/CommandTerminalWrapper.tsx b/src/workspaces/officerdev/src/apps/Terminal/CommandTerminalWrapper.tsx new file mode 100644 index 00000000..465b269b --- /dev/null +++ b/src/workspaces/officerdev/src/apps/Terminal/CommandTerminalWrapper.tsx @@ -0,0 +1,43 @@ +import { useEffect, useRef } from 'react'; +import { useWorkspace } from '@/components/Workspace'; +import { useWorkspacesState } from 'state/useWorkspacesState'; +import { TerminalView } from './Terminal'; + +const EMPTY_TERMINALS: Record = {}; + +type CommandTerminalWrapperProps = { + panelId: string; + command: string; + statePrefix: string; +}; + +export const CommandTerminalWrapper = ({ panelId, command, statePrefix }: CommandTerminalWrapperProps) => { + const { workspaceId, cwd } = useWorkspace(); + const stateKey = workspaceId ? `ws-${statePrefix}-${workspaceId}` : `ws-${statePrefix}-default`; + const { value: terminals, setValue: setTerminals } = useWorkspacesState>(stateKey, EMPTY_TERMINALS); + const setTerminalsRef = useRef(setTerminals); + setTerminalsRef.current = setTerminals; + + const sessionId = terminals[panelId]; + + useEffect(() => { + if (!sessionId) { + setTerminals((prev) => ({ ...prev, [panelId]: crypto.randomUUID() })); + } + }, [panelId, sessionId, setTerminals]); + + useEffect(() => { + return () => { + setTerminalsRef.current((prev) => { + const { [panelId]: _, ...rest } = prev; + return rest; + }); + }; + }, [panelId]); + + if (!sessionId) return null; + + const fullCommand = cwd && cwd !== '~' ? `cd ${cwd} && ${command}` : command; + + return ; +}; diff --git a/src/workspaces/officerdev/src/apps/Terminal/Headers.tsx b/src/workspaces/officerdev/src/apps/Terminal/Headers.tsx new file mode 100644 index 00000000..765f97f1 --- /dev/null +++ b/src/workspaces/officerdev/src/apps/Terminal/Headers.tsx @@ -0,0 +1,62 @@ +import { TerminalSquare, Monitor, Columns2, PenLine } from 'lucide-react'; +import { useWorkspace } from '@/components/Workspace'; +import { useAuth } from 'hooks/useAuth'; +import { useTerminalMode } from './useTerminalMode'; + +export const TerminalHeader = ({ panelId }: { panelId: string }) => { + const { cwd } = useWorkspace(); + const { user } = useAuth(); + const { mode, toggle } = useTerminalMode(panelId); + const isHost = mode === 'host'; + const Icon = isHost ? Monitor : TerminalSquare; + + return ( + <> + + Terminal + {user?.role === 'Super Admin' && ( + + )} + {cwd} + + ); +}; + +export const HostTerminalHeader = () => { + const { cwd } = useWorkspace(); + return ( + <> + + Terminal + {cwd} + + ); +}; + +export const TmuxHeader = () => { + const { cwd } = useWorkspace(); + return ( + <> + + Tmux + {cwd} + + ); +}; + +export const NvimHeader = () => { + const { cwd } = useWorkspace(); + return ( + <> + + Neovim + {cwd} + + ); +}; diff --git a/src/workspaces/officerdev/src/apps/Terminal/HostTerminalWrapper.tsx b/src/workspaces/officerdev/src/apps/Terminal/HostTerminalWrapper.tsx new file mode 100644 index 00000000..1a55a220 --- /dev/null +++ b/src/workspaces/officerdev/src/apps/Terminal/HostTerminalWrapper.tsx @@ -0,0 +1,45 @@ +import { useEffect, useRef } from 'react'; +import { useWorkspace } from '@/components/Workspace'; +import { useAuth } from 'hooks/useAuth'; +import { useWorkspacesState } from 'state/useWorkspacesState'; +import { TerminalView } from './Terminal'; + +const EMPTY_TERMINALS: Record = {}; + +export const HostTerminalWrapper = ({ panelId }: { panelId: string }) => { + const { user } = useAuth(); + const { workspaceId, cwd } = useWorkspace(); + const stateKey = workspaceId ? `ws-host-terminals-${workspaceId}` : 'ws-host-terminals-default'; + const { value: terminals, setValue: setTerminals } = useWorkspacesState>(stateKey, EMPTY_TERMINALS); + const setTerminalsRef = useRef(setTerminals); + setTerminalsRef.current = setTerminals; + + const sessionId = terminals[panelId]; + + useEffect(() => { + if (!sessionId) { + setTerminals((prev) => ({ ...prev, [panelId]: crypto.randomUUID() })); + } + }, [panelId, sessionId, setTerminals]); + + useEffect(() => { + return () => { + setTerminalsRef.current((prev) => { + const { [panelId]: _, ...rest } = prev; + return rest; + }); + }; + }, [panelId]); + + if (user?.role !== 'Super Admin') { + return ( +
+ Host Terminal requires Super Admin permissions. +
+ ); + } + + if (!sessionId) return null; + + return ; +}; diff --git a/src/workspaces/officerdev/src/apps/Terminal/TerminalWrapper.tsx b/src/workspaces/officerdev/src/apps/Terminal/TerminalWrapper.tsx new file mode 100644 index 00000000..2f357050 --- /dev/null +++ b/src/workspaces/officerdev/src/apps/Terminal/TerminalWrapper.tsx @@ -0,0 +1,38 @@ +import { useEffect, useRef } from 'react'; +import { useWorkspace } from '@/components/Workspace'; +import { useWorkspacesState } from 'state/useWorkspacesState'; +import { TerminalView } from './Terminal'; +import { useTerminalMode } from './useTerminalMode'; + +const EMPTY_TERMINALS: Record = {}; + +export const TerminalWrapper = ({ panelId }: { panelId: string }) => { + const { workspaceId, cwd } = useWorkspace(); + const { mode } = useTerminalMode(panelId); + const sandboxed = mode === 'sandboxed'; + const stateKey = workspaceId ? `ws-terminals-${mode}-${workspaceId}` : `ws-terminals-${mode}-default`; + const { value: terminals, setValue: setTerminals } = useWorkspacesState>(stateKey, EMPTY_TERMINALS); + const setTerminalsRef = useRef(setTerminals); + setTerminalsRef.current = setTerminals; + + const sessionId = terminals[panelId]; + + useEffect(() => { + if (!sessionId) { + setTerminals((prev) => ({ ...prev, [panelId]: crypto.randomUUID() })); + } + }, [panelId, sessionId, setTerminals]); + + useEffect(() => { + return () => { + setTerminalsRef.current((prev) => { + const { [panelId]: _, ...rest } = prev; + return rest; + }); + }; + }, [panelId]); + + if (!sessionId) return null; + + return ; +}; diff --git a/src/workspaces/officerdev/src/apps/Terminal/index.ts b/src/workspaces/officerdev/src/apps/Terminal/index.ts deleted file mode 100644 index e8f90209..00000000 --- a/src/workspaces/officerdev/src/apps/Terminal/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { TerminalView, type TerminalViewProps } from './Terminal'; diff --git a/src/workspaces/officerdev/src/apps/Terminal/index.tsx b/src/workspaces/officerdev/src/apps/Terminal/index.tsx new file mode 100644 index 00000000..82438426 --- /dev/null +++ b/src/workspaces/officerdev/src/apps/Terminal/index.tsx @@ -0,0 +1,48 @@ +import type { AppRegistryMeta } from '../../AppRegistry'; +import { TerminalSquare, Monitor, Columns2, PenLine } from 'lucide-react'; +import { TerminalWrapper } from './TerminalWrapper'; +import { HostTerminalWrapper } from './HostTerminalWrapper'; +import { CommandTerminalWrapper } from './CommandTerminalWrapper'; +import { TerminalHeader, HostTerminalHeader, TmuxHeader, NvimHeader } from './Headers'; + +export { TerminalView, type TerminalViewProps } from './Terminal'; + +const TmuxWrapper = ({ panelId }: { panelId: string }) => ( + +); + +const NvimWrapper = ({ panelId }: { panelId: string }) => ( + +); + +export const appRegistryMetas: AppRegistryMeta[] = [ + { + key: 'officerdev/terminal', + name: 'Terminal', + icon: TerminalSquare, + component: TerminalWrapper, + header: TerminalHeader, + }, + { + key: 'officerdev/terminal-host', + name: 'Host Terminal', + icon: Monitor, + component: HostTerminalWrapper, + header: HostTerminalHeader, + availableOnPanel: false, + }, + { + key: 'officerdev/tmux', + name: 'Tmux', + icon: Columns2, + component: TmuxWrapper, + header: TmuxHeader, + }, + { + key: 'officerdev/nvim', + name: 'Neovim', + icon: PenLine, + component: NvimWrapper, + header: NvimHeader, + }, +]; diff --git a/src/workspaces/officerdev/src/apps/Terminal/useTerminalMode.ts b/src/workspaces/officerdev/src/apps/Terminal/useTerminalMode.ts new file mode 100644 index 00000000..6bbc0169 --- /dev/null +++ b/src/workspaces/officerdev/src/apps/Terminal/useTerminalMode.ts @@ -0,0 +1,11 @@ +import { useGlobal } from 'hooks/useGlobal'; + +type TerminalMode = 'sandboxed' | 'host'; + +export const useTerminalMode = (panelId: string) => { + const [mode, setMode] = useGlobal(`terminal-mode-${panelId}`, 'sandboxed'); + + const toggle = () => setMode((prev) => (prev === 'sandboxed' ? 'host' : 'sandboxed')); + + return { mode, setMode, toggle }; +}; diff --git a/src/apps/officer-web/Screens/Dashboard/Workspaces/WorkspaceListApp.tsx b/src/workspaces/officerdev/src/apps/Workspaces/WorkspaceListApp.tsx similarity index 98% rename from src/apps/officer-web/Screens/Dashboard/Workspaces/WorkspaceListApp.tsx rename to src/workspaces/officerdev/src/apps/Workspaces/WorkspaceListApp.tsx index be8ebbfa..1340b248 100644 --- a/src/apps/officer-web/Screens/Dashboard/Workspaces/WorkspaceListApp.tsx +++ b/src/workspaces/officerdev/src/apps/Workspaces/WorkspaceListApp.tsx @@ -5,8 +5,8 @@ import { useQueryClient } from '@tanstack/react-query'; import { useGlobal } from 'hooks/useGlobal'; import { useClient } from 'hooks/useClient'; import { generateSlug } from 'helpers/slug'; -import { useUserState } from '@/state/useUserState'; -import { useWorkspacesState } from '@/state/useWorkspacesState'; +import { useUserState } from 'state/useUserState'; +import { useWorkspacesState } from 'state/useWorkspacesState'; import type { WorkspaceDefinition } from '@/components/Workspace'; import { AlertDialog, diff --git a/src/apps/officer-web/Screens/Dashboard/Workspaces/WorkspaceListScreen.tsx b/src/workspaces/officerdev/src/apps/Workspaces/WorkspacePreview.tsx similarity index 84% rename from src/apps/officer-web/Screens/Dashboard/Workspaces/WorkspaceListScreen.tsx rename to src/workspaces/officerdev/src/apps/Workspaces/WorkspacePreview.tsx index cfe96bbb..a09641c2 100644 --- a/src/apps/officer-web/Screens/Dashboard/Workspaces/WorkspaceListScreen.tsx +++ b/src/workspaces/officerdev/src/apps/Workspaces/WorkspacePreview.tsx @@ -1,28 +1,25 @@ -import { useEffect, useState } from 'react'; -import { Link, Navigate, useNavigate, useSearchParams } from 'react-router'; +import { useState } from 'react'; +import { Link, useNavigate } from 'react-router'; import { FolderOpen, LayoutGrid, Plus, ArrowRight, Type, Rocket, FileText, Layout } from 'lucide-react'; import { useQueryClient } from '@tanstack/react-query'; import { useGlobal } from 'hooks/useGlobal'; import { useClient } from 'hooks/useClient'; -import { useUserState } from '@/state/useUserState'; -import { useWorkspacesState } from '@/state/useWorkspacesState'; +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 { Button } from '@/components/ui/button'; import { generateSlug, slugify } from 'helpers/slug'; -import { FileBrowserApp } from 'officerdev'; -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'; - -const defaultLayout: LayoutNode = { - type: 'group', - id: 'ws-home-root', - direction: 'horizontal', - children: [ - { node: { type: 'panel', id: 'ws-home-list', appType: 'workspace-list' }, size: 50 }, - { node: { type: 'panel', id: 'ws-home-right', appType: 'workspace-preview' }, size: 50 }, - ], -}; +import { FileBrowserApp } from '../FileBrowser'; +import { useAppRegistry } from '../../AppRegistry'; +import { + SELECTED_WORKSPACE_KEY, + CREATING_WORKSPACE_KEY, + EDITING_WORKSPACE_KEY, + NEW_WS_NAME_KEY, + NEW_WS_DESC_KEY, + NEW_WS_TEMPLATE_KEY, +} from './constants'; // --- Layout Templates --- @@ -316,13 +313,11 @@ const CreatePanel = () => { const newLayoutKey = `ws-layout-${newId}`; if (idChanged) { - // Remove old keys from cache const oldKeys = [`ws-layout-${editingId}`, `ws-terminals-${editingId}`, `ws-host-terminals-${editingId}`]; const cleaned = { ...currentState }; for (const k of oldKeys) delete cleaned[k]; queryClient.setQueryData(['WORKSPACES_STATE'], { ...cleaned, [newLayoutKey]: wsLayout }); - // Delete old files, write new layout const patch: Record = { [newLayoutKey]: wsLayout }; for (const k of oldKeys) patch[k] = null; client.patch('/workspaces', patch).catch(() => {}); @@ -391,7 +386,7 @@ const CreatePanel = () => { ); }; -// --- CwdFileBrowser (basePath-constrained file browser for workspace form) --- +// --- CwdFileBrowser --- const CwdFileBrowser = () => { const [currentPath] = useUserState('files/currentPath', '/'); @@ -433,17 +428,20 @@ const newWsLayout: LayoutNode = { ], }; -const newWsRegistry = { - ...appRegistry, - 'file-browser-cwd': { name: 'File Browser', icon: FolderOpen, component: CwdFileBrowser }, - 'new-ws-name': { name: 'Name', icon: Type, component: NamePanel }, - 'new-ws-template': { name: 'Template', icon: Layout, component: TemplatePanel }, - 'new-ws-create': { name: 'Create', icon: Rocket, component: CreatePanel }, +const NewWorkspaceForm = () => { + const { registry } = useAppRegistry(); + const newWsRegistry = { + ...registry, + 'file-browser-cwd': { name: 'File Browser', icon: FolderOpen, component: CwdFileBrowser }, + 'new-ws-name': { name: 'Name', icon: Type, component: NamePanel }, + 'new-ws-template': { name: 'Template', icon: Layout, component: TemplatePanel }, + 'new-ws-create': { name: 'Create', icon: Rocket, component: CreatePanel }, + }; + + return {}} registry={newWsRegistry} />; }; -const NewWorkspaceForm = () => ( - {}} registry={newWsRegistry} /> -); +// --- Empty State --- const WorkspacePreviewEmpty = () => { const [creating, setCreating] = useGlobal(CREATING_WORKSPACE_KEY, false); @@ -477,13 +475,17 @@ const WorkspacePreviewEmpty = () => { ); }; +// --- Preview Inner --- + const WorkspacePreviewInner = ({ workspace }: { workspace: WorkspaceDefinition }) => { const ws = useWorkspacesState(`ws-layout-${workspace.id}`, createDefaultLayout()); - return ; + return ; }; -const WorkspacePreview = () => { +// --- Main Export --- + +export const WorkspacePreview = () => { const [selectedId] = useGlobal(SELECTED_WORKSPACE_KEY, null); const { value: workspaces } = useWorkspacesState('workspaces', []); const workspace = selectedId ? workspaces.find((ws) => ws.id === selectedId) : null; @@ -502,40 +504,3 @@ const WorkspacePreview = () => { ); }; - -const registry = { ...appRegistry, 'workspace-preview': { name: 'Workspace Preview', icon: LayoutGrid, component: WorkspacePreview } }; - -export const WorkspaceListScreen = () => { - const workspace = useWorkspacesState('screens/homepage', defaultLayout); - - return ( -
- -
- ); -}; - -export const NewWorkspaceRedirect = () => { - const navigate = useNavigate(); - const [params] = useSearchParams(); - const [, setName] = useGlobal(NEW_WS_NAME_KEY, ''); - const [, setDescription] = useGlobal(NEW_WS_DESC_KEY, ''); - const [, setTemplateIdx] = useGlobal(NEW_WS_TEMPLATE_KEY, 0); - const [, setCreating] = useGlobal(CREATING_WORKSPACE_KEY, false); - const [, setEditing] = useGlobal(EDITING_WORKSPACE_KEY, null); - const [, setSelected] = useGlobal(SELECTED_WORKSPACE_KEY, null); - const [, setFilePath] = useUserState('files/currentPath', '/'); - - useEffect(() => { - setSelected(null); - setEditing(null); - setName(params.get('name') || generateSlug()); - setDescription(''); - setTemplateIdx(0); - setFilePath(params.get('cwd') || '/'); - setCreating(true); - navigate('/workspaces', { replace: true }); - }, []); - - return null; -}; diff --git a/src/apps/officer-web/Screens/Dashboard/Workspaces/constants.ts b/src/workspaces/officerdev/src/apps/Workspaces/constants.ts similarity index 100% rename from src/apps/officer-web/Screens/Dashboard/Workspaces/constants.ts rename to src/workspaces/officerdev/src/apps/Workspaces/constants.ts diff --git a/src/workspaces/officerdev/src/apps/Workspaces/index.ts b/src/workspaces/officerdev/src/apps/Workspaces/index.ts new file mode 100644 index 00000000..c53f8e6a --- /dev/null +++ b/src/workspaces/officerdev/src/apps/Workspaces/index.ts @@ -0,0 +1,25 @@ +import type { AppRegistryMeta } from '../../AppRegistry'; +import { LayoutGrid } from 'lucide-react'; +import { WorkspaceListApp } from './WorkspaceListApp'; +import { WorkspacePreview } from './WorkspacePreview'; + +export { WorkspaceListApp }; +export { WorkspacePreview }; +export { SELECTED_WORKSPACE_KEY, CREATING_WORKSPACE_KEY, EDITING_WORKSPACE_KEY, NEW_WS_NAME_KEY, NEW_WS_DESC_KEY, NEW_WS_TEMPLATE_KEY } from './constants'; + +export const appRegistryMetas: AppRegistryMeta[] = [ + { + key: 'workspace-list', + name: 'Workspaces', + icon: LayoutGrid, + component: WorkspaceListApp, + availableOnPanel: false + }, + { + key: 'workspace-preview', + name: 'Workspace Preview', + icon: LayoutGrid, + component: WorkspacePreview, + availableOnPanel: false + }, +]; diff --git a/src/workspaces/officerdev/src/hooks/appRegistry/appRegistry.ts b/src/workspaces/officerdev/src/hooks/appRegistry/appRegistry.ts deleted file mode 100644 index 3a73800e..00000000 --- a/src/workspaces/officerdev/src/hooks/appRegistry/appRegistry.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { AppRegistry } from '@/components/Workspace'; - -export function useAppRegistry(): AppRegistry { - return {}; -} diff --git a/src/workspaces/officerdev/src/hooks/appRegistry/index.ts b/src/workspaces/officerdev/src/hooks/appRegistry/index.ts deleted file mode 100644 index 090ec6fb..00000000 --- a/src/workspaces/officerdev/src/hooks/appRegistry/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { useAppRegistry } from './appRegistry'; diff --git a/src/workspaces/officerdev/src/hooks/index.ts b/src/workspaces/officerdev/src/hooks/index.ts index fc78df2b..5ccfdca9 100644 --- a/src/workspaces/officerdev/src/hooks/index.ts +++ b/src/workspaces/officerdev/src/hooks/index.ts @@ -1,4 +1,3 @@ -export * from './appRegistry'; export * from './useFilesAPI'; export * from './useFileViewerPanels'; export * from './usePiChat'; diff --git a/src/workspaces/officerdev/src/hooks/useFileViewerPanels/Providers.tsx b/src/workspaces/officerdev/src/hooks/useFileViewerPanels/Providers.tsx index 729cc816..07af692f 100644 --- a/src/workspaces/officerdev/src/hooks/useFileViewerPanels/Providers.tsx +++ b/src/workspaces/officerdev/src/hooks/useFileViewerPanels/Providers.tsx @@ -1,6 +1,6 @@ import type { ReactNode } from 'react'; import { useSearchParams } from 'react-router'; -import { useUserState } from '@/state/useUserState'; +import { useUserState } from 'state/useUserState'; import { FileViewerProvider } from '../../apps/FileViewer'; export function ViewerProvider({ children }: { children: ReactNode }) { diff --git a/src/workspaces/officerdev/src/hooks/usePiChat.ts b/src/workspaces/officerdev/src/hooks/usePiChat.ts index a30ed520..5870c0f3 100644 --- a/src/workspaces/officerdev/src/hooks/usePiChat.ts +++ b/src/workspaces/officerdev/src/hooks/usePiChat.ts @@ -1,6 +1,6 @@ import { useState, useEffect, useRef } from 'react'; import { useChatWebSocket } from 'hooks/useChatWebSocket'; -import { useChatSessions } from '@/state/useChatSessions'; +import { useChatSessions } from 'state/useChatSessions'; import type { ChatMessage, ServerMessage, TaskInfo, Message } from '../apps/Chat/types'; const SAVE_DEBOUNCE_MS = 1000; diff --git a/src/workspaces/officerdev/src/index.ts b/src/workspaces/officerdev/src/index.ts index 2dfd8348..9684a77e 100644 --- a/src/workspaces/officerdev/src/index.ts +++ b/src/workspaces/officerdev/src/index.ts @@ -1,7 +1,16 @@ export * from './hooks'; -export * from './apps/Chat'; -export * from './apps/ChatHistory'; -export * from './apps/CodeEditor'; -export * from './apps/FileBrowser'; -export * from './apps/FileViewer'; -export * from './apps/Terminal'; +export * from './AppRegistry'; + +// Re-export app modules (excluding appRegistryMetas to avoid name collisions) +export { MessageList, MessageBubble, StreamingBubble, ToolActivity, QuestionActivity, ModelSelector, InputArea, ChatLauncher, AttachmentList, AttachButton, WebpageDialog, EmbeddableChat, usePiChat, ChatList, useSlashCommands, useChatSessions, useChatSession, useAttachments, useAudioRecording } from './apps/Chat'; +export type { UseEmbeddableChatType, UsePiChatType, UseChatSessionsType, UseChatSessionType, UseAttachmentsType, UseAudioRecordingType } from './apps/Chat'; +export * from './apps/Chat/types'; +export { SessionBar } from './apps/ChatHistory'; +export { CodeEditorView } from './apps/CodeEditor'; +export { useFilesAPI, useTasks, useRecentFiles, usePinnedFiles, FileBrowserApp, FileBrowserPanelWrapper, FileBrowserWidget, TaskRunnerModal } from './apps/FileBrowser'; +export type { DirEntry, TaskSummary } from './apps/FileBrowser'; +export { FileViewerView, FileViewerProvider, FileViewerHeader, FileViewerBody, FileViewerPanelProvider, useFileViewer, getFileType, getLang, getExt, getArchiveBaseName, ARCHIVE_EXTS } from './apps/FileViewer'; +export type { FileType } from './apps/FileViewer'; +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'; diff --git a/src/workspaces/state/package.json b/src/workspaces/state/package.json new file mode 100644 index 00000000..00928fec --- /dev/null +++ b/src/workspaces/state/package.json @@ -0,0 +1,14 @@ +{ + "name": "state", + "version": "0.0.1", + "license": "MIT", + "type": "module", + "exports": { + ".": "./src/index.ts", + "./*": "./src/*.ts" + }, + "dependencies": { + "hooks": "workspace:*", + "officerdev": "workspace:*" + } +} diff --git a/src/workspaces/state/src/index.ts b/src/workspaces/state/src/index.ts new file mode 100644 index 00000000..ef2eace7 --- /dev/null +++ b/src/workspaces/state/src/index.ts @@ -0,0 +1,16 @@ +export { useSettings, DEFAULT_SETTINGS } from './useSettings'; +export type { UseSettingsType, UserSettings, UserState } from './useSettings'; +export { useUserState } from './useUserState'; +export { useWorkspacesState } from './useWorkspacesState'; +export { useProjectsState } from './useProjectsState'; +export { usePiModels, useVisiblePiModels, modelKey } from './useModels'; +export type { ModelOption } from './useModels'; +export { useRecentModels } from './useRecentModels'; +export { usePlans } from './usePlans'; +export { useLandingPage } from './useLandingPage'; +export { useServerSettings } from './useServerSettings'; +export { useResources, getResourceCategory } from './useResources'; +export type { Resource, ResourceCredentials, ResourceConnectionConfig, PingResult, ResourceCategory } from './useResources'; +export { useChatSessions } from './useChatSessions'; +export type { UseChatSessionsType } from './useChatSessions'; +export { useChatGroups } from './useChatGroups'; diff --git a/src/apps/officer-web/state/useChatGroups.ts b/src/workspaces/state/src/useChatGroups.ts similarity index 100% rename from src/apps/officer-web/state/useChatGroups.ts rename to src/workspaces/state/src/useChatGroups.ts diff --git a/src/apps/officer-web/state/useChatSessions.ts b/src/workspaces/state/src/useChatSessions.ts similarity index 100% rename from src/apps/officer-web/state/useChatSessions.ts rename to src/workspaces/state/src/useChatSessions.ts diff --git a/src/apps/officer-web/state/useLandingPage.ts b/src/workspaces/state/src/useLandingPage.ts similarity index 100% rename from src/apps/officer-web/state/useLandingPage.ts rename to src/workspaces/state/src/useLandingPage.ts diff --git a/src/apps/officer-web/state/useModels.ts b/src/workspaces/state/src/useModels.ts similarity index 100% rename from src/apps/officer-web/state/useModels.ts rename to src/workspaces/state/src/useModels.ts diff --git a/src/apps/officer-web/state/usePlans.ts b/src/workspaces/state/src/usePlans.ts similarity index 100% rename from src/apps/officer-web/state/usePlans.ts rename to src/workspaces/state/src/usePlans.ts diff --git a/src/apps/officer-web/state/useProjectsState.ts b/src/workspaces/state/src/useProjectsState.ts similarity index 96% rename from src/apps/officer-web/state/useProjectsState.ts rename to src/workspaces/state/src/useProjectsState.ts index fbec3ffe..201cb0fc 100644 --- a/src/apps/officer-web/state/useProjectsState.ts +++ b/src/workspaces/state/src/useProjectsState.ts @@ -2,7 +2,7 @@ import { useCallback, useRef } from 'react'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useClient } from 'hooks/useClient'; import { useAuth } from 'hooks/useAuth'; -import type { UserState } from './types/user-settings'; +import type { UserState } from './useSettings'; const QUERY_KEY = ['PROJECTS_STATE']; diff --git a/src/apps/officer-web/state/useRecentModels.ts b/src/workspaces/state/src/useRecentModels.ts similarity index 100% rename from src/apps/officer-web/state/useRecentModels.ts rename to src/workspaces/state/src/useRecentModels.ts diff --git a/src/apps/officer-web/state/useResources.ts b/src/workspaces/state/src/useResources.ts similarity index 100% rename from src/apps/officer-web/state/useResources.ts rename to src/workspaces/state/src/useResources.ts diff --git a/src/apps/officer-web/state/useServerSettings.ts b/src/workspaces/state/src/useServerSettings.ts similarity index 100% rename from src/apps/officer-web/state/useServerSettings.ts rename to src/workspaces/state/src/useServerSettings.ts diff --git a/src/apps/officer-web/state/useSettings.ts b/src/workspaces/state/src/useSettings.ts similarity index 100% rename from src/apps/officer-web/state/useSettings.ts rename to src/workspaces/state/src/useSettings.ts diff --git a/src/apps/officer-web/state/useUserState.ts b/src/workspaces/state/src/useUserState.ts similarity index 96% rename from src/apps/officer-web/state/useUserState.ts rename to src/workspaces/state/src/useUserState.ts index 8d745cab..354a7025 100644 --- a/src/apps/officer-web/state/useUserState.ts +++ b/src/workspaces/state/src/useUserState.ts @@ -2,7 +2,7 @@ import { useCallback, useRef } from 'react'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useClient } from 'hooks/useClient'; import { useAuth } from 'hooks/useAuth'; -import type { UserState } from './types/user-settings'; +import type { UserState } from './useSettings'; const QUERY_KEY = ['USER_STATE']; diff --git a/src/apps/officer-web/state/useWorkspacesState.ts b/src/workspaces/state/src/useWorkspacesState.ts similarity index 97% rename from src/apps/officer-web/state/useWorkspacesState.ts rename to src/workspaces/state/src/useWorkspacesState.ts index e777a560..b87b4836 100644 --- a/src/apps/officer-web/state/useWorkspacesState.ts +++ b/src/workspaces/state/src/useWorkspacesState.ts @@ -2,7 +2,7 @@ import { useCallback, useEffect, useRef } from 'react'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useClient } from 'hooks/useClient'; import { useAuth } from 'hooks/useAuth'; -import type { UserState } from './types/user-settings'; +import type { UserState } from './useSettings'; const QUERY_KEY = ['WORKSPACES_STATE']; diff --git a/src/workspaces/widgets/widget-registry.tsx b/src/workspaces/widgets/widget-registry.tsx index e382ef88..8d4e5fe3 100644 --- a/src/workspaces/widgets/widget-registry.tsx +++ b/src/workspaces/widgets/widget-registry.tsx @@ -7,9 +7,9 @@ import { DailyGoals } from './DailyGoals/index'; import { QuickNotes } from './QuickNotes/index'; export const widgetRegistry: Record = { - 'clock': { name: 'Clock', icon: ClockIcon, component: () => , widget: true }, - 'weather': { name: 'Weather', icon: CloudSun, component: () => , widget: true }, - 'pomodoro': { name: 'Pomodoro', icon: Timer, component: () => , widget: true }, - 'daily-goals': { name: 'Daily Goals', icon: Target, component: () => , widget: true }, - 'quick-notes': { name: 'Quick Notes', icon: StickyNote, component: () => , widget: true }, + 'clock': { name: 'Clock', icon: ClockIcon, component: () => , availableOnPanel: false }, + 'weather': { name: 'Weather', icon: CloudSun, component: () => , availableOnPanel: false }, + 'pomodoro': { name: 'Pomodoro', icon: Timer, component: () => , availableOnPanel: false }, + 'daily-goals': { name: 'Daily Goals', icon: Target, component: () => , availableOnPanel: false }, + 'quick-notes': { name: 'Quick Notes', icon: StickyNote, component: () => , availableOnPanel: false }, };