main f6d8405

button-loading.svelte RawHistoryFolder

1<script lang="ts">
2	import { onDestroy } from 'svelte';
3
4	// Props with defaults:
5	// - text: button label when not loading (default "Envoyer")
6	// - loading: indicates if the button should show a spinner (default false)
7	// - spinnerChar: the character to use for the spinner animation (default '.')
8	let {
9		onclick,
10		disabled = $bindable(false),
11		text = 'Envoyer',
12		loading = $bindable(false),
13		spinnerChar = $bindable('.')
14	} = $props();
15
16	// Using $state for internal reactive variables.
17	let animatedDots = $state(''); // will hold the animated spinner text
18	let count = $state(1);
19	let isDecreasing = $state(true);
20	let intervalID = $state<number | null>(null);
21
22	// Effect that starts/stops the spinner based on loading state.
23	$effect(() => {
24		if (loading) {
25			console.debug('LoadingButton: Spinner started');
26			// Immediately update spinner text to show 3 dots and start decreasing
27			animatedDots = spinnerChar.repeat(3);
28			count = 3;
29			isDecreasing = true;
30
31			if (!intervalID) {
32				intervalID = window.setInterval(() => {
33					// Ping-pong logic: 3,2,1,2,3...
34					count = count + (isDecreasing ? -1 : 1);
35					if (count <= 1) {
36						count = 1;
37						isDecreasing = false;
38					} else if (count >= 3) {
39						count = 3;
40						isDecreasing = true;
41					}
42					animatedDots = spinnerChar.repeat(count);
43					console.debug('LoadingButton: Animated dots updated to', animatedDots);
44				}, 500);
45			}
46		} else {
47			console.debug('LoadingButton: Spinner stopped');
48			if (intervalID) {
49				clearInterval(intervalID);
50				intervalID = null;
51			}
52			// When not loading, display the default text.
53			animatedDots = text;
54		}
55	});
56
57	onDestroy(() => {
58		if (intervalID) {
59			console.debug('LoadingButton: onDestroy clearing interval');
60			clearInterval(intervalID);
61		}
62	});
63</script>
64
65<button {onclick} {disabled}>
66	{#if loading}
67		<span>{animatedDots}</span>
68	{:else}
69		<span>{text}</span>
70	{/if}
71</button>
72
73<style>
74	button {
75		padding: 0.5rem 1rem;
76		font-size: 1rem;
77		cursor: pointer;
78	}
79	button:disabled {
80		cursor: not-allowed;
81		opacity: 0.5;
82		filter: grayscale(50%);
83	}
84</style>

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 3

Tags 8

↑↓ moveenter openesc close