@genspectrum/dashboard-components 1.6.0 → 1.8.0

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 (44) hide show
  1. package/README.md +4 -0
  2. package/custom-elements.json +82 -1
  3. package/dist/{NumberRangeFilterChangedEvent-CQ32Qy8D.js → NumberRangeFilterChangedEvent-BnPI-Asz.js} +17 -3
  4. package/dist/NumberRangeFilterChangedEvent-BnPI-Asz.js.map +1 -0
  5. package/dist/assets/{mutationOverTimeWorker-BmB6BvVM.js.map → mutationOverTimeWorker-DPS3tmOd.js.map} +1 -1
  6. package/dist/components.d.ts +52 -25
  7. package/dist/components.js +209 -75
  8. package/dist/components.js.map +1 -1
  9. package/dist/util.d.ts +48 -24
  10. package/dist/util.js +2 -1
  11. package/package.json +1 -1
  12. package/src/preact/MutationLinkTemplateContext.tsx +56 -0
  13. package/src/preact/components/annotated-mutation.stories.tsx +69 -17
  14. package/src/preact/components/annotated-mutation.tsx +45 -19
  15. package/src/preact/genomeViewer/CDSPlot.tsx +13 -2
  16. package/src/preact/genomeViewer/loadGff3.ts +6 -0
  17. package/src/preact/mutationComparison/mutation-comparison-table.tsx +3 -0
  18. package/src/preact/mutationFilter/mutation-filter.stories.tsx +2 -1
  19. package/src/preact/mutationFilter/mutation-filter.tsx +24 -27
  20. package/src/preact/mutationFilter/parseAndValidateMutation.ts +11 -11
  21. package/src/preact/mutationFilter/parseMutation.spec.ts +32 -22
  22. package/src/preact/mutations/mutations-table.tsx +3 -0
  23. package/src/preact/mutationsOverTime/mutations-over-time.tsx +7 -4
  24. package/src/preact/shared/stories/expectMutationAnnotation.ts +3 -1
  25. package/src/types.ts +17 -1
  26. package/src/utilEntrypoint.ts +4 -0
  27. package/src/utils/mutations.spec.ts +19 -0
  28. package/src/utils/mutations.ts +57 -10
  29. package/src/web-components/gs-app.spec-d.ts +7 -0
  30. package/src/web-components/gs-app.stories.ts +32 -2
  31. package/src/web-components/gs-app.ts +17 -0
  32. package/src/web-components/input/gs-mutation-filter.stories.ts +2 -1
  33. package/src/web-components/input/gs-mutation-filter.tsx +2 -6
  34. package/src/web-components/mutation-link-template-context.ts +13 -0
  35. package/src/web-components/mutationLinks.mdx +27 -0
  36. package/src/web-components/visualization/gs-mutation-comparison.tsx +18 -8
  37. package/src/web-components/visualization/gs-mutations-over-time.stories.ts +12 -1
  38. package/src/web-components/visualization/gs-mutations-over-time.tsx +24 -14
  39. package/src/web-components/visualization/gs-mutations.tsx +19 -9
  40. package/src/web-components/wastewaterVisualization/gs-wastewater-mutations-over-time.tsx +17 -7
  41. package/standalone-bundle/assets/{mutationOverTimeWorker-B_xP8pIC.js.map → mutationOverTimeWorker-Dp-A14AP.js.map} +1 -1
  42. package/standalone-bundle/dashboard-components.js +7295 -7193
  43. package/standalone-bundle/dashboard-components.js.map +1 -1
  44. package/dist/NumberRangeFilterChangedEvent-CQ32Qy8D.js.map +0 -1
@@ -11,6 +11,7 @@ import { referenceGenomeContext } from './reference-genome-context';
11
11
  import { withComponentDocs } from '../../.storybook/ComponentDocsBlock';
12
12
  import { LAPIS_URL, REFERENCE_GENOME_ENDPOINT } from '../constants';
13
13
  import { type MutationAnnotations, mutationAnnotationsContext } from './mutation-annotations-context';
14
+ import { type MutationLinkTemplate, mutationLinkTemplateContext } from './mutation-link-template-context';
14
15
  import type { ReferenceGenome } from '../lapisApi/ReferenceGenome';
