/// import { Cell, cell, Default, derive, handler, ID, ifElse, NAME, navigateTo, recipe, str, UI, } from "commontools"; // User object for better data modeling interface User { name: string; } interface ChatMessage { [ID]: number; author: User; message: string; timestamp: number; x: number; y: number; hidden?: boolean; } type MainRecipeInput = { messages: Default; }; type UserSessionInput = { messages: Default; user: User; }; type UserSessionResult = { messages: Default; user: User; }; type InputEventType = { detail: { message: string; }; }; function formatTime(ts: number): string { const d = new Date(ts); return d.toLocaleTimeString(undefined, { hour: "numeric", minute: "2-digit", }); } const handleCanvasClick = handler< { detail: { x: number; y: number } }, { messages: Cell; user: Cell; } >((event, { messages, user }) => { // Create a new message at the clicked position with empty text const currentMessages = messages.get(); messages.push({ [ID]: currentMessages.length, author: user.get(), message: "", timestamp: Date.now(), x: event.detail.x, y: event.detail.y, }); }); const updateMessage = handler< InputEventType, { messages: Cell; index: number; } >((event, { messages, index }) => { const text = event.detail?.message ?? ""; messages.key(index).key("message").set(text); }); const updateMessagePosition = handler< { detail: { x: number; y: number } }, { messages: Cell; index: number; } >((event, { messages, index }) => { messages.key(index).key("x").set(event.detail.x); messages.key(index).key("y").set(event.detail.y); }); const deleteMessage = handler< any, { messages: Cell; index: number; } >((_, { messages, index }) => { // Set hidden flag instead of removing messages.key(index).key("hidden").set(true); }); const setUsername = handler< InputEventType, { user: Cell; } >((event, { user }) => { const name = (event.detail?.message ?? "").trim(); if (name) { user.key("name").set(name); } }); // User Session Recipe - Individual instance with local state export const UserSession = recipe< UserSessionInput, UserSessionResult >( "Canvas", ({ messages, user }) => { return { [NAME]: str`Canvas v19-random` as any, [UI]: (

{messages.map((m, index) => { return ( msg.x)} y={derive(m, (msg) => msg.y)} hidden={ifElse( derive(m, (msg) => msg.hidden === true), "true" as const, undefined, )} onpositionchange={updateMessagePosition({ messages, index, })} >
×
{m.author.name} · {derive(m.timestamp, formatTime)}
); })}
), messages, user, }; }, ); const createUserSession = handler< never, { messages: Default; } >((_, { messages }) => { const sessionCharm = UserSession({ messages: messages as any, user: cell({ name: "" }), }); return navigateTo(sessionCharm); }); export default recipe( "Canvas", ({ messages }) => { return { [NAME]: "Canvas", [UI]: (

Canvas

Messages: {messages.length}

Click below to create your personal session:

Generate User Session
), messages, }; }, );