@metacell/geppetto-meta-client
Version:
Geppetto web frontend. Geppetto is an open-source platform to build web-based tools to visualize and simulate neuroscience data and models.
644 lines (627 loc) • 24.3 kB
JavaScript
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }
function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; }
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
import * as React from "react";
import * as FlexLayout from "flexlayout-react";
import { Actions, DockLocation } from "flexlayout-react";
import { WidgetStatus } from "./model";
import WidgetFactory from "./WidgetFactory";
import TabsetIconFactory from "./TabsetIconFactory";
import defaultLayoutConfiguration from "./defaultLayout";
import { getWidget as _getWidget, widget2Node } from "./utils";
import * as GeppettoActions from "../actions";
import { layoutActions, removeWidgetFromStore, setLayout, updateLayout } from "./actions";
import { MinimizeHelper } from "./helpers/MinimizeHelper";
import { createTabSet, moveWidget } from "./helpers/FlexLayoutHelper";
var styles = {
container: {
flexGrow: 1,
display: "flex",
flexDirection: "row",
alignItems: "stretch",
position: "relative"
},
flexlayout: {
flexGrow: 1,
position: "relative"
}
};
var instance = null;
/**
* Wraps the FlexLayout component in order to allow a declarative specification (widgets).
* of the layout and the components displayed.
*
* Handles layout state update and layout import and export.
*
* @memberof Control
*/
export var LayoutManager = /*#__PURE__*/function () {
/**
* @constructor
* @param model
* @param componentMap
* @param tabsetIconFactory
* @param isMinimizeEnabled
*/
function LayoutManager(model, componentMap) {
var _this = this;
var tabsetIconFactory = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
var isMinimizeEnabled = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
_classCallCheck(this, LayoutManager);
_defineProperty(this, "model", void 0);
/**
* Used to restore weights from the default layout
*/
_defineProperty(this, "defaultWeights", {});
_defineProperty(this, "widgetFactory", void 0);
_defineProperty(this, "tabsetIconFactory", void 0);
_defineProperty(this, "store", void 0);
_defineProperty(this, "layoutManager", this);
_defineProperty(this, "minimizeHelper", void 0);
_defineProperty(this, "layout", void 0);
/**
* Handle rendering of tab set.
*
* @param panel
* @param renderValues
* @param tabSetButtons
*/
_defineProperty(this, "onRenderTabSet", function (panel, renderValues, tabSetButtons) {
if (panel.getType() === "tabset") {
_this.minimizeHelper.addMinimizeButtonToTabset(panel, renderValues);
if (Array.isArray(tabSetButtons) && tabSetButtons.length > 0) {
tabSetButtons.forEach(function (Button) {
renderValues.stickyButtons.push( /*#__PURE__*/React.createElement(Button, {
key: panel.getId(),
panel: panel
}));
});
}
}
});
/**
* Handle rendering of tab set.
*
* @param panel
* @param renderValues
* @param tabButtons
*/
_defineProperty(this, "onRenderTab", function (panel, renderValues, tabButtons) {
if (panel.getType() === "tab") {
if (Array.isArray(tabButtons) && tabButtons.length > 0) {
tabButtons.forEach(function (Button) {
renderValues.buttons.push( /*#__PURE__*/React.createElement(Button, {
key: panel.getId(),
panel: panel
}));
});
}
}
});
/**
* Layout wrapper component
*
* @memberof Component
*
*/
_defineProperty(this, "Component", function (layoutManager, config) {
return function (props) {
return /*#__PURE__*/React.createElement("div", {
className: "layout-outer-wrapper",
style: styles.container
}, /*#__PURE__*/React.createElement("div", {
className: "layout-wrapper",
style: styles.flexlayout
}, /*#__PURE__*/React.createElement(FlexLayout.Layout, {
ref: _this.layout,
model: _this.model,
factory: _this.factory,
icons: config === null || config === void 0 ? void 0 : config.icons
// iconFactory={layoutManager.iconFactory.bind(this)}
,
onAction: function onAction(action) {
return layoutManager.onAction(action);
},
onRenderTab: function onRenderTab(node, renderValues) {
return layoutManager.onRenderTab(node, renderValues, config === null || config === void 0 ? void 0 : config.tabButtons);
},
onRenderTabSet: function onRenderTabSet(node, renderValues) {
layoutManager.onRenderTabSet(node, renderValues, config === null || config === void 0 ? void 0 : config.tabSetButtons);
}
})));
};
});
/**
* Get the layout component.
* @memberof Control
*/
_defineProperty(this, "getComponent", function (config) {
return _this.Component(_this, config);
});
/**
* Layout manager Redux middleware.
* Sets the layout from Redux actions.
*
* @memberof Control
* @memberof Control
*/
_defineProperty(this, "middleware", function (store) {
return function (next) {
return function (action) {
if (!_this.store) {
next(setLayout(_this.model));
}
// This is a hack to unlock transitory state in the model before any other action is dispatched. See https://metacell.atlassian.net/browse/GEP-126
// @ts-ignore
// On the last version it looks like the n
// this.model.doAction(Actions.UPDATE_MODEL_ATTRIBUTES, {});
_this.store = store;
_this.widgetFactory.setStore(store);
_this.minimizeHelper.setStore(store);
var nextAction = true;
var nextSetLayout = true;
switch (action.type) {
case layoutActions.ADD_WIDGET:
{
_this.addWidget(action.data);
break;
}
case layoutActions.ADD_WIDGETS:
{
_this.addWidgets(action.data);
break;
}
case layoutActions.UPDATE_WIDGET:
{
var _this$layout;
var updatedWidget = _this.updateWidget(action.data);
action = _objectSpread(_objectSpread({}, action), {}, {
data: updatedWidget
});
(_this$layout = _this.layout) === null || _this$layout === void 0 || (_this$layout = _this$layout.current) === null || _this$layout === void 0 || _this$layout.redraw();
break;
}
case layoutActions.DESTROY_WIDGET:
{
var widget = action.data;
_this.deleteWidget(widget);
break;
}
case layoutActions.REMOVE_WIDGET:
{
var _widget = action.data;
_this.widgetFactory.deleteWidget(_widget.id);
break;
}
case layoutActions.ACTIVATE_WIDGET:
{
action.data.status = WidgetStatus.ACTIVE;
var _widget2 = _this.getWidget(action.data.id);
_widget2.status = WidgetStatus.ACTIVE;
_this.updateWidget(_widget2);
break;
}
case layoutActions.SET_WIDGETS:
{
var newWidgets = action.data;
var _iterator = _createForOfIteratorHelper(_this.getWidgets()),
_step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done;) {
var _widget3 = _step.value;
if (!newWidgets[_widget3.id]) {
_this.deleteWidget(_widget3);
}
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
_this.addWidgets(Object.values(newWidgets));
break;
}
case layoutActions.SET_LAYOUT:
{
next(setLayout(action.data));
return;
}
case GeppettoActions.IMPORT_APPLICATION_STATE:
{
var incomingState = action.data.redux.layout;
_this.model = FlexLayout.Model.fromJson(incomingState);
_this.minimizeHelper = new MinimizeHelper(_this.minimizeHelper.getIsMinimizeEnabled(), _this.model);
_this.importSession(action.data.sessions);
nextSetLayout = false;
}
default:
{
nextSetLayout = false;
}
}
if (nextAction) {
next(action);
}
if (nextSetLayout) {
_this.fixRowRecursive(_this.model.getRoot());
next(updateLayout(_this.model));
}
};
};
});
this.setLayout(model);
this.widgetFactory = new WidgetFactory(componentMap);
this.tabsetIconFactory = tabsetIconFactory ? tabsetIconFactory : new TabsetIconFactory();
this.middleware = this.middleware.bind(this);
this.factory = this.factory.bind(this);
this.minimizeHelper = new MinimizeHelper(isMinimizeEnabled, this.model);
this.layout = /*#__PURE__*/React.createRef();
}
return _createClass(LayoutManager, [{
key: "setLayout",
value: function setLayout(model) {
var _this2 = this;
this.model = FlexLayout.Model.fromJson(model || defaultLayoutConfiguration);
this.model.visitNodes(function (node) {
var fn = node === null || node === void 0 ? void 0 : node.getWeight;
if (!fn) {
return;
}
_this2.defaultWeights[node.getId()] = fn.bind(node)();
});
// for (const node of allNodes) {
// this.defaultWeights[node.getId()] = (node as BaseNode).getWeight();
// console.log("Visit", node)
// }
this.fixRowRecursive(this.model.getRoot());
}
/**
* Adds a widget to the layout.
*
* @param {Widget} widgetConfiguration widget to add
*/
}, {
key: "addWidget",
value: function addWidget(widgetConfiguration) {
var _widgetConfiguration$;
if (this.getWidget(widgetConfiguration.id) && this.model.getNodeById(widgetConfiguration.id)) {
return this.updateWidget(widgetConfiguration);
}
var model = this.model;
var tabset = model.getNodeById(widgetConfiguration.panelName);
if (tabset === undefined) {
tabset = createTabSet(this.model, widgetConfiguration.panelName, widgetConfiguration.defaultPosition, widgetConfiguration.defaultWeight);
}
widgetConfiguration.pos = (_widgetConfiguration$ = widgetConfiguration.pos) !== null && _widgetConfiguration$ !== void 0 ? _widgetConfiguration$ : tabset.getChildren().length;
this.model.doAction(Actions.addNode(widget2Node(widgetConfiguration), widgetConfiguration.panelName, DockLocation.CENTER, widgetConfiguration.pos, widgetConfiguration.status === WidgetStatus.ACTIVE));
}
}, {
key: "exportSession",
value:
/**
* Export a session.
*/
function exportSession() {
var confs = {};
var components = this.widgetFactory.getComponents();
for (var wid in components) {
confs[wid] = components[wid].exportSession();
}
return confs;
}
/**
* Import a widget session.
*
* @param {string} widgetId id of widget
* @param conf widget configuration
*/
}, {
key: "importWidgetSession",
value: function importWidgetSession(widgetId, conf) {
var _this3 = this;
var component = this.widgetFactory.getComponent(widgetId);
if (component) {
try {
component.importSession(conf);
} catch (e) {
console.error("Error importing session for", widgetId, e);
}
} else {
// The component may not be yet initialized when loading the session
setTimeout(function () {
return _this3.importWidgetSession(widgetId, conf);
}, 100);
}
}
/**
* Import complete session.
*
* @param confs configuration map
*/
}, {
key: "importSession",
value: function importSession(confs) {
var imported = new Set();
for (var wid in confs) {
this.importWidgetSession(wid, confs[wid]);
imported.add(wid);
}
// Some components may have a current status here but no state exported in the session file
for (var _wid in this.widgetFactory.getComponents()) {
if (!imported.has(_wid)) {
this.importWidgetSession(_wid, null);
}
}
}
}, {
key: "setTabsetWeight",
value: function setTabsetWeight(node, weight) {
this.model.doAction(FlexLayout.Actions.updateNodeAttributes(node.getId(), {
weight: 0
}));
}
}, {
key: "restoreWeight",
value: function restoreWeight(node) {
var _baseNode$getWeight;
var baseNode = node;
if ((baseNode === null || baseNode === void 0 || (_baseNode$getWeight = baseNode.getWeight) === null || _baseNode$getWeight === void 0 ? void 0 : _baseNode$getWeight.call(baseNode)) === 0) {
var _this$defaultWeights$;
this.setTabsetWeight(baseNode, (_this$defaultWeights$ = this.defaultWeights[baseNode.getId()]) !== null && _this$defaultWeights$ !== void 0 ? _this$defaultWeights$ : 50);
}
if (baseNode.getParent()) {
this.restoreWeight(baseNode.getParent());
}
}
}, {
key: "containsWidget",
value: function containsWidget(node) {
if (node.getChildren().length === 0 || !node.getChildren().every(function (n) {
return n.getType() === "tab";
})) {
return false;
}
var _iterator2 = _createForOfIteratorHelper(node.getChildren()),
_step2;
try {
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
var child = _step2.value;
if (this.containsWidget(child)) {
return true;
}
}
} catch (err) {
_iterator2.e(err);
} finally {
_iterator2.f();
}
return false;
}
}, {
key: "fixRowRecursive",
value: function fixRowRecursive(node) {
if (node.getType() === "row" || node.getType() === "tabset") {
if (node.getChildren().length === 0) {
this.setTabsetWeight(node, 0);
return true;
} else {
var empty = true;
var _iterator3 = _createForOfIteratorHelper(node.getChildren()),
_step3;
try {
for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
var child = _step3.value;
empty = this.fixRowRecursive(child) && empty;
}
} catch (err) {
_iterator3.e(err);
} finally {
_iterator3.f();
}
if (!empty) {
this.restoreWeight(node);
} else {
this.setTabsetWeight(node, 0);
}
return empty;
}
}
return false;
}
/**
* Add a list of widgets.
*
* @param {Array<Widget>} newWidgets list of widgets
* @private
*/
}, {
key: "addWidgets",
value: function addWidgets(newWidgets) {
var actives = [];
var _iterator4 = _createForOfIteratorHelper(newWidgets),
_step4;
try {
for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
var widget = _step4.value;
if (widget.status === WidgetStatus.ACTIVE) {
actives.push(widget.id);
}
this.addWidget(widget);
}
} catch (err) {
_iterator4.e(err);
} finally {
_iterator4.f();
}
for (var _i = 0, _actives = actives; _i < _actives.length; _i++) {
var active = _actives[_i];
this.model.doAction(FlexLayout.Actions.selectTab(active));
}
}
/**
* Delete a widget.
*
* @param widget
* @private
*/
}, {
key: "deleteWidget",
value: function deleteWidget(widget) {
this.model.doAction(Actions.deleteTab(widget.id));
this.widgetFactory.deleteWidget(widget.id);
}
/**
* Return widgets.
*
* @private
*/
}, {
key: "getWidgets",
value: function getWidgets() {
return Object.values(this.store.getState().widgets);
}
/**
* Get specific widget.
*
* @param id
* @private
*/
}, {
key: "getWidget",
value: function getWidget(id) {
return _getWidget(this.store, id);
}
/**
* Handles state update related to actions in the flex layout
* (e.g. select or move tab)
*
* @memberof Control
* @param action
*/
}, {
key: "onAction",
value: function onAction(action) {
var oldModel = this.model.toJson();
var defaultAction = true;
switch (action.type) {
case Actions.SET_ACTIVE_TABSET:
break;
case Actions.SELECT_TAB:
var widget = this.getWidget(action.data.tabNode);
if (widget && widget.status === WidgetStatus.MINIMIZED) {
this.minimizeHelper.restoreWidget(widget);
}
break;
case Actions.DELETE_TAB:
{
if (this.getWidget(action.data.node).hideOnClose) {
// widget only minimized, won't be removed from layout nor widgets list
this.minimizeHelper.minimizeWidget(action.data.node);
defaultAction = false;
} else {
// remove widget from widgets list
this.store.dispatch(removeWidgetFromStore(action.data.node));
}
break;
}
case Actions.MAXIMIZE_TOGGLE:
// reminder, widgets are not maximised but tabsets are
break;
case Actions.RENAME_TAB:
break;
// case Actions.ADJUST_SPLIT:
// break;
case Actions.ADD_NODE:
{
break;
}
case Actions.MOVE_NODE:
{
break;
}
default:
{
this.model.doAction(action);
}
}
if (defaultAction) {
this.model.doAction(action);
}
this.fixRowRecursive(this.model.getRoot());
var newModel = this.model.toJson();
if (oldModel !== newModel) {
this.store.dispatch(updateLayout(this.model));
}
return undefined;
}
/**
* Update a widget.
*
* @param widget
* @private
*/
}, {
key: "updateWidget",
value: function updateWidget(widget) {
var model = this.model;
var previousWidget = this.getWidget(widget.id);
var mergedWidget = _objectSpread(_objectSpread({}, previousWidget), widget);
var widgetRestored = this.minimizeHelper.restoreWidgetIfNecessary(previousWidget, mergedWidget);
if (!widgetRestored) {
moveWidget(model, mergedWidget);
}
this.widgetFactory.updateWidget(mergedWidget);
var node = this.model.getNodeById(widget.id);
if (node) {
model.doAction(Actions.updateNodeAttributes(mergedWidget.id, widget2Node(mergedWidget)));
if (mergedWidget.status === WidgetStatus.ACTIVE) {
model.doAction(FlexLayout.Actions.selectTab(mergedWidget.id));
}
var parent = node.getParent();
if (widget.status === WidgetStatus.MAXIMIZED && !parent.isMaximized() || widget.status === WidgetStatus.ACTIVE && parent.isMaximized()) {
this.model.doAction(FlexLayout.Actions.maximizeToggle(node.getParent().getId()));
} else if (widget.status === WidgetStatus.MINIMIZED && !this.minimizeHelper.isMinimized(widget)) {
this.minimizeHelper.minimizeWidget(node.getId());
}
}
return mergedWidget;
}
/**
* Create widget for node.
*
* @param node
*/
}, {
key: "factory",
value: function factory(node) {
return this.widgetFactory.factory(node.getConfig());
}
/**
* Create icon for node.
*
* @param node
*/
}, {
key: "iconFactory",
value: function iconFactory(node) {
// TODO move to newest flexlayout-react to add this functionality when needed
return this.tabsetIconFactory.factory(node.getConfig());
}
}]);
}();
export function initLayoutManager(model, componentMap, iconFactory, isMinimizeEnabled) {
instance = new LayoutManager(model, componentMap, iconFactory, isMinimizeEnabled);
return instance;
}
export var getLayoutManagerInstance = function getLayoutManagerInstance() {
return instance;
};