@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,431 @@
1
+ * {
2
+ box-sizing: border-box;
3
+ }
4
+
5
+ html,
6
+ body {
7
+ min-height: 100%;
8
+ }
9
+
10
+ body {
11
+ display: flex;
12
+ flex-direction: column;
13
+ margin: 0;
14
+ font-family: "Roboto", Arial, sans-serif;
15
+ text-align: center;
16
+ background: #1b2a41;
17
+ color: white;
18
+ }
19
+ body .bg {
20
+ background-size: cover;
21
+ position: fixed;
22
+ width: 100%;
23
+ height: 100%;
24
+ z-index: 0;
25
+ filter: blur(8px);
26
+ opacity: 0.35;
27
+ }
28
+
29
+ h1,
30
+ h2 {
31
+ font-weight: bold;
32
+ font-size: 1.3rem;
33
+ margin: 0;
34
+ }
35
+ h1 strong,
36
+ h2 strong {
37
+ color: #66E979;
38
+ }
39
+
40
+ header {
41
+ background: rgba(27, 42, 65, 0.85);
42
+ border-radius: 16px;
43
+ height: 86px;
44
+ display: flex;
45
+ align-items: center;
46
+ position: fixed;
47
+ width: calc(100% - 32px);
48
+ z-index: 1;
49
+ padding: 0 28px;
50
+ }
51
+ header .logo {
52
+ display: block;
53
+ position: relative;
54
+ }
55
+ header .socials {
56
+ display: flex;
57
+ justify-content: center;
58
+ margin-right: 48px;
59
+ column-gap: 12px;
60
+ }
61
+ header .btn-header-wrapper {
62
+ display: flex;
63
+ align-items: center;
64
+ justify-content: space-around;
65
+ column-gap: 12px;
66
+ }
67
+
68
+ .content {
69
+ padding: 16px;
70
+ position: relative;
71
+ z-index: 1;
72
+ }
73
+ .content main {
74
+ min-height: 100vh;
75
+ display: flex;
76
+ align-items: center;
77
+ justify-content: center;
78
+ gap: 32px;
79
+ flex-wrap: wrap;
80
+ padding-top: 134px;
81
+ padding-bottom: 121px;
82
+ }
83
+
84
+ .btn {
85
+ display: inline-block;
86
+ padding: 13px 20px 12px;
87
+ background-color: rgba(65, 86, 246, 0.9);
88
+ color: white;
89
+ border-radius: 8px;
90
+ text-decoration: none;
91
+ text-align: center;
92
+ width: auto;
93
+ position: relative;
94
+ font-size: 15px;
95
+ outline: none;
96
+ border: none;
97
+ transition: all 0.5s ease-out;
98
+ cursor: pointer;
99
+ }
100
+ .btn:hover {
101
+ filter: brightness(135%);
102
+ }
103
+ .btn.btn-light {
104
+ background-color: rgba(255, 255, 255, 0.1);
105
+ border: 1px solid rgba(255, 255, 255, 0.1);
106
+ }
107
+
108
+ .card-map {
109
+ max-width: 342px;
110
+ border-radius: 12px;
111
+ border: 1px solid rgba(255, 255, 255, 0.1);
112
+ overflow: hidden;
113
+ display: flex;
114
+ flex-direction: column;
115
+ flex-wrap: wrap;
116
+ align-items: center;
117
+ background: rgba(27, 42, 65, 0.5);
118
+ height: fit-content;
119
+ min-width: 340px;
120
+ }
121
+ .card-map .map-cover {
122
+ min-height: 230px;
123
+ width: 100%;
124
+ background-size: cover;
125
+ background-position: center;
126
+ }
127
+ .card-map .map-date {
128
+ font-family: "Roboto Condensed", Arial, sans-serif;
129
+ font-style: normal;
130
+ font-weight: 400;
131
+ font-size: 14px;
132
+ line-height: 24px;
133
+ display: flex;
134
+ align-items: center;
135
+ text-align: center;
136
+ color: #ffffff;
137
+ opacity: 0.5;
138
+ margin-top: 12px;
139
+ }
140
+ .card-map .map-name {
141
+ font-family: "Roboto", "Arial", sans-serif;
142
+ font-style: normal;
143
+ font-weight: 700;
144
+ font-size: 20px;
145
+ line-height: 28px;
146
+ display: flex;
147
+ align-items: center;
148
+ text-align: center;
149
+ color: #ffffff;
150
+ }
151
+ .card-map .map-detail {
152
+ display: flex;
153
+ align-items: center;
154
+ gap: 12px;
155
+ margin-bottom: 6px;
156
+ }
157
+ .card-map .map-detail .map-file {
158
+ font-family: "Roboto Condensed", Arial, sans-serif;
159
+ font-style: italic;
160
+ font-weight: 400;
161
+ font-size: 13px;
162
+ line-height: 20px;
163
+ display: flex;
164
+ align-items: center;
165
+ text-align: center;
166
+ color: #ffffff;
167
+ }
168
+ .card-map .map-detail .map-weight {
169
+ font-family: "Roboto Condensed", Arial, sans-serif;
170
+ font-style: italic;
171
+ font-weight: 400;
172
+ font-size: 13px;
173
+ line-height: 20px;
174
+ display: flex;
175
+ align-items: center;
176
+ text-align: center;
177
+ color: #ffffff;
178
+ }
179
+ .card-map .map-desc {
180
+ font-style: normal;
181
+ font-weight: 400;
182
+ font-size: 14px;
183
+ line-height: 20px;
184
+ display: flex;
185
+ align-items: center;
186
+ text-align: center;
187
+ color: #ffffff;
188
+ margin-bottom: 16px;
189
+ padding: 2px 12px;
190
+ }
191
+ .card-map .map-copyright {
192
+ font-size: 12px;
193
+ opacity: 0.7;
194
+ padding: 2px 12px;
195
+ }
196
+ .card-map .map-testurl {
197
+ display: flex;
198
+ flex-direction: column;
199
+ align-items: center;
200
+ padding: 12px;
201
+ gap: 12px;
202
+ width: 100%;
203
+ background: #1b2a41;
204
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
205
+ }
206
+
207
+ .form-center {
208
+ display: flex;
209
+ flex-direction: column;
210
+ align-items: center;
211
+ width: 100%;
212
+ padding-bottom: 80px;
213
+ }
214
+ .form-center.steps {
215
+ align-items: start;
216
+ text-align: left;
217
+ gap: 48px;
218
+ max-width: 780px;
219
+ margin: auto;
220
+ transition: all 1s ease-out;
221
+ }
222
+ .form-center.steps .step {
223
+ display: flex;
224
+ flex-direction: column;
225
+ gap: 8px;
226
+ width: 100%;
227
+ }
228
+ .form-center.steps .step.inactive {
229
+ opacity: 0.35;
230
+ pointer-events: none;
231
+ }
232
+ .form-center.steps .step .step-title {
233
+ display: flex;
234
+ align-items: center;
235
+ gap: 12px;
236
+ margin-bottom: 12px;
237
+ }
238
+ .form-center.steps .step .step-title .step-number {
239
+ border-radius: 8px;
240
+ padding: 6px 12px;
241
+ border: 1px solid rgb(255, 255, 255);
242
+ }
243
+ .form-center.steps .step .alert {
244
+ display: flex;
245
+ flex-direction: row;
246
+ justify-content: center;
247
+ align-items: center;
248
+ padding: 12px 12px 12px 24px;
249
+ gap: 32px;
250
+ background: rgba(233, 109, 81, 0.2);
251
+ border: 1px solid rgba(233, 109, 81, 0.5);
252
+ border-radius: 16px;
253
+ align-self: stretch;
254
+ text-align: left;
255
+ }
256
+ .form-center.steps .step .alert .alert-text {
257
+ font-family: "Roboto Condensed";
258
+ font-style: normal;
259
+ font-weight: 700;
260
+ font-size: 16px;
261
+ line-height: 24px;
262
+ color: #F4D5CB;
263
+ }
264
+ .form-center.steps .step .step-text {
265
+ font-style: normal;
266
+ font-weight: 400;
267
+ font-size: 16px;
268
+ line-height: 24px;
269
+ color: rgba(255, 255, 255, 0.75);
270
+ }
271
+ .form-center.steps .step input[type=text], .form-center.steps .step input[type=password], .form-center.steps .step input[type=url] {
272
+ display: flex;
273
+ flex-direction: row;
274
+ align-items: center;
275
+ padding: 0px 6px 0px 16px;
276
+ color: white;
277
+ font-size: 18px;
278
+ gap: 4px;
279
+ height: 52px;
280
+ width: 100%;
281
+ background: #1B2A41;
282
+ border: 1px solid #3C5E90;
283
+ border-radius: 12px;
284
+ flex: none;
285
+ order: 0;
286
+ align-self: stretch;
287
+ flex-grow: 0;
288
+ }
289
+ .form-center.steps .step input[type=text].error, .form-center.steps .step input[type=password].error, .form-center.steps .step input[type=url].error {
290
+ border: 1px solid #E96D51;
291
+ color: #E96D51;
292
+ }
293
+ .form-center.steps .step input[type=text].success, .form-center.steps .step input[type=password].success, .form-center.steps .step input[type=url].success {
294
+ color: #66E979;
295
+ border: 1px solid #66E979;
296
+ }
297
+ .form-center h1 {
298
+ font-family: "Oswald";
299
+ font-weight: 400;
300
+ font-size: 40px;
301
+ }
302
+ .form-center .sub-heading {
303
+ font-size: 14px;
304
+ line-height: 24px;
305
+ margin-bottom: 48px;
306
+ }
307
+ .form-center .radio-wrapper {
308
+ display: flex;
309
+ gap: 32px;
310
+ }
311
+ .form-center .radio-wrapper .radio-card {
312
+ cursor: pointer;
313
+ }
314
+ .form-center .radio-wrapper .radio-card .radio {
315
+ opacity: 0;
316
+ display: none;
317
+ }
318
+ .form-center .radio-wrapper .radio-card .radio:hover ~ .card-details {
319
+ background: rgba(255, 255, 255, 0.1);
320
+ }
321
+ .form-center .radio-wrapper .radio-card .radio:checked ~ .card-details {
322
+ background: white;
323
+ }
324
+ .form-center .radio-wrapper .radio-card .radio:checked ~ .card-details h2 {
325
+ color: #4156F6;
326
+ }
327
+ .form-center .radio-wrapper .radio-card .radio:checked ~ .card-details svg {
328
+ display: flex;
329
+ }
330
+ .form-center .radio-wrapper .radio-card .radio:checked ~ .card-details div strong {
331
+ color: #1B2A41;
332
+ }
333
+ .form-center .radio-wrapper .radio-card .radio:checked ~ .card-details div {
334
+ color: #1B2A41;
335
+ }
336
+ .form-center .radio-wrapper .radio-card .card-details {
337
+ border: 1px solid rgb(255, 255, 255);
338
+ border-radius: 24px;
339
+ padding: 32px 48px;
340
+ transition: all 1s ease-out;
341
+ position: relative;
342
+ }
343
+ .form-center .radio-wrapper .radio-card .card-details h2 {
344
+ font-family: "Oswald";
345
+ font-weight: 400;
346
+ font-size: 26px;
347
+ margin-bottom: 8px;
348
+ }
349
+ .form-center .radio-wrapper .radio-card .card-details svg {
350
+ display: none;
351
+ position: absolute;
352
+ right: 24px;
353
+ top: 24px;
354
+ }
355
+ .form-center .radio-wrapper .radio-card .card-details div {
356
+ font-size: 16px;
357
+ line-height: 24px;
358
+ color: rgba(255, 255, 255, 0.5);
359
+ }
360
+ .form-center .radio-wrapper .radio-card .card-details div strong {
361
+ display: block;
362
+ font-weight: 700;
363
+ color: rgb(255, 255, 255);
364
+ }
365
+
366
+ .button-wrapper {
367
+ background: rgba(27, 42, 65, 0.85);
368
+ display: flex;
369
+ align-items: center;
370
+ position: fixed;
371
+ left: 0;
372
+ bottom: 0;
373
+ width: 100%;
374
+ z-index: 1;
375
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
376
+ padding: 16px;
377
+ flex-direction: row;
378
+ justify-content: center;
379
+ text-align: center;
380
+ }
381
+ .button-wrapper a,
382
+ .button-wrapper .btn {
383
+ display: flex;
384
+ flex-direction: row;
385
+ justify-content: center;
386
+ align-items: center;
387
+ text-align: center;
388
+ padding: 16px 64px;
389
+ font-size: 18px;
390
+ gap: 8px;
391
+ background: #e96d51;
392
+ border-radius: 8px;
393
+ color: white;
394
+ text-decoration: none;
395
+ }
396
+ .button-wrapper a.btn,
397
+ .button-wrapper .btn.btn {
398
+ transition: all 1s ease-out;
399
+ }
400
+ .button-wrapper a.btn.btn-ghost,
401
+ .button-wrapper .btn.btn.btn-ghost {
402
+ background: rgba(255, 255, 255, 0.1);
403
+ }
404
+ .button-wrapper a.btn.btn-ghost:hover,
405
+ .button-wrapper .btn.btn.btn-ghost:hover {
406
+ background: rgba(255, 255, 255, 0.2);
407
+ }
408
+ .button-wrapper a.btn.btn-secondary,
409
+ .button-wrapper .btn.btn.btn-secondary {
410
+ background: #4156F6;
411
+ }
412
+
413
+ .bg-image {
414
+ position: absolute;
415
+ top: 0;
416
+ left: 0;
417
+ width: 100%;
418
+ height: 100%;
419
+ background-size: cover;
420
+ background-position: center;
421
+ transition: opacity 2s ease-in-out;
422
+ filter: blur(2px);
423
+ opacity: 0;
424
+ z-index: -1;
425
+ }
426
+ .bg-image.active {
427
+ opacity: 0.60;
428
+ z-index: 0;
429
+ }
430
+
431
+ /*# sourceMappingURL=styles.css.map */
@@ -0,0 +1 @@
1
+ {"version":3,"sourceRoot":"","sources":["styles.scss"],"names":[],"mappings":"AAAA;EACE;;;AAEF;AAAA;EAEE;;;AAEF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AACA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;;AAIJ;AAAA;EAEE;EACA;EACA;;AACA;AAAA;EACE;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AACA;EACE;EACA;;AAEF;EACE;EACA;EACA;EACA;;AAEF;EACE;EACA;EACA;EACA;;;AAIJ;EACE;EACA;EACA;;AACA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AACA;EACE;;AAEF;EACE;EACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AACA;EACE;EACA;EACA;EACA;;AAEF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEF;EACE;EACA;EACA;EACA;;AACA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAIJ;EACE;EACA;EACA;EACA;;AACA;EACE;EACA;EACA;EACA;EACA;EACA;;AACA;EACE;EACA;EACA;EACA;;AACA;EACE;EACA;;AAEF;EACE;EACA;EACA;EACA;;AACA;EACE;EACA;EACA;;AAGJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AACA;EACE;EACA;EACA;EACA;EACA;EACA;;AAGJ;EACE;EACA;EACA;EACA;EACA;;AAEF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AACA;EACE;EACA;;AAEF;EACE;EACA;;AAKR;EACE;EACA;EACA;;AAEF;EACE;EACA;EACA;;AAEF;EACE;EACA;;AAEE;EACE;EACA;;AACA;EACE;;AAEF;EACE;;AACA;EACE;;AAEF;EACE;;AAGA;EACE;;AAFJ;EAIE;;AAIN;EACE;EACA;EACA;EACA;EACA;;AACA;EACE;EACA;EACA;EACA;;AAEF;EACE;EACA;EACA;EACA;;AAEF;EACE;EACA;EACA;;AACA;EACE;EACA;EACA;;;AAQZ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AACA;AAAA;EAEE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AACA;AAAA;EACE;;AACA;AAAA;EACE;;AACA;AAAA;EACE;;AAGJ;AAAA;EACE","file":"styles.css"}
@@ -0,0 +1,8 @@
1
+ export const branches = ["master"];
2
+ export const plugins = [
3
+ "@semantic-release/commit-analyzer",
4
+ "@semantic-release/release-notes-generator",
5
+ "@semantic-release/npm",
6
+ "@semantic-release/git",
7
+ "@semantic-release/github",
8
+ ];
@@ -0,0 +1,95 @@
1
+ import express from 'express';
2
+ import * as fs from "node:fs";
3
+ import * as path from "node:path";
4
+ import Mustache from 'mustache';
5
+ import { getCoreRoot } from '../getCoreRoot.js';
6
+
7
+ export class FrontController {
8
+ private app: express.Application;
9
+
10
+ constructor(app: express.Application) {
11
+ this.app = app;
12
+ // Assets are now configured in app.ts
13
+ this.setupRoutes();
14
+ this.setupRoutesStep1();
15
+ this.setupRoutesStep2();
16
+ this.setupRoutesStep3();
17
+ this.setupRoutesStep4();
18
+ }
19
+
20
+ private setupRoutes() {
21
+ // Route for the Mustache renderer on "/"
22
+ this.app.get('/', async (_, res) => {
23
+ try {
24
+ res.send(await this.renderTemplate('index'));
25
+ } catch (error) {
26
+ console.error('Error rendering template:', error);
27
+ res.status(500).send('Error rendering template');
28
+ }
29
+ });
30
+ }
31
+
32
+ /**
33
+ * Render a template file
34
+ * @param filename - The filename of the template to render
35
+ * @returns The rendered template
36
+ */
37
+ private async renderTemplate(filename: string): Promise<string> {
38
+ const coreRoot = getCoreRoot();
39
+ const templatesDir = path.join(coreRoot, 'src/views');
40
+ if(!fs.existsSync(templatesDir)) {
41
+ throw new Error(`Templates directory not found: ${templatesDir}`);
42
+ }
43
+ const templatePath = path.join(templatesDir, `${filename}.html`);
44
+ const template = await fs.promises.readFile(templatePath, 'utf-8');
45
+ // Render the template with Mustache (without data for now)
46
+ return Mustache.render(template, {});
47
+ }
48
+
49
+ /**
50
+ * Setup the routes for file "step1-git.html"
51
+ * @returns void
52
+ */
53
+ private setupRoutesStep1() {
54
+ this.app.get('/step1-git', async (_, res) => {
55
+ res.send(await this.renderTemplate('step1-git'));
56
+ });
57
+ }
58
+
59
+ /**
60
+ * Setup the routes for file "step2-hosting.html"
61
+ * @returns void
62
+ */
63
+ private setupRoutesStep2() {
64
+ this.app.get('/step2-hosting', async (_, res) => {
65
+ res.send(await this.renderTemplate('step2-hosting'));
66
+ });
67
+ }
68
+
69
+ /**
70
+ * Setup the routes for file "step3-steps.html"
71
+ * @returns void
72
+ */
73
+ private setupRoutesStep3() {
74
+ this.app.get('/step3-steps', async (_, res) => {
75
+ res.send(await this.renderTemplate('step3-steps'));
76
+ });
77
+ this.app.get('/step3-steps-selfhosted', async (_, res) => {
78
+ res.send(await this.renderTemplate('step3-steps-selfhosted'));
79
+ });
80
+ }
81
+
82
+ /**
83
+ * Setup the routes for file "step4-map.html"
84
+ * @returns void
85
+ */
86
+ private setupRoutesStep4() {
87
+ this.app.get('/step4-validated', async (_, res) => {
88
+ res.send(await this.renderTemplate('step4-validated'));
89
+ });
90
+ this.app.get('/step4-validated-selfhosted', async (_, res) => {
91
+ res.send(await this.renderTemplate('step4-validated-selfhosted'));
92
+ });
93
+ }
94
+
95
+ }
@@ -0,0 +1,104 @@
1
+ import express from 'express';
2
+ import * as fs from "node:fs";
3
+ import * as path from "node:path";
4
+
5
+ export class MapController {
6
+ private router: express.Router;
7
+ private app: express.Application;
8
+
9
+ constructor(app: express.Application) {
10
+ this.app = app;
11
+ this.router = express.Router();
12
+ this.setupRoutes();
13
+ // Register the router on the application with the "/maps" prefix
14
+ this.app.use('/maps', this.router);
15
+ }
16
+
17
+ /**
18
+ * Setup the routes for the map controller
19
+ * @returns void
20
+ */
21
+ private setupRoutes() {
22
+ // Route to retrieve the list of maps with their properties
23
+ this.router.get('/list', async (_, res) => {
24
+ try {
25
+ const mapsDir = './';
26
+ const maps = await this.getMapsWithProperties(mapsDir);
27
+ res.json(maps);
28
+ } catch (error) {
29
+ console.error('Error getting maps list:', error);
30
+ res.status(500).json({ error: 'Error getting maps list' });
31
+ }
32
+ });
33
+ }
34
+
35
+ /**
36
+ * Get the list of maps with their properties
37
+ * @param dir - The directory to search for maps
38
+ * @param baseDir - The base directory to use for relative paths
39
+ * @returns The list of maps with their properties
40
+ */
41
+ private async getMapsWithProperties(dir: string, baseDir: string = dir): Promise<any[]> {
42
+ let files = await fs.promises.readdir(dir, { withFileTypes: true });
43
+ const maps: any[] = [];
44
+
45
+ for (const file of files) {
46
+ const fullPath = path.join(dir, file.name);
47
+
48
+ // Exclude the "dist" folder
49
+ if(file.name === 'dist') continue;
50
+
51
+ if (file.isDirectory()) {
52
+ // Recursively search subdirectories
53
+ const subMaps = await this.getMapsWithProperties(fullPath, baseDir);
54
+ maps.push(...subMaps);
55
+ } else if (file.name.endsWith('.tmj')) {
56
+ try {
57
+ // Read and parse TMJ file
58
+ const tmjContent = await fs.promises.readFile(fullPath, 'utf-8');
59
+ const tmjData = JSON.parse(tmjContent);
60
+
61
+ // Extract properties
62
+ const properties = tmjData.properties || [];
63
+ const findProperty = (key: string) => {
64
+ const item = properties.find((p: any) => p.name === key);
65
+ return item ? item.value : null;
66
+ };
67
+
68
+ // Get file stats for size and date
69
+ const stats = await fs.promises.stat(fullPath);
70
+ const fileSizeInMB = (stats.size / (1024 * 1024)).toFixed(2);
71
+ const lastModified = stats.mtime;
72
+
73
+ // Get relative path
74
+ const relativePath = path.relative(baseDir, fullPath).replace(/\\/g, '/');
75
+
76
+ // Extract filename without extension
77
+ const filename = path.basename(file.name, '.tmj');
78
+
79
+ maps.push({
80
+ path: relativePath,
81
+ filename: filename,
82
+ mapName: findProperty('mapName') || filename,
83
+ mapImage: findProperty('mapImage') || null,
84
+ mapDescription: findProperty('mapDescription') || '',
85
+ mapCopyright: findProperty('mapCopyright') || '',
86
+ size: fileSizeInMB,
87
+ lastModified: lastModified.toISOString(),
88
+ lastModifiedFormatted: lastModified.toLocaleDateString('fr-FR', {
89
+ day: '2-digit',
90
+ month: '2-digit',
91
+ year: 'numeric',
92
+ hour: '2-digit',
93
+ minute: '2-digit'
94
+ })
95
+ });
96
+ } catch (error) {
97
+ console.error(`Error reading TMJ file ${fullPath}:`, error);
98
+ }
99
+ }
100
+ }
101
+
102
+ return maps;
103
+ }
104
+ }