144 lines
3.7 KiB
Svelte
144 lines
3.7 KiB
Svelte
<script lang="ts">
|
|
import { getSegmentsUpToChar } from "./utils";
|
|
import { user } from "$lib/config";
|
|
import TuiLine from "./TuiLine.svelte";
|
|
import type { DisplayedLine, TerminalLine } from "./types";
|
|
import "$lib/assets/css/tui-body.css";
|
|
|
|
interface Props {
|
|
displayedLines: DisplayedLine[];
|
|
currentLineIndex?: number;
|
|
isTyping?: boolean;
|
|
selectedIndex?: number;
|
|
ref?: HTMLDivElement | undefined;
|
|
onButtonClick: (idx: number, line?: TerminalLine) => void;
|
|
onHoverButton: (idx: number) => void;
|
|
onLinkClick: (idx: number) => void;
|
|
terminalSettings: { showCursor: boolean };
|
|
}
|
|
|
|
let {
|
|
displayedLines = [],
|
|
currentLineIndex = 0,
|
|
isTyping = false,
|
|
selectedIndex = -1,
|
|
ref = $bindable(undefined),
|
|
onButtonClick,
|
|
onHoverButton,
|
|
onLinkClick,
|
|
terminalSettings,
|
|
}: Props = $props();
|
|
|
|
// Group consecutive inline items together
|
|
type ProcessedGroup =
|
|
| { kind: "single"; index: number; displayed: DisplayedLine }
|
|
| {
|
|
kind: "inline";
|
|
items: Array<{ index: number; displayed: DisplayedLine }>;
|
|
};
|
|
|
|
const processedGroups = $derived.by(() => {
|
|
const groups: ProcessedGroup[] = [];
|
|
let i = 0;
|
|
|
|
while (i < displayedLines.length) {
|
|
const displayed = displayedLines[i];
|
|
const isInline =
|
|
displayed.parsed.line.inline ||
|
|
displayed.parsed.line.display === "inline";
|
|
|
|
if (isInline) {
|
|
const inlineItems: Array<{
|
|
index: number;
|
|
displayed: DisplayedLine;
|
|
}> = [];
|
|
// Collect consecutive inline items
|
|
while (i < displayedLines.length) {
|
|
const nextLine = displayedLines[i].parsed.line;
|
|
const nextIsInline =
|
|
nextLine.inline || nextLine.display === "inline";
|
|
|
|
if (!nextIsInline) break;
|
|
|
|
inlineItems.push({
|
|
index: i,
|
|
displayed: displayedLines[i],
|
|
});
|
|
i++;
|
|
}
|
|
groups.push({ kind: "inline", items: inlineItems });
|
|
} else {
|
|
groups.push({ kind: "single", index: i, displayed });
|
|
i++;
|
|
}
|
|
}
|
|
return groups;
|
|
});
|
|
</script>
|
|
|
|
<div class="tui-body" bind:this={ref}>
|
|
{#each processedGroups as group, gi (gi)}
|
|
{#if group.kind === "inline"}
|
|
<div class="tui-inline-group">
|
|
{#each group.items as item (item.index)}
|
|
<TuiLine
|
|
line={item.displayed.parsed.line}
|
|
index={item.index}
|
|
segments={getSegmentsUpToChar(
|
|
item.displayed.parsed.segments,
|
|
item.displayed.charIndex,
|
|
)}
|
|
complete={item.displayed.complete}
|
|
showImage={item.displayed.showImage}
|
|
{selectedIndex}
|
|
inline={true}
|
|
showCursor={terminalSettings.showCursor &&
|
|
item.index === currentLineIndex &&
|
|
!item.displayed.complete &&
|
|
isTyping &&
|
|
item.displayed.parsed.line.type !== "image"}
|
|
{onButtonClick}
|
|
{onHoverButton}
|
|
{onLinkClick}
|
|
/>
|
|
{/each}
|
|
</div>
|
|
{:else}
|
|
<TuiLine
|
|
line={group.displayed.parsed.line}
|
|
index={group.index}
|
|
segments={getSegmentsUpToChar(
|
|
group.displayed.parsed.segments,
|
|
group.displayed.charIndex,
|
|
)}
|
|
complete={group.displayed.complete}
|
|
showImage={group.displayed.showImage}
|
|
{selectedIndex}
|
|
inline={false}
|
|
showCursor={terminalSettings.showCursor &&
|
|
group.index === currentLineIndex &&
|
|
!group.displayed.complete &&
|
|
isTyping &&
|
|
group.displayed.parsed.line.type !== "image"}
|
|
{onButtonClick}
|
|
{onHoverButton}
|
|
{onLinkClick}
|
|
/>
|
|
{/if}
|
|
{/each}
|
|
|
|
{#if terminalSettings.showCursor && !isTyping && displayedLines.length > 0}
|
|
<div class="tui-line prompt">
|
|
<span class="prompt">
|
|
<span class="user">{user.username}</span><span class="at"
|
|
>@</span
|
|
><span class="host">{user.hostname}</span>
|
|
<span class="separator">:</span><span class="path">~</span><span
|
|
class="symbol">$</span
|
|
>
|
|
</span>
|
|
<span class="cursor blink"></span>
|
|
</div>
|
|
{/if}
|
|
</div>
|