@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.
Files changed (285) hide show
  1. package/.nvmrc +1 -1
  2. package/.travis.yml +8 -8
  3. package/README.md +411 -1
  4. package/eslint.config.mjs +11 -0
  5. package/package.json +40 -35
  6. package/scripts/deploy_npm_registry.sh +5 -0
  7. package/src/App.tsx +30 -24
  8. package/src/assets/sass/main.scss +12 -4
  9. package/src/components/Allele.tsx +95 -0
  10. package/src/components/Anchor.tsx +1 -1
  11. package/src/components/Breadcrumb.tsx +6 -4
  12. package/src/components/DatasetDropdown.tsx +12 -25
  13. package/src/components/ErrorNotification.tsx +9 -0
  14. package/src/components/GenomeBrowser.tsx +40 -23
  15. package/src/components/HpoTerm.tsx +1 -1
  16. package/src/components/{record/Pager.tsx → Pager.tsx} +21 -14
  17. package/src/components/RecordsPerPage.tsx +9 -7
  18. package/src/components/RecordsTable.tsx +130 -0
  19. package/src/components/SampleTable.tsx +70 -98
  20. package/src/components/SearchBox.tsx +8 -2
  21. package/src/components/Sort.tsx +28 -25
  22. package/src/components/Table.tsx +16 -0
  23. package/src/components/Tooltip.tsx +20 -0
  24. package/src/components/VariantBreadcrumb.tsx +54 -0
  25. package/src/components/VariantConsequenceContainer.tsx +100 -0
  26. package/src/components/VariantConsequenceTable.tsx +58 -0
  27. package/src/components/VariantContainer.tsx +71 -0
  28. package/src/components/VariantFilters.tsx +27 -0
  29. package/src/components/VariantGenotypeTable.tsx +44 -0
  30. package/src/components/VariantInfoTable.tsx +24 -33
  31. package/src/components/VariantResults.tsx +103 -0
  32. package/src/components/VariantTable.tsx +62 -66
  33. package/src/components/VariantTypeSelect.tsx +34 -0
  34. package/src/components/VariantsContainer.tsx +150 -0
  35. package/src/components/VariantsContainerHeader.tsx +70 -0
  36. package/src/components/field/Field.tsx +80 -0
  37. package/src/components/field/FieldAlt.tsx +19 -0
  38. package/src/components/field/FieldChrom.tsx +6 -0
  39. package/src/components/{record/Id.tsx → field/FieldFilter.tsx} +2 -1
  40. package/src/components/field/FieldFormat.tsx +10 -0
  41. package/src/components/{record/Filter.tsx → field/FieldId.tsx} +1 -1
  42. package/src/components/field/FieldPos.tsx +6 -0
  43. package/src/components/field/FieldQual.tsx +6 -0
  44. package/src/components/field/FieldRef.tsx +8 -0
  45. package/src/components/field/composed/FieldClinVar.tsx +72 -0
  46. package/src/components/field/composed/FieldComposed.tsx +68 -0
  47. package/src/components/field/composed/FieldGene.tsx +39 -0
  48. package/src/components/field/composed/FieldGenotype.tsx +35 -0
  49. package/src/components/{record/format/GenotypeField.tsx → field/composed/FieldGenotypeSnvSv.tsx} +20 -16
  50. package/src/components/field/composed/FieldGenotypeStr.tsx +31 -0
  51. package/src/components/field/composed/FieldGnomAd.tsx +58 -0
  52. package/src/components/field/composed/FieldHpo.tsx +50 -0
  53. package/src/components/field/composed/FieldInheritanceModes.tsx +32 -0
  54. package/src/components/field/composed/FieldLocus.tsx +18 -0
  55. package/src/components/field/composed/FieldVipC.tsx +25 -0
  56. package/src/components/field/composed/FieldVipCS.tsx +15 -0
  57. package/src/components/field/composed/FieldVkgl.tsx +37 -0
  58. package/src/components/field/genotype/FieldGenotype.tsx +19 -0
  59. package/src/components/field/genotype/FieldGenotypeType.tsx +9 -0
  60. package/src/components/field/info/FieldConsequence.tsx +15 -0
  61. package/src/components/{record/info/Hgvs.tsx → field/info/FieldHgvs.tsx} +4 -6
  62. package/src/components/field/info/FieldInfo.tsx +27 -0
  63. package/src/components/{record/info/PubMed.tsx → field/info/FieldPubMed.tsx} +4 -7
  64. package/src/components/field/typed/FieldCategorical.tsx +17 -0
  65. package/src/components/{record/field/FieldValueCharacter.tsx → field/typed/FieldCharacter.tsx} +3 -2
  66. package/src/components/{record/field/FieldValueFlag.tsx → field/typed/FieldFlag.tsx} +3 -2
  67. package/src/components/{record/field/FieldValueFloat.tsx → field/typed/FieldFloat.tsx} +3 -2
  68. package/src/components/{record/field/FieldValueInteger.tsx → field/typed/FieldInteger.tsx} +3 -2
  69. package/src/components/{record/field/FieldValueString.tsx → field/typed/FieldString.tsx} +3 -2
  70. package/src/components/field/typed/FieldTyped.tsx +20 -0
  71. package/src/components/field/typed/FieldTypedItem.tsx +49 -0
  72. package/src/components/field/typed/FieldTypedMultiple.tsx +21 -0
  73. package/src/components/filter/Filter.tsx +56 -48
  74. package/src/components/filter/FilterWrapper.scss +23 -0
  75. package/src/components/filter/FilterWrapper.tsx +63 -0
  76. package/src/components/filter/composed/FilterAllelicImbalance.tsx +26 -0
  77. package/src/components/filter/composed/FilterComposed.tsx +92 -0
  78. package/src/components/filter/composed/FilterDeNovo.tsx +35 -0
  79. package/src/components/filter/composed/FilterHpo.tsx +16 -0
  80. package/src/components/filter/composed/FilterInheritance.tsx +42 -0
  81. package/src/components/filter/composed/FilterLocus.tsx +75 -0
  82. package/src/components/filter/composed/FilterVipC.tsx +16 -0
  83. package/src/components/filter/composed/FilterVipCS.tsx +16 -0
  84. package/src/components/filter/fixed/FilterAlt.tsx +20 -0
  85. package/src/components/filter/fixed/FilterChrom.tsx +22 -0
  86. package/src/components/filter/fixed/FilterFilter.tsx +20 -0
  87. package/src/components/filter/fixed/FilterFixed.tsx +96 -0
  88. package/src/components/filter/fixed/FilterId.tsx +20 -0
  89. package/src/components/filter/fixed/FilterPos.tsx +22 -0
  90. package/src/components/filter/fixed/FilterQual.tsx +21 -0
  91. package/src/components/filter/fixed/FilterRef.tsx +22 -0
  92. package/src/components/filter/typed/FilterCategorical.tsx +119 -0
  93. package/src/components/filter/typed/FilterFlag.tsx +23 -0
  94. package/src/components/filter/typed/FilterInterval.tsx +72 -0
  95. package/src/components/filter/typed/FilterString.tsx +43 -0
  96. package/src/components/filter/typed/FilterTyped.tsx +56 -0
  97. package/src/components/form/ButtonApply.tsx +11 -0
  98. package/src/components/form/ButtonDownload.tsx +11 -0
  99. package/src/components/form/ButtonReset.tsx +9 -0
  100. package/src/components/{Checkbox.tsx → form/Checkbox.tsx} +4 -9
  101. package/src/components/form/Input.tsx +19 -0
  102. package/src/components/form/Select.scss +7 -0
  103. package/src/components/form/Select.tsx +34 -0
  104. package/src/components/tree/DecisionTreeBoolMultiQuery.tsx +1 -1
  105. package/src/components/tree/DecisionTreeBoolQuery.tsx +1 -1
  106. package/src/components/tree/DecisionTreeNode.tsx +41 -39
  107. package/src/components/tree/DecisionTreeNodeBool.tsx +1 -1
  108. package/src/components/tree/DecisionTreeNodeBoolMulti.tsx +1 -1
  109. package/src/components/tree/DecisionTreeNodeCategorical.tsx +1 -1
  110. package/src/components/tree/DecisionTreeNodeExists.tsx +1 -1
  111. package/src/components/tree/DecisionTreeNodeLeaf.tsx +1 -1
  112. package/src/components/tree/DecisionTreeOutcomeNode.tsx +1 -1
  113. package/src/components/tree/DecisionTreePath.tsx +1 -1
  114. package/src/igv.d.ts +2 -1
  115. package/src/index.tsx +48 -19
  116. package/src/mocks/GRCh37/decisionTree.json +23 -22
  117. package/src/mocks/GRCh37/field_metadata.json +435 -95
  118. package/src/mocks/GRCh37/sampleTree.json +21 -1
  119. package/src/mocks/GRCh37/static.ts +62 -134
  120. package/src/mocks/GRCh37/vcf/family.vcf.blob +9 -3
  121. package/src/mocks/GRCh38/decisionTree.json +52 -33
  122. package/src/mocks/GRCh38/decisionTreeStr.json +572 -0
  123. package/src/mocks/GRCh38/fasta/chr1_149380406-149403321.fasta.gz.blob +0 -0
  124. package/src/mocks/GRCh38/field_metadata.json +435 -95
  125. package/src/mocks/GRCh38/sampleTree.json +175 -0
  126. package/src/mocks/GRCh38/static.ts +101 -42
  127. package/src/mocks/GRCh38/str.cram.blob +0 -0
  128. package/src/mocks/GRCh38/str.cram.crai.blob +0 -0
  129. package/src/mocks/GRCh38/vcf/family.vcf.blob +25 -24
  130. package/src/mocks/GRCh38/vcf/no_vep.vcf.blob +29 -28
  131. package/src/mocks/GRCh38/vcf/samples_0.vcf.blob +28 -27
  132. package/src/mocks/GRCh38/vcf/samples_1.vcf.blob +29 -28
  133. package/src/mocks/GRCh38/vcf/samples_100.vcf.blob +28 -27
  134. package/src/mocks/GRCh38/vcf/str.vcf.blob +321 -0
  135. package/src/mocks/MockApiClient.ts +339 -332
  136. package/src/mocks/config_cram.json +701 -0
  137. package/src/mocks/config_vcf.json +699 -0
  138. package/src/store/app.ts +30 -0
  139. package/src/store/index.tsx +3 -168
  140. package/src/store/variants.ts +182 -0
  141. package/src/types/config.d.ts +190 -0
  142. package/src/types/configCellComposed.d.ts +86 -0
  143. package/src/types/configCells.d.ts +129 -0
  144. package/src/types/configFilter.d.ts +80 -0
  145. package/src/types/configFilterComposed.d.ts +60 -0
  146. package/src/types/configSort.d.ts +13 -0
  147. package/src/types/filter.d.ts +17 -0
  148. package/src/types/store.d.ts +34 -0
  149. package/src/utils/api.ts +281 -0
  150. package/src/utils/config/config.ts +182 -0
  151. package/src/utils/config/configCells.ts +74 -0
  152. package/src/utils/config/configCellsComposed.ts +508 -0
  153. package/src/utils/config/configCellsField.ts +61 -0
  154. package/src/utils/config/configCellsFixed.ts +126 -0
  155. package/src/utils/config/configFilters.ts +46 -0
  156. package/src/utils/config/configFiltersComposed.ts +208 -0
  157. package/src/utils/config/configFiltersField.ts +49 -0
  158. package/src/utils/config/configFiltersFixed.ts +106 -0
  159. package/src/utils/config/configSorts.ts +44 -0
  160. package/src/utils/config/configValidator.ts +380 -0
  161. package/src/utils/config/configVip.ts +25 -0
  162. package/src/utils/csq.ts +115 -0
  163. package/src/utils/decisionTree.ts +45 -0
  164. package/src/utils/download.ts +30 -0
  165. package/src/utils/error.ts +69 -0
  166. package/src/utils/query/query.ts +55 -0
  167. package/src/utils/query/queryFilter.ts +132 -0
  168. package/src/utils/query/queryFilterComposed.ts +247 -0
  169. package/src/utils/query/queryFilterField.ts +75 -0
  170. package/src/utils/query/queryFilterFixed.ts +44 -0
  171. package/src/utils/query/querySample.ts +18 -0
  172. package/src/utils/query/queryVariantType.ts +76 -0
  173. package/src/utils/query/selector.ts +41 -0
  174. package/src/utils/{sortUtils.ts → query/sort.ts} +32 -11
  175. package/src/utils/sample.ts +19 -35
  176. package/src/utils/utils.ts +66 -2
  177. package/src/utils/variantType.ts +43 -0
  178. package/src/utils/vcf.ts +352 -0
  179. package/src/views/Help.tsx +109 -114
  180. package/src/views/Home.tsx +3 -2
  181. package/src/views/Sample.tsx +12 -7
  182. package/src/views/SampleVariant.tsx +23 -112
  183. package/src/views/SampleVariantConsequence.tsx +54 -144
  184. package/src/views/SampleVariants.tsx +33 -445
  185. package/src/views/SampleVariantsRedirect.tsx +20 -0
  186. package/src/views/Samples.tsx +7 -10
  187. package/src/views/Variant.tsx +31 -61
  188. package/src/views/VariantConsequence.tsx +42 -72
  189. package/src/views/Variants.tsx +29 -138
  190. package/src/views/VariantsRedirect.tsx +25 -0
  191. package/src/views/data/data.tsx +32 -6
  192. package/tests/store/variants.test.ts +122 -0
  193. package/tests/utils/config/config.test.ts +167 -0
  194. package/tests/utils/config/configCells.test.ts +86 -0
  195. package/tests/utils/config/configCellsComposed.test.ts +1163 -0
  196. package/tests/utils/config/configCellsField.test.ts +164 -0
  197. package/tests/utils/config/configCellsFixed.test.ts +99 -0
  198. package/tests/utils/config/configFilters.test.ts +80 -0
  199. package/tests/utils/config/configFiltersComposed.test.ts +504 -0
  200. package/tests/utils/config/configFiltersField.test.ts +140 -0
  201. package/tests/utils/config/configFiltersFixed.test.ts +81 -0
  202. package/tests/utils/config/configSorts.test.ts +55 -0
  203. package/tests/utils/config/configValidator.test.ts +56 -0
  204. package/tests/utils/config/configVip.test.ts +53 -0
  205. package/tests/utils/decisionTree.test.ts +71 -0
  206. package/tests/utils/download.test.ts +20 -0
  207. package/tests/utils/query/query.test.ts +84 -0
  208. package/tests/utils/query/queryFilter.test.ts +243 -0
  209. package/tests/utils/query/queryFilterComposed.test.ts +301 -0
  210. package/tests/utils/query/queryFilterField.test.ts +75 -0
  211. package/tests/utils/query/queryFilterFixed.test.ts +86 -0
  212. package/tests/utils/query/querySample.test.ts +45 -0
  213. package/tests/utils/query/queryVariantType.test.ts +56 -0
  214. package/{src/__tests__/sortUtils.test.ts → tests/utils/query/sort.test.ts} +3 -4
  215. package/tests/utils/sample.test.ts +259 -0
  216. package/tests/utils/utils.test.ts +120 -0
  217. package/tests/utils/variantType.test.ts +48 -0
  218. package/tests/utils/vcf.test.ts +649 -0
  219. package/tsconfig.json +6 -2
  220. package/vite.config.mts +20 -3
  221. package/.eslintignore +0 -4
  222. package/.eslintrc.js +0 -23
  223. package/src/Api.ts +0 -12
  224. package/src/__tests__/decisionTreeUtils.test.ts +0 -75
  225. package/src/__tests__/field.test.ts +0 -107
  226. package/src/__tests__/query.test.ts +0 -188
  227. package/src/__tests__/sample.test.ts +0 -184
  228. package/src/__tests__/utils.test.ts +0 -24
  229. package/src/__tests__/viewUtils.test.ts +0 -125
  230. package/src/components/ConsequenceTable.tsx +0 -45
  231. package/src/components/Error.tsx +0 -9
  232. package/src/components/FieldHeader.tsx +0 -26
  233. package/src/components/InfoCollapsablePane.tsx +0 -90
  234. package/src/components/VariantInfoNestedTable.tsx +0 -127
  235. package/src/components/VariantSampleTable.tsx +0 -58
  236. package/src/components/VariantsSampleTable.tsx +0 -184
  237. package/src/components/VariantsTable.tsx +0 -125
  238. package/src/components/filter/FilterAllelicBalance.tsx +0 -81
  239. package/src/components/filter/FilterCategorical.tsx +0 -81
  240. package/src/components/filter/FilterClinVar.tsx +0 -21
  241. package/src/components/filter/FilterGene.tsx +0 -34
  242. package/src/components/filter/FilterHpo.tsx +0 -161
  243. package/src/components/filter/FilterInheritance.tsx +0 -162
  244. package/src/components/filter/FilterIntegerGq.tsx +0 -47
  245. package/src/components/filter/FilterVI.tsx +0 -68
  246. package/src/components/filter/FilterVariantType.tsx +0 -146
  247. package/src/components/filter/Filters.tsx +0 -29
  248. package/src/components/filter/InfoFilter.tsx +0 -39
  249. package/src/components/filter/InfoFilters.tsx +0 -35
  250. package/src/components/filter/SampleFilters.tsx +0 -93
  251. package/src/components/filter/SamplesFilters.tsx +0 -33
  252. package/src/components/record/Allele.tsx +0 -38
  253. package/src/components/record/AlleleBreakend.tsx +0 -5
  254. package/src/components/record/AlleleMissing.tsx +0 -5
  255. package/src/components/record/AlleleNucs.tsx +0 -49
  256. package/src/components/record/AlleleSymbolic.tsx +0 -5
  257. package/src/components/record/Alt.tsx +0 -17
  258. package/src/components/record/Chrom.tsx +0 -5
  259. package/src/components/record/Format.tsx +0 -40
  260. package/src/components/record/Info.tsx +0 -55
  261. package/src/components/record/Pos.tsx +0 -5
  262. package/src/components/record/Qual.tsx +0 -5
  263. package/src/components/record/RecordDownload.tsx +0 -66
  264. package/src/components/record/Ref.tsx +0 -6
  265. package/src/components/record/field/Field.tsx +0 -36
  266. package/src/components/record/field/FieldMultipleValue.tsx +0 -22
  267. package/src/components/record/field/FieldSingleValue.tsx +0 -35
  268. package/src/components/record/info/ClinVar.tsx +0 -81
  269. package/src/components/record/info/Consequence.tsx +0 -18
  270. package/src/components/record/info/Gene.tsx +0 -56
  271. package/src/components/record/info/GnomAD.tsx +0 -54
  272. package/src/components/record/info/Hpo.tsx +0 -52
  273. package/src/components/record/info/InheritanceModes.tsx +0 -22
  274. package/src/components/record/info/VipC.tsx +0 -23
  275. package/src/components/record/info/Vkgl.tsx +0 -42
  276. package/src/mocks/GRCh37/vcf/no_vep.vcf.blob +0 -61
  277. package/src/mocks/GRCh37/vcf/samples_0.vcf.blob +0 -93
  278. package/src/mocks/GRCh37/vcf/samples_1.vcf.blob +0 -93
  279. package/src/mocks/GRCh37/vcf/samples_100.vcf.blob +0 -93
  280. package/src/utils/ApiUtils.ts +0 -263
  281. package/src/utils/csqUtils.ts +0 -27
  282. package/src/utils/decisionTreeUtils.ts +0 -31
  283. package/src/utils/field.ts +0 -49
  284. package/src/utils/query.ts +0 -154
  285. 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
