@genspectrum/dashboard-components 0.12.1 → 0.13.1
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 +292 -25
- package/dist/{LocationChangedEvent-CORvQvXv.js → LineageFilterChangedEvent-GedKNGFI.js} +25 -3
- package/dist/LineageFilterChangedEvent-GedKNGFI.js.map +1 -0
- package/dist/assets/mutationOverTimeWorker-B1-WrM4b.js.map +1 -0
- package/dist/components.d.ts +124 -25
- package/dist/components.js +765 -572
- package/dist/components.js.map +1 -1
- package/dist/style.css +3 -0
- package/dist/util.d.ts +48 -18
- package/dist/util.js +3 -1
- package/package.json +2 -2
- package/src/constants.ts +6 -0
- package/src/lapisApi/__mockData__/wiseReferenceGenome.json +9 -0
- package/src/lapisApi/lapisApi.ts +17 -0
- package/src/lapisApi/lapisTypes.ts +7 -1
- package/src/operator/FetchDetailsOperator.ts +28 -0
- package/src/preact/components/downshift-combobox.tsx +145 -0
- package/src/preact/components/tabs.tsx +1 -1
- package/src/preact/lineageFilter/LineageFilterChangedEvent.ts +11 -0
- package/src/preact/lineageFilter/fetchLineageAutocompleteList.spec.ts +16 -2
- package/src/preact/lineageFilter/fetchLineageAutocompleteList.ts +13 -2
- package/src/preact/lineageFilter/lineage-filter.stories.tsx +110 -9
- package/src/preact/lineageFilter/lineage-filter.tsx +40 -50
- package/src/preact/locationFilter/LocationChangedEvent.ts +1 -1
- package/src/preact/locationFilter/fetchAutocompletionList.spec.ts +6 -2
- package/src/preact/locationFilter/fetchAutocompletionList.ts +16 -6
- package/src/preact/locationFilter/location-filter.stories.tsx +33 -30
- package/src/preact/locationFilter/location-filter.tsx +47 -144
- package/src/preact/mutationsOverTime/MutationOverTimeData.ts +9 -5
- package/src/preact/mutationsOverTime/mutations-over-time-grid.tsx +5 -3
- package/src/preact/shared/sort/sortSubstitutionsAndDeletions.ts +4 -7
- package/src/preact/textInput/TextInputChangedEvent.ts +1 -1
- package/src/preact/textInput/fetchStringAutocompleteList.ts +20 -0
- package/src/preact/textInput/text-input.stories.tsx +14 -11
- package/src/preact/textInput/text-input.tsx +39 -140
- package/src/preact/wastewater/mutationsOverTime/__mockData__/details.json +88 -0
- package/src/preact/wastewater/mutationsOverTime/computeWastewaterMutationsOverTimeDataPerLocation.spec.ts +159 -0
- package/src/preact/wastewater/mutationsOverTime/computeWastewaterMutationsOverTimeDataPerLocation.ts +51 -0
- package/src/preact/wastewater/mutationsOverTime/wastewater-mutations-over-time.stories.tsx +71 -0
- package/src/preact/wastewater/mutationsOverTime/wastewater-mutations-over-time.tsx +151 -0
- package/src/query/queryMutationsOverTime.ts +6 -14
- package/src/query/queryWastewaterMutationsOverTime.spec.ts +94 -0
- package/src/query/queryWastewaterMutationsOverTime.ts +55 -0
- package/src/types.ts +3 -0
- package/src/utilEntrypoint.ts +2 -0
- package/src/utils/map2d.ts +39 -0
- package/src/web-components/index.ts +1 -0
- package/src/web-components/input/gs-lineage-filter.stories.ts +120 -31
- package/src/web-components/input/gs-lineage-filter.tsx +24 -8
- package/src/web-components/input/gs-location-filter.stories.ts +9 -0
- package/src/web-components/input/gs-location-filter.tsx +21 -3
- package/src/web-components/input/gs-text-input.stories.ts +14 -5
- package/src/web-components/input/gs-text-input.tsx +23 -7
- package/src/web-components/wastewaterVisualization/gs-wastewater-mutations-over-time.stories.ts +82 -0
- package/src/web-components/wastewaterVisualization/gs-wastewater-mutations-over-time.tsx +112 -0
- package/src/web-components/wastewaterVisualization/index.ts +1 -0
- package/standalone-bundle/assets/{mutationOverTimeWorker-DEybsZ5r.js.map → mutationOverTimeWorker-Cls1J0cl.js.map} +1 -1
- package/standalone-bundle/dashboard-components.js +6972 -6796
- package/standalone-bundle/dashboard-components.js.map +1 -1
- package/standalone-bundle/style.css +1 -1
- package/dist/LocationChangedEvent-CORvQvXv.js.map +0 -1
- package/dist/assets/mutationOverTimeWorker-DTv93Ere.js.map +0 -1
- package/src/preact/textInput/fetchAutocompleteList.ts +0 -9
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import z from 'zod';
|
|
2
|
+
|
|
3
|
+
import { FetchDetailsOperator } from '../operator/FetchDetailsOperator';
|
|
4
|
+
import { type LapisFilter } from '../types';
|
|
5
|
+
import { type Substitution, SubstitutionClass } from '../utils/mutations';
|
|
6
|
+
import { parseDateStringToTemporal, type TemporalClass, toTemporalClass } from '../utils/temporalClass';
|
|
7
|
+
|
|
8
|
+
export type WastewaterData = {
|
|
9
|
+
location: string;
|
|
10
|
+
date: TemporalClass;
|
|
11
|
+
nucleotideMutationFrequency: { mutation: Substitution; proportion: number | null }[];
|
|
12
|
+
aminoAcidMutationFrequency: { mutation: Substitution; proportion: number | null }[];
|
|
13
|
+
}[];
|
|
14
|
+
|
|
15
|
+
export async function queryWastewaterMutationsOverTime(
|
|
16
|
+
lapis: string,
|
|
17
|
+
lapisFilter: LapisFilter,
|
|
18
|
+
signal?: AbortSignal,
|
|
19
|
+
): Promise<WastewaterData> {
|
|
20
|
+
const fetchData = new FetchDetailsOperator(lapisFilter, [
|
|
21
|
+
'date',
|
|
22
|
+
'location',
|
|
23
|
+
'nucleotideMutationFrequency',
|
|
24
|
+
'aminoAcidMutationFrequency',
|
|
25
|
+
]);
|
|
26
|
+
const data = (await fetchData.evaluate(lapis, signal)).content;
|
|
27
|
+
|
|
28
|
+
return data.map((row) => ({
|
|
29
|
+
location: row.location as string,
|
|
30
|
+
date: toTemporalClass(parseDateStringToTemporal(row.date as string, 'day')),
|
|
31
|
+
nucleotideMutationFrequency:
|
|
32
|
+
row.nucleotideMutationFrequency !== null
|
|
33
|
+
? transformMutations(JSON.parse(row.nucleotideMutationFrequency as string))
|
|
34
|
+
: [],
|
|
35
|
+
aminoAcidMutationFrequency:
|
|
36
|
+
row.aminoAcidMutationFrequency !== null
|
|
37
|
+
? transformMutations(JSON.parse(row.aminoAcidMutationFrequency as string))
|
|
38
|
+
: [],
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const mutationFrequencySchema = z.record(z.number().nullable());
|
|
43
|
+
|
|
44
|
+
function transformMutations(input: unknown): { mutation: Substitution; proportion: number | null }[] {
|
|
45
|
+
const mutationFrequency = mutationFrequencySchema.safeParse(input);
|
|
46
|
+
|
|
47
|
+
if (!mutationFrequency.success) {
|
|
48
|
+
throw new Error(`Failed to parse mutation frequency: ${mutationFrequency.error.message}`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return Object.entries(mutationFrequency.data).map(([key, value]) => ({
|
|
52
|
+
mutation: SubstitutionClass.parse(key)!,
|
|
53
|
+
proportion: value,
|
|
54
|
+
}));
|
|
55
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -28,6 +28,9 @@ export const namedLapisFilterSchema = z.object({
|
|
|
28
28
|
});
|
|
29
29
|
export type NamedLapisFilter = z.infer<typeof namedLapisFilterSchema>;
|
|
30
30
|
|
|
31
|
+
export const lapisLocationFilterSchema = z.record(z.union([z.string(), z.undefined()]));
|
|
32
|
+
export type LapisLocationFilter = z.infer<typeof lapisLocationFilterSchema>;
|
|
33
|
+
|
|
31
34
|
export const temporalGranularitySchema = z.union([
|
|
32
35
|
z.literal('day'),
|
|
33
36
|
z.literal('week'),
|
package/src/utilEntrypoint.ts
CHANGED
|
@@ -34,3 +34,5 @@ export type { ConfidenceIntervalMethod } from './preact/shared/charts/confideceI
|
|
|
34
34
|
export type { AxisMax, YAxisMaxConfig } from './preact/shared/charts/getYAxisMax';
|
|
35
35
|
|
|
36
36
|
export { LocationChangedEvent } from './preact/locationFilter/LocationChangedEvent';
|
|
37
|
+
export { LineageFilterChangedEvent } from './preact/lineageFilter/LineageFilterChangedEvent';
|
|
38
|
+
export { TextInputChangedEvent } from './preact/textInput/TextInputChangedEvent';
|
package/src/utils/map2d.ts
CHANGED
|
@@ -17,6 +17,8 @@ export interface Map2d<Key1, Key2, Value> {
|
|
|
17
17
|
|
|
18
18
|
serializeSecondAxis(key: Key2): string;
|
|
19
19
|
|
|
20
|
+
getContents(): Map2DContents<Key1, Key2, Value>;
|
|
21
|
+
|
|
20
22
|
readonly keysFirstAxis: Map<string, Key1>;
|
|
21
23
|
readonly keysSecondAxis: Map<string, Key2>;
|
|
22
24
|
}
|
|
@@ -106,6 +108,35 @@ export class Map2dBase<Key1 extends object | string, Key2 extends object | strin
|
|
|
106
108
|
}
|
|
107
109
|
}
|
|
108
110
|
|
|
111
|
+
export class SortedMap2d<Key1 extends object | string, Key2 extends object | string, Value> extends Map2dBase<
|
|
112
|
+
Key1,
|
|
113
|
+
Key2,
|
|
114
|
+
Value
|
|
115
|
+
> {
|
|
116
|
+
constructor(
|
|
117
|
+
delegate: Map2d<Key1, Key2, Value>,
|
|
118
|
+
sortFirstAxis: (a: Key1, b: Key1) => number,
|
|
119
|
+
sortSecondAxis: (a: Key2, b: Key2) => number,
|
|
120
|
+
) {
|
|
121
|
+
const contents = delegate.getContents();
|
|
122
|
+
const sortedFirstAxisKeys = new Map(
|
|
123
|
+
[...contents.keysFirstAxis.entries()].sort((a, b) => sortFirstAxis(a[1], b[1])),
|
|
124
|
+
);
|
|
125
|
+
const sortedSecondAxisKeys = new Map(
|
|
126
|
+
[...contents.keysSecondAxis.entries()].sort((a, b) => sortSecondAxis(a[1], b[1])),
|
|
127
|
+
);
|
|
128
|
+
super(
|
|
129
|
+
(key: Key1) => delegate.serializeFirstAxis(key),
|
|
130
|
+
(key: Key2) => delegate.serializeSecondAxis(key),
|
|
131
|
+
{
|
|
132
|
+
keysFirstAxis: sortedFirstAxisKeys,
|
|
133
|
+
keysSecondAxis: sortedSecondAxisKeys,
|
|
134
|
+
data: contents.data,
|
|
135
|
+
},
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
109
140
|
export class Map2dView<Key1 extends object | string, Key2 extends object | string, Value>
|
|
110
141
|
implements Map2d<Key1, Key2, Value>
|
|
111
142
|
{
|
|
@@ -175,4 +206,12 @@ export class Map2dView<Key1 extends object | string, Key2 extends object | strin
|
|
|
175
206
|
|
|
176
207
|
return this.baseMap.getRow(key);
|
|
177
208
|
}
|
|
209
|
+
|
|
210
|
+
getContents() {
|
|
211
|
+
return {
|
|
212
|
+
keysFirstAxis: this.keysFirstAxis,
|
|
213
|
+
keysSecondAxis: this.keysSecondAxis,
|
|
214
|
+
data: this.baseMap.getContents().data,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
178
217
|
}
|
|
@@ -14,8 +14,9 @@ import { withinShadowRoot } from '../withinShadowRoot.story';
|
|
|
14
14
|
const codeExample = String.raw`
|
|
15
15
|
<gs-lineage-filter
|
|
16
16
|
lapisField="pangoLineage"
|
|
17
|
+
lapisFilter='{"counrty": "Germany"}'
|
|
17
18
|
placeholderText="Enter lineage"
|
|
18
|
-
|
|
19
|
+
value="B.1.1.7"
|
|
19
20
|
width="50%">
|
|
20
21
|
</gs-lineage-filter>`;
|
|
21
22
|
|
|
@@ -34,6 +35,7 @@ const meta: Meta<Required<LineageFilterProps>> = {
|
|
|
34
35
|
url: AGGREGATED_ENDPOINT,
|
|
35
36
|
body: {
|
|
36
37
|
fields: ['pangoLineage'],
|
|
38
|
+
country: 'Germany',
|
|
37
39
|
},
|
|
38
40
|
},
|
|
39
41
|
response: {
|
|
@@ -50,18 +52,47 @@ const meta: Meta<Required<LineageFilterProps>> = {
|
|
|
50
52
|
},
|
|
51
53
|
}),
|
|
52
54
|
tags: ['autodocs'],
|
|
55
|
+
argTypes: {
|
|
56
|
+
lapisField: {
|
|
57
|
+
control: {
|
|
58
|
+
type: 'select',
|
|
59
|
+
},
|
|
60
|
+
options: ['host'],
|
|
61
|
+
},
|
|
62
|
+
placeholderText: {
|
|
63
|
+
control: {
|
|
64
|
+
type: 'text',
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
value: {
|
|
68
|
+
control: {
|
|
69
|
+
type: 'text',
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
width: {
|
|
73
|
+
control: {
|
|
74
|
+
type: 'text',
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
lapisFilter: {
|
|
78
|
+
control: {
|
|
79
|
+
type: 'object',
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
},
|
|
53
83
|
};
|
|
54
84
|
|
|
55
85
|
export default meta;
|
|
56
86
|
|
|
57
|
-
|
|
87
|
+
const Template: StoryObj<Required<LineageFilterProps>> = {
|
|
58
88
|
render: (args) => {
|
|
59
89
|
return html` <gs-app lapis="${LAPIS_URL}">
|
|
60
90
|
<div class="max-w-screen-lg">
|
|
61
91
|
<gs-lineage-filter
|
|
62
92
|
.lapisField=${args.lapisField}
|
|
93
|
+
.lapisFilter=${args.lapisFilter}
|
|
63
94
|
.placeholderText=${args.placeholderText}
|
|
64
|
-
.
|
|
95
|
+
.value=${args.value}
|
|
65
96
|
.width=${args.width}
|
|
66
97
|
></gs-lineage-filter>
|
|
67
98
|
</div>
|
|
@@ -69,18 +100,86 @@ export const Default: StoryObj<Required<LineageFilterProps>> = {
|
|
|
69
100
|
},
|
|
70
101
|
args: {
|
|
71
102
|
lapisField: 'pangoLineage',
|
|
72
|
-
|
|
73
|
-
|
|
103
|
+
lapisFilter: {
|
|
104
|
+
country: 'Germany',
|
|
105
|
+
},
|
|
106
|
+
placeholderText: 'Enter a lineage',
|
|
107
|
+
value: 'B.1.1.7',
|
|
74
108
|
width: '100%',
|
|
75
109
|
},
|
|
76
110
|
};
|
|
77
111
|
|
|
112
|
+
const aggregatedEndpointMatcher = {
|
|
113
|
+
name: 'pangoLineage',
|
|
114
|
+
url: AGGREGATED_ENDPOINT,
|
|
115
|
+
body: {
|
|
116
|
+
fields: ['pangoLineage'],
|
|
117
|
+
country: 'Germany',
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export const LineageFilter: StoryObj<Required<LineageFilterProps>> = {
|
|
122
|
+
...Template,
|
|
123
|
+
play: async ({ canvasElement }) => {
|
|
124
|
+
const canvas = await withinShadowRoot(canvasElement, 'gs-lineage-filter');
|
|
125
|
+
await waitFor(() => {
|
|
126
|
+
return expect(canvas.getByPlaceholderText('Enter a lineage')).toBeVisible();
|
|
127
|
+
});
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export const DelayToShowLoadingState: StoryObj<Required<LineageFilterProps>> = {
|
|
132
|
+
...Template,
|
|
133
|
+
parameters: {
|
|
134
|
+
fetchMock: {
|
|
135
|
+
mocks: [
|
|
136
|
+
{
|
|
137
|
+
matcher: aggregatedEndpointMatcher,
|
|
138
|
+
response: {
|
|
139
|
+
status: 200,
|
|
140
|
+
body: aggregatedData,
|
|
141
|
+
},
|
|
142
|
+
options: {
|
|
143
|
+
delay: 5000,
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
],
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
export const FetchingLocationsFails: StoryObj<Required<LineageFilterProps>> = {
|
|
152
|
+
...Template,
|
|
153
|
+
parameters: {
|
|
154
|
+
fetchMock: {
|
|
155
|
+
mocks: [
|
|
156
|
+
{
|
|
157
|
+
matcher: aggregatedEndpointMatcher,
|
|
158
|
+
response: {
|
|
159
|
+
status: 400,
|
|
160
|
+
body: {
|
|
161
|
+
error: { status: 400, detail: 'Dummy error message from mock LAPIS', type: 'about:blank' },
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
play: async ({ canvasElement }) => {
|
|
169
|
+
const canvas = await withinShadowRoot(canvasElement, 'gs-lineage-filter');
|
|
170
|
+
|
|
171
|
+
await waitFor(() =>
|
|
172
|
+
expect(canvas.getByText('Oops! Something went wrong.', { exact: false })).toBeInTheDocument(),
|
|
173
|
+
);
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
|
|
78
177
|
export const FiresEvent: StoryObj<Required<LineageFilterProps>> = {
|
|
79
|
-
...
|
|
178
|
+
...LineageFilter,
|
|
80
179
|
play: async ({ canvasElement, step }) => {
|
|
81
180
|
const canvas = await withinShadowRoot(canvasElement, 'gs-lineage-filter');
|
|
82
181
|
|
|
83
|
-
const inputField = () => canvas.getByPlaceholderText('Enter lineage');
|
|
182
|
+
const inputField = () => canvas.getByPlaceholderText('Enter a lineage');
|
|
84
183
|
const listenerMock = fn();
|
|
85
184
|
await step('Setup event listener mock', async () => {
|
|
86
185
|
canvasElement.addEventListener('gs-lineage-filter-changed', listenerMock);
|
|
@@ -99,38 +198,28 @@ export const FiresEvent: StoryObj<Required<LineageFilterProps>> = {
|
|
|
99
198
|
|
|
100
199
|
await step('Empty input', async () => {
|
|
101
200
|
await userEvent.type(inputField(), '{backspace>9/}');
|
|
102
|
-
await
|
|
103
|
-
pangoLineage: undefined,
|
|
104
|
-
});
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
await step('Enter a valid lineage value', async () => {
|
|
108
|
-
await userEvent.type(inputField(), 'B.1.1.7');
|
|
201
|
+
await userEvent.click(canvas.getByLabelText('toggle menu'));
|
|
109
202
|
|
|
110
|
-
await
|
|
111
|
-
expect.
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}),
|
|
116
|
-
);
|
|
203
|
+
await waitFor(() => {
|
|
204
|
+
return expect(listenerMock.mock.calls.at(-1)![0].detail).toStrictEqual({
|
|
205
|
+
pangoLineage: undefined,
|
|
206
|
+
});
|
|
207
|
+
});
|
|
117
208
|
});
|
|
118
209
|
|
|
119
210
|
await step('Enter a valid lineage value', async () => {
|
|
120
|
-
await userEvent.type(inputField(), '{backspace>9/}');
|
|
121
211
|
await userEvent.type(inputField(), 'B.1.1.7*');
|
|
212
|
+
await userEvent.click(canvas.getByRole('option', { name: 'B.1.1.7*' }));
|
|
122
213
|
|
|
123
|
-
await
|
|
124
|
-
expect.
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}),
|
|
129
|
-
);
|
|
214
|
+
await waitFor(() => {
|
|
215
|
+
return expect(listenerMock.mock.calls.at(-1)![0].detail).toStrictEqual({
|
|
216
|
+
pangoLineage: 'B.1.1.7*',
|
|
217
|
+
});
|
|
218
|
+
});
|
|
130
219
|
});
|
|
131
220
|
},
|
|
132
221
|
args: {
|
|
133
|
-
...
|
|
134
|
-
|
|
222
|
+
...LineageFilter.args,
|
|
223
|
+
value: '',
|
|
135
224
|
},
|
|
136
225
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { customElement, property } from 'lit/decorators.js';
|
|
2
2
|
import type { DetailedHTMLProps, HTMLAttributes } from 'react';
|
|
3
3
|
|
|
4
|
+
import { type LineageFilterChangedEvent } from '../../preact/lineageFilter/LineageFilterChangedEvent';
|
|
4
5
|
import { LineageFilter, type LineageFilterProps } from '../../preact/lineageFilter/lineage-filter';
|
|
5
6
|
import type { Equals, Expect } from '../../utils/typeAssertions';
|
|
6
7
|
import { PreactLitAdapter } from '../PreactLitAdapter';
|
|
@@ -13,11 +14,11 @@ import { PreactLitAdapter } from '../PreactLitAdapter';
|
|
|
13
14
|
* Currently, it is designed to work well with Pango Lineages,
|
|
14
15
|
* but it may also be used for other lineage types, if suitable.
|
|
15
16
|
*
|
|
16
|
-
* It fetches all available values of the `lapisField` from the LAPIS instance
|
|
17
|
+
* It fetches all available values of the `lapisField` from the LAPIS instance within the given `lapisFilter`
|
|
17
18
|
* and provides an autocomplete list with the available values of the lineage and sublineage queries
|
|
18
19
|
* (a `*` appended to the lineage value).
|
|
19
20
|
*
|
|
20
|
-
* @fires {CustomEvent<Record<string, string>>} gs-lineage-filter-changed
|
|
21
|
+
* @fires {CustomEvent<Record<string, string | undefined>>} gs-lineage-filter-changed
|
|
21
22
|
* Fired when the input field is changed.
|
|
22
23
|
* The `details` of this event contain an object with the `lapisField` as key and the input value as value.
|
|
23
24
|
* Example:
|
|
@@ -33,7 +34,7 @@ export class LineageFilterComponent extends PreactLitAdapter {
|
|
|
33
34
|
* The initial value to use for this lineage filter.
|
|
34
35
|
*/
|
|
35
36
|
@property()
|
|
36
|
-
|
|
37
|
+
value: string = '';
|
|
37
38
|
|
|
38
39
|
/**
|
|
39
40
|
* Required.
|
|
@@ -44,6 +45,19 @@ export class LineageFilterComponent extends PreactLitAdapter {
|
|
|
44
45
|
@property()
|
|
45
46
|
lapisField = '';
|
|
46
47
|
|
|
48
|
+
/**
|
|
49
|
+
* The filter that is used to fetch the available the autocomplete options.
|
|
50
|
+
* If not set it fetches all available options.
|
|
51
|
+
* It must be a valid LAPIS filter object.
|
|
52
|
+
*/
|
|
53
|
+
@property({ type: Object })
|
|
54
|
+
lapisFilter: Record<string, string | string[] | number | null | boolean | undefined> & {
|
|
55
|
+
nucleotideMutations?: string[];
|
|
56
|
+
aminoAcidMutations?: string[];
|
|
57
|
+
nucleotideInsertions?: string[];
|
|
58
|
+
aminoAcidInsertions?: string[];
|
|
59
|
+
} = {};
|
|
60
|
+
|
|
47
61
|
/**
|
|
48
62
|
* The placeholder text to display in the input field.
|
|
49
63
|
*/
|
|
@@ -62,8 +76,9 @@ export class LineageFilterComponent extends PreactLitAdapter {
|
|
|
62
76
|
return (
|
|
63
77
|
<LineageFilter
|
|
64
78
|
lapisField={this.lapisField}
|
|
79
|
+
lapisFilter={this.lapisFilter}
|
|
65
80
|
placeholderText={this.placeholderText}
|
|
66
|
-
|
|
81
|
+
value={this.value}
|
|
67
82
|
width={this.width}
|
|
68
83
|
/>
|
|
69
84
|
);
|
|
@@ -76,7 +91,7 @@ declare global {
|
|
|
76
91
|
}
|
|
77
92
|
|
|
78
93
|
interface HTMLElementEventMap {
|
|
79
|
-
'gs-lineage-filter-changed':
|
|
94
|
+
'gs-lineage-filter-changed': LineageFilterChangedEvent;
|
|
80
95
|
}
|
|
81
96
|
}
|
|
82
97
|
|
|
@@ -90,12 +105,13 @@ declare global {
|
|
|
90
105
|
}
|
|
91
106
|
|
|
92
107
|
/* eslint-disable @typescript-eslint/no-unused-vars, no-unused-vars */
|
|
93
|
-
type InitialValueMatches = Expect<
|
|
94
|
-
Equals<typeof LineageFilterComponent.prototype.initialValue, LineageFilterProps['initialValue']>
|
|
95
|
-
>;
|
|
108
|
+
type InitialValueMatches = Expect<Equals<typeof LineageFilterComponent.prototype.value, LineageFilterProps['value']>>;
|
|
96
109
|
type LapisFieldMatches = Expect<
|
|
97
110
|
Equals<typeof LineageFilterComponent.prototype.lapisField, LineageFilterProps['lapisField']>
|
|
98
111
|
>;
|
|
112
|
+
type LapisFilterMatches = Expect<
|
|
113
|
+
Equals<typeof LineageFilterComponent.prototype.lapisFilter, LineageFilterProps['lapisFilter']>
|
|
114
|
+
>;
|
|
99
115
|
type PlaceholderTextMatches = Expect<
|
|
100
116
|
Equals<typeof LineageFilterComponent.prototype.placeholderText, LineageFilterProps['placeholderText']>
|
|
101
117
|
>;
|
|
@@ -15,6 +15,7 @@ import { withinShadowRoot } from '../withinShadowRoot.story';
|
|
|
15
15
|
const codeExample = String.raw`
|
|
16
16
|
<gs-location-filter
|
|
17
17
|
fields='["region", "country"]'
|
|
18
|
+
lapisFilter='{"age": 10}'
|
|
18
19
|
value='{ "region": "Europe", "country": null}'
|
|
19
20
|
width="100%"
|
|
20
21
|
placeholderText="Enter a location"
|
|
@@ -54,6 +55,9 @@ const meta: Meta = {
|
|
|
54
55
|
type: 'text',
|
|
55
56
|
},
|
|
56
57
|
},
|
|
58
|
+
lapisFilter: {
|
|
59
|
+
age: 18,
|
|
60
|
+
},
|
|
57
61
|
},
|
|
58
62
|
tags: ['autodocs'],
|
|
59
63
|
};
|
|
@@ -66,6 +70,7 @@ const Template: StoryObj<LocationFilterProps> = {
|
|
|
66
70
|
<div class="max-w-screen-lg">
|
|
67
71
|
<gs-location-filter
|
|
68
72
|
.fields=${args.fields}
|
|
73
|
+
.lapisFilter=${args.lapisFilter}
|
|
69
74
|
.value=${args.value}
|
|
70
75
|
.width=${args.width}
|
|
71
76
|
placeholderText=${ifDefined(args.placeholderText)}
|
|
@@ -75,6 +80,9 @@ const Template: StoryObj<LocationFilterProps> = {
|
|
|
75
80
|
},
|
|
76
81
|
args: {
|
|
77
82
|
fields: ['region', 'country', 'division', 'location'],
|
|
83
|
+
lapisFilter: {
|
|
84
|
+
age: 18,
|
|
85
|
+
},
|
|
78
86
|
value: undefined,
|
|
79
87
|
width: '100%',
|
|
80
88
|
placeholderText: 'Enter a location',
|
|
@@ -86,6 +94,7 @@ const aggregatedEndpointMatcher = {
|
|
|
86
94
|
url: AGGREGATED_ENDPOINT,
|
|
87
95
|
body: {
|
|
88
96
|
fields: ['region', 'country', 'division', 'location'],
|
|
97
|
+
age: 18,
|
|
89
98
|
},
|
|
90
99
|
};
|
|
91
100
|
|
|
@@ -12,10 +12,11 @@ import { PreactLitAdapter } from '../PreactLitAdapter';
|
|
|
12
12
|
* This component provides an input field to specify filters for locations.
|
|
13
13
|
*
|
|
14
14
|
* It expects a list of fields that form a strict hierarchical order, such as continent, country, and city.
|
|
15
|
-
* The component retrieves a list of all possible values for these fields from the Lapis instance
|
|
15
|
+
* The component retrieves a list of all possible values for these fields from the Lapis instance,
|
|
16
|
+
* within the `lapisFilter`.
|
|
16
17
|
* This list is then utilized to display autocomplete suggestions and to validate the input.
|
|
17
18
|
*
|
|
18
|
-
* @fires {CustomEvent<Record<string, string>>} gs-location-changed
|
|
19
|
+
* @fires {CustomEvent<Record<string, string | undefined>>} gs-location-changed
|
|
19
20
|
* Fired when a value from the datalist is selected or when a valid value is typed into the field.
|
|
20
21
|
* The `details` of this event contain an object with all `fields` as keys
|
|
21
22
|
* and the corresponding values as values, even if they are `undefined`.
|
|
@@ -35,7 +36,7 @@ export class LocationFilterComponent extends PreactLitAdapter {
|
|
|
35
36
|
* The initial value to use for this location filter.
|
|
36
37
|
*/
|
|
37
38
|
@property({ type: Object })
|
|
38
|
-
value: Record<string, string |
|
|
39
|
+
value: Record<string, string | undefined> | undefined = undefined;
|
|
39
40
|
|
|
40
41
|
/**
|
|
41
42
|
* Required.
|
|
@@ -48,6 +49,19 @@ export class LocationFilterComponent extends PreactLitAdapter {
|
|
|
48
49
|
@property({ type: Array })
|
|
49
50
|
fields: string[] = [];
|
|
50
51
|
|
|
52
|
+
/**
|
|
53
|
+
* The filter that is used to fetch the available the autocomplete options.
|
|
54
|
+
* If not set it fetches all available options.
|
|
55
|
+
* It must be a valid LAPIS filter object.
|
|
56
|
+
*/
|
|
57
|
+
@property({ type: Object })
|
|
58
|
+
lapisFilter: Record<string, string | string[] | number | null | boolean | undefined> & {
|
|
59
|
+
nucleotideMutations?: string[];
|
|
60
|
+
aminoAcidMutations?: string[];
|
|
61
|
+
nucleotideInsertions?: string[];
|
|
62
|
+
aminoAcidInsertions?: string[];
|
|
63
|
+
} = {};
|
|
64
|
+
|
|
51
65
|
/**
|
|
52
66
|
* The width of the component.
|
|
53
67
|
*
|
|
@@ -67,6 +81,7 @@ export class LocationFilterComponent extends PreactLitAdapter {
|
|
|
67
81
|
<LocationFilter
|
|
68
82
|
value={this.value}
|
|
69
83
|
fields={this.fields}
|
|
84
|
+
lapisFilter={this.lapisFilter}
|
|
70
85
|
width={this.width}
|
|
71
86
|
placeholderText={this.placeholderText}
|
|
72
87
|
/>
|
|
@@ -96,6 +111,9 @@ declare global {
|
|
|
96
111
|
/* eslint-disable @typescript-eslint/no-unused-vars, no-unused-vars */
|
|
97
112
|
type InitialValueMatches = Expect<Equals<typeof LocationFilterComponent.prototype.value, LocationFilterProps['value']>>;
|
|
98
113
|
type FieldsMatches = Expect<Equals<typeof LocationFilterComponent.prototype.fields, LocationFilterProps['fields']>>;
|
|
114
|
+
type LapisFilterMatches = Expect<
|
|
115
|
+
Equals<typeof LocationFilterComponent.prototype.lapisFilter, LocationFilterProps['lapisFilter']>
|
|
116
|
+
>;
|
|
99
117
|
type PlaceholderTextMatches = Expect<
|
|
100
118
|
Equals<typeof LocationFilterComponent.prototype.placeholderText, LocationFilterProps['placeholderText']>
|
|
101
119
|
>;
|
|
@@ -14,8 +14,9 @@ import { withinShadowRoot } from '../withinShadowRoot.story';
|
|
|
14
14
|
const codeExample = String.raw`
|
|
15
15
|
<gs-text-input
|
|
16
16
|
lapisField="host"
|
|
17
|
+
lapisFilter='{"country": "Germany"}'
|
|
17
18
|
placeholderText="Enter host name"
|
|
18
|
-
|
|
19
|
+
value="Homo sapiens"
|
|
19
20
|
width="50%">
|
|
20
21
|
</gs-text-input>`;
|
|
21
22
|
|
|
@@ -34,6 +35,7 @@ const meta: Meta<Required<TextInputProps>> = {
|
|
|
34
35
|
url: AGGREGATED_ENDPOINT,
|
|
35
36
|
body: {
|
|
36
37
|
fields: ['host'],
|
|
38
|
+
country: 'Germany',
|
|
37
39
|
},
|
|
38
40
|
},
|
|
39
41
|
response: {
|
|
@@ -60,7 +62,7 @@ const meta: Meta<Required<TextInputProps>> = {
|
|
|
60
62
|
type: 'text',
|
|
61
63
|
},
|
|
62
64
|
},
|
|
63
|
-
|
|
65
|
+
value: {
|
|
64
66
|
control: {
|
|
65
67
|
type: 'text',
|
|
66
68
|
},
|
|
@@ -70,6 +72,11 @@ const meta: Meta<Required<TextInputProps>> = {
|
|
|
70
72
|
type: 'text',
|
|
71
73
|
},
|
|
72
74
|
},
|
|
75
|
+
lapisFilter: {
|
|
76
|
+
control: {
|
|
77
|
+
type: 'object',
|
|
78
|
+
},
|
|
79
|
+
},
|
|
73
80
|
},
|
|
74
81
|
tags: ['autodocs'],
|
|
75
82
|
};
|
|
@@ -82,8 +89,9 @@ export const Default: StoryObj<Required<TextInputProps>> = {
|
|
|
82
89
|
<div class="max-w-screen-lg">
|
|
83
90
|
<gs-text-input
|
|
84
91
|
.lapisField=${args.lapisField}
|
|
92
|
+
.lapisFilter=${args.lapisFilter}
|
|
85
93
|
.placeholderText=${args.placeholderText}
|
|
86
|
-
.
|
|
94
|
+
.value=${args.value}
|
|
87
95
|
.width=${args.width}
|
|
88
96
|
></gs-text-input>
|
|
89
97
|
</div>
|
|
@@ -91,8 +99,9 @@ export const Default: StoryObj<Required<TextInputProps>> = {
|
|
|
91
99
|
},
|
|
92
100
|
args: {
|
|
93
101
|
lapisField: 'host',
|
|
102
|
+
lapisFilter: { country: 'Germany' },
|
|
94
103
|
placeholderText: 'Enter host name',
|
|
95
|
-
|
|
104
|
+
value: 'Homo sapiens',
|
|
96
105
|
width: '100%',
|
|
97
106
|
},
|
|
98
107
|
};
|
|
@@ -163,6 +172,6 @@ export const FiresEvents: StoryObj<Required<TextInputProps>> = {
|
|
|
163
172
|
},
|
|
164
173
|
args: {
|
|
165
174
|
...Default.args,
|
|
166
|
-
|
|
175
|
+
value: '',
|
|
167
176
|
},
|
|
168
177
|
};
|