Skip to content

Commit e5ba52f

Browse files
committed
refactor(frontend): extract full-page loading screen controller
1 parent 803e3d1 commit e5ba52f

File tree

2 files changed

+124
-117
lines changed

2 files changed

+124
-117
lines changed
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import * as Misc from "../utils/misc";
2+
import * as PageLoading from "../pages/loading";
3+
import type Page from "../pages/page";
4+
import type { LoadingOptions } from "../pages/page";
5+
6+
// global abort controller for keyframe promises
7+
let keyframeAbortController: AbortController | null = null;
8+
9+
export async function showBlockingLoadingScreen({
10+
loadingOptions,
11+
totalDuration = Misc.applyReducedMotion(250),
12+
}: {
13+
loadingOptions: LoadingOptions[];
14+
totalDuration?: number;
15+
}): Promise<void> {
16+
PageLoading.page.element.show().setStyle({ opacity: "0" });
17+
await PageLoading.page.beforeShow({});
18+
19+
const fillDivider = loadingOptions.length;
20+
const fillOffset = 100 / fillDivider;
21+
22+
try {
23+
//void here to run the loading promise as soon as possible
24+
void PageLoading.page.element.promiseAnimate({
25+
opacity: "1",
26+
duration: totalDuration / 2,
27+
});
28+
29+
for (let index = 0; index < loadingOptions.length; index++) {
30+
const currentOffset = fillOffset * index;
31+
const options = loadingOptions[index] as LoadingOptions;
32+
33+
if (options.style === "bar") {
34+
await PageLoading.showBar();
35+
if (index === 0) {
36+
await PageLoading.updateBar(0, 0);
37+
PageLoading.updateText("");
38+
}
39+
} else {
40+
PageLoading.showSpinner();
41+
}
42+
43+
if (options.style === "bar") {
44+
await getLoadingPromiseWithBarKeyframes(
45+
options,
46+
fillDivider,
47+
currentOffset,
48+
);
49+
void PageLoading.updateBar(100, 125);
50+
PageLoading.updateText("Done");
51+
} else {
52+
await options.loadingPromise();
53+
}
54+
}
55+
56+
if (keyframeAbortController) {
57+
keyframeAbortController.abort();
58+
keyframeAbortController = null;
59+
}
60+
61+
await PageLoading.page.element.promiseAnimate({
62+
opacity: "0",
63+
duration: totalDuration / 2,
64+
});
65+
66+
await PageLoading.page.afterHide();
67+
PageLoading.page.element.hide();
68+
} catch (error) {
69+
if (keyframeAbortController) {
70+
keyframeAbortController.abort();
71+
keyframeAbortController = null;
72+
}
73+
74+
throw error;
75+
}
76+
}
77+
78+
async function getLoadingPromiseWithBarKeyframes(
79+
loadingOptions: Extract<
80+
NonNullable<Page<unknown>["loadingOptions"]>,
81+
{ style: "bar" }
82+
>,
83+
fillDivider: number,
84+
fillOffset: number,
85+
): Promise<void> {
86+
const loadingPromise = loadingOptions.loadingPromise();
87+
88+
// Create abort controller for this keyframe sequence
89+
const localAbortController = new AbortController();
90+
keyframeAbortController = localAbortController;
91+
92+
// Animate bar keyframes, but allow aborting if loading.promise finishes first or if globally aborted
93+
const keyframePromise = (async () => {
94+
for (const keyframe of loadingOptions.keyframes) {
95+
if (localAbortController.signal.aborted) break;
96+
if (keyframe.text !== undefined) {
97+
PageLoading.updateText(keyframe.text);
98+
}
99+
await PageLoading.updateBar(
100+
fillOffset + keyframe.percentage / fillDivider,
101+
keyframe.durationMs,
102+
);
103+
}
104+
})();
105+
106+
// Wait for either the keyframes or the loading.promise to finish
107+
await Promise.race([
108+
keyframePromise,
109+
(async () => {
110+
await loadingPromise;
111+
localAbortController.abort();
112+
})(),
113+
]);
114+
115+
// Always wait for loading.promise to finish before continuing
116+
await loadingPromise;
117+
118+
// Clean up the abort controller
119+
if (keyframeAbortController === localAbortController) {
120+
keyframeAbortController = null;
121+
}
122+
}

frontend/src/ts/controllers/page-controller.ts

Lines changed: 2 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030
readGetParameters,
3131
} from "../states/leaderboard-selection";
3232
import { configurationPromise as serverConfigurationPromise } from "../ape/server-configuration";
33+
import { showBlockingLoadingScreen } from "./loading-screen";
3334

