@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.
- package/README.md +4 -0
- package/custom-elements.json +82 -1
- package/dist/{NumberRangeFilterChangedEvent-CQ32Qy8D.js → NumberRangeFilterChangedEvent-BnPI-Asz.js} +17 -3
- package/dist/NumberRangeFilterChangedEvent-BnPI-Asz.js.map +1 -0
- package/dist/assets/{mutationOverTimeWorker-BmB6BvVM.js.map → mutationOverTimeWorker-DPS3tmOd.js.map} +1 -1
- package/dist/components.d.ts +52 -25
- package/dist/components.js +209 -75
- package/dist/components.js.map +1 -1
- package/dist/util.d.ts +48 -24
- package/dist/util.js +2 -1
- package/package.json +1 -1
- package/src/preact/MutationLinkTemplateContext.tsx +56 -0
- package/src/preact/components/annotated-mutation.stories.tsx +69 -17
- package/src/preact/components/annotated-mutation.tsx +45 -19
- package/src/preact/genomeViewer/CDSPlot.tsx +13 -2
- package/src/preact/genomeViewer/loadGff3.ts +6 -0
- package/src/preact/mutationComparison/mutation-comparison-table.tsx +3 -0
- package/src/preact/mutationFilter/mutation-filter.stories.tsx +2 -1
- package/src/preact/mutationFilter/mutation-filter.tsx +24 -27
- package/src/preact/mutationFilter/parseAndValidateMutation.ts +11 -11
- package/src/preact/mutationFilter/parseMutation.spec.ts +32 -22
- package/src/preact/mutations/mutations-table.tsx +3 -0
- package/src/preact/mutationsOverTime/mutations-over-time.tsx +7 -4
- package/src/preact/shared/stories/expectMutationAnnotation.ts +3 -1
- package/src/types.ts +17 -1
- package/src/utilEntrypoint.ts +4 -0
- package/src/utils/mutations.spec.ts +19 -0
- package/src/utils/mutations.ts +57 -10
- package/src/web-components/gs-app.spec-d.ts +7 -0
- package/src/web-components/gs-app.stories.ts +32 -2
- package/src/web-components/gs-app.ts +17 -0
- package/src/web-components/input/gs-mutation-filter.stories.ts +2 -1
- package/src/web-components/input/gs-mutation-filter.tsx +2 -6
- package/src/web-components/mutation-link-template-context.ts +13 -0
- package/src/web-components/mutationLinks.mdx +27 -0
- package/src/web-components/visualization/gs-mutation-comparison.tsx +18 -8
- package/src/web-components/visualization/gs-mutations-over-time.stories.ts +12 -1
- package/src/web-components/visualization/gs-mutations-over-time.tsx +24 -14
- package/src/web-components/visualization/gs-mutations.tsx +19 -9
- package/src/web-components/wastewaterVisualization/gs-wastewater-mutations-over-time.tsx +17 -7
- package/standalone-bundle/assets/{mutationOverTimeWorker-B_xP8pIC.js.map → mutationOverTimeWorker-Dp-A14AP.js.map} +1 -1
- package/standalone-bundle/dashboard-components.js +7295 -7193
- package/standalone-bundle/dashboard-components.js.map +1 -1
- package/dist/NumberRangeFilterChangedEvent-CQ32Qy8D.js.map +0 -1
package/dist/util.d.ts
CHANGED
|
@@ -194,6 +194,19 @@ declare const mapSourceSchema: default_2.ZodObject<{
|
|
|
194
194
|
topologyObjectsKey: string;
|
|
195
195
|
}>;
|
|
196
196
|
|
|
197
|
+
export declare type MeanProportionInterval = default_2.infer<typeof meanProportionIntervalSchema>;
|
|
198
|
+
|
|
199
|
+
declare const meanProportionIntervalSchema: default_2.ZodObject<{
|
|
200
|
+
min: default_2.ZodNumber;
|
|
201
|
+
max: default_2.ZodNumber;
|
|
202
|
+
}, "strip", default_2.ZodTypeAny, {
|
|
203
|
+
min: number;
|
|
204
|
+
max: number;
|
|
205
|
+
}, {
|
|
206
|
+
min: number;
|
|
207
|
+
max: number;
|
|
208
|
+
}>;
|
|
209
|
+
|
|
197
210
|
export declare type MutationAnnotation = default_2.infer<typeof mutationAnnotationSchema>;
|
|
198
211
|
|
|
199
212
|
export declare type MutationAnnotations = default_2.infer<typeof mutationAnnotationsSchema>;
|
|
@@ -430,6 +443,17 @@ export declare type MutationsView = default_2.infer<typeof mutationsViewSchema>;
|
|
|
430
443
|
|
|
431
444
|
declare const mutationsViewSchema: default_2.ZodUnion<[default_2.ZodLiteral<"table">, default_2.ZodLiteral<"grid">, default_2.ZodLiteral<"insertions">]>;
|
|
432
445
|
|
|
446
|
+
export declare type MutationType = default_2.infer<typeof mutationTypeSchema>;
|
|
447
|
+
|
|
448
|
+
export declare const mutationType: {
|
|
449
|
+
readonly nucleotideMutations: "nucleotideMutations";
|
|
450
|
+
readonly nucleotideInsertions: "nucleotideInsertions";
|
|
451
|
+
readonly aminoAcidMutations: "aminoAcidMutations";
|
|
452
|
+
readonly aminoAcidInsertions: "aminoAcidInsertions";
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
declare const mutationTypeSchema: default_2.ZodEnum<["nucleotideMutations", "nucleotideInsertions", "aminoAcidMutations", "aminoAcidInsertions"]>;
|
|
456
|
+
|
|
433
457
|
export declare type NamedLapisFilter = default_2.infer<typeof namedLapisFilterSchema>;
|
|
434
458
|
|
|
435
459
|
declare const namedLapisFilterSchema: default_2.ZodObject<{
|
|
@@ -917,7 +941,7 @@ declare global {
|
|
|
917
941
|
|
|
918
942
|
declare global {
|
|
919
943
|
interface HTMLElementTagNameMap {
|
|
920
|
-
'gs-
|
|
944
|
+
'gs-genome-data-viewer': GenomeDataViewerComponent;
|
|
921
945
|
}
|
|
922
946
|
}
|
|
923
947
|
|
|
@@ -925,7 +949,7 @@ declare global {
|
|
|
925
949
|
declare global {
|
|
926
950
|
namespace JSX {
|
|
927
951
|
interface IntrinsicElements {
|
|
928
|
-
'gs-
|
|
952
|
+
'gs-genome-data-viewer': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
929
953
|
}
|
|
930
954
|
}
|
|
931
955
|
}
|
|
@@ -947,22 +971,6 @@ declare global {
|
|
|
947
971
|
}
|
|
948
972
|
|
|
949
973
|
|
|
950
|
-
declare global {
|
|
951
|
-
interface HTMLElementTagNameMap {
|
|
952
|
-
'gs-genome-data-viewer': GenomeDataViewerComponent;
|
|
953
|
-
}
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
declare global {
|
|
958
|
-
namespace JSX {
|
|
959
|
-
interface IntrinsicElements {
|
|
960
|
-
'gs-genome-data-viewer': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
961
|
-
}
|
|
962
|
-
}
|
|
963
|
-
}
|
|
964
|
-
|
|
965
|
-
|
|
966
974
|
declare global {
|
|
967
975
|
interface HTMLElementTagNameMap {
|
|
968
976
|
'gs-mutations': MutationsComponent;
|
|
@@ -1091,6 +1099,22 @@ declare global {
|
|
|
1091
1099
|
}
|
|
1092
1100
|
|
|
1093
1101
|
|
|
1102
|
+
declare global {
|
|
1103
|
+
interface HTMLElementTagNameMap {
|
|
1104
|
+
'gs-wastewater-mutations-over-time': WastewaterMutationsOverTimeComponent;
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
|
|
1109
|
+
declare global {
|
|
1110
|
+
namespace JSX {
|
|
1111
|
+
interface IntrinsicElements {
|
|
1112
|
+
'gs-wastewater-mutations-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
|
|
1094
1118
|
declare global {
|
|
1095
1119
|
interface HTMLElementTagNameMap {
|
|
1096
1120
|
'gs-date-range-filter': DateRangeFilterComponent;
|
|
@@ -1113,10 +1137,10 @@ declare global {
|
|
|
1113
1137
|
|
|
1114
1138
|
declare global {
|
|
1115
1139
|
interface HTMLElementTagNameMap {
|
|
1116
|
-
'gs-
|
|
1140
|
+
'gs-text-filter': TextFilterComponent;
|
|
1117
1141
|
}
|
|
1118
1142
|
interface HTMLElementEventMap {
|
|
1119
|
-
[gsEventNames.
|
|
1143
|
+
[gsEventNames.textFilterChanged]: TextFilterChangedEvent;
|
|
1120
1144
|
}
|
|
1121
1145
|
}
|
|
1122
1146
|
|
|
@@ -1124,7 +1148,7 @@ declare global {
|
|
|
1124
1148
|
declare global {
|
|
1125
1149
|
namespace JSX {
|
|
1126
1150
|
interface IntrinsicElements {
|
|
1127
|
-
'gs-
|
|
1151
|
+
'gs-text-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1128
1152
|
}
|
|
1129
1153
|
}
|
|
1130
1154
|
}
|
|
@@ -1132,10 +1156,10 @@ declare global {
|
|
|
1132
1156
|
|
|
1133
1157
|
declare global {
|
|
1134
1158
|
interface HTMLElementTagNameMap {
|
|
1135
|
-
'gs-
|
|
1159
|
+
'gs-location-filter': LocationFilterComponent;
|
|
1136
1160
|
}
|
|
1137
1161
|
interface HTMLElementEventMap {
|
|
1138
|
-
[gsEventNames.
|
|
1162
|
+
[gsEventNames.locationChanged]: LocationChangedEvent;
|
|
1139
1163
|
}
|
|
1140
1164
|
}
|
|
1141
1165
|
|
|
@@ -1143,7 +1167,7 @@ declare global {
|
|
|
1143
1167
|
declare global {
|
|
1144
1168
|
namespace JSX {
|
|
1145
1169
|
interface IntrinsicElements {
|
|
1146
|
-
'gs-
|
|
1170
|
+
'gs-location-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1147
1171
|
}
|
|
1148
1172
|
}
|
|
1149
1173
|
}
|
package/dist/util.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { D, a, L, N, b, T, d, g, v } from "./NumberRangeFilterChangedEvent-
|
|
1
|
+
import { D, a, L, N, b, T, d, g, m, v } from "./NumberRangeFilterChangedEvent-BnPI-Asz.js";
|
|
2
2
|
export {
|
|
3
3
|
D as DateRangeOptionChangedEvent,
|
|
4
4
|
a as LineageFilterChangedEvent,
|
|
@@ -8,6 +8,7 @@ export {
|
|
|
8
8
|
T as TextFilterChangedEvent,
|
|
9
9
|
d as dateRangeOptionPresets,
|
|
10
10
|
g as gsEventNames,
|
|
11
|
+
m as mutationType,
|
|
11
12
|
v as views
|
|
12
13
|
};
|
|
13
14
|
//# sourceMappingURL=util.js.map
|
package/package.json
CHANGED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { createContext, type Provider } from 'preact';
|
|
2
|
+
import { useContext, useMemo } from 'preact/hooks';
|
|
3
|
+
|
|
4
|
+
import type { SequenceType } from '../types';
|
|
5
|
+
import type { Deletion, Substitution } from '../utils/mutations';
|
|
6
|
+
import { mutationLinkTemplateSchema } from '../web-components/mutation-link-template-context';
|
|
7
|
+
import { ErrorDisplay } from './components/error-display';
|
|
8
|
+
import { ResizeContainer } from './components/resize-container';
|
|
9
|
+
|
|
10
|
+
type MutationLinkTemplate = {
|
|
11
|
+
nucleotideMutation?: string;
|
|
12
|
+
aminoAcidMutation?: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const MutationLinkTemplateContext = createContext<MutationLinkTemplate>({
|
|
16
|
+
nucleotideMutation: undefined,
|
|
17
|
+
aminoAcidMutation: undefined,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export const MutationLinkTemplateContextProvider: Provider<MutationLinkTemplate> = ({ value, children }) => {
|
|
21
|
+
const parseResult = useMemo(() => mutationLinkTemplateSchema.safeParse(value), [value]);
|
|
22
|
+
|
|
23
|
+
if (!parseResult.success) {
|
|
24
|
+
return (
|
|
25
|
+
<ResizeContainer size={{ width: '100%' }}>
|
|
26
|
+
<ErrorDisplay error={parseResult.error} layout='vertical' />
|
|
27
|
+
</ResizeContainer>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<MutationLinkTemplateContext.Provider value={parseResult.data}>{children}</MutationLinkTemplateContext.Provider>
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export function useMutationLinkProvider() {
|
|
37
|
+
const linkTemplate = useContext(MutationLinkTemplateContext);
|
|
38
|
+
|
|
39
|
+
return (mutation: Substitution | Deletion, sequenceType: SequenceType) => {
|
|
40
|
+
switch (sequenceType) {
|
|
41
|
+
case 'nucleotide': {
|
|
42
|
+
if (linkTemplate.nucleotideMutation !== undefined) {
|
|
43
|
+
return linkTemplate.nucleotideMutation.replace('{{mutation}}', encodeURIComponent(mutation.code));
|
|
44
|
+
}
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
case 'amino acid': {
|
|
49
|
+
if (linkTemplate.aminoAcidMutation !== undefined) {
|
|
50
|
+
return linkTemplate.aminoAcidMutation.replace('{{mutation}}', encodeURIComponent(mutation.code));
|
|
51
|
+
}
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -2,15 +2,25 @@ import { type Meta, type StoryObj } from '@storybook/preact';
|
|
|
2
2
|
import { expect, userEvent, waitFor, within } from '@storybook/test';
|
|
3
3
|
|
|
4
4
|
import { AnnotatedMutation, type AnnotatedMutationProps } from './annotated-mutation';
|
|
5
|
-
import {
|
|
5
|
+
import type { MutationAnnotations } from '../../web-components/mutation-annotations-context';
|
|
6
|
+
import type { MutationLinkTemplate } from '../../web-components/mutation-link-template-context';
|
|
6
7
|
import { MutationAnnotationsContextProvider } from '../MutationAnnotationsContext';
|
|
8
|
+
import { MutationLinkTemplateContextProvider } from '../MutationLinkTemplateContext';
|
|
7
9
|
|
|
8
|
-
|
|
10
|
+
type ContextProps = {
|
|
11
|
+
annotations: MutationAnnotations;
|
|
12
|
+
linkTemplate: MutationLinkTemplate;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type StoryProps = AnnotatedMutationProps & ContextProps;
|
|
16
|
+
|
|
17
|
+
const meta: Meta<StoryProps> = {
|
|
9
18
|
title: 'Component/Annotated Mutation',
|
|
10
19
|
component: AnnotatedMutation,
|
|
11
20
|
parameters: { fetchMock: {} },
|
|
12
21
|
argTypes: {
|
|
13
22
|
annotations: { control: { type: 'object' } },
|
|
23
|
+
linkTemplate: { control: { type: 'object' } },
|
|
14
24
|
mutation: { control: { type: 'object' } },
|
|
15
25
|
sequenceType: {
|
|
16
26
|
options: ['nucleotide', 'amino acid'],
|
|
@@ -21,14 +31,16 @@ const meta: Meta<AnnotatedMutationProps & { annotations: MutationAnnotations }>
|
|
|
21
31
|
|
|
22
32
|
export default meta;
|
|
23
33
|
|
|
24
|
-
export const MutationWithoutAnnotationEntry: StoryObj<
|
|
34
|
+
export const MutationWithoutAnnotationEntry: StoryObj<StoryProps> = {
|
|
25
35
|
render: (args) => {
|
|
26
|
-
const { annotations, ...annotatedMutationsArgs } = args;
|
|
36
|
+
const { annotations, linkTemplate, ...annotatedMutationsArgs } = args;
|
|
27
37
|
|
|
28
38
|
return (
|
|
29
|
-
<
|
|
30
|
-
<
|
|
31
|
-
|
|
39
|
+
<MutationLinkTemplateContextProvider value={linkTemplate}>
|
|
40
|
+
<MutationAnnotationsContextProvider value={annotations}>
|
|
41
|
+
<AnnotatedMutation {...annotatedMutationsArgs} />
|
|
42
|
+
</MutationAnnotationsContextProvider>
|
|
43
|
+
</MutationLinkTemplateContextProvider>
|
|
32
44
|
);
|
|
33
45
|
},
|
|
34
46
|
args: {
|
|
@@ -48,6 +60,7 @@ export const MutationWithoutAnnotationEntry: StoryObj<AnnotatedMutationProps & {
|
|
|
48
60
|
nucleotideMutations: ['123T'],
|
|
49
61
|
},
|
|
50
62
|
],
|
|
63
|
+
linkTemplate: {},
|
|
51
64
|
},
|
|
52
65
|
play: async ({ canvasElement }) => {
|
|
53
66
|
const canvas = within(canvasElement);
|
|
@@ -58,7 +71,7 @@ export const MutationWithoutAnnotationEntry: StoryObj<AnnotatedMutationProps & {
|
|
|
58
71
|
},
|
|
59
72
|
};
|
|
60
73
|
|
|
61
|
-
export const MutationWithAnnotationEntry: StoryObj<
|
|
74
|
+
export const MutationWithAnnotationEntry: StoryObj<StoryProps> = {
|
|
62
75
|
...MutationWithoutAnnotationEntry,
|
|
63
76
|
args: {
|
|
64
77
|
...MutationWithoutAnnotationEntry.args,
|
|
@@ -77,15 +90,13 @@ export const MutationWithAnnotationEntry: StoryObj<AnnotatedMutationProps & { an
|
|
|
77
90
|
await waitFor(() => expect(canvas.getByText('A23403G')).toBeVisible());
|
|
78
91
|
await expect(getAnnotationIndicator(canvas)).toBeVisible();
|
|
79
92
|
|
|
80
|
-
await userEvent.click(canvas.getByText('
|
|
93
|
+
await userEvent.click(canvas.getByText('*'));
|
|
81
94
|
await waitFor(() => expect(getAnnotationName(canvas)).toBeVisible());
|
|
82
95
|
await expect(canvas.getByRole('link', { name: 'with a link.' })).toBeVisible();
|
|
83
96
|
},
|
|
84
97
|
};
|
|
85
98
|
|
|
86
|
-
export const MutationWithMultipleAnnotationEntries: StoryObj<
|
|
87
|
-
AnnotatedMutationProps & { annotations: MutationAnnotations }
|
|
88
|
-
> = {
|
|
99
|
+
export const MutationWithMultipleAnnotationEntries: StoryObj<StoryProps> = {
|
|
89
100
|
...MutationWithoutAnnotationEntry,
|
|
90
101
|
args: {
|
|
91
102
|
...MutationWithoutAnnotationEntry.args,
|
|
@@ -111,17 +122,16 @@ export const MutationWithMultipleAnnotationEntries: StoryObj<
|
|
|
111
122
|
await expect(getAnnotationIndicator(canvas)).toBeVisible();
|
|
112
123
|
await expect(canvas.queryByText('+')).toBeVisible();
|
|
113
124
|
|
|
114
|
-
await userEvent.click(canvas.getByText('
|
|
125
|
+
await userEvent.click(canvas.getByText('*'));
|
|
115
126
|
await waitFor(() => expect(getAnnotationName(canvas)).toBeVisible());
|
|
116
127
|
await expect(canvas.queryByText('Another test annotation')).toBeVisible();
|
|
117
128
|
},
|
|
118
129
|
};
|
|
119
130
|
|
|
120
|
-
export const AminoAcidMutationWithAnnotationEntry: StoryObj<
|
|
121
|
-
AnnotatedMutationProps & { annotations: MutationAnnotations }
|
|
122
|
-
> = {
|
|
131
|
+
export const AminoAcidMutationWithAnnotationEntry: StoryObj<StoryProps> = {
|
|
123
132
|
...MutationWithoutAnnotationEntry,
|
|
124
133
|
args: {
|
|
134
|
+
...MutationWithoutAnnotationEntry.args,
|
|
125
135
|
mutation: {
|
|
126
136
|
type: 'substitution',
|
|
127
137
|
code: 'S:A501G',
|
|
@@ -145,7 +155,7 @@ export const AminoAcidMutationWithAnnotationEntry: StoryObj<
|
|
|
145
155
|
await waitFor(() => expect(canvas.getByText('S:A501G')).toBeVisible());
|
|
146
156
|
await expect(getAnnotationIndicator(canvas)).toBeVisible();
|
|
147
157
|
|
|
148
|
-
await userEvent.click(canvas.getByText('
|
|
158
|
+
await userEvent.click(canvas.getByText('*'));
|
|
149
159
|
await waitFor(() => expect(getAnnotationName(canvas)).toBeVisible());
|
|
150
160
|
},
|
|
151
161
|
};
|
|
@@ -157,3 +167,45 @@ function getAnnotationIndicator(canvas: ReturnType<typeof within>) {
|
|
|
157
167
|
function getAnnotationName(canvas: ReturnType<typeof within>) {
|
|
158
168
|
return canvas.queryByText('Test annotation');
|
|
159
169
|
}
|
|
170
|
+
|
|
171
|
+
export const NucleotideMutationWithLink: StoryObj<StoryProps> = {
|
|
172
|
+
...MutationWithoutAnnotationEntry,
|
|
173
|
+
args: {
|
|
174
|
+
...MutationWithoutAnnotationEntry.args,
|
|
175
|
+
linkTemplate: {
|
|
176
|
+
nucleotideMutation: 'http://foo.com/query?nucMut={{mutation}}',
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
play: async ({ canvasElement }) => {
|
|
180
|
+
const canvas = within(canvasElement);
|
|
181
|
+
|
|
182
|
+
await waitFor(() => expect(canvas.getByText('A23403G')).toBeVisible());
|
|
183
|
+
const link = canvas.getByText('A23403G').closest('a');
|
|
184
|
+
void expect(link).toHaveAttribute('href', 'http://foo.com/query?nucMut=A23403G');
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
export const AminoAcidMutationWithLink: StoryObj<StoryProps> = {
|
|
189
|
+
...MutationWithoutAnnotationEntry,
|
|
190
|
+
args: {
|
|
191
|
+
...MutationWithoutAnnotationEntry.args,
|
|
192
|
+
mutation: {
|
|
193
|
+
type: 'substitution',
|
|
194
|
+
code: 'S:A501G',
|
|
195
|
+
position: 501,
|
|
196
|
+
valueAtReference: 'A',
|
|
197
|
+
substitutionValue: 'G',
|
|
198
|
+
},
|
|
199
|
+
sequenceType: 'amino acid',
|
|
200
|
+
linkTemplate: {
|
|
201
|
+
aminoAcidMutation: 'http://foo.com/query?aaMut={{mutation}}',
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
play: async ({ canvasElement }) => {
|
|
205
|
+
const canvas = within(canvasElement);
|
|
206
|
+
|
|
207
|
+
await waitFor(() => expect(canvas.getByText('S:A501G')).toBeVisible());
|
|
208
|
+
const link = canvas.getByText('S:A501G').closest('a');
|
|
209
|
+
void expect(link).toHaveAttribute('href', 'http://foo.com/query?aaMut=S%3AA501G');
|
|
210
|
+
},
|
|
211
|
+
};
|
|
@@ -7,6 +7,7 @@ import type { Deletion, Substitution } from '../../utils/mutations';
|
|
|
7
7
|
import { useMutationAnnotationsProvider } from '../MutationAnnotationsContext';
|
|
8
8
|
import { InfoHeadline1, InfoHeadline2, InfoParagraph } from './info';
|
|
9
9
|
import { ButtonWithModalDialog, useModalRef } from './modal';
|
|
10
|
+
import { useMutationLinkProvider } from '../MutationLinkTemplateContext';
|
|
10
11
|
|
|
11
12
|
export type AnnotatedMutationProps = {
|
|
12
13
|
mutation: Substitution | Deletion;
|
|
@@ -15,13 +16,22 @@ export type AnnotatedMutationProps = {
|
|
|
15
16
|
|
|
16
17
|
export const AnnotatedMutation: FunctionComponent<AnnotatedMutationProps> = (props) => {
|
|
17
18
|
const annotationsProvider = useMutationAnnotationsProvider();
|
|
19
|
+
const linkProvider = useMutationLinkProvider();
|
|
18
20
|
const modalRef = useModalRef();
|
|
19
21
|
|
|
20
|
-
return
|
|
22
|
+
return (
|
|
23
|
+
<AnnotatedMutationWithoutContext
|
|
24
|
+
{...props}
|
|
25
|
+
annotationsProvider={annotationsProvider}
|
|
26
|
+
linkProvider={linkProvider}
|
|
27
|
+
modalRef={modalRef}
|
|
28
|
+
/>
|
|
29
|
+
);
|
|
21
30
|
};
|
|
22
31
|
|
|
23
32
|
type GridJsAnnotatedMutationProps = AnnotatedMutationProps & {
|
|
24
33
|
annotationsProvider: ReturnType<typeof useMutationAnnotationsProvider>;
|
|
34
|
+
linkProvider: ReturnType<typeof useMutationLinkProvider>;
|
|
25
35
|
};
|
|
26
36
|
|
|
27
37
|
/**
|
|
@@ -43,12 +53,26 @@ const AnnotatedMutationWithoutContext: FunctionComponent<AnnotatedMutationWithou
|
|
|
43
53
|
mutation,
|
|
44
54
|
sequenceType,
|
|
45
55
|
annotationsProvider,
|
|
56
|
+
linkProvider,
|
|
46
57
|
modalRef,
|
|
47
58
|
}) => {
|
|
59
|
+
const link = linkProvider(mutation, sequenceType);
|
|
60
|
+
let innerLabel = <>{mutation.code}</>;
|
|
61
|
+
if (link !== undefined) {
|
|
62
|
+
innerLabel = (
|
|
63
|
+
<a
|
|
64
|
+
className='hover:text-blue-800 focus:outline-none focus:ring-2 focus:ring-blue-300 underline'
|
|
65
|
+
href={link}
|
|
66
|
+
>
|
|
67
|
+
{mutation.code}
|
|
68
|
+
</a>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
48
72
|
const mutationAnnotations = annotationsProvider(mutation, sequenceType);
|
|
49
73
|
|
|
50
74
|
if (mutationAnnotations === undefined || mutationAnnotations.length === 0) {
|
|
51
|
-
return
|
|
75
|
+
return innerLabel;
|
|
52
76
|
}
|
|
53
77
|
|
|
54
78
|
const modalContent = (
|
|
@@ -66,22 +90,24 @@ const AnnotatedMutationWithoutContext: FunctionComponent<AnnotatedMutationWithou
|
|
|
66
90
|
);
|
|
67
91
|
|
|
68
92
|
return (
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
<
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
93
|
+
<>
|
|
94
|
+
{innerLabel}
|
|
95
|
+
<ButtonWithModalDialog
|
|
96
|
+
buttonClassName={'select-text cursor-pointer'}
|
|
97
|
+
modalContent={modalContent}
|
|
98
|
+
modalRef={modalRef}
|
|
99
|
+
>
|
|
100
|
+
<sup className='hover:underline focus-visible:underline decoration-red-600'>
|
|
101
|
+
{mutationAnnotations
|
|
102
|
+
.map((annotation) => annotation.symbol)
|
|
103
|
+
.map((symbol, index) => (
|
|
104
|
+
<Fragment key={symbol}>
|
|
105
|
+
<span className='text-red-600'>{symbol}</span>
|
|
106
|
+
{index !== mutationAnnotations.length - 1 && ','}
|
|
107
|
+
</Fragment>
|
|
108
|
+
))}
|
|
109
|
+
</sup>
|
|
110
|
+
</ButtonWithModalDialog>
|
|
111
|
+
</>
|
|
86
112
|
);
|
|
87
113
|
};
|
|
@@ -20,9 +20,16 @@ function getMaxTickNumber(fullWidth: number): number {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
function getTicks(zoomStart: number, zoomEnd: number, fullWidth: number) {
|
|
23
|
-
|
|
23
|
+
let maxTickNumber = getMaxTickNumber(fullWidth);
|
|
24
24
|
const length = zoomEnd - zoomStart;
|
|
25
|
-
|
|
25
|
+
let minTickSize = length / maxTickNumber;
|
|
26
|
+
if (minTickSize <= 1) {
|
|
27
|
+
maxTickNumber = MIN_TICK_NUMBER;
|
|
28
|
+
minTickSize = length / maxTickNumber;
|
|
29
|
+
}
|
|
30
|
+
if (minTickSize <= 1) {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
26
33
|
let maxTickSize = 10 ** Math.round(Math.log(minTickSize) / Math.log(10));
|
|
27
34
|
const numTicks = Math.round(length / maxTickSize);
|
|
28
35
|
if (numTicks > maxTickNumber) {
|
|
@@ -78,6 +85,7 @@ const XAxis: FunctionComponent<XAxisProps> = (componentProps) => {
|
|
|
78
85
|
width: `calc(${widthPercent}% - 1px)`,
|
|
79
86
|
}}
|
|
80
87
|
>
|
|
88
|
+
{/* TODO(#994): determine if text can be shown based on text width */}
|
|
81
89
|
{width >= averageWidth ? tick.start : ''}
|
|
82
90
|
</div>
|
|
83
91
|
);
|
|
@@ -142,6 +150,9 @@ const CDSBars: FunctionComponent<CDSBarsProps> = (componentProps) => {
|
|
|
142
150
|
if (start >= end) {
|
|
143
151
|
return null;
|
|
144
152
|
}
|
|
153
|
+
if (zoomEnd - zoomStart <= 2) {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
145
156
|
|
|
146
157
|
const widthPercent = ((end - start) / visibleRegionLength) * 100;
|
|
147
158
|
const leftPercent = ((start - zoomStart) / visibleRegionLength) * 100;
|
|
@@ -22,6 +22,12 @@ export async function loadGff3(gff3Source: string, genomeLength: number | undefi
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
const response = await fetch(gff3Source);
|
|
25
|
+
if (!response.ok) {
|
|
26
|
+
throw new UserFacingError(
|
|
27
|
+
'GFF3 download failed',
|
|
28
|
+
`Server returned ${response.status} ${response.statusText} for ${response.url}`,
|
|
29
|
+
);
|
|
30
|
+
}
|
|
25
31
|
const content = await response.text();
|
|
26
32
|
genomeLength ??= loadGenomeLength(content);
|
|
27
33
|
return { features: parseGFF3(content), length: genomeLength };
|
|
@@ -6,6 +6,7 @@ import { type Dataset } from '../../operator/Dataset';
|
|
|
6
6
|
import type { SequenceType } from '../../types';
|
|
7
7
|
import { type DeletionClass, type SubstitutionClass } from '../../utils/mutations';
|
|
8
8
|
import { useMutationAnnotationsProvider } from '../MutationAnnotationsContext';
|
|
9
|
+
import { useMutationLinkProvider } from '../MutationLinkTemplateContext';
|
|
9
10
|
import { GridJsAnnotatedMutation } from '../components/annotated-mutation';
|
|
10
11
|
import { type ProportionInterval } from '../components/proportion-selector';
|
|
11
12
|
import { Table } from '../components/table';
|
|
@@ -26,6 +27,7 @@ export const MutationComparisonTable: FunctionComponent<MutationsTableProps> = (
|
|
|
26
27
|
sequenceType,
|
|
27
28
|
}) => {
|
|
28
29
|
const annotationsProvider = useMutationAnnotationsProvider();
|
|
30
|
+
const linkProvider = useMutationLinkProvider();
|
|
29
31
|
|
|
30
32
|
const headers = [
|
|
31
33
|
{
|
|
@@ -38,6 +40,7 @@ export const MutationComparisonTable: FunctionComponent<MutationsTableProps> = (
|
|
|
38
40
|
mutation={cell}
|
|
39
41
|
sequenceType={sequenceType}
|
|
40
42
|
annotationsProvider={annotationsProvider}
|
|
43
|
+
linkProvider={linkProvider}
|
|
41
44
|
/>
|
|
42
45
|
),
|
|
43
46
|
},
|
|
@@ -6,6 +6,7 @@ import { MutationFilter, type MutationFilterProps } from './mutation-filter';
|
|
|
6
6
|
import { previewHandles } from '../../../.storybook/preview';
|
|
7
7
|
import { LAPIS_URL } from '../../constants';
|
|
8
8
|
import referenceGenome from '../../lapisApi/__mockData__/referenceGenome.json';
|
|
9
|
+
import { mutationType } from '../../types';
|
|
9
10
|
import { gsEventNames } from '../../utils/gsEventNames';
|
|
10
11
|
import { LapisUrlContextProvider } from '../LapisUrlContext';
|
|
11
12
|
import { ReferenceGenomeContext } from '../ReferenceGenomeContext';
|
|
@@ -230,7 +231,7 @@ export const FiltersOutDisabledMutationTypes: StoryObj<MutationFilterProps> = {
|
|
|
230
231
|
...Default,
|
|
231
232
|
args: {
|
|
232
233
|
...Default.args,
|
|
233
|
-
enabledMutationTypes: [
|
|
234
|
+
enabledMutationTypes: [mutationType.nucleotideMutations],
|
|
234
235
|
},
|
|
235
236
|
play: async ({ canvasElement, step }) => {
|
|
236
237
|
const { canvas, changedListenerMock } = await prepare(canvasElement, step);
|