@jsenv/server 16.1.1 → 16.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -55,6 +55,10 @@
55
55
  color: dodgerblue;
56
56
  }
57
57
 
58
+ html {
59
+ scrollbar-gutter: stable both-edges;
60
+ }
61
+
58
62
  body {
59
63
  font-family:
60
64
  system-ui,
@@ -81,32 +85,35 @@
81
85
  aside {
82
86
  width: 250px;
83
87
  flex-shrink: 0;
84
- border-radius: 6px;
85
- overflow: hidden;
86
- background: var(--background-color);
87
88
  transition:
88
89
  width 0.3s ease,
89
90
  margin-left 0.3s ease;
90
- max-height: calc(100vh - 40px);
91
- overflow-y: auto;
91
+ height: 100%;
92
92
  margin-right: 20px;
93
- padding-top: 20px;
94
- padding-left: 10px;
93
+ position: fixed;
95
94
  }
96
95
  main {
97
96
  flex-grow: 1;
98
97
  flex-basis: 0;
99
98
  min-width: 0;
100
99
  transition: margin-left 0.3s ease;
101
- padding-top: 10px;
100
+ padding-top: 20px;
102
101
  padding-right: 20px;
102
+ margin-left: 280px;
103
103
  }
104
104
 
105
105
  .header_row {
106
106
  display: flex;
107
- align-items: center;
107
+ align-items: flex-start;
108
108
  gap: 10px;
109
109
  }
110
+ .header_title {
111
+ margin: 0;
112
+ line-height: 1;
113
+ display: flex;
114
+ gap: 10px;
115
+ align-items: center;
116
+ }
110
117
  .note_button_open {
111
118
  background: none;
112
119
  border: none;
@@ -334,11 +341,58 @@
334
341
  }
335
342
  }
336
343
 
