ttrpg-tools/src/components/journal/StreamMessage.tsx

76 lines
2.3 KiB
TypeScript
Raw Normal View History

/**
* StreamMessage single message card in the stream
*/
import { Component, Show, createMemo } from "solid-js";
import type { StreamMessage as StreamMessageType } from "./registry";
import { getMessageType } from "./registry";
export const StreamMessageCard: Component<{
message: StreamMessageType;
}> = (props) => {
const def = createMemo(() => getMessageType(props.message.type));
const rendered = createMemo(() =>
def()?.render?.(props.message.payload, props.message as any),
);
const time = createMemo(() => {
const d = new Date(props.message.timestamp);
return d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
});
const senderClass = () =>
props.message.sender === "gm"
? "text-purple-600 font-semibold"
: "text-blue-600 font-semibold";
return (
<Show
when={!props.message.reverted}
fallback={<RevealedCard msg={props.message} />}
>
<div class="bg-white rounded-lg border border-gray-200 shadow-sm overflow-hidden">
{/* Header */}
<div class="flex items-center gap-1.5 px-2.5 py-1 bg-gray-50 border-b border-gray-100">
<span class={`text-xs ${senderClass()}`}>{props.message.sender}</span>
<span class="text-xs text-gray-400">{time()}</span>
<span class="text-xs text-gray-300 bg-gray-100 px-1 rounded ml-auto">
{props.message.type}
</span>
</div>
{/* Body */}
<div class="px-2.5 py-1.5 text-sm text-gray-800">
<Show
when={rendered()}
fallback={<JsonBody payload={props.message.payload} />}
>
{rendered()}
</Show>
</div>
</div>
</Show>
);
};
/** Fallback: render payload as formatted JSON */
const JsonBody: Component<{ payload: unknown }> = (props) => {
const text = () => JSON.stringify(props.payload, null, 2);
return (
<pre class="text-xs text-gray-600 whitespace-pre-wrap font-mono">
{text()}
</pre>
);
};
/** Reverted card: struck-through, grey */
const RevealedCard: Component<{ msg: StreamMessageType }> = (props) => {
return (
<div class="bg-gray-100 rounded-lg border border-gray-200 opacity-50 px-2.5 py-1.5">
<span class="text-xs text-gray-400 line-through">
{props.msg.sender} reverted
</span>
</div>
);
};