Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .github/workflows/typecheck.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Typecheck

on:
push:
branches:
- main
- master
pull_request:

permissions:
contents: read

jobs:
typecheck:
name: Typecheck
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm

- name: Install dependencies
run: npm ci

- name: Run TypeScript typecheck
run: npx tsc -p tsconfig.json
7 changes: 3 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"name": "@workadventure/map-starter-kit-core",
"version": "1.1.0",
"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.",
"type": "module",
"main": "dist/server.js",
"types": "dist/server.d.ts",
"scripts": {
Expand Down Expand Up @@ -35,13 +36,11 @@
"exports": {
".": {
"types": "./dist/server.d.ts",
"import": "./dist/server.js",
"require": "./dist/server.js"
"import": "./dist/server.js"
},
"./dist/server.js": {
"types": "./dist/server.d.ts",
"import": "./dist/server.js",
"require": "./dist/server.js"
"import": "./dist/server.js"
}
},
"dependencies": {
Expand Down
69 changes: 68 additions & 1 deletion public/assets/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,4 +162,71 @@ async function loadTMJ() {
}
}

export { getMapsList, getImagesList, createBackgroundImageFade, loadTMJ };
async function setupPublishingActions() {
const publishButton = document.getElementById('publishNowButton');
if (!publishButton) return;
const isInitiallyEnabled = !publishButton.disabled;

publishButton.addEventListener('click', async () => {
if (publishButton.disabled) {
return;
}
const defaultLabel = publishButton.textContent;
publishButton.disabled = true;
publishButton.setAttribute('aria-disabled', 'true');
publishButton.setAttribute('aria-busy', 'true');
publishButton.textContent = 'Publishing...';
const startTime = Date.now();
const minDuration = 2000;
const mapStorageInput = document.getElementById('mapStorageURL');
const mapStorageUrl = mapStorageInput && 'value' in mapStorageInput
? mapStorageInput.value
: '';

if (typeof window.showLoadingOverlay === 'function') {
window.showLoadingOverlay('Publishing your map...');
}

try {
const response = await fetch('/uploader/upload', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
});

if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
const errorMessage = errorData.message || errorData.error || 'Failed to publish maps. Please try again.';
throw new Error(errorMessage);
}

const elapsed = Date.now() - startTime;
if (elapsed < minDuration) {
await new Promise(resolve => setTimeout(resolve, minDuration - elapsed));
}

const redirectUrl = typeof window.getPostPublishRedirect === 'function'
? window.getPostPublishRedirect(mapStorageUrl)
: '/step4-validated';
window.location.href = redirectUrl;
} catch (error) {
console.error('Error publishing maps:', error);
if (typeof window.hideLoadingOverlay === 'function') {
window.hideLoadingOverlay();
}
const errorMessage = error instanceof Error ? error.message : 'An error occurred while publishing maps.';
if (typeof window.showErrorPopup === 'function') {
window.showErrorPopup(errorMessage);
} else {
window.alert(errorMessage);
}
publishButton.textContent = defaultLabel;
publishButton.removeAttribute('aria-busy');
publishButton.disabled = !isInitiallyEnabled;
publishButton.setAttribute('aria-disabled', String(!isInitiallyEnabled));
}
});
}

export { getMapsList, getImagesList, createBackgroundImageFade, loadTMJ, setupPublishingActions };
64 changes: 11 additions & 53 deletions public/assets/views/index.html
Original file line number Diff line number Diff line change
@@ -1,71 +1,29 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="robots" content="noindex">
<meta name="title" content="WorkAdventure Starter Kit">

<link href="public/styles/styles.css" rel="stylesheet">

<title>WorkAdventure build your map</title>
<link rel="icon" href="public/images/favicon.svg" type="image/svg+xml">
<script type="module">
document.addEventListener("DOMContentLoaded", () => {
import('/public/assets/js/index.js').then((module) => {
module.loadTMJ();
});
});
</script>
</head>
{{> head}}

