Support for tables UI
1273
package-lock.json
generated
@ -35,8 +35,8 @@
|
|||||||
"@hotwired/turbo": "^8.0.2",
|
"@hotwired/turbo": "^8.0.2",
|
||||||
"@koa/router": "^12.0.1",
|
"@koa/router": "^12.0.1",
|
||||||
"@playwright/test": "^1.36.1",
|
"@playwright/test": "^1.36.1",
|
||||||
"@sealcode/jdd": "^0.3.0",
|
"@sealcode/jdd": "^0.3.1",
|
||||||
"@sealcode/sealgen": "^0.13.0",
|
"@sealcode/sealgen": "^0.14.3",
|
||||||
"@sealcode/ts-predicates": "^0.4.3",
|
"@sealcode/ts-predicates": "^0.4.3",
|
||||||
"@types/kill-port": "^2.0.0",
|
"@types/kill-port": "^2.0.0",
|
||||||
"get-port": "^7.0.0",
|
"get-port": "^7.0.0",
|
||||||
@ -46,7 +46,8 @@
|
|||||||
"multiple-scripts-tmux": "^1.0.4",
|
"multiple-scripts-tmux": "^1.0.4",
|
||||||
"nodemon": "^3.0.1",
|
"nodemon": "^3.0.1",
|
||||||
"object-path": "^0.11.8",
|
"object-path": "^0.11.8",
|
||||||
"sealious": "^0.17.48",
|
"qs": "^6.12.0",
|
||||||
|
"sealious": "^0.18.1",
|
||||||
"stimulus": "^2.0.0",
|
"stimulus": "^2.0.0",
|
||||||
"tempstream": "^0.3.15",
|
"tempstream": "^0.3.15",
|
||||||
"unplugin-auto-import": "^0.17.5",
|
"unplugin-auto-import": "^0.17.5",
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import _locreq from "locreq";
|
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 type { LoggerLevel } from "sealious/@types/src/app/logger.js";
|
||||||
import { collections } from "./collections/collections.js";
|
import { collections } from "./collections/collections.js";
|
||||||
import {
|
import {
|
||||||
@ -17,7 +17,7 @@ const locreq = _locreq(module_dirname(import.meta.url));
|
|||||||
|
|
||||||
declare module "koa" {
|
declare module "koa" {
|
||||||
interface BaseContext {
|
interface BaseContext {
|
||||||
$context: Sealious.Context;
|
$context: SealiousContext;
|
||||||
$app: TheApp;
|
$app: TheApp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,11 +3,14 @@ import { Registry } from "@sealcode/jdd";
|
|||||||
|
|
||||||
export const registry = new Registry();
|
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";
|
import { ImageDemo } from "./image-demo/image-demo.jdd.js";
|
||||||
registry.add("image-demo", ImageDemo);
|
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";
|
import { NiceBox } from "./nice-box/nice-box.jdd.js";
|
||||||
registry.add("nice-box", NiceBox);
|
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;
|
state: State;
|
||||||
arg_path: string[];
|
arg_path: string[];
|
||||||
arg: List<ComponentArgument<T>>;
|
arg: List<T>;
|
||||||
value: T[];
|
value: T[];
|
||||||
page: StatefulPage<ComponentPreviewState, typeof ComponentPreviewActions>;
|
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 { ComponentArgument } from "@sealcode/jdd";
|
||||||
import { StatefulPage } from "@sealcode/sealgen";
|
import { StatefulPage } from "@sealcode/sealgen";
|
||||||
import { TempstreamJSX } from "tempstream";
|
import { TempstreamJSX } from "tempstream";
|
||||||
@ -7,6 +7,7 @@ import { ComponentInputEnum } from "./component-input-enum.js";
|
|||||||
import { ComponentInputImage } from "./component-input-image.js";
|
import { ComponentInputImage } from "./component-input-image.js";
|
||||||
import { ComponentInputList } from "./component-input-list.js";
|
import { ComponentInputList } from "./component-input-list.js";
|
||||||
import { ComponentInputStructured } from "./component-input-structured.js";
|
import { ComponentInputStructured } from "./component-input-structured.js";
|
||||||
|
import { ComponentInputTable } from "./component-input-table.js";
|
||||||
import { printArgPath } from "./print-arg-path.js";
|
import { printArgPath } from "./print-arg-path.js";
|
||||||
|
|
||||||
export function ComponentInput<State extends ComponentPreviewState, T>({
|
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 (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<label>
|
<label>
|
||||||
@ -67,7 +78,7 @@ export function ComponentInput<State extends ComponentPreviewState, T>({
|
|||||||
<textarea
|
<textarea
|
||||||
name={`$.component_args${printArgPath(arg_path)}`}
|
name={`$.component_args${printArgPath(arg_path)}`}
|
||||||
onblur={page.rerender()}
|
onblur={page.rerender()}
|
||||||
cols="70"
|
cols="40"
|
||||||
>
|
>
|
||||||
{value as string}
|
{value as string}
|
||||||
</textarea>
|
</textarea>
|
||||||
@ -76,7 +87,7 @@ export function ComponentInput<State extends ComponentPreviewState, T>({
|
|||||||
type="text"
|
type="text"
|
||||||
name={`$.component_args${printArgPath(arg_path)}`}
|
name={`$.component_args${printArgPath(arg_path)}`}
|
||||||
value={value as string}
|
value={value as string}
|
||||||
size="70"
|
size="40"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</label>
|
</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 objectPath from "object-path";
|
||||||
import { registry } from "../../jdd-components/components.js";
|
import { registry } from "../../jdd-components/components.js";
|
||||||
import type { ComponentPreviewState } from "../components.sreact.js";
|
import type { ComponentPreviewState } from "../components.sreact.js";
|
||||||
import { jdd_context } from "../jdd-context.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>{
|
export const ComponentPreviewActions = <const>{
|
||||||
add_array_item: (
|
add_array_item: (
|
||||||
state: ComponentPreviewState,
|
state: ComponentPreviewState,
|
||||||
@ -58,4 +66,140 @@ export const ComponentPreviewActions = <const>{
|
|||||||
component_args: (await component?.getExampleValues(jdd_context)) || {},
|
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 {
|
.component-preview-parameters {
|
||||||
fieldset {
|
fieldset {
|
||||||
background-color: #80808024;
|
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 {
|
import { render } from "@sealcode/jdd";
|
||||||
ComponentArgument,
|
|
||||||
List,
|
|
||||||
render,
|
|
||||||
simpleJDDContext,
|
|
||||||
Structured,
|
|
||||||
} from "@sealcode/jdd";
|
|
||||||
import { StateAndMetadata, StatefulPage, to_base64 } from "@sealcode/sealgen";
|
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 { BaseContext } from "koa";
|
||||||
import { Templatable, TempstreamJSX } from "tempstream";
|
import { Templatable, TempstreamJSX } from "tempstream";
|
||||||
import html from "../html.js";
|
import html from "../html.js";
|
||||||
@ -17,149 +11,6 @@ import { jdd_context } from "./jdd-context.js";
|
|||||||
|
|
||||||
export const actionName = "Components";
|
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 = {
|
export type ComponentPreviewState = {
|
||||||
component: string;
|
component: string;
|
||||||
component_args: Record<string, unknown>;
|
component_args: Record<string, unknown>;
|
||||||
@ -203,21 +54,11 @@ export default new (class ComponentsPage extends StatefulPage<
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async preprocessRequestBody<
|
async preprocessOverrides(
|
||||||
T extends StateAndMetadata<ComponentPreviewState, typeof ComponentPreviewActions>
|
state: ComponentPreviewState,
|
||||||
>(values: Record<string, unknown>): Promise<T> {
|
overrides: Record<string, unknown>
|
||||||
const old_component = hasFieldOfType(values, "component", predicates.string)
|
) {
|
||||||
? values.component
|
const component_name = state.component;
|
||||||
: null;
|
|
||||||
|
|
||||||
const new_component = hasShape(
|
|
||||||
{ $: predicates.shape({ component: predicates.string }) },
|
|
||||||
values
|
|
||||||
)
|
|
||||||
? values.$.component
|
|
||||||
: null;
|
|
||||||
|
|
||||||
const component_name = new_component || old_component;
|
|
||||||
if (!component_name) {
|
if (!component_name) {
|
||||||
throw new Error("Unspecified component name");
|
throw new Error("Unspecified component name");
|
||||||
}
|
}
|
||||||
@ -225,30 +66,25 @@ export default new (class ComponentsPage extends StatefulPage<
|
|||||||
if (!component) {
|
if (!component) {
|
||||||
throw new Error(`Unknown component: ${component_name}`);
|
throw new Error(`Unknown component: ${component_name}`);
|
||||||
}
|
}
|
||||||
if (
|
if (!hasShape({ component_args: predicates.object }, overrides)) {
|
||||||
!hasShape(
|
return overrides;
|
||||||
{ $: 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;
|
|
||||||
}
|
}
|
||||||
const promises = Object.entries(component.getArguments()).map(
|
const promises = Object.entries(component.getArguments()).map(
|
||||||
async ([arg_name, arg]) => {
|
async ([arg_name, arg]) => {
|
||||||
const value = values.$.component_args[arg_name];
|
const value = overrides.component_args[arg_name];
|
||||||
if (value) {
|
if (value) {
|
||||||
const new_value = await (
|
const new_value = await arg.parseFormInput(
|
||||||
componentArgToRequestProcessor[arg.getTypeName()] || id
|
jdd_context,
|
||||||
)(arg, arg_name, value);
|
value,
|
||||||
values.$.component_args[arg_name] = new_value;
|
arg_name
|
||||||
|
);
|
||||||
|
overrides.component_args[arg_name] = new_value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||||
return values as T;
|
return overrides;
|
||||||
}
|
}
|
||||||
|
|
||||||
render(_ctx: BaseContext, state: ComponentPreviewState) {
|
render(_ctx: BaseContext, state: ComponentPreviewState) {
|
||||||
@ -256,7 +92,11 @@ export default new (class ComponentsPage extends StatefulPage<
|
|||||||
const component =
|
const component =
|
||||||
registry.get(state.component) || Object.values(all_components)[0];
|
registry.get(state.component) || Object.values(all_components)[0];
|
||||||
return (
|
return (
|
||||||
<div class="two-column" id="component-debugger">
|
<div
|
||||||
|
class="two-column"
|
||||||
|
id="component-debugger"
|
||||||
|
style="--resizable-column-width: 50vw"
|
||||||
|
>
|
||||||
<div class="resizable">
|
<div class="resizable">
|
||||||
{/*The below button has to be here in order for it to be the default behavior */}
|
{/*The below button has to be here in order for it to be the default behavior */}
|
||||||
<input type="submit" value="Preview" />
|
<input type="submit" value="Preview" />
|
||||||
@ -309,10 +149,12 @@ export default new (class ComponentsPage extends StatefulPage<
|
|||||||
origin_width + (e.clientX - origin_x),
|
origin_width + (e.clientX - origin_x),
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
document.documentElement.style.setProperty(
|
document
|
||||||
"--resizable-column-width",
|
.getElementById("component-debugger")
|
||||||
new_width + "px"
|
.style.setProperty(
|
||||||
);
|
"--resizable-column-width",
|
||||||
|
new_width + "px"
|
||||||
|
);
|
||||||
};
|
};
|
||||||
gutter.addEventListener("mousedown", (e) => {
|
gutter.addEventListener("mousedown", (e) => {
|
||||||
is_resizing = true;
|
is_resizing = true;
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
@import "../node_modules/@sealcode/sealgen/src/forms/forms.css";
|
@import "../node_modules/@sealcode/sealgen/src/forms/forms.css";
|
||||||
@import "back/jdd-components/image-demo/image-demo.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/map-with-pins/map-with-pins.css";
|
||||||
|
@import "back/jdd-components/table/table.css";
|
||||||
@import "back/routes/common/ui/input.css";
|
@import "back/routes/common/ui/input.css";
|
||||||
@import "back/routes/components.css";
|
@import "back/routes/components.css";
|
||||||
@import "colors.css";
|
@import "colors.css";
|
||||||
|