@molgenis/vip-report-template 3.1.2 → 3.1.3
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/package.json +21 -21
- package/src/components/VariantsTable.tsx +6 -3
- package/src/components/record/RecordDownload.tsx +14 -3
- package/src/components/record/info/Gene.tsx +2 -2
- package/src/mocks/GRCh37/vcf/family.vcf.blob +2 -2
- package/src/mocks/GRCh37/vcf/samples_1.vcf.blob +1 -1
- package/src/mocks/MockApiClient.ts +1 -1
- package/src/store/index.tsx +41 -27
- package/src/utils/query.ts +0 -1
- package/src/views/SampleVariantConsequence.tsx +4 -1
- package/src/views/SampleVariants.tsx +1 -1
- package/src/views/Samples.tsx +97 -38
- package/src/views/VariantConsequence.tsx +15 -11
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@molgenis/vip-report-template",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.3",
|
|
4
4
|
"description": "Report Template for Variant Call Format (VCF) Report Generator",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build": "vite build",
|
|
@@ -15,32 +15,32 @@
|
|
|
15
15
|
},
|
|
16
16
|
"license": "LGPL-3.0",
|
|
17
17
|
"devDependencies": {
|
|
18
|
-
"@molgenis/vite-plugin-inline": "^1.0.
|
|
19
|
-
"@typescript-eslint/eslint-plugin": "^5.
|
|
20
|
-
"@typescript-eslint/parser": "^5.
|
|
18
|
+
"@molgenis/vite-plugin-inline": "^1.0.8",
|
|
19
|
+
"@typescript-eslint/eslint-plugin": "^5.35.1",
|
|
20
|
+
"@typescript-eslint/parser": "^5.35.1",
|
|
21
|
+
"@vitest/coverage-c8": "^0.22.1",
|
|
21
22
|
"bulma": "^0.9.4",
|
|
22
|
-
"
|
|
23
|
-
"eslint": "^8.17.0",
|
|
23
|
+
"eslint": "^8.22.0",
|
|
24
24
|
"eslint-config-prettier": "^8.5.0",
|
|
25
|
-
"eslint-plugin-prettier": "^4.
|
|
26
|
-
"eslint-plugin-solid": "^0.7.
|
|
25
|
+
"eslint-plugin-prettier": "^4.2.1",
|
|
26
|
+
"eslint-plugin-solid": "^0.7.1",
|
|
27
27
|
"husky": "^8.0.1",
|
|
28
|
-
"prettier": "^2.7.
|
|
29
|
-
"sass": "^1.
|
|
30
|
-
"typescript": "^4.7.
|
|
31
|
-
"vite": "^
|
|
32
|
-
"vite-plugin-solid": "^2.
|
|
33
|
-
"vitest": "^0.
|
|
28
|
+
"prettier": "^2.7.1",
|
|
29
|
+
"sass": "^1.54.5",
|
|
30
|
+
"typescript": "^4.7.4",
|
|
31
|
+
"vite": "^3.0.9",
|
|
32
|
+
"vite-plugin-solid": "^2.3.0",
|
|
33
|
+
"vitest": "^0.22.1"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@fortawesome/fontawesome-svg-core": "^6.1.
|
|
37
|
-
"@fortawesome/free-solid-svg-icons": "^6.1.
|
|
38
|
-
"@molgenis/vip-report-api": "^3.5.
|
|
39
|
-
"@molgenis/vip-report-vcf": "^1.2.
|
|
36
|
+
"@fortawesome/fontawesome-svg-core": "^6.1.2",
|
|
37
|
+
"@fortawesome/free-solid-svg-icons": "^6.1.2",
|
|
38
|
+
"@molgenis/vip-report-api": "^3.5.4",
|
|
39
|
+
"@molgenis/vip-report-vcf": "^1.2.3",
|
|
40
40
|
"base64-js": "^1.5.1",
|
|
41
|
-
"igv": "^2.
|
|
42
|
-
"solid-app-router": "^0.
|
|
43
|
-
"solid-js": "^1.4.
|
|
41
|
+
"igv": "^2.13.1",
|
|
42
|
+
"solid-app-router": "^0.4.2",
|
|
43
|
+
"solid-js": "^1.4.8"
|
|
44
44
|
},
|
|
45
45
|
"lint-staged": {
|
|
46
46
|
"src/**/*.{tsx,ts}": [
|
|
@@ -56,9 +56,12 @@ export const VariantsTable: Component<{
|
|
|
56
56
|
<tr>
|
|
57
57
|
<For each={infoFieldsNested()}>
|
|
58
58
|
{(infoField) => (
|
|
59
|
-
|
|
60
|
-
{
|
|
61
|
-
|
|
59
|
+
<>
|
|
60
|
+
{infoField.number.count !== 1 && <th />}
|
|
61
|
+
<For each={infoField.nested?.items}>
|
|
62
|
+
{(nestedInfoField) => <FieldHeader field={nestedInfoField} />}
|
|
63
|
+
</For>
|
|
64
|
+
</>
|
|
62
65
|
)}
|
|
63
66
|
</For>
|
|
64
67
|
</tr>
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import { Component } from "solid-js";
|
|
2
|
-
import { Query, Sample } from "@molgenis/vip-report-api/src/Api";
|
|
2
|
+
import { HtsFileMetadata, Query, Sample } from "@molgenis/vip-report-api/src/Api";
|
|
3
3
|
import { Metadata } from "@molgenis/vip-report-vcf/src/Vcf";
|
|
4
4
|
import api from "../../Api";
|
|
5
5
|
import { Filter, writeVcf } from "@molgenis/vip-report-vcf/src/VcfWriter";
|
|
6
6
|
|
|
7
|
+
const DOWNLOAD_POSTFIX = "_report.vcf";
|
|
7
8
|
export const RecordDownload: Component<{ recordsMetadata: Metadata; query?: Query; samples?: Sample[] }> = (props) => {
|
|
8
9
|
const filter = (): Filter | undefined =>
|
|
9
10
|
props.samples ? { samples: props.samples.map((sample) => sample.person.individualId) } : undefined;
|
|
10
11
|
|
|
12
|
+
function parseFilename(htsFile: HtsFileMetadata) {
|
|
13
|
+
return htsFile.uri.split("\\").pop()?.split("/").pop()?.split(".").shift();
|
|
14
|
+
}
|
|
15
|
+
|
|
11
16
|
function onClick() {
|
|
12
17
|
const handler = async () => {
|
|
13
18
|
const records = await api.getRecords({ query: props.query, size: Number.MAX_SAFE_INTEGER });
|
|
@@ -16,7 +21,9 @@ export const RecordDownload: Component<{ recordsMetadata: Metadata; query?: Quer
|
|
|
16
21
|
const url = window.URL.createObjectURL(new Blob([vcf]));
|
|
17
22
|
const link = document.createElement("a");
|
|
18
23
|
link.href = url;
|
|
19
|
-
|
|
24
|
+
const htsFile = await api.getHtsFileMetadata();
|
|
25
|
+
const filename = parseFilename(htsFile) as string;
|
|
26
|
+
link.setAttribute("download", filename + DOWNLOAD_POSTFIX);
|
|
20
27
|
document.body.appendChild(link);
|
|
21
28
|
link.click();
|
|
22
29
|
document.body.removeChild(link);
|
|
@@ -26,7 +33,11 @@ export const RecordDownload: Component<{ recordsMetadata: Metadata; query?: Quer
|
|
|
26
33
|
|
|
27
34
|
return (
|
|
28
35
|
<div class="control">
|
|
29
|
-
<button
|
|
36
|
+
<button
|
|
37
|
+
class="button is-info"
|
|
38
|
+
onClick={onClick}
|
|
39
|
+
title="Download vcf file with records matching filters and search queries"
|
|
40
|
+
>
|
|
30
41
|
<span class="icon is-small">
|
|
31
42
|
<i class="fas fa-download" />
|
|
32
43
|
</span>
|
|
@@ -45,9 +45,9 @@ export const Gene: Component<FieldProps> = (props) => {
|
|
|
45
45
|
<span>{symbol}</span>
|
|
46
46
|
</Anchor>
|
|
47
47
|
{incompletePenetrance() && (
|
|
48
|
-
<
|
|
48
|
+
<span class="ml-1">
|
|
49
49
|
<Abbr title="gene is known for incomplete penetrance" value="IP" />
|
|
50
|
-
</
|
|
50
|
+
</span>
|
|
51
51
|
)}
|
|
52
52
|
</>
|
|
53
53
|
)}
|
|
@@ -80,8 +80,8 @@
|
|
|
80
80
|
1 16375583 . GA G . PASS CSQ=-|intron_variant|MODIFIER|CLCNKB|1188|Transcript|NM_000085.5|protein_coding||7/19|NM_000085.5:c.656-31del|||||||rs751608665|1||1||1|EntrezGene||||||||uncertain_significance&pathogenic||1||||||||20|45|-14|-45|0.05|0.00|0.00|0.00|CLCNKB|VUS|0.032507|HP:0000951||AR|LP||||1:16375584-16375585|0.000548455|0|638508|Conflicting_interpretations_of_pathogenicity||criteria_provided&_conflicting_interpretations|LP|filter&vkgl&exit_lp|,-|frameshift_variant|HIGH|CLCNKB|1188|Transcript|NM_001165945.2|protein_coding|1/13||NM_001165945.2:c.118del|NP_001159417.2:p.Arg40GlyfsTer4|328/2174|118/1554|40/517|R/X|Agg/gg|rs751608665|1||1|||EntrezGene||||||||uncertain_significance&pathogenic||1||||||||20|45|-14|-45|0.05|0.00|0.00|0.00|CLCNKB|VUS|0.9770811|HP:0000951||AR|LP||||1:16375584-16375585|0.000548455|0|638508|Conflicting_interpretations_of_pathogenicity||criteria_provided&_conflicting_interpretations|LP|filter&vkgl&exit_lp| GT:AD:DP:VI:VIC:VID:VIG:VIM:VIS 1|0:25,25:50:AR:1_16376412_G_A,1_16376412_G_N:0:1188:1:AR_C 1|0:25,25:50:.:.:.:.:.:. 0|0:50,0:50:.:.:.:.:.:. 0|0:50,0:50:.:.:.:.:.:. 0|0:50,0:50:.:.:.:.:.:. 0|0:50,0:50:.:.:.:.:.:.
|
|
81
81
|
1 16376412 n_alt G N . PASS CSQ=N|splice_donor_variant|HIGH|CLCNKB|1188|Transcript|NM_000085.5|protein_coding||10/19||||||||CS1211892&CS971662|1||1||1|EntrezGene||||||||||1&1|||||||||||||||||VUS|0.373989|HP:0000951||AR||||||||||||LP|filter&vkgl&clinVar&gnomad&effect&spliceAI&annotSV&impact&exit_lp|,N|splice_donor_variant|HIGH|CLCNKB|1188|Transcript|NM_001165945.2|protein_coding||3/12||||||||CS1211892&CS971662|1||1|||EntrezGene||||||||||1&1|||||||||||||||||VUS|0.373989|HP:0000951||AR||||||||||||LP|filter&vkgl&clinVar&gnomad&effect&spliceAI&annotSV&impact&exit_lp| GT:AD:DP:VI:VIC:VID:VIG:VIM:VIS 1|0:5,45:50:AR,AD:1_16376412_G_A,1_16375583_GA_G:1:1188:1:AR_C 0|0:50,0:50:.:.:.:.:.:. 0|0:50,0:50:.:.:.:.:.:. 0|0:50,0:50:.:.:.:.:.:. 0|0:50,0:50:.:.:.:.:.:. 0|0:50,0:50:.:.:.:.:.:.
|
|
82
82
|
1 16376412 . G A . PASS CSQ=A|splice_donor_variant|HIGH|CLCNKB|1188|Transcript|NM_000085.5|protein_coding||10/19|NM_000085.5:c.968+1G>A|||||||rs201204502&CS1211892&CS971662|1||1||1|EntrezGene||||||||||0&1&1||||||||17|-36|-28|-1|0.00|0.00|0.03|0.97|CLCNKB|VUS|0.9855972|HP:0000951||AR|LP||||1:16376412-16376412|6.38086e-05|0|1285112|Pathogenic/Likely_pathogenic||no_assertion_criteria_provided|LP|filter&vkgl&exit_lp|,A|splice_donor_variant|HIGH|CLCNKB|1188|Transcript|NM_001165945.2|protein_coding||3/12|NM_001165945.2:c.461+1G>A|||||||rs201204502&CS1211892&CS971662|1||1|||EntrezGene||||||||||0&1&1||||||||17|-36|-28|-1|0.00|0.00|0.03|0.97|CLCNKB|VUS|0.9855972|HP:0000951||AR|LP||||1:16376412-16376412|6.38086e-05|0|1285112|Pathogenic/Likely_pathogenic||no_assertion_criteria_provided|LP|filter&vkgl&exit_lp| GT:AD:DP:VI:VIC:VID:VIG:VIM:VIS 0|1:50:50:AR:1_16375583_GA_G,1_16376412_G_N:0:1188:1:AR_C 0|0:50,0:50:.:.:.:.:.:. 1|0:25,25:50:.:.:.:.:.:. 0|0:50,0:50:.:.:.:.:.:. 0|0:50,0:50:.:.:.:.:.:. 0|0:50,0:50:.:.:.:.:.:.
|
|
83
|
-
1 17349219 g_ref G A . PASS CSQ=A|missense_variant|MODERATE|SDHB|6390|Transcript|NM_003000.3|protein_coding|7/8||NM_003000.3:c.649C>T|NP_002991.2:p.Arg217Cys|662/1015|649/843|217/280|R/C|Cgc/Tgc|rs200245469&CM094752&CM1210440&COSV64965760|1||-1||1|EntrezGene|||||0|1||likely_pathogenic|0&0&0&1|1&1&1&1||||||||28|6|6|5|0.00|0.00|0.00|0.00|SDHB|VUS|0.89070946|HP:0000951||AD&AR|LP|||||||183735|Pathogenic/Likely_pathogenic||criteria_provided&_multiple_submitters&_no_conflicts|LP|filter&vkgl&exit_lp| GT:AD:DP:VI:VIC:VID:VIG:VIM 1|1:0,50:50:AR:.:1:6390:1 1|0:45,5:50:.:.:.:.:. 0|0:50,0:50:.:.:.:.:. 1|0:25,25:50:.:.:.:.:. 0|0:50,0:50:.:.:.:.:. 0|0:50,0:50:.:.:.:.:.
|
|
84
|
-
1 17355094 . C T . PASS CSQ=T|splice_donor_variant|HIGH|SDHB|6390|Transcript|NM_003000.3|protein_coding||4/7|NM_003000.3:c.423+1G>A|||||||rs398122805&CS056774&CS062098&COSV64965540|1||-1||1|EntrezGene||||||||pathogenic|0&0&0&1|1&1&1&1|21348866&17667967&17804857&16405730&21565294&15383933|||||||-49|12|26|1|0.00|0.00|0.14|0.97|SDHB|VUS|0.9641474|HP:
|
|
83
|
+
1 17349219 g_ref G A . PASS CSQ=A|missense_variant|MODERATE|SDHB|6390|Transcript|NM_003000.3|protein_coding|7/8||NM_003000.3:c.649C>T|NP_002991.2:p.Arg217Cys|662/1015|649/843|217/280|R/C|Cgc/Tgc|rs200245469&CM094752&CM1210440&COSV64965760|1||-1||1|EntrezGene|||||0|1||likely_pathogenic|0&0&0&1|1&1&1&1||||||||28|6|6|5|0.00|0.00|0.00|0.00|SDHB|VUS|0.89070946|HP:0000951&HP:0003124||AD&AR|LP|||||||183735|Pathogenic/Likely_pathogenic||criteria_provided&_multiple_submitters&_no_conflicts|LP|filter&vkgl&exit_lp| GT:AD:DP:VI:VIC:VID:VIG:VIM 1|1:0,50:50:AR:.:1:6390:1 1|0:45,5:50:.:.:.:.:. 0|0:50,0:50:.:.:.:.:. 1|0:25,25:50:.:.:.:.:. 0|0:50,0:50:.:.:.:.:. 0|0:50,0:50:.:.:.:.:.
|
|
84
|
+
1 17355094 . C T . PASS CSQ=T|splice_donor_variant|HIGH|SDHB|6390|Transcript|NM_003000.3|protein_coding||4/7|NM_003000.3:c.423+1G>A|||||||rs398122805&CS056774&CS062098&COSV64965540|1||-1||1|EntrezGene||||||||pathogenic|0&0&0&1|1&1&1&1|21348866&17667967&17804857&16405730&21565294&15383933|||||||-49|12|26|1|0.00|0.00|0.14|0.97|SDHB|VUS|0.9641474|HP:0003124||AD&AR|LP||||1:17355094-17355094|1.19344e-05|0|29896|Pathogenic||criteria_provided&_multiple_submitters&_no_conflicts|LP|filter&vkgl&exit_lp| GT:AD:DP:VI:VIC:VID:VIG:VIM 1|0:5,45:50:AD:.:1:6390:1 0|0:50,0:50:.:.:.:.:. 0|0:50,0:50:.:.:.:.:. 0|0:50,0:50:.:.:.:.:. 0|0:50,0:50:.:.:.:.:. 0|0:50,0:50:.:.:.:.:.
|
|
85
85
|
1 152520788 symbolic1 A <DEL> . PASS CSQ=deletion|transcript_ablation|HIGH|LCE3D|84648|Transcript|NM_032563.2|protein_coding|||||||||||1||-1||1|EntrezGene|||||||||||||||||||||||||||||||||||||||||||LP|filter&vkgl&clinVar&gnomad&effect&spliceAI&annotSV&impact&exit_lp|,deletion|transcript_ablation|HIGH|LCE3E|353145|Transcript|NM_178435.4|protein_coding|||||||||||1||-1|||EntrezGene|||||||||||||||||||||||||||||||||||||||||||LP|filter&vkgl&clinVar&gnomad&effect&spliceAI&annotSV&impact&exit_lp|,deletion|TFBS_ablation&TF_binding_site_variant|MODERATE|||MotifFeature|ENSM00701519957||||||||||||1||||||||||||||||||||||||||||||||||||||||||||||||VUS|filter&vkgl&clinVar&gnomad&effect&spliceAI&annotSV&impact&exit_vus|,deletion|TFBS_ablation&TF_binding_site_variant|MODERATE|||MotifFeature|ENSM00905971885||||||||||||1||||||||||||||||||||||||||||||||||||||||||||||||VUS|filter&vkgl&clinVar&gnomad&effect&spliceAI&annotSV&impact&exit_vus|,deletion|TFBS_ablation&TF_binding_site_variant|MODERATE|||MotifFeature|ENSM00522719201||||||||||||1||||||||||||||||||||||||||||||||||||||||||||||||VUS|filter&vkgl&clinVar&gnomad&effect&spliceAI&annotSV&impact&exit_vus|;SVLEN=-49314;SVTYPE=DEL GT:AD:DP:VI:VIC:VID:VIM 1|1:0,50:50:AR:.:1:0 0|0:50,0:50:.:.:.:. 0|0:50,0:50:.:.:.:. 0|0:50,0:50:.:.:.:. 0|0:50,0:50:.:.:.:. 1|0:25,25:50:.:.:.:.
|
|
86
86
|
4 106320294 . G A . PASS CSQ=A|missense_variant|MODERATE|PPA2|27068|Transcript|NM_006903.4|protein_coding|7/11||NM_006903.4:c.596C>T|NP_008834.3:p.Pro199Leu|616/1586|596/918|199/305|P/L|cCg/cTg|rs138215926&CM1610192&COSV58994362|1||-1|||EntrezGene|||||0|0.985||pathogenic|0&0&1|1&1&1|27523597|||||||-34|27|-2|-43|0.00|0.00|0.00|0.00|PPA2|VUS|0.8097311|||AR|LP||||4:106320294-106320294|0.000211803|0|372226|Likely_pathogenic||criteria_provided&_single_submitter|LP|filter&vkgl&exit_lp|,A|missense_variant|MODERATE|PPA2|27068|Transcript|NM_176866.2|protein_coding|4/8||NM_176866.2:c.377C>T|NP_789842.2:p.Pro126Leu|397/1367|377/699|126/232|P/L|cCg/cTg|rs138215926&CM1610192&COSV58994362|1||-1|||EntrezGene|||||0|0.992||pathogenic|0&0&1|1&1&1|27523597|||||||-34|27|-2|-43|0.00|0.00|0.00|0.00|PPA2|VUS|0.7550335|||AR|LP||||4:106320294-106320294|0.000211803|0|372226|Likely_pathogenic||criteria_provided&_single_submitter|LP|filter&vkgl&exit_lp|,A|missense_variant|MODERATE|PPA2|27068|Transcript|NM_176867.3|protein_coding|2/6||NM_176867.3:c.185C>T|NP_789843.2:p.Pro62Leu|205/1175|185/507|62/168|P/L|cCg/cTg|rs138215926&CM1610192&COSV58994362|1||-1|||EntrezGene|||||0|0.995||pathogenic|0&0&1|1&1&1|27523597|||||||-34|27|-2|-43|0.00|0.00|0.00|0.00|PPA2|VUS|0.8145551|||AR|LP||||4:106320294-106320294|0.000211803|0|372226|Likely_pathogenic||criteria_provided&_single_submitter|LP|filter&vkgl&exit_lp|,A|missense_variant|MODERATE|PPA2|27068|Transcript|NM_176869.3|protein_coding|8/12||NM_176869.3:c.683C>T|NP_789845.1:p.Pro228Leu|695/1665|683/1005|228/334|P/L|cCg/cTg|rs138215926&CM1610192&COSV58994362|1||-1||1|EntrezGene|||||0|0.993||pathogenic|0&0&1|1&1&1|27523597|||||||-34|27|-2|-43|0.00|0.00|0.00|0.00|PPA2|VUS|0.76490384|||AR|LP||||4:106320294-106320294|0.000211803|0|372226|Likely_pathogenic||criteria_provided&_single_submitter|LP|filter&vkgl&exit_lp|,A|regulatory_region_variant|MODIFIER|||RegulatoryFeature|ENSR00001994744|promoter_flanking_region||||||||||rs138215926&CM1610192&COSV58994362|1|||||||||||||pathogenic|0&0&1|1&1&1|27523597|||||||||||||||||||||||||4:106320294-106320294|0.000211803|0|372226|Likely_pathogenic||criteria_provided&_single_submitter|LP|filter&vkgl&clinVar&exit_lp| GT:AD:DP:VI:VIC:VID:VIG:VIM 1|1:0,50:50:AR:.:1:27068:1 1|0:25,25:50:.:.:.:.:. 0|0:50,0:50:.:.:.:.:. 0|0:50,0:50:.:.:.:.:. 0|0:50,0:50:.:.:.:.:. 1|1:0,50:50:.:.:.:.:.
|
|
87
87
|
9 107546633 . AAAGAT A . PASS CSQ=-|frameshift_variant|HIGH|ABCA1|19|Transcript|NM_005502.4|protein_coding|50/50||NM_005502.4:c.6744_6748del|NP_005493.2:p.Phe2250ThrfsTer3|7057-7061/10408|6744-6748/6786|2248-2250/2261|TSF/TX|acATCTTtt/actt||1||-1||1|EntrezGene|||||||||||||||||||||||||||VUS|0.9823047|HP:0000951||AD&AR||||||||||||LP|filter&vkgl&clinVar&gnomad&effect&spliceAI&annotSV&impact&exit_lp| GT:AD:DP:VI:VIC:VID:VIG:VIM 1|1:0,10:10:AR:.:0:19:1 0|1:1,49:50:.:.:.:.:. 1|0:0,50:50:.:.:.:.:. 0|0:50,0:50:.:.:.:.:. 0|0:50,0:50:.:.:.:.:. 0|0:50,0:50:.:.:.:.:.
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
##FORMAT=<ID=GT,Number=1,Type=String,Description="Genotype">
|
|
10
10
|
##HPO=List of HPO terms for the gene
|
|
11
11
|
##INFO=<ID=BND_DEPTH,Number=1,Type=Integer,Description="Read depth at local translocation breakend">
|
|
12
|
-
##INFO=<ID=CSQ,Number=.,Type=String,Description="Consequence annotations from Ensembl VEP. Format: Allele|Consequence|IMPACT|SYMBOL|Gene|Feature_type|Feature|BIOTYPE|EXON|INTRON|HGVSc|HGVSp|cDNA_position|CDS_position|Protein_position|Amino_acids|Codons|Existing_variation|ALLELE_NUM|DISTANCE|STRAND|FLAGS|PICK|SYMBOL_SOURCE|HGNC_ID|REFSEQ_MATCH|REFSEQ_OFFSET|SOURCE|SIFT|PolyPhen|HGVS_OFFSET|CLIN_SIG|SOMATIC|PHENO|PUBMED|CHECK_REF|MOTIF_NAME|MOTIF_POS|HIGH_INF_POS|MOTIF_SCORE_CHANGE|TRANSCRIPTION_FACTORS|SpliceAI_pred_DP_AG|SpliceAI_pred_DP_AL|SpliceAI_pred_DP_DG|SpliceAI_pred_DP_DL|SpliceAI_pred_DS_AG|SpliceAI_pred_DS_AL|SpliceAI_pred_DS_DG|SpliceAI_pred_DS_DL|SpliceAI_pred_SYMBOL|CAPICE_CL|CAPICE_SC|HPO|IncompletePenetrance|InheritanceModesGene|VKGL_CL|ASV_ACMG_class|ASV_AnnotSV_ranking_criteria|ASV_AnnotSV_ranking_score|gnomAD|gnomAD_AF|gnomAD_HN|VIPC|
|
|
12
|
+
##INFO=<ID=CSQ,Number=.,Type=String,Description="Consequence annotations from Ensembl VEP. Format: Allele|Consequence|IMPACT|SYMBOL|Gene|Feature_type|Feature|BIOTYPE|EXON|INTRON|HGVSc|HGVSp|cDNA_position|CDS_position|Protein_position|Amino_acids|Codons|Existing_variation|ALLELE_NUM|DISTANCE|STRAND|FLAGS|PICK|SYMBOL_SOURCE|HGNC_ID|REFSEQ_MATCH|REFSEQ_OFFSET|SOURCE|SIFT|PolyPhen|HGVS_OFFSET|CLIN_SIG|SOMATIC|PHENO|PUBMED|CHECK_REF|MOTIF_NAME|MOTIF_POS|HIGH_INF_POS|MOTIF_SCORE_CHANGE|TRANSCRIPTION_FACTORS|SpliceAI_pred_DP_AG|SpliceAI_pred_DP_AL|SpliceAI_pred_DP_DG|SpliceAI_pred_DP_DL|SpliceAI_pred_DS_AG|SpliceAI_pred_DS_AL|SpliceAI_pred_DS_DG|SpliceAI_pred_DS_DL|SpliceAI_pred_SYMBOL|CAPICE_CL|CAPICE_SC|HPO|IncompletePenetrance|InheritanceModesGene|VKGL_CL|ASV_ACMG_class|ASV_AnnotSV_ranking_criteria|ASV_AnnotSV_ranking_score|gnomAD|gnomAD_AF|gnomAD_HN|VIPC|VIPP_renamed|VIPL_renamed">
|
|
13
13
|
##INFO=<ID=MATEID,Number=.,Type=String,Description="ID of mate breakend">
|
|
14
14
|
##INFO=<ID=MATE_BND_DEPTH,Number=1,Type=Integer,Description="Read depth at remote translocation mate breakend">
|
|
15
15
|
##INFO=<ID=SVLEN,Number=.,Type=Integer,Description="Difference in length between REF and ALT alleles">
|
|
@@ -120,7 +120,7 @@ export class MockApiClient implements Api {
|
|
|
120
120
|
args: "-i testdata_b37_vip.vcf -t /Users/user/vip-report-template/dist/vip-report-template.html -f",
|
|
121
121
|
},
|
|
122
122
|
htsFile: {
|
|
123
|
-
uri: "testdata_b37_vip.vcf",
|
|
123
|
+
uri: "/path/to/testdata_b37_vip.vcf",
|
|
124
124
|
htsFormat: "VCF",
|
|
125
125
|
genomeAssembly: "GRCh37",
|
|
126
126
|
},
|
package/src/store/index.tsx
CHANGED
|
@@ -14,11 +14,18 @@ type AppStateVariants = {
|
|
|
14
14
|
sort?: SortOrder | null; // null: do not sort. undefined: sort undefined
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
+
type AppStateSamples = {
|
|
18
|
+
page?: number;
|
|
19
|
+
searchQuery?: string;
|
|
20
|
+
probandFilterValue?: boolean;
|
|
21
|
+
};
|
|
22
|
+
|
|
17
23
|
export type AppState = {
|
|
18
24
|
variants?: AppStateVariants;
|
|
19
|
-
|
|
25
|
+
sampleVariants?: {
|
|
20
26
|
[key: number]: { variants: AppStateVariants };
|
|
21
27
|
};
|
|
28
|
+
samples?: AppStateSamples;
|
|
22
29
|
};
|
|
23
30
|
|
|
24
31
|
export type AppActions = {
|
|
@@ -26,22 +33,23 @@ export type AppActions = {
|
|
|
26
33
|
setVariantsPage(page: number): void;
|
|
27
34
|
setVariantsPageSize(pageSize: number): void;
|
|
28
35
|
setVariantsSearchQuery(searchQuery: string): void;
|
|
29
|
-
clearVariantsSearchQuery(): void;
|
|
30
36
|
setVariantsFilterQuery(query: QueryClause): void;
|
|
31
37
|
clearVariantsFilterQuery(selector: Selector): void;
|
|
32
38
|
setVariantsSort(sort: SortOrder | null): void;
|
|
33
39
|
setSampleVariantsPage(sample: Item<Sample>, page: number): void;
|
|
34
40
|
setSampleVariantsPageSize(sample: Item<Sample>, pageSize: number): void;
|
|
35
41
|
setSampleVariantsSearchQuery(sample: Item<Sample>, searchQuery: string): void;
|
|
36
|
-
clearSampleVariantsSearchQuery(sample: Item<Sample>): void;
|
|
37
42
|
setSampleVariantsFilterQuery(sample: Item<Sample>, query: QueryClause): void;
|
|
38
43
|
clearSampleVariantsFilterQuery(sample: Item<Sample>, selector: Selector): void;
|
|
39
44
|
setSampleVariantsSort(sample: Item<Sample>, sort: SortOrder | null): void;
|
|
45
|
+
setSamplePage(page: number): void;
|
|
46
|
+
setSampleSearchQuery(searchQuery: string): void;
|
|
47
|
+
setSampleProbandFilterValue(probandFilterValue: boolean): void;
|
|
40
48
|
};
|
|
41
49
|
|
|
42
50
|
export type AppStore = [state: AppState, actions: AppActions];
|
|
43
51
|
|
|
44
|
-
const defaultState: AppState = { variants: {},
|
|
52
|
+
const defaultState: AppState = { variants: {}, sampleVariants: {} };
|
|
45
53
|
|
|
46
54
|
const StoreContext = createContext<AppStore>() as Context<AppStore>;
|
|
47
55
|
|
|
@@ -49,12 +57,12 @@ export const Provider: ParentComponent = (props) => {
|
|
|
49
57
|
const [state, setState] = createStore(defaultState);
|
|
50
58
|
|
|
51
59
|
function getVariants(sample: Item<Sample>) {
|
|
52
|
-
return state.
|
|
60
|
+
return state.sampleVariants ? state.sampleVariants[sample.id]?.variants || {} : {};
|
|
53
61
|
}
|
|
54
62
|
|
|
55
63
|
const actions: AppActions = {
|
|
56
64
|
reset() {
|
|
57
|
-
setState({ variants: undefined, samples: undefined });
|
|
65
|
+
setState({ variants: undefined, sampleVariants: undefined, samples: undefined });
|
|
58
66
|
},
|
|
59
67
|
setVariantsPage(page: number) {
|
|
60
68
|
setState({ variants: { ...(state.variants || {}), page } });
|
|
@@ -65,9 +73,6 @@ export const Provider: ParentComponent = (props) => {
|
|
|
65
73
|
setVariantsSearchQuery(searchQuery: string) {
|
|
66
74
|
setState({ variants: { ...(state.variants || {}), searchQuery } });
|
|
67
75
|
},
|
|
68
|
-
clearVariantsSearchQuery() {
|
|
69
|
-
setState({ variants: { ...(state.variants || {}), searchQuery: undefined } });
|
|
70
|
-
},
|
|
71
76
|
setVariantsFilterQuery(query: QueryClause) {
|
|
72
77
|
setState({
|
|
73
78
|
variants: {
|
|
@@ -93,37 +98,34 @@ export const Provider: ParentComponent = (props) => {
|
|
|
93
98
|
});
|
|
94
99
|
},
|
|
95
100
|
setSampleVariantsPage(sample: Item<Sample>, page: number) {
|
|
96
|
-
setState({
|
|
101
|
+
setState({
|
|
102
|
+
sampleVariants: {
|
|
103
|
+
...(state.sampleVariants || {}),
|
|
104
|
+
[sample.id]: { variants: { ...getVariants(sample), page } },
|
|
105
|
+
},
|
|
106
|
+
});
|
|
97
107
|
},
|
|
98
108
|
setSampleVariantsPageSize(sample: Item<Sample>, pageSize: number) {
|
|
99
109
|
setState({
|
|
100
|
-
|
|
101
|
-
...(state.
|
|
110
|
+
sampleVariants: {
|
|
111
|
+
...(state.sampleVariants || {}),
|
|
102
112
|
[sample.id]: { variants: { ...getVariants(sample), pageSize, page: undefined } },
|
|
103
113
|
},
|
|
104
114
|
});
|
|
105
115
|
},
|
|
106
116
|
setSampleVariantsSearchQuery(sample: Item<Sample>, searchQuery: string) {
|
|
107
117
|
setState({
|
|
108
|
-
|
|
109
|
-
...(state.
|
|
118
|
+
sampleVariants: {
|
|
119
|
+
...(state.sampleVariants || {}),
|
|
110
120
|
[sample.id]: { variants: { ...getVariants(sample), searchQuery, page: undefined } },
|
|
111
121
|
},
|
|
112
122
|
});
|
|
113
123
|
},
|
|
114
|
-
clearSampleVariantsSearchQuery(sample: Item<Sample>) {
|
|
115
|
-
setState({
|
|
116
|
-
samples: {
|
|
117
|
-
...(state.samples || {}),
|
|
118
|
-
[sample.id]: { variants: { ...getVariants(sample), searchQuery: undefined, page: undefined } },
|
|
119
|
-
},
|
|
120
|
-
});
|
|
121
|
-
},
|
|
122
124
|
setSampleVariantsFilterQuery(sample: Item<Sample>, query: QueryClause) {
|
|
123
125
|
const variants = getVariants(sample);
|
|
124
126
|
setState({
|
|
125
|
-
|
|
126
|
-
...(state.
|
|
127
|
+
sampleVariants: {
|
|
128
|
+
...(state.sampleVariants || {}),
|
|
127
129
|
[sample.id]: {
|
|
128
130
|
variants: {
|
|
129
131
|
...variants,
|
|
@@ -137,8 +139,8 @@ export const Provider: ParentComponent = (props) => {
|
|
|
137
139
|
clearSampleVariantsFilterQuery(sample: Item<Sample>, selector: Selector) {
|
|
138
140
|
const variants = getVariants(sample);
|
|
139
141
|
setState({
|
|
140
|
-
|
|
141
|
-
...(state.
|
|
142
|
+
sampleVariants: {
|
|
143
|
+
...(state.sampleVariants || {}),
|
|
142
144
|
[sample.id]: {
|
|
143
145
|
variants: {
|
|
144
146
|
...getVariants(sample),
|
|
@@ -151,9 +153,21 @@ export const Provider: ParentComponent = (props) => {
|
|
|
151
153
|
},
|
|
152
154
|
setSampleVariantsSort(sample: Item<Sample>, sort: SortOrder | null) {
|
|
153
155
|
setState({
|
|
154
|
-
|
|
156
|
+
sampleVariants: {
|
|
157
|
+
...(state.sampleVariants || {}),
|
|
158
|
+
[sample.id]: { variants: { ...getVariants(sample), sort } },
|
|
159
|
+
},
|
|
155
160
|
});
|
|
156
161
|
},
|
|
162
|
+
setSamplePage(page: number) {
|
|
163
|
+
setState({ samples: { ...(state.samples || {}), page } });
|
|
164
|
+
},
|
|
165
|
+
setSampleSearchQuery(searchQuery: string) {
|
|
166
|
+
setState({ samples: { ...(state.samples || {}), searchQuery } });
|
|
167
|
+
},
|
|
168
|
+
setSampleProbandFilterValue(probandFilterValue: boolean) {
|
|
169
|
+
setState({ samples: { ...(state.samples || {}), probandFilterValue } });
|
|
170
|
+
},
|
|
157
171
|
};
|
|
158
172
|
const store: AppStore = [state, actions];
|
|
159
173
|
|
package/src/utils/query.ts
CHANGED
|
@@ -29,7 +29,6 @@ export function createSampleQuery(
|
|
|
29
29
|
const searchFilterQuery = createQuery(search, filters, metadata);
|
|
30
30
|
const query: Query =
|
|
31
31
|
searchFilterQuery !== null ? { operator: "and", args: [sampleQuery, searchFilterQuery] } : sampleQuery;
|
|
32
|
-
console.log(query);
|
|
33
32
|
return query;
|
|
34
33
|
}
|
|
35
34
|
|
|
@@ -63,6 +63,9 @@ export const SampleVariantConsequence: Component<{
|
|
|
63
63
|
consequenceId: number;
|
|
64
64
|
decisionTree: DecisionTree | null;
|
|
65
65
|
}> = (props) => {
|
|
66
|
+
const hasDecisionTreePathMeta = () =>
|
|
67
|
+
(props.recordsMeta.info.CSQ?.nested?.items || []).findIndex((csq) => csq.id === "VIPP") !== -1;
|
|
68
|
+
|
|
66
69
|
return (
|
|
67
70
|
<>
|
|
68
71
|
<div class="columns">
|
|
@@ -74,7 +77,7 @@ export const SampleVariantConsequence: Component<{
|
|
|
74
77
|
record={props.variant}
|
|
75
78
|
/>
|
|
76
79
|
</div>
|
|
77
|
-
{props.decisionTree !== null && (
|
|
80
|
+
{props.decisionTree !== null && hasDecisionTreePathMeta() && (
|
|
78
81
|
<div class="column">
|
|
79
82
|
<h1 class="title is-5">Classification tree path</h1>
|
|
80
83
|
<DecisionTreePath
|
|
@@ -66,7 +66,7 @@ export const SampleVariants: Component<{
|
|
|
66
66
|
const [state, actions] = useStore();
|
|
67
67
|
|
|
68
68
|
function getStateVariants() {
|
|
69
|
-
return state.
|
|
69
|
+
return state.sampleVariants ? state.sampleVariants[props.sample.id]?.variants : undefined;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
// state initialization - start
|
package/src/views/Samples.tsx
CHANGED
|
@@ -1,57 +1,116 @@
|
|
|
1
|
-
import { Component, createResource,
|
|
1
|
+
import { Component, createResource, Show } from "solid-js";
|
|
2
2
|
import { SampleTable } from "../components/SampleTable";
|
|
3
3
|
import { Pager } from "../components/record/Pager";
|
|
4
4
|
import { SearchBox } from "../components/SearchBox";
|
|
5
5
|
import { Checkbox, CheckboxEvent } from "../components/Checkbox";
|
|
6
6
|
import { Breadcrumb } from "../components/Breadcrumb";
|
|
7
|
-
import { EMPTY_PARAMS, EMPTY_PHENOTYPES,
|
|
7
|
+
import { EMPTY_PARAMS, EMPTY_PHENOTYPES, fetchPhenotypes, fetchSamples } from "../utils/ApiUtils";
|
|
8
|
+
import { useStore } from "../store";
|
|
9
|
+
import { Params, Query, QueryClause } from "@molgenis/vip-report-api/src/Api";
|
|
10
|
+
import { Loader } from "../components/Loader";
|
|
8
11
|
|
|
9
12
|
export const Samples: Component = () => {
|
|
10
|
-
const [
|
|
11
|
-
const [samples] = createResource(params, fetchSamples, { initialValue: EMPTY_SAMPLES_PAGE });
|
|
13
|
+
const [state, actions] = useStore();
|
|
12
14
|
const [phenotypes] = createResource(EMPTY_PARAMS, fetchPhenotypes, { initialValue: EMPTY_PHENOTYPES });
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
function getStateSamples() {
|
|
17
|
+
return state.samples ? state.samples : undefined;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (getStateSamples()?.page === undefined) {
|
|
21
|
+
actions.setSamplePage(0);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const onPageChange = (page: number) => actions.setSamplePage(page);
|
|
25
|
+
const onSearchChange = (search: string) => {
|
|
26
|
+
actions.setSampleSearchQuery(search);
|
|
27
|
+
actions.setSamplePage(0);
|
|
28
|
+
};
|
|
20
29
|
const onProbandFilterChange = (event: CheckboxEvent) => {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
30
|
+
actions.setSampleProbandFilterValue(event.checked);
|
|
31
|
+
actions.setSamplePage(0);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function createQuery(search: string | undefined, probandFilterValue: boolean | undefined): Query | null {
|
|
35
|
+
const searchQuery: QueryClause | undefined =
|
|
36
|
+
search !== undefined && search !== ""
|
|
37
|
+
? { selector: ["person", "individualId"], operator: "~=", args: search }
|
|
38
|
+
: undefined;
|
|
39
|
+
const probandQuery: QueryClause | undefined =
|
|
40
|
+
probandFilterValue !== undefined && probandFilterValue
|
|
41
|
+
? { selector: ["proband"], operator: "==", args: probandFilterValue }
|
|
42
|
+
: undefined;
|
|
43
|
+
if (searchQuery !== undefined || probandQuery != undefined) {
|
|
44
|
+
const args: QueryClause[] = [];
|
|
45
|
+
if (searchQuery !== undefined) {
|
|
46
|
+
args.push(searchQuery);
|
|
47
|
+
}
|
|
48
|
+
if (probandQuery !== undefined) {
|
|
49
|
+
args.push(probandQuery);
|
|
50
|
+
}
|
|
51
|
+
if (args.length > 0) {
|
|
52
|
+
const query: Query = {
|
|
53
|
+
operator: "and",
|
|
54
|
+
args: args,
|
|
55
|
+
};
|
|
56
|
+
return query;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const params = (): Params => {
|
|
63
|
+
return {
|
|
64
|
+
query: createQuery(searchQuery(), probandFilterValue()) || undefined,
|
|
65
|
+
page: page() || undefined,
|
|
66
|
+
};
|
|
25
67
|
};
|
|
26
68
|
|
|
69
|
+
const page = () => getStateSamples()?.page;
|
|
70
|
+
const searchQuery = () => getStateSamples()?.searchQuery;
|
|
71
|
+
const probandFilterValue = () => getStateSamples()?.probandFilterValue;
|
|
72
|
+
|
|
73
|
+
const [samples] = createResource(params, fetchSamples);
|
|
74
|
+
|
|
27
75
|
return (
|
|
28
76
|
<>
|
|
29
77
|
<Breadcrumb items={[{ text: "Samples" }]} />
|
|
30
|
-
<
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
78
|
+
<Show when={samples()} fallback={<Loader />}>
|
|
79
|
+
{(samples) => (
|
|
80
|
+
<>
|
|
81
|
+
<div class="columns">
|
|
82
|
+
<div class="column is-4 is-offset-3">{<Pager page={samples.page} onPageChange={onPageChange} />}</div>
|
|
83
|
+
<div class="column is-2 is-offset-1">
|
|
84
|
+
{<span class="is-pulled-right">{samples.page.totalElements} records</span>}
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<div class="columns">
|
|
89
|
+
<div class="column is-1-fullhd is-2">
|
|
90
|
+
<SearchBox onInput={onSearchChange} value={state.samples?.searchQuery} />
|
|
91
|
+
<p class="has-text-weight-semibold">Proband</p>
|
|
92
|
+
<div class="field">
|
|
93
|
+
<div class="control">
|
|
94
|
+
<Checkbox
|
|
95
|
+
value={"proband"}
|
|
96
|
+
label=""
|
|
97
|
+
onChange={onProbandFilterChange}
|
|
98
|
+
checked={state.samples?.probandFilterValue}
|
|
99
|
+
/>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
<div class="column">
|
|
104
|
+
<span class="is-italic">
|
|
105
|
+
Click on an individual id for detailed information for this sample. In this screen a link to the
|
|
106
|
+
variants for this sample is available.
|
|
107
|
+
</span>
|
|
108
|
+
{!phenotypes.loading && <SampleTable samples={samples.items} phenotypes={phenotypes().items} />}
|
|
109
|
+
</div>
|
|
46
110
|
</div>
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
{!samples.loading && !phenotypes.loading && (
|
|
51
|
-
<SampleTable samples={samples().items} phenotypes={phenotypes().items} />
|
|
52
|
-
)}
|
|
53
|
-
</div>
|
|
54
|
-
</div>
|
|
111
|
+
</>
|
|
112
|
+
)}
|
|
113
|
+
</Show>
|
|
55
114
|
</>
|
|
56
115
|
);
|
|
57
116
|
};
|
|
@@ -28,6 +28,8 @@ export const VariantConsequence: Component = () => {
|
|
|
28
28
|
|
|
29
29
|
const csqFields = (): FieldMetadata[] => recordsMetadata().info.CSQ?.nested?.items || [];
|
|
30
30
|
const csqValues = (): ValueArray => getSpecificConsequence(variant().data.n.CSQ as ValueArray, consequenceId);
|
|
31
|
+
|
|
32
|
+
const hasDecisionTreePathMeta = () => csqFields().findIndex((csq) => csq.id === "VIPP") !== -1;
|
|
31
33
|
return (
|
|
32
34
|
<Show when={!variant.loading} fallback={<Loader />}>
|
|
33
35
|
<Breadcrumb
|
|
@@ -42,17 +44,19 @@ export const VariantConsequence: Component = () => {
|
|
|
42
44
|
<h1 class="title is-5">Consequence</h1>
|
|
43
45
|
<ConsequenceTable csqMetadata={csqFields()} csqValues={csqValues()} record={variant()} />
|
|
44
46
|
</div>
|
|
45
|
-
|
|
46
|
-
{(decisionTree)
|
|
47
|
-
|
|
48
|
-
<
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
47
|
+
{hasDecisionTreePathMeta() && (
|
|
48
|
+
<Show when={!recordsMetadata.loading && !decisionTree.loading && (decisionTree() as DecisionTree)}>
|
|
49
|
+
{(decisionTree) => (
|
|
50
|
+
<div class="column">
|
|
51
|
+
<h1 class="title is-5">Classification tree path</h1>
|
|
52
|
+
<DecisionTreePath
|
|
53
|
+
decisionTree={decisionTree}
|
|
54
|
+
path={getDecisionTreePath(recordsMetadata(), variant(), consequenceId)}
|
|
55
|
+
/>
|
|
56
|
+
</div>
|
|
57
|
+
)}
|
|
58
|
+
</Show>
|
|
59
|
+
)}
|
|
56
60
|
</div>
|
|
57
61
|
<div class="columns">
|
|
58
62
|
<div class="column is-6">
|