react-msaview 5.2.0 → 5.2.1

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 (141) hide show
  1. package/README.md +41 -8
  2. package/bundle/index.js +17 -17
  3. package/bundle/index.js.LICENSE.txt +1 -1
  4. package/bundle/index.js.map +1 -1
  5. package/dist/components/ConservationTrack.js +2 -13
  6. package/dist/components/ConservationTrack.js.map +1 -1
  7. package/dist/components/MSAView.js +8 -1
  8. package/dist/components/MSAView.js.map +1 -1
  9. package/dist/components/ResizeHandles.d.ts +3 -0
  10. package/dist/components/ResizeHandles.js +60 -105
  11. package/dist/components/ResizeHandles.js.map +1 -1
  12. package/dist/components/TextTrack.js +2 -1
  13. package/dist/components/TextTrack.js.map +1 -1
  14. package/dist/components/Track.js +1 -1
  15. package/dist/components/Track.js.map +1 -1
  16. package/dist/components/VerticalScrollbar.js +2 -2
  17. package/dist/components/VerticalScrollbar.js.map +1 -1
  18. package/dist/components/dialogs/UserProvidedDomainsDialog.js +1 -2
  19. package/dist/components/dialogs/UserProvidedDomainsDialog.js.map +1 -1
  20. package/dist/components/header/ColorSchemeMenu.js +1 -1
  21. package/dist/components/header/ColorSchemeMenu.js.map +1 -1
  22. package/dist/components/header/FileMenu.js +1 -1
  23. package/dist/components/header/FileMenu.js.map +1 -1
  24. package/dist/components/header/HeaderInfoArea.js +28 -8
  25. package/dist/components/header/HeaderInfoArea.js.map +1 -1
  26. package/dist/components/header/MSASettingsMenu.js +1 -1
  27. package/dist/components/header/MSASettingsMenu.js.map +1 -1
  28. package/dist/components/header/TreeSettingsMenu.js +1 -1
  29. package/dist/components/header/TreeSettingsMenu.js.map +1 -1
  30. package/dist/components/header/ZoomControls.js +7 -2
  31. package/dist/components/header/ZoomControls.js.map +1 -1
  32. package/dist/components/header/ZoomMenu.js +9 -0
  33. package/dist/components/header/ZoomMenu.js.map +1 -1
  34. package/dist/components/minimap/Minimap.js +6 -12
  35. package/dist/components/minimap/Minimap.js.map +1 -1
  36. package/dist/components/minimap/MinimapSVG.js +7 -13
  37. package/dist/components/minimap/MinimapSVG.js.map +1 -1
  38. package/dist/components/minimap/minimapLayout.d.ts +9 -0
  39. package/dist/components/minimap/minimapLayout.js +15 -0
  40. package/dist/components/minimap/minimapLayout.js.map +1 -0
  41. package/dist/components/msa/MSACanvas.js +6 -1
  42. package/dist/components/msa/MSACanvas.js.map +1 -1
  43. package/dist/components/msa/MSACanvasBlock.js +5 -1
  44. package/dist/components/msa/MSACanvasBlock.js.map +1 -1
  45. package/dist/components/msa/MSAMouseoverCanvas.js +2 -2
  46. package/dist/components/msa/MSAMouseoverCanvas.js.map +1 -1
  47. package/dist/components/msa/renderMSABlock.js +5 -7
  48. package/dist/components/msa/renderMSABlock.js.map +1 -1
  49. package/dist/components/msa/renderMSAMouseover.js +3 -2
  50. package/dist/components/msa/renderMSAMouseover.js.map +1 -1
  51. package/dist/components/tracks/renderTracksSvg.js +2 -1
  52. package/dist/components/tracks/renderTracksSvg.js.map +1 -1
  53. package/dist/components/tree/renderTreeCanvas.js +2 -2
  54. package/dist/components/tree/renderTreeCanvas.js.map +1 -1
  55. package/dist/constants.d.ts +4 -0
  56. package/dist/constants.js +7 -0
  57. package/dist/constants.js.map +1 -1
  58. package/dist/flatToTree.js +6 -0
  59. package/dist/flatToTree.js.map +1 -1
  60. package/dist/hierarchy.d.ts +1 -1
  61. package/dist/hierarchy.js +136 -102
  62. package/dist/hierarchy.js.map +1 -1
  63. package/dist/index.d.ts +1 -0
  64. package/dist/index.js +1 -0
  65. package/dist/index.js.map +1 -1
  66. package/dist/launchInterProScan.js +22 -9
  67. package/dist/launchInterProScan.js.map +1 -1
  68. package/dist/measureTextCanvas.js +2 -1
  69. package/dist/measureTextCanvas.js.map +1 -1
  70. package/dist/model.d.ts +212 -150
  71. package/dist/model.js +239 -205
  72. package/dist/model.js.map +1 -1
  73. package/dist/parseAsn1.js +8 -2
  74. package/dist/parseAsn1.js.map +1 -1
  75. package/dist/renderToSvg.js +27 -24
  76. package/dist/renderToSvg.js.map +1 -1
  77. package/dist/setFontSize.d.ts +3 -0
  78. package/dist/setFontSize.js +14 -0
  79. package/dist/setFontSize.js.map +1 -0
  80. package/dist/useDevicePixelRatio.d.ts +1 -0
  81. package/dist/useDevicePixelRatio.js +27 -0
  82. package/dist/useDevicePixelRatio.js.map +1 -0
  83. package/dist/useWheelScroll.d.ts +3 -1
  84. package/dist/useWheelScroll.js +64 -22
  85. package/dist/useWheelScroll.js.map +1 -1
  86. package/dist/util.d.ts +4 -0
  87. package/dist/util.js +36 -0
  88. package/dist/util.js.map +1 -1
  89. package/dist/version.d.ts +1 -1
  90. package/dist/version.js +1 -1
  91. package/dist/webpack.js +0 -1
  92. package/dist/webpack.js.map +1 -1
  93. package/dist/wheelZoom.d.ts +4 -0
  94. package/dist/wheelZoom.js +43 -0
  95. package/dist/wheelZoom.js.map +1 -0
  96. package/package.json +5 -5
  97. package/src/components/ConservationTrack.tsx +2 -18
  98. package/src/components/MSAView.tsx +10 -1
  99. package/src/components/ResizeHandles.tsx +101 -113
  100. package/src/components/TextTrack.tsx +2 -1
  101. package/src/components/Track.tsx +1 -1
  102. package/src/components/VerticalScrollbar.tsx +2 -2
  103. package/src/components/dialogs/UserProvidedDomainsDialog.tsx +1 -2
  104. package/src/components/header/ColorSchemeMenu.tsx +1 -0
  105. package/src/components/header/FileMenu.tsx +1 -0
  106. package/src/components/header/HeaderInfoArea.tsx +32 -7
  107. package/src/components/header/MSASettingsMenu.tsx +1 -0
  108. package/src/components/header/TreeSettingsMenu.tsx +1 -0
  109. package/src/components/header/ZoomControls.tsx +14 -1
  110. package/src/components/header/ZoomMenu.tsx +10 -0
  111. package/src/components/minimap/Minimap.tsx +6 -15
  112. package/src/components/minimap/MinimapSVG.tsx +9 -22
  113. package/src/components/minimap/minimapLayout.ts +17 -0
  114. package/src/components/msa/MSACanvas.tsx +9 -0
  115. package/src/components/msa/MSACanvasBlock.tsx +7 -1
  116. package/src/components/msa/MSAMouseoverCanvas.tsx +4 -3
  117. package/src/components/msa/renderMSABlock.ts +6 -9
  118. package/src/components/msa/renderMSAMouseover.ts +3 -1
  119. package/src/components/tracks/renderTracksSvg.ts +2 -1
  120. package/src/components/tree/renderTreeCanvas.ts +2 -2
  121. package/src/constants.ts +8 -0
  122. package/src/flatToTree.test.ts +30 -0
  123. package/src/flatToTree.ts +10 -1
  124. package/src/hierarchy.test.ts +53 -1
  125. package/src/hierarchy.ts +139 -113
  126. package/src/index.ts +1 -0
  127. package/src/launchInterProScan.ts +33 -11
  128. package/src/measureTextCanvas.ts +3 -1
  129. package/src/model.ts +312 -242
  130. package/src/parseAsn1.test.ts +6 -0
  131. package/src/parseAsn1.ts +11 -3
  132. package/src/renderToSvg.tsx +79 -67
  133. package/src/setFontSize.test.ts +33 -0
  134. package/src/setFontSize.ts +20 -0
  135. package/src/useDevicePixelRatio.ts +29 -0
  136. package/src/useWheelScroll.ts +79 -21
  137. package/src/util.ts +35 -0
  138. package/src/version.ts +1 -1
  139. package/src/webpack.ts +0 -1
  140. package/src/wheelZoom.test.ts +46 -0
  141. package/src/wheelZoom.ts +47 -0
