@object-ui/app-shell 3.3.0 → 3.3.2
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 +73 -0
- package/README.md +22 -2
- package/dist/chrome/CommandPalette.d.ts +16 -0
- package/dist/chrome/CommandPalette.js +74 -0
- package/dist/chrome/ConditionalAuthWrapper.d.ts +23 -0
- package/dist/chrome/ConditionalAuthWrapper.js +135 -0
- package/dist/chrome/ConsoleToaster.d.ts +11 -0
- package/dist/chrome/ConsoleToaster.js +28 -0
- package/dist/chrome/ErrorBoundary.d.ts +39 -0
- package/dist/chrome/ErrorBoundary.js +64 -0
- package/dist/chrome/KeyboardShortcutsDialog.d.ts +7 -0
- package/dist/chrome/KeyboardShortcutsDialog.js +63 -0
- package/dist/chrome/LoadingScreen.d.ts +12 -0
- package/dist/chrome/LoadingScreen.js +44 -0
- package/dist/chrome/OnboardingWalkthrough.d.ts +9 -0
- package/dist/chrome/OnboardingWalkthrough.js +11 -0
- package/dist/chrome/ThemeProvider.d.ts +13 -0
- package/dist/chrome/ThemeProvider.js +37 -0
- package/dist/chrome/index.d.ts +8 -0
- package/dist/chrome/index.js +8 -0
- package/dist/console/AppContent.d.ts +26 -0
- package/dist/console/AppContent.js +266 -0
- package/dist/console/ConsoleShell.d.ts +66 -0
- package/dist/console/ConsoleShell.js +97 -0
- package/dist/console/auth/AuthPageLayout.d.ts +9 -0
- package/dist/console/auth/AuthPageLayout.js +4 -0
- package/dist/console/auth/ForgotPasswordPage.d.ts +4 -0
- package/dist/console/auth/ForgotPasswordPage.js +23 -0
- package/dist/console/auth/LoginPage.d.ts +4 -0
- package/dist/console/auth/LoginPage.js +24 -0
- package/dist/console/auth/RegisterPage.d.ts +4 -0
- package/dist/console/auth/RegisterPage.js +29 -0
- package/dist/console/auth/index.d.ts +4 -0
- package/dist/console/auth/index.js +4 -0
- package/dist/console/home/AppCard.d.ts +14 -0
- package/dist/console/home/AppCard.js +36 -0
- package/dist/console/home/HomeLayout.d.ts +15 -0
- package/dist/console/home/HomeLayout.js +20 -0
- package/dist/console/home/HomePage.d.ts +18 -0
- package/dist/console/home/HomePage.js +54 -0
- package/dist/console/home/QuickActions.d.ts +9 -0
- package/dist/console/home/QuickActions.js +53 -0
- package/dist/console/home/RecentApps.d.ts +13 -0
- package/dist/console/home/RecentApps.js +32 -0
- package/dist/console/home/StarredApps.d.ts +13 -0
- package/dist/console/home/StarredApps.js +29 -0
- package/dist/console/home/index.d.ts +11 -0
- package/dist/console/home/index.js +11 -0
- package/dist/console/organizations/CreateWorkspaceDialog.d.ts +16 -0
- package/dist/console/organizations/CreateWorkspaceDialog.js +71 -0
- package/dist/console/organizations/OrganizationsLayout.d.ts +15 -0
- package/dist/console/organizations/OrganizationsLayout.js +20 -0
- package/dist/console/organizations/OrganizationsPage.d.ts +9 -0
- package/dist/console/organizations/OrganizationsPage.js +76 -0
- package/dist/console/organizations/index.d.ts +8 -0
- package/dist/console/organizations/index.js +8 -0
- package/dist/console/organizations/manage/AcceptInvitationPage.d.ts +7 -0
- package/dist/console/organizations/manage/AcceptInvitationPage.js +100 -0
- package/dist/console/organizations/manage/InvitationsPage.d.ts +6 -0
- package/dist/console/organizations/manage/InvitationsPage.js +107 -0
- package/dist/console/organizations/manage/InviteMemberDialog.d.ts +16 -0
- package/dist/console/organizations/manage/InviteMemberDialog.js +83 -0
- package/dist/console/organizations/manage/MembersPage.d.ts +6 -0
- package/dist/console/organizations/manage/MembersPage.js +89 -0
- package/dist/console/organizations/manage/OrganizationLayout.d.ts +8 -0
- package/dist/console/organizations/manage/OrganizationLayout.js +51 -0
- package/dist/console/organizations/manage/SettingsPage.d.ts +6 -0
- package/dist/console/organizations/manage/SettingsPage.js +141 -0
- package/dist/console/organizations/manage/orgContext.d.ts +5 -0
- package/dist/console/organizations/manage/orgContext.js +4 -0
- package/dist/context/FavoritesProvider.d.ts +50 -0
- package/dist/context/FavoritesProvider.js +129 -0
- package/dist/context/NavigationContext.d.ts +46 -0
- package/dist/context/NavigationContext.js +59 -0
- package/dist/context/index.d.ts +3 -0
- package/dist/context/index.js +2 -0
- package/dist/hooks/index.d.ts +7 -0
- package/dist/hooks/index.js +7 -0
- package/dist/hooks/useFavorites.d.ts +13 -0
- package/dist/hooks/useFavorites.js +13 -0
- package/dist/hooks/useMetadataService.d.ts +13 -0
- package/dist/hooks/useMetadataService.js +18 -0
- package/dist/hooks/useNavPins.d.ts +16 -0
- package/dist/hooks/useNavPins.js +72 -0
- package/dist/hooks/useNavigationSync.d.ts +82 -0
- package/dist/hooks/useNavigationSync.js +495 -0
- package/dist/hooks/useObjectActions.d.ts +38 -0
- package/dist/hooks/useObjectActions.js +92 -0
- package/dist/hooks/useRecentItems.d.ts +21 -0
- package/dist/hooks/useRecentItems.js +50 -0
- package/dist/hooks/useResponsiveSidebar.d.ts +8 -0
- package/dist/hooks/useResponsiveSidebar.js +28 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +35 -0
- package/dist/layout/ActivityFeed.d.ts +22 -0
- package/dist/layout/ActivityFeed.js +57 -0
- package/dist/layout/AppHeader.d.ts +33 -0
- package/dist/layout/AppHeader.js +182 -0
- package/dist/layout/AppSidebar.d.ts +14 -0
- package/dist/layout/AppSidebar.js +202 -0
- package/dist/layout/AppSwitcher.d.ts +12 -0
- package/dist/layout/AppSwitcher.js +24 -0
- package/dist/layout/AuthPageLayout.d.ts +9 -0
- package/dist/layout/AuthPageLayout.js +4 -0
- package/dist/layout/ConnectionStatus.d.ts +14 -0
- package/dist/layout/ConnectionStatus.js +36 -0
- package/dist/layout/ConsoleFloatingChatbot.d.ts +10 -0
- package/dist/layout/ConsoleFloatingChatbot.js +27 -0
- package/dist/layout/ConsoleLayout.d.ts +22 -0
- package/dist/layout/ConsoleLayout.js +49 -0
- package/dist/layout/LocaleSwitcher.d.ts +7 -0
- package/dist/layout/LocaleSwitcher.js +27 -0
- package/dist/layout/ModeToggle.d.ts +1 -0
- package/dist/layout/ModeToggle.js +10 -0
- package/dist/layout/UnifiedSidebar.d.ts +27 -0
- package/dist/layout/UnifiedSidebar.js +186 -0
- package/dist/layout/index.d.ts +11 -0
- package/dist/layout/index.js +10 -0
- package/dist/providers/AdapterProvider.d.ts +21 -0
- package/dist/providers/AdapterProvider.js +52 -0
- package/dist/providers/ExpressionProvider.d.ts +50 -0
- package/dist/providers/ExpressionProvider.js +67 -0
- package/dist/providers/MetadataProvider.d.ts +18 -0
- package/dist/providers/MetadataProvider.js +324 -0
- package/dist/services/MetadataService.d.ts +119 -0
- package/dist/services/MetadataService.js +211 -0
- package/dist/skeletons/SkeletonDashboard.d.ts +13 -0
- package/dist/skeletons/SkeletonDashboard.js +12 -0
- package/dist/skeletons/SkeletonDetail.d.ts +15 -0
- package/dist/skeletons/SkeletonDetail.js +12 -0
- package/dist/skeletons/SkeletonGrid.d.ts +15 -0
- package/dist/skeletons/SkeletonGrid.js +12 -0
- package/dist/skeletons/index.d.ts +3 -0
- package/dist/skeletons/index.js +3 -0
- package/dist/utils/getIcon.d.ts +24 -0
- package/dist/utils/getIcon.js +87 -0
- package/dist/utils/index.d.ts +33 -0
- package/dist/utils/index.js +62 -0
- package/dist/utils/metadataConverters.d.ts +78 -0
- package/dist/utils/metadataConverters.js +74 -0
- package/dist/views/ActionConfirmDialog.d.ts +22 -0
- package/dist/views/ActionConfirmDialog.js +22 -0
- package/dist/views/ActionParamDialog.d.ts +23 -0
- package/dist/views/ActionParamDialog.js +67 -0
- package/dist/views/DashboardView.d.ts +9 -0
- package/dist/views/DashboardView.js +357 -0
- package/dist/views/DesignDrawer.d.ts +28 -0
- package/dist/views/DesignDrawer.js +51 -0
- package/dist/views/MetadataInspector.d.ts +40 -0
- package/dist/views/MetadataInspector.js +69 -0
- package/dist/views/ObjectView.d.ts +11 -0
- package/dist/views/ObjectView.js +769 -0
- package/dist/views/PageView.d.ts +6 -0
- package/dist/views/PageView.js +49 -0
- package/dist/views/RecordDetailView.d.ts +14 -0
- package/dist/views/RecordDetailView.js +508 -0
- package/dist/views/ReportView.d.ts +4 -0
- package/dist/views/ReportView.js +285 -0
- package/dist/views/SearchResultsPage.d.ts +9 -0
- package/dist/views/SearchResultsPage.js +107 -0
- package/dist/views/ViewConfigPanel.d.ts +56 -0
- package/dist/views/ViewConfigPanel.js +91 -0
- package/dist/views/index.d.ts +11 -0
- package/dist/views/index.js +11 -0
- package/package.json +59 -11
- package/.turbo/turbo-build.log +0 -4
- package/src/components/AppShell.tsx +0 -31
- package/src/components/DashboardRenderer.tsx +0 -36
- package/src/components/FormRenderer.tsx +0 -71
- package/src/components/ObjectRenderer.tsx +0 -122
- package/src/components/PageRenderer.tsx +0 -28
- package/src/index.ts +0 -20
- package/src/types.ts +0 -78
- package/tsconfig.json +0 -20
- package/tsconfig.tsbuildinfo +0 -1
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* UnifiedSidebar
|
|
4
|
+
*
|
|
5
|
+
* Airtable-style contextual sidebar that dynamically switches between Home and App navigation.
|
|
6
|
+
* Features:
|
|
7
|
+
* - Persistent across all authenticated routes
|
|
8
|
+
* - Context-aware navigation (Home vs App)
|
|
9
|
+
* - Pinned bottom area (Settings, Help, User Profile)
|
|
10
|
+
* - Smooth transitions between contexts
|
|
11
|
+
* - Back to Home navigation from App context
|
|
12
|
+
* - App switcher dropdown
|
|
13
|
+
*
|
|
14
|
+
* @module
|
|
15
|
+
*/
|
|
16
|
+
import * as React from 'react';
|
|
17
|
+
import { Link, useLocation } from 'react-router-dom';
|
|
18
|
+
import { getIcon } from '../utils/getIcon';
|
|
19
|
+
import { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupLabel, SidebarGroupContent, SidebarMenu, SidebarMenuItem, SidebarMenuButton, SidebarMenuAction, SidebarTrigger, useSidebar, } from '@object-ui/components';
|
|
20
|
+
import { Clock, Star, StarOff, ChevronRight, Layers, } from 'lucide-react';
|
|
21
|
+
import { NavigationRenderer } from '@object-ui/layout';
|
|
22
|
+
import { useMetadata } from '../providers/MetadataProvider';
|
|
23
|
+
import { useExpressionContext, evaluateVisibility } from '../providers/ExpressionProvider';
|
|
24
|
+
import { usePermissions } from '@object-ui/permissions';
|
|
25
|
+
import { useRecentItems } from '../hooks/useRecentItems';
|
|
26
|
+
import { useFavorites } from '../hooks/useFavorites';
|
|
27
|
+
import { useNavPins } from '../hooks/useNavPins';
|
|
28
|
+
import { resolveI18nLabel } from '../utils';
|
|
29
|
+
import { useObjectTranslation, useObjectLabel } from '@object-ui/i18n';
|
|
30
|
+
// useObjectLabel provides appLabel/appDescription for convention-based
|
|
31
|
+
// i18n lookup — `{ns}.apps.{name}.label` resolves to the translated label
|
|
32
|
+
// loaded from /api/v1/i18n/translations/:locale.
|
|
33
|
+
import { useNavigationContext } from '../context/NavigationContext';
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
// useNavOrder – localStorage-persisted drag-and-drop reorder for nav items
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
function useNavOrder(appName) {
|
|
38
|
+
const storageKey = `objectui-nav-order-${appName}`;
|
|
39
|
+
const [orderMap, setOrderMap] = React.useState(() => {
|
|
40
|
+
try {
|
|
41
|
+
const raw = localStorage.getItem(storageKey);
|
|
42
|
+
if (!raw)
|
|
43
|
+
return {};
|
|
44
|
+
const parsed = JSON.parse(raw);
|
|
45
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed))
|
|
46
|
+
return {};
|
|
47
|
+
const result = {};
|
|
48
|
+
for (const [k, v] of Object.entries(parsed)) {
|
|
49
|
+
if (Array.isArray(v) && v.every((i) => typeof i === 'string')) {
|
|
50
|
+
result[k] = v;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return {};
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
const persist = React.useCallback((next) => {
|
|
60
|
+
setOrderMap(next);
|
|
61
|
+
try {
|
|
62
|
+
localStorage.setItem(storageKey, JSON.stringify(next));
|
|
63
|
+
}
|
|
64
|
+
catch { /* full */ }
|
|
65
|
+
}, [storageKey]);
|
|
66
|
+
const applyOrder = React.useCallback((items) => {
|
|
67
|
+
const saved = orderMap['__root__'];
|
|
68
|
+
if (!saved)
|
|
69
|
+
return items;
|
|
70
|
+
const byId = new Map(items.map(i => [i.id, i]));
|
|
71
|
+
const ordered = [];
|
|
72
|
+
for (const id of saved) {
|
|
73
|
+
const item = byId.get(id);
|
|
74
|
+
if (item) {
|
|
75
|
+
ordered.push(item);
|
|
76
|
+
byId.delete(id);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
byId.forEach(item => ordered.push(item));
|
|
80
|
+
return ordered;
|
|
81
|
+
}, [orderMap]);
|
|
82
|
+
const handleReorder = React.useCallback((reorderedItems) => {
|
|
83
|
+
const ids = reorderedItems.map(i => i.id);
|
|
84
|
+
persist({ ...orderMap, __root__: ids });
|
|
85
|
+
}, [orderMap, persist]);
|
|
86
|
+
return { applyOrder, handleReorder };
|
|
87
|
+
}
|
|
88
|
+
export function UnifiedSidebar({ activeAppName }) {
|
|
89
|
+
const { isMobile } = useSidebar();
|
|
90
|
+
const location = useLocation();
|
|
91
|
+
const { t } = useObjectTranslation();
|
|
92
|
+
const { objectLabel: resolveNavObjectLabel } = useObjectLabel();
|
|
93
|
+
const { context, currentAppName } = useNavigationContext();
|
|
94
|
+
// Swipe-from-left-edge gesture to open sidebar on mobile
|
|
95
|
+
React.useEffect(() => {
|
|
96
|
+
const EDGE_THRESHOLD = 30;
|
|
97
|
+
const SWIPE_DISTANCE = 50;
|
|
98
|
+
let touchStartX = 0;
|
|
99
|
+
const handleTouchStart = (e) => {
|
|
100
|
+
touchStartX = e.touches[0].clientX;
|
|
101
|
+
};
|
|
102
|
+
const handleTouchEnd = (e) => {
|
|
103
|
+
const deltaX = e.changedTouches[0].clientX - touchStartX;
|
|
104
|
+
if (touchStartX < EDGE_THRESHOLD && deltaX > SWIPE_DISTANCE && isMobile) {
|
|
105
|
+
document.querySelector('[data-sidebar="trigger"]')?.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
document.addEventListener('touchstart', handleTouchStart, { passive: true });
|
|
109
|
+
document.addEventListener('touchend', handleTouchEnd, { passive: true });
|
|
110
|
+
return () => {
|
|
111
|
+
document.removeEventListener('touchstart', handleTouchStart);
|
|
112
|
+
document.removeEventListener('touchend', handleTouchEnd);
|
|
113
|
+
};
|
|
114
|
+
}, [isMobile]);
|
|
115
|
+
const { recentItems } = useRecentItems();
|
|
116
|
+
const { favorites, removeFavorite } = useFavorites();
|
|
117
|
+
const { apps: metadataApps } = useMetadata();
|
|
118
|
+
const apps = metadataApps || [];
|
|
119
|
+
const activeApps = apps.filter((a) => a.active !== false);
|
|
120
|
+
const activeApp = activeApps.find((a) => a.name === (activeAppName || currentAppName)) || activeApps[0];
|
|
121
|
+
// Drag-reorder and pin persistence
|
|
122
|
+
const { applyOrder, handleReorder } = useNavOrder(activeApp?.name || 'home');
|
|
123
|
+
const { togglePin, applyPins } = useNavPins();
|
|
124
|
+
// Area management
|
|
125
|
+
const areas = activeApp?.areas || [];
|
|
126
|
+
const [activeAreaId, setActiveAreaId] = React.useState(() => areas.length > 0 ? areas[0].id : null);
|
|
127
|
+
React.useEffect(() => {
|
|
128
|
+
if (areas.length > 0) {
|
|
129
|
+
setActiveAreaId(prev => areas.some((a) => a.id === prev) ? prev : areas[0].id);
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
setActiveAreaId(null);
|
|
133
|
+
}
|
|
134
|
+
}, [activeApp?.name, areas.length]);
|
|
135
|
+
// Resolve navigation items
|
|
136
|
+
const activeArea = areas.find((a) => a.id === activeAreaId);
|
|
137
|
+
const appNavigation = activeArea?.navigation || activeApp?.navigation || [];
|
|
138
|
+
// Home navigation items
|
|
139
|
+
const homeNavigation = React.useMemo(() => [
|
|
140
|
+
{ id: 'home-dashboard', label: t('home.nav', { defaultValue: 'Home' }), type: 'url', url: '/home', icon: 'home' },
|
|
141
|
+
], [t]);
|
|
142
|
+
// Determine which navigation to show based on context
|
|
143
|
+
const navigationItems = context === 'home' ? homeNavigation : appNavigation;
|
|
144
|
+
// Apply saved order and pin state
|
|
145
|
+
const processedNavigation = React.useMemo(() => {
|
|
146
|
+
const ordered = applyOrder(navigationItems);
|
|
147
|
+
return applyPins(ordered);
|
|
148
|
+
}, [navigationItems, applyOrder, applyPins]);
|
|
149
|
+
// Recent section collapsed by default
|
|
150
|
+
const [recentExpanded, setRecentExpanded] = React.useState(false);
|
|
151
|
+
// Visibility evaluation
|
|
152
|
+
const { evaluator } = useExpressionContext();
|
|
153
|
+
const evalVis = React.useCallback((expr) => evaluateVisibility(expr, evaluator), [evaluator]);
|
|
154
|
+
// Permission check
|
|
155
|
+
const { can } = usePermissions();
|
|
156
|
+
const checkPerm = React.useCallback((permissions) => permissions.every((perm) => {
|
|
157
|
+
const parts = perm.split(':');
|
|
158
|
+
const [object, action] = parts.length >= 2
|
|
159
|
+
? [parts[0], parts[1]]
|
|
160
|
+
: [perm, 'read'];
|
|
161
|
+
return can(object, action);
|
|
162
|
+
}), [can]);
|
|
163
|
+
const basePath = context === 'app' && activeApp ? `/apps/${activeApp.name}` : '';
|
|
164
|
+
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" }), "Area"] }), _jsx(SidebarGroupContent, { children: _jsx(SidebarMenu, { children: areas.map((area) => {
|
|
165
|
+
const AreaIcon = getIcon(area.icon);
|
|
166
|
+
const isActiveArea = area.id === activeAreaId;
|
|
167
|
+
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: 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(_Fragment, { children: [_jsx(SidebarGroup, { children: _jsx(SidebarGroupContent, { children: _jsx(SidebarMenu, { children: homeNavigation.map((item) => {
|
|
169
|
+
const NavIcon = getIcon(item.icon);
|
|
170
|
+
const isActive = location.pathname === item.url;
|
|
171
|
+
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));
|
|
172
|
+
}) }) }) }), favorites.filter(f => f.type === 'object' || f.type === 'dashboard' || f.type === 'page').length > 0 && (_jsxs(SidebarGroup, { children: [_jsxs(SidebarGroupLabel, { className: "flex items-center gap-1.5", children: [_jsx(Star, { className: "h-3.5 w-3.5" }), "Starred"] }), _jsx(SidebarGroupContent, { children: _jsx(SidebarMenu, { children: favorites.filter(f => f.type === 'object' || f.type === 'dashboard' || f.type === 'page').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 === '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))) }) })] }))] })) }) }), _jsx(SidebarFooter, { className: "border-t p-1", children: _jsx(SidebarTrigger, { className: "w-full justify-start pl-2 group-data-[state=collapsed]:justify-center group-data-[state=collapsed]:pl-0" }) })] }), isMobile && context === 'app' && (_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: processedNavigation.filter((n) => n.type !== 'group').slice(0, 5).map((item) => {
|
|
173
|
+
const NavIcon = getIcon(item.icon);
|
|
174
|
+
let href = item.url || '#';
|
|
175
|
+
if (item.type === 'object') {
|
|
176
|
+
href = `${basePath}/${item.objectName}`;
|
|
177
|
+
if (item.viewName)
|
|
178
|
+
href += `/view/${item.viewName}`;
|
|
179
|
+
}
|
|
180
|
+
else if (item.type === 'dashboard')
|
|
181
|
+
href = item.dashboardName ? `${basePath}/dashboard/${item.dashboardName}` : '#';
|
|
182
|
+
else if (item.type === 'page')
|
|
183
|
+
href = item.pageName ? `${basePath}/page/${item.pageName}` : '#';
|
|
184
|
+
return (_jsxs(Link, { to: href, className: "flex flex-col items-center gap-0.5 px-2 py-1.5 text-muted-foreground hover:text-foreground transition-colors min-w-[44px] min-h-[44px] justify-center", children: [_jsx(NavIcon, { className: "h-5 w-5" }), _jsx("span", { className: "text-[10px] truncate max-w-[60px]", children: resolveI18nLabel(item.label, t) })] }, item.id));
|
|
185
|
+
}) }))] }));
|
|
186
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { ConsoleLayout } from './ConsoleLayout';
|
|
2
|
+
export { AppHeader } from './AppHeader';
|
|
3
|
+
export { AppSidebar } from './AppSidebar';
|
|
4
|
+
export { UnifiedSidebar } from './UnifiedSidebar';
|
|
5
|
+
export { AppSwitcher } from './AppSwitcher';
|
|
6
|
+
export { ConnectionStatus } from './ConnectionStatus';
|
|
7
|
+
export { ActivityFeed } from './ActivityFeed';
|
|
8
|
+
export type { ActivityItem } from './ActivityFeed';
|
|
9
|
+
export { LocaleSwitcher } from './LocaleSwitcher';
|
|
10
|
+
export { ModeToggle } from './ModeToggle';
|
|
11
|
+
export { AuthPageLayout } from './AuthPageLayout';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { ConsoleLayout } from './ConsoleLayout';
|
|
2
|
+
export { AppHeader } from './AppHeader';
|
|
3
|
+
export { AppSidebar } from './AppSidebar';
|
|
4
|
+
export { UnifiedSidebar } from './UnifiedSidebar';
|
|
5
|
+
export { AppSwitcher } from './AppSwitcher';
|
|
6
|
+
export { ConnectionStatus } from './ConnectionStatus';
|
|
7
|
+
export { ActivityFeed } from './ActivityFeed';
|
|
8
|
+
export { LocaleSwitcher } from './LocaleSwitcher';
|
|
9
|
+
export { ModeToggle } from './ModeToggle';
|
|
10
|
+
export { AuthPageLayout } from './AuthPageLayout';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AdapterProvider
|
|
3
|
+
*
|
|
4
|
+
* Creates and provides an ObjectStackAdapter instance to the component tree.
|
|
5
|
+
* Also exposes a `useAdapter` hook for consuming the adapter in child components.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
import { type ReactNode } from 'react';
|
|
10
|
+
import { ObjectStackAdapter } from '@object-ui/data-objectstack';
|
|
11
|
+
export { useAdapter } from '@object-ui/react';
|
|
12
|
+
interface AdapterProviderProps {
|
|
13
|
+
children: ReactNode;
|
|
14
|
+
/** Optional pre-created adapter (useful for testing). */
|
|
15
|
+
adapter?: ObjectStackAdapter | null;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Creates an ObjectStackAdapter, connects to the API, then provides it to children.
|
|
19
|
+
* Shows nothing (returns null) until the adapter is ready.
|
|
20
|
+
*/
|
|
21
|
+
export declare function AdapterProvider({ children, adapter: externalAdapter }: AdapterProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* AdapterProvider
|
|
4
|
+
*
|
|
5
|
+
* Creates and provides an ObjectStackAdapter instance to the component tree.
|
|
6
|
+
* Also exposes a `useAdapter` hook for consuming the adapter in child components.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import { useState, useEffect } from 'react';
|
|
11
|
+
import { ObjectStackAdapter } from '@object-ui/data-objectstack';
|
|
12
|
+
import { createAuthenticatedFetch } from '@object-ui/auth';
|
|
13
|
+
import { AdapterCtx } from '@object-ui/react';
|
|
14
|
+
export { useAdapter } from '@object-ui/react';
|
|
15
|
+
/**
|
|
16
|
+
* Creates an ObjectStackAdapter, connects to the API, then provides it to children.
|
|
17
|
+
* Shows nothing (returns null) until the adapter is ready.
|
|
18
|
+
*/
|
|
19
|
+
export function AdapterProvider({ children, adapter: externalAdapter }) {
|
|
20
|
+
const [adapter, setAdapter] = useState(externalAdapter ?? null);
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (externalAdapter) {
|
|
23
|
+
setAdapter(externalAdapter);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
let cancelled = false;
|
|
27
|
+
async function init() {
|
|
28
|
+
try {
|
|
29
|
+
const a = new ObjectStackAdapter({
|
|
30
|
+
baseUrl: import.meta.env.VITE_SERVER_URL || '',
|
|
31
|
+
fetch: createAuthenticatedFetch(),
|
|
32
|
+
autoReconnect: true,
|
|
33
|
+
maxReconnectAttempts: 5,
|
|
34
|
+
reconnectDelay: 1000,
|
|
35
|
+
cache: { maxSize: 50, ttl: 300000 },
|
|
36
|
+
});
|
|
37
|
+
await a.connect();
|
|
38
|
+
if (!cancelled) {
|
|
39
|
+
setAdapter(a);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
if (!cancelled) {
|
|
44
|
+
console.error('[Console] Failed to initialize:', err);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
init();
|
|
49
|
+
return () => { cancelled = true; };
|
|
50
|
+
}, [externalAdapter]);
|
|
51
|
+
return (_jsx(AdapterCtx.Provider, { value: adapter, children: children }));
|
|
52
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ExpressionContext Provider
|
|
3
|
+
*
|
|
4
|
+
* Provides expression evaluation context (user, app, data) to all child components.
|
|
5
|
+
* Used by useCondition/useExpression hooks from @object-ui/react to evaluate
|
|
6
|
+
* dynamic visibility, disabled, and hidden expressions in navigation items,
|
|
7
|
+
* fields, and components.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```tsx
|
|
11
|
+
* <ExpressionProvider user={currentUser} app={activeApp}>
|
|
12
|
+
* <AppSidebar />
|
|
13
|
+
* </ExpressionProvider>
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
import React from 'react';
|
|
17
|
+
import { ExpressionEvaluator } from '@object-ui/core';
|
|
18
|
+
export interface ExpressionContextValue {
|
|
19
|
+
/** Current authenticated user */
|
|
20
|
+
user: Record<string, any>;
|
|
21
|
+
/** Active application config */
|
|
22
|
+
app: Record<string, any>;
|
|
23
|
+
/** Additional data scope */
|
|
24
|
+
data: Record<string, any>;
|
|
25
|
+
/** The evaluator instance (for imperative use) */
|
|
26
|
+
evaluator: ExpressionEvaluator;
|
|
27
|
+
}
|
|
28
|
+
interface ExpressionProviderProps {
|
|
29
|
+
children: React.ReactNode;
|
|
30
|
+
user?: Record<string, any>;
|
|
31
|
+
app?: Record<string, any>;
|
|
32
|
+
data?: Record<string, any>;
|
|
33
|
+
}
|
|
34
|
+
export declare function ExpressionProvider({ children, user, app, data }: ExpressionProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
35
|
+
/**
|
|
36
|
+
* Hook to access the expression context.
|
|
37
|
+
* Returns the full context value or a default empty context.
|
|
38
|
+
*/
|
|
39
|
+
export declare function useExpressionContext(): ExpressionContextValue;
|
|
40
|
+
/**
|
|
41
|
+
* Evaluate a visibility expression.
|
|
42
|
+
* Supports:
|
|
43
|
+
* - boolean: true/false
|
|
44
|
+
* - string "true"/"false"
|
|
45
|
+
* - template expression: "${user.role === 'admin'}"
|
|
46
|
+
*
|
|
47
|
+
* Returns true if the item should be visible.
|
|
48
|
+
*/
|
|
49
|
+
export declare function evaluateVisibility(expression: string | boolean | undefined, evaluator: ExpressionEvaluator): boolean;
|
|
50
|
+
export {};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* ExpressionContext Provider
|
|
4
|
+
*
|
|
5
|
+
* Provides expression evaluation context (user, app, data) to all child components.
|
|
6
|
+
* Used by useCondition/useExpression hooks from @object-ui/react to evaluate
|
|
7
|
+
* dynamic visibility, disabled, and hidden expressions in navigation items,
|
|
8
|
+
* fields, and components.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```tsx
|
|
12
|
+
* <ExpressionProvider user={currentUser} app={activeApp}>
|
|
13
|
+
* <AppSidebar />
|
|
14
|
+
* </ExpressionProvider>
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
import { createContext, useContext, useMemo } from 'react';
|
|
18
|
+
import { ExpressionEvaluator } from '@object-ui/core';
|
|
19
|
+
const ExprCtx = createContext(null);
|
|
20
|
+
export function ExpressionProvider({ children, user = {}, app = {}, data = {} }) {
|
|
21
|
+
const value = useMemo(() => {
|
|
22
|
+
const context = { user, app, data };
|
|
23
|
+
const evaluator = new ExpressionEvaluator(context);
|
|
24
|
+
return { user, app, data, evaluator };
|
|
25
|
+
}, [user, app, data]);
|
|
26
|
+
return _jsx(ExprCtx.Provider, { value: value, children: children });
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Hook to access the expression context.
|
|
30
|
+
* Returns the full context value or a default empty context.
|
|
31
|
+
*/
|
|
32
|
+
export function useExpressionContext() {
|
|
33
|
+
const ctx = useContext(ExprCtx);
|
|
34
|
+
if (!ctx) {
|
|
35
|
+
// Return a safe default so components can be used outside the provider
|
|
36
|
+
const fallback = { user: {}, app: {}, data: {} };
|
|
37
|
+
return { ...fallback, evaluator: new ExpressionEvaluator(fallback) };
|
|
38
|
+
}
|
|
39
|
+
return ctx;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Evaluate a visibility expression.
|
|
43
|
+
* Supports:
|
|
44
|
+
* - boolean: true/false
|
|
45
|
+
* - string "true"/"false"
|
|
46
|
+
* - template expression: "${user.role === 'admin'}"
|
|
47
|
+
*
|
|
48
|
+
* Returns true if the item should be visible.
|
|
49
|
+
*/
|
|
50
|
+
export function evaluateVisibility(expression, evaluator) {
|
|
51
|
+
if (expression === undefined || expression === null)
|
|
52
|
+
return true;
|
|
53
|
+
if (expression === true || expression === 'true')
|
|
54
|
+
return true;
|
|
55
|
+
if (expression === false || expression === 'false')
|
|
56
|
+
return false;
|
|
57
|
+
if (typeof expression === 'string' && expression.includes('${')) {
|
|
58
|
+
try {
|
|
59
|
+
const result = evaluator.evaluateCondition(expression);
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return true; // Default to visible on error
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';
|
|
3
|
+
import { useMetadata, type MetadataContextValue, type MetadataState } from '@object-ui/react';
|
|
4
|
+
export type { MetadataState, MetadataContextValue };
|
|
5
|
+
export { useMetadataItem } from '@object-ui/react';
|
|
6
|
+
export { useMetadata };
|
|
7
|
+
export type MetadataTypeStatus = 'idle' | 'loading' | 'ready' | 'error';
|
|
8
|
+
interface MetadataProviderProps {
|
|
9
|
+
children: ReactNode;
|
|
10
|
+
adapter: ObjectStackAdapter;
|
|
11
|
+
ttlMs?: number;
|
|
12
|
+
}
|
|
13
|
+
export declare function MetadataProvider({ children, adapter, ttlMs }: MetadataProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
14
|
+
export declare function useMetadataType(type: string): {
|
|
15
|
+
items: any[];
|
|
16
|
+
loading: boolean;
|
|
17
|
+
error: Error | null;
|
|
18
|
+
};
|