You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

121 lines
3.1 KiB
TypeScript

import { useEffect, useRef, type CSSProperties, type RefObject } from 'react';
import type { AdventureLogEntry } from '../game/types';
import { useT } from '../i18n';
function formatTime(timestamp: number): string {
const d = new Date(timestamp);
const hh = String(d.getHours()).padStart(2, '0');
const mm = String(d.getMinutes()).padStart(2, '0');
return `${hh}:${mm}`;
}
const scrollAreaStyle: CSSProperties = {
flex: 1,
overflowY: 'auto',
padding: '4px 2px',
fontSize: 12,
lineHeight: 1.6,
color: '#bbb',
maxHeight: 'min(52vh, 420px)',
};
const entryStyle: CSSProperties = {
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
};
const timestampStyle: CSSProperties = {
color: 'rgba(140,160,200,0.7)',
marginRight: 4,
fontFamily: 'monospace',
fontSize: 11,
};
const battleChildStyle: CSSProperties = {
paddingLeft: 14,
fontSize: 11,
color: '#9aa',
whiteSpace: 'normal',
overflow: 'visible',
textOverflow: 'unset',
lineHeight: 1.45,
};
const detailsStyle: CSSProperties = {
marginBottom: 4,
};
const summaryStyle: CSSProperties = {
cursor: 'pointer',
};
/** Scrollable adventure log list (Hero sheet Journal tab). */
export function AdventureLogEntries({
entries,
scrollRef,
}: {
entries: AdventureLogEntry[];
scrollRef?: RefObject<HTMLDivElement | null>;
}) {
const tr = useT();
const innerRef = useRef<HTMLDivElement>(null);
const ref = scrollRef ?? innerRef;
useEffect(() => {
if (ref.current) {
ref.current.scrollTop = ref.current.scrollHeight;
}
}, [entries.length, ref]);
const lastBattleIdx = (() => {
for (let i = entries.length - 1; i >= 0; i--) {
if (entries[i]?.kind === 'battle_group') return i;
}
return -1;
})();
return (
<div ref={ref} style={scrollAreaStyle}>
{entries.length === 0 && (
<div style={{ textAlign: 'center', opacity: 0.5, padding: 12 }}>
{tr.noEventsYet}
</div>
)}
{entries.map((entry, idx) => {
if (entry.kind === 'battle_group') {
const isLastBattle = idx === lastBattleIdx;
return (
<details
key={entry.id}
className="ah-adventure-details"
style={detailsStyle}
open={isLastBattle}
>
<summary style={summaryStyle}>
<span className="ah-adventure-summary-text">
<span style={timestampStyle}>[{formatTime(entry.timestamp)}]</span>
<span style={{ ...entryStyle, whiteSpace: 'normal' }}>{entry.title}</span>
</span>
</summary>
<div style={{ marginTop: 4 }}>
{entry.lines.map((line) => (
<div key={line.id} style={battleChildStyle}>
{line.message}
</div>
))}
</div>
</details>
);
}
return (
<div key={entry.id} style={entryStyle}>
<span style={timestampStyle}>[{formatTime(entry.timestamp)}]</span>
{entry.message}
</div>
);
})}
</div>
);
}