package/dist/util.js CHANGED
@@ -49,6 +49,42 @@ export function skipBlanks(blanks, str) {
49
49
  }
50
50
  return chunks.join('');
51
51
  }
52
+ export function computeRowInsertions(blanks, seq) {
53
+ const insertions = [];
54
+ const blanksLen = blanks.length;
55
+ let displayPos = 0;
56
+ let blankIdx = 0;
57
+ let currentInsertPos = -1;
58
+ let letterChars = [];
59
+ for (let i = 0; i < seq.length; i++) {
60
+ if (blankIdx < blanksLen && blanks[blankIdx] === i) {
61
+ // bit trick: (code - 45) >>> 0 <= 1 checks for '-' (45) or '.' (46)
62
+ if (!((seq.charCodeAt(i) - 45) >>> 0 <= 1)) {
63
+ if (currentInsertPos === displayPos) {
64
+ letterChars.push(seq[i]);
65
+ }
66
+ else {
67
+ if (letterChars.length > 0) {
68
+ insertions.push({
69
+ pos: currentInsertPos,
70
+ letters: letterChars.join(''),
71
+ });
72
+ }
73
+ currentInsertPos = displayPos;
74
+ letterChars = [seq[i]];
75
+ }
76
+ }
77
+ blankIdx++;
78
+ }
79
+ else {
80
+ displayPos++;
81
+ }
82
+ }
83
+ if (letterChars.length > 0) {
84
+ insertions.push({ pos: currentInsertPos, letters: letterChars.join('') });
85
+ }
86
+ return insertions;
87
+ }
52
88
  export function len(a) {
53
89
  return a.end - a.start;
54
90
  }