- .scrolling-div {
85
- margin-top: 8px;
86
- overflow-y: auto;
87
- max-height: calc(100vh - 78px) !important;
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,7 +1,7 @@
1
1
  import { ParentComponent, Show } from "solid-js";
2
2
 
3
3
  export const Anchor: ParentComponent<{
4
- href: string | null | undefined;
4
+ href?: string | null | undefined;
5
5
  }> = (props) => {
6
6
  return (
7
7
  <Show when={props.href} fallback={props.children} keyed>
@@ -1,10 +1,10 @@
1
1
  import { Component, For } from "solid-js";
2
2
  import { A } from "@solidjs/router";
3
3
 
4
- export type BreadCrumbItem = { href?: string; text: string };
4
+ export type BreadcrumbItem = { href?: string; text: string };
5
5
 
6
6
  export const Breadcrumb: Component<{
7
- items: BreadCrumbItem[];
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 || "#"}>{link.text}</A>
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 api from "../Api";
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
- (async () => {
17
- navigate(`/`);
18
- const samples = await api.getSamples({ query: { selector: ["proband"], operator: "==", args: true } });
19
- if (samples.page.totalElements === 1) {
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
- <A class="navbar-link">{selectedDataset()}</A>
23
+ <a class="navbar-link">{selectedDataset()}</a>
32
24
  <div class="navbar-dropdown">
33
- <For each={api.getDatasetIds()}>
25
+ <For each={getDatasetIds()}>
34
26
  {(dataset: string) => (
35
- <A
36
- class="navbar-item"
37
- onClick={() => {
38
- switchIt(dataset);
39
- }}
40
- >
27
+ <a class="navbar-item" onClick={() => switchIt(dataset)}>
41
28
  {dataset}
42
- </A>
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/src/VcfWriter";
6
- import { ComposedQuery, Sample } from "@molgenis/vip-report-api/src/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 createVcf(contig: string, position: number, samples: Sample[]): Promise<Uint8Array> {
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 data = await Promise.all([api.getRecordsMeta(), api.getRecords({ query, size: Number.MAX_SAFE_INTEGER })]);
27
+ const records = await fetchRecords({ query, size: Number.MAX_SAFE_INTEGER });
23
28
  const vcf = writeVcf(
24
- { metadata: data[0], data: data[1].items.map((item) => item.data) },
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 (contig: string, position: number, samples: Sample[]): Promise<unknown> => {
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
- api.getFastaGz(contig, position),
35
- createVcf(contig, position, samples),
36
- api.getGenesGz(),
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: htsFileMetadata.genomeAssembly,
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) => api.getCram(sample.person.individualId))]);
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<{ contig: string; position: number; samples: Sample[] }> = (props) => {
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.contig, props.position, props.samples);
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
- })().catch((err) => console.error(err));
126
+ })();
113
127
  });
