/* eslint-disable @typescript-eslint/consistent-type-assertions */ import type { Registry, TableData } from "@sealcode/jdd"; import { List, Table } from "@sealcode/jdd"; import type { BaseContext } from "koa"; import { isTableData, isTableRegularRow } from "@sealcode/jdd"; import objectPath from "object-path"; import type { JDDPageState } from "./jdd-page.js"; import { registry } from "../../jdd-components/registry.js"; import { makeJDDContext } from "../../jdd-context.js"; function moveElement(array: Array, fromIndex: number, toIndex: number): Array { const element = array.splice(fromIndex, 1)[0]; array.splice(toIndex, 0, element); return array; } export function getComponentData( state: JDDPageState, arg_path: string[], registry: Registry ) { const index_arg = arg_path[1]; if (!index_arg) { throw new Error("Missing component index in arg path"); } const component_index = parseInt(index_arg); const component_args = state.components[component_index]?.args || {}; const component_name = state.components[component_index]?.component_name || ""; const component = registry.get(component_name); const arg_path_within_component = arg_path.slice(3); // remove "components" and the index of the component and "args" const [argument, , argument_value] = component?.getArgumentAtPath( arg_path_within_component, component_args ) || [null, null, null]; return { component_index, component_args, component_name, component, argument, argument_value, arg_path_within_component, }; } export const ComponentPreviewActions = { add_array_item: async ( ctx: BaseContext, state: JDDPageState, _: Record, arg_path: string[] ) => { const { component_name, component, argument, arg_path_within_component, argument_value, } = getComponentData(state, arg_path, registry); if (!component) { console.error("unknown component: ", component_name); return state; } if (!argument) { console.error( "Didn't find a list argument at this path", arg_path_within_component ); return state; } if (!(argument instanceof List)) { throw new Error( `Expected argument in path ${arg_path.join( "." )} to be an instance of List` ); } objectPath.insert( state, arg_path, await argument.item_type.getExampleValue(makeJDDContext(ctx)), // eslint-disable-next-line @typescript-eslint/consistent-type-assertions Array.isArray(argument_value) ? argument_value.length : 0 ); return state; }, remove_array_item: ( _ctx: BaseContext, state: JDDPageState, _: Record, arg_path: string[], index_to_remove: number ): JDDPageState => { objectPath.del(state, [...arg_path, index_to_remove]); return state; }, move_array_item_up: async ( _ctx: BaseContext, state: JDDPageState, _inputs: Record, arg_path: string[], element_index: number ): Promise => { const array_values = objectPath.get(state, arg_path); const curr = array_values[element_index]; const prev = array_values[element_index - 1]; if (!prev || !curr) { throw new Error("No element at such index or cannot move it up"); } [array_values[element_index - 1], array_values[element_index]] = [curr, prev]; return state; }, move_array_item_down: async ( _ctx: BaseContext, state: JDDPageState, _inputs: Record, arg_path: string[], element_index: number ): Promise => { const array_values = objectPath.get(state, arg_path); const curr = array_values[element_index]; const next = array_values[element_index + 1]; if (!next || !curr) { throw new Error("No element at such index or cannot move it up"); } [array_values[element_index], array_values[element_index + 1]] = [next, curr]; return state; }, change_component: async ( ctx: BaseContext, _state: JDDPageState, inputs: Record ): Promise => { const component_name = inputs.component; if (!component_name || typeof component_name !== "string") { throw new Error( "Missing input: 'component' for action change_component. It should contain the name of the new component type" ); } const component = registry.get(component_name); if (!component) { throw new Error(`Unknown or disallowed component name: ${component_name}`); } return { components: [ { component_name: component_name, args: (await component?.getExampleValues(makeJDDContext(ctx))) || {}, }, ], }; }, randomize_args: async ( ctx: BaseContext, state: JDDPageState, _inputs: Record, component_index_str: string ): Promise => { const { component_index, component } = getComponentData( state, ["components", component_index_str], registry ); const component_data = state.components[component_index]; if (!component_data) { throw new Error("Missing component data"); } component_data.args = (await component?.getExampleValues(makeJDDContext(ctx))) || {}; return { ...state, }; }, add_table_row: async ( ctx: BaseContext, state: JDDPageState, _: Record, arg_path: string[], columns: number, type: "header" | "row" = "row" ) => { const jdd_context = makeJDDContext(ctx); const { component_args, argument } = getComponentData(state, arg_path, registry); let row; if (!argument) { console.error("Unknown component at path", arg_path); return state; } if (!(argument instanceof Table)) { throw new Error( `Expected argument at path ${arg_path.join(".")} to be of type Table` ); } if (type == "header") { row = { type: "header", // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment header_content: await argument.header_type.getExampleValue(jdd_context), }; } else { const cells = []; for (let i = 0; i < columns; i++) { // eslint-disable-next-line no-await-in-loop cells.push(await argument.cell_type.getExampleValue(jdd_context)); } row = { type: "row", cells }; } objectPath.insert( state, [...arg_path, "rows"], row, // eslint-disable-next-line @typescript-eslint/consistent-type-assertions ((objectPath.get(component_args, [...arg_path, "rows"]) as unknown[]) || []) .length ); return state; }, add_table_column: async ( ctx: BaseContext, state: JDDPageState, _: Record, arg_path: string[] ) => { const { argument } = getComponentData(state, arg_path, registry); if (!argument) { console.error("Unknown component at path", arg_path); return state; } // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const tableData: TableData = objectPath.get(state, arg_path); if (!isTableData(tableData)) { throw new Error("wrong table data"); } // eslint-disable-next-line @typescript-eslint/no-for-in-array for (const i in tableData.rows) { const row = tableData.rows[i]; if (isTableRegularRow(row)) { // eslint-disable-next-line no-await-in-loop row.cells.push(await argument.getExampleValue(makeJDDContext(ctx))); } } objectPath.set(state, arg_path, tableData); return state; }, remove_table_column: ( _ctx: BaseContext, state: JDDPageState, _: Record, arg_path: string[], column_index_to_remove: number ): JDDPageState => { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const tableData: TableData = objectPath.get(state, arg_path); if (!isTableData(tableData)) { throw new Error("wrong table data"); } // eslint-disable-next-line @typescript-eslint/no-for-in-array for (const i in tableData.rows) { const row = tableData.rows[i]; if (isTableRegularRow(row)) { row.cells = row.cells.filter((_, i) => i != column_index_to_remove); } } objectPath.set(state, arg_path, tableData); return state; }, remove_table_row: ( _ctx: BaseContext, state: JDDPageState, _: Record, arg_path: string[], row_index: number ): JDDPageState => { objectPath.del(state, [...arg_path, "rows", row_index]); return state; }, move_table_column_right: ( _ctx: BaseContext, state: JDDPageState, _: Record, arg_path: string[], column_index: number ) => { const { component_args } = getComponentData(state, arg_path, registry); const last_path_element = arg_path.at(-1); if (!last_path_element) { throw new Error("arg path is empty"); } const data = objectPath.get(component_args, last_path_element, ""); if (!isTableData(data)) { throw new Error("Expected arg value for a table to be properly shaped"); } for (const row of data.rows) { if (row.type == "row") { moveElement(row.cells, column_index, column_index + 1); } } objectPath.set(state, [...arg_path, "rows"], data.rows); return state; }, move_table_row_down: ( _ctx: BaseContext, state: JDDPageState, _: Record, arg_path: string[], row_index: number ) => { const { component_args } = getComponentData(state, arg_path, registry); const last_path_element = arg_path.at(-1); if (!last_path_element) { throw new Error("arg path is empty"); } const data = objectPath.get(component_args, last_path_element, ""); if (!isTableData(data)) { throw new Error("Expected arg value for a table to be properly shaped"); } moveElement(data.rows, row_index, row_index + 1); objectPath.set(state, [...arg_path, "rows"], data.rows); return state; }, change_size: ( _ctx: BaseContext, state: JDDPageState, inputs: Record ) => { return { ...state, preview_size: inputs.size, }; }, add_component: async ( ctx: BaseContext, state: JDDPageState, inputs: Record ): Promise => { const component_name = inputs.component; if (!component_name) { throw new Error("Missing component name"); } const component = registry.get(component_name); return { ...state, components: [ ...state.components, { component_name: component_name, args: (await component?.getExampleValues(makeJDDContext(ctx))) || {}, }, ], }; }, remove_component: async ( _ctx: BaseContext, state: JDDPageState, _inputs: Record, component_index: number ): Promise => { const newComponentState = [...state.components]; newComponentState.splice(component_index, 1); return { ...state, components: newComponentState, }; }, move_component_up: async ( _ctx: BaseContext, state: JDDPageState, _inputs: Record, component_index: number ): Promise => { const newComps = [...state.components]; const prev = newComps[component_index - 1]; const curr = newComps[component_index]; if (!prev || !curr) { throw new Error("No component at such index or cannot move it up"); } [newComps[component_index], newComps[component_index - 1]] = [prev, curr]; return { ...state, components: newComps }; }, move_component_down: async ( _ctx: BaseContext, state: JDDPageState, _inputs: Record, component_index: number ): Promise => { const newComps = [...state.components]; const next = newComps[component_index + 1]; const curr = newComps[component_index]; if (!next || !curr) { throw new Error("No component at such index or cannot move it up"); } [newComps[component_index], newComps[component_index + 1]] = [next, curr]; return { ...state, components: newComps }; }, remove_file: async ( _ctx: BaseContext, state: JDDPageState, _: Record, arg_path: string[] ): Promise => { objectPath.set(state, arg_path, null); return state; }, };