improvement(canvas): add multi-block select, add batch handle, enabled, and edge operations (#2738)

* improvement(canvas): add multi-block select, add batch handle, enabled, and edge operations

* feat(i18n): update translations (#2732)

Co-authored-by: icecrasher321 <icecrasher321@users.noreply.github.com>

* don't allow flip handles for subflows

* ack PR comments

* more

* fix missing handler

* remove dead subflow-specific ops

* remove unused code

* fixed subflow ops

* keep edges on subflow actions intact

* fix subflow resizing

* fix remove from subflow bulk

* improvement(canvas): add multi-block select, add batch handle, enabled, and edge operations

* don't allow flip handles for subflows

* ack PR comments

* more

* fix missing handler

* remove dead subflow-specific ops

* remove unused code

* fixed subflow ops

* fix subflow resizing

* keep edges on subflow actions intact

* fixed copy from inside subflow

* types improvement, preview fixes

* fetch varible data in deploy modal

* moved remove from subflow one position to the right

* fix subflow issues

* address greptile comment

* fix test

* improvement(preview): ui/ux

* fix(preview): subflows

* added batch add edges

* removed recovery

* use consolidated consts for sockets operations

* more

---------

Co-authored-by: icecrasher321 <icecrasher321@users.noreply.github.com>
Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai>
Co-authored-by: Emir Karabeg <emirkarabeg@berkeley.edu>
This commit is contained in:
Waleed
2026-01-09 14:48:23 -08:00
committed by GitHub
parent 753600ed60
commit 05bbf34265
110 changed files with 5943 additions and 2418 deletions

View File

@@ -120,24 +120,25 @@ export {
} from './serialized-block.factory'
// Undo/redo operation factories
export {
type AddEdgeOperation,
type BaseOperation,
type BatchAddBlocksOperation,
type BatchAddEdgesOperation,
type BatchMoveBlocksOperation,
type BatchRemoveBlocksOperation,
type BatchRemoveEdgesOperation,
type BatchUpdateParentOperation,
createAddBlockEntry,
createAddEdgeEntry,
createBatchRemoveEdgesEntry,
createBatchUpdateParentEntry,
createMoveBlockEntry,
createRemoveBlockEntry,
createRemoveEdgeEntry,
createUpdateParentEntry,
type MoveBlockOperation,
type Operation,
type OperationEntry,
type OperationType,
type RemoveEdgeOperation,
type UpdateParentOperation,
} from './undo-redo.factory'
// User/workspace factories
export {
createUser,
createUserWithWorkspace,
@@ -147,7 +148,6 @@ export {
type WorkflowObjectFactoryOptions,
type WorkspaceFactoryOptions,
} from './user.factory'
// Workflow factories
export {
createBranchingWorkflow,
createLinearWorkflow,

View File

@@ -8,10 +8,11 @@ import { nanoid } from 'nanoid'
export type OperationType =
| 'batch-add-blocks'
| 'batch-remove-blocks'
| 'add-edge'
| 'remove-edge'
| 'move-block'
| 'batch-add-edges'
| 'batch-remove-edges'
| 'batch-move-blocks'
| 'update-parent'
| 'batch-update-parent'
/**
* Base operation interface.
@@ -25,14 +26,16 @@ export interface BaseOperation {
}
/**
* Move block operation data.
* Batch move blocks operation data.
*/
export interface MoveBlockOperation extends BaseOperation {
type: 'move-block'
export interface BatchMoveBlocksOperation extends BaseOperation {
type: 'batch-move-blocks'
data: {
blockId: string
before: { x: number; y: number; parentId?: string }
after: { x: number; y: number; parentId?: string }
moves: Array<{
blockId: string
before: { x: number; y: number; parentId?: string }
after: { x: number; y: number; parentId?: string }
}>
}
}
@@ -61,19 +64,19 @@ export interface BatchRemoveBlocksOperation extends BaseOperation {
}
/**
* Add edge operation data.
* Batch add edges operation data.
*/
export interface AddEdgeOperation extends BaseOperation {
type: 'add-edge'
data: { edgeId: string }
export interface BatchAddEdgesOperation extends BaseOperation {
type: 'batch-add-edges'
data: { edgeSnapshots: any[] }
}
/**
* Remove edge operation data.
* Batch remove edges operation data.
*/
export interface RemoveEdgeOperation extends BaseOperation {
type: 'remove-edge'
data: { edgeId: string; edgeSnapshot: any }
export interface BatchRemoveEdgesOperation extends BaseOperation {
type: 'batch-remove-edges'
data: { edgeSnapshots: any[] }
}
/**
@@ -90,13 +93,28 @@ export interface UpdateParentOperation extends BaseOperation {
}
}
export interface BatchUpdateParentOperation extends BaseOperation {
type: 'batch-update-parent'
data: {
updates: Array<{
blockId: string
oldParentId?: string
newParentId?: string
oldPosition: { x: number; y: number }
newPosition: { x: number; y: number }
affectedEdges?: any[]
}>
}
}
export type Operation =
| BatchAddBlocksOperation
| BatchRemoveBlocksOperation
| AddEdgeOperation
| RemoveEdgeOperation
| MoveBlockOperation
| BatchAddEdgesOperation
| BatchRemoveEdgesOperation
| BatchMoveBlocksOperation
| UpdateParentOperation
| BatchUpdateParentOperation
/**
* Operation entry with forward and inverse operations.
@@ -208,40 +226,45 @@ export function createRemoveBlockEntry(
}
/**
* Creates a mock add-edge operation entry.
* Creates a mock batch-add-edges operation entry for a single edge.
*/
export function createAddEdgeEntry(edgeId: string, options: OperationEntryOptions = {}): any {
export function createAddEdgeEntry(
edgeId: string,
edgeSnapshot: any = null,
options: OperationEntryOptions = {}
): any {
const { id = nanoid(8), workflowId = 'wf-1', userId = 'user-1', createdAt = Date.now() } = options
const timestamp = Date.now()
const snapshot = edgeSnapshot || { id: edgeId, source: 'block-1', target: 'block-2' }
return {
id,
createdAt,
operation: {
id: nanoid(8),
type: 'add-edge',
type: 'batch-add-edges',
timestamp,
workflowId,
userId,
data: { edgeId },
data: { edgeSnapshots: [snapshot] },
},
inverse: {
id: nanoid(8),
type: 'remove-edge',
type: 'batch-remove-edges',
timestamp,
workflowId,
userId,
data: { edgeId, edgeSnapshot: null },
data: { edgeSnapshots: [snapshot] },
},
}
}
/**
* Creates a mock remove-edge operation entry.
* Creates a mock batch-remove-edges operation entry.
*/
export function createRemoveEdgeEntry(
edgeId: string,
edgeSnapshot: any = null,
export function createBatchRemoveEdgesEntry(
edgeSnapshots: any[],
options: OperationEntryOptions = {}
): any {
const { id = nanoid(8), workflowId = 'wf-1', userId = 'user-1', createdAt = Date.now() } = options
@@ -252,19 +275,19 @@ export function createRemoveEdgeEntry(
createdAt,
operation: {
id: nanoid(8),
type: 'remove-edge',
type: 'batch-remove-edges',
timestamp,
workflowId,
userId,
data: { edgeId, edgeSnapshot },
data: { edgeSnapshots },
},
inverse: {
id: nanoid(8),
type: 'add-edge',
type: 'batch-add-edges',
timestamp,
workflowId,
userId,
data: { edgeId },
data: { edgeSnapshots },
},
}
}
@@ -275,7 +298,7 @@ interface MoveBlockOptions extends OperationEntryOptions {
}
/**
* Creates a mock move-block operation entry.
* Creates a mock batch-move-blocks operation entry for a single block.
*/
export function createMoveBlockEntry(blockId: string, options: MoveBlockOptions = {}): any {
const {
@@ -293,19 +316,19 @@ export function createMoveBlockEntry(blockId: string, options: MoveBlockOptions
createdAt,
operation: {
id: nanoid(8),
type: 'move-block',
type: 'batch-move-blocks',
timestamp,
workflowId,
userId,
data: { blockId, before, after },
data: { moves: [{ blockId, before, after }] },
},
inverse: {
id: nanoid(8),
type: 'move-block',
type: 'batch-move-blocks',
timestamp,
workflowId,
userId,
data: { blockId, before: after, after: before },
data: { moves: [{ blockId, before: after, after: before }] },
},
}
}
@@ -361,3 +384,75 @@ export function createUpdateParentEntry(
},
}
}
interface BatchUpdateParentOptions extends OperationEntryOptions {
updates?: Array<{
blockId: string
oldParentId?: string
newParentId?: string
oldPosition?: { x: number; y: number }
newPosition?: { x: number; y: number }
affectedEdges?: any[]
}>
}
/**
* Creates a mock batch-update-parent operation entry.
*/
export function createBatchUpdateParentEntry(options: BatchUpdateParentOptions = {}): any {
const {
id = nanoid(8),
workflowId = 'wf-1',
userId = 'user-1',
createdAt = Date.now(),
updates = [
{
blockId: 'block-1',
oldParentId: undefined,
newParentId: 'loop-1',
oldPosition: { x: 0, y: 0 },
newPosition: { x: 50, y: 50 },
},
],
} = options
const timestamp = Date.now()
const processedUpdates = updates.map((u) => ({
blockId: u.blockId,
oldParentId: u.oldParentId,
newParentId: u.newParentId,
oldPosition: u.oldPosition || { x: 0, y: 0 },
newPosition: u.newPosition || { x: 50, y: 50 },
affectedEdges: u.affectedEdges,
}))
return {
id,
createdAt,
operation: {
id: nanoid(8),
type: 'batch-update-parent',
timestamp,
workflowId,
userId,
data: { updates: processedUpdates },
},
inverse: {
id: nanoid(8),
type: 'batch-update-parent',
timestamp,
workflowId,
userId,
data: {
updates: processedUpdates.map((u) => ({
blockId: u.blockId,
oldParentId: u.newParentId,
newParentId: u.oldParentId,
oldPosition: u.newPosition,
newPosition: u.oldPosition,
affectedEdges: u.affectedEdges,
})),
},
},
}
}