Skip to content

Commit 461e19f

Browse files
author
Pascal Klesse
committed
fix: simplify loader composable and integrate with GitHub/GitLab
Remove global fetch interceptor plugin and polling state dependency. Implement explicit loader control in GitHub and GitLab composables for better predictability. Consolidate timer logic and reduce state complexity in useLoader.
1 parent aa869a4 commit 461e19f

File tree

4 files changed

+93
-122
lines changed

4 files changed

+93
-122
lines changed

projects/app/src/composables/use-github.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,45 @@
11
import type { Source } from '~/base/default';
22

3+
import { useLoader } from '~/composables/use-loader';
4+
35
export function useGithub(source: Source) {
46
const config = useRuntimeConfig();
57
const apiUrl = source.url.includes('//api.') ? source.url : 'https://api.github.com';
8+
const { start, stop } = useLoader();
69

710
async function getRepos() {
11+
start();
812
const params: URLSearchParams = new URLSearchParams({
913
page: '1',
1014
per_page: '150',
1115
});
1216

13-
return useFetch(`${apiUrl}/user/repos?${params}`, {
17+
const result = useFetch(`${apiUrl}/user/repos?${params}`, {
1418
headers: {
1519
Authorization: `Bearer ${source.token}`,
1620
},
1721
});
22+
23+
result.finally(() => {
24+
stop();
25+
});
26+
27+
return result;
1828
}
1929

2030
async function getBranches(fullName: string) {
21-
return useFetch(`${apiUrl}/repos/${fullName}/branches`, {
31+
start();
32+
const result = useFetch(`${apiUrl}/repos/${fullName}/branches`, {
2233
headers: {
2334
Authorization: `Bearer ${source.token}`,
2435
},
2536
});
37+
38+
result.finally(() => {
39+
stop();
40+
});
41+
42+
return result;
2643
}
2744

2845
async function checkWebhookExist(fullName: string) {

projects/app/src/composables/use-gitlab.ts

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,55 @@
11
import type { Source } from '~/base/default';
22

3+
import { useLoader } from '~/composables/use-loader';
4+
35
export function useGitlab(source: Source) {
46
const config = useRuntimeConfig();
57
const apiUrl = source.url.includes('/api/') ? source.url : source.url + '/api/v4';
8+
const { start, stop } = useLoader();
69

710
async function getGroups() {
11+
start();
812
const params: URLSearchParams = new URLSearchParams({
913
page: '1',
1014
per_page: '100',
1115
});
1216

13-
return useFetch(`${apiUrl}/groups?${params}`, {
17+
const result = useFetch(`${apiUrl}/groups?${params}`, {
1418
headers: {
1519
'Access-Control-Allow-Headers': '*',
1620
'Access-Control-Allow-Origin': '*',
1721
Authorization: `Bearer ${source.token}`,
1822
},
1923
});
24+
25+
result.finally(() => {
26+
stop();
27+
});
28+
29+
return result;
2030
}
2131

2232
async function getProjects() {
33+
start();
2334
const params: URLSearchParams = new URLSearchParams({
2435
membership: 'true',
2536
page: '1',
2637
per_page: '100',
2738
});
2839

29-
return useFetch(`${apiUrl}/projects?${params}`, {
40+
const result = useFetch(`${apiUrl}/projects?${params}`, {
3041
headers: {
3142
'Access-Control-Allow-Headers': '*',
3243
'Access-Control-Allow-Origin': '*',
3344
Authorization: `Bearer ${source.token}`,
3445
},
3546
});
47+
48+
result.finally(() => {
49+
stop();
50+
});
51+
52+
return result;
3653
}
3754

3855
async function getProjectById(id: string) {
@@ -46,33 +63,47 @@ export function useGitlab(source: Source) {
4663
}
4764

4865
async function getBranches(id: string) {
66+
start();
4967
const params: URLSearchParams = new URLSearchParams({
5068
page: '1',
5169
per_page: '100',
5270
});
5371

54-
return useFetch(`${apiUrl}/projects/${id}/repository/branches?${params}`, {
72+
const result = useFetch(`${apiUrl}/projects/${id}/repository/branches?${params}`, {
5573
headers: {
5674
'Access-Control-Allow-Headers': '*',
5775
'Access-Control-Allow-Origin': '*',
5876
Authorization: `Bearer ${source.token}`,
5977
},
6078
});
79+
80+
result.finally(() => {
81+
stop();
82+
});
83+
84+
return result;
6185
}
6286

6387
async function getReleases(id: string) {
88+
start();
6489
const params: URLSearchParams = new URLSearchParams({
6590
page: '1',
6691
per_page: '100',
6792
});
6893

69-
return useFetch(`${apiUrl}/projects/${id}/releases?${params}`, {
94+
const result = useFetch(`${apiUrl}/projects/${id}/releases?${params}`, {
7095
headers: {
7196
'Access-Control-Allow-Headers': '*',
7297
'Access-Control-Allow-Origin': '*',
7398
Authorization: `Bearer ${source.token}`,
7499
},
75100
});
101+
102+
result.finally(() => {
103+
stop();
104+
});
105+
106+
return result;
76107
}
77108

78109
async function checkWebhookExist(id: string) {
@@ -109,7 +140,7 @@ export function useGitlab(source: Source) {
109140
return { data: { id: webhookExist.id } };
110141
}
111142

112-
const body: any = {
143+
const body: Record<string, unknown> = {
113144
merge_requests_events: true,
114145
push_events: true,
115146
releases_events: true,

projects/app/src/composables/use-loader.ts

Lines changed: 38 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,21 @@
1-
import { usePollingState } from '~/states/polling';
2-
31
// ============================================================================
42
// Constants
53
// ============================================================================
64
const SHOW_DELAY = 150;
75
const MIN_DURATION = 300;
86

97
// ============================================================================
10-
// Global State (shared across all components)
8+
// Global State (module-level singleton)
119
// ============================================================================
12-
const loaderState = () => useState<number>('loader_active_requests', () => 0);
13-
const loaderVisible = () => useState<boolean>('loader_visible', () => false);
10+
const activeRequests = ref<number>(0);
11+
const isVisible = ref<boolean>(false);
1412

15-
// Timer references (module-level for singleton behavior)
13+
// Timer references
1614
let showDelayTimer: ReturnType<typeof setTimeout> | null = null;
1715
let minDurationTimer: ReturnType<typeof setTimeout> | null = null;
18-
let visibleSince: number = 0;
16+
let visibleSince = 0;
1917

2018
export function useLoader() {
21-
// ============================================================================
22-
// Composables
23-
// ============================================================================
24-
const { isPolling } = usePollingState();
25-
26-
// ============================================================================
27-
// Variables
28-
// ============================================================================
29-
const activeRequests = loaderState();
30-
const isVisible = loaderVisible();
31-
32-
// ============================================================================
33-
// Computed Properties
34-
// ============================================================================
35-
const shouldShow = computed<boolean>(() => activeRequests.value > 0 && !isPolling.value);
36-
3719
// ============================================================================
3820
// Functions
3921
// ============================================================================
@@ -53,102 +35,63 @@ export function useLoader() {
5335
}
5436

5537
/**
56-
* Show the loader after delay
57-
*/
58-
function scheduleShow(): void {
59-
if (showDelayTimer || isVisible.value) {
60-
return;
61-
}
62-
63-
showDelayTimer = setTimeout(() => {
64-
showDelayTimer = null;
65-
if (shouldShow.value) {
66-
isVisible.value = true;
67-
visibleSince = Date.now();
68-
}
69-
}, SHOW_DELAY);
70-
}
71-
72-
/**
73-
* Hide the loader, respecting minimum duration
74-
*/
75-
function scheduleHide(): void {
76-
if (!isVisible.value) {
77-
clearTimers();
78-
return;
79-
}
80-
81-
const elapsed: number = Date.now() - visibleSince;
82-
const remaining: number = MIN_DURATION - elapsed;
83-
84-
if (remaining <= 0) {
85-
isVisible.value = false;
86-
clearTimers();
87-
} else {
88-
if (minDurationTimer) {
89-
return;
90-
}
91-
92-
minDurationTimer = setTimeout(() => {
93-
minDurationTimer = null;
94-
if (!shouldShow.value) {
95-
isVisible.value = false;
96-
}
97-
}, remaining);
98-
}
99-
}
100-
101-
/**
102-
* Update visibility based on current state
38+
* Update loader visibility with anti-flicker logic
10339
*/
10440
function updateVisibility(): void {
105-
if (shouldShow.value && !isVisible.value) {
106-
scheduleShow();
107-
} else if (!shouldShow.value && isVisible.value) {
108-
scheduleHide();
109-
} else if (!shouldShow.value && !isVisible.value) {
41+
const shouldShow: boolean = activeRequests.value > 0;
42+
43+
if (shouldShow && !isVisible.value && !showDelayTimer) {
44+
// Schedule showing after delay
45+
showDelayTimer = setTimeout(() => {
46+
showDelayTimer = null;
47+
if (activeRequests.value > 0) {
48+
isVisible.value = true;
49+
visibleSince = Date.now();
50+
}
51+
}, SHOW_DELAY);
52+
} else if (!shouldShow && isVisible.value) {
53+
// Hide with minimum duration
54+
const elapsed: number = Date.now() - visibleSince;
55+
const remaining: number = MIN_DURATION - elapsed;
56+
57+
if (remaining <= 0) {
58+
isVisible.value = false;
59+
clearTimers();
60+
} else if (!minDurationTimer) {
61+
minDurationTimer = setTimeout(() => {
62+
minDurationTimer = null;
63+
if (activeRequests.value === 0) {
64+
isVisible.value = false;
65+
}
66+
}, remaining);
67+
}
68+
} else if (!shouldShow && !isVisible.value) {
11069
clearTimers();
11170
}
11271
}
11372

11473
/**
115-
* Increment active request count
74+
* Start loading indicator
11675
*/
117-
function increment(): void {
76+
function start(): void {
11877
activeRequests.value++;
11978
updateVisibility();
12079
}
12180

12281
/**
123-
* Decrement active request count
82+
* Stop loading indicator
12483
*/
125-
function decrement(): void {
84+
function stop(): void {
12685
if (activeRequests.value > 0) {
12786
activeRequests.value--;
12887
}
12988
updateVisibility();
13089
}
13190

132-
/**
133-
* Legacy: Start loading (alias for increment)
134-
*/
135-
function start(): void {
136-
increment();
137-
}
138-
139-
/**
140-
* Legacy: Stop loading (alias for decrement)
141-
*/
142-
function stop(): void {
143-
decrement();
144-
}
145-
14691
// ============================================================================
14792
// Return
14893
// ============================================================================
14994
return {
150-
decrement,
151-
increment,
15295
loading: readonly(isVisible),
15396
start,
15497
stop,

projects/app/src/plugins/loader.client.ts

Lines changed: 0 additions & 20 deletions
This file was deleted.

0 commit comments

Comments
 (0)