@genspectrum/dashboard-components 0.1.3 → 0.1.4
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 +311 -75
- package/dist/dashboard-components.js +622 -434
- package/dist/dashboard-components.js.map +1 -1
- package/dist/genspectrum-components.d.ts +229 -42
- package/dist/style.css +132 -139
- package/package.json +9 -5
- package/src/preact/aggregatedData/aggregate.stories.tsx +5 -5
- package/src/preact/aggregatedData/aggregate.tsx +8 -4
- package/src/preact/components/ReferenceGenomesAwaiter.tsx +25 -0
- package/src/preact/components/csv-download-button.tsx +8 -2
- package/src/preact/components/headline.tsx +16 -4
- 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 +57 -43
- 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 +8 -1
- package/src/preact/mutationComparison/mutation-comparison.tsx +13 -4
- 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 +8 -1
- package/src/preact/mutations/mutations.tsx +16 -5
- 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 +4 -0
- package/src/preact/prevalenceOverTime/prevalence-over-time.tsx +17 -9
- package/src/preact/relativeGrowthAdvantage/relative-growth-advantage-chart.tsx +8 -5
- package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.stories.tsx +12 -0
- package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.tsx +13 -8
- 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 +21 -11
- package/src/web-components/display/aggregate-component.tsx +12 -5
- package/src/web-components/display/mutation-comparison-component.stories.ts +29 -11
- package/src/web-components/display/mutation-comparison-component.tsx +72 -4
- package/src/web-components/display/mutations-component.stories.ts +14 -13
- package/src/web-components/display/mutations-component.tsx +14 -1
- package/src/web-components/display/prevalence-over-time-component.stories.ts +20 -18
- package/src/web-components/display/prevalence-over-time-component.tsx +12 -0
- package/src/web-components/display/relative-growth-advantage-component.stories.ts +11 -10
- package/src/web-components/display/relative-growth-advantage-component.tsx +12 -0
- package/src/web-components/input/date-range-selector-component.stories.ts +35 -8
- package/src/web-components/input/date-range-selector-component.tsx +18 -5
- 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 +20 -9
- package/src/web-components/input/mutation-filter-component.tsx +10 -2
- package/src/web-components/input/text-input-component.stories.ts +13 -4
- package/src/web-components/input/text-input-component.tsx +11 -2
- package/src/web-components/display/aggregate-component.mdx +0 -25
- package/src/web-components/input/location-filter.mdx +0 -25
|
@@ -16,6 +16,7 @@ import Headline from '../components/headline';
|
|
|
16
16
|
import Info 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,7 @@ export interface PrevalenceOverTimeProps {
|
|
|
31
32
|
smoothingWindow: number;
|
|
32
33
|
views: View[];
|
|
33
34
|
confidenceIntervalMethods: ConfidenceIntervalMethod[];
|
|
35
|
+
size?: Size;
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
export const PrevalenceOverTime: FunctionComponent<PrevalenceOverTimeProps> = ({
|
|
@@ -40,6 +42,7 @@ export const PrevalenceOverTime: FunctionComponent<PrevalenceOverTimeProps> = ({
|
|
|
40
42
|
smoothingWindow,
|
|
41
43
|
views,
|
|
42
44
|
confidenceIntervalMethods,
|
|
45
|
+
size,
|
|
43
46
|
}) => {
|
|
44
47
|
const lapis = useContext(LapisUrlContext);
|
|
45
48
|
|
|
@@ -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
|
|
|
@@ -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,16 @@ 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
|
+
},
|
|
13
23
|
};
|
|
14
24
|
|
|
15
25
|
export const Primary = {
|
|
@@ -20,6 +30,7 @@ export const Primary = {
|
|
|
20
30
|
denominator={args.denominator}
|
|
21
31
|
generationTime={args.generationTime}
|
|
22
32
|
views={args.views}
|
|
33
|
+
size={args.size}
|
|
23
34
|
/>
|
|
24
35
|
</LapisUrlContext.Provider>
|
|
25
36
|
),
|
|
@@ -28,6 +39,7 @@ export const Primary = {
|
|
|
28
39
|
denominator: { country: 'Switzerland', dateFrom: '2020-12-01', dateTo: '2021-03-01' },
|
|
29
40
|
generationTime: 7,
|
|
30
41
|
views: ['line'],
|
|
42
|
+
size: { width: '100%', height: '700px' },
|
|
31
43
|
},
|
|
32
44
|
parameters: {
|
|
33
45
|
fetchMock: {
|
|
@@ -13,6 +13,7 @@ import Headline from '../components/headline';
|
|
|
13
13
|
import Info 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,7 @@ export interface RelativeGrowthAdvantageProps {
|
|
|
25
26
|
denominator: LapisFilter;
|
|
26
27
|
generationTime: number;
|
|
27
28
|
views: View[];
|
|
29
|
+
size?: Size;
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
export const RelativeGrowthAdvantage: FunctionComponent<RelativeGrowthAdvantageProps> = ({
|
|
@@ -32,6 +34,7 @@ export const RelativeGrowthAdvantage: FunctionComponent<RelativeGrowthAdvantageP
|
|
|
32
34
|
denominator,
|
|
33
35
|
generationTime,
|
|
34
36
|
views,
|
|
37
|
+
size,
|
|
35
38
|
}) => {
|
|
36
39
|
const lapis = useContext(LapisUrlContext);
|
|
37
40
|
const [yAxisScaleType, setYAxisScaleType] = useState<ScaleType>('linear');
|
|
@@ -67,14 +70,16 @@ export const RelativeGrowthAdvantage: FunctionComponent<RelativeGrowthAdvantageP
|
|
|
67
70
|
}
|
|
68
71
|
|
|
69
72
|
return (
|
|
70
|
-
<
|
|
71
|
-
<
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
73
|
+
<ResizeContainer size={size} defaultSize={{ height: '700px', width: '100%' }}>
|
|
74
|
+
<Headline heading={headline}>
|
|
75
|
+
<RelativeGrowthAdvantageTabs
|
|
76
|
+
data={data}
|
|
77
|
+
yAxisScaleType={yAxisScaleType}
|
|
78
|
+
setYAxisScaleType={setYAxisScaleType}
|
|
79
|
+
views={views}
|
|
80
|
+
/>
|
|
81
|
+
</Headline>
|
|
82
|
+
</ResizeContainer>
|
|
78
83
|
);
|
|
79
84
|
};
|
|
80
85
|
|
|
@@ -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}>
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { consume } from '@lit/context';
|
|
2
|
+
import { withActions } from '@storybook/addon-actions/decorator';
|
|
3
|
+
import { expect, waitFor, within } from '@storybook/test';
|
|
4
|
+
import type { Meta, StoryObj } from '@storybook/web-components';
|
|
5
|
+
import { html, LitElement } from 'lit';
|
|
6
|
+
import { customElement } from 'lit/decorators.js';
|
|
7
|
+
|
|
8
|
+
import './app';
|
|
9
|
+
|
|
10
|
+
import { lapisContext } from './lapis-context';
|
|
11
|
+
import { referenceGenomeContext } from './reference-genome-context';
|
|
12
|
+
import { withComponentDocs } from '../../.storybook/ComponentDocsBlock';
|
|
13
|
+
import { LAPIS_URL, REFERENCE_GENOME_ENDPOINT } from '../constants';
|
|
14
|
+
import type { ReferenceGenome } from '../lapisApi/ReferenceGenome';
|
|
15
|
+
import referenceGenome from '../lapisApi/__mockData__/referenceGenome.json';
|
|
16
|
+
|
|
17
|
+
const codeExample = String.raw`
|
|
18
|
+
<gs-app lapis="https://url.to.lapis">
|
|
19
|
+
<p>Your application code goes here.</p>
|
|
20
|
+
</gs-app>`;
|
|
21
|
+
|
|
22
|
+
const meta: Meta = {
|
|
23
|
+
title: 'Wrapper/App',
|
|
24
|
+
component: 'gs-app',
|
|
25
|
+
parameters: withComponentDocs({
|
|
26
|
+
fetchMock: {},
|
|
27
|
+
componentDocs: {
|
|
28
|
+
tag: 'gs-app',
|
|
29
|
+
opensShadowDom: false,
|
|
30
|
+
expectsChildren: true,
|
|
31
|
+
codeExample,
|
|
32
|
+
},
|
|
33
|
+
}),
|
|
34
|
+
decorators: [withActions],
|
|
35
|
+
tags: ['autodocs'],
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export default meta;
|
|
39
|
+
|
|
40
|
+
const Template: StoryObj<{ lapis: string }> = {
|
|
41
|
+
render: (args) => {
|
|
42
|
+
return html` <gs-app lapis="${args.lapis}">
|
|
43
|
+
<gs-app-display></gs-app-display>
|
|
44
|
+
</gs-app>`;
|
|
45
|
+
},
|
|
46
|
+
args: {
|
|
47
|
+
lapis: LAPIS_URL,
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const Default: StoryObj<{ lapis: string }> = {
|
|
52
|
+
...Template,
|
|
53
|
+
play: async ({ canvasElement }) => {
|
|
54
|
+
const canvas = within(canvasElement);
|
|
55
|
+
|
|
56
|
+
await waitFor(() => {
|
|
57
|
+
expect(canvas.getByText(LAPIS_URL)).toBeVisible();
|
|
58
|
+
expect(canvas.getByText('"name": "ORF1a",', { exact: false })).toBeVisible();
|
|
59
|
+
});
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const DelayFetchingReferenceGenome: StoryObj<{ lapis: string }> = {
|
|
64
|
+
...Template,
|
|
65
|
+
parameters: {
|
|
66
|
+
fetchMock: {
|
|
67
|
+
mocks: [
|
|
68
|
+
{
|
|
69
|
+
matcher: {
|
|
70
|
+
name: 'referenceGenome',
|
|
71
|
+
url: REFERENCE_GENOME_ENDPOINT,
|
|
72
|
+
},
|
|
73
|
+
response: {
|
|
74
|
+
status: 200,
|
|
75
|
+
body: referenceGenome,
|
|
76
|
+
},
|
|
77
|
+
options: {
|
|
78
|
+
delay: 5000,
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export const FailsToFetchReferenceGenome: StoryObj<{ lapis: string }> = {
|
|
87
|
+
...Template,
|
|
88
|
+
args: {
|
|
89
|
+
lapis: 'definitely-not-a-valid-url',
|
|
90
|
+
},
|
|
91
|
+
play: async ({ canvasElement }) => {
|
|
92
|
+
const canvas = within(canvasElement);
|
|
93
|
+
|
|
94
|
+
await waitFor(() => {
|
|
95
|
+
expect(canvas.getByText('Error')).toBeVisible();
|
|
96
|
+
});
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
@customElement('gs-app-display')
|
|
101
|
+
// eslint-disable-next-line no-unused-vars,@typescript-eslint/no-unused-vars -- it is used in the story above
|
|
102
|
+
class AppDisplay extends LitElement {
|
|
103
|
+
@consume({ context: lapisContext })
|
|
104
|
+
lapis: string = '';
|
|
105
|
+
|
|
106
|
+
@consume({ context: referenceGenomeContext, subscribe: true })
|
|
107
|
+
referenceGenome: ReferenceGenome = {
|
|
108
|
+
nucleotideSequences: [],
|
|
109
|
+
genes: [],
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
override render() {
|
|
113
|
+
return html`
|
|
114
|
+
<h1 class="text-xl font-bold">Dummy component</h1>
|
|
115
|
+
<p>
|
|
116
|
+
What you can see here is a dummy component that displays the values of the wrapping "gs-app". Actually
|
|
117
|
+
"gs-app" doesn't display anything.
|
|
118
|
+
</p>
|
|
119
|
+
<h2 class="text-lg font-bold">LAPIS URL</h2>
|
|
120
|
+
<p>${this.lapis}</p>
|
|
121
|
+
<h2 class="text-lg font-bold">Reference genomes</h2>
|
|
122
|
+
<pre><code>${JSON.stringify(this.referenceGenome, null, 2)}</code></pre>
|
|
123
|
+
`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
override createRenderRoot() {
|
|
127
|
+
return this;
|
|
128
|
+
}
|
|
129
|
+
}
|