mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
Grouping
This commit is contained in:
@@ -794,11 +794,14 @@ const WorkflowContent = React.memo(() => {
|
||||
}, [contextMenuBlocks, collaborativeGroupBlocks])
|
||||
|
||||
const handleContextUngroupBlocks = useCallback(() => {
|
||||
// Find the first block with a groupId and ungroup that entire group
|
||||
// Find the first block with a groupId
|
||||
const groupedBlock = contextMenuBlocks.find((block) => block.groupId)
|
||||
if (groupedBlock?.groupId) {
|
||||
collaborativeUngroupBlocks(groupedBlock.groupId)
|
||||
}
|
||||
if (!groupedBlock?.groupId) return
|
||||
|
||||
// The block's groupId is the group we want to ungroup
|
||||
// This is the direct group the block belongs to, which is the "top level" from the user's perspective
|
||||
// (the most recently created group that contains this block)
|
||||
collaborativeUngroupBlocks(groupedBlock.groupId)
|
||||
}, [contextMenuBlocks, collaborativeUngroupBlocks])
|
||||
|
||||
const handleContextRemoveFromSubflow = useCallback(() => {
|
||||
|
||||
@@ -819,6 +819,7 @@ async function handleBlocksOperationTx(
|
||||
|
||||
logger.info(`Grouping ${blockIds.length} blocks into group ${groupId} in workflow ${workflowId}`)
|
||||
|
||||
// Update blocks: set groupId and push to groupStack
|
||||
for (const blockId of blockIds) {
|
||||
const [currentBlock] = await tx
|
||||
.select({ data: workflowBlocks.data })
|
||||
@@ -831,8 +832,13 @@ async function handleBlocksOperationTx(
|
||||
continue
|
||||
}
|
||||
|
||||
const currentData = currentBlock?.data || {}
|
||||
const updatedData = { ...currentData, groupId }
|
||||
const currentData = (currentBlock?.data || {}) as Record<string, any>
|
||||
const currentStack = Array.isArray(currentData.groupStack) ? currentData.groupStack : []
|
||||
const updatedData = {
|
||||
...currentData,
|
||||
groupId,
|
||||
groupStack: [...currentStack, groupId],
|
||||
}
|
||||
|
||||
await tx
|
||||
.update(workflowBlocks)
|
||||
@@ -848,7 +854,7 @@ async function handleBlocksOperationTx(
|
||||
}
|
||||
|
||||
case BLOCKS_OPERATIONS.UNGROUP_BLOCKS: {
|
||||
const { groupId, blockIds, parentGroupId } = payload
|
||||
const { groupId, blockIds } = payload
|
||||
if (!groupId || !Array.isArray(blockIds)) {
|
||||
logger.debug('Invalid payload for ungroup blocks operation')
|
||||
return
|
||||
@@ -856,6 +862,7 @@ async function handleBlocksOperationTx(
|
||||
|
||||
logger.info(`Ungrouping ${blockIds.length} blocks from group ${groupId} in workflow ${workflowId}`)
|
||||
|
||||
// Update blocks: pop from groupStack and set groupId to the previous level
|
||||
for (const blockId of blockIds) {
|
||||
const [currentBlock] = await tx
|
||||
.select({ data: workflowBlocks.data })
|
||||
@@ -868,15 +875,23 @@ async function handleBlocksOperationTx(
|
||||
continue
|
||||
}
|
||||
|
||||
const currentData = currentBlock?.data || {}
|
||||
let updatedData: Record<string, any>
|
||||
const currentData = (currentBlock?.data || {}) as Record<string, any>
|
||||
const currentStack = Array.isArray(currentData.groupStack) ? [...currentData.groupStack] : []
|
||||
|
||||
if (parentGroupId) {
|
||||
// Move to parent group
|
||||
updatedData = { ...currentData, groupId: parentGroupId }
|
||||
// Pop the current groupId from the stack
|
||||
if (currentStack.length > 0 && currentStack[currentStack.length - 1] === groupId) {
|
||||
currentStack.pop()
|
||||
}
|
||||
|
||||
// The new groupId is the top of the remaining stack, or undefined if empty
|
||||
const newGroupId = currentStack.length > 0 ? currentStack[currentStack.length - 1] : undefined
|
||||
|
||||
let updatedData: Record<string, any>
|
||||
if (newGroupId) {
|
||||
updatedData = { ...currentData, groupId: newGroupId, groupStack: currentStack }
|
||||
} else {
|
||||
// Remove from group entirely
|
||||
const { groupId: _removed, ...restData } = currentData
|
||||
// Remove groupId and groupStack if stack is empty
|
||||
const { groupId: _removed, groupStack: _removedStack, ...restData } = currentData
|
||||
updatedData = restData
|
||||
}
|
||||
|
||||
|
||||
@@ -298,11 +298,26 @@ export const useWorkflowRegistry = create<WorkflowRegistry>()(
|
||||
let workflowState: any
|
||||
|
||||
if (workflowData?.state) {
|
||||
const blocks = workflowData.state.blocks || {}
|
||||
|
||||
// Reconstruct groups from blocks' groupId data
|
||||
const reconstructedGroups: Record<string, { id: string; blockIds: string[] }> = {}
|
||||
Object.entries(blocks).forEach(([blockId, block]: [string, any]) => {
|
||||
const groupId = block?.data?.groupId
|
||||
if (groupId) {
|
||||
if (!reconstructedGroups[groupId]) {
|
||||
reconstructedGroups[groupId] = { id: groupId, blockIds: [] }
|
||||
}
|
||||
reconstructedGroups[groupId].blockIds.push(blockId)
|
||||
}
|
||||
})
|
||||
|
||||
workflowState = {
|
||||
blocks: workflowData.state.blocks || {},
|
||||
blocks,
|
||||
edges: workflowData.state.edges || [],
|
||||
loops: workflowData.state.loops || {},
|
||||
parallels: workflowData.state.parallels || {},
|
||||
groups: reconstructedGroups,
|
||||
lastSaved: Date.now(),
|
||||
deploymentStatuses: {},
|
||||
}
|
||||
@@ -312,6 +327,7 @@ export const useWorkflowRegistry = create<WorkflowRegistry>()(
|
||||
edges: [],
|
||||
loops: {},
|
||||
parallels: {},
|
||||
groups: {},
|
||||
deploymentStatuses: {},
|
||||
lastSaved: Date.now(),
|
||||
}
|
||||
|
||||
@@ -1345,34 +1345,25 @@ export const useWorkflowStore = create<WorkflowStore>()(
|
||||
const currentGroups = get().groups || {}
|
||||
const currentBlocks = get().blocks
|
||||
|
||||
// Remove blocks from any existing groups they might be in
|
||||
// Create the new group with all selected block IDs
|
||||
const updatedGroups = { ...currentGroups }
|
||||
for (const gId of Object.keys(updatedGroups)) {
|
||||
updatedGroups[gId] = {
|
||||
...updatedGroups[gId],
|
||||
blockIds: updatedGroups[gId].blockIds.filter((bid) => !blockIds.includes(bid)),
|
||||
}
|
||||
// Remove empty groups
|
||||
if (updatedGroups[gId].blockIds.length === 0) {
|
||||
delete updatedGroups[gId]
|
||||
}
|
||||
}
|
||||
|
||||
// Create the new group
|
||||
updatedGroups[newGroupId] = {
|
||||
id: newGroupId,
|
||||
blockIds: [...blockIds],
|
||||
}
|
||||
|
||||
// Update blocks with the new groupId
|
||||
// Update blocks: set groupId and push to groupStack
|
||||
const newBlocks = { ...currentBlocks }
|
||||
for (const blockId of blockIds) {
|
||||
if (newBlocks[blockId]) {
|
||||
const currentData = newBlocks[blockId].data || {}
|
||||
const currentStack = Array.isArray(currentData.groupStack) ? currentData.groupStack : []
|
||||
newBlocks[blockId] = {
|
||||
...newBlocks[blockId],
|
||||
data: {
|
||||
...newBlocks[blockId].data,
|
||||
...currentData,
|
||||
groupId: newGroupId,
|
||||
groupStack: [...currentStack, newGroupId],
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1384,7 +1375,10 @@ export const useWorkflowStore = create<WorkflowStore>()(
|
||||
})
|
||||
|
||||
get().updateLastSaved()
|
||||
logger.info('Created block group', { groupId: newGroupId, blockCount: blockIds.length })
|
||||
logger.info('Created block group', {
|
||||
groupId: newGroupId,
|
||||
blockCount: blockIds.length,
|
||||
})
|
||||
return newGroupId
|
||||
},
|
||||
|
||||
@@ -1399,68 +1393,65 @@ export const useWorkflowStore = create<WorkflowStore>()(
|
||||
}
|
||||
|
||||
const blockIds = [...group.blockIds]
|
||||
const parentGroupId = group.parentGroupId
|
||||
|
||||
// Remove the group
|
||||
// Remove the group from the groups record
|
||||
const updatedGroups = { ...currentGroups }
|
||||
delete updatedGroups[groupId]
|
||||
|
||||
// Update blocks - remove groupId or assign to parent group
|
||||
// Update blocks: pop from groupStack and set groupId to the previous level
|
||||
const newBlocks = { ...currentBlocks }
|
||||
for (const blockId of blockIds) {
|
||||
if (newBlocks[blockId]) {
|
||||
const newData = { ...newBlocks[blockId].data }
|
||||
if (parentGroupId) {
|
||||
newData.groupId = parentGroupId
|
||||
} else {
|
||||
delete newData.groupId
|
||||
const currentData = { ...newBlocks[blockId].data }
|
||||
const currentStack = Array.isArray(currentData.groupStack)
|
||||
? [...currentData.groupStack]
|
||||
: []
|
||||
|
||||
// Pop the current groupId from the stack
|
||||
if (currentStack.length > 0 && currentStack[currentStack.length - 1] === groupId) {
|
||||
currentStack.pop()
|
||||
}
|
||||
|
||||
// The new groupId is the top of the remaining stack, or undefined if empty
|
||||
const newGroupId =
|
||||
currentStack.length > 0 ? currentStack[currentStack.length - 1] : undefined
|
||||
|
||||
if (newGroupId) {
|
||||
currentData.groupId = newGroupId
|
||||
currentData.groupStack = currentStack
|
||||
} else {
|
||||
// Remove groupId and groupStack if stack is empty
|
||||
delete currentData.groupId
|
||||
delete currentData.groupStack
|
||||
}
|
||||
|
||||
newBlocks[blockId] = {
|
||||
...newBlocks[blockId],
|
||||
data: newData,
|
||||
data: currentData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If there's a parent group, add the blocks to it
|
||||
if (parentGroupId && updatedGroups[parentGroupId]) {
|
||||
updatedGroups[parentGroupId] = {
|
||||
...updatedGroups[parentGroupId],
|
||||
blockIds: [...updatedGroups[parentGroupId].blockIds, ...blockIds],
|
||||
}
|
||||
}
|
||||
|
||||
set({
|
||||
blocks: newBlocks,
|
||||
groups: updatedGroups,
|
||||
})
|
||||
|
||||
get().updateLastSaved()
|
||||
logger.info('Ungrouped blocks', { groupId, blockCount: blockIds.length })
|
||||
logger.info('Ungrouped blocks', {
|
||||
groupId,
|
||||
blockCount: blockIds.length,
|
||||
})
|
||||
return blockIds
|
||||
},
|
||||
|
||||
getGroupBlockIds: (groupId: string, recursive = false) => {
|
||||
getGroupBlockIds: (groupId: string) => {
|
||||
const groups = get().groups || {}
|
||||
const group = groups[groupId]
|
||||
|
||||
if (!group) return []
|
||||
|
||||
if (!recursive) {
|
||||
return [...group.blockIds]
|
||||
}
|
||||
|
||||
// Recursively get all block IDs, including from nested groups
|
||||
const allBlockIds: string[] = [...group.blockIds]
|
||||
|
||||
// Find child groups (groups whose parentGroupId is this group)
|
||||
for (const g of Object.values(groups)) {
|
||||
if (g.parentGroupId === groupId) {
|
||||
allBlockIds.push(...get().getGroupBlockIds(g.id, true))
|
||||
}
|
||||
}
|
||||
|
||||
return allBlockIds
|
||||
return [...group.blockIds]
|
||||
},
|
||||
|
||||
getGroups: () => {
|
||||
|
||||
@@ -66,6 +66,8 @@ export interface BlockData {
|
||||
|
||||
// Block group membership
|
||||
groupId?: string
|
||||
/** Stack of group IDs for hierarchical grouping (oldest to newest) */
|
||||
groupStack?: string[]
|
||||
}
|
||||
|
||||
export interface BlockLayoutState {
|
||||
@@ -159,8 +161,6 @@ export interface BlockGroup {
|
||||
name?: string
|
||||
/** Block IDs that are direct members of this group */
|
||||
blockIds: string[]
|
||||
/** Parent group ID if this group is nested inside another group */
|
||||
parentGroupId?: string
|
||||
}
|
||||
|
||||
export interface DragStartPosition {
|
||||
|
||||
Reference in New Issue
Block a user