feat: Implement collapsible left and right panels with dynamic column spanning, updated styling, and integrated a bottom status bar in the DesignerRoot.

This commit is contained in:
2026-02-03 13:58:47 -05:00
parent 0ec63b3c97
commit 388897c70e
17 changed files with 1147 additions and 719 deletions

View File

@@ -158,3 +158,89 @@ export function convertActionToDatabase(
category: action.category,
};
}
// Reconstruct designer steps from database records
export function convertDatabaseToSteps(
dbSteps: any[] // Typing as any[] because Drizzle types are complex to import here without circular deps
): ExperimentStep[] {
// Paranoid Sort: Ensure steps are strictly ordered by index before assigning Triggers.
// This safeguards against API returning unsorted data.
const sortedSteps = [...dbSteps].sort((a, b) => (a.orderIndex ?? 0) - (b.orderIndex ?? 0));
return sortedSteps.map((dbStep, idx) => {
// console.log(`[block-converter] Step ${dbStep.name} OrderIndex:`, dbStep.orderIndex, dbStep.order_index);
return {
id: dbStep.id,
name: dbStep.name,
description: dbStep.description ?? undefined,
type: mapDatabaseToStepType(dbStep.type),
order: dbStep.orderIndex ?? idx, // Fallback to array index if missing
trigger: {
// Enforce Sequential Architecture: Validated by user requirement.
// Index 0 is Trial Start, all others are Previous Step.
type: idx === 0 ? "trial_start" : "previous_step",
conditions: (dbStep.conditions as Record<string, unknown>) || {},
},
expanded: true, // Default to expanded in designer
actions: (dbStep.actions || []).map((dbAction: any) =>
convertDatabaseToAction(dbAction)
),
};
});
}
function mapDatabaseToStepType(type: string): ExperimentStep["type"] {
switch (type) {
case "wizard":
return "sequential";
case "parallel":
return "parallel";
case "conditional":
return "conditional"; // Loop is also stored as conditional, distinction lost unless encoded in metadata
default:
return "sequential";
}
}
export function convertDatabaseToAction(dbAction: any): ExperimentAction {
// Reconstruct nested source object
const source: ExperimentAction["source"] = {
kind: (dbAction.sourceKind || dbAction.source_kind || "core") as "core" | "plugin",
pluginId: dbAction.pluginId || dbAction.plugin_id || undefined,
pluginVersion: dbAction.pluginVersion || dbAction.plugin_version || undefined,
robotId: dbAction.robotId || dbAction.robot_id || undefined,
baseActionId: dbAction.baseActionId || dbAction.base_action_id || undefined,
};
// Robust Inference: If properties are missing but Type suggests a plugin (e.g., "nao6-ros2.say_text"),
// assume/infer the pluginId to ensure validation passes.
if (dbAction.type && dbAction.type.includes(".") && !source.pluginId) {
const parts = dbAction.type.split(".");
if (parts.length === 2) {
source.kind = "plugin";
source.pluginId = parts[0];
// Fallback robotId if missing
if (!source.robotId) source.robotId = parts[0];
}
}
// Reconstruct execution object
const execution: ExecutionDescriptor = {
transport: dbAction.transport as ExecutionDescriptor["transport"],
ros2: dbAction.ros2 as ExecutionDescriptor["ros2"],
rest: dbAction.rest as ExecutionDescriptor["rest"],
retryable: dbAction.retryable ?? false,
};
return {
id: dbAction.id,
name: dbAction.name,
description: dbAction.description ?? undefined,
type: dbAction.type,
category: dbAction.category ?? "general",
parameters: (dbAction.parameters as Record<string, unknown>) || {},
source,
execution,
parameterSchemaRaw: dbAction.parameterSchemaRaw,
};
}

View File

@@ -119,26 +119,13 @@ export const TRIGGER_OPTIONS = [
];
// Step type options for UI
// IMPORTANT: Steps should ALWAYS execute sequentially
// Parallel execution, conditionals, and loops should be implemented via control flow ACTIONS
export const STEP_TYPE_OPTIONS = [
{
value: "sequential" as const,
label: "Sequential",
description: "Actions run one after another",
},
{
value: "parallel" as const,
label: "Parallel",
description: "Actions run at the same time",
},
{
value: "conditional" as const,
label: "Conditional",
description: "Actions run if condition is met",
},
{
value: "loop" as const,
label: "Loop",
description: "Actions repeat multiple times",
description: "Actions run one after another (enforced for all steps)",
},
];