@genspectrum/dashboard-components 0.7.2 → 0.8.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.
Files changed (31) hide show
  1. package/custom-elements.json +1 -24
  2. package/dist/assets/mutationOverTimeWorker-ICjqmm9j.js.map +1 -0
  3. package/dist/dashboard-components.js +143 -90
  4. package/dist/dashboard-components.js.map +1 -1
  5. package/dist/genspectrum-components.d.ts +39 -50
  6. package/dist/style.css +36 -0
  7. package/package.json +1 -1
  8. package/src/lapisApi/lapisApi.ts +59 -34
  9. package/src/preact/aggregatedData/aggregate.stories.tsx +35 -0
  10. package/src/preact/aggregatedData/aggregate.tsx +1 -2
  11. package/src/preact/components/error-boundary.tsx +9 -4
  12. package/src/preact/components/error-display.stories.tsx +23 -3
  13. package/src/preact/components/error-display.tsx +37 -25
  14. package/src/preact/dateRangeSelector/date-range-selector.tsx +1 -1
  15. package/src/preact/lineageFilter/lineage-filter.tsx +2 -3
  16. package/src/preact/locationFilter/location-filter.tsx +2 -3
  17. package/src/preact/mutationComparison/mutation-comparison.tsx +1 -2
  18. package/src/preact/mutationFilter/mutation-filter.stories.tsx +5 -34
  19. package/src/preact/mutationFilter/mutation-filter.tsx +1 -14
  20. package/src/preact/mutations/mutations.tsx +1 -2
  21. package/src/preact/mutationsOverTime/mutations-over-time.tsx +11 -6
  22. package/src/preact/numberSequencesOverTime/number-sequences-over-time.tsx +1 -2
  23. package/src/preact/prevalenceOverTime/prevalence-over-time.tsx +1 -2
  24. package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.tsx +1 -2
  25. package/src/preact/textInput/text-input.tsx +2 -3
  26. package/src/preact/webWorkers/useWebWorker.ts +4 -8
  27. package/src/web-components/input/gs-mutation-filter.stories.ts +1 -27
  28. package/src/web-components/input/gs-mutation-filter.tsx +0 -11
  29. package/standalone-bundle/dashboard-components.js +4152 -4068
  30. package/standalone-bundle/dashboard-components.js.map +1 -1
  31. package/dist/assets/mutationOverTimeWorker-BOCXtKzd.js.map +0 -1
@@ -4,7 +4,6 @@ import { useContext, useRef } from 'preact/hooks';
4
4
  import { fetchLineageAutocompleteList } from './fetchLineageAutocompleteList';
5
5
  import { LapisUrlContext } from '../LapisUrlContext';
6
6
  import { ErrorBoundary } from '../components/error-boundary';
7
- import { ErrorDisplay } from '../components/error-display';
8
7
  import { LoadingDisplay } from '../components/loading-display';
9
8
  import { NoDataDisplay } from '../components/no-data-display';
10
9
  import { ResizeContainer } from '../components/resize-container';
