@workadventure/map-starter-kit-core 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,502 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
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 - Self-hosted">
10
+
11
+ <link href="public/styles/styles.css" rel="stylesheet">
12
+
13
+ <title>WorkAdventure test map - Self-hosted</title>
14
+ <link rel="icon" href="/images/favicon.svg" type="image/svg+xml">
15
+ <script type="module">
16
+ document.addEventListener("DOMContentLoaded", (event) => {
17
+ import('/public/assets/index.js').then(() => {
18
+ window.createBackgroundImageFade();
19
+ });
20
+ });
21
+ </script>
22
+ </head>
23
+
24
+ <body>
25
+ <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>
55
+ <main class="container">
56
+ <section id="configureSteps" class="form-center steps">
57
+ <input type="hidden" id="stepProgress" value="1" />
58
+ <div id="step1" class="step">
59
+ <div class="step-title">
60
+ <div class="step-number">1</div>
61
+ <h2>Open your WorkAdventure self-hosted .env file</h2>
62
+ </div>
63
+ <div class="step-text">
64
+ In your WorkAdventure self-hosted installation directory, open the <code>.env</code> file. You will need the values of <strong>PUBLIC_MAP_STORAGE_URL</strong> and <strong>MAP_STORAGE_API_TOKEN</strong> from this file for the next steps.
65
+ </div>
66
+ <div style="margin-top: 16px;">
67
+ <button id="step1-done" class="btn">I have opened my .env file</button>
68
+ </div>
69
+ </div>
70
+
71
+ <div id="step2" class="step inactive">
72
+ <div class="step-title">
73
+ <div class="step-number">2</div>
74
+ <h2>Copy your PUBLIC_MAP_STORAGE_URL</h2>
75
+ </div>
76
+ <div class="step-text">
77
+ In your <code>.env</code> file, find the line <strong>PUBLIC_MAP_STORAGE_URL</strong>. Copy the value (the URL of your map storage service) and paste it below.
78
+ </div>
79
+ <div style="margin-top: 8px;">
80
+ <input id="mapStorageURL" type="url" placeholder="Paste here your MAP_STORAGE_URL..." />
81
+ </div>
82
+ </div>
83
+
84
+ <div id="step3" class="step inactive">
85
+ <div class="step-title">
86
+ <div class="step-number">3</div>
87
+ <h2>Copy your MAP_STORAGE_API_TOKEN</h2>
88
+ </div>
89
+ <div class="step-text">
90
+ In the same <code>.env</code> file, find the line <strong>MAP_STORAGE_API_TOKEN</strong>. Copy the token value and paste it below.
91
+ </div>
92
+ <div>
93
+ <input id="apiKey"
94
+ type="password"
95
+ placeholder="Paste here your MAP_STORAGE_API_TOKEN..."
96
+ autocomplete="off"
97
+ />
98
+ </div>
99
+ </div>
100
+
101
+ <div id="step4" class="step inactive">
102
+ <div class="step-title">
103
+ <div class="step-number">4</div>
104
+ <h2>Choose a name / directory for your world</h2>
105
+ </div>
106
+ <div class="step-text">
107
+ You can use an existing directory or create a new one. This is the directory where your maps will be stored on your map storage.
108
+ </div>
109
+ <div>
110
+ <input id="uploadDirectory" type="text" placeholder="Name of your world" />
111
+ </div>
112
+ </div>
113
+ </section>
114
+ </main>
115
+ <!-- Loading overlay -->
116
+ <div id="loadingOverlay" class="loading-overlay" style="display: none;">
117
+ <div class="loading-content">
118
+ <div class="loading-spinner"></div>
119
+ <p id="loadingText">Verifying your data...</p>
120
+ </div>
121
+ </div>
122
+ <!-- Error popup -->
123
+ <div id="errorPopup" class="error-popup" style="display: none;">
124
+ <div class="error-popup-content">
125
+ <div class="error-icon">
126
+ <svg width="48" height="48" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
127
+ <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2"/>
128
+ <path d="M12 8v4M12 16h.01" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
129
+ </svg>
130
+ </div>
131
+ <h3 class="error-title">Error</h3>
132
+ <p id="errorMessage" class="error-message"></p>
133
+ <button id="errorCloseButton" class="btn btn-secondary error-close-btn">Close</button>
134
+ </div>
135
+ </div>
136
+ <div class="button-wrapper">
137
+ <div>
138
+ <a href="step2-hosting" class="btn btn-ghost">
139
+ Previous
140
+ </a>
141
+ </div>
142
+ <div style="flex-grow: 1;">
143
+ </div>
144
+ <div>
145
+ <button id="saveButton" class="btn btn-secondary">
146
+ Upload
147
+ </button>
148
+ </div>
149
+ </div>
150
+ </div>
151
+ <div class="bg"></div>
152
+ <script>
153
+ let stepProgress = document.getElementById('stepProgress');
154
+ let step1 = document.getElementById('step1');
155
+ let step2 = document.getElementById('step2');
156
+ let step3 = document.getElementById('step3');
157
+ let step4 = document.getElementById('step4');
158
+ let mapStorageURL = document.getElementById('mapStorageURL');
159
+ let apiKey = document.getElementById('apiKey');
160
+ let uploadDirectory = document.getElementById('uploadDirectory');
161
+ const step1DoneButton = document.getElementById("step1-done");
162
+ const saveButton = document.getElementById("saveButton");
163
+ let formIsValid = false;
164
+
165
+ // For self-hosted: accept any valid HTTPS URL (not only workadventu.re)
166
+ function isValidMapStorageUrl(value) {
167
+ try {
168
+ const url = new URL(value);
169
+ return url.protocol === 'https:' || url.protocol === 'http:';
170
+ } catch {
171
+ return false;
172
+ }
173
+ }
174
+
175
+ async function loadConfiguration() {
176
+ try {
177
+ const loadingOverlay = document.getElementById('loadingOverlay');
178
+ loadingOverlay.style.display = 'flex';
179
+
180
+ const loadingText = document.getElementById('loadingText');
181
+ loadingText.innerHTML = 'Verifying your data...';
182
+
183
+ const response = await fetch("/uploader/status");
184
+ if (!response.ok) {
185
+ throw new Error('Failed to load configuration status');
186
+ }
187
+
188
+ loadingText.innerHTML = 'Loading your configuration...';
189
+
190
+ const data = await response.json();
191
+
192
+ if (data.secretConfig) {
193
+ const config = data.secretConfig;
194
+ let currentStep = 1;
195
+
196
+ if (config.mapStorageUrl && isValidMapStorageUrl(config.mapStorageUrl)) {
197
+ mapStorageURL.value = config.mapStorageUrl;
198
+ mapStorageURL.classList.remove("error");
199
+ mapStorageURL.classList.add("success");
200
+ currentStep = 3;
201
+ step2.style.opacity = 1;
202
+ step2.classList.remove("inactive");
203
+ step3.style.opacity = 1;
204
+ step3.classList.remove("inactive");
205
+ formIsValid = 1;
206
+ }
207
+
208
+ if (config.mapStorageApiKey) {
209
+ apiKey.value = config.mapStorageApiKey;
210
+ apiKey.classList.remove("error");
211
+ apiKey.classList.add("success");
212
+ currentStep = 4;
213
+ step2.style.opacity = 1;
214
+ step2.classList.remove("inactive");
215
+ step3.style.opacity = 1;
216
+ step3.classList.remove("inactive");
217
+ step4.style.opacity = 1;
218
+ step4.classList.remove("inactive");
219
+ formIsValid = 2;
220
+ }
221
+
222
+ if (config.uploadDirectory) {
223
+ uploadDirectory.value = config.uploadDirectory;
224
+ if (config.uploadDirectory) {
225
+ uploadDirectory.classList.remove("error");
226
+ uploadDirectory.classList.add("success");
227
+ formIsValid = 3;
228
+ }
229
+ }
230
+
231
+ if (currentStep > 1) {
232
+ stepProgress.value = currentStep;
233
+ }
234
+
235
+ loadingOverlay.style.display = 'none';
236
+ } else {
237
+ loadingOverlay.style.display = 'none';
238
+ }
239
+ } catch (error) {
240
+ console.error('Error loading configuration:', error);
241
+ const loadingOverlay = document.getElementById('loadingOverlay');
242
+ loadingOverlay.style.display = 'none';
243
+ }
244
+ }
245
+
246
+ loadConfiguration();
247
+
248
+ step1DoneButton.addEventListener("click", (event) => {
249
+ stepProgress.value = 2;
250
+ step2.style.opacity = 1;
251
+ step2.classList.remove("inactive");
252
+ step2.scrollIntoView({ behavior: "smooth" });
253
+ });
254
+
255
+ mapStorageURL.addEventListener('input', function (evt) {
256
+ if (isValidMapStorageUrl(this.value)) {
257
+ mapStorageURL.classList.remove("error");
258
+ mapStorageURL.classList.add("success");
259
+ setTimeout(function () {
260
+ step3.style.opacity = 1;
261
+ step3.classList.remove("inactive");
262
+ step3.scrollIntoView({ behavior: "smooth" });
263
+ }, 500);
264
+ formIsValid++;
265
+ } else {
266
+ mapStorageURL.classList.remove("success");
267
+ mapStorageURL.classList.add("error");
268
+ formIsValid = false;
269
+ }
270
+ });
271
+
272
+ apiKey.addEventListener('input', function (evt) {
273
+ if (this.value) {
274
+ apiKey.classList.remove("error");
275
+ apiKey.classList.add("success");
276
+ setTimeout(function () {
277
+ step4.style.opacity = 1;
278
+ step4.classList.remove("inactive");
279
+ step4.scrollIntoView({ behavior: "smooth" });
280
+ formIsValid++;
281
+ }, 500);
282
+ } else {
283
+ apiKey.classList.remove("success");
284
+ apiKey.classList.add("error");
285
+ formIsValid = false;
286
+ }
287
+ });
288
+
289
+ uploadDirectory.addEventListener('input', function (evt) {
290
+ if (this.value) {
291
+ uploadDirectory.classList.remove("error");
292
+ uploadDirectory.classList.add("success");
293
+ formIsValid++;
294
+ } else {
295
+ uploadDirectory.classList.remove("success");
296
+ uploadDirectory.classList.add("error");
297
+ formIsValid = false;
298
+ }
299
+ });
300
+
301
+ function showErrorPopup(message) {
302
+ const errorPopup = document.getElementById('errorPopup');
303
+ const errorMessage = document.getElementById('errorMessage');
304
+ errorMessage.textContent = message;
305
+ errorPopup.style.display = 'flex';
306
+ }
307
+
308
+ function hideErrorPopup() {
309
+ const errorPopup = document.getElementById('errorPopup');
310
+ errorPopup.style.display = 'none';
311
+ }
312
+
313
+ document.getElementById('errorCloseButton').addEventListener('click', hideErrorPopup);
314
+
315
+ document.getElementById('errorPopup').addEventListener('click', function (e) {
316
+ if (e.target === this) {
317
+ hideErrorPopup();
318
+ }
319
+ });
320
+
321
+ const loadingText = document.getElementById('loadingText');
322
+
323
+ saveButton.addEventListener("click", async (event) => {
324
+ if (!mapStorageURL.value || !isValidMapStorageUrl(mapStorageURL.value) ||
325
+ !apiKey.value || !uploadDirectory.value) {
326
+ showErrorPopup('Please fill in all required fields correctly.');
327
+ return;
328
+ }
329
+
330
+ const loadingOverlay = document.getElementById('loadingOverlay');
331
+ loadingOverlay.style.display = 'flex';
332
+
333
+ const startTime = Date.now();
334
+ const minDuration = 2000;
335
+
336
+ try {
337
+ loadingText.innerHTML = 'Saving your configuration...';
338
+
339
+ const configureResponse = await fetch("/uploader/configure", {
340
+ method: "POST",
341
+ headers: {
342
+ "Content-Type": "application/json"
343
+ },
344
+ body: JSON.stringify({
345
+ mapStorageUrl: mapStorageURL.value,
346
+ mapStorageApiKey: apiKey.value,
347
+ uploadDirectory: uploadDirectory.value
348
+ })
349
+ });
350
+
351
+ if (!configureResponse.ok) {
352
+ const errorData = await configureResponse.json().catch(() => ({}));
353
+ throw new Error(errorData.message || errorData.error || 'Failed to save configuration. Please check your inputs and try again.');
354
+ }
355
+
356
+ loadingText.innerHTML = 'Uploading your map...';
357
+
358
+ const uploadResponse = await fetch("/uploader/upload", {
359
+ method: "POST",
360
+ headers: {
361
+ "Content-Type": "application/json"
362
+ }
363
+ });
364
+
365
+ if (!uploadResponse.ok) {
366
+ const errorData = await uploadResponse.json().catch(() => ({}));
367
+ const errorMsg = errorData.message || errorData.error || 'Failed to upload map. Please verify your configuration and try again.';
368
+ throw new Error(errorMsg);
369
+ }
370
+
371
+ const elapsed = Date.now() - startTime;
372
+ if (elapsed < minDuration) {
373
+ await new Promise(resolve => setTimeout(resolve, minDuration - elapsed));
374
+ }
375
+
376
+ window.location.href = "/step4-validated-selfhosted";
377
+ } catch (error) {
378
+ console.error('Error saving configuration:', error);
379
+ loadingOverlay.style.display = 'none';
380
+
381
+ const errorMessage = error instanceof Error ? error.message : 'An error occurred while saving your configuration. Please try again.';
382
+ showErrorPopup(errorMessage);
383
+ }
384
+ });
385
+ </script>
386
+ <style>
387
+ .loading-overlay {
388
+ position: fixed;
389
+ top: 0;
390
+ left: 0;
391
+ width: 100%;
392
+ height: 100%;
393
+ background-color: rgba(27, 42, 65, 0.9);
394
+ display: flex;
395
+ justify-content: center;
396
+ align-items: center;
397
+ z-index: 9999;
398
+ }
399
+
400
+ .loading-content {
401
+ text-align: center;
402
+ color: white;
403
+ }
404
+
405
+ .loading-spinner {
406
+ border: 4px solid rgba(255, 255, 255, 0.3);
407
+ border-top: 4px solid #66E979;
408
+ border-radius: 50%;
409
+ width: 50px;
410
+ height: 50px;
411
+ animation: spin 1s linear infinite;
412
+ margin: 0 auto 20px;
413
+ }
414
+
415
+ @keyframes spin {
416
+ 0% { transform: rotate(0deg); }
417
+ 100% { transform: rotate(360deg); }
418
+ }
419
+
420
+ .loading-content p {
421
+ margin: 0;
422
+ font-size: 18px;
423
+ font-weight: 500;
424
+ }
425
+
426
+ .error-popup {
427
+ position: fixed;
428
+ top: 0;
429
+ left: 0;
430
+ width: 100%;
431
+ height: 100%;
432
+ background-color: rgba(27, 42, 65, 0.85);
433
+ display: flex;
434
+ justify-content: center;
435
+ align-items: center;
436
+ z-index: 10000;
437
+ animation: fadeIn 0.3s ease-in-out;
438
+ }
439
+
440
+ @keyframes fadeIn {
441
+ from { opacity: 0; }
442
+ to { opacity: 1; }
443
+ }
444
+
445
+ .error-popup-content {
446
+ background: rgba(27, 42, 65, 0.95);
447
+ border: 2px solid #ff4444;
448
+ border-radius: 16px;
449
+ padding: 32px;
450
+ max-width: 500px;
451
+ width: 90%;
452
+ text-align: center;
453
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
454
+ animation: slideUp 0.3s ease-out;
455
+ }
456
+
457
+ @keyframes slideUp {
458
+ from { transform: translateY(20px); opacity: 0; }
459
+ to { transform: translateY(0); opacity: 1; }
460
+ }
461
+
462
+ .error-icon {
463
+ color: #ff4444;
464
+ margin-bottom: 16px;
465
+ display: flex;
466
+ justify-content: center;
467
+ }
468
+
469
+ .error-icon svg {
470
+ width: 64px;
471
+ height: 64px;
472
+ }
473
+
474
+ .error-title {
475
+ color: #ff4444;
476
+ font-size: 24px;
477
+ font-weight: bold;
478
+ margin: 0 0 16px 0;
479
+ }
480
+
481
+ .error-message {
482
+ color: #ffffff;
483
+ font-size: 16px;
484
+ line-height: 1.5;
485
+ margin: 0 0 24px 0;
486
+ }
487
+
488
+ .error-close-btn {
489
+ margin-top: 8px;
490
+ min-width: 120px;
491
+ }
492
+
493
+ .step-text code {
494
+ background: rgba(255, 255, 255, 0.1);
495
+ padding: 2px 6px;
496
+ border-radius: 4px;
497
+ font-family: monospace;
498
+ }
499
+ </style>
500
+ </body>
501
+
502
+ </html>