Merge pull request #197 from johndoe6345789/codex/refactor-computed-json-configs

Remove legacy `computed` data sources; migrate to expression/valueTemplate and update UI
This commit is contained in:
2026-01-18 18:33:08 +00:00
committed by GitHub
40 changed files with 653 additions and 903 deletions
+1 -38
View File
@@ -1,9 +1,8 @@
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useCallback, useEffect, useState } from 'react'
import { DataSource } from '@/types/json-ui'
export function useDataSourceEditor(
dataSource: DataSource | null,
allDataSources: DataSource[],
) {
const [editingSource, setEditingSource] = useState<DataSource | null>(dataSource)
@@ -15,44 +14,8 @@ export function useDataSourceEditor(
setEditingSource(prev => (prev ? { ...prev, [field]: value } : prev))
}, [])
const addDependency = useCallback((depId: string) => {
setEditingSource(prev => {
if (!prev || prev.type !== 'computed') return prev
const deps = prev.dependencies || []
if (deps.includes(depId)) return prev
return { ...prev, dependencies: [...deps, depId] }
})
}, [])
const removeDependency = useCallback((depId: string) => {
setEditingSource(prev => {
if (!prev || prev.type !== 'computed') return prev
const deps = prev.dependencies || []
return { ...prev, dependencies: deps.filter(dep => dep !== depId) }
})
}, [])
const availableDeps = useMemo(() => {
if (!editingSource) return []
return allDataSources.filter(
ds => ds.id !== editingSource.id && ds.type !== 'computed',
)
}, [allDataSources, editingSource])
const selectedDeps = useMemo(() => editingSource?.dependencies || [], [editingSource])
const unselectedDeps = useMemo(
() => availableDeps.filter(ds => !selectedDeps.includes(ds.id)),
[availableDeps, selectedDeps],
)
return {
editingSource,
updateField,
addDependency,
removeDependency,
availableDeps,
selectedDeps,
unselectedDeps,
}
}
@@ -9,7 +9,6 @@ export function useDataSourceManager(initialSources: DataSource[] = []) {
id: `ds-${Date.now()}`,
type,
...(type === 'kv' && { key: '', defaultValue: null }),
...(type === 'computed' && { expression: '', dependencies: [] }),
...(type === 'static' && { defaultValue: null }),
}
@@ -33,7 +32,6 @@ export function useDataSourceManager(initialSources: DataSource[] = []) {
const getDependents = useCallback((sourceId: string) => {
return dataSources.filter(ds =>
ds.type === 'computed' &&
ds.dependencies?.includes(sourceId)
)
}, [dataSources])
+1 -10
View File
@@ -1,13 +1,11 @@
import { useKV } from '@/hooks/use-kv'
export type DataSourceType = 'kv' | 'static' | 'computed'
export type DataSourceType = 'kv' | 'static'
export interface DataSourceConfig<T = any> {
type: DataSourceType
key?: string
defaultValue?: T
compute?: (allData: Record<string, any>) => T
dependencies?: string[]
}
export function useKVDataSource<T = any>(key: string, defaultValue?: T) {
@@ -18,13 +16,6 @@ export function useStaticDataSource<T = any>(defaultValue: T) {
return [defaultValue, () => {}, () => {}] as const
}
export function useComputedDataSource<T = any>(
compute: (allData: Record<string, any>) => T,
dependencies: Record<string, any>
) {
return compute(dependencies)
}
export function useMultipleDataSources(_sources: DataSourceConfig[]) {
return {}
}
+5 -5
View File
@@ -41,20 +41,20 @@ export function useDataSources(dataSources: DataSource[]) {
}, [])
useEffect(() => {
const computedSources = dataSources.filter(ds => ds.type === 'computed')
computedSources.forEach(source => {
const derivedSources = dataSources.filter(ds => ds.expression || ds.valueTemplate)
derivedSources.forEach(source => {
const deps = source.dependencies || []
const hasAllDeps = deps.every(dep => dep in data)
if (hasAllDeps) {
const evaluationContext = { data }
const computedValue = source.expression
const derivedValue = source.expression
? evaluateExpression(source.expression, evaluationContext)
: source.valueTemplate
? evaluateTemplate(source.valueTemplate, evaluationContext)
: source.defaultValue
setData(prev => ({ ...prev, [source.id]: computedValue }))
setData(prev => ({ ...prev, [source.id]: derivedValue }))
}
})
}, [data, dataSources])
+5 -5
View File
@@ -13,8 +13,8 @@ export function useDataSources(dataSources: DataSource[]) {
[dataSources]
)
const computedSources = useMemo(
() => dataSources.filter((ds) => ds.type === 'computed'),
const derivedSources = useMemo(
() => dataSources.filter((ds) => ds.expression || ds.valueTemplate),
[dataSources]
)
@@ -54,8 +54,8 @@ export function useDataSources(dataSources: DataSource[]) {
const computedData = useMemo(() => {
const result: Record<string, any> = {}
computedSources.forEach((ds) => {
const evaluationContext = { data }
derivedSources.forEach((ds) => {
const evaluationContext = { data: { ...data, ...result } }
if (ds.expression) {
result[ds.id] = evaluateExpression(ds.expression, evaluationContext)
return
@@ -70,7 +70,7 @@ export function useDataSources(dataSources: DataSource[]) {
})
return result
}, [computedSources, data])
}, [derivedSources, data])
const allData = useMemo(
() => ({ ...data, ...computedData }),
+16 -12
View File
@@ -45,22 +45,26 @@ export function usePage(schema: PageSchema) {
useEffect(() => {
if (schema.data) {
const computed: Record<string, any> = {}
schema.data.forEach(source => {
if (source.type === 'computed') {
if (source.expression) {
computed[source.id] = evaluateBindingExpression(source.expression, dataContext, {
fallback: undefined,
label: `computed data (${source.id})`,
})
} else if (source.valueTemplate) {
computed[source.id] = evaluateTemplate(source.valueTemplate, { data: dataContext })
}
} else if (source.type === 'static' && source.defaultValue !== undefined) {
if (source.expression) {
computed[source.id] = evaluateBindingExpression(source.expression, { ...dataContext, ...computed }, {
fallback: undefined,
label: `derived data (${source.id})`,
})
return
}
if (source.valueTemplate) {
computed[source.id] = evaluateTemplate(source.valueTemplate, { data: { ...dataContext, ...computed } })
return
}
if (source.type === 'static' && source.defaultValue !== undefined) {
computed[source.id] = source.defaultValue
}
})
setComputedData(computed)
}
}, [schema.data, dataContext])
+1 -39
View File
@@ -1,16 +1,14 @@
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useCallback, useEffect, useState } from 'react'
import { DataSource } from '@/types/json-ui'
interface UseDataSourceEditorParams {
dataSource: DataSource | null
allDataSources: DataSource[]
onSave: (dataSource: DataSource) => void
onOpenChange: (open: boolean) => void
}
export function useDataSourceEditor({
dataSource,
allDataSources,
onSave,
onOpenChange,
}: UseDataSourceEditorParams) {
@@ -27,51 +25,15 @@ export function useDataSourceEditor({
})
}, [])
const addDependency = useCallback((depId: string) => {
setEditingSource((prev) => {
if (!prev || prev.type !== 'computed') return prev
const deps = prev.dependencies || []
if (deps.includes(depId)) return prev
return { ...prev, dependencies: [...deps, depId] }
})
}, [])
const removeDependency = useCallback((depId: string) => {
setEditingSource((prev) => {
if (!prev || prev.type !== 'computed') return prev
const deps = prev.dependencies || []
return { ...prev, dependencies: deps.filter((id) => id !== depId) }
})
}, [])
const handleSave = useCallback(() => {
if (!editingSource) return
onSave(editingSource)
onOpenChange(false)
}, [editingSource, onOpenChange, onSave])
const availableDeps = useMemo(() => {
if (!editingSource) return []
return allDataSources.filter(
(ds) => ds.id !== editingSource.id && ds.type !== 'computed',
)
}, [allDataSources, editingSource])
const selectedDeps = useMemo(() => editingSource?.dependencies || [], [editingSource])
const unselectedDeps = useMemo(() => {
if (!editingSource) return []
return availableDeps.filter((ds) => !selectedDeps.includes(ds.id))
}, [availableDeps, editingSource, selectedDeps])
return {
editingSource,
updateField,
addDependency,
removeDependency,
handleSave,
availableDeps,
selectedDeps,
unselectedDeps,
}
}