@apollo-annotation/jbrowse-plugin-apollo 0.1.18 → 0.1.20

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 (85) hide show
  1. package/dist/index.esm.js +3189 -3575
  2. package/dist/index.esm.js.map +1 -1
  3. package/dist/jbrowse-plugin-apollo.cjs.development.js +3185 -3570
  4. package/dist/jbrowse-plugin-apollo.cjs.development.js.map +1 -1
  5. package/dist/jbrowse-plugin-apollo.cjs.production.min.js +1 -1
  6. package/dist/jbrowse-plugin-apollo.cjs.production.min.js.map +1 -1
  7. package/dist/jbrowse-plugin-apollo.umd.development.js +14884 -15905
  8. package/dist/jbrowse-plugin-apollo.umd.development.js.map +1 -1
  9. package/dist/jbrowse-plugin-apollo.umd.production.min.js +1 -1
  10. package/dist/jbrowse-plugin-apollo.umd.production.min.js.map +1 -1
  11. package/package.json +33 -33
  12. package/src/ApolloInternetAccount/addMenuItems.ts +18 -0
  13. package/src/ApolloInternetAccount/components/AuthTypeSelector.tsx +1 -0
  14. package/src/ApolloInternetAccount/configSchema.ts +5 -2
  15. package/src/ApolloInternetAccount/model.ts +14 -5
  16. package/src/ApolloRefNameAliasAdapter/ApolloRefNameAliasAdapter.ts +94 -0
  17. package/src/ApolloRefNameAliasAdapter/configSchema.ts +12 -0
  18. package/src/ApolloRefNameAliasAdapter/index.ts +21 -0
  19. package/src/ApolloSequenceAdapter/ApolloSequenceAdapter.ts +1 -0
  20. package/src/ApolloSixFrameRenderer/components/ApolloRendering.tsx +10 -10
  21. package/src/ApolloTextSearchAdapter/ApolloTextSearchAdapter.ts +35 -32
  22. package/src/BackendDrivers/BackendDriver.ts +8 -0
  23. package/src/BackendDrivers/CollaborationServerDriver.ts +49 -1
  24. package/src/BackendDrivers/DesktopFileDriver.ts +14 -1
  25. package/src/BackendDrivers/InMemoryFileDriver.ts +17 -1
  26. package/src/ChangeManager.ts +1 -1
  27. package/src/FeatureDetailsWidget/ApolloFeatureDetailsWidget.tsx +5 -25
  28. package/src/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.tsx +82 -0
  29. package/src/FeatureDetailsWidget/Attributes.tsx +11 -3
  30. package/src/FeatureDetailsWidget/BasicInformation.tsx +38 -30
  31. package/src/FeatureDetailsWidget/Sequence.tsx +7 -7
  32. package/src/FeatureDetailsWidget/TranscriptBasic.tsx +446 -0
  33. package/src/FeatureDetailsWidget/TranscriptSequence.tsx +365 -0
  34. package/src/FeatureDetailsWidget/index.ts +2 -0
  35. package/src/FeatureDetailsWidget/model.ts +77 -9
  36. package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +0 -2
  37. package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +453 -380
  38. package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +520 -0
  39. package/src/LinearApolloDisplay/glyphs/GenericChildGlyph.ts +138 -134
  40. package/src/LinearApolloDisplay/glyphs/Glyph.ts +38 -370
  41. package/src/LinearApolloDisplay/glyphs/index.ts +1 -2
  42. package/src/LinearApolloDisplay/stateModel/base.ts +3 -6
  43. package/src/LinearApolloDisplay/stateModel/getGlyph.ts +30 -30
  44. package/src/LinearApolloDisplay/stateModel/index.ts +5 -1
  45. package/src/LinearApolloDisplay/stateModel/layouts.ts +32 -24
  46. package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +206 -217
  47. package/src/LinearApolloDisplay/stateModel/rendering.ts +43 -67
  48. package/src/OntologyManager/OntologyStore/fulltext.ts +1 -1
  49. package/src/OntologyManager/OntologyStore/index.ts +2 -1
  50. package/src/OntologyManager/index.ts +6 -2
  51. package/src/OntologyManager/util.ts +2 -2
  52. package/src/SixFrameFeatureDisplay/stateModel.ts +15 -10
  53. package/src/TabularEditor/HybridGrid/ChangeHandling.ts +21 -46
  54. package/src/TabularEditor/HybridGrid/Feature.tsx +31 -82
  55. package/src/TabularEditor/HybridGrid/FeatureAttributes.tsx +3 -2
  56. package/src/TabularEditor/HybridGrid/HybridGrid.tsx +2 -3
  57. package/src/TabularEditor/HybridGrid/NumberCell.tsx +1 -0
  58. package/src/TabularEditor/HybridGrid/featureContextMenuItems.ts +46 -5
  59. package/src/TabularEditor/model.ts +5 -3
  60. package/src/components/AddAssembly.tsx +15 -9
  61. package/src/components/AddChildFeature.tsx +7 -73
  62. package/src/components/AddFeature.tsx +2 -57
  63. package/src/components/AddRefSeqAliases.tsx +285 -0
  64. package/src/components/CopyFeature.tsx +16 -33
  65. package/src/components/DeleteFeature.tsx +4 -6
  66. package/src/components/ImportFeatures.tsx +6 -3
  67. package/src/components/LogOut.tsx +105 -0
  68. package/src/components/ManageChecks.tsx +1 -0
  69. package/src/components/ManageUsers.tsx +21 -1
  70. package/src/components/ModifyFeatureAttribute.tsx +2 -2
  71. package/src/components/OntologyTermAutocomplete.tsx +0 -2
  72. package/src/components/OntologyTermMultiSelect.tsx +1 -0
  73. package/src/components/OpenLocalFile.tsx +6 -5
  74. package/src/components/ViewChangeLog.tsx +1 -0
  75. package/src/components/ViewCheckResults.tsx +1 -0
  76. package/src/components/index.ts +4 -0
  77. package/src/extensions/annotationFromPileup.ts +10 -16
  78. package/src/index.ts +57 -3
  79. package/src/session/ClientDataStore.ts +49 -46
  80. package/src/session/session.ts +186 -114
  81. package/src/util/loadAssemblyIntoClient.ts +4 -210
  82. package/src/FeatureDetailsWidget/RelatedFeature.tsx +0 -97
  83. package/src/LinearApolloDisplay/glyphs/CanonicalGeneGlyph.ts +0 -1204
  84. package/src/LinearApolloDisplay/glyphs/ImplicitExonGeneGlyph.ts +0 -716
  85. package/src/LinearApolloDisplay/stateModel/glyphs.ts +0 -47