114
128
  onCleanup(() => {
115
- if (browser) {
116
- igv.removeBrowser(browser);
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/src/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/src/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: (page: number) => void;
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(currentPage(), nrPages()));
24
+ const pages = createMemo(() => createPages(props.page.number, nrPages()));
25
+ const page = createMemo(() => props.page);
23
26
 
24
27
  return (
25
28
  <>
26
- {props.page.totalElements > props.page.size && (
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": currentPage() === 0 }}
32
- onClick={currentPage() > 0 ? () => props.onPageChange(currentPage() - 1) : undefined}
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
- {(page) =>
39
- page !== null ? (
41
+ {(currentPage) =>
42
+ currentPage !== null ? (
40
43
  <a
41
- classList={{ "pagination-link": true, "is-current": page === currentPage() }}
42
- onClick={page !== currentPage() ? () => props.onPageChange(page) : undefined}
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
- {page + 1}
49
+ {currentPage + 1}
45
50
  </a>
46
51
  ) : (
47
52
  <span class="pagination-ellipsis">&hellip;</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": currentPage() === nrPages() - 1 }}
54
- onClick={currentPage() < nrPages() - 1 ? () => props.onPageChange(currentPage() + 1) : undefined}
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
- export type RecordsPerPageEvent = { number: number };
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
- initialValue: number;
6
- onChange: (event: RecordsPerPageEvent) => void;
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={options}>
20
+ <For each={props.config}>
19
21
  {(option) => (
20
- <option value={option} selected={option == props.initialValue}>
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
+ };