Skip to content

Commit 9e0ceec

Browse files
authored
fix: Code and UI refactoring (#1)
* Code and UI refactoring - Moving duplicated HTML code in Mustache partials - Adding a "Publish now" button right on the first page and another "Configure publishing" button - Moving the module to ESM - Fixing path in self-hosted single-domain mode * Adding Typecheck in CI * Fixing Copilot feedback
1 parent 11bd9a1 commit 9e0ceec

23 files changed

+835
-1008
lines changed

.github/workflows/typecheck.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: Typecheck
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
- master
8+
pull_request:
9+
10+
permissions:
11+
contents: read
12+
13+
jobs:
14+
typecheck:
15+
name: Typecheck
16+
runs-on: ubuntu-latest
17+
steps:
18+
- name: Checkout
19+
uses: actions/checkout@v4
20+
21+
- name: Setup Node.js
22+
uses: actions/setup-node@v4
23+
with:
24+
node-version: 20
25+
cache: npm
26+
27+
- name: Install dependencies
28+
run: npm ci
29+
30+
- name: Run TypeScript typecheck
31+
run: npx tsc -p tsconfig.json

package.json

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"name": "@workadventure/map-starter-kit-core",
33
"version": "1.1.0",
44
"description": "Core app, HTML pages and static assets for the WorkAdventure Map Starter Kit. Update this package to get new UI and server features without touching your maps or config.",
5+
"type": "module",
56
"main": "dist/server.js",
67
"types": "dist/server.d.ts",
78
"scripts": {
@@ -35,13 +36,11 @@
3536
"exports": {
3637
".": {
3738
"types": "./dist/server.d.ts",
38-
"import": "./dist/server.js",
39-
"require": "./dist/server.js"
39+
"import": "./dist/server.js"
4040
},
4141
"./dist/server.js": {
4242
"types": "./dist/server.d.ts",
43-
"import": "./dist/server.js",
44-
"require": "./dist/server.js"
43+
"import": "./dist/server.js"
4544
}
4645
},
4746
"dependencies": {

public/assets/js/index.js

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,4 +162,71 @@ async function loadTMJ() {
162162
}
163163
}
164164

165-
export { getMapsList, getImagesList, createBackgroundImageFade, loadTMJ };
165+
async function setupPublishingActions() {
166+
const publishButton = document.getElementById('publishNowButton');
167+
if (!publishButton) return;
168+
const isInitiallyEnabled = !publishButton.disabled;
169+
170+
publishButton.addEventListener('click', async () => {
171+
if (publishButton.disabled) {
172+
return;
173+
}
174+
const defaultLabel = publishButton.textContent;
175+
publishButton.disabled = true;
176+
publishButton.setAttribute('aria-disabled', 'true');
177+
publishButton.setAttribute('aria-busy', 'true');
178+
publishButton.textContent = 'Publishing...';
179+
const startTime = Date.now();
180+
const minDuration = 2000;
181+
const mapStorageInput = document.getElementById('mapStorageURL');
182+
const mapStorageUrl = mapStorageInput && 'value' in mapStorageInput
183+
? mapStorageInput.value
184+
: '';
185+
186+
if (typeof window.showLoadingOverlay === 'function') {
187+
window.showLoadingOverlay('Publishing your map...');
188+
}
189+
190+
try {
191+
const response = await fetch('/uploader/upload', {
192+
method: 'POST',
193+
headers: {
194+
'Content-Type': 'application/json'
195+
}
196+
});
197+
198+
if (!response.ok) {
199+
const errorData = await response.json().catch(() => ({}));
200+
const errorMessage = errorData.message || errorData.error || 'Failed to publish maps. Please try again.';
201+
throw new Error(errorMessage);
202+
}
203+
204+
const elapsed = Date.now() - startTime;
205+
if (elapsed < minDuration) {
206+
await new Promise(resolve => setTimeout(resolve, minDuration - elapsed));
207+
}
208+
209+
const redirectUrl = typeof window.getPostPublishRedirect === 'function'
210+
? window.getPostPublishRedirect(mapStorageUrl)
211+
: '/step4-validated';
212+
window.location.href = redirectUrl;
213+
} catch (error) {
214+
console.error('Error publishing maps:', error);
215+
if (typeof window.hideLoadingOverlay === 'function') {
216+
window.hideLoadingOverlay();
217+
}
218+
const errorMessage = error instanceof Error ? error.message : 'An error occurred while publishing maps.';
219+
if (typeof window.showErrorPopup === 'function') {
220+
window.showErrorPopup(errorMessage);
221+
} else {
222+
window.alert(errorMessage);
223+
}
224+
publishButton.textContent = defaultLabel;
225+
publishButton.removeAttribute('aria-busy');
226+
publishButton.disabled = !isInitiallyEnabled;
227+
publishButton.setAttribute('aria-disabled', String(!isInitiallyEnabled));
228+
}
229+
});
230+
}
231+
232+
export { getMapsList, getImagesList, createBackgroundImageFade, loadTMJ, setupPublishingActions };

public/assets/views/index.html

Lines changed: 11 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,29 @@
11
<!DOCTYPE html>
22
<html lang="en">
33

4-
<head>
5-
<meta charset="UTF-8">
6-
<meta http-equiv="X-UA-Compatible" content="IE=edge">
7-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
8-
<meta name="robots" content="noindex">
9-
<meta name="title" content="WorkAdventure Starter Kit">
10-
11-
<link href="public/styles/styles.css" rel="stylesheet">
12-
13-
<title>WorkAdventure build your map</title>
14-
<link rel="icon" href="public/images/favicon.svg" type="image/svg+xml">
15-
<script type="module">
16-
document.addEventListener("DOMContentLoaded", () => {
17-
import('/public/assets/js/index.js').then((module) => {
18-
module.loadTMJ();
19-
});
20-
});
21-
</script>
22-
</head>
4+
{{> head}}
235

246
<body>
257
<div class="content">
26-
<header>
27-
<div class="logo">
28-
<a href="https://workadventu.re/" target="_blank" title="Workadventure">
29-
<img src="public/images/logo.svg" alt="Workadventure logo" height="36" />
30-
</a>
31-
</div>
32-
<div style="flex-grow: 1;"></div>
33-
<div class="socials">
34-
<a href="https://discord.gg/G6Xh9ZM9aR" target="_blank" title="discord">
35-
<img src="/public/images/brand-discord.svg" alt="discord">
36-
</a>
37-
<a href="https://github.com/thecodingmachine/workadventure" target="_blank" title="github">
38-
<img src="/public/images/brand-github.svg" alt="github">
39-
</a>
40-
<a href="https://www.youtube.com/channel/UCXJ9igV-kb9gw1ftR33y5tA" target="_blank" title="youtube">
41-
<img src="/public/images/brand-youtube.svg" alt="youtube">
42-
</a>
43-
<a href="https://twitter.com/Workadventure_" target="_blank" title="twitter">
44-
<img src="/public/images/brand-x.svg" alt="X">
45-
</a>
46-
<a href="https://www.linkedin.com/company/workadventu-re" target="_blank" title="linkedin">
47-
<img src="/public/images/brand-linkedin.svg" alt="linkedin">
48-
</a>
49-
</div>
50-
<div class="btn-header-wrapper">
51-
<a href="https://discord.gg/G6Xh9ZM9aR" target="_blank" class="btn btn-light">Talk to the community</a>
52-
<a href="https://docs.workadventu.re/map-building/" target="_blank" class="btn">Documentation</a>
53-
</div>
54-
</header>
8+
{{> header}}
559
<main>
5610
<!-- Map cards are injected here by index.js -->
5711
</main>
12+
<input type="hidden" id="mapStorageURL" value="{{mapStorageUrl}}" />
5813
<div class="button-wrapper">
5914
<div style="flex-grow: 1;">
6015
</div>
61-
<div>
62-
<a href="step1-git">
63-
Publish
16+
<div class="button-group">
17+
<a href="step1-git" class="btn btn-ghost">
18+
Configure map publishing
6419
</a>
20+
<button type="button" class="btn btn-secondary" id="publishNowButton" {{^isPublishingConfigured}}disabled aria-disabled="true" title="Configure map publishing to enable this action."{{/isPublishingConfigured}}{{#isPublishingConfigured}}title="Publish your maps now."{{/isPublishingConfigured}}>
21+
Publish now
22+
</button>
6523
</div>
6624
</div>
6725
</div>
68-
<div class="bg"></div>
26+
{{> footer}}
6927
</body>
7028

71-
</html>
29+
</html>
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<div id="errorPopup" class="error-popup" style="display: none;">
2+
<div class="error-popup-content">
3+
<div class="error-icon">
4+
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
5+
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2"/>
6+
<path d="M12 8v4M12 16h.01" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
7+
</svg>
8+
</div>
9+
<h3 class="error-title">Error</h3>
10+
<pre id="errorMessage" class="error-message"></pre>
11+
<button id="errorCloseButton" class="btn btn-secondary error-close-btn">Close</button>
12+
</div>
13+
</div>
14+
<script>
15+
(function() {
16+
const errorPopup = document.getElementById('errorPopup');
17+
const errorMessage = document.getElementById('errorMessage');
18+
const errorCloseButton = document.getElementById('errorCloseButton');
19+
20+
if (!errorPopup || !errorMessage || !errorCloseButton) {
21+
return;
22+
}
23+
24+
function showErrorPopup(message) {
25+
errorMessage.textContent = message;
26+
errorPopup.style.display = 'flex';
27+
}
28+
29+
function hideErrorPopup() {
30+
errorPopup.style.display = 'none';
31+
}
32+
33+
window.showErrorPopup = showErrorPopup;
34+
window.hideErrorPopup = hideErrorPopup;
35+
36+
errorCloseButton.addEventListener('click', hideErrorPopup);
37+
errorPopup.addEventListener('click', function(e) {
38+
if (e.target === errorPopup) {
39+
hideErrorPopup();
40+
}
41+
});
42+
})();
43+
</script>
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<div class="bg"></div>
2+
{{> error-popup}}
3+
<div id="loadingOverlay" class="loading-overlay" style="display: none;">
4+
<div class="loading-content">
5+
<div class="loading-spinner"></div>
6+
<p id="loadingText">Working on it...</p>
7+
</div>
8+
</div>
9+
<script>
10+
(function() {
11+
const loadingOverlay = document.getElementById('loadingOverlay');
12+
const loadingText = document.getElementById('loadingText');
13+
14+
function showLoadingOverlay(message) {
15+
if (!loadingOverlay || !loadingText) {
16+
return;
17+
}
18+
loadingText.textContent = message || 'Working on it...';
19+
loadingOverlay.style.display = 'flex';
20+
}
21+
22+
function hideLoadingOverlay() {
23+
if (!loadingOverlay) {
24+
return;
25+
}
26+
loadingOverlay.style.display = 'none';
27+
}
28+
29+
function isSaasMapStorageUrl(value) {
30+
const regex = /^https:\/\/[a-zA-Z0-9.-]+\.map-storage\.workadventu\.re\/?$/;
31+
return regex.test(String(value || '').trim());
32+
}
33+
34+
function getPostPublishRedirect(mapStorageUrl) {
35+
if (!mapStorageUrl) {
36+
return '/step4-validated';
37+
}
38+
return isSaasMapStorageUrl(mapStorageUrl)
39+
? '/step4-validated'
40+
: '/step4-validated-selfhosted';
41+
}
42+
43+
window.showLoadingOverlay = showLoadingOverlay;
44+
window.hideLoadingOverlay = hideLoadingOverlay;
45+
window.isSaasMapStorageUrl = isSaasMapStorageUrl;
46+
window.getPostPublishRedirect = getPostPublishRedirect;
47+
})();
48+
</script>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<script type="module">
2+
document.addEventListener("DOMContentLoaded", (event) => {
3+
import('/public/assets/js/index.js').then((module) => {
4+
module.createBackgroundImageFade();
5+
});
6+
});
7+
</script>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<script type="module">
2+
document.addEventListener("DOMContentLoaded", (event) => {
3+
// Load index.js to have access to getMapsList
4+
import('/public/assets/js/index.js').then((module) => {
5+
module.createBackgroundImageFade();
6+
});
7+
});
8+
</script>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<script type="module">
2+
document.addEventListener("DOMContentLoaded", () => {
3+
import('/public/assets/js/index.js').then((module) => {
4+
module.loadTMJ();
5+
module.setupPublishingActions();
6+
});
7+
});
8+
</script>

0 commit comments

Comments
 (0)