@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,549 @@
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">
10
+
11
+ <link href="public/styles/styles.css" rel="stylesheet">
12
+
13
+ <title>WorkAdventure test map</title>
14
+ <link rel="icon" href="/images/favicon.svg" type="image/svg+xml">
15
+ <script type="module">
16
+ document.addEventListener("DOMContentLoaded", (event) => {
17
+ // Load index.js to have access to getMapsList
18
+ import('/public/assets/index.js').then(() => {
19
+ window.createBackgroundImageFade();
20
+ });
21
+ });
22
+ </script>
23
+ </head>
24
+
25
+ <body>
26
+ <div class="content">
27
+ <header>
28
+ <div class="logo">
29
+ <a href="https://workadventu.re/" target="_blank" title="Workadventure">
30
+ <img src="public/images/logo.svg" alt="Workadventure logo" height="36" />
31
+ </a>
32
+ </div>
33
+ <div style="flex-grow: 1;"></div>
34
+ <div class="socials">
35
+ <a href="https://discord.gg/G6Xh9ZM9aR" target="_blank" title="discord">
36
+ <img src="/public/images/brand-discord.svg" alt="discord">
37
+ </a>
38
+ <a href="https://github.com/thecodingmachine/workadventure" target="_blank" title="github">
39
+ <img src="/public/images/brand-github.svg" alt="github">
40
+ </a>
41
+ <a href="https://www.youtube.com/channel/UCXJ9igV-kb9gw1ftR33y5tA" target="_blank" title="youtube">
42
+ <img src="/public/images/brand-youtube.svg" alt="youtube">
43
+ </a>
44
+ <a href="https://twitter.com/Workadventure_" target="_blank" title="twitter">
45
+ <img src="/public/images/brand-x.svg" alt="X">
46
+ </a>
47
+ <a href="https://www.linkedin.com/company/workadventu-re" target="_blank" title="linkedin">
48
+ <img src="/public/images/brand-linkedin.svg" alt="linkedin">
49
+ </a>
50
+ </div>
51
+ <div class="btn-header-wrapper">
52
+ <a href="https://discord.gg/G6Xh9ZM9aR" target="_blank" class="btn btn-light">Talk to the community</a>
53
+ <a href="https://docs.workadventu.re/map-building/" target="_blank" class="btn">Documentation</a>
54
+ </div>
55
+ </header>
56
+ <main class="container">
57
+ <section id="configureSteps" class="form-center steps">
58
+ <input type="hidden" id="stepProgress" value="1" />
59
+ <div id="step1" class="step">
60
+ <div class="step-title">
61
+ <div class="step-number">1</div>
62
+ <h2>Connect to your back-office </h2>
63
+ </div>
64
+ <div>
65
+ <button id="bo-connect" class="btn">Log-in to your back-office</button>
66
+ </div>
67
+ </div>
68
+
69
+ <div id="step2" class="step inactive">
70
+ <div class="step-title">
71
+ <div class="step-number">2</div>
72
+ <h2>Complete the URL of your map storage</h2>
73
+ </div>
74
+ <div class="alert">
75
+ <div class="alert-text">
76
+ Be careful to select the proper world just above before copying your map storage url and create your key
77
+ </div>
78
+ <img src="public/images/world-select.png" alt="World selection" />
79
+ </div>
80
+ <div class="step-text">
81
+ You can find it in here. Log in. In the left panel, click on "Developers" tab then "API keys / Zapier". There are 3 links, be careful to take the Map-storage API endpoint, it is the url for uploading files to the map storage service of WorkAdventure.
82
+ </div>
83
+ <div style="margin-top: 8px;">
84
+ <input id="mapStorageURL" type="url" placeholder="Paste here your URL..." />
85
+ </div>
86
+ </div>
87
+
88
+ <div id="step3" class="step inactive">
89
+ <div class="step-title">
90
+ <div class="step-number">3</div>
91
+ <h2>Add your map storage API Key</h2>
92
+ </div>
93
+ <div class="step-text">
94
+ In the same section than the step before:
95
+ <ol>
96
+ <li>Create a new token on back-office</li>
97
+ <li>Refresh the page</li>
98
+ <li>Click on the button “Copy” of your token</li>
99
+ </ol>
100
+ </div>
101
+ <div>
102
+ <input id="apiKey"
103
+ type="password"
104
+ placeholder="Paste here your API Key..."
105
+ autocomplete="off"
106
+ />
107
+ </div>
108
+ </div>
109
+
110
+ <div id="step4" class="step inactive">
111
+ <div class="step-title">
112
+ <div class="step-number">4</div>
113
+ <h2>Choose a name / directory for your world</h2>
114
+ </div>
115
+ <div class="step-text">
116
+ You can use an existing directory or create a new one.
117
+ </div>
118
+ <div>
119
+ <input id="uploadDirectory" type="text" placeholder="Name of your world" />
120
+ </div>
121
+ </div>
122
+ </section>
123
+ </main>
124
+ <!-- Loading overlay -->
125
+ <div id="loadingOverlay" class="loading-overlay" style="display: none;">
126
+ <div class="loading-content">
127
+ <div class="loading-spinner"></div>
128
+ <p id="loadingText">Verifying your data...</p>
129
+ </div>
130
+ </div>
131
+ <!-- Error popup -->
132
+ <div id="errorPopup" class="error-popup" style="display: none;">
133
+ <div class="error-popup-content">
134
+ <div class="error-icon">
135
+ <svg width="48" height="48" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
136
+ <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2"/>
137
+ <path d="M12 8v4M12 16h.01" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
138
+ </svg>
139
+ </div>
140
+ <h3 class="error-title">Error</h3>
141
+ <p id="errorMessage" class="error-message"></p>
142
+ <button id="errorCloseButton" class="btn btn-secondary error-close-btn">Close</button>
143
+ </div>
144
+ </div>
145
+ <div class="button-wrapper">
146
+ <div>
147
+ <a href="step2-hosting" class="btn btn-ghost">
148
+ Previous
149
+ </a>
150
+ </div>
151
+ <div style="flex-grow: 1;">
152
+ </div>
153
+ <div>
154
+ <button id="saveButton" class="btn btn-secondary">
155
+ Upload
156
+ </button>
157
+ </div>
158
+ </div>
159
+ </div>
160
+ <div class="bg"></div>
161
+ <script>
162
+ let stepProgress = document.getElementById('stepProgress');
163
+ let step1 = document.getElementById('step1');
164
+ let step2 = document.getElementById('step2');
165
+ let step3 = document.getElementById('step3');
166
+ let step4 = document.getElementById('step4');
167
+ let mapStorageURL = document.getElementById('mapStorageURL');
168
+ let apiKey = document.getElementById('apiKey');
169
+ let uploadDirectory = document.getElementById('uploadDirectory');
170
+ const button = document.getElementById("bo-connect");
171
+ const saveButton = document.getElementById("saveButton");
172
+ let formIsValid = false;
173
+
174
+ // Function to validate map storage URL
175
+ function isValidMapStorageUrl(value) {
176
+ const regex = /^https:\/\/[a-zA-Z0-9.-]+\.map-storage\.workadventu\.re\/?$/;
177
+ return regex.test(value);
178
+ }
179
+
180
+ // Function to load existing configuration and auto-advance steps
181
+ async function loadConfiguration() {
182
+ try {
183
+ // Show loading overlay
184
+ const loadingOverlay = document.getElementById('loadingOverlay');
185
+ loadingOverlay.style.display = 'flex';
186
+
187
+ const loadingText = document.getElementById('loadingText');
188
+ loadingText.innerHTML = 'Verifying your data...';
189
+
190
+ const response = await fetch("/uploader/status");
191
+ if (!response.ok) {
192
+ throw new Error('Failed to load configuration status');
193
+ return;
194
+ }
195
+
196
+ loadingText.innerHTML = 'Loading your configuration...';
197
+
198
+ const data = await response.json();
199
+
200
+ // If we have secret config, pre-fill the form fields
201
+ if (!data.secretConfig) {
202
+ throw new Error('No configuration found');
203
+ }
204
+ const config = data.secretConfig;
205
+ let currentStep = 1;
206
+
207
+ // Pre-fill mapStorageURL if available
208
+ if (config.mapStorageUrl) {
209
+ mapStorageURL.value = config.mapStorageUrl;
210
+ // Trigger validation and step advancement
211
+ if (isValidMapStorageUrl(config.mapStorageUrl)) {
212
+ mapStorageURL.classList.remove("error");
213
+ mapStorageURL.classList.add("success");
214
+ // Advance to step 2 and 3
215
+ currentStep = 3;
216
+ step2.style.opacity = 1;
217
+ step2.classList.remove("inactive");
218
+ step3.style.opacity = 1;
219
+ step3.classList.remove("inactive");
220
+ formIsValid = 1;
221
+ }
222
+ }
223
+
224
+ // Pre-fill apiKey if available
225
+ if (config.mapStorageApiKey) {
226
+ apiKey.value = config.mapStorageApiKey;
227
+ // Trigger validation and step advancement
228
+ if (config.mapStorageApiKey) {
229
+ apiKey.classList.remove("error");
230
+ apiKey.classList.add("success");
231
+ // Advance to step 4
232
+ currentStep = 4;
233
+ totalHeight = step1.offsetHeight + step2.offsetHeight + (step3.offsetHeight + 100) + step4.offsetHeight;
234
+ step2.style.opacity = 1;
235
+ step2.classList.remove("inactive");
236
+ step3.style.opacity = 1;
237
+ step3.classList.remove("inactive");
238
+ step4.style.opacity = 1;
239
+ step4.classList.remove("inactive");
240
+ formIsValid = 2;
241
+ }
242
+ }
243
+
244
+ // Pre-fill uploadDirectory if available
245
+ if (config.uploadDirectory) {
246
+ uploadDirectory.value = config.uploadDirectory;
247
+ // Trigger validation
248
+ if (config.uploadDirectory) {
249
+ uploadDirectory.classList.remove("error");
250
+ uploadDirectory.classList.add("success");
251
+ formIsValid = 3;
252
+ }
253
+ }
254
+
255
+ // Update step progress and height if we advanced
256
+ if (currentStep > 1) {
257
+ stepProgress.value = currentStep;
258
+ }
259
+
260
+ // Hide loading overlay
261
+ loadingOverlay.style.display = 'none';
262
+ } catch (error) {
263
+ console.error('Error loading configuration:', error);
264
+ const loadingOverlay = document.getElementById('loadingOverlay');
265
+ loadingOverlay.style.display = 'none';
266
+ }
267
+ }
268
+
269
+ // Load configuration on page load
270
+ loadConfiguration();
271
+
272
+ button.addEventListener("click", (event) => {
273
+ stepProgress.value = 2;
274
+ step2.style.opacity = 1;
275
+ step2.classList.remove("inactive");
276
+ // Scroll to step 2
277
+ step2.scrollIntoView({ behavior: "smooth" });
278
+ // Open new window to "https://admin.workadventu.re/login"
279
+ window.open("https://admin.workadventu.re/login", "_blank");
280
+ });
281
+
282
+ mapStorageURL.addEventListener('input', function (evt) {
283
+ if(isValidMapStorageUrl(this.value)) {
284
+ mapStorageURL.classList.remove("error");
285
+ mapStorageURL.classList.add("success");
286
+ setTimeout(function() {
287
+ step3.style.opacity = 1;
288
+ step3.classList.remove("inactive");
289
+ // Scroll to step 3
290
+ step3.scrollIntoView({ behavior: "smooth" });
291
+ }, 500);
292
+ formIsValid++;
293
+ } else {
294
+ mapStorageURL.classList.remove("success");
295
+ mapStorageURL.classList.add("error");
296
+ formIsValid = false;
297
+ }
298
+ });
299
+
300
+ apiKey.addEventListener('input', function (evt) {
301
+ if(this.value) {
302
+ apiKey.classList.remove("error");
303
+ apiKey.classList.add("success");
304
+ setTimeout(function() {
305
+ step4.style.opacity = 1;
306
+ step4.classList.remove("inactive");
307
+ // Scroll to step 4
308
+ console.log('Scroll to step 4');
309
+ step4.scrollIntoView({ behavior: "smooth" });
310
+ formIsValid++;
311
+ }, 500);
312
+ } else {
313
+ apiKey.classList.remove("success");
314
+ apiKey.classList.add("error");
315
+ formIsValid = false;
316
+ }
317
+ });
318
+
319
+ uploadDirectory.addEventListener('input', function (evt) {
320
+ if(this.value) {
321
+ uploadDirectory.classList.remove("error");
322
+ uploadDirectory.classList.add("success");
323
+ formIsValid++;
324
+ } else {
325
+ uploadDirectory.classList.remove("success");
326
+ uploadDirectory.classList.add("error");
327
+ formIsValid = false;
328
+ }
329
+ });
330
+
331
+ // Function to show error popup
332
+ function showErrorPopup(message) {
333
+ const errorPopup = document.getElementById('errorPopup');
334
+ const errorMessage = document.getElementById('errorMessage');
335
+ errorMessage.textContent = message;
336
+ errorPopup.style.display = 'flex';
337
+ }
338
+
339
+ // Function to hide error popup
340
+ function hideErrorPopup() {
341
+ const errorPopup = document.getElementById('errorPopup');
342
+ errorPopup.style.display = 'none';
343
+ }
344
+
345
+ // Close error popup when clicking the button
346
+ document.getElementById('errorCloseButton').addEventListener('click', hideErrorPopup);
347
+
348
+ // Close error popup when clicking outside
349
+ document.getElementById('errorPopup').addEventListener('click', function(e) {
350
+ if (e.target === this) {
351
+ hideErrorPopup();
352
+ }
353
+ });
354
+
355
+ saveButton.addEventListener("click", async (event) => {
356
+ // Validate form before proceeding
357
+ if (!mapStorageURL.value || !isValidMapStorageUrl(mapStorageURL.value) ||
358
+ !apiKey.value || !uploadDirectory.value) {
359
+ showErrorPopup('Please fill in all required fields correctly.');
360
+ return;
361
+ }
362
+
363
+ // Show loading overlay
364
+ const loadingOverlay = document.getElementById('loadingOverlay');
365
+ loadingOverlay.style.display = 'flex';
366
+
367
+ // Start verification process with 2 second minimum
368
+ const startTime = Date.now();
369
+ const minDuration = 2000; // 2 seconds
370
+
371
+ try {
372
+ loadingText.innerHTML = 'Saving your configuration...';
373
+ // Send the form data to the server to configure
374
+ const configureResponse = await fetch("/uploader/configure", {
375
+ method: "POST",
376
+ headers: {
377
+ "Content-Type": "application/json"
378
+ },
379
+ body: JSON.stringify({
380
+ mapStorageUrl: mapStorageURL.value,
381
+ mapStorageApiKey: apiKey.value,
382
+ uploadDirectory: uploadDirectory.value
383
+ })
384
+ });
385
+
386
+ if (!configureResponse.ok) {
387
+ const errorData = await configureResponse.json().catch(() => ({}));
388
+ throw new Error(errorData.message || errorData.error || 'Failed to save configuration. Please check your inputs and try again.');
389
+ }
390
+
391
+ const configureData = await configureResponse.json();
392
+
393
+ // Update loading message
394
+ loadingText.innerHTML = 'Uploading your map...';
395
+
396
+ // Upload the map
397
+ const uploadResponse = await fetch("/uploader/upload", {
398
+ method: "POST",
399
+ headers: {
400
+ "Content-Type": "application/json"
401
+ }
402
+ });
403
+
404
+ if (!uploadResponse.ok) {
405
+ const errorData = await uploadResponse.json().catch(() => ({}));
406
+ const errorMsg = errorData.message || errorData.error || 'Failed to upload map. Please verify your configuration and try again.';
407
+ throw new Error(errorMsg);
408
+ }
409
+
410
+ // Ensure minimum 2 seconds have passed
411
+ const elapsed = Date.now() - startTime;
412
+ if (elapsed < minDuration) {
413
+ await new Promise(resolve => setTimeout(resolve, minDuration - elapsed));
414
+ }
415
+
416
+ // Redirect to success page
417
+ window.location.href = "/step4-validated";
418
+ } catch (error) {
419
+ console.error('Error saving configuration:', error);
420
+ // Hide loading overlay on error
421
+ loadingOverlay.style.display = 'none';
422
+
423
+ // Show error popup with a user-friendly message
424
+ const errorMessage = error instanceof Error ? error.message : 'An error occurred while saving your configuration. Please try again.';
425
+ showErrorPopup(errorMessage);
426
+ }
427
+ });
428
+ </script>
429
+ <style>
430
+ .loading-overlay {
431
+ position: fixed;
432
+ top: 0;
433
+ left: 0;
434
+ width: 100%;
435
+ height: 100%;
436
+ background-color: rgba(27, 42, 65, 0.9);
437
+ display: flex;
438
+ justify-content: center;
439
+ align-items: center;
440
+ z-index: 9999;
441
+ }
442
+
443
+ .loading-content {
444
+ text-align: center;
445
+ color: white;
446
+ }
447
+
448
+ .loading-spinner {
449
+ border: 4px solid rgba(255, 255, 255, 0.3);
450
+ border-top: 4px solid #66E979;
451
+ border-radius: 50%;
452
+ width: 50px;
453
+ height: 50px;
454
+ animation: spin 1s linear infinite;
455
+ margin: 0 auto 20px;
456
+ }
457
+
458
+ @keyframes spin {
459
+ 0% { transform: rotate(0deg); }
460
+ 100% { transform: rotate(360deg); }
461
+ }
462
+
463
+ .loading-content p {
464
+ margin: 0;
465
+ font-size: 18px;
466
+ font-weight: 500;
467
+ }
468
+
469
+ /* Error popup styles */
470
+ .error-popup {
471
+ position: fixed;
472
+ top: 0;
473
+ left: 0;
474
+ width: 100%;
475
+ height: 100%;
476
+ background-color: rgba(27, 42, 65, 0.85);
477
+ display: flex;
478
+ justify-content: center;
479
+ align-items: center;
480
+ z-index: 10000;
481
+ animation: fadeIn 0.3s ease-in-out;
482
+ }
483
+
484
+ @keyframes fadeIn {
485
+ from {
486
+ opacity: 0;
487
+ }
488
+ to {
489
+ opacity: 1;
490
+ }
491
+ }
492
+
493
+ .error-popup-content {
494
+ background: rgba(27, 42, 65, 0.95);
495
+ border: 2px solid #ff4444;
496
+ border-radius: 16px;
497
+ padding: 32px;
498
+ max-width: 500px;
499
+ width: 90%;
500
+ text-align: center;
501
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
502
+ animation: slideUp 0.3s ease-out;
503
+ }
504
+
505
+ @keyframes slideUp {
506
+ from {
507
+ transform: translateY(20px);
508
+ opacity: 0;
509
+ }
510
+ to {
511
+ transform: translateY(0);
512
+ opacity: 1;
513
+ }
514
+ }
515
+
516
+ .error-icon {
517
+ color: #ff4444;
518
+ margin-bottom: 16px;
519
+ display: flex;
520
+ justify-content: center;
521
+ }
522
+
523
+ .error-icon svg {
524
+ width: 64px;
525
+ height: 64px;
526
+ }
527
+
528
+ .error-title {
529
+ color: #ff4444;
530
+ font-size: 24px;
531
+ font-weight: bold;
532
+ margin: 0 0 16px 0;
533
+ }
534
+
535
+ .error-message {
536
+ color: #ffffff;
537
+ font-size: 16px;
538
+ line-height: 1.5;
539
+ margin: 0 0 24px 0;
540
+ }
541
+
542
+ .error-close-btn {
543
+ margin-top: 8px;
544
+ min-width: 120px;
545
+ }
546
+ </style>
547
+ </body>
548
+
549
+ </html>