Sreact page for editing JDD
Summary: Ref T2764 Reviewers: #testers, kuba-orlik Reviewed By: #testers, kuba-orlik Subscribers: kuba-orlik, jenkins-user Maniphest Tasks: T2764 Differential Revision: https://hub.sealcode.org/D1406
This commit is contained in:
parent
a3965ae47e
commit
bec96ac604
@ -16,6 +16,7 @@ module.exports = {
|
||||
project: "./tsconfig-back.json",
|
||||
},
|
||||
rules: {
|
||||
"no-unused-vars": [2, { varsIgnorePattern: "TempstreamJSX" }],
|
||||
"@typescript-eslint/no-unused-vars": [2, { varsIgnorePattern: "TempstreamJSX" }],
|
||||
"no-unused-vars": 0,
|
||||
"@typescript-eslint/require-await": 0,
|
||||
|
619
package-lock.json
generated
619
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -43,8 +43,8 @@
|
||||
"@koa/router": "^12.0.1",
|
||||
"@playwright/test": "^1.36.1",
|
||||
"@sealcode/file-manager": "^1.0.2",
|
||||
"@sealcode/jdd": "^0.4.7",
|
||||
"@sealcode/sealgen": "^0.15.15",
|
||||
"@sealcode/jdd": "^0.4.10",
|
||||
"@sealcode/sealgen": "^0.15.16",
|
||||
"@sealcode/ts-predicates": "^0.6.2",
|
||||
"@types/kill-port": "^2.0.0",
|
||||
"@types/leaflet": "^1.9.8",
|
||||
|
@ -1,9 +1,9 @@
|
||||
import type { Context } from "koa";
|
||||
import { TempstreamJSX } from "tempstream";
|
||||
import { tempstream, TempstreamJSX } from "tempstream";
|
||||
import { Page } from "@sealcode/sealgen";
|
||||
import html from "../html.js";
|
||||
import html, { defaultHead } from "../html.js";
|
||||
import { registry } from "../jdd-components/components.js";
|
||||
import { documentContainerFromParsed, render } from "@sealcode/jdd";
|
||||
import { documentContainerFromParsed, render, renderEarlyAssets } from "@sealcode/jdd";
|
||||
import { shuffle } from "../util.js";
|
||||
import { makeJDDContext } from "../jdd-context.js";
|
||||
|
||||
@ -33,7 +33,14 @@ export default new (class AllComponentsPage extends Page {
|
||||
"AllComponents",
|
||||
<div>
|
||||
{render(registry, documentContainerFromParsed(document), jdd_context)}
|
||||
</div>
|
||||
</div>,
|
||||
{},
|
||||
(...args) =>
|
||||
tempstream`${defaultHead(...args)}${renderEarlyAssets(
|
||||
registry,
|
||||
documentContainerFromParsed(document),
|
||||
jdd_context
|
||||
)}`
|
||||
);
|
||||
}
|
||||
})();
|
||||
|
@ -18,10 +18,7 @@ export function ComponentInputEnum<State, S extends string, T extends Enum<S>>({
|
||||
<div>
|
||||
<label>
|
||||
{arg_path.at(-1) || ""}
|
||||
<select
|
||||
name={`$.component_args${printArgPath(arg_path)}`}
|
||||
onchange={onchange}
|
||||
>
|
||||
<select name={`$${printArgPath(arg_path)}`} onchange={onchange}>
|
||||
{arg.values.map((v: S) => (
|
||||
<option value={v} selected={value == v}>
|
||||
{v}
|
||||
|
@ -1,15 +1,15 @@
|
||||
import type { Image } from "@sealcode/jdd";
|
||||
import type { BaseContext } from "koa";
|
||||
import type { StatefulPage } from "@sealcode/sealgen";
|
||||
import { TempstreamJSX } from "tempstream";
|
||||
import type { ComponentPreviewState } from "../components.sreact.js";
|
||||
import type { FilePointer } from "@sealcode/file-manager";
|
||||
import type { Image } from "@sealcode/jdd";
|
||||
import type { StatefulPage } from "@sealcode/sealgen";
|
||||
import type { ComponentPreviewActions } from "./component-preview-actions.js";
|
||||
import type { JDDPageState } from "./jdd-page.js";
|
||||
import { makeJDDContext } from "../../jdd-context.js";
|
||||
import { printArgPath } from "./print-arg-path.js";
|
||||
import { htmlEscape } from "escape-goat";
|
||||
import type { FilePointer } from "@sealcode/file-manager";
|
||||
import { makeJDDContext } from "../../jdd-context.js";
|
||||
|
||||
export function ComponentInputImage<State extends ComponentPreviewState>({
|
||||
export function ComponentInputImage<State extends JDDPageState>({
|
||||
arg_path,
|
||||
arg,
|
||||
value,
|
||||
@ -19,9 +19,9 @@ export function ComponentInputImage<State extends ComponentPreviewState>({
|
||||
arg_path: string[];
|
||||
arg: Image;
|
||||
value: FilePointer | null;
|
||||
page: StatefulPage<ComponentPreviewState, typeof ComponentPreviewActions>;
|
||||
page: StatefulPage<JDDPageState, typeof ComponentPreviewActions>;
|
||||
ctx: BaseContext;
|
||||
}) {
|
||||
}): JSX.Element {
|
||||
const jdd_context = makeJDDContext(ctx);
|
||||
return (
|
||||
<div style="margin-bottom: 10px">
|
||||
@ -38,7 +38,7 @@ export function ComponentInputImage<State extends ComponentPreviewState>({
|
||||
})}
|
||||
<input
|
||||
type="file"
|
||||
name={`$.component_args${printArgPath(arg_path)}.new`}
|
||||
name={`$${printArgPath(arg_path)}.new`}
|
||||
value=""
|
||||
autocomplete="off"
|
||||
data-action="change->input-image-preview#handleChange"
|
||||
@ -48,7 +48,7 @@ export function ComponentInputImage<State extends ComponentPreviewState>({
|
||||
<div>
|
||||
<input
|
||||
type="hidden"
|
||||
name={`$.component_args${printArgPath(arg_path)}.old`}
|
||||
name={`$${printArgPath(arg_path)}.old`}
|
||||
value={htmlEscape(value?.token || "")}
|
||||
autocomplete="off"
|
||||
/>
|
||||
|
@ -1,13 +1,14 @@
|
||||
import type { ComponentArgument, List } from "@sealcode/jdd";
|
||||
import type { StatefulPage } from "@sealcode/sealgen";
|
||||
import type { BaseContext } from "koa";
|
||||
import { TempstreamJSX } from "tempstream";
|
||||
import type { ComponentPreviewState } from "../components.sreact.js";
|
||||
import type { ComponentArgument, List } from "@sealcode/jdd";
|
||||
import type { JDDPageState } from "./jdd-page.js";
|
||||
import type { StatefulPage } from "@sealcode/sealgen";
|
||||
import type { Readable } from "node:stream";
|
||||
import { ComponentInput } from "./component-input.js";
|
||||
import type { ComponentPreviewActions } from "./component-preview-actions.js";
|
||||
|
||||
export async function ComponentInputList<
|
||||
State extends ComponentPreviewState,
|
||||
State extends JDDPageState,
|
||||
T extends ComponentArgument<unknown>
|
||||
>({
|
||||
state,
|
||||
@ -20,10 +21,11 @@ export async function ComponentInputList<
|
||||
state: State;
|
||||
ctx: BaseContext;
|
||||
arg_path: string[];
|
||||
arg: List<T>;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
arg: List<T, any>;
|
||||
value: T[];
|
||||
page: StatefulPage<ComponentPreviewState, typeof ComponentPreviewActions>;
|
||||
}) {
|
||||
page: StatefulPage<JDDPageState, typeof ComponentPreviewActions>;
|
||||
}): Promise<Readable> {
|
||||
if (!value) {
|
||||
value = [];
|
||||
}
|
||||
|
@ -2,8 +2,8 @@ import type { BaseContext } from "koa";
|
||||
import type { ComponentArgument, Structured } from "@sealcode/jdd";
|
||||
import type { StatefulPage } from "@sealcode/sealgen";
|
||||
import { TempstreamJSX } from "tempstream";
|
||||
import type { ComponentPreviewState } from "../components.sreact.js";
|
||||
import { ComponentInput } from "./component-input.js";
|
||||
import type { JDDPageState } from "./jdd-page.js";
|
||||
import type { ComponentPreviewActions } from "./component-preview-actions.js";
|
||||
|
||||
export function ComponentInputStructured<
|
||||
@ -17,13 +17,13 @@ export function ComponentInputStructured<
|
||||
rerender_callback,
|
||||
page,
|
||||
}: {
|
||||
state: ComponentPreviewState;
|
||||
state: JDDPageState;
|
||||
ctx: BaseContext;
|
||||
arg_path: string[];
|
||||
arg: T;
|
||||
value: Record<string, unknown>;
|
||||
rerender_callback?: string;
|
||||
page: StatefulPage<ComponentPreviewState, typeof ComponentPreviewActions>;
|
||||
page: StatefulPage<JDDPageState, typeof ComponentPreviewActions>;
|
||||
}) {
|
||||
return (
|
||||
<fieldset>
|
||||
|
@ -3,10 +3,9 @@ import { isTableHeader } from "@sealcode/jdd";
|
||||
import type { StatefulPage } from "@sealcode/sealgen";
|
||||
import type { BaseContext } from "koa";
|
||||
import { TempstreamJSX } from "tempstream";
|
||||
import type { ComponentPreviewState } from "../components.sreact.js";
|
||||
import { ComponentInput } from "./component-input.js";
|
||||
import type { ComponentPreviewActions } from "./component-preview-actions.js";
|
||||
|
||||
import type { JDDPageState } from "./jdd-page.js";
|
||||
import add_column_right_icon from "./table-add-column-right.svg";
|
||||
import add_row_below_icon from "./table-add-row-below.svg";
|
||||
import add_column_header_icon from "./table-add-row-header-below.svg";
|
||||
@ -16,7 +15,7 @@ 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,
|
||||
State extends JDDPageState,
|
||||
CellType,
|
||||
HeaderType
|
||||
>({
|
||||
@ -32,8 +31,8 @@ export async function ComponentInputTable<
|
||||
arg_path: string[];
|
||||
arg: Table<CellType, HeaderType>;
|
||||
value: TableData<CellType, HeaderType>;
|
||||
page: StatefulPage<ComponentPreviewState, typeof ComponentPreviewActions>;
|
||||
}) {
|
||||
page: StatefulPage<JDDPageState, typeof ComponentPreviewActions>;
|
||||
}): Promise<import("stream").Readable> {
|
||||
if (!value) {
|
||||
value = arg.getEmptyValue();
|
||||
}
|
||||
|
@ -1,23 +1,24 @@
|
||||
import type { FilePointer } from "@sealcode/file-manager";
|
||||
import { printArgPath } from "./print-arg-path.js";
|
||||
import type { BaseContext } from "koa";
|
||||
import type { ComponentArgument, TableData } from "@sealcode/jdd";
|
||||
import { ComponentArguments, Enum, Image, List, Structured, Table } from "@sealcode/jdd";
|
||||
import { ComponentInputStructured } from "./component-input-structured.js";
|
||||
import type { StatefulPage } from "@sealcode/sealgen";
|
||||
import { is, predicates } from "@sealcode/ts-predicates";
|
||||
import type { BaseContext } from "koa";
|
||||
import { TempstreamJSX } from "tempstream";
|
||||
import type { ComponentPreviewState } from "../components.sreact.js";
|
||||
import type { ComponentPreviewActions } from "./component-preview-actions.js";
|
||||
import type { Readable } from "node:stream";
|
||||
import { ComponentInputList } from "./component-input-list.js";
|
||||
import type { JDDPageState } from "./jdd-page.js";
|
||||
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 type { ComponentPreviewActions } from "./component-preview-actions.js";
|
||||
import { printArgPath } from "./print-arg-path.js";
|
||||
import { TempstreamJSX } from "tempstream";
|
||||
import type { FilePointer } from "@sealcode/file-manager";
|
||||
import { is, predicates } from "@sealcode/ts-predicates";
|
||||
|
||||
export const actionName = "Components";
|
||||
const absoluteUrlPattern = "http(s?)(://)((www.)?)(([^.]+).)?([a-zA-z0-9-_]+)";
|
||||
|
||||
export function ComponentInput<State extends ComponentPreviewState, T>({
|
||||
export function ComponentInput<State extends JDDPageState, T>({
|
||||
ctx,
|
||||
state,
|
||||
arg_path,
|
||||
@ -30,8 +31,8 @@ export function ComponentInput<State extends ComponentPreviewState, T>({
|
||||
arg_path: string[];
|
||||
arg: ComponentArgument<T>;
|
||||
value: T;
|
||||
page: StatefulPage<ComponentPreviewState, typeof ComponentPreviewActions>;
|
||||
}) {
|
||||
page: StatefulPage<JDDPageState, typeof ComponentPreviewActions>;
|
||||
}): Readable | Promise<Readable> {
|
||||
if (value === undefined) {
|
||||
value = arg.getEmptyValue();
|
||||
}
|
||||
@ -106,7 +107,7 @@ export function ComponentInput<State extends ComponentPreviewState, T>({
|
||||
{arg_path.at(-1) || ""}
|
||||
{argType == "markdown" ? (
|
||||
<textarea
|
||||
name={`$.component_args${printArgPath(arg_path)}`}
|
||||
name={`$${printArgPath(arg_path)}`}
|
||||
onblur={page.rerender()}
|
||||
cols="40"
|
||||
>
|
||||
@ -115,7 +116,7 @@ export function ComponentInput<State extends ComponentPreviewState, T>({
|
||||
) : (
|
||||
<input
|
||||
type={inputType}
|
||||
name={`$.component_args${printArgPath(arg_path)}`}
|
||||
name={`$${printArgPath(arg_path)}`}
|
||||
value={is(value, predicates.string) ? value : ""}
|
||||
size="40"
|
||||
pattern={isUrlAbsolute ? absoluteUrlPattern : undefined}
|
||||
|
@ -1,9 +1,10 @@
|
||||
import type { ComponentArgument, List, Table, TableData } from "@sealcode/jdd";
|
||||
import type { Registry, TableData } from "@sealcode/jdd";
|
||||
import { List, Table } from "@sealcode/jdd";
|
||||
import type { BaseContext } from "koa";
|
||||
import { isTableData, isTableRegularRow } from "@sealcode/jdd";
|
||||
import objectPath from "object-path";
|
||||
import { registry } from "../../jdd-components/components.js";
|
||||
import type { ComponentPreviewState } from "../components.sreact.js";
|
||||
import type { JDDPageState } from "./jdd-page.js";
|
||||
import { registry } from "../../jdd-components/registry.js";
|
||||
import { makeJDDContext } from "../../jdd-context.js";
|
||||
|
||||
function moveElement<T>(array: Array<T>, fromIndex: number, toIndex: number): Array<T> {
|
||||
@ -12,109 +13,152 @@ function moveElement<T>(array: Array<T>, fromIndex: number, toIndex: number): Ar
|
||||
return array;
|
||||
}
|
||||
|
||||
export function getComponentData(
|
||||
state: JDDPageState,
|
||||
arg_path: string[],
|
||||
registry: Registry
|
||||
) {
|
||||
const component_index = parseInt(arg_path[1]);
|
||||
const component_args = state.components[component_index].args;
|
||||
const component_name = state.components[component_index].component_name;
|
||||
const component = registry.get(component_name);
|
||||
const arg_path_within_component = arg_path.slice(3); // remove "components" and the index of the component and "args"
|
||||
|
||||
const [argument, , argument_value] = component?.getArgumentAtPath(
|
||||
arg_path_within_component,
|
||||
component_args
|
||||
) || [null, null, null];
|
||||
|
||||
return {
|
||||
component_index,
|
||||
component_args,
|
||||
component_name,
|
||||
component,
|
||||
argument,
|
||||
argument_value,
|
||||
arg_path_within_component,
|
||||
};
|
||||
}
|
||||
|
||||
export const ComponentPreviewActions = <const>{
|
||||
add_array_item: async (
|
||||
ctx: BaseContext,
|
||||
state: ComponentPreviewState,
|
||||
state: JDDPageState,
|
||||
_: Record<string, string>,
|
||||
arg_path: string[]
|
||||
) => {
|
||||
const component_args = state.component_args;
|
||||
const component = registry.get(state.component);
|
||||
const {
|
||||
component_name,
|
||||
component,
|
||||
argument,
|
||||
arg_path_within_component,
|
||||
argument_value,
|
||||
} = getComponentData(state, arg_path, registry);
|
||||
if (!component) {
|
||||
console.error("unknown component: ", state.component);
|
||||
console.error("unknown component: ", component_name);
|
||||
return state;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
const argument = component.getArgumentAtPath(
|
||||
arg_path,
|
||||
state.component_args
|
||||
) as List<ComponentArgument<unknown>> | null;
|
||||
if (!argument) {
|
||||
console.error("Didn't find a list argument at this path", arg_path);
|
||||
console.error(
|
||||
"Didn't find a list argument at this path",
|
||||
arg_path_within_component
|
||||
);
|
||||
return state;
|
||||
}
|
||||
if (!(argument instanceof List)) {
|
||||
throw new Error(
|
||||
`Expected argument in path ${arg_path.join(
|
||||
"."
|
||||
)} to be an instance of List`
|
||||
);
|
||||
}
|
||||
objectPath.insert(
|
||||
component_args,
|
||||
state,
|
||||
arg_path,
|
||||
await argument.item_type.getExampleValue(makeJDDContext(ctx)),
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
((objectPath.get(component_args, arg_path) as unknown[]) || []).length
|
||||
Array.isArray(argument_value) ? argument_value.length : 0
|
||||
);
|
||||
return {
|
||||
...state,
|
||||
component_args,
|
||||
};
|
||||
return state;
|
||||
},
|
||||
remove_array_item: (
|
||||
_ctx: BaseContext,
|
||||
state: ComponentPreviewState,
|
||||
state: JDDPageState,
|
||||
_: Record<string, string>,
|
||||
arg_path: string[],
|
||||
index_to_remove: number
|
||||
) => {
|
||||
const component_args = state.component_args;
|
||||
objectPath.del(component_args, [...arg_path, index_to_remove]);
|
||||
return {
|
||||
...state,
|
||||
component_args,
|
||||
};
|
||||
): JDDPageState => {
|
||||
objectPath.del(state, [...arg_path, index_to_remove]);
|
||||
return state;
|
||||
},
|
||||
|
||||
change_component: async (
|
||||
ctx: BaseContext,
|
||||
state: ComponentPreviewState,
|
||||
inputs: Record<string, string>
|
||||
) => {
|
||||
state: JDDPageState,
|
||||
inputs: Record<string, unknown>
|
||||
): Promise<JDDPageState> => {
|
||||
const component_name = inputs.component;
|
||||
if (!component_name || typeof component_name !== "string") {
|
||||
throw new Error(
|
||||
"Missing input: 'component' for action change_component. It should contain the name of the new component type"
|
||||
);
|
||||
}
|
||||
const component = registry.get(component_name);
|
||||
if (!component) {
|
||||
throw new Error(`Unknown or disallowed component name: ${component_name}`);
|
||||
}
|
||||
return {
|
||||
...state,
|
||||
component: component_name,
|
||||
component_args:
|
||||
(await component?.getExampleValues(makeJDDContext(ctx))) || {},
|
||||
components: [
|
||||
{
|
||||
component_name: component_name,
|
||||
args: (await component?.getExampleValues(makeJDDContext(ctx))) || {},
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
randomize_args: async (
|
||||
ctx: BaseContext,
|
||||
state: ComponentPreviewState,
|
||||
inputs: Record<string, string>
|
||||
) => {
|
||||
const component_name = inputs.component;
|
||||
const component = registry.get(component_name);
|
||||
state: JDDPageState,
|
||||
_inputs: Record<string, string>,
|
||||
component_index_str: string
|
||||
): Promise<JDDPageState> => {
|
||||
const { component_index, component } = getComponentData(
|
||||
state,
|
||||
["components", component_index_str],
|
||||
registry
|
||||
);
|
||||
|
||||
state.components[component_index].args =
|
||||
(await component?.getExampleValues(makeJDDContext(ctx))) || {};
|
||||
return {
|
||||
...state,
|
||||
component_args:
|
||||
(await component?.getExampleValues(makeJDDContext(ctx))) || {},
|
||||
};
|
||||
},
|
||||
add_table_row: async (
|
||||
ctx: BaseContext,
|
||||
state: ComponentPreviewState,
|
||||
state: JDDPageState,
|
||||
_: Record<string, string>,
|
||||
arg_path: string[],
|
||||
columns: number,
|
||||
type: "header" | "row" = "row"
|
||||
) => {
|
||||
const jdd_context = makeJDDContext(ctx);
|
||||
const component_args = state.component_args;
|
||||
const { component_args, argument } = getComponentData(state, arg_path, registry);
|
||||
let row;
|
||||
const component = registry.get(state.component);
|
||||
if (!component) {
|
||||
console.error("Unknown component: ", state.component);
|
||||
return state;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
const argument = component.getArgumentAtPath(
|
||||
arg_path,
|
||||
state.component_args
|
||||
) as Table<unknown, unknown> | null;
|
||||
|
||||
if (!argument) {
|
||||
console.error("Unknown component at path", arg_path);
|
||||
return state;
|
||||
}
|
||||
if (!(argument instanceof Table)) {
|
||||
throw new Error(
|
||||
`Expected argument at path ${arg_path.join(".")} to be of type Table`
|
||||
);
|
||||
}
|
||||
if (type == "header") {
|
||||
row = {
|
||||
type: "header",
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
header_content: await argument.header_type.getExampleValue(jdd_context),
|
||||
};
|
||||
} else {
|
||||
@ -126,42 +170,28 @@ export const ComponentPreviewActions = <const>{
|
||||
row = { type: "row", cells };
|
||||
}
|
||||
objectPath.insert(
|
||||
component_args,
|
||||
state,
|
||||
[...arg_path, "rows"],
|
||||
row,
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
((objectPath.get(component_args, [...arg_path, "rows"]) as unknown[]) || [])
|
||||
.length
|
||||
);
|
||||
const result = {
|
||||
...state,
|
||||
component_args,
|
||||
};
|
||||
return result;
|
||||
return state;
|
||||
},
|
||||
add_table_column: async (
|
||||
ctx: BaseContext,
|
||||
state: ComponentPreviewState,
|
||||
state: JDDPageState,
|
||||
_: Record<string, string>,
|
||||
arg_path: string[]
|
||||
) => {
|
||||
const jdd_context = makeJDDContext(ctx);
|
||||
const component_args = state.component_args;
|
||||
const component = registry.get(state.component);
|
||||
if (!component) {
|
||||
console.error("Unknown component: ", state.component);
|
||||
return state;
|
||||
}
|
||||
const argument = component.getArgumentAtPath(arg_path, state.component_args);
|
||||
const { argument } = getComponentData(state, arg_path, registry);
|
||||
if (!argument) {
|
||||
console.error("Unknown component at path", arg_path);
|
||||
return state;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const tableData: TableData<unknown, unknown> = objectPath.get(
|
||||
component_args,
|
||||
arg_path
|
||||
);
|
||||
const tableData: TableData<unknown, unknown> = objectPath.get(state, arg_path);
|
||||
if (!isTableData(tableData)) {
|
||||
throw new Error("wrong table data");
|
||||
}
|
||||
@ -170,29 +200,22 @@ export const ComponentPreviewActions = <const>{
|
||||
const row = tableData.rows[i];
|
||||
if (isTableRegularRow(row)) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
row.cells.push(await argument.getExampleValue(jdd_context));
|
||||
row.cells.push(await argument.getExampleValue(makeJDDContext(ctx)));
|
||||
}
|
||||
}
|
||||
objectPath.set(component_args, arg_path, tableData);
|
||||
return {
|
||||
...state,
|
||||
component_args,
|
||||
};
|
||||
objectPath.set(state, arg_path, tableData);
|
||||
return state;
|
||||
},
|
||||
|
||||
remove_table_column: (
|
||||
_ctx: BaseContext,
|
||||
state: ComponentPreviewState,
|
||||
state: JDDPageState,
|
||||
_: Record<string, string>,
|
||||
arg_path: string[],
|
||||
column_index_to_remove: number
|
||||
) => {
|
||||
const component_args = state.component_args;
|
||||
): JDDPageState => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const tableData: TableData<unknown, unknown> = objectPath.get(
|
||||
component_args,
|
||||
arg_path
|
||||
);
|
||||
const tableData: TableData<unknown, unknown> = objectPath.get(state, arg_path);
|
||||
if (!isTableData(tableData)) {
|
||||
throw new Error("wrong table data");
|
||||
}
|
||||
@ -203,84 +226,135 @@ export const ComponentPreviewActions = <const>{
|
||||
row.cells = row.cells.filter((_, i) => i != column_index_to_remove);
|
||||
}
|
||||
}
|
||||
objectPath.set(component_args, arg_path, tableData);
|
||||
return {
|
||||
...state,
|
||||
component_args,
|
||||
};
|
||||
objectPath.set(state, arg_path, tableData);
|
||||
return state;
|
||||
},
|
||||
|
||||
remove_table_row: (
|
||||
_ctx: BaseContext,
|
||||
state: ComponentPreviewState,
|
||||
state: JDDPageState,
|
||||
_: 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;
|
||||
): JDDPageState => {
|
||||
objectPath.del(state, [...arg_path, "rows", row_index]);
|
||||
return state;
|
||||
},
|
||||
move_table_column_right: (
|
||||
_ctx: BaseContext,
|
||||
state: ComponentPreviewState,
|
||||
state: JDDPageState,
|
||||
_: Record<string, string>,
|
||||
arg_path: string[],
|
||||
column_index: number
|
||||
) => {
|
||||
const component_args = state.component_args;
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
const data = objectPath.get(component_args, arg_path) as TableData<
|
||||
unknown,
|
||||
unknown
|
||||
>;
|
||||
const { component_args } = getComponentData(state, arg_path, registry);
|
||||
|
||||
const last_path_element = arg_path.at(-1);
|
||||
if (!last_path_element) {
|
||||
throw new Error("arg path is empty");
|
||||
}
|
||||
const data = objectPath.get<unknown>(component_args, last_path_element, "");
|
||||
if (!isTableData(data)) {
|
||||
throw new Error("Expected arg value for a table to be properly shaped");
|
||||
}
|
||||
for (const row of data.rows) {
|
||||
if (row.type == "row") {
|
||||
moveElement(row.cells, column_index, column_index + 1);
|
||||
}
|
||||
}
|
||||
objectPath.set(component_args, [...arg_path, "rows"], data.rows);
|
||||
const result = {
|
||||
...state,
|
||||
component_args,
|
||||
};
|
||||
return result;
|
||||
objectPath.set(state, [...arg_path, "rows"], data.rows);
|
||||
return state;
|
||||
},
|
||||
|
||||
move_table_row_down: (
|
||||
_ctx: BaseContext,
|
||||
state: ComponentPreviewState,
|
||||
state: JDDPageState,
|
||||
_: Record<string, string>,
|
||||
arg_path: string[],
|
||||
row_index: number
|
||||
) => {
|
||||
const component_args = state.component_args;
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
const data = objectPath.get(component_args, arg_path) as TableData<
|
||||
unknown,
|
||||
unknown
|
||||
>;
|
||||
const { component_args } = getComponentData(state, arg_path, registry);
|
||||
const last_path_element = arg_path.at(-1);
|
||||
if (!last_path_element) {
|
||||
throw new Error("arg path is empty");
|
||||
}
|
||||
const data = objectPath.get<unknown>(component_args, last_path_element, "");
|
||||
if (!isTableData(data)) {
|
||||
throw new Error("Expected arg value for a table to be properly shaped");
|
||||
}
|
||||
moveElement(data.rows, row_index, row_index + 1);
|
||||
objectPath.set(component_args, [...arg_path, "rows"], data.rows);
|
||||
const result = {
|
||||
...state,
|
||||
component_args,
|
||||
};
|
||||
return result;
|
||||
objectPath.set(state, [...arg_path, "rows"], data.rows);
|
||||
return state;
|
||||
},
|
||||
|
||||
change_size: (
|
||||
_ctx: BaseContext,
|
||||
state: ComponentPreviewState,
|
||||
state: JDDPageState,
|
||||
inputs: Record<string, string>
|
||||
) => {
|
||||
return {
|
||||
...state,
|
||||
...inputs,
|
||||
current_size: inputs.size,
|
||||
preview_size: inputs.size,
|
||||
};
|
||||
},
|
||||
|
||||
add_component: async (
|
||||
ctx: BaseContext,
|
||||
state: JDDPageState,
|
||||
inputs: Record<string, string>
|
||||
): Promise<JDDPageState> => {
|
||||
const component_name = inputs.component;
|
||||
const component = registry.get(component_name);
|
||||
|
||||
return {
|
||||
...state,
|
||||
components: [
|
||||
...state.components,
|
||||
{
|
||||
component_name: component_name,
|
||||
args: (await component?.getExampleValues(makeJDDContext(ctx))) || {},
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
|
||||
remove_component: async (
|
||||
_ctx: BaseContext,
|
||||
state: JDDPageState,
|
||||
_inputs: Record<string, string>,
|
||||
component_index: number
|
||||
): Promise<JDDPageState> => {
|
||||
const newComponentState = [...state.components];
|
||||
newComponentState.splice(component_index, 1);
|
||||
return {
|
||||
...state,
|
||||
components: newComponentState,
|
||||
};
|
||||
},
|
||||
|
||||
move_component_up: async (
|
||||
_ctx: BaseContext,
|
||||
state: JDDPageState,
|
||||
_inputs: Record<string, string>,
|
||||
component_index: number
|
||||
): Promise<JDDPageState> => {
|
||||
const newComps = [...state.components];
|
||||
// prettier-ignore
|
||||
[newComps[component_index], newComps[component_index - 1]] =
|
||||
[newComps[component_index - 1], newComps[component_index]];
|
||||
return { ...state, components: newComps };
|
||||
},
|
||||
|
||||
move_component_down: async (
|
||||
_ctx: BaseContext,
|
||||
state: JDDPageState,
|
||||
_inputs: Record<string, string>,
|
||||
component_index: number
|
||||
): Promise<JDDPageState> => {
|
||||
const newComps = [...state.components];
|
||||
// prettier-ignore
|
||||
[newComps[component_index], newComps[component_index + 1]] =
|
||||
[newComps[component_index + 1], newComps[component_index]];
|
||||
return { ...state, components: newComps };
|
||||
},
|
||||
};
|
||||
|
105
src/back/routes/component-preview/jdd-creator.tsx
Normal file
105
src/back/routes/component-preview/jdd-creator.tsx
Normal file
@ -0,0 +1,105 @@
|
||||
import type { BaseContext } from "koa";
|
||||
import { TempstreamJSX } from "tempstream";
|
||||
import { ComponentPreviewActions } from "./component-preview-actions.js";
|
||||
import type { JDDPageState } from "./jdd-page.js";
|
||||
import JDDPage from "./jdd-page.js";
|
||||
|
||||
import move_row_down_icon from "./table-move-row-down.svg";
|
||||
import move_row_up_icon from "./table-move-row-up.svg";
|
||||
|
||||
export default abstract class JDDCreator extends JDDPage {
|
||||
actions = ComponentPreviewActions;
|
||||
|
||||
/**
|
||||
* This method returns list of components allowed in JDD Editor instance.
|
||||
* If list is empty it will allow all of the components in registry,
|
||||
* if you overide this function you can decide on what components should
|
||||
* available.
|
||||
*/
|
||||
getAllowedComponents(): string[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
getRegistryCompoments() {
|
||||
const all_components = super.getRegistryCompoments();
|
||||
const allowed_components = this.getAllowedComponents();
|
||||
|
||||
if (allowed_components.length > 0) {
|
||||
return Object.fromEntries(
|
||||
Object.entries(all_components).filter(([name]) =>
|
||||
allowed_components.includes(name)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return all_components;
|
||||
}
|
||||
|
||||
renderParameterButtons(state: JDDPageState) {
|
||||
{
|
||||
/*The below button has to be here in order for it to be the default behavior */
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<input type="submit" value="Preview" />
|
||||
<select name="component">
|
||||
{Object.keys(this.getRegistryCompoments()).map((cmp) => (
|
||||
<option value={cmp}>{cmp}</option>
|
||||
))}
|
||||
</select>
|
||||
{this.makeActionButton(state, {
|
||||
action: "add_component",
|
||||
label: "Add component",
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderComponentBlock(
|
||||
ctx: BaseContext,
|
||||
state: JDDPageState,
|
||||
component: {
|
||||
component_name: string;
|
||||
args: Record<string, unknown>;
|
||||
},
|
||||
component_index: number
|
||||
) {
|
||||
return (
|
||||
<fieldset>
|
||||
<legend>Component - {component.component_name}</legend>
|
||||
{this.makeActionButton(
|
||||
state,
|
||||
{ action: "remove_component", label: "❌" },
|
||||
component_index
|
||||
)}
|
||||
{this.makeActionButton(
|
||||
state,
|
||||
{
|
||||
action: "move_component_up",
|
||||
label: "Move this row up",
|
||||
content: /* HTML */ `<img
|
||||
width="20"
|
||||
height="20"
|
||||
src="${move_row_up_icon.url}"
|
||||
/>`,
|
||||
},
|
||||
component_index
|
||||
)}
|
||||
{this.makeActionButton(
|
||||
state,
|
||||
{
|
||||
action: "move_component_down",
|
||||
label: "Move this row down",
|
||||
content: /* HTML */ `<img
|
||||
width="20"
|
||||
height="20"
|
||||
src="${move_row_down_icon.url}"
|
||||
/>`,
|
||||
},
|
||||
component_index
|
||||
)}
|
||||
{super.renderComponentBlock(ctx, state, component, component_index)}
|
||||
</fieldset>
|
||||
);
|
||||
}
|
||||
}
|
298
src/back/routes/component-preview/jdd-page.tsx
Normal file
298
src/back/routes/component-preview/jdd-page.tsx
Normal file
@ -0,0 +1,298 @@
|
||||
import type { Component, RawJDDocument } from "@sealcode/jdd";
|
||||
import { documentContainerFromParsed } from "@sealcode/jdd";
|
||||
import { render, renderEarlyAssets } from "@sealcode/jdd";
|
||||
import { StatefulPage } from "@sealcode/sealgen";
|
||||
import { hasFieldOfType, hasShape, predicates } from "@sealcode/ts-predicates";
|
||||
import type { BaseContext } from "koa";
|
||||
import type { FlatTemplatable, Templatable } from "tempstream";
|
||||
import { tempstream, TempstreamJSX } from "tempstream";
|
||||
import html, { defaultHead } from "../../html.js";
|
||||
import { registry } from "../../jdd-components/components.js";
|
||||
import { makeJDDContext } from "../../jdd-context.js";
|
||||
import { ComponentInput } from "./component-input.js";
|
||||
import { ComponentPreviewActions } from "./component-preview-actions.js";
|
||||
|
||||
export const actionName = "Components";
|
||||
|
||||
export type JDDPageState = {
|
||||
components: RawJDDocument;
|
||||
preview_size?: string;
|
||||
};
|
||||
|
||||
export default abstract class JDDPage extends StatefulPage<
|
||||
JDDPageState,
|
||||
typeof ComponentPreviewActions
|
||||
> {
|
||||
actions = ComponentPreviewActions;
|
||||
|
||||
previewSizes = ["320", "600", "800", "1024", "1300", "1920"];
|
||||
|
||||
getRegistryCompoments() {
|
||||
return registry.getAll();
|
||||
}
|
||||
|
||||
async getInitialState(ctx: BaseContext) {
|
||||
const [component_name, component] = Object.entries(
|
||||
this.getRegistryCompoments()
|
||||
)[0];
|
||||
const initial_state = {
|
||||
components: [
|
||||
{
|
||||
component_name: component_name,
|
||||
args: await component.getExampleValues(makeJDDContext(ctx)),
|
||||
},
|
||||
],
|
||||
};
|
||||
return initial_state;
|
||||
}
|
||||
|
||||
wrapInLayout(
|
||||
ctx: BaseContext,
|
||||
content: Templatable,
|
||||
state: JDDPageState
|
||||
): Templatable {
|
||||
return html(
|
||||
ctx,
|
||||
"Components",
|
||||
content,
|
||||
{
|
||||
morphing: true,
|
||||
preserveScroll: true,
|
||||
autoRefreshCSS: true,
|
||||
navbar: () => ``,
|
||||
},
|
||||
(...args) =>
|
||||
tempstream`${defaultHead(...args)}${renderEarlyAssets(
|
||||
registry,
|
||||
documentContainerFromParsed(state.components),
|
||||
makeJDDContext(ctx)
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
async preprocessOverrides(
|
||||
_ctx: BaseContext,
|
||||
state: JDDPageState,
|
||||
overrides: Record<string, unknown>
|
||||
) {
|
||||
const jdd_context = makeJDDContext(_ctx);
|
||||
if (
|
||||
!hasFieldOfType(
|
||||
"components",
|
||||
overrides,
|
||||
predicates.array(
|
||||
predicates.shape({
|
||||
args: predicates.object,
|
||||
})
|
||||
)
|
||||
)
|
||||
) {
|
||||
return {};
|
||||
}
|
||||
for (const [component_index, { component_name }] of Object.entries(
|
||||
state.components
|
||||
)) {
|
||||
const component = registry.get(component_name);
|
||||
if (!component) {
|
||||
throw new Error(`Unknown component: ${component_name}`);
|
||||
}
|
||||
const overrides_for_component =
|
||||
overrides.components[parseInt(component_index)];
|
||||
const promises = Object.entries(component.getArguments()).map(
|
||||
async ([arg_name, arg]) => {
|
||||
const value = overrides_for_component.args[arg_name];
|
||||
if (value) {
|
||||
const new_value = await arg.receivedToParsed(jdd_context, value);
|
||||
overrides_for_component.args[arg_name] = new_value;
|
||||
}
|
||||
}
|
||||
);
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await Promise.all(promises);
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
return overrides;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
abstract renderParameterButtons(_state: JDDPageState): FlatTemplatable;
|
||||
|
||||
renderComponentArgs<C extends Component>(
|
||||
ctx: BaseContext,
|
||||
state: JDDPageState,
|
||||
component: C,
|
||||
args: Record<string, unknown>,
|
||||
index: number
|
||||
) {
|
||||
const jdd_context = makeJDDContext(ctx);
|
||||
return (
|
||||
<fieldset class="component-preview-parameters">
|
||||
<legend>Parameters</legend>
|
||||
{Object.entries(component.getArguments()).map(async ([arg_name, arg]) => (
|
||||
<ComponentInput
|
||||
{...{
|
||||
state,
|
||||
arg_path: ["components", index.toString(), "args", arg_name],
|
||||
ctx,
|
||||
arg,
|
||||
value:
|
||||
args[arg_name] === undefined
|
||||
? await arg.getExampleValue(jdd_context)
|
||||
: args[arg_name],
|
||||
onblur: this.rerender(),
|
||||
page: this,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
<input type="submit" value="Preview" />
|
||||
</fieldset>
|
||||
);
|
||||
}
|
||||
|
||||
renderComponentBlock(
|
||||
ctx: BaseContext,
|
||||
state: JDDPageState,
|
||||
{
|
||||
component_name,
|
||||
args: component_args,
|
||||
}: {
|
||||
component_name: string;
|
||||
args: Record<string, unknown>;
|
||||
},
|
||||
component_index: number
|
||||
) {
|
||||
const component = registry.get(component_name);
|
||||
if (!component) {
|
||||
return null;
|
||||
}
|
||||
return this.renderComponentArgs(
|
||||
ctx,
|
||||
state,
|
||||
component,
|
||||
component_args,
|
||||
component_index
|
||||
);
|
||||
}
|
||||
|
||||
async serializeState(ctx: BaseContext, state: JDDPageState) {
|
||||
const serialized_components = await Promise.all(
|
||||
state.components.map(async ({ component_name, args }) => {
|
||||
const component = registry.get(component_name);
|
||||
const single_result = {
|
||||
component_name,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
||||
args: component
|
||||
? await component.convertParsedToStorage(
|
||||
makeJDDContext(ctx),
|
||||
args
|
||||
)
|
||||
: {},
|
||||
};
|
||||
return single_result;
|
||||
})
|
||||
);
|
||||
const serialized_state = JSON.stringify({ components: serialized_components });
|
||||
return serialized_state;
|
||||
}
|
||||
|
||||
async deserializeState(ctx: BaseContext, state_string: string) {
|
||||
const jdd_context = makeJDDContext(ctx);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const raw = JSON.parse(state_string);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
|
||||
const components_storage = raw.components;
|
||||
if (!Array.isArray(components_storage)) {
|
||||
throw new Error(
|
||||
"'components' key is not an array, got ${components_storage}"
|
||||
);
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const components_parsed = await Promise.all(
|
||||
components_storage.map(async (entry) => {
|
||||
if (
|
||||
!hasShape(
|
||||
{
|
||||
component_name: predicates.string,
|
||||
args: predicates.object,
|
||||
},
|
||||
entry
|
||||
)
|
||||
) {
|
||||
throw new Error(
|
||||
`Expected components[] items to be objects with 'component_name' and 'args' keys, got ${entry}`
|
||||
);
|
||||
}
|
||||
const { component_name, args } = entry;
|
||||
const component = registry.get(component_name);
|
||||
if (!component) {
|
||||
throw new Error("Unknown component: ${component_name}");
|
||||
}
|
||||
return {
|
||||
component_name,
|
||||
args: await component.convertStorageToParsed(jdd_context, args),
|
||||
};
|
||||
})
|
||||
);
|
||||
const result = { components: components_parsed };
|
||||
return result;
|
||||
}
|
||||
|
||||
render(ctx: BaseContext, state: JDDPageState) {
|
||||
return (
|
||||
<div
|
||||
class="two-column"
|
||||
id="component-debugger"
|
||||
style="--resizable-column-width: 50vw"
|
||||
data-controller="component-debugger"
|
||||
>
|
||||
<div class="component-arguments">
|
||||
{this.renderParameterButtons(state)}
|
||||
{state.components.map((component, component_index) =>
|
||||
this.renderComponentBlock(ctx, state, component, component_index)
|
||||
)}
|
||||
<code>{this.serializeState(ctx, state)}</code>
|
||||
</div>
|
||||
<div class="resize-gutter" data-component-debugger-target="gutter"></div>
|
||||
<div class="component-preview" data-component-debugger-target="preview">
|
||||
<fieldset>
|
||||
<legend>
|
||||
Preview{" "}
|
||||
<span data-component-debugger-target="component-width"></span>
|
||||
<select
|
||||
name="size"
|
||||
autocomplete="off"
|
||||
class="component-preview-size-select"
|
||||
data-component-debugger-target="size-select"
|
||||
data-action="change->component-debugger#handleWidthDropdown"
|
||||
>
|
||||
{this.previewSizes.map((size) => (
|
||||
<option
|
||||
value={size}
|
||||
selected={size === (state.preview_size || "800")}
|
||||
>
|
||||
{`${size} px`}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<noscript>
|
||||
{this.makeActionButton(state, "change_size")}
|
||||
</noscript>
|
||||
</legend>
|
||||
{render(
|
||||
registry,
|
||||
documentContainerFromParsed(state.components),
|
||||
makeJDDContext(ctx)
|
||||
)}
|
||||
</fieldset>
|
||||
{
|
||||
/* HTML */ `<script>
|
||||
(function () {
|
||||
const gutter = document.querySelector(".resize-gutter");
|
||||
})();
|
||||
</script>`
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
26
src/back/routes/component-preview/table-move-row-up.svg
Normal file
26
src/back/routes/component-preview/table-move-row-up.svg
Normal file
@ -0,0 +1,26 @@
|
||||
<?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"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<path
|
||||
d="M 14,13.474609 C 14,13.76453 13.831094,14 13.621094,14 H 0.378906 C 0.168806,14 0,13.76453 0,13.474609 V 8.6835938 C 0,8.3935724 0.168806,8.1601563 0.378906,8.1601562 h 13.242188 c 0.21,0 0.378906,0.2334162 0.378906,0.5234376 z M 12.941406,12.791016 V 9.3710938 H 9.7265626 v 3.4199222 z m -4.2832028,0 V 9.3710938 H 5.4433594 v 3.4199222 z m -4.2832031,0 V 9.3710938 H 1.158203 v 3.4199222 z"
|
||||
id="path1" />
|
||||
<path
|
||||
fill="green"
|
||||
d="m 2.968353,4.030706 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 3.642611 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.368486 l 1.5216,1.5159882 q 0.1863,0.1964467 0.4658,0.1964467 0.2794998,0 0.4657998,-0.1964467 l 0.3882,-0.3880946 q 0.1966,-0.1966467 0.1966,-0.4656736 0,-0.274126 -0.1966,-0.470872 L 7.4659532,0.191548 Q 7.2847532,0 7.0001532,0 q -0.2795,0 -0.471,0.191548 L 3.159853,3.559834 q -0.1915,0.201845 -0.1915,0.470872 z"
|
||||
id="path2"
|
||||
style="fill:#0086be;fill-opacity:1" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
@ -1,34 +1,52 @@
|
||||
import { documentContainerFromParsed, render, renderEarlyAssets } from "@sealcode/jdd";
|
||||
import { StatefulPage } from "@sealcode/sealgen";
|
||||
import { hasShape, predicates } from "@sealcode/ts-predicates";
|
||||
import { documentContainerFromParsed, renderEarlyAssets } from "@sealcode/jdd";
|
||||
import type { BaseContext } from "koa";
|
||||
import type { Templatable } from "tempstream";
|
||||
import { tempstream, TempstreamJSX } from "tempstream";
|
||||
import type { Stringifiable } from "tempstream/@types/stringify.js";
|
||||
import html, { defaultHead } from "../html.js";
|
||||
import { registry } from "../jdd-components/components.js";
|
||||
import { makeJDDContext } from "../jdd-context.js";
|
||||
import { ComponentInput } from "./component-preview/component-input.js";
|
||||
import { ComponentPreviewActions } from "./component-preview/component-preview-actions.js";
|
||||
import type { JDDPageState } from "./component-preview/jdd-page.js";
|
||||
import JDDPage from "./component-preview/jdd-page.js";
|
||||
|
||||
export const actionName = "Components";
|
||||
|
||||
export type ComponentPreviewState = {
|
||||
component: string;
|
||||
component_args: Record<string, unknown>;
|
||||
current_size?: string;
|
||||
};
|
||||
|
||||
export default new (class ComponentsPage extends StatefulPage<
|
||||
ComponentPreviewState,
|
||||
typeof ComponentPreviewActions
|
||||
> {
|
||||
export default new (class JddcomponentDebuggerPage extends JDDPage {
|
||||
renderParameterButtons(state: JDDPageState): Stringifiable {
|
||||
const all_components = super.getRegistryCompoments();
|
||||
return (
|
||||
<div>
|
||||
<input type="submit" value="Preview" />
|
||||
<select
|
||||
name="component"
|
||||
onchange={this.makeActionCallback("change_component")}
|
||||
autocomplete="off"
|
||||
>
|
||||
{Object.entries(all_components).map(([name]) => (
|
||||
<option
|
||||
value={name}
|
||||
selected={name == state.components[0].component_name}
|
||||
>
|
||||
{name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{this.makeActionButton(state, "randomize_args", "0")}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
actions = ComponentPreviewActions;
|
||||
|
||||
async getInitialState(ctx: BaseContext) {
|
||||
const [component_name, component] = Object.entries(registry.getAll())[0];
|
||||
const initial_state = {
|
||||
component: component_name,
|
||||
component_args: await component.getExampleValues(makeJDDContext(ctx)),
|
||||
components: [
|
||||
{
|
||||
component_name: component_name,
|
||||
args: await component.getExampleValues(makeJDDContext(ctx)),
|
||||
},
|
||||
],
|
||||
};
|
||||
return initial_state;
|
||||
}
|
||||
@ -73,7 +91,7 @@ export default new (class ComponentsPage extends StatefulPage<
|
||||
wrapInLayout(
|
||||
ctx: BaseContext,
|
||||
content: Templatable,
|
||||
state: ComponentPreviewState
|
||||
state: JDDPageState
|
||||
): Templatable {
|
||||
return html(
|
||||
ctx,
|
||||
@ -88,147 +106,11 @@ export default new (class ComponentsPage extends StatefulPage<
|
||||
(...args) =>
|
||||
tempstream`${defaultHead(...args)}${renderEarlyAssets(
|
||||
registry,
|
||||
documentContainerFromParsed([
|
||||
{
|
||||
component_name: state.component,
|
||||
args: state.component_args,
|
||||
},
|
||||
]),
|
||||
documentContainerFromParsed(state.components),
|
||||
makeJDDContext(ctx)
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
async preprocessOverrides(
|
||||
ctx: BaseContext,
|
||||
state: ComponentPreviewState,
|
||||
overrides: Record<string, unknown>
|
||||
) {
|
||||
const jdd_context = makeJDDContext(ctx);
|
||||
const component_name = state.component;
|
||||
if (!component_name) {
|
||||
throw new Error("Unspecified component name");
|
||||
}
|
||||
const component = registry.get(component_name);
|
||||
if (!component) {
|
||||
throw new Error(`Unknown component: ${component_name}`);
|
||||
}
|
||||
if (!hasShape({ component_args: predicates.object }, overrides)) {
|
||||
return overrides;
|
||||
}
|
||||
const promises = Object.entries(component.getArguments()).map(
|
||||
async ([arg_name, arg]) => {
|
||||
const value = overrides.component_args[arg_name];
|
||||
if (value) {
|
||||
const new_value = await arg.receivedToParsed(jdd_context, value);
|
||||
overrides.component_args[arg_name] = new_value;
|
||||
}
|
||||
}
|
||||
);
|
||||
await Promise.all(promises);
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
return overrides;
|
||||
}
|
||||
|
||||
containerSizes = ["320", "600", "800", "1024", "1300", "1920"];
|
||||
|
||||
render(ctx: BaseContext, state: ComponentPreviewState) {
|
||||
const jdd_context = makeJDDContext(ctx);
|
||||
const all_components = registry.getAll();
|
||||
const component =
|
||||
registry.get(state.component) || Object.values(all_components)[0];
|
||||
return (
|
||||
<div
|
||||
class="two-column"
|
||||
id="component-debugger"
|
||||
style="--resizable-column-width: 50vw"
|
||||
data-controller="component-debugger"
|
||||
>
|
||||
<div class="component-arguments">
|
||||
{/*The below button has to be here in order for it to be the default behavior */}
|
||||
<input type="submit" value="Preview" />
|
||||
<select
|
||||
name="component"
|
||||
onchange={this.makeActionCallback("change_component")}
|
||||
autocomplete="off"
|
||||
>
|
||||
{Object.entries(all_components).map(([name]) => (
|
||||
<option value={name} selected={name == state.component}>
|
||||
{name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{this.makeActionButton(state, "randomize_args")}
|
||||
<fieldset class="component-preview-parameters">
|
||||
<legend>Parameters</legend>
|
||||
{Object.entries(component.getArguments()).map(
|
||||
async ([arg_name, arg]) => (
|
||||
<ComponentInput
|
||||
{...{
|
||||
state,
|
||||
ctx,
|
||||
arg_path: [arg_name],
|
||||
arg,
|
||||
value:
|
||||
state.component_args[arg_name] === undefined
|
||||
? await arg.getExampleValue(jdd_context)
|
||||
: state.component_args[arg_name],
|
||||
onblur: this.rerender(),
|
||||
page: this,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
<input type="submit" value="Preview" />
|
||||
</fieldset>
|
||||
<code>{this.serializeState(ctx, state)}</code>
|
||||
</div>
|
||||
<div class="resize-gutter" data-component-debugger-target="gutter"></div>
|
||||
<div class="component-preview" data-component-debugger-target="preview">
|
||||
<fieldset>
|
||||
<legend>
|
||||
Preview{" "}
|
||||
<span data-component-debugger-target="component-width"></span>
|
||||
<select
|
||||
name="size"
|
||||
autocomplete="off"
|
||||
class="component-preview-size-select"
|
||||
data-component-debugger-target="size-select"
|
||||
data-action="change->component-debugger#handleWidthDropdown"
|
||||
>
|
||||
{this.containerSizes.map((size) => (
|
||||
<option
|
||||
value={size}
|
||||
selected={size === (state.current_size || "800")}
|
||||
>
|
||||
{`${size} px`}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<noscript>
|
||||
{this.makeActionButton(state, "change_size")}
|
||||
</noscript>
|
||||
</legend>
|
||||
{render(
|
||||
registry,
|
||||
documentContainerFromParsed([
|
||||
{
|
||||
component_name: state.component,
|
||||
args: state.component_args,
|
||||
},
|
||||
]),
|
||||
jdd_context
|
||||
)}
|
||||
</fieldset>
|
||||
{
|
||||
/* HTML */ `<script>
|
||||
(function () {
|
||||
const gutter = document.querySelector(".resize-gutter");
|
||||
})();
|
||||
</script>`
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
})();
|
||||
|
10
src/back/routes/jdd-preview.sreact.tsx
Normal file
10
src/back/routes/jdd-preview.sreact.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
import JDDCreator from "./component-preview/jdd-creator.js";
|
||||
|
||||
export const actionName = "JDDPreview";
|
||||
|
||||
export default new (class JDDCreatePreviewPage extends JDDCreator {
|
||||
// uncomment to create whitelist of allowed components
|
||||
// getAllowedComponents() {
|
||||
// return ["nice-box"];
|
||||
// }
|
||||
})();
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "node16",
|
||||
"module": "es2022",
|
||||
"moduleResolution": "node16",
|
||||
"noImplicitAny": true,
|
||||
"noImplicitThis": true,
|
||||
|
Loading…
x
Reference in New Issue
Block a user