@genspectrum/dashboard-components 0.1.3 → 0.1.5
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/custom-elements.json +488 -117
- package/dist/dashboard-components.js +904 -466
- package/dist/dashboard-components.js.map +1 -1
- package/dist/genspectrum-components.d.ts +473 -67
- package/dist/style.css +273 -153
- package/package.json +11 -7
- package/src/preact/aggregatedData/aggregate.stories.tsx +7 -5
- package/src/preact/aggregatedData/aggregate.tsx +16 -7
- package/src/preact/components/ReferenceGenomesAwaiter.tsx +25 -0
- package/src/preact/components/csv-download-button.tsx +8 -2
- package/src/preact/components/headline.stories.tsx +19 -1
- package/src/preact/components/headline.tsx +25 -5
- package/src/preact/components/info.stories.tsx +24 -3
- package/src/preact/components/info.tsx +49 -5
- package/src/preact/components/min-max-range-slider.tsx +4 -4
- package/src/preact/components/percent-intput.tsx +2 -3
- package/src/preact/components/resize-container.tsx +23 -0
- package/src/preact/components/table.tsx +1 -0
- package/src/preact/components/tabs.stories.tsx +2 -2
- package/src/preact/components/tabs.tsx +47 -24
- package/src/preact/dateRangeSelector/date-range-selector.stories.tsx +36 -4
- package/src/preact/dateRangeSelector/date-range-selector.tsx +67 -53
- package/src/preact/locationFilter/location-filter.tsx +2 -2
- package/src/preact/mutationComparison/getMutationComparisonTableData.spec.ts +5 -5
- package/src/preact/mutationComparison/getMutationComparisonTableData.ts +45 -10
- package/src/preact/mutationComparison/mutation-comparison-table.tsx +20 -22
- package/src/preact/mutationComparison/mutation-comparison-venn.tsx +6 -3
- package/src/preact/mutationComparison/mutation-comparison.stories.tsx +11 -1
- package/src/preact/mutationComparison/mutation-comparison.tsx +16 -7
- package/src/preact/mutationFilter/mutation-filter.stories.tsx +70 -31
- package/src/preact/mutationFilter/mutation-filter.tsx +62 -14
- package/src/preact/mutations/getInsertionsTableData.spec.ts +6 -4
- package/src/preact/mutations/getInsertionsTableData.ts +1 -1
- package/src/preact/mutations/getMutationsTableData.spec.ts +9 -19
- package/src/preact/mutations/getMutationsTableData.ts +1 -1
- package/src/preact/mutations/mutations-insertions-table.tsx +3 -1
- package/src/preact/mutations/mutations-table.tsx +3 -1
- package/src/preact/mutations/mutations.stories.tsx +11 -1
- package/src/preact/mutations/mutations.tsx +24 -7
- package/src/preact/prevalenceOverTime/prevalence-over-time-bar-chart.tsx +1 -0
- package/src/preact/prevalenceOverTime/prevalence-over-time-bubble-chart.tsx +1 -0
- package/src/preact/prevalenceOverTime/prevalence-over-time-line-chart.tsx +1 -0
- package/src/preact/prevalenceOverTime/prevalence-over-time.stories.tsx +8 -0
- package/src/preact/prevalenceOverTime/prevalence-over-time.tsx +31 -13
- package/src/preact/relativeGrowthAdvantage/relative-growth-advantage-chart.tsx +8 -5
- package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.stories.tsx +15 -0
- package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.tsx +62 -12
- package/src/preact/shared/sort/sortInsertions.spec.ts +11 -10
- package/src/preact/shared/sort/sortInsertions.ts +10 -17
- package/src/preact/shared/sort/sortSubstitutionsAndDeletions.spec.ts +19 -10
- package/src/preact/shared/sort/sortSubstitutionsAndDeletions.ts +45 -12
- package/src/preact/textInput/text-input.stories.tsx +22 -1
- package/src/preact/textInput/text-input.tsx +3 -1
- package/src/utils/typeAssertions.spec.ts +31 -0
- package/src/utils/typeAssertions.ts +16 -0
- package/src/web-components/PreactLitAdapter.tsx +0 -1
- package/src/web-components/app.stories.ts +129 -0
- package/src/web-components/app.ts +27 -6
- package/src/web-components/display/aggregate-component.stories.ts +24 -11
- package/src/web-components/display/aggregate-component.tsx +26 -5
- package/src/web-components/display/mutation-comparison-component.stories.ts +32 -11
- package/src/web-components/display/mutation-comparison-component.tsx +79 -4
- package/src/web-components/display/mutations-component.stories.ts +40 -19
- package/src/web-components/display/mutations-component.tsx +71 -4
- package/src/web-components/display/prevalence-over-time-component.stories.ts +44 -18
- package/src/web-components/display/prevalence-over-time-component.tsx +105 -5
- package/src/web-components/display/relative-growth-advantage-component.stories.ts +32 -10
- package/src/web-components/display/relative-growth-advantage-component.tsx +66 -3
- package/src/web-components/input/date-range-selector-component.stories.ts +51 -9
- package/src/web-components/input/date-range-selector-component.tsx +69 -4
- package/src/web-components/input/location-filter-component.stories.ts +15 -4
- package/src/web-components/input/location-filter-component.tsx +2 -6
- package/src/web-components/input/mutation-filter-component.stories.ts +33 -12
- package/src/web-components/input/mutation-filter-component.tsx +60 -4
- package/src/web-components/input/text-input-component.stories.ts +26 -6
- package/src/web-components/input/text-input-component.tsx +34 -3
- package/src/web-components/display/aggregate-component.mdx +0 -25
- package/src/web-components/input/location-filter.mdx +0 -25
|
@@ -13,9 +13,10 @@ import { ConfidenceIntervalSelector } from '../components/confidence-interval-se
|
|
|
13
13
|
import { CsvDownloadButton } from '../components/csv-download-button';
|
|
14
14
|
import { ErrorDisplay } from '../components/error-display';
|
|
15
15
|
import Headline from '../components/headline';
|
|
16
|
-
import Info from '../components/info';
|
|
16
|
+
import Info, { InfoHeadline1, InfoParagraph } from '../components/info';
|
|
17
17
|
import { LoadingDisplay } from '../components/loading-display';
|
|
18
18
|
import { NoDataDisplay } from '../components/no-data-display';
|
|
19
|
+
import { ResizeContainer, type Size } from '../components/resize-container';
|
|
19
20
|
import { ScalingSelector } from '../components/scaling-selector';
|
|
20
21
|
import Tabs from '../components/tabs';
|
|
21
22
|
import { type ConfidenceIntervalMethod } from '../shared/charts/confideceInterval';
|
|
@@ -31,6 +32,8 @@ export interface PrevalenceOverTimeProps {
|
|
|
31
32
|
smoothingWindow: number;
|
|
32
33
|
views: View[];
|
|
33
34
|
confidenceIntervalMethods: ConfidenceIntervalMethod[];
|
|
35
|
+
size?: Size;
|
|
36
|
+
headline?: string;
|
|
34
37
|
}
|
|
35
38
|
|
|
36
39
|
export const PrevalenceOverTime: FunctionComponent<PrevalenceOverTimeProps> = ({
|
|
@@ -40,6 +43,8 @@ export const PrevalenceOverTime: FunctionComponent<PrevalenceOverTimeProps> = ({
|
|
|
40
43
|
smoothingWindow,
|
|
41
44
|
views,
|
|
42
45
|
confidenceIntervalMethods,
|
|
46
|
+
size,
|
|
47
|
+
headline = 'Prevalence over time',
|
|
43
48
|
}) => {
|
|
44
49
|
const lapis = useContext(LapisUrlContext);
|
|
45
50
|
|
|
@@ -48,8 +53,6 @@ export const PrevalenceOverTime: FunctionComponent<PrevalenceOverTimeProps> = ({
|
|
|
48
53
|
[lapis, numerator, denominator, granularity, smoothingWindow],
|
|
49
54
|
);
|
|
50
55
|
|
|
51
|
-
const headline = 'Prevalence over time';
|
|
52
|
-
|
|
53
56
|
if (isLoading) {
|
|
54
57
|
return (
|
|
55
58
|
<Headline heading={headline}>
|
|
@@ -75,14 +78,16 @@ export const PrevalenceOverTime: FunctionComponent<PrevalenceOverTimeProps> = ({
|
|
|
75
78
|
}
|
|
76
79
|
|
|
77
80
|
return (
|
|
78
|
-
<
|
|
79
|
-
<
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
81
|
+
<ResizeContainer size={size} defaultSize={{ height: '600px', width: '100%' }}>
|
|
82
|
+
<Headline heading={headline}>
|
|
83
|
+
<PrevalenceOverTimeTabs
|
|
84
|
+
views={views}
|
|
85
|
+
data={data}
|
|
86
|
+
granularity={granularity}
|
|
87
|
+
confidenceIntervalMethods={confidenceIntervalMethods}
|
|
88
|
+
/>
|
|
89
|
+
</Headline>
|
|
90
|
+
</ResizeContainer>
|
|
86
91
|
);
|
|
87
92
|
};
|
|
88
93
|
|
|
@@ -134,7 +139,10 @@ const PrevalenceOverTimeTabs: FunctionComponent<PrevalenceOverTimeTabsProps> = (
|
|
|
134
139
|
content: <PrevalenceOverTimeBubbleChart data={data} yAxisScaleType={yAxisScaleType} />,
|
|
135
140
|
};
|
|
136
141
|
case 'table':
|
|
137
|
-
return {
|
|
142
|
+
return {
|
|
143
|
+
title: 'Table',
|
|
144
|
+
content: <PrevalenceOverTimeTable data={data} granularity={granularity} />,
|
|
145
|
+
};
|
|
138
146
|
}
|
|
139
147
|
};
|
|
140
148
|
|
|
@@ -194,9 +202,19 @@ const Toolbar: FunctionComponent<ToolbarProps> = ({
|
|
|
194
202
|
getData={() => getPrevalenceOverTimeTableData(data, granularity)}
|
|
195
203
|
filename='prevalence-over-time.csv'
|
|
196
204
|
/>
|
|
197
|
-
|
|
205
|
+
|
|
206
|
+
<PrevalenceOverTimeInfo />
|
|
198
207
|
</div>
|
|
199
208
|
);
|
|
200
209
|
};
|
|
201
210
|
|
|
211
|
+
const PrevalenceOverTimeInfo: FunctionComponent = () => {
|
|
212
|
+
return (
|
|
213
|
+
<Info size={{ width: '600px', height: '30vh' }}>
|
|
214
|
+
<InfoHeadline1>Prevalence over time</InfoHeadline1>
|
|
215
|
+
<InfoParagraph>Prevalence over time info.</InfoParagraph>
|
|
216
|
+
</Info>
|
|
217
|
+
);
|
|
218
|
+
};
|
|
219
|
+
|
|
202
220
|
export default PrevalenceOverTime;
|
|
@@ -37,6 +37,7 @@ const RelativeGrowthAdvantageChart = ({ data, yAxisScaleType }: RelativeGrowthAd
|
|
|
37
37
|
datasets: datasets(data),
|
|
38
38
|
},
|
|
39
39
|
options: {
|
|
40
|
+
maintainAspectRatio: false,
|
|
40
41
|
animation: false,
|
|
41
42
|
scales: {
|
|
42
43
|
y: getYAxisScale(yAxisScaleType),
|
|
@@ -51,13 +52,15 @@ const RelativeGrowthAdvantageChart = ({ data, yAxisScaleType }: RelativeGrowthAd
|
|
|
51
52
|
};
|
|
52
53
|
|
|
53
54
|
return (
|
|
54
|
-
|
|
55
|
-
<
|
|
56
|
-
|
|
55
|
+
<div className='flex flex-col h-full'>
|
|
56
|
+
<div className='flex-1'>
|
|
57
|
+
<GsChart configuration={config} />
|
|
58
|
+
</div>
|
|
59
|
+
<p>
|
|
57
60
|
Advantage: {(data.params.fd.value * 100).toFixed(2)}% ({(data.params.fd.ciLower * 100).toFixed(2)}% -{' '}
|
|
58
61
|
{(data.params.fd.ciUpper * 100).toFixed(2)}%)
|
|
59
|
-
</
|
|
60
|
-
|
|
62
|
+
</p>
|
|
63
|
+
</div>
|
|
61
64
|
);
|
|
62
65
|
};
|
|
63
66
|
|
|
@@ -10,6 +10,17 @@ export default {
|
|
|
10
10
|
parameters: {
|
|
11
11
|
fetchMock: {},
|
|
12
12
|
},
|
|
13
|
+
argTypes: {
|
|
14
|
+
numerator: { control: 'object' },
|
|
15
|
+
denominator: { control: 'object' },
|
|
16
|
+
generationTime: { control: 'number' },
|
|
17
|
+
views: {
|
|
18
|
+
options: ['line'],
|
|
19
|
+
control: { type: 'check' },
|
|
20
|
+
},
|
|
21
|
+
size: [{ control: 'object' }],
|
|
22
|
+
headline: { control: 'text' },
|
|
23
|
+
},
|
|
13
24
|
};
|
|
14
25
|
|
|
15
26
|
export const Primary = {
|
|
@@ -20,6 +31,8 @@ export const Primary = {
|
|
|
20
31
|
denominator={args.denominator}
|
|
21
32
|
generationTime={args.generationTime}
|
|
22
33
|
views={args.views}
|
|
34
|
+
size={args.size}
|
|
35
|
+
headline={args.headline}
|
|
23
36
|
/>
|
|
24
37
|
</LapisUrlContext.Provider>
|
|
25
38
|
),
|
|
@@ -28,6 +41,8 @@ export const Primary = {
|
|
|
28
41
|
denominator: { country: 'Switzerland', dateFrom: '2020-12-01', dateTo: '2021-03-01' },
|
|
29
42
|
generationTime: 7,
|
|
30
43
|
views: ['line'],
|
|
44
|
+
size: { width: '100%', height: '700px' },
|
|
45
|
+
headline: 'Relative growth advantage',
|
|
31
46
|
},
|
|
32
47
|
parameters: {
|
|
33
48
|
fetchMock: {
|
|
@@ -10,9 +10,10 @@ import { type LapisFilter } from '../../types';
|
|
|
10
10
|
import { LapisUrlContext } from '../LapisUrlContext';
|
|
11
11
|
import { ErrorDisplay } from '../components/error-display';
|
|
12
12
|
import Headline from '../components/headline';
|
|
13
|
-
import Info from '../components/info';
|
|
13
|
+
import Info, { InfoHeadline1, InfoHeadline2, InfoLink, InfoParagraph } from '../components/info';
|
|
14
14
|
import { LoadingDisplay } from '../components/loading-display';
|
|
15
15
|
import { NoDataDisplay } from '../components/no-data-display';
|
|
16
|
+
import { ResizeContainer, type Size } from '../components/resize-container';
|
|
16
17
|
import { ScalingSelector } from '../components/scaling-selector';
|
|
17
18
|
import Tabs from '../components/tabs';
|
|
18
19
|
import { type ScaleType } from '../shared/charts/getYAxisScale';
|
|
@@ -25,6 +26,8 @@ export interface RelativeGrowthAdvantageProps {
|
|
|
25
26
|
denominator: LapisFilter;
|
|
26
27
|
generationTime: number;
|
|
27
28
|
views: View[];
|
|
29
|
+
size?: Size;
|
|
30
|
+
headline?: string;
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
export const RelativeGrowthAdvantage: FunctionComponent<RelativeGrowthAdvantageProps> = ({
|
|
@@ -32,6 +35,8 @@ export const RelativeGrowthAdvantage: FunctionComponent<RelativeGrowthAdvantageP
|
|
|
32
35
|
denominator,
|
|
33
36
|
generationTime,
|
|
34
37
|
views,
|
|
38
|
+
size,
|
|
39
|
+
headline = 'Relative growth advantage',
|
|
35
40
|
}) => {
|
|
36
41
|
const lapis = useContext(LapisUrlContext);
|
|
37
42
|
const [yAxisScaleType, setYAxisScaleType] = useState<ScaleType>('linear');
|
|
@@ -41,7 +46,6 @@ export const RelativeGrowthAdvantage: FunctionComponent<RelativeGrowthAdvantageP
|
|
|
41
46
|
[lapis, numerator, denominator, generationTime, views],
|
|
42
47
|
);
|
|
43
48
|
|
|
44
|
-
const headline = 'Relative growth advantage';
|
|
45
49
|
if (isLoading) {
|
|
46
50
|
return (
|
|
47
51
|
<Headline heading={headline}>
|
|
@@ -67,14 +71,17 @@ export const RelativeGrowthAdvantage: FunctionComponent<RelativeGrowthAdvantageP
|
|
|
67
71
|
}
|
|
68
72
|
|
|
69
73
|
return (
|
|
70
|
-
<
|
|
71
|
-
<
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
74
|
+
<ResizeContainer size={size} defaultSize={{ height: '700px', width: '100%' }}>
|
|
75
|
+
<Headline heading={headline}>
|
|
76
|
+
<RelativeGrowthAdvantageTabs
|
|
77
|
+
data={data}
|
|
78
|
+
yAxisScaleType={yAxisScaleType}
|
|
79
|
+
setYAxisScaleType={setYAxisScaleType}
|
|
80
|
+
views={views}
|
|
81
|
+
generationTime={generationTime}
|
|
82
|
+
/>
|
|
83
|
+
</Headline>
|
|
84
|
+
</ResizeContainer>
|
|
78
85
|
);
|
|
79
86
|
};
|
|
80
87
|
|
|
@@ -83,6 +90,7 @@ type RelativeGrowthAdvantageTabsProps = {
|
|
|
83
90
|
yAxisScaleType: ScaleType;
|
|
84
91
|
setYAxisScaleType: (scaleType: ScaleType) => void;
|
|
85
92
|
views: View[];
|
|
93
|
+
generationTime: number;
|
|
86
94
|
};
|
|
87
95
|
|
|
88
96
|
const RelativeGrowthAdvantageTabs: FunctionComponent<RelativeGrowthAdvantageTabsProps> = ({
|
|
@@ -90,6 +98,7 @@ const RelativeGrowthAdvantageTabs: FunctionComponent<RelativeGrowthAdvantageTabs
|
|
|
90
98
|
yAxisScaleType,
|
|
91
99
|
setYAxisScaleType,
|
|
92
100
|
views,
|
|
101
|
+
generationTime,
|
|
93
102
|
}) => {
|
|
94
103
|
const getTab = (view: View) => {
|
|
95
104
|
switch (view) {
|
|
@@ -112,7 +121,11 @@ const RelativeGrowthAdvantageTabs: FunctionComponent<RelativeGrowthAdvantageTabs
|
|
|
112
121
|
|
|
113
122
|
const tabs = views.map((view) => getTab(view));
|
|
114
123
|
const toolbar = () => (
|
|
115
|
-
<RelativeGrowthAdvantageToolbar
|
|
124
|
+
<RelativeGrowthAdvantageToolbar
|
|
125
|
+
generationTime={generationTime}
|
|
126
|
+
yAxisScaleType={yAxisScaleType}
|
|
127
|
+
setYAxisScaleType={setYAxisScaleType}
|
|
128
|
+
/>
|
|
116
129
|
);
|
|
117
130
|
|
|
118
131
|
return <Tabs tabs={tabs} toolbar={toolbar} />;
|
|
@@ -121,16 +134,53 @@ const RelativeGrowthAdvantageTabs: FunctionComponent<RelativeGrowthAdvantageTabs
|
|
|
121
134
|
type RelativeGrowthAdvantageToolbarProps = {
|
|
122
135
|
yAxisScaleType: ScaleType;
|
|
123
136
|
setYAxisScaleType: (scaleType: ScaleType) => void;
|
|
137
|
+
generationTime: number;
|
|
124
138
|
};
|
|
125
139
|
|
|
126
140
|
const RelativeGrowthAdvantageToolbar: FunctionComponent<RelativeGrowthAdvantageToolbarProps> = ({
|
|
127
141
|
yAxisScaleType,
|
|
128
142
|
setYAxisScaleType,
|
|
143
|
+
generationTime,
|
|
129
144
|
}) => {
|
|
130
145
|
return (
|
|
131
146
|
<div class='flex'>
|
|
132
147
|
<ScalingSelector yAxisScaleType={yAxisScaleType} setYAxisScaleType={setYAxisScaleType} />
|
|
133
|
-
<
|
|
148
|
+
<RelativeGrowthAdvantageInfo generationTime={generationTime} />
|
|
134
149
|
</div>
|
|
135
150
|
);
|
|
136
151
|
};
|
|
152
|
+
|
|
153
|
+
const RelativeGrowthAdvantageInfo: FunctionComponent<{ generationTime: number }> = ({ generationTime }) => {
|
|
154
|
+
return (
|
|
155
|
+
<Info size={{ width: '600px', height: '30vh' }}>
|
|
156
|
+
<InfoHeadline1>Relative growth advantage</InfoHeadline1>
|
|
157
|
+
<InfoParagraph>
|
|
158
|
+
If variants spread pre-dominantly by local transmission across demographic groups, this estimate
|
|
159
|
+
reflects the relative viral intrinsic growth advantage of the focal variant in the selected country and
|
|
160
|
+
time frame. We report the relative growth advantage per {generationTime} days (in percentage; 0% means
|
|
161
|
+
equal growth). Importantly, the relative growth advantage estimate reflects the advantage compared to
|
|
162
|
+
the co-circulating variants. Thus, as new variants spread, the advantage of the focal variant may
|
|
163
|
+
decrease. Different mechanisms can alter the intrinsic growth rate, including an intrinsic transmission
|
|
164
|
+
advantage, immune evasion, and a prolonged infectious period. When absolute numbers of a variant are
|
|
165
|
+
low, the growth advantage may merely reflect the current importance of introductions from abroad or the
|
|
166
|
+
variant spreading in a particular demographic group. In this case, the estimate does not provide
|
|
167
|
+
information on any intrinsic fitness advantages.
|
|
168
|
+
</InfoParagraph>
|
|
169
|
+
<InfoParagraph>
|
|
170
|
+
Example: Assume that 10 infections from the focal variant and 100 infections from the co-circulating
|
|
171
|
+
variants occur today and that the focal variant has a relative growth advantage of 50%. Then, if the
|
|
172
|
+
number of new infections from the co-circulating variants remains at 100 in {generationTime} days from
|
|
173
|
+
today, we expect the number of new infections from the focal variant to be 15.
|
|
174
|
+
</InfoParagraph>
|
|
175
|
+
|
|
176
|
+
<InfoHeadline2>Reference</InfoHeadline2>
|
|
177
|
+
<InfoParagraph>
|
|
178
|
+
Chen, Chaoran, et al. "Quantification of the spread of SARS-CoV-2 variant B.1.1.7 in Switzerland."
|
|
179
|
+
Epidemics (2021); doi:{' '}
|
|
180
|
+
<InfoLink href='https://www.sciencedirect.com/science/article/pii/S1755436521000335?via=ihub'>
|
|
181
|
+
10.1016/j.epidem.2021.100480
|
|
182
|
+
</InfoLink>
|
|
183
|
+
</InfoParagraph>
|
|
184
|
+
</Info>
|
|
185
|
+
);
|
|
186
|
+
};
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import { describe, expect, test } from 'vitest';
|
|
2
2
|
|
|
3
3
|
import { sortInsertions } from './sortInsertions';
|
|
4
|
+
import { Insertion } from '../../../utils/mutations';
|
|
4
5
|
|
|
5
6
|
describe('sortInsertions with no segments', () => {
|
|
6
7
|
test('should sort for positions first', () => {
|
|
7
|
-
const a = '
|
|
8
|
-
const b = '
|
|
8
|
+
const a = new Insertion(undefined, 1, 'A');
|
|
9
|
+
const b = new Insertion(undefined, 2, 'A');
|
|
9
10
|
|
|
10
11
|
expect(sortInsertions(a, b)).toBeLessThan(0);
|
|
11
12
|
expect(sortInsertions(b, a)).toBeGreaterThan(0);
|
|
12
13
|
});
|
|
13
14
|
|
|
14
15
|
test('should sort for symbols second', () => {
|
|
15
|
-
const a = '
|
|
16
|
-
const b = '
|
|
16
|
+
const a = new Insertion(undefined, 1, 'A');
|
|
17
|
+
const b = new Insertion(undefined, 1, 'B');
|
|
17
18
|
|
|
18
19
|
expect(sortInsertions(a, b)).toBeLessThan(0);
|
|
19
20
|
expect(sortInsertions(b, a)).toBeGreaterThan(0);
|
|
@@ -22,24 +23,24 @@ describe('sortInsertions with no segments', () => {
|
|
|
22
23
|
|
|
23
24
|
describe('sortInsertions with segments', () => {
|
|
24
25
|
test('should sort for segments first', () => {
|
|
25
|
-
const a = '
|
|
26
|
-
const b = '
|
|
26
|
+
const a = new Insertion('AA1', 1, 'A');
|
|
27
|
+
const b = new Insertion('BB1', 1, 'A');
|
|
27
28
|
|
|
28
29
|
expect(sortInsertions(a, b)).toBeLessThan(0);
|
|
29
30
|
expect(sortInsertions(b, a)).toBeGreaterThan(0);
|
|
30
31
|
});
|
|
31
32
|
|
|
32
33
|
test('should sort for positions second', () => {
|
|
33
|
-
const a = '
|
|
34
|
-
const b = '
|
|
34
|
+
const a = new Insertion('AA1', 1, 'A');
|
|
35
|
+
const b = new Insertion('AA1', 2, 'A');
|
|
35
36
|
|
|
36
37
|
expect(sortInsertions(a, b)).toBeLessThan(0);
|
|
37
38
|
expect(sortInsertions(b, a)).toBeGreaterThan(0);
|
|
38
39
|
});
|
|
39
40
|
|
|
40
41
|
test('should sort for symbols third', () => {
|
|
41
|
-
const a = '
|
|
42
|
-
const b = '
|
|
42
|
+
const a = new Insertion('AA1', 1, 'A');
|
|
43
|
+
const b = new Insertion('AA1', 1, 'B');
|
|
43
44
|
|
|
44
45
|
expect(sortInsertions(a, b)).toBeLessThan(0);
|
|
45
46
|
expect(sortInsertions(b, a)).toBeGreaterThan(0);
|
|
@@ -1,21 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { comparePositions, compareSegments } from './sortSubstitutionsAndDeletions';
|
|
2
|
+
import { type Insertion } from '../../../utils/mutations';
|
|
2
3
|
|
|
3
|
-
export const sortInsertions = (a:
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
export const sortInsertions = (a: Insertion, b: Insertion) => {
|
|
5
|
+
if (a.segment !== b.segment) {
|
|
6
|
+
return compareSegments(a.segment, b.segment);
|
|
7
|
+
}
|
|
6
8
|
|
|
7
|
-
if (
|
|
8
|
-
|
|
9
|
-
const segmentB = insertionB.segment;
|
|
10
|
-
if (segmentA !== undefined && segmentB !== undefined && segmentA !== segmentB) {
|
|
11
|
-
return segmentA.localeCompare(segmentB);
|
|
12
|
-
}
|
|
13
|
-
const positionA = insertionA.position;
|
|
14
|
-
const positionB = insertionB.position;
|
|
15
|
-
if (positionA !== positionB) {
|
|
16
|
-
return positionA - positionB;
|
|
17
|
-
}
|
|
18
|
-
return insertionA.insertedSymbols.localeCompare(insertionB.insertedSymbols);
|
|
9
|
+
if (a.position !== b.position) {
|
|
10
|
+
return comparePositions(a.position, b.position);
|
|
19
11
|
}
|
|
20
|
-
|
|
12
|
+
|
|
13
|
+
return a.insertedSymbols.localeCompare(b.insertedSymbols);
|
|
21
14
|
};
|
|
@@ -1,19 +1,28 @@
|
|
|
1
1
|
import { describe, expect, test } from 'vitest';
|
|
2
2
|
|
|
3
3
|
import { sortSubstitutionsAndDeletions } from './sortSubstitutionsAndDeletions';
|
|
4
|
+
import { Deletion, Substitution } from '../../../utils/mutations';
|
|
4
5
|
|
|
5
6
|
describe('sortSubstitutionsAndDeletions with no segments', () => {
|
|
6
7
|
test('should sort for positions first', () => {
|
|
7
|
-
const a = '
|
|
8
|
-
const b = '
|
|
8
|
+
const a = new Substitution(undefined, 'A', 'B', 123);
|
|
9
|
+
const b = new Substitution(undefined, 'A', 'B', 234);
|
|
9
10
|
|
|
10
11
|
expect(sortSubstitutionsAndDeletions(a, b)).toBeLessThan(0);
|
|
11
12
|
expect(sortSubstitutionsAndDeletions(b, a)).toBeGreaterThan(0);
|
|
12
13
|
});
|
|
13
14
|
|
|
14
15
|
test('should sort for substitutionValue second', () => {
|
|
15
|
-
const a = '
|
|
16
|
-
const b = '
|
|
16
|
+
const a = new Substitution(undefined, 'A', 'A', 123);
|
|
17
|
+
const b = new Substitution(undefined, 'A', 'B', 123);
|
|
18
|
+
|
|
19
|
+
expect(sortSubstitutionsAndDeletions(a, b)).toBeLessThan(0);
|
|
20
|
+
expect(sortSubstitutionsAndDeletions(b, a)).toBeGreaterThan(0);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('should sort for substitutionValue over deletion', () => {
|
|
24
|
+
const a = new Substitution(undefined, 'A', 'A', 123);
|
|
25
|
+
const b = new Deletion(undefined, 'A', 123);
|
|
17
26
|
|
|
18
27
|
expect(sortSubstitutionsAndDeletions(a, b)).toBeLessThan(0);
|
|
19
28
|
expect(sortSubstitutionsAndDeletions(b, a)).toBeGreaterThan(0);
|
|
@@ -22,24 +31,24 @@ describe('sortSubstitutionsAndDeletions with no segments', () => {
|
|
|
22
31
|
|
|
23
32
|
describe('sortSubstitutionsAndDeletions with segments', () => {
|
|
24
33
|
test('should sort for segment first', () => {
|
|
25
|
-
const a = 'AA1
|
|
26
|
-
const b = 'BB1
|
|
34
|
+
const a = new Substitution('AA1', 'A', 'B', 123);
|
|
35
|
+
const b = new Substitution('BB1', 'A', 'B', 234);
|
|
27
36
|
|
|
28
37
|
expect(sortSubstitutionsAndDeletions(a, b)).toBeLessThan(0);
|
|
29
38
|
expect(sortSubstitutionsAndDeletions(b, a)).toBeGreaterThan(0);
|
|
30
39
|
});
|
|
31
40
|
|
|
32
41
|
test('should sort for position second', () => {
|
|
33
|
-
const a = 'AA1
|
|
34
|
-
const b = 'AA1
|
|
42
|
+
const a = new Substitution('AA1', 'A', 'B', 123);
|
|
43
|
+
const b = new Substitution('AA1', 'A', 'B', 234);
|
|
35
44
|
|
|
36
45
|
expect(sortSubstitutionsAndDeletions(a, b)).toBeLessThan(0);
|
|
37
46
|
expect(sortSubstitutionsAndDeletions(b, a)).toBeGreaterThan(0);
|
|
38
47
|
});
|
|
39
48
|
|
|
40
49
|
test('should sort for substitutionValue third', () => {
|
|
41
|
-
const a = 'AA1
|
|
42
|
-
const b = 'AA1
|
|
50
|
+
const a = new Substitution('AA1', 'A', 'A', 123);
|
|
51
|
+
const b = new Substitution('AA1', 'A', 'B', 123);
|
|
43
52
|
|
|
44
53
|
expect(sortSubstitutionsAndDeletions(a, b)).toBeLessThan(0);
|
|
45
54
|
expect(sortSubstitutionsAndDeletions(b, a)).toBeGreaterThan(0);
|
|
@@ -1,17 +1,50 @@
|
|
|
1
|
-
|
|
1
|
+
import { Deletion, type Substitution } from '../../../utils/mutations';
|
|
2
2
|
|
|
3
|
-
export const sortSubstitutionsAndDeletions = (a:
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
export const sortSubstitutionsAndDeletions = (a: Substitution | Deletion, b: Substitution | Deletion) => {
|
|
4
|
+
if (a.segment !== b.segment) {
|
|
5
|
+
compareSegments(a.segment, b.segment);
|
|
6
|
+
}
|
|
6
7
|
|
|
7
|
-
if (
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
if (a.position !== b.position) {
|
|
9
|
+
return comparePositions(a.position, b.position);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const aIsDeletion = a instanceof Deletion;
|
|
13
|
+
const bIsDeletion = b instanceof Deletion;
|
|
14
|
+
|
|
15
|
+
if (aIsDeletion !== bIsDeletion) {
|
|
16
|
+
return aIsDeletion ? 1 : -1;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (!aIsDeletion && !bIsDeletion) {
|
|
20
|
+
if (a.substitutionValue !== b.substitutionValue) {
|
|
21
|
+
return compareSubstitutionValues(a.substitutionValue, b.substitutionValue);
|
|
13
22
|
}
|
|
14
|
-
return aMatch[4].localeCompare(bMatch[4]);
|
|
15
23
|
}
|
|
16
|
-
|
|
24
|
+
|
|
25
|
+
return 0;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const compareSegments = (a: string | undefined, b: string | undefined) => {
|
|
29
|
+
if (a === undefined) {
|
|
30
|
+
return -1;
|
|
31
|
+
}
|
|
32
|
+
if (b === undefined) {
|
|
33
|
+
return 1;
|
|
34
|
+
}
|
|
35
|
+
return a.localeCompare(b);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const comparePositions = (a: number, b: number) => {
|
|
39
|
+
return a - b;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const compareSubstitutionValues = (a: string | undefined, b: string | undefined) => {
|
|
43
|
+
if (a === undefined) {
|
|
44
|
+
return -1;
|
|
45
|
+
}
|
|
46
|
+
if (b === undefined) {
|
|
47
|
+
return 1;
|
|
48
|
+
}
|
|
49
|
+
return a.localeCompare(b);
|
|
17
50
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { withActions } from '@storybook/addon-actions/decorator';
|
|
2
2
|
import { type Meta, type StoryObj } from '@storybook/preact';
|
|
3
|
+
import { expect, waitFor, within } from '@storybook/test';
|
|
3
4
|
|
|
4
5
|
import data from './__mockData__/aggregated_hosts.json';
|
|
5
6
|
import { TextInput, type TextInputProps } from './text-input';
|
|
@@ -39,7 +40,11 @@ export default meta;
|
|
|
39
40
|
export const Default: StoryObj<TextInputProps> = {
|
|
40
41
|
render: (args) => (
|
|
41
42
|
<LapisUrlContext.Provider value={LAPIS_URL}>
|
|
42
|
-
<TextInput
|
|
43
|
+
<TextInput
|
|
44
|
+
lapisField={args.lapisField}
|
|
45
|
+
placeholderText={args.placeholderText}
|
|
46
|
+
initialValue={args.initialValue}
|
|
47
|
+
/>
|
|
43
48
|
</LapisUrlContext.Provider>
|
|
44
49
|
),
|
|
45
50
|
args: {
|
|
@@ -47,3 +52,19 @@ export const Default: StoryObj<TextInputProps> = {
|
|
|
47
52
|
placeholderText: 'Enter a host name',
|
|
48
53
|
},
|
|
49
54
|
};
|
|
55
|
+
|
|
56
|
+
export const WithInitialValue: StoryObj<TextInputProps> = {
|
|
57
|
+
...Default,
|
|
58
|
+
args: {
|
|
59
|
+
...Default.args,
|
|
60
|
+
initialValue: 'Homo sapiens',
|
|
61
|
+
},
|
|
62
|
+
play: async ({ canvasElement }) => {
|
|
63
|
+
const canvas = within(canvasElement);
|
|
64
|
+
|
|
65
|
+
await waitFor(() => {
|
|
66
|
+
const input = canvas.getByPlaceholderText('Enter a host name', { exact: false });
|
|
67
|
+
expect(input).toHaveValue('Homo sapiens');
|
|
68
|
+
});
|
|
69
|
+
},
|
|
70
|
+
};
|
|
@@ -11,9 +11,10 @@ import { useQuery } from '../useQuery';
|
|
|
11
11
|
export interface TextInputProps {
|
|
12
12
|
lapisField: string;
|
|
13
13
|
placeholderText?: string;
|
|
14
|
+
initialValue?: string;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
export const TextInput: FunctionComponent<TextInputProps> = ({ lapisField, placeholderText }) => {
|
|
17
|
+
export const TextInput: FunctionComponent<TextInputProps> = ({ lapisField, placeholderText, initialValue }) => {
|
|
17
18
|
const lapis = useContext(LapisUrlContext);
|
|
18
19
|
|
|
19
20
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
@@ -62,6 +63,7 @@ export const TextInput: FunctionComponent<TextInputProps> = ({ lapisField, place
|
|
|
62
63
|
onInput={onInput}
|
|
63
64
|
ref={inputRef}
|
|
64
65
|
list={lapisField}
|
|
66
|
+
value={initialValue}
|
|
65
67
|
/>
|
|
66
68
|
<datalist id={lapisField}>
|
|
67
69
|
{data.map((item) => (
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { describe, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { type Equals, type Expect } from './typeAssertions';
|
|
4
|
+
|
|
5
|
+
/* eslint-disable @typescript-eslint/no-unused-vars, no-unused-vars */
|
|
6
|
+
describe('Expect', () => {
|
|
7
|
+
it('should only accept true', () => {
|
|
8
|
+
type ShouldBeTrue = Expect<true>;
|
|
9
|
+
// @ts-expect-error Expect should only accept true
|
|
10
|
+
type ShouldFail = Expect<false>;
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
describe('Equals', () => {
|
|
15
|
+
it('should accept equal types', () => {
|
|
16
|
+
type Equals1 = Expect<Equals<1, 1>>;
|
|
17
|
+
type Equals1Or2 = Expect<Equals<1 | 2, 1 | 2>>;
|
|
18
|
+
type EqualsWithObject = Expect<Equals<{ key: string }, { key: string }>>;
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should reject unequal types', () => {
|
|
22
|
+
// @ts-expect-error 1 is not 2
|
|
23
|
+
type Equals1Vs2 = Expect<Equals<1, 2>>;
|
|
24
|
+
// @ts-expect-error disjoint union types
|
|
25
|
+
type EqualsDisjointUnion = Expect<Equals<1 | 2, 1 | 999>>;
|
|
26
|
+
// @ts-expect-error object with different keys
|
|
27
|
+
type EqualsObjectWithDifferentKey = Expect<Equals<{ key: string }, { otherKey: string }>>;
|
|
28
|
+
// @ts-expect-error object with different value types
|
|
29
|
+
type EqualsObjectWithDifferentValue = Expect<Equals<{ key: string }, { key: number }>>;
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Those are helpers to make sure that types are equal.
|
|
3
|
+
*
|
|
4
|
+
* #### Why do we need this?
|
|
5
|
+
*
|
|
6
|
+
* The custom element manifest does not fully resolve the types of properties,
|
|
7
|
+
* so Storybook will only show non-resolved types (such as `View[]`).
|
|
8
|
+
* To give users full type information, we have to inline the types in the web component definitions.
|
|
9
|
+
* These assertions help us to make sure that the inlined type is equal to the type
|
|
10
|
+
* that is defined in the Preact components.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export type Expect<T extends true> = T;
|
|
14
|
+
export type IsAssignable<X, Y> = X extends Y ? true : false;
|
|
15
|
+
export type And<X, Y> = X extends true ? (Y extends true ? true : false) : false;
|
|
16
|
+
export type Equals<X, Y> = And<IsAssignable<X, Y>, IsAssignable<Y, X>>;
|
|
@@ -46,7 +46,6 @@ export abstract class PreactLitAdapter extends ReactiveElement {
|
|
|
46
46
|
};
|
|
47
47
|
|
|
48
48
|
override update(changedProperties: PropertyValues) {
|
|
49
|
-
console.log('this.lapis', this.lapis);
|
|
50
49
|
const vdom = (
|
|
51
50
|
<LapisUrlContext.Provider value={this.lapis}>
|
|
52
51
|
<ReferenceGenomeContext.Provider value={this.referenceGenome}>
|