Add in-app updater functionality
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
AlertCircle,
|
||||
Download,
|
||||
LoaderCircle,
|
||||
PackageCheck,
|
||||
Sparkles,
|
||||
X,
|
||||
} from "@lucide/svelte";
|
||||
|
||||
type UpdateToastState = "available" | "downloading" | "installed" | "error";
|
||||
|
||||
interface Props {
|
||||
state: UpdateToastState;
|
||||
version: string;
|
||||
currentVersion: string;
|
||||
progress: number;
|
||||
error: string;
|
||||
onInstall: () => void;
|
||||
onLater: () => void;
|
||||
onDismiss: () => void;
|
||||
}
|
||||
|
||||
let {
|
||||
state,
|
||||
version,
|
||||
currentVersion = "",
|
||||
progress = 0,
|
||||
error = "",
|
||||
onInstall,
|
||||
onLater,
|
||||
onDismiss,
|
||||
}: Props = $props();
|
||||
|
||||
const clampedProgress = $derived(Math.max(0, Math.min(100, Math.round(progress))));
|
||||
const title = $derived(
|
||||
state === "installed"
|
||||
? "Update installed"
|
||||
: state === "downloading"
|
||||
? "Installing update"
|
||||
: state === "error"
|
||||
? "Update failed"
|
||||
: "Update available",
|
||||
);
|
||||
const description = $derived(
|
||||
state === "installed"
|
||||
? "Restart GitLite to use the new version."
|
||||
: state === "downloading"
|
||||
? "Download and installation are running in the background."
|
||||
: state === "error"
|
||||
? (error || "The update could not be installed.")
|
||||
: "Do you want to install this version now?",
|
||||
);
|
||||
const versionLabel = $derived(
|
||||
version && currentVersion
|
||||
? `${currentVersion} -> ${version}`
|
||||
: version
|
||||
? `Version ${version}`
|
||||
: "New version",
|
||||
);
|
||||
const showProgress = $derived(state === "downloading" || state === "installed");
|
||||
const isBusy = $derived(state === "downloading");
|
||||
</script>
|
||||
|
||||
<section
|
||||
class="update-toast"
|
||||
class:error={state === "error"}
|
||||
class:installed={state === "installed"}
|
||||
role={state === "error" ? "alert" : "status"}
|
||||
aria-live={state === "error" ? "assertive" : "polite"}
|
||||
>
|
||||
<div class="update-toast-glow" aria-hidden="true"></div>
|
||||
|
||||
<div class="update-toast-icon" class:busy={isBusy}>
|
||||
{#if state === "downloading"}
|
||||
<LoaderCircle class="spin" size={21} aria-hidden="true" />
|
||||
{:else if state === "installed"}
|
||||
<PackageCheck size={21} aria-hidden="true" />
|
||||
{:else if state === "error"}
|
||||
<AlertCircle size={21} aria-hidden="true" />
|
||||
{:else}
|
||||
<Sparkles size={21} aria-hidden="true" />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="update-toast-content">
|
||||
<div class="update-toast-top">
|
||||
<div class="update-toast-copy">
|
||||
<span class="update-toast-kicker">{versionLabel}</span>
|
||||
<h2>{title}</h2>
|
||||
</div>
|
||||
|
||||
{#if !isBusy}
|
||||
<button class="update-toast-close" type="button" onclick={onDismiss} title="Close" aria-label="Close">
|
||||
<X size={15} aria-hidden="true" />
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<p>{description}</p>
|
||||
|
||||
{#if showProgress}
|
||||
<div
|
||||
class="update-progress"
|
||||
class:indeterminate={state === "downloading" && clampedProgress === 0}
|
||||
role="progressbar"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
aria-valuenow={clampedProgress}
|
||||
>
|
||||
<span style={`width: ${clampedProgress}%`}></span>
|
||||
</div>
|
||||
<div class="update-progress-label">
|
||||
{state === "installed" ? "100% complete" : clampedProgress > 0 ? `${clampedProgress}%` : "Waiting for download size"}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="update-toast-actions">
|
||||
{#if state === "installed"}
|
||||
<button class="update-toast-secondary" type="button" onclick={onDismiss}>
|
||||
Got it
|
||||
</button>
|
||||
{:else if state === "error"}
|
||||
<button class="update-toast-secondary" type="button" onclick={onDismiss}>
|
||||
Close
|
||||
</button>
|
||||
<button class="update-toast-primary" type="button" onclick={onInstall}>
|
||||
<Download size={14} aria-hidden="true" />
|
||||
Try again
|
||||
</button>
|
||||
{:else}
|
||||
<button class="update-toast-secondary" type="button" onclick={onLater} disabled={isBusy}>
|
||||
Later
|
||||
</button>
|
||||
<button class="update-toast-primary" type="button" onclick={onInstall} disabled={isBusy}>
|
||||
{#if isBusy}
|
||||
<LoaderCircle class="spin" size={14} aria-hidden="true" />
|
||||
Installing
|
||||
{:else}
|
||||
<Download size={14} aria-hidden="true" />
|
||||
Install
|
||||
{/if}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
Reference in New Issue
Block a user