337
- .route_list {
344
+ .route_container > * {
338
345
  margin-top: 20px;
339
346
  flex-grow: 1;
340
347
  min-width: 0; /* Prevent flexbox from expanding beyond available space */
341
348
  }
349
+ .route_group_details {
350
+ margin: 0 0 15px;
351
+ }
352
+ .summary_marker {
353
+ width: 24px;
354
+ height: 24px;
355
+ }
356
+ .route_group_details > summary {
357
+ cursor: pointer;
358
+ list-style: none;
359
+ border-bottom: 1px solid var(--border-color);
360
+ margin-bottom: 7px;
361
+ }
362
+ .route_group_details > summary::marker {
363
+ display: none;
364
+ }
365
+ .route_group_details[open] > summary .summary_marker {
366
+ transform: rotate(180deg);
367
+ }
368
+ .route_group_summary_container {
369
+ display: flex;
370
+ align-items: center;
371
+ padding: 5px;
372
+ }
373
+ .route_group_name {
374
+ display: flex;
375
+ border-radius: 3px;
376
+ font-family: sans-serif;
377
+ font-size: 14px;
378
+ font-weight: 700;
379
+ min-width: 80px;
380
+ padding: 6px 0;
381
+ text-align: center;
382
+ text-shadow: 0 1px 0 rgba(0, 0, 0, 0.1);
383
+ gap: 0.3em;
384
+ }
385
+ .route_group_description {
386
+ margin-left: 8px;
387
+ font-family: monospace;
388
+ font-size: 16px;
389
+ font-weight: 600;
390
+ word-break: break-word;
391
+ margin-top: 0;
392
+ margin-bottom: 0;
393
+ user-select: none;
394
+ }
395
+
342
396
  .route_details {
343
397
  margin-bottom: 8px;
344
398
  box-shadow: 0 0 3px rgba(0, 0, 0, 0.19);
@@ -355,6 +409,9 @@
355
409
  .route_details > summary::marker {
356
410
  display: none;
357
411
  }
412
+ .route_details:open > summary .summary_marker {
413
+ transform: rotate(180deg);
414
+ }
358
415
  .route_summary_container {
359
416
  display: flex;
360
417
  align-items: center;
@@ -381,25 +438,32 @@
381
438
  margin-top: 0;
382
439
  margin-bottom: 0;
383
440
  }
384
- .route_details[data-method="HEAD"] {
441
+ .route_details[data-method="HEAD"],
442
+ .route_group_details[data-method="HEAD"] {
385
443
  --method-color: var(--method-head-color);
386
444
  }
387
- .route_details[data-method="OPTIONS"] {
445
+ .route_details[data-method="OPTIONS"],
446
+ .route_group_details[data-method="OPTIONS"] {
388
447
  --method-color: var(--method-options-color);
389
448
  }
390
- .route_details[data-method="GET"] {
449
+ .route_details[data-method="GET"],
450
+ .route_group_details[data-method="GET"] {
391
451
  --method-color: var(--method-get-color);
392
452
  }
393
- .route_details[data-method="POST"] {
453
+ .route_details[data-method="POST"],
454
+ .route_group_details[data-method="POST"] {
394
455
  --method-color: var(--method-post-color);
395
456
  }
396
- .route_details[data-method="PUT"] {
457
+ .route_details[data-method="PUT"],
458
+ .route_group_details[data-method="PUT"] {
397
459
  --method-color: var(--method-put-color);
398
460
  }
399
- .route_details[data-method="PATCH"] {
461
+ .route_details[data-method="PATCH"],
462
+ .route_group_details[data-method="PATCH"] {
400
463
  --method-color: var(--method-patch-color);
401
464
  }
402
- .route_details[data-method="DELETE"] {
465
+ .route_details[data-method="DELETE"],
466
+ .route_group_details[data-method="DELETE"] {
403
467
  --method-color: var(--method-delete-color);
404
468
  }
405
469
 
@@ -428,10 +492,11 @@
428
492
 
429
493
  .filter_container {
430
494
  margin-bottom: 20px;
431
- border-radius: 4px;
432
495
  overflow: hidden;
433
- border: 1px solid var(--border-color);
496
+ overflow-y: auto;
497
+ border-right: 1px solid var(--border-color);
434
498
  background: var(--secondary-color);
499
+ height: 100%;
435
500
  }
436
501
  .filter_container::marker {
437
502
  display: none;
@@ -445,16 +510,23 @@
445
510
  justify-content: space-between;
446
511
  user-select: none;
447
512
  }
448
- .filter:open .arrow_down {
513
+ .filter:open > summary .summary_marker {
449
514
  transform: rotate(180deg);
450
515
  }
516
+ .summary_text {
517
+ overflow: hidden;
518
+ width: 100%;
519
+ display: block;
520
+ white-space: nowrap;
521
+ text-overflow: ellipsis;
522
+ }
451
523
  .filter_list {
452
524
  background: var(--background-color);
453
- border-top: 1px solid var(--border-color);
454
525
  overflow: hidden;
455
526
  }
456
527
  .filter_item {
457
528
  margin: 15px;
529
+ margin-left: 0;
458
530
  }
459
531
  .filter_badge {
460
532
  background-color: var(--primary-color);
@@ -506,7 +578,6 @@
506
578
  overflow: hidden;
507
579
  }
508
580
  .filter_text[data-method] {
509
- border-left: 5px solid var(--method-color);
510
581
  padding-left: 4px;
511
582
  }
512
583
  .filter_text[data-method="all"] {
@@ -566,33 +637,41 @@
566
637
  transparent
567
638
  );
568
639
  }
640
+
641
+ .status_pill {
642
+ display: flex;
643
+ align-self: stretch;
644
+ width: 0.4em;
645
+ }
569
646
  </style>
570
647
  <div class="container">
571
648
  <aside></aside>
572
649
  <main>
573
650
  <div class="header_row">
574
- <h1 style="margin: 0">Route inspector</h1>
575
- <button
576
- class="note_button_open"
577
- aria-label="Show information"
578
- title="About HTTP Methods"
579
- >
580
- <svg
581
- xmlns="http://www.w3.org/2000/svg"
582
- viewBox="0 0 24 24"
583
- width="24"
584
- height="24"
585
- fill="none"
586
- stroke="currentColor"
587
- stroke-width="2"
588
- stroke-linecap="round"
589
- stroke-linejoin="round"
651
+ <h1 class="header_title">
652
+ Route inspector
653
+ <button
654
+ class="note_button_open"
655
+ aria-label="Show information"
656
+ title="About HTTP Methods"
590
657
  >
591
- <circle cx="12" cy="12" r="10"></circle>
592
- <line x1="12" y1="16" x2="12" y2="12"></line>
593
- <line x1="12" y1="8" x2="12.01" y2="8"></line>
594
- </svg>
595
- </button>
658
+ <svg
659
+ xmlns="http://www.w3.org/2000/svg"
660
+ viewBox="0 0 24 24"
661
+ width="24"
662
+ height="24"
663
+ fill="none"
664
+ stroke="currentColor"
665
+ stroke-width="2"
666
+ stroke-linecap="round"
667
+ stroke-linejoin="round"
668
+ >
669
+ <circle cx="12" cy="12" r="10"></circle>
670
+ <line x1="12" y1="16" x2="12" y2="12"></line>
671
+ <line x1="12" y1="8" x2="12.01" y2="8"></line>
672
+ </svg>
673
+ </button>
674
+ </h1>
596
675
  <div style="flex: 1"></div>
597
676
  <div class="theme-toggle">
598
677
  <button id="theme-toggle-btn" aria-label="Toggle dark/light mode">
@@ -700,7 +779,7 @@
700
779
  import { render } from "preact";
701
780
  import { html } from "htm/preact";
702
781
  import { signal, computed } from "@preact/signals";
703
- import { useState, useRef, useCallback } from "preact/hooks";
782
+ import { useRef, useCallback } from "preact/hooks";
704
783
 
705
784
  const routesLoadingSignal = signal(false);
706
785
  const routeLoadingErrorSignal = signal(null);
@@ -763,11 +842,18 @@
763
842
  if (routes.length === 0) {
764
843
  return null;
765
844
  }
766
- return html` <${FiltersPanel} routes=${routes} /> `;
845
+ return html`
846
+ <div>
847
+ <${FiltersPanel} routes="${routes}" />
848
+ </div>`;
767
849
  };
768
850
 
769
851
  const App = () => {
770
- return html` <${RouteList} /> `;
852
+ return html`
853
+ <div>
854
+ <${Toolbox} />
855
+ <${RouteResults} />
856
+ </div>`;
771
857
  };
772
858
  const ProgressBar = ({ width = "100%", height = 6, loading }) => {
773
859
  if (!loading) {
@@ -776,11 +862,11 @@
776
862
  return html`
777
863
  <div
778
864
  class="progress-bar"
779
- style=${{
865
+ style="${{
780
866
  width: typeof Number(width) === "number" ? `${width}px` : width,
781
867
  height:
782
868
  typeof Number(height) === "number" ? `${height}px` : height,
783
- }}
869
+ }}"
784
870
  >
785
871
  <div class="progress-container progress-indeterminate">
786
872
  <div class="progress-bar-long"></div>
@@ -789,19 +875,20 @@
789
875
  </div>
790
876
  `;
791
877
  };
792
- const RouteList = () => {
878
+ const RouteResults = () => {
793
879
  const routes = routesSignal.value;
794
880
  const loading = routesLoadingSignal.value;
795
881
  const routeLoadingError = routeLoadingErrorSignal.value;
882
+ const [groupBy] = useGroupByUrlStringParam();
796
883
 
797
884
  if (loading) {
798
- return html`<${ProgressBar} width="150" loading=${true} />`;
885
+ return html`<${ProgressBar} width="150" loading="${true}" />`;
799
886
  }
800
887
 
801
888
  if (routeLoadingError) {
802
889
  return html`<p>
803
- Error loading routes: ${routeLoadingError.message}
804
- </p>`;
890
+ Error loading routes: ${routeLoadingError.message}
891
+ </p>`;
805
892
  }
806
893
 
807
894
  if (!routes || routes.length === 0) {
@@ -809,10 +896,74 @@
809
896
  }
810
897
 
811
898
  return html`<div class="route_container">
812
- <div class="route_list">
899
+ ${groupBy
900
+ ? html`<${RouteGroups} groupBy="${groupBy}" routes="${routes}" />`
901
+ : html`<${RouteList} routes="${routes}" />`}
902
+ </div>`;
903
+ };
904
+
905
+ const RouteGroups = ({ groupBy, routes }) => {
906
+ const groups = {};
907
+ for (const route of routes) {
908
+ const groupKey =
909
+ groupBy === "by_method"
910
+ ? route.method
911
+ : route.meta.packageName || NO_PACKAGE_NAME;
912
+ if (!groups[groupKey]) {
913
+ groups[groupKey] = [];
914
+ }
915
+ groups[groupKey].push(route);
916
+ }
917
+
918
+ return html`
919
+ <ul style="margin-left: 0; padding: 0">
920
+ <li style="list-style: none; padding: 0;">
921
+ ${Object.entries(groups).map(([groupKey, groupRoutes]) => {
922
+ const groupText =
923
+ groupBy === "by_method"
924
+ ? html`<span
925
+ style="margin-left: 0.5em; display: inline-flex; gap: 0.5em;"
926
+ >
927
+ <${MethodsStatusPill} methodNames="${[groupKey]}" />
928
+ <span>${groupKey}</span>
929
+ </span>`
930
+ : html`<span>${groupKey}</span>`;
931
+
932
+ return html`
933
+ <details
934
+ class="route_group_details"
935
+ data-method="${groupBy === "by_method"
936
+ ? groupKey
937
+ : undefined}"
938
+ >
939
+ <summary>
940
+ <div class="route_group_summary_container">
941
+ <span class="route_group_name">
942
+ ${groupBy === "by_method" ? "method" : "package"}:
943
+ ${groupText}
944
+ </span>
945
+ <span class="route_group_description">
946
+ <span class="filter_count">
947
+ ${groupRoutes.length}
948
+ </span>
949
+ </span>
950
+ <span style="flex: 1"></span>
951
+ <span class="summary_marker">
952
+ <${ArrowDown} />
953
+ </span>
954
+ </div>
955
+ </summary>
956
+ <${RouteList} routes="${groupRoutes}" />
957
+ </details>`;
958
+ })}
959
+ </li>
960
+ </ul>`;
961
+ };
962
+
963
+ const RouteList = ({ routes }) => {
964
+ return html`<div class="route_list">
813
965
  ${routes.map((route) => html`<${RouteDetails} ...${route} />`)}
814
- </div>
815
- </div>`;
966
+ </div>`;
816
967
  };
817
968
 
818
969
  const documentUrlSignal = signal(window.location.href);
@@ -891,6 +1042,13 @@
891
1042
  };
892
1043
  }
893
1044
 
1045
+ const useGroupByUrlStringParam = () => {
1046
+ const availableGroupByValues = ["by_method", "by_package"];
1047
+ return useUrlStringParam("groupBy", {
1048
+ availableValues: availableGroupByValues,
1049
+ });
1050
+ };
1051
+
894
1052
  const useMethodUrlStringParam = () => {
895
1053
  const availableMethodNames = availableMethodNamesSignal.value;
896
1054
  return useUrlStringParam("method", {
@@ -905,6 +1063,66 @@
905
1063
  });
906
1064
  };
907
1065
 
1066
+ let Toolbox;
1067
+ groups: {
1068
+ Toolbox = () => {
1069
+ return html``;
1070
+ };
1071
+ }
1072
+
1073
+ const StatusPill = ({ children, color }) => {
1074
+ return html`
1075
+ <span
1076
+ class="status_pill"
1077
+ style="${{
1078
+ background: color,
1079
+ }}"
1080
+ >
1081
+ ${children}
1082
+ </span>`;
1083
+ };
1084
+ const buildLinearGradient = (colors) => {
1085
+ const percentagePerColor = Math.floor(100 / (colors.length - 1));
1086
+ const gradientSteps = [];
1087
+ let progress = 0;
1088
+ let index = 0;
1089
+ for (const color of colors) {
1090
+ if (index === colors.length - 1) {
1091
+ progress = 100;
1092
+ }
1093
+ gradientSteps.push(`${color} ${progress}%`);
1094
+ progress += percentagePerColor;
1095
+ index++;
1096
+ }
1097
+ const gradient = `linear-gradient(to bottom, ${gradientSteps.join(", ")})`;
1098
+ return gradient;
1099
+ };
1100
+ const MethodsStatusPill = ({ methodNames }) => {
1101
+ if (methodNames.length === 1) {
1102
+ return html`
1103
+ <${StatusPill}
1104
+ color="${getMethodColor(methodNames[0])}"
1105
+ />
1106
+ `;
1107
+ }
1108
+ const colors = methodNames.map((methodName) =>
1109
+ getMethodColor(methodName),
1110
+ );
1111
+ const linearGradient = buildLinearGradient(colors);
1112
+ return html`
1113
+ <${StatusPill}
1114
+ color="${linearGradient}"
1115
+ />
1116
+ `;
1117
+ };
1118
+ const getMethodColor = (methodName) => {
1119
+ const bodyStyle = window.getComputedStyle(document.body);
1120
+ const methodColor = bodyStyle.getPropertyValue(
1121
+ `--method-${methodName.toLowerCase()}-color`,
1122
+ );
1123
+ return methodColor;
1124
+ };
1125
+
908
1126
  let FiltersPanel;
909
1127
  filters: {
910
1128
  FiltersPanel = ({ routes }) => {
@@ -912,57 +1130,81 @@
912
1130
  <div class="filter_container">
913
1131
  <div class="filter_list">
914
1132
  <div class="filter_item">
915
- <${MethodFilter} routes=${routes} />
1133
+ <${GroupSelector} />
916
1134
  </div>
917
1135
  <div class="filter_item">
918
- <${PackageFilter} routes=${routes} />
1136
+ <${MethodFilter} routes="${routes}" />
1137
+ </div>
1138
+ <div class="filter_item">
1139
+ <${PackageFilter} routes="${routes}" />
919
1140
  </div>
920
1141
  </div>
921
1142
  </div>
922
1143
  `;
923
1144
  };
1145
+
1146
+ const GroupSelector = () => {
1147
+ const [groupBy, setGroupBy] = useGroupByUrlStringParam();
1148
+ const label = html`
1149
+ <span>
1150
+ Group: ${groupBy || "none"}
1151
+ </span>`;
1152
+ const options = [
1153
+ {
1154
+ value: "",
1155
+ text: html`
1156
+ <span class="filter_text">
1157
+ none
1158
+ </span>
1159
+ `,
1160
+ selected: !groupBy,
1161
+ },
1162
+ {
1163
+ value: "by_method",
1164
+ text: html`
1165
+ <span class="filter_text">
1166
+ by method
1167
+ </span>
1168
+ `,
1169
+ selected: groupBy === "by_method",
1170
+ },
1171
+ {
1172
+ value: "by_package",
1173
+ text: html`
1174
+ <span class="filter_text">
1175
+ by package
1176
+ </span>
1177
+ `,
1178
+ selected: groupBy === "by_package",
1179
+ },
1180
+ ];
1181
+
1182
+ return html`
1183
+ <${FilterSelector}
1184
+ label="${label}"
1185
+ options="${options}"
1186
+ onChange="${(groupByToSelect) => {
1187
+ setGroupBy(groupByToSelect);
1188
+ }}"
1189
+ />
1190
+ `;
1191
+ };
924
1192
  const MethodFilter = ({ routes }) => {
925
1193
  const availableMethodNames = availableMethodNamesSignal.value;
926
1194
  const availableMethodRepartition = methodRepartitionSignal.value;
927
1195
  const [selectedMethod, setSelectedMethod] = useMethodUrlStringParam();
928
1196
 
929
- // "all" border image is a combination of available method colors
930
- const getMethodColor = (methodName) => {
931
- const bodyStyle = window.getComputedStyle(document.body);
932
- const methodColor = bodyStyle.getPropertyValue(
933
- `--method-${methodName.toLowerCase()}-color`,
934
- );
935
- return methodColor;
936
- };
937
- const percentagePerColor = Math.floor(
938
- 100 / (availableMethodNames.length - 1),
939
- );
940
- const gradientSteps = [];
941
- let progress = 0;
942
- let index = 0;
943
- for (const methodName of availableMethodNames) {
944
- if (index === availableMethodNames.length - 1) {
945
- progress = 100;
946
- }
947
- gradientSteps.push(`${getMethodColor(methodName)} ${progress}%`);
948
- progress += percentagePerColor;
949
- index++;
950
- }
951
- const gradient = `linear-gradient(to bottom, ${gradientSteps.join(", ")})`;
952
-
953
1197
  const options = [
954
1198
  {
955
1199
  value: "",
956
1200
  text: html`
957
- <span
958
- class="filter_text"
959
- data-method="all"
960
- style=${{
961
- borderImageSource: gradient,
962
- }}
963
- >all</span
964
- >
965
- <span class="filter_count">${routes.length}</span>
1201
+ <${MethodsStatusPill}
1202
+ methodNames="${availableMethodNames}"
1203
+ />
1204
+ <span class="filter_text" data-method="all">all</span>
1205
+ <span class="filter_count">
1206
+ ${routes.length}
1207
+ </span>
966
1208
  `,
967
1209
  selected: !selectedMethod,
968
1210
  },
@@ -970,8 +1212,14 @@
970
1212
  return {
971
1213
  value: availableMethodName,
972
1214
  text: html`
973
- <span class="filter_text" data-method="${availableMethodName}"
974
- >${availableMethodName}
1215
+ <${MethodsStatusPill}
1216
+ methodNames="${[availableMethodName]}"
1217
+ />
1218
+ <span
1219
+ class="filter_text"
1220
+ data-method="${availableMethodName}"
1221
+ >
1222
+ ${availableMethodName}
975
1223
  </span>
976
1224
  <span class="filter_count">
977
1225
  ${availableMethodRepartition[availableMethodName]}
@@ -982,13 +1230,24 @@
982
1230
  }),
983
1231
  ];
984
1232
 
1233
+ const label = html`<span
1234
+ style="display: flex; align-items: center; gap: 5px"
1235
+ >
1236
+ <${MethodsStatusPill}
1237
+ methodNames="${selectedMethod
1238
+ ? [selectedMethod]
1239
+ : availableMethodNames}"
1240
+ />
1241
+ <span>Methods: ${selectedMethod || "all"}</span>
1242
+ </span>`;
1243
+
985
1244
  return html`
986
1245
  <${FilterSelector}
987
- label="Methods"
988
- options=${options}
989
- onChange=${(methodToSelect) => {
1246
+ label="${label}"
1247
+ options="${options}"
1248
+ onChange="${(methodToSelect) => {
990
1249
  setSelectedMethod(methodToSelect);
991
- }}
1250
+ }}"
992
1251
  />
993
1252
  `;
994
1253
  };
@@ -1020,13 +1279,20 @@
1020
1279
  };
1021
1280
  }),
1022
1281
  ];
1282
+
1283
+ const label = html`
1284
+ <span>
1285
+ Packages: ${selectedPackageName || "all"}
1286
+ </span>
1287
+ `;
1288
+
1023
1289
  return html`
1024
1290
  <${FilterSelector}
1025
- label="Packages"
1026
- options=${options}
1027
- onChange=${(packageNameToSelect) => {
1291
+ label="${label}"
1292
+ options="${options}"
1293
+ onChange="${(packageNameToSelect) => {
1028
1294
  setSelectedPackageName(packageNameToSelect);
1029
- }}
1295
+ }}"
1030
1296
  />
1031
1297
  `;
1032
1298
  };
