@motiadev/workbench 0.8.2-beta.140-306759 → 0.8.2-beta.140-131607
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/dist/middleware.d.ts +8 -1
- package/dist/middleware.js +5 -3
- package/dist/src/App.js +49 -49
- package/dist/src/components/flow/flow-page.js +2 -1
- package/dist/src/components/logs/logs-tab-label.d.ts +1 -0
- package/dist/src/components/logs/logs-tab-label.js +5 -0
- package/dist/src/components/observability/trace-item/trace-item-detail.js +1 -1
- package/dist/src/components/observability/trace-tab-label.d.ts +1 -0
- package/dist/src/components/observability/trace-tab-label.js +5 -0
- package/dist/src/components/observability/trace-timeline.js +2 -1
- package/dist/src/components/observability/traces-groups.js +1 -7
- package/dist/src/components/states/state-tab-label.d.ts +1 -0
- package/dist/src/components/states/state-tab-label.js +5 -0
- package/dist/src/index.css +0 -1
- package/dist/src/lib/__tests__/utils.test.d.ts +1 -0
- package/dist/src/lib/__tests__/utils.test.js +94 -0
- package/dist/src/lib/plugins.d.ts +1 -0
- package/dist/src/lib/plugins.js +55 -0
- package/dist/src/lib/utils.d.ts +5 -0
- package/dist/src/lib/utils.js +25 -2
- package/dist/src/project-view-mode.d.ts +1 -0
- package/dist/src/project-view-mode.js +18 -0
- package/dist/src/stores/use-app-tabs-store.d.ts +18 -0
- package/dist/src/stores/use-app-tabs-store.js +37 -0
- package/dist/src/stores/use-tabs-store.js +7 -5
- package/dist/src/system-view-mode.d.ts +1 -0
- package/dist/src/system-view-mode.js +36 -0
- package/dist/tsconfig.app.tsbuildinfo +1 -1
- package/dist/tsconfig.node.tsbuildinfo +1 -1
- package/dist/vite-plugin-motia-plugins.d.ts +9 -0
- package/dist/vite-plugin-motia-plugins.js +69 -0
- package/package.json +6 -4
package/dist/middleware.d.ts
CHANGED
|
@@ -1,2 +1,9 @@
|
|
|
1
1
|
import type { Express } from 'express';
|
|
2
|
-
|
|
2
|
+
import { type WorkbenchPlugin } from './vite-plugin-motia-plugins';
|
|
3
|
+
export type ApplyMiddlewareParams = {
|
|
4
|
+
app: Express;
|
|
5
|
+
port: number;
|
|
6
|
+
workbenchBase: string;
|
|
7
|
+
plugins: WorkbenchPlugin[];
|
|
8
|
+
};
|
|
9
|
+
export declare const applyMiddleware: ({ app, port, workbenchBase, plugins }: ApplyMiddlewareParams) => Promise<void>;
|
package/dist/middleware.js
CHANGED
|
@@ -4,10 +4,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.applyMiddleware = void 0;
|
|
7
|
+
const plugin_react_1 = __importDefault(require("@vitejs/plugin-react"));
|
|
7
8
|
const fs_1 = __importDefault(require("fs"));
|
|
8
9
|
const path_1 = __importDefault(require("path"));
|
|
9
10
|
const vite_1 = require("vite");
|
|
10
|
-
const
|
|
11
|
+
const vite_plugin_motia_plugins_1 = __importDefault(require("./vite-plugin-motia-plugins"));
|
|
11
12
|
const processCwdPlugin = () => {
|
|
12
13
|
return {
|
|
13
14
|
name: 'html-transform',
|
|
@@ -35,7 +36,7 @@ const reoPlugin = () => {
|
|
|
35
36
|
},
|
|
36
37
|
};
|
|
37
38
|
};
|
|
38
|
-
const applyMiddleware = async (app, port, workbenchBase
|
|
39
|
+
const applyMiddleware = async ({ app, port, workbenchBase, plugins }) => {
|
|
39
40
|
const vite = await (0, vite_1.createServer)({
|
|
40
41
|
appType: 'spa',
|
|
41
42
|
root: __dirname,
|
|
@@ -58,9 +59,10 @@ const applyMiddleware = async (app, port, workbenchBase = '') => {
|
|
|
58
59
|
alias: {
|
|
59
60
|
'@': path_1.default.resolve(__dirname, './src'),
|
|
60
61
|
'@/assets': path_1.default.resolve(__dirname, './src/assets'),
|
|
62
|
+
// antd: path.join(process.cwd(), './node_modules/antd'),
|
|
61
63
|
},
|
|
62
64
|
},
|
|
63
|
-
plugins: [(0, plugin_react_1.default)(), processCwdPlugin(), reoPlugin()],
|
|
65
|
+
plugins: [(0, plugin_react_1.default)(), processCwdPlugin(), reoPlugin(), (0, vite_plugin_motia_plugins_1.default)(plugins)],
|
|
64
66
|
assetsInclude: ['**/*.png', '**/*.jpg', '**/*.jpeg', '**/*.gif', '**/*.svg', '**/*.ico', '**/*.webp', '**/*.avif'],
|
|
65
67
|
});
|
|
66
68
|
app.use(workbenchBase, vite.middlewares);
|
package/dist/src/App.js
CHANGED
|
@@ -1,56 +1,56 @@
|
|
|
1
|
-
import { jsx as _jsx
|
|
2
|
-
import {
|
|
3
|
-
import { analytics } from '@/lib/analytics';
|
|
4
|
-
import { ReactFlowProvider } from '@xyflow/react';
|
|
5
|
-
import { File, GanttChart, Link2, LogsIcon } from 'lucide-react';
|
|
6
|
-
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo } from 'react';
|
|
7
3
|
import { FlowPage } from './components/flow/flow-page';
|
|
8
4
|
import { FlowTabMenuItem } from './components/flow/flow-tab-menu-item';
|
|
9
|
-
import { Header } from './components/header/header';
|
|
10
5
|
import { LogsPage } from './components/logs/logs-page';
|
|
6
|
+
import { LogsTabLabel } from './components/logs/logs-tab-label';
|
|
7
|
+
import { TracingTabLabel } from './components/observability/trace-tab-label';
|
|
11
8
|
import { TracesPage } from './components/observability/traces-page';
|
|
12
|
-
import {
|
|
9
|
+
import { StatesTabLabel } from './components/states/state-tab-label';
|
|
13
10
|
import { StatesPage } from './components/states/states-page';
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
11
|
+
import { registerPluginTabs } from './lib/plugins';
|
|
12
|
+
import { getViewModeFromURL } from './lib/utils';
|
|
13
|
+
import { ProjectViewMode } from './project-view-mode';
|
|
14
|
+
import { setAppTabs, TabLocation } from './stores/use-app-tabs-store';
|
|
15
|
+
import { SystemViewMode } from './system-view-mode';
|
|
16
|
+
const TAB_IDS = {
|
|
17
|
+
FLOW: 'flow',
|
|
18
|
+
TRACING: 'tracing',
|
|
19
|
+
LOGS: 'logs',
|
|
20
|
+
STATES: 'states',
|
|
21
|
+
};
|
|
22
|
+
const registerDefaultTabs = () => {
|
|
23
|
+
const topTabs = [
|
|
24
|
+
{
|
|
25
|
+
id: TAB_IDS.FLOW,
|
|
26
|
+
tabLabel: FlowTabMenuItem,
|
|
27
|
+
content: FlowPage,
|
|
28
|
+
},
|
|
29
|
+
];
|
|
30
|
+
const bottomTabs = [
|
|
31
|
+
{
|
|
32
|
+
id: TAB_IDS.TRACING,
|
|
33
|
+
tabLabel: TracingTabLabel,
|
|
34
|
+
content: TracesPage,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
id: TAB_IDS.LOGS,
|
|
38
|
+
tabLabel: LogsTabLabel,
|
|
39
|
+
content: LogsPage,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: TAB_IDS.STATES,
|
|
43
|
+
tabLabel: StatesTabLabel,
|
|
44
|
+
content: StatesPage,
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
setAppTabs(TabLocation.TOP, topTabs);
|
|
48
|
+
setAppTabs(TabLocation.BOTTOM, bottomTabs);
|
|
49
|
+
};
|
|
50
|
+
registerDefaultTabs();
|
|
51
|
+
registerPluginTabs();
|
|
21
52
|
export const App = () => {
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
const [viewMode, setViewMode] = useState('system');
|
|
26
|
-
const tabChangeCallbacks = useMemo(() => ({
|
|
27
|
-
[TabLocation.TOP]: setTopTab,
|
|
28
|
-
[TabLocation.BOTTOM]: setBottomTab,
|
|
29
|
-
}), [setTopTab, setBottomTab]);
|
|
30
|
-
const onTabChange = useCallback((location) => (newTab) => {
|
|
31
|
-
analytics.track(`${location} tab changed`, { [`new.${location}`]: newTab, tab });
|
|
32
|
-
tabChangeCallbacks[location](newTab);
|
|
33
|
-
}, [tabChangeCallbacks, tab]);
|
|
34
|
-
useEffect(() => {
|
|
35
|
-
const url = new URL(window.location.href);
|
|
36
|
-
const viewMode = url.searchParams.get('view-mode');
|
|
37
|
-
if (viewMode) {
|
|
38
|
-
setViewMode(viewMode);
|
|
39
|
-
}
|
|
40
|
-
}, [setViewMode]);
|
|
41
|
-
if (viewMode === 'project') {
|
|
42
|
-
return (_jsxs("div", { className: "grid grid-rows-1 grid-cols-[1fr_auto] bg-background text-foreground h-screen", children: [_jsx("main", { className: "m-2 overflow-hidden", role: "main", children: _jsx(Panel, { tabs: [
|
|
43
|
-
{
|
|
44
|
-
label: 'Flow',
|
|
45
|
-
labelComponent: _jsx(FlowTabMenuItem, {}),
|
|
46
|
-
content: (_jsx(ReactFlowProvider, { children: _jsx("div", { className: "h-[calc(100vh-100px)] w-full", children: _jsx(FlowPage, {}) }) })),
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
label: 'Endpoint',
|
|
50
|
-
labelComponent: (_jsxs(_Fragment, { children: [_jsx(Link2, {}), "Endpoint"] })),
|
|
51
|
-
content: _jsx(EndpointsPage, {}),
|
|
52
|
-
},
|
|
53
|
-
] }) }), _jsx("div", { id: APP_SIDEBAR_CONTAINER_ID })] }));
|
|
54
|
-
}
|
|
55
|
-
return (_jsxs("div", { className: "grid grid-rows-[auto_1fr] grid-cols-[1fr_auto] bg-background text-foreground h-screen", children: [_jsx("div", { className: "col-span-2", children: _jsx(Header, {}) }), _jsx("main", { className: "m-2 overflow-hidden", role: "main", children: _jsxs(CollapsiblePanelGroup, { autoSaveId: "app-panel", direction: "vertical", className: "gap-1 h-full", "aria-label": "Workbench panels", children: [_jsxs(CollapsiblePanel, { id: "top-panel", variant: 'tabs', defaultTab: tab.top, onTabChange: onTabChange(TabLocation.TOP), header: _jsxs(TabsList, { children: [_jsx(TabsTrigger, { value: "flow", "data-testid": "flows-link", children: _jsx(FlowTabMenuItem, {}) }), _jsxs(TabsTrigger, { value: "endpoint", "data-testid": "endpoints-link", className: "cursor-pointer", children: [_jsx(Link2, {}), "Endpoint"] })] }), children: [_jsx(TabsContent, { value: "flow", className: "h-full", asChild: true, children: _jsx(ReactFlowProvider, { children: _jsx(FlowPage, {}) }) }), _jsx(TabsContent, { value: "endpoint", asChild: true, children: _jsx(EndpointsPage, {}) })] }), _jsxs(CollapsiblePanel, { id: "bottom-panel", variant: 'tabs', defaultTab: tab.bottom, onTabChange: onTabChange(TabLocation.BOTTOM), header: _jsxs(TabsList, { children: [_jsxs(TabsTrigger, { value: "tracing", "data-testid": "traces-link", className: "cursor-pointer", children: [_jsx(GanttChart, {}), " Tracing"] }), _jsxs(TabsTrigger, { value: "logs", "data-testid": "logs-link", className: "cursor-pointer", children: [_jsx(LogsIcon, {}), "Logs"] }), _jsxs(TabsTrigger, { value: "states", "data-testid": "states-link", className: "cursor-pointer", children: [_jsx(File, {}), "States"] })] }), children: [_jsx(TabsContent, { value: "tracing", className: "max-h-fit", asChild: true, children: _jsx(TracesPage, {}) }), _jsx(TabsContent, { value: "logs", asChild: true, children: _jsx(LogsPage, {}) }), _jsx(TabsContent, { value: "states", asChild: true, children: _jsx(StatesPage, {}) })] })] }) }), _jsx("div", { id: APP_SIDEBAR_CONTAINER_ID })] }));
|
|
53
|
+
const viewMode = useMemo(getViewModeFromURL, []);
|
|
54
|
+
const ViewComponent = viewMode === 'project' ? ProjectViewMode : SystemViewMode;
|
|
55
|
+
return _jsx(ViewComponent, {});
|
|
56
56
|
};
|
|
@@ -2,6 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { useFlowStore } from '@/stores/use-flow-store';
|
|
3
3
|
import { useStreamItem } from '@motiadev/stream-client-react';
|
|
4
4
|
import { FlowView } from './flow-view';
|
|
5
|
+
import { ReactFlowProvider } from '@xyflow/react';
|
|
5
6
|
export const FlowPage = () => {
|
|
6
7
|
const selectedFlowId = useFlowStore((state) => state.selectedFlowId);
|
|
7
8
|
const { data: flow } = useStreamItem({
|
|
@@ -16,5 +17,5 @@ export const FlowPage = () => {
|
|
|
16
17
|
});
|
|
17
18
|
if (!flow || flow.error)
|
|
18
19
|
return (_jsx("div", { className: "w-full h-full bg-background flex flex-col items-center justify-center", children: _jsx("p", { children: flow?.error }) }));
|
|
19
|
-
return _jsx(FlowView, { flow: flow, flowConfig: flowConfig });
|
|
20
|
+
return (_jsx(ReactFlowProvider, { children: _jsx(FlowView, { flow: flow, flowConfig: flowConfig }) }));
|
|
20
21
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const LogsTabLabel: import("react").MemoExoticComponent<() => import("react/jsx-runtime").JSX.Element>;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { LogsIcon } from 'lucide-react';
|
|
3
|
+
import { memo } from 'react';
|
|
4
|
+
export const LogsTabLabel = memo(() => (_jsxs(_Fragment, { children: [_jsx(LogsIcon, { "aria-hidden": "true" }), _jsx("span", { children: "Logs" })] })));
|
|
5
|
+
LogsTabLabel.displayName = 'LogsTabLabel';
|
|
@@ -6,5 +6,5 @@ import { memo } from 'react';
|
|
|
6
6
|
import { EventIcon } from '../events/event-icon';
|
|
7
7
|
import { TraceEvent } from '../events/trace-event';
|
|
8
8
|
export const TraceItemDetail = memo(({ trace, onClose }) => {
|
|
9
|
-
return (_jsxs(Sidebar, { onClose: onClose, title: "Trace Details", subtitle: `Viewing details from step ${trace.name}`, actions: [{ icon: _jsx(X, {}), onClick: onClose, label: 'Close' }], children: [_jsxs("div", { className: "px-2 w-[800px] overflow-auto", children: [_jsxs("div", { className: "flex items-center gap-4 text-sm text-muted-foreground mb-4", children: [trace.endTime && _jsxs("span", { children: ["Duration: ", formatDuration(trace.endTime - trace.startTime)] }), _jsx("div", { className: "bg-blue-500 font-bold text-xs px-[4px] py-[2px] rounded-sm text-blue-100", children: trace.entryPoint.type }), trace.correlationId && _jsxs(Badge, { variant: "outline", children: ["Correlated: ", trace.correlationId] })] }), _jsx("div", { className: "pl-6 border-l-1 border-gray-500/40 font-mono text-xs flex flex-col gap-3", children: trace.events.map((event, index) => (_jsxs("div", { className: "relative", children: [_jsx("div", { className: "absolute -left-[26px] top-[8px] w-1 h-1 rounded-full bg-emerald-500 outline outline-2 outline-emerald-500/50" }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(EventIcon, { event: event }), _jsxs("span", { className: "text-sm font-mono text-muted-foreground", children: ["+", Math.floor(event.timestamp - trace.startTime)
|
|
9
|
+
return (_jsxs(Sidebar, { onClose: onClose, title: "Trace Details", subtitle: `Viewing details from step ${trace.name}`, actions: [{ icon: _jsx(X, {}), onClick: onClose, label: 'Close' }], children: [_jsxs("div", { className: "px-2 w-[800px] overflow-auto", children: [_jsxs("div", { className: "flex items-center gap-4 text-sm text-muted-foreground mb-4", children: [trace.endTime && _jsxs("span", { children: ["Duration: ", formatDuration(trace.endTime - trace.startTime)] }), _jsx("div", { className: "bg-blue-500 font-bold text-xs px-[4px] py-[2px] rounded-sm text-blue-100", children: trace.entryPoint.type }), trace.correlationId && _jsxs(Badge, { variant: "outline", children: ["Correlated: ", trace.correlationId] })] }), _jsx("div", { className: "pl-6 border-l-1 border-gray-500/40 font-mono text-xs flex flex-col gap-3", children: trace.events.map((event, index) => (_jsxs("div", { className: "relative", children: [_jsx("div", { className: "absolute -left-[26px] top-[8px] w-1 h-1 rounded-full bg-emerald-500 outline outline-2 outline-emerald-500/50" }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(EventIcon, { event: event }), _jsxs("span", { className: "text-sm font-mono text-muted-foreground", children: ["+", formatDuration(Math.floor(event.timestamp - trace.startTime))] }), _jsx(TraceEvent, { event: event })] })] }, index))) })] }), trace.error && (_jsxs("div", { className: "p-4 bg-red-800/10", children: [_jsx("div", { className: "text-sm text-red-800 dark:text-red-400 font-semibold", children: trace.error.message }), _jsx("div", { className: "text-sm text-red-800 dark:text-red-400 pl-4", children: trace.error.stack })] }))] }));
|
|
10
10
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const TracingTabLabel: import("react").MemoExoticComponent<() => import("react/jsx-runtime").JSX.Element>;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { GanttChart } from 'lucide-react';
|
|
3
|
+
import { memo } from 'react';
|
|
4
|
+
export const TracingTabLabel = memo(() => (_jsxs(_Fragment, { children: [_jsx(GanttChart, { "aria-hidden": "true" }), _jsx("span", { children: "Tracing" })] })));
|
|
5
|
+
TracingTabLabel.displayName = 'TracingTabLabel';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useGlobalStore } from '@/stores/use-global-store';
|
|
3
|
+
import { formatDuration } from '@/lib/utils';
|
|
3
4
|
import { useStreamGroup, useStreamItem } from '@motiadev/stream-client-react';
|
|
4
5
|
import { Button } from '@motiadev/ui';
|
|
5
6
|
import { Minus, Plus } from 'lucide-react';
|
|
@@ -25,5 +26,5 @@ export const TraceTimeline = memo(({ groupId }) => {
|
|
|
25
26
|
};
|
|
26
27
|
if (!group)
|
|
27
28
|
return null;
|
|
28
|
-
return (_jsxs(_Fragment, { children: [_jsx("div", { className: "flex flex-col flex-1 overflow-x-auto h-full relative", children: _jsxs("div", { className: "flex flex-col items-center min-w-full sticky top-0", style: { width: `${zoom * 1000}px` }, children: [_jsxs("div", { className: "flex flex-1 w-full sticky top-0 bg-background z-10", children: [_jsxs("div", { className: "w-full min-h-[37px] h-[37px] min-w-[200px] max-w-[200px] flex items-center justify-center gap-2 sticky left-0 top-0 bg-card backdrop-blur-[4px] backdrop-filter", children: [_jsx(Button, { variant: "icon", size: "sm", className: "px-2", onClick: zoomMinus, children: _jsx(Minus, { className: "w-4 h-4 cursor-pointer" }) }), _jsxs("span", { className: "text-sm font-bold text-muted-foreground", children: [Math.floor(zoom * 100), "%"] }), _jsx(Button, { variant: "icon", size: "sm", className: "px-2", onClick: () => setZoom(zoom + 0.1), children: _jsx(Plus, { className: "w-4 h-4 cursor-pointer" }) })] }), _jsxs("div", { className: "flex justify-between font-mono p-2 w-full text-xs text-muted-foreground bg-card", children: [_jsx("span", { children:
|
|
29
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { className: "flex flex-col flex-1 overflow-x-auto h-full relative", children: _jsxs("div", { className: "flex flex-col items-center min-w-full sticky top-0", style: { width: `${zoom * 1000}px` }, children: [_jsxs("div", { className: "flex flex-1 w-full sticky top-0 bg-background z-10", children: [_jsxs("div", { className: "w-full min-h-[37px] h-[37px] min-w-[200px] max-w-[200px] flex items-center justify-center gap-2 sticky left-0 top-0 bg-card backdrop-blur-[4px] backdrop-filter", children: [_jsx(Button, { variant: "icon", size: "sm", className: "px-2", onClick: zoomMinus, children: _jsx(Minus, { className: "w-4 h-4 cursor-pointer" }) }), _jsxs("span", { className: "text-sm font-bold text-muted-foreground", children: [Math.floor(zoom * 100), "%"] }), _jsx(Button, { variant: "icon", size: "sm", className: "px-2", onClick: () => setZoom(zoom + 0.1), children: _jsx(Plus, { className: "w-4 h-4 cursor-pointer" }) })] }), _jsxs("div", { className: "flex justify-between font-mono p-2 w-full text-xs text-muted-foreground bg-card", children: [_jsx("span", { children: formatDuration(0) }), _jsx("span", { children: formatDuration(Math.floor((endTime - group.startTime) * 0.25)) }), _jsx("span", { children: formatDuration(Math.floor((endTime - group.startTime) * 0.5)) }), _jsx("span", { children: formatDuration(Math.floor((endTime - group.startTime) * 0.75)) }), _jsx("span", { children: formatDuration(Math.floor(endTime - group.startTime)) }), _jsxs("div", { className: "absolute bottom-[-4px] w-full flex justify-between", children: [_jsx("span", { className: "w-[1px] h-full bg-blue-500" }), _jsx("span", { className: "w-[1px] h-full bg-blue-500" }), _jsx("span", { className: "w-[1px] h-full bg-blue-500" }), _jsx("span", { className: "w-[1px] h-full bg-blue-500" }), _jsx("span", { className: "w-[1px] h-full bg-blue-500" })] })] })] }), _jsx("div", { className: "flex flex-col w-full h-full", children: data?.map((trace) => (_jsx(TraceItem, { trace: trace, group: group, groupEndTime: endTime, onExpand: selectTraceId }, trace.id))) })] }) }), selectedTrace && _jsx(TraceItemDetail, { trace: selectedTrace, onClose: () => selectTraceId(undefined) })] }));
|
|
29
30
|
});
|
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { formatDuration } from '@/lib/utils';
|
|
2
3
|
import { cn } from '@motiadev/ui';
|
|
3
4
|
import { formatDistanceToNow } from 'date-fns';
|
|
4
5
|
import { memo } from 'react';
|
|
5
6
|
import { TraceStatusBadge } from './trace-status';
|
|
6
7
|
export const TracesGroups = memo(({ groups, selectedGroupId, onGroupSelect }) => {
|
|
7
|
-
const formatDuration = (duration) => {
|
|
8
|
-
if (!duration)
|
|
9
|
-
return 'N/A';
|
|
10
|
-
if (duration < 1000)
|
|
11
|
-
return `${duration}ms`;
|
|
12
|
-
return `${(duration / 1000).toFixed(1)}s`;
|
|
13
|
-
};
|
|
14
8
|
return (_jsx("div", { className: "overflow-auto", children: groups.length > 0 && (_jsx("div", { children: [...groups].reverse().map((group) => (_jsx("div", { "data-testid": `trace-${group.id}`, className: cn('motia-trace-group cursor-pointer transition-colors', selectedGroupId === group.id ? 'bg-muted-foreground/10' : 'hover:bg-muted/70'), onClick: () => onGroupSelect(group), children: _jsxs("div", { className: "p-3 flex flex-col gap-1", children: [_jsxs("div", { className: "flex flex-row justify-between items-center gap-2", children: [_jsx("span", { className: "font-semibold text-lg", children: group.name }), _jsx(TraceStatusBadge, { status: group.status, duration: group.endTime ? formatDuration(group.endTime - group.startTime) : undefined })] }), _jsxs("div", { className: "text-xs text-muted-foreground space-y-1", children: [_jsxs("div", { className: "flex justify-between", children: [_jsx("div", { "data-testid": "trace-id", className: "text-xs text-muted-foreground font-mono tracking-[1px]", children: group.id }), _jsxs("span", { children: [group.metadata.totalSteps, " steps"] })] }), _jsxs("div", { className: "flex justify-between", children: [formatDistanceToNow(group.startTime), " ago"] }), group.metadata.activeSteps > 0 && (_jsxs("div", { className: "text-blue-600", children: [group.metadata.activeSteps, " active"] }))] })] }) }, group.id))) })) }));
|
|
15
9
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const StatesTabLabel: import("react").MemoExoticComponent<() => import("react/jsx-runtime").JSX.Element>;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { memo } from 'react';
|
|
3
|
+
import { File } from 'lucide-react';
|
|
4
|
+
export const StatesTabLabel = memo(() => (_jsxs(_Fragment, { children: [_jsx(File, { "aria-hidden": "true" }), _jsx("span", { children: "States" })] })));
|
|
5
|
+
StatesTabLabel.displayName = 'StatesTabLabel';
|
package/dist/src/index.css
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { formatDuration } from '../utils';
|
|
2
|
+
describe('formatDuration', () => {
|
|
3
|
+
describe('milliseconds', () => {
|
|
4
|
+
it('should format values under 1 second as milliseconds', () => {
|
|
5
|
+
expect(formatDuration(0)).toBe('0ms');
|
|
6
|
+
expect(formatDuration(1)).toBe('1ms');
|
|
7
|
+
expect(formatDuration(250)).toBe('250ms');
|
|
8
|
+
expect(formatDuration(500)).toBe('500ms');
|
|
9
|
+
expect(formatDuration(999)).toBe('999ms');
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
describe('seconds', () => {
|
|
13
|
+
it('should format values between 1 second and 1 minute as seconds with 1 decimal', () => {
|
|
14
|
+
expect(formatDuration(1000)).toBe('1.0s');
|
|
15
|
+
expect(formatDuration(1500)).toBe('1.5s');
|
|
16
|
+
expect(formatDuration(15000)).toBe('15.0s');
|
|
17
|
+
expect(formatDuration(45500)).toBe('45.5s');
|
|
18
|
+
expect(formatDuration(59999)).toBe('60.0s');
|
|
19
|
+
});
|
|
20
|
+
it('should round to 1 decimal place', () => {
|
|
21
|
+
expect(formatDuration(1234)).toBe('1.2s');
|
|
22
|
+
expect(formatDuration(1567)).toBe('1.6s');
|
|
23
|
+
expect(formatDuration(12345)).toBe('12.3s');
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
describe('minutes', () => {
|
|
27
|
+
it('should format values between 1 minute and 1 hour as minutes with 1 decimal', () => {
|
|
28
|
+
expect(formatDuration(60000)).toBe('1.0min');
|
|
29
|
+
expect(formatDuration(90000)).toBe('1.5min');
|
|
30
|
+
expect(formatDuration(300000)).toBe('5.0min');
|
|
31
|
+
expect(formatDuration(1800000)).toBe('30.0min');
|
|
32
|
+
expect(formatDuration(3599999)).toBe('60.0min');
|
|
33
|
+
});
|
|
34
|
+
it('should round to 1 decimal place', () => {
|
|
35
|
+
expect(formatDuration(123456)).toBe('2.1min');
|
|
36
|
+
expect(formatDuration(567890)).toBe('9.5min');
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
describe('hours', () => {
|
|
40
|
+
it('should format values 1 hour or more as hours with 1 decimal', () => {
|
|
41
|
+
expect(formatDuration(3600000)).toBe('1.0h');
|
|
42
|
+
expect(formatDuration(5400000)).toBe('1.5h');
|
|
43
|
+
expect(formatDuration(7200000)).toBe('2.0h');
|
|
44
|
+
expect(formatDuration(36000000)).toBe('10.0h');
|
|
45
|
+
});
|
|
46
|
+
it('should round to 1 decimal place', () => {
|
|
47
|
+
expect(formatDuration(3661000)).toBe('1.0h');
|
|
48
|
+
expect(formatDuration(5432100)).toBe('1.5h');
|
|
49
|
+
expect(formatDuration(9000000)).toBe('2.5h');
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
describe('edge cases', () => {
|
|
53
|
+
it('should return "N/A" for undefined', () => {
|
|
54
|
+
expect(formatDuration(undefined)).toBe('N/A');
|
|
55
|
+
});
|
|
56
|
+
it('should return "N/A" for null', () => {
|
|
57
|
+
expect(formatDuration(null)).toBe('N/A');
|
|
58
|
+
});
|
|
59
|
+
it('should return "N/A" for 0 when treated as falsy', () => {
|
|
60
|
+
expect(formatDuration(0)).toBe('0ms');
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
describe('boundary values', () => {
|
|
64
|
+
it('should correctly handle millisecond-second boundary (999ms vs 1.0s)', () => {
|
|
65
|
+
expect(formatDuration(999)).toBe('999ms');
|
|
66
|
+
expect(formatDuration(1000)).toBe('1.0s');
|
|
67
|
+
});
|
|
68
|
+
it('should correctly handle second-minute boundary (59.9s vs 1.0min)', () => {
|
|
69
|
+
expect(formatDuration(59999)).toBe('60.0s');
|
|
70
|
+
expect(formatDuration(60000)).toBe('1.0min');
|
|
71
|
+
});
|
|
72
|
+
it('should correctly handle minute-hour boundary (59.9min vs 1.0h)', () => {
|
|
73
|
+
expect(formatDuration(3599999)).toBe('60.0min');
|
|
74
|
+
expect(formatDuration(3600000)).toBe('1.0h');
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
describe('real-world scenarios', () => {
|
|
78
|
+
it('should format typical API response times', () => {
|
|
79
|
+
expect(formatDuration(50)).toBe('50ms');
|
|
80
|
+
expect(formatDuration(150)).toBe('150ms');
|
|
81
|
+
expect(formatDuration(2500)).toBe('2.5s');
|
|
82
|
+
});
|
|
83
|
+
it('should format typical workflow execution times', () => {
|
|
84
|
+
expect(formatDuration(5000)).toBe('5.0s');
|
|
85
|
+
expect(formatDuration(30000)).toBe('30.0s');
|
|
86
|
+
expect(formatDuration(120000)).toBe('2.0min');
|
|
87
|
+
});
|
|
88
|
+
it('should format long-running tasks', () => {
|
|
89
|
+
expect(formatDuration(600000)).toBe('10.0min');
|
|
90
|
+
expect(formatDuration(1800000)).toBe('30.0min');
|
|
91
|
+
expect(formatDuration(7200000)).toBe('2.0h');
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const registerPluginTabs: () => void;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { addAppTab, TabLocation } from '@/stores/use-app-tabs-store';
|
|
3
|
+
import { DynamicIcon, dynamicIconImports } from 'lucide-react/dynamic';
|
|
4
|
+
import { memo } from 'react';
|
|
5
|
+
import { plugins } from 'virtual:motia-plugins';
|
|
6
|
+
import { isValidTabLocation } from './utils';
|
|
7
|
+
export const registerPluginTabs = () => {
|
|
8
|
+
if (!Array.isArray(plugins)) {
|
|
9
|
+
console.warn('[Motia] Invalid plugins configuration: expected array');
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
plugins.forEach((plugin, index) => {
|
|
13
|
+
try {
|
|
14
|
+
if (!plugin.label) {
|
|
15
|
+
console.warn(`[Motia] Plugin at index ${index} missing label, skipping`);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (!plugin.component) {
|
|
19
|
+
console.warn(`[Motia] Plugin "${plugin.label}" missing component, skipping`);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const position = plugin.position || 'top';
|
|
23
|
+
if (!isValidTabLocation(position)) {
|
|
24
|
+
console.warn(`[Motia] Plugin "${plugin.label}" has invalid position "${position}", defaulting to "top"`);
|
|
25
|
+
}
|
|
26
|
+
const tabLocation = isValidTabLocation(position) ? position : TabLocation.TOP;
|
|
27
|
+
const PluginTabLabel = memo(() => {
|
|
28
|
+
const hasIcon = Object.keys(dynamicIconImports).includes(plugin.labelIcon);
|
|
29
|
+
const iconName = hasIcon ? plugin.labelIcon : 'toy-brick';
|
|
30
|
+
if (!hasIcon) {
|
|
31
|
+
console.warn(`[Motia] Plugin "${plugin.label}" has invalid icon "${plugin.labelIcon}", defaulting to "toy-brick"`);
|
|
32
|
+
}
|
|
33
|
+
return (_jsxs(_Fragment, { children: [_jsx(DynamicIcon, { name: iconName }), _jsx("span", { children: plugin.label })] }));
|
|
34
|
+
});
|
|
35
|
+
PluginTabLabel.displayName = `${plugin.label}TabLabel`;
|
|
36
|
+
const PluginContent = memo(() => {
|
|
37
|
+
const Component = plugin.component;
|
|
38
|
+
const props = plugin.props || {};
|
|
39
|
+
if (!Component) {
|
|
40
|
+
return _jsx("div", { children: "Error: Plugin component not found" });
|
|
41
|
+
}
|
|
42
|
+
return _jsx(Component, { ...props });
|
|
43
|
+
});
|
|
44
|
+
PluginContent.displayName = `${plugin.label}Content`;
|
|
45
|
+
addAppTab(tabLocation, {
|
|
46
|
+
id: plugin.label,
|
|
47
|
+
tabLabel: PluginTabLabel,
|
|
48
|
+
content: PluginContent,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
console.error(`[Motia] Error registering plugin "${plugin.label}":`, error);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
};
|
package/dist/src/lib/utils.d.ts
CHANGED
|
@@ -1,2 +1,7 @@
|
|
|
1
|
+
import { TabLocation } from '@/stores/use-app-tabs-store';
|
|
1
2
|
export declare const formatDuration: (duration?: number) => string;
|
|
2
3
|
export declare const formatTimestamp: (time: number) => string;
|
|
4
|
+
export type ViewMode = 'project' | 'system';
|
|
5
|
+
export declare const DEFAULT_VIEW_MODE: ViewMode;
|
|
6
|
+
export declare const getViewModeFromURL: () => ViewMode;
|
|
7
|
+
export declare const isValidTabLocation: (position: string) => position is TabLocation;
|
package/dist/src/lib/utils.js
CHANGED
|
@@ -1,11 +1,34 @@
|
|
|
1
|
+
import { TabLocation } from '@/stores/use-app-tabs-store';
|
|
1
2
|
export const formatDuration = (duration) => {
|
|
2
|
-
if (
|
|
3
|
+
if (duration === undefined || duration === null)
|
|
3
4
|
return 'N/A';
|
|
4
5
|
if (duration < 1000)
|
|
5
6
|
return `${duration}ms`;
|
|
6
|
-
|
|
7
|
+
if (duration < 60000)
|
|
8
|
+
return `${(duration / 1000).toFixed(1)}s`;
|
|
9
|
+
if (duration < 3600000)
|
|
10
|
+
return `${(duration / 60000).toFixed(1)}min`;
|
|
11
|
+
return `${(duration / 3600000).toFixed(1)}h`;
|
|
7
12
|
};
|
|
8
13
|
export const formatTimestamp = (time) => {
|
|
9
14
|
const date = new Date(Number(time));
|
|
10
15
|
return `${date.toLocaleDateString('en-US', { year: undefined, month: 'short', day: '2-digit' })}, ${date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', second: '2-digit', hourCycle: 'h24' })}.${date.getMilliseconds().toString().padStart(3, '0')}`;
|
|
11
16
|
};
|
|
17
|
+
export const DEFAULT_VIEW_MODE = 'system';
|
|
18
|
+
export const getViewModeFromURL = () => {
|
|
19
|
+
try {
|
|
20
|
+
const url = new URL(window.location.href);
|
|
21
|
+
const viewMode = url.searchParams.get('view-mode');
|
|
22
|
+
if (viewMode === 'project' || viewMode === 'system') {
|
|
23
|
+
return viewMode;
|
|
24
|
+
}
|
|
25
|
+
return DEFAULT_VIEW_MODE;
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
console.error('[Motia] Error parsing URL:', error);
|
|
29
|
+
return DEFAULT_VIEW_MODE;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
export const isValidTabLocation = (position) => {
|
|
33
|
+
return Object.values(TabLocation).includes(position);
|
|
34
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const ProjectViewMode: () => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { TabLocation, useAppTabsStore } from './stores/use-app-tabs-store';
|
|
3
|
+
import { APP_SIDEBAR_CONTAINER_ID, Panel } from '@motiadev/ui';
|
|
4
|
+
import { useShallow } from 'zustand/react/shallow';
|
|
5
|
+
const topTabs = (state) => state.tabs[TabLocation.TOP];
|
|
6
|
+
export const ProjectViewMode = () => {
|
|
7
|
+
const tabs = useAppTabsStore(useShallow(topTabs));
|
|
8
|
+
return (_jsxs("div", { className: "grid grid-rows-1 grid-cols-[1fr_auto] bg-background text-foreground h-screen ", children: [_jsx("main", { className: "m-2 overflow-hidden", role: "main", children: _jsx(Panel, { contentClassName: 'p-0', tabs: tabs.map((tab) => {
|
|
9
|
+
const Element = tab.content;
|
|
10
|
+
const LabelComponent = tab.tabLabel;
|
|
11
|
+
return {
|
|
12
|
+
label: tab.id,
|
|
13
|
+
labelComponent: _jsx(LabelComponent, {}),
|
|
14
|
+
content: _jsx(Element, {}),
|
|
15
|
+
'data-testid': tab.id,
|
|
16
|
+
};
|
|
17
|
+
}) }) }), _jsx("div", { id: APP_SIDEBAR_CONTAINER_ID })] }));
|
|
18
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare enum TabLocation {
|
|
2
|
+
TOP = "top",
|
|
3
|
+
BOTTOM = "bottom"
|
|
4
|
+
}
|
|
5
|
+
export type AppTab = {
|
|
6
|
+
id: string;
|
|
7
|
+
tabLabel: React.ElementType;
|
|
8
|
+
content: React.ElementType;
|
|
9
|
+
};
|
|
10
|
+
export interface AppTabsState {
|
|
11
|
+
tabs: Record<TabLocation, AppTab[]>;
|
|
12
|
+
addTab: (position: TabLocation, tab: AppTab) => void;
|
|
13
|
+
setTabs: (position: TabLocation, tabs: AppTab[]) => void;
|
|
14
|
+
removeTab: (position: TabLocation, id: string) => void;
|
|
15
|
+
}
|
|
16
|
+
export declare const useAppTabsStore: import("zustand").UseBoundStore<import("zustand").StoreApi<AppTabsState>>;
|
|
17
|
+
export declare const setAppTabs: (position: TabLocation, tabs: AppTab[]) => void;
|
|
18
|
+
export declare const addAppTab: (position: TabLocation, tab: AppTab) => void;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { create } from 'zustand';
|
|
2
|
+
export var TabLocation;
|
|
3
|
+
(function (TabLocation) {
|
|
4
|
+
TabLocation["TOP"] = "top";
|
|
5
|
+
TabLocation["BOTTOM"] = "bottom";
|
|
6
|
+
})(TabLocation || (TabLocation = {}));
|
|
7
|
+
const defaultTabs = {
|
|
8
|
+
[TabLocation.TOP]: [],
|
|
9
|
+
[TabLocation.BOTTOM]: [],
|
|
10
|
+
};
|
|
11
|
+
export const useAppTabsStore = create((set) => ({
|
|
12
|
+
tabs: defaultTabs,
|
|
13
|
+
addTab: (position, tab) => set((state) => ({
|
|
14
|
+
tabs: {
|
|
15
|
+
...state.tabs,
|
|
16
|
+
[position]: [...state.tabs[position], tab],
|
|
17
|
+
},
|
|
18
|
+
})),
|
|
19
|
+
setTabs: (position, tabs) => set((state) => ({
|
|
20
|
+
tabs: {
|
|
21
|
+
...state.tabs,
|
|
22
|
+
[position]: tabs,
|
|
23
|
+
},
|
|
24
|
+
})),
|
|
25
|
+
removeTab: (position, id) => set((state) => ({
|
|
26
|
+
tabs: {
|
|
27
|
+
...state.tabs,
|
|
28
|
+
[position]: state.tabs[position].filter((tab) => tab.id !== id),
|
|
29
|
+
},
|
|
30
|
+
})),
|
|
31
|
+
}));
|
|
32
|
+
export const setAppTabs = (position, tabs) => {
|
|
33
|
+
useAppTabsStore.getState().setTabs(position, tabs);
|
|
34
|
+
};
|
|
35
|
+
export const addAppTab = (position, tab) => {
|
|
36
|
+
useAppTabsStore.getState().addTab(position, tab);
|
|
37
|
+
};
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { create } from
|
|
2
|
-
import { createJSONStorage, persist } from
|
|
1
|
+
import { create } from 'zustand';
|
|
2
|
+
import { createJSONStorage, persist } from 'zustand/middleware';
|
|
3
3
|
export const useTabsStore = create(persist((set) => ({
|
|
4
4
|
tab: {
|
|
5
|
-
top:
|
|
6
|
-
bottom:
|
|
5
|
+
top: 'flow',
|
|
6
|
+
bottom: 'tracing',
|
|
7
|
+
},
|
|
8
|
+
setTopTab: (tab) => {
|
|
9
|
+
set((state) => ({ tab: { ...state.tab, top: tab } }));
|
|
7
10
|
},
|
|
8
|
-
setTopTab: (tab) => set((state) => ({ tab: { ...state.tab, top: tab } })),
|
|
9
11
|
setBottomTab: (tab) => set((state) => ({ tab: { ...state.tab, bottom: tab } })),
|
|
10
12
|
}), {
|
|
11
13
|
name: 'motia-tabs-storage',
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const SystemViewMode: () => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { APP_SIDEBAR_CONTAINER_ID, CollapsiblePanel, CollapsiblePanelGroup, TabsContent, TabsList, TabsTrigger, } from '@motiadev/ui';
|
|
3
|
+
import { useCallback, useMemo } from 'react';
|
|
4
|
+
import { useShallow } from 'zustand/react/shallow';
|
|
5
|
+
import { Header } from './components/header/header';
|
|
6
|
+
import { analytics } from './lib/analytics';
|
|
7
|
+
import { TabLocation, useAppTabsStore } from './stores/use-app-tabs-store';
|
|
8
|
+
import { useTabsStore } from './stores/use-tabs-store';
|
|
9
|
+
const topTabs = (state) => state.tabs;
|
|
10
|
+
export const SystemViewMode = () => {
|
|
11
|
+
const tab = useTabsStore((state) => state.tab);
|
|
12
|
+
const setTopTab = useTabsStore((state) => state.setTopTab);
|
|
13
|
+
const setBottomTab = useTabsStore((state) => state.setBottomTab);
|
|
14
|
+
const tabs = useAppTabsStore(useShallow(topTabs));
|
|
15
|
+
const tabChangeCallbacks = useMemo(() => ({
|
|
16
|
+
[TabLocation.TOP]: setTopTab,
|
|
17
|
+
[TabLocation.BOTTOM]: setBottomTab,
|
|
18
|
+
}), [setTopTab, setBottomTab]);
|
|
19
|
+
const onTabChange = useCallback((location) => (newTab) => {
|
|
20
|
+
analytics.track(`${location} tab changed`, { [`new.${location}`]: newTab, tab });
|
|
21
|
+
tabChangeCallbacks[location](newTab);
|
|
22
|
+
}, [tabChangeCallbacks, tab]);
|
|
23
|
+
return (_jsxs("div", { className: "grid grid-rows-[auto_1fr] grid-cols-[1fr_auto] bg-background text-foreground h-screen", children: [_jsx("div", { className: "col-span-2", children: _jsx(Header, {}) }), _jsx("main", { className: "m-2 overflow-hidden", role: "main", children: _jsxs(CollapsiblePanelGroup, { autoSaveId: "app-panel", direction: "vertical", className: "gap-1 h-full", "aria-label": "Workbench panels", children: [_jsx(CollapsiblePanel, { id: "top-panel", variant: 'tabs', defaultTab: tab.top, onTabChange: onTabChange(TabLocation.TOP), header: _jsx(TabsList, { children: tabs[TabLocation.TOP].map((tab) => {
|
|
24
|
+
const LabelComponent = tab.tabLabel;
|
|
25
|
+
return (_jsx(TabsTrigger, { value: tab.id, "data-testid": `${tab.id.toLowerCase()}-link`, className: "cursor-pointer", children: _jsx(LabelComponent, {}) }, tab.id));
|
|
26
|
+
}) }), children: tabs[TabLocation.TOP].map((tab) => {
|
|
27
|
+
const Element = tab.content;
|
|
28
|
+
return (_jsx(TabsContent, { value: tab.id, className: "h-full", children: _jsx(Element, {}) }, tab.id));
|
|
29
|
+
}) }), _jsx(CollapsiblePanel, { id: "bottom-panel", variant: 'tabs', defaultTab: tab.bottom, onTabChange: onTabChange(TabLocation.BOTTOM), header: _jsx(TabsList, { children: tabs[TabLocation.BOTTOM].map((tab) => {
|
|
30
|
+
const LabelComponent = tab.tabLabel;
|
|
31
|
+
return (_jsx(TabsTrigger, { value: tab.id, "data-testid": `${tab.id.toLowerCase()}-link`, className: "cursor-pointer", children: _jsx(LabelComponent, {}) }, tab.id));
|
|
32
|
+
}) }), children: tabs[TabLocation.BOTTOM].map((tab) => {
|
|
33
|
+
const Element = tab.content;
|
|
34
|
+
return (_jsx(TabsContent, { value: tab.id, className: "h-full", children: _jsx(Element, {}) }, tab.id));
|
|
35
|
+
}) })] }) }), _jsx("div", { id: APP_SIDEBAR_CONTAINER_ID })] }));
|
|
36
|
+
};
|