@motiadev/workbench 0.14.0-beta.165-285707 → 0.15.0-beta.165
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/index.d.ts +189 -10
- package/dist/index.html +1 -1
- package/dist/index.js +1065 -7
- package/dist/middleware.d.ts +66 -8
- package/dist/middleware.js +694 -86
- package/dist/motia-plugin/__tests__/generator.test.ts +129 -0
- package/dist/motia-plugin/__tests__/resolver.test.ts +82 -0
- package/dist/motia-plugin/__tests__/validator.test.ts +71 -0
- package/dist/motia-plugin/{generator.js → generator.ts} +37 -35
- package/dist/motia-plugin/hmr.ts +123 -0
- package/dist/motia-plugin/index.ts +183 -0
- package/dist/motia-plugin/{resolver.d.ts → resolver.ts} +38 -5
- package/dist/motia-plugin/types.ts +198 -0
- package/dist/motia-plugin/{utils.d.ts → utils.ts} +17 -4
- package/dist/motia-plugin/validator.ts +197 -0
- package/dist/src/App.tsx +41 -0
- package/dist/src/components/NotFoundPage.tsx +11 -0
- package/dist/src/components/bottom-panel.tsx +39 -0
- package/dist/src/components/flow/base-edge.tsx +61 -0
- package/dist/src/components/flow/flow-loader.tsx +3 -0
- package/dist/src/components/flow/flow-page.tsx +75 -0
- package/dist/src/components/flow/flow-tab-menu-item.tsx +52 -0
- package/dist/src/components/flow/flow-view.tsx +66 -0
- package/dist/src/components/flow/hooks/use-get-flow-state.tsx +171 -0
- package/dist/src/components/flow/hooks/use-save-workflow-config.ts +25 -0
- package/dist/src/components/flow/node-organizer.tsx +103 -0
- package/dist/src/components/flow/nodes/api-flow-node.tsx +6 -0
- package/dist/src/components/flow/nodes/cron-flow-node.tsx +6 -0
- package/dist/src/components/flow/nodes/event-flow-node.tsx +6 -0
- package/dist/src/components/flow/nodes/noop-flow-node.tsx +6 -0
- package/dist/src/components/header/deploy-button.tsx +110 -0
- package/dist/src/components/header/header.tsx +39 -0
- package/dist/src/components/root-motia.tsx +10 -0
- package/dist/src/components/top-panel.tsx +40 -0
- package/dist/src/components/tutorial/engine/tutorial-engine.ts +26 -0
- package/dist/src/components/tutorial/engine/tutorial-types.ts +26 -0
- package/dist/src/components/tutorial/engine/workbench-xpath.ts +53 -0
- package/dist/src/components/tutorial/hooks/tutorial-utils.ts +26 -0
- package/dist/src/components/tutorial/hooks/use-tutorial-engine.ts +213 -0
- package/dist/src/components/tutorial/hooks/use-tutorial.ts +14 -0
- package/dist/src/components/tutorial/tutorial-button.tsx +46 -0
- package/dist/src/components/tutorial/tutorial-step.tsx +82 -0
- package/dist/src/components/tutorial/tutorial.tsx +59 -0
- package/dist/src/components/ui/json-editor.tsx +68 -0
- package/dist/src/components/ui/table.tsx +75 -0
- package/dist/src/components/ui/theme-toggle.tsx +54 -0
- package/dist/src/components/ui/tooltip.tsx +26 -0
- package/dist/src/hooks/use-debounced.ts +22 -0
- package/dist/src/hooks/use-fetch-flows.ts +33 -0
- package/dist/src/hooks/use-mobile.ts +19 -0
- package/dist/src/hooks/use-update-handle-positions.ts +42 -0
- package/dist/src/index.css +5 -5
- package/dist/src/lib/__tests__/utils.test.ts +110 -0
- package/dist/src/lib/motia-analytics.ts +140 -0
- package/dist/src/lib/plugins.tsx +132 -0
- package/dist/src/lib/utils.ts +37 -0
- package/dist/src/main.tsx +30 -0
- package/dist/src/project-view-mode.tsx +32 -0
- package/dist/src/publicComponents/api-node.tsx +26 -0
- package/dist/src/publicComponents/base-node/base-handle.tsx +50 -0
- package/dist/src/publicComponents/base-node/base-node.tsx +114 -0
- package/dist/src/publicComponents/base-node/code-display.tsx +119 -0
- package/dist/src/publicComponents/base-node/emits.tsx +17 -0
- package/dist/src/publicComponents/base-node/feature-card.tsx +32 -0
- package/dist/src/publicComponents/base-node/language-indicator.tsx +131 -0
- package/dist/src/publicComponents/base-node/node-header.tsx +49 -0
- package/dist/src/publicComponents/base-node/node-sidebar.tsx +41 -0
- package/dist/src/publicComponents/base-node/subscribe.tsx +13 -0
- package/dist/src/publicComponents/cron-node.tsx +24 -0
- package/dist/src/publicComponents/event-node.tsx +20 -0
- package/dist/src/publicComponents/node-props.tsx +15 -0
- package/dist/src/publicComponents/noop-node.tsx +19 -0
- package/dist/src/setupTests.ts +1 -0
- package/dist/src/stores/use-app-tabs-store.ts +49 -0
- package/dist/src/stores/use-flow-store.ts +31 -0
- package/dist/src/stores/use-global-store.ts +24 -0
- package/dist/src/stores/use-motia-config-store.ts +36 -0
- package/dist/src/stores/use-tabs-store.ts +34 -0
- package/dist/src/system-view-mode.tsx +28 -0
- package/dist/src/types/endpoint.ts +12 -0
- package/dist/src/types/file.ts +7 -0
- package/dist/src/types/flow.ts +103 -0
- package/eslint.config.cjs +22 -0
- package/jest.config.cjs +68 -0
- package/package.json +53 -51
- package/dist/motia-plugin/__tests__/generator.test.d.ts +0 -1
- package/dist/motia-plugin/__tests__/generator.test.js +0 -97
- package/dist/motia-plugin/__tests__/resolver.test.d.ts +0 -1
- package/dist/motia-plugin/__tests__/resolver.test.js +0 -64
- package/dist/motia-plugin/__tests__/validator.test.d.ts +0 -1
- package/dist/motia-plugin/__tests__/validator.test.js +0 -59
- package/dist/motia-plugin/generator.d.ts +0 -78
- package/dist/motia-plugin/hmr.d.ts +0 -22
- package/dist/motia-plugin/hmr.js +0 -100
- package/dist/motia-plugin/index.d.ts +0 -3
- package/dist/motia-plugin/index.js +0 -153
- package/dist/motia-plugin/resolver.js +0 -92
- package/dist/motia-plugin/types.d.ts +0 -169
- package/dist/motia-plugin/types.js +0 -36
- package/dist/motia-plugin/utils.js +0 -75
- package/dist/motia-plugin/validator.d.ts +0 -19
- package/dist/motia-plugin/validator.js +0 -163
- package/dist/src/App.d.ts +0 -2
- package/dist/src/App.js +0 -35
- package/dist/src/components/NotFoundPage.d.ts +0 -1
- package/dist/src/components/NotFoundPage.js +0 -3
- package/dist/src/components/bottom-panel.d.ts +0 -1
- package/dist/src/components/bottom-panel.js +0 -15
- package/dist/src/components/flow/base-edge.d.ts +0 -3
- package/dist/src/components/flow/base-edge.js +0 -39
- package/dist/src/components/flow/flow-loader.d.ts +0 -1
- package/dist/src/components/flow/flow-loader.js +0 -4
- package/dist/src/components/flow/flow-page.d.ts +0 -1
- package/dist/src/components/flow/flow-page.js +0 -25
- package/dist/src/components/flow/flow-tab-menu-item.d.ts +0 -1
- package/dist/src/components/flow/flow-tab-menu-item.js +0 -18
- package/dist/src/components/flow/flow-view.d.ts +0 -12
- package/dist/src/components/flow/flow-view.js +0 -22
- package/dist/src/components/flow/hooks/use-get-flow-state.d.ts +0 -10
- package/dist/src/components/flow/hooks/use-get-flow-state.js +0 -133
- package/dist/src/components/flow/hooks/use-save-workflow-config.d.ts +0 -2
- package/dist/src/components/flow/hooks/use-save-workflow-config.js +0 -22
- package/dist/src/components/flow/node-organizer.d.ts +0 -10
- package/dist/src/components/flow/node-organizer.js +0 -82
- package/dist/src/components/flow/nodes/api-flow-node.d.ts +0 -2
- package/dist/src/components/flow/nodes/api-flow-node.js +0 -5
- package/dist/src/components/flow/nodes/cron-flow-node.d.ts +0 -2
- package/dist/src/components/flow/nodes/cron-flow-node.js +0 -5
- package/dist/src/components/flow/nodes/event-flow-node.d.ts +0 -2
- package/dist/src/components/flow/nodes/event-flow-node.js +0 -5
- package/dist/src/components/flow/nodes/noop-flow-node.d.ts +0 -2
- package/dist/src/components/flow/nodes/noop-flow-node.js +0 -5
- package/dist/src/components/header/deploy-button.d.ts +0 -1
- package/dist/src/components/header/deploy-button.js +0 -28
- package/dist/src/components/header/header.d.ts +0 -2
- package/dist/src/components/header/header.js +0 -23
- package/dist/src/components/root-motia.d.ts +0 -2
- package/dist/src/components/root-motia.js +0 -7
- package/dist/src/components/top-panel.d.ts +0 -1
- package/dist/src/components/top-panel.js +0 -15
- package/dist/src/components/tutorial/engine/tutorial-engine.d.ts +0 -12
- package/dist/src/components/tutorial/engine/tutorial-engine.js +0 -36
- package/dist/src/components/tutorial/engine/tutorial-types.d.ts +0 -22
- package/dist/src/components/tutorial/engine/tutorial-types.js +0 -1
- package/dist/src/components/tutorial/engine/workbench-xpath.d.ts +0 -45
- package/dist/src/components/tutorial/engine/workbench-xpath.js +0 -45
- package/dist/src/components/tutorial/hooks/tutorial-utils.d.ts +0 -1
- package/dist/src/components/tutorial/hooks/tutorial-utils.js +0 -17
- package/dist/src/components/tutorial/hooks/use-tutorial-engine.d.ts +0 -15
- package/dist/src/components/tutorial/hooks/use-tutorial-engine.js +0 -183
- package/dist/src/components/tutorial/hooks/use-tutorial.d.ts +0 -5
- package/dist/src/components/tutorial/hooks/use-tutorial.js +0 -10
- package/dist/src/components/tutorial/tutorial-button.d.ts +0 -2
- package/dist/src/components/tutorial/tutorial-button.js +0 -21
- package/dist/src/components/tutorial/tutorial-step.d.ts +0 -14
- package/dist/src/components/tutorial/tutorial-step.js +0 -19
- package/dist/src/components/tutorial/tutorial.d.ts +0 -2
- package/dist/src/components/tutorial/tutorial.js +0 -32
- package/dist/src/components/ui/json-editor.d.ts +0 -12
- package/dist/src/components/ui/json-editor.js +0 -35
- package/dist/src/components/ui/table.d.ts +0 -10
- package/dist/src/components/ui/table.js +0 -20
- package/dist/src/components/ui/theme-toggle.d.ts +0 -2
- package/dist/src/components/ui/theme-toggle.js +0 -19
- package/dist/src/components/ui/tooltip.d.ts +0 -6
- package/dist/src/components/ui/tooltip.js +0 -3
- package/dist/src/hooks/use-debounced.d.ts +0 -1
- package/dist/src/hooks/use-debounced.js +0 -18
- package/dist/src/hooks/use-fetch-flows.d.ts +0 -1
- package/dist/src/hooks/use-fetch-flows.js +0 -26
- package/dist/src/hooks/use-mobile.d.ts +0 -1
- package/dist/src/hooks/use-mobile.js +0 -15
- package/dist/src/hooks/use-update-handle-positions.d.ts +0 -10
- package/dist/src/hooks/use-update-handle-positions.js +0 -35
- package/dist/src/lib/__tests__/utils.test.d.ts +0 -1
- package/dist/src/lib/__tests__/utils.test.js +0 -94
- package/dist/src/lib/motia-analytics.d.ts +0 -38
- package/dist/src/lib/motia-analytics.js +0 -132
- package/dist/src/lib/plugins.d.ts +0 -2
- package/dist/src/lib/plugins.js +0 -105
- package/dist/src/lib/utils.d.ts +0 -7
- package/dist/src/lib/utils.js +0 -34
- package/dist/src/main.d.ts +0 -2
- package/dist/src/main.js +0 -17
- package/dist/src/project-view-mode.d.ts +0 -1
- package/dist/src/project-view-mode.js +0 -20
- package/dist/src/publicComponents/api-node.d.ts +0 -5
- package/dist/src/publicComponents/api-node.js +0 -5
- package/dist/src/publicComponents/base-node/base-handle.d.ts +0 -9
- package/dist/src/publicComponents/base-node/base-handle.js +0 -8
- package/dist/src/publicComponents/base-node/base-node.d.ts +0 -15
- package/dist/src/publicComponents/base-node/base-node.js +0 -30
- package/dist/src/publicComponents/base-node/code-display.d.ts +0 -9
- package/dist/src/publicComponents/base-node/code-display.js +0 -64
- package/dist/src/publicComponents/base-node/emits.d.ts +0 -5
- package/dist/src/publicComponents/base-node/emits.js +0 -5
- package/dist/src/publicComponents/base-node/feature-card.d.ts +0 -10
- package/dist/src/publicComponents/base-node/feature-card.js +0 -5
- package/dist/src/publicComponents/base-node/language-indicator.d.ts +0 -10
- package/dist/src/publicComponents/base-node/language-indicator.js +0 -29
- package/dist/src/publicComponents/base-node/node-header.d.ts +0 -13
- package/dist/src/publicComponents/base-node/node-header.js +0 -30
- package/dist/src/publicComponents/base-node/node-sidebar.d.ts +0 -14
- package/dist/src/publicComponents/base-node/node-sidebar.js +0 -9
- package/dist/src/publicComponents/base-node/subscribe.d.ts +0 -4
- package/dist/src/publicComponents/base-node/subscribe.js +0 -4
- package/dist/src/publicComponents/cron-node.d.ts +0 -4
- package/dist/src/publicComponents/cron-node.js +0 -6
- package/dist/src/publicComponents/event-node.d.ts +0 -4
- package/dist/src/publicComponents/event-node.js +0 -5
- package/dist/src/publicComponents/node-props.d.ts +0 -21
- package/dist/src/publicComponents/node-props.js +0 -1
- package/dist/src/publicComponents/noop-node.d.ts +0 -4
- package/dist/src/publicComponents/noop-node.js +0 -5
- package/dist/src/setupTests.d.ts +0 -1
- package/dist/src/setupTests.js +0 -1
- package/dist/src/stores/use-app-tabs-store.d.ts +0 -16
- package/dist/src/stores/use-app-tabs-store.js +0 -31
- package/dist/src/stores/use-flow-store.d.ts +0 -21
- package/dist/src/stores/use-flow-store.js +0 -16
- package/dist/src/stores/use-global-store.d.ts +0 -18
- package/dist/src/stores/use-global-store.js +0 -12
- package/dist/src/stores/use-motia-config-store.d.ts +0 -12
- package/dist/src/stores/use-motia-config-store.js +0 -24
- package/dist/src/stores/use-tabs-store.d.ts +0 -19
- package/dist/src/stores/use-tabs-store.js +0 -22
- package/dist/src/system-view-mode.d.ts +0 -1
- package/dist/src/system-view-mode.js +0 -10
- package/dist/src/types/endpoint.d.ts +0 -14
- package/dist/src/types/endpoint.js +0 -1
- package/dist/src/types/file.d.ts +0 -7
- package/dist/src/types/file.js +0 -1
- package/dist/src/types/flow.d.ts +0 -115
- package/dist/src/types/flow.js +0 -1
- package/dist/tsconfig.app.tsbuildinfo +0 -1
- package/dist/tsconfig.node.tsbuildinfo +0 -1
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { BackgroundEffect } from '@motiadev/ui'
|
|
2
|
+
import type React from 'react'
|
|
3
|
+
import { forwardRef, useEffect } from 'react'
|
|
4
|
+
import type { TutorialImage } from './engine/tutorial-types'
|
|
5
|
+
|
|
6
|
+
type TutorialStepProps = {
|
|
7
|
+
step: number
|
|
8
|
+
totalSteps: number
|
|
9
|
+
title: string
|
|
10
|
+
description: React.ReactNode
|
|
11
|
+
link?: string
|
|
12
|
+
image?: TutorialImage
|
|
13
|
+
onNext: () => void
|
|
14
|
+
onClose: () => void
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const TutorialStep = forwardRef<HTMLDivElement, TutorialStepProps>(
|
|
18
|
+
({ step, totalSteps, title, description, link, image, onNext, onClose }, ref) => {
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
21
|
+
if (step > 0 && e.key === 'Escape') {
|
|
22
|
+
onClose()
|
|
23
|
+
} else if (step > 0 && e.key === 'ArrowRight') {
|
|
24
|
+
onNext()
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
window.addEventListener('keydown', handleKeyDown)
|
|
28
|
+
|
|
29
|
+
return () => window.removeEventListener('keydown', handleKeyDown)
|
|
30
|
+
}, [onClose, onNext, step])
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div ref={ref} className="driver-popover ">
|
|
34
|
+
{image && (
|
|
35
|
+
<img
|
|
36
|
+
src={image.src}
|
|
37
|
+
alt="Step visual"
|
|
38
|
+
className="driver-popover-image object-cover"
|
|
39
|
+
style={{ height: image.height, width: '100%' }}
|
|
40
|
+
/>
|
|
41
|
+
)}
|
|
42
|
+
|
|
43
|
+
<div className="isolate relative">
|
|
44
|
+
<BackgroundEffect />
|
|
45
|
+
<div className="driver-popover-title">
|
|
46
|
+
<h2 className="popover-title">{title}</h2>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<div className="driver-popover-description">{description}</div>
|
|
50
|
+
|
|
51
|
+
{link && (
|
|
52
|
+
<a href={link} target="_blank" className="text-foreground text-xs font-semibold px-4 hover:underline">
|
|
53
|
+
Learn more
|
|
54
|
+
</a>
|
|
55
|
+
)}
|
|
56
|
+
|
|
57
|
+
<div className="driver-popover-footer flex items-center justify-between">
|
|
58
|
+
<div className="text-sm text-muted-foreground font-semibold">
|
|
59
|
+
{step} <span className="text-foreground">/</span> {totalSteps}
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<div className="driver-popover-navigation-btns driver-popover-navigation-btns-hint flex gap-2">
|
|
63
|
+
<button className="driver-popover-next-btn" onClick={onNext}>
|
|
64
|
+
{step < totalSteps ? 'Continue' : 'Finish'}
|
|
65
|
+
</button>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
{step < totalSteps && (
|
|
70
|
+
<div className="tutorial-opt-out-container">
|
|
71
|
+
<button className="tutorial-opt-out-button" onClick={onClose}>
|
|
72
|
+
Close
|
|
73
|
+
</button>
|
|
74
|
+
</div>
|
|
75
|
+
)}
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
)
|
|
79
|
+
},
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
TutorialStep.displayName = 'TutorialStep'
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { motiaAnalytics } from '../../lib/motia-analytics'
|
|
2
|
+
import { useTutorialEngine } from './hooks/use-tutorial-engine'
|
|
3
|
+
import { TutorialStep } from './tutorial-step'
|
|
4
|
+
import './tutorial.css'
|
|
5
|
+
|
|
6
|
+
export const Tutorial = () => {
|
|
7
|
+
const engine = useTutorialEngine()
|
|
8
|
+
|
|
9
|
+
const onNext = () => {
|
|
10
|
+
const currentStep = engine.currentStepRef.current
|
|
11
|
+
const nextStep = currentStep + 1
|
|
12
|
+
|
|
13
|
+
engine.moveStep(nextStep)
|
|
14
|
+
|
|
15
|
+
if (engine.currentStep === engine.totalSteps) {
|
|
16
|
+
motiaAnalytics.track('tutorial_completed', {
|
|
17
|
+
manualOpen: engine.manualOpenRef.current,
|
|
18
|
+
})
|
|
19
|
+
} else {
|
|
20
|
+
motiaAnalytics.track('tutorial_next_step', {
|
|
21
|
+
step: nextStep,
|
|
22
|
+
manualOpen: engine.manualOpenRef.current,
|
|
23
|
+
})
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const onClose = () => {
|
|
28
|
+
motiaAnalytics.track('tutorial_closed', {
|
|
29
|
+
step: engine.currentStepRef.current,
|
|
30
|
+
manualOpen: engine.manualOpenRef.current,
|
|
31
|
+
})
|
|
32
|
+
engine.onClose()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div>
|
|
37
|
+
{/* backdrop container */}
|
|
38
|
+
<div className="fixed inset-0 z-[9999]" />
|
|
39
|
+
|
|
40
|
+
{/* Highlighter container */}
|
|
41
|
+
<div
|
|
42
|
+
className="absolute top-5 left-5 w-full h-full rounded-lg shadow-[0_0_0_9999px_rgba(0,0,0,0.5)] z-[10000] pointer-events-none"
|
|
43
|
+
ref={engine.highlighterRef}
|
|
44
|
+
/>
|
|
45
|
+
|
|
46
|
+
<TutorialStep
|
|
47
|
+
ref={engine.ref}
|
|
48
|
+
step={engine.currentStep}
|
|
49
|
+
totalSteps={engine.totalSteps}
|
|
50
|
+
title={engine.title}
|
|
51
|
+
description={engine.description}
|
|
52
|
+
link={engine.link}
|
|
53
|
+
image={engine.image}
|
|
54
|
+
onNext={onNext}
|
|
55
|
+
onClose={onClose}
|
|
56
|
+
/>
|
|
57
|
+
</div>
|
|
58
|
+
)
|
|
59
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import Editor, { useMonaco } from '@monaco-editor/react'
|
|
2
|
+
import { useThemeStore } from '@motiadev/ui'
|
|
3
|
+
import { type FC, useEffect, useMemo } from 'react'
|
|
4
|
+
|
|
5
|
+
type JsonEditorProps = {
|
|
6
|
+
value: string
|
|
7
|
+
height?: number | string
|
|
8
|
+
schema?: Record<string, unknown>
|
|
9
|
+
onChange?: (value: string) => void
|
|
10
|
+
onValidate?: (isValid: boolean) => void
|
|
11
|
+
language?: 'json' | string
|
|
12
|
+
readOnly?: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const JsonEditor: FC<JsonEditorProps> = ({
|
|
16
|
+
value,
|
|
17
|
+
height = 300,
|
|
18
|
+
schema,
|
|
19
|
+
onChange,
|
|
20
|
+
onValidate,
|
|
21
|
+
language = 'json',
|
|
22
|
+
readOnly = false,
|
|
23
|
+
}) => {
|
|
24
|
+
const monaco = useMonaco()
|
|
25
|
+
const theme = useThemeStore((state) => state.theme)
|
|
26
|
+
const editorTheme = useMemo(() => (theme === 'dark' ? 'vs-dark' : 'light'), [theme])
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (!monaco) return
|
|
30
|
+
|
|
31
|
+
// @ts-expect-error - monaco.languages.typescript.javascriptDefaults is not typed
|
|
32
|
+
monaco.languages.typescript.javascriptDefaults.setCompilerOptions({ isolatedModules: true })
|
|
33
|
+
// @ts-expect-error - monaco.languages.json.jsonDefaults is not typed
|
|
34
|
+
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
|
|
35
|
+
schemas: schema
|
|
36
|
+
? [
|
|
37
|
+
{
|
|
38
|
+
uri: window.location.href,
|
|
39
|
+
fileMatch: ['*'],
|
|
40
|
+
schema,
|
|
41
|
+
},
|
|
42
|
+
]
|
|
43
|
+
: [],
|
|
44
|
+
})
|
|
45
|
+
}, [monaco, schema, language])
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<Editor
|
|
49
|
+
data-testid="json-editor"
|
|
50
|
+
height={height}
|
|
51
|
+
language={language}
|
|
52
|
+
value={value}
|
|
53
|
+
theme={editorTheme}
|
|
54
|
+
onChange={(value) => {
|
|
55
|
+
if (!value) {
|
|
56
|
+
onValidate?.(false)
|
|
57
|
+
}
|
|
58
|
+
onChange?.(value ?? '')
|
|
59
|
+
}}
|
|
60
|
+
onValidate={(markers) => onValidate?.(markers.length === 0)}
|
|
61
|
+
options={{
|
|
62
|
+
readOnly,
|
|
63
|
+
scrollBeyondLastLine: false,
|
|
64
|
+
minimap: { enabled: false },
|
|
65
|
+
}}
|
|
66
|
+
/>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { cn } from '@motiadev/ui'
|
|
2
|
+
import * as React from 'react'
|
|
3
|
+
|
|
4
|
+
const Table = React.forwardRef<HTMLTableElement, React.HTMLAttributes<HTMLTableElement>>(
|
|
5
|
+
({ className, ...props }, ref) => (
|
|
6
|
+
<div className="relative w-full overflow-auto">
|
|
7
|
+
<table ref={ref} className={cn('w-full caption-bottom text-sm', className)} {...props} />
|
|
8
|
+
</div>
|
|
9
|
+
),
|
|
10
|
+
)
|
|
11
|
+
Table.displayName = 'Table'
|
|
12
|
+
|
|
13
|
+
const TableHeader = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(
|
|
14
|
+
({ className, ...props }, ref) => <thead ref={ref} className={cn('[&_tr]:border-b', className)} {...props} />,
|
|
15
|
+
)
|
|
16
|
+
TableHeader.displayName = 'TableHeader'
|
|
17
|
+
|
|
18
|
+
const TableBody = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(
|
|
19
|
+
({ className, ...props }, ref) => (
|
|
20
|
+
<tbody ref={ref} className={cn('[&_tr:last-child]:border-0', className)} {...props} />
|
|
21
|
+
),
|
|
22
|
+
)
|
|
23
|
+
TableBody.displayName = 'TableBody'
|
|
24
|
+
|
|
25
|
+
const TableFooter = React.forwardRef<HTMLTableSectionElement, React.HTMLAttributes<HTMLTableSectionElement>>(
|
|
26
|
+
({ className, ...props }, ref) => (
|
|
27
|
+
<tfoot ref={ref} className={cn('border-t bg-muted/50 font-medium [&>tr]:last:border-b-0', className)} {...props} />
|
|
28
|
+
),
|
|
29
|
+
)
|
|
30
|
+
TableFooter.displayName = 'TableFooter'
|
|
31
|
+
|
|
32
|
+
const TableRow = React.forwardRef<HTMLTableRowElement, React.HTMLAttributes<HTMLTableRowElement>>(
|
|
33
|
+
({ className, ...props }, ref) => (
|
|
34
|
+
<tr
|
|
35
|
+
ref={ref}
|
|
36
|
+
className={cn('border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted', className)}
|
|
37
|
+
{...props}
|
|
38
|
+
/>
|
|
39
|
+
),
|
|
40
|
+
)
|
|
41
|
+
TableRow.displayName = 'TableRow'
|
|
42
|
+
|
|
43
|
+
const TableHead = React.forwardRef<HTMLTableCellElement, React.ThHTMLAttributes<HTMLTableCellElement>>(
|
|
44
|
+
({ className, ...props }, ref) => (
|
|
45
|
+
<th
|
|
46
|
+
ref={ref}
|
|
47
|
+
className={cn(
|
|
48
|
+
'h-10 px-2 text-left align-middle text-md font-medium bg-muted text-muted-foreground font-bold [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',
|
|
49
|
+
className,
|
|
50
|
+
)}
|
|
51
|
+
{...props}
|
|
52
|
+
/>
|
|
53
|
+
),
|
|
54
|
+
)
|
|
55
|
+
TableHead.displayName = 'TableHead'
|
|
56
|
+
|
|
57
|
+
const TableCell = React.forwardRef<HTMLTableCellElement, React.TdHTMLAttributes<HTMLTableCellElement>>(
|
|
58
|
+
({ className, ...props }, ref) => (
|
|
59
|
+
<td
|
|
60
|
+
ref={ref}
|
|
61
|
+
className={cn('p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]', className)}
|
|
62
|
+
{...props}
|
|
63
|
+
/>
|
|
64
|
+
),
|
|
65
|
+
)
|
|
66
|
+
TableCell.displayName = 'TableCell'
|
|
67
|
+
|
|
68
|
+
const TableCaption = React.forwardRef<HTMLTableCaptionElement, React.HTMLAttributes<HTMLTableCaptionElement>>(
|
|
69
|
+
({ className, ...props }, ref) => (
|
|
70
|
+
<caption ref={ref} className={cn('mt-4 text-sm text-muted-foreground', className)} {...props} />
|
|
71
|
+
),
|
|
72
|
+
)
|
|
73
|
+
TableCaption.displayName = 'TableCaption'
|
|
74
|
+
|
|
75
|
+
export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption }
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { cn, type Theme, useThemeStore } from '@motiadev/ui'
|
|
2
|
+
import { Moon, Sun } from 'lucide-react'
|
|
3
|
+
import type React from 'react'
|
|
4
|
+
import { useEffect } from 'react'
|
|
5
|
+
|
|
6
|
+
export const ThemeToggle: React.FC = () => {
|
|
7
|
+
const theme = useThemeStore((state) => state.theme)
|
|
8
|
+
const setTheme = useThemeStore((state) => state.setTheme)
|
|
9
|
+
|
|
10
|
+
const toggleTheme = () => {
|
|
11
|
+
setTheme(theme === 'light' ? 'dark' : 'light')
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
const url = new URL(window.location.href)
|
|
16
|
+
const colorScheme = url.searchParams.get('color-scheme') as Theme
|
|
17
|
+
if (colorScheme) {
|
|
18
|
+
setTheme(colorScheme)
|
|
19
|
+
}
|
|
20
|
+
}, [setTheme])
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<button
|
|
24
|
+
onClick={toggleTheme}
|
|
25
|
+
className="relative flex items-center cursor-pointer w-16 h-8 border bg-muted-foreground/10 rounded-full p-1 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
|
26
|
+
aria-label={`Switch to ${theme === 'light' ? 'dark' : 'light'} theme`}
|
|
27
|
+
>
|
|
28
|
+
<div
|
|
29
|
+
className={cn(
|
|
30
|
+
'absolute w-6 h-6 bg-background border border-border rounded-full shadow-sm transition-transform duration-200 ease-in-out',
|
|
31
|
+
theme === 'dark' ? 'translate-x-8' : 'translate-x-0',
|
|
32
|
+
)}
|
|
33
|
+
/>
|
|
34
|
+
|
|
35
|
+
<div className="flex items-center justify-center w-6 h-6 z-10">
|
|
36
|
+
<Sun
|
|
37
|
+
className={cn(
|
|
38
|
+
'h-3.5 w-3.5 transition-colors duration-200',
|
|
39
|
+
theme === 'light' ? 'text-foreground' : 'text-muted-foreground',
|
|
40
|
+
)}
|
|
41
|
+
/>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<div className="flex items-center justify-center w-6 h-6 z-10 ml-2">
|
|
45
|
+
<Moon
|
|
46
|
+
className={cn(
|
|
47
|
+
'h-3.5 w-3.5 transition-colors duration-200',
|
|
48
|
+
theme === 'dark' ? 'text-foreground' : 'text-muted-foreground',
|
|
49
|
+
)}
|
|
50
|
+
/>
|
|
51
|
+
</div>
|
|
52
|
+
</button>
|
|
53
|
+
)
|
|
54
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as TooltipPrimitive from '@radix-ui/react-tooltip'
|
|
2
|
+
import type { ReactNode } from 'react'
|
|
3
|
+
|
|
4
|
+
export const Tooltip = ({
|
|
5
|
+
children,
|
|
6
|
+
content,
|
|
7
|
+
disabled,
|
|
8
|
+
}: {
|
|
9
|
+
children: ReactNode
|
|
10
|
+
content: string | ReactNode
|
|
11
|
+
disabled?: boolean
|
|
12
|
+
}) => (
|
|
13
|
+
<TooltipPrimitive.Provider disableHoverableContent={disabled}>
|
|
14
|
+
<TooltipPrimitive.Root>
|
|
15
|
+
<TooltipPrimitive.Trigger asChild>{children}</TooltipPrimitive.Trigger>
|
|
16
|
+
<TooltipPrimitive.Portal>
|
|
17
|
+
<TooltipPrimitive.Content className="TooltipContent" side="bottom">
|
|
18
|
+
<div className="p-2 bg-background text-popover-foreground text-sm rounded-lg border border-light-800">
|
|
19
|
+
{content}
|
|
20
|
+
</div>
|
|
21
|
+
<TooltipPrimitive.Arrow className="TooltipArrow" />
|
|
22
|
+
</TooltipPrimitive.Content>
|
|
23
|
+
</TooltipPrimitive.Portal>
|
|
24
|
+
</TooltipPrimitive.Root>
|
|
25
|
+
</TooltipPrimitive.Provider>
|
|
26
|
+
)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef } from 'react'
|
|
2
|
+
|
|
3
|
+
export const useDebounced = (fn: () => void, delay = 500) => {
|
|
4
|
+
const saveTimeoutRef = useRef<NodeJS.Timeout>(null)
|
|
5
|
+
|
|
6
|
+
const debouncedFn = useCallback(() => {
|
|
7
|
+
if (saveTimeoutRef.current) {
|
|
8
|
+
clearTimeout(saveTimeoutRef.current)
|
|
9
|
+
}
|
|
10
|
+
saveTimeoutRef.current = setTimeout(fn, delay)
|
|
11
|
+
}, [fn, delay])
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
return () => {
|
|
15
|
+
if (saveTimeoutRef.current) {
|
|
16
|
+
clearTimeout(saveTimeoutRef.current)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}, [])
|
|
20
|
+
|
|
21
|
+
return debouncedFn
|
|
22
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { useStreamGroup } from '@motiadev/stream-client-react'
|
|
2
|
+
import { useEffect } from 'react'
|
|
3
|
+
import { useFlowStore } from '../stores/use-flow-store'
|
|
4
|
+
import type { FlowResponse } from '../types/flow'
|
|
5
|
+
|
|
6
|
+
const streamGroupArgs = { streamName: '__motia.flows', groupId: 'default' }
|
|
7
|
+
|
|
8
|
+
export const useFetchFlows = () => {
|
|
9
|
+
const setFlows = useFlowStore((state) => state.setFlows)
|
|
10
|
+
const selectFlowId = useFlowStore((state) => state.selectFlowId)
|
|
11
|
+
const clearSelectedFlowId = useFlowStore((state) => state.clearSelectedFlowId)
|
|
12
|
+
const selectedFlowId = useFlowStore((state) => state.selectedFlowId)
|
|
13
|
+
|
|
14
|
+
const { data: flows } = useStreamGroup<FlowResponse>(streamGroupArgs)
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (flows) setFlows(flows.map((flow) => flow.id))
|
|
18
|
+
}, [flows, setFlows])
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
const hasFlows = flows.length > 0
|
|
22
|
+
const isSelectedFlowValid = selectedFlowId && flows.some((flow) => flow.id === selectedFlowId)
|
|
23
|
+
|
|
24
|
+
if (!hasFlows && selectedFlowId) {
|
|
25
|
+
clearSelectedFlowId()
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (hasFlows && (!selectedFlowId || !isSelectedFlowValid)) {
|
|
30
|
+
selectFlowId(flows[0].id)
|
|
31
|
+
}
|
|
32
|
+
}, [flows, selectedFlowId, selectFlowId, clearSelectedFlowId])
|
|
33
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
|
|
3
|
+
const MOBILE_BREAKPOINT = 768
|
|
4
|
+
|
|
5
|
+
export function useIsMobile() {
|
|
6
|
+
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
|
|
7
|
+
|
|
8
|
+
React.useEffect(() => {
|
|
9
|
+
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
|
|
10
|
+
const onChange = () => {
|
|
11
|
+
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
|
12
|
+
}
|
|
13
|
+
mql.addEventListener('change', onChange)
|
|
14
|
+
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
|
15
|
+
return () => mql.removeEventListener('change', onChange)
|
|
16
|
+
}, [])
|
|
17
|
+
|
|
18
|
+
return !!isMobile
|
|
19
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Position, useReactFlow, useUpdateNodeInternals } from '@xyflow/react'
|
|
2
|
+
import type { BaseNodeProps } from '../publicComponents/node-props'
|
|
3
|
+
|
|
4
|
+
export const useHandlePositions = (data: BaseNodeProps) => {
|
|
5
|
+
const reactFlow = useReactFlow()
|
|
6
|
+
const updateNodeInternals = useUpdateNodeInternals()
|
|
7
|
+
const sourcePosition = data.nodeConfig?.sourceHandlePosition === 'bottom' ? Position.Bottom : Position.Right
|
|
8
|
+
const targetPosition = data.nodeConfig?.targetHandlePosition === 'top' ? Position.Top : Position.Left
|
|
9
|
+
|
|
10
|
+
const updateSourcePosition = (position: 'bottom' | 'right') => {
|
|
11
|
+
reactFlow.updateNode(data.id, {
|
|
12
|
+
data: { ...data, nodeConfig: { ...data.nodeConfig, sourceHandlePosition: position } },
|
|
13
|
+
})
|
|
14
|
+
updateNodeInternals(data.id)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const updateTargetPosition = (position: 'top' | 'left') => {
|
|
18
|
+
reactFlow.updateNode(data.id, {
|
|
19
|
+
data: { ...data, nodeConfig: { ...data.nodeConfig, targetHandlePosition: position } },
|
|
20
|
+
})
|
|
21
|
+
updateNodeInternals(data.id)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const toggleTargetPosition = () => {
|
|
25
|
+
const newPosition = targetPosition === Position.Top ? Position.Left : Position.Top
|
|
26
|
+
updateTargetPosition(newPosition)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const toggleSourcePosition = () => {
|
|
30
|
+
const newPosition = sourcePosition === Position.Bottom ? Position.Right : Position.Bottom
|
|
31
|
+
updateSourcePosition(newPosition)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
sourcePosition,
|
|
36
|
+
targetPosition,
|
|
37
|
+
updateSourcePosition,
|
|
38
|
+
updateTargetPosition,
|
|
39
|
+
toggleTargetPosition,
|
|
40
|
+
toggleSourcePosition,
|
|
41
|
+
}
|
|
42
|
+
}
|
package/dist/src/index.css
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
@import
|
|
2
|
-
@import
|
|
1
|
+
@import '@motiadev/ui/styles.css';
|
|
2
|
+
@import '@motiadev/ui/globals.css';
|
|
3
3
|
|
|
4
|
-
@import
|
|
4
|
+
@import 'tw-animate-css';
|
|
5
5
|
@config "../tailwind.config.js";
|
|
6
6
|
|
|
7
7
|
@keyframes flowDash {
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
.font-mono {
|
|
30
|
-
font-family:
|
|
30
|
+
font-family: 'IBM Plex Mono', 'Courier New', monospace;
|
|
31
31
|
font-size: 14px;
|
|
32
32
|
}
|
|
33
33
|
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
.json-view {
|
|
41
41
|
border-radius: 8px;
|
|
42
42
|
padding: 12px 12px;
|
|
43
|
-
font-family:
|
|
43
|
+
font-family: 'IBM Plex Mono', 'Courier New', monospace;
|
|
44
44
|
font-size: 14px;
|
|
45
45
|
line-height: 1.5;
|
|
46
46
|
font-weight: 500;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { formatDuration } from '../utils'
|
|
2
|
+
|
|
3
|
+
describe('formatDuration', () => {
|
|
4
|
+
describe('milliseconds', () => {
|
|
5
|
+
it('should format values under 1 second as milliseconds', () => {
|
|
6
|
+
expect(formatDuration(0)).toBe('0ms')
|
|
7
|
+
expect(formatDuration(1)).toBe('1ms')
|
|
8
|
+
expect(formatDuration(250)).toBe('250ms')
|
|
9
|
+
expect(formatDuration(500)).toBe('500ms')
|
|
10
|
+
expect(formatDuration(999)).toBe('999ms')
|
|
11
|
+
})
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
describe('seconds', () => {
|
|
15
|
+
it('should format values between 1 second and 1 minute as seconds with 1 decimal', () => {
|
|
16
|
+
expect(formatDuration(1000)).toBe('1.0s')
|
|
17
|
+
expect(formatDuration(1500)).toBe('1.5s')
|
|
18
|
+
expect(formatDuration(15000)).toBe('15.0s')
|
|
19
|
+
expect(formatDuration(45500)).toBe('45.5s')
|
|
20
|
+
expect(formatDuration(59999)).toBe('60.0s')
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('should round to 1 decimal place', () => {
|
|
24
|
+
expect(formatDuration(1234)).toBe('1.2s')
|
|
25
|
+
expect(formatDuration(1567)).toBe('1.6s')
|
|
26
|
+
expect(formatDuration(12345)).toBe('12.3s')
|
|
27
|
+
})
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
describe('minutes', () => {
|
|
31
|
+
it('should format values between 1 minute and 1 hour as minutes with 1 decimal', () => {
|
|
32
|
+
expect(formatDuration(60000)).toBe('1.0min')
|
|
33
|
+
expect(formatDuration(90000)).toBe('1.5min')
|
|
34
|
+
expect(formatDuration(300000)).toBe('5.0min')
|
|
35
|
+
expect(formatDuration(1800000)).toBe('30.0min')
|
|
36
|
+
expect(formatDuration(3599999)).toBe('60.0min')
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('should round to 1 decimal place', () => {
|
|
40
|
+
expect(formatDuration(123456)).toBe('2.1min')
|
|
41
|
+
expect(formatDuration(567890)).toBe('9.5min')
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
describe('hours', () => {
|
|
46
|
+
it('should format values 1 hour or more as hours with 1 decimal', () => {
|
|
47
|
+
expect(formatDuration(3600000)).toBe('1.0h')
|
|
48
|
+
expect(formatDuration(5400000)).toBe('1.5h')
|
|
49
|
+
expect(formatDuration(7200000)).toBe('2.0h')
|
|
50
|
+
expect(formatDuration(36000000)).toBe('10.0h')
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('should round to 1 decimal place', () => {
|
|
54
|
+
expect(formatDuration(3661000)).toBe('1.0h')
|
|
55
|
+
expect(formatDuration(5432100)).toBe('1.5h')
|
|
56
|
+
expect(formatDuration(9000000)).toBe('2.5h')
|
|
57
|
+
})
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
describe('edge cases', () => {
|
|
61
|
+
it('should return "N/A" for undefined', () => {
|
|
62
|
+
expect(formatDuration(undefined)).toBe('N/A')
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('should return "N/A" for null', () => {
|
|
66
|
+
expect(formatDuration(null as any)).toBe('N/A')
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('should return "N/A" for 0 when treated as falsy', () => {
|
|
70
|
+
expect(formatDuration(0)).toBe('0ms')
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
describe('boundary values', () => {
|
|
75
|
+
it('should correctly handle millisecond-second boundary (999ms vs 1.0s)', () => {
|
|
76
|
+
expect(formatDuration(999)).toBe('999ms')
|
|
77
|
+
expect(formatDuration(1000)).toBe('1.0s')
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('should correctly handle second-minute boundary (59.9s vs 1.0min)', () => {
|
|
81
|
+
expect(formatDuration(59999)).toBe('60.0s')
|
|
82
|
+
expect(formatDuration(60000)).toBe('1.0min')
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('should correctly handle minute-hour boundary (59.9min vs 1.0h)', () => {
|
|
86
|
+
expect(formatDuration(3599999)).toBe('60.0min')
|
|
87
|
+
expect(formatDuration(3600000)).toBe('1.0h')
|
|
88
|
+
})
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
describe('real-world scenarios', () => {
|
|
92
|
+
it('should format typical API response times', () => {
|
|
93
|
+
expect(formatDuration(50)).toBe('50ms')
|
|
94
|
+
expect(formatDuration(150)).toBe('150ms')
|
|
95
|
+
expect(formatDuration(2500)).toBe('2.5s')
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('should format typical workflow execution times', () => {
|
|
99
|
+
expect(formatDuration(5000)).toBe('5.0s')
|
|
100
|
+
expect(formatDuration(30000)).toBe('30.0s')
|
|
101
|
+
expect(formatDuration(120000)).toBe('2.0min')
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
it('should format long-running tasks', () => {
|
|
105
|
+
expect(formatDuration(600000)).toBe('10.0min')
|
|
106
|
+
expect(formatDuration(1800000)).toBe('30.0min')
|
|
107
|
+
expect(formatDuration(7200000)).toBe('2.0h')
|
|
108
|
+
})
|
|
109
|
+
})
|
|
110
|
+
})
|