UNPKG

@jbrowse/core

Version:

JBrowse 2 core libraries used by plugins

240 lines (239 loc) 6.96 kB
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; }