Skip to content

Commit 7cc0dfa

Browse files
committed
feat: refactor app handling to use InstalledPackage type and update package management logic
1 parent 53e18e4 commit 7cc0dfa

3 files changed

Lines changed: 66 additions & 46 deletions

File tree

apps/console/src/App.tsx

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { Toaster } from "@/components/ui/toaster"
1010
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
1111
import { Database, Layers, Sparkles, Zap } from 'lucide-react';
1212
import { getApiBaseUrl, config } from './lib/config';
13-
import type { AppPackage } from './mocks/browser';
13+
import type { InstalledPackage } from '@objectstack/spec/kernel';
1414

1515
function DashboardWelcome() {
1616
return (
@@ -120,8 +120,8 @@ function DashboardWelcome() {
120120

121121
export default function App() {
122122
const [client, setClient] = useState<ObjectStackClient | null>(null);
123-
const [apps, setApps] = useState<AppPackage[]>([]);
124-
const [selectedApp, setSelectedApp] = useState<AppPackage | null>(null);
123+
const [packages, setPackages] = useState<InstalledPackage[]>([]);
124+
const [selectedPackage, setSelectedPackage] = useState<InstalledPackage | null>(null);
125125
const [selectedObject, setSelectedObject] = useState<string | null>(null);
126126
const [selectedView, setSelectedView] = useState<'dashboard' | 'packages' | 'object'>('dashboard');
127127
const [editingRecord, setEditingRecord] = useState<any>(null);
@@ -138,29 +138,29 @@ export default function App() {
138138
setClient(newClient);
139139
}, []);
140140

141-
// 2. Fetch app list from the server API (not hardcoded)
141+
// 2. Fetch installed packages from the server API
142142
useEffect(() => {
143143
if (!client) return;
144144
let mounted = true;
145145

146-
async function loadApps() {
146+
async function loadPackages() {
147147
try {
148-
// Spec: GET /api/v1/meta/appsGetMetaItemsResponse = { type: 'apps', items: AppSchema[] }
149-
const result: any = await client!.meta.getItems('apps');
150-
const items: AppPackage[] = result?.items || result?.value || (Array.isArray(result) ? result : []);
148+
// Spec: GET /api/v1/packagesListPackagesResponse = { packages: InstalledPackage[], total }
149+
const result = await client!.packages.list();
150+
const items: InstalledPackage[] = result?.packages || [];
151151

152-
console.log('[App] Fetched apps from API:', items.map((a: any) => a.label || a.name));
152+
console.log('[App] Fetched packages from API:', items.map((p) => p.manifest?.name || p.manifest?.id));
153153

154154
if (mounted && items.length > 0) {
155-
setApps(items);
156-
setSelectedApp(items[0]);
155+
setPackages(items);
156+
setSelectedPackage(items[0]);
157157
}
158158
} catch (err) {
159-
console.error('[App] Failed to fetch apps from API:', err);
159+
console.error('[App] Failed to fetch packages from API:', err);
160160
}
161161
}
162162

163-
loadApps();
163+
loadPackages();
164164
return () => { mounted = false; };
165165
}, [client]);
166166

@@ -183,8 +183,8 @@ export default function App() {
183183
setEditingRecord(null);
184184
}
185185

186-
function handleSelectApp(app: AppPackage) {
187-
setSelectedApp(app);
186+
function handleSelectPackage(pkg: InstalledPackage) {
187+
setSelectedPackage(pkg);
188188
setSelectedObject(null);
189189
setSelectedView('dashboard');
190190
setShowForm(false);
@@ -214,14 +214,14 @@ export default function App() {
214214
client={client}
215215
selectedObject={selectedObject}
216216
onSelectObject={handleSelectObject}
217-
apps={apps}
218-
selectedApp={selectedApp}
219-
onSelectApp={handleSelectApp}
217+
packages={packages}
218+
selectedPackage={selectedPackage}
219+
onSelectPackage={handleSelectPackage}
220220
onSelectView={handleSelectView}
221221
selectedView={selectedView}
222222
/>
223223
<main className="flex min-w-0 flex-1 flex-col bg-background">
224-
<SiteHeader selectedObject={selectedObject} appLabel={selectedApp?.label || selectedApp?.name} />
224+
<SiteHeader selectedObject={selectedObject} appLabel={selectedPackage?.manifest?.name || selectedPackage?.manifest?.id} />
225225
<div className="flex flex-1 flex-col overflow-hidden">
226226
{selectedView === 'object' && selectedObject ? (
227227
<div className="flex flex-1 flex-col gap-4 p-4">

apps/console/src/components/app-sidebar.tsx

Lines changed: 39 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ import {
88
Sparkles,
99
Search,
1010
Check,
11-
Briefcase,
12-
CheckSquare,
1311
Zap,
1412
BarChart3,
1513
FileText,
@@ -24,7 +22,7 @@ import {
2422
} from "lucide-react"
2523
import { useState, useEffect, useCallback } from "react"
2624
import { ObjectStackClient } from '@objectstack/client';
27-
import type { AppPackage } from "@/mocks/browser";
25+
import type { InstalledPackage } from '@objectstack/spec/kernel';
2826

2927
import {
3028
Sidebar,
@@ -53,11 +51,6 @@ import {
5351
DropdownMenuTrigger,
5452
} from "@/components/ui/dropdown-menu"
5553

56-
const APP_ICONS: Record<string, React.ElementType> = {
57-
'check-square': CheckSquare,
58-
'briefcase': Briefcase,
59-
};
60-
6154
/** Icon & label hints for well-known metadata types */
6255
const META_TYPE_HINTS: Record<string, { label: string; icon: LucideIcon }> = {
6356
object: { label: 'Objects', icon: Package },
@@ -89,18 +82,32 @@ function getTypeIcon(type: string): LucideIcon {
8982
/** Types that are internal / should be hidden from the sidebar */
9083
const HIDDEN_TYPES = new Set(['plugin', 'plugins', 'kind', 'app', 'apps', 'package']);
9184

85+
/** Icon mapping for package types */
86+
const PKG_TYPE_ICONS: Record<string, LucideIcon> = {
87+
app: AppWindow,
88+
plugin: Layers,
89+
driver: Database,
90+
server: Globe,
91+
ui: Sparkles,
92+
theme: Sparkles,
93+
agent: Bot,
94+
module: Package,
95+
objectql: Database,
96+
adapter: Zap,
97+
};
98+
9299
interface AppSidebarProps extends React.ComponentProps<typeof Sidebar> {
93100
client: ObjectStackClient | null;
94101
selectedObject: string | null;
95102
onSelectObject: (name: string) => void;
96-
apps: AppPackage[];
97-
selectedApp: AppPackage | null;
98-
onSelectApp: (app: AppPackage) => void;
103+
packages: InstalledPackage[];
104+
selectedPackage: InstalledPackage | null;
105+
onSelectPackage: (pkg: InstalledPackage) => void;
99106
onSelectView?: (view: 'dashboard' | 'packages') => void;
100107
selectedView?: 'dashboard' | 'packages' | 'object';
101108
}
102109

103-
export function AppSidebar({ client, selectedObject, onSelectObject, apps, selectedApp, onSelectApp, onSelectView, selectedView, ...props }: AppSidebarProps) {
110+
export function AppSidebar({ client, selectedObject, onSelectObject, packages, selectedPackage, onSelectPackage, onSelectView, selectedView, ...props }: AppSidebarProps) {
104111
const [loading, setLoading] = useState(false);
105112
const [searchQuery, setSearchQuery] = useState("");
106113
// Dynamic metadata: type -> items[]
@@ -165,55 +172,60 @@ export function AppSidebar({ client, selectedObject, onSelectObject, apps, selec
165172
label.toLowerCase().includes(searchQuery.toLowerCase()) ||
166173
name.toLowerCase().includes(searchQuery.toLowerCase());
167174

168-
const AppIcon = selectedApp?.icon ? (APP_ICONS[selectedApp.icon] || Sparkles) : Sparkles;
175+
const SelectedPkgIcon = selectedPackage ? (PKG_TYPE_ICONS[selectedPackage.manifest?.type] || Package) : Sparkles;
169176

170177
return (
171178
<Sidebar {...props}>
172179
<SidebarHeader className="border-b">
173-
{/* App Switcher */}
180+
{/* Package Switcher */}
174181
<DropdownMenu>
175182
<DropdownMenuTrigger asChild>
176183
<button className="flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left hover:bg-sidebar-accent transition-colors">
177184
<div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-primary text-primary-foreground">
178-
<AppIcon className="h-4 w-4" />
185+
<SelectedPkgIcon className="h-4 w-4" />
179186
</div>
180187
<div className="flex flex-1 flex-col gap-0.5 leading-none overflow-hidden">
181188
<span className="truncate font-semibold text-sm">
182-
{selectedApp ? (selectedApp.label || selectedApp.name) : 'ObjectStack'}
189+
{selectedPackage ? (selectedPackage.manifest?.name || selectedPackage.manifest?.id) : 'ObjectStack'}
183190
</span>
184191
<span className="truncate text-xs text-muted-foreground">
185-
{selectedApp ? (selectedApp.description || 'No description') : 'Loading apps...'}
192+
{selectedPackage ? `v${selectedPackage.manifest?.version} · ${selectedPackage.manifest?.type}` : 'Loading packages...'}
186193
</span>
187194
</div>
188195
<ChevronsUpDown className="ml-auto h-4 w-4 shrink-0 text-muted-foreground" />
189196
</button>
190197
</DropdownMenuTrigger>
191198
<DropdownMenuContent className="w-[--radix-dropdown-menu-trigger-width] min-w-64" align="start" sideOffset={4}>
192-
<DropdownMenuLabel>Applications</DropdownMenuLabel>
199+
<DropdownMenuLabel>Installed Packages</DropdownMenuLabel>
193200
<DropdownMenuSeparator />
194-
{apps.map((app) => {
195-
const Icon = app.icon ? (APP_ICONS[app.icon] || Package) : Package;
201+
{packages.map((pkg) => {
202+
const Icon = PKG_TYPE_ICONS[pkg.manifest?.type] || Package;
203+
const isSelected = selectedPackage?.manifest?.id === pkg.manifest?.id;
196204
return (
197205
<DropdownMenuItem
198-
key={app.name}
199-
onClick={() => onSelectApp(app)}
206+
key={pkg.manifest?.id}
207+
onClick={() => onSelectPackage(pkg)}
200208
className="gap-2 py-2"
201209
>
202210
<div className="flex h-6 w-6 shrink-0 items-center justify-center rounded bg-primary/10 text-primary">
203211
<Icon className="h-3.5 w-3.5" />
204212
</div>
205213
<div className="flex flex-1 flex-col leading-tight">
206-
<span className="text-sm font-medium">{app.label || app.name}</span>
207-
{app.description && (
208-
<span className="text-xs text-muted-foreground">{app.description}</span>
209-
)}
214+
<span className="text-sm font-medium">{pkg.manifest?.name || pkg.manifest?.id}</span>
215+
<span className="text-xs text-muted-foreground">
216+
v{pkg.manifest?.version} · {pkg.manifest?.type}
217+
{!pkg.enabled && ' · disabled'}
218+
</span>
210219
</div>
211-
{selectedApp?.name === app.name && (
220+
{isSelected && (
212221
<Check className="h-4 w-4 text-primary" />
213222
)}
214223
</DropdownMenuItem>
215224
);
216225
})}
226+
{packages.length === 0 && (
227+
<div className="px-2 py-4 text-center text-xs text-muted-foreground">No packages installed</div>
228+
)}
217229
</DropdownMenuContent>
218230
</DropdownMenu>
219231
</SidebarHeader>

packages/objectql/src/engine.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,14 @@ vi.mock('./registry', () => {
1111
getObject: vi.fn((name) => mockObjects.get(name)),
1212
registerObject: vi.fn((obj) => mockObjects.set(obj.name, obj)),
1313
registerKind: vi.fn(),
14+
registerItem: vi.fn(),
15+
registerApp: vi.fn(),
16+
installPackage: vi.fn((manifest) => ({
17+
manifest,
18+
status: 'installed',
19+
enabled: true,
20+
installedAt: new Date().toISOString(),
21+
})),
1422
metadata: {
1523
get: vi.fn(() => mockObjects) // Expose for verification if needed
1624
}

0 commit comments

Comments
 (0)