Support for tables UI
1273
package-lock.json
generated
@ -35,8 +35,8 @@
|
||||
"@hotwired/turbo": "^8.0.2",
|
||||
"@koa/router": "^12.0.1",
|
||||
"@playwright/test": "^1.36.1",
|
||||
"@sealcode/jdd": "^0.3.0",
|
||||
"@sealcode/sealgen": "^0.13.0",
|
||||
"@sealcode/jdd": "^0.3.1",
|
||||
"@sealcode/sealgen": "^0.14.3",
|
||||
"@sealcode/ts-predicates": "^0.4.3",
|
||||
"@types/kill-port": "^2.0.0",
|
||||
"get-port": "^7.0.0",
|
||||
@ -46,7 +46,8 @@
|
||||
"multiple-scripts-tmux": "^1.0.4",
|
||||
"nodemon": "^3.0.1",
|
||||
"object-path": "^0.11.8",
|
||||
"sealious": "^0.17.48",
|
||||
"qs": "^6.12.0",
|
||||
"sealious": "^0.18.1",
|
||||
"stimulus": "^2.0.0",
|
||||
"tempstream": "^0.3.15",
|
||||
"unplugin-auto-import": "^0.17.5",
|
||||
|
@ -1,5 +1,5 @@
|
||||
import _locreq from "locreq";
|
||||
import Sealious, { App, LoggerMailer, SMTPMailer } from "sealious";
|
||||
import { App, LoggerMailer, SMTPMailer, Context as SealiousContext } from "sealious";
|
||||
import type { LoggerLevel } from "sealious/@types/src/app/logger.js";
|
||||
import { collections } from "./collections/collections.js";
|
||||
import {
|
||||
@ -17,7 +17,7 @@ const locreq = _locreq(module_dirname(import.meta.url));
|
||||
|
||||
declare module "koa" {
|
||||
interface BaseContext {
|
||||
$context: Sealious.Context;
|
||||
$context: SealiousContext;
|
||||
$app: TheApp;
|
||||
}
|
||||
}
|
||||
|
@ -3,11 +3,14 @@ import { Registry } from "@sealcode/jdd";
|
||||
|
||||
export const registry = new Registry();
|
||||
|
||||
import { MapWithPins } from "./map-with-pins/map-with-pins.jdd.js";
|
||||
registry.add("map-with-pins", MapWithPins);
|
||||
|
||||
import { ImageDemo } from "./image-demo/image-demo.jdd.js";
|
||||
registry.add("image-demo", ImageDemo);
|
||||
|
||||
import { MapWithPins } from "./map-with-pins/map-with-pins.jdd.js";
|
||||
registry.add("map-with-pins", MapWithPins);
|
||||
|
||||
import { NiceBox } from "./nice-box/nice-box.jdd.js";
|
||||
registry.add("nice-box", NiceBox);
|
||||
|
||||
import { Table } from "./table/table.jdd.js";
|
||||
registry.add("table", Table);
|
||||
|
2
src/back/jdd-components/table/table.css
Normal file
@ -0,0 +1,2 @@
|
||||
.table {
|
||||
}
|
63
src/back/jdd-components/table/table.jdd.tsx
Normal file
@ -0,0 +1,63 @@
|
||||
import { FlatTemplatable, TempstreamJSX } from "tempstream";
|
||||
import {
|
||||
Component,
|
||||
ComponentArguments,
|
||||
ExtractStructuredComponentArgumentsValues,
|
||||
isTableHeader,
|
||||
JDDContext,
|
||||
} from "@sealcode/jdd";
|
||||
|
||||
const component_arguments = {
|
||||
table: new ComponentArguments.Table(
|
||||
new ComponentArguments.ShortText(),
|
||||
new ComponentArguments.Structured({
|
||||
color: new ComponentArguments.Enum(["red", "blue", "green", "aquamarine"]),
|
||||
word: new ComponentArguments.ShortText().setExampleValues([
|
||||
"apple",
|
||||
"banana",
|
||||
"pineapple",
|
||||
"carrot",
|
||||
]),
|
||||
})
|
||||
),
|
||||
} as const;
|
||||
|
||||
export class Table extends Component<typeof component_arguments> {
|
||||
getArguments() {
|
||||
return component_arguments;
|
||||
}
|
||||
|
||||
toHTML({
|
||||
table,
|
||||
}: ExtractStructuredComponentArgumentsValues<
|
||||
typeof component_arguments
|
||||
>): FlatTemplatable {
|
||||
return (
|
||||
<div class="table">
|
||||
<table>
|
||||
<tbody>
|
||||
{table.rows.map((row) =>
|
||||
isTableHeader(row) ? (
|
||||
<tr>
|
||||
<th
|
||||
colspan={this.getArguments()
|
||||
.table.getColumnsCount(table)
|
||||
.toString()}
|
||||
>
|
||||
{row.header_content}
|
||||
</th>
|
||||
</tr>
|
||||
) : (
|
||||
<tr>
|
||||
{row.cells.map(({ color, word }) => (
|
||||
<td style={`color: ${color}`}>{word}</td>
|
||||
))}
|
||||
</tr>
|
||||
)
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@ -15,7 +15,7 @@ export async function ComponentInputList<State extends ComponentPreviewState, T>
|
||||
}: {
|
||||
state: State;
|
||||
arg_path: string[];
|
||||
arg: List<ComponentArgument<T>>;
|
||||
arg: List<T>;
|
||||
value: T[];
|
||||
page: StatefulPage<ComponentPreviewState, typeof ComponentPreviewActions>;
|
||||
}) {
|
||||
|
229
src/back/routes/component-preview/component-input-table.tsx
Normal file
@ -0,0 +1,229 @@
|
||||
import { ComponentArgument, isTableHeader, List, Table, TableData } from "@sealcode/jdd";
|
||||
import { StatefulPage } from "@sealcode/sealgen";
|
||||
import { TempstreamJSX } from "tempstream";
|
||||
import { ComponentPreviewState } from "../components.sreact.js";
|
||||
import { jdd_context } from "../jdd-context.js";
|
||||
import { ComponentInput } from "./component-input.js";
|
||||
import type { ComponentPreviewActions } from "./component-preview-actions.js";
|
||||
|
||||
import delete_column_icon from "./table-delete-column.svg";
|
||||
import add_row_below_icon from "./table-add-row-below.svg";
|
||||
import add_column_right_icon from "./table-add-column-right.svg";
|
||||
import delete_row_icon from "./table-delete-row.svg";
|
||||
import add_column_header_icon from "./table-add-row-header-below.svg";
|
||||
import move_column_right_icon from "./table-move-column-right.svg";
|
||||
import move_row_down_icon from "./table-move-row-down.svg";
|
||||
|
||||
export async function ComponentInputTable<
|
||||
State extends ComponentPreviewState,
|
||||
CellType,
|
||||
HeaderType
|
||||
>({
|
||||
state,
|
||||
arg_path,
|
||||
arg,
|
||||
value,
|
||||
page,
|
||||
}: {
|
||||
state: State;
|
||||
arg_path: string[];
|
||||
arg: Table<CellType, HeaderType>;
|
||||
value: TableData<CellType, HeaderType>;
|
||||
page: StatefulPage<ComponentPreviewState, typeof ComponentPreviewActions>;
|
||||
}) {
|
||||
if (!value) {
|
||||
value = arg.getEmptyValue();
|
||||
}
|
||||
const empty_cell_value = arg.cell_type.getExampleValue(jdd_context);
|
||||
const empty_header_value = arg.header_type.getExampleValue(jdd_context);
|
||||
|
||||
return (
|
||||
<fieldset>
|
||||
<legend>{arg_path.at(-1)}</legend>
|
||||
<div
|
||||
style={`max-width: calc(var(--resizable-column-width) - ${
|
||||
arg_path.length + 3
|
||||
} * 14px); overflow-x: auto; max-height: 500px; overflow-y: auto;`}
|
||||
>
|
||||
<table style="position: relative; /* necessary for sticky th*/">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td></td>
|
||||
{[...Array(arg.getColumnsCount(value)).keys()].map(
|
||||
(column_index) => (
|
||||
<th class="sticky sticky--top subdued">
|
||||
{page.makeActionButton(
|
||||
state,
|
||||
{
|
||||
action: "remove_table_column",
|
||||
label: "Remove column",
|
||||
content: /* HTML */ `<img
|
||||
width="20"
|
||||
height="20"
|
||||
src="${delete_column_icon.url}"
|
||||
/>`,
|
||||
},
|
||||
arg_path,
|
||||
column_index
|
||||
)}
|
||||
{column_index >= arg.getColumnsCount(value) - 1
|
||||
? ""
|
||||
: page.makeActionButton(
|
||||
state,
|
||||
{
|
||||
action: "move_table_column_right",
|
||||
label: "Move column to the right",
|
||||
content: /* HTML */ `<img
|
||||
width="20"
|
||||
height="20"
|
||||
src="${move_column_right_icon.url}"
|
||||
/>`,
|
||||
},
|
||||
arg_path,
|
||||
column_index
|
||||
)}
|
||||
</th>
|
||||
)
|
||||
)}
|
||||
</tr>
|
||||
{value.rows.map((row, row_index) => (
|
||||
<tr>
|
||||
<td class="sticky sticky--left subdued">
|
||||
<div style="display: flex; flex-flow: column; row-gap: 5px;">
|
||||
{page.makeActionButton(
|
||||
state,
|
||||
{
|
||||
action: "remove_table_row",
|
||||
label: "Remove row",
|
||||
content: /* HTML */ `<img
|
||||
width="20"
|
||||
height="20"
|
||||
src="${delete_row_icon.url}"
|
||||
/>`,
|
||||
},
|
||||
arg_path,
|
||||
row_index
|
||||
)}
|
||||
{page.makeActionButton(
|
||||
state,
|
||||
{
|
||||
action: "move_table_row_down",
|
||||
label: "Move this row down",
|
||||
content: /* HTML */ `<img
|
||||
width="20"
|
||||
height="20"
|
||||
src="${move_row_down_icon.url}"
|
||||
/>`,
|
||||
},
|
||||
arg_path,
|
||||
row_index
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
{isTableHeader(row) ? (
|
||||
<th colspan={arg.getColumnsCount(value).toString()}>
|
||||
<ComponentInput
|
||||
{...{
|
||||
state,
|
||||
arg_path: [
|
||||
...arg_path,
|
||||
"rows",
|
||||
row_index.toString(),
|
||||
"header_content",
|
||||
],
|
||||
arg: arg.header_type,
|
||||
value: row.header_content,
|
||||
page,
|
||||
}}
|
||||
/>
|
||||
</th>
|
||||
) : (
|
||||
row.cells.map((cell, cell_index) => (
|
||||
<td>
|
||||
<ComponentInput
|
||||
{...{
|
||||
state,
|
||||
arg_path: [
|
||||
...arg_path,
|
||||
"rows",
|
||||
row_index.toString(),
|
||||
"cells",
|
||||
cell_index.toString(),
|
||||
],
|
||||
arg: arg.cell_type,
|
||||
value: cell,
|
||||
page,
|
||||
}}
|
||||
/>
|
||||
</td>
|
||||
))
|
||||
)}
|
||||
{row_index == 0 ? (
|
||||
<td
|
||||
class="subdued"
|
||||
rowspan={value.rows.length.toString()}
|
||||
>
|
||||
{page.makeActionButton(
|
||||
state,
|
||||
{
|
||||
action: "add_table_column",
|
||||
label: "Add column",
|
||||
content: /* HTML */ `<img
|
||||
width="20"
|
||||
height="20"
|
||||
src="${add_column_right_icon.url}"
|
||||
/>`,
|
||||
},
|
||||
arg_path,
|
||||
empty_cell_value
|
||||
)}
|
||||
</td>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</tr>
|
||||
))}
|
||||
<tr>
|
||||
<td
|
||||
class="subdued"
|
||||
colspan={(arg.getColumnsCount(value) + 1).toString()}
|
||||
>
|
||||
{page.makeActionButton(
|
||||
state,
|
||||
{
|
||||
action: "add_table_row",
|
||||
label: "Add table row",
|
||||
content: /* HTML */ `<img
|
||||
width="20"
|
||||
height="20"
|
||||
src="${add_row_below_icon.url}"
|
||||
/>`,
|
||||
},
|
||||
arg_path,
|
||||
empty_cell_value,
|
||||
arg.getColumnsCount(value)
|
||||
)}
|
||||
{page.makeActionButton(
|
||||
state,
|
||||
{
|
||||
action: "add_table_row",
|
||||
label: "Add table header",
|
||||
content: /* HTML */ `<img
|
||||
width="20"
|
||||
height="20"
|
||||
src="${add_column_header_icon.url}"
|
||||
/>`,
|
||||
},
|
||||
arg_path,
|
||||
empty_header_value,
|
||||
arg.getColumnsCount(value),
|
||||
"header"
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</fieldset>
|
||||
);
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { Enum, Image, List, Structured } from "@sealcode/jdd";
|
||||
import { Enum, Image, List, Structured, Table, TableData } from "@sealcode/jdd";
|
||||
import { ComponentArgument } from "@sealcode/jdd";
|
||||
import { StatefulPage } from "@sealcode/sealgen";
|
||||
import { TempstreamJSX } from "tempstream";
|
||||
@ -7,6 +7,7 @@ import { ComponentInputEnum } from "./component-input-enum.js";
|
||||
import { ComponentInputImage } from "./component-input-image.js";
|
||||
import { ComponentInputList } from "./component-input-list.js";
|
||||
import { ComponentInputStructured } from "./component-input-structured.js";
|
||||
import { ComponentInputTable } from "./component-input-table.js";
|
||||
import { printArgPath } from "./print-arg-path.js";
|
||||
|
||||
export function ComponentInput<State extends ComponentPreviewState, T>({
|
||||
@ -59,6 +60,16 @@ export function ComponentInput<State extends ComponentPreviewState, T>({
|
||||
});
|
||||
}
|
||||
|
||||
if (arg instanceof Table) {
|
||||
return ComponentInputTable({
|
||||
state,
|
||||
arg_path,
|
||||
arg,
|
||||
value: value as TableData<unknown, unknown>,
|
||||
page,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<label>
|
||||
@ -67,7 +78,7 @@ export function ComponentInput<State extends ComponentPreviewState, T>({
|
||||
<textarea
|
||||
name={`$.component_args${printArgPath(arg_path)}`}
|
||||
onblur={page.rerender()}
|
||||
cols="70"
|
||||
cols="40"
|
||||
>
|
||||
{value as string}
|
||||
</textarea>
|
||||
@ -76,7 +87,7 @@ export function ComponentInput<State extends ComponentPreviewState, T>({
|
||||
type="text"
|
||||
name={`$.component_args${printArgPath(arg_path)}`}
|
||||
value={value as string}
|
||||
size="70"
|
||||
size="40"
|
||||
/>
|
||||
)}
|
||||
</label>
|
||||
|
@ -1,8 +1,16 @@
|
||||
import { isTableData, isTableRegularRow, TableData } from "@sealcode/jdd";
|
||||
import { is, predicates } from "@sealcode/ts-predicates";
|
||||
import objectPath from "object-path";
|
||||
import { registry } from "../../jdd-components/components.js";
|
||||
import type { ComponentPreviewState } from "../components.sreact.js";
|
||||
import { jdd_context } 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 const ComponentPreviewActions = <const>{
|
||||
add_array_item: (
|
||||
state: ComponentPreviewState,
|
||||
@ -58,4 +66,140 @@ export const ComponentPreviewActions = <const>{
|
||||
component_args: (await component?.getExampleValues(jdd_context)) || {},
|
||||
};
|
||||
},
|
||||
add_table_row: (
|
||||
state: ComponentPreviewState,
|
||||
_: Record<string, string>,
|
||||
arg_path: string[],
|
||||
empty_value: unknown,
|
||||
columns: number,
|
||||
type: "header" | "row" = "row"
|
||||
) => {
|
||||
const component_args = state.component_args;
|
||||
let row;
|
||||
if (type == "header") {
|
||||
row = { type: "header", header_content: empty_value };
|
||||
} else {
|
||||
const cells = [];
|
||||
for (let i = 0; i < columns; i++) {
|
||||
cells.push(empty_value);
|
||||
}
|
||||
row = { type: "row", cells };
|
||||
}
|
||||
objectPath.insert(
|
||||
component_args,
|
||||
[...arg_path, "rows"],
|
||||
row,
|
||||
((objectPath.get(component_args, [...arg_path, "rows"]) as unknown[]) || [])
|
||||
.length
|
||||
);
|
||||
const result = {
|
||||
...state,
|
||||
component_args,
|
||||
};
|
||||
return result;
|
||||
},
|
||||
add_table_column: (
|
||||
state: ComponentPreviewState,
|
||||
_: Record<string, string>,
|
||||
arg_path: string[],
|
||||
empty_value: unknown
|
||||
) => {
|
||||
const component_args = state.component_args;
|
||||
const tableData = objectPath.get(component_args, arg_path);
|
||||
if (!isTableData(tableData)) {
|
||||
throw new Error("wrong table data");
|
||||
}
|
||||
for (const i in tableData.rows) {
|
||||
const row = tableData.rows[i];
|
||||
if (isTableRegularRow(row)) {
|
||||
row.cells.push(empty_value);
|
||||
}
|
||||
}
|
||||
objectPath.set(component_args, arg_path, tableData);
|
||||
return {
|
||||
...state,
|
||||
component_args,
|
||||
};
|
||||
},
|
||||
|
||||
remove_table_column: (
|
||||
state: ComponentPreviewState,
|
||||
_: Record<string, string>,
|
||||
arg_path: string[],
|
||||
column_index_to_remove: number
|
||||
) => {
|
||||
const component_args = state.component_args;
|
||||
const tableData = objectPath.get(component_args, arg_path);
|
||||
if (!isTableData(tableData)) {
|
||||
throw new Error("wrong table data");
|
||||
}
|
||||
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(component_args, arg_path, tableData);
|
||||
return {
|
||||
...state,
|
||||
component_args,
|
||||
};
|
||||
},
|
||||
remove_table_row: (
|
||||
state: ComponentPreviewState,
|
||||
_: Record<string, string>,
|
||||
arg_path: string[],
|
||||
row_index: number
|
||||
) => {
|
||||
const component_args = state.component_args;
|
||||
objectPath.del(component_args, [...arg_path, "rows", row_index]);
|
||||
const result = {
|
||||
...state,
|
||||
component_args,
|
||||
};
|
||||
return result;
|
||||
},
|
||||
move_table_column_right: (
|
||||
state: ComponentPreviewState,
|
||||
_: Record<string, string>,
|
||||
arg_path: string[],
|
||||
column_index: number
|
||||
) => {
|
||||
const component_args = state.component_args;
|
||||
const data = objectPath.get(component_args, arg_path) as TableData<
|
||||
unknown,
|
||||
unknown
|
||||
>;
|
||||
for (const row of data.rows) {
|
||||
if (row.type == "row") {
|
||||
moveElement(row.cells, column_index, column_index + 1);
|
||||
}
|
||||
}
|
||||
objectPath.set(component_args, [...arg_path, "rows"], data.rows);
|
||||
const result = {
|
||||
...state,
|
||||
component_args,
|
||||
};
|
||||
return result;
|
||||
},
|
||||
|
||||
move_table_row_down: (
|
||||
state: ComponentPreviewState,
|
||||
_: Record<string, string>,
|
||||
arg_path: string[],
|
||||
row_index: number
|
||||
) => {
|
||||
const component_args = state.component_args;
|
||||
const data = objectPath.get(component_args, arg_path) as TableData<
|
||||
unknown,
|
||||
unknown
|
||||
>;
|
||||
moveElement(data.rows, row_index, row_index + 1);
|
||||
objectPath.set(component_args, [...arg_path, "rows"], data.rows);
|
||||
const result = {
|
||||
...state,
|
||||
component_args,
|
||||
};
|
||||
return result;
|
||||
},
|
||||
};
|
||||
|
50
src/back/routes/component-preview/table-add-column-right.svg
Normal file
@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
|
||||
<svg
|
||||
width="533.33331"
|
||||
height="533.33331"
|
||||
viewBox="0 0 15.999999 15.999999"
|
||||
fill="none"
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
sodipodi:docname="table-add-column-right.svg"
|
||||
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="namedview2"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="0.86875"
|
||||
inkscape:cx="265.89928"
|
||||
inkscape:cy="266.47482"
|
||||
inkscape:window-width="1536"
|
||||
inkscape:window-height="928"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg2" />
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M 0,16 H 16 V 0 H 0 Z m 1.5,-1.5 v -2 h 3 v 2 z m 4.5,0 v -13 h 8.5 v 13 z M 4.5,1.5 v 2.51959 h -3 V 1.5 Z m -3,4.01959 h 3 V 7.5 h -3 z M 1.5,9 h 3 v 2 h -3 z"
|
||||
fill="#1f2328"
|
||||
id="path1" />
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M 9.5,8.5 V 11 H 11 V 8.5 h 2.5001 V 7 H 11 V 4.5 H 9.5 V 7 H 7 v 1.5 z"
|
||||
fill="#1F2328"
|
||||
id="path2"
|
||||
style="fill:#009e00;fill-opacity:1" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
50
src/back/routes/component-preview/table-add-row-below.svg
Normal file
@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
|
||||
<svg
|
||||
width="533.33331"
|
||||
height="533.33331"
|
||||
viewBox="0 0 15.999999 15.999999"
|
||||
fill="none"
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
sodipodi:docname="table-add-row-below.svg"
|
||||
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="namedview2"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="0.86875"
|
||||
inkscape:cx="265.89928"
|
||||
inkscape:cy="266.47482"
|
||||
inkscape:window-width="1536"
|
||||
inkscape:window-height="928"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg2" />
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M 0,0 H 16 V 16 H 0 Z M 1.5,1.5 V 4 h 3 V 1.5 Z M 6,1.5 V 4 H 9.9999 L 10,1.5 Z m 5.5,0 -10e-5,2.5 H 14.5 V 1.5 Z m 3,4 h -13 v 9 h 13 z"
|
||||
fill="#1f2328"
|
||||
id="path1" />
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M 7.5,9.5 V 7 H 9 v 2.5 h 2.5001 V 11 H 9 v 2.5 H 7.5 V 11 H 5 V 9.5 Z"
|
||||
fill="#1F2328"
|
||||
id="path2"
|
||||
style="fill:#009e00;fill-opacity:1" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
@ -0,0 +1,70 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
|
||||
<svg
|
||||
width="533.33331"
|
||||
height="533.33331"
|
||||
viewBox="0 0 15.999999 15.999999"
|
||||
fill="none"
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
sodipodi:docname="table-add-row-header-below.svg"
|
||||
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="namedview2"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="0.86875"
|
||||
inkscape:cx="265.89928"
|
||||
inkscape:cy="266.47482"
|
||||
inkscape:window-width="1536"
|
||||
inkscape:window-height="928"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg2" />
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M 0,0 H 16 V 16 H 0 Z M 1.5,1.5 V 4 h 3 V 1.5 Z M 6,1.5 V 4 H 9.9999 L 10,1.5 Z m 5.5,0 -10e-5,2.5 H 14.5 V 1.5 Z m 3,4 h -13 v 9 h 13 z"
|
||||
fill="#1f2328"
|
||||
id="path1" />
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M 4.4849442,9.891467 V 7.692536 H 5.8043028 V 9.891467 H 8.003322 v 1.319359 H 5.8043028 v 2.198931 H 4.4849442 V 11.210826 H 2.286013 V 9.891467 Z"
|
||||
fill="#1F2328"
|
||||
id="path2"
|
||||
style="fill:#009e00;fill-opacity:1;stroke-width:0.879573" />
|
||||
<g
|
||||
id="g1"
|
||||
transform="translate(-3.2633543,-4)">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="m 12.442783,13.891467 v -2.198931 h 1.319359 v 2.198931 h 2.199019 v 1.319359 h -2.199019 v 2.198931 h -1.319359 v -2.198931 z"
|
||||
fill="#1F2328"
|
||||
id="path2-6"
|
||||
style="fill:#009e00;fill-opacity:1;stroke-width:0.879573"
|
||||
sodipodi:nodetypes="ccccccccccc" />
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M 16.758927,13.898931 V 11.7 h -1.319359 v 2.198931 h -2.199019 v 1.319359 h 2.199019 v 2.198931 h 1.319359 V 15.21829 Z"
|
||||
fill="#1F2328"
|
||||
id="path2-6-7"
|
||||
style="fill:#009e00;fill-opacity:1;stroke-width:0.879573"
|
||||
sodipodi:nodetypes="ccccccccccc" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.4 KiB |
50
src/back/routes/component-preview/table-delete-column.svg
Normal file
@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
|
||||
<svg
|
||||
width="566.59668"
|
||||
height="533.33331"
|
||||
viewBox="0 0 16.9979 15.999999"
|
||||
fill="none"
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
sodipodi:docname="table-delete-column.svg"
|
||||
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="namedview2"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="0.86875"
|
||||
inkscape:cx="265.89928"
|
||||
inkscape:cy="266.47482"
|
||||
inkscape:window-width="1536"
|
||||
inkscape:window-height="928"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg2" />
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="m 16,12 v 4 H 0 V 0 H 16 V 4 H 14.5 V 1.5 H 11 v 13 h 3.5 V 12 Z M 1.5,12.5 v 2 h 3 v -2 z m 4.5,0 v 2 h 3.5 v -2 z M 9.5,4.01957 V 1.5 H 6 v 2.51958 z m -5,1e-5 V 1.5 h -3 v 2.51959 z m 0,1.5 -3,10e-6 V 7.5 h 3 z M 4.5,9 h -3 v 2 h 3 z M 6,9 v 2 H 9.5 V 9 Z M 6,7.5 H 9.5 V 5.51957 l -3.5,1e-5 z"
|
||||
fill="#1f2328"
|
||||
id="path1" />
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M 14.7176,9.0606 15.9373,10.2803 16.9979,9.2197 15.7782,8 16.9979,6.7803 15.9372,5.71967 14.7176,6.9393 13.4979,5.71968 12.4373,6.7803 13.6569,8 l -1.2196,1.2196 1.0606,1.0607 z"
|
||||
fill="#1F2328"
|
||||
id="path2"
|
||||
style="fill:#ff0000;fill-opacity:1" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
50
src/back/routes/component-preview/table-delete-row.svg
Normal file
@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
|
||||
<svg
|
||||
width="533.33331"
|
||||
height="533.33667"
|
||||
viewBox="0 0 15.999999 16.0001"
|
||||
fill="none"
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
sodipodi:docname="table-delete-row.svg"
|
||||
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="namedview2"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="0.86875"
|
||||
inkscape:cx="265.89928"
|
||||
inkscape:cy="266.47482"
|
||||
inkscape:window-width="1536"
|
||||
inkscape:window-height="928"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg2" />
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M 16,0 H 0 V 16 H 4 V 14.5 H 1.5 V 9 h 13 v 5.5 H 12 V 16 h 4 z M 1.5,4 V 1.5 h 3 V 4 Z M 6,4 V 1.5 h 4 L 9.9999,4 Z M 11.4999,4 11.5,1.5 h 3 V 4 Z m 0,1.5 H 14.5 v 2 H 11.4998 Z M 4.5,7.5 h -3 v -2 h 3 z m 1.5,0 v -2 h 3.9999 l -10e-5,2 z"
|
||||
fill="#1f2328"
|
||||
id="path1" />
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M 8,14.7804 9.2197,16.0001 10.2803,14.9395 9.0606,13.7197 10.2802,12.5001 9.2196,11.4395 8,12.6591 6.7803,11.4395 5.71968,12.5001 6.9393,13.7197 5.71967,14.9394 6.7803,16.0001 Z"
|
||||
fill="#1F2328"
|
||||
id="path2"
|
||||
style="fill:#ff0000;fill-opacity:1" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
|
||||
<svg
|
||||
fill="#000000"
|
||||
width="800px"
|
||||
height="800px"
|
||||
viewBox="0 0 14 14"
|
||||
role="img"
|
||||
focusable="false"
|
||||
aria-hidden="true"
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
sodipodi:docname="gui-table-col-after-svgrepo-com.svg"
|
||||
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="namedview2"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="0.86875"
|
||||
inkscape:cx="399.42446"
|
||||
inkscape:cy="400"
|
||||
inkscape:window-width="1536"
|
||||
inkscape:window-height="928"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg2" />
|
||||
<path
|
||||
d="M.52539062.00195313C.23546962.00195313 0 .17085937 0 .38085938V13.623047c0 .2101.23546962.378906.52539062.378906H5.3164062c.2900214 0 .5234375-.168806.5234376-.378906V.38085938c0-.21-.2334162-.37890626-.5234376-.37890626H.52539062zM1.2089844 1.0605469h3.4199218v3.2148437H1.2089844V1.0605469zm0 4.2832031h3.4199218v3.2148438H1.2089844V5.34375zm0 4.2832031h3.4199218V12.84375H1.2089844V9.6269531z"
|
||||
id="path1" />
|
||||
<path
|
||||
fill="green"
|
||||
d="M9.969294 11.0336q-.2639283 0-.4656736-.1915l-.3880946-.3882q-.1964467-.1966-.1964467-.4709 0-.2795.1964467-.4658l1.5159882-1.5217H6.988903q-.269027 0-.4370814-.194-.1682543-.1941-.1682543-.4684v-.6625q0-.2743.1682543-.4684.1680544-.1941.4370814-.1941h3.642611L9.1155258 4.4865q-.1964467-.1863-.1964467-.4658 0-.2795.1964467-.4658l.3880946-.3882q.1966467-.1966.4656736-.1966.274126 0 .470872.1966l3.368286 3.3693Q14 6.7172 14 7.0018q0 .2795-.191548.471l-3.368286 3.3693q-.201845.1915-.470872.1915z"
|
||||
id="path2"
|
||||
style="fill:#0086be;fill-opacity:1" />
|
||||
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
48
src/back/routes/component-preview/table-move-row-down.svg
Normal file
@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
|
||||
<svg
|
||||
fill="#000000"
|
||||
width="800"
|
||||
height="800"
|
||||
viewBox="0 0 14 14"
|
||||
role="img"
|
||||
focusable="false"
|
||||
aria-hidden="true"
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
sodipodi:docname="table-move-row-up.svg"
|
||||
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="namedview2"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="0.86875"
|
||||
inkscape:cx="399.42446"
|
||||
inkscape:cy="400"
|
||||
inkscape:window-width="1536"
|
||||
inkscape:window-height="928"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg2" />
|
||||
<path
|
||||
d="M 0,0.525391 C 0,0.23547 0.16890625,0 0.37890626,0 H 13.621094 C 13.831194,0 14,0.23547 14,0.525391 v 4.7910152 c 0,0.2900214 -0.168806,0.5234375 -0.378906,0.5234376 H 0.37890626 C 0.16890626,5.8398438 0,5.6064276 0,5.3164062 Z M 1.0585937,1.208984 V 4.6289062 H 4.2734374 V 1.208984 Z m 4.2832031,0 V 4.6289062 H 8.5566406 V 1.208984 Z m 4.2832031,0 V 4.6289062 H 12.841797 V 1.208984 Z"
|
||||
id="path1" />
|
||||
<path
|
||||
fill="green"
|
||||
d="m 11.031647,9.969294 q 0,-0.2639283 -0.1915,-0.4656736 l -0.3882,-0.3880946 q -0.1966,-0.1964467 -0.4709002,-0.1964467 -0.2795,0 -0.4658,0.1964467 l -1.5217,1.5159882 V 6.988903 q 0,-0.269027 -0.194,-0.4370814 -0.1941,-0.1682543 -0.4684,-0.1682543 h -0.6625 q -0.2743,0 -0.4684,0.1682543 -0.1941,0.1680544 -0.1941,0.4370814 v 3.642611 l -1.5216,-1.5159882 q -0.1863,-0.1964467 -0.4658,-0.1964467 -0.2795,0 -0.4658,0.1964467 l -0.3882,0.3880946 q -0.1966,0.1966467 -0.1966,0.4656736 0,0.274126 0.1966,0.470872 l 3.3693,3.368286 Q 6.7152468,14 6.9998468,14 q 0.2795,0 0.471,-0.191548 l 3.3693002,-3.368286 q 0.1915,-0.201845 0.1915,-0.470872 z"
|
||||
id="path2"
|
||||
style="fill:#0086be;fill-opacity:1" />
|
||||
</svg>
|
After Width: | Height: | Size: 2.3 KiB |
@ -29,6 +29,36 @@
|
||||
.component-preview-parameters {
|
||||
fieldset {
|
||||
background-color: #80808024;
|
||||
|
||||
table {
|
||||
td,
|
||||
th {
|
||||
outline: 0.5px solid #0000006b;
|
||||
|
||||
&.subdued > * {
|
||||
opacity: 50%;
|
||||
}
|
||||
|
||||
&.subdued:hover > * {
|
||||
opacity: 100%;
|
||||
}
|
||||
|
||||
&.sticky {
|
||||
position: sticky;
|
||||
background-color: #ececec;
|
||||
}
|
||||
|
||||
&.sticky--left {
|
||||
left: 0;
|
||||
box-shadow: 5px 0px 10px -4px #00000047;
|
||||
}
|
||||
|
||||
&.sticky--top {
|
||||
top: 0;
|
||||
box-shadow: 0px 5px 10px -4px #00000047;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,12 +1,6 @@
|
||||
import {
|
||||
ComponentArgument,
|
||||
List,
|
||||
render,
|
||||
simpleJDDContext,
|
||||
Structured,
|
||||
} from "@sealcode/jdd";
|
||||
import { render } from "@sealcode/jdd";
|
||||
import { StateAndMetadata, StatefulPage, to_base64 } from "@sealcode/sealgen";
|
||||
import { hasFieldOfType, hasShape, is, predicates } from "@sealcode/ts-predicates";
|
||||
import { hasFieldOfType, hasShape, predicates } from "@sealcode/ts-predicates";
|
||||
import { BaseContext } from "koa";
|
||||
import { Templatable, TempstreamJSX } from "tempstream";
|
||||
import html from "../html.js";
|
||||
@ -17,149 +11,6 @@ import { jdd_context } from "./jdd-context.js";
|
||||
|
||||
export const actionName = "Components";
|
||||
|
||||
function id<X>(_: unknown, __: unknown, x: X): X {
|
||||
return x;
|
||||
}
|
||||
|
||||
function isSealiousFile(x: unknown): x is { data: { path: string } } {
|
||||
return hasShape(
|
||||
{
|
||||
getDataPath: predicates.any,
|
||||
data: predicates.shape({ path: predicates.string }),
|
||||
},
|
||||
x
|
||||
);
|
||||
}
|
||||
|
||||
async function encodeSealiousFile(maybe_file: Record<string, unknown>) {
|
||||
if (isSealiousFile(maybe_file)) {
|
||||
return simpleJDDContext.encode_file(
|
||||
{
|
||||
type: "path",
|
||||
// asserting that this is an instance of sealious' FileFromPath
|
||||
path: maybe_file.data.path,
|
||||
},
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const componentArgToRequestProcessor: Record<
|
||||
string,
|
||||
(
|
||||
arg: ComponentArgument<unknown>,
|
||||
arg_name: string,
|
||||
value: unknown
|
||||
) => Promise<unknown>
|
||||
> = {
|
||||
list: async function (
|
||||
arg: List<ComponentArgument<unknown>>,
|
||||
arg_name,
|
||||
value: unknown
|
||||
) {
|
||||
if (
|
||||
!is(value, predicates.array(predicates.object)) &&
|
||||
!is(value, predicates.object)
|
||||
) {
|
||||
throw new Error(`$.${arg_name} is not a list or object`);
|
||||
}
|
||||
const values = Array.isArray(value) ? value : Object.values(value);
|
||||
const nested_arg_type = arg.item_type;
|
||||
let array_result: Array<unknown> = await Promise.all(
|
||||
values.map(async (value, index) => {
|
||||
const result = await (
|
||||
componentArgToRequestProcessor[nested_arg_type.getTypeName()] || id
|
||||
)(nested_arg_type, `${arg_name}[${index}]`, value);
|
||||
return result;
|
||||
})
|
||||
);
|
||||
if (nested_arg_type.getTypeName() != "list") {
|
||||
array_result = array_result.flat();
|
||||
}
|
||||
return array_result;
|
||||
},
|
||||
structured: async function (
|
||||
arg: Structured<Record<string, ComponentArgument<unknown>>>,
|
||||
arg_name,
|
||||
value
|
||||
) {
|
||||
if (!is(value, predicates.object)) {
|
||||
throw new Error(`${arg_name} is not an object`);
|
||||
}
|
||||
const result: Record<string, unknown> = {};
|
||||
await Promise.all(
|
||||
Object.entries(value).map(async ([obj_key, obj_value]) => {
|
||||
const nested_arg_type: ComponentArgument<unknown> =
|
||||
arg.structure[obj_key];
|
||||
if (!nested_arg_type) {
|
||||
return [obj_key, null];
|
||||
}
|
||||
const new_value = await (
|
||||
componentArgToRequestProcessor[nested_arg_type.getTypeName()] || id
|
||||
)(arg, `${arg_name}[${obj_key}]`, obj_value);
|
||||
result[obj_key] = new_value;
|
||||
})
|
||||
);
|
||||
|
||||
// if we're in a list and any of the values return an array, we will multiply the object
|
||||
if (arg.hasParent("list")) {
|
||||
const keys_with_unexpected_arrays = Object.entries(result)
|
||||
.filter(([key, value]) => {
|
||||
const nested_arg_type: ComponentArgument<unknown> =
|
||||
arg.structure[key];
|
||||
return (
|
||||
nested_arg_type.getTypeName() !== "list" && Array.isArray(value)
|
||||
);
|
||||
})
|
||||
.map(([key]) => key);
|
||||
|
||||
if (keys_with_unexpected_arrays.length > 1) {
|
||||
throw new Error(
|
||||
"Multiplying on multiple fields at the same time is not implemented yet"
|
||||
);
|
||||
}
|
||||
if (keys_with_unexpected_arrays.length == 1) {
|
||||
const key = keys_with_unexpected_arrays[0];
|
||||
const old_result = result;
|
||||
const array = old_result[key];
|
||||
if (!Array.isArray(array)) {
|
||||
throw new Error("expected an array");
|
||||
}
|
||||
|
||||
return array.map((value: unknown) => ({
|
||||
...old_result,
|
||||
[key]: value,
|
||||
}));
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
},
|
||||
image: async function (arg, _, value: unknown) {
|
||||
if (
|
||||
!hasShape(
|
||||
{
|
||||
new: predicates.maybe(predicates.array(predicates.object)),
|
||||
old: predicates.string,
|
||||
},
|
||||
value
|
||||
)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
const files = (value.new || []).filter((e) => e);
|
||||
if (files.length == 0) {
|
||||
return value.old;
|
||||
} else if (files.length == 1) {
|
||||
return encodeSealiousFile(files[0]);
|
||||
} else if (arg.hasParent("list")) {
|
||||
return Promise.all(files.map(encodeSealiousFile));
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export type ComponentPreviewState = {
|
||||
component: string;
|
||||
component_args: Record<string, unknown>;
|
||||
@ -203,21 +54,11 @@ export default new (class ComponentsPage extends StatefulPage<
|
||||
);
|
||||
}
|
||||
|
||||
async preprocessRequestBody<
|
||||
T extends StateAndMetadata<ComponentPreviewState, typeof ComponentPreviewActions>
|
||||
>(values: Record<string, unknown>): Promise<T> {
|
||||
const old_component = hasFieldOfType(values, "component", predicates.string)
|
||||
? values.component
|
||||
: null;
|
||||
|
||||
const new_component = hasShape(
|
||||
{ $: predicates.shape({ component: predicates.string }) },
|
||||
values
|
||||
)
|
||||
? values.$.component
|
||||
: null;
|
||||
|
||||
const component_name = new_component || old_component;
|
||||
async preprocessOverrides(
|
||||
state: ComponentPreviewState,
|
||||
overrides: Record<string, unknown>
|
||||
) {
|
||||
const component_name = state.component;
|
||||
if (!component_name) {
|
||||
throw new Error("Unspecified component name");
|
||||
}
|
||||
@ -225,30 +66,25 @@ export default new (class ComponentsPage extends StatefulPage<
|
||||
if (!component) {
|
||||
throw new Error(`Unknown component: ${component_name}`);
|
||||
}
|
||||
if (
|
||||
!hasShape(
|
||||
{ $: predicates.shape({ component_args: predicates.object }) },
|
||||
values
|
||||
)
|
||||
) {
|
||||
// no component args to overwrite
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
return values as T;
|
||||
if (!hasShape({ component_args: predicates.object }, overrides)) {
|
||||
return overrides;
|
||||
}
|
||||
const promises = Object.entries(component.getArguments()).map(
|
||||
async ([arg_name, arg]) => {
|
||||
const value = values.$.component_args[arg_name];
|
||||
const value = overrides.component_args[arg_name];
|
||||
if (value) {
|
||||
const new_value = await (
|
||||
componentArgToRequestProcessor[arg.getTypeName()] || id
|
||||
)(arg, arg_name, value);
|
||||
values.$.component_args[arg_name] = new_value;
|
||||
const new_value = await arg.parseFormInput(
|
||||
jdd_context,
|
||||
value,
|
||||
arg_name
|
||||
);
|
||||
overrides.component_args[arg_name] = new_value;
|
||||
}
|
||||
}
|
||||
);
|
||||
await Promise.all(promises);
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
return values as T;
|
||||
return overrides;
|
||||
}
|
||||
|
||||
render(_ctx: BaseContext, state: ComponentPreviewState) {
|
||||
@ -256,7 +92,11 @@ export default new (class ComponentsPage extends StatefulPage<
|
||||
const component =
|
||||
registry.get(state.component) || Object.values(all_components)[0];
|
||||
return (
|
||||
<div class="two-column" id="component-debugger">
|
||||
<div
|
||||
class="two-column"
|
||||
id="component-debugger"
|
||||
style="--resizable-column-width: 50vw"
|
||||
>
|
||||
<div class="resizable">
|
||||
{/*The below button has to be here in order for it to be the default behavior */}
|
||||
<input type="submit" value="Preview" />
|
||||
@ -309,7 +149,9 @@ export default new (class ComponentsPage extends StatefulPage<
|
||||
origin_width + (e.clientX - origin_x),
|
||||
1
|
||||
);
|
||||
document.documentElement.style.setProperty(
|
||||
document
|
||||
.getElementById("component-debugger")
|
||||
.style.setProperty(
|
||||
"--resizable-column-width",
|
||||
new_width + "px"
|
||||
);
|
||||
|
@ -3,6 +3,7 @@
|
||||
@import "../node_modules/@sealcode/sealgen/src/forms/forms.css";
|
||||
@import "back/jdd-components/image-demo/image-demo.css";
|
||||
@import "back/jdd-components/map-with-pins/map-with-pins.css";
|
||||
@import "back/jdd-components/table/table.css";
|
||||
@import "back/routes/common/ui/input.css";
|
||||
@import "back/routes/components.css";
|
||||
@import "colors.css";
|
||||
|