@jbrowse/core
Version:
JBrowse 2 core libraries used by plugins
240 lines (239 loc) • 6.96 kB
JavaScript
import { types } from '@jbrowse/mobx-state-tree';
import { getEnv } from "../util/index.js";
import { stringToJexlExpression } from "../util/jexlStrings.js";
import { FileLocation } from "../util/types/mst.js";
function isValidColorString(_str) {
return true;
}
const typeModels = {
stringArray: types.array(types.string),
stringArrayMap: types.map(types.array(types.string)),
numberMap: types.map(types.number),
boolean: types.boolean,
color: types.refinement('Color', types.string, isValidColorString),
integer: types.integer,
number: types.number,
string: types.string,
text: types.string,
fileLocation: FileLocation,
frozen: types.frozen(),
};
const fallbackDefaults = {
stringArray: [],
stringArrayMap: {},
numberMap: {},
boolean: true,
color: 'black',
integer: 1,
number: 1,
string: '',
text: '',
fileLocation: { uri: '/path/to/resource.txt', locationType: 'UriLocation' },
frozen: {},
};
const literalJSON = (self) => ({
views: {
get valueJSON() {
return self.value;
},
},
});
const objectJSON = (self) => ({
views: {
get valueJSON() {
return JSON.stringify(self.value);
},
},
});
const typeModelExtensions = {
fileLocation: objectJSON,
number: literalJSON,
integer: literalJSON,
boolean: literalJSON,
frozen: objectJSON,
stringArray: (self) => ({
views: {
get valueJSON() {
return JSON.stringify(self.value);
},
},
actions: {
add(val) {
self.value.push(val);
},
removeAtIndex(idx) {
self.value.splice(idx, 1);
},
setAtIndex(idx, val) {
self.value[idx] = val;
},
},
}),
stringArrayMap: (self) => ({
views: {
get valueJSON() {
return JSON.stringify(self.value);
},
},
actions: {
add(key, val) {
self.value.set(key, val);
},
remove(key) {
self.value.delete(key);
},
addToKey(key, val) {
const ar = self.value.get(key);
if (!ar) {
throw new Error(`${key} not found`);
}
ar.push(val);
},
removeAtKeyIndex(key, idx) {
const ar = self.value.get(key);
if (!ar) {
throw new Error(`${key} not found`);
}
ar.splice(idx, 1);
},
setAtKeyIndex(key, idx, val) {
const ar = self.value.get(key);
if (!ar) {
throw new Error(`${key} not found`);
}
ar[idx] = val;
},
},
}),
numberMap: (self) => ({
views: {
get valueJSON() {
return JSON.stringify(self.value);
},
},
actions: {
add(key, val) {
self.value.set(key, val);
},
remove(key) {
self.value.delete(key);
},
},
}),
};
const JexlStringType = types.refinement('JexlString', types.string, str => str.startsWith('jexl:'));
function json(value) {
return value?.toJSON ? value.toJSON() : `"${value}"`;
}
export default function ConfigSlot(slotName, { description = '', model, type, defaultValue, contextVariable = [], }) {
if (!type) {
throw new Error('type name required');
}
if (!model) {
model = typeModels[type];
}
if (!model) {
throw new Error(`no builtin config slot type "${type}", and no 'model' param provided`);
}
if (defaultValue === undefined) {
throw new Error("no 'defaultValue' provided");
}
const configSlotModelName = `${slotName.charAt(0).toUpperCase()}${slotName.slice(1)}ConfigSlot`;
let slot = types
.model(configSlotModelName, {
name: types.literal(slotName),
description: types.literal(description),
type: types.literal(type),
value: types.optional(types.union(JexlStringType, model), defaultValue),
})
.volatile(() => ({
contextVariable,
}))
.views(self => ({
get isCallback() {
return String(self.value).startsWith('jexl:');
},
}))
.views(self => ({
get expr() {
return self.isCallback
? stringToJexlExpression(String(self.value), getEnv(self).pluginManager.jexl)
: {
eval: () => self.value,
};
},
get valueJSON() {
return self.isCallback ? undefined : json(self.value);
},
}))
.views(self => ({
getValue(args = {}) {
const v = self.value;
return typeof v === 'string' && v.startsWith('jexl:')
? self.expr.eval(args)
: v;
},
}))
.preProcessSnapshot(val => typeof val === 'object' && val.name === slotName
? val
: {
name: slotName,
description,
type,
value: val,
})
.postProcessSnapshot(snap => {
if (typeof snap.value === 'object') {
return JSON.stringify(snap.value) !== JSON.stringify(defaultValue)
? snap.value
: undefined;
}
return snap.value !== defaultValue ? snap.value : undefined;
})
.actions(self => ({
set(newVal) {
self.value = newVal;
},
reset() {
self.value = defaultValue;
},
convertToCallback() {
if (self.isCallback) {
return;
}
self.value = `jexl:${self.valueJSON || "''"}`;
},
convertToValue() {
if (!self.isCallback) {
return;
}
try {
const funcResult = self.expr.eval();
if (funcResult !== undefined) {
self.value = funcResult;
return;
}
}
catch (e) {
}
self.value = defaultValue;
if (!(type in fallbackDefaults)) {
throw new Error(`no fallbackDefault defined for type ${type}`);
}
self.value = fallbackDefaults[type];
},
}));
if (typeModelExtensions[type]) {
slot = slot.extend(typeModelExtensions[type]);
}
const completeModel = types.optional(slot, {
name: slotName,
type,
description,
value: defaultValue,
});
Object.defineProperty(completeModel, 'isJBrowseConfigurationSlot', {
value: true,
});
return completeModel;
}