Skip to content

Commit 7b442cd

Browse files
committed
Add a simple asset loader to preload images
1 parent 8a5a215 commit 7b442cd

File tree

3 files changed

+102
-4
lines changed

3 files changed

+102
-4
lines changed

index.css

+4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
height: 100%;
77
}
88

9+
.loader-wrapper > .loading-label {
10+
padding: 8px;
11+
}
12+
913
.loader {
1014
position: absolute;
1115
top: calc(50% - 60px);

src/index.ts

+14-4
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import { UndoManager } from "./manager/undo";
3232
import { formatTilesetName } from "./util/format";
3333

3434
import "./styles/global.css";
35+
import { AssetManager } from "./manager/asset";
3536

3637
const STANDARD_THEME = "standard";
3738
const LIGHT_THEME = "light";
@@ -83,6 +84,7 @@ document.addEventListener("DOMContentLoaded", async () => {
8384
let animationManager = new AnimationManager();
8485
let undoManager = new UndoManager();
8586
let gameStorage = new BrowserGameStorage();
87+
let assetManager = new AssetManager(document.querySelector(".loader-wrapper") as HTMLElement);
8688
// Store unlockable statuses so that their unlock messages don't display again if player achieved the same conditions again
8789
let unlockedClassic = false;
8890
let unlockedInitialCommit = false;
@@ -438,7 +440,9 @@ document.addEventListener("DOMContentLoaded", async () => {
438440
}
439441
const welcomeText = document.querySelector(".classic-welcome-text") as HTMLElement;
440442
const scoreLabel = document.querySelector(".score-box > .score-label") as HTMLElement;
441-
const highscoreLabel = document.querySelector(".highscore-box > .score-label") as HTMLElement;
443+
const highscoreLabel = document.querySelector(
444+
".highscore-box > .score-label"
445+
) as HTMLElement;
442446
if (selectedTheme === CLASSIC_THEME) {
443447
welcomeText.style.display = "block";
444448
classicTimeout = setTimeout(() => {
@@ -886,8 +890,6 @@ document.addEventListener("DOMContentLoaded", async () => {
886890
(document.querySelector("link[rel='canonical']") as HTMLLinkElement).href =
887891
import.meta.env.VITE_WEBSITE_URL || "https://coteh.github.io/2048-clone/";
888892

889-
(document.querySelector(".loader-wrapper") as HTMLElement).style.display = "none";
890-
891893
Sentry.onLoad(() => {
892894
Sentry.init({
893895
release: `2048-clone@${GAME_VERSION}`,
@@ -924,13 +926,21 @@ document.addEventListener("DOMContentLoaded", async () => {
924926
posthog.capture("game open", { version: GAME_VERSION });
925927

926928
try {
929+
await assetManager.loadAssets([
930+
"/images/CheckerboardTiles.png",
931+
"/images/Checkbox_unchecked.png",
932+
"/images/Checkbox_checked.png",
933+
]);
934+
935+
(document.querySelector(".loader-wrapper") as HTMLElement).style.display = "none";
936+
927937
await initGame(eventHandler, spawnManager, animationManager, undoManager, gameStorage);
928938
} catch (e: any) {
929939
if (typeof Sentry !== "undefined") Sentry.captureException(e);
930940
const elem = createDialogContentFromTemplate("#error-dialog-content");
931941
const errorContent = elem.querySelector(".error-text") as HTMLElement;
932942

933-
console.error("Unknown error occurred", e);
943+
console.error("Could not initialize game due to error:", e);
934944
errorContent.innerText = e.message;
935945

936946
renderDialog(elem, true, false);

src/manager/asset.ts

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
export class AssetManager {
2+
private parentElement: HTMLElement;
3+
private loadingLabel: HTMLElement;
4+
private loaderElem: HTMLElement;
5+
6+
constructor(parentElement: HTMLElement) {
7+
this.parentElement = parentElement;
8+
this.loadingLabel = document.createElement("div");
9+
this.loadingLabel.classList.add("loading-label");
10+
this.loaderElem = this.parentElement.querySelector(".loader") as HTMLElement;
11+
}
12+
13+
async loadAssets(imagesList: string[]) {
14+
this.loadingLabel.innerText = "Loading Assets...";
15+
const progressElem = document.createElement("span");
16+
this.loadingLabel.appendChild(progressElem);
17+
this.parentElement.appendChild(this.loadingLabel);
18+
try {
19+
await this.preloadImages(imagesList, (progress: number) => {
20+
progressElem.innerText = Math.round(progress * 100) + "%";
21+
});
22+
} catch (e) {
23+
const animations = this.loaderElem.getAnimations();
24+
animations[0].pause();
25+
throw e;
26+
}
27+
this.parentElement.removeChild(this.loadingLabel);
28+
}
29+
30+
preloadImages(
31+
imagesList: string[],
32+
onProgressCallback: (progress: number) => void
33+
): Promise<void> {
34+
return new Promise((resolve, reject) => {
35+
var loadedCount = 0;
36+
37+
const onAssetLoaded = (url: string) => {
38+
loadedCount++;
39+
const progress = loadedCount / imagesList.length;
40+
console.log(
41+
"Asset loaded: " + url + ", Progress: " + Math.round(progress * 100) + "%"
42+
);
43+
44+
onProgressCallback(progress);
45+
46+
if (loadedCount === imagesList.length) {
47+
resolve();
48+
}
49+
};
50+
51+
const onAssetLoadError = (url: string) => {
52+
loadedCount++;
53+
const progress = loadedCount / imagesList.length;
54+
console.error(
55+
"Asset did not load: " + url + ", Progress: " + Math.round(progress * 100) + "%"
56+
);
57+
58+
this.loadingLabel.innerText = "Could not load asset: " + url;
59+
this.loadingLabel.style.color = "red";
60+
61+
reject(new Error(`Could not load asset ${url}`));
62+
};
63+
64+
imagesList.forEach((url) => {
65+
this.loadingLabel.innerText = "Loading Asset: " + url;
66+
this.preloadImage(url).then(onAssetLoaded).catch(onAssetLoadError);
67+
});
68+
});
69+
}
70+
71+
preloadImage(url: string): Promise<string> {
72+
return new Promise((resolve, reject) => {
73+
const img = new Image();
74+
img.onload = function () {
75+
resolve(url);
76+
};
77+
img.onerror = function () {
78+
console.error("Error loading image: " + url);
79+
reject(url);
80+
};
81+
img.src = url;
82+
});
83+
}
84+
}

0 commit comments

Comments
 (0)