Export template source from 15b93e42825a3f8055f6305999a12c5b1cc73330:quixos-instance/packages/todo-react-runtime

This commit is contained in:
Quixos Template Publisher
2000-01-01 00:00:00 +00:00
commit 74aca20aac
22 changed files with 1651 additions and 0 deletions
@@ -0,0 +1,29 @@
// @generated by camino-codegen-ts-runtime. Do not edit.
import {
createWebStudioComponent,
type ReactComponentHostProps,
type ObjectRef,
} from "@quixos/web-studio-react-runtime";
export type TodoReactComponentRenderProps = {
"selected"?: boolean;
"compact"?: boolean;
};
export type TodoReactComponentAction = {
"type"?: TodoReactComponentActionType;
};
export type TodoReactComponentActionType = "TODO_REACT_COMPONENT_ACTION_TYPE_UNSPECIFIED" | "SELECT";
export type TodoComponentProps = {
forObject: ObjectRef<"atom:task">;
} & TodoReactComponentRenderProps & ReactComponentHostProps<TodoReactComponentAction>;
export const TodoComponent = createWebStudioComponent<
ObjectRef<"atom:task">,
TodoReactComponentRenderProps,
TodoReactComponentAction
>({
expectedAtomId: "atom:task",
});
+232
View File
@@ -0,0 +1,232 @@
// Generated by quixos-codegen-ts. Do not edit. Binding ABI version 1.
import { bindQxHandler, type BindingValue, type QxObjectRef, type QxWatchHandle, type QxHandler, type QxDerived, type QxContextLifecycle, type QxLiveValue, type RelationshipCollection, type QxHandlerSpec, type QxMessages } from "@quixos/camino-package-runtime";
import { opaqueReactPropsBinding as message0 } from "@quixos/camino-package-runtime";
export const packageRevisionId = "package:todo-react-runtime@1";
export type Contexts = { "createComponent": { "objectId": QxObjectRef<"atom:atom:task-react-component">; "input": QxObjectRef<"atom:atom:task">; "ports": { } } & QxContextLifecycle<{ "objectId": QxObjectRef<"atom:atom:task-react-component">; "input": QxObjectRef<"atom:atom:task">; "ports": { } } & {signal?: AbortSignal}>; "propsGet": { "objectId": QxObjectRef<"atom:atom:task-react-component">; "input": null; "ports": { "subject": { "resolve": () => Promise<Array<QxObjectRef<"atom:atom:task">>>; "collection": () => Promise<RelationshipCollection<QxObjectRef<"atom:atom:task">>> }; "title": { "get": () => Promise<string>; "live": () => Promise<QxLiveValue> }; "status": { "get": () => Promise<string>; "live": () => Promise<QxLiveValue> }; "priority": { "get": () => Promise<string>; "live": () => Promise<QxLiveValue> }; "notes": { "get": () => Promise<string>; "live": () => Promise<QxLiveValue> }; "dueDate": { "get": () => Promise<string>; "live": () => Promise<QxLiveValue> }; "energy": { "get": () => Promise<string>; "live": () => Promise<QxLiveValue> } } } & QxContextLifecycle<{ "objectId": QxObjectRef<"atom:atom:task-react-component">; "input": null; "ports": { "subject": { "resolve": () => Promise<Array<QxObjectRef<"atom:atom:task">>>; "collection": () => Promise<RelationshipCollection<QxObjectRef<"atom:atom:task">>> }; "title": { "get": () => Promise<string>; "live": () => Promise<QxLiveValue> }; "status": { "get": () => Promise<string>; "live": () => Promise<QxLiveValue> }; "priority": { "get": () => Promise<string>; "live": () => Promise<QxLiveValue> }; "notes": { "get": () => Promise<string>; "live": () => Promise<QxLiveValue> }; "dueDate": { "get": () => Promise<string>; "live": () => Promise<QxLiveValue> }; "energy": { "get": () => Promise<string>; "live": () => Promise<QxLiveValue> } } } & {signal?: AbortSignal}>; "propsWatch": { "objectId": QxObjectRef<"atom:atom:task-react-component">; "input": null; "ports": { "subject": { "resolve": () => Promise<Array<QxObjectRef<"atom:atom:task">>>; "collection": () => Promise<RelationshipCollection<QxObjectRef<"atom:atom:task">>> }; "title": { "get": () => Promise<string>; "live": () => Promise<QxLiveValue> }; "status": { "get": () => Promise<string>; "live": () => Promise<QxLiveValue> }; "priority": { "get": () => Promise<string>; "live": () => Promise<QxLiveValue> }; "notes": { "get": () => Promise<string>; "live": () => Promise<QxLiveValue> }; "dueDate": { "get": () => Promise<string>; "live": () => Promise<QxLiveValue> }; "energy": { "get": () => Promise<string>; "live": () => Promise<QxLiveValue> } } } & QxContextLifecycle<{ "objectId": QxObjectRef<"atom:atom:task-react-component">; "input": null; "ports": { "subject": { "resolve": () => Promise<Array<QxObjectRef<"atom:atom:task">>>; "collection": () => Promise<RelationshipCollection<QxObjectRef<"atom:atom:task">>> }; "title": { "get": () => Promise<string>; "live": () => Promise<QxLiveValue> }; "status": { "get": () => Promise<string>; "live": () => Promise<QxLiveValue> }; "priority": { "get": () => Promise<string>; "live": () => Promise<QxLiveValue> }; "notes": { "get": () => Promise<string>; "live": () => Promise<QxLiveValue> }; "dueDate": { "get": () => Promise<string>; "live": () => Promise<QxLiveValue> }; "energy": { "get": () => Promise<string>; "live": () => Promise<QxLiveValue> } } } & {signal?: AbortSignal}>; "propsWatchStop": { "objectId": QxObjectRef<"atom:atom:task-react-component">; "input": QxWatchHandle; "ports": { } } & QxContextLifecycle<{ "objectId": QxObjectRef<"atom:atom:task-react-component">; "input": QxWatchHandle; "ports": { } } & {signal?: AbortSignal}>; "sourceGet": { "objectId": QxObjectRef<"atom:atom:task-react-component">; "input": null; "ports": { } } & QxContextLifecycle<{ "objectId": QxObjectRef<"atom:atom:task-react-component">; "input": null; "ports": { } } & {signal?: AbortSignal}> };
export type Implementation = { "createComponent": QxHandler<Contexts["createComponent"], QxObjectRef<"atom:atom:task-react-component">>; "propsGet": QxHandler<Contexts["propsGet"], BindingValue<typeof message0>> | QxDerived<Contexts["propsGet"], BindingValue<typeof message0>>; "propsWatch": QxDerived<Contexts["propsWatch"], BindingValue<typeof message0>>; "propsWatchStop": QxHandler<Contexts["propsWatchStop"], null>; "sourceGet": QxHandler<Contexts["sourceGet"], string> | QxDerived<Contexts["sourceGet"], string> };
const messages = { "org.quixos.web-studio.ReactProps": message0 } satisfies QxMessages;
const specs = {
"createComponent": {
"inputType": {
"kind": "object-ref",
"expectation": {
"kind": "atom",
"atomId": "atom:task"
}
},
"outputType": {
"kind": "object-ref",
"expectation": {
"kind": "atom",
"atomId": "atom:task-react-component"
}
},
"ports": {}
},
"propsGet": {
"inputType": {
"kind": "builtin",
"name": "unit"
},
"outputType": {
"kind": "message",
"descriptorId": "org.quixos.web-studio.ReactProps"
},
"ports": {
"subject": {
"kind": "edge",
"id": "port:todo-react:subject",
"primitives": [
"resolve"
]
},
"title": {
"kind": "state",
"valueType": {
"kind": "scalar",
"name": "string"
},
"primitives": [
"read"
],
"id": "port:todo-react:title"
},
"status": {
"kind": "state",
"valueType": {
"kind": "scalar",
"name": "string"
},
"primitives": [
"read"
],
"id": "port:todo-react:status"
},
"priority": {
"kind": "state",
"valueType": {
"kind": "scalar",
"name": "string"
},
"primitives": [
"read"
],
"id": "port:todo-react:priority"
},
"notes": {
"kind": "state",
"valueType": {
"kind": "scalar",
"name": "string"
},
"primitives": [
"read"
],
"id": "port:todo-react:notes"
},
"dueDate": {
"kind": "state",
"valueType": {
"kind": "scalar",
"name": "string"
},
"primitives": [
"read"
],
"id": "port:todo-react:due-date"
},
"energy": {
"kind": "state",
"valueType": {
"kind": "scalar",
"name": "string"
},
"primitives": [
"read"
],
"id": "port:todo-react:energy"
}
}
},
"propsWatch": {
"inputType": {
"kind": "builtin",
"name": "unit"
},
"outputType": {
"kind": "builtin",
"name": "watch-handle"
},
"eventType": {
"kind": "message",
"descriptorId": "org.quixos.web-studio.ReactProps"
},
"ports": {
"subject": {
"kind": "edge",
"id": "port:todo-react:subject",
"primitives": [
"resolve"
]
},
"title": {
"kind": "state",
"valueType": {
"kind": "scalar",
"name": "string"
},
"primitives": [
"read"
],
"id": "port:todo-react:title"
},
"status": {
"kind": "state",
"valueType": {
"kind": "scalar",
"name": "string"
},
"primitives": [
"read"
],
"id": "port:todo-react:status"
},
"priority": {
"kind": "state",
"valueType": {
"kind": "scalar",
"name": "string"
},
"primitives": [
"read"
],
"id": "port:todo-react:priority"
},
"notes": {
"kind": "state",
"valueType": {
"kind": "scalar",
"name": "string"
},
"primitives": [
"read"
],
"id": "port:todo-react:notes"
},
"dueDate": {
"kind": "state",
"valueType": {
"kind": "scalar",
"name": "string"
},
"primitives": [
"read"
],
"id": "port:todo-react:due-date"
},
"energy": {
"kind": "state",
"valueType": {
"kind": "scalar",
"name": "string"
},
"primitives": [
"read"
],
"id": "port:todo-react:energy"
}
}
},
"propsWatchStop": {
"inputType": {
"kind": "builtin",
"name": "watch-handle"
},
"outputType": {
"kind": "builtin",
"name": "unit"
},
"ports": {}
},
"sourceGet": {
"inputType": {
"kind": "builtin",
"name": "unit"
},
"outputType": {
"kind": "scalar",
"name": "string"
},
"ports": {}
}
} satisfies Record<string, QxHandlerSpec>;
export const createRuntime = (implementation: Implementation) => ({
packageRevisionId,
exports: {
"export:todo-react-runtime:create-component": bindQxHandler(specs["createComponent"], implementation["createComponent"], messages),
"export:todo-react-runtime:props-get": bindQxHandler(specs["propsGet"], implementation["propsGet"], messages),
"export:todo-react-runtime:props-watch": bindQxHandler(specs["propsWatch"], implementation["propsWatch"], messages),
"export:todo-react-runtime:props-watch-stop": bindQxHandler(specs["propsWatchStop"], implementation["propsWatchStop"], messages),
"export:todo-react-runtime:source-get": bindQxHandler(specs["sourceGet"], implementation["sourceGet"], messages),
},
});
+54
View File
@@ -0,0 +1,54 @@
declare module "@quixos/web-studio-react-runtime" {
export type ObjectRef<AtomId extends string> = string & {
readonly $quixosAtom: AtomId;
};
export type LiveFieldProp<T> = {
value: T;
source: {
objectId: string;
slotId: string;
valueType?: string;
storagePolicy?: string;
revision?: string | number | bigint;
crdtSnapshot?: {
type: string;
encoding: string;
payload: string;
};
};
};
export type ReactComponentHostProps<Action> = {
onAction?: (action: Action) => void;
fallback?: unknown;
className?: string;
style?: Record<string, string | number | undefined>;
onError?: (error: Error) => void;
};
export type ReactComponentImplementationProps<CaminoProps, RenderProps, Action> = {
camino: CaminoProps;
render: RenderProps;
dispatch: (action: Action) => void;
};
export const createWebStudioComponent: <
ForObject extends ObjectRef<string>, RenderProps extends object, Action,
>(config: { expectedAtomId: string }) => (props: {
forObject: ForObject;
} & RenderProps & ReactComponentHostProps<Action>) => any;
export const invokeCapability: <Result = unknown>(
objectId: string,
interfaceRevisionId: string,
operationId: string,
value?: unknown,
) => Promise<Result>;
export const h: (...args: any[]) => any;
export const useLiveField: <T>(
field: LiveFieldProp<T>,
options?: {
reconcileRegister?: (state: {
confirmed: T;
optimistic: T;
pending: boolean;
}) => T;
},
) => [T, (value: T) => void | Promise<void>];
}
+8
View File
@@ -0,0 +1,8 @@
import { servePackageRuntime } from "@quixos/camino-package-runtime";
import { createRuntime, type Implementation } from "./gen/qx.js";
import { createComponent, props, source, stopWatch } from "./todo-react.impl.js";
const implementation: Implementation = {
createComponent, sourceGet: source, propsGet: props, propsWatch: props, propsWatchStop: stopWatch,
};
servePackageRuntime(createRuntime(implementation));
+222
View File
@@ -0,0 +1,222 @@
import {
h,
useLiveField,
type LiveFieldProp,
type ReactComponentImplementationProps,
} from "@quixos/web-studio-react-runtime";
import type {
TodoReactComponentAction,
TodoReactComponentRenderProps,
} from "./gen/quixos.todo.TodoReactComponent.react.js";
type TodoReactComponentCaminoProps = {
task_id: string;
title: LiveFieldProp<string>;
status: LiveFieldProp<string>;
priority: LiveFieldProp<string>;
notes: LiveFieldProp<string>;
due_date: LiveFieldProp<string>;
energy: LiveFieldProp<string>;
display_label: string;
};
declare global {
namespace JSX {
interface IntrinsicElements {
div: Record<string, unknown>;
button: Record<string, unknown>;
input: Record<string, unknown>;
label: Record<string, unknown>;
option: Record<string, unknown>;
select: Record<string, unknown>;
span: Record<string, unknown>;
strong: Record<string, unknown>;
textarea: Record<string, unknown>;
}
}
}
const palette = {
ink: "#28362d",
muted: "#6f776e",
cream: "#fffdf5",
line: "#d9d0ba",
moss: "#5f7f63",
honey: "#d99b3d",
berry: "#a9544f",
};
const statusTone = (status: string) => status === "DONE"
? palette.moss
: status === "ARCHIVED" ? palette.muted : "#52749a";
const fieldLabelStyle = {
display: "grid",
gap: "4px",
color: palette.muted,
fontSize: "10px",
fontWeight: 750,
letterSpacing: "0.08em",
textTransform: "uppercase" as const,
};
const controlStyle = {
minWidth: 0,
width: "100%",
border: `1px solid ${palette.line}`,
borderRadius: "7px",
background: "#fff",
color: palette.ink,
fontSize: "11px",
padding: "6px 8px",
};
type TodoComponentImplementationProps = ReactComponentImplementationProps<
TodoReactComponentCaminoProps,
TodoReactComponentRenderProps,
TodoReactComponentAction
>;
export default function TodoComponent({
camino: props,
render,
dispatch,
}: TodoComponentImplementationProps) {
const [title, setTitle] = useLiveField(props.title);
const [status, setStatus] = useLiveField(props.status);
const [priority, setPriority] = useLiveField(props.priority);
const [notes, setNotes] = useLiveField(props.notes);
const [dueDate, setDueDate] = useLiveField(props.due_date);
const [energy, setEnergy] = useLiveField(props.energy);
const done = status === "DONE";
return (
<div style={{
display: "grid",
gap: render.compact ? "9px" : "13px",
minWidth: 0,
height: "100%",
padding: render.compact ? "11px" : "15px",
border: render.selected ? `2px solid ${palette.honey}` : `1px solid ${palette.line}`,
borderRadius: "12px",
background: palette.cream,
boxShadow: render.selected ? "0 0 0 3px rgba(217,155,61,.14)" : "none",
color: palette.ink,
fontFamily: "Avenir Next, Nunito, ui-sans-serif, system-ui, sans-serif",
overflow: "auto",
}}>
<div style={{ display: "flex", alignItems: "flex-start", justifyContent: "space-between", gap: "10px" }}>
<button
type="button"
aria-label={done ? "Reopen task" : "Complete task"}
onClick={() => void setStatus(done ? "OPEN" : "DONE")}
style={{
flex: "0 0 auto",
width: "26px",
height: "26px",
border: `1px solid ${done ? palette.moss : palette.line}`,
borderRadius: "8px",
background: done ? palette.moss : "#fff",
color: "#fff",
fontWeight: 900,
cursor: "pointer",
}}
>
{done ? "✓" : ""}
</button>
<div style={{ display: "grid", flex: 1, gap: "3px", minWidth: 0 }}>
<strong style={{
overflow: "hidden",
fontSize: render.compact ? "14px" : "17px",
lineHeight: 1.25,
textDecoration: done ? "line-through" : "none",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
}}>
{title || "Untitled task"}
</strong>
<span style={{ color: statusTone(status), fontSize: "10px", fontWeight: 800, letterSpacing: ".08em" }}>
{status || "OPEN"} · {priority || "NORMAL"}
</span>
</div>
<button
type="button"
onClick={() => dispatch({ type: "SELECT" })}
style={{
border: "none",
background: "transparent",
color: palette.muted,
cursor: "pointer",
fontSize: "12px",
}}
>
{render.selected ? "Pinned" : "Open"}
</button>
</div>
{!render.compact ? (
<div style={{ display: "contents" }}>
<label style={fieldLabelStyle}>
Title
<input
value={title}
onInput={(event: { currentTarget: HTMLInputElement }) => void setTitle(event.currentTarget.value)}
style={controlStyle}
/>
</label>
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(112px, 1fr))", gap: "8px" }}>
<label style={fieldLabelStyle}>
Status
<select style={controlStyle} value={status} onChange={(event: { currentTarget: HTMLSelectElement }) => void setStatus(event.currentTarget.value)}>
<option value="OPEN">Open</option>
<option value="DONE">Done</option>
<option value="ARCHIVED">Archived</option>
</select>
</label>
<label style={fieldLabelStyle}>
Priority
<select style={controlStyle} value={priority} onChange={(event: { currentTarget: HTMLSelectElement }) => void setPriority(event.currentTarget.value)}>
<option value="LOW">Low</option>
<option value="NORMAL">Normal</option>
<option value="HIGH">High</option>
</select>
</label>
<label style={fieldLabelStyle}>
Energy
<select style={controlStyle} value={energy} onChange={(event: { currentTarget: HTMLSelectElement }) => void setEnergy(event.currentTarget.value)}>
<option value="LOW">Low</option>
<option value="MEDIUM">Medium</option>
<option value="HIGH">High</option>
</select>
</label>
<label style={fieldLabelStyle}>
Due
<input style={controlStyle} type="date" value={dueDate} onInput={(event: { currentTarget: HTMLInputElement }) => void setDueDate(event.currentTarget.value)} />
</label>
</div>
<label style={fieldLabelStyle}>
Notes
<textarea
aria-label="Notes"
value={notes}
onInput={(event: { currentTarget: HTMLTextAreaElement }) => void setNotes(event.currentTarget.value)}
rows={4}
placeholder="Leave a trail for your future self…"
style={{ ...controlStyle, minHeight: "78px", resize: "vertical" }}
/>
</label>
</div>
) : (
<div style={{ display: "flex", flexWrap: "wrap", gap: "6px", color: palette.muted, fontSize: "11px" }}>
{dueDate ? <span>Due {dueDate}</span> : <span>No due date</span>}
<span>·</span>
<span>{energy || "MEDIUM"} energy</span>
</div>
)}
<div style={{ color: palette.muted, fontSize: "10px", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
{props.task_id}
</div>
</div>
);
}
+24
View File
@@ -0,0 +1,24 @@
import fs from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import type { Contexts } from "./gen/qx.js";
const componentSourcePath = path.join(path.dirname(fileURLToPath(import.meta.url)), "todo-component.mjs");
export const source = () => fs.readFile(componentSourcePath, "utf8");
export const createComponent = ({ objectId }: Contexts["createComponent"]) => objectId;
export const props = { kind: "derived" as const, get: async ({ objectId, ports }: Contexts["propsGet"]): Promise<Record<string, unknown>> => {
const tasks = await ports.subject.resolve();
if (tasks.length !== 1) throw new Error(`Task component has ${tasks.length} subjects`);
const [title, status, priority, titleLive, statusLive, priorityLive, notesLive, dueDateLive, energyLive] = await Promise.all([
ports.title.get(), ports.status.get(), ports.priority.get(),
ports.title.live(), ports.status.live(), ports.priority.live(),
ports.notes.live(), ports.dueDate.live(), ports.energy.live(),
]);
const priorityText = priority && priority !== "NORMAL" ? ` ${priority.toLowerCase()}` : "";
return {
task_id: tasks[0], component_id: objectId, title: titleLive, status: statusLive,
priority: priorityLive, notes: notesLive, due_date: dueDateLive, energy: energyLive,
display_label: `[${status || "OPEN"}]${priorityText} ${title || "Untitled task"}`,
};
}};
export const stopWatch = () => null;