@@ -1,427 +1,500 @@
1
- /* eslint-disable @typescript-eslint/no-unnecessary-condition */
2
- /* eslint-disable @typescript-eslint/unbound-method */
3
- import { AnnotationFeatureI } from '@apollo-annotation/mst'
4
- import {
5
- LocationEndChange,
6
- LocationStartChange,
7
- } from '@apollo-annotation/shared'
1
+ import { AnnotationFeature } from '@apollo-annotation/mst'
8
2
  import { Theme, alpha } from '@mui/material'
3
+ import { MenuItem } from '@jbrowse/core/ui'
4
+
5
+ import { AbstractSessionModel, SessionWithWidgets } from '@jbrowse/core/util'
6
+
7
+ import {
8
+ AddChildFeature,
9
+ CopyFeature,
10
+ DeleteFeature,
11
+ ModifyFeatureAttribute,
12
+ } from '../../components'
9
13
 
10
14
  import { LinearApolloDisplay } from '../stateModel'
11
- import { MousePosition } from '../stateModel/mouseEvents'
15
+ import {
16
+ isMousePositionWithFeatureAndGlyph,
17
+ LinearApolloDisplayMouseEvents,
18
+ MousePosition,
19
+ MousePositionWithFeatureAndGlyph,
20
+ } from '../stateModel/mouseEvents'
12
21
  import { CanvasMouseEvent } from '../types'
13
22
  import { Glyph } from './Glyph'
23
+ import { LinearApolloDisplayRendering } from '../stateModel/rendering'
14
24
 
