diff --git a/ui/detection-app/src/App.vue b/ui/detection-app/src/App.vue index b07be31..2e38800 100644 --- a/ui/detection-app/src/App.vue +++ b/ui/detection-app/src/App.vue @@ -138,7 +138,7 @@ function onJobStarted(newJobId: string) {
- + diff --git a/ui/detection-app/src/components/StageConfigSliders.vue b/ui/detection-app/src/components/StageConfigSliders.vue index e4a0fdd..19d50d2 100644 --- a/ui/detection-app/src/components/StageConfigSliders.vue +++ b/ui/detection-app/src/components/StageConfigSliders.vue @@ -1,5 +1,7 @@ + + + + diff --git a/ui/framework/src/components/SplitPane.vue b/ui/framework/src/components/SplitPane.vue index af77b73..7a1b0f4 100644 --- a/ui/framework/src/components/SplitPane.vue +++ b/ui/framework/src/components/SplitPane.vue @@ -64,9 +64,11 @@ const isHorizontal = computed(() => props.direction === 'horizontal') const sizedStyle = computed(() => { if (props.sizeMode === 'px') { + const sizeStr = size.value + 'px' + const minStr = props.min + 'px' return isHorizontal.value - ? { width: size.value + 'px', flexShrink: '0' } - : { height: size.value + 'px', flexShrink: '0' } + ? { width: sizeStr, minWidth: minStr, flexShrink: '0' } + : { height: sizeStr, minHeight: minStr, flexShrink: '0' } } return { flex: String(size.value) } }) diff --git a/ui/framework/src/composables/useEditorExecution.ts b/ui/framework/src/composables/useEditorExecution.ts new file mode 100644 index 0000000..e968408 --- /dev/null +++ b/ui/framework/src/composables/useEditorExecution.ts @@ -0,0 +1,57 @@ +import { ref } from 'vue' + +export interface EditorExecutionOptions { + /** Debounce delay in ms for auto-apply. Default: 150 */ + debounceMs?: number +} + +/** + * Generic editor execution pattern — debounced apply with auto-apply toggle, + * loading/error/timing state tracking. + * + * The caller provides the actual execution function. This composable handles + * the orchestration: debounce, auto-apply, loading state, timing. + */ +export function useEditorExecution( + executeFn: () => Promise, + options: EditorExecutionOptions = {}, +) { + const debounceMs = options.debounceMs ?? 150 + + const loading = ref(false) + const error = ref(null) + const autoApply = ref(true) + const execTimeMs = ref(null) + + let debounceTimer: ReturnType | null = null + + async function apply() { + loading.value = true + error.value = null + execTimeMs.value = null + const t0 = performance.now() + try { + await executeFn() + execTimeMs.value = Math.round(performance.now() - t0) + } catch (e) { + error.value = String(e) + } finally { + loading.value = false + } + } + + function onParameterChange() { + if (!autoApply.value) return + if (debounceTimer) clearTimeout(debounceTimer) + debounceTimer = setTimeout(() => apply(), debounceMs) + } + + return { + loading, + error, + autoApply, + execTimeMs, + apply, + onParameterChange, + } +} diff --git a/ui/framework/src/index.ts b/ui/framework/src/index.ts index ddcaa5f..8d97ff1 100644 --- a/ui/framework/src/index.ts +++ b/ui/framework/src/index.ts @@ -4,12 +4,16 @@ export { SSEDataSource } from './datasources/SSEDataSource' export { StaticDataSource } from './datasources/StaticDataSource' export { useDataSource } from './composables/useDataSource' export { useRegistry } from './composables/useRegistry' +export { useEditorExecution } from './composables/useEditorExecution' +export type { EditorExecutionOptions } from './composables/useEditorExecution' // Components export { default as Panel } from './components/Panel.vue' export { default as LayoutGrid } from './components/LayoutGrid.vue' export { default as ResizeHandle } from './components/ResizeHandle.vue' export { default as SplitPane } from './components/SplitPane.vue' +export { default as ParameterEditor } from './components/ParameterEditor.vue' +export type { ConfigField } from './components/ParameterEditor.vue' // Renderers export { default as LogRenderer } from './renderers/LogRenderer.vue'