fix[ux]: referenced block renaming (#132)

* fix[ui]: referenced block renaming #120

* improvement[block-renaming]: added support for condition/function blocks

---------

Co-authored-by: Braulio Fernandes <brauliopf+github@gmail.com>
This commit is contained in:
Emir Karabeg
2025-03-20 23:24:17 -07:00
committed by GitHub
co-authored by Braulio Fernandes
parent 73f224de33
commit f5063af161
2 changed files with 127 additions and 41 deletions
+63 -1
View File
@@ -402,11 +402,15 @@ export const useWorkflowStore = create<WorkflowStoreWithHistory>()(
},
updateBlockName: (id: string, name: string) => {
const oldBlock = get().blocks[id]
if (!oldBlock) return
// Create a new state with the updated block name
const newState = {
blocks: {
...get().blocks,
[id]: {
...get().blocks[id],
...oldBlock,
name,
},
},
@@ -414,6 +418,64 @@ export const useWorkflowStore = create<WorkflowStoreWithHistory>()(
loops: { ...get().loops },
}
// Update references in subblock store
const subBlockStore = useSubBlockStore.getState()
const activeWorkflowId = useWorkflowRegistry.getState().activeWorkflowId
if (activeWorkflowId) {
// Get the workflow values for the active workflow
// workflowValues: {[block_id]:{[subblock_id]:[subblock_value]}}
const workflowValues = subBlockStore.workflowValues[activeWorkflowId] || {}
const updatedWorkflowValues = { ...workflowValues }
// Loop through blocks
Object.entries(workflowValues).forEach(([blockId, blockValues]) => {
if (blockId === id) return // Skip the block being renamed
// Loop through subblocks and update references
Object.entries(blockValues).forEach(([subBlockId, value]) => {
const oldBlockName = oldBlock.name.replace(/\s+/g, '').toLowerCase()
const newBlockName = name.replace(/\s+/g, '').toLowerCase()
const regex = new RegExp(`<${oldBlockName}\\.`, 'g')
// Use a recursive function to handle all object types
updatedWorkflowValues[blockId][subBlockId] = updateReferences(value, regex, `<${newBlockName}.`)
// Helper function to recursively update references in any data structure
function updateReferences(value: any, regex: RegExp, replacement: string): any {
// Handle string values
if (typeof value === 'string') {
return regex.test(value) ? value.replace(regex, replacement) : value
}
// Handle arrays
if (Array.isArray(value)) {
return value.map(item => updateReferences(item, regex, replacement))
}
// Handle objects
if (value !== null && typeof value === 'object') {
const result = { ...value }
for (const key in result) {
result[key] = updateReferences(result[key], regex, replacement)
}
return result
}
// Return unchanged for other types
return value
}
})
})
// Update the subblock store with the new values
useSubBlockStore.setState({
workflowValues: {
...subBlockStore.workflowValues,
[activeWorkflowId]: updatedWorkflowValues,
},
})
}
set(newState)
pushHistory(set, get, newState, `${name} block name updated`)
get().updateLastSaved()
@@ -36,12 +36,6 @@ interface ConditionInputProps {
export function ConditionInput({ blockId, subBlockId, isConnecting }: ConditionInputProps) {
const [storeValue, setStoreValue] = useSubBlockValue(blockId, subBlockId)
const [lineCount, setLineCount] = useState(1)
const [showTags, setShowTags] = useState(false)
const [showEnvVars, setShowEnvVars] = useState(false)
const [searchTerm, setSearchTerm] = useState('')
const [cursorPosition, setCursorPosition] = useState(0)
const [activeSourceBlockId, setActiveSourceBlockId] = useState<string | null>(null)
const editorRef = useRef<HTMLDivElement>(null)
const [visualLineHeights, setVisualLineHeights] = useState<{
[key: string]: number[]
@@ -50,6 +44,11 @@ export function ConditionInput({ blockId, subBlockId, isConnecting }: ConditionI
const removeEdge = useWorkflowStore((state) => state.removeEdge)
const edges = useWorkflowStore((state) => state.edges)
// Use a ref to track the previous store value for comparison
const prevStoreValueRef = useRef<string | null>(null)
// Use a ref to track if we're currently syncing from store to prevent loops
const isSyncingFromStoreRef = useRef(false)
// Initialize conditional blocks with if and else blocks
const [conditionalBlocks, setConditionalBlocks] = useState<ConditionalBlock[]>([
{
@@ -74,46 +73,71 @@ export function ConditionInput({ blockId, subBlockId, isConnecting }: ConditionI
},
])
// Sync store value with conditional blocks on initial load
// Sync store value with conditional blocks when storeValue changes
useEffect(() => {
if (storeValue !== null) {
try {
const parsedValue = JSON.parse(storeValue.toString())
if (Array.isArray(parsedValue)) {
setConditionalBlocks(parsedValue)
}
} catch {
// If the store value isn't valid JSON, initialize with default blocks
setConditionalBlocks([
{
id: crypto.randomUUID(),
title: 'if',
value: '',
showTags: false,
showEnvVars: false,
searchTerm: '',
cursorPosition: 0,
activeSourceBlockId: null,
},
{
id: crypto.randomUUID(),
title: 'else',
value: '',
showTags: false,
showEnvVars: false,
searchTerm: '',
cursorPosition: 0,
activeSourceBlockId: null,
},
])
// Skip if we don't have a store value
if (storeValue === null) return
// Skip if the store value hasn't changed
const storeValueStr = storeValue.toString()
if (storeValueStr === prevStoreValueRef.current) return
// Update the previous store value ref
prevStoreValueRef.current = storeValueStr
// Set that we're syncing from store to prevent loops
isSyncingFromStoreRef.current = true
try {
const parsedValue = JSON.parse(storeValueStr)
if (Array.isArray(parsedValue)) {
setConditionalBlocks(parsedValue)
}
} catch (error) {
// If parsing fails, set default blocks
setConditionalBlocks([
{
id: crypto.randomUUID(),
title: 'if',
value: '',
showTags: false,
showEnvVars: false,
searchTerm: '',
cursorPosition: 0,
activeSourceBlockId: null,
},
{
id: crypto.randomUUID(),
title: 'else',
value: '',
showTags: false,
showEnvVars: false,
searchTerm: '',
cursorPosition: 0,
activeSourceBlockId: null,
},
])
} finally {
// Reset the syncing flag
setTimeout(() => {
isSyncingFromStoreRef.current = false
}, 0)
}
}, [])
}, [storeValue])
// Update store whenever conditional blocks change
useEffect(() => {
setStoreValue(JSON.stringify(conditionalBlocks))
updateNodeInternals(`${blockId}-${subBlockId}`)
// Skip if we're currently syncing from store to prevent loops
if (isSyncingFromStoreRef.current) return
const newValue = JSON.stringify(conditionalBlocks)
// Only update if the value has actually changed
if (newValue !== prevStoreValueRef.current) {
prevStoreValueRef.current = newValue
setStoreValue(newValue)
updateNodeInternals(`${blockId}-${subBlockId}`)
}
}, [conditionalBlocks, blockId, subBlockId])
// Update block value with trigger checks - handle both tag and env var triggers consistently