@@ -24,7 +23,7 @@ export const LineageFilter: FunctionComponent<LineageFilterProps> = ({ width, ..
24
23
  const size = { width, height: '3rem' };
25
24
 
26
25
  return (
27
- <ErrorBoundary size={size}>
26
+ <ErrorBoundary size={size} layout='horizontal'>
28
27
  <ResizeContainer size={size}>
29
28
  <LineageFilterInner {...innerProps} />
30
29
  </ResizeContainer>
@@ -51,7 +50,7 @@ const LineageFilterInner: FunctionComponent<LineageFilterInnerProps> = ({
51
50
  }
52
51
 
53
52
  if (error !== null) {
54
- return <ErrorDisplay error={error} />;
53
+ throw error;
55
54
  }
56
55
 
57
56
  if (data === null) {
@@ -5,7 +5,6 @@ import { type JSXInternal } from 'preact/src/jsx';
5
5
  import { fetchAutocompletionList } from './fetchAutocompletionList';
6
6
  import { LapisUrlContext } from '../LapisUrlContext';
7
7
  import { ErrorBoundary } from '../components/error-boundary';
8
- import { ErrorDisplay } from '../components/error-display';
9
8
  import { LoadingDisplay } from '../components/loading-display';
10
9
  import { ResizeContainer } from '../components/resize-container';
11
10
  import { useQuery } from '../useQuery';
@@ -24,7 +23,7 @@ export const LocationFilter: FunctionComponent<LocationFilterProps> = ({ width,
24
23
  const size = { width, height: '3rem' };
25
24
 
26
25
  return (
27
- <ErrorBoundary size={size}>
26
+ <ErrorBoundary size={size} layout='horizontal'>
28
27
  <ResizeContainer size={size}>
29
28
  <LocationFilterInner {...innerProps} />
30
29
  </ResizeContainer>
@@ -46,7 +45,7 @@ export const LocationFilterInner = ({ initialValue, fields, placeholderText }: L
46
45
  return <LoadingDisplay />;
47
46
  }
48
47
  if (error) {
49
- return <ErrorDisplay error={error} />;
48
+ throw error;
50
49
  }
51
50
 
52
51
  const onInput = (event: JSXInternal.TargetedInputEvent<HTMLInputElement>) => {
@@ -9,7 +9,6 @@ import { type NamedLapisFilter, type SequenceType } from '../../types';
9
9
  import { LapisUrlContext } from '../LapisUrlContext';
10
10
  import { CsvDownloadButton } from '../components/csv-download-button';
11
11
  import { ErrorBoundary } from '../components/error-boundary';
12
- import { ErrorDisplay } from '../components/error-display';
13
12
  import { Fullscreen } from '../components/fullscreen';
14
13
  import Info, { InfoComponentCode, InfoHeadline1, InfoParagraph } from '../components/info';
15
14
  import { LoadingDisplay } from '../components/loading-display';
@@ -59,7 +58,7 @@ export const MutationComparisonInner: FunctionComponent<MutationComparisonProps>
59
58
  }
60
59
 
61
60
  if (error !== null) {
62
- return <ErrorDisplay error={error} />;
61
+ throw error;
63
62
  }
64
63
 
65
64
  if (data === null) {
@@ -14,7 +14,7 @@ const meta: Meta<MutationFilterProps> = {
14
14
  component: MutationFilter,
15
15
  parameters: {
16
16
  actions: {
17
- handles: ['gs-mutation-filter-changed', 'gs-mutation-filter-on-blur', ...previewHandles],
17
+ handles: ['gs-mutation-filter-changed', ...previewHandles],
18
18
  },
19
19
  fetchMock: {},
20
20
  },
@@ -120,32 +120,6 @@ export const FiresFilterChangedEvents: StoryObj<MutationFilterProps> = {
120
120
  },
121
121
  };
122
122
 
123
- export const FiresFilterOnBlurEvent: StoryObj<MutationFilterProps> = {
124
- ...Default,
125
- play: async ({ canvasElement, step }) => {
126
- const { canvas, onBlurListenerMock } = await prepare(canvasElement, step);
127
-
128
- await step('Move outside of input', async () => {
129
- await submitMutation(canvas, 'A234T');
130
- await submitMutation(canvas, 'S:A123G');
131
- await submitMutation(canvas, 'ins_123:AAA');
132
- await submitMutation(canvas, 'ins_S:123:AAA');
133
- await userEvent.tab();
134
-
135
- await expect(onBlurListenerMock).toHaveBeenCalledWith(
136
- expect.objectContaining({
137
- detail: {
138
- nucleotideMutations: ['A234T'],
139
- aminoAcidMutations: ['S:A123G'],
140
- nucleotideInsertions: ['ins_123:AAA'],
141
- aminoAcidInsertions: ['ins_S:123:AAA'],
142
- },
143
- }),
144
- );
145
- });
146
- },
147
- };
148
-
149
123
  export const WithInitialValue: StoryObj<MutationFilterProps> = {
150
124
  render: (args) => (
151
125
  <LapisUrlContext.Provider value={LAPIS_URL}>
@@ -164,13 +138,12 @@ export const WithInitialValue: StoryObj<MutationFilterProps> = {
164
138
  width: '100%',
165
139
  },
166
140
  play: async ({ canvasElement, step }) => {
167
- const { canvas, onBlurListenerMock } = await prepare(canvasElement, step);
141
+ const { canvas, changedListenerMock } = await prepare(canvasElement, step);
168
142
 
169
- await step('Move outside of input', async () => {
143
+ await step('Enter additional input', async () => {
170
144
  await submitMutation(canvas, 'G500T');
171
- await userEvent.tab();
172
145
 
173
- await expect(onBlurListenerMock).toHaveBeenCalledWith(
146
+ await expect(changedListenerMock).toHaveBeenCalledWith(
174
147
  expect.objectContaining({
175
148
  detail: {
176
149
  nucleotideMutations: ['A234T', 'G500T'],
@@ -187,10 +160,8 @@ export const WithInitialValue: StoryObj<MutationFilterProps> = {
187
160
  async function prepare(canvasElement: HTMLElement, step: StepFunction<PreactRenderer, unknown>) {
188
161
  const canvas = within(canvasElement);
189
162
 
190
- const onBlurListenerMock = fn();
191
163
  const changedListenerMock = fn();
192
164
  await step('Setup event listener mock', async () => {
193
- canvasElement.addEventListener('gs-mutation-filter-on-blur', onBlurListenerMock);
194
165
  canvasElement.addEventListener('gs-mutation-filter-changed', changedListenerMock);
195
166
  });
196
167
 
@@ -200,7 +171,7 @@ async function prepare(canvasElement: HTMLElement, step: StepFunction<PreactRend
200
171
  });
201
172
  });
202
173
 
203
- return { canvas, onBlurListenerMock, changedListenerMock };
174
+ return { canvas, changedListenerMock };
204
175
  }
205
176
 
206
177
  const submitMutation = async (canvas: ReturnType<typeof within>, mutation: string) => {
@@ -35,7 +35,7 @@ export type SelectedMutationFilterStrings = {
35
35
 
36
36
  export const MutationFilter: FunctionComponent<MutationFilterProps> = ({ initialValue, width }) => {
37
37
  return (
38
- <ErrorBoundary size={{ height: '3.375rem', width }}>
38
+ <ErrorBoundary size={{ height: '3.375rem', width }} layout='horizontal'>
39
39
  <div style={width}>
40
40
  <MutationFilterInner initialValue={initialValue} />
41
41
  </div>
@@ -87,18 +87,6 @@ export const MutationFilterInner: FunctionComponent<MutationFilterInnerProps> =
87
87
  );
88
88
  };
89
89
 
90
- const handleOnBlur = () => {
91
- const detail = mapToMutationFilterStrings(selectedFilters);
92
-
93
- formRef.current?.dispatchEvent(
94
- new CustomEvent<SelectedMutationFilterStrings>('gs-mutation-filter-on-blur', {
95
- detail,
96
- bubbles: true,
97
- composed: true,
98
- }),
99
- );
100
- };
101
-
102
90
  const handleInputChange = (event: Event) => {
103
91
  setInputValue((event.target as HTMLInputElement).value);
104
92
  setIsError(false);
@@ -124,7 +112,6 @@ export const MutationFilterInner: FunctionComponent<MutationFilterInnerProps> =
124
112
  value={inputValue}
125
113
  onInput={handleInputChange}
126
114
  placeholder={getPlaceholder(referenceGenome)}
127
- onBlur={handleOnBlur}
128
115
  />
129
116
  <button type='submit' className='btn btn-xs m-1'>
130
117
  +
@@ -16,7 +16,6 @@ import {
16
16
  import { LapisUrlContext } from '../LapisUrlContext';
17
17
  import { CsvDownloadButton } from '../components/csv-download-button';
18
18
  import { ErrorBoundary } from '../components/error-boundary';
19
- import { ErrorDisplay } from '../components/error-display';
20
19
  import { Fullscreen } from '../components/fullscreen';
21
20
  import Info, { InfoComponentCode, InfoHeadline1, InfoHeadline2, InfoLink, InfoParagraph } from '../components/info';
22
21
  import { LoadingDisplay } from '../components/loading-display';
@@ -66,7 +65,7 @@ export const MutationsInner: FunctionComponent<MutationsProps> = (componentProps
66
65
  }
67
66
 
68
67
  if (error !== null) {
69
- return <ErrorDisplay error={error} />;
68
+ throw error;
70
69
  }
71
70
 
72
71
  if (data === null) {
@@ -21,7 +21,6 @@ import { type ColorScale } from '../components/color-scale-selector';
21
21
  import { ColorScaleSelectorDropdown } from '../components/color-scale-selector-dropdown';
22
22
  import { CsvDownloadButton } from '../components/csv-download-button';
23
23
  import { ErrorBoundary } from '../components/error-boundary';
24
- import { ErrorDisplay } from '../components/error-display';
25
24
  import { Fullscreen } from '../components/fullscreen';
26
25
  import Info, { InfoComponentCode, InfoHeadline1, InfoParagraph } from '../components/info';
27
26
  import { LoadingDisplay } from '../components/loading-display';
@@ -63,15 +62,21 @@ export const MutationsOverTimeInner: FunctionComponent<MutationsOverTimeProps> =
63
62
  const lapis = useContext(LapisUrlContext);
64
63
  const { lapisFilter, sequenceType, granularity, lapisDateField } = componentProps;
65
64
 
66
- const { data, error, isLoading } = useWebWorker<MutationOverTimeQuery, MutationOverTimeWorkerResponse>(
67
- {
65
+ const messageToWorker = useMemo(() => {
66
+ return {
68
67
  lapisFilter,
69
68
  sequenceType,
70
69
  granularity,
71
70
  lapisDateField,
72
71
  lapis,
73
- },
74
- new MutationOverTimeWorker() as Worker,
72
+ };
73
+ }, [granularity, lapis, lapisDateField, lapisFilter, sequenceType]);
74
+
75
+ const worker = useMemo(() => new MutationOverTimeWorker(), []);
76
+
77
+ const { data, error, isLoading } = useWebWorker<MutationOverTimeQuery, MutationOverTimeWorkerResponse>(
78
+ messageToWorker,
79
+ worker,
75
80
  );
76
81
 
77
82
  if (isLoading) {
@@ -79,7 +84,7 @@ export const MutationsOverTimeInner: FunctionComponent<MutationsOverTimeProps> =
79
84
  }
80
85
 
81
86
  if (error !== undefined) {
82
- return <ErrorDisplay error={error} />;
87
+ throw error;
83
88
  }
84
89
 
85
90
  if (data === null || data === undefined) {
@@ -13,7 +13,6 @@ import type { NamedLapisFilter, TemporalGranularity } from '../../types';
13
13
  import { LapisUrlContext } from '../LapisUrlContext';
14
14
  import { CsvDownloadButton } from '../components/csv-download-button';
15
15
  import { ErrorBoundary } from '../components/error-boundary';
16
- import { ErrorDisplay } from '../components/error-display';
17
16
  import { Fullscreen } from '../components/fullscreen';
18
17
  import Info, { InfoComponentCode, InfoHeadline1, InfoParagraph } from '../components/info';
19
18
  import { LoadingDisplay } from '../components/loading-display';
@@ -64,7 +63,7 @@ const NumberSequencesOverTimeInner = (componentProps: NumberSequencesOverTimePro
64
63
  }
65
64
 
66
65
  if (error !== null) {
67
- return <ErrorDisplay error={error} />;
66
+ throw error;
68
67
  }
69
68
 
70
69
  if (data === null) {
@@ -12,7 +12,6 @@ import { LapisUrlContext } from '../LapisUrlContext';
12
12
  import { ConfidenceIntervalSelector } from '../components/confidence-interval-selector';
13
13
  import { CsvDownloadButton } from '../components/csv-download-button';
14
14
  import { ErrorBoundary } from '../components/error-boundary';
15
- import { ErrorDisplay } from '../components/error-display';
16
15
  import { Fullscreen } from '../components/fullscreen';
17
16
  import Info, { InfoComponentCode, InfoHeadline1, InfoHeadline2, InfoParagraph } from '../components/info';
18
17
  import { LoadingDisplay } from '../components/loading-display';
@@ -77,7 +76,7 @@ export const PrevalenceOverTimeInner: FunctionComponent<PrevalenceOverTimeProps>
77
76
  }
78
77
 
79
78
  if (error !== null) {
80
- return <ErrorDisplay error={error} />;
79
+ throw error;
81
80
  }
82
81
 
83
82
  if (data === null || data.every((variant) => variant.content.length === 0)) {
@@ -9,7 +9,6 @@ import {
9
9
  import { type LapisFilter } from '../../types';
10
10
  import { LapisUrlContext } from '../LapisUrlContext';
11
11
  import { ErrorBoundary } from '../components/error-boundary';
12
- import { ErrorDisplay } from '../components/error-display';
13
12
  import { Fullscreen } from '../components/fullscreen';
14
13
  import Info, { InfoComponentCode, InfoHeadline1, InfoHeadline2, InfoLink, InfoParagraph } from '../components/info';
15
14
  import { LoadingDisplay } from '../components/loading-display';
@@ -63,7 +62,7 @@ export const RelativeGrowthAdvantageInner: FunctionComponent<RelativeGrowthAdvan
63
62
  }
64
63
 
65
64
  if (error !== null) {
66
- return <ErrorDisplay error={error} />;
65
+ throw error;
67
66
  }
68
67
 
69
68
  if (data === null) {
@@ -4,7 +4,6 @@ import { useContext, useRef } from 'preact/hooks';
4
4
  import { fetchAutocompleteList } from './fetchAutocompleteList';
5
5
  import { LapisUrlContext } from '../LapisUrlContext';
6
6
  import { ErrorBoundary } from '../components/error-boundary';
7
- import { ErrorDisplay } from '../components/error-display';
8
7
  import { LoadingDisplay } from '../components/loading-display';
9
8
  import { NoDataDisplay } from '../components/no-data-display';
10
9
  import { ResizeContainer } from '../components/resize-container';
@@ -24,7 +23,7 @@ export const TextInput: FunctionComponent<TextInputProps> = ({ width, ...innerPr
24
23
  const size = { width, height: '3rem' };
25
24
 
26
25
  return (
27
- <ErrorBoundary size={size}>
26
+ <ErrorBoundary size={size} layout='horizontal'>
28
27
  <ResizeContainer size={size}>
29
28
  <TextInputInner {...innerProps} />
30
29
  </ResizeContainer>
@@ -44,7 +43,7 @@ const TextInputInner: FunctionComponent<TextInputInnerProps> = ({ lapisField, pl
44
43
  }
45
44
 
46
45
  if (error !== null) {
47
- return <ErrorDisplay error={error} />;
46
+ throw error;
48
47
  }
49
48
 
50
49
  if (data === null) {
@@ -1,4 +1,4 @@
1
- import { useEffect, useMemo, useState } from 'preact/hooks';
1
+ import { useEffect, useState } from 'preact/hooks';
2
2
 
3
3
  export function useWebWorker<Request, Response>(messageToWorker: Request, worker: Worker) {
4
4
  const [data, setData] = useState<Response | undefined>(undefined);
@@ -37,15 +37,11 @@ export function useWebWorker<Request, Response>(messageToWorker: Request, worker
37
37
  setError(new Error(`Worker received a message that it cannot deserialize: ${event.data}`));
38
38
  setIsLoading(false);
39
39
  };
40
+ }, [worker]);
40
41
 
41
- return () => {
42
- worker.terminate();
43
- };
44
- }, []);
45
-
46
- useMemo(() => {
42
+ useEffect(() => {
47
43
  worker.postMessage(messageToWorker);
48
- }, [messageToWorker]);
44
+ }, [messageToWorker, worker]);
49
45
 
50
46
  return { data, error, isLoading };
51
47
  }
@@ -21,7 +21,7 @@ const meta: Meta<MutationFilterProps> = {
21
21
  component: 'gs-mutation-filter',
22
22
  parameters: withComponentDocs({
23
23
  actions: {
24
- handles: ['gs-mutation-filter-changed', 'gs-mutation-filter-on-blur', ...previewHandles],
24
+ handles: ['gs-mutation-filter-changed', ...previewHandles],
25
25
  },
26
26
  fetchMock: {},
27
27
  componentDocs: {
@@ -102,32 +102,6 @@ export const FiresFilterChangedEvent: StoryObj<MutationFilterProps> = {
102
102
  },
103
103
  };
104
104
 
105
- export const FiresFilterOnBlurEvent: StoryObj<MutationFilterProps> = {
106
- ...Template,
107
- play: async ({ canvasElement, step }) => {
108
- const canvas = await withinShadowRoot(canvasElement, 'gs-mutation-filter');
109
-
110
- const inputField = () => canvas.getByPlaceholderText('Enter a mutation', { exact: false });
111
- const listenerMock = fn();
112
- await step('Setup event listener mock', async () => {
113
- canvasElement.addEventListener('gs-mutation-filter-on-blur', listenerMock);
114
- });
115
-
116
- await step('wait until data is loaded', async () => {
117
- await waitFor(() => {
118
- return expect(inputField()).toBeEnabled();
119
- });
120
- });
121
-
122
- await step('Move outside of input', async () => {
123
- await userEvent.type(inputField(), 'A123T');
124
- await userEvent.tab();
125
-
126
- await expect(listenerMock).toHaveBeenCalled();
127
- });
128
- },
129
- };
130
-
131
105
  export const MultiSegmentedReferenceGenomes: StoryObj<MutationFilterProps> = {
132
106
  ...Template,
133
107
  args: {
@@ -53,16 +53,6 @@ import { PreactLitAdapter } from '../PreactLitAdapter';
53
53
  * Fired when:
54
54
  * - The user has submitted a valid mutation or insertion
55
55
  * - The user has removed a mutation or insertion
56
- *
57
- * @fires {CustomEvent<{
58
- * nucleotideMutations: string[],
59
- * aminoAcidMutations: string[],
60
- * nucleotideInsertions: string[],
61
- * aminoAcidInsertions: string[]
62
- * }>} gs-mutation-filter-on-blur
63
- * Fired when:
64
- * - the mutation filter has lost focus
65
- * Contains the selected mutations in the format
66
56
  */
67
57
  @customElement('gs-mutation-filter')
68
58
  export class MutationFilterComponent extends PreactLitAdapter {
@@ -108,7 +98,6 @@ declare global {
108
98
 
109
99
  interface HTMLElementEventMap {
110
100
  'gs-mutation-filter-changed': CustomEvent<SelectedMutationFilterStrings>;
111
- 'gs-mutation-filter-on-blur': CustomEvent<SelectedMutationFilterStrings>;
112
101
  }
113
102
  }
114
103