@molgenis/vip-report-template 6.1.1 → 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 +9 -11
  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 +35 -29
  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 +8 -5
  12. package/src/components/DatasetDropdown.tsx +10 -23
  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 +143 -0
  119. package/src/mocks/GRCh37/static.ts +63 -133
  120. package/src/mocks/GRCh37/vcf/family.vcf.blob +37 -31
  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 +341 -328
  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 -114
  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 -183
  237. package/src/components/VariantsTable.tsx +0 -124
  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 -259
  281. package/src/utils/csqUtils.ts +0 -27
  282. package/src/utils/decisionTreeUtils.ts +0 -14
  283. package/src/utils/field.ts +0 -49
  284. package/src/utils/query.ts +0 -154
  285. package/src/utils/viewUtils.ts +0 -32
package/src/App.tsx CHANGED
@@ -1,61 +1,67 @@
1
1
  import { onMount, ParentComponent } from "solid-js";
2
- import { useLocation, useNavigate } from "@solidjs/router";
3
- import api from "./Api";
2
+ import { A, Location, Navigator, useLocation, useNavigate } from "@solidjs/router";
4
3
  import { DatasetDropdown } from "./components/DatasetDropdown";
4
+ import { fetchSampleProbandIds, isDatasetSupport } from "./utils/api.ts";
5
+ import { href } from "./utils/utils.ts";
6
+ import { getMetadata } from "./views/data/data.tsx";
7
+
8
+ // export for development purposes
9
+ export function init(navigate: Navigator, location?: Location) {
10
+ (async () => {
11
+ document.title = `VCF Report (${(await getMetadata()).htsFile.uri})`;
12
+ const sampleIds = await fetchSampleProbandIds();
13
+ if (location === undefined || location.pathname === "/") {
14
+ let components: (string | number)[];
15
+ if (sampleIds.length === 1) {
16
+ components = ["samples", sampleIds[0]!, "variants"];
17
+ } else if (sampleIds.length === 0) {
18
+ components = ["variants"];
19
+ } else {
20
+ components = ["samples"];
21
+ }
22
+ navigate(href(components));
23
+ }
24
+ })();
25
+ }
5
26
 
6
27
  const App: ParentComponent = (props) => {
7
28
  const navigate = useNavigate();
8
29
  const location = useLocation();
9
30
 
10
- onMount(() => {
11
- (async () => {
12
- const htsFile = await api.getHtsFileMetadata();
13
- document.title = `VCF Report (${htsFile.uri})`;
14
- const samples = await api.getSamples({ query: { selector: ["proband"], operator: "==", args: true } });
15
- if (location.pathname === "/") {
16
- if (samples.page.totalElements === 1) {
17
- navigate(`/samples/${samples.items[0].id}/variants`);
18
- } else if (samples.total === 0) {
19
- navigate(`/variants`);
20
- } else {
21
- navigate(`/samples`);
22
- }
23
- }
24
- })().catch((err) => console.error(err));
25
- });
31
+ onMount(() => init(navigate, location));
26
32
 
27
33
  return (
28
34
  <>
29
35
  <nav class="navbar is-fixed-top is-light" role="navigation" aria-label="main navigation">
30
36
  <div class="navbar-brand">
31
- <a class="navbar-item has-text-weight-semibold" href="/">
37
+ <A class="navbar-item has-text-weight-semibold" href="/" end={true}>
32
38
  Variant Interpretation Pipeline
33
- </a>
39
+ </A>
34
40
  </div>
35
41
  <div class="navbar-menu">
36
42
  <div class="navbar-item has-dropdown is-hoverable">
37
- <a class="navbar-link" href="/">
43
+ <A class="navbar-link" href={"/"} end={true}>
38
44
  Report
39
- </a>
45
+ </A>
40
46
  <div class="navbar-dropdown">
41
- <a class="navbar-item" href="/samples">
47
+ <A class="navbar-item" href={"/samples"} end={true}>
42
48
  Samples
43
- </a>
49
+ </A>
44
50
  <hr class="navbar-divider" />
45
- <a class="navbar-item" href="/variants">
51
+ <A class="navbar-item" href={"/variants"} end={true}>
46
52
  Variants
47
- </a>
53
+ </A>
48
54
  </div>
49
55
  </div>
50
- {api.isDatasetSupport() && (
56
+ {isDatasetSupport() && (
51
57
  <div class="navbar-start">
52
58
  <DatasetDropdown />
53
59
  </div>
54
60
  )}
55
61
  <div class="navbar-end">
56
- <a class="navbar-item" href="/help">
62
+ <A class="navbar-item" href={"/help"} end={true}>
57
63
  Help
58
- </a>
64
+ </A>
59
65
  </div>
60
66
  </div>
61
67
  </nav>
@@ -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,9 +1,10 @@
1
1
  import { Component, For } from "solid-js";
2
+ import { A } from "@solidjs/router";
2
3
 
3
- export type BreadCrumbItem = { href?: string; text: string };
4
+ export type BreadcrumbItem = { href?: string; text: string };
4
5
 
5
6
  export const Breadcrumb: Component<{
6
- items: BreadCrumbItem[];
7
+ items: BreadcrumbItem[];
7
8
  }> = (props) => {
8
9
  return (
9
10
  <div class="columns is-gapless">
@@ -11,17 +12,19 @@ export const Breadcrumb: Component<{
11
12
  <nav class="breadcrumb has-succeeds-separator">
12
13
  <ul>
13
14
  <li classList={{ "is-active": props.items.length === 0 }}>
14
- <a href="/">
15
+ <A href="/" end={true}>
15
16
  <span class="icon is-small mr-2">
16
17
  <i class="fas fa-home" />
17
18
  </span>
18
19
  <span>Report</span>
19
- </a>
20
+ </A>
20
21
  </li>
21
22
  <For each={props.items}>
22
23
  {(link, i) => (
23
24
  <li classList={{ "is-active": i() === props.items.length - 1 }}>
24
- <a href={link.href || "#"}>{link.text}</a>
25
+ <A href={link.href || "#"} end={true}>
26
+ {link.text}
27
+ </A>
25
28
  </li>
26
29
  )}
27
30
  </For>
@@ -1,43 +1,30 @@
1
1
  import { Component, createSignal, For } from "solid-js";
2
- import api from "../Api";
3
- import { useNavigate } 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
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
29
  </a>
43
30
  )}
@@ -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>