@jbrowse/plugin-linear-genome-view 2.1.7 → 2.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 (147) hide show
  1. package/dist/BaseLinearDisplay/components/LinearBlocks.d.ts +2 -2
  2. package/dist/BaseLinearDisplay/components/ServerSideRenderedBlockContent.js +2 -22
  3. package/dist/BaseLinearDisplay/components/ServerSideRenderedBlockContent.js.map +1 -1
  4. package/dist/BaseLinearDisplay/components/Tooltip.d.ts +1 -1
  5. package/dist/BaseLinearDisplay/models/BaseLinearDisplayModel.d.ts +149 -4
  6. package/dist/BaseLinearDisplay/models/BaseLinearDisplayModel.js +600 -464
  7. package/dist/BaseLinearDisplay/models/BaseLinearDisplayModel.js.map +1 -1
  8. package/dist/BaseLinearDisplay/models/baseLinearDisplayConfigSchema.js +19 -1
  9. package/dist/BaseLinearDisplay/models/baseLinearDisplayConfigSchema.js.map +1 -1
  10. package/dist/BaseLinearDisplay/models/serverSideRenderedBlock.d.ts +2 -2
  11. package/dist/BasicTrack/configSchema.d.ts +3 -0
  12. package/dist/BasicTrack/configSchema.js +18 -0
  13. package/dist/BasicTrack/configSchema.js.map +1 -0
  14. package/dist/BasicTrack/index.d.ts +3 -0
  15. package/dist/BasicTrack/index.js +18 -0
  16. package/dist/BasicTrack/index.js.map +1 -0
  17. package/dist/FeatureTrack/configSchema.d.ts +3 -0
  18. package/dist/FeatureTrack/configSchema.js +21 -0
  19. package/dist/FeatureTrack/configSchema.js.map +1 -0
  20. package/dist/FeatureTrack/index.d.ts +3 -0
  21. package/dist/FeatureTrack/index.js +18 -0
  22. package/dist/FeatureTrack/index.js.map +1 -0
  23. package/dist/LinearBareDisplay/configSchema.d.ts +5 -1
  24. package/dist/LinearBareDisplay/configSchema.js +12 -1
  25. package/dist/LinearBareDisplay/configSchema.js.map +1 -1
  26. package/dist/LinearBareDisplay/model.d.ts +16 -0
  27. package/dist/LinearBareDisplay/model.js +16 -0
  28. package/dist/LinearBareDisplay/model.js.map +1 -1
  29. package/dist/LinearBasicDisplay/configSchema.d.ts +5 -1
  30. package/dist/LinearBasicDisplay/configSchema.js +16 -1
  31. package/dist/LinearBasicDisplay/configSchema.js.map +1 -1
  32. package/dist/LinearBasicDisplay/model.d.ts +79 -8
  33. package/dist/LinearBasicDisplay/model.js +167 -111
  34. package/dist/LinearBasicDisplay/model.js.map +1 -1
  35. package/dist/LinearGenomeView/components/CenterLine.d.ts +1 -1
  36. package/dist/LinearGenomeView/components/GetSequenceDialog.d.ts +1 -1
  37. package/dist/LinearGenomeView/components/Gridlines.d.ts +1 -1
  38. package/dist/LinearGenomeView/components/Header.d.ts +1 -1
  39. package/dist/LinearGenomeView/components/ImportForm.d.ts +1 -1
  40. package/dist/LinearGenomeView/components/ImportForm.js +38 -31
  41. package/dist/LinearGenomeView/components/ImportForm.js.map +1 -1
  42. package/dist/LinearGenomeView/components/LinearGenomeView.d.ts +1 -1
  43. package/dist/LinearGenomeView/components/LinearGenomeView.js +2 -24
  44. package/dist/LinearGenomeView/components/LinearGenomeView.js.map +1 -1
  45. package/dist/LinearGenomeView/components/LinearGenomeViewSvg.d.ts +1 -1
  46. package/dist/LinearGenomeView/components/LinearGenomeViewSvg.js +7 -5
  47. package/dist/LinearGenomeView/components/LinearGenomeViewSvg.js.map +1 -1
  48. package/dist/LinearGenomeView/components/OverviewRubberBand.d.ts +1 -1
  49. package/dist/LinearGenomeView/components/OverviewScaleBar.d.ts +27 -35
  50. package/dist/LinearGenomeView/components/RubberBand.d.ts +1 -1
  51. package/dist/LinearGenomeView/components/ScaleBar.d.ts +1 -1
  52. package/dist/LinearGenomeView/components/TrackContainer.d.ts +1 -1
  53. package/dist/LinearGenomeView/components/TrackContainer.js +12 -9
  54. package/dist/LinearGenomeView/components/TrackContainer.js.map +1 -1
  55. package/dist/LinearGenomeView/components/TrackLabel.js +9 -1
  56. package/dist/LinearGenomeView/components/TrackLabel.js.map +1 -1
  57. package/dist/LinearGenomeView/components/TracksContainer.d.ts +1 -1
  58. package/dist/LinearGenomeView/index.d.ts +273 -18
  59. package/dist/LinearGenomeView/index.js +409 -90
  60. package/dist/LinearGenomeView/index.js.map +1 -1
  61. package/dist/index.d.ts +30 -90
  62. package/dist/index.js +4 -25
  63. package/dist/index.js.map +1 -1
  64. package/esm/BaseLinearDisplay/components/LinearBlocks.d.ts +2 -2
  65. package/esm/BaseLinearDisplay/components/ServerSideRenderedBlockContent.js +2 -22
  66. package/esm/BaseLinearDisplay/components/ServerSideRenderedBlockContent.js.map +1 -1
  67. package/esm/BaseLinearDisplay/components/Tooltip.d.ts +1 -1
  68. package/esm/BaseLinearDisplay/models/BaseLinearDisplayModel.d.ts +149 -4
  69. package/esm/BaseLinearDisplay/models/BaseLinearDisplayModel.js +600 -464
  70. package/esm/BaseLinearDisplay/models/BaseLinearDisplayModel.js.map +1 -1
  71. package/esm/BaseLinearDisplay/models/baseLinearDisplayConfigSchema.js +19 -1
  72. package/esm/BaseLinearDisplay/models/baseLinearDisplayConfigSchema.js.map +1 -1
  73. package/esm/BaseLinearDisplay/models/serverSideRenderedBlock.d.ts +2 -2
  74. package/esm/BasicTrack/configSchema.d.ts +3 -0
  75. package/esm/BasicTrack/configSchema.js +16 -0
  76. package/esm/BasicTrack/configSchema.js.map +1 -0
  77. package/esm/BasicTrack/index.d.ts +3 -0
  78. package/esm/BasicTrack/index.js +13 -0
  79. package/esm/BasicTrack/index.js.map +1 -0
  80. package/esm/FeatureTrack/configSchema.d.ts +3 -0
  81. package/esm/FeatureTrack/configSchema.js +19 -0
  82. package/esm/FeatureTrack/configSchema.js.map +1 -0
  83. package/esm/FeatureTrack/index.d.ts +3 -0
  84. package/esm/FeatureTrack/index.js +13 -0
  85. package/esm/FeatureTrack/index.js.map +1 -0
  86. package/esm/LinearBareDisplay/configSchema.d.ts +5 -1
  87. package/esm/LinearBareDisplay/configSchema.js +14 -2
  88. package/esm/LinearBareDisplay/configSchema.js.map +1 -1
  89. package/esm/LinearBareDisplay/model.d.ts +16 -0
  90. package/esm/LinearBareDisplay/model.js +16 -0
  91. package/esm/LinearBareDisplay/model.js.map +1 -1
  92. package/esm/LinearBasicDisplay/configSchema.d.ts +5 -1
  93. package/esm/LinearBasicDisplay/configSchema.js +18 -2
  94. package/esm/LinearBasicDisplay/configSchema.js.map +1 -1
  95. package/esm/LinearBasicDisplay/model.d.ts +79 -8
  96. package/esm/LinearBasicDisplay/model.js +167 -111
  97. package/esm/LinearBasicDisplay/model.js.map +1 -1
  98. package/esm/LinearGenomeView/components/CenterLine.d.ts +1 -1
  99. package/esm/LinearGenomeView/components/GetSequenceDialog.d.ts +1 -1
  100. package/esm/LinearGenomeView/components/Gridlines.d.ts +1 -1
  101. package/esm/LinearGenomeView/components/Header.d.ts +1 -1
  102. package/esm/LinearGenomeView/components/ImportForm.d.ts +1 -1
  103. package/esm/LinearGenomeView/components/ImportForm.js +40 -33
  104. package/esm/LinearGenomeView/components/ImportForm.js.map +1 -1
  105. package/esm/LinearGenomeView/components/LinearGenomeView.d.ts +1 -1
  106. package/esm/LinearGenomeView/components/LinearGenomeView.js +3 -25
  107. package/esm/LinearGenomeView/components/LinearGenomeView.js.map +1 -1
  108. package/esm/LinearGenomeView/components/LinearGenomeViewSvg.d.ts +1 -1
  109. package/esm/LinearGenomeView/components/LinearGenomeViewSvg.js +7 -5
  110. package/esm/LinearGenomeView/components/LinearGenomeViewSvg.js.map +1 -1
  111. package/esm/LinearGenomeView/components/OverviewRubberBand.d.ts +1 -1
  112. package/esm/LinearGenomeView/components/OverviewScaleBar.d.ts +27 -35
  113. package/esm/LinearGenomeView/components/RubberBand.d.ts +1 -1
  114. package/esm/LinearGenomeView/components/ScaleBar.d.ts +1 -1
  115. package/esm/LinearGenomeView/components/TrackContainer.d.ts +1 -1
  116. package/esm/LinearGenomeView/components/TrackContainer.js +13 -10
  117. package/esm/LinearGenomeView/components/TrackContainer.js.map +1 -1
  118. package/esm/LinearGenomeView/components/TrackLabel.js +9 -1
  119. package/esm/LinearGenomeView/components/TrackLabel.js.map +1 -1
  120. package/esm/LinearGenomeView/components/TracksContainer.d.ts +1 -1
  121. package/esm/LinearGenomeView/index.d.ts +273 -18
  122. package/esm/LinearGenomeView/index.js +409 -90
  123. package/esm/LinearGenomeView/index.js.map +1 -1
  124. package/esm/index.d.ts +30 -90
  125. package/esm/index.js +4 -25
  126. package/esm/index.js.map +1 -1
  127. package/package.json +2 -2
  128. package/src/BaseLinearDisplay/components/ServerSideRenderedBlockContent.tsx +2 -24
  129. package/src/BaseLinearDisplay/models/BaseLinearDisplayModel.tsx +695 -555
  130. package/src/BaseLinearDisplay/models/baseLinearDisplayConfigSchema.ts +20 -1
  131. package/src/BasicTrack/configSchema.ts +23 -0
  132. package/src/BasicTrack/index.ts +18 -0
  133. package/src/FeatureTrack/configSchema.ts +27 -0
  134. package/src/FeatureTrack/index.ts +21 -0
  135. package/src/LinearBareDisplay/configSchema.ts +15 -2
  136. package/src/LinearBareDisplay/model.ts +16 -0
  137. package/src/LinearBasicDisplay/configSchema.ts +19 -2
  138. package/src/LinearBasicDisplay/model.ts +63 -11
  139. package/src/LinearGenomeView/components/ImportForm.tsx +85 -67
  140. package/src/LinearGenomeView/components/LinearGenomeView.tsx +3 -33
  141. package/src/LinearGenomeView/components/LinearGenomeViewSvg.tsx +21 -18
  142. package/src/LinearGenomeView/components/TrackContainer.tsx +38 -27
  143. package/src/LinearGenomeView/components/TrackLabel.tsx +10 -1
  144. package/src/LinearGenomeView/components/__snapshots__/LinearGenomeView.test.tsx.snap +204 -204
  145. package/src/LinearGenomeView/index.test.ts +33 -26
  146. package/src/LinearGenomeView/index.tsx +471 -133
  147. package/src/index.ts +6 -46