package/dist/util.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AACvC,OAAO,UAAU,MAAM,qBAAqB,CAAA;AAC5C,OAAO,WAAW,MAAM,sBAAsB,CAAA;AAI9C,MAAM,CAAC,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC,CAAA;AAEjC,MAAM,mBAAmB,GAAG,IAAI,GAAG,EAAkB,CAAA;AAErD,0EAA0E;AAC1E,uEAAuE;AACvE,8DAA8D;AAC9D,MAAM,UAAU,sBAAsB,CAAC,KAAa,EAAE,EAAU;IAC9D,MAAM,QAAQ,GAAG,GAAG,KAAK,IAAI,EAAE,EAAE,CAAA;IACjC,MAAM,MAAM,GAAG,mBAAmB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;IAChD,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,OAAO,MAAM,CAAA;IACf,CAAC;IACD,IAAI,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAA;IACrB,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE,CAAC,EAAE,CAAC;QACtB,MAAM,QAAQ,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC,SAAS,EAAE,GAAG,GAAG,CAAA;QAC7C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YACjD,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;QACjD,CAAC;IACH,CAAC;IACD,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,EAAE,CAAA;IACxB,mBAAmB,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;IACzC,OAAO,MAAM,CAAA;AACf,CAAC;AAED,MAAM,UAAU,SAAS,CACvB,GAAsB,EACtB,EAAsC;IAEtC,OAAO,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAA;AACxD,CAAC;AAED,MAAM,UAAU,aAAa,CAC3B,WAAmC,EACnC,KAAY;IAEZ,OAAO,SAAS,CAAC,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC;QACjD,MAAM;QACN,KAAK,CAAC,OAAO,CAAC,eAAe,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,CAAC;KACrD,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,MAAgB,EAAE,GAAW;IACtD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO,GAAG,CAAA;IACZ,CAAC;IACD,MAAM,MAAM,GAAG,EAAE,CAAA;IACjB,IAAI,OAAO,GAAG,CAAC,CAAA;IACf,KAAK,MAAM,QAAQ,IAAI,MAAM,EAAE,CAAC;QAC9B,IAAI,QAAQ,GAAG,OAAO,EAAE,CAAC;YACvB,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAA;QAC3C,CAAC;QACD,OAAO,GAAG,QAAQ,GAAG,CAAC,CAAA;IACxB,CAAC;IACD,IAAI,OAAO,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC;QACzB,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAA;IACjC,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;AACxB,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,MAAgB,EAAE,GAAW;IAChE,MAAM,UAAU,GAAuC,EAAE,CAAA;IACzD,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAA;IAC/B,IAAI,UAAU,GAAG,CAAC,CAAA;IAClB,IAAI,QAAQ,GAAG,CAAC,CAAA;IAChB,IAAI,gBAAgB,GAAG,CAAC,CAAC,CAAA;IACzB,IAAI,WAAW,GAAa,EAAE,CAAA;IAC9B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACpC,IAAI,QAAQ,GAAG,SAAS,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YACnD,oEAAoE;YACpE,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;gBAC3C,IAAI,gBAAgB,KAAK,UAAU,EAAE,CAAC;oBACpC,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAE,CAAC,CAAA;gBAC3B,CAAC;qBAAM,CAAC;oBACN,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;wBAC3B,UAAU,CAAC,IAAI,CAAC;4BACd,GAAG,EAAE,gBAAgB;4BACrB,OAAO,EAAE,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC;yBAC9B,CAAC,CAAA;oBACJ,CAAC;oBACD,gBAAgB,GAAG,UAAU,CAAA;oBAC7B,WAAW,GAAG,CAAC,GAAG,CAAC,CAAC,CAAE,CAAC,CAAA;gBACzB,CAAC;YACH,CAAC;YACD,QAAQ,EAAE,CAAA;QACZ,CAAC;aAAM,CAAC;YACN,UAAU,EAAE,CAAA;QACd,CAAC;IACH,CAAC;IACD,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3B,UAAU,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,gBAAgB,EAAE,OAAO,EAAE,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,CAAA;IAC3E,CAAC;IACD,OAAO,UAAU,CAAA;AACnB,CAAC;AAED,MAAM,UAAU,GAAG,CAAC,CAAiC;IACnD,OAAO,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,KAAK,CAAA;AACxB,CAAC;AAED,8CAA8C;AAC9C,oCAAoC;AACpC,MAAM,UAAU,OAAO,CAAC,CAAU;IAChC,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,GAAG,CAAA;AAC/B,CAAC"}
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const version = "5.2.0";
1
+ export declare const version = "5.2.1";
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
- export const version = '5.2.0';
1
+ export const version = '5.2.1';
2
2
  //# sourceMappingURL=version.js.map