@@ -1041,66 +1307,68 @@
1041
1307
 
1042
1308
  return html`<details open class="filter">
1043
1309
  <summary class="filter_summary">
1044
- ${label}
1045
- <${ArrowDown} />
1310
+ <span class="summary_text">${label}</span>
1311
+ <span class="summary_marker">
1312
+ <${ArrowDown} />
1313
+ </span>
1046
1314
  </summary>
1047
1315
  <form ref=${formRef} class="filter_form">
1048
1316
  ${options.map((option) => {
1049
1317
  return html`<label>
1050
- <input
1051
- type="${multiple ? "checkbox" : "radio"}"
1052
- name="${label}"
1053
- checked=${option.selected}
1054
- value=${option.value}
1055
- onChange=${multiple
1056
- ? () => {
1057
- const formNode = formRef.current;
1058
- // even if we create a checkbox list the HTML spec
1059
- // return a radio node list and we have to gather the checked values
1060
- const radioNodeList = formNode.elements[label];
1061
- const selectedValues = [];
1062
- for (const checkboxNode of Array.from(
1063
- radioNodeList,
1064
- )) {
1065
- if (checkboxNode.checked) {
1066
- selectedValues.push(checkboxNode.value);
1318
+ <input
1319
+ type="${multiple ? "checkbox" : "radio"}"
1320
+ name="${label}"
1321
+ checked="${option.selected}"
1322
+ value="${option.value}"
1323
+ onChange="${multiple
1324
+ ? () => {
1325
+ const formNode = formRef.current;
1326
+ // even if we create a checkbox list the HTML spec
1327
+ // return a radio node list and we have to gather the checked values
1328
+ const radioNodeList = formNode.elements[label];
1329
+ const selectedValues = [];
1330
+ for (const checkboxNode of Array.from(
1331
+ radioNodeList,
1332
+ )) {
1333
+ if (checkboxNode.checked) {
1334
+ selectedValues.push(checkboxNode.value);
1335
+ }
1067
1336
  }
1337
+ onChange(selectedValues);
1068
1338
  }
1069
- onChange(selectedValues);
1070
- }
1071
- : () => {
1072
- const formNode = formRef.current;
1073
- const radioNodeList = formNode.elements[label];
1074
- const selectedValue = radioNodeList.value;
1075
- onChange(selectedValue);
1076
- }}
1077
- />
1078
- ${option.text}
1079
- </label>`;
1339
+ : () => {
1340
+ const formNode = formRef.current;
1341
+ const radioNodeList = formNode.elements[label];
1342
+ const selectedValue = radioNodeList.value;
1343
+ onChange(selectedValue);
1344
+ }}"
1345
+ />
1346
+ ${option.text}
1347
+ </label>`;
1080
1348
  })}
