Page MenuHomeSealhub

jdd-page.tsx
No OneTemporary

jdd-page.tsx

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>
);
}
}

File Metadata

Mime Type
text/x-java
Expires
Fri, Nov 22, 20:14 (19 h, 48 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
547731
Default Alt Text
jdd-page.tsx (7 KB)

Event Timeline