├── .gitignore ├── .vscode └── extensions.json ├── README.md ├── departures.json ├── index.html ├── journey.json ├── package-lock.json ├── package.json ├── public ├── Parisine-Bold.otf ├── Parisine-Regular.otf └── vite.svg ├── src ├── App.vue ├── Mock.ts ├── app │ ├── Graph.ts │ └── utils.ts ├── assets │ └── vue.svg ├── colors.ts ├── components │ ├── AnimatedPath.vue │ ├── Header.vue │ ├── OfflineHeader.vue │ ├── RepairScreen.vue │ ├── ShortTrainBubble.vue │ ├── StopName.vue │ ├── Stops.vue │ └── Time.vue ├── fetch.ts ├── geo.test.ts ├── geo.ts ├── layout.ts ├── main.ts ├── services │ └── Wagon.ts ├── style.css └── vite-env.d.ts ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar"] 3 | } 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vue 3 + TypeScript + Vite 2 | 3 | This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 ` 12 | 13 | 14 | -------------------------------------------------------------------------------- /journey.json: -------------------------------------------------------------------------------- 1 | { 2 | "Need an API key ?": "http://arno.cl/lmon", 3 | "sources": [ 4 | { 5 | "name": "Île-de-France Mobilités", 6 | "url": "https://www.iledefrance-mobilites.fr/" 7 | } 8 | ], 9 | "data": { 10 | "line": { 11 | "id": "line:IDFM:C00632", 12 | "number": "2234", 13 | "backgroundColor": "640082", 14 | "textColor": "ffffff", 15 | "shape": "rectangle-filled", 16 | "illustration": "https://transportsapp-cdn-1.arno.cl/fr-idf/BUS_DEFAULT.png", 17 | "picto": "https://transportsapp-cdn-1.arno.cl/fr-idf/pictos/Bus.png", 18 | "importance": 9, 19 | "isOnRoad": true, 20 | "modeSvg": "", 21 | "numberShapeSvg": "2234" 22 | }, 23 | "stops": [ 24 | { 25 | "stop": { 26 | "id": "stop_area:IDFM:68385", 27 | "name": "Marne-la-Vallée Chessy", 28 | "averagePosition": [ 29 | 48.871034, 30 | 2.783909 31 | ], 32 | "stops": [ 33 | { 34 | "id": "stop_point:IDFM:417792", 35 | "position": [ 36 | 48.871034, 37 | 2.783909 38 | ], 39 | "name": "Chessy Nord - Quai K", 40 | "lines": [ 41 | "line:IDFM:C00632" 42 | ] 43 | } 44 | ] 45 | }, 46 | "arrivesAt": { 47 | "theoretical": "2025-01-21T16:45:00.000Z", 48 | "canceled": false 49 | }, 50 | "leavesAt": { 51 | "theoretical": "2025-01-21T16:45:00.000Z", 52 | "canceled": false 53 | }, 54 | "isSkipped": false, 55 | "isClosed": false 56 | }, 57 | { 58 | "stop": { 59 | "id": "stop_area:IDFM:68351", 60 | "name": "Séquoia Lodge", 61 | "averagePosition": [ 62 | 48.870656, 63 | 2.793849 64 | ], 65 | "stops": [ 66 | { 67 | "id": "stop_point:IDFM:15587", 68 | "position": [ 69 | 48.870656, 70 | 2.793849 71 | ], 72 | "name": "Séquoia Lodge", 73 | "lines": [ 74 | "line:IDFM:C00632" 75 | ] 76 | } 77 | ] 78 | }, 79 | "arrivesAt": { 80 | "theoretical": "2025-01-21T16:47:00.000Z", 81 | "canceled": false 82 | }, 83 | "leavesAt": { 84 | "theoretical": "2025-01-21T16:47:00.000Z", 85 | "canceled": false 86 | }, 87 | "isSkipped": false, 88 | "isClosed": false 89 | }, 90 | { 91 | "stop": { 92 | "id": "stop_area:IDFM:68425", 93 | "name": "Hôtels du Val de France / Explorer", 94 | "averagePosition": [ 95 | 48.876935, 96 | 2.805508 97 | ], 98 | "stops": [ 99 | { 100 | "id": "stop_point:IDFM:426628", 101 | "position": [ 102 | 48.876935, 103 | 2.805508 104 | ], 105 | "name": "Hôtels du Val de France", 106 | "lines": [ 107 | "line:IDFM:C00632" 108 | ] 109 | } 110 | ] 111 | }, 112 | "arrivesAt": { 113 | "theoretical": "2025-01-21T16:49:00.000Z", 114 | "canceled": false 115 | }, 116 | "leavesAt": { 117 | "theoretical": "2025-01-21T16:49:00.000Z", 118 | "canceled": false 119 | }, 120 | "isSkipped": false, 121 | "isClosed": false 122 | }, 123 | { 124 | "stop": { 125 | "id": "stop_area:IDFM:68440", 126 | "name": "La Boucle Des Trois Ormes", 127 | "averagePosition": [ 128 | 48.877199, 129 | 2.813115 130 | ], 131 | "stops": [ 132 | { 133 | "id": "stop_point:IDFM:15644", 134 | "position": [ 135 | 48.877199, 136 | 2.813115 137 | ], 138 | "name": "La Boucle des Trois Ormes", 139 | "lines": [ 140 | "line:IDFM:C00632" 141 | ] 142 | } 143 | ] 144 | }, 145 | "arrivesAt": { 146 | "theoretical": "2025-01-21T16:51:00.000Z", 147 | "canceled": false 148 | }, 149 | "leavesAt": { 150 | "theoretical": "2025-01-21T16:51:00.000Z", 151 | "canceled": false 152 | }, 153 | "isSkipped": false, 154 | "isClosed": false 155 | }, 156 | { 157 | "stop": { 158 | "id": "stop_area:IDFM:417799", 159 | "name": "Coulée Verte", 160 | "averagePosition": [ 161 | 48.87274, 162 | 2.814317 163 | ], 164 | "stops": [ 165 | { 166 | "id": "stop_point:IDFM:417800", 167 | "position": [ 168 | 48.87274, 169 | 2.814317 170 | ], 171 | "name": "Coulée Verte", 172 | "lines": [ 173 | "line:IDFM:C00632" 174 | ] 175 | } 176 | ] 177 | }, 178 | "arrivesAt": { 179 | "theoretical": "2025-01-21T16:52:00.000Z", 180 | "canceled": false 181 | }, 182 | "leavesAt": { 183 | "theoretical": "2025-01-21T16:52:00.000Z", 184 | "canceled": false 185 | }, 186 | "isSkipped": false, 187 | "isClosed": false 188 | }, 189 | { 190 | "stop": { 191 | "id": "stop_area:IDFM:68360", 192 | "name": "Collège Jacqueline de Romilly", 193 | "averagePosition": [ 194 | 48.868806, 195 | 2.811728 196 | ], 197 | "stops": [ 198 | { 199 | "id": "stop_point:IDFM:15602", 200 | "position": [ 201 | 48.868806, 202 | 2.811728 203 | ], 204 | "name": "Collège Jacqueline de Romilly", 205 | "lines": [ 206 | "line:IDFM:C00632" 207 | ] 208 | } 209 | ] 210 | }, 211 | "arrivesAt": { 212 | "theoretical": "2025-01-21T16:53:00.000Z", 213 | "canceled": false 214 | }, 215 | "leavesAt": { 216 | "theoretical": "2025-01-21T16:53:00.000Z", 217 | "canceled": false 218 | }, 219 | "isSkipped": false, 220 | "isClosed": false 221 | }, 222 | { 223 | "stop": { 224 | "id": "stop_area:IDFM:68335", 225 | "name": "ZAC du Centre", 226 | "averagePosition": [ 227 | 48.86708, 228 | 2.812306 229 | ], 230 | "stops": [ 231 | { 232 | "id": "stop_point:IDFM:15604", 233 | "position": [ 234 | 48.86708, 235 | 2.812306 236 | ], 237 | "name": "ZAC du Centre", 238 | "lines": [ 239 | "line:IDFM:C00632" 240 | ] 241 | } 242 | ] 243 | }, 244 | "arrivesAt": { 245 | "theoretical": "2025-01-21T16:54:00.000Z", 246 | "canceled": false 247 | }, 248 | "leavesAt": { 249 | "theoretical": "2025-01-21T16:54:00.000Z", 250 | "canceled": false 251 | }, 252 | "isSkipped": false, 253 | "isClosed": false 254 | }, 255 | { 256 | "stop": { 257 | "id": "stop_area:IDFM:68339", 258 | "name": "Clé des Champs", 259 | "averagePosition": [ 260 | 48.867544, 261 | 2.815754 262 | ], 263 | "stops": [ 264 | { 265 | "id": "stop_point:IDFM:15582", 266 | "position": [ 267 | 48.867544, 268 | 2.815754 269 | ], 270 | "name": "Clé des Champs", 271 | "lines": [ 272 | "line:IDFM:C00632" 273 | ] 274 | } 275 | ] 276 | }, 277 | "arrivesAt": { 278 | "theoretical": "2025-01-21T16:55:00.000Z", 279 | "canceled": false 280 | }, 281 | "leavesAt": { 282 | "theoretical": "2025-01-21T16:55:00.000Z", 283 | "canceled": false 284 | }, 285 | "isSkipped": false, 286 | "isClosed": false 287 | }, 288 | { 289 | "stop": { 290 | "id": "stop_area:IDFM:68302", 291 | "name": "La Boiserie", 292 | "averagePosition": [ 293 | 48.862476, 294 | 2.818034 295 | ], 296 | "stops": [ 297 | { 298 | "id": "stop_point:IDFM:15720", 299 | "position": [ 300 | 48.862476, 301 | 2.818034 302 | ], 303 | "name": "La Boiserie", 304 | "lines": [ 305 | "line:IDFM:C00632" 306 | ] 307 | } 308 | ] 309 | }, 310 | "arrivesAt": { 311 | "theoretical": "2025-01-21T16:59:00.000Z", 312 | "canceled": false 313 | }, 314 | "leavesAt": { 315 | "theoretical": "2025-01-21T16:59:00.000Z", 316 | "canceled": false 317 | }, 318 | "isSkipped": false, 319 | "isClosed": false 320 | }, 321 | { 322 | "stop": { 323 | "id": "stop_area:IDFM:68283", 324 | "name": "Pré de Bray / Le Verger", 325 | "averagePosition": [ 326 | 48.859233, 327 | 2.814543 328 | ], 329 | "stops": [ 330 | { 331 | "id": "stop_point:IDFM:15212", 332 | "position": [ 333 | 48.859233, 334 | 2.814543 335 | ], 336 | "name": "Pré de Bray", 337 | "lines": [ 338 | "line:IDFM:C00632" 339 | ] 340 | } 341 | ] 342 | }, 343 | "arrivesAt": { 344 | "theoretical": "2025-01-21T17:00:00.000Z", 345 | "canceled": false 346 | }, 347 | "leavesAt": { 348 | "theoretical": "2025-01-21T17:00:00.000Z", 349 | "canceled": false 350 | }, 351 | "isSkipped": false, 352 | "isClosed": false 353 | }, 354 | { 355 | "stop": { 356 | "id": "stop_area:IDFM:68251", 357 | "name": "L'Orme Rond", 358 | "averagePosition": [ 359 | 48.853238, 360 | 2.816881 361 | ], 362 | "stops": [ 363 | { 364 | "id": "stop_point:IDFM:15576", 365 | "position": [ 366 | 48.853238, 367 | 2.816881 368 | ], 369 | "name": "L'Orme Rond", 370 | "lines": [ 371 | "line:IDFM:C00632" 372 | ] 373 | } 374 | ] 375 | }, 376 | "arrivesAt": { 377 | "theoretical": "2025-01-21T17:02:00.000Z", 378 | "canceled": false 379 | }, 380 | "leavesAt": { 381 | "theoretical": "2025-01-21T17:02:00.000Z", 382 | "canceled": false 383 | }, 384 | "isSkipped": false, 385 | "isClosed": false 386 | }, 387 | { 388 | "stop": { 389 | "id": "stop_area:IDFM:68230", 390 | "name": "Les Genêts", 391 | "averagePosition": [ 392 | 48.852097, 393 | 2.81807 394 | ], 395 | "stops": [ 396 | { 397 | "id": "stop_point:IDFM:15559", 398 | "position": [ 399 | 48.852097, 400 | 2.81807 401 | ], 402 | "name": "Les Genêts", 403 | "lines": [ 404 | "line:IDFM:C00632" 405 | ] 406 | } 407 | ] 408 | }, 409 | "arrivesAt": { 410 | "theoretical": "2025-01-21T17:02:00.000Z", 411 | "canceled": false 412 | }, 413 | "leavesAt": { 414 | "theoretical": "2025-01-21T17:02:00.000Z", 415 | "canceled": false 416 | }, 417 | "isSkipped": false, 418 | "isClosed": false 419 | }, 420 | { 421 | "stop": { 422 | "id": "stop_area:IDFM:68209", 423 | "name": "Place de l'Europe", 424 | "averagePosition": [ 425 | 48.850158, 426 | 2.820326 427 | ], 428 | "stops": [ 429 | { 430 | "id": "stop_point:IDFM:15589", 431 | "position": [ 432 | 48.850158, 433 | 2.820326 434 | ], 435 | "name": "Place de l'Europe", 436 | "lines": [ 437 | "line:IDFM:C00632" 438 | ] 439 | } 440 | ] 441 | }, 442 | "arrivesAt": { 443 | "theoretical": "2025-01-21T17:03:00.000Z", 444 | "canceled": false 445 | }, 446 | "leavesAt": { 447 | "theoretical": "2025-01-21T17:03:00.000Z", 448 | "canceled": false 449 | }, 450 | "isSkipped": false, 451 | "isClosed": false 452 | }, 453 | { 454 | "stop": { 455 | "id": "stop_area:IDFM:68207", 456 | "name": "Gymnase", 457 | "averagePosition": [ 458 | 48.849993, 459 | 2.824455 460 | ], 461 | "stops": [ 462 | { 463 | "id": "stop_point:IDFM:15608", 464 | "position": [ 465 | 48.849993, 466 | 2.824455 467 | ], 468 | "name": "Gymnase", 469 | "lines": [ 470 | "line:IDFM:C00632" 471 | ] 472 | } 473 | ] 474 | }, 475 | "arrivesAt": { 476 | "theoretical": "2025-01-21T17:04:00.000Z", 477 | "canceled": false 478 | }, 479 | "leavesAt": { 480 | "theoretical": "2025-01-21T17:04:00.000Z", 481 | "canceled": false 482 | }, 483 | "isSkipped": false, 484 | "isClosed": false 485 | }, 486 | { 487 | "stop": { 488 | "id": "stop_area:IDFM:68196", 489 | "name": "Boulevard des Artisans", 490 | "averagePosition": [ 491 | 48.848768, 492 | 2.829229 493 | ], 494 | "stops": [ 495 | { 496 | "id": "stop_point:IDFM:11071", 497 | "position": [ 498 | 48.848768, 499 | 2.829229 500 | ], 501 | "name": "Boulevard des Artisans", 502 | "lines": [ 503 | "line:IDFM:C00632" 504 | ] 505 | } 506 | ] 507 | }, 508 | "arrivesAt": { 509 | "theoretical": "2025-01-21T17:06:00.000Z", 510 | "canceled": false 511 | }, 512 | "leavesAt": { 513 | "theoretical": "2025-01-21T17:06:00.000Z", 514 | "canceled": false 515 | }, 516 | "isSkipped": false, 517 | "isClosed": false 518 | }, 519 | { 520 | "stop": { 521 | "id": "stop_area:IDFM:480626", 522 | "name": "Place de la Mairie", 523 | "averagePosition": [ 524 | 48.846727, 525 | 2.823199 526 | ], 527 | "stops": [ 528 | { 529 | "id": "stop_point:IDFM:10493", 530 | "position": [ 531 | 48.846727, 532 | 2.823199 533 | ], 534 | "name": "Place de la Mairie", 535 | "lines": [ 536 | "line:IDFM:C00632" 537 | ] 538 | } 539 | ] 540 | }, 541 | "arrivesAt": { 542 | "theoretical": "2025-01-21T17:07:00.000Z", 543 | "canceled": false 544 | }, 545 | "leavesAt": { 546 | "theoretical": "2025-01-21T17:07:00.000Z", 547 | "canceled": false 548 | }, 549 | "isSkipped": false, 550 | "isClosed": false 551 | }, 552 | { 553 | "stop": { 554 | "id": "stop_area:IDFM:68171", 555 | "name": "École des Girandoles", 556 | "averagePosition": [ 557 | 48.846081, 558 | 2.818269 559 | ], 560 | "stops": [ 561 | { 562 | "id": "stop_point:IDFM:10492", 563 | "position": [ 564 | 48.846081, 565 | 2.818269 566 | ], 567 | "name": "École des Girandoles", 568 | "lines": [ 569 | "line:IDFM:C00632" 570 | ] 571 | } 572 | ] 573 | }, 574 | "arrivesAt": { 575 | "theoretical": "2025-01-21T17:08:00.000Z", 576 | "canceled": false 577 | }, 578 | "leavesAt": { 579 | "theoretical": "2025-01-21T17:08:00.000Z", 580 | "canceled": false 581 | }, 582 | "isSkipped": false, 583 | "isClosed": false 584 | }, 585 | { 586 | "stop": { 587 | "id": "stop_area:IDFM:68185", 588 | "name": "Rue de Bellesmes", 589 | "averagePosition": [ 590 | 48.847364, 591 | 2.816084 592 | ], 593 | "stops": [ 594 | { 595 | "id": "stop_point:IDFM:15667", 596 | "position": [ 597 | 48.847364, 598 | 2.816084 599 | ], 600 | "name": "Rue de Bellesmes", 601 | "lines": [ 602 | "line:IDFM:C00632" 603 | ] 604 | } 605 | ] 606 | }, 607 | "arrivesAt": { 608 | "theoretical": "2025-01-21T17:09:00.000Z", 609 | "canceled": false 610 | }, 611 | "leavesAt": { 612 | "theoretical": "2025-01-21T17:09:00.000Z", 613 | "canceled": false 614 | }, 615 | "isSkipped": false, 616 | "isClosed": false 617 | }, 618 | { 619 | "stop": { 620 | "id": "stop_area:IDFM:68192", 621 | "name": "Armières", 622 | "averagePosition": [ 623 | 48.848959, 624 | 2.812208 625 | ], 626 | "stops": [ 627 | { 628 | "id": "stop_point:IDFM:15557", 629 | "position": [ 630 | 48.848959, 631 | 2.812208 632 | ], 633 | "name": "Armières", 634 | "lines": [ 635 | "line:IDFM:C00632" 636 | ] 637 | } 638 | ] 639 | }, 640 | "arrivesAt": { 641 | "theoretical": "2025-01-21T17:10:00.000Z", 642 | "canceled": false 643 | }, 644 | "leavesAt": { 645 | "theoretical": "2025-01-21T17:10:00.000Z", 646 | "canceled": false 647 | }, 648 | "isSkipped": false, 649 | "isClosed": false 650 | }, 651 | { 652 | "stop": { 653 | "id": "stop_area:IDFM:68167", 654 | "name": "Centre Aquatique", 655 | "averagePosition": [ 656 | 48.844993, 657 | 2.80154 658 | ], 659 | "stops": [ 660 | { 661 | "id": "stop_point:IDFM:22491", 662 | "position": [ 663 | 48.844993, 664 | 2.80154 665 | ], 666 | "name": "Centre Aquatique", 667 | "lines": [ 668 | "line:IDFM:C00632" 669 | ] 670 | } 671 | ] 672 | }, 673 | "arrivesAt": { 674 | "theoretical": "2025-01-21T17:13:00.000Z", 675 | "canceled": false 676 | }, 677 | "leavesAt": { 678 | "theoretical": "2025-01-21T17:13:00.000Z", 679 | "canceled": false 680 | }, 681 | "isSkipped": false, 682 | "isClosed": false 683 | }, 684 | { 685 | "stop": { 686 | "id": "stop_area:IDFM:68150", 687 | "name": "Robert Thiboust", 688 | "averagePosition": [ 689 | 48.843219, 690 | 2.794638 691 | ], 692 | "stops": [ 693 | { 694 | "id": "stop_point:IDFM:15577", 695 | "position": [ 696 | 48.843219, 697 | 2.794638 698 | ], 699 | "name": "Robert Thiboust", 700 | "lines": [ 701 | "line:IDFM:C00632" 702 | ] 703 | } 704 | ] 705 | }, 706 | "arrivesAt": { 707 | "theoretical": "2025-01-21T17:15:00.000Z", 708 | "canceled": false 709 | }, 710 | "leavesAt": { 711 | "theoretical": "2025-01-21T17:15:00.000Z", 712 | "canceled": false 713 | }, 714 | "isSkipped": false, 715 | "isClosed": false 716 | }, 717 | { 718 | "stop": { 719 | "id": "stop_area:IDFM:68139", 720 | "name": "Pressoir-Parc D'Entreprises", 721 | "averagePosition": [ 722 | 48.841564, 723 | 2.789413 724 | ], 725 | "stops": [ 726 | { 727 | "id": "stop_point:IDFM:15579", 728 | "position": [ 729 | 48.841564, 730 | 2.789413 731 | ], 732 | "name": "Pressoir-Parc d'Entreprises", 733 | "lines": [ 734 | "line:IDFM:C00632" 735 | ] 736 | } 737 | ] 738 | }, 739 | "arrivesAt": { 740 | "theoretical": "2025-01-21T17:16:00.000Z", 741 | "canceled": false 742 | }, 743 | "leavesAt": { 744 | "theoretical": "2025-01-21T17:16:00.000Z", 745 | "canceled": false 746 | }, 747 | "isSkipped": false, 748 | "isClosed": false 749 | }, 750 | { 751 | "stop": { 752 | "id": "stop_area:IDFM:68138", 753 | "name": "Les Pléïades", 754 | "averagePosition": [ 755 | 48.841393, 756 | 2.784059 757 | ], 758 | "stops": [ 759 | { 760 | "id": "stop_point:IDFM:15250", 761 | "position": [ 762 | 48.841393, 763 | 2.784059 764 | ], 765 | "name": "Les Pléïades", 766 | "lines": [ 767 | "line:IDFM:C00632" 768 | ] 769 | } 770 | ] 771 | }, 772 | "arrivesAt": { 773 | "theoretical": "2025-01-21T17:18:00.000Z", 774 | "canceled": false 775 | }, 776 | "leavesAt": { 777 | "theoretical": "2025-01-21T17:18:00.000Z", 778 | "canceled": false 779 | }, 780 | "isSkipped": false, 781 | "isClosed": false 782 | }, 783 | { 784 | "stop": { 785 | "id": "stop_area:IDFM:68169", 786 | "name": "Place Thomas le Pileur", 787 | "averagePosition": [ 788 | 48.84559, 789 | 2.782665 790 | ], 791 | "stops": [ 792 | { 793 | "id": "stop_point:IDFM:15316", 794 | "position": [ 795 | 48.84559, 796 | 2.782665 797 | ], 798 | "name": "Place Thomas le Pileur", 799 | "lines": [ 800 | "line:IDFM:C00632" 801 | ] 802 | } 803 | ] 804 | }, 805 | "arrivesAt": { 806 | "theoretical": "2025-01-21T17:20:00.000Z", 807 | "canceled": false 808 | }, 809 | "leavesAt": { 810 | "theoretical": "2025-01-21T17:20:00.000Z", 811 | "canceled": false 812 | }, 813 | "isSkipped": false, 814 | "isClosed": false 815 | }, 816 | { 817 | "stop": { 818 | "id": "stop_area:IDFM:68211", 819 | "name": "Collège Madeleine Renaud / Les Marmousets", 820 | "averagePosition": [ 821 | 48.850553, 822 | 2.786924 823 | ], 824 | "stops": [ 825 | { 826 | "id": "stop_point:IDFM:15248", 827 | "position": [ 828 | 48.850553, 829 | 2.786924 830 | ], 831 | "name": "Les Marmousets", 832 | "lines": [ 833 | "line:IDFM:C00632" 834 | ] 835 | } 836 | ] 837 | }, 838 | "arrivesAt": { 839 | "theoretical": "2025-01-21T17:23:00.000Z", 840 | "canceled": false 841 | }, 842 | "leavesAt": { 843 | "theoretical": "2025-01-21T17:23:00.000Z", 844 | "canceled": false 845 | }, 846 | "isSkipped": false, 847 | "isClosed": false 848 | }, 849 | { 850 | "stop": { 851 | "id": "stop_area:IDFM:68235", 852 | "name": "Garonne", 853 | "averagePosition": [ 854 | 48.852701, 855 | 2.786193 856 | ], 857 | "stops": [ 858 | { 859 | "id": "stop_point:IDFM:15642", 860 | "position": [ 861 | 48.852701, 862 | 2.786193 863 | ], 864 | "name": "Garonne", 865 | "lines": [ 866 | "line:IDFM:C00632" 867 | ] 868 | } 869 | ] 870 | }, 871 | "arrivesAt": { 872 | "theoretical": "2025-01-21T17:24:00.000Z", 873 | "canceled": false 874 | }, 875 | "leavesAt": { 876 | "theoretical": "2025-01-21T17:24:00.000Z", 877 | "canceled": false 878 | }, 879 | "isSkipped": false, 880 | "isClosed": false 881 | }, 882 | { 883 | "stop": { 884 | "id": "stop_area:IDFM:68267", 885 | "name": "Hôtel de Ville", 886 | "averagePosition": [ 887 | 48.856421, 888 | 2.784147 889 | ], 890 | "stops": [ 891 | { 892 | "id": "stop_point:IDFM:15660", 893 | "position": [ 894 | 48.856421, 895 | 2.784147 896 | ], 897 | "name": "Hôtel de Ville", 898 | "lines": [ 899 | "line:IDFM:C00632" 900 | ] 901 | } 902 | ] 903 | }, 904 | "arrivesAt": { 905 | "theoretical": "2025-01-21T17:26:00.000Z", 906 | "canceled": false 907 | }, 908 | "leavesAt": { 909 | "theoretical": "2025-01-21T17:26:00.000Z", 910 | "canceled": false 911 | }, 912 | "isSkipped": false, 913 | "isClosed": false 914 | }, 915 | { 916 | "stop": { 917 | "id": "stop_area:IDFM:68269", 918 | "name": "Lycée Emilie du Châtelet", 919 | "averagePosition": [ 920 | 48.856763, 921 | 2.779416 922 | ], 923 | "stops": [ 924 | { 925 | "id": "stop_point:IDFM:21147", 926 | "position": [ 927 | 48.856763, 928 | 2.779416 929 | ], 930 | "name": "Lycée Emilie du Châtelet", 931 | "lines": [ 932 | "line:IDFM:C00632" 933 | ] 934 | } 935 | ] 936 | }, 937 | "arrivesAt": { 938 | "theoretical": "2025-01-21T17:27:00.000Z", 939 | "canceled": false 940 | }, 941 | "leavesAt": { 942 | "theoretical": "2025-01-21T17:27:00.000Z", 943 | "canceled": false 944 | }, 945 | "isSkipped": false, 946 | "isClosed": false 947 | }, 948 | { 949 | "stop": { 950 | "id": "stop_area:IDFM:68266", 951 | "name": "Val d'Europe", 952 | "averagePosition": [ 953 | 48.855205, 954 | 2.773603 955 | ], 956 | "stops": [ 957 | { 958 | "id": "stop_point:IDFM:412423", 959 | "position": [ 960 | 48.855205, 961 | 2.773603 962 | ], 963 | "name": "Gare de Val d'Europe", 964 | "lines": [ 965 | "line:IDFM:C00632" 966 | ] 967 | } 968 | ] 969 | }, 970 | "arrivesAt": { 971 | "theoretical": "2025-01-21T17:31:00.000Z", 972 | "canceled": false 973 | }, 974 | "leavesAt": { 975 | "theoretical": "2025-01-21T17:31:00.000Z", 976 | "canceled": false 977 | }, 978 | "isSkipped": false, 979 | "isClosed": false 980 | } 981 | ], 982 | "journeyId": "vehicle_journey:IDFM:TRANSDEV_MARNE_LA_VALLEE:167083-C00632-19498071", 983 | "congestion": null, 984 | "disruptions": [] 985 | }, 986 | "crowdsourcing": { 987 | "userReports": [] 988 | } 989 | } -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "syspad", 3 | "version": "0.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "syspad", 9 | "version": "0.0.0", 10 | "dependencies": { 11 | "@vueuse/core": "^12.5.0", 12 | "dayjs": "^1.11.13", 13 | "vue": "^3.5.13" 14 | }, 15 | "devDependencies": { 16 | "@vitejs/plugin-vue": "^5.2.1", 17 | "@vue/tsconfig": "^0.7.0", 18 | "typescript": "~5.6.2", 19 | "vite": "^6.0.5", 20 | "vitest": "^3.0.4", 21 | "vue-tsc": "^2.2.0" 22 | } 23 | }, 24 | "node_modules/@babel/helper-string-parser": { 25 | "version": "7.25.9", 26 | "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", 27 | "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", 28 | "engines": { 29 | "node": ">=6.9.0" 30 | } 31 | }, 32 | "node_modules/@babel/helper-validator-identifier": { 33 | "version": "7.25.9", 34 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", 35 | "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", 36 | "engines": { 37 | "node": ">=6.9.0" 38 | } 39 | }, 40 | "node_modules/@babel/parser": { 41 | "version": "7.26.5", 42 | "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.5.tgz", 43 | "integrity": "sha512-SRJ4jYmXRqV1/Xc+TIVG84WjHBXKlxO9sHQnA2Pf12QQEAp1LOh6kDzNHXcUnbH1QI0FDoPPVOt+vyUDucxpaw==", 44 | "dependencies": { 45 | "@babel/types": "^7.26.5" 46 | }, 47 | "bin": { 48 | "parser": "bin/babel-parser.js" 49 | }, 50 | "engines": { 51 | "node": ">=6.0.0" 52 | } 53 | }, 54 | "node_modules/@babel/types": { 55 | "version": "7.26.5", 56 | "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.5.tgz", 57 | "integrity": "sha512-L6mZmwFDK6Cjh1nRCLXpa6no13ZIioJDz7mdkzHv399pThrTa/k0nUlNaenOeh2kWu/iaOQYElEpKPUswUa9Vg==", 58 | "dependencies": { 59 | "@babel/helper-string-parser": "^7.25.9", 60 | "@babel/helper-validator-identifier": "^7.25.9" 61 | }, 62 | "engines": { 63 | "node": ">=6.9.0" 64 | } 65 | }, 66 | "node_modules/@esbuild/aix-ppc64": { 67 | "version": "0.24.2", 68 | "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz", 69 | "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==", 70 | "cpu": [ 71 | "ppc64" 72 | ], 73 | "dev": true, 74 | "optional": true, 75 | "os": [ 76 | "aix" 77 | ], 78 | "engines": { 79 | "node": ">=18" 80 | } 81 | }, 82 | "node_modules/@esbuild/android-arm": { 83 | "version": "0.24.2", 84 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz", 85 | "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==", 86 | "cpu": [ 87 | "arm" 88 | ], 89 | "dev": true, 90 | "optional": true, 91 | "os": [ 92 | "android" 93 | ], 94 | "engines": { 95 | "node": ">=18" 96 | } 97 | }, 98 | "node_modules/@esbuild/android-arm64": { 99 | "version": "0.24.2", 100 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz", 101 | "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==", 102 | "cpu": [ 103 | "arm64" 104 | ], 105 | "dev": true, 106 | "optional": true, 107 | "os": [ 108 | "android" 109 | ], 110 | "engines": { 111 | "node": ">=18" 112 | } 113 | }, 114 | "node_modules/@esbuild/android-x64": { 115 | "version": "0.24.2", 116 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz", 117 | "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==", 118 | "cpu": [ 119 | "x64" 120 | ], 121 | "dev": true, 122 | "optional": true, 123 | "os": [ 124 | "android" 125 | ], 126 | "engines": { 127 | "node": ">=18" 128 | } 129 | }, 130 | "node_modules/@esbuild/darwin-arm64": { 131 | "version": "0.24.2", 132 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz", 133 | "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==", 134 | "cpu": [ 135 | "arm64" 136 | ], 137 | "dev": true, 138 | "optional": true, 139 | "os": [ 140 | "darwin" 141 | ], 142 | "engines": { 143 | "node": ">=18" 144 | } 145 | }, 146 | "node_modules/@esbuild/darwin-x64": { 147 | "version": "0.24.2", 148 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz", 149 | "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==", 150 | "cpu": [ 151 | "x64" 152 | ], 153 | "dev": true, 154 | "optional": true, 155 | "os": [ 156 | "darwin" 157 | ], 158 | "engines": { 159 | "node": ">=18" 160 | } 161 | }, 162 | "node_modules/@esbuild/freebsd-arm64": { 163 | "version": "0.24.2", 164 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz", 165 | "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==", 166 | "cpu": [ 167 | "arm64" 168 | ], 169 | "dev": true, 170 | "optional": true, 171 | "os": [ 172 | "freebsd" 173 | ], 174 | "engines": { 175 | "node": ">=18" 176 | } 177 | }, 178 | "node_modules/@esbuild/freebsd-x64": { 179 | "version": "0.24.2", 180 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz", 181 | "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==", 182 | "cpu": [ 183 | "x64" 184 | ], 185 | "dev": true, 186 | "optional": true, 187 | "os": [ 188 | "freebsd" 189 | ], 190 | "engines": { 191 | "node": ">=18" 192 | } 193 | }, 194 | "node_modules/@esbuild/linux-arm": { 195 | "version": "0.24.2", 196 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz", 197 | "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==", 198 | "cpu": [ 199 | "arm" 200 | ], 201 | "dev": true, 202 | "optional": true, 203 | "os": [ 204 | "linux" 205 | ], 206 | "engines": { 207 | "node": ">=18" 208 | } 209 | }, 210 | "node_modules/@esbuild/linux-arm64": { 211 | "version": "0.24.2", 212 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz", 213 | "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==", 214 | "cpu": [ 215 | "arm64" 216 | ], 217 | "dev": true, 218 | "optional": true, 219 | "os": [ 220 | "linux" 221 | ], 222 | "engines": { 223 | "node": ">=18" 224 | } 225 | }, 226 | "node_modules/@esbuild/linux-ia32": { 227 | "version": "0.24.2", 228 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz", 229 | "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==", 230 | "cpu": [ 231 | "ia32" 232 | ], 233 | "dev": true, 234 | "optional": true, 235 | "os": [ 236 | "linux" 237 | ], 238 | "engines": { 239 | "node": ">=18" 240 | } 241 | }, 242 | "node_modules/@esbuild/linux-loong64": { 243 | "version": "0.24.2", 244 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz", 245 | "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==", 246 | "cpu": [ 247 | "loong64" 248 | ], 249 | "dev": true, 250 | "optional": true, 251 | "os": [ 252 | "linux" 253 | ], 254 | "engines": { 255 | "node": ">=18" 256 | } 257 | }, 258 | "node_modules/@esbuild/linux-mips64el": { 259 | "version": "0.24.2", 260 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz", 261 | "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==", 262 | "cpu": [ 263 | "mips64el" 264 | ], 265 | "dev": true, 266 | "optional": true, 267 | "os": [ 268 | "linux" 269 | ], 270 | "engines": { 271 | "node": ">=18" 272 | } 273 | }, 274 | "node_modules/@esbuild/linux-ppc64": { 275 | "version": "0.24.2", 276 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz", 277 | "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==", 278 | "cpu": [ 279 | "ppc64" 280 | ], 281 | "dev": true, 282 | "optional": true, 283 | "os": [ 284 | "linux" 285 | ], 286 | "engines": { 287 | "node": ">=18" 288 | } 289 | }, 290 | "node_modules/@esbuild/linux-riscv64": { 291 | "version": "0.24.2", 292 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz", 293 | "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==", 294 | "cpu": [ 295 | "riscv64" 296 | ], 297 | "dev": true, 298 | "optional": true, 299 | "os": [ 300 | "linux" 301 | ], 302 | "engines": { 303 | "node": ">=18" 304 | } 305 | }, 306 | "node_modules/@esbuild/linux-s390x": { 307 | "version": "0.24.2", 308 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz", 309 | "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==", 310 | "cpu": [ 311 | "s390x" 312 | ], 313 | "dev": true, 314 | "optional": true, 315 | "os": [ 316 | "linux" 317 | ], 318 | "engines": { 319 | "node": ">=18" 320 | } 321 | }, 322 | "node_modules/@esbuild/linux-x64": { 323 | "version": "0.24.2", 324 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz", 325 | "integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==", 326 | "cpu": [ 327 | "x64" 328 | ], 329 | "dev": true, 330 | "optional": true, 331 | "os": [ 332 | "linux" 333 | ], 334 | "engines": { 335 | "node": ">=18" 336 | } 337 | }, 338 | "node_modules/@esbuild/netbsd-arm64": { 339 | "version": "0.24.2", 340 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz", 341 | "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==", 342 | "cpu": [ 343 | "arm64" 344 | ], 345 | "dev": true, 346 | "optional": true, 347 | "os": [ 348 | "netbsd" 349 | ], 350 | "engines": { 351 | "node": ">=18" 352 | } 353 | }, 354 | "node_modules/@esbuild/netbsd-x64": { 355 | "version": "0.24.2", 356 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz", 357 | "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==", 358 | "cpu": [ 359 | "x64" 360 | ], 361 | "dev": true, 362 | "optional": true, 363 | "os": [ 364 | "netbsd" 365 | ], 366 | "engines": { 367 | "node": ">=18" 368 | } 369 | }, 370 | "node_modules/@esbuild/openbsd-arm64": { 371 | "version": "0.24.2", 372 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz", 373 | "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==", 374 | "cpu": [ 375 | "arm64" 376 | ], 377 | "dev": true, 378 | "optional": true, 379 | "os": [ 380 | "openbsd" 381 | ], 382 | "engines": { 383 | "node": ">=18" 384 | } 385 | }, 386 | "node_modules/@esbuild/openbsd-x64": { 387 | "version": "0.24.2", 388 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz", 389 | "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==", 390 | "cpu": [ 391 | "x64" 392 | ], 393 | "dev": true, 394 | "optional": true, 395 | "os": [ 396 | "openbsd" 397 | ], 398 | "engines": { 399 | "node": ">=18" 400 | } 401 | }, 402 | "node_modules/@esbuild/sunos-x64": { 403 | "version": "0.24.2", 404 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz", 405 | "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==", 406 | "cpu": [ 407 | "x64" 408 | ], 409 | "dev": true, 410 | "optional": true, 411 | "os": [ 412 | "sunos" 413 | ], 414 | "engines": { 415 | "node": ">=18" 416 | } 417 | }, 418 | "node_modules/@esbuild/win32-arm64": { 419 | "version": "0.24.2", 420 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz", 421 | "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==", 422 | "cpu": [ 423 | "arm64" 424 | ], 425 | "dev": true, 426 | "optional": true, 427 | "os": [ 428 | "win32" 429 | ], 430 | "engines": { 431 | "node": ">=18" 432 | } 433 | }, 434 | "node_modules/@esbuild/win32-ia32": { 435 | "version": "0.24.2", 436 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz", 437 | "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==", 438 | "cpu": [ 439 | "ia32" 440 | ], 441 | "dev": true, 442 | "optional": true, 443 | "os": [ 444 | "win32" 445 | ], 446 | "engines": { 447 | "node": ">=18" 448 | } 449 | }, 450 | "node_modules/@esbuild/win32-x64": { 451 | "version": "0.24.2", 452 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz", 453 | "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==", 454 | "cpu": [ 455 | "x64" 456 | ], 457 | "dev": true, 458 | "optional": true, 459 | "os": [ 460 | "win32" 461 | ], 462 | "engines": { 463 | "node": ">=18" 464 | } 465 | }, 466 | "node_modules/@jridgewell/sourcemap-codec": { 467 | "version": "1.5.0", 468 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", 469 | "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" 470 | }, 471 | "node_modules/@rollup/rollup-android-arm-eabi": { 472 | "version": "4.31.0", 473 | "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.31.0.tgz", 474 | "integrity": "sha512-9NrR4033uCbUBRgvLcBrJofa2KY9DzxL2UKZ1/4xA/mnTNyhZCWBuD8X3tPm1n4KxcgaraOYgrFKSgwjASfmlA==", 475 | "cpu": [ 476 | "arm" 477 | ], 478 | "dev": true, 479 | "optional": true, 480 | "os": [ 481 | "android" 482 | ] 483 | }, 484 | "node_modules/@rollup/rollup-android-arm64": { 485 | "version": "4.31.0", 486 | "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.31.0.tgz", 487 | "integrity": "sha512-iBbODqT86YBFHajxxF8ebj2hwKm1k8PTBQSojSt3d1FFt1gN+xf4CowE47iN0vOSdnd+5ierMHBbu/rHc7nq5g==", 488 | "cpu": [ 489 | "arm64" 490 | ], 491 | "dev": true, 492 | "optional": true, 493 | "os": [ 494 | "android" 495 | ] 496 | }, 497 | "node_modules/@rollup/rollup-darwin-arm64": { 498 | "version": "4.31.0", 499 | "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.31.0.tgz", 500 | "integrity": "sha512-WHIZfXgVBX30SWuTMhlHPXTyN20AXrLH4TEeH/D0Bolvx9PjgZnn4H677PlSGvU6MKNsjCQJYczkpvBbrBnG6g==", 501 | "cpu": [ 502 | "arm64" 503 | ], 504 | "dev": true, 505 | "optional": true, 506 | "os": [ 507 | "darwin" 508 | ] 509 | }, 510 | "node_modules/@rollup/rollup-darwin-x64": { 511 | "version": "4.31.0", 512 | "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.31.0.tgz", 513 | "integrity": "sha512-hrWL7uQacTEF8gdrQAqcDy9xllQ0w0zuL1wk1HV8wKGSGbKPVjVUv/DEwT2+Asabf8Dh/As+IvfdU+H8hhzrQQ==", 514 | "cpu": [ 515 | "x64" 516 | ], 517 | "dev": true, 518 | "optional": true, 519 | "os": [ 520 | "darwin" 521 | ] 522 | }, 523 | "node_modules/@rollup/rollup-freebsd-arm64": { 524 | "version": "4.31.0", 525 | "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.31.0.tgz", 526 | "integrity": "sha512-S2oCsZ4hJviG1QjPY1h6sVJLBI6ekBeAEssYKad1soRFv3SocsQCzX6cwnk6fID6UQQACTjeIMB+hyYrFacRew==", 527 | "cpu": [ 528 | "arm64" 529 | ], 530 | "dev": true, 531 | "optional": true, 532 | "os": [ 533 | "freebsd" 534 | ] 535 | }, 536 | "node_modules/@rollup/rollup-freebsd-x64": { 537 | "version": "4.31.0", 538 | "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.31.0.tgz", 539 | "integrity": "sha512-pCANqpynRS4Jirn4IKZH4tnm2+2CqCNLKD7gAdEjzdLGbH1iO0zouHz4mxqg0uEMpO030ejJ0aA6e1PJo2xrPA==", 540 | "cpu": [ 541 | "x64" 542 | ], 543 | "dev": true, 544 | "optional": true, 545 | "os": [ 546 | "freebsd" 547 | ] 548 | }, 549 | "node_modules/@rollup/rollup-linux-arm-gnueabihf": { 550 | "version": "4.31.0", 551 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.31.0.tgz", 552 | "integrity": "sha512-0O8ViX+QcBd3ZmGlcFTnYXZKGbFu09EhgD27tgTdGnkcYXLat4KIsBBQeKLR2xZDCXdIBAlWLkiXE1+rJpCxFw==", 553 | "cpu": [ 554 | "arm" 555 | ], 556 | "dev": true, 557 | "optional": true, 558 | "os": [ 559 | "linux" 560 | ] 561 | }, 562 | "node_modules/@rollup/rollup-linux-arm-musleabihf": { 563 | "version": "4.31.0", 564 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.31.0.tgz", 565 | "integrity": "sha512-w5IzG0wTVv7B0/SwDnMYmbr2uERQp999q8FMkKG1I+j8hpPX2BYFjWe69xbhbP6J9h2gId/7ogesl9hwblFwwg==", 566 | "cpu": [ 567 | "arm" 568 | ], 569 | "dev": true, 570 | "optional": true, 571 | "os": [ 572 | "linux" 573 | ] 574 | }, 575 | "node_modules/@rollup/rollup-linux-arm64-gnu": { 576 | "version": "4.31.0", 577 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.31.0.tgz", 578 | "integrity": "sha512-JyFFshbN5xwy6fulZ8B/8qOqENRmDdEkcIMF0Zz+RsfamEW+Zabl5jAb0IozP/8UKnJ7g2FtZZPEUIAlUSX8cA==", 579 | "cpu": [ 580 | "arm64" 581 | ], 582 | "dev": true, 583 | "optional": true, 584 | "os": [ 585 | "linux" 586 | ] 587 | }, 588 | "node_modules/@rollup/rollup-linux-arm64-musl": { 589 | "version": "4.31.0", 590 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.31.0.tgz", 591 | "integrity": "sha512-kpQXQ0UPFeMPmPYksiBL9WS/BDiQEjRGMfklVIsA0Sng347H8W2iexch+IEwaR7OVSKtr2ZFxggt11zVIlZ25g==", 592 | "cpu": [ 593 | "arm64" 594 | ], 595 | "dev": true, 596 | "optional": true, 597 | "os": [ 598 | "linux" 599 | ] 600 | }, 601 | "node_modules/@rollup/rollup-linux-loongarch64-gnu": { 602 | "version": "4.31.0", 603 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.31.0.tgz", 604 | "integrity": "sha512-pMlxLjt60iQTzt9iBb3jZphFIl55a70wexvo8p+vVFK+7ifTRookdoXX3bOsRdmfD+OKnMozKO6XM4zR0sHRrQ==", 605 | "cpu": [ 606 | "loong64" 607 | ], 608 | "dev": true, 609 | "optional": true, 610 | "os": [ 611 | "linux" 612 | ] 613 | }, 614 | "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { 615 | "version": "4.31.0", 616 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.31.0.tgz", 617 | "integrity": "sha512-D7TXT7I/uKEuWiRkEFbed1UUYZwcJDU4vZQdPTcepK7ecPhzKOYk4Er2YR4uHKme4qDeIh6N3XrLfpuM7vzRWQ==", 618 | "cpu": [ 619 | "ppc64" 620 | ], 621 | "dev": true, 622 | "optional": true, 623 | "os": [ 624 | "linux" 625 | ] 626 | }, 627 | "node_modules/@rollup/rollup-linux-riscv64-gnu": { 628 | "version": "4.31.0", 629 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.31.0.tgz", 630 | "integrity": "sha512-wal2Tc8O5lMBtoePLBYRKj2CImUCJ4UNGJlLwspx7QApYny7K1cUYlzQ/4IGQBLmm+y0RS7dwc3TDO/pmcneTw==", 631 | "cpu": [ 632 | "riscv64" 633 | ], 634 | "dev": true, 635 | "optional": true, 636 | "os": [ 637 | "linux" 638 | ] 639 | }, 640 | "node_modules/@rollup/rollup-linux-s390x-gnu": { 641 | "version": "4.31.0", 642 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.31.0.tgz", 643 | "integrity": "sha512-O1o5EUI0+RRMkK9wiTVpk2tyzXdXefHtRTIjBbmFREmNMy7pFeYXCFGbhKFwISA3UOExlo5GGUuuj3oMKdK6JQ==", 644 | "cpu": [ 645 | "s390x" 646 | ], 647 | "dev": true, 648 | "optional": true, 649 | "os": [ 650 | "linux" 651 | ] 652 | }, 653 | "node_modules/@rollup/rollup-linux-x64-gnu": { 654 | "version": "4.31.0", 655 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.31.0.tgz", 656 | "integrity": "sha512-zSoHl356vKnNxwOWnLd60ixHNPRBglxpv2g7q0Cd3Pmr561gf0HiAcUBRL3S1vPqRC17Zo2CX/9cPkqTIiai1g==", 657 | "cpu": [ 658 | "x64" 659 | ], 660 | "dev": true, 661 | "optional": true, 662 | "os": [ 663 | "linux" 664 | ] 665 | }, 666 | "node_modules/@rollup/rollup-linux-x64-musl": { 667 | "version": "4.31.0", 668 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.31.0.tgz", 669 | "integrity": "sha512-ypB/HMtcSGhKUQNiFwqgdclWNRrAYDH8iMYH4etw/ZlGwiTVxBz2tDrGRrPlfZu6QjXwtd+C3Zib5pFqID97ZA==", 670 | "cpu": [ 671 | "x64" 672 | ], 673 | "dev": true, 674 | "optional": true, 675 | "os": [ 676 | "linux" 677 | ] 678 | }, 679 | "node_modules/@rollup/rollup-win32-arm64-msvc": { 680 | "version": "4.31.0", 681 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.31.0.tgz", 682 | "integrity": "sha512-JuhN2xdI/m8Hr+aVO3vspO7OQfUFO6bKLIRTAy0U15vmWjnZDLrEgCZ2s6+scAYaQVpYSh9tZtRijApw9IXyMw==", 683 | "cpu": [ 684 | "arm64" 685 | ], 686 | "dev": true, 687 | "optional": true, 688 | "os": [ 689 | "win32" 690 | ] 691 | }, 692 | "node_modules/@rollup/rollup-win32-ia32-msvc": { 693 | "version": "4.31.0", 694 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.31.0.tgz", 695 | "integrity": "sha512-U1xZZXYkvdf5MIWmftU8wrM5PPXzyaY1nGCI4KI4BFfoZxHamsIe+BtnPLIvvPykvQWlVbqUXdLa4aJUuilwLQ==", 696 | "cpu": [ 697 | "ia32" 698 | ], 699 | "dev": true, 700 | "optional": true, 701 | "os": [ 702 | "win32" 703 | ] 704 | }, 705 | "node_modules/@rollup/rollup-win32-x64-msvc": { 706 | "version": "4.31.0", 707 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.31.0.tgz", 708 | "integrity": "sha512-ul8rnCsUumNln5YWwz0ted2ZHFhzhRRnkpBZ+YRuHoRAlUji9KChpOUOndY7uykrPEPXVbHLlsdo6v5yXo/TXw==", 709 | "cpu": [ 710 | "x64" 711 | ], 712 | "dev": true, 713 | "optional": true, 714 | "os": [ 715 | "win32" 716 | ] 717 | }, 718 | "node_modules/@types/estree": { 719 | "version": "1.0.6", 720 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", 721 | "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", 722 | "dev": true 723 | }, 724 | "node_modules/@types/web-bluetooth": { 725 | "version": "0.0.20", 726 | "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", 727 | "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==" 728 | }, 729 | "node_modules/@vitejs/plugin-vue": { 730 | "version": "5.2.1", 731 | "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.1.tgz", 732 | "integrity": "sha512-cxh314tzaWwOLqVes2gnnCtvBDcM1UMdn+iFR+UjAn411dPT3tOmqrJjbMd7koZpMAmBM/GqeV4n9ge7JSiJJQ==", 733 | "dev": true, 734 | "engines": { 735 | "node": "^18.0.0 || >=20.0.0" 736 | }, 737 | "peerDependencies": { 738 | "vite": "^5.0.0 || ^6.0.0", 739 | "vue": "^3.2.25" 740 | } 741 | }, 742 | "node_modules/@vitest/expect": { 743 | "version": "3.0.4", 744 | "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.0.4.tgz", 745 | "integrity": "sha512-Nm5kJmYw6P2BxhJPkO3eKKhGYKRsnqJqf+r0yOGRKpEP+bSCBDsjXgiu1/5QFrnPMEgzfC38ZEjvCFgaNBC0Eg==", 746 | "dev": true, 747 | "dependencies": { 748 | "@vitest/spy": "3.0.4", 749 | "@vitest/utils": "3.0.4", 750 | "chai": "^5.1.2", 751 | "tinyrainbow": "^2.0.0" 752 | }, 753 | "funding": { 754 | "url": "https://opencollective.com/vitest" 755 | } 756 | }, 757 | "node_modules/@vitest/mocker": { 758 | "version": "3.0.4", 759 | "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.0.4.tgz", 760 | "integrity": "sha512-gEef35vKafJlfQbnyOXZ0Gcr9IBUsMTyTLXsEQwuyYAerpHqvXhzdBnDFuHLpFqth3F7b6BaFr4qV/Cs1ULx5A==", 761 | "dev": true, 762 | "dependencies": { 763 | "@vitest/spy": "3.0.4", 764 | "estree-walker": "^3.0.3", 765 | "magic-string": "^0.30.17" 766 | }, 767 | "funding": { 768 | "url": "https://opencollective.com/vitest" 769 | }, 770 | "peerDependencies": { 771 | "msw": "^2.4.9", 772 | "vite": "^5.0.0 || ^6.0.0" 773 | }, 774 | "peerDependenciesMeta": { 775 | "msw": { 776 | "optional": true 777 | }, 778 | "vite": { 779 | "optional": true 780 | } 781 | } 782 | }, 783 | "node_modules/@vitest/mocker/node_modules/estree-walker": { 784 | "version": "3.0.3", 785 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", 786 | "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", 787 | "dev": true, 788 | "dependencies": { 789 | "@types/estree": "^1.0.0" 790 | } 791 | }, 792 | "node_modules/@vitest/pretty-format": { 793 | "version": "3.0.4", 794 | "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.4.tgz", 795 | "integrity": "sha512-ts0fba+dEhK2aC9PFuZ9LTpULHpY/nd6jhAQ5IMU7Gaj7crPCTdCFfgvXxruRBLFS+MLraicCuFXxISEq8C93g==", 796 | "dev": true, 797 | "dependencies": { 798 | "tinyrainbow": "^2.0.0" 799 | }, 800 | "funding": { 801 | "url": "https://opencollective.com/vitest" 802 | } 803 | }, 804 | "node_modules/@vitest/runner": { 805 | "version": "3.0.4", 806 | "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.0.4.tgz", 807 | "integrity": "sha512-dKHzTQ7n9sExAcWH/0sh1elVgwc7OJ2lMOBrAm73J7AH6Pf9T12Zh3lNE1TETZaqrWFXtLlx3NVrLRb5hCK+iw==", 808 | "dev": true, 809 | "dependencies": { 810 | "@vitest/utils": "3.0.4", 811 | "pathe": "^2.0.2" 812 | }, 813 | "funding": { 814 | "url": "https://opencollective.com/vitest" 815 | } 816 | }, 817 | "node_modules/@vitest/snapshot": { 818 | "version": "3.0.4", 819 | "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.0.4.tgz", 820 | "integrity": "sha512-+p5knMLwIk7lTQkM3NonZ9zBewzVp9EVkVpvNta0/PlFWpiqLaRcF4+33L1it3uRUCh0BGLOaXPPGEjNKfWb4w==", 821 | "dev": true, 822 | "dependencies": { 823 | "@vitest/pretty-format": "3.0.4", 824 | "magic-string": "^0.30.17", 825 | "pathe": "^2.0.2" 826 | }, 827 | "funding": { 828 | "url": "https://opencollective.com/vitest" 829 | } 830 | }, 831 | "node_modules/@vitest/spy": { 832 | "version": "3.0.4", 833 | "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.0.4.tgz", 834 | "integrity": "sha512-sXIMF0oauYyUy2hN49VFTYodzEAu744MmGcPR3ZBsPM20G+1/cSW/n1U+3Yu/zHxX2bIDe1oJASOkml+osTU6Q==", 835 | "dev": true, 836 | "dependencies": { 837 | "tinyspy": "^3.0.2" 838 | }, 839 | "funding": { 840 | "url": "https://opencollective.com/vitest" 841 | } 842 | }, 843 | "node_modules/@vitest/utils": { 844 | "version": "3.0.4", 845 | "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.0.4.tgz", 846 | "integrity": "sha512-8BqC1ksYsHtbWH+DfpOAKrFw3jl3Uf9J7yeFh85Pz52IWuh1hBBtyfEbRNNZNjl8H8A5yMLH9/t+k7HIKzQcZQ==", 847 | "dev": true, 848 | "dependencies": { 849 | "@vitest/pretty-format": "3.0.4", 850 | "loupe": "^3.1.2", 851 | "tinyrainbow": "^2.0.0" 852 | }, 853 | "funding": { 854 | "url": "https://opencollective.com/vitest" 855 | } 856 | }, 857 | "node_modules/@volar/language-core": { 858 | "version": "2.4.11", 859 | "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.11.tgz", 860 | "integrity": "sha512-lN2C1+ByfW9/JRPpqScuZt/4OrUUse57GLI6TbLgTIqBVemdl1wNcZ1qYGEo2+Gw8coYLgCy7SuKqn6IrQcQgg==", 861 | "dev": true, 862 | "dependencies": { 863 | "@volar/source-map": "2.4.11" 864 | } 865 | }, 866 | "node_modules/@volar/source-map": { 867 | "version": "2.4.11", 868 | "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.11.tgz", 869 | "integrity": "sha512-ZQpmafIGvaZMn/8iuvCFGrW3smeqkq/IIh9F1SdSx9aUl0J4Iurzd6/FhmjNO5g2ejF3rT45dKskgXWiofqlZQ==", 870 | "dev": true 871 | }, 872 | "node_modules/@volar/typescript": { 873 | "version": "2.4.11", 874 | "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.11.tgz", 875 | "integrity": "sha512-2DT+Tdh88Spp5PyPbqhyoYavYCPDsqbHLFwcUI9K1NlY1YgUJvujGdrqUp0zWxnW7KWNTr3xSpMuv2WnaTKDAw==", 876 | "dev": true, 877 | "dependencies": { 878 | "@volar/language-core": "2.4.11", 879 | "path-browserify": "^1.0.1", 880 | "vscode-uri": "^3.0.8" 881 | } 882 | }, 883 | "node_modules/@vue/compiler-core": { 884 | "version": "3.5.13", 885 | "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.13.tgz", 886 | "integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==", 887 | "dependencies": { 888 | "@babel/parser": "^7.25.3", 889 | "@vue/shared": "3.5.13", 890 | "entities": "^4.5.0", 891 | "estree-walker": "^2.0.2", 892 | "source-map-js": "^1.2.0" 893 | } 894 | }, 895 | "node_modules/@vue/compiler-dom": { 896 | "version": "3.5.13", 897 | "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz", 898 | "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==", 899 | "dependencies": { 900 | "@vue/compiler-core": "3.5.13", 901 | "@vue/shared": "3.5.13" 902 | } 903 | }, 904 | "node_modules/@vue/compiler-sfc": { 905 | "version": "3.5.13", 906 | "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz", 907 | "integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==", 908 | "dependencies": { 909 | "@babel/parser": "^7.25.3", 910 | "@vue/compiler-core": "3.5.13", 911 | "@vue/compiler-dom": "3.5.13", 912 | "@vue/compiler-ssr": "3.5.13", 913 | "@vue/shared": "3.5.13", 914 | "estree-walker": "^2.0.2", 915 | "magic-string": "^0.30.11", 916 | "postcss": "^8.4.48", 917 | "source-map-js": "^1.2.0" 918 | } 919 | }, 920 | "node_modules/@vue/compiler-ssr": { 921 | "version": "3.5.13", 922 | "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz", 923 | "integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==", 924 | "dependencies": { 925 | "@vue/compiler-dom": "3.5.13", 926 | "@vue/shared": "3.5.13" 927 | } 928 | }, 929 | "node_modules/@vue/compiler-vue2": { 930 | "version": "2.7.16", 931 | "resolved": "https://registry.npmjs.org/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz", 932 | "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==", 933 | "dev": true, 934 | "dependencies": { 935 | "de-indent": "^1.0.2", 936 | "he": "^1.2.0" 937 | } 938 | }, 939 | "node_modules/@vue/language-core": { 940 | "version": "2.2.0", 941 | "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.2.0.tgz", 942 | "integrity": "sha512-O1ZZFaaBGkKbsRfnVH1ifOK1/1BUkyK+3SQsfnh6PmMmD4qJcTU8godCeA96jjDRTL6zgnK7YzCHfaUlH2r0Mw==", 943 | "dev": true, 944 | "dependencies": { 945 | "@volar/language-core": "~2.4.11", 946 | "@vue/compiler-dom": "^3.5.0", 947 | "@vue/compiler-vue2": "^2.7.16", 948 | "@vue/shared": "^3.5.0", 949 | "alien-signals": "^0.4.9", 950 | "minimatch": "^9.0.3", 951 | "muggle-string": "^0.4.1", 952 | "path-browserify": "^1.0.1" 953 | }, 954 | "peerDependencies": { 955 | "typescript": "*" 956 | }, 957 | "peerDependenciesMeta": { 958 | "typescript": { 959 | "optional": true 960 | } 961 | } 962 | }, 963 | "node_modules/@vue/reactivity": { 964 | "version": "3.5.13", 965 | "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.13.tgz", 966 | "integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==", 967 | "dependencies": { 968 | "@vue/shared": "3.5.13" 969 | } 970 | }, 971 | "node_modules/@vue/runtime-core": { 972 | "version": "3.5.13", 973 | "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.13.tgz", 974 | "integrity": "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==", 975 | "dependencies": { 976 | "@vue/reactivity": "3.5.13", 977 | "@vue/shared": "3.5.13" 978 | } 979 | }, 980 | "node_modules/@vue/runtime-dom": { 981 | "version": "3.5.13", 982 | "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz", 983 | "integrity": "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==", 984 | "dependencies": { 985 | "@vue/reactivity": "3.5.13", 986 | "@vue/runtime-core": "3.5.13", 987 | "@vue/shared": "3.5.13", 988 | "csstype": "^3.1.3" 989 | } 990 | }, 991 | "node_modules/@vue/server-renderer": { 992 | "version": "3.5.13", 993 | "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.13.tgz", 994 | "integrity": "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==", 995 | "dependencies": { 996 | "@vue/compiler-ssr": "3.5.13", 997 | "@vue/shared": "3.5.13" 998 | }, 999 | "peerDependencies": { 1000 | "vue": "3.5.13" 1001 | } 1002 | }, 1003 | "node_modules/@vue/shared": { 1004 | "version": "3.5.13", 1005 | "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", 1006 | "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==" 1007 | }, 1008 | "node_modules/@vue/tsconfig": { 1009 | "version": "0.7.0", 1010 | "resolved": "https://registry.npmjs.org/@vue/tsconfig/-/tsconfig-0.7.0.tgz", 1011 | "integrity": "sha512-ku2uNz5MaZ9IerPPUyOHzyjhXoX2kVJaVf7hL315DC17vS6IiZRmmCPfggNbU16QTvM80+uYYy3eYJB59WCtvg==", 1012 | "dev": true, 1013 | "peerDependencies": { 1014 | "typescript": "5.x", 1015 | "vue": "^3.4.0" 1016 | }, 1017 | "peerDependenciesMeta": { 1018 | "typescript": { 1019 | "optional": true 1020 | }, 1021 | "vue": { 1022 | "optional": true 1023 | } 1024 | } 1025 | }, 1026 | "node_modules/@vueuse/core": { 1027 | "version": "12.5.0", 1028 | "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-12.5.0.tgz", 1029 | "integrity": "sha512-GVyH1iYqNANwcahAx8JBm6awaNgvR/SwZ1fjr10b8l1HIgDp82ngNbfzJUgOgWEoxjL+URAggnlilAEXwCOZtg==", 1030 | "dependencies": { 1031 | "@types/web-bluetooth": "^0.0.20", 1032 | "@vueuse/metadata": "12.5.0", 1033 | "@vueuse/shared": "12.5.0", 1034 | "vue": "^3.5.13" 1035 | }, 1036 | "funding": { 1037 | "url": "https://github.com/sponsors/antfu" 1038 | } 1039 | }, 1040 | "node_modules/@vueuse/metadata": { 1041 | "version": "12.5.0", 1042 | "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-12.5.0.tgz", 1043 | "integrity": "sha512-Ui7Lo2a7AxrMAXRF+fAp9QsXuwTeeZ8fIB9wsLHqzq9MQk+2gMYE2IGJW48VMJ8ecvCB3z3GsGLKLbSasQ5Qlg==", 1044 | "funding": { 1045 | "url": "https://github.com/sponsors/antfu" 1046 | } 1047 | }, 1048 | "node_modules/@vueuse/shared": { 1049 | "version": "12.5.0", 1050 | "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-12.5.0.tgz", 1051 | "integrity": "sha512-vMpcL1lStUU6O+kdj6YdHDixh0odjPAUM15uJ9f7MY781jcYkIwFA4iv2EfoIPO6vBmvutI1HxxAwmf0cx5ISQ==", 1052 | "dependencies": { 1053 | "vue": "^3.5.13" 1054 | }, 1055 | "funding": { 1056 | "url": "https://github.com/sponsors/antfu" 1057 | } 1058 | }, 1059 | "node_modules/alien-signals": { 1060 | "version": "0.4.14", 1061 | "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-0.4.14.tgz", 1062 | "integrity": "sha512-itUAVzhczTmP2U5yX67xVpsbbOiquusbWVyA9N+sy6+r6YVbFkahXvNCeEPWEOMhwDYwbVbGHFkVL03N9I5g+Q==", 1063 | "dev": true 1064 | }, 1065 | "node_modules/assertion-error": { 1066 | "version": "2.0.1", 1067 | "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", 1068 | "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", 1069 | "dev": true, 1070 | "engines": { 1071 | "node": ">=12" 1072 | } 1073 | }, 1074 | "node_modules/balanced-match": { 1075 | "version": "1.0.2", 1076 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 1077 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 1078 | "dev": true 1079 | }, 1080 | "node_modules/brace-expansion": { 1081 | "version": "2.0.1", 1082 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", 1083 | "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", 1084 | "dev": true, 1085 | "dependencies": { 1086 | "balanced-match": "^1.0.0" 1087 | } 1088 | }, 1089 | "node_modules/cac": { 1090 | "version": "6.7.14", 1091 | "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", 1092 | "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", 1093 | "dev": true, 1094 | "engines": { 1095 | "node": ">=8" 1096 | } 1097 | }, 1098 | "node_modules/chai": { 1099 | "version": "5.1.2", 1100 | "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.2.tgz", 1101 | "integrity": "sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==", 1102 | "dev": true, 1103 | "dependencies": { 1104 | "assertion-error": "^2.0.1", 1105 | "check-error": "^2.1.1", 1106 | "deep-eql": "^5.0.1", 1107 | "loupe": "^3.1.0", 1108 | "pathval": "^2.0.0" 1109 | }, 1110 | "engines": { 1111 | "node": ">=12" 1112 | } 1113 | }, 1114 | "node_modules/check-error": { 1115 | "version": "2.1.1", 1116 | "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", 1117 | "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", 1118 | "dev": true, 1119 | "engines": { 1120 | "node": ">= 16" 1121 | } 1122 | }, 1123 | "node_modules/csstype": { 1124 | "version": "3.1.3", 1125 | "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", 1126 | "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" 1127 | }, 1128 | "node_modules/dayjs": { 1129 | "version": "1.11.13", 1130 | "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", 1131 | "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==" 1132 | }, 1133 | "node_modules/de-indent": { 1134 | "version": "1.0.2", 1135 | "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", 1136 | "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", 1137 | "dev": true 1138 | }, 1139 | "node_modules/debug": { 1140 | "version": "4.4.0", 1141 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", 1142 | "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", 1143 | "dev": true, 1144 | "dependencies": { 1145 | "ms": "^2.1.3" 1146 | }, 1147 | "engines": { 1148 | "node": ">=6.0" 1149 | }, 1150 | "peerDependenciesMeta": { 1151 | "supports-color": { 1152 | "optional": true 1153 | } 1154 | } 1155 | }, 1156 | "node_modules/deep-eql": { 1157 | "version": "5.0.2", 1158 | "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", 1159 | "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", 1160 | "dev": true, 1161 | "engines": { 1162 | "node": ">=6" 1163 | } 1164 | }, 1165 | "node_modules/entities": { 1166 | "version": "4.5.0", 1167 | "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", 1168 | "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", 1169 | "engines": { 1170 | "node": ">=0.12" 1171 | }, 1172 | "funding": { 1173 | "url": "https://github.com/fb55/entities?sponsor=1" 1174 | } 1175 | }, 1176 | "node_modules/es-module-lexer": { 1177 | "version": "1.6.0", 1178 | "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", 1179 | "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==", 1180 | "dev": true 1181 | }, 1182 | "node_modules/esbuild": { 1183 | "version": "0.24.2", 1184 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz", 1185 | "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==", 1186 | "dev": true, 1187 | "hasInstallScript": true, 1188 | "bin": { 1189 | "esbuild": "bin/esbuild" 1190 | }, 1191 | "engines": { 1192 | "node": ">=18" 1193 | }, 1194 | "optionalDependencies": { 1195 | "@esbuild/aix-ppc64": "0.24.2", 1196 | "@esbuild/android-arm": "0.24.2", 1197 | "@esbuild/android-arm64": "0.24.2", 1198 | "@esbuild/android-x64": "0.24.2", 1199 | "@esbuild/darwin-arm64": "0.24.2", 1200 | "@esbuild/darwin-x64": "0.24.2", 1201 | "@esbuild/freebsd-arm64": "0.24.2", 1202 | "@esbuild/freebsd-x64": "0.24.2", 1203 | "@esbuild/linux-arm": "0.24.2", 1204 | "@esbuild/linux-arm64": "0.24.2", 1205 | "@esbuild/linux-ia32": "0.24.2", 1206 | "@esbuild/linux-loong64": "0.24.2", 1207 | "@esbuild/linux-mips64el": "0.24.2", 1208 | "@esbuild/linux-ppc64": "0.24.2", 1209 | "@esbuild/linux-riscv64": "0.24.2", 1210 | "@esbuild/linux-s390x": "0.24.2", 1211 | "@esbuild/linux-x64": "0.24.2", 1212 | "@esbuild/netbsd-arm64": "0.24.2", 1213 | "@esbuild/netbsd-x64": "0.24.2", 1214 | "@esbuild/openbsd-arm64": "0.24.2", 1215 | "@esbuild/openbsd-x64": "0.24.2", 1216 | "@esbuild/sunos-x64": "0.24.2", 1217 | "@esbuild/win32-arm64": "0.24.2", 1218 | "@esbuild/win32-ia32": "0.24.2", 1219 | "@esbuild/win32-x64": "0.24.2" 1220 | } 1221 | }, 1222 | "node_modules/estree-walker": { 1223 | "version": "2.0.2", 1224 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", 1225 | "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" 1226 | }, 1227 | "node_modules/expect-type": { 1228 | "version": "1.1.0", 1229 | "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.1.0.tgz", 1230 | "integrity": "sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==", 1231 | "dev": true, 1232 | "engines": { 1233 | "node": ">=12.0.0" 1234 | } 1235 | }, 1236 | "node_modules/fsevents": { 1237 | "version": "2.3.3", 1238 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 1239 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 1240 | "dev": true, 1241 | "hasInstallScript": true, 1242 | "optional": true, 1243 | "os": [ 1244 | "darwin" 1245 | ], 1246 | "engines": { 1247 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 1248 | } 1249 | }, 1250 | "node_modules/he": { 1251 | "version": "1.2.0", 1252 | "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", 1253 | "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", 1254 | "dev": true, 1255 | "bin": { 1256 | "he": "bin/he" 1257 | } 1258 | }, 1259 | "node_modules/loupe": { 1260 | "version": "3.1.2", 1261 | "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz", 1262 | "integrity": "sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==", 1263 | "dev": true 1264 | }, 1265 | "node_modules/magic-string": { 1266 | "version": "0.30.17", 1267 | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", 1268 | "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", 1269 | "dependencies": { 1270 | "@jridgewell/sourcemap-codec": "^1.5.0" 1271 | } 1272 | }, 1273 | "node_modules/minimatch": { 1274 | "version": "9.0.5", 1275 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", 1276 | "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", 1277 | "dev": true, 1278 | "dependencies": { 1279 | "brace-expansion": "^2.0.1" 1280 | }, 1281 | "engines": { 1282 | "node": ">=16 || 14 >=14.17" 1283 | }, 1284 | "funding": { 1285 | "url": "https://github.com/sponsors/isaacs" 1286 | } 1287 | }, 1288 | "node_modules/ms": { 1289 | "version": "2.1.3", 1290 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 1291 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 1292 | "dev": true 1293 | }, 1294 | "node_modules/muggle-string": { 1295 | "version": "0.4.1", 1296 | "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", 1297 | "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", 1298 | "dev": true 1299 | }, 1300 | "node_modules/nanoid": { 1301 | "version": "3.3.8", 1302 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", 1303 | "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", 1304 | "funding": [ 1305 | { 1306 | "type": "github", 1307 | "url": "https://github.com/sponsors/ai" 1308 | } 1309 | ], 1310 | "bin": { 1311 | "nanoid": "bin/nanoid.cjs" 1312 | }, 1313 | "engines": { 1314 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 1315 | } 1316 | }, 1317 | "node_modules/path-browserify": { 1318 | "version": "1.0.1", 1319 | "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", 1320 | "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", 1321 | "dev": true 1322 | }, 1323 | "node_modules/pathe": { 1324 | "version": "2.0.2", 1325 | "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.2.tgz", 1326 | "integrity": "sha512-15Ztpk+nov8DR524R4BF7uEuzESgzUEAV4Ah7CUMNGXdE5ELuvxElxGXndBl32vMSsWa1jpNf22Z+Er3sKwq+w==", 1327 | "dev": true 1328 | }, 1329 | "node_modules/pathval": { 1330 | "version": "2.0.0", 1331 | "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", 1332 | "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", 1333 | "dev": true, 1334 | "engines": { 1335 | "node": ">= 14.16" 1336 | } 1337 | }, 1338 | "node_modules/picocolors": { 1339 | "version": "1.1.1", 1340 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", 1341 | "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" 1342 | }, 1343 | "node_modules/postcss": { 1344 | "version": "8.5.1", 1345 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz", 1346 | "integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==", 1347 | "funding": [ 1348 | { 1349 | "type": "opencollective", 1350 | "url": "https://opencollective.com/postcss/" 1351 | }, 1352 | { 1353 | "type": "tidelift", 1354 | "url": "https://tidelift.com/funding/github/npm/postcss" 1355 | }, 1356 | { 1357 | "type": "github", 1358 | "url": "https://github.com/sponsors/ai" 1359 | } 1360 | ], 1361 | "dependencies": { 1362 | "nanoid": "^3.3.8", 1363 | "picocolors": "^1.1.1", 1364 | "source-map-js": "^1.2.1" 1365 | }, 1366 | "engines": { 1367 | "node": "^10 || ^12 || >=14" 1368 | } 1369 | }, 1370 | "node_modules/rollup": { 1371 | "version": "4.31.0", 1372 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.31.0.tgz", 1373 | "integrity": "sha512-9cCE8P4rZLx9+PjoyqHLs31V9a9Vpvfo4qNcs6JCiGWYhw2gijSetFbH6SSy1whnkgcefnUwr8sad7tgqsGvnw==", 1374 | "dev": true, 1375 | "dependencies": { 1376 | "@types/estree": "1.0.6" 1377 | }, 1378 | "bin": { 1379 | "rollup": "dist/bin/rollup" 1380 | }, 1381 | "engines": { 1382 | "node": ">=18.0.0", 1383 | "npm": ">=8.0.0" 1384 | }, 1385 | "optionalDependencies": { 1386 | "@rollup/rollup-android-arm-eabi": "4.31.0", 1387 | "@rollup/rollup-android-arm64": "4.31.0", 1388 | "@rollup/rollup-darwin-arm64": "4.31.0", 1389 | "@rollup/rollup-darwin-x64": "4.31.0", 1390 | "@rollup/rollup-freebsd-arm64": "4.31.0", 1391 | "@rollup/rollup-freebsd-x64": "4.31.0", 1392 | "@rollup/rollup-linux-arm-gnueabihf": "4.31.0", 1393 | "@rollup/rollup-linux-arm-musleabihf": "4.31.0", 1394 | "@rollup/rollup-linux-arm64-gnu": "4.31.0", 1395 | "@rollup/rollup-linux-arm64-musl": "4.31.0", 1396 | "@rollup/rollup-linux-loongarch64-gnu": "4.31.0", 1397 | "@rollup/rollup-linux-powerpc64le-gnu": "4.31.0", 1398 | "@rollup/rollup-linux-riscv64-gnu": "4.31.0", 1399 | "@rollup/rollup-linux-s390x-gnu": "4.31.0", 1400 | "@rollup/rollup-linux-x64-gnu": "4.31.0", 1401 | "@rollup/rollup-linux-x64-musl": "4.31.0", 1402 | "@rollup/rollup-win32-arm64-msvc": "4.31.0", 1403 | "@rollup/rollup-win32-ia32-msvc": "4.31.0", 1404 | "@rollup/rollup-win32-x64-msvc": "4.31.0", 1405 | "fsevents": "~2.3.2" 1406 | } 1407 | }, 1408 | "node_modules/siginfo": { 1409 | "version": "2.0.0", 1410 | "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", 1411 | "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", 1412 | "dev": true 1413 | }, 1414 | "node_modules/source-map-js": { 1415 | "version": "1.2.1", 1416 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", 1417 | "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", 1418 | "engines": { 1419 | "node": ">=0.10.0" 1420 | } 1421 | }, 1422 | "node_modules/stackback": { 1423 | "version": "0.0.2", 1424 | "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", 1425 | "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", 1426 | "dev": true 1427 | }, 1428 | "node_modules/std-env": { 1429 | "version": "3.8.0", 1430 | "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.0.tgz", 1431 | "integrity": "sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==", 1432 | "dev": true 1433 | }, 1434 | "node_modules/tinybench": { 1435 | "version": "2.9.0", 1436 | "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", 1437 | "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", 1438 | "dev": true 1439 | }, 1440 | "node_modules/tinyexec": { 1441 | "version": "0.3.2", 1442 | "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", 1443 | "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", 1444 | "dev": true 1445 | }, 1446 | "node_modules/tinypool": { 1447 | "version": "1.0.2", 1448 | "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.2.tgz", 1449 | "integrity": "sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==", 1450 | "dev": true, 1451 | "engines": { 1452 | "node": "^18.0.0 || >=20.0.0" 1453 | } 1454 | }, 1455 | "node_modules/tinyrainbow": { 1456 | "version": "2.0.0", 1457 | "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", 1458 | "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", 1459 | "dev": true, 1460 | "engines": { 1461 | "node": ">=14.0.0" 1462 | } 1463 | }, 1464 | "node_modules/tinyspy": { 1465 | "version": "3.0.2", 1466 | "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", 1467 | "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", 1468 | "dev": true, 1469 | "engines": { 1470 | "node": ">=14.0.0" 1471 | } 1472 | }, 1473 | "node_modules/typescript": { 1474 | "version": "5.6.3", 1475 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", 1476 | "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", 1477 | "devOptional": true, 1478 | "bin": { 1479 | "tsc": "bin/tsc", 1480 | "tsserver": "bin/tsserver" 1481 | }, 1482 | "engines": { 1483 | "node": ">=14.17" 1484 | } 1485 | }, 1486 | "node_modules/vite": { 1487 | "version": "6.0.11", 1488 | "resolved": "https://registry.npmjs.org/vite/-/vite-6.0.11.tgz", 1489 | "integrity": "sha512-4VL9mQPKoHy4+FE0NnRE/kbY51TOfaknxAjt3fJbGJxhIpBZiqVzlZDEesWWsuREXHwNdAoOFZ9MkPEVXczHwg==", 1490 | "dev": true, 1491 | "dependencies": { 1492 | "esbuild": "^0.24.2", 1493 | "postcss": "^8.4.49", 1494 | "rollup": "^4.23.0" 1495 | }, 1496 | "bin": { 1497 | "vite": "bin/vite.js" 1498 | }, 1499 | "engines": { 1500 | "node": "^18.0.0 || ^20.0.0 || >=22.0.0" 1501 | }, 1502 | "funding": { 1503 | "url": "https://github.com/vitejs/vite?sponsor=1" 1504 | }, 1505 | "optionalDependencies": { 1506 | "fsevents": "~2.3.3" 1507 | }, 1508 | "peerDependencies": { 1509 | "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", 1510 | "jiti": ">=1.21.0", 1511 | "less": "*", 1512 | "lightningcss": "^1.21.0", 1513 | "sass": "*", 1514 | "sass-embedded": "*", 1515 | "stylus": "*", 1516 | "sugarss": "*", 1517 | "terser": "^5.16.0", 1518 | "tsx": "^4.8.1", 1519 | "yaml": "^2.4.2" 1520 | }, 1521 | "peerDependenciesMeta": { 1522 | "@types/node": { 1523 | "optional": true 1524 | }, 1525 | "jiti": { 1526 | "optional": true 1527 | }, 1528 | "less": { 1529 | "optional": true 1530 | }, 1531 | "lightningcss": { 1532 | "optional": true 1533 | }, 1534 | "sass": { 1535 | "optional": true 1536 | }, 1537 | "sass-embedded": { 1538 | "optional": true 1539 | }, 1540 | "stylus": { 1541 | "optional": true 1542 | }, 1543 | "sugarss": { 1544 | "optional": true 1545 | }, 1546 | "terser": { 1547 | "optional": true 1548 | }, 1549 | "tsx": { 1550 | "optional": true 1551 | }, 1552 | "yaml": { 1553 | "optional": true 1554 | } 1555 | } 1556 | }, 1557 | "node_modules/vite-node": { 1558 | "version": "3.0.4", 1559 | "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.4.tgz", 1560 | "integrity": "sha512-7JZKEzcYV2Nx3u6rlvN8qdo3QV7Fxyt6hx+CCKz9fbWxdX5IvUOmTWEAxMrWxaiSf7CKGLJQ5rFu8prb/jBjOA==", 1561 | "dev": true, 1562 | "dependencies": { 1563 | "cac": "^6.7.14", 1564 | "debug": "^4.4.0", 1565 | "es-module-lexer": "^1.6.0", 1566 | "pathe": "^2.0.2", 1567 | "vite": "^5.0.0 || ^6.0.0" 1568 | }, 1569 | "bin": { 1570 | "vite-node": "vite-node.mjs" 1571 | }, 1572 | "engines": { 1573 | "node": "^18.0.0 || ^20.0.0 || >=22.0.0" 1574 | }, 1575 | "funding": { 1576 | "url": "https://opencollective.com/vitest" 1577 | } 1578 | }, 1579 | "node_modules/vitest": { 1580 | "version": "3.0.4", 1581 | "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.0.4.tgz", 1582 | "integrity": "sha512-6XG8oTKy2gnJIFTHP6LD7ExFeNLxiTkK3CfMvT7IfR8IN+BYICCf0lXUQmX7i7JoxUP8QmeP4mTnWXgflu4yjw==", 1583 | "dev": true, 1584 | "dependencies": { 1585 | "@vitest/expect": "3.0.4", 1586 | "@vitest/mocker": "3.0.4", 1587 | "@vitest/pretty-format": "^3.0.4", 1588 | "@vitest/runner": "3.0.4", 1589 | "@vitest/snapshot": "3.0.4", 1590 | "@vitest/spy": "3.0.4", 1591 | "@vitest/utils": "3.0.4", 1592 | "chai": "^5.1.2", 1593 | "debug": "^4.4.0", 1594 | "expect-type": "^1.1.0", 1595 | "magic-string": "^0.30.17", 1596 | "pathe": "^2.0.2", 1597 | "std-env": "^3.8.0", 1598 | "tinybench": "^2.9.0", 1599 | "tinyexec": "^0.3.2", 1600 | "tinypool": "^1.0.2", 1601 | "tinyrainbow": "^2.0.0", 1602 | "vite": "^5.0.0 || ^6.0.0", 1603 | "vite-node": "3.0.4", 1604 | "why-is-node-running": "^2.3.0" 1605 | }, 1606 | "bin": { 1607 | "vitest": "vitest.mjs" 1608 | }, 1609 | "engines": { 1610 | "node": "^18.0.0 || ^20.0.0 || >=22.0.0" 1611 | }, 1612 | "funding": { 1613 | "url": "https://opencollective.com/vitest" 1614 | }, 1615 | "peerDependencies": { 1616 | "@edge-runtime/vm": "*", 1617 | "@types/debug": "^4.1.12", 1618 | "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", 1619 | "@vitest/browser": "3.0.4", 1620 | "@vitest/ui": "3.0.4", 1621 | "happy-dom": "*", 1622 | "jsdom": "*" 1623 | }, 1624 | "peerDependenciesMeta": { 1625 | "@edge-runtime/vm": { 1626 | "optional": true 1627 | }, 1628 | "@types/debug": { 1629 | "optional": true 1630 | }, 1631 | "@types/node": { 1632 | "optional": true 1633 | }, 1634 | "@vitest/browser": { 1635 | "optional": true 1636 | }, 1637 | "@vitest/ui": { 1638 | "optional": true 1639 | }, 1640 | "happy-dom": { 1641 | "optional": true 1642 | }, 1643 | "jsdom": { 1644 | "optional": true 1645 | } 1646 | } 1647 | }, 1648 | "node_modules/vscode-uri": { 1649 | "version": "3.0.8", 1650 | "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", 1651 | "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==", 1652 | "dev": true 1653 | }, 1654 | "node_modules/vue": { 1655 | "version": "3.5.13", 1656 | "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.13.tgz", 1657 | "integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==", 1658 | "dependencies": { 1659 | "@vue/compiler-dom": "3.5.13", 1660 | "@vue/compiler-sfc": "3.5.13", 1661 | "@vue/runtime-dom": "3.5.13", 1662 | "@vue/server-renderer": "3.5.13", 1663 | "@vue/shared": "3.5.13" 1664 | }, 1665 | "peerDependencies": { 1666 | "typescript": "*" 1667 | }, 1668 | "peerDependenciesMeta": { 1669 | "typescript": { 1670 | "optional": true 1671 | } 1672 | } 1673 | }, 1674 | "node_modules/vue-tsc": { 1675 | "version": "2.2.0", 1676 | "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.2.0.tgz", 1677 | "integrity": "sha512-gtmM1sUuJ8aSb0KoAFmK9yMxb8TxjewmxqTJ1aKphD5Cbu0rULFY6+UQT51zW7SpUcenfPUuflKyVwyx9Qdnxg==", 1678 | "dev": true, 1679 | "dependencies": { 1680 | "@volar/typescript": "~2.4.11", 1681 | "@vue/language-core": "2.2.0" 1682 | }, 1683 | "bin": { 1684 | "vue-tsc": "bin/vue-tsc.js" 1685 | }, 1686 | "peerDependencies": { 1687 | "typescript": ">=5.0.0" 1688 | } 1689 | }, 1690 | "node_modules/why-is-node-running": { 1691 | "version": "2.3.0", 1692 | "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", 1693 | "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", 1694 | "dev": true, 1695 | "dependencies": { 1696 | "siginfo": "^2.0.0", 1697 | "stackback": "0.0.2" 1698 | }, 1699 | "bin": { 1700 | "why-is-node-running": "cli.js" 1701 | }, 1702 | "engines": { 1703 | "node": ">=8" 1704 | } 1705 | } 1706 | } 1707 | } 1708 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "syspad", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "test": "vitest", 9 | "build": "vue-tsc -b && vite build", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@vueuse/core": "^12.5.0", 14 | "dayjs": "^1.11.13", 15 | "vue": "^3.5.13" 16 | }, 17 | "devDependencies": { 18 | "@vitejs/plugin-vue": "^5.2.1", 19 | "@vue/tsconfig": "^0.7.0", 20 | "typescript": "~5.6.2", 21 | "vite": "^6.0.5", 22 | "vitest": "^3.0.4", 23 | "vue-tsc": "^2.2.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /public/Parisine-Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arnoclr/syspad/3311ec8dcded4263ac4436ea545b5920b8cb854f/public/Parisine-Bold.otf -------------------------------------------------------------------------------- /public/Parisine-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arnoclr/syspad/3311ec8dcded4263ac4436ea545b5920b8cb854f/public/Parisine-Regular.otf -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 101 | 102 | 136 | 137 | 173 | -------------------------------------------------------------------------------- /src/Mock.ts: -------------------------------------------------------------------------------- 1 | import { Graph } from "./app/Graph"; 2 | 3 | function identity(x: T): T { 4 | return x; 5 | } 6 | 7 | const graph = new Graph(identity); 8 | 9 | // graph.add(["Juvisy", "Viry-Châtillon"]); 10 | 11 | // // south 12 | // graph.add( 13 | // [ 14 | // "Grigny Centre", 15 | // "Orangis Bois de l'Epine", 16 | // "Evry Courcouronnes", 17 | // "Le Bras de Fer", 18 | // ], 19 | // ["Viry-Châtillon"] 20 | // ); 21 | 22 | // // north 23 | // graph.add(["Ris Orangis", "Grand Bourg", "Evry"], ["Viry-Châtillon"]); 24 | 25 | // graph.add(["Corbeil-Essonnes"], ["Evry", "Le Bras de Fer"]); 26 | 27 | // graph.add(["A", "B", "C", "E", "G"]); 28 | // graph.add(["A", "B", "D", "E", "G"]); 29 | // graph.add(["A", "F", "G"]); 30 | 31 | graph.add(["A", "B", "D"]); 32 | graph.add(["A", "C", "D"]); 33 | 34 | export { graph }; 35 | -------------------------------------------------------------------------------- /src/app/Graph.ts: -------------------------------------------------------------------------------- 1 | export class Graph { 2 | private adjacencyList: Map>; 3 | private nodeMap: Map = new Map(); 4 | private initialPaths: T[][] = []; 5 | 6 | constructor(private identity: (x: T) => string) { 7 | this.adjacencyList = new Map(); 8 | this.nodeMap = new Map(); 9 | } 10 | 11 | /** 12 | * Adds nodes to the graph, connecting each node in the list to the next as its successor. 13 | * @param nodes - The ordered list of nodes to add, where each is the predecessor of the next. 14 | * @param predecessors - Additional predecessors to connect to the first node in the list. 15 | */ 16 | add(nodes: T[], predecessors: T[] = []): void { 17 | if (nodes.length === 0) return; 18 | 19 | this.initialPaths.push(nodes); 20 | 21 | // Ensure all nodes in the list are added and connected sequentially 22 | for (let i = 0; i < nodes.length; i++) { 23 | const currentId = this.identity(nodes[i]); 24 | this.nodeMap.set(currentId, nodes[i]); 25 | 26 | // Ensure the current node exists in the adjacency list 27 | if (!this.adjacencyList.has(currentId)) { 28 | this.adjacencyList.set(currentId, new Set()); 29 | } 30 | 31 | // If there’s a next node in the list, connect the current node to it 32 | if (i < nodes.length - 1) { 33 | const nextId = this.identity(nodes[i + 1]); 34 | if (!this.adjacencyList.has(nextId)) { 35 | this.adjacencyList.set(nextId, new Set()); 36 | } 37 | this.adjacencyList.get(currentId)?.add(nextId); 38 | } 39 | } 40 | 41 | // Connect all additional predecessors to the first node in the list 42 | const firstNodeId = this.identity(nodes[0]); 43 | predecessors.forEach((pred) => { 44 | const predId = this.identity(pred); 45 | if (!this.adjacencyList.has(predId)) { 46 | this.adjacencyList.set(predId, new Set()); 47 | } 48 | this.adjacencyList.get(predId)?.add(firstNodeId); 49 | }); 50 | } 51 | 52 | paths(from: T): string[][] { 53 | const fromId = this.identity(from); 54 | const paths: string[][] = []; 55 | const visited = new Set(); 56 | 57 | const dfs = (current: string, path: string[]) => { 58 | visited.add(current); 59 | path.push(current); 60 | 61 | const successors = this.adjacencyList.get(current) || new Set(); 62 | 63 | // If the current node has no children, add the current path to paths 64 | if (successors.size === 0) { 65 | paths.push([...path]); 66 | } else { 67 | // Continue to search for successors 68 | successors.forEach((neighbor) => { 69 | if (!visited.has(neighbor)) { 70 | dfs(neighbor, path); 71 | } 72 | }); 73 | } 74 | 75 | // Backtrack 76 | path.pop(); 77 | visited.delete(current); 78 | }; 79 | 80 | // Start DFS from the 'from' node 81 | dfs(fromId, []); 82 | 83 | return paths; 84 | } 85 | 86 | groupedTopologicalSort(): T[][] { 87 | const inDegrees = new Map(); 88 | const groups: T[][] = []; 89 | 90 | // Initialize in-degrees for all nodes 91 | this.adjacencyList.forEach((_, node) => { 92 | inDegrees.set(node, 0); 93 | }); 94 | 95 | // Calculate in-degrees 96 | this.adjacencyList.forEach((neighbors, _) => { 97 | neighbors.forEach((neighbor) => { 98 | inDegrees.set(neighbor, (inDegrees.get(neighbor) || 0) + 1); 99 | }); 100 | }); 101 | 102 | // Process nodes with in-degree 0 103 | const queue: string[] = []; 104 | inDegrees.forEach((degree, node) => { 105 | if (degree === 0) { 106 | queue.push(node); 107 | } 108 | }); 109 | 110 | // BFS-like processing to group nodes 111 | while (queue.length > 0) { 112 | const group: T[] = []; 113 | const nextQueue: string[] = []; 114 | 115 | // Process all nodes in the current queue 116 | queue.forEach((node) => { 117 | const originalNode = this.nodeMap.get(node); // Retrieve the original object 118 | if (originalNode) { 119 | group.push(originalNode); 120 | } 121 | 122 | // Reduce in-degree of all neighbors 123 | this.adjacencyList.get(node)?.forEach((neighbor) => { 124 | const newDegree = (inDegrees.get(neighbor) || 0) - 1; 125 | inDegrees.set(neighbor, newDegree); 126 | 127 | // If in-degree becomes 0, add to the next queue 128 | if (newDegree === 0) { 129 | nextQueue.push(neighbor); 130 | } 131 | }); 132 | }); 133 | 134 | // Add the current group to the result 135 | groups.push(group); 136 | 137 | // Move to the next level 138 | queue.splice(0, queue.length, ...nextQueue); 139 | } 140 | 141 | return groups; 142 | } 143 | 144 | private predecessors(node: string): string[] { 145 | const preds: string[] = []; 146 | this.adjacencyList.forEach((neighbors, neighbor) => { 147 | if (neighbors.has(node)) { 148 | preds.push(neighbor); 149 | } 150 | }); 151 | return preds; 152 | } 153 | 154 | private successors(node: string): string[] { 155 | const succs: string[] = []; 156 | const neighbors = this.adjacencyList.get(node); 157 | if (neighbors) { 158 | neighbors.forEach((neighbor) => { 159 | succs.push(neighbor); 160 | }); 161 | } 162 | return succs; 163 | } 164 | 165 | get commonPaths(): Set { 166 | const result = new Set(); // Stocke les sous-chemins uniques 167 | 168 | for (const path of this.initialPaths.sort((a, b) => b.length - a.length)) { 169 | const subPaths: T[][] = []; 170 | let subPath: T[] = []; 171 | 172 | for (const node of path) { 173 | const nodeId = this.identity(node); 174 | const preds = this.predecessors(nodeId); 175 | const succs = this.successors(nodeId); 176 | 177 | if (preds.length > 1) { 178 | // Si le nœud a plusieurs prédécesseurs, on ferme le sous-chemin actuel 179 | if (subPath.length > 0) { 180 | subPaths.push(subPath); 181 | } 182 | // Démarre un nouveau sous-chemin incluant ce nœud 183 | subPath = [node]; 184 | } else { 185 | subPath.push(node); 186 | } 187 | 188 | if (succs.length > 1) { 189 | // Si le nœud a plusieurs successeurs, on ferme le sous-chemin actuel après l'avoir ajouté 190 | subPaths.push(subPath); 191 | subPath = []; 192 | } 193 | } 194 | 195 | // Ajoute le dernier sous-chemin s'il contient encore des éléments 196 | if (subPath.length > 0) { 197 | subPaths.push(subPath); 198 | } 199 | 200 | for (const subPath of subPaths) { 201 | if ( 202 | ![...result].some((existingPath) => 203 | this.arraysEqual(existingPath, subPath) 204 | ) 205 | ) { 206 | result.add(subPath); 207 | } 208 | } 209 | } 210 | 211 | return result; 212 | } 213 | 214 | /** 215 | * Vérifie si deux tableaux sont égaux en contenu et en ordre. 216 | */ 217 | private arraysEqual(arr1: T[], arr2: T[]): boolean { 218 | if (arr1.length !== arr2.length) return false; 219 | return arr1.every( 220 | (element, index) => this.identity(element) === this.identity(arr2[index]) 221 | ); 222 | } 223 | 224 | get groupedTopologicalPaths(): T[][][] { 225 | const topoSort = this.groupedTopologicalSort(); // Récupère le tri topologique normal 226 | const paths = this.commonPaths; // Récupère les chemins communs 227 | const result: T[][][] = []; // Liste des niveaux du tri topo 228 | const processed = new Set(); // Garde en mémoire les éléments déjà ajoutés 229 | 230 | for (const level of topoSort) { 231 | const newLevel: T[][] = []; 232 | 233 | for (const node of level) { 234 | const nodeId = this.identity(node); 235 | 236 | if (processed.has(nodeId)) continue; // On ignore les éléments déjà placés 237 | 238 | // Cherche un chemin contenant ce noeud 239 | const path = [...paths].find((p) => 240 | p.some((n) => this.identity(n) === nodeId) 241 | ); 242 | 243 | if (path) { 244 | newLevel.push(path); // Ajoute le chemin entier au niveau du tri topo 245 | path.forEach((n) => processed.add(this.identity(n))); // Marque tous les éléments du chemin 246 | } else { 247 | newLevel.push([node]); // Ajoute l'élément seul 248 | processed.add(nodeId); 249 | } 250 | } 251 | 252 | if (newLevel.length > 0) { 253 | result.push(newLevel); // On n'ajoute que si le niveau contient des éléments 254 | } 255 | } 256 | 257 | return result; 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /src/app/utils.ts: -------------------------------------------------------------------------------- 1 | export function firstUnique( 2 | count: number, 3 | identity: (x: T) => string, 4 | items: T[] 5 | ): T[] { 6 | const frequency = new Map(); 7 | const unique: T[] = []; 8 | 9 | for (const item of items) { 10 | const id = identity(item); 11 | const current = frequency.get(id) || 0; 12 | 13 | if (current === 0) { 14 | unique.push(item); 15 | } 16 | 17 | frequency.set(id, current + 1); 18 | } 19 | 20 | return unique.slice(0, count); 21 | } 22 | -------------------------------------------------------------------------------- /src/assets/vue.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/colors.ts: -------------------------------------------------------------------------------- 1 | export const Colors = { 2 | hexToRgb: function (hex: string) { 3 | const match = hex.match(/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i); 4 | if (!match) { 5 | throw new Error(`Invalid color: ${hex}`); 6 | } 7 | return { 8 | r: parseInt(match[1], 16), 9 | g: parseInt(match[2], 16), 10 | b: parseInt(match[3], 16), 11 | }; 12 | }, 13 | 14 | rgbToHex: function (rgb: { r: number; g: number; b: number }) { 15 | return ( 16 | "#" + 17 | [rgb.r, rgb.g, rgb.b] 18 | .map((x) => { 19 | const hex = x.toString(16); 20 | return hex.length === 1 ? "0" + hex : hex; 21 | }) 22 | .join("") 23 | ); 24 | }, 25 | 26 | mix: function (color: string, _with: string, by: number) { 27 | const c = this.hexToRgb(color); 28 | const w = this.hexToRgb(_with); 29 | return this.rgbToHex({ 30 | r: Math.round(c.r + (w.r - c.r) * by), 31 | g: Math.round(c.g + (w.g - c.g) * by), 32 | b: Math.round(c.b + (w.b - c.b) * by), 33 | }); 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /src/components/AnimatedPath.vue: -------------------------------------------------------------------------------- 1 | 89 | 90 | 167 | 168 | 219 | -------------------------------------------------------------------------------- /src/components/Header.vue: -------------------------------------------------------------------------------- 1 | 57 | 58 | 96 | 97 | 238 | -------------------------------------------------------------------------------- /src/components/OfflineHeader.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 44 | -------------------------------------------------------------------------------- /src/components/RepairScreen.vue: -------------------------------------------------------------------------------- 1 | 49 | 50 | 64 | -------------------------------------------------------------------------------- /src/components/ShortTrainBubble.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 136 | 137 | 434 | -------------------------------------------------------------------------------- /src/components/StopName.vue: -------------------------------------------------------------------------------- 1 | 73 | 74 | 106 | 107 | 155 | -------------------------------------------------------------------------------- /src/components/Stops.vue: -------------------------------------------------------------------------------- 1 | 188 | 189 | 271 | 272 | 410 | -------------------------------------------------------------------------------- /src/components/Time.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 17 | 18 | 29 | -------------------------------------------------------------------------------- /src/fetch.ts: -------------------------------------------------------------------------------- 1 | import dayjs from "dayjs"; 2 | import { firstUnique } from "./app/utils"; 3 | import { nearest, type Point } from "./geo"; 4 | import { 5 | Wagon, 6 | type SimpleDeparture, 7 | type SimpleJourney, 8 | } from "./services/Wagon"; 9 | 10 | // function randomize(array: T[]): T[] { 11 | // return array.sort(() => Math.random() - 0.5); 12 | // } 13 | 14 | export async function nextTrainJourneys( 15 | currentStopId: string, 16 | lineId: string, 17 | terminusPosition: Point | undefined, 18 | previousJourneys: SimpleJourney[] 19 | ): Promise { 20 | const departures = await Wagon.departures(lineId, [currentStopId]); 21 | 22 | const nearestTerminusBranchHash = 23 | terminusPosition && 24 | nearest(terminusPosition, departures, (d) => ({ 25 | lat: d.destination.averagePosition.lat, 26 | lon: d.destination.averagePosition.long, 27 | }))?.branchHash; 28 | 29 | const first = firstUnique( 30 | 4, 31 | (x) => x.journeyCode?.slice(0, 2) || x.destination.name, 32 | departures.filter((x) => 33 | nearestTerminusBranchHash 34 | ? x.branchHash === nearestTerminusBranchHash 35 | : true 36 | ) 37 | ); 38 | 39 | async function getJourney(departure: SimpleDeparture) { 40 | const needToRefreshFirstJourney = dayjs().minute() % 5 === 0; 41 | const isFirstJourney = first.at(0)?.id === departure.id; 42 | 43 | if (isFirstJourney && needToRefreshFirstJourney) { 44 | return Wagon.journey(departure.id); 45 | } 46 | 47 | return ( 48 | previousJourneys.find((x) => x.id === departure.id) || 49 | Wagon.journey(departure.id) 50 | ); 51 | } 52 | 53 | const result = []; 54 | 55 | for (const [i, departure] of first.entries()) { 56 | const journey = await getJourney(departure); 57 | const indexOfOrigin = journey.stops.findIndex( 58 | (x) => x.id === currentStopId 59 | ); 60 | const stopsFromOrigin = journey.stops.slice( 61 | indexOfOrigin <= 0 || i === 0 ? indexOfOrigin : indexOfOrigin - 1 62 | ); 63 | console.log(stopsFromOrigin.map((x) => x.name).join(" -> ")); 64 | result.push({ 65 | userStopDeparture: departure, 66 | ...journey, 67 | stops: stopsFromOrigin, 68 | }); 69 | } 70 | 71 | return result; 72 | } 73 | -------------------------------------------------------------------------------- /src/geo.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from "vitest"; 2 | import { isInParis } from "./geo"; 3 | 4 | const LA_DEFENSE = { 5 | id: "stop_area:IDFM:71517", 6 | name: "La Défense", 7 | position: { 8 | lat: 48.89223, 9 | long: 2.238419, 10 | }, 11 | lines: [], 12 | }; 13 | 14 | const CDG_ETOILE = { 15 | id: "stop_area:IDFM:71347", 16 | name: "Charles de Gaulle - Etoile", 17 | position: { 18 | lat: 48.874173, 19 | long: 2.295231, 20 | }, 21 | lines: [], 22 | }; 23 | 24 | const AUBER = { 25 | id: "stop_area:IDFM:478926", 26 | name: "Auber", 27 | position: { 28 | lat: 48.872349, 29 | long: 2.329659, 30 | }, 31 | lines: [], 32 | }; 33 | 34 | const NATION = { 35 | id: "stop_area:IDFM:71673", 36 | name: "Nation", 37 | position: { 38 | lat: 48.848233, 39 | long: 2.395944, 40 | }, 41 | lines: [], 42 | }; 43 | 44 | const VINCENNES = { 45 | id: "stop_area:IDFM:71651", 46 | name: "Vincennes", 47 | position: { 48 | lat: 48.847347, 49 | long: 2.432584, 50 | }, 51 | lines: [], 52 | }; 53 | 54 | describe("Is in Paris ?", () => { 55 | it("should return true for all stops in Paris", () => { 56 | expect(isInParis(CDG_ETOILE)).toBe(true); 57 | expect(isInParis(AUBER)).toBe(true); 58 | expect(isInParis(NATION)).toBe(true); 59 | }); 60 | 61 | it("should return false for all stops outside Paris", () => { 62 | expect(isInParis(LA_DEFENSE)).toBe(false); 63 | expect(isInParis(VINCENNES)).toBe(false); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /src/geo.ts: -------------------------------------------------------------------------------- 1 | import type { SimpleStop } from "./services/Wagon"; 2 | 3 | export type Point = { lat: number; lon: number }; 4 | 5 | function isInside(p: Point, polygon: Point[]): boolean { 6 | let isInside = false; 7 | for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) { 8 | const xi = polygon[i].lat; 9 | const yi = polygon[i].lon; 10 | const xj = polygon[j].lat; 11 | const yj = polygon[j].lon; 12 | const intersect = 13 | yi > p.lon !== yj > p.lon && 14 | p.lat < ((xj - xi) * (p.lon - yi)) / (yj - yi) + xi; 15 | if (intersect) { 16 | isInside = !isInside; 17 | } 18 | } 19 | return isInside; 20 | } 21 | 22 | export function isInParis(stop: SimpleStop): boolean { 23 | const PARIS = [ 24 | [2.2797157023652233, 48.87851114803195], 25 | [2.260758439431669, 48.85943705776893], 26 | [2.250725603630201, 48.851771232014215], 27 | [2.252587203777324, 48.83892471441001], 28 | [2.256918652080401, 48.83446204263046], 29 | [2.271550968899362, 48.83445914285036], 30 | [2.291114621872964, 48.82663535815962], 31 | [2.334447983719997, 48.815784140139016], 32 | [2.345287926634228, 48.81575541284957], 33 | [2.3642418596329833, 48.81522793302469], 34 | [2.4130302040167066, 48.835187986521504], 35 | [2.416281192355882, 48.84998854577634], 36 | [2.4141860657588268, 48.87062868036486], 37 | [2.408755907552944, 48.87933336904129], 38 | [2.397272521667105, 48.88614948295714], 39 | [2.3932591798833585, 48.899713403700105], 40 | [2.379991770128811, 48.9013263118716], 41 | [2.330120794975727, 48.90204326235818], 42 | [2.315209600628549, 48.898845542250285], 43 | [2.2875653501001807, 48.886359489323866], 44 | [2.2797157023652233, 48.87851114803195], 45 | ]; 46 | 47 | return isInside( 48 | { lat: stop.position.lat, lon: stop.position.long }, 49 | PARIS.map(([lon, lat]) => ({ lat, lon })) 50 | ); 51 | } 52 | 53 | function radians(degree: number) { 54 | let rad: number = (degree * Math.PI) / 180; 55 | 56 | return rad; 57 | } 58 | 59 | function haversine(lat1: number, lon1: number, lat2: number, lon2: number) { 60 | let dlat, dlon, a, c, R: number; 61 | 62 | R = 6372.8; // km 63 | dlat = radians(lat2 - lat1); 64 | dlon = radians(lon2 - lon1); 65 | lat1 = radians(lat1); 66 | lat2 = radians(lat2); 67 | a = 68 | Math.sin(dlat / 2) * Math.sin(dlat / 2) + 69 | Math.sin(dlon / 2) * Math.sin(dlon / 2) * Math.cos(lat1) * Math.cos(lat2); 70 | c = 2 * Math.asin(Math.sqrt(a)); 71 | return R * c; 72 | } 73 | 74 | export function nearest( 75 | from: Point, 76 | elements: T[], 77 | getPosition: (x: T) => Point 78 | ) { 79 | let nearestElement: T | null = null; 80 | let nearestDistance = Infinity; 81 | 82 | for (const element of elements) { 83 | const position = getPosition(element); 84 | const distance = haversine(from.lat, from.lon, position.lat, position.lon); 85 | 86 | if (distance < nearestDistance) { 87 | nearestElement = element; 88 | nearestDistance = distance; 89 | } 90 | } 91 | 92 | return nearestElement; 93 | } 94 | -------------------------------------------------------------------------------- /src/layout.ts: -------------------------------------------------------------------------------- 1 | export function getFixedPosition(element: HTMLElement) { 2 | const rect = element.getBoundingClientRect(); 3 | return { 4 | x: rect.left + window.scrollX, 5 | y: rect.top + window.scrollY, 6 | }; 7 | } 8 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import './style.css' 3 | import App from './App.vue' 4 | 5 | createApp(App).mount('#app') 6 | -------------------------------------------------------------------------------- /src/services/Wagon.ts: -------------------------------------------------------------------------------- 1 | import dayjs, { Dayjs } from "dayjs"; 2 | 3 | export interface SimpleLine { 4 | id: string; 5 | number: string; 6 | backgroundColor: string; 7 | textColor: string; 8 | pictoSvg?: string; 9 | numberShapeSvg?: string; 10 | importance: number; 11 | } 12 | 13 | export interface SimpleStop { 14 | id: string; 15 | name: string; 16 | position: Position; 17 | lines: SimpleLine[]; 18 | } 19 | 20 | export interface Position { 21 | lat: number; 22 | long: number; 23 | } 24 | 25 | export interface SimpleDeparture { 26 | destination: { 27 | name: string; 28 | averagePosition: Position; 29 | }; 30 | leavesAt: Dayjs; 31 | arrivesAt: Dayjs; 32 | id: string; 33 | branchHash?: string; 34 | journeyCode?: string; 35 | vehicleLength?: "SHORT" | "LONG"; 36 | } 37 | 38 | export type SimpleJourney = { 39 | id: string; 40 | line: SimpleLine; 41 | userStopDeparture: SimpleDeparture; 42 | stops: SimpleStop[]; 43 | closedStops: Set; 44 | skippedStops: Set; 45 | }; 46 | 47 | function processSVG(svg: string): string { 48 | return svg 49 | .replace(/width="[^"]+"/, "") 50 | .replace(/height="[^"]+"/, `height="100%"`) 51 | .replace('font-family="', 'font-weight="bold" font-family="Parisine, '); 52 | } 53 | 54 | export class Wagon { 55 | private static BASE_URL = "https://api-wagon.arno.cl/gantry/"; 56 | 57 | private static get baseUrl(): string { 58 | return Wagon.BASE_URL; 59 | } 60 | 61 | private static get apiKey(): string { 62 | return "spad"; 63 | } 64 | 65 | private static positionFromDTO(positionDto: any): Position { 66 | return { 67 | lat: positionDto[0], 68 | long: positionDto[1], 69 | }; 70 | } 71 | 72 | private static stopFromDTO(stopDto: any, lines: SimpleLine[]): SimpleStop { 73 | const linesIds = Array.from( 74 | new Set( 75 | stopDto.stops 76 | .map((stop: any) => stop.lines.map((line: any) => line)) 77 | .flat() 78 | ) 79 | ); 80 | return { 81 | id: stopDto.id, 82 | name: stopDto.name, 83 | position: this.positionFromDTO(stopDto.averagePosition), 84 | lines: lines.filter((line) => linesIds.includes(line.id)), 85 | }; 86 | } 87 | 88 | private static lineFromDTO(lineDto: any): SimpleLine { 89 | return { 90 | id: lineDto.id, 91 | number: lineDto.number, 92 | backgroundColor: lineDto.backgroundColor, 93 | textColor: lineDto.textColor, 94 | pictoSvg: processSVG(lineDto.modeSvg ?? ""), 95 | numberShapeSvg: processSVG(lineDto.numberShapeSvg ?? ""), 96 | importance: lineDto.importance, 97 | }; 98 | } 99 | 100 | public static async searchStops(search: string): Promise { 101 | const params = new URLSearchParams(); 102 | params.append("action", "searchStops"); 103 | params.append("coordinates", "48.86,2.34"); 104 | params.append("compatibilityDate", "2024-03-30"); 105 | params.append("apiKey", this.apiKey); 106 | params.append("q", search); 107 | 108 | const response = await fetch(`${this.baseUrl}?${params.toString()}`); 109 | 110 | if (!response.ok) { 111 | throw new Error("Failed to search stations"); 112 | } 113 | 114 | const json = await response.json(); 115 | 116 | const lines: SimpleLine[] = json.data.lines.map((line: any) => { 117 | return this.lineFromDTO(line); 118 | }); 119 | 120 | const stops: SimpleStop[] = json.data.stops.map((stop: any) => { 121 | return this.stopFromDTO(stop, lines); 122 | }); 123 | 124 | return stops; 125 | } 126 | 127 | public static async departures( 128 | lineId: string, 129 | stopIds: string[], 130 | /** @deprecated */ 131 | isTerminus: boolean = false 132 | ): Promise { 133 | let params = new URLSearchParams(); 134 | 135 | params.append("action", "departures"); 136 | params.append("coordinates", "48.8,2.3"); 137 | params.append("line", lineId); 138 | params.append( 139 | "stops", 140 | "[" + stopIds.map((id) => `"${id}"`).join(",") + "]" 141 | ); 142 | params.append("isTerminus", isTerminus ? "1" : "0"); 143 | params.append("compatibilityDate", "2025-01-21"); 144 | params.append("apiKey", this.apiKey); 145 | 146 | const response = await fetch(`${this.baseUrl}?${params.toString()}`); 147 | 148 | if (!response.ok) { 149 | throw new Error("Failed to fetch departures"); 150 | } 151 | 152 | const json = await response.json(); 153 | 154 | return json.data.departures 155 | .map((departure: any) => { 156 | return { 157 | destination: { 158 | name: departure.destinationLabel, 159 | averagePosition: this.positionFromDTO( 160 | departure.destination.averagePosition 161 | ), 162 | }, 163 | leavesAt: dayjs( 164 | departure.departure.realTime || 165 | departure.departure.theoretical || 166 | "invalid" 167 | ), 168 | arrivesAt: dayjs( 169 | departure.arrival.realTime || 170 | departure.arrival.theoretical || 171 | "invalid" 172 | ), 173 | id: departure.journeyId, 174 | branchHash: departure.branchHash, 175 | journeyCode: departure.journeyCode, 176 | vehicleLength: departure.vehicleLength, 177 | }; 178 | }) 179 | .filter( 180 | (departure: SimpleDeparture) => 181 | departure.leavesAt.isValid() && departure.leavesAt.isAfter(dayjs()) 182 | ); 183 | } 184 | 185 | public static async journey(journeyId: string): Promise<{ 186 | id: string; 187 | stops: SimpleStop[]; 188 | line: SimpleLine; 189 | closedStops: Set; 190 | skippedStops: Set; 191 | }> { 192 | let params = new URLSearchParams(); 193 | 194 | params.append("action", "journey"); 195 | params.append("coordinates", "48.8,2.3"); 196 | params.append("journeyId", journeyId); 197 | params.append("compatibilityDate", "2025-01-21"); 198 | params.append("apiKey", this.apiKey); 199 | 200 | const response = await fetch(`${this.baseUrl}?${params.toString()}`); 201 | 202 | if (!response.ok) { 203 | throw new Error("Failed to fetch journey"); 204 | } 205 | 206 | const json = await response.json(); 207 | 208 | const stops = json.data.stops.map((stop: any) => { 209 | return this.stopFromDTO(stop.stop, []); 210 | }); 211 | 212 | const line = this.lineFromDTO(json.data.line); 213 | 214 | return { 215 | id: journeyId, 216 | stops, 217 | line, 218 | closedStops: new Set( 219 | json.data.stops 220 | .filter((x: any) => x.isClosed) 221 | .map((x: any) => x.stop.id) 222 | ), 223 | skippedStops: new Set( 224 | json.data.stops 225 | .filter((x: any) => x.isSkipped) 226 | .map((x: any) => x.stop.id) 227 | ), 228 | }; 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /src/style.css: -------------------------------------------------------------------------------- 1 | body, 2 | html { 3 | margin: 0; 4 | padding: 0; 5 | overflow: hidden; 6 | } 7 | 8 | body { 9 | background-color: #F3EDEA; 10 | font-family: Parisine, Arial, Helvetica, sans-serif; 11 | } 12 | 13 | @font-face { 14 | font-family: 'Parisine'; 15 | font-weight: bold; 16 | src: url('/Parisine-Bold.otf'); 17 | } 18 | 19 | @font-face { 20 | font-family: 'Parisine'; 21 | font-weight: normal; 22 | src: url('/Parisine-Regular.otf'); 23 | } 24 | 25 | :root { 26 | --title-color: #1C55B1; 27 | --time-color: #FFBE00; 28 | --warning-color: rgb(255, 205, 0); 29 | --gray: #565759; 30 | } -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.dom.json", 3 | "compilerOptions": { 4 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 5 | "target": "es2022", 6 | "lib": [ 7 | "es2022", 8 | "dom" 9 | ], 10 | /* Linting */ 11 | "strict": true, 12 | "noUnusedLocals": true, 13 | "noUnusedParameters": true, 14 | "noFallthroughCasesInSwitch": true, 15 | "noUncheckedSideEffectImports": true 16 | }, 17 | "include": [ 18 | "src/**/*.ts", 19 | "src/**/*.tsx", 20 | "src/**/*.vue" 21 | ] 22 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { "path": "./tsconfig.app.json" }, 5 | { "path": "./tsconfig.node.json" } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 4 | "target": "ES2022", 5 | "lib": ["ES2023"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "isolatedModules": true, 13 | "moduleDetection": "force", 14 | "noEmit": true, 15 | 16 | /* Linting */ 17 | "strict": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "noFallthroughCasesInSwitch": true, 21 | "noUncheckedSideEffectImports": true 22 | }, 23 | "include": ["vite.config.ts"] 24 | } 25 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | 4 | // https://vite.dev/config/ 5 | export default defineConfig({ 6 | plugins: [vue()], 7 | }) 8 | --------------------------------------------------------------------------------