<body>
<div class="content">
<header>
<div class="logo">
<a href="https://workadventu.re/" target="_blank" title="Workadventure">
<img src="public/images/logo.svg" alt="Workadventure logo" height="36" />
</a>
</div>
<div style="flex-grow: 1;"></div>
<div class="socials">
<a href="https://discord.gg/G6Xh9ZM9aR" target="_blank" title="discord">
<img src="/public/images/brand-discord.svg" alt="discord">
</a>
<a href="https://github.com/thecodingmachine/workadventure" target="_blank" title="github">
<img src="/public/images/brand-github.svg" alt="github">
</a>
<a href="https://www.youtube.com/channel/UCXJ9igV-kb9gw1ftR33y5tA" target="_blank" title="youtube">
<img src="/public/images/brand-youtube.svg" alt="youtube">
</a>
<a href="https://twitter.com/Workadventure_" target="_blank" title="twitter">
<img src="/public/images/brand-x.svg" alt="X">
</a>
<a href="https://www.linkedin.com/company/workadventu-re" target="_blank" title="linkedin">
<img src="/public/images/brand-linkedin.svg" alt="linkedin">
</a>
</div>
<div class="btn-header-wrapper">
<a href="https://discord.gg/G6Xh9ZM9aR" target="_blank" class="btn btn-light">Talk to the community</a>
<a href="https://docs.workadventu.re/map-building/" target="_blank" class="btn">Documentation</a>
</div>
</header>
{{> header}}
<main>
<!-- Map cards are injected here by index.js -->
</main>
<input type="hidden" id="mapStorageURL" value="{{mapStorageUrl}}" />
<div class="button-wrapper">
<div style="flex-grow: 1;">
</div>
<div>
<a href="step1-git">
Publish
<div class="button-group">
<a href="step1-git" class="btn btn-ghost">
Configure map publishing
</a>
<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}}>
Publish now
</button>
</div>
</div>
</div>
<div class="bg"></div>
{{> footer}}
</body>

</html>
</html>
43 changes: 43 additions & 0 deletions public/assets/views/partials/error-popup.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<div id="errorPopup" class="error-popup" style="display: none;">
<div class="error-popup-content">
<div class="error-icon">
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2"/>
<path d="M12 8v4M12 16h.01" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
</div>
<h3 class="error-title">Error</h3>
<pre id="errorMessage" class="error-message"></pre>
<button id="errorCloseButton" class="btn btn-secondary error-close-btn">Close</button>
</div>
</div>
<script>
(function() {
const errorPopup = document.getElementById('errorPopup');
const errorMessage = document.getElementById('errorMessage');
const errorCloseButton = document.getElementById('errorCloseButton');

if (!errorPopup || !errorMessage || !errorCloseButton) {
return;
}

function showErrorPopup(message) {
errorMessage.textContent = message;
errorPopup.style.display = 'flex';
}

function hideErrorPopup() {
errorPopup.style.display = 'none';
}

window.showErrorPopup = showErrorPopup;
window.hideErrorPopup = hideErrorPopup;

errorCloseButton.addEventListener('click', hideErrorPopup);
errorPopup.addEventListener('click', function(e) {
if (e.target === errorPopup) {
hideErrorPopup();
}
});
})();
</script>
48 changes: 48 additions & 0 deletions public/assets/views/partials/footer.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<div class="bg"></div>
{{> error-popup}}
<div id="loadingOverlay" class="loading-overlay" style="display: none;">
<div class="loading-content">
<div class="loading-spinner"></div>
<p id="loadingText">Working on it...</p>
</div>
</div>
<script>
(function() {
const loadingOverlay = document.getElementById('loadingOverlay');
const loadingText = document.getElementById('loadingText');

function showLoadingOverlay(message) {
if (!loadingOverlay || !loadingText) {
return;
}
loadingText.textContent = message || 'Working on it...';
loadingOverlay.style.display = 'flex';
}

function hideLoadingOverlay() {
if (!loadingOverlay) {
return;
}
loadingOverlay.style.display = 'none';
}

function isSaasMapStorageUrl(value) {
const regex = /^https:\/\/[a-zA-Z0-9.-]+\.map-storage\.workadventu\.re\/?$/;
return regex.test(String(value || '').trim());
}

function getPostPublishRedirect(mapStorageUrl) {
if (!mapStorageUrl) {
return '/step4-validated';
}
return isSaasMapStorageUrl(mapStorageUrl)
? '/step4-validated'
: '/step4-validated-selfhosted';
}

window.showLoadingOverlay = showLoadingOverlay;
window.hideLoadingOverlay = hideLoadingOverlay;
window.isSaasMapStorageUrl = isSaasMapStorageUrl;
window.getPostPublishRedirect = getPostPublishRedirect;
})();
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<script type="module">
document.addEventListener("DOMContentLoaded", (event) => {
import('/public/assets/js/index.js').then((module) => {
module.createBackgroundImageFade();
});
});
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<script type="module">
document.addEventListener("DOMContentLoaded", (event) => {
// Load index.js to have access to getMapsList
import('/public/assets/js/index.js').then((module) => {
module.createBackgroundImageFade();
});
});
</script>
8 changes: 8 additions & 0 deletions public/assets/views/partials/head-scripts/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<script type="module">
document.addEventListener("DOMContentLoaded", () => {
import('/public/assets/js/index.js').then((module) => {
module.loadTMJ();
module.setupPublishingActions();
});
});
</script>
Loading
Loading