UNPKG

@jbrowse/plugin-linear-genome-view

Version:

JBrowse 2 linear genome view

306 lines (305 loc) 13 kB
import { lazy } from 'react'; import { ConfigurationReference, getConf, readConfObject, } from '@jbrowse/core/configuration'; import SerializableFilterChain from '@jbrowse/core/pluggableElementTypes/renderers/util/serializableFilterChain'; import { SimpleFeature, getSession } from '@jbrowse/core/util'; import { getRpcSessionId } from '@jbrowse/core/util/tracks'; import { addDisposer, cast, getParent, isAlive, types, } from '@jbrowse/mobx-state-tree'; import ClearAllIcon from '@mui/icons-material/ClearAll'; import VisibilityIcon from '@mui/icons-material/Visibility'; import { reaction, toJS } from 'mobx'; import { BaseLinearDisplay } from "../BaseLinearDisplay/index.js"; const SetMaxHeightDialog = lazy(() => import("./components/SetMaxHeightDialog.js")); const AddFiltersDialog = lazy(() => import("./components/AddFiltersDialog.js")); function stateModelFactory(configSchema) { return types .compose('LinearFeatureDisplay', BaseLinearDisplay, types.model({ type: types.literal('LinearFeatureDisplay'), trackShowLabels: types.maybe(types.boolean), trackShowDescriptions: types.maybe(types.boolean), trackDisplayMode: types.maybe(types.string), trackMaxHeight: types.maybe(types.number), configuration: ConfigurationReference(configSchema), jexlFiltersSetting: types.maybe(types.array(types.string)), })) .volatile(() => ({ featureUnderMouseVolatile: undefined, })) .views(self => ({ activeFilters() { return (toJS(self.jexlFiltersSetting) ?? getConf(self, 'jexlFilters').map((r) => `jexl:${r}`)); }, get rendererTypeName() { return getConf(self, ['renderer', 'type']); }, get sequenceAdapter() { const { assemblyManager } = getSession(self); const track = getParent(self, 2); const assemblyNames = readConfObject(track.configuration, 'assemblyNames'); const assembly = assemblyManager.get(assemblyNames[0]); return assembly ? getConf(assembly, ['sequence', 'adapter']) : undefined; }, get showLabels() { return self.trackShowLabels ?? getConf(self, ['renderer', 'showLabels']); }, get showDescriptions() { return (self.trackShowDescriptions ?? getConf(self, ['renderer', 'showDescriptions'])); }, get maxHeight() { return self.trackMaxHeight ?? getConf(self, ['renderer', 'maxHeight']); }, get displayMode() { return (self.trackDisplayMode ?? getConf(self, ['renderer', 'displayMode'])); }, })) .views(self => ({ get rendererConfig() { const configBlob = getConf(self, ['renderer']) || {}; const config = configBlob; return { ...config, showLabels: self.trackShowLabels ?? config.showLabels, showDescriptions: self.trackShowDescriptions ?? config.showDescriptions, displayMode: self.trackDisplayMode ?? config.displayMode, maxHeight: self.trackMaxHeight ?? config.maxHeight, }; }, })) .actions(self => ({ setJexlFilters(f) { self.jexlFiltersSetting = cast(f); }, setFeatureUnderMouse(feat) { self.featureUnderMouseVolatile = feat; }, toggleShowLabels() { self.trackShowLabels = !self.showLabels; }, toggleShowDescriptions() { self.trackShowDescriptions = !self.showDescriptions; }, setDisplayMode(val) { self.trackDisplayMode = val; }, setMaxHeight(val) { self.trackMaxHeight = val; }, })) .views(self => ({ get featureUnderMouse() { return self.featureUnderMouseVolatile; }, })) .views(self => { const { renderProps: superRenderProps, renderingProps: superRenderingProps, } = self; return { renderProps() { const superProps = superRenderProps(); return { ...superProps, config: self.rendererConfig, filters: new SerializableFilterChain({ filters: self.activeFilters(), }), sequenceAdapter: self.sequenceAdapter, }; }, renderingProps() { const superProps = superRenderingProps(); const session = getSession(self); return { ...superProps, async onFeatureClick(_, featureId) { const { rpcManager } = session; const { parentTrack } = self; try { const f = featureId || self.featureIdUnderMouse; if (!f) { self.clearFeatureSelection(); } else { const sessionId = getRpcSessionId(self); const { feature } = (await rpcManager.call(sessionId, 'CoreGetFeatureDetails', { featureId: f, sessionId, trackInstanceId: parentTrack.id, rendererType: self.rendererTypeName, })); if (isAlive(self) && feature) { self.selectFeature(new SimpleFeature(feature)); } } } catch (e) { console.error(e); session.notifyError(`${e}`, e); } }, async onFeatureContextMenu(_, featureId) { const { rpcManager } = session; const { parentTrack } = self; try { const f = featureId || self.featureIdUnderMouse; if (!f) { self.clearFeatureSelection(); } else { const sessionId = getRpcSessionId(self); const { feature } = (await rpcManager.call(sessionId, 'CoreGetFeatureDetails', { featureId: f, sessionId, trackInstanceId: parentTrack.id, rendererType: self.rendererTypeName, })); if (isAlive(self) && feature) { self.setContextMenuFeature(new SimpleFeature(feature)); } } } catch (e) { console.error(e); session.notifyError(`${e}`, e); } }, }; }, filterMenuItems() { return [ { label: 'Edit filters...', onClick: () => { getSession(self).queueDialog(handleClose => [ AddFiltersDialog, { model: self, handleClose, }, ]); }, }, ]; }, }; }) .views(self => { const { trackMenuItems: superTrackMenuItems } = self; return { trackMenuItems() { return [ ...superTrackMenuItems(), { label: 'Show...', icon: VisibilityIcon, subMenu: [ { label: 'Show tooltips', type: 'checkbox', checked: self.showTooltipsEnabled, onClick: () => { self.setShowTooltips(!self.showTooltipsEnabled); }, }, { label: 'Show labels', type: 'checkbox', checked: self.showLabels, onClick: () => { self.toggleShowLabels(); }, }, { label: 'Show descriptions', type: 'checkbox', checked: self.showDescriptions, onClick: () => { self.toggleShowDescriptions(); }, }, ], }, { label: 'Display mode', icon: VisibilityIcon, subMenu: [ 'normal', 'compact', 'reducedRepresentation', 'collapse', ].map(val => ({ label: val, type: 'radio', checked: self.displayMode === val, onClick: () => { self.setDisplayMode(val); }, })), }, { label: 'Set max track height', onClick: () => { getSession(self).queueDialog(handleClose => [ SetMaxHeightDialog, { model: self, handleClose, }, ]); }, }, { label: 'Filter by...', icon: ClearAllIcon, subMenu: self.filterMenuItems(), }, ]; }, }; }) .actions(self => ({ afterAttach() { addDisposer(self, reaction(() => self.featureIdUnderMouse, async (featureId) => { if (!featureId) { self.setFeatureUnderMouse(undefined); return; } const session = getSession(self); const { rpcManager } = session; const { parentTrack } = self; try { const sessionId = getRpcSessionId(self); const { feature } = (await rpcManager.call(sessionId, 'CoreGetFeatureDetails', { featureId, sessionId, trackInstanceId: parentTrack.id, rendererType: self.rendererTypeName, })); if (isAlive(self) && feature && self.featureIdUnderMouse === featureId) { self.setFeatureUnderMouse(new SimpleFeature(feature)); } } catch (e) { } }, { delay: 50, name: 'LinearFeatureDisplayMouseoverReaction' })); }, })) .postProcessSnapshot(snap => { if (!snap) { return snap; } const { trackShowLabels, trackShowDescriptions, trackDisplayMode, trackMaxHeight, jexlFiltersSetting, ...rest } = snap; return { ...rest, ...(trackShowLabels !== undefined ? { trackShowLabels } : {}), ...(trackShowDescriptions !== undefined ? { trackShowDescriptions } : {}), ...(trackDisplayMode !== undefined ? { trackDisplayMode } : {}), ...(trackMaxHeight !== undefined ? { trackMaxHeight } : {}), ...(jexlFiltersSetting?.length ? { jexlFiltersSetting } : {}), }; }); } export default stateModelFactory;