1081
1349
  </form>
1082
1350
  ${
1083
1351
  actions &&
1084
1352
  html` <div class="filter_actions">
1085
- <button
1086
- type="button"
1087
- class="filter_button"
1088
- onClick=${() => {
1089
- onChange(options.map((option) => option.value));
1090
- }}
1091
- >
1092
- Select All
1093
- </button>
1094
- <button
1095
- type="button"
1096
- class="filter_button"
1097
- onClick=${() => {
1098
- onChange([]);
1099
- }}
1100
- >
1101
- Clear All
1102
- </button>
1103
- </div>`
1353
+ <button
1354
+ type="button"
1355
+ class="filter_button"
1356
+ onClick="${() => {
1357
+ onChange(options.map((option) => option.value));
1358
+ }}"
1359
+ >
1360
+ Select All
1361
+ </button>
1362
+ <button
1363
+ type="button"
1364
+ class="filter_button"
1365
+ onClick="${() => {
1366
+ onChange([]);
1367
+ }}"
1368
+ >
1369
+ Clear All
1370
+ </button>
1371
+ </div>`
1104
1372
  }
1105
1373
  </div>`;
1106
1374
  };
@@ -1120,7 +1388,6 @@
1120
1388
  clientCodeExample,
1121
1389
  declarationSource,
