diff --git a/packages/apollo-mst/src/AnnotationFeature.ts b/packages/apollo-mst/src/AnnotationFeature.ts index c7016e116..ca4c62a9a 100644 --- a/packages/apollo-mst/src/AnnotationFeature.ts +++ b/packages/apollo-mst/src/AnnotationFeature.ts @@ -134,7 +134,7 @@ export const AnnotationFeature = types const dl = self.discontinuousLocations if (dl && dl.length > 0 && dl[index].end !== end) { dl[index].end = end - if (index === 0) { + if (index === dl.length - 1) { self.end = end } } diff --git a/packages/apollo-shared/src/Changes/DiscontinuousLocationChange.ts b/packages/apollo-shared/src/Changes/DiscontinuousLocationChange.ts deleted file mode 100644 index 8116ace51..000000000 --- a/packages/apollo-shared/src/Changes/DiscontinuousLocationChange.ts +++ /dev/null @@ -1,155 +0,0 @@ -import { - ChangeOptions, - ClientDataStore, - FeatureChange, - LocalGFF3DataStore, - SerializedFeatureChange, - ServerDataStore, -} from 'apollo-common' - -interface SerializedDiscontinuousLocationChangeBase - extends SerializedFeatureChange { - typeName: 'DiscontinuousLocationChange' -} - -interface DiscontinuousLocationChangeDetails { - featureId: string - start?: { - newStart: number - index: number // discontinuous location index - } - end?: { - newEnd: number - index: number // discontinuous location index - } -} - -interface SerializedDiscontinuousLocationChangeSingle - extends SerializedDiscontinuousLocationChangeBase, - DiscontinuousLocationChangeDetails {} - -interface SerializedDiscontinuousLocationChangeMultiple - extends SerializedDiscontinuousLocationChangeBase { - changes: DiscontinuousLocationChangeDetails[] -} - -type SerializedDiscontinuousLocationChange = - | SerializedDiscontinuousLocationChangeSingle - | SerializedDiscontinuousLocationChangeMultiple - -export class DiscontinuousLocationChange extends FeatureChange { - typeName = 'DiscontinuousLocationChange' as const - changes: DiscontinuousLocationChangeDetails[] - - constructor( - json: SerializedDiscontinuousLocationChange, - options?: ChangeOptions, - ) { - super(json, options) - this.changes = 'changes' in json ? json.changes : [json] - } - - toJSON(): SerializedDiscontinuousLocationChange { - const { assembly, changedIds, changes, typeName } = this - if (changes.length === 1) { - const [{ end, featureId, start }] = changes - return { typeName, changedIds, assembly, featureId, start, end } - } - return { typeName, changedIds, assembly, changes } - } - - async executeOnServer(backend: ServerDataStore) { - const { featureModel, session } = backend - const { changes, logger } = this - for (const change of changes) { - const { end, featureId, start } = change - const topLevelFeature = await featureModel - .findOne({ allIds: featureId }) - .session(session) - .exec() - - if (!topLevelFeature) { - const errMsg = `ERROR: The following featureId was not found in database ='${featureId}'` - logger.error(errMsg) - throw new Error(errMsg) - } - - const cdsFeature = this.getFeatureFromId(topLevelFeature, featureId) - if (!cdsFeature?.discontinuousLocations) { - const errMsg = 'ERROR when searching feature by featureId' - logger.error(errMsg) - throw new Error(errMsg) - } - - let locChanged - if (start) { - cdsFeature.discontinuousLocations[start.index].start = start.newStart - if (start.index === 0) { - cdsFeature.start = start.newStart - } - locChanged = true - } - - if (end) { - cdsFeature.discontinuousLocations[end.index].end = end.newEnd - if (end.index === 0) { - cdsFeature.end = end.newEnd - } - locChanged = true - } - - if (locChanged) { - try { - // Mark as modified. Without this save() -method is not updating data in database - topLevelFeature.markModified('children') - await topLevelFeature.save() - } catch (error) { - logger.debug?.(`*** FAILED: ${error}`) - throw error - } - } - } - } - - async executeOnLocalGFF3(_backend: LocalGFF3DataStore) { - throw new Error('executeOnLocalGFF3 not implemented') - } - - async executeOnClient(dataStore: ClientDataStore) { - if (!dataStore) { - throw new Error('No data store') - } - for (const [idx, changedId] of this.changedIds.entries()) { - const feature = dataStore.getFeature(changedId) - if (!feature) { - throw new Error(`Could not find feature with identifier "${changedId}"`) - } - const { end, start } = this.changes[idx] - if (start) { - feature.setCDSDiscontinuousLocationStart(start.newStart, start.index) - } - if (end) { - feature.setCDSDiscontinuousLocationEnd(end.newEnd, end.index) - } - } - } - - getInverse() { - const { assembly, changedIds, changes, logger, typeName } = this - const inverseChangedIds = [...changedIds].reverse() - const inverseChanges = [...changes].reverse().map((c) => ({ - featureId: c.featureId, - start: c.start, - end: c.end, - })) - return new DiscontinuousLocationChange( - { - changedIds: inverseChangedIds, - typeName, - changes: inverseChanges, - assembly, - }, - { logger }, - ) - } -} diff --git a/packages/apollo-shared/src/Changes/DiscontinuousLocationEndChange.ts b/packages/apollo-shared/src/Changes/DiscontinuousLocationEndChange.ts new file mode 100644 index 000000000..c08e55ac5 --- /dev/null +++ b/packages/apollo-shared/src/Changes/DiscontinuousLocationEndChange.ts @@ -0,0 +1,175 @@ +import { + ChangeOptions, + ClientDataStore, + FeatureChange, + LocalGFF3DataStore, + SerializedFeatureChange, + ServerDataStore, +} from 'apollo-common' + +interface SerializedDiscontinuousLocationEndChangeBase + extends SerializedFeatureChange { + typeName: 'DiscontinuousLocationEndChange' +} + +interface DiscontinuousLocationEndChangeDetails { + featureId: string + oldEnd: number + newEnd: number + index: number +} + +interface SerializedDiscontinuousLocationEndChangeSingle + extends SerializedDiscontinuousLocationEndChangeBase, + DiscontinuousLocationEndChangeDetails {} + +interface SerializedDiscontinuousLocationEndChangeMultiple + extends SerializedDiscontinuousLocationEndChangeBase { + changes: DiscontinuousLocationEndChangeDetails[] +} + +type SerializedDiscontinuousLocationEndChange = + | SerializedDiscontinuousLocationEndChangeSingle + | SerializedDiscontinuousLocationEndChangeMultiple + +export class DiscontinuousLocationEndChange extends FeatureChange { + typeName = 'DiscontinuousLocationEndChange' as const + changes: DiscontinuousLocationEndChangeDetails[] + + constructor( + json: SerializedDiscontinuousLocationEndChange, + options?: ChangeOptions, + ) { + super(json, options) + this.changes = 'changes' in json ? json.changes : [json] + } + + toJSON(): SerializedDiscontinuousLocationEndChange { + const { assembly, changedIds, changes, typeName } = this + if (changes.length === 1) { + const [{ featureId, index, newEnd, oldEnd }] = changes + return { + typeName, + changedIds, + assembly, + featureId, + oldEnd, + newEnd, + index, + } + } + return { typeName, changedIds, assembly, changes } + } + + async executeOnServer(backend: ServerDataStore) { + const { featureModel, session } = backend + const { changes, logger } = this + for (const change of changes) { + const { featureId, index, newEnd, oldEnd: expectedOldEnd } = change + const topLevelFeature = await featureModel + .findOne({ allIds: featureId }) + .session(session) + .exec() + + if (!topLevelFeature) { + const errMsg = `ERROR: The following featureId was not found in database ='${featureId}'` + logger.error(errMsg) + throw new Error(errMsg) + } + + const feature = this.getFeatureFromId(topLevelFeature, featureId) + if (!feature) { + const errMsg = 'ERROR when searching feature by featureId' + logger.error(errMsg) + throw new Error(errMsg) + } + logger.debug?.(`*** Found feature: ${JSON.stringify(feature)}`) + if ( + !feature.discontinuousLocations || + feature.discontinuousLocations.length === 0 + ) { + const errMsg = + 'Must use "LocationEndChange" to change a feature end that does not have discontinuous locations' + logger.error(errMsg) + throw new Error(errMsg) + } + const oldEnd = feature.discontinuousLocations[index].end + if (oldEnd !== expectedOldEnd) { + const errMsg = `Location's current end value ${oldEnd} doesn't match with expected value ${expectedOldEnd}` + logger.error(errMsg) + throw new Error(errMsg) + } + const { start } = feature.discontinuousLocations[index] + if (newEnd <= start) { + const errMsg = `location end (${newEnd}) can't be smaller than location start (${start})` + logger.error(errMsg) + throw new Error(errMsg) + } + const nextLocation = feature.discontinuousLocations[index + 1] + if (nextLocation && newEnd >= nextLocation.start) { + const errMsg = `Location end (${newEnd}) can't be larger than the next location's start (${nextLocation.start})` + logger.error(errMsg) + throw new Error(errMsg) + } + feature.discontinuousLocations[index].end = newEnd + if (index === feature.discontinuousLocations.length - 1) { + feature.end = newEnd + } + + try { + topLevelFeature.markModified('discontinuousLocations') + await topLevelFeature.save() + } catch (error) { + logger.debug?.(`*** FAILED: ${error}`) + throw error + } + } + } + + async executeOnLocalGFF3(_backend: LocalGFF3DataStore) { + throw new Error('executeOnLocalGFF3 not implemented') + } + + async executeOnClient(dataStore: ClientDataStore) { + if (!dataStore) { + throw new Error('No data store') + } + for (const [idx, changedId] of this.changedIds.entries()) { + const feature = dataStore.getFeature(changedId) + if (!feature) { + throw new Error(`Could not find feature with identifier "${changedId}"`) + } + const { index, newEnd } = this.changes[idx] + feature.setCDSDiscontinuousLocationEnd(newEnd, index) + } + } + + getInverse() { + const { assembly, changedIds, changes, logger, typeName } = this + const inverseChangedIds = [...changedIds].reverse() + const inverseChanges = [...changes].reverse().map((change) => ({ + featureId: change.featureId, + oldEnd: change.newEnd, + newEnd: change.oldEnd, + index: change.index, + })) + return new DiscontinuousLocationEndChange( + { + changedIds: inverseChangedIds, + typeName, + changes: inverseChanges, + assembly, + }, + { logger }, + ) + } +} + +export function isDiscontinuousLocationEndChange( + change: unknown, +): change is DiscontinuousLocationEndChange { + return ( + (change as DiscontinuousLocationEndChange).typeName === + 'DiscontinuousLocationEndChange' + ) +} diff --git a/packages/apollo-shared/src/Changes/DiscontinuousLocationStartChange.ts b/packages/apollo-shared/src/Changes/DiscontinuousLocationStartChange.ts new file mode 100644 index 000000000..cecc7b829 --- /dev/null +++ b/packages/apollo-shared/src/Changes/DiscontinuousLocationStartChange.ts @@ -0,0 +1,175 @@ +import { + ChangeOptions, + ClientDataStore, + FeatureChange, + LocalGFF3DataStore, + SerializedFeatureChange, + ServerDataStore, +} from 'apollo-common' + +interface SerializedDiscontinuousLocationStartChangeBase + extends SerializedFeatureChange { + typeName: 'DiscontinuousLocationStartChange' +} + +interface DiscontinuousLocationStartChangeDetails { + featureId: string + oldStart: number + newStart: number + index: number +} + +interface SerializedDiscontinuousLocationStartChangeSingle + extends SerializedDiscontinuousLocationStartChangeBase, + DiscontinuousLocationStartChangeDetails {} + +interface SerializedDiscontinuousLocationStartChangeMultiple + extends SerializedDiscontinuousLocationStartChangeBase { + changes: DiscontinuousLocationStartChangeDetails[] +} + +type SerializedDiscontinuousLocationStartChange = + | SerializedDiscontinuousLocationStartChangeSingle + | SerializedDiscontinuousLocationStartChangeMultiple + +export class DiscontinuousLocationStartChange extends FeatureChange { + typeName = 'DiscontinuousLocationStartChange' as const + changes: DiscontinuousLocationStartChangeDetails[] + + constructor( + json: SerializedDiscontinuousLocationStartChange, + options?: ChangeOptions, + ) { + super(json, options) + this.changes = 'changes' in json ? json.changes : [json] + } + + toJSON(): SerializedDiscontinuousLocationStartChange { + const { assembly, changedIds, changes, typeName } = this + if (changes.length === 1) { + const [{ featureId, index, newStart, oldStart }] = changes + return { + typeName, + changedIds, + assembly, + featureId, + oldStart, + newStart, + index, + } + } + return { typeName, changedIds, assembly, changes } + } + + async executeOnServer(backend: ServerDataStore) { + const { featureModel, session } = backend + const { changes, logger } = this + for (const change of changes) { + const { featureId, index, newStart, oldStart: expectedOldStart } = change + const topLevelFeature = await featureModel + .findOne({ allIds: featureId }) + .session(session) + .exec() + + if (!topLevelFeature) { + const errMsg = `ERROR: The following featureId was not found in database ='${featureId}'` + logger.error(errMsg) + throw new Error(errMsg) + } + + const feature = this.getFeatureFromId(topLevelFeature, featureId) + if (!feature) { + const errMsg = 'ERROR when searching feature by featureId' + logger.error(errMsg) + throw new Error(errMsg) + } + logger.debug?.(`*** Found feature: ${JSON.stringify(feature)}`) + if ( + !feature.discontinuousLocations || + feature.discontinuousLocations.length === 0 + ) { + const errMsg = + 'Must use "LocationStartChange" to change a feature start that does not have discontinuous locations' + logger.error(errMsg) + throw new Error(errMsg) + } + const oldStart = feature.discontinuousLocations[index].start + if (oldStart !== expectedOldStart) { + const errMsg = `Location's current start value ${oldStart} doesn't match with expected value ${expectedOldStart}` + logger.error(errMsg) + throw new Error(errMsg) + } + const { end } = feature.discontinuousLocations[index] + if (newStart >= end) { + const errMsg = `location start (${newStart}) can't be larger than location end (${end})` + logger.error(errMsg) + throw new Error(errMsg) + } + const previousLocation = feature.discontinuousLocations[index - 1] + if (previousLocation && newStart <= previousLocation.end) { + const errMsg = `Location start (${newStart}) can't be larger than the previous location's end (${previousLocation.end})` + logger.error(errMsg) + throw new Error(errMsg) + } + feature.discontinuousLocations[index].start = newStart + if (index === 0) { + feature.start = newStart + } + + try { + topLevelFeature.markModified('discontinuousLocations') + await topLevelFeature.save() + } catch (error) { + logger.debug?.(`*** FAILED: ${error}`) + throw error + } + } + } + + async executeOnLocalGFF3(_backend: LocalGFF3DataStore) { + throw new Error('executeOnLocalGFF3 not implemented') + } + + async executeOnClient(dataStore: ClientDataStore) { + if (!dataStore) { + throw new Error('No data store') + } + for (const [idx, changedId] of this.changedIds.entries()) { + const feature = dataStore.getFeature(changedId) + if (!feature) { + throw new Error(`Could not find feature with identifier "${changedId}"`) + } + const { index, newStart } = this.changes[idx] + feature.setCDSDiscontinuousLocationStart(newStart, index) + } + } + + getInverse() { + const { assembly, changedIds, changes, logger, typeName } = this + const inverseChangedIds = [...changedIds].reverse() + const inverseChanges = [...changes].reverse().map((change) => ({ + featureId: change.featureId, + oldStart: change.newStart, + newStart: change.oldStart, + index: change.index, + })) + return new DiscontinuousLocationStartChange( + { + changedIds: inverseChangedIds, + typeName, + changes: inverseChanges, + assembly, + }, + { logger }, + ) + } +} + +export function isDiscontinuousLocationStartChange( + change: unknown, +): change is DiscontinuousLocationStartChange { + return ( + (change as DiscontinuousLocationStartChange).typeName === + 'DiscontinuousLocationStartChange' + ) +} diff --git a/packages/apollo-shared/src/Changes/LocationEndChange.ts b/packages/apollo-shared/src/Changes/LocationEndChange.ts index bfa0b2ac1..d2e3c7483 100644 --- a/packages/apollo-shared/src/Changes/LocationEndChange.ts +++ b/packages/apollo-shared/src/Changes/LocationEndChange.ts @@ -86,6 +86,15 @@ export class LocationEndChange extends FeatureChange { throw new Error(errMsg) } logger.debug?.(`*** Found feature: ${JSON.stringify(foundFeature)}`) + if ( + foundFeature.discontinuousLocations && + foundFeature.discontinuousLocations.length > 0 + ) { + const errMsg = + 'Must use "DiscontinuousLocationEndChange" to change a feature end that has discontinuous locations' + logger.error(errMsg) + throw new Error(errMsg) + } if (foundFeature.end !== oldEnd) { const errMsg = `*** ERROR: Feature's current end value ${foundFeature.end} doesn't match with expected value ${oldEnd}` logger.error(errMsg) diff --git a/packages/apollo-shared/src/Changes/LocationStartChange.ts b/packages/apollo-shared/src/Changes/LocationStartChange.ts index 87f7d80d0..4de31678f 100644 --- a/packages/apollo-shared/src/Changes/LocationStartChange.ts +++ b/packages/apollo-shared/src/Changes/LocationStartChange.ts @@ -86,8 +86,17 @@ export class LocationStartChange extends FeatureChange { throw new Error(errMsg) } logger.debug?.(`*** Found feature: ${JSON.stringify(foundFeature)}`) + if ( + foundFeature.discontinuousLocations && + foundFeature.discontinuousLocations.length > 0 + ) { + const errMsg = + 'Must use "DiscontinuousLocationStartChange" to change a feature start that has discontinuous locations' + logger.error(errMsg) + throw new Error(errMsg) + } if (foundFeature.start !== oldStart) { - const errMsg = `*** ERROR: Feature's current start value ${topLevelFeature.start} doesn't match with expected value ${oldStart}` + const errMsg = `*** ERROR: Feature's current start value ${foundFeature.start} doesn't match with expected value ${oldStart}` logger.error(errMsg) throw new Error(errMsg) } diff --git a/packages/apollo-shared/src/Changes/index.ts b/packages/apollo-shared/src/Changes/index.ts index 92845bd88..b39bf479c 100644 --- a/packages/apollo-shared/src/Changes/index.ts +++ b/packages/apollo-shared/src/Changes/index.ts @@ -6,7 +6,8 @@ import { AddFeaturesFromFileChange } from './AddFeaturesFromFileChange' import { DeleteAssemblyChange } from './DeleteAssemblyChange' import { DeleteFeatureChange } from './DeleteFeatureChange' import { DeleteUserChange } from './DeleteUserChange' -import { DiscontinuousLocationChange } from './DiscontinuousLocationChange' +import { DiscontinuousLocationEndChange } from './DiscontinuousLocationEndChange' +import { DiscontinuousLocationStartChange } from './DiscontinuousLocationStartChange' import { FeatureAttributeChange } from './FeatureAttributeChange' import { LocationEndChange } from './LocationEndChange' import { LocationStartChange } from './LocationStartChange' @@ -21,11 +22,12 @@ export const changes = { AddFeaturesFromFileChange, DeleteAssemblyChange, DeleteFeatureChange, - FeatureAttributeChange, DeleteUserChange, + DiscontinuousLocationEndChange, + DiscontinuousLocationStartChange, + FeatureAttributeChange, LocationEndChange, LocationStartChange, - DiscontinuousLocationChange, TypeChange, UserChange, } @@ -38,9 +40,10 @@ export * from './AddFeaturesFromFileChange' export * from './DeleteAssemblyChange' export * from './DeleteFeatureChange' export * from './DeleteUserChange' +export * from './DiscontinuousLocationEndChange' +export * from './DiscontinuousLocationStartChange' export * from './FeatureAttributeChange' export * from './LocationEndChange' export * from './LocationStartChange' -export * from './DiscontinuousLocationChange' export * from './TypeChange' export * from './UserChange' diff --git a/packages/jbrowse-plugin-apollo/src/LinearApolloDisplay/glyphs/CanonicalGeneGlyph.ts b/packages/jbrowse-plugin-apollo/src/LinearApolloDisplay/glyphs/CanonicalGeneGlyph.ts index 73d0a98f8..b6adada69 100644 --- a/packages/jbrowse-plugin-apollo/src/LinearApolloDisplay/glyphs/CanonicalGeneGlyph.ts +++ b/packages/jbrowse-plugin-apollo/src/LinearApolloDisplay/glyphs/CanonicalGeneGlyph.ts @@ -1,7 +1,8 @@ import { alpha } from '@mui/material' import { AnnotationFeatureI } from 'apollo-mst' import { - DiscontinuousLocationChange, + DiscontinuousLocationEndChange, + DiscontinuousLocationStartChange, LocationEndChange, LocationStartChange, } from 'apollo-shared' @@ -14,6 +15,12 @@ import { import { CanvasMouseEvent } from '../types' import { Glyph } from './Glyph' +type LocationChange = + | DiscontinuousLocationEndChange + | DiscontinuousLocationStartChange + | LocationEndChange + | LocationStartChange + let forwardFill: CanvasPattern | null = null let backwardFill: CanvasPattern | null = null if ('document' in window) { @@ -989,7 +996,8 @@ export class CanonicalGeneGlyph extends Glyph { const changes: ( | LocationStartChange | LocationEndChange - | DiscontinuousLocationChange + | DiscontinuousLocationEndChange + | DiscontinuousLocationStartChange )[] = [] if (edge === 'start') { @@ -998,12 +1006,15 @@ export class CanonicalGeneGlyph extends Glyph { feature.discontinuousLocations && feature.discontinuousLocations.length > 0 ) { + const oldStart = + feature.discontinuousLocations[discontinuousLocation.idx].start this.addDiscontinuousLocStartChange( changes, feature, newBp, + oldStart, assembly, - discontinuousLocation?.idx, + discontinuousLocation.idx, ) const exonCDSRelations = this.exonCDSRelation(feature, topLevelFeature) @@ -1032,12 +1043,15 @@ export class CanonicalGeneGlyph extends Glyph { feature.discontinuousLocations && feature.discontinuousLocations.length > 0 ) { + const oldEnd = + feature.discontinuousLocations[discontinuousLocation.idx].end this.addDiscontinuousLocEndChange( changes, feature, newBp, + oldEnd, assembly, - discontinuousLocation?.idx, + discontinuousLocation.idx, ) const exonCDSRelations = this.exonCDSRelation(feature, topLevelFeature) @@ -1073,57 +1087,51 @@ export class CanonicalGeneGlyph extends Glyph { } addDiscontinuousLocStartChange( - changes: ( - | LocationStartChange - | LocationEndChange - | DiscontinuousLocationChange - )[], + changes: LocationChange[], feature: AnnotationFeatureI, // cds newBp: number, + oldStart: number, assembly: string, index: number, ) { const featureId = feature._id changes.push( - new DiscontinuousLocationChange({ - typeName: 'DiscontinuousLocationChange', + new DiscontinuousLocationStartChange({ + typeName: 'DiscontinuousLocationStartChange', changedIds: [feature._id], featureId, - start: { newStart: newBp, index }, + newStart: newBp, + oldStart, + index, assembly, }), ) } addDiscontinuousLocEndChange( - changes: ( - | LocationStartChange - | LocationEndChange - | DiscontinuousLocationChange - )[], + changes: LocationChange[], feature: AnnotationFeatureI, // cds newBp: number, + oldEnd: number, assembly: string, index: number, ) { const featureId = feature._id changes.push( - new DiscontinuousLocationChange({ - typeName: 'DiscontinuousLocationChange', + new DiscontinuousLocationEndChange({ + typeName: 'DiscontinuousLocationEndChange', changedIds: [feature._id], featureId, - end: { newEnd: newBp, index }, + newEnd: newBp, + oldEnd, + index, assembly, }), ) } addEndLocationChange( - changes: ( - | LocationStartChange - | LocationEndChange - | DiscontinuousLocationChange - )[], + changes: LocationChange[], feature: AnnotationFeatureI, newBp: number, assembly: string, @@ -1144,11 +1152,7 @@ export class CanonicalGeneGlyph extends Glyph { } addStartLocationChange( - changes: ( - | LocationStartChange - | LocationEndChange - | DiscontinuousLocationChange - )[], + changes: LocationChange[], feature: AnnotationFeatureI, newBp: number, assembly: string, diff --git a/packages/jbrowse-plugin-apollo/src/TabularEditor/HybridGrid/ChangeHandling.ts b/packages/jbrowse-plugin-apollo/src/TabularEditor/HybridGrid/ChangeHandling.ts index 6aa5303ed..fe9ac6a0f 100644 --- a/packages/jbrowse-plugin-apollo/src/TabularEditor/HybridGrid/ChangeHandling.ts +++ b/packages/jbrowse-plugin-apollo/src/TabularEditor/HybridGrid/ChangeHandling.ts @@ -1,5 +1,7 @@ import type { AnnotationFeatureI } from 'apollo-mst' import { + DiscontinuousLocationEndChange, + DiscontinuousLocationStartChange, LocationEndChange, LocationStartChange, TypeChange, @@ -30,16 +32,28 @@ export function handleFeatureStartChange( feature: AnnotationFeatureI, oldStart: number, newStart: number, + index?: number, ) { const featureId = feature._id - const change = new LocationStartChange({ - typeName: 'LocationStartChange', - changedIds: [featureId], - featureId, - oldStart, - newStart, - assembly: feature.assemblyId, - }) + const change = + index === undefined + ? new LocationStartChange({ + typeName: 'LocationStartChange', + changedIds: [featureId], + featureId, + oldStart, + newStart, + assembly: feature.assemblyId, + }) + : new DiscontinuousLocationStartChange({ + typeName: 'DiscontinuousLocationStartChange', + changedIds: [featureId], + featureId, + oldStart, + newStart, + assembly: feature.assemblyId, + index, + }) return changeManager.submit(change) } @@ -48,15 +62,27 @@ export function handleFeatureEndChange( feature: AnnotationFeatureI, oldEnd: number, newEnd: number, + index?: number, ) { const featureId = feature._id - const change = new LocationEndChange({ - typeName: 'LocationEndChange', - changedIds: [featureId], - featureId, - oldEnd, - newEnd, - assembly: feature.assemblyId, - }) + const change = + index === undefined + ? new LocationEndChange({ + typeName: 'LocationEndChange', + changedIds: [featureId], + featureId, + oldEnd, + newEnd, + assembly: feature.assemblyId, + }) + : new DiscontinuousLocationEndChange({ + typeName: 'DiscontinuousLocationEndChange', + changedIds: [featureId], + featureId, + oldEnd, + newEnd, + assembly: feature.assemblyId, + index, + }) return changeManager.submit(change) } diff --git a/packages/jbrowse-plugin-apollo/src/TabularEditor/HybridGrid/Feature.tsx b/packages/jbrowse-plugin-apollo/src/TabularEditor/HybridGrid/Feature.tsx index aead0eeb7..980401813 100644 --- a/packages/jbrowse-plugin-apollo/src/TabularEditor/HybridGrid/Feature.tsx +++ b/packages/jbrowse-plugin-apollo/src/TabularEditor/HybridGrid/Feature.tsx @@ -113,10 +113,20 @@ export const Feature = observer(function Feature({ tabularEditor: tabularEditorState, } = displayState const { featureCollapsed, filterText } = tabularEditorState - const expanded = !featureCollapsed.get(feature._id) + const { + _id, + children, + discontinuousLocations, + end, + phase, + start, + strand, + type, + } = feature + const expanded = !featureCollapsed.get(_id) const toggleExpanded = (e: React.MouseEvent) => { e.stopPropagation() - tabularEditorState.setFeatureCollapsed(feature._id, expanded) + tabularEditorState.setFeatureCollapsed(_id, expanded) } // pop up a snackbar in the session notifying user of an error @@ -159,7 +169,7 @@ export const Feature = observer(function Feature({ borderLeft: `${depth * 2}em solid transparent`, }} > - {feature.children?.size ? ( + {children?.size ? ( // TODO: a11y // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
{ @@ -210,43 +220,78 @@ export const Feature = observer(function Feature({
- - handleFeatureStartChange( - changeManager, - feature, - feature.start, - newStart, - ) - } - /> - - - - handleFeatureEndChange( - changeManager, - feature, - feature.end, - newEnd, - ) - } - /> + {discontinuousLocations && discontinuousLocations.length > 0 ? ( +
+ {discontinuousLocations.map((loc, index) => ( + + handleFeatureStartChange( + changeManager, + feature, + discontinuousLocations[index].start, + newStart, + index, + ) + } + /> + ))} +
+ ) : ( + + handleFeatureStartChange( + changeManager, + feature, + start, + newStart, + ) + } + /> + )} - {feature.strand === 1 ? '+' : feature.strand === -1 ? '-' : undefined} + {discontinuousLocations && discontinuousLocations.length > 0 ? ( +
+ {discontinuousLocations.map((loc, index) => ( + + handleFeatureEndChange( + changeManager, + feature, + discontinuousLocations[index].end, + newEnd, + index, + ) + } + /> + ))} +
+ ) : ( + + handleFeatureEndChange(changeManager, feature, end, newEnd) + } + /> + )} - {feature.phase} + {strand === 1 ? '+' : strand === -1 ? '-' : undefined} + {phase} - {expanded && feature.children - ? [...feature.children.entries()] + {expanded && children + ? [...children.entries()] .filter((entry) => { if (!filterText) { return true