new console
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
<script lang="ts">
|
||||
import Icon from "./Icon.svelte";
|
||||
import { launcher } from "../lib/launcher.svelte";
|
||||
|
||||
let scroller: HTMLElement | undefined = $state();
|
||||
|
||||
const lines = $derived(launcher.consoleLines);
|
||||
const running = $derived(launcher.consoleRunning);
|
||||
const exit = $derived(launcher.consoleExit);
|
||||
|
||||
// Follow the output, but only while the reader is already at the bottom: scrolling back to
|
||||
// read an error must not be yanked away by the next line that arrives.
|
||||
let follow = $state(true);
|
||||
function onScroll() {
|
||||
const el = scroller;
|
||||
if (!el) return;
|
||||
follow = el.scrollHeight - el.scrollTop - el.clientHeight < 24;
|
||||
}
|
||||
$effect(() => {
|
||||
void lines.length;
|
||||
if (follow && scroller) scroller.scrollTop = scroller.scrollHeight;
|
||||
});
|
||||
|
||||
// A step header is worth picking out of a wall of build output, as is a failure.
|
||||
function kind(line: string): string {
|
||||
if (/^\s*={3}/.test(line)) return "step";
|
||||
if (/^FAILED:|^\s*Erreur|error[: ]/i.test(line)) return "bad";
|
||||
if (/nothing to (release|deploy)|No pack cut|Release finished/i.test(line)) return "note";
|
||||
return "";
|
||||
}
|
||||
</script>
|
||||
|
||||
<section class="console glass" aria-label="Release output">
|
||||
<header class="head">
|
||||
<h2 class="eyebrow">Release</h2>
|
||||
<span class="state mono" class:live={running} class:bad={!running && exit !== null && exit !== 0}>
|
||||
{#if running}
|
||||
running
|
||||
{:else if exit === null}
|
||||
idle
|
||||
{:else if exit === 0}
|
||||
finished
|
||||
{:else}
|
||||
failed ({exit})
|
||||
{/if}
|
||||
</span>
|
||||
<span class="grow"></span>
|
||||
<span class="total mono">{lines.length}</span>
|
||||
<button class="icon-btn" aria-label="Close release output" onclick={() => launcher.closeConsole()}>
|
||||
<Icon name="close" size={14} />
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<!-- Both are deliberate: `log` is the role for streamed output, and it has to be
|
||||
focusable or a keyboard cannot scroll back through a failed build. -->
|
||||
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
||||
<div bind:this={scroller} class="body mono selectable" onscroll={onScroll} tabindex="0" role="log">
|
||||
{#if lines.length}
|
||||
{#each lines as line, i (i)}
|
||||
<div class="line {kind(line)}">{line || " "}</div>
|
||||
{/each}
|
||||
{:else}
|
||||
<p class="empty">Waiting for the first line…</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if !running && exit !== null}
|
||||
<footer class="foot">
|
||||
{exit === 0 ? "Closing this keeps the output until the next release." : "The full transcript is in state\\release\\release.log."}
|
||||
</footer>
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.console {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
background: var(--glass-strong);
|
||||
}
|
||||
|
||||
.head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 8px 8px 8px 16px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.grow {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.total,
|
||||
.state {
|
||||
color: var(--muted);
|
||||
font-size: var(--text-xs);
|
||||
}
|
||||
|
||||
/* The dot earns its place: from across the desk it is the only thing that says whether a
|
||||
release is still going. */
|
||||
.state.live::before,
|
||||
.state.bad::before {
|
||||
display: inline-block;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
margin-right: 6px;
|
||||
border-radius: 50%;
|
||||
background: var(--accent, #4ade80);
|
||||
content: "";
|
||||
vertical-align: middle;
|
||||
}
|
||||
.state.live {
|
||||
color: var(--ink-2);
|
||||
}
|
||||
.state.bad {
|
||||
color: var(--danger);
|
||||
}
|
||||
.state.bad::before {
|
||||
background: var(--danger);
|
||||
}
|
||||
|
||||
.body {
|
||||
flex: 1;
|
||||
padding: 8px 0;
|
||||
overflow-y: auto;
|
||||
min-height: 0;
|
||||
font-size: var(--text-xs);
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.line {
|
||||
padding: 0 16px;
|
||||
color: var(--ink-2);
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.line.step {
|
||||
margin-top: 6px;
|
||||
color: var(--ink);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.line.bad {
|
||||
color: var(--danger);
|
||||
}
|
||||
|
||||
.line.note {
|
||||
color: var(--warn);
|
||||
}
|
||||
|
||||
.empty {
|
||||
padding: 8px 16px;
|
||||
color: var(--muted);
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
|
||||
.foot {
|
||||
padding: 8px 16px;
|
||||
border-top: 1px solid var(--line);
|
||||
color: var(--muted);
|
||||
font-size: var(--text-xs);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user