UNPKG

@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
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; };