1122
1390
  }) => {
1123
- const [opened, setOpened] = useState(false);
1124
1391
  const [selectedMethod] = useUrlStringParam("method");
1125
1392
  const [selectedPackageName] = usePackageUrlStringParam();
1126
1393
 
@@ -1132,37 +1399,38 @@
1132
1399
  return null;
1133
1400
  }
1134
1401
 
1135
- return html`<details
1136
- class="route_details"
1137
- onToggle=${(e) => {
1138
- setOpened(e.newState === "open");
1139
- }}
1140
- data-method=${method}
1141
- >
1142
- <summary>
1143
- <div class="route_summary_container">
1402
+ return html`<details class="route_details" data-method="${method}">
1403
+ <summary class="route_summary_container">
1144
1404
  <span class="route_method">${method}</span>
1145
1405
  <p class="route_resource">${resource}</p>
1406
+ ${method === "GET"
1407
+ ? html`<a
1408
+ style="display:inline-flex; margin-left: 5px;"
1409
+ href="${resource}"
1410
+ ><${OpenLink}
1411
+ /></a>`
1412
+ : null}
1146
1413
  <span style="flex:1"></span>
1147
- <${RouteBadge} meta=${meta} />
1148
- ${opened ? html`<${ArrowUp} />` : html`<${ArrowDown} />`}
1149
- </div>
1150
- </summary>
1151
- <${RouteDetailsContent}
1152
- method=${method}
1153
- resource=${resource}
1154
- description=${description}
1155
- availableMediaTypes=${availableMediaTypes}
1156
- availableLanguages=${availableLanguages}
1157
- availableVersions=${availableVersions}
1158
- availableEncodings=${availableEncodings}
1159
- acceptedMediaTypes=${acceptedMediaTypes}
1160
- websocket=${websocket}
1161
- clientCodeExample=${clientCodeExample}
1162
- declarationSource=${declarationSource}
1163
- meta=${meta}
1164
- />
1165
- </details>`;
1414
+ <${RouteBadge} meta="${meta}" />
1415
+ <span class="summary_marker">
1416
+ <${ArrowDown} />
1417
+ </span>
1418
+ </summary>
1419
+ <${RouteDetailsContent}
1420
+ method="${method}"
1421
+ resource="${resource}"
1422
+ description="${description}"
1423
+ availableMediaTypes="${availableMediaTypes}"
1424
+ availableLanguages="${availableLanguages}"
1425
+ availableVersions="${availableVersions}"
1426
+ availableEncodings="${availableEncodings}"
1427
+ acceptedMediaTypes="${acceptedMediaTypes}"
1428
+ websocket="${websocket}"
1429
+ clientCodeExample="${clientCodeExample}"
1430
+ declarationSource="${declarationSource}"
1431
+ meta="${meta}"
1432
+ />
1433
+ </details>`;
1166
1434
  };
