@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.
529 lines (521 loc) • 24.8 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 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 _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 _superPropGet(t, e, r, o) { var p = _get(_getPrototypeOf(1 & o ? t.prototype : t), e, r); return 2 & o ? function (t) { return p.apply(r, t); } : p; }
function _get() { return _get = "undefined" != typeof Reflect && Reflect.get ? Reflect.get.bind() : function (e, t, r) { var p = _superPropBase(e, t); if (p) { var n = Object.getOwnPropertyDescriptor(p, t); return n.get ? n.get.call(arguments.length < 3 ? e : r) : n.value; } }, _get.apply(null, arguments); }
function _superPropBase(t, o) { for (; !{}.hasOwnProperty.call(t, o) && null !== (t = _getPrototypeOf(t));); return t; }
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 _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); }
function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); }
function _possibleConstructorReturn(t, e) { if (e && ("object" == _typeof(e) || "function" == typeof e)) return e; if (void 0 !== e) throw new TypeError("Derived constructors may only return object or undefined"); return _assertThisInitialized(t); }
function _assertThisInitialized(e) { if (void 0 === e) throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); return e; }
function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }
function _getPrototypeOf(t) { return _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function (t) { return t.__proto__ || Object.getPrototypeOf(t); }, _getPrototypeOf(t); }
function _inherits(t, e) { if ("function" != typeof e && null !== e) throw new TypeError("Super expression must either be null or a function"); t.prototype = Object.create(e && e.prototype, { constructor: { value: t, writable: !0, configurable: !0 } }), Object.defineProperty(t, "prototype", { writable: !1 }), e && _setPrototypeOf(t, e); }
function _setPrototypeOf(t, e) { return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (t, e) { return t.__proto__ = e, t; }, _setPrototypeOf(t, e); }
/**
*
* Synched capability
* @module Widgets/Widget
* @author Adrian Quintana (adrian@metacell.us)
* @author Matteo Cantarelli (matteo@metacell.us)
*/
import React from 'react';
import Utils from './GeppettoJupyterUtils';
export default function createPythonControlledComponent(WrappedComponent) {
if (typeof WrappedComponent !== 'function') {
var Wrapper = /*#__PURE__*/function (_React$Component) {
function Wrapper() {
_classCallCheck(this, Wrapper);
return _callSuper(this, Wrapper, arguments);
}
_inherits(Wrapper, _React$Component);
return _createClass(Wrapper, [{
key: "render",
value: function render() {
return /*#__PURE__*/React.createElement(WrappedComponent, this.props);
}
}]);
}(React.Component);
WrappedComponent = Wrapper;
}
var PythonControlledComponent = /*#__PURE__*/function (_WrappedComponent) {
function PythonControlledComponent(props) {
var _this;
_classCallCheck(this, PythonControlledComponent);
_this = _callSuper(this, PythonControlledComponent, [props]);
if (_this.state == undefined) {
_this.state = {};
}
_this.state.model = props.model;
_this.state.componentType = getNameFromWrappedComponent(WrappedComponent);
_this.id = _this.props.id == undefined ? _this.props.model : _this.props.id;
_this._isMounted = false;
return _this;
}
_inherits(PythonControlledComponent, _WrappedComponent);
return _createClass(PythonControlledComponent, [{
key: "setSyncValueWithPythonHandler",
value: function setSyncValueWithPythonHandler(handler) {
this.syncValueWithPython = handler;
}
}, {
key: "connectToPython",
value: function connectToPython(componentType, model) {
Utils.execPythonMessage('jupyter_geppetto.ComponentSync(componentType="' + componentType + '",model="' + model + '",id="' + this.id + '").connect()');
}
}, {
key: "disconnectFromPython",
value: function disconnectFromPython() {
Utils.execPythonMessage('jupyter_geppetto.remove_component_sync(componentType="' + this.state.componentType + '",model="' + this.id + '")');
}
}, {
key: "componentWillUnmount",
value: function componentWillUnmount() {
this._isMounted = false;
this.disconnectFromPython();
}
}, {
key: "UNSAFE_componentWillReceiveProps",
value: function UNSAFE_componentWillReceiveProps(nextProps) {
this.disconnectFromPython();
this.id = nextProps.id == undefined ? nextProps.model : nextProps.id;
this.connectToPython(this.state.componentType, nextProps.model);
if (this.state.value != nextProps.value) {
this.setState({
value: nextProps.value === undefined ? '' : nextProps.value
});
}
}
}, {
key: "componentDidMount",
value: function componentDidMount() {
this._isMounted = true;
if (this.props.model != undefined) {
this.connectToPython(this.state.componentType, this.props.model);
}
if (this.props.value != undefined) {
this.setState({
value: this.props.value
});
}
}
}]);
}(WrappedComponent);
return PythonControlledComponent;
}
export function createPythonControlledControl(WrappedComponent) {
var PythonControlledComponent = this.createPythonControlledComponent(WrappedComponent);
var PythonControlledControl = /*#__PURE__*/function (_PythonControlledComp) {
function PythonControlledControl(props) {
var _this2;
_classCallCheck(this, PythonControlledControl);
_this2 = _callSuper(this, PythonControlledControl, [props]);
_this2.state = _objectSpread(_objectSpread({}, _this2.state), {}, {
value: '',
searchText: '',
checked: false
});
// If a handleChange method is passed as a props it will overwrite the handleChange python controlled capability
_this2.handleChange = _this2.props.handleChange === undefined ? _this2.handleChange.bind(_this2) : _this2.props.handleChange.bind(_this2);
_this2.handleUpdateInput = _this2.handleUpdateInput.bind(_this2);
_this2.handleUpdateCheckbox = _this2.handleUpdateCheckbox.bind(_this2);
return _this2;
}
_inherits(PythonControlledControl, _PythonControlledComp);
return _createClass(PythonControlledControl, [{
key: "componentDidMount",
value: function componentDidMount() {
_superPropGet(PythonControlledControl, "componentDidMount", this, 3)([]);
this.UNRELIABLE_SyncDefaultValueWithPython();
}
/*
* since we don't know when a component will be synched with python,
* we can't know when to check if this.state.value should be replaced
* with this.props.default
*/
}, {
key: "UNRELIABLE_SyncDefaultValueWithPython",
value: function UNRELIABLE_SyncDefaultValueWithPython() {
var _this3 = this;
var timeInterval = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 100;
var attemps = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
if (attemps < 3) {
setTimeout(function () {
if (_this3.props["default"] && _this3.state.value === '') {
if (_this3.syncValueWithPython) {
// this function is added by jupyter_geppetto after the component is synched with python
_this3.syncValueWithPython(_this3.props["default"]);
} else {
_this3.UNRELIABLE_SyncDefaultValueWithPython(timeInterval * 2, attemps + 1);
}
}
}, timeInterval);
} else {
console.warn("Tried to sync default value for ".concat(this.props.model, " and failed after 3 attemps."));
}
}
}, {
key: "UNSAFE_componentWillReceiveProps",
value: function UNSAFE_componentWillReceiveProps(nextProps) {
this.disconnectFromPython();
this.id = nextProps.id == undefined ? nextProps.model : nextProps.id;
this.connectToPython(this.state.componentType, nextProps.model);
if (this.state.searchText != nextProps.searchText && nextProps.searchText != undefined) {
this.setState({
searchText: nextProps.searchText
});
}
if (this.state.checked != nextProps.checked && nextProps.checked != undefined) {
this.setState({
checked: nextProps.checked
});
}
if (this.state.value != nextProps.value && nextProps.value != undefined) {
this.setState({
value: nextProps.value
});
}
if (this.state.model != nextProps.model && nextProps.model != undefined) {
this.setState({
model: nextProps.model
});
}
}
}, {
key: "componentDidUpdate",
value: function componentDidUpdate(prevProps, prevState) {
var _this4 = this;
switch (getNameFromWrappedComponent(WrappedComponent)) {
case 'AutoComplete':
if (this.state.searchText !== prevState.searchText && this.props.onChange) {
this.props.onChange(this.state.searchText);
}
break;
case 'Checkbox':
if (this.state.checked !== prevState.checked && this.props.onChange) {
this.props.onChange(null, this.state.checked);
}
break;
default:
if (this.state.value !== prevState.value && this.props.onChange) {
this.props.onChange(null, null, this.state.value);
}
break;
}
if (this.props.validate) {
this.props.validate(this.state.value).then(function (response) {
if (_this4.state.errorState !== response.errorMsg) {
_this4.setState({
errorState: response.errorMsg
});
}
});
}
if (
/*
* If the component changes id without unmounting,
* then default values will never be synched with python
*/
this.props.model === prevProps.model && this.state.value === '' && this.props["default"]) {
this.UNRELIABLE_SyncDefaultValueWithPython(1000);
}
}
}, {
key: "updatePythonValue",
value: function updatePythonValue(newValue) {
if (this.props.prePythonSyncProcessing !== undefined) {
newValue = this.props.prePythonSyncProcessing(newValue);
}
// whenever we invoke syncValueWithPython we will propagate the Javascript value of the model to Python
if (this.syncValueWithPython) {
// this.syncValueWithPython((event.target.type == 'number') ? parseFloat(this.state.value) : this.state.value, this.props.requirement);
switch (this.props.realType) {
case 'float':
if (!isNaN(newValue) && newValue !== '') {
newValue = parseFloat(newValue);
}
break;
case 'dict':
if (typeof newValue === 'string') {
newValue = JSON.parse(newValue);
}
break;
default:
break;
}
// Don't sync if new value is emtpy string
if (newValue !== '') {
this.syncValueWithPython(newValue);
}
if (this.props.callback) {
this.props.callback(newValue, this.oldValue);
}
this.oldValue = undefined;
}
this.setState({
value: newValue,
searchText: newValue,
checked: newValue
});
this.forceUpdate();
}
}, {
key: "triggerUpdate",
value: function triggerUpdate(updateMethod) {
// common strategy when triggering processing of a value change, delay it, every time there is a change we reset
if (this.updateTimer != undefined) {
clearTimeout(this.updateTimer);
}
this.updateTimer = setTimeout(updateMethod, 1000);
}
// Default handle (mainly textfields and dropdowns)
}, {
key: "handleChange",
value: function handleChange(event, index, value) {
var _this5 = this;
var targetValue = value;
if (event != null && event.target.value != undefined) {
targetValue = event.target.value;
}
if (this.oldValue === undefined) {
this.oldValue = this.state.value;
}
this.setState({
value: targetValue
});
if (this.props.validate) {
this.props.validate(targetValue).then(function (response) {
if (response.errorMsg !== _this5.state.errorMsg) {
_this5.setState({
errorMsg: response.errorMsg
});
}
});
}
// For textfields value is retrieved from the event. For dropdown value is retrieved from the value
this.triggerUpdate(function () {
return _this5.updatePythonValue(targetValue);
});
}
// Autocomplete handle
}, {
key: "handleUpdateInput",
value: function handleUpdateInput(value) {
var _this6 = this;
this.triggerUpdate(function () {
return _this6.updatePythonValue(value);
});
}
// Checkbox
}, {
key: "handleUpdateCheckbox",
value: function handleUpdateCheckbox(event, isInputChecked) {
this.updatePythonValue(isInputChecked);
}
}, {
key: "render",
value: function render() {
var wrappedComponentProps = Object.assign({}, this.props);
if (wrappedComponentProps.key == undefined) {
wrappedComponentProps.key = wrappedComponentProps.model;
}
if (wrappedComponentProps.id == undefined) {
wrappedComponentProps.id = wrappedComponentProps.model;
}
delete wrappedComponentProps.model;
delete wrappedComponentProps.handleChange;
delete wrappedComponentProps.modelName;
delete wrappedComponentProps.dimensionType;
delete wrappedComponentProps.noStyle;
delete wrappedComponentProps.validate;
delete wrappedComponentProps.prePythonSyncProcessing;
delete wrappedComponentProps.callback;
if (wrappedComponentProps.realType == 'func' || wrappedComponentProps.realType == 'float') {
wrappedComponentProps['helperText'] = this.state.errorMsg;
}
if (!getNameFromWrappedComponent(WrappedComponent).includes('ListComponent')) {
delete wrappedComponentProps.realType;
}
switch (getNameFromWrappedComponent(WrappedComponent)) {
case 'AutoComplete':
wrappedComponentProps['onUpdateInput'] = this.handleUpdateInput;
wrappedComponentProps['searchText'] = this.state.searchText;
break;
case 'Checkbox':
wrappedComponentProps['onChange'] = this.handleUpdateCheckbox;
wrappedComponentProps['checked'] = this.state.checked;
delete wrappedComponentProps.searchText;
delete wrappedComponentProps.dataSource;
delete wrappedComponentProps.floatingLabelText;
delete wrappedComponentProps.hintText;
break;
default:
wrappedComponentProps['onChange'] = this.handleChange;
wrappedComponentProps.value = _typeof(this.state.value) === 'object' && this.state.value !== null && !Array.isArray(this.state.value) ? JSON.stringify(this.state.value) : this.state.value;
// Fix case with multiple values: need to set an empty list in case the value is undefined
wrappedComponentProps.value = wrappedComponentProps.multiple && wrappedComponentProps.value !== undefined && !wrappedComponentProps.value ? [] : wrappedComponentProps.value;
delete wrappedComponentProps.searchText;
delete wrappedComponentProps.dataSource;
break;
}
return /*#__PURE__*/React.createElement(WrappedComponent, wrappedComponentProps);
}
}]);
}(PythonControlledComponent);
return PythonControlledControl;
}
export function createPythonControlledControlWithPythonDataFetch(WrappedComponent) {
var PythonControlledComponent = this.createPythonControlledComponent(WrappedComponent);
var PythonControlledControlWithPythonDataFetch = /*#__PURE__*/function (_PythonControlledComp2) {
function PythonControlledControlWithPythonDataFetch(props) {
var _this7;
_classCallCheck(this, PythonControlledControlWithPythonDataFetch);
_this7 = _callSuper(this, PythonControlledControlWithPythonDataFetch, [props]);
_defineProperty(_this7, "callPythonMethod", function (value) {
Utils.evalPythonMessage(_this7.props.method, []).then(function (response) {
if (_this7._isMounted) {
if (Object.keys(response).length != 0) {
_this7.setState({
pythonData: response
});
} else {
_this7.setState({
pythonData: []
});
}
}
});
});
_this7.state = _objectSpread(_objectSpread({}, _this7.state), {}, {
value: [],
pythonData: []
});
// If a handleChange method is passed as a props it will overwrite the handleChange python controlled capability
_this7.handleChange = _this7.props.handleChange == undefined ? _this7.handleChange.bind(_this7) : _this7.props.handleChange.bind(_this7);
_this7.callPythonMethod();
return _this7;
}
_inherits(PythonControlledControlWithPythonDataFetch, _PythonControlledComp2);
return _createClass(PythonControlledControlWithPythonDataFetch, [{
key: "UNSAFE_componentWillReceiveProps",
value: function UNSAFE_componentWillReceiveProps(nextProps) {
this.disconnectFromPython();
this.id = nextProps.id == undefined ? nextProps.model : nextProps.id;
this.connectToPython(this.state.componentType, nextProps.model);
this.callPythonMethod();
}
/*
* TODO: this function appears defined 2 times
* I think the last def is picked up, so I am commenting this one
* componentDidUpdate (prevProps, prevState) {
* if (this.state.value != prevState.value && this.props.onChange) {
* this.props.onChange(null, null, this.state.value);
* }
* }
*/
}, {
key: "updatePythonValue",
value: function updatePythonValue(newValue) {
this.setState({
value: newValue,
searchText: newValue,
checked: newValue
});
if (this.syncValueWithPython) {
this.syncValueWithPython(newValue);
}
this.forceUpdate();
}
// Default handle (mainly textfields and dropdowns)
}, {
key: "handleChange",
value: function handleChange(event, index, value) {
var targetValue = value;
if (event != null && event.target.value != undefined) {
targetValue = event.target.value;
}
this.setState({
value: targetValue
});
this.updatePythonValue(targetValue);
}
}, {
key: "compareArrays",
value: function compareArrays(array1, array2) {
// if the other array is a falsy value, return
if (!array1 || !array2) {
return false;
}
// compare lengths - can save a lot of time
if (array1.length != array2.length) {
return false;
}
for (var i = 0, l = array1.length; i < l; i++) {
// Check if we have nested arrays
if (array1[i] instanceof Array && array2[i] instanceof Array) {
// recurse into the nested arrays
if (!array1[i].equals(array2[i])) {
return false;
}
} else if (array1[i] != array2[i]) {
// Warning - two different object instances will never be equal: {x:20} != {x:20}
return false;
}
}
return true;
}
}, {
key: "componentDidUpdate",
value: function componentDidUpdate(prevProps, prevState) {
if (!this.compareArrays(this.state.value, prevState.value)) {
if (Array.isArray(this.state.value)) {
for (var v in this.state.value) {
if (this.state.pythonData.indexOf(this.state.value[v]) < 0) {
var newValue = [this.state.value[v]];
this.setState({
pythonData: this.state.pythonData.concat(newValue)
});
}
}
}
}
}
}, {
key: "shouldComponentUpdate",
value: function shouldComponentUpdate(nextProps, nextState) {
return !this.compareArrays(this.state.pythonData, nextState.pythonData) || !this.compareArrays(this.state.value, nextState.value);
}
}, {
key: "render",
value: function render() {
var wrappedComponentProps = Object.assign({}, this.props);
if (wrappedComponentProps.key == undefined) {
wrappedComponentProps.key = wrappedComponentProps.model;
}
if (wrappedComponentProps.id == undefined) {
wrappedComponentProps.id = wrappedComponentProps.model;
}
wrappedComponentProps.onChange = this.handleChange;
wrappedComponentProps.value = wrappedComponentProps.multiple && this.state.value !== undefined && !this.state.value ? [] : this.state.value;
delete wrappedComponentProps.model;
delete wrappedComponentProps.postProcessItems;
delete wrappedComponentProps.validate;
delete wrappedComponentProps.prePythonSyncProcessing;
delete wrappedComponentProps.updates;
if (this.props.postProcessItems) {
var items = this.props.postProcessItems(this.state.pythonData, wrappedComponentProps.value);
}
return /*#__PURE__*/React.createElement(WrappedComponent, wrappedComponentProps, items);
}
}]);
}(PythonControlledComponent);
return PythonControlledControlWithPythonDataFetch;
}
function getNameFromWrappedComponent(WrappedComponent) {
return WrappedComponent.name || WrappedComponent.displayName || WrappedComponent.Naked.render.name;
}