package/dist/webpack.js CHANGED
@@ -2,6 +2,5 @@ import * as React from 'react';
2
2
  export { default as MSAView } from "./components/Loading.js";
3
3
  export { default as MSAModelF } from "./model.js";
4
4
  export * from 'react-dom/client';
5
- // eslint-disable-next-line unicorn/prefer-export-from
6
5
  export { React };
7
6
  //# sourceMappingURL=webpack.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"webpack.js","sourceRoot":"","sources":["../src/webpack.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,yBAA0B,CAAA;AAC7D,OAAO,EAAqB,OAAO,IAAI,SAAS,EAAE,MAAM,YAAY,CAAA;AAEpE,cAAc,kBAAkB,CAAA;AAEhC,sDAAsD;AACtD,OAAO,EAAE,KAAK,EAAE,CAAA"}
1
+ {"version":3,"file":"webpack.js","sourceRoot":"","sources":["../src/webpack.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,yBAA0B,CAAA;AAC7D,OAAO,EAAqB,OAAO,IAAI,SAAS,EAAE,MAAM,YAAY,CAAA;AAEpE,cAAc,kBAAkB,CAAA;AAEhC,OAAO,EAAE,KAAK,EAAE,CAAA"}
@@ -0,0 +1,4 @@
1
+ export declare const SCROLL_ZOOM_DIVISOR = 500;
2
+ export declare function normalizeWheelDelta(delta: number, mode: number): number;
3
+ export declare function getZoomNormalizer(deltaY: number): 500 | 25 | 150 | 75;
4
+ export declare function zoomFactorFromAccum(zoomAccum: number, elapsedMs: number): number;
@@ -0,0 +1,43 @@
1
+ // Wheel / trackpad-pinch delta handling, adapted from @jbrowse/core's
2
+ // util/wheelZoom (not yet in our pinned @jbrowse/core). Turns raw wheel deltas
3
+ // into a per-frame zoom factor that feels consistent across fine trackpad
4
+ // pinches and coarse mouse notches.
5
+ // Fixed divisor for plain-wheel scroll-zoom mode, where deltas are big notches.
6
+ export const SCROLL_ZOOM_DIVISOR = 500;
7
+ // Cap the zoom change per millisecond so a burst of momentum-scroll events
8
+ // coalesced into one frame can't jump the zoom level (0.2 per ~60fps frame).
9
+ const MAX_ZOOM_RATE_PER_MS = 0.2 / 16.67;
10
+ // Convert a wheel delta to pixels regardless of the browser's deltaMode; some
11
+ // browsers/mice report lines or pages instead of pixels.
12
+ export function normalizeWheelDelta(delta, mode) {
13
+ if (mode === 1) {
14
+ return delta * 16; // DOM_DELTA_LINE
15
+ }
16
+ if (mode === 2) {
17
+ return delta * 100; // DOM_DELTA_PAGE
18
+ }
19
+ return delta; // DOM_DELTA_PIXEL
20
+ }
21
+ // Adaptive divisor: tiny pinch deltas zoom in larger steps, big mouse notches
22
+ // in finer steps, so a trackpad pinch and a mouse wheel feel similar.
23
+ export function getZoomNormalizer(deltaY) {
24
+ const abs = Math.abs(deltaY);
25
+ if (abs < 6) {
26
+ return 25;
27
+ }
28
+ if (abs > 150) {
29
+ return 500;
30
+ }
31
+ if (abs > 30) {
32
+ return 150;
33
+ }
34
+ return 75;
35
+ }
36
+ // Turn accumulated, normalized zoom intent into a colWidth/rowHeight multiplier.
37
+ // Positive accum (wheel toward user) zooms out (factor < 1); negative zooms in.
38
+ export function zoomFactorFromAccum(zoomAccum, elapsedMs) {
39
+ const max = MAX_ZOOM_RATE_PER_MS * elapsedMs;
40
+ const d = Math.max(-max, Math.min(max, zoomAccum));
41
+ return d > 0 ? 1 / (1 + d) : 1 - d;
42
+ }
43
+ //# sourceMappingURL=wheelZoom.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wheelZoom.js","sourceRoot":"","sources":["../src/wheelZoom.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,+EAA+E;AAC/E,0EAA0E;AAC1E,oCAAoC;AAEpC,gFAAgF;AAChF,MAAM,CAAC,MAAM,mBAAmB,GAAG,GAAG,CAAA;AAEtC,2EAA2E;AAC3E,6EAA6E;AAC7E,MAAM,oBAAoB,GAAG,GAAG,GAAG,KAAK,CAAA;AAExC,8EAA8E;AAC9E,yDAAyD;AACzD,MAAM,UAAU,mBAAmB,CAAC,KAAa,EAAE,IAAY;IAC7D,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;QACf,OAAO,KAAK,GAAG,EAAE,CAAA,CAAC,iBAAiB;IACrC,CAAC;IACD,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;QACf,OAAO,KAAK,GAAG,GAAG,CAAA,CAAC,iBAAiB;IACtC,CAAC;IACD,OAAO,KAAK,CAAA,CAAC,kBAAkB;AACjC,CAAC;AAED,8EAA8E;AAC9E,sEAAsE;AACtE,MAAM,UAAU,iBAAiB,CAAC,MAAc;IAC9C,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;IAC5B,IAAI,GAAG,GAAG,CAAC,EAAE,CAAC;QACZ,OAAO,EAAE,CAAA;IACX,CAAC;IACD,IAAI,GAAG,GAAG,GAAG,EAAE,CAAC;QACd,OAAO,GAAG,CAAA;IACZ,CAAC;IACD,IAAI,GAAG,GAAG,EAAE,EAAE,CAAC;QACb,OAAO,GAAG,CAAA;IACZ,CAAC;IACD,OAAO,EAAE,CAAA;AACX,CAAC;AAED,iFAAiF;AACjF,gFAAgF;AAChF,MAAM,UAAU,mBAAmB,CAAC,SAAiB,EAAE,SAAiB;IACtE,MAAM,GAAG,GAAG,oBAAoB,GAAG,SAAS,CAAA;IAC5C,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC,CAAA;IAClD,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;AACpC,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "react-msaview",
3
3
  "author": "Colin",
4
- "version": "5.2.0",
4
+ "version": "5.2.1",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "repository": {
@@ -35,15 +35,15 @@
35
35
  "react-dom": ">=16.8.0"
36
36
  },
37
37
  "devDependencies": {
38
- "@types/node": "^25.9.1"
38
+ "@types/node": "^25.9.2"
39
39
  },
40
40
  "dependencies": {
41
41
  "@mui/icons-material": "^7.3.11",
42
42
  "@mui/material": "^7.3.11",
43
43
  "colord": "^2.9.3",
44
- "flatbush": "^4.5.1",
45
- "msa-parsers": "5.2.0",
46
- "@jbrowse/svgcanvas": "5.2.0"
44
+ "flatbush": "^4.6.1",
45
+ "@jbrowse/svgcanvas": "5.2.1",
46
+ "msa-parsers": "5.2.1"
47
47
  },
48
48
  "scripts": {
49
49
  "clean": "rimraf dist bundle",
@@ -1,9 +1,9 @@
1
1
  import React, { useEffect, useRef } from 'react'
2
2
 
3
- import { ResizeHandle } from '@jbrowse/core/ui'
4
3
  import { autorun } from 'mobx'
5
4
  import { observer } from 'mobx-react'
6
5
 
6
+ import { ConservationTrackResizeHandle } from './ResizeHandles.tsx'
7
7
  import { drawConservationBars } from './tracks/renderTracksSvg.ts'
8
8
 
9
9
  import type { MsaViewModel } from '../model.ts'
@@ -87,23 +87,7 @@ const ConservationTrack = observer(function ({
87
87
  trackHeight={trackHeight}
88
88
  />
89
89
  ))}
90
- <ResizeHandle
91
- onDrag={delta => {
92
- model.setConservationTrackHeight(
93
- Math.max(10, model.conservationTrackHeight + delta),
94
- )
95
- return delta
96
- }}
97
- style={{
98
- position: 'absolute',
99
- bottom: 0,
100
- left: 0,
101
- height: 4,
102
- width: '100%',
103
- zIndex: 1,
104
- background: 'rgba(200,200,200,0.5)',
105
- }}
106
- />
90
+ <ConservationTrackResizeHandle model={model} />
107
91
  </div>
108
92
  )
109
93
  })
