@molgenis/vip-report-template 6.2.0 → 7.0.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/.nvmrc +1 -1
- package/.travis.yml +8 -8
- package/README.md +411 -1
- package/eslint.config.mjs +11 -0
- package/package.json +40 -35
- package/scripts/deploy_npm_registry.sh +5 -0
- package/src/App.tsx +30 -24
- package/src/assets/sass/main.scss +12 -4
- package/src/components/Allele.tsx +95 -0
- package/src/components/Anchor.tsx +1 -1
- package/src/components/Breadcrumb.tsx +6 -4
- package/src/components/DatasetDropdown.tsx +12 -25
- package/src/components/ErrorNotification.tsx +9 -0
- package/src/components/GenomeBrowser.tsx +40 -23
- package/src/components/HpoTerm.tsx +1 -1
- package/src/components/{record/Pager.tsx → Pager.tsx} +21 -14
- package/src/components/RecordsPerPage.tsx +9 -7
- package/src/components/RecordsTable.tsx +130 -0
- package/src/components/SampleTable.tsx +70 -98
- package/src/components/SearchBox.tsx +8 -2
- package/src/components/Sort.tsx +28 -25
- package/src/components/Table.tsx +16 -0
- package/src/components/Tooltip.tsx +20 -0
- package/src/components/VariantBreadcrumb.tsx +54 -0
- package/src/components/VariantConsequenceContainer.tsx +100 -0
- package/src/components/VariantConsequenceTable.tsx +58 -0
- package/src/components/VariantContainer.tsx +71 -0
- package/src/components/VariantFilters.tsx +27 -0
- package/src/components/VariantGenotypeTable.tsx +44 -0
- package/src/components/VariantInfoTable.tsx +24 -33
- package/src/components/VariantResults.tsx +103 -0
- package/src/components/VariantTable.tsx +62 -66
- package/src/components/VariantTypeSelect.tsx +34 -0
- package/src/components/VariantsContainer.tsx +150 -0
- package/src/components/VariantsContainerHeader.tsx +70 -0
- package/src/components/field/Field.tsx +80 -0
- package/src/components/field/FieldAlt.tsx +19 -0
- package/src/components/field/FieldChrom.tsx +6 -0
- package/src/components/{record/Id.tsx → field/FieldFilter.tsx} +2 -1
- package/src/components/field/FieldFormat.tsx +10 -0
- package/src/components/{record/Filter.tsx → field/FieldId.tsx} +1 -1
- package/src/components/field/FieldPos.tsx +6 -0
- package/src/components/field/FieldQual.tsx +6 -0
- package/src/components/field/FieldRef.tsx +8 -0
- package/src/components/field/composed/FieldClinVar.tsx +72 -0
- package/src/components/field/composed/FieldComposed.tsx +68 -0
- package/src/components/field/composed/FieldGene.tsx +39 -0
- package/src/components/field/composed/FieldGenotype.tsx +35 -0
- package/src/components/{record/format/GenotypeField.tsx → field/composed/FieldGenotypeSnvSv.tsx} +20 -16
- package/src/components/field/composed/FieldGenotypeStr.tsx +31 -0
- package/src/components/field/composed/FieldGnomAd.tsx +58 -0
- package/src/components/field/composed/FieldHpo.tsx +50 -0
- package/src/components/field/composed/FieldInheritanceModes.tsx +32 -0
- package/src/components/field/composed/FieldLocus.tsx +18 -0
- package/src/components/field/composed/FieldVipC.tsx +25 -0
- package/src/components/field/composed/FieldVipCS.tsx +15 -0
- package/src/components/field/composed/FieldVkgl.tsx +37 -0
- package/src/components/field/genotype/FieldGenotype.tsx +19 -0
- package/src/components/field/genotype/FieldGenotypeType.tsx +9 -0
- package/src/components/field/info/FieldConsequence.tsx +15 -0
- package/src/components/{record/info/Hgvs.tsx → field/info/FieldHgvs.tsx} +4 -6
- package/src/components/field/info/FieldInfo.tsx +27 -0
- package/src/components/{record/info/PubMed.tsx → field/info/FieldPubMed.tsx} +4 -7
- package/src/components/field/typed/FieldCategorical.tsx +17 -0
- package/src/components/{record/field/FieldValueCharacter.tsx → field/typed/FieldCharacter.tsx} +3 -2
- package/src/components/{record/field/FieldValueFlag.tsx → field/typed/FieldFlag.tsx} +3 -2
- package/src/components/{record/field/FieldValueFloat.tsx → field/typed/FieldFloat.tsx} +3 -2
- package/src/components/{record/field/FieldValueInteger.tsx → field/typed/FieldInteger.tsx} +3 -2
- package/src/components/{record/field/FieldValueString.tsx → field/typed/FieldString.tsx} +3 -2
- package/src/components/field/typed/FieldTyped.tsx +20 -0
- package/src/components/field/typed/FieldTypedItem.tsx +49 -0
- package/src/components/field/typed/FieldTypedMultiple.tsx +21 -0
- package/src/components/filter/Filter.tsx +56 -48
- package/src/components/filter/FilterWrapper.scss +23 -0
- package/src/components/filter/FilterWrapper.tsx +63 -0
- package/src/components/filter/composed/FilterAllelicImbalance.tsx +26 -0
- package/src/components/filter/composed/FilterComposed.tsx +92 -0
- package/src/components/filter/composed/FilterDeNovo.tsx +35 -0
- package/src/components/filter/composed/FilterHpo.tsx +16 -0
- package/src/components/filter/composed/FilterInheritance.tsx +42 -0
- package/src/components/filter/composed/FilterLocus.tsx +75 -0
- package/src/components/filter/composed/FilterVipC.tsx +16 -0
- package/src/components/filter/composed/FilterVipCS.tsx +16 -0
- package/src/components/filter/fixed/FilterAlt.tsx +20 -0
- package/src/components/filter/fixed/FilterChrom.tsx +22 -0
- package/src/components/filter/fixed/FilterFilter.tsx +20 -0
- package/src/components/filter/fixed/FilterFixed.tsx +96 -0
- package/src/components/filter/fixed/FilterId.tsx +20 -0
- package/src/components/filter/fixed/FilterPos.tsx +22 -0
- package/src/components/filter/fixed/FilterQual.tsx +21 -0
- package/src/components/filter/fixed/FilterRef.tsx +22 -0
- package/src/components/filter/typed/FilterCategorical.tsx +119 -0
- package/src/components/filter/typed/FilterFlag.tsx +23 -0
- package/src/components/filter/typed/FilterInterval.tsx +72 -0
- package/src/components/filter/typed/FilterString.tsx +43 -0
- package/src/components/filter/typed/FilterTyped.tsx +56 -0
- package/src/components/form/ButtonApply.tsx +11 -0
- package/src/components/form/ButtonDownload.tsx +11 -0
- package/src/components/form/ButtonReset.tsx +9 -0
- package/src/components/{Checkbox.tsx → form/Checkbox.tsx} +4 -9
- package/src/components/form/Input.tsx +19 -0
- package/src/components/form/Select.scss +7 -0
- package/src/components/form/Select.tsx +34 -0
- package/src/components/tree/DecisionTreeBoolMultiQuery.tsx +1 -1
- package/src/components/tree/DecisionTreeBoolQuery.tsx +1 -1
- package/src/components/tree/DecisionTreeNode.tsx +41 -39
- package/src/components/tree/DecisionTreeNodeBool.tsx +1 -1
- package/src/components/tree/DecisionTreeNodeBoolMulti.tsx +1 -1
- package/src/components/tree/DecisionTreeNodeCategorical.tsx +1 -1
- package/src/components/tree/DecisionTreeNodeExists.tsx +1 -1
- package/src/components/tree/DecisionTreeNodeLeaf.tsx +1 -1
- package/src/components/tree/DecisionTreeOutcomeNode.tsx +1 -1
- package/src/components/tree/DecisionTreePath.tsx +1 -1
- package/src/igv.d.ts +2 -1
- package/src/index.tsx +48 -19
- package/src/mocks/GRCh37/decisionTree.json +23 -22
- package/src/mocks/GRCh37/field_metadata.json +435 -95
- package/src/mocks/GRCh37/sampleTree.json +21 -1
- package/src/mocks/GRCh37/static.ts +62 -134
- package/src/mocks/GRCh37/vcf/family.vcf.blob +9 -3
- package/src/mocks/GRCh38/decisionTree.json +52 -33
- package/src/mocks/GRCh38/decisionTreeStr.json +572 -0
- package/src/mocks/GRCh38/fasta/chr1_149380406-149403321.fasta.gz.blob +0 -0
- package/src/mocks/GRCh38/field_metadata.json +435 -95
- package/src/mocks/GRCh38/sampleTree.json +175 -0
- package/src/mocks/GRCh38/static.ts +101 -42
- package/src/mocks/GRCh38/str.cram.blob +0 -0
- package/src/mocks/GRCh38/str.cram.crai.blob +0 -0
- package/src/mocks/GRCh38/vcf/family.vcf.blob +25 -24
- package/src/mocks/GRCh38/vcf/no_vep.vcf.blob +29 -28
- package/src/mocks/GRCh38/vcf/samples_0.vcf.blob +28 -27
- package/src/mocks/GRCh38/vcf/samples_1.vcf.blob +29 -28
- package/src/mocks/GRCh38/vcf/samples_100.vcf.blob +28 -27
- package/src/mocks/GRCh38/vcf/str.vcf.blob +321 -0
- package/src/mocks/MockApiClient.ts +339 -332
- package/src/mocks/config_cram.json +701 -0
- package/src/mocks/config_vcf.json +699 -0
- package/src/store/app.ts +30 -0
- package/src/store/index.tsx +3 -168
- package/src/store/variants.ts +182 -0
- package/src/types/config.d.ts +190 -0
- package/src/types/configCellComposed.d.ts +86 -0
- package/src/types/configCells.d.ts +129 -0
- package/src/types/configFilter.d.ts +80 -0
- package/src/types/configFilterComposed.d.ts +60 -0
- package/src/types/configSort.d.ts +13 -0
- package/src/types/filter.d.ts +17 -0
- package/src/types/store.d.ts +34 -0
- package/src/utils/api.ts +281 -0
- package/src/utils/config/config.ts +182 -0
- package/src/utils/config/configCells.ts +74 -0
- package/src/utils/config/configCellsComposed.ts +508 -0
- package/src/utils/config/configCellsField.ts +61 -0
- package/src/utils/config/configCellsFixed.ts +126 -0
- package/src/utils/config/configFilters.ts +46 -0
- package/src/utils/config/configFiltersComposed.ts +208 -0
- package/src/utils/config/configFiltersField.ts +49 -0
- package/src/utils/config/configFiltersFixed.ts +106 -0
- package/src/utils/config/configSorts.ts +44 -0
- package/src/utils/config/configValidator.ts +380 -0
- package/src/utils/config/configVip.ts +25 -0
- package/src/utils/csq.ts +115 -0
- package/src/utils/decisionTree.ts +45 -0
- package/src/utils/download.ts +30 -0
- package/src/utils/error.ts +69 -0
- package/src/utils/query/query.ts +55 -0
- package/src/utils/query/queryFilter.ts +132 -0
- package/src/utils/query/queryFilterComposed.ts +247 -0
- package/src/utils/query/queryFilterField.ts +75 -0
- package/src/utils/query/queryFilterFixed.ts +44 -0
- package/src/utils/query/querySample.ts +18 -0
- package/src/utils/query/queryVariantType.ts +76 -0
- package/src/utils/query/selector.ts +41 -0
- package/src/utils/{sortUtils.ts → query/sort.ts} +32 -11
- package/src/utils/sample.ts +19 -35
- package/src/utils/utils.ts +66 -2
- package/src/utils/variantType.ts +43 -0
- package/src/utils/vcf.ts +352 -0
- package/src/views/Help.tsx +109 -114
- package/src/views/Home.tsx +3 -2
- package/src/views/Sample.tsx +12 -7
- package/src/views/SampleVariant.tsx +23 -112
- package/src/views/SampleVariantConsequence.tsx +54 -144
- package/src/views/SampleVariants.tsx +33 -445
- package/src/views/SampleVariantsRedirect.tsx +20 -0
- package/src/views/Samples.tsx +7 -10
- package/src/views/Variant.tsx +31 -61
- package/src/views/VariantConsequence.tsx +42 -72
- package/src/views/Variants.tsx +29 -138
- package/src/views/VariantsRedirect.tsx +25 -0
- package/src/views/data/data.tsx +32 -6
- package/tests/store/variants.test.ts +122 -0
- package/tests/utils/config/config.test.ts +167 -0
- package/tests/utils/config/configCells.test.ts +86 -0
- package/tests/utils/config/configCellsComposed.test.ts +1163 -0
- package/tests/utils/config/configCellsField.test.ts +164 -0
- package/tests/utils/config/configCellsFixed.test.ts +99 -0
- package/tests/utils/config/configFilters.test.ts +80 -0
- package/tests/utils/config/configFiltersComposed.test.ts +504 -0
- package/tests/utils/config/configFiltersField.test.ts +140 -0
- package/tests/utils/config/configFiltersFixed.test.ts +81 -0
- package/tests/utils/config/configSorts.test.ts +55 -0
- package/tests/utils/config/configValidator.test.ts +56 -0
- package/tests/utils/config/configVip.test.ts +53 -0
- package/tests/utils/decisionTree.test.ts +71 -0
- package/tests/utils/download.test.ts +20 -0
- package/tests/utils/query/query.test.ts +84 -0
- package/tests/utils/query/queryFilter.test.ts +243 -0
- package/tests/utils/query/queryFilterComposed.test.ts +301 -0
- package/tests/utils/query/queryFilterField.test.ts +75 -0
- package/tests/utils/query/queryFilterFixed.test.ts +86 -0
- package/tests/utils/query/querySample.test.ts +45 -0
- package/tests/utils/query/queryVariantType.test.ts +56 -0
- package/{src/__tests__/sortUtils.test.ts → tests/utils/query/sort.test.ts} +3 -4
- package/tests/utils/sample.test.ts +259 -0
- package/tests/utils/utils.test.ts +120 -0
- package/tests/utils/variantType.test.ts +48 -0
- package/tests/utils/vcf.test.ts +649 -0
- package/tsconfig.json +6 -2
- package/vite.config.mts +20 -3
- package/.eslintignore +0 -4
- package/.eslintrc.js +0 -23
- package/src/Api.ts +0 -12
- package/src/__tests__/decisionTreeUtils.test.ts +0 -75
- package/src/__tests__/field.test.ts +0 -107
- package/src/__tests__/query.test.ts +0 -188
- package/src/__tests__/sample.test.ts +0 -184
- package/src/__tests__/utils.test.ts +0 -24
- package/src/__tests__/viewUtils.test.ts +0 -125
- package/src/components/ConsequenceTable.tsx +0 -45
- package/src/components/Error.tsx +0 -9
- package/src/components/FieldHeader.tsx +0 -26
- package/src/components/InfoCollapsablePane.tsx +0 -90
- package/src/components/VariantInfoNestedTable.tsx +0 -127
- package/src/components/VariantSampleTable.tsx +0 -58
- package/src/components/VariantsSampleTable.tsx +0 -184
- package/src/components/VariantsTable.tsx +0 -125
- package/src/components/filter/FilterAllelicBalance.tsx +0 -81
- package/src/components/filter/FilterCategorical.tsx +0 -81
- package/src/components/filter/FilterClinVar.tsx +0 -21
- package/src/components/filter/FilterGene.tsx +0 -34
- package/src/components/filter/FilterHpo.tsx +0 -161
- package/src/components/filter/FilterInheritance.tsx +0 -162
- package/src/components/filter/FilterIntegerGq.tsx +0 -47
- package/src/components/filter/FilterVI.tsx +0 -68
- package/src/components/filter/FilterVariantType.tsx +0 -146
- package/src/components/filter/Filters.tsx +0 -29
- package/src/components/filter/InfoFilter.tsx +0 -39
- package/src/components/filter/InfoFilters.tsx +0 -35
- package/src/components/filter/SampleFilters.tsx +0 -93
- package/src/components/filter/SamplesFilters.tsx +0 -33
- package/src/components/record/Allele.tsx +0 -38
- package/src/components/record/AlleleBreakend.tsx +0 -5
- package/src/components/record/AlleleMissing.tsx +0 -5
- package/src/components/record/AlleleNucs.tsx +0 -49
- package/src/components/record/AlleleSymbolic.tsx +0 -5
- package/src/components/record/Alt.tsx +0 -17
- package/src/components/record/Chrom.tsx +0 -5
- package/src/components/record/Format.tsx +0 -40
- package/src/components/record/Info.tsx +0 -55
- package/src/components/record/Pos.tsx +0 -5
- package/src/components/record/Qual.tsx +0 -5
- package/src/components/record/RecordDownload.tsx +0 -66
- package/src/components/record/Ref.tsx +0 -6
- package/src/components/record/field/Field.tsx +0 -36
- package/src/components/record/field/FieldMultipleValue.tsx +0 -22
- package/src/components/record/field/FieldSingleValue.tsx +0 -35
- package/src/components/record/info/ClinVar.tsx +0 -81
- package/src/components/record/info/Consequence.tsx +0 -18
- package/src/components/record/info/Gene.tsx +0 -56
- package/src/components/record/info/GnomAD.tsx +0 -54
- package/src/components/record/info/Hpo.tsx +0 -52
- package/src/components/record/info/InheritanceModes.tsx +0 -22
- package/src/components/record/info/VipC.tsx +0 -23
- package/src/components/record/info/Vkgl.tsx +0 -42
- package/src/mocks/GRCh37/vcf/no_vep.vcf.blob +0 -61
- package/src/mocks/GRCh37/vcf/samples_0.vcf.blob +0 -93
- package/src/mocks/GRCh37/vcf/samples_1.vcf.blob +0 -93
- package/src/mocks/GRCh37/vcf/samples_100.vcf.blob +0 -93
- package/src/utils/ApiUtils.ts +0 -263
- package/src/utils/csqUtils.ts +0 -27
- package/src/utils/decisionTreeUtils.ts +0 -31
- package/src/utils/field.ts +0 -49
- package/src/utils/query.ts +0 -154
- package/src/utils/viewUtils.ts +0 -32
|
@@ -81,8 +81,16 @@ table.is-borderless td,
|
|
|
81
81
|
line-height: 32.5px
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
.
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
84
|
+
.tooltip {
|
|
85
|
+
position: absolute;
|
|
86
|
+
z-index: 10;
|
|
87
|
+
margin: 0.5rem 0 0 0.5rem;
|
|
88
|
+
padding: 0.5rem;
|
|
89
|
+
cursor: default;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.control {
|
|
93
|
+
white-space: nowrap;
|
|
94
|
+
overflow: hidden;
|
|
95
|
+
text-overflow: ellipsis;
|
|
88
96
|
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { Component, createMemo, For, Match, Switch } from "solid-js";
|
|
2
|
+
import { ErrorNotification } from "./ErrorNotification";
|
|
3
|
+
|
|
4
|
+
type AlleleType = "breakend" | "missing" | "symbolic" | "nucs";
|
|
5
|
+
|
|
6
|
+
export const Allele: Component<{ value: string | null; isAbbreviate: boolean }> = (props) => {
|
|
7
|
+
const type = createMemo((): AlleleType => {
|
|
8
|
+
const value = props.value;
|
|
9
|
+
let type: AlleleType;
|
|
10
|
+
|
|
11
|
+
if (value === null) type = "missing";
|
|
12
|
+
else if (value.startsWith("<") && value.endsWith(">")) type = "symbolic";
|
|
13
|
+
else if (value.indexOf("[") !== -1 || value.indexOf("]") !== -1 || value.indexOf(".") !== -1) type = "breakend";
|
|
14
|
+
else type = "nucs";
|
|
15
|
+
|
|
16
|
+
return type;
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<Switch fallback={<ErrorNotification error={`unexpected allele type '${type()}'`} />}>
|
|
21
|
+
<Match when={type() === "breakend"}>
|
|
22
|
+
<AlleleBreakend value={props.value as string} />
|
|
23
|
+
</Match>
|
|
24
|
+
<Match when={type() === "missing"}>
|
|
25
|
+
<AlleleMissing />
|
|
26
|
+
</Match>
|
|
27
|
+
<Match when={type() === "symbolic"}>
|
|
28
|
+
<AlleleSymbolic value={props.value as string} />
|
|
29
|
+
</Match>
|
|
30
|
+
<Match when={type() === "nucs"}>
|
|
31
|
+
<AlleleNucs values={(props.value as string).split("")} isAbbreviate={props.isAbbreviate} />
|
|
32
|
+
</Match>
|
|
33
|
+
</Switch>
|
|
34
|
+
);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const AlleleBreakend: Component<{ value: string }> = (props) => {
|
|
38
|
+
return <span class="base-n">{props.value}</span>;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const AlleleMissing: Component = () => {
|
|
42
|
+
return <span class="base-n">?</span>;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const AlleleSymbolic: Component<{ value: string }> = (props) => {
|
|
46
|
+
return <span class="base-n">{props.value}</span>;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const Nucs: Component<{ bases: string[] }> = (props) => {
|
|
50
|
+
return (
|
|
51
|
+
<For each={props.bases}>
|
|
52
|
+
{(base) => (
|
|
53
|
+
<span
|
|
54
|
+
classList={{
|
|
55
|
+
base: true,
|
|
56
|
+
"base-a": base === "A",
|
|
57
|
+
"base-c": base === "C",
|
|
58
|
+
"base-g": base === "G",
|
|
59
|
+
"base-n": base === "N",
|
|
60
|
+
"base-t": base === "T",
|
|
61
|
+
}}
|
|
62
|
+
>
|
|
63
|
+
{base}
|
|
64
|
+
</span>
|
|
65
|
+
)}
|
|
66
|
+
</For>
|
|
67
|
+
);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const AlleleNucs: Component<{ values: string[]; isAbbreviate: boolean }> = (props) => {
|
|
71
|
+
const abbreviate = (): boolean => props.values.length > 4 && props.isAbbreviate;
|
|
72
|
+
|
|
73
|
+
const nucs = (): string[] => {
|
|
74
|
+
let nucs = props.values;
|
|
75
|
+
if (abbreviate()) {
|
|
76
|
+
const lastNuc = nucs[nucs.length - 1] as string;
|
|
77
|
+
nucs = nucs.slice(0, 2);
|
|
78
|
+
nucs.push("\u2026"); // ellipsis
|
|
79
|
+
nucs.push(lastNuc);
|
|
80
|
+
}
|
|
81
|
+
return nucs;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<>
|
|
86
|
+
{abbreviate() ? (
|
|
87
|
+
<abbr title={props.values.join("")}>
|
|
88
|
+
<Nucs bases={nucs()} />
|
|
89
|
+
</abbr>
|
|
90
|
+
) : (
|
|
91
|
+
<Nucs bases={nucs()} />
|
|
92
|
+
)}
|
|
93
|
+
</>
|
|
94
|
+
);
|
|
95
|
+
};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Component, For } from "solid-js";
|
|
2
2
|
import { A } from "@solidjs/router";
|
|
3
3
|
|
|
4
|
-
export type
|
|
4
|
+
export type BreadcrumbItem = { href?: string; text: string };
|
|
5
5
|
|
|
6
6
|
export const Breadcrumb: Component<{
|
|
7
|
-
items:
|
|
7
|
+
items: BreadcrumbItem[];
|
|
8
8
|
}> = (props) => {
|
|
9
9
|
return (
|
|
10
10
|
<div class="columns is-gapless">
|
|
@@ -12,7 +12,7 @@ export const Breadcrumb: Component<{
|
|
|
12
12
|
<nav class="breadcrumb has-succeeds-separator">
|
|
13
13
|
<ul>
|
|
14
14
|
<li classList={{ "is-active": props.items.length === 0 }}>
|
|
15
|
-
<A href="/">
|
|
15
|
+
<A href="/" end={true}>
|
|
16
16
|
<span class="icon is-small mr-2">
|
|
17
17
|
<i class="fas fa-home" />
|
|
18
18
|
</span>
|
|
@@ -22,7 +22,9 @@ export const Breadcrumb: Component<{
|
|
|
22
22
|
<For each={props.items}>
|
|
23
23
|
{(link, i) => (
|
|
24
24
|
<li classList={{ "is-active": i() === props.items.length - 1 }}>
|
|
25
|
-
<A href={link.href || "#"}
|
|
25
|
+
<A href={link.href || "#"} end={true}>
|
|
26
|
+
{link.text}
|
|
27
|
+
</A>
|
|
26
28
|
</li>
|
|
27
29
|
)}
|
|
28
30
|
</For>
|
|
@@ -1,45 +1,32 @@
|
|
|
1
1
|
import { Component, createSignal, For } from "solid-js";
|
|
2
|
-
import
|
|
3
|
-
import { useNavigate, A } from "@solidjs/router";
|
|
2
|
+
import { query, useNavigate } from "@solidjs/router";
|
|
4
3
|
import { useStore } from "../store";
|
|
4
|
+
import { getDatasetIds, selectDataset } from "../utils/api.ts";
|
|
5
|
+
import { init } from "../App.tsx";
|
|
5
6
|
|
|
6
7
|
export const DatasetDropdown: Component = () => {
|
|
8
|
+
const navigate = useNavigate();
|
|
7
9
|
const [, actions] = useStore();
|
|
8
10
|
|
|
9
|
-
const navigate = useNavigate();
|
|
10
11
|
const [selectedDataset, setSelectedDataset] = createSignal("GRCh37 Family");
|
|
11
12
|
|
|
12
13
|
function switchIt(datasetName: string) {
|
|
13
|
-
setSelectedDataset(datasetName);
|
|
14
|
-
api.selectDataset(datasetName);
|
|
15
14
|
actions.reset();
|
|
16
|
-
(
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
navigate(`/samples/${samples.items[0].id}/variants`);
|
|
21
|
-
} else if (samples.total === 0) {
|
|
22
|
-
navigate(`/variants`);
|
|
23
|
-
} else {
|
|
24
|
-
navigate(`/samples`);
|
|
25
|
-
}
|
|
26
|
-
})().catch((err: Error) => console.error(err.stack));
|
|
15
|
+
query.clear(); // flush cache
|
|
16
|
+
setSelectedDataset(datasetName);
|
|
17
|
+
selectDataset(datasetName);
|
|
18
|
+
init(navigate);
|
|
27
19
|
}
|
|
28
20
|
|
|
29
21
|
return (
|
|
30
22
|
<div class="navbar-item has-dropdown is-hoverable">
|
|
31
|
-
<
|
|
23
|
+
<a class="navbar-link">{selectedDataset()}</a>
|
|
32
24
|
<div class="navbar-dropdown">
|
|
33
|
-
<For each={
|
|
25
|
+
<For each={getDatasetIds()}>
|
|
34
26
|
{(dataset: string) => (
|
|
35
|
-
<
|
|
36
|
-
class="navbar-item"
|
|
37
|
-
onClick={() => {
|
|
38
|
-
switchIt(dataset);
|
|
39
|
-
}}
|
|
40
|
-
>
|
|
27
|
+
<a class="navbar-item" onClick={() => switchIt(dataset)}>
|
|
41
28
|
{dataset}
|
|
42
|
-
</
|
|
29
|
+
</a>
|
|
43
30
|
)}
|
|
44
31
|
</For>
|
|
45
32
|
</div>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Component, createEffect } from "solid-js";
|
|
2
|
+
|
|
3
|
+
// renamed from Error to avoid naming conflict with JavaScript error
|
|
4
|
+
export const ErrorNotification: Component<{
|
|
5
|
+
error: unknown;
|
|
6
|
+
}> = (props) => {
|
|
7
|
+
createEffect(() => console.error(props.error));
|
|
8
|
+
return <div class="notification is-danger is-light">An unexpected error occurred</div>;
|
|
9
|
+
};
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { Component, onCleanup, onMount } from "solid-js";
|
|
2
2
|
import igv, { Browser } from "igv";
|
|
3
|
-
import api from "../Api";
|
|
4
3
|
import { fromByteArray } from "base64-js";
|
|
5
|
-
import { writeVcf } from "@molgenis/vip-report-vcf
|
|
6
|
-
import { ComposedQuery, Sample } from "@molgenis/vip-report-api
|
|
4
|
+
import { VcfRecord, writeVcf } from "@molgenis/vip-report-vcf";
|
|
5
|
+
import { ComposedQuery, Cram, Item, Sample } from "@molgenis/vip-report-api";
|
|
6
|
+
import { fetchCram, fetchFastaGz, fetchGenesGz, fetchRecords, MetadataContainer } from "../utils/api.ts";
|
|
7
7
|
|
|
8
|
-
async function
|
|
8
|
+
async function fetchVcf(
|
|
9
|
+
metadata: MetadataContainer,
|
|
10
|
+
contig: string,
|
|
11
|
+
position: number,
|
|
12
|
+
samples: Item<Sample>[],
|
|
13
|
+
): Promise<Uint8Array> {
|
|
9
14
|
const query: ComposedQuery = {
|
|
10
15
|
operator: "and",
|
|
11
16
|
args: [
|
|
@@ -19,21 +24,28 @@ async function createVcf(contig: string, position: number, samples: Sample[]): P
|
|
|
19
24
|
},
|
|
20
25
|
],
|
|
21
26
|
};
|
|
22
|
-
const
|
|
27
|
+
const records = await fetchRecords({ query, size: Number.MAX_SAFE_INTEGER });
|
|
23
28
|
const vcf = writeVcf(
|
|
24
|
-
{ metadata:
|
|
25
|
-
{ samples: samples.map((sample) => sample.person.individualId) },
|
|
29
|
+
{ metadata: metadata.records, data: records.items.map((item) => item.data) },
|
|
30
|
+
{ samples: samples.map((sample) => sample.data.person.individualId) },
|
|
26
31
|
);
|
|
27
32
|
return toBytes(vcf);
|
|
28
33
|
}
|
|
29
34
|
|
|
30
35
|
const toBytes = (str: string): Uint8Array => Uint8Array.from(str.split("").map((letter) => letter.charCodeAt(0)));
|
|
31
36
|
|
|
32
|
-
const createBrowserConfig = async (
|
|
37
|
+
const createBrowserConfig = async (
|
|
38
|
+
metadata: MetadataContainer,
|
|
39
|
+
record: Item<VcfRecord>,
|
|
40
|
+
samples: Item<Sample>[],
|
|
41
|
+
): Promise<unknown> => {
|
|
42
|
+
const contig = record.data.c;
|
|
43
|
+
const position = record.data.p;
|
|
44
|
+
|
|
33
45
|
const data = await Promise.all([
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
46
|
+
fetchFastaGz(contig, position),
|
|
47
|
+
fetchVcf(metadata, contig, position, samples),
|
|
48
|
+
fetchGenesGz(),
|
|
37
49
|
]);
|
|
38
50
|
const fastaGz = data[0];
|
|
39
51
|
const vcf = data[1];
|
|
@@ -61,11 +73,9 @@ const createBrowserConfig = async (contig: string, position: number, samples: Sa
|
|
|
61
73
|
url: "data:application/octet-stream;base64," + fromByteArray(vcf),
|
|
62
74
|
});
|
|
63
75
|
|
|
64
|
-
const htsFileMetadata = await api.getHtsFileMetadata();
|
|
65
|
-
|
|
66
76
|
return {
|
|
67
77
|
reference: {
|
|
68
|
-
id:
|
|
78
|
+
id: metadata.htsFile.genomeAssembly,
|
|
69
79
|
name: "Reference",
|
|
70
80
|
fastaURL: "data:application/gzip;base64," + fromByteArray(fastaGz),
|
|
71
81
|
tracks: tracks,
|
|
@@ -77,15 +87,15 @@ const createBrowserConfig = async (contig: string, position: number, samples: Sa
|
|
|
77
87
|
};
|
|
78
88
|
};
|
|
79
89
|
|
|
80
|
-
const updateBrowser = async (browser: Browser, samples: Sample[]): Promise<void> => {
|
|
81
|
-
const data = await Promise.all([...samples.map((sample) =>
|
|
90
|
+
const updateBrowser = async (browser: Browser, samples: Item<Sample>[]): Promise<void> => {
|
|
91
|
+
const data = await Promise.all([...samples.map((sample) => fetchCram(sample.data.person.individualId))]);
|
|
82
92
|
const crams = data.slice(0);
|
|
83
93
|
|
|
84
94
|
for (let i = 0; i < samples.length; ++i) {
|
|
85
|
-
const cram = crams[i];
|
|
95
|
+
const cram = crams[i] as Cram | null;
|
|
86
96
|
|
|
87
97
|
if (cram !== null) {
|
|
88
|
-
const sampleId = samples[i].person.individualId;
|
|
98
|
+
const sampleId = samples[i]!.data.person.individualId;
|
|
89
99
|
await browser.loadTrack({
|
|
90
100
|
type: "alignment",
|
|
91
101
|
format: "cram",
|
|
@@ -99,22 +109,29 @@ const updateBrowser = async (browser: Browser, samples: Sample[]): Promise<void>
|
|
|
99
109
|
}
|
|
100
110
|
};
|
|
101
111
|
|
|
102
|
-
export const GenomeBrowser: Component<{
|
|
112
|
+
export const GenomeBrowser: Component<{
|
|
113
|
+
metadata: MetadataContainer;
|
|
114
|
+
record: Item<VcfRecord>;
|
|
115
|
+
samples: Item<Sample>[];
|
|
116
|
+
}> = (props) => {
|
|
103
117
|
let divRef: HTMLDivElement;
|
|
104
118
|
let browser: Browser;
|
|
105
119
|
onMount(() => {
|
|
106
120
|
(async () => {
|
|
107
|
-
const config = await createBrowserConfig(props.
|
|
121
|
+
const config = await createBrowserConfig(props.metadata, props.record, props.samples);
|
|
108
122
|
if (config !== null) {
|
|
109
123
|
browser = await igv.createBrowser(divRef, config);
|
|
110
124
|
await updateBrowser(browser, props.samples);
|
|
111
125
|
}
|
|
112
|
-
})()
|
|
126
|
+
})();
|
|
113
127
|
});
|
|
114
128
|
onCleanup(() => {
|
|
115
|
-
|
|
116
|
-
|
|
129
|
+
//cannot use "removeBrowser" here https://github.com/igvteam/igv.js/issues/1918
|
|
130
|
+
if (browser !== undefined) {
|
|
131
|
+
browser.root.remove();
|
|
132
|
+
browser.removeAllTracks();
|
|
117
133
|
}
|
|
118
134
|
});
|
|
135
|
+
// noinspection JSUnusedAssignment
|
|
119
136
|
return <div ref={divRef!} />;
|
|
120
137
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Component } from "solid-js";
|
|
2
2
|
import { Anchor } from "./Anchor";
|
|
3
|
-
import { OntologyClass } from "@molgenis/vip-report-api
|
|
3
|
+
import { OntologyClass } from "@molgenis/vip-report-api";
|
|
4
4
|
|
|
5
5
|
export const HpoTerm: Component<{
|
|
6
6
|
ontologyClass: OntologyClass;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Component, createMemo, For } from "solid-js";
|
|
2
|
-
import { Page } from "@molgenis/vip-report-api
|
|
2
|
+
import { Page } from "@molgenis/vip-report-api";
|
|
3
3
|
|
|
4
4
|
const createPages = (page: number, pages: number): (number | null)[] => {
|
|
5
5
|
if (pages <= 5) {
|
|
@@ -13,35 +13,40 @@ const createPages = (page: number, pages: number): (number | null)[] => {
|
|
|
13
13
|
}
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
+
export type PageChangeEvent = { page: number };
|
|
17
|
+
export type PageChangeCallback = (event: PageChangeEvent) => void;
|
|
18
|
+
|
|
16
19
|
export const Pager: Component<{
|
|
17
20
|
page: Page;
|
|
18
|
-
onPageChange:
|
|
21
|
+
onPageChange: PageChangeCallback;
|
|
19
22
|
}> = (props) => {
|
|
20
|
-
const currentPage = () => props.page.number;
|
|
21
23
|
const nrPages = createMemo(() => Math.ceil(props.page.totalElements / props.page.size));
|
|
22
|
-
const pages = createMemo(() => createPages(
|
|
24
|
+
const pages = createMemo(() => createPages(props.page.number, nrPages()));
|
|
25
|
+
const page = createMemo(() => props.page);
|
|
23
26
|
|
|
24
27
|
return (
|
|
25
28
|
<>
|
|
26
|
-
{
|
|
29
|
+
{page().totalElements > page().size && (
|
|
27
30
|
<nav class="pagination is-centered">
|
|
28
31
|
<ul class="pagination-list">
|
|
29
32
|
<li>
|
|
30
33
|
<a
|
|
31
|
-
classList={{ "pagination-previous": true, "is-invisible":
|
|
32
|
-
onClick={
|
|
34
|
+
classList={{ "pagination-previous": true, "is-invisible": page().number === 0 }}
|
|
35
|
+
onClick={page().number > 0 ? () => props.onPageChange({ page: page().number - 1 }) : undefined}
|
|
33
36
|
>
|
|
34
37
|
Previous
|
|
35
38
|
</a>
|
|
36
39
|
</li>
|
|
37
40
|
<For each={pages()}>
|
|
38
|
-
{(
|
|
39
|
-
|
|
41
|
+
{(currentPage) =>
|
|
42
|
+
currentPage !== null ? (
|
|
40
43
|
<a
|
|
41
|
-
classList={{ "pagination-link": true, "is-current":
|
|
42
|
-
onClick={
|
|
44
|
+
classList={{ "pagination-link": true, "is-current": currentPage === page().number }}
|
|
45
|
+
onClick={() =>
|
|
46
|
+
currentPage !== page().number ? props.onPageChange({ page: currentPage }) : undefined
|
|
47
|
+
}
|
|
43
48
|
>
|
|
44
|
-
{
|
|
49
|
+
{currentPage + 1}
|
|
45
50
|
</a>
|
|
46
51
|
) : (
|
|
47
52
|
<span class="pagination-ellipsis">…</span>
|
|
@@ -50,8 +55,10 @@ export const Pager: Component<{
|
|
|
50
55
|
</For>
|
|
51
56
|
<li>
|
|
52
57
|
<a
|
|
53
|
-
classList={{ "pagination-next": true, "is-invisible":
|
|
54
|
-
onClick={
|
|
58
|
+
classList={{ "pagination-next": true, "is-invisible": page().number === nrPages() - 1 }}
|
|
59
|
+
onClick={
|
|
60
|
+
page().number < nrPages() - 1 ? () => props.onPageChange({ page: page().number + 1 }) : undefined
|
|
61
|
+
}
|
|
55
62
|
>
|
|
56
63
|
Next
|
|
57
64
|
</a>
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { Component, For } from "solid-js";
|
|
2
|
-
|
|
2
|
+
import { ConfigRecordsPerPage } from "../types/config";
|
|
3
|
+
|
|
4
|
+
export type RecordsPerPageChangeEvent = { number: number };
|
|
5
|
+
export type RecordsPerPageChangeCallback = (event: RecordsPerPageChangeEvent) => void;
|
|
3
6
|
|
|
4
7
|
export const RecordsPerPage: Component<{
|
|
5
|
-
|
|
6
|
-
onChange: (event:
|
|
8
|
+
config: ConfigRecordsPerPage;
|
|
9
|
+
onChange: (event: RecordsPerPageChangeEvent) => void;
|
|
7
10
|
}> = (props) => {
|
|
8
|
-
const options = [10, 20, 50, 100];
|
|
9
11
|
const onRecordsPerPageChange = (event: Event) => {
|
|
10
12
|
props.onChange({ number: Number((event.target as HTMLInputElement).value) });
|
|
11
13
|
};
|
|
@@ -15,10 +17,10 @@ export const RecordsPerPage: Component<{
|
|
|
15
17
|
<span class="inline-control-text mr-2">Records per page</span>
|
|
16
18
|
<div class="select">
|
|
17
19
|
<select onChange={onRecordsPerPageChange}>
|
|
18
|
-
<For each={
|
|
20
|
+
<For each={props.config}>
|
|
19
21
|
{(option) => (
|
|
20
|
-
<option value={option} selected={option
|
|
21
|
-
{option}
|
|
22
|
+
<option value={option.number} selected={option.selected}>
|
|
23
|
+
{option.number}
|
|
22
24
|
</option>
|
|
23
25
|
)}
|
|
24
26
|
</For>
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { VcfRecord } from "@molgenis/vip-report-vcf";
|
|
2
|
+
import { Item } from "@molgenis/vip-report-api";
|
|
3
|
+
import { Component, For, Match, Show, Switch } from "solid-js";
|
|
4
|
+
import { CellValue, ConfigCellGroup, ConfigCellItem } from "../types/configCells";
|
|
5
|
+
import { ConfigCells } from "../types/config";
|
|
6
|
+
import { Abbr } from "./Abbr";
|
|
7
|
+
import { Field } from "./field/Field";
|
|
8
|
+
import { Table } from "./Table.tsx";
|
|
9
|
+
|
|
10
|
+
export const RecordsTable: Component<{
|
|
11
|
+
fieldConfigs: ConfigCells;
|
|
12
|
+
records: Item<VcfRecord>[];
|
|
13
|
+
verticalHeaders?: boolean;
|
|
14
|
+
}> = (props) => {
|
|
15
|
+
return (
|
|
16
|
+
<Table>
|
|
17
|
+
<RecordsTableHeader fieldConfigs={props.fieldConfigs} verticalHeaders={props.verticalHeaders} />
|
|
18
|
+
<RecordsTableBody fieldConfigs={props.fieldConfigs} records={props.records} />
|
|
19
|
+
</Table>
|
|
20
|
+
);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const RecordsTableHeader: Component<{
|
|
24
|
+
fieldConfigs: ConfigCells;
|
|
25
|
+
verticalHeaders?: boolean;
|
|
26
|
+
}> = (props) => {
|
|
27
|
+
return (
|
|
28
|
+
<thead>
|
|
29
|
+
<tr style={props.verticalHeaders ? { "writing-mode": "vertical-rl" } : undefined}>
|
|
30
|
+
<RecordsTableHeaderCells fieldConfigs={props.fieldConfigs} />
|
|
31
|
+
</tr>
|
|
32
|
+
</thead>
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const RecordsTableHeaderCells: Component<{ fieldConfigs: ConfigCells }> = (props) => {
|
|
37
|
+
return (
|
|
38
|
+
<For each={props.fieldConfigs}>
|
|
39
|
+
{(fieldConfig) => (
|
|
40
|
+
<Switch fallback={<RecordsTableHeaderCell fieldConfig={fieldConfig as ConfigCellItem} />}>
|
|
41
|
+
<Match when={fieldConfig.type === "group"}>
|
|
42
|
+
<For each={(fieldConfig as ConfigCellGroup).fieldConfigs}>
|
|
43
|
+
{(childConfigField) => <RecordsTableHeaderCell fieldConfig={childConfigField} />}
|
|
44
|
+
</For>
|
|
45
|
+
</Match>
|
|
46
|
+
</Switch>
|
|
47
|
+
)}
|
|
48
|
+
</For>
|
|
49
|
+
);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const RecordsTableHeaderCell: Component<{
|
|
53
|
+
fieldConfig: ConfigCellItem;
|
|
54
|
+
}> = (props) => {
|
|
55
|
+
const label = () => props.fieldConfig.label();
|
|
56
|
+
const description = () => props.fieldConfig.description();
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<th>
|
|
60
|
+
<Show when={description()} fallback={label()}>
|
|
61
|
+
{(description) => <Abbr title={description()} value={label()} />}
|
|
62
|
+
</Show>
|
|
63
|
+
</th>
|
|
64
|
+
);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const RecordsTableBody: Component<{
|
|
68
|
+
fieldConfigs: ConfigCells;
|
|
69
|
+
records: Item<VcfRecord>[];
|
|
70
|
+
}> = (props) => {
|
|
71
|
+
return (
|
|
72
|
+
<tbody>
|
|
73
|
+
<For each={props.records}>
|
|
74
|
+
{(record) => (
|
|
75
|
+
<tr>
|
|
76
|
+
<RecordsTableCells fieldConfigs={props.fieldConfigs} record={record} />
|
|
77
|
+
</tr>
|
|
78
|
+
)}
|
|
79
|
+
</For>
|
|
80
|
+
</tbody>
|
|
81
|
+
);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export const RecordsTableCells: Component<{ fieldConfigs: ConfigCells; record: Item<VcfRecord> }> = (props) => {
|
|
85
|
+
return (
|
|
86
|
+
<For each={props.fieldConfigs}>
|
|
87
|
+
{(fieldConfig) => (
|
|
88
|
+
<Switch fallback={<RecordsTableCell fieldConfig={fieldConfig as ConfigCellItem} record={props.record} />}>
|
|
89
|
+
<Match when={fieldConfig.type === "group"}>
|
|
90
|
+
<For each={(fieldConfig as ConfigCellGroup).fieldConfigs}>
|
|
91
|
+
{(childConfigField) => <RecordsTableCell fieldConfig={childConfigField} record={props.record} />}
|
|
92
|
+
</For>
|
|
93
|
+
</Match>
|
|
94
|
+
</Switch>
|
|
95
|
+
)}
|
|
96
|
+
</For>
|
|
97
|
+
);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export const RecordsTableCell: Component<{
|
|
101
|
+
fieldConfig: ConfigCellItem;
|
|
102
|
+
record: Item<VcfRecord>;
|
|
103
|
+
}> = (props) => {
|
|
104
|
+
const lineIndices = () => [...Array(props.fieldConfig.valueCount(props.record))].map((_, i) => i);
|
|
105
|
+
return (
|
|
106
|
+
<td>
|
|
107
|
+
<For each={lineIndices()}>
|
|
108
|
+
{(valueIndex) => (
|
|
109
|
+
<RecordsTableCellLine fieldConfig={props.fieldConfig} record={props.record} valueIndex={valueIndex} />
|
|
110
|
+
)}
|
|
111
|
+
</For>
|
|
112
|
+
</td>
|
|
113
|
+
);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const RecordsTableCellLine: Component<{
|
|
117
|
+
fieldConfig: ConfigCellItem;
|
|
118
|
+
record: Item<VcfRecord>;
|
|
119
|
+
valueIndex: number;
|
|
120
|
+
}> = (props) => {
|
|
121
|
+
const value = (): CellValue => props.fieldConfig.value(props.record, props.valueIndex);
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
<div>
|
|
125
|
+
<Field config={props.fieldConfig} value={value()} />
|
|
126
|
+
{/* print Unicode zero width space character to ensure a line with non-zero height */}
|
|
127
|
+
<span>{"\u200b"}</span>
|
|
128
|
+
</div>
|
|
129
|
+
);
|
|
130
|
+
};
|