3435
type ChangeOptions = {
3536
force?: boolean;
@@ -95,111 +96,6 @@ function updateTitle(nextPage: { id: string; display?: string }): void {
9596
}
9697
}
9798

98-
async function showSyncLoading({
99-
loadingOptions,
100-
totalDuration,
101-
}: {
102-
loadingOptions: LoadingOptions[];
103-
totalDuration: number;
104-
}): Promise<void> {
105-
PageLoading.page.element.show().setStyle({ opacity: "0" });
106-
await PageLoading.page.beforeShow({});
107-
108-
const fillDivider = loadingOptions.length;
109-
const fillOffset = 100 / fillDivider;
110-
111-
//void here to run the loading promise as soon as possible
112-
void PageLoading.page.element.promiseAnimate({
113-
opacity: "1",
114-
duration: totalDuration / 2,
115-
});
116-
117-
for (let i = 0; i < loadingOptions.length; i++) {
118-
const currentOffset = fillOffset * i;
119-
const options = loadingOptions[i] as LoadingOptions;
120-
if (options.style === "bar") {
121-
await PageLoading.showBar();
122-
if (i === 0) {
123-
await PageLoading.updateBar(0, 0);
124-
PageLoading.updateText("");
125-
}
126-
} else {
127-
PageLoading.showSpinner();
128-
}
129-
130-
if (options.style === "bar") {
131-
await getLoadingPromiseWithBarKeyframes(
132-
options,
133-
fillDivider,
134-
currentOffset,
135-
);
136-
void PageLoading.updateBar(100, 125);
137-
PageLoading.updateText("Done");
138-
} else {
139-
await options.loadingPromise();
140-
}
141-
}
142-
143-
await PageLoading.page.element.promiseAnimate({
144-
opacity: "0",
145-
duration: totalDuration / 2,
146-
});
147-
148-
await PageLoading.page.afterHide();
149-
PageLoading.page.element.hide();
150-
}
151-
152-
// Global abort controller for keyframe promises
153-
let keyframeAbortController: AbortController | null = null;
154-
155-
async function getLoadingPromiseWithBarKeyframes(
156-
loadingOptions: Extract<
157-
NonNullable<Page<unknown>["loadingOptions"]>,
158-
{ style: "bar" }
159-
>,
160-
fillDivider: number,
161-
fillOffset: number,
162-
): Promise<void> {
163-
let loadingPromise = loadingOptions.loadingPromise();
164-
165-
// Create abort controller for this keyframe sequence
166-
const localAbortController = new AbortController();
167-
keyframeAbortController = localAbortController;
168-
169-
// Animate bar keyframes, but allow aborting if loading.promise finishes first or if globally aborted
170-
const keyframePromise = (async () => {
171-
for (const keyframe of loadingOptions.keyframes) {
172-
if (localAbortController.signal.aborted) break;
173-
if (keyframe.text !== undefined) {
174-
PageLoading.updateText(keyframe.text);
175-
}
176-
await PageLoading.updateBar(
177-
fillOffset + keyframe.percentage / fillDivider,
178-
keyframe.durationMs,
179-
);
180-
}
181-
})();
182-
183-
// Wait for either the keyframes or the loading.promise to finish
184-
await Promise.race([
185-
keyframePromise,
186-
(async () => {
187-
await loadingPromise;
188-
localAbortController.abort();
189-
})(),
190-
]);
191-
192-
// Always wait for loading.promise to finish before continuing
193-
await loadingPromise;
194-
195-
// Clean up the abort controller
196-
if (keyframeAbortController === localAbortController) {
197-
keyframeAbortController = null;
198-
}
199-
200-
return;
201-
}
202-
20399
export async function change(
204100
pageName: PageName,
205101
options = {} as ChangeOptions,
@@ -256,23 +152,12 @@ export async function change(
256152
}
257153

258154
if (syncLoadingOptions.length > 0) {
259-
await showSyncLoading({
155+
await showBlockingLoadingScreen({
260156
loadingOptions: syncLoadingOptions,
261157
totalDuration,
262158
});
263159
}
264-
265-
// Clean up abort controller after successful loading
266-
if (keyframeAbortController) {
267-
keyframeAbortController = null;
268-
}
269160
} catch (error) {
270-
// Abort any running keyframe promises
271-
if (keyframeAbortController) {
272-
keyframeAbortController.abort();
273-
keyframeAbortController = null;
274-
}
275-
276161
pages.loading.element.addClass("active");
277162
setActivePage(pages.loading.id);
278163
Focus.set(false);

0 commit comments

Comments
 (0)