@@ -1,6 +1,5 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
2
  import React from 'react'
3
- import TooLargeMessage from './TooLargeMessage'
4
3
  import { BaseDisplay } from '@jbrowse/core/pluggableElementTypes/models'
5
4
  import { getConf } from '@jbrowse/core/configuration'
6
5
  import { MenuItem } from '@jbrowse/core/ui'
@@ -24,9 +23,12 @@ import {
24
23
  } from '@jbrowse/core/util/tracks'
25
24
  import { autorun } from 'mobx'
26
25
  import { addDisposer, isAlive, types, Instance } from 'mobx-state-tree'
26
+
27
27
  // icons
28
28
  import MenuOpenIcon from '@mui/icons-material/MenuOpen'
29
29
 
30
+ // locals
31
+ import TooLargeMessage from './TooLargeMessage'
30
32
  import { LinearGenomeViewModel, ExportSvgOptions } from '../../LinearGenomeView'
31
33
  import { Tooltip } from '../components/BaseLinearDisplay'
32
34
  import BlockState, { renderBlockData } from './serverSideRenderedBlock'
@@ -64,598 +66,736 @@ function getDisplayStr(totalBytes: number) {
64
66
  const minDisplayHeight = 20
65
67
  const defaultDisplayHeight = 100
66
68
 
67
- export const BaseLinearDisplay = types
68
- .compose(
69
- 'BaseLinearDisplay',
70
- BaseDisplay,
71
- types.model({
72
- height: types.optional(
73
- types.refinement(
74
- 'displayHeight',
75
- types.number,
76
- n => n >= minDisplayHeight,
69
+ /**
70
+ * #stateModel BaseLinearDisplay
71
+ * extends `BaseDisplay`
72
+ */
73
+ function stateModelFactory() {
74
+ return types
75
+ .compose(
76
+ 'BaseLinearDisplay',
77
+ BaseDisplay,
78
+ types.model({
79
+ /**
80
+ * #property
81
+ */
82
+ height: types.optional(
83
+ types.refinement(
84
+ 'displayHeight',
85
+ types.number,
86
+ n => n >= minDisplayHeight,
87
+ ),
88
+ defaultDisplayHeight,
77
89
  ),
78
- defaultDisplayHeight,
79
- ),
80
- blockState: types.map(BlockState),
81
- userBpPerPxLimit: types.maybe(types.number),
82
- userByteSizeLimit: types.maybe(types.number),
83
- }),
84
- )
85
- .volatile(() => ({
86
- currBpPerPx: 0,
87
- message: '',
88
- featureIdUnderMouse: undefined as undefined | string,
89
- contextMenuFeature: undefined as undefined | Feature,
90
- scrollTop: 0,
91
- estimatedRegionStatsP: undefined as undefined | Promise<Stats>,
92
- estimatedRegionStats: undefined as undefined | Stats,
93
- }))
94
- .views(self => ({
95
- get blockType(): 'staticBlocks' | 'dynamicBlocks' {
96
- return 'staticBlocks'
97
- },
98
- get blockDefinitions() {
99
- const { blockType } = this
100
- const view = getContainingView(self) as LGV
101
- if (!view.initialized) {
102
- throw new Error('view not initialized yet')
103
- }
104
- return view[blockType]
105
- },
106
- }))
107
- .views(self => ({
108
- /**
109
- * how many milliseconds to wait for the display to
110
- * "settle" before re-rendering a block
111
- */
112
- get renderDelay() {
113
- return 50
114
- },
115
-
116
- get TooltipComponent(): React.FC<any> {
117
- return Tooltip as unknown as React.FC
118
- },
119
-
120
- /**
121
- * returns a string feature ID if the globally-selected object
122
- * is probably a feature
123
- */
124
- get selectedFeatureId() {
125
- if (isAlive(self)) {
126
- const { selection } = getSession(self)
127
- // does it quack like a feature?
128
- if (isFeature(selection)) {
129
- return selection.id()
130
- }
131
- }
132
- return undefined
133
- },
134
- /**
135
- * if a display-level message should be displayed instead of the blocks,
136
- * make this return a react component
137
- */
138
- get DisplayMessageComponent() {
139
- return undefined as undefined | React.FC<any>
140
- },
141
- }))
142
- .views(self => ({
143
- /**
144
- * a CompositeMap of `featureId -> feature obj` that
145
- * just looks in all the block data for that feature
146
- */
147
- get features() {
148
- const featureMaps = []
149
- for (const block of self.blockState.values()) {
150
- if (block && block.features) {
151
- featureMaps.push(block.features)
90
+ /**
91
+ * #property
92
+ * updated via autorun
93
+ */
94
+ blockState: types.map(BlockState),
95
+ /**
96
+ * #property
97
+ */
98
+ userBpPerPxLimit: types.maybe(types.number),
99
+ /**
100
+ * #property
101
+ */
102
+ userByteSizeLimit: types.maybe(types.number),
103
+ }),
104
+ )
105
+ .volatile(() => ({
106
+ currBpPerPx: 0,
107
+ message: '',
108
+ featureIdUnderMouse: undefined as undefined | string,
109
+ contextMenuFeature: undefined as undefined | Feature,
110
+ scrollTop: 0,
111
+ estimatedRegionStatsP: undefined as undefined | Promise<Stats>,
112
+ estimatedRegionStats: undefined as undefined | Stats,
113
+ }))
114
+ .views(self => ({
115
+ /**
116
+ * #getter
117
+ */
118
+ get blockType(): 'staticBlocks' | 'dynamicBlocks' {
119
+ return 'staticBlocks'
120
+ },
121
+ /**
122
+ * #getter
123
+ */
124
+ get blockDefinitions() {
125
+ const { blockType } = this
126
+ const view = getContainingView(self) as LGV
127
+ if (!view.initialized) {
128
+ throw new Error('view not initialized yet')
152
129
  }
153
- }
154
- return new CompositeMap(featureMaps)
155
- },
156
-
157
- get featureUnderMouse() {
158
- const feat = self.featureIdUnderMouse
159
- return feat ? this.features.get(feat) : undefined
160
- },
161
-
162
- getFeatureOverlapping(blockKey: string, x: number, y: number) {
163
- return self.blockState.get(blockKey)?.layout?.getByCoord(x, y)
164
- },
165
-
166
- getFeatureByID(blockKey: string, id: string): LayoutRecord | undefined {
167
- return self.blockState.get(blockKey)?.layout?.getByID(id)
168
- },
169
-
170
- // if block key is not supplied, can look at all blocks
171
- searchFeatureByID(id: string): LayoutRecord | undefined {
172
- let ret
173
- self.blockState.forEach(block => {
174
- const val = block?.layout?.getByID(id)
175
- if (val) {
176
- ret = val
130
+ return view[blockType]
131
+ },
132
+ }))
133
+ .views(self => ({
134
+ /**
135
+ * #getter
136
+ * how many milliseconds to wait for the display to
137
+ * "settle" before re-rendering a block
138
+ */
139
+ get renderDelay() {
140
+ return 50
141
+ },
142
+
143
+ /**
144
+ * #getter
145
+ */
146
+ get TooltipComponent(): React.FC<any> {
147
+ return Tooltip as unknown as React.FC
148
+ },
149
+
150
+ /**
151
+ * #getter
152
+ * returns a string feature ID if the globally-selected object
153
+ * is probably a feature
154
+ */
155
+ get selectedFeatureId() {
156
+ if (isAlive(self)) {
157
+ const { selection } = getSession(self)
158
+ // does it quack like a feature?
159
+ if (isFeature(selection)) {
160
+ return selection.id()
161
+ }
177
162
  }
178
- })
179
- return ret
180
- },
181
-
182
- get currentBytesRequested() {
183
- return self.estimatedRegionStats?.bytes || 0
184
- },
185
-
186
- get currentFeatureScreenDensity() {
187
- const view = getContainingView(self) as LGV
188
- return (self.estimatedRegionStats?.featureDensity || 0) * view.bpPerPx
189
- },
190
-
191
- get maxFeatureScreenDensity() {
192
- return getConf(self, 'maxFeatureScreenDensity')
193
- },
194
- get estimatedStatsReady() {
195
- return !!self.estimatedRegionStats
196
- },
197
-
198
- get maxAllowableBytes() {
199
- return (
200
- self.userByteSizeLimit ||
201
- self.estimatedRegionStats?.fetchSizeLimit ||
202
- (getConf(self, 'fetchSizeLimit') as number)
203
- )
204
- },
205
- }))
206
- .actions(self => ({
207
- // base display reload does nothing, see specialized displays for details
208
- setMessage(message: string) {
209
- self.message = message
210
- },
211
-
212
- afterAttach() {
213
- // watch the parent's blocks to update our block state when they change,
214
- // then we recreate the blocks on our own model (creating and deleting to
215
- // match the parent blocks)
216
- const blockWatchDisposer = autorun(() => {
217
- const blocksPresent: { [key: string]: boolean } = {}
218
- const view = getContainingView(self) as LGV
219
- if (view.initialized) {
220
- self.blockDefinitions.contentBlocks.forEach(block => {
221
- blocksPresent[block.key] = true
222
- if (!self.blockState.has(block.key)) {
223
- this.addBlock(block.key, block)
224
- }
225
- })
226
- self.blockState.forEach((_, key) => {
227
- if (!blocksPresent[key]) {
228
- this.deleteBlock(key)
229
- }
230
- })
163
+ return undefined
164
+ },
165
+ /**
166
+ * #getter
167
+ * if a display-level message should be displayed instead of the blocks,
168
+ * make this return a react component
169
+ */
170
+ get DisplayMessageComponent() {
171
+ return undefined as undefined | React.FC<any>
172
+ },
173
+ }))
174
+ .views(self => ({
175
+ /**
176
+ * #getter
177
+ * a CompositeMap of `featureId -> feature obj` that
178
+ * just looks in all the block data for that feature
179
+ */
180
+ get features() {
181
+ const featureMaps = []
182
+ for (const block of self.blockState.values()) {
183
+ if (block && block.features) {
184
+ featureMaps.push(block.features)
185
+ }
231
186
  }
232
- })
187
+ return new CompositeMap(featureMaps)
188
+ },
233
189
 
234
- addDisposer(self, blockWatchDisposer)
235
- },
190
+ /**
191
+ * #getter
192
+ */
193
+ get featureUnderMouse() {
194
+ const feat = self.featureIdUnderMouse
195
+ return feat ? this.features.get(feat) : undefined
196
+ },
236
197
 
237
- estimateRegionsStats(
238
- regions: Region[],
239
- opts: {
240
- headers?: Record<string, string>
241
- signal?: AbortSignal
242
- filters?: string[]
198
+ /**
199
+ * #getter
200
+ */
201
+ getFeatureOverlapping(blockKey: string, x: number, y: number) {
202
+ return self.blockState.get(blockKey)?.layout?.getByCoord(x, y)
243
203
  },
244
- ) {
245
- if (self.estimatedRegionStatsP) {
246
- return self.estimatedRegionStatsP
247
- }
248
204
 
249
- const { rpcManager } = getSession(self)
250
- const { adapterConfig } = self
251
- if (!adapterConfig) {
252
- // A track extending the base track might not have an adapter config
253
- // e.g. Apollo tracks don't use adapters
254
- return Promise.resolve({})
255
- }
256
- const sessionId = getRpcSessionId(self)
257
-
258
- const params = {
259
- sessionId,
260
- regions,
261
- adapterConfig,
262
- statusCallback: (message: string) => {
263
- if (isAlive(self)) {
264
- this.setMessage(message)
205
+ /**
206
+ * #getter
207
+ */
208
+ getFeatureByID(blockKey: string, id: string): LayoutRecord | undefined {
209
+ return self.blockState.get(blockKey)?.layout?.getByID(id)
210
+ },
211
+
212
+ /**
213
+ * #getter
214
+ */
215
+ searchFeatureByID(id: string): LayoutRecord | undefined {
216
+ let ret
217
+ self.blockState.forEach(block => {
218
+ const val = block?.layout?.getByID(id)
219
+ if (val) {
220
+ ret = val
265
221
  }
266
- },
267
- ...opts,
268
- }
222
+ })
223
+ return ret
224
+ },
269
225
 
270
- self.estimatedRegionStatsP = rpcManager
271
- .call(sessionId, 'CoreEstimateRegionStats', params)
272
- .catch(e => {
273
- this.setRegionStatsP(undefined)
274
- throw e
275
- }) as Promise<Stats>
276
-
277
- return self.estimatedRegionStatsP
278
- },
279
- setRegionStatsP(p?: Promise<Stats>) {
280
- self.estimatedRegionStatsP = p
281
- },
282
- setRegionStats(estimatedRegionStats?: Stats) {
283
- self.estimatedRegionStats = estimatedRegionStats
284
- },
285
- clearRegionStats() {
286
- self.estimatedRegionStatsP = undefined
287
- self.estimatedRegionStats = undefined
288
- },
289
- setHeight(displayHeight: number) {
290
- if (displayHeight > minDisplayHeight) {
291
- self.height = displayHeight
292
- } else {
293
- self.height = minDisplayHeight
294
- }
295
- return self.height
296
- },
297
- resizeHeight(distance: number) {
298
- const oldHeight = self.height
299
- const newHeight = this.setHeight(self.height + distance)
300
- return newHeight - oldHeight
301
- },
302
-
303
- setScrollTop(scrollTop: number) {
304
- self.scrollTop = scrollTop
305
- },
306
-
307
- updateStatsLimit(stats: Stats) {
308
- const view = getContainingView(self) as LGV
309
- if (stats.bytes) {
310
- self.userByteSizeLimit = stats.bytes
311
- } else {
312
- self.userBpPerPxLimit = view.bpPerPx
313
- }
314
- },
226
+ /**
227
+ * #getter
228
+ */
229
+ get currentBytesRequested() {
230
+ return self.estimatedRegionStats?.bytes || 0
231
+ },
315
232
 
316
- addBlock(key: string, block: BaseBlock) {
317
- self.blockState.set(
318
- key,
319
- BlockState.create({
320
- key,
321
- region: block.toRegion(),
322
- }),
323
- )
324
- },
325
- setCurrBpPerPx(n: number) {
326
- self.currBpPerPx = n
327
- },
328
- deleteBlock(key: string) {
329
- self.blockState.delete(key)
330
- },
331
- selectFeature(feature: Feature) {
332
- const session = getSession(self)
333
- if (isSessionModelWithWidgets(session)) {
334
- const featureWidget = session.addWidget(
335
- 'BaseFeatureWidget',
336
- 'baseFeature',
337
- {
338
- view: getContainingView(self),
339
- track: getContainingTrack(self),
340
- featureData: feature.toJSON(),
341
- },
233
+ /**
234
+ * #getter
235
+ */
236
+ get currentFeatureScreenDensity() {
237
+ const view = getContainingView(self) as LGV
238
+ return (self.estimatedRegionStats?.featureDensity || 0) * view.bpPerPx
239
+ },
240
+
241
+ /**
242
+ * #getter
243
+ */
244
+ get maxFeatureScreenDensity() {
245
+ return getConf(self, 'maxFeatureScreenDensity')
246
+ },
247
+ /**
248
+ * #getter
249
+ */
250
+ get estimatedStatsReady() {
251
+ return !!self.estimatedRegionStats
252
+ },
253
+
254
+ /**
255
+ * #getter
256
+ */
257
+ get maxAllowableBytes() {
258
+ return (
259
+ self.userByteSizeLimit ||
260
+ self.estimatedRegionStats?.fetchSizeLimit ||
261
+ (getConf(self, 'fetchSizeLimit') as number)
342
262
  )
263
+ },
264
+ }))
265
+ .actions(self => ({
266
+ /**
267
+ * #action
268
+ */
269
+ setMessage(message: string) {
270
+ self.message = message
271
+ },
343
272
 
344
- session.showWidget(featureWidget)
345
- }
346
- if (isSelectionContainer(session)) {
347
- session.setSelection(feature)
348
- }
349
- },
350
- clearFeatureSelection() {
351
- const session = getSession(self)
352
- session.clearSelection()
353
- },
354
- setFeatureIdUnderMouse(feature: string | undefined) {
355
- self.featureIdUnderMouse = feature
356
- },
357
- reload() {
358
- ;[...self.blockState.values()].map(val => val.doReload())
359
- },
360
- setContextMenuFeature(feature?: Feature) {
361
- self.contextMenuFeature = feature
362
- },
363
- }))
364
- .views(self => ({
365
- // region is too large if:
366
- // - stats are ready
367
- // - region is greater than 20kb (don't warn when zoomed in less than that)
368
- // - and bytes > max allowed bytes || curr density>max density
369
- get regionTooLarge() {
370
- const view = getContainingView(self) as LGV
371
- if (!self.estimatedStatsReady || view.dynamicBlocks.totalBp < 20_000) {
372
- return false
373
- }
374
- const bpLimitOrDensity = self.userBpPerPxLimit
375
- ? view.bpPerPx > self.userBpPerPxLimit
376
- : self.currentFeatureScreenDensity > self.maxFeatureScreenDensity
377
-
378
- return (
379
- self.currentBytesRequested > self.maxAllowableBytes || bpLimitOrDensity
380
- )
381
- },
382
-
383
- // only shows a message of bytes requested is defined, the feature density
384
- // based stats don't produce any helpful message besides to zoom in
385
- get regionTooLargeReason() {
386
- const req = self.currentBytesRequested
387
- const max = self.maxAllowableBytes
388
-
389
- return req && req > max
390
- ? `Requested too much data (${getDisplayStr(req)})`
391
- : ''
392
- },
393
- }))
394
- .actions(self => {
395
- const { reload: superReload } = self
396
-
397
- return {
398
- async reload() {
399
- self.setError()
400
- const aborter = new AbortController()
401
- const view = getContainingView(self) as LGV
273
+ afterAttach() {
274
+ // watch the parent's blocks to update our block state when they change,
275
+ // then we recreate the blocks on our own model (creating and deleting to
276
+ // match the parent blocks)
277
+ const blockWatchDisposer = autorun(() => {
278
+ const blocksPresent: { [key: string]: boolean } = {}
279
+ const view = getContainingView(self) as LGV
280
+ if (view.initialized) {
281
+ self.blockDefinitions.contentBlocks.forEach(block => {
282
+ blocksPresent[block.key] = true
283
+ if (!self.blockState.has(block.key)) {
284
+ this.addBlock(block.key, block)
285
+ }
286
+ })
287
+ self.blockState.forEach((_, key) => {
288
+ if (!blocksPresent[key]) {
289
+ this.deleteBlock(key)
290
+ }
291
+ })
292
+ }
293
+ })
294
+
295
+ addDisposer(self, blockWatchDisposer)
296
+ },
402
297
 
403
- // extra check for contentBlocks.length
404
- // https://github.com/GMOD/jbrowse-components/issues/2694
405
- if (!view.initialized || !view.staticBlocks.contentBlocks.length) {
406
- return
298
+ /**
299
+ * #action
300
+ */
301
+ estimateRegionsStats(
302
+ regions: Region[],
303
+ opts: {
304
+ headers?: Record<string, string>
305
+ signal?: AbortSignal
306
+ filters?: string[]
307
+ },
308
+ ) {
309
+ if (self.estimatedRegionStatsP) {
310
+ return self.estimatedRegionStatsP
407
311
  }
408
312
 
409
- try {
410
- self.estimatedRegionStatsP = self.estimateRegionsStats(
411
- view.staticBlocks.contentBlocks,
412
- { signal: aborter.signal },
413
- )
414
- const estimatedRegionStats = await self.estimatedRegionStatsP
313
+ const { rpcManager } = getSession(self)
314
+ const { adapterConfig } = self
315
+ if (!adapterConfig) {
316
+ // A track extending the base track might not have an adapter config
317
+ // e.g. Apollo tracks don't use adapters
318
+ return Promise.resolve({})
319
+ }
320
+ const sessionId = getRpcSessionId(self)
321
+
322
+ const params = {
323
+ sessionId,
324
+ regions,
325
+ adapterConfig,
326
+ statusCallback: (message: string) => {
327
+ if (isAlive(self)) {
328
+ this.setMessage(message)
329
+ }
330
+ },
331
+ ...opts,
332
+ }
415
333
 
416
- if (isAlive(self)) {
417
- self.setRegionStats(estimatedRegionStats)
418
- superReload()
419
- } else {
420
- return
421
- }
422
- } catch (e) {
423
- console.error(e)
424
- self.setError(e)
334
+ self.estimatedRegionStatsP = rpcManager
335
+ .call(sessionId, 'CoreEstimateRegionStats', params)
336
+ .catch(e => {
337
+ this.setRegionStatsP(undefined)
338
+ throw e
339
+ }) as Promise<Stats>
340
+
341
+ return self.estimatedRegionStatsP
342
+ },
343
+ /**
344
+ * #action
345
+ */
346
+ setRegionStatsP(p?: Promise<Stats>) {
347
+ self.estimatedRegionStatsP = p
348
+ },
349
+ /**
350
+ * #action
351
+ */
352
+ setRegionStats(estimatedRegionStats?: Stats) {
353
+ self.estimatedRegionStats = estimatedRegionStats
354
+ },
355
+ /**
356
+ * #action
357
+ */
358
+ clearRegionStats() {
359
+ self.estimatedRegionStatsP = undefined
360
+ self.estimatedRegionStats = undefined
361
+ },
362
+ /**
363
+ * #action
364
+ */
365
+ setHeight(displayHeight: number) {
366
+ if (displayHeight > minDisplayHeight) {
367
+ self.height = displayHeight
368
+ } else {
369
+ self.height = minDisplayHeight
425
370
  }
371
+ return self.height
372
+ },
373
+ /**
374
+ * #action
375
+ */
376
+ resizeHeight(distance: number) {
377
+ const oldHeight = self.height
378
+ const newHeight = this.setHeight(self.height + distance)
379
+ return newHeight - oldHeight
426
380
  },
427
- afterAttach() {
428
- // this autorun performs stats estimation
429
- //
430
- // the chain of events calls estimateRegionsStats against the data
431
- // adapter which by default uses featureDensity, but can also respond
432
- // with a byte size estimate and fetch size limit (data adapter can
433
- // define what is too much data)
434
- addDisposer(
435
- self,
436
- autorun(
437
- async () => {
438
- try {
439
- const aborter = new AbortController()
440
- const view = getContainingView(self) as LGV
441
-
442
- // extra check for contentBlocks.length
443
- // https://github.com/GMOD/jbrowse-components/issues/2694
444
- if (
445
- !view.initialized ||
446
- !view.staticBlocks.contentBlocks.length
447
- ) {
448
- return
449
- }
450
381
 
451
- // don't re-estimate featureDensity even if zoom level changes,
452
- // jbrowse1-style assume it's sort of representative
453
- if (self.estimatedRegionStats?.featureDensity !== undefined) {
454
- self.setCurrBpPerPx(view.bpPerPx)
455
- return
456
- }
382
+ /**
383
+ * #action
384
+ */
385
+ setScrollTop(scrollTop: number) {
386
+ self.scrollTop = scrollTop
387
+ },
457
388
 
458
- // we estimate stats once at a given zoom level
459
- if (view.bpPerPx === self.currBpPerPx) {
460
- return
461
- }
389
+ /**
390
+ * #action
391
+ */
392
+ updateStatsLimit(stats: Stats) {
393
+ const view = getContainingView(self) as LGV
394
+ if (stats.bytes) {
395
+ self.userByteSizeLimit = stats.bytes
396
+ } else {
397
+ self.userBpPerPxLimit = view.bpPerPx
398
+ }
399
+ },
462
400
 
463
- self.clearRegionStats()
464
- self.setCurrBpPerPx(view.bpPerPx)
465
- const statsP = self.estimateRegionsStats(
466
- view.staticBlocks.contentBlocks,
467
- { signal: aborter.signal },
468
- )
469
- self.setRegionStatsP(statsP)
470
- const estimatedRegionStats = await statsP
471
-
472
- if (isAlive(self)) {
473
- self.setRegionStats(estimatedRegionStats)
474
- }
475
- } catch (e) {
476
- if (!isAbortException(e) && isAlive(self)) {
477
- console.error(e)
478
- self.setError(e)
479
- }
480
- }
481
- },
482
- { delay: 500 },
483
- ),
401
+ /**
402
+ * #action
403
+ */
404
+ addBlock(key: string, block: BaseBlock) {
405
+ self.blockState.set(
406
+ key,
407
+ BlockState.create({
408
+ key,
409
+ region: block.toRegion(),
410
+ }),
484
411
  )
485
412
  },
486
- }
487
- })
488
- .views(self => ({
489
- regionCannotBeRenderedText(_region: Region) {
490
- return self.regionTooLarge ? 'Force load to see features' : ''
491
- },
492
-
493
- /**
494
- * @param region -
495
- * @returns falsy if the region is fine to try rendering. Otherwise,
496
- * return a react node + string of text.
497
- * string of text describes why it cannot be rendered
498
- * react node allows user to force load at current setting
499
- */
500
- regionCannotBeRendered(_region: Region) {
501
- const { regionTooLarge } = self
502
- return regionTooLarge ? <TooLargeMessage model={self} /> : null
503
- },
504
-
505
- trackMenuItems(): MenuItem[] {
506
- return []
507
- },
508
-
509
- contextMenuItems() {
510
- return self.contextMenuFeature
511
- ? [
413
+ /**
414
+ * #action
415
+ */
416
+ setCurrBpPerPx(n: number) {
417
+ self.currBpPerPx = n
418
+ },
419
+ /**
420
+ * #action
421
+ */
422
+ deleteBlock(key: string) {
423
+ self.blockState.delete(key)
424
+ },
425
+ /**
426
+ * #action
427
+ */
428
+ selectFeature(feature: Feature) {
429
+ const session = getSession(self)
430
+ if (isSessionModelWithWidgets(session)) {
431
+ const featureWidget = session.addWidget(
432
+ 'BaseFeatureWidget',
433
+ 'baseFeature',
512
434
  {
513
- label: 'Open feature details',
514
- icon: MenuOpenIcon,
515
- onClick: () => {
516
- if (self.contextMenuFeature) {
517
- self.selectFeature(self.contextMenuFeature)
518
- }
519
- },
435
+ view: getContainingView(self),
436
+ track: getContainingTrack(self),
437
+ featureData: feature.toJSON(),
520
438
  },
521
- ]
522
- : []
523
- },
524
- renderProps() {
525
- const view = getContainingView(self) as LGV
439
+ )
440
+
441
+ session.showWidget(featureWidget)
442
+ }
443
+ if (isSelectionContainer(session)) {
444
+ session.setSelection(feature)
445
+ }
446
+ },
447
+ /**
448
+ * #action
449
+ */
450
+ clearFeatureSelection() {
451
+ const session = getSession(self)
452
+ session.clearSelection()
453
+ },
454
+ /**
455
+ * #action
456
+ */
457
+ setFeatureIdUnderMouse(feature: string | undefined) {
458
+ self.featureIdUnderMouse = feature
459
+ },
460
+ /**
461
+ * #action
462
+ */
463
+ reload() {
464
+ ;[...self.blockState.values()].map(val => val.doReload())
465
+ },
466
+ /**
467
+ * #action
468
+ */
469
+ setContextMenuFeature(feature?: Feature) {
470
+ self.contextMenuFeature = feature
471
+ },
472
+ }))
473
+ .views(self => ({
474
+ /**
475
+ * #getter
476
+ * region is too large if:
477
+ * - stats are ready
478
+ * - region is greater than 20kb (don't warn when zoomed in less than that)
479
+ * - and bytes is greater than max allowed bytes or density greater than max density
480
+ */
481
+ get regionTooLarge() {
482
+ const view = getContainingView(self) as LGV
483
+ if (!self.estimatedStatsReady || view.dynamicBlocks.totalBp < 20_000) {
484
+ return false
485
+ }
486
+ const bpLimitOrDensity = self.userBpPerPxLimit
487
+ ? view.bpPerPx > self.userBpPerPxLimit
488
+ : self.currentFeatureScreenDensity > self.maxFeatureScreenDensity
489
+
490
+ return (
491
+ self.currentBytesRequested > self.maxAllowableBytes ||
492
+ bpLimitOrDensity
493
+ )
494
+ },
495
+
496
+ /**
497
+ * #getter
498
+ * only shows a message of bytes requested is defined, the feature density
499
+ * based stats don't produce any helpful message besides to zoom in
500
+ */
501
+ get regionTooLargeReason() {
502
+ const req = self.currentBytesRequested
503
+ const max = self.maxAllowableBytes
504
+
505
+ return req && req > max
506
+ ? `Requested too much data (${getDisplayStr(req)})`
507
+ : ''
508
+ },
509
+ }))
510
+ .actions(self => {
511
+ const { reload: superReload } = self
512
+
526
513
  return {
527
- ...(getParentRenderProps(self) as any),
528
- notReady:
529
- self.currBpPerPx !== view.bpPerPx || !self.estimatedRegionStats,
530
- rpcDriverName: self.rpcDriverName,
531
- displayModel: self,
532
- onFeatureClick(_: unknown, featureId?: string) {
533
- const f = featureId || self.featureIdUnderMouse
534
- if (!f) {
535
- self.clearFeatureSelection()
536
- } else {
537
- const feature = self.features.get(f)
538
- if (feature) {
539
- self.selectFeature(feature)
514
+ /**
515
+ * #action
516
+ */
517
+ async reload() {
518
+ self.setError()
519
+ const aborter = new AbortController()
520
+ const view = getContainingView(self) as LGV
521
+
522
+ // extra check for contentBlocks.length
523
+ // https://github.com/GMOD/jbrowse-components/issues/2694
524
+ if (!view.initialized || !view.staticBlocks.contentBlocks.length) {
525
+ return
526
+ }
527
+
528
+ try {
529
+ self.estimatedRegionStatsP = self.estimateRegionsStats(
530
+ view.staticBlocks.contentBlocks,
531
+ { signal: aborter.signal },
532
+ )
533
+ const estimatedRegionStats = await self.estimatedRegionStatsP
534
+
535
+ if (isAlive(self)) {
536
+ self.setRegionStats(estimatedRegionStats)
537
+ superReload()
538
+ } else {
539
+ return
540
540
  }
541
+ } catch (e) {
542
+ console.error(e)
543
+ self.setError(e)
541
544
  }
542
545
  },
543
- onClick() {
544
- self.clearFeatureSelection()
546
+ afterAttach() {
547
+ // this autorun performs stats estimation
548
+ //
549
+ // the chain of events calls estimateRegionsStats against the data
550
+ // adapter which by default uses featureDensity, but can also respond
551
+ // with a byte size estimate and fetch size limit (data adapter can
552
+ // define what is too much data)
553
+ addDisposer(
554
+ self,
555
+ autorun(
556
+ async () => {
557
+ try {
558
+ const aborter = new AbortController()
559
+ const view = getContainingView(self) as LGV
560
+
561
+ // extra check for contentBlocks.length
562
+ // https://github.com/GMOD/jbrowse-components/issues/2694
563
+ if (
564
+ !view.initialized ||
565
+ !view.staticBlocks.contentBlocks.length
566
+ ) {
567
+ return
568
+ }
569
+
570
+ // don't re-estimate featureDensity even if zoom level changes,
571
+ // jbrowse1-style assume it's sort of representative
572
+ if (self.estimatedRegionStats?.featureDensity !== undefined) {
573
+ self.setCurrBpPerPx(view.bpPerPx)
574
+ return
575
+ }
576
+
577
+ // we estimate stats once at a given zoom level
578
+ if (view.bpPerPx === self.currBpPerPx) {
579
+ return
580
+ }
581
+
582
+ self.clearRegionStats()
583
+ self.setCurrBpPerPx(view.bpPerPx)
584
+ const statsP = self.estimateRegionsStats(
585
+ view.staticBlocks.contentBlocks,
586
+ { signal: aborter.signal },
587
+ )
588
+ self.setRegionStatsP(statsP)
589
+ const estimatedRegionStats = await statsP
590
+
591
+ if (isAlive(self)) {
592
+ self.setRegionStats(estimatedRegionStats)
593
+ }
594
+ } catch (e) {
595
+ if (!isAbortException(e) && isAlive(self)) {
596
+ console.error(e)
597
+ self.setError(e)
598
+ }
599
+ }
600
+ },
601
+ { delay: 500 },
602
+ ),
603
+ )
545
604
  },
546
- // similar to click but opens a menu with further options
547
- onFeatureContextMenu(_: unknown, featureId?: string) {
548
- const f = featureId || self.featureIdUnderMouse
549
- if (!f) {
605
+ }
606
+ })
607
+ .views(self => ({
608
+ /**
609
+ * #method
610
+ */
611
+ regionCannotBeRenderedText(_region: Region) {
612
+ return self.regionTooLarge ? 'Force load to see features' : ''
613
+ },
614
+
615
+ /**
616
+ * #method
617
+ * @param region -
618
+ * @returns falsy if the region is fine to try rendering. Otherwise,
619
+ * return a react node + string of text.
620
+ * string of text describes why it cannot be rendered
621
+ * react node allows user to force load at current setting
622
+ */
623
+ regionCannotBeRendered(_region: Region) {
624
+ const { regionTooLarge } = self
625
+ return regionTooLarge ? <TooLargeMessage model={self} /> : null
626
+ },
627
+
628
+ /**
629
+ * #method
630
+ */
631
+ trackMenuItems(): MenuItem[] {
632
+ return []
633
+ },
634
+
635
+ /**
636
+ * #method
637
+ */
638
+ contextMenuItems() {
639
+ return self.contextMenuFeature
640
+ ? [
641
+ {
642
+ label: 'Open feature details',
643
+ icon: MenuOpenIcon,
644
+ onClick: () => {
645
+ if (self.contextMenuFeature) {
646
+ self.selectFeature(self.contextMenuFeature)
647
+ }
648
+ },
649
+ },
650
+ ]
651
+ : []
652
+ },
653
+ /**
654
+ * #method
655
+ */
656
+ renderProps() {
657
+ const view = getContainingView(self) as LGV
658
+ return {
659
+ ...(getParentRenderProps(self) as any),
660
+ notReady:
661
+ self.currBpPerPx !== view.bpPerPx || !self.estimatedRegionStats,
662
+ rpcDriverName: self.rpcDriverName,
663
+ displayModel: self,
664
+ onFeatureClick(_: unknown, featureId?: string) {
665
+ const f = featureId || self.featureIdUnderMouse
666
+ if (!f) {
667
+ self.clearFeatureSelection()
668
+ } else {
669
+ const feature = self.features.get(f)
670
+ if (feature) {
671
+ self.selectFeature(feature)
672
+ }
673
+ }
674
+ },
675
+ onClick() {
550
676
  self.clearFeatureSelection()
551
- } else {
552
- // feature id under mouse passed to context menu
553
- self.setContextMenuFeature(self.features.get(f))
554
- }
555
- },
677
+ },
678
+ // similar to click but opens a menu with further options
679
+ onFeatureContextMenu(_: unknown, featureId?: string) {
680
+ const f = featureId || self.featureIdUnderMouse
681
+ if (!f) {
682
+ self.clearFeatureSelection()
683
+ } else {
684
+ // feature id under mouse passed to context menu
685
+ self.setContextMenuFeature(self.features.get(f))
686
+ }
687
+ },
556
688
 
557
- onMouseMove(_: unknown, featureId?: string) {
558
- self.setFeatureIdUnderMouse(featureId)
559
- },
689
+ onMouseMove(_: unknown, featureId?: string) {
690
+ self.setFeatureIdUnderMouse(featureId)
691
+ },
560
692
 
561
- onMouseLeave(_: unknown) {
562
- self.setFeatureIdUnderMouse(undefined)
563
- },
693
+ onMouseLeave(_: unknown) {
694
+ self.setFeatureIdUnderMouse(undefined)
695
+ },
564
696
 
565
- onContextMenu() {
566
- self.setContextMenuFeature(undefined)
567
- self.clearFeatureSelection()
568
- },
569
- }
570
- },
571
- }))
572
- .actions(self => ({
573
- async renderSvg(opts: ExportSvgOptions & { overrideHeight: number }) {
574
- const { height, id } = self
575
- const { overrideHeight } = opts
576
- const view = getContainingView(self) as LGV
577
- const { offsetPx: viewOffsetPx, roundedDynamicBlocks, width } = view
578
-
579
- const renderings = await Promise.all(
580
- roundedDynamicBlocks.map(block => {
581
- const blockState = BlockState.create({
582
- key: block.key,
583
- region: block,
584
- })
585
-
586
- // regionCannotBeRendered can return jsx so look for plaintext
587
- // version, or just get the default if none available
588
- const cannotBeRenderedReason =
589
- self.regionCannotBeRenderedText(block) ||
590
- self.regionCannotBeRendered(block)
591
-
592
- if (cannotBeRenderedReason) {
593
- return {
594
- reactElement: (
595
- <>
596
- <rect x={0} y={0} width={width} height={20} fill="#aaa" />
597
- <text x={0} y={15}>
598
- {cannotBeRenderedReason}
599
- </text>
600
- </>
601
- ),
697
+ onContextMenu() {
698
+ self.setContextMenuFeature(undefined)
699
+ self.clearFeatureSelection()
700
+ },
701
+ }
702
+ },
703
+ }))
704
+ .actions(self => ({
705
+ /**
706
+ * #method
707
+ */
708
+ async renderSvg(opts: ExportSvgOptions & { overrideHeight: number }) {
709
+ const { height, id } = self
710
+ const { overrideHeight } = opts
711
+ const view = getContainingView(self) as LGV
712
+ const { offsetPx: viewOffsetPx, roundedDynamicBlocks, width } = view
713
+
714
+ const renderings = await Promise.all(
715
+ roundedDynamicBlocks.map(block => {
716
+ const blockState = BlockState.create({
717
+ key: block.key,
718
+ region: block,
719
+ })
720
+
721
+ // regionCannotBeRendered can return jsx so look for plaintext
722
+ // version, or just get the default if none available
723
+ const cannotBeRenderedReason =
724
+ self.regionCannotBeRenderedText(block) ||
725
+ self.regionCannotBeRendered(block)
726
+
727
+ if (cannotBeRenderedReason) {
728
+ return {
729
+ reactElement: (
730
+ <>
731
+ <rect x={0} y={0} width={width} height={20} fill="#aaa" />
732
+ <text x={0} y={15}>
733
+ {cannotBeRenderedReason}
734
+ </text>
735
+ </>
736
+ ),
737
+ }
602
738
  }
603
- }
604
739
 
605
- const { rpcManager, renderArgs, renderProps, rendererType } =
606
- renderBlockData(blockState, self)
607
-
608
- return rendererType.renderInClient(rpcManager, {
609
- ...renderArgs,
610
- ...renderProps,
611
- viewParams: getViewParams(self, true),
612
- exportSVG: opts,
613
- })
614
- }),
615
- )
616
-
617
- return (
618
- <>
619
- {renderings.map((rendering, index) => {
620
- const { offsetPx } = roundedDynamicBlocks[index]
621
- const offset = offsetPx - viewOffsetPx
622
- const clipid = getId(id, index)
623
-
624
- return (
625
- <React.Fragment key={`frag-${index}`}>
626
- <defs>
627
- <clipPath id={clipid}>
628
- <rect
629
- x={0}
630
- y={0}
631
- width={width}
632
- height={overrideHeight || height}
633
- />
634
- </clipPath>
635
- </defs>
636
- <g transform={`translate(${offset} 0)`}>
637
- <g clipPath={`url(#${clipid})`}>
638
- {React.isValidElement(rendering.reactElement) ? (
639
- rendering.reactElement
640
- ) : (
641
- // eslint-disable-next-line react/no-danger
642
- <g dangerouslySetInnerHTML={{ __html: rendering.html }} />
643
- )}
740
+ const { rpcManager, renderArgs, renderProps, rendererType } =
741
+ renderBlockData(blockState, self)
742
+
743
+ return rendererType.renderInClient(rpcManager, {
744
+ ...renderArgs,
745
+ ...renderProps,
746
+ viewParams: getViewParams(self, true),
747
+ exportSVG: opts,
748
+ })
749
+ }),
750
+ )
751
+
752
+ return (
753
+ <>
754
+ {renderings.map((rendering, index) => {
755
+ const { offsetPx } = roundedDynamicBlocks[index]
756
+ const offset = offsetPx - viewOffsetPx
757
+ const clipid = getId(id, index)
758
+
759
+ return (
760
+ <React.Fragment key={`frag-${index}`}>
761
+ <defs>
762
+ <clipPath id={clipid}>
763
+ <rect
764
+ x={0}
765
+ y={0}
766
+ width={width}
767
+ height={overrideHeight || height}
768
+ />
769
+ </clipPath>
770
+ </defs>
771
+ <g transform={`translate(${offset} 0)`}>
772
+ <g clipPath={`url(#${clipid})`}>
773
+ {React.isValidElement(rendering.reactElement) ? (
774
+ rendering.reactElement
775
+ ) : (
776
+ <g
777
+ /* eslint-disable-next-line react/no-danger */
778
+ dangerouslySetInnerHTML={{ __html: rendering.html }}
779
+ />
780
+ )}
781
+ </g>
644
782
  </g>
645
- </g>
646
- </React.Fragment>
647
- )
648
- })}
649
- </>
650
- )
651
- },
652
- }))
653
- .postProcessSnapshot(self => {
654
- // xref https://github.com/mobxjs/mobx-state-tree/issues/1524 for Omit
655
- const r = self as Omit<typeof self, symbol>
656
- const { blockState, ...rest } = r
657
- return rest
658
- })
783
+ </React.Fragment>
784
+ )
785
+ })}
786
+ </>
787
+ )
788
+ },
789
+ }))
790
+ .postProcessSnapshot(self => {
791
+ // xref https://github.com/mobxjs/mobx-state-tree/issues/1524 for Omit
792
+ const r = self as Omit<typeof self, symbol>
793
+ const { blockState, ...rest } = r
794
+ return rest
795
+ })
796
+ }
797
+
798
+ export const BaseLinearDisplay = stateModelFactory()
659
799
 
660
800
  export type BaseLinearDisplayStateModel = typeof BaseLinearDisplay
661
801
  export type BaseLinearDisplayModel = Instance<BaseLinearDisplayStateModel>