@genspectrum/dashboard-components 0.6.10 → 0.6.12
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.
- package/dist/dashboard-components.js +1006 -618
- package/dist/dashboard-components.js.map +1 -1
- package/dist/genspectrum-components.d.ts +7 -7
- package/dist/style.css +147 -1
- package/package.json +2 -2
- package/src/preact/aggregatedData/aggregate.tsx +12 -4
- package/src/preact/components/checkbox-selector.stories.tsx +93 -11
- package/src/preact/components/checkbox-selector.tsx +19 -0
- package/src/preact/components/color-scale-selector-dropdown.tsx +5 -3
- package/src/preact/components/dropdown.tsx +3 -3
- package/src/preact/components/info.tsx +88 -1
- package/src/preact/components/mutation-type-selector.stories.tsx +115 -0
- package/src/preact/components/mutation-type-selector.tsx +33 -8
- package/src/preact/components/percent-input.stories.tsx +93 -0
- package/src/preact/components/percent-intput.tsx +4 -0
- package/src/preact/components/proportion-selector-dropdown.stories.tsx +2 -2
- package/src/preact/components/proportion-selector-dropdown.tsx +9 -7
- package/src/preact/components/proportion-selector.stories.tsx +4 -4
- package/src/preact/components/proportion-selector.tsx +46 -12
- package/src/preact/components/segment-selector.stories.tsx +151 -0
- package/src/preact/components/{SegmentSelector.tsx → segment-selector.tsx} +29 -20
- package/src/preact/mutationComparison/mutation-comparison.stories.tsx +1 -1
- package/src/preact/mutationComparison/mutation-comparison.tsx +1 -1
- package/src/preact/mutationComparison/queryMutationData.ts +1 -1
- package/src/preact/mutations/mutations-grid.tsx +5 -1
- package/src/preact/mutations/mutations.tsx +33 -3
- package/src/preact/mutations/queryMutations.ts +1 -1
- package/src/preact/mutationsOverTime/getFilteredMutationsOverTime.spec.ts +4 -4
- package/src/preact/mutationsOverTime/getFilteredMutationsOverTimeData.ts +3 -2
- package/src/preact/mutationsOverTime/mutations-over-time.tsx +1 -1
- package/src/preact/numberSequencesOverTime/number-sequences-over-time.tsx +43 -8
- package/src/preact/prevalenceOverTime/prevalence-over-time.stories.tsx +2 -1
- package/src/preact/prevalenceOverTime/prevalence-over-time.tsx +44 -53
- package/src/preact/useQuery.ts +1 -1
- package/src/query/queryMutationsOverTime.ts +3 -3
- package/src/utils/map2d.spec.ts +83 -22
- package/src/utils/map2d.ts +158 -0
- package/src/web-components/visualization/gs-prevalence-over-time.tsx +2 -4
- package/src/utils/Map2d.ts +0 -75
|
@@ -990,14 +990,14 @@ declare global {
|
|
|
990
990
|
|
|
991
991
|
declare global {
|
|
992
992
|
interface HTMLElementTagNameMap {
|
|
993
|
-
'gs-
|
|
993
|
+
'gs-number-sequences-over-time': NumberSequencesOverTimeComponent;
|
|
994
994
|
}
|
|
995
995
|
}
|
|
996
996
|
|
|
997
997
|
|
|
998
998
|
declare global {
|
|
999
999
|
interface HTMLElementTagNameMap {
|
|
1000
|
-
'gs-
|
|
1000
|
+
'gs-aggregate-component': AggregateComponent;
|
|
1001
1001
|
}
|
|
1002
1002
|
}
|
|
1003
1003
|
|
|
@@ -1041,21 +1041,21 @@ declare global {
|
|
|
1041
1041
|
|
|
1042
1042
|
declare global {
|
|
1043
1043
|
interface HTMLElementTagNameMap {
|
|
1044
|
-
'gs-
|
|
1044
|
+
'gs-lineage-filter': LineageFilterComponent;
|
|
1045
1045
|
}
|
|
1046
1046
|
interface HTMLElementEventMap {
|
|
1047
|
-
'gs-
|
|
1048
|
-
'gs-mutation-filter-on-blur': CustomEvent<SelectedMutationFilterStrings>;
|
|
1047
|
+
'gs-lineage-filter-changed': CustomEvent<Record<string, string>>;
|
|
1049
1048
|
}
|
|
1050
1049
|
}
|
|
1051
1050
|
|
|
1052
1051
|
|
|
1053
1052
|
declare global {
|
|
1054
1053
|
interface HTMLElementTagNameMap {
|
|
1055
|
-
'gs-
|
|
1054
|
+
'gs-mutation-filter': MutationFilterComponent;
|
|
1056
1055
|
}
|
|
1057
1056
|
interface HTMLElementEventMap {
|
|
1058
|
-
'gs-
|
|
1057
|
+
'gs-mutation-filter-changed': CustomEvent<SelectedMutationFilterStrings>;
|
|
1058
|
+
'gs-mutation-filter-on-blur': CustomEvent<SelectedMutationFilterStrings>;
|
|
1059
1059
|
}
|
|
1060
1060
|
}
|
|
1061
1061
|
|
package/dist/style.css
CHANGED
|
@@ -376,7 +376,7 @@ input[type="range"] {
|
|
|
376
376
|
background-color: #C6C6C6;
|
|
377
377
|
pointer-events: none;
|
|
378
378
|
}/*
|
|
379
|
-
! tailwindcss v3.4.
|
|
379
|
+
! tailwindcss v3.4.7 | MIT License | https://tailwindcss.com
|
|
380
380
|
*//*
|
|
381
381
|
1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
|
|
382
382
|
2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
|
|
@@ -983,6 +983,39 @@ html {
|
|
|
983
983
|
--tw-contain-paint: ;
|
|
984
984
|
--tw-contain-style: ;
|
|
985
985
|
}
|
|
986
|
+
.container {
|
|
987
|
+
width: 100%;
|
|
988
|
+
}
|
|
989
|
+
@media (min-width: 640px) {
|
|
990
|
+
|
|
991
|
+
.container {
|
|
992
|
+
max-width: 640px;
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
@media (min-width: 768px) {
|
|
996
|
+
|
|
997
|
+
.container {
|
|
998
|
+
max-width: 768px;
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
@media (min-width: 1024px) {
|
|
1002
|
+
|
|
1003
|
+
.container {
|
|
1004
|
+
max-width: 1024px;
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
@media (min-width: 1280px) {
|
|
1008
|
+
|
|
1009
|
+
.container {
|
|
1010
|
+
max-width: 1280px;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
@media (min-width: 1536px) {
|
|
1014
|
+
|
|
1015
|
+
.container {
|
|
1016
|
+
max-width: 1536px;
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
986
1019
|
.alert {
|
|
987
1020
|
display: grid;
|
|
988
1021
|
width: 100%;
|
|
@@ -1024,6 +1057,15 @@ html {
|
|
|
1024
1057
|
color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));
|
|
1025
1058
|
}
|
|
1026
1059
|
|
|
1060
|
+
.menu li > *:not(ul, .menu-title, details, .btn):active,
|
|
1061
|
+
.menu li > *:not(ul, .menu-title, details, .btn).active,
|
|
1062
|
+
.menu li > details > summary:active {
|
|
1063
|
+
--tw-bg-opacity: 1;
|
|
1064
|
+
background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));
|
|
1065
|
+
--tw-text-opacity: 1;
|
|
1066
|
+
color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity)));
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1027
1069
|
.tab:hover {
|
|
1028
1070
|
--tw-text-opacity: 1;
|
|
1029
1071
|
}
|
|
@@ -1117,6 +1159,25 @@ html {
|
|
|
1117
1159
|
container-type: inline-size;
|
|
1118
1160
|
grid-template-columns: auto 1fr;
|
|
1119
1161
|
}
|
|
1162
|
+
.divider {
|
|
1163
|
+
display: flex;
|
|
1164
|
+
flex-direction: row;
|
|
1165
|
+
align-items: center;
|
|
1166
|
+
align-self: stretch;
|
|
1167
|
+
margin-top: 1rem;
|
|
1168
|
+
margin-bottom: 1rem;
|
|
1169
|
+
height: 1rem;
|
|
1170
|
+
white-space: nowrap;
|
|
1171
|
+
}
|
|
1172
|
+
.divider:before,
|
|
1173
|
+
.divider:after {
|
|
1174
|
+
height: 0.125rem;
|
|
1175
|
+
width: 100%;
|
|
1176
|
+
flex-grow: 1;
|
|
1177
|
+
--tw-content: '';
|
|
1178
|
+
content: var(--tw-content);
|
|
1179
|
+
background-color: var(--fallback-bc,oklch(var(--bc)/0.1));
|
|
1180
|
+
}
|
|
1120
1181
|
.dropdown {
|
|
1121
1182
|
position: relative;
|
|
1122
1183
|
display: inline-block;
|
|
@@ -1362,6 +1423,10 @@ html {
|
|
|
1362
1423
|
border-radius: inherit;
|
|
1363
1424
|
}
|
|
1364
1425
|
}
|
|
1426
|
+
.link {
|
|
1427
|
+
cursor: pointer;
|
|
1428
|
+
text-decoration-line: underline;
|
|
1429
|
+
}
|
|
1365
1430
|
.menu li.disabled {
|
|
1366
1431
|
cursor: not-allowed;
|
|
1367
1432
|
-webkit-user-select: none;
|
|
@@ -1629,6 +1694,11 @@ input.tab:checked + .tab-content,
|
|
|
1629
1694
|
--alert-bg: var(--fallback-er,oklch(var(--er)/1));
|
|
1630
1695
|
--alert-bg-mix: var(--fallback-b1,oklch(var(--b1)/1));
|
|
1631
1696
|
}
|
|
1697
|
+
.btm-nav > *:where(.active) {
|
|
1698
|
+
border-top-width: 2px;
|
|
1699
|
+
--tw-bg-opacity: 1;
|
|
1700
|
+
background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));
|
|
1701
|
+
}
|
|
1632
1702
|
.btm-nav > *.disabled,
|
|
1633
1703
|
.btm-nav > *[disabled] {
|
|
1634
1704
|
pointer-events: none;
|
|
@@ -1781,6 +1851,9 @@ input.tab:checked + .tab-content,
|
|
|
1781
1851
|
background-position-y: 0;
|
|
1782
1852
|
}
|
|
1783
1853
|
}
|
|
1854
|
+
.divider:not(:empty) {
|
|
1855
|
+
gap: 1rem;
|
|
1856
|
+
}
|
|
1784
1857
|
.dropdown.dropdown-open .dropdown-content,
|
|
1785
1858
|
.dropdown:focus .dropdown-content,
|
|
1786
1859
|
.dropdown:focus-within .dropdown-content {
|
|
@@ -1858,6 +1931,14 @@ input.tab:checked + .tab-content,
|
|
|
1858
1931
|
.join > :where(*:not(:first-child)):is(.btn) {
|
|
1859
1932
|
margin-inline-start: calc(var(--border-btn) * -1);
|
|
1860
1933
|
}
|
|
1934
|
+
.link:focus {
|
|
1935
|
+
outline: 2px solid transparent;
|
|
1936
|
+
outline-offset: 2px;
|
|
1937
|
+
}
|
|
1938
|
+
.link:focus-visible {
|
|
1939
|
+
outline: 2px solid currentColor;
|
|
1940
|
+
outline-offset: 2px;
|
|
1941
|
+
}
|
|
1861
1942
|
.loading {
|
|
1862
1943
|
pointer-events: none;
|
|
1863
1944
|
display: inline-block;
|
|
@@ -1888,6 +1969,14 @@ input.tab:checked + .tab-content,
|
|
|
1888
1969
|
outline: 2px solid transparent;
|
|
1889
1970
|
outline-offset: 2px;
|
|
1890
1971
|
}
|
|
1972
|
+
.menu li > *:not(ul, .menu-title, details, .btn):active,
|
|
1973
|
+
.menu li > *:not(ul, .menu-title, details, .btn).active,
|
|
1974
|
+
.menu li > details > summary:active {
|
|
1975
|
+
--tw-bg-opacity: 1;
|
|
1976
|
+
background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));
|
|
1977
|
+
--tw-text-opacity: 1;
|
|
1978
|
+
color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity)));
|
|
1979
|
+
}
|
|
1891
1980
|
.mockup-phone .display {
|
|
1892
1981
|
overflow: hidden;
|
|
1893
1982
|
border-radius: 40px;
|
|
@@ -2384,6 +2473,12 @@ input.tab:checked + .tab-content,
|
|
|
2384
2473
|
--tw-bg-opacity: 1;
|
|
2385
2474
|
background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)));
|
|
2386
2475
|
}
|
|
2476
|
+
.table-zebra tr.active,
|
|
2477
|
+
.table-zebra tr.active:nth-child(even),
|
|
2478
|
+
.table-zebra-zebra tbody tr:nth-child(even) {
|
|
2479
|
+
--tw-bg-opacity: 1;
|
|
2480
|
+
background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity)));
|
|
2481
|
+
}
|
|
2387
2482
|
.table :where(thead tr, tbody tr:not(:last-child), tbody tr:first-child:last-child) {
|
|
2388
2483
|
border-bottom-width: 1px;
|
|
2389
2484
|
--tw-border-opacity: 1;
|
|
@@ -2456,6 +2551,18 @@ input.tab:checked + .tab-content,
|
|
|
2456
2551
|
--togglehandleborder: 0 0 0 3px var(--fallback-bc,oklch(var(--bc)/1)) inset,
|
|
2457
2552
|
var(--handleoffsetcalculator) 0 0 3px var(--fallback-bc,oklch(var(--bc)/1)) inset;
|
|
2458
2553
|
}
|
|
2554
|
+
.btm-nav-xs > *:where(.active) {
|
|
2555
|
+
border-top-width: 1px;
|
|
2556
|
+
}
|
|
2557
|
+
.btm-nav-sm > *:where(.active) {
|
|
2558
|
+
border-top-width: 2px;
|
|
2559
|
+
}
|
|
2560
|
+
.btm-nav-md > *:where(.active) {
|
|
2561
|
+
border-top-width: 2px;
|
|
2562
|
+
}
|
|
2563
|
+
.btm-nav-lg > *:where(.active) {
|
|
2564
|
+
border-top-width: 4px;
|
|
2565
|
+
}
|
|
2459
2566
|
.btn-xs {
|
|
2460
2567
|
height: 1.5rem;
|
|
2461
2568
|
min-height: 1.5rem;
|
|
@@ -2875,6 +2982,9 @@ input.tab:checked + .tab-content,
|
|
|
2875
2982
|
margin-top: 1rem;
|
|
2876
2983
|
margin-bottom: 1rem;
|
|
2877
2984
|
}
|
|
2985
|
+
.mb-0 {
|
|
2986
|
+
margin-bottom: 0px;
|
|
2987
|
+
}
|
|
2878
2988
|
.mb-1 {
|
|
2879
2989
|
margin-bottom: 0.25rem;
|
|
2880
2990
|
}
|
|
@@ -2884,6 +2994,9 @@ input.tab:checked + .tab-content,
|
|
|
2884
2994
|
.ml-1 {
|
|
2885
2995
|
margin-left: 0.25rem;
|
|
2886
2996
|
}
|
|
2997
|
+
.ml-2 {
|
|
2998
|
+
margin-left: 0.5rem;
|
|
2999
|
+
}
|
|
2887
3000
|
.ml-2\.5 {
|
|
2888
3001
|
margin-left: 0.625rem;
|
|
2889
3002
|
}
|
|
@@ -2896,6 +3009,9 @@ input.tab:checked + .tab-content,
|
|
|
2896
3009
|
.mr-2 {
|
|
2897
3010
|
margin-right: 0.5rem;
|
|
2898
3011
|
}
|
|
3012
|
+
.mt-0 {
|
|
3013
|
+
margin-top: 0px;
|
|
3014
|
+
}
|
|
2899
3015
|
.mt-0\.5 {
|
|
2900
3016
|
margin-top: 0.125rem;
|
|
2901
3017
|
}
|
|
@@ -2935,12 +3051,24 @@ input.tab:checked + .tab-content,
|
|
|
2935
3051
|
.w-16 {
|
|
2936
3052
|
width: 4rem;
|
|
2937
3053
|
}
|
|
3054
|
+
.w-20 {
|
|
3055
|
+
width: 5rem;
|
|
3056
|
+
}
|
|
3057
|
+
.w-24 {
|
|
3058
|
+
width: 6rem;
|
|
3059
|
+
}
|
|
2938
3060
|
.w-32 {
|
|
2939
3061
|
width: 8rem;
|
|
2940
3062
|
}
|
|
3063
|
+
.w-44 {
|
|
3064
|
+
width: 11rem;
|
|
3065
|
+
}
|
|
2941
3066
|
.w-64 {
|
|
2942
3067
|
width: 16rem;
|
|
2943
3068
|
}
|
|
3069
|
+
.w-\[6rem\] {
|
|
3070
|
+
width: 6rem;
|
|
3071
|
+
}
|
|
2944
3072
|
.w-\[7\.5rem\] {
|
|
2945
3073
|
width: 7.5rem;
|
|
2946
3074
|
}
|
|
@@ -2977,6 +3105,12 @@ input.tab:checked + .tab-content,
|
|
|
2977
3105
|
.resize {
|
|
2978
3106
|
resize: both;
|
|
2979
3107
|
}
|
|
3108
|
+
.list-inside {
|
|
3109
|
+
list-style-position: inside;
|
|
3110
|
+
}
|
|
3111
|
+
.list-disc {
|
|
3112
|
+
list-style-type: disc;
|
|
3113
|
+
}
|
|
2980
3114
|
.flex-row {
|
|
2981
3115
|
flex-direction: row;
|
|
2982
3116
|
}
|
|
@@ -3007,6 +3141,9 @@ input.tab:checked + .tab-content,
|
|
|
3007
3141
|
.overflow-auto {
|
|
3008
3142
|
overflow: auto;
|
|
3009
3143
|
}
|
|
3144
|
+
.overflow-x-auto {
|
|
3145
|
+
overflow-x: auto;
|
|
3146
|
+
}
|
|
3010
3147
|
.whitespace-nowrap {
|
|
3011
3148
|
white-space: nowrap;
|
|
3012
3149
|
}
|
|
@@ -3016,6 +3153,9 @@ input.tab:checked + .tab-content,
|
|
|
3016
3153
|
.rounded-full {
|
|
3017
3154
|
border-radius: 9999px;
|
|
3018
3155
|
}
|
|
3156
|
+
.rounded-lg {
|
|
3157
|
+
border-radius: 0.5rem;
|
|
3158
|
+
}
|
|
3019
3159
|
.rounded-md {
|
|
3020
3160
|
border-radius: 0.375rem;
|
|
3021
3161
|
}
|
|
@@ -3271,6 +3411,12 @@ input.tab:checked + .tab-content,
|
|
|
3271
3411
|
}
|
|
3272
3412
|
.peer:hover ~ .peer-hover\:visible {
|
|
3273
3413
|
visibility: visible;
|
|
3414
|
+
}
|
|
3415
|
+
@media (min-width: 640px) {
|
|
3416
|
+
|
|
3417
|
+
.sm\:max-w-5xl {
|
|
3418
|
+
max-width: 64rem;
|
|
3419
|
+
}
|
|
3274
3420
|
}.flatpickr-calendar{background:transparent;opacity:0;display:none;text-align:center;visibility:hidden;padding:0;-webkit-animation:none;animation:none;direction:ltr;border:0;font-size:14px;line-height:24px;border-radius:5px;position:absolute;width:307.875px;-webkit-box-sizing:border-box;box-sizing:border-box;-ms-touch-action:manipulation;touch-action:manipulation;background:#fff;-webkit-box-shadow:1px 0 0 #e6e6e6,-1px 0 0 #e6e6e6,0 1px 0 #e6e6e6,0 -1px 0 #e6e6e6,0 3px 13px rgba(0,0,0,0.08);box-shadow:1px 0 0 #e6e6e6,-1px 0 0 #e6e6e6,0 1px 0 #e6e6e6,0 -1px 0 #e6e6e6,0 3px 13px rgba(0,0,0,0.08)}.flatpickr-calendar.open,.flatpickr-calendar.inline{opacity:1;max-height:640px;visibility:visible}.flatpickr-calendar.open{display:inline-block;z-index:99999}.flatpickr-calendar.animate.open{-webkit-animation:fpFadeInDown 300ms cubic-bezier(.23,1,.32,1);animation:fpFadeInDown 300ms cubic-bezier(.23,1,.32,1)}.flatpickr-calendar.inline{display:block;position:relative;top:2px}.flatpickr-calendar.static{position:absolute;top:calc(100% + 2px)}.flatpickr-calendar.static.open{z-index:999;display:block}.flatpickr-calendar.multiMonth .flatpickr-days .dayContainer:nth-child(n+1) .flatpickr-day.inRange:nth-child(7n+7){-webkit-box-shadow:none !important;box-shadow:none !important}.flatpickr-calendar.multiMonth .flatpickr-days .dayContainer:nth-child(n+2) .flatpickr-day.inRange:nth-child(7n+1){-webkit-box-shadow:-2px 0 0 #e6e6e6,5px 0 0 #e6e6e6;box-shadow:-2px 0 0 #e6e6e6,5px 0 0 #e6e6e6}.flatpickr-calendar .hasWeeks .dayContainer,.flatpickr-calendar .hasTime .dayContainer{border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.flatpickr-calendar .hasWeeks .dayContainer{border-left:0}.flatpickr-calendar.hasTime .flatpickr-time{height:40px;border-top:1px solid #e6e6e6}.flatpickr-calendar.noCalendar.hasTime .flatpickr-time{height:auto}.flatpickr-calendar:before,.flatpickr-calendar:after{position:absolute;display:block;pointer-events:none;border:solid transparent;content:'';height:0;width:0;left:22px}.flatpickr-calendar.rightMost:before,.flatpickr-calendar.arrowRight:before,.flatpickr-calendar.rightMost:after,.flatpickr-calendar.arrowRight:after{left:auto;right:22px}.flatpickr-calendar.arrowCenter:before,.flatpickr-calendar.arrowCenter:after{left:50%;right:50%}.flatpickr-calendar:before{border-width:5px;margin:0 -5px}.flatpickr-calendar:after{border-width:4px;margin:0 -4px}.flatpickr-calendar.arrowTop:before,.flatpickr-calendar.arrowTop:after{bottom:100%}.flatpickr-calendar.arrowTop:before{border-bottom-color:#e6e6e6}.flatpickr-calendar.arrowTop:after{border-bottom-color:#fff}.flatpickr-calendar.arrowBottom:before,.flatpickr-calendar.arrowBottom:after{top:100%}.flatpickr-calendar.arrowBottom:before{border-top-color:#e6e6e6}.flatpickr-calendar.arrowBottom:after{border-top-color:#fff}.flatpickr-calendar:focus{outline:0}.flatpickr-wrapper{position:relative;display:inline-block}.flatpickr-months{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.flatpickr-months .flatpickr-month{background:transparent;color:rgba(0,0,0,0.9);fill:rgba(0,0,0,0.9);height:34px;line-height:1;text-align:center;position:relative;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;overflow:hidden;-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1}.flatpickr-months .flatpickr-prev-month,.flatpickr-months .flatpickr-next-month{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;text-decoration:none;cursor:pointer;position:absolute;top:0;height:34px;padding:10px;z-index:3;color:rgba(0,0,0,0.9);fill:rgba(0,0,0,0.9)}.flatpickr-months .flatpickr-prev-month.flatpickr-disabled,.flatpickr-months .flatpickr-next-month.flatpickr-disabled{display:none}.flatpickr-months .flatpickr-prev-month i,.flatpickr-months .flatpickr-next-month i{position:relative}.flatpickr-months .flatpickr-prev-month.flatpickr-prev-month,.flatpickr-months .flatpickr-next-month.flatpickr-prev-month{/*
|
|
3275
3421
|
/*rtl:begin:ignore*/left:0/*
|
|
3276
3422
|
/*rtl:end:ignore*/}/*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@genspectrum/dashboard-components",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.12",
|
|
4
4
|
"description": "GenSpectrum web components for building dashboards",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "AGPL-3.0-only",
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
"@storybook/types": "^8.0.9",
|
|
91
91
|
"@storybook/web-components": "^8.0.9",
|
|
92
92
|
"@storybook/web-components-vite": "^8.0.9",
|
|
93
|
-
"@types/node": "^
|
|
93
|
+
"@types/node": "^22.0.0",
|
|
94
94
|
"@types/object-hash": "^3.0.6",
|
|
95
95
|
"@typescript-eslint/eslint-plugin": "^7.14.1",
|
|
96
96
|
"@typescript-eslint/parser": "^7.14.1",
|
|
@@ -9,7 +9,7 @@ import { CsvDownloadButton } from '../components/csv-download-button';
|
|
|
9
9
|
import { ErrorBoundary } from '../components/error-boundary';
|
|
10
10
|
import { ErrorDisplay } from '../components/error-display';
|
|
11
11
|
import { Fullscreen } from '../components/fullscreen';
|
|
12
|
-
import Info from '../components/info';
|
|
12
|
+
import Info, { InfoHeadline1, InfoParagraph } from '../components/info';
|
|
13
13
|
import { LoadingDisplay } from '../components/loading-display';
|
|
14
14
|
import { NoDataDisplay } from '../components/no-data-display';
|
|
15
15
|
import { ResizeContainer } from '../components/resize-container';
|
|
@@ -94,18 +94,26 @@ const AggregatedDataTabs: FunctionComponent<AggregatedDataTabsProps> = ({ data,
|
|
|
94
94
|
|
|
95
95
|
const tabs = views.map((view) => getTab(view));
|
|
96
96
|
|
|
97
|
-
return <Tabs tabs={tabs} toolbar={<Toolbar data={data} />} />;
|
|
97
|
+
return <Tabs tabs={tabs} toolbar={<Toolbar data={data} fields={fields} />} />;
|
|
98
98
|
};
|
|
99
99
|
|
|
100
100
|
type ToolbarProps = {
|
|
101
101
|
data: AggregateData;
|
|
102
|
+
fields: string[];
|
|
102
103
|
};
|
|
103
104
|
|
|
104
|
-
const Toolbar: FunctionComponent<ToolbarProps> = ({ data }) => {
|
|
105
|
+
const Toolbar: FunctionComponent<ToolbarProps> = ({ data, fields }) => {
|
|
105
106
|
return (
|
|
106
107
|
<div class='flex flex-row'>
|
|
107
108
|
<CsvDownloadButton className='mx-1 btn btn-xs' getData={() => data} filename='aggregate.csv' />
|
|
108
|
-
<Info>
|
|
109
|
+
<Info>
|
|
110
|
+
<InfoHeadline1>Aggregated data</InfoHeadline1>
|
|
111
|
+
<InfoParagraph>
|
|
112
|
+
This table shows the number and proportion of sequences stratified by the following fields:{' '}
|
|
113
|
+
{fields.join(', ')}. The proportion is calculated with respect to the total count within the
|
|
114
|
+
filtered dataset.
|
|
115
|
+
</InfoParagraph>
|
|
116
|
+
</Info>
|
|
109
117
|
<Fullscreen />
|
|
110
118
|
</div>
|
|
111
119
|
);
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { type Meta, type StoryObj } from '@storybook/preact';
|
|
2
2
|
import { expect, fn, waitFor, within } from '@storybook/test';
|
|
3
|
+
import { type FunctionComponent } from 'preact';
|
|
4
|
+
import { useState } from 'preact/hooks';
|
|
3
5
|
|
|
4
6
|
import { type CheckboxItem, CheckboxSelector, type CheckboxSelectorProps } from './checkbox-selector';
|
|
5
7
|
|
|
@@ -14,12 +16,33 @@ const meta: Meta<CheckboxSelectorProps> = {
|
|
|
14
16
|
|
|
15
17
|
export default meta;
|
|
16
18
|
|
|
19
|
+
const WrapperWithState: FunctionComponent<CheckboxSelectorProps> = ({
|
|
20
|
+
items: initialItems,
|
|
21
|
+
label,
|
|
22
|
+
setItems: setItemsMock,
|
|
23
|
+
}) => {
|
|
24
|
+
const [items, setItems] = useState<CheckboxItem[]>(initialItems);
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<div className='w-32'>
|
|
28
|
+
<CheckboxSelector
|
|
29
|
+
items={items}
|
|
30
|
+
label={label}
|
|
31
|
+
setItems={(items: CheckboxItem[]) => {
|
|
32
|
+
setItemsMock(items);
|
|
33
|
+
setItems(items);
|
|
34
|
+
}}
|
|
35
|
+
/>
|
|
36
|
+
</div>
|
|
37
|
+
);
|
|
38
|
+
};
|
|
39
|
+
|
|
17
40
|
export const CheckboxSelectorStory: StoryObj<CheckboxSelectorProps> = {
|
|
18
41
|
render: (args) => {
|
|
19
42
|
let wrapperStateItems = args.items;
|
|
20
43
|
|
|
21
44
|
return (
|
|
22
|
-
<
|
|
45
|
+
<WrapperWithState
|
|
23
46
|
items={wrapperStateItems}
|
|
24
47
|
label={args.label}
|
|
25
48
|
setItems={(items: CheckboxItem[]) => {
|
|
@@ -37,20 +60,79 @@ export const CheckboxSelectorStory: StoryObj<CheckboxSelectorProps> = {
|
|
|
37
60
|
label: 'Some label',
|
|
38
61
|
setItems: fn(),
|
|
39
62
|
},
|
|
40
|
-
play: async ({ canvasElement, args }) => {
|
|
63
|
+
play: async ({ canvasElement, args, step }) => {
|
|
41
64
|
const canvas = within(canvasElement);
|
|
42
65
|
|
|
43
|
-
const open = () => canvas.getByText('Some label'
|
|
66
|
+
const open = () => canvas.getByText('Some label');
|
|
67
|
+
const selectAll = () => canvas.getByText('Select all');
|
|
68
|
+
const selectNone = () => canvas.getByText('Select none');
|
|
69
|
+
const firstItem = () => canvas.getByLabelText('item1');
|
|
44
70
|
open().click();
|
|
45
71
|
|
|
46
|
-
|
|
47
|
-
|
|
72
|
+
await step('Select one item', async () => {
|
|
73
|
+
firstItem().click();
|
|
48
74
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
75
|
+
await waitFor(() =>
|
|
76
|
+
expect(args.setItems).toHaveBeenCalledWith([
|
|
77
|
+
{ checked: true, label: 'item1' },
|
|
78
|
+
{ checked: false, label: 'item2' },
|
|
79
|
+
]),
|
|
80
|
+
);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
await step('Select all items with one item already selected', async () => {
|
|
84
|
+
selectAll().click();
|
|
85
|
+
|
|
86
|
+
await waitFor(() =>
|
|
87
|
+
expect(args.setItems).toHaveBeenCalledWith([
|
|
88
|
+
{ checked: true, label: 'item1' },
|
|
89
|
+
{ checked: true, label: 'item2' },
|
|
90
|
+
]),
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
await step('Deselect one item', async () => {
|
|
95
|
+
firstItem().click();
|
|
96
|
+
|
|
97
|
+
await waitFor(() =>
|
|
98
|
+
expect(args.setItems).toHaveBeenCalledWith([
|
|
99
|
+
{ checked: false, label: 'item1' },
|
|
100
|
+
{ checked: true, label: 'item2' },
|
|
101
|
+
]),
|
|
102
|
+
);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
await step('Select none with one item already selected', async () => {
|
|
106
|
+
selectNone().click();
|
|
107
|
+
|
|
108
|
+
await waitFor(() =>
|
|
109
|
+
expect(args.setItems).toHaveBeenCalledWith([
|
|
110
|
+
{ checked: false, label: 'item1' },
|
|
111
|
+
{ checked: false, label: 'item2' },
|
|
112
|
+
]),
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
await step('Select all items with none selected', async () => {
|
|
117
|
+
selectAll().click();
|
|
118
|
+
|
|
119
|
+
await waitFor(() =>
|
|
120
|
+
expect(args.setItems).toHaveBeenCalledWith([
|
|
121
|
+
{ checked: true, label: 'item1' },
|
|
122
|
+
{ checked: true, label: 'item2' },
|
|
123
|
+
]),
|
|
124
|
+
);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
await step('Select none with all items selected', async () => {
|
|
128
|
+
selectNone().click();
|
|
129
|
+
|
|
130
|
+
await waitFor(() =>
|
|
131
|
+
expect(args.setItems).toHaveBeenCalledWith([
|
|
132
|
+
{ checked: false, label: 'item1' },
|
|
133
|
+
{ checked: false, label: 'item2' },
|
|
134
|
+
]),
|
|
135
|
+
);
|
|
136
|
+
});
|
|
55
137
|
},
|
|
56
138
|
};
|
|
@@ -18,6 +18,25 @@ export const CheckboxSelector = <Item extends CheckboxItem>({
|
|
|
18
18
|
}: CheckboxSelectorProps<Item>) => {
|
|
19
19
|
return (
|
|
20
20
|
<Dropdown buttonTitle={label} placement={'bottom-start'}>
|
|
21
|
+
<button
|
|
22
|
+
className='btn btn-xs btn-ghost'
|
|
23
|
+
onClick={() => {
|
|
24
|
+
const newItems = items.map((item) => ({ ...item, checked: true }));
|
|
25
|
+
setItems(newItems);
|
|
26
|
+
}}
|
|
27
|
+
>
|
|
28
|
+
Select all
|
|
29
|
+
</button>
|
|
30
|
+
<button
|
|
31
|
+
className='btn btn-xs btn-ghost'
|
|
32
|
+
onClick={() => {
|
|
33
|
+
const newItems = items.map((item) => ({ ...item, checked: false }));
|
|
34
|
+
setItems(newItems);
|
|
35
|
+
}}
|
|
36
|
+
>
|
|
37
|
+
Select none
|
|
38
|
+
</button>
|
|
39
|
+
<div className='divider mt-0 mb-0' />
|
|
21
40
|
<ul>
|
|
22
41
|
{items.map((item, index) => (
|
|
23
42
|
<li className='flex flex-row items-center' key={item.label}>
|
|
@@ -10,8 +10,10 @@ export const ColorScaleSelectorDropdown: FunctionComponent<ColorScaleSelectorDro
|
|
|
10
10
|
setColorScale,
|
|
11
11
|
}) => {
|
|
12
12
|
return (
|
|
13
|
-
<
|
|
14
|
-
<
|
|
15
|
-
|
|
13
|
+
<div className='w-20'>
|
|
14
|
+
<Dropdown buttonTitle={`Color scale`} placement={'bottom-start'}>
|
|
15
|
+
<ColorScaleSelector colorScale={colorScale} setColorScale={setColorScale} />
|
|
16
|
+
</Dropdown>
|
|
17
|
+
</div>
|
|
16
18
|
);
|
|
17
19
|
};
|
|
@@ -28,13 +28,13 @@ export const Dropdown: FunctionComponent<DropdownProps> = ({ children, buttonTit
|
|
|
28
28
|
};
|
|
29
29
|
|
|
30
30
|
return (
|
|
31
|
-
|
|
32
|
-
<button type='button' className='btn btn-xs whitespace-nowrap' onClick={toggle} ref={referenceRef}>
|
|
31
|
+
<>
|
|
32
|
+
<button type='button' className='btn btn-xs whitespace-nowrap w-full' onClick={toggle} ref={referenceRef}>
|
|
33
33
|
{buttonTitle}
|
|
34
34
|
</button>
|
|
35
35
|
<div ref={floatingRef} className={`${dropdownClass} ${showContent ? '' : 'hidden'}`}>
|
|
36
36
|
{children}
|
|
37
37
|
</div>
|
|
38
|
-
|
|
38
|
+
</>
|
|
39
39
|
);
|
|
40
40
|
};
|
|
@@ -16,7 +16,7 @@ const Info: FunctionComponent<InfoProps> = ({ children }) => {
|
|
|
16
16
|
?
|
|
17
17
|
</button>
|
|
18
18
|
<dialog ref={dialogRef} className={'modal modal-bottom sm:modal-middle'}>
|
|
19
|
-
<div className='modal-box'>
|
|
19
|
+
<div className='modal-box sm:max-w-5xl'>
|
|
20
20
|
<form method='dialog'>
|
|
21
21
|
<button className='btn btn-sm btn-circle btn-ghost absolute right-2 top-2'>✕</button>
|
|
22
22
|
</form>
|
|
@@ -55,4 +55,91 @@ export const InfoLink: FunctionComponent<{ href: string }> = ({ children, href }
|
|
|
55
55
|
);
|
|
56
56
|
};
|
|
57
57
|
|
|
58
|
+
export type InfoComponentCodeProps = {
|
|
59
|
+
componentName: string;
|
|
60
|
+
params: object;
|
|
61
|
+
lapisUrl: string;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const InfoComponentCode: FunctionComponent<InfoComponentCodeProps> = ({ componentName, params, lapisUrl }) => {
|
|
65
|
+
const componentCode = componentParametersToCode(componentName, params, lapisUrl);
|
|
66
|
+
const codePenData = {
|
|
67
|
+
title: 'GenSpectrum dashboard component',
|
|
68
|
+
html: generateFullExampleCode(componentCode, componentName),
|
|
69
|
+
layout: 'left',
|
|
70
|
+
editors: '100',
|
|
71
|
+
};
|
|
72
|
+
return (
|
|
73
|
+
<>
|
|
74
|
+
<InfoHeadline2>Use this component yourself</InfoHeadline2>
|
|
75
|
+
<InfoParagraph>
|
|
76
|
+
This component was created using the following parameters:
|
|
77
|
+
<div className='p-4 border border-gray-200 rounded-lg overflow-x-auto'>
|
|
78
|
+
<pre>
|
|
79
|
+
<code>{componentCode}</code>
|
|
80
|
+
</pre>
|
|
81
|
+
</div>
|
|
82
|
+
</InfoParagraph>
|
|
83
|
+
<InfoParagraph>
|
|
84
|
+
You can add this component to your own website using the{' '}
|
|
85
|
+
<InfoLink href='https://github.com/GenSpectrum/dashboard-components'>
|
|
86
|
+
GenSpectrum dashboard components library
|
|
87
|
+
</InfoLink>{' '}
|
|
88
|
+
and the code from above.
|
|
89
|
+
</InfoParagraph>
|
|
90
|
+
<InfoParagraph>
|
|
91
|
+
<form action='https://codepen.io/pen/define' method='POST' target='_blank'>
|
|
92
|
+
<input
|
|
93
|
+
type='hidden'
|
|
94
|
+
name='data'
|
|
95
|
+
value={JSON.stringify(codePenData).replace(/"/g, '"').replace(/'/g, ''')}
|
|
96
|
+
/>
|
|
97
|
+
|
|
98
|
+
<button className='text-blue-600 hover:text-blue-800' type='submit'>
|
|
99
|
+
Click here to try it out on CodePen.
|
|
100
|
+
</button>
|
|
101
|
+
</form>
|
|
102
|
+
</InfoParagraph>
|
|
103
|
+
</>
|
|
104
|
+
);
|
|
105
|
+
};
|
|
106
|
+
|
|
58
107
|
export default Info;
|
|
108
|
+
|
|
109
|
+
function componentParametersToCode(componentName: string, params: object, lapisUrl: string) {
|
|
110
|
+
const stringifyIfNeeded = (value: unknown) => {
|
|
111
|
+
return typeof value === 'object' ? JSON.stringify(value) : value;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const attributes = indentLines(
|
|
115
|
+
Object.entries(params)
|
|
116
|
+
.map(([key, value]) => `${key}='${stringifyIfNeeded(value)}'`)
|
|
117
|
+
.join('\n'),
|
|
118
|
+
4,
|
|
119
|
+
);
|
|
120
|
+
return `<gs-app lapis="${lapisUrl}">\n <gs-${componentName}\n${attributes}\n />\n</gs-app>`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function generateFullExampleCode(componentCode: string, componentName: string) {
|
|
124
|
+
const storyBookPath = `/docs/visualization-${componentName}--docs`;
|
|
125
|
+
return `<html>
|
|
126
|
+
<head>
|
|
127
|
+
<script type="module" src="https://unpkg.com/@genspectrum/dashboard-components@latest/dist/dashboard-components.js"></script>
|
|
128
|
+
<link rel="stylesheet" href="https://unpkg.com/@genspectrum/dashboard-components@latest/dist/style.css" />
|
|
129
|
+
</head>
|
|
130
|
+
|
|
131
|
+
<body>
|
|
132
|
+
<!-- Component documentation: https://genspectrum.github.io/dashboard-components/?path=${storyBookPath} -->
|
|
133
|
+
${indentLines(componentCode, 2)}
|
|
134
|
+
</body>
|
|
135
|
+
</html>
|
|
136
|
+
`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function indentLines(text: string, numberSpaces: number) {
|
|
140
|
+
const spaces = ' '.repeat(numberSpaces);
|
|
141
|
+
return text
|
|
142
|
+
.split('\n')
|
|
143
|
+
.map((line) => spaces + line)
|
|
144
|
+
.join('\n');
|
|
145
|
+
}
|