1167
1435
 
1168
1436
  const RouteBadge = ({ meta }) => {
@@ -1171,9 +1439,9 @@
1171
1439
  }
1172
1440
  let text = meta.packageName;
1173
1441
  let title = `This endpoint is declared by ${meta.packageName}`;
1174
- return html`<span title=${title} class="route_owner_name"
1175
- >${text}</span
1176
- >`;
1442
+ return html`<span title="${title}" class="route_owner_name"
1443
+ >${text}</span
1444
+ >`;
1177
1445
  };
1178
1446
 
1179
1447
  const RouteDetailsContent = ({
@@ -1191,51 +1459,51 @@
1191
1459
  meta,
1192
1460
  }) => {
1193
1461
  return html`<div class="route_details_content">
1194
- <${RoutedeclarationSource}
1195
- meta=${meta}
1196
- declarationSource=${declarationSource}
1197
- />
1198
- <p style="margin-top: 0"><strong>Description:</strong></p>
1199
- <p class="route_description">
1200
- ${description || "No description available"}
1201
- </p>
1202
- ${availableMediaTypes.length > 0 &&
1203
- html`<${RouteContentNegotiationSection}
1204
- type="content-type"
1205
- title="Available media types"
1206
- availableValues=${availableMediaTypes}
1207
- />`}
1208
- ${availableLanguages.length > 0 &&
1209
- html`<${RouteContentNegotiationSection}
1210
- type="language"
1211
- title="Available languages"
1212
- availableValues=${availableLanguages}
1213
- />`}
1214
- ${availableVersions.length > 0 &&
1215
- html`<${RouteContentNegotiationSection}
1216
- type="version"
1217
- title="Available versions"
1218
- availableValues=${availableVersions}
1219
- />`}
1220
- ${availableEncodings.length > 0 &&
1221
- html`<${RouteContentNegotiationSection}
1222
- type="encoding"
1223
- title="Available encodings"
1224
- availableValues=${availableEncodings}
1225
- />`}
1226
- ${acceptedMediaTypes.length > 0 &&
1227
- html`<${RouteAcceptableContentSection}
1228
- acceptedMediaTypes=${acceptedMediaTypes}
1229
- />`}
1230
- <p style="margin-bottom:0; font-weight:bold">Example:</p>
1231
- <${RouteCodeExample}
1232
- method=${method}
1233
- resource=${resource}
1234
- availableMediaTypes=${availableMediaTypes}
1235
- websocket=${websocket}
1236
- clientCodeExample=${clientCodeExample}
1237
- />
1238
- </div>`;
1462
+ <${RoutedeclarationSource}
1463
+ meta="${meta}"
1464
+ declarationSource="${declarationSource}"
1465
+ />
1466
+ <p style="margin-top: 0"><strong>Description:</strong></p>
1467
+ <p class="route_description">
1468
+ ${description || "No description available"}
1469
+ </p>
1470
+ ${availableMediaTypes.length > 0 &&
1471
+ html`<${RouteContentNegotiationSection}
1472
+ type="content-type"
1473
+ title="Available media types"
1474
+ availableValues="${availableMediaTypes}"
1475
+ />`}
1476
+ ${availableLanguages.length > 0 &&
1477
+ html`<${RouteContentNegotiationSection}
1478
+ type="language"
1479
+ title="Available languages"
1480
+ availableValues="${availableLanguages}"
1481
+ />`}
1482
+ ${availableVersions.length > 0 &&
1483
+ html`<${RouteContentNegotiationSection}
1484
+ type="version"
1485
+ title="Available versions"
1486
+ availableValues="${availableVersions}"
1487
+ />`}
1488
+ ${availableEncodings.length > 0 &&
1489
+ html`<${RouteContentNegotiationSection}
1490
+ type="encoding"
1491
+ title="Available encodings"
1492
+ availableValues="${availableEncodings}"
1493
+ />`}
1494
+ ${acceptedMediaTypes.length > 0 &&
1495
+ html`<${RouteAcceptableContentSection}
1496
+ acceptedMediaTypes="${acceptedMediaTypes}"
1497
+ />`}
1498
+ <p style="margin-bottom:0; font-weight:bold">Example:</p>
1499
+ <${RouteCodeExample}
1500
+ method="${method}"
1501
+ resource="${resource}"
1502
+ availableMediaTypes="${availableMediaTypes}"
1503
+ websocket="${websocket}"
1504
+ clientCodeExample="${clientCodeExample}"
1505
+ />
1506
+ </div>`;
1239
1507
  };
1240
1508
  const RoutedeclarationSource = ({ meta, declarationSource }) => {
1241
1509
  if (!declarationSource) {
@@ -1244,8 +1512,8 @@
1244
1512
  return html`<p style="margin-top: 0"><strong>Declaration:</strong></p>
1245
1513
  <p>
1246
1514
  <${RouteDeclarationText}
1247
- ownerLink=${meta.ownerLink}
1248
- declarationLink=${meta.declarationLink}
1515
+ ownerLink="${meta.ownerLink}"
1516
+ declarationLink="${meta.declarationLink}"
1249
1517
  />
1250
1518
  </p>`;
1251
1519
  };
@@ -1259,23 +1527,23 @@
1259
1527
  availableValues,
1260
1528
  }) => {
1261
1529
  return html`<div class="route_content_nego" data-nego-type="${type}">
1262
- <p class="route_content_nego_title">${title}:</p>
1263
- <ul>
1264
- ${availableValues.map(
1265
- (availableValue) => html`<li>${availableValue}</li>`,
1266
- )}
1267
- </ul>
1268
- </div>`;
1530
+ <p class="route_content_nego_title">${title}:</p>
1531
+ <ul>
1532
+ ${availableValues.map(
1533
+ (availableValue) => html`<li>${availableValue}</li>`,
1534
+ )}
1535
+ </ul>
1536
+ </div>`;
1269
1537
  };
