Files
website/src/lib/components/tui/TuiLine.svelte
T
2025-11-29 22:14:25 +00:00

144 lines
5.7 KiB
Svelte

<script lang="ts">
import Icon from '@iconify/svelte';
import { getSegmentStyle, getLinePrefix } from './utils';
import { handleNavigation } from './terminal-keyboard';
import { user } from '$lib/config';
import TuiButton from './TuiButton.svelte';
import TuiLink from './TuiLink.svelte';
import TuiCard from './TuiCard.svelte';
import TuiProgress from './TuiProgress.svelte';
import TuiAccordion from './TuiAccordion.svelte';
import TuiTable from './TuiTable.svelte';
import TuiTooltip from './TuiTooltip.svelte';
import TuiCardGrid from './TuiCardGrid.svelte';
import TuiInput from './TuiInput.svelte';
import TuiTextarea from './TuiTextarea.svelte';
import TuiCheckbox from './TuiCheckbox.svelte';
import TuiRadio from './TuiRadio.svelte';
import TuiSelect from './TuiSelect.svelte';
import TuiToggle from './TuiToggle.svelte';
import TuiGroup from './TuiGroup.svelte';
import type { TerminalLine, TextSegment } from './types';
interface Props {
line: TerminalLine;
index: number;
segments: TextSegment[];
complete?: boolean;
showImage?: boolean;
selectedIndex?: number;
inline?: boolean;
showCursor?: boolean;
onButtonClick?: (idx: number) => void;
onHoverButton?: (idx: number) => void;
onLinkClick?: (idx: number) => void;
}
let {
line,
index,
segments,
complete = true,
showImage = false,
selectedIndex = -1,
inline = false,
showCursor = false,
onButtonClick = () => {},
onHoverButton = () => {},
onLinkClick = () => {}
}: Props = $props();
// Component types that have their own wrapper
const componentTypes = new Set(['button', 'card', 'cardgrid', 'progress', 'accordion', 'table', 'input', 'textarea', 'checkbox', 'radio', 'select', 'toggle', 'group']);
// Types that need special handling
const isComponent = $derived(componentTypes.has(line.type));
const isBlank = $derived(line.type === 'blank');
const isDivider = $derived(line.type === 'divider');
const isImage = $derived(line.type === 'image');
const isPrompt = $derived(line.type === 'command' || line.type === 'prompt');
const isHeader = $derived(line.type === 'header');
const isLink = $derived(line.type === 'link');
const isTooltip = $derived(line.type === 'tooltip');
</script>
{#if isBlank}
{#if inline}<span class="inline-blank"></span>{:else}<div class="tui-line blank"></div>{/if}
{:else if isDivider}
<div class="tui-divider" id={line.id}>
<span class="divider-line"></span>
{#if line.content}<span class="divider-text">{line.content}</span>{/if}
<span class="divider-line"></span>
</div>
{:else if line.type === 'button'}
<TuiButton {line} {index} selected={selectedIndex === index} onClick={onButtonClick} onHover={onHoverButton} {inline} />
{:else if isLink}
{#if inline}
<TuiLink {line} onClick={() => handleNavigation(line)} />
{:else}
<div class="tui-line link"><TuiLink {line} onClick={() => onLinkClick(index)} /></div>
{/if}
{:else if isTooltip}
{#if inline}<TuiTooltip {line} />{:else}<div class="tui-line"><TuiTooltip {line} /></div>{/if}
{:else if line.type === 'card'}
<TuiCard {line} />
{:else if line.type === 'cardgrid'}
<TuiCardGrid {line} />
{:else if line.type === 'progress'}
<TuiProgress {line} {inline} />
{:else if line.type === 'accordion'}
<TuiAccordion {line} />
{:else if line.type === 'table'}
<TuiTable {line} />
{:else if line.type === 'input'}
<TuiInput {line} {inline} />
{:else if line.type === 'textarea'}
<TuiTextarea {line} />
{:else if line.type === 'checkbox'}
<TuiCheckbox {line} {inline} />
{:else if line.type === 'radio'}
<TuiRadio {line} />
{:else if line.type === 'select'}
<TuiSelect {line} />
{:else if line.type === 'toggle'}
<TuiToggle {line} {inline} />
{:else if line.type === 'group'}
<TuiGroup {line} {inline} {onButtonClick} {onHoverButton} {onLinkClick} />
{:else if isImage && showImage}
<div class="tui-image" class:inline-image={inline}>
<img src={line.image} alt={line.imageAlt || 'Image'} style="max-width: {line.imageWidth || 300}px" />
{#if line.content}<span class="image-caption">{line.content}</span>{/if}
</div>
{:else if !isImage}
{#if inline && isHeader}
<span class="header-text inline-header">
<Icon icon="mdi:pound" width="20" class="header-icon" />
{#each segments as seg}{#if seg.icon}<Icon icon={seg.icon} width="20" class="inline-icon" />{:else if getSegmentStyle(seg)}<span style={getSegmentStyle(seg)}>{seg.text}</span>{:else}{seg.text}{/if}{/each}
</span>
{:else if inline}
<span class="inline-content {line.type}">
{getLinePrefix(line.type)}{#each segments as seg}{#if seg.icon}<Icon icon={seg.icon} width="14" class="inline-icon" />{:else if getSegmentStyle(seg)}<span style={getSegmentStyle(seg)}>{seg.text}</span>{:else}{seg.text}{/if}{/each}
</span>
{:else}
<div class="tui-line {line.type}" class:complete id={line.id}>
{#if isPrompt}
<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>
{/if}
{#if isHeader}
<span class="content header-text">
<Icon icon="mdi:pound" width="25" class="header-icon" />
{#each segments as seg}{#if seg.icon}<Icon icon={seg.icon} width="25" class="inline-icon" />{:else if getSegmentStyle(seg)}<span style={getSegmentStyle(seg)}>{seg.text}</span>{:else}{seg.text}{/if}{/each}
</span>
{:else}
<span class="content">
{getLinePrefix(line.type)}{#each segments as seg}{#if seg.icon}<Icon icon={seg.icon} width="14" class="inline-icon" />{:else if getSegmentStyle(seg)}<span style={getSegmentStyle(seg)}>{seg.text}</span>{:else}{seg.text}{/if}{/each}
</span>
{/if}
{#if showCursor}<span class="cursor"></span>{/if}
</div>
{/if}
{/if}