Export template source from ba8d4f92667f39bb02d542cb492b71a32ec914bf:quixos-instance/packages/todo-react-runtime
This commit is contained in:
@@ -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",
|
||||
});
|
||||
+362
File diff suppressed because one or more lines are too long
+54
@@ -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>];
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
// This adapter keeps the implementation file hand-authored.
|
||||
export {createComponent as handler} from "../todo-react.impl.js";
|
||||
@@ -0,0 +1,2 @@
|
||||
// This adapter keeps the implementation file hand-authored.
|
||||
export {props as handler} from "../todo-react.impl.js";
|
||||
@@ -0,0 +1,2 @@
|
||||
// This adapter keeps the implementation file hand-authored.
|
||||
export {props as handler} from "../todo-react.impl.js";
|
||||
@@ -0,0 +1,2 @@
|
||||
// This adapter keeps the implementation file hand-authored.
|
||||
export {stopWatch as handler} from "../todo-react.impl.js";
|
||||
@@ -0,0 +1,2 @@
|
||||
// This adapter keeps the implementation file hand-authored.
|
||||
export {source as handler} from "../todo-react.impl.js";
|
||||
@@ -0,0 +1,3 @@
|
||||
// Generated by qx-scaffold-v1
|
||||
import {serveMigration} from "@quixos/camino-package-runtime";
|
||||
await serveMigration({});
|
||||
@@ -0,0 +1,15 @@
|
||||
// Generated by qx-scaffold-v1
|
||||
import {servePackageRuntime} from "@quixos/camino-package-runtime";
|
||||
import {createRuntime} from "./gen/qx.js";
|
||||
import {handler as impl0} from "./impl/createComponent.js";
|
||||
import {handler as impl1} from "./impl/sourceGet.js";
|
||||
import {handler as impl2} from "./impl/propsGet.js";
|
||||
import {handler as impl3} from "./impl/propsWatch.js";
|
||||
import {handler as impl4} from "./impl/propsWatchStop.js";
|
||||
servePackageRuntime(createRuntime({
|
||||
"createComponent": impl0,
|
||||
"sourceGet": impl1,
|
||||
"propsGet": impl2,
|
||||
"propsWatch": impl3,
|
||||
"propsWatchStop": impl4,
|
||||
}));
|
||||
@@ -0,0 +1,205 @@
|
||||
import {
|
||||
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;
|
||||
};
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
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 [title, status, priority, titleLive, statusLive, priorityLive, notesLive, dueDateLive, energyLive] = await Promise.all([
|
||||
ports.data["title.get"](), ports.data["status.get"](), ports.data["priority.get"](),
|
||||
ports.data.live["title.get"](), ports.data.live["status.get"](), ports.data.live["priority.get"](),
|
||||
ports.data.live["notes.get"](), ports.data.live["dueDate.get"](), ports.data.live["energy.get"](),
|
||||
]);
|
||||
const priorityText = priority && priority !== "NORMAL" ? ` ${priority.toLowerCase()}` : "";
|
||||
return {
|
||||
task_id: ports.data.objectId, 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;
|
||||
Reference in New Issue
Block a user