425 lines
12 KiB
TypeScript
425 lines
12 KiB
TypeScript
/* 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<T>(array: Array<T>, fromIndex: number, toIndex: number): Array<T> {
|
|
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 = <const>{
|
|
add_array_item: async (
|
|
ctx: BaseContext,
|
|
state: JDDPageState,
|
|
_: Record<string, string>,
|
|
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<string, string>,
|
|
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<string, string>,
|
|
arg_path: string[],
|
|
element_index: number
|
|
): Promise<JDDPageState> => {
|
|
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<string, string>,
|
|
arg_path: string[],
|
|
element_index: number
|
|
): Promise<JDDPageState> => {
|
|
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<string, unknown>
|
|
): Promise<JDDPageState> => {
|
|
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<string, string>,
|
|
component_index_str: string
|
|
): Promise<JDDPageState> => {
|
|
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<string, string>,
|
|
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<string, string>,
|
|
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<unknown, unknown> = 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<string, string>,
|
|
arg_path: string[],
|
|
column_index_to_remove: number
|
|
): JDDPageState => {
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
const tableData: TableData<unknown, unknown> = 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<string, string>,
|
|
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<string, string>,
|
|
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<unknown>(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<string, string>,
|
|
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<unknown>(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<string, string>
|
|
) => {
|
|
return {
|
|
...state,
|
|
preview_size: inputs.size,
|
|
};
|
|
},
|
|
|
|
add_component: async (
|
|
ctx: BaseContext,
|
|
state: JDDPageState,
|
|
inputs: Record<string, string>
|
|
): Promise<JDDPageState> => {
|
|
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<string, string>,
|
|
component_index: number
|
|
): Promise<JDDPageState> => {
|
|
const newComponentState = [...state.components];
|
|
newComponentState.splice(component_index, 1);
|
|
return {
|
|
...state,
|
|
components: newComponentState,
|
|
};
|
|
},
|
|
|
|
move_component_up: async (
|
|
_ctx: BaseContext,
|
|
state: JDDPageState,
|
|
_inputs: Record<string, string>,
|
|
component_index: number
|
|
): Promise<JDDPageState> => {
|
|
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<string, string>,
|
|
component_index: number
|
|
): Promise<JDDPageState> => {
|
|
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<string, string>,
|
|
arg_path: string[]
|
|
): Promise<JDDPageState> => {
|
|
objectPath.set(state, arg_path, null);
|
|
return state;
|
|
},
|
|
};
|