15
- export class BoxGlyph extends Glyph {
16
- getRowCount(_feature: AnnotationFeatureI) {
17
- return 1
25
+ function drawBoxOutline(
26
+ ctx: CanvasRenderingContext2D,
27
+ x: number,
28
+ y: number,
29
+ width: number,
30
+ height: number,
31
+ color: string,
32
+ ) {
33
+ drawBox(ctx, x, y, width, height, color)
34
+ if (width <= 2) {
35
+ return
18
36
  }
37
+ ctx.clearRect(x + 1, y + 1, width - 2, height - 2)
38
+ }
19
39
 
20
- protected getIsSelectedFeature(
21
- feature: AnnotationFeatureI,
22
- selectedFeature: AnnotationFeatureI | undefined,
23
- ) {
24
- return Boolean(selectedFeature && feature._id === selectedFeature._id)
25
- }
40
+ function drawBoxFill(
41
+ ctx: CanvasRenderingContext2D,
42
+ x: number,
43
+ y: number,
44
+ width: number,
45
+ height: number,
46
+ color: string,
47
+ ) {
48
+ drawBox(ctx, x + 1, y + 1, width - 2, height - 2, color)
49
+ }
26
50
 
27
- protected getBackgroundColor(theme: Theme | undefined, selected: boolean) {
28
- return selected
29
- ? theme?.palette.text.primary ?? 'black'
30
- : theme?.palette.background.default ?? 'white'
31
- }
51
+ function drawBoxText(
52
+ ctx: CanvasRenderingContext2D,
53
+ x: number,
54
+ y: number,
55
+ width: number,
56
+ color: string,
57
+ text: string,
58
+ ) {
59
+ ctx.fillStyle = color
60
+ const textStart = Math.max(x + 1, 0)
61
+ const textWidth = x - 1 + width - textStart
62
+ ctx.fillText(text, textStart, y + 11, textWidth)
63
+ }
32
64
 
33
- protected getTextColor(theme: Theme | undefined, selected: boolean) {
34
- return selected
35
- ? theme?.palette.getContrastText(
36
- this.getBackgroundColor(theme, selected),
37
- ) ?? 'white'
38
- : theme?.palette.text.primary ?? 'black'
65
+ function draw(
66
+ ctx: CanvasRenderingContext2D,
67
+ feature: AnnotationFeature,
68
+ row: number,
69
+ stateModel: LinearApolloDisplayRendering,
70
+ displayedRegionIndex: number,
71
+ ) {
72
+ const { apolloRowHeight: heightPx, lgv, session, theme } = stateModel
73
+ const { bpPerPx, displayedRegions, offsetPx } = lgv
74
+ const displayedRegion = displayedRegions[displayedRegionIndex]
75
+ const minX =
76
+ (lgv.bpToPx({
77
+ refName: displayedRegion.refName,
78
+ coord: feature.min,
79
+ regionNumber: displayedRegionIndex,
80
+ })?.offsetPx ?? 0) - offsetPx
81
+ const { reversed } = displayedRegion
82
+ const { apolloSelectedFeature } = session
83
+ const widthPx = feature.length / bpPerPx
84
+ const startPx = reversed ? minX - widthPx : minX
85
+ const top = row * heightPx
86
+ const isSelected = isSelectedFeature(feature, apolloSelectedFeature)
87
+ const backgroundColor = getBackgroundColor(theme, isSelected)
88
+ const textColor = getTextColor(theme, isSelected)
89
+ const featureBox: [number, number, number, number] = [
90
+ startPx,
91
+ top,
92
+ widthPx,
93
+ heightPx,
94
+ ]
95
+ drawBoxOutline(ctx, ...featureBox, textColor)
96
+ if (widthPx <= 2) {
97
+ // Don't need to add details if the feature is too small to see them
98
+ return
39
99
  }
40
100
 
41
- protected drawBox(
42
- ctx: CanvasRenderingContext2D,
43
- x: number,
44
- y: number,
45
- width: number,
46
- height: number,
47
- color: string,
48
- ) {
49
- ctx.fillStyle = color
50
- ctx.fillRect(x, y, width, height)
51
- }
101
+ drawBoxFill(ctx, startPx, top, widthPx, heightPx, backgroundColor)
102
+ drawBoxText(ctx, startPx, top, widthPx, textColor, feature.type)
103
+ }
52
104
 
53
- protected drawBoxOutline(
54
- ctx: CanvasRenderingContext2D,
55
- x: number,
56
- y: number,
57
- width: number,
58
- height: number,
59
- color: string,
60
- ) {
61
- this.drawBox(ctx, x, y, width, height, color)
62
- ctx.clearRect(x + 1, y + 1, width - 2, height - 2)
105
+ function drawDragPreview(
106
+ stateModel: LinearApolloDisplay,
107
+ overlayCtx: CanvasRenderingContext2D,
108
+ ) {
109
+ const { apolloDragging, apolloRowHeight, lgv, theme } = stateModel
110
+ const { bpPerPx, displayedRegions, offsetPx } = lgv
111
+ if (!apolloDragging) {
112
+ return
63
113
  }
114
+ const { current, edge, feature, start } = apolloDragging
115
+
116
+ const row = Math.floor(start.y / apolloRowHeight)
117
+ const region = displayedRegions[start.regionNumber]
118
+ const rowCount = getRowCount(feature)
119
+
120
+ const featureEdgeBp = region.reversed
121
+ ? region.end - feature[edge]
122
+ : feature[edge] - region.start
123
+ const featureEdgePx = featureEdgeBp / bpPerPx - offsetPx
124
+ const rectX = Math.min(current.x, featureEdgePx)
125
+ const rectY = row * apolloRowHeight
126
+ const rectWidth = Math.abs(current.x - featureEdgePx)
127
+ const rectHeight = apolloRowHeight * rowCount
128
+ overlayCtx.strokeStyle = theme?.palette.info.main ?? 'rgb(255,0,0)'
129
+ overlayCtx.setLineDash([6])
130
+ overlayCtx.strokeRect(rectX, rectY, rectWidth, rectHeight)
131
+ overlayCtx.fillStyle = alpha(theme?.palette.info.main ?? 'rgb(255,0,0)', 0.2)
132
+ overlayCtx.fillRect(rectX, rectY, rectWidth, rectHeight)
133
+ }
64
134
 
65
- protected drawBoxFill(
66
- ctx: CanvasRenderingContext2D,
67
- x: number,
68
- y: number,
69
- width: number,
70
- height: number,
71
- color: string,
72
- ) {
73
- this.drawBox(ctx, x + 1, y + 1, width - 2, height - 2, color)
135
+ function drawHover(
136
+ stateModel: LinearApolloDisplay,
137
+ ctx: CanvasRenderingContext2D,
138
+ ) {
139
+ const { apolloHover, apolloRowHeight, lgv, theme } = stateModel
140
+ if (!apolloHover) {
141
+ return
142
+ }
143
+ const { feature } = apolloHover
144
+ const position = stateModel.getFeatureLayoutPosition(feature)
145
+ if (!position) {
146
+ return
74
147
  }
148
+ const { bpPerPx, displayedRegions, offsetPx } = lgv
149
+ const { layoutIndex, layoutRow } = position
150
+ const displayedRegion = displayedRegions[layoutIndex]
151
+ const { refName, reversed } = displayedRegion
152
+ const { length, max, min } = feature
153
+ const startPx =
154
+ (lgv.bpToPx({
155
+ refName,
156
+ coord: reversed ? max : min,
157
+ regionNumber: layoutIndex,
158
+ })?.offsetPx ?? 0) - offsetPx
159
+ const top = layoutRow * apolloRowHeight
160
+ const widthPx = length / bpPerPx
161
+ ctx.fillStyle = theme?.palette.action.focus ?? 'rgba(0,0,0,0.04)'
162
+ ctx.fillRect(startPx, top, widthPx, apolloRowHeight)
163
+ }
75
164
 
76
- protected drawBoxText(
77
- ctx: CanvasRenderingContext2D,
78
- x: number,
79
- y: number,
80
- width: number,
81
- color: string,
82
- text: string,
83
- ) {
84
- ctx.fillStyle = color
85
- const textStart = Math.max(x + 1, 0)
86
- const textWidth = x - 1 + width - textStart
87
- ctx.fillText(text, textStart, y + 11, textWidth)
165
+ function drawTooltip(
166
+ display: LinearApolloDisplayMouseEvents,
167
+ context: CanvasRenderingContext2D,
168
+ ): void {
169
+ const { apolloHover, apolloRowHeight, lgv, theme } = display
170
+ if (!apolloHover) {
171
+ return
88
172
  }
173
+ const { feature } = apolloHover
174
+ const position = display.getFeatureLayoutPosition(feature)
175
+ if (!position) {
176
+ return
177
+ }
178
+ const { layoutIndex, layoutRow } = position
179
+ const { bpPerPx, displayedRegions, offsetPx } = lgv
180
+ const displayedRegion = displayedRegions[layoutIndex]
181
+ const { refName, reversed } = displayedRegion
89
182
 
90
- draw(
91
- stateModel: LinearApolloDisplay,
92
- ctx: CanvasRenderingContext2D,
93
- feature: AnnotationFeatureI,
94
- xOffset: number,
95
- row: number,
96
- reversed: boolean,
97
- ) {
98
- const { apolloRowHeight: heightPx, lgv, session, theme } = stateModel
99
- const { bpPerPx } = lgv
100
- const { apolloSelectedFeature } = session
101
- const offsetPx = (feature.start - feature.min) / bpPerPx
102
- const widthPx = feature.length / bpPerPx
103
- const startPx = reversed ? xOffset - offsetPx - widthPx : xOffset + offsetPx
104
- const top = row * heightPx
105
- const isSelected = this.getIsSelectedFeature(feature, apolloSelectedFeature)
106
- const backgroundColor = this.getBackgroundColor(theme, isSelected)
107
- const textColor = this.getTextColor(theme, isSelected)
108
- const groupingColor = isSelected
109
- ? 'rgba(130,0,0,0.45)'
110
- : 'rgba(255,0,0,0.25)'
111
- const featureBox: [number, number, number, number] = [
112
- startPx,
113
- top,
114
- widthPx,
115
- heightPx,
116
- ]
117
- this.drawBoxOutline(ctx, ...featureBox, textColor)
118
- if (widthPx <= 2) {
119
- // Don't need to add details if the feature is too small to see them
120
- return
121
- }
183
+ let location = 'Loc: '
122
184
 
123
- let featureLocations: { start: number; end: number; type: string }[] = [
124
- feature,
125
- ]
126
- if (
127
- feature.discontinuousLocations &&
128
- feature.discontinuousLocations.length > 0
129
- ) {
130
- featureLocations = feature.discontinuousLocations.map((f) => ({
131
- start: f.start,
132
- end: f.end,
133
- type: feature.type,
134
- }))
135
- }
136
- if (featureLocations.length > 1) {
137
- this.drawBoxFill(ctx, ...featureBox, groupingColor)
138
- for (const location of featureLocations) {
139
- const offsetPx = (location.start - feature.min) / bpPerPx
140
- const widthPx = (location.end - location.start) / bpPerPx
141
- const startPx = reversed
142
- ? xOffset - offsetPx - widthPx
143
- : xOffset + offsetPx
144
- this.drawBoxOutline(ctx, startPx, top, widthPx, heightPx, textColor)
145
- }
146
- }
185
+ const { length, max, min } = feature
186
+ location += `${min + 1}–${max}`
147
187
 
148
- for (const location of featureLocations) {
149
- const offsetPx = (location.start - feature.min) / bpPerPx
150
- const widthPx = (location.end - location.start) / bpPerPx
151
- const startPx = reversed
152
- ? xOffset - offsetPx - widthPx
153
- : xOffset + offsetPx
154
- this.drawBoxFill(ctx, startPx, top, widthPx, heightPx, backgroundColor)
155
- this.drawBoxText(ctx, startPx, top, widthPx, textColor, location.type)
156
- }
157
- }
188
+ let startPx =
189
+ (lgv.bpToPx({
190
+ refName,
191
+ coord: reversed ? max : min,
192
+ regionNumber: layoutIndex,
193
+ })?.offsetPx ?? 0) - offsetPx
194
+ const top = layoutRow * apolloRowHeight
195
+ const widthPx = length / bpPerPx
158
196
 
159
- getFeatureFromLayout(
160
- feature: AnnotationFeatureI,
161
- _bp: number,
162
- _row: number,
163
- ): AnnotationFeatureI | undefined {
164
- return feature
197
+ const featureType = `Type: ${feature.type}`
198
+ const { attributes } = feature
199
+ const featureName = attributes.get('gff_name')?.find((name) => name !== '')
200
+ const textWidth = [
201
+ context.measureText(featureType).width,
202
+ context.measureText(location).width,
203
+ ]
204
+ if (featureName) {
205
+ textWidth.push(context.measureText(`Name: ${featureName}`).width)
165
206
  }
207
+ const maxWidth = Math.max(...textWidth)
166
208
 
167
- getRowForFeature(
168
- _feature: AnnotationFeatureI,
169
- _childFeature: AnnotationFeatureI,
170
- ): number | undefined {
171
- return 0
209
+ startPx = startPx + widthPx + 5
210
+ context.fillStyle = alpha(theme?.palette.text.primary ?? 'rgb(1, 1, 1)', 0.7)
211
+ context.fillRect(startPx, top, maxWidth + 4, textWidth.length === 3 ? 45 : 35)
212
+ context.beginPath()
213
+ context.moveTo(startPx, top)
214
+ context.lineTo(startPx - 5, top + 5)
215
+ context.lineTo(startPx, top + 10)
216
+ context.fill()
217
+ context.fillStyle = theme?.palette.background.default ?? 'rgba(255, 255, 255)'
218
+ let textTop = top + 12
219
+ context.fillText(featureType, startPx + 2, textTop)
220
+ if (featureName) {
221
+ textTop = textTop + 12
222
+ context.fillText(`Name: ${featureName}`, startPx + 2, textTop)
172
223
  }
224
+ textTop = textTop + 12
225
+ context.fillText(location, startPx + 2, textTop)
226
+ }
173
227
 
174
- /** @returns undefined if mouse not on the edge of this feature, otherwise 'start' or 'end' depending on which edge */
175
- isMouseOnFeatureEdge(
176
- mousePosition: MousePosition,
177
- feature: AnnotationFeatureI,
178
- stateModel: LinearApolloDisplay,
179
- ) {
180
- if (!mousePosition) {
181
- return
182
- }
183
- const { refName, regionNumber, x } = mousePosition
184
- const { lgv } = stateModel
185
- const { bpToPx, offsetPx } = lgv
186
- const startPxInfo = bpToPx({ refName, coord: feature.start, regionNumber })
187
- const endPxInfo = bpToPx({ refName, coord: feature.end, regionNumber })
188
- if (startPxInfo !== undefined && endPxInfo !== undefined) {
189
- const startPx = startPxInfo.offsetPx - offsetPx
190
- const endPx = endPxInfo.offsetPx - offsetPx
191
- if (Math.abs(endPx - startPx) < 8) {
192
- return
193
- }
194
- if (Math.abs(startPx - x) < 4) {
195
- return 'start'
196
- }
197
- if (Math.abs(endPx - x) < 4) {
198
- return 'end'
199
- }
200
- }
201
- return
202
- }
228
+ export function isSelectedFeature(
229
+ feature: AnnotationFeature,
230
+ selectedFeature: AnnotationFeature | undefined,
231
+ ) {
232
+ return Boolean(selectedFeature && feature._id === selectedFeature._id)
233
+ }
203
234
 
204
- drawHover(stateModel: LinearApolloDisplay, ctx: CanvasRenderingContext2D) {
205
- const { apolloHover, apolloRowHeight, displayedRegions, lgv, theme } =
206
- stateModel
207
- if (!apolloHover) {
208
- return
209
- }
210
- const { feature, mousePosition } = apolloHover
211
- if (!feature || !mousePosition) {
212
- return
213
- }
214
- const { bpPerPx, bpToPx, offsetPx } = lgv
215
- const displayedRegion = displayedRegions[mousePosition.regionNumber]
216
- const { refName, reversed } = displayedRegion
217
- const { end, length, start } = feature
218
- const { regionNumber, y } = mousePosition
219
- const startPx =
220
- (bpToPx({ refName, coord: reversed ? end : start, regionNumber })
221
- ?.offsetPx ?? 0) - offsetPx
222
- const row = Math.floor(y / apolloRowHeight)
223
- const top = row * apolloRowHeight
224
- const widthPx = length / bpPerPx
225
- ctx.fillStyle = theme?.palette.action.focus ?? 'rgba(0,0,0,0.04)'
226
- ctx.fillRect(startPx, top, widthPx, apolloRowHeight)
227
- }
235
+ function getBackgroundColor(theme: Theme | undefined, selected: boolean) {
236
+ return selected
237
+ ? theme?.palette.text.primary ?? 'black'
238
+ : theme?.palette.background.default ?? 'white'
239
+ }
228
240
 
229
- drawDragPreview(
230
- stateModel: LinearApolloDisplay,
231
- overlayCtx: CanvasRenderingContext2D,
232
- ) {
233
- const { apolloDragging, apolloRowHeight, displayedRegions, lgv, theme } =
234
- stateModel
235
- const { bpPerPx, offsetPx } = lgv
236
- if (!apolloDragging) {
237
- return
238
- }
239
- const {
240
- feature,
241
- glyph,
242
- mousePosition: startingMousePosition,
243
- } = apolloDragging.start
244
- if (!feature) {
245
- throw new Error('no feature for drag preview??')
246
- }
247
- if (glyph !== this) {
248
- throw new Error('drawDragPreview() called on wrong glyph?')
249
- }
250
- const { mousePosition: currentMousePosition } = apolloDragging.current
251
- const edge = this.isMouseOnFeatureEdge(
252
- startingMousePosition,
253
- feature,
254
- stateModel,
255
- )
256
- if (!edge) {
257
- return
258
- }
241
+ function getTextColor(theme: Theme | undefined, selected: boolean) {
242
+ return selected
243
+ ? theme?.palette.getContrastText(getBackgroundColor(theme, selected)) ??
244
+ 'white'
245
+ : theme?.palette.text.primary ?? 'black'
246
+ }
259
247
 
260
- const row = Math.floor(startingMousePosition.y / apolloRowHeight)
261
- const region = displayedRegions[startingMousePosition.regionNumber]
262
- const rowCount = this.getRowCount(feature)
263
-
264
- const featureEdgeBp = region.reversed
265
- ? region.end - feature[edge]
266
- : feature[edge] - region.start
267
- const featureEdgePx = featureEdgeBp / bpPerPx - offsetPx
268
-
269
- const rectX = Math.min(currentMousePosition.x, featureEdgePx)
270
- const rectY = row * apolloRowHeight
271
- const rectWidth = Math.abs(currentMousePosition.x - featureEdgePx)
272
- const rectHeight = apolloRowHeight * rowCount
273
-
274
- overlayCtx.strokeStyle = theme?.palette.info.main ?? 'rgb(255,0,0)'
275
- overlayCtx.setLineDash([6])
276
- overlayCtx.strokeRect(rectX, rectY, rectWidth, rectHeight)
277
- overlayCtx.fillStyle = alpha(
278
- theme?.palette.info.main ?? 'rgb(255,0,0)',
279
- 0.2,
280
- )
281
- overlayCtx.fillRect(rectX, rectY, rectWidth, rectHeight)
282
- }
248
+ export function drawBox(
249
+ ctx: CanvasRenderingContext2D,
250
+ x: number,
251
+ y: number,
252
+ width: number,
253
+ height: number,
254
+ color: string,
255
+ ) {
256
+ ctx.fillStyle = color
257
+ ctx.fillRect(x, y, width, height)
258
+ }
283
259
 
284
- onMouseMove(stateModel: LinearApolloDisplay, event: CanvasMouseEvent) {
285
- const { feature, mousePosition } =
286
- stateModel.getFeatureAndGlyphUnderMouse(event)
287
- if (stateModel.apolloDragging) {
288
- stateModel.setCursor('col-resize')
289
- return
290
- }
291
- if (feature && mousePosition) {
292
- const edge = this.isMouseOnFeatureEdge(mousePosition, feature, stateModel)
293
- if (edge) {
294
- stateModel.setCursor('col-resize')
295
- } else {
296
- stateModel.setCursor()
297
- }
298
- }
260
+ function getContextMenuItems(
261
+ display: LinearApolloDisplayMouseEvents,
262
+ ): MenuItem[] {
263
+ const {
264
+ apolloHover,
265
+ apolloInternetAccount: internetAccount,
266
+ changeManager,
267
+ regions,
268
+ selectedFeature,
269
+ session,
270
+ } = display
271
+ const menuItems: MenuItem[] = []
272
+ if (!apolloHover) {
273
+ return menuItems
299
274
  }
275
+ const { feature: sourceFeature } = apolloHover
276
+ const role = internetAccount ? internetAccount.role : 'admin'
277
+ const admin = role === 'admin'
278
+ const readOnly = !(role && ['admin', 'user'].includes(role))
279
+ const [region] = regions
280
+ const sourceAssemblyId = display.getAssemblyId(region.assemblyName)
281
+ const currentAssemblyId = display.getAssemblyId(region.assemblyName)
282
+ menuItems.push(
283
+ {
284
+ label: 'Add child feature',
285
+ disabled: readOnly,
286
+ onClick: () => {
287
+ ;(session as unknown as AbstractSessionModel).queueDialog(
288
+ (doneCallback) => [
289
+ AddChildFeature,
290
+ {
291
+ session,
292
+ handleClose: () => {
293
+ doneCallback()
294
+ },
295
+ changeManager,
296
+ sourceFeature,
297
+ sourceAssemblyId,
298
+ internetAccount,
299
+ },
300
+ ],
301
+ )
302
+ },
303
+ },
304
+ {
305
+ label: 'Copy features and annotations',
306
+ disabled: readOnly,
307
+ onClick: () => {
308
+ ;(session as unknown as AbstractSessionModel).queueDialog(
309
+ (doneCallback) => [
310
+ CopyFeature,
311
+ {
312
+ session,
313
+ handleClose: () => {
314
+ doneCallback()
315
+ },
316
+ changeManager,
317
+ sourceFeature,
318
+ sourceAssemblyId: currentAssemblyId,
319
+ },
320
+ ],
321
+ )
322
+ },
323
+ },
324
+ {
325
+ label: 'Delete feature',
326
+ disabled: !admin,
327
+ onClick: () => {
328
+ ;(session as unknown as AbstractSessionModel).queueDialog(
329
+ (doneCallback) => [
330
+ DeleteFeature,
331
+ {
332
+ session,
333
+ handleClose: () => {
334
+ doneCallback()
335
+ },
336
+ changeManager,
337
+ sourceFeature,
338
+ sourceAssemblyId: currentAssemblyId,
339
+ selectedFeature,
340
+ setSelectedFeature: (feature?: AnnotationFeature) => {
341
+ display.setSelectedFeature(feature)
342
+ },
343
+ },
344
+ ],
345
+ )
346
+ },
347
+ },
348
+ {
349
+ label: 'Modify feature attribute',
350
+ disabled: readOnly,
351
+ onClick: () => {
352
+ ;(session as unknown as AbstractSessionModel).queueDialog(
353
+ (doneCallback) => [
354
+ ModifyFeatureAttribute,
355
+ {
356
+ session,
357
+ handleClose: () => {
358
+ doneCallback()
359
+ },
360
+ changeManager,
361
+ sourceFeature,
362
+ sourceAssemblyId: currentAssemblyId,
363
+ },
364
+ ],
365
+ )
366
+ },
367
+ },
368
+ {
369
+ label: 'Edit feature details',
370
+ onClick: () => {
371
+ const apolloFeatureWidget = (
372
+ session as unknown as SessionWithWidgets
373
+ ).addWidget(
374
+ 'ApolloFeatureDetailsWidget',
375
+ 'apolloFeatureDetailsWidget',
376
+ {
377
+ feature: sourceFeature,
378
+ assembly: currentAssemblyId,
379
+ refName: region.refName,
380
+ },
381
+ )
382
+ ;(session as unknown as SessionWithWidgets).showWidget(
383
+ apolloFeatureWidget,
384
+ )
385
+ },
386
+ },
387
+ )
388
+ return menuItems
389
+ }
300
390
 
301
- onMouseDown(stateModel: LinearApolloDisplay, event: CanvasMouseEvent) {
302
- // swallow the mouseDown if we are on the edge of the feature
303
- const { feature, mousePosition } =
304
- stateModel.getFeatureAndGlyphUnderMouse(event)
305
- if (feature && mousePosition) {
306
- const edge = this.isMouseOnFeatureEdge(mousePosition, feature, stateModel)
307
- if (edge) {
308
- event.stopPropagation()
309
- }
310
- }
391
+ function getFeatureFromLayout(
392
+ feature: AnnotationFeature,
393
+ _bp: number,
394
+ _row: number,
395
+ ): AnnotationFeature {
396
+ return feature
397
+ }
398
+
399
+ function getRowCount(_feature: AnnotationFeature) {
400
+ return 1
401
+ }
402
+
403
+ function getRowForFeature(
404
+ _feature: AnnotationFeature,
405
+ _childFeature: AnnotationFeature,
406
+ ): number | undefined {
407
+ return 0
408
+ }
409
+
410
+ function onMouseDown(
411
+ stateModel: LinearApolloDisplay,
412
+ currentMousePosition: MousePositionWithFeatureAndGlyph,
413
+ event: CanvasMouseEvent,
414
+ ) {
415
+ const { featureAndGlyphUnderMouse } = currentMousePosition
416
+ // swallow the mouseDown if we are on the edge of the feature so that we
417
+ // don't start dragging the view if we try to drag the feature edge
418
+ const { feature } = featureAndGlyphUnderMouse
419
+ const edge = isMouseOnFeatureEdge(currentMousePosition, feature, stateModel)
420
+ if (edge) {
421
+ event.stopPropagation()
422
+ stateModel.startDrag(currentMousePosition, feature, edge)
311
423
  }
424
+ }
425
+
426
+ function onMouseLeave(): void {
427
+ return
428
+ }
312
429
 
313
- onMouseUp(stateModel: LinearApolloDisplay, event: CanvasMouseEvent) {
314
- if (stateModel.apolloDragging ?? event.button !== 0) {
430
+ function onMouseMove(
431
+ stateModel: LinearApolloDisplay,
432
+ mousePosition: MousePosition,
433
+ ) {
434
+ if (isMousePositionWithFeatureAndGlyph(mousePosition)) {
435
+ const { featureAndGlyphUnderMouse } = mousePosition
436
+ stateModel.setApolloHover(featureAndGlyphUnderMouse)
437
+ const { feature } = featureAndGlyphUnderMouse
438
+ const edge = isMouseOnFeatureEdge(mousePosition, feature, stateModel)
439
+ if (edge) {
440
+ stateModel.setCursor('col-resize')
315
441
  return
316
442
  }
317
- const { feature } = stateModel.getFeatureAndGlyphUnderMouse(event)
318
- if (feature) {
319
- stateModel.setSelectedFeature(feature)
320
- }
321
443
  }
444
+ stateModel.setCursor()
445
+ }
322
446
 
323
- startDrag(stateModel: LinearApolloDisplay): boolean {
324
- // only accept the drag if we are on the edge of the feature
325
- const { feature, mousePosition } = stateModel.apolloDragging?.start ?? {}
326
- if (feature && mousePosition) {
327
- const edge = this.isMouseOnFeatureEdge(mousePosition, feature, stateModel)
328
- if (edge) {
329
- return true
330
- }
331
- }
332
- return false
447
+ function onMouseUp(
448
+ stateModel: LinearApolloDisplay,
449
+ mousePosition: MousePosition,
450
+ ) {
451
+ if (stateModel.apolloDragging) {
452
+ return
333
453
  }
334
-
335
- continueDrag(
336
- stateModel: LinearApolloDisplay,
337
- currentMousePosition: MousePosition,
338
- ) {
339
- const { feature, glyph, mousePosition, topLevelFeature } =
340
- stateModel.apolloDragging?.start ?? {}
341
- if (!(currentMousePosition && mousePosition)) {
342
- return
343
- }
344
- stateModel.setDragging({
345
- start: {
346
- feature,
347
- topLevelFeature,
348
- glyph,
349
- mousePosition,
350
- },
351
- current: {
352
- feature,
353
- topLevelFeature,
354
- glyph,
355
- mousePosition: currentMousePosition,
356
- },
357
- })
454
+ const { featureAndGlyphUnderMouse } = mousePosition
455
+ if (featureAndGlyphUnderMouse?.feature) {
456
+ stateModel.setSelectedFeature(featureAndGlyphUnderMouse.feature)
358
457
  }
458
+ }
359
459
 
360
- executeDrag(stateModel: LinearApolloDisplay) {
361
- const {
362
- apolloDragging,
363
- changeManager,
364
- displayedRegions,
365
- getAssemblyId,
366
- setCursor,
367
- } = stateModel
368
- if (!apolloDragging) {
460
+ /** @returns undefined if mouse not on the edge of this feature, otherwise 'start' or 'end' depending on which edge */
461
+ function isMouseOnFeatureEdge(
462
+ mousePosition: MousePosition,
463
+ feature: AnnotationFeature,
464
+ stateModel: LinearApolloDisplay,
465
+ ) {
466
+ const { refName, regionNumber, x } = mousePosition
467
+ const { lgv } = stateModel
468
+ const { offsetPx } = lgv
469
+ const minPxInfo = lgv.bpToPx({ refName, coord: feature.min, regionNumber })
470
+ const maxPxInfo = lgv.bpToPx({ refName, coord: feature.max, regionNumber })
471
+ if (minPxInfo !== undefined && maxPxInfo !== undefined) {
472
+ const minPx = minPxInfo.offsetPx - offsetPx
473
+ const maxPx = maxPxInfo.offsetPx - offsetPx
474
+ if (Math.abs(maxPx - minPx) < 8) {
369
475
  return
370
476
  }
371
- const {
372
- feature,
373
- glyph,
374
- mousePosition: startingMousePosition,
375
- } = apolloDragging.start
376
- if (!feature) {
377
- throw new Error('no feature for drag preview??')
378
- }
379
- if (glyph !== this) {
380
- throw new Error('drawDragPreview() called on wrong glyph?')
477
+ if (Math.abs(minPx - x) < 4) {
478
+ return 'min'
381
479
  }
382
- const edge = this.isMouseOnFeatureEdge(
383
- startingMousePosition,
384
- feature,
385
- stateModel,
386
- )
387
- if (!edge) {
388
- return
480
+ if (Math.abs(maxPx - x) < 4) {
481
+ return 'max'
389
482
  }
390
-
391
- const { mousePosition: currentMousePosition } = apolloDragging.current
392
- const region = displayedRegions[startingMousePosition.regionNumber]
393
- const newBp = currentMousePosition.bp
394
- const assembly = getAssemblyId(region.assemblyName)
395
- let change: LocationEndChange | LocationStartChange
396
- if (edge === 'end') {
397
- const featureId = feature._id
398
- const oldEnd = feature.end
399
- const newEnd = newBp
400
- change = new LocationEndChange({
401
- typeName: 'LocationEndChange',
402
- changedIds: [featureId],
403
- featureId,
404
- oldEnd,
405
- newEnd,
406
- assembly,
407
- })
408
- } else {
409
- const featureId = feature._id
410
- const oldStart = feature.start
411
- const newStart = newBp
412
- change = new LocationStartChange({
413
- typeName: 'LocationStartChange',
414
- changedIds: [featureId],
415
- featureId,
416
- oldStart,
417
- newStart,
418
- assembly,
419
- })
420
- }
421
- if (!changeManager) {
422
- throw new Error('no change manager')
423
- }
424
- void changeManager.submit(change)
425
- setCursor()
426
483
  }
484
+ return
485
+ }
486
+
487
+ export const boxGlyph: Glyph = {
488
+ draw,
489
+ drawDragPreview,
490
+ drawHover,
491
+ drawTooltip,
492
+ getContextMenuItems,
493
+ getFeatureFromLayout,
494
+ getRowCount,
495
+ getRowForFeature,
496
+ onMouseDown,
497
+ onMouseLeave,
498
+ onMouseMove,
499
+ onMouseUp,
427
500
  }