@object-ui/app-shell 4.0.10 → 4.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/dist/layout/AppSidebar.js +20 -2
- package/dist/layout/UnifiedSidebar.js +13 -2
- package/dist/views/CreateViewDialog.js +21 -3
- package/dist/views/DashboardView.js +95 -5
- package/dist/views/ObjectView.js +14 -0
- package/package.json +24 -24
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# @object-ui/app-shell — Changelog
|
|
2
2
|
|
|
3
|
+
## 4.0.11
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 7ea1d93: dashboard
|
|
8
|
+
- Updated dependencies [1909bc3]
|
|
9
|
+
- @object-ui/i18n@4.0.11
|
|
10
|
+
- @object-ui/components@4.0.11
|
|
11
|
+
- @object-ui/fields@4.0.11
|
|
12
|
+
- @object-ui/plugin-calendar@4.0.11
|
|
13
|
+
- @object-ui/plugin-charts@4.0.11
|
|
14
|
+
- @object-ui/plugin-dashboard@4.0.11
|
|
15
|
+
- @object-ui/plugin-designer@4.0.11
|
|
16
|
+
- @object-ui/plugin-kanban@4.0.11
|
|
17
|
+
- @object-ui/plugin-list@4.0.11
|
|
18
|
+
- @object-ui/react@4.0.11
|
|
19
|
+
- @object-ui/layout@4.0.11
|
|
20
|
+
- @object-ui/plugin-chatbot@4.0.11
|
|
21
|
+
- @object-ui/plugin-detail@4.0.11
|
|
22
|
+
- @object-ui/plugin-form@4.0.11
|
|
23
|
+
- @object-ui/plugin-grid@4.0.11
|
|
24
|
+
- @object-ui/plugin-report@4.0.11
|
|
25
|
+
- @object-ui/plugin-view@4.0.11
|
|
26
|
+
- @object-ui/types@4.0.11
|
|
27
|
+
- @object-ui/core@4.0.11
|
|
28
|
+
- @object-ui/data-objectstack@4.0.11
|
|
29
|
+
- @object-ui/auth@4.0.11
|
|
30
|
+
- @object-ui/permissions@4.0.11
|
|
31
|
+
- @object-ui/collaboration@4.0.11
|
|
32
|
+
|
|
3
33
|
## 4.0.10
|
|
4
34
|
|
|
5
35
|
### Patch Changes
|
|
@@ -118,7 +118,7 @@ export function AppSidebar({ activeAppName, onAppChange }) {
|
|
|
118
118
|
}, [isMobile]);
|
|
119
119
|
const { recentItems } = useRecentItems();
|
|
120
120
|
const { favorites, removeFavorite } = useFavorites();
|
|
121
|
-
const { apps: metadataApps } = useMetadata();
|
|
121
|
+
const { apps: metadataApps, objects: metadataObjects } = useMetadata();
|
|
122
122
|
const apps = metadataApps || [];
|
|
123
123
|
// Filter out inactive apps
|
|
124
124
|
const activeApps = apps.filter((a) => a.active !== false);
|
|
@@ -166,6 +166,24 @@ export function AppSidebar({ activeAppName, onAppChange }) {
|
|
|
166
166
|
: [perm, 'read'];
|
|
167
167
|
return can(object, action);
|
|
168
168
|
}), [can]);
|
|
169
|
+
// Runtime capability checker — gates nav entries with `requiresObject` /
|
|
170
|
+
// `requiresService` against the runtime's actual SchemaRegistry contents.
|
|
171
|
+
// Currently we only probe registered objects (sourced from the metadata
|
|
172
|
+
// provider, which fetches `GET /api/v1/meta/object` on mount). Service
|
|
173
|
+
// gates default to pass since there is no client-side service registry
|
|
174
|
+
// probe yet — callers should wire one when needed.
|
|
175
|
+
const registeredObjectNames = React.useMemo(() => new Set((metadataObjects || []).map((o) => o?.name).filter(Boolean)), [metadataObjects]);
|
|
176
|
+
const checkCap = React.useCallback((kind, name) => {
|
|
177
|
+
if (kind === 'object') {
|
|
178
|
+
// While metadata is still loading we have an empty set; show
|
|
179
|
+
// entries by default to avoid a "menu flicker" where everything
|
|
180
|
+
// disappears momentarily on first render.
|
|
181
|
+
if (registeredObjectNames.size === 0)
|
|
182
|
+
return true;
|
|
183
|
+
return registeredObjectNames.has(name);
|
|
184
|
+
}
|
|
185
|
+
return true;
|
|
186
|
+
}, [registeredObjectNames]);
|
|
169
187
|
const basePath = activeApp ? `/apps/${activeAppName}` : '';
|
|
170
188
|
// Fallback system navigation when no active app exists — routes into the Setup app.
|
|
171
189
|
const systemFallbackNavigation = React.useMemo(() => [
|
|
@@ -181,7 +199,7 @@ export function AppSidebar({ activeAppName, onAppChange }) {
|
|
|
181
199
|
const AreaIcon = getIcon(area.icon);
|
|
182
200
|
const isActiveArea = area.id === activeAreaId;
|
|
183
201
|
return (_jsx(SidebarMenuItem, { children: _jsxs(SidebarMenuButton, { isActive: isActiveArea, tooltip: area.label, onClick: () => setActiveAreaId(area.id), children: [_jsx(AreaIcon, { className: "h-4 w-4" }), _jsx("span", { children: area.label })] }) }, area.id));
|
|
184
|
-
}) }) })] })), _jsx(SidebarGroup, { className: "py-0", children: _jsxs(SidebarGroupContent, { className: "relative", children: [_jsx(Search, { className: "pointer-events-none absolute left-2 top-1/2 size-4 -translate-y-1/2 select-none opacity-70" }), _jsx(SidebarInput, { placeholder: "Search navigation...", value: navSearchQuery, onChange: (e) => setNavSearchQuery(e.target.value), className: "pl-8" })] }) }), _jsx(NavigationRenderer, { items: processedNavigation, basePath: basePath, evaluateVisibility: evalVis, checkPermission: checkPerm, searchQuery: navSearchQuery, enablePinning: true, onPinToggle: togglePin, enableReorder: true, onReorder: handleReorder, resolveObjectLabel: (objectName, fallback) => resolveNavObjectLabel({ name: objectName, label: fallback }), t: t }), recentItems.length > 0 && (_jsxs(SidebarGroup, { children: [_jsxs(SidebarGroupLabel, { className: "flex items-center gap-1.5 cursor-pointer select-none", onClick: () => setRecentExpanded(prev => !prev), children: [_jsx(ChevronRight, { className: `h-3 w-3 transition-transform duration-150 ${recentExpanded ? 'rotate-90' : ''}` }), _jsx(Clock, { className: "h-3.5 w-3.5" }), "Recent"] }), recentExpanded && (_jsx(SidebarGroupContent, { children: _jsx(SidebarMenu, { children: recentItems.slice(0, 5).map(item => (_jsx(SidebarMenuItem, { children: _jsx(SidebarMenuButton, { asChild: true, tooltip: item.label, children: _jsxs(Link, { to: item.href, children: [_jsx("span", { className: "text-muted-foreground", children: item.type === 'dashboard' ? '📊' : item.type === 'report' ? '📈' : '📄' }), _jsx("span", { className: "truncate", children: item.label })] }) }) }, item.id))) }) }))] })), favorites.length > 0 && (_jsxs(SidebarGroup, { children: [_jsxs(SidebarGroupLabel, { className: "flex items-center gap-1.5", children: [_jsx(Star, { className: "h-3.5 w-3.5" }), "Favorites"] }), _jsx(SidebarGroupContent, { children: _jsx(SidebarMenu, { children: favorites.slice(0, 8).map(item => (_jsxs(SidebarMenuItem, { children: [_jsx(SidebarMenuButton, { asChild: true, tooltip: item.label, children: _jsxs(Link, { to: item.href, children: [_jsx("span", { className: "text-muted-foreground", children: item.type === 'dashboard' ? '📊' : item.type === 'report' ? '📈' : item.type === 'page' ? '📄' : '📋' }), _jsx("span", { className: "truncate", children: item.label })] }) }), _jsx(SidebarMenuAction, { showOnHover: true, onClick: (e) => { e.stopPropagation(); removeFavorite(item.id); }, "aria-label": `Remove ${item.label} from favorites`, children: _jsx(StarOff, { className: "h-3 w-3" }) })] }, item.id))) }) })] }))] })) : (_jsxs(SidebarGroup, { "data-testid": "system-fallback-nav", children: [_jsxs(SidebarGroupLabel, { className: "flex items-center gap-1.5", children: [_jsx(Settings, { className: "h-3.5 w-3.5" }), "System"] }), _jsx(SidebarGroupContent, { children: _jsx(SidebarMenu, { children: systemFallbackNavigation.map((item) => {
|
|
202
|
+
}) }) })] })), _jsx(SidebarGroup, { className: "py-0", children: _jsxs(SidebarGroupContent, { className: "relative", children: [_jsx(Search, { className: "pointer-events-none absolute left-2 top-1/2 size-4 -translate-y-1/2 select-none opacity-70" }), _jsx(SidebarInput, { placeholder: "Search navigation...", value: navSearchQuery, onChange: (e) => setNavSearchQuery(e.target.value), className: "pl-8" })] }) }), _jsx(NavigationRenderer, { items: processedNavigation, basePath: basePath, evaluateVisibility: evalVis, checkPermission: checkPerm, checkCapability: checkCap, searchQuery: navSearchQuery, enablePinning: true, onPinToggle: togglePin, enableReorder: true, onReorder: handleReorder, resolveObjectLabel: (objectName, fallback) => resolveNavObjectLabel({ name: objectName, label: fallback }), t: t }), recentItems.length > 0 && (_jsxs(SidebarGroup, { children: [_jsxs(SidebarGroupLabel, { className: "flex items-center gap-1.5 cursor-pointer select-none", onClick: () => setRecentExpanded(prev => !prev), children: [_jsx(ChevronRight, { className: `h-3 w-3 transition-transform duration-150 ${recentExpanded ? 'rotate-90' : ''}` }), _jsx(Clock, { className: "h-3.5 w-3.5" }), "Recent"] }), recentExpanded && (_jsx(SidebarGroupContent, { children: _jsx(SidebarMenu, { children: recentItems.slice(0, 5).map(item => (_jsx(SidebarMenuItem, { children: _jsx(SidebarMenuButton, { asChild: true, tooltip: item.label, children: _jsxs(Link, { to: item.href, children: [_jsx("span", { className: "text-muted-foreground", children: item.type === 'dashboard' ? '📊' : item.type === 'report' ? '📈' : '📄' }), _jsx("span", { className: "truncate", children: item.label })] }) }) }, item.id))) }) }))] })), favorites.length > 0 && (_jsxs(SidebarGroup, { children: [_jsxs(SidebarGroupLabel, { className: "flex items-center gap-1.5", children: [_jsx(Star, { className: "h-3.5 w-3.5" }), "Favorites"] }), _jsx(SidebarGroupContent, { children: _jsx(SidebarMenu, { children: favorites.slice(0, 8).map(item => (_jsxs(SidebarMenuItem, { children: [_jsx(SidebarMenuButton, { asChild: true, tooltip: item.label, children: _jsxs(Link, { to: item.href, children: [_jsx("span", { className: "text-muted-foreground", children: item.type === 'dashboard' ? '📊' : item.type === 'report' ? '📈' : item.type === 'page' ? '📄' : '📋' }), _jsx("span", { className: "truncate", children: item.label })] }) }), _jsx(SidebarMenuAction, { showOnHover: true, onClick: (e) => { e.stopPropagation(); removeFavorite(item.id); }, "aria-label": `Remove ${item.label} from favorites`, children: _jsx(StarOff, { className: "h-3 w-3" }) })] }, item.id))) }) })] }))] })) : (_jsxs(SidebarGroup, { "data-testid": "system-fallback-nav", children: [_jsxs(SidebarGroupLabel, { className: "flex items-center gap-1.5", children: [_jsx(Settings, { className: "h-3.5 w-3.5" }), "System"] }), _jsx(SidebarGroupContent, { children: _jsx(SidebarMenu, { children: systemFallbackNavigation.map((item) => {
|
|
185
203
|
const NavIcon = getIcon(item.icon);
|
|
186
204
|
return (_jsx(SidebarMenuItem, { children: _jsx(SidebarMenuButton, { asChild: true, tooltip: item.label, children: _jsxs(Link, { to: item.url || '/system', children: [_jsx(NavIcon, { className: "h-4 w-4" }), _jsx("span", { children: item.label })] }) }) }, item.id));
|
|
187
205
|
}) }) })] })) }), _jsx(SidebarFooter, { children: _jsx(SidebarMenu, { children: _jsx(SidebarMenuItem, { children: _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs(SidebarMenuButton, { size: "lg", className: "data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground", children: [_jsxs(Avatar, { className: "h-8 w-8 rounded-lg", children: [_jsx(AvatarImage, { src: user?.image, alt: user?.name ?? 'User' }), _jsx(AvatarFallback, { className: "rounded-lg bg-primary text-primary-foreground", children: getUserInitials(user) })] }), _jsxs("div", { className: "grid flex-1 text-left text-sm leading-tight", children: [_jsx("span", { className: "truncate font-semibold", children: user?.name ?? 'User' }), _jsx("span", { className: "truncate text-xs text-muted-foreground", children: user?.email ?? '' })] }), _jsx(ChevronsUpDown, { className: "ml-auto size-4" })] }) }), _jsxs(DropdownMenuContent, { className: "w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg", side: isMobile ? "bottom" : "right", align: "end", sideOffset: 4, children: [_jsx(DropdownMenuLabel, { className: "p-0 font-normal", children: _jsxs("div", { className: "flex items-center gap-2 px-1 py-1.5 text-left text-sm", children: [_jsxs(Avatar, { className: "h-8 w-8 rounded-lg", children: [_jsx(AvatarImage, { src: user?.image, alt: user?.name ?? 'User' }), _jsx(AvatarFallback, { className: "rounded-lg bg-primary text-primary-foreground", children: getUserInitials(user) })] }), _jsxs("div", { className: "grid flex-1 text-left text-sm leading-tight", children: [_jsx("span", { className: "truncate font-semibold", children: user?.name ?? 'User' }), _jsx("span", { className: "truncate text-xs text-muted-foreground", children: user?.email ?? '' })] })] }) }), _jsx(DropdownMenuSeparator, {}), _jsx(DropdownMenuGroup, { children: _jsxs(DropdownMenuItem, { onClick: () => navigate('/apps/setup'), children: [_jsx(Settings, { className: "mr-2 h-4 w-4" }), t('user.settings', { defaultValue: 'Settings' })] }) }), isAuthEnabled && (_jsxs(_Fragment, { children: [_jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuItem, { className: "text-destructive focus:text-destructive", onClick: () => signOut(), children: [_jsx(LogOut, { className: "mr-2 h-4 w-4" }), t('user.logout', { defaultValue: 'Log out' })] })] }))] })] }) }) }) })] }), isMobile && (_jsx("div", { className: "fixed bottom-0 left-0 right-0 z-50 flex items-center justify-around border-t bg-background/95 backdrop-blur-sm px-2 py-1 sm:hidden safe-area-bottom", children: (activeApp ? resolvedNavigation : systemFallbackNavigation).filter((n) => n.type !== 'group').slice(0, 5).map((item) => {
|
|
@@ -114,7 +114,7 @@ export function UnifiedSidebar({ activeAppName }) {
|
|
|
114
114
|
}, [isMobile]);
|
|
115
115
|
const { recentItems } = useRecentItems();
|
|
116
116
|
const { favorites, removeFavorite } = useFavorites();
|
|
117
|
-
const { apps: metadataApps } = useMetadata();
|
|
117
|
+
const { apps: metadataApps, objects: metadataObjects } = useMetadata();
|
|
118
118
|
const apps = metadataApps || [];
|
|
119
119
|
const activeApps = apps.filter((a) => a.active !== false);
|
|
120
120
|
const activeApp = activeApps.find((a) => a.name === (activeAppName || currentAppName)) || activeApps[0];
|
|
@@ -160,12 +160,23 @@ export function UnifiedSidebar({ activeAppName }) {
|
|
|
160
160
|
: [perm, 'read'];
|
|
161
161
|
return can(object, action);
|
|
162
162
|
}), [can]);
|
|
163
|
+
// Runtime capability gate: hide nav items targeting objects/services
|
|
164
|
+
// not registered in this runtime (e.g. cloud-only `sys_app`).
|
|
165
|
+
const registeredObjectNames = React.useMemo(() => new Set((metadataObjects || []).map((o) => o?.name).filter(Boolean)), [metadataObjects]);
|
|
166
|
+
const checkCap = React.useCallback((kind, name) => {
|
|
167
|
+
if (kind === 'object') {
|
|
168
|
+
if (registeredObjectNames.size === 0)
|
|
169
|
+
return true;
|
|
170
|
+
return registeredObjectNames.has(name);
|
|
171
|
+
}
|
|
172
|
+
return true;
|
|
173
|
+
}, [registeredObjectNames]);
|
|
163
174
|
const basePath = context === 'app' && activeApp ? `/apps/${activeApp.name}` : '';
|
|
164
175
|
return (_jsxs(_Fragment, { children: [_jsxs(Sidebar, { collapsible: "icon", className: "!top-14 !h-[calc(100svh-3.5rem)]", children: [_jsx(SidebarContent, { className: "pt-2", children: _jsx("div", { className: "transition-opacity duration-200 ease-in-out", children: context === 'app' && activeApp ? (_jsxs(_Fragment, { children: [areas.length > 1 && (_jsxs(SidebarGroup, { children: [_jsxs(SidebarGroupLabel, { className: "flex items-center gap-1.5", children: [_jsx(Layers, { className: "h-3.5 w-3.5" }), t('sidebar.area', { defaultValue: 'Area' })] }), _jsx(SidebarGroupContent, { children: _jsx(SidebarMenu, { children: areas.map((area) => {
|
|
165
176
|
const AreaIcon = getIcon(area.icon);
|
|
166
177
|
const isActiveArea = area.id === activeAreaId;
|
|
167
178
|
return (_jsx(SidebarMenuItem, { children: _jsxs(SidebarMenuButton, { isActive: isActiveArea, tooltip: area.label, onClick: () => setActiveAreaId(area.id), children: [_jsx(AreaIcon, { className: "h-4 w-4" }), _jsx("span", { children: area.label })] }) }, area.id));
|
|
168
|
-
}) }) })] })), _jsx(NavigationRenderer, { items: processedNavigation, basePath: basePath, evaluateVisibility: evalVis, checkPermission: checkPerm, enablePinning: !isMobile, onPinToggle: togglePin, enableReorder: !isMobile, onReorder: handleReorder, resolveObjectLabel: (objectName, fallback) => resolveNavObjectLabel({ name: objectName, label: fallback }), resolveDashboardLabel: (dashboardName, fallback) => resolveNavDashboardLabel({ name: dashboardName, label: fallback }), resolveGroupLabel: activeApp ? (groupId, fallback) => resolveNavGroupLabel(activeApp.name, groupId, fallback) : undefined, t: t }), recentItems.length > 0 && (_jsxs(SidebarGroup, { children: [_jsxs(SidebarGroupLabel, { className: "flex items-center gap-1.5 cursor-pointer select-none", onClick: () => setRecentExpanded(prev => !prev), children: [_jsx(ChevronRight, { className: `h-3 w-3 transition-transform duration-150 ${recentExpanded ? 'rotate-90' : ''}` }), _jsx(Clock, { className: "h-3.5 w-3.5" }), t('sidebar.recent', { defaultValue: 'Recent' })] }), recentExpanded && (_jsx(SidebarGroupContent, { children: _jsx(SidebarMenu, { children: recentItems.slice(0, 5).map(item => (_jsx(SidebarMenuItem, { children: _jsx(SidebarMenuButton, { asChild: true, tooltip: item.label, children: _jsxs(Link, { to: item.href, children: [_jsx("span", { className: "text-muted-foreground", children: item.type === 'dashboard' ? '📊' : item.type === 'report' ? '📈' : '📄' }), _jsx("span", { className: "truncate", children: item.label })] }) }) }, item.id))) }) }))] })), favorites.length > 0 && (_jsxs(SidebarGroup, { children: [_jsxs(SidebarGroupLabel, { className: "flex items-center gap-1.5", children: [_jsx(Star, { className: "h-3.5 w-3.5" }), t('sidebar.favorites', { defaultValue: 'Favorites' })] }), _jsx(SidebarGroupContent, { children: _jsx(SidebarMenu, { children: favorites.slice(0, 8).map(item => (_jsxs(SidebarMenuItem, { children: [_jsx(SidebarMenuButton, { asChild: true, tooltip: item.label, children: _jsxs(Link, { to: item.href, children: [_jsx("span", { className: "text-muted-foreground", children: item.type === 'dashboard' ? '📊' : item.type === 'report' ? '📈' : item.type === 'page' ? '📄' : '📋' }), _jsx("span", { className: "truncate", children: item.label })] }) }), _jsx(SidebarMenuAction, { showOnHover: true, onClick: (e) => { e.stopPropagation(); removeFavorite(item.id); }, "aria-label": t('sidebar.removeFromFavorites', { defaultValue: 'Remove {{name}} from favorites', name: item.label }), children: _jsx(StarOff, { className: "h-3 w-3" }) })] }, item.id))) }) })] }))] })) : (_jsxs(_Fragment, { children: [_jsx(SidebarGroup, { children: _jsx(SidebarGroupContent, { children: _jsx(SidebarMenu, { children: homeNavigation.map((item) => {
|
|
179
|
+
}) }) })] })), _jsx(NavigationRenderer, { items: processedNavigation, basePath: basePath, evaluateVisibility: evalVis, checkPermission: checkPerm, checkCapability: checkCap, enablePinning: !isMobile, onPinToggle: togglePin, enableReorder: !isMobile, onReorder: handleReorder, resolveObjectLabel: (objectName, fallback) => resolveNavObjectLabel({ name: objectName, label: fallback }), resolveDashboardLabel: (dashboardName, fallback) => resolveNavDashboardLabel({ name: dashboardName, label: fallback }), resolveGroupLabel: activeApp ? (groupId, fallback) => resolveNavGroupLabel(activeApp.name, groupId, fallback) : undefined, t: t }), recentItems.length > 0 && (_jsxs(SidebarGroup, { children: [_jsxs(SidebarGroupLabel, { className: "flex items-center gap-1.5 cursor-pointer select-none", onClick: () => setRecentExpanded(prev => !prev), children: [_jsx(ChevronRight, { className: `h-3 w-3 transition-transform duration-150 ${recentExpanded ? 'rotate-90' : ''}` }), _jsx(Clock, { className: "h-3.5 w-3.5" }), t('sidebar.recent', { defaultValue: 'Recent' })] }), recentExpanded && (_jsx(SidebarGroupContent, { children: _jsx(SidebarMenu, { children: recentItems.slice(0, 5).map(item => (_jsx(SidebarMenuItem, { children: _jsx(SidebarMenuButton, { asChild: true, tooltip: item.label, children: _jsxs(Link, { to: item.href, children: [_jsx("span", { className: "text-muted-foreground", children: item.type === 'dashboard' ? '📊' : item.type === 'report' ? '📈' : '📄' }), _jsx("span", { className: "truncate", children: item.label })] }) }) }, item.id))) }) }))] })), favorites.length > 0 && (_jsxs(SidebarGroup, { children: [_jsxs(SidebarGroupLabel, { className: "flex items-center gap-1.5", children: [_jsx(Star, { className: "h-3.5 w-3.5" }), t('sidebar.favorites', { defaultValue: 'Favorites' })] }), _jsx(SidebarGroupContent, { children: _jsx(SidebarMenu, { children: favorites.slice(0, 8).map(item => (_jsxs(SidebarMenuItem, { children: [_jsx(SidebarMenuButton, { asChild: true, tooltip: item.label, children: _jsxs(Link, { to: item.href, children: [_jsx("span", { className: "text-muted-foreground", children: item.type === 'dashboard' ? '📊' : item.type === 'report' ? '📈' : item.type === 'page' ? '📄' : '📋' }), _jsx("span", { className: "truncate", children: item.label })] }) }), _jsx(SidebarMenuAction, { showOnHover: true, onClick: (e) => { e.stopPropagation(); removeFavorite(item.id); }, "aria-label": t('sidebar.removeFromFavorites', { defaultValue: 'Remove {{name}} from favorites', name: item.label }), children: _jsx(StarOff, { className: "h-3 w-3" }) })] }, item.id))) }) })] }))] })) : (_jsxs(_Fragment, { children: [_jsx(SidebarGroup, { children: _jsx(SidebarGroupContent, { children: _jsx(SidebarMenu, { children: homeNavigation.map((item) => {
|
|
169
180
|
const NavIcon = getIcon(item.icon);
|
|
170
181
|
const isActive = location.pathname === item.url;
|
|
171
182
|
return (_jsx(SidebarMenuItem, { children: _jsx(SidebarMenuButton, { asChild: true, tooltip: item.label, isActive: isActive, children: _jsxs(Link, { to: item.url || '/home', children: [_jsx(NavIcon, { className: "h-4 w-4" }), _jsx("span", { children: item.label })] }) }) }, item.id));
|
|
@@ -65,15 +65,27 @@ const REQUIRED_FIELDS_BY_TYPE = {
|
|
|
65
65
|
filter: (f) => f.type === 'date',
|
|
66
66
|
preferred: PRIMARY_DATE_PREFERRED,
|
|
67
67
|
},
|
|
68
|
+
{
|
|
69
|
+
key: 'titleField',
|
|
70
|
+
i18nKey: 'console.objectView.titleField',
|
|
71
|
+
helpI18nKey: 'console.objectView.titleFieldHelp',
|
|
72
|
+
filter: (f) => f.type === 'text',
|
|
73
|
+
},
|
|
68
74
|
],
|
|
69
75
|
timeline: [
|
|
70
76
|
{
|
|
71
|
-
key: '
|
|
72
|
-
i18nKey: 'console.objectView.
|
|
77
|
+
key: 'startDateField',
|
|
78
|
+
i18nKey: 'console.objectView.startDateField',
|
|
73
79
|
helpI18nKey: 'console.objectView.timelineDateFieldHelp',
|
|
74
80
|
filter: (f) => f.type === 'date',
|
|
75
81
|
preferred: PRIMARY_DATE_PREFERRED,
|
|
76
82
|
},
|
|
83
|
+
{
|
|
84
|
+
key: 'titleField',
|
|
85
|
+
i18nKey: 'console.objectView.titleField',
|
|
86
|
+
helpI18nKey: 'console.objectView.titleFieldHelp',
|
|
87
|
+
filter: (f) => f.type === 'text',
|
|
88
|
+
},
|
|
77
89
|
],
|
|
78
90
|
gantt: [
|
|
79
91
|
{
|
|
@@ -90,10 +102,16 @@ const REQUIRED_FIELDS_BY_TYPE = {
|
|
|
90
102
|
filter: (f) => f.type === 'date',
|
|
91
103
|
preferred: END_DATE_PREFERRED,
|
|
92
104
|
},
|
|
105
|
+
{
|
|
106
|
+
key: 'titleField',
|
|
107
|
+
i18nKey: 'console.objectView.titleField',
|
|
108
|
+
helpI18nKey: 'console.objectView.titleFieldHelp',
|
|
109
|
+
filter: (f) => f.type === 'text',
|
|
110
|
+
},
|
|
93
111
|
],
|
|
94
112
|
gallery: [
|
|
95
113
|
{
|
|
96
|
-
key: '
|
|
114
|
+
key: 'coverField',
|
|
97
115
|
i18nKey: 'console.objectView.imageField',
|
|
98
116
|
helpI18nKey: 'console.objectView.imageFieldHelp',
|
|
99
117
|
filter: (f) => isImageLikeField(f),
|
|
@@ -27,7 +27,7 @@ const WIDGET_TYPES = [
|
|
|
27
27
|
{ type: 'line', label: 'Line Chart', Icon: LineChart },
|
|
28
28
|
{ type: 'pie', label: 'Pie Chart', Icon: PieChart },
|
|
29
29
|
{ type: 'table', label: 'Table', Icon: Table2 },
|
|
30
|
-
{ type: '
|
|
30
|
+
{ type: 'pivot', label: 'Pivot', Icon: LayoutGrid },
|
|
31
31
|
];
|
|
32
32
|
let widgetCounter = 0;
|
|
33
33
|
function createWidgetId() {
|
|
@@ -74,12 +74,22 @@ const TOP_LEVEL_WIDGET_KEYS = new Set([
|
|
|
74
74
|
'layoutW',
|
|
75
75
|
'layoutH',
|
|
76
76
|
'id',
|
|
77
|
+
// Spec-declared top-level fields that the renderer reads directly off the
|
|
78
|
+
// widget (not from options). Keeping them here ensures the unflatten path
|
|
79
|
+
// does not silently drop them into options where the renderer ignores them.
|
|
80
|
+
'searchable',
|
|
81
|
+
'pagination',
|
|
82
|
+
// Drill-down virtual flat keys are handled explicitly below — listing them
|
|
83
|
+
// here keeps the generic options-collector from duplicating them.
|
|
84
|
+
'drillDownEnabled',
|
|
85
|
+
'drillDownTarget',
|
|
77
86
|
]);
|
|
78
87
|
function flattenWidgetConfig(widget) {
|
|
79
88
|
// Spread options first so explicit top-level widget fields take precedence
|
|
80
89
|
// on collision. This surfaces type-specific options (pivot/table/chart axes,
|
|
81
90
|
// list itemTemplate, etc.) so they appear pre-filled in the config panel.
|
|
82
91
|
const options = (widget.options ?? {});
|
|
92
|
+
const drillDown = (options.drillDown ?? {});
|
|
83
93
|
return {
|
|
84
94
|
...options,
|
|
85
95
|
title: widget.title ?? '',
|
|
@@ -93,6 +103,11 @@ function flattenWidgetConfig(widget) {
|
|
|
93
103
|
layoutH: widget.layout?.h ?? 1,
|
|
94
104
|
colorVariant: widget.colorVariant ?? options.colorVariant ?? 'default',
|
|
95
105
|
actionUrl: widget.actionUrl ?? options.actionUrl ?? '',
|
|
106
|
+
searchable: widget.searchable ?? options.searchable ?? false,
|
|
107
|
+
pagination: widget.pagination ?? options.pagination ?? false,
|
|
108
|
+
// Surface drill-down nested shape as flat switches for the panel
|
|
109
|
+
drillDownEnabled: drillDown.enabled ?? false,
|
|
110
|
+
drillDownTarget: drillDown.target ?? 'drawer',
|
|
96
111
|
};
|
|
97
112
|
}
|
|
98
113
|
function unflattenWidgetConfig(config, base) {
|
|
@@ -107,6 +122,17 @@ function unflattenWidgetConfig(config, base) {
|
|
|
107
122
|
continue;
|
|
108
123
|
newOptions[key] = value;
|
|
109
124
|
}
|
|
125
|
+
// Re-nest drill-down flat keys back under options.drillDown so the renderer
|
|
126
|
+
// (which reads `options.drillDown`) picks them up.
|
|
127
|
+
const drillDownEnabled = config.drillDownEnabled;
|
|
128
|
+
const drillDownTarget = config.drillDownTarget;
|
|
129
|
+
if (drillDownEnabled !== undefined || drillDownTarget !== undefined) {
|
|
130
|
+
newOptions.drillDown = {
|
|
131
|
+
...(baseOptions.drillDown || {}),
|
|
132
|
+
...(drillDownEnabled !== undefined ? { enabled: !!drillDownEnabled } : {}),
|
|
133
|
+
...(drillDownTarget !== undefined ? { target: drillDownTarget } : {}),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
110
136
|
return {
|
|
111
137
|
title: config.title,
|
|
112
138
|
description: config.description,
|
|
@@ -118,6 +144,8 @@ function unflattenWidgetConfig(config, base) {
|
|
|
118
144
|
layout: { ...(base.layout || {}), w: config.layoutW, h: config.layoutH },
|
|
119
145
|
colorVariant: config.colorVariant,
|
|
120
146
|
actionUrl: config.actionUrl,
|
|
147
|
+
...(config.searchable !== undefined ? { searchable: !!config.searchable } : {}),
|
|
148
|
+
...(config.pagination !== undefined ? { pagination: !!config.pagination } : {}),
|
|
121
149
|
...(Object.keys(newOptions).length > 0 ? { options: newOptions } : {}),
|
|
122
150
|
};
|
|
123
151
|
}
|
|
@@ -129,6 +157,7 @@ function extractDashboardConfig(schema) {
|
|
|
129
157
|
rowHeight: String(s.rowHeight ?? '120'),
|
|
130
158
|
refreshInterval: String(s.refreshInterval ?? '0'),
|
|
131
159
|
title: s.title ?? '',
|
|
160
|
+
description: s.description ?? '',
|
|
132
161
|
showDescription: s.showDescription ?? true,
|
|
133
162
|
theme: s.theme ?? 'auto',
|
|
134
163
|
};
|
|
@@ -231,7 +260,12 @@ export function DashboardView({ dataSource }) {
|
|
|
231
260
|
refresh().catch(() => { });
|
|
232
261
|
}
|
|
233
262
|
catch (err) {
|
|
263
|
+
// Surface the failure — previously this was a silent console.warn,
|
|
264
|
+
// which produced "I saw it work, then refresh wiped it" reports
|
|
265
|
+
// because the optimistic state update masked a server-side reject.
|
|
266
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
234
267
|
console.warn('[DashboardView] Auto-save failed:', err);
|
|
268
|
+
toast.error(`Failed to save dashboard: ${message}`);
|
|
235
269
|
}
|
|
236
270
|
}, [adapter, dashboardName, refresh]);
|
|
237
271
|
// ---- Open / close config panel ------------------------------------------
|
|
@@ -279,6 +313,18 @@ export function DashboardView({ dataSource }) {
|
|
|
279
313
|
setSelectedWidgetId(null);
|
|
280
314
|
}
|
|
281
315
|
}, [editSchema, selectedWidgetId, saveSchema]);
|
|
316
|
+
// Reorder widgets via drag-and-drop from DashboardRenderer's design mode.
|
|
317
|
+
const handleWidgetsReorder = useCallback((nextWidgets) => {
|
|
318
|
+
const baseSchema = editSchema || dashboard;
|
|
319
|
+
if (!baseSchema)
|
|
320
|
+
return;
|
|
321
|
+
const newSchema = {
|
|
322
|
+
...baseSchema,
|
|
323
|
+
widgets: nextWidgets,
|
|
324
|
+
};
|
|
325
|
+
setEditSchema(newSchema);
|
|
326
|
+
saveSchema(newSchema);
|
|
327
|
+
}, [editSchema, dashboard, saveSchema]);
|
|
282
328
|
// ---- Dashboard config panel handlers ------------------------------------
|
|
283
329
|
// Stabilize config reference: only recompute after explicit actions (panel
|
|
284
330
|
// open, save, widget add). configVersion is incremented on those actions.
|
|
@@ -301,6 +347,7 @@ export function DashboardView({ dataSource }) {
|
|
|
301
347
|
rowHeight: toNum(config.rowHeight, editSchema.rowHeight),
|
|
302
348
|
refreshInterval: toNum(config.refreshInterval, 0) ?? 0,
|
|
303
349
|
title: config.title,
|
|
350
|
+
description: config.description,
|
|
304
351
|
showDescription: config.showDescription,
|
|
305
352
|
theme: config.theme,
|
|
306
353
|
};
|
|
@@ -391,6 +438,34 @@ export function DashboardView({ dataSource }) {
|
|
|
391
438
|
label: f.label || key,
|
|
392
439
|
}));
|
|
393
440
|
}, [selectedWidget?.object, metadataObjects]);
|
|
441
|
+
// ---- Runtime capability gate (must run before guards to respect Rules of Hooks)
|
|
442
|
+
// Hide widgets whose `requiresObject` is not registered (mirrors
|
|
443
|
+
// NavigationItem.requiresObject for nav entries). Defaults to widget.object
|
|
444
|
+
// when not set, so any object-bound widget disappears gracefully when its
|
|
445
|
+
// backing object isn't in this runtime (e.g. cloud-only
|
|
446
|
+
// `sys_package_installation` on system_overview).
|
|
447
|
+
const registeredObjectNamesForFilter = useMemo(() => new Set((metadataObjects || []).map((o) => o?.name).filter(Boolean)), [metadataObjects]);
|
|
448
|
+
const previewSchemaSrc = editSchema || dashboard;
|
|
449
|
+
const previewSchema = useMemo(() => {
|
|
450
|
+
if (!previewSchemaSrc)
|
|
451
|
+
return previewSchemaSrc;
|
|
452
|
+
// Defer pruning until metadata has actually loaded — otherwise the
|
|
453
|
+
// empty Set would hide every object-bound widget on first render.
|
|
454
|
+
if (registeredObjectNamesForFilter.size === 0)
|
|
455
|
+
return previewSchemaSrc;
|
|
456
|
+
const widgets = previewSchemaSrc.widgets;
|
|
457
|
+
if (!Array.isArray(widgets) || widgets.length === 0)
|
|
458
|
+
return previewSchemaSrc;
|
|
459
|
+
const filtered = widgets.filter((w) => {
|
|
460
|
+
const required = w?.requiresObject ?? w?.object;
|
|
461
|
+
if (!required)
|
|
462
|
+
return true;
|
|
463
|
+
return registeredObjectNamesForFilter.has(required);
|
|
464
|
+
});
|
|
465
|
+
if (filtered.length === widgets.length)
|
|
466
|
+
return previewSchemaSrc;
|
|
467
|
+
return { ...previewSchemaSrc, widgets: filtered };
|
|
468
|
+
}, [previewSchemaSrc, registeredObjectNamesForFilter]);
|
|
394
469
|
// ---- Loading / not-found guards -----------------------------------------
|
|
395
470
|
if (isLoading) {
|
|
396
471
|
return _jsx(SkeletonDashboard, {});
|
|
@@ -398,14 +473,29 @@ export function DashboardView({ dataSource }) {
|
|
|
398
473
|
if (!dashboard) {
|
|
399
474
|
return (_jsx("div", { className: "h-full flex items-center justify-center p-8", children: _jsxs(Empty, { children: [_jsx("div", { className: "mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-muted", children: _jsx(LayoutDashboard, { className: "h-6 w-6 text-muted-foreground" }) }), _jsx(EmptyTitle, { children: t('empty.dashboardNotFound') }), _jsx(EmptyDescription, { children: t('empty.dashboardNotFoundDescription', { name: dashboardName }) })] }) }));
|
|
400
475
|
}
|
|
401
|
-
|
|
402
|
-
|
|
476
|
+
return (_jsxs("div", { className: "flex flex-col h-full overflow-hidden bg-background", children: [_jsxs("div", { className: "flex flex-col sm:flex-row justify-between sm:items-center gap-3 sm:gap-4 p-4 sm:p-6 border-b shrink-0", children: [_jsxs("div", { className: "min-w-0 flex-1", children: [(() => {
|
|
477
|
+
// Per @objectstack/spec, DashboardSchema.title is "the dashboard
|
|
478
|
+
// title displayed in the header". We prefer it when present so
|
|
479
|
+
// edits made through the config panel (which writes `title`) are
|
|
480
|
+
// visible after save/reload. Falls back to `label` (the metadata
|
|
481
|
+
// display name) and finally to the raw `name`. We also follow
|
|
482
|
+
// `previewSchema` instead of the cached `dashboard` so the H1
|
|
483
|
+
// updates live while the user is typing in the config panel.
|
|
484
|
+
const headerSrc = previewSchema || dashboard;
|
|
485
|
+
const resolvedTitle = resolveI18nLabel(headerSrc.title, t);
|
|
486
|
+
const resolvedLabel = resolveI18nLabel(dashboard.label, t);
|
|
487
|
+
const fallbackLabel = dashboardLabel({ name: dashboard.name, label: resolvedLabel });
|
|
488
|
+
const display = resolvedTitle || fallbackLabel || dashboard.name;
|
|
489
|
+
return (_jsx("h1", { className: "text-lg sm:text-xl md:text-2xl font-bold tracking-tight truncate", children: display }));
|
|
490
|
+
})(), (() => {
|
|
491
|
+
const headerSrc = previewSchema || dashboard;
|
|
492
|
+
const rawDesc = headerSrc.description ?? dashboard.description;
|
|
403
493
|
const desc = dashboardDescription({
|
|
404
494
|
name: dashboard.name,
|
|
405
|
-
description: resolveI18nLabel(
|
|
495
|
+
description: resolveI18nLabel(rawDesc, t),
|
|
406
496
|
});
|
|
407
497
|
return desc ? (_jsx("p", { className: "text-sm text-muted-foreground mt-1 line-clamp-2", children: desc })) : null;
|
|
408
|
-
})()] }), _jsxs("div", { className: "shrink-0 flex items-center gap-1.5", children: [configPanelOpen && (_jsx("div", { className: "flex items-center gap-1 mr-2", role: "toolbar", "aria-label": "Add widgets", "data-testid": "dashboard-widget-toolbar", children: WIDGET_TYPES.map(({ type, label, Icon }) => (_jsxs("button", { type: "button", "data-testid": `dashboard-add-${type}`, onClick: () => addWidget(type), className: "inline-flex items-center gap-1 rounded-md border border-input bg-background px-2 py-1.5 text-[11px] font-medium text-muted-foreground hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", title: `Add ${label}`, "aria-label": `Add ${label} widget`, children: [_jsx(Plus, { className: "h-3 w-3" }), _jsx(Icon, { className: "h-3 w-3" })] }, type))) })), _jsxs("button", { type: "button", onClick: handleOpenConfigPanel, className: "inline-flex items-center gap-1.5 rounded-md border border-input bg-background px-2.5 py-1.5 text-xs font-medium text-muted-foreground shadow-sm hover:bg-accent hover:text-accent-foreground", "data-testid": "dashboard-edit-button", children: [_jsx(Pencil, { className: "h-3.5 w-3.5" }), t('common.edit', { defaultValue: 'Edit' })] })] })] }), _jsxs("div", { className: "flex-1 overflow-hidden flex flex-col sm:flex-row relative", children: [_jsx("div", { className: "flex-1 min-w-0 overflow-auto p-2 sm:p-4 md:p-6", children: _jsx(DashboardRenderer, { schema: previewSchema, dataSource: dataSource, designMode: configPanelOpen, selectedWidgetId: selectedWidgetId, onWidgetClick: setSelectedWidgetId, modalHandler: modalHandler, scriptHandlers: scriptHandlers }) }), selectedWidget ? (_jsx(WidgetConfigPanel, { open: configPanelOpen, onClose: handleCloseConfigPanel, config: widgetConfig, onSave: handleWidgetConfigSave, onFieldChange: handleWidgetFieldChange, availableObjects: availableObjects, availableFields: availableFields, headerExtra: _jsx(Button, { size: "sm", variant: "ghost", onClick: () => removeWidget(selectedWidgetId), className: "h-7 w-7 p-0 text-destructive hover:text-destructive", "data-testid": "widget-delete-button", title: "Delete widget", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) }) }, selectedWidgetId)) : (_jsx(DashboardConfigPanel, { open: configPanelOpen, onClose: handleCloseConfigPanel, config: dashboardConfig, onSave: handleDashboardConfigSave, onFieldChange: handleDashboardFieldChange })), _jsx(MetadataPanel, { open: showDebug, sections: [{ title: 'Dashboard Configuration', data: previewSchema }] })] }), modalState && modalState.schema?.objectName ? (_jsx(ModalForm, { schema: {
|
|
498
|
+
})()] }), _jsxs("div", { className: "shrink-0 flex items-center gap-1.5", children: [configPanelOpen && (_jsx("div", { className: "flex items-center gap-1 mr-2", role: "toolbar", "aria-label": "Add widgets", "data-testid": "dashboard-widget-toolbar", children: WIDGET_TYPES.map(({ type, label, Icon }) => (_jsxs("button", { type: "button", "data-testid": `dashboard-add-${type}`, onClick: () => addWidget(type), className: "inline-flex items-center gap-1 rounded-md border border-input bg-background px-2 py-1.5 text-[11px] font-medium text-muted-foreground hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", title: `Add ${label}`, "aria-label": `Add ${label} widget`, children: [_jsx(Plus, { className: "h-3 w-3" }), _jsx(Icon, { className: "h-3 w-3" })] }, type))) })), _jsxs("button", { type: "button", onClick: handleOpenConfigPanel, className: "inline-flex items-center gap-1.5 rounded-md border border-input bg-background px-2.5 py-1.5 text-xs font-medium text-muted-foreground shadow-sm hover:bg-accent hover:text-accent-foreground", "data-testid": "dashboard-edit-button", children: [_jsx(Pencil, { className: "h-3.5 w-3.5" }), t('common.edit', { defaultValue: 'Edit' })] })] })] }), _jsxs("div", { className: "flex-1 overflow-hidden flex flex-col sm:flex-row relative", children: [_jsx("div", { className: "flex-1 min-w-0 overflow-auto p-2 sm:p-4 md:p-6", children: _jsx(DashboardRenderer, { schema: previewSchema, dataSource: dataSource, designMode: configPanelOpen, selectedWidgetId: selectedWidgetId, onWidgetClick: setSelectedWidgetId, onWidgetsReorder: handleWidgetsReorder, modalHandler: modalHandler, scriptHandlers: scriptHandlers, hideHeaderText: true }) }), selectedWidget ? (_jsx(WidgetConfigPanel, { open: configPanelOpen, onClose: handleCloseConfigPanel, config: widgetConfig, onSave: handleWidgetConfigSave, onFieldChange: handleWidgetFieldChange, availableObjects: availableObjects, availableFields: availableFields, headerExtra: _jsx(Button, { size: "sm", variant: "ghost", onClick: () => removeWidget(selectedWidgetId), className: "h-7 w-7 p-0 text-destructive hover:text-destructive", "data-testid": "widget-delete-button", title: "Delete widget", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) }) }, selectedWidgetId)) : (_jsx(DashboardConfigPanel, { open: configPanelOpen, onClose: handleCloseConfigPanel, config: dashboardConfig, onSave: handleDashboardConfigSave, onFieldChange: handleDashboardFieldChange })), _jsx(MetadataPanel, { open: showDebug, sections: [{ title: 'Dashboard Configuration', data: previewSchema }] })] }), modalState && modalState.schema?.objectName ? (_jsx(ModalForm, { schema: {
|
|
409
499
|
type: 'object-form',
|
|
410
500
|
formType: 'modal',
|
|
411
501
|
objectName: modalState.schema.objectName,
|
package/dist/views/ObjectView.js
CHANGED
|
@@ -400,6 +400,20 @@ export function ObjectView({ dataSource, objects, onEdit, externalRefreshKey })
|
|
|
400
400
|
// the physical `sys_view` table (whose columns no longer
|
|
401
401
|
// accommodate the spec shape: arrays, nested objects, etc.).
|
|
402
402
|
const spec = { ...config, columns: incomingColumns };
|
|
403
|
+
// Per @objectstack/spec, certain view types nest their card/field
|
|
404
|
+
// list inside their type-specific subconfig (e.g. kanban.columns,
|
|
405
|
+
// gallery.visibleFields). The CreateViewDialog only collects
|
|
406
|
+
// required *picker* fields; we mirror the resolved column list
|
|
407
|
+
// into the subconfig here so the spec validator accepts the row.
|
|
408
|
+
if (config.type === 'kanban') {
|
|
409
|
+
spec.kanban = { ...(spec.kanban || {}), columns: incomingColumns };
|
|
410
|
+
}
|
|
411
|
+
else if (config.type === 'gallery') {
|
|
412
|
+
const existing = spec.gallery || {};
|
|
413
|
+
if (!Array.isArray(existing.visibleFields) || existing.visibleFields.length === 0) {
|
|
414
|
+
spec.gallery = { ...existing, visibleFields: incomingColumns };
|
|
415
|
+
}
|
|
416
|
+
}
|
|
403
417
|
if (typeof dataSource?.createView === 'function') {
|
|
404
418
|
const created = await dataSource.createView(objectName, spec);
|
|
405
419
|
createdId = created?.name || config?.name;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@object-ui/app-shell",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.11",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Minimal application shell for ObjectUI - framework-agnostic rendering engine",
|
|
@@ -27,34 +27,34 @@
|
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"lucide-react": "^1.14.0",
|
|
29
29
|
"sonner": "^2.0.7",
|
|
30
|
-
"@object-ui/auth": "4.0.
|
|
31
|
-
"@object-ui/collaboration": "4.0.
|
|
32
|
-
"@object-ui/components": "4.0.
|
|
33
|
-
"@object-ui/core": "4.0.
|
|
34
|
-
"@object-ui/data-objectstack": "4.0.
|
|
35
|
-
"@object-ui/fields": "4.0.
|
|
36
|
-
"@object-ui/i18n": "4.0.
|
|
37
|
-
"@object-ui/layout": "4.0.
|
|
38
|
-
"@object-ui/permissions": "4.0.
|
|
39
|
-
"@object-ui/react": "4.0.
|
|
40
|
-
"@object-ui/types": "4.0.
|
|
30
|
+
"@object-ui/auth": "4.0.11",
|
|
31
|
+
"@object-ui/collaboration": "4.0.11",
|
|
32
|
+
"@object-ui/components": "4.0.11",
|
|
33
|
+
"@object-ui/core": "4.0.11",
|
|
34
|
+
"@object-ui/data-objectstack": "4.0.11",
|
|
35
|
+
"@object-ui/fields": "4.0.11",
|
|
36
|
+
"@object-ui/i18n": "4.0.11",
|
|
37
|
+
"@object-ui/layout": "4.0.11",
|
|
38
|
+
"@object-ui/permissions": "4.0.11",
|
|
39
|
+
"@object-ui/react": "4.0.11",
|
|
40
|
+
"@object-ui/types": "4.0.11"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
43
|
"react": "^18.0.0 || ^19.0.0",
|
|
44
44
|
"react-dom": "^18.0.0 || ^19.0.0",
|
|
45
45
|
"react-router-dom": "^6.0.0 || ^7.0.0",
|
|
46
|
-
"@object-ui/plugin-calendar": "4.0.
|
|
47
|
-
"@object-ui/plugin-charts": "4.0.
|
|
48
|
-
"@object-ui/plugin-chatbot": "4.0.
|
|
49
|
-
"@object-ui/plugin-dashboard": "4.0.
|
|
50
|
-
"@object-ui/plugin-designer": "4.0.
|
|
51
|
-
"@object-ui/plugin-detail": "4.0.
|
|
52
|
-
"@object-ui/plugin-form": "4.0.
|
|
53
|
-
"@object-ui/plugin-grid": "4.0.
|
|
54
|
-
"@object-ui/plugin-kanban": "4.0.
|
|
55
|
-
"@object-ui/plugin-list": "4.0.
|
|
56
|
-
"@object-ui/plugin-report": "4.0.
|
|
57
|
-
"@object-ui/plugin-view": "4.0.
|
|
46
|
+
"@object-ui/plugin-calendar": "4.0.11",
|
|
47
|
+
"@object-ui/plugin-charts": "4.0.11",
|
|
48
|
+
"@object-ui/plugin-chatbot": "4.0.11",
|
|
49
|
+
"@object-ui/plugin-dashboard": "4.0.11",
|
|
50
|
+
"@object-ui/plugin-designer": "4.0.11",
|
|
51
|
+
"@object-ui/plugin-detail": "4.0.11",
|
|
52
|
+
"@object-ui/plugin-form": "4.0.11",
|
|
53
|
+
"@object-ui/plugin-grid": "4.0.11",
|
|
54
|
+
"@object-ui/plugin-kanban": "4.0.11",
|
|
55
|
+
"@object-ui/plugin-list": "4.0.11",
|
|
56
|
+
"@object-ui/plugin-report": "4.0.11",
|
|
57
|
+
"@object-ui/plugin-view": "4.0.11"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@types/node": "^25.7.0",
|