@@ -1,7 +1,8 @@
1
- import React, { Suspense } from 'react'
1
+ import React, { Suspense, useEffect } from 'react'
2
2
 
3
3
  import { observer } from 'mobx-react'
4
4
 
5
+ import { useDevicePixelRatio } from '../useDevicePixelRatio.ts'
5
6
  import {
6
7
  HorizontalResizeHandle,
7
8
  VerticalResizeHandle,
@@ -128,6 +129,14 @@ const View = observer(function ({ model }: { model: MsaViewModel }) {
128
129
 
129
130
  const MSAView = observer(function ({ model }: { model: MsaViewModel }) {
130
131
  const { height, viewInitialized, DialogComponent, DialogProps } = model
132
+
133
+ // mirror the live device pixel ratio into the model so canvas backing stores
134
+ // re-scale when the window moves between monitors or the browser zooms
135
+ const devicePixelRatio = useDevicePixelRatio()
136
+ useEffect(() => {
137
+ model.setHighResScaleFactor(devicePixelRatio)
138
+ }, [model, devicePixelRatio])
139
+
131
140
  return (
132
141
  <div>
133
142
  {viewInitialized ? (
@@ -1,74 +1,49 @@
1
- import React, { useEffect, useRef, useState } from 'react'
1
+ import React, { useCallback, useState } from 'react'
2
2
 
3
3
  import { observer } from 'mobx-react'
4
4
 
5
+ import { useDragScroll } from '../useDragScroll.ts'
6
+
5
7
  import type { MsaViewModel } from '../model.ts'
6
8
 
9
+ const handleBg = 'rgba(200,200,200)'
10
+ const handleBgHover = 'rgba(150,150,150)'
11
+
7
12
  export const VerticalResizeHandle = observer(function ({
8
13
  model,
9
14
  }: {
10
15
  model: MsaViewModel
11
16
  }) {
12
17
  const { resizeHandleWidth } = model
13
- const [mouseDragging, setMouseDragging] = useState(false)
14
- const scheduled = useRef(false)
15
- const prevX = useRef(0)
16
-
17
- useEffect(() => {
18
- function globalMouseMove(event: MouseEvent) {
19
- event.preventDefault()
20
- const currX = event.clientX
21
- if (prevX.current === 0) {
22
- prevX.current = event.clientX
23
- } else {
24
- const distance = currX - prevX.current
25
- if (distance) {
26
- // use rAF to make it so multiple event handlers aren't fired per-frame
27
- // see https://calendar.perfplanet.com/2013/the-runtime-performance-checklist/
28
- if (!scheduled.current) {
29
- scheduled.current = true
30
- window.requestAnimationFrame(() => {
31
- model.setTreeAreaWidth(model.treeAreaWidth + distance)
32
- scheduled.current = false
33
- prevX.current = event.clientX
34
- })
35
- }
36
- }
37
- }
38
- }
39
-
40
- function globalMouseUp() {
41
- prevX.current = 0
42
- if (mouseDragging) {
43
- setMouseDragging(false)
44
- }
45
- }
46
- if (mouseDragging) {
47
- document.addEventListener('mousemove', globalMouseMove, true)
48
- document.addEventListener('mouseup', globalMouseUp, true)
49
- return () => {
50
- document.removeEventListener('mousemove', globalMouseMove, true)
51
- document.removeEventListener('mouseup', globalMouseUp, true)
52
- }
53
- }
54
- return () => {}
55
- }, [mouseDragging, model])
18
+ const [hovered, setHovered] = useState(false)
19
+ const { startDrag } = useDragScroll(
20
+ 'x',
21
+ useCallback(
22
+ (delta: number, startWidth: number) => {
23
+ model.setTreeAreaWidth(startWidth + delta)
24
+ },
25
+ [model],
26
+ ),
27
+ )
56
28
 
57
29
  return (
58
- <div>
59
- <div
60
- onMouseDown={() => {
61
- setMouseDragging(true)
62
- }}
63
- style={{
64
- cursor: 'ew-resize',
65
- height: '100%',
66
- width: resizeHandleWidth,
67
- background: 'rgba(200,200,200)',
68
- position: 'relative',
69
- }}
70
- />
71
- </div>
30
+ <div
31
+ onMouseDown={event => {
32
+ startDrag(event, model.treeAreaWidth)
33
+ }}
34
+ onMouseEnter={() => {
35
+ setHovered(true)
36
+ }}
37
+ onMouseLeave={() => {
38
+ setHovered(false)
39
+ }}
40
+ style={{
41
+ cursor: 'ew-resize',
42
+ width: resizeHandleWidth,
43
+ background: hovered ? handleBgHover : handleBg,
44
+ position: 'relative',
45
+ }}
46
+ />
72
47
  )
73
48
  })
74
49
 
@@ -78,64 +53,77 @@ export const HorizontalResizeHandle = observer(function ({
78
53
  model: MsaViewModel
79
54
  }) {
80
55
  const { resizeHandleWidth } = model
81
- const [mouseDragging, setMouseDragging] = useState(false)
82
- const scheduled = useRef(false)
83
- const prevY = useRef(0)
56
+ const [hovered, setHovered] = useState(false)
57
+ const { startDrag } = useDragScroll(
58
+ 'y',
59
+ useCallback(
60
+ (delta: number, startHeight: number) => {
61
+ model.setHeight(startHeight + delta)
62
+ },
63
+ [model],
64
+ ),
65
+ )
84
66
 
85
- useEffect(() => {
86
- function globalMouseMove(event: MouseEvent) {
87
- event.preventDefault()
88
- const currY = event.clientY
89
- if (prevY.current === 0) {
90
- prevY.current = event.clientY
91
- } else {
92
- const distance = currY - prevY.current
93
- if (distance) {
94
- // use rAF to make it so multiple event handlers aren't fired per-frame
95
- // see https://calendar.perfplanet.com/2013/the-runtime-performance-checklist/
96
- if (!scheduled.current) {
97
- scheduled.current = true
98
- window.requestAnimationFrame(() => {
99
- model.setHeight(model.height + distance)
100
- scheduled.current = false
101
- prevY.current = event.clientY
102
- })
103
- }
104
- }
105
- }
106
- }
67
+ return (
68
+ <div
69
+ onMouseDown={event => {
70
+ startDrag(event, model.height)
71
+ }}
72
+ onMouseEnter={() => {
73
+ setHovered(true)
74
+ }}
75
+ onMouseLeave={() => {
76
+ setHovered(false)
77
+ }}
78
+ style={{
79
+ cursor: 'ns-resize',
80
+ width: '100%',
81
+ height: resizeHandleWidth,
82
+ background: hovered ? handleBgHover : handleBg,
83
+ position: 'relative',
84
+ }}
85
+ />
86
+ )
87
+ })
107
88
 
108
- function globalMouseUp() {
109
- prevY.current = 0
110
- if (mouseDragging) {
111
- setMouseDragging(false)
112
- }
113
- }
114
- if (mouseDragging) {
115
- document.addEventListener('mousemove', globalMouseMove, true)
116
- document.addEventListener('mouseup', globalMouseUp, true)
117
- return () => {
118
- document.removeEventListener('mousemove', globalMouseMove, true)
119
- document.removeEventListener('mouseup', globalMouseUp, true)
120
- }
121
- }
122
- return () => {}
123
- }, [mouseDragging, model])
89
+ export const ConservationTrackResizeHandle = observer(function ({
90
+ model,
91
+ }: {
92
+ model: MsaViewModel
93
+ }) {
94
+ const { resizeHandleWidth } = model
95
+ const [hovered, setHovered] = useState(false)
96
+ const { startDrag } = useDragScroll(
97
+ 'y',
98
+ useCallback(
99
+ (delta: number, startHeight: number) => {
100
+ model.setConservationTrackHeight(Math.max(10, startHeight + delta))
101
+ },
102
+ [model],
103
+ ),
104
+ )
124
105
 
125
106
  return (
126
- <div>
127
- <div
128
- onMouseDown={() => {
129
- setMouseDragging(true)
130
- }}
131
- style={{
132
- cursor: 'ns-resize',
133
- width: '100%',
134
- height: resizeHandleWidth,
135
- background: 'rgba(200,200,200)',
136
- position: 'relative',
137
- }}
138
- />
139
- </div>
107
+ <div
108
+ onMouseDown={event => {
109
+ startDrag(event, model.conservationTrackHeight)
110
+ }}
111
+ onMouseEnter={() => {
112
+ setHovered(true)
113
+ }}
114
+ onMouseLeave={() => {
115
+ setHovered(false)
116
+ }}
117
+ style={{
118
+ position: 'absolute',
119
+ bottom: 0,
120
+ left: 0,
121
+ cursor: 'ns-resize',
122
+ width: '100%',
123
+ height: resizeHandleWidth,
124
+ zIndex: 1,
125
+ background: hovered ? handleBgHover : handleBg,
126
+ }}
127
+ />
140
128
  )
141
129
  })
@@ -3,6 +3,7 @@ import React, { useEffect, useRef } from 'react'
3
3
  import { autorun } from 'mobx'
4
4
  import { observer } from 'mobx-react'
5
5
 
6
+ import { setFontSize } from '../setFontSize.ts'
6
7
  import { useColorContrast } from '../useColorContrast.ts'
7
8
  import { drawTextTrackContent } from './tracks/renderTracksSvg.ts'
8
9
 
@@ -51,7 +52,7 @@ const AnnotationBlock = observer(function ({
51
52
  ctx.clearRect(0, 0, blockSize, rowHeight)
52
53
  ctx.translate(-offsetX, 0)
53
54
  ctx.textAlign = 'center'
54
- ctx.font = ctx.font.replace(/\d+px/, `${fontSize}px`)
55
+ setFontSize(ctx, fontSize)
55
56
 
56
57
  drawTextTrackContent({
57
58
  ctx,
@@ -31,7 +31,7 @@ const TrackLabel = observer(function TrackLabel({
31
31
  model: { name, height },
32
32
  } = track
33
33
  const { classes } = useStyles()
34
- const trackLabelHeight = Math.max(8, rowHeight - 8)
34
+ const trackLabelHeight = Math.min(height, Math.max(8, rowHeight - 8))
35
35
 
36
36
  return (
37
37
  <div
@@ -20,9 +20,9 @@ const VerticalScrollbar = observer(({ model }: { model: MsaViewModel }) => {
20
20
  'y',
21
21
  useCallback(
22
22
  (delta: number, startScroll: number) => {
23
- model.setScrollY(clamp(startScroll - delta / unit, -totalHeight, 0))
23
+ model.setScrollY(clamp(startScroll - delta / unit, model.maxScrollY, 0))
24
24
  },
25
- [model, unit, totalHeight],
25
+ [model, unit],
26
26
  ),
27
27
  )
28
28
  return (
@@ -112,10 +112,9 @@ const UserProvidedDomainsDialog = observer(function ({
112
112
  ? JSON.parse(await file.text())
113
113
  : await jsonfetch(interProURL)
114
114
 
115
- model.setInterProAnnotations(
115
+ model.setDomains(
116
116
  Object.fromEntries(ret.results.map(r => [r.xref[0]!.id, r])),
117
117
  )
118
- model.setShowDomains(true)
119
118
  getSession(model).notify(
120
119
  'Loaded interproscan results',
121
120
  'success',
@@ -12,6 +12,7 @@ const ColorSchemeMenu = observer(function ({ model }: { model: MsaViewModel }) {
12
12
  const { colorSchemeName } = model
13
13
  return (
14
14
  <CascadingMenuButton
15
+ data-testid="color_scheme_menu"
15
16
  closeAfterItemClick
16
17
  anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
17
18
  transformOrigin={{ vertical: 'top', horizontal: 'left' }}
@@ -19,6 +19,7 @@ const SettingsDialog = lazy(() => import('../dialogs/SettingsDialog.tsx'))
19
19
  const FileMenu = observer(({ model }: { model: MsaViewModel }) => {
20
20
  return (
21
21
  <CascadingMenuButton
22
+ data-testid="file_menu"
22
23
  menuItems={[
23
24
  {
24
25
  label: 'Return to import form',
@@ -7,22 +7,47 @@ import { makeStyles } from 'tss-react/mui'
7
7
  import type { MsaViewModel } from '../../model.ts'
8
8
 
9
9
  const useStyles = makeStyles()({
10
- margin: {
10
+ container: {
11
+ display: 'flex',
12
+ overflow: 'hidden',
11
13
  margin: 'auto',
12
14
  marginLeft: 10,
13
15
  },
16
+ nameFirst: {
17
+ overflow: 'hidden',
18
+ textOverflow: 'ellipsis',
19
+ whiteSpace: 'nowrap',
20
+ minWidth: 0,
21
+ },
22
+ nameRest: {
23
+ whiteSpace: 'nowrap',
24
+ flexShrink: 0,
25
+ },
14
26
  })
15
27
 
16
28
  const HeaderInfoArea = observer(function ({ model }: { model: MsaViewModel }) {
17
29
  const { mouseOverRowName, mouseCol } = model
18
30
  const { classes } = useStyles()
19
- return mouseOverRowName && mouseCol !== undefined ? (
20
- <Typography className={classes.margin}>
21
- {mouseOverRowName}:
22
- {model.visibleColToSeqPosOneBased(mouseOverRowName, mouseCol)} (
23
- {model.visibleColToRowLetter(mouseOverRowName, mouseCol)})
31
+ if (!mouseOverRowName || mouseCol === undefined) {
32
+ return null
33
+ }
34
+ const mid = Math.floor(mouseOverRowName.length / 2)
35
+ const pos = model.visibleColToSeqPosOneBased(mouseOverRowName, mouseCol)
36
+ const letter = model.visibleColToRowLetter(mouseOverRowName, mouseCol)
37
+ return (
38
+ <Typography
39
+ component="div"
40
+ className={classes.container}
41
+ title={mouseOverRowName}
42
+ >
43
+ <span className={classes.nameFirst}>
44
+ {mouseOverRowName.slice(0, mid)}
45
+ </span>
46
+ <span className={classes.nameRest}>
47
+ {mouseOverRowName.slice(mid)}:{pos} ({letter})
48
+ </span>
24
49
  </Typography>
25
- ) : null
50
+ )
26
51
  })
27
52
 
28
53
  export default HeaderInfoArea
@@ -11,6 +11,7 @@ import type { MsaViewModel } from '../../model.ts'
11
11
  const MSASettingsMenu = observer(function ({ model }: { model: MsaViewModel }) {
12
12
  return (
13
13
  <CascadingMenuButton
14
+ data-testid="msa_settings_menu"
14
15
  closeAfterItemClick={false}
15
16
  anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
16
17
  transformOrigin={{ vertical: 'top', horizontal: 'left' }}
@@ -16,6 +16,7 @@ const TreeSettingsMenu = observer(function ({
16
16
  const { rows } = model
17
17
  return (
18
18
  <CascadingMenuButton
19
+ data-testid="tree_settings_menu"
19
20
  closeAfterItemClick={false}
20
21
  anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
21
22
  transformOrigin={{ vertical: 'top', horizontal: 'left' }}
@@ -1,8 +1,9 @@
1
1
  import React from 'react'
2
2
 
3
3
  import ZoomIn from '@mui/icons-material/ZoomIn'
4
+ import ZoomInMap from '@mui/icons-material/ZoomInMap'
4
5
  import ZoomOut from '@mui/icons-material/ZoomOut'
5
- import { IconButton } from '@mui/material'
6
+ import { IconButton, ToggleButton } from '@mui/material'
6
7
  import { observer } from 'mobx-react'
7
8
 
8
9
  import type { MsaViewModel } from '../../model.ts'
@@ -28,6 +29,18 @@ const ZoomControls = observer(function ZoomControls({
28
29
  >
29
30
  <ZoomOut />
30
31
  </IconButton>
32
+ <ToggleButton
33
+ value="scrollZoom"
34
+ selected={model.scrollZoom}
35
+ title="Toggle scroll-to-zoom (zoom on plain mouse wheel; ctrl+wheel always zooms)"
36
+ size="small"
37
+ sx={{ border: 'none' }}
38
+ onChange={() => {
39
+ model.setScrollZoom(!model.scrollZoom)
40
+ }}
41
+ >
42
+ <ZoomInMap />
43
+ </ToggleButton>
31
44
  </>
32
45
  )
33
46
  })