1270
1538
  const RouteAcceptableContentSection = ({ acceptedMediaTypes }) => {
1271
1539
  return html`<div class="route_accepted_content_type">
1272
- <p><strong>Accepted content types:</strong></p>
1273
- <ul>
1274
- ${acceptedMediaTypes.map(
1275
- (acceptedMediaType) => html`<li>${acceptedMediaType}</li>`,
1276
- )}
1277
- </ul>
1278
- </div>`;
1540
+ <p><strong>Accepted content types:</strong></p>
1541
+ <ul>
1542
+ ${acceptedMediaTypes.map(
1543
+ (acceptedMediaType) => html`<li>${acceptedMediaType}</li>`,
1544
+ )}
1545
+ </ul>
1546
+ </div>`;
1279
1547
  };
1280
1548
 
1281
1549
  const RouteCodeExample = ({
@@ -1327,33 +1595,37 @@
1327
1595
  }
1328
1596
 
1329
1597
  return html`<code-block lang="js" data-escaped data-copy-button
1330
- >${jsSource}</code-block
1331
- >`;
1598
+ >${jsSource}</code-block
1599
+ >`;
1332
1600
  };
1333
1601
 
1334
- const ArrowDown = () => {
1602
+ const ArrowDown = ({ width = 24, height = 24 }) => {
1335
1603
  return html`<svg
1336
- xmlns="http://www.w3.org/2000/svg"
1337
- viewBox="0 -960 960 960"
1338
- width="24"
1339
- height="24"
1340
- fill="currentColor"
1341
- class="arrow_down"
1342
- >
1343
- <path d="M480-345 240-585l56-56 184 184 184-184 56 56-240 240Z" />
1344
- </svg>`;
1604
+ viewBox="0 -960 960 960"
1605
+ width="${width}"
1606
+ height="${height}"
1607
+ fill="currentColor"
1608
+ xmlns="http://www.w3.org/2000/svg"
1609
+ >
1610
+ <path d="M480-345 240-585l56-56 184 184 184-184 56 56-240 240Z" />
1611
+ </svg>`;
1345
1612
  };
1346
- const ArrowUp = () => {
1613
+ const OpenLink = () => {
1347
1614
  return html`<svg
1348
- xmlns="http://www.w3.org/2000/svg"
1349
- viewBox="0 -960 960 960"
1350
- width="24"
1351
- height="24"
1352
- fill="currentColor"
1353
- class="arrow_up"
1354
- >
1355
- <path d="M480-528 296-344l-56-56 240-240 240 240-56 56-184-184Z" />
1356
- </svg>`;
1615
+ xmlns="http://www.w3.org/2000/svg"
1616
+ viewBox="0 0 64 64"
1617
+ stroke-width="3"
1618
+ stroke="currentColor"
1619
+ fill="none"
1620
+ width="24"
1621
+ height="24"
1622
+ >
1623
+ <path
1624
+ d="M55.4,32V53.58a1.81,1.81,0,0,1-1.82,1.82H10.42A1.81,1.81,0,0,1,8.6,53.58V10.42A1.81,1.81,0,0,1,10.42,8.6H32"
1625
+ />
1626
+ <polyline points="40.32 8.6 55.4 8.6 55.4 24.18" />
1627
+ <line x1="19.32" y1="45.72" x2="54.61" y2="8.91" />
1628
+ </svg>`;
1357
1629
  };
1358
1630
 
1359
1631
  render(html`<${Sidebar} />`, document.querySelector("aside"));