Files
Ivan Fontosh d1f180ee0a Initial TTRPG character meta-builder with visual sheet editor.
Offline Electron app seeds 10 system presets, supports create-from-scratch designer with palette/drag/resize/bricks, and exports .ttrpg-system.json for the player.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-12 14:49:31 +08:00

96 lines
3.2 KiB
TypeScript

import type { CharacterValues, SheetWidget, SystemDefinition } from '../shared/types.ts';
import { computeDerived } from '../shared/derived.ts';
type Props = {
widget: SheetWidget;
system: SystemDefinition;
};
/** Compact in-widget preview for design mode (no nested canvas). */
export function WidgetPreview({ widget, system }: Props) {
const values: CharacterValues = {
attributes: Object.fromEntries(
(system.bricks.attributes ?? []).map((a) => [a.key, typeof a.default === 'number' ? a.default : 10]),
),
skills: Object.fromEntries(
(system.bricks.skills ?? []).map((a) => [a.key, typeof a.default === 'number' ? a.default : 0]),
),
resources: Object.fromEntries((system.bricks.resources ?? []).map((a) => [a.key, a.default ?? 0])),
meta: { notes: '…', title: system.name },
};
const derived = computeDerived(system.bricks.derived ?? [], values);
const bind = widget.bind ?? '';
const [, key] = bind.split('.');
if (widget.type === 'section') {
return <strong>{String(widget.props?.title ?? 'Section')}</strong>;
}
if (widget.type === 'dots') {
const max = Number(widget.props?.max ?? 5);
const cur = Number(values.attributes?.[key!] ?? 2);
return (
<div>
<div className="muted">{String(widget.props?.label ?? key)}</div>
<div className="row">
{Array.from({ length: max }, (_, i) => (
<span
key={i}
style={{
width: 12,
height: 12,
borderRadius: '50%',
border: '1px solid var(--stroke2)',
background: i < cur ? 'var(--accent2)' : 'transparent',
}}
/>
))}
</div>
</div>
);
}
if (widget.type === 'track' || widget.type === 'resourceBar') {
const v = Number(values.resources?.[key!] ?? 5);
const max = Number(widget.props?.max ?? 10);
return (
<div>
<div className="muted">{String(widget.props?.label ?? key)}</div>
<div style={{ height: 8, background: 'var(--stroke)', borderRadius: 4 }}>
<div style={{ width: `${(v / max) * 100}%`, height: '100%', background: 'var(--accent)' }} />
</div>
</div>
);
}
if (widget.type === 'skillList') {
return (
<div className="muted">
{(system.bricks.skills ?? []).slice(0, 4).map((s) => (
<div key={s.key}>
{s.label}: {String(values.skills?.[s.key] ?? 0)}
</div>
))}
…
</div>
);
}
if (widget.type === 'notes' || widget.type === 'text') {
return <div className="muted">{String(values.meta?.notes ?? 'Notes')}</div>;
}
if (bind.startsWith('derived.')) {
return (
<div>
<div className="muted">{String(widget.props?.label ?? key)}</div>
<div style={{ fontSize: 20, fontWeight: 700 }}>{derived[key!] ?? 0}</div>
</div>
);
}
if (bind.startsWith('attr.')) {
return (
<div>
<div className="muted">{String(widget.props?.label ?? key)}</div>
<div style={{ fontSize: 20, fontWeight: 700 }}>{String(values.attributes?.[key!] ?? 10)}</div>
</div>
);
}
return <div className="muted">{widget.type}</div>;
}