15
16
  import referenceGenome from '../lapisApi/__mockData__/referenceGenome.json';
16
17
 
@@ -35,11 +36,19 @@ const meta: Meta = {
35
36
 
36
37
  export default meta;
37
38
 
38
- type StoryProps = { lapis: string; mutationAnnotations: MutationAnnotations };
39
+ type StoryProps = {
40
+ lapis: string;
41
+ mutationAnnotations: MutationAnnotations;
42
+ mutationLinkTemplate: MutationLinkTemplate;
43
+ };
39
44
 
40
45
  const Template: StoryObj<StoryProps> = {
41
46
  render: (args) => {
42
- return html` <gs-app lapis="${args.lapis}" .mutationAnnotations="${args.mutationAnnotations}">
47
+ return html` <gs-app
48
+ lapis="${args.lapis}"
49
+ .mutationAnnotations="${args.mutationAnnotations}"
50
+ .mutationLinkTemplate="${args.mutationLinkTemplate}"
51
+ >
43
52
  <gs-app-display></gs-app-display>
44
53
  </gs-app>`;
45
54
  },
@@ -56,6 +65,10 @@ const Template: StoryObj<StoryProps> = {
56
65
  aminoAcidPositions: ['S:123'],
57
66
  },
58
67
  ],
68
+ mutationLinkTemplate: {
69
+ nucleotideMutation: 'http://foo.com/query?nucMut={{mutation}}',
70
+ aminoAcidMutation: 'http://foo.com/query?aaMut={{mutation}}',
71
+ },
59
72
  },
60
73
  };
61
74
 
@@ -128,6 +141,18 @@ export const FailsToFetchReferenceGenome: StoryObj<StoryProps> = {
128
141
  },
129
142
  };
130
143
 
144
+ export const ProvidesMutationLinkTemplateToChildren: StoryObj<StoryProps> = {
145
+ ...Template,
146
+ play: async ({ canvasElement }) => {
147
+ const canvas = within(canvasElement);
148
+
149
+ await waitFor(async () => {
150
+ await expect(canvas.getByText('http://foo.com/query?nucMut={{mutation}}', { exact: false })).toBeVisible();
151
+ await expect(canvas.getByText('http://foo.com/query?aaMut={{mutation}}', { exact: false })).toBeVisible();
152
+ });
153
+ },
154
+ };
155
+
131
156
  @customElement('gs-app-display')
132
157
  // eslint-disable-next-line @typescript-eslint/no-unused-vars -- it is used in the story above
133
158
  class AppDisplay extends LitElement {
@@ -143,6 +168,9 @@ class AppDisplay extends LitElement {
143
168
  @consume({ context: mutationAnnotationsContext, subscribe: true })
144
169
  mutationAnnotations: MutationAnnotations = [];
145
170
 
171
+ @consume({ context: mutationLinkTemplateContext, subscribe: true })
172
+ mutationLinkTemplate: MutationLinkTemplate = {};
173
+
146
174
  override render() {
147
175
  return html`
148
176
  <h1 class="text-xl font-bold">Dummy component</h1>
@@ -156,6 +184,8 @@ class AppDisplay extends LitElement {
156
184
  <pre><code>${JSON.stringify(this.referenceGenome, null, 2)}</code></pre>
157
185
  <h2 class="text-lg font-bold">Mutation annotations</h2>
158
186
  <pre><code>${JSON.stringify(this.mutationAnnotations, null, 2)}</code></pre>
187
+ <h2 class="text-lg font-bold">Mutation link template</h2>
188
+ <pre><code>${JSON.stringify(this.mutationLinkTemplate, null, 2)}</code></pre>
159
189
  `;
160
190
  }
161
191
 
@@ -7,6 +7,7 @@ import z from 'zod';
7
7
 
8
8
  import { lapisContext } from './lapis-context';
9
9
  import { mutationAnnotationsContext } from './mutation-annotations-context';
10
+ import { mutationLinkTemplateContext } from './mutation-link-template-context';
10
11
  import { referenceGenomeContext } from './reference-genome-context';
11
12
  import { type ReferenceGenome } from '../lapisApi/ReferenceGenome';
12
13
  import { fetchReferenceGenome } from '../lapisApi/lapisApi';
@@ -59,6 +60,22 @@ export class AppComponent extends LitElement {
59
60
  aminoAcidPositions?: string[];
60
61
  }[] = [];
61
62
 
63
+ /**
64
+ * Supply a link template for nucleotide and amino acid mutations.
65
+ * The template should include '{{mutation}}' where the mutation code will be inserted, for example:
66
+ *
67
+ * https://my-site.org/query?nucleotideMutation={{mutation}}
68
+ */
69
+ @provide({ context: mutationLinkTemplateContext })
70
+ @property({ type: Object })
71
+ mutationLinkTemplate: {
72
+ nucleotideMutation?: string;
73
+ aminoAcidMutation?: string;
74
+ } = {
75
+ nucleotideMutation: undefined,
76
+ aminoAcidMutation: undefined,
77
+ };
78
+
62
79
  /**
63
80
  * @internal
64
81
  */
@@ -7,6 +7,7 @@ import { previewHandles } from '../../../.storybook/preview';
7
7
  import { LAPIS_URL, REFERENCE_GENOME_ENDPOINT } from '../../constants';
8
8
  import '../gs-app';
9
9
  import { type MutationFilterProps } from '../../preact/mutationFilter/mutation-filter';
10
+ import { mutationType } from '../../types';
10
11
  import { gsEventNames } from '../../utils/gsEventNames';
11
12
  import { withinShadowRoot } from '../withinShadowRoot.story';
12
13
  import './gs-mutation-filter';
@@ -117,7 +118,7 @@ export const RestrictEnabledMutationTypes: StoryObj<MutationFilterProps> = {
117
118
  ...Template,
118
119
  args: {
119
120
  ...Template.args,
120
- enabledMutationTypes: ['nucleotideMutations', 'aminoAcidMutations'],
121
+ enabledMutationTypes: [mutationType.nucleotideMutations, mutationType.aminoAcidMutations],
121
122
  },
122
123
  play: async ({ canvasElement }) => {
123
124
  const canvas = await withinShadowRoot(canvasElement, 'gs-mutation-filter');
@@ -2,12 +2,8 @@ import { customElement, property } from 'lit/decorators.js';
2
2
  import type { DetailedHTMLProps, HTMLAttributes } from 'react';
3
3
 
4
4
  import { ReferenceGenomesAwaiter } from '../../preact/components/ReferenceGenomesAwaiter';
5
- import {
6
- MutationFilter,
7
- type MutationType,
8
- type MutationFilterProps,
9
- } from '../../preact/mutationFilter/mutation-filter';
10
- import type { MutationsFilter } from '../../types';
5
+ import { MutationFilter, type MutationFilterProps } from '../../preact/mutationFilter/mutation-filter';
6
+ import type { MutationType, MutationsFilter } from '../../types';
11
7
  import { type gsEventNames } from '../../utils/gsEventNames';
12
8
  import type { Equals, Expect } from '../../utils/typeAssertions';
13
9
  import { PreactLitAdapter } from '../PreactLitAdapter';
@@ -0,0 +1,13 @@
1
+ import { createContext } from '@lit/context';
2
+ import z from 'zod';
3
+
4
+ export const mutationLinkTemplateSchema = z.object({
5
+ nucleotideMutation: z.string().optional(),
6
+ aminoAcidMutation: z.string().optional(),
7
+ });
8
+
9
+ export type MutationLinkTemplate = z.infer<typeof mutationLinkTemplateSchema>;
10
+
11
+ export const mutationLinkTemplateContext = createContext<MutationLinkTemplate>(
12
+ Symbol('mutation-link-template-context'),
13
+ );
@@ -0,0 +1,27 @@
1
+ import { Meta } from '@storybook/blocks';
2
+
3
+ <Meta title='Concepts/External Mutation Links' />
4
+
5
+ # External Mutation Links
6
+
7
+ When inspecting mutations, you might want to be able to quickly look up a mutation somewhere else.
8
+ For this, it's possible to supply a _link template_ which will then lead to mutation labels being clickable using
9
+ that link template.
10
+
11
+ The mutation link template can be (optionally) supplied to `gs-app` as a JSON object:
12
+
13
+ ```html
14
+ <gs-app
15
+ lapis="https://your.lapis.url"
16
+ mutationLinkTemplate='{
17
+ "nucleotideMutation": "https://cov-spectrum.org/explore/Switzerland/AllSamples/Past6M/variants?nucMutations={{mutation}}",
18
+ "aminoAcidMutation": "https://cov-spectrum.org/explore/Switzerland/AllSamples/Past6M/variants?aaMutations={{mutation}}",
19
+ }'
20
+ >
21
+ {/* children... */}
22
+ </gs-app>
23
+ ```
24
+
25
+ For example (like in the code above) you can directly link to cov-spectrum.
26
+
27
+ Your link needs to have the placeholder `{{mutation}}` in it, which will then be replaced with the mutation code.
@@ -3,10 +3,12 @@ import { customElement, property } from 'lit/decorators.js';
3
3
  import type { DetailedHTMLProps, HTMLAttributes } from 'react';
4
4
 
5
5
  import { MutationAnnotationsContextProvider } from '../../preact/MutationAnnotationsContext';
6
+ import { MutationLinkTemplateContextProvider } from '../../preact/MutationLinkTemplateContext';
6
7
  import { MutationComparison, type MutationComparisonProps } from '../../preact/mutationComparison/mutation-comparison';
7
8
  import { type Equals, type Expect } from '../../utils/typeAssertions';
8
9
  import { PreactLitAdapterWithGridJsStyles } from '../PreactLitAdapterWithGridJsStyles';
9
10
  import { type MutationAnnotations, mutationAnnotationsContext } from '../mutation-annotations-context';
11
+ import { type MutationLinkTemplate, mutationLinkTemplateContext } from '../mutation-link-template-context';
10
12
 
11
13
  /**
12
14
  * ## Context
@@ -103,17 +105,25 @@ export class MutationComparisonComponent extends PreactLitAdapterWithGridJsStyle
103
105
  @consume({ context: mutationAnnotationsContext, subscribe: true })
104
106
  mutationAnnotations: MutationAnnotations = [];
105
107
 
108
+ /**
109
+ * @internal
110
+ */
111
+ @consume({ context: mutationLinkTemplateContext, subscribe: true })
112
+ mutationLinkTemplate: MutationLinkTemplate = {};
113
+
106
114
  override render() {
107
115
  return (
108
116
  <MutationAnnotationsContextProvider value={this.mutationAnnotations}>
109
- <MutationComparison
110
- lapisFilters={this.lapisFilters}
111
- sequenceType={this.sequenceType}
112
- views={this.views}
113
- width={this.width}
114
- height={this.height}
115
- pageSize={this.pageSize}
116
- />
117
+ <MutationLinkTemplateContextProvider value={this.mutationLinkTemplate}>
118
+ <MutationComparison
119
+ lapisFilters={this.lapisFilters}
120
+ sequenceType={this.sequenceType}
121
+ views={this.views}
122
+ width={this.width}
123
+ height={this.height}
124
+ pageSize={this.pageSize}
125
+ />
126
+ </MutationLinkTemplateContextProvider>
117
127
  </MutationAnnotationsContextProvider>
118
128
  );
119
129
  }
@@ -89,9 +89,20 @@ const mutationAnnotations = [
89
89
  },
90
90
  ];
91
91
 
92
+ const mutationLinkTemplate = {
93
+ nucleotideMutation:
94
+ 'https://cov-spectrum.org/explore/Switzerland/AllSamples/Past6M/variants?nucMutations={{mutation}}',
95
+ aminoAcidMutation:
96
+ 'https://cov-spectrum.org/explore/Switzerland/AllSamples/Past6M/variants?aaMutations={{mutation}}',
97
+ };
98
+
92
99
  const Template: StoryObj<Required<MutationsOverTimeProps>> = {
93
100
  render: (args) => html`
94
- <gs-app lapis="${LAPIS_URL}" .mutationAnnotations=${mutationAnnotations}>
101
+ <gs-app
102
+ lapis="${LAPIS_URL}"
103
+ .mutationAnnotations=${mutationAnnotations}
104
+ .mutationLinkTemplate=${mutationLinkTemplate}
105
+ >
95
106
  <gs-mutations-over-time
96
107
  .lapisFilter=${args.lapisFilter}
97
108
  .sequenceType=${args.sequenceType}
@@ -3,9 +3,11 @@ import { customElement, property } from 'lit/decorators.js';
3
3
  import type { DetailedHTMLProps, HTMLAttributes } from 'react';
4
4
 
5
5
  import { MutationAnnotationsContextProvider } from '../../preact/MutationAnnotationsContext';
6
+ import { MutationLinkTemplateContextProvider } from '../../preact/MutationLinkTemplateContext';
6
7
  import { MutationsOverTime } from '../../preact/mutationsOverTime/mutations-over-time';
7
8
  import { PreactLitAdapterWithGridJsStyles } from '../PreactLitAdapterWithGridJsStyles';
8
9
  import { type MutationAnnotations, mutationAnnotationsContext } from '../mutation-annotations-context';
10
+ import { type MutationLinkTemplate, mutationLinkTemplateContext } from '../mutation-link-template-context';
9
11
 
10
12
  /**
11
13
  * ## Context
@@ -140,23 +142,31 @@ export class MutationsOverTimeComponent extends PreactLitAdapterWithGridJsStyles
140
142
  @consume({ context: mutationAnnotationsContext, subscribe: true })
141
143
  mutationAnnotations: MutationAnnotations = [];
142
144
 
145
+ /**
146
+ * @internal
147
+ */
148
+ @consume({ context: mutationLinkTemplateContext, subscribe: true })
149
+ mutationLinkTemplate: MutationLinkTemplate = {};
150
+
143
151
  override render() {
144
152
  return (
145
153
  <MutationAnnotationsContextProvider value={this.mutationAnnotations}>
146
- <MutationsOverTime
147
- lapisFilter={this.lapisFilter}
148
- sequenceType={this.sequenceType}
149
- views={this.views}
150
- width={this.width}
151
- height={this.height}
152
- granularity={this.granularity}
153
- lapisDateField={this.lapisDateField}
154
- displayMutations={this.displayMutations}
155
- initialMeanProportionInterval={this.initialMeanProportionInterval}
156
- hideGaps={this.hideGaps}
157
- useNewEndpoint={this.useNewEndpoint}
158
- pageSizes={this.pageSizes}
159
- />
154
+ <MutationLinkTemplateContextProvider value={this.mutationLinkTemplate}>
155
+ <MutationsOverTime
156
+ lapisFilter={this.lapisFilter}
157
+ sequenceType={this.sequenceType}
158
+ views={this.views}
159
+ width={this.width}
160
+ height={this.height}
161
+ granularity={this.granularity}
162
+ lapisDateField={this.lapisDateField}
163
+ displayMutations={this.displayMutations}
164
+ initialMeanProportionInterval={this.initialMeanProportionInterval}
165
+ hideGaps={this.hideGaps}
166
+ useNewEndpoint={this.useNewEndpoint}
167
+ pageSizes={this.pageSizes}
168
+ />
169
+ </MutationLinkTemplateContextProvider>
160
170
  </MutationAnnotationsContextProvider>
161
171
  );
162
172
  }
@@ -3,10 +3,12 @@ import { customElement, property } from 'lit/decorators.js';
3
3
  import type { DetailedHTMLProps, HTMLAttributes } from 'react';
4
4
 
5
5
  import { MutationAnnotationsContextProvider } from '../../preact/MutationAnnotationsContext';
6
+ import { MutationLinkTemplateContextProvider } from '../../preact/MutationLinkTemplateContext';
6
7
  import { Mutations, type MutationsProps } from '../../preact/mutations/mutations';
7
8
  import type { Equals, Expect } from '../../utils/typeAssertions';
8
9
  import { PreactLitAdapterWithGridJsStyles } from '../PreactLitAdapterWithGridJsStyles';
9
10
  import { type MutationAnnotations, mutationAnnotationsContext } from '../mutation-annotations-context';
11
+ import { type MutationLinkTemplate, mutationLinkTemplateContext } from '../mutation-link-template-context';
10
12
 
11
13
  /**
12
14
  * ## Context
@@ -134,18 +136,26 @@ export class MutationsComponent extends PreactLitAdapterWithGridJsStyles {
134
136
  @consume({ context: mutationAnnotationsContext, subscribe: true })
135
137
  mutationAnnotations: MutationAnnotations = [];
136
138
 
139
+ /**
140
+ * @internal
141
+ */
142
+ @consume({ context: mutationLinkTemplateContext, subscribe: true })
143
+ mutationLinkTemplate: MutationLinkTemplate = {};
144
+
137
145
  override render() {
138
146
  return (
139
147
  <MutationAnnotationsContextProvider value={this.mutationAnnotations}>
140
- <Mutations
141
- lapisFilter={this.lapisFilter}
142
- sequenceType={this.sequenceType}
143
- views={this.views}
144
- width={this.width}
145
- height={this.height}
146
- pageSize={this.pageSize}
147
- baselineLapisFilter={this.baselineLapisFilter}
148
- />
148
+ <MutationLinkTemplateContextProvider value={this.mutationLinkTemplate}>
149
+ <Mutations
150
+ lapisFilter={this.lapisFilter}
151
+ sequenceType={this.sequenceType}
152
+ views={this.views}
153
+ width={this.width}
154
+ height={this.height}
155
+ pageSize={this.pageSize}
156
+ baselineLapisFilter={this.baselineLapisFilter}
157
+ />
158
+ </MutationLinkTemplateContextProvider>
149
159
  </MutationAnnotationsContextProvider>
150
160
  );
151
161
  }
@@ -3,9 +3,11 @@ import { customElement, property } from 'lit/decorators.js';
3
3
  import { type DetailedHTMLProps, type HTMLAttributes } from 'react';
4
4
 
5
5
  import { MutationAnnotationsContextProvider } from '../../preact/MutationAnnotationsContext';
6
+ import { MutationLinkTemplateContextProvider } from '../../preact/MutationLinkTemplateContext';
6
7
  import { WastewaterMutationsOverTime } from '../../preact/wastewater/mutationsOverTime/wastewater-mutations-over-time';
7
8
  import { PreactLitAdapterWithGridJsStyles } from '../PreactLitAdapterWithGridJsStyles';
8
9
  import { type MutationAnnotations, mutationAnnotationsContext } from '../mutation-annotations-context';
10
+ import { type MutationLinkTemplate, mutationLinkTemplateContext } from '../mutation-link-template-context';
9
11
 
10
12
  /**
11
13
  * ## Context
@@ -79,16 +81,24 @@ export class WastewaterMutationsOverTimeComponent extends PreactLitAdapterWithGr
79
81
  @consume({ context: mutationAnnotationsContext, subscribe: true })
80
82
  mutationAnnotations: MutationAnnotations = [];
81
83
 
84
+ /**
85
+ * @internal
86
+ */
87
+ @consume({ context: mutationLinkTemplateContext, subscribe: true })
88
+ mutationLinkTemplate: MutationLinkTemplate = {};
89
+
82
90
  override render() {
83
91
  return (
84
92
  <MutationAnnotationsContextProvider value={this.mutationAnnotations}>
85
- <WastewaterMutationsOverTime
86
- lapisFilter={this.lapisFilter}
87
- sequenceType={this.sequenceType}
88
- width={this.width}
89
- height={this.height}
90
- pageSizes={this.pageSizes}
91
- />
93
+ <MutationLinkTemplateContextProvider value={this.mutationLinkTemplate}>
94
+ <WastewaterMutationsOverTime
95
+ lapisFilter={this.lapisFilter}
96
+ sequenceType={this.sequenceType}
97
+ width={this.width}
98
+ height={this.height}
99
+ pageSizes={this.pageSizes}
100
+ />
101
+ </MutationLinkTemplateContextProvider>
92
102
  </MutationAnnotationsContextProvider>
93
103
  );
94
104
  }