@jbrowse/plugin-linear-genome-view
Version:
JBrowse 2 linear genome view
306 lines (305 loc) • 13 kB
JavaScript
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;