Files
gui-video-clipper/src/lib/components/SetupWizard.svelte
2026-09-21 10:50:24 -04:00

228 lines
4.7 KiB
Svelte

<script lang="ts">
import {
checkDependencies,
installDependency,
type DependencyStatus,
} from '$lib/bindings/dependencies';
let {
onComplete,
}: {
onComplete: () => void;
} = $props();
let statuses = $state<DependencyStatus[]>([]);
let loading = $state(true);
let installingIndex = $state<number | null>(null);
let installLog = $state<string[]>([]);
let installError = $state<string | null>(null);
$effect(() => {
runChecks();
});
async function runChecks() {
loading = true;
statuses = await checkDependencies();
loading = false;
const allMet = statuses.every((s) => s.installed);
if (allMet) {
onComplete();
}
}
async function install(index: number) {
const dep = statuses[index];
installingIndex = index;
installLog = [];
installError = null;
try {
await installDependency(
dep.name,
(line) => {
installLog = [...installLog, line];
},
(success) => {
if (!success) {
installError = 'Installation failed. Check the log for details.';
}
}
);
await runChecks();
} catch (e) {
installError = String(e);
} finally {
installingIndex = null;
}
}
function skip() {
const criticalMet = statuses[0]?.installed && statuses[1]?.installed;
if (criticalMet) {
onComplete();
}
}
let criticalMet = $derived(statuses[0]?.installed && statuses[1]?.installed);
</script>
{#if loading}
<div class="wizard-overlay">
<div class="wizard">
<h2>Checking dependencies…</h2>
</div>
</div>
{:else}
<div class="wizard-overlay">
<div class="wizard">
<h2>Setup</h2>
<p class="subtitle">
Video Clipper requires a few tools to work. Let's make sure they're installed.
</p>
<ul class="dep-list">
{#each statuses as dep, i}
<li class="dep-row">
<span class="dep-status">
{#if dep.installed}
✅
{:else if installingIndex === i}
⏳
{:else}
❌
{/if}
</span>
<span class="dep-info">
<strong>{dep.name}</strong>
{#if dep.version}
<span class="version">{dep.version}</span>
{/if}
</span>
{#if !dep.installed && installingIndex === null}
<button onclick={() => install(i)}>Install</button>
{/if}
</li>
{/each}
</ul>
{#if installLog.length > 0}
<div class="log-area">
{#each installLog as line}
<div class="log-line">{line}</div>
{/each}
</div>
{/if}
{#if installError}
<div class="error">{installError}</div>
{/if}
<div class="wizard-actions">
{#if criticalMet}
<button class="primary" onclick={skip}>
{statuses.every((s) => s.installed) ? 'Continue' : 'Skip optional & Continue'}
</button>
{:else}
<p class="hint">ffmpeg and yt-dlp are required to continue.</p>
{/if}
</div>
</div>
</div>
{/if}
<style>
.wizard-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.7);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.wizard {
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: 12px;
padding: 24px 32px;
max-width: 520px;
width: 100%;
}
h2 {
margin-bottom: 8px;
}
.subtitle {
color: var(--text-secondary);
margin-bottom: 16px;
font-size: 14px;
}
.dep-list {
list-style: none;
margin-bottom: 16px;
}
.dep-row {
display: flex;
align-items: center;
gap: 12px;
padding: 8px 0;
border-bottom: 1px solid var(--border);
}
.dep-info {
flex: 1;
}
.version {
color: var(--text-muted);
font-size: 12px;
margin-left: 8px;
}
.log-area {
max-height: 120px;
overflow-y: auto;
background: var(--bg-primary);
border-radius: 4px;
padding: 8px;
font-family: var(--font-mono);
font-size: 11px;
color: var(--text-secondary);
margin-bottom: 12px;
}
.log-line {
white-space: pre-wrap;
word-break: break-all;
}
.error {
color: var(--danger);
font-size: 13px;
margin-bottom: 12px;
}
.wizard-actions {
display: flex;
justify-content: flex-end;
gap: 12px;
}
.primary {
background: var(--accent);
color: var(--bg-primary);
font-weight: 600;
}
.hint {
color: var(--text-muted);
font-size: 13px;
}
</style>