goblin-laboratory 2.2.1 → 2.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +9 -9
- package/.eslintrc.js +28 -28
- package/.zou-flow +3 -3
- package/README.md +107 -107
- package/carnotzet.js +10 -10
- package/config.js +13 -13
- package/laboratory.js +13 -13
- package/lib/.webpack-config.js +53 -53
- package/lib/carnotzet.js +118 -118
- package/lib/helpers.js +16 -16
- package/lib/index.js +66 -66
- package/package.json +47 -47
- package/widgets/connect-helpers/arrayEquals.js +5 -5
- package/widgets/connect-helpers/arraysEquals.js +24 -24
- package/widgets/connect-helpers/c.js +99 -99
- package/widgets/connect-helpers/join-models.js +16 -16
- package/widgets/connect-helpers/with-c.js +276 -276
- package/widgets/devtools.js +5 -5
- package/widgets/disconnect-overlay/styles.js +50 -50
- package/widgets/disconnect-overlay/widget.js +40 -40
- package/widgets/fields-view/widget.js +34 -34
- package/widgets/form/index.js +79 -79
- package/widgets/frame/widget.js +47 -47
- package/widgets/frontend-form/reducer.js +18 -18
- package/widgets/frontend-form/widget.js +15 -15
- package/widgets/importer/default.js +14 -14
- package/widgets/importer/importer.js +54 -53
- package/widgets/importer/index.js +4 -4
- package/widgets/index-browsers.js +195 -195
- package/widgets/index-electron-ws.js +153 -153
- package/widgets/index-electron.js +69 -69
- package/widgets/index.js +1 -1
- package/widgets/laboratory/service.js +542 -542
- package/widgets/laboratory/widget.js +98 -98
- package/widgets/maintenance/styles.js +38 -38
- package/widgets/maintenance/widget.js +65 -65
- package/widgets/props-binder/widget.js +48 -48
- package/widgets/renderer.js +85 -85
- package/widgets/root/index.js +54 -54
- package/widgets/searchkit/index.js +68 -68
- package/widgets/store/backend-reducer.js +116 -116
- package/widgets/store/commands-reducer.js +14 -14
- package/widgets/store/middlewares.js +171 -171
- package/widgets/store/network-reducer.js +23 -23
- package/widgets/store/root-reducer.js +35 -35
- package/widgets/store/store.js +40 -40
- package/widgets/store/widgets-reducer.js +95 -95
- package/widgets/theme-context/js-to-css.js +20 -20
- package/widgets/theme-context/widget.js +130 -130
- package/widgets/view/index.js +31 -31
- package/widgets/widget/index.js +1205 -1205
- package/widgets/widget/utils/connect.js +47 -47
- package/widgets/widget/utils/connectBackend.js +48 -48
- package/widgets/widget/utils/connectWidget.js +31 -31
- package/widgets/widget/utils/manifest.txt +134 -134
- package/widgets/widget/utils/shallowEqualShredder.js +36 -36
- package/widgets/widget/utils/widgets-actions.js +21 -21
- package/widgets/widget/utils/wrapMapStateToProps.js +26 -26
- package/widgets/with-desktop-id/widget.js +20 -20
- package/widgets/with-model/context.js +5 -5
- package/widgets/with-model/widget.js +42 -42
- package/widgets/with-workitem/widget.js +30 -30
package/widgets/widget/index.js
CHANGED
|
@@ -1,1205 +1,1205 @@
|
|
|
1
|
-
//T:2019-02-27
|
|
2
|
-
|
|
3
|
-
import React from 'react';
|
|
4
|
-
import _ from 'lodash';
|
|
5
|
-
import {connect} from 'react-redux';
|
|
6
|
-
import PropTypes from 'prop-types';
|
|
7
|
-
import Shredder from 'xcraft-core-shredder';
|
|
8
|
-
import LinkedList from 'linked-list';
|
|
9
|
-
import {push} from 'connected-react-router/immutable';
|
|
10
|
-
import {matchPath} from 'react-router';
|
|
11
|
-
import fasterStringify from 'faster-stable-stringify';
|
|
12
|
-
import {StyleSheet as Aphrodite, flushToStyleTag} from 'aphrodite/no-important';
|
|
13
|
-
import traverse from 'traverse';
|
|
14
|
-
import importer from 'goblin_importer';
|
|
15
|
-
import shallowEqualShredder from './utils/shallowEqualShredder';
|
|
16
|
-
import _connect from './utils/connect';
|
|
17
|
-
import connectWidget from './utils/connectWidget';
|
|
18
|
-
import connectBackend from './utils/connectBackend';
|
|
19
|
-
import * as widgetsActions from './utils/widgets-actions';
|
|
20
|
-
import {getParameter} from '../../lib/helpers.js';
|
|
21
|
-
|
|
22
|
-
const stylesImporter = importer('styles');
|
|
23
|
-
const reducerImporter = importer('reducer');
|
|
24
|
-
|
|
25
|
-
let stylesCache = new Map();
|
|
26
|
-
let stylesCacheUses = new LinkedList();
|
|
27
|
-
let myStyleCache = {};
|
|
28
|
-
|
|
29
|
-
export function clearStylesCache() {
|
|
30
|
-
stylesCache = new Map();
|
|
31
|
-
stylesCacheUses = new LinkedList();
|
|
32
|
-
myStyleCache = {};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const debounce500 = _.debounce((fct) => fct(), 500);
|
|
36
|
-
const throttle250 = _.throttle((fct) => fct(), 250);
|
|
37
|
-
|
|
38
|
-
function isFunction(functionToCheck) {
|
|
39
|
-
var getType = {};
|
|
40
|
-
return (
|
|
41
|
-
functionToCheck &&
|
|
42
|
-
getType.toString.call(functionToCheck) === '[object Function]'
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// See https://github.com/Khan/aphrodite/issues/319#issuecomment-393857964
|
|
47
|
-
const {StyleSheet, css} = Aphrodite.extend([
|
|
48
|
-
{
|
|
49
|
-
selectorHandler: (selector, baseSelector, generateSubtreeStyles) => {
|
|
50
|
-
const nestedTags = [];
|
|
51
|
-
const selectors = selector.split(',');
|
|
52
|
-
_.each(selectors, (subselector, key) => {
|
|
53
|
-
if (selector[0] === '&') {
|
|
54
|
-
const tag = key === 0 ? subselector.slice(1) : subselector;
|
|
55
|
-
const nestedTag = generateSubtreeStyles(
|
|
56
|
-
`${baseSelector}${tag}`.replace(/ +(?= )/g, '')
|
|
57
|
-
);
|
|
58
|
-
nestedTags.push(nestedTag);
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
return _.isEmpty(nestedTags) ? null : _.flattenDeep(nestedTags);
|
|
62
|
-
},
|
|
63
|
-
},
|
|
64
|
-
]);
|
|
65
|
-
|
|
66
|
-
const injectCSS = (classes) => {
|
|
67
|
-
traverse(classes).forEach(function (style) {
|
|
68
|
-
if (style === undefined || style === null) {
|
|
69
|
-
this.delete();
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
const sheet = StyleSheet.create(classes);
|
|
74
|
-
Object.keys(sheet).forEach((key) => (sheet[key] = css(sheet[key])));
|
|
75
|
-
return sheet;
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
function getWidgetName(constructorName) {
|
|
79
|
-
return constructorName.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
class Widget extends React.Component {
|
|
83
|
-
constructor() {
|
|
84
|
-
super(...arguments);
|
|
85
|
-
this._names = this._getInheritedNames();
|
|
86
|
-
this._name = this._names[0];
|
|
87
|
-
|
|
88
|
-
const reducer = reducerImporter(this._name);
|
|
89
|
-
if (reducer) {
|
|
90
|
-
const widgetId = this.widgetId;
|
|
91
|
-
if (widgetId) {
|
|
92
|
-
this.dispatch({type: 'WIDGETS_CREATE'});
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
_getInheritedNames() {
|
|
98
|
-
let p = this;
|
|
99
|
-
const names = new Set();
|
|
100
|
-
while ((p = Object.getPrototypeOf(p))) {
|
|
101
|
-
const constructorName = p.constructor.name;
|
|
102
|
-
if (constructorName === 'Widget') {
|
|
103
|
-
break;
|
|
104
|
-
}
|
|
105
|
-
const widgetName = getWidgetName(constructorName);
|
|
106
|
-
names.add(widgetName);
|
|
107
|
-
}
|
|
108
|
-
return [...names];
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
//? static get propTypes() {
|
|
112
|
-
//? return {
|
|
113
|
-
//? id: PropTypes.string,
|
|
114
|
-
//? entityId: PropTypes.string,
|
|
115
|
-
//? hinter: PropTypes.string,
|
|
116
|
-
//? };
|
|
117
|
-
//? }
|
|
118
|
-
|
|
119
|
-
static get childContextTypes() {
|
|
120
|
-
return this.contextTypes;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
getChildContext() {
|
|
124
|
-
if (this.props.id) {
|
|
125
|
-
return {...this.context, ...{nearestParentId: this.props.id}};
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
return this.context;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
static get contextTypes() {
|
|
132
|
-
return {
|
|
133
|
-
labId: PropTypes.string,
|
|
134
|
-
dispatch: PropTypes.func,
|
|
135
|
-
store: PropTypes.object,
|
|
136
|
-
theme: PropTypes.object,
|
|
137
|
-
model: PropTypes.any,
|
|
138
|
-
register: PropTypes.func,
|
|
139
|
-
id: PropTypes.string,
|
|
140
|
-
desktopId: PropTypes.string,
|
|
141
|
-
contextId: PropTypes.string,
|
|
142
|
-
entityId: PropTypes.string,
|
|
143
|
-
dragControllerId: PropTypes.string,
|
|
144
|
-
dragServiceId: PropTypes.string,
|
|
145
|
-
readonly: PropTypes.any,
|
|
146
|
-
themeContextName: PropTypes.string,
|
|
147
|
-
nearestParentId: PropTypes.string,
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
get name() {
|
|
152
|
-
return this._name;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
get widgetId() {
|
|
156
|
-
return this.props.widgetId || this.props.id;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
get styles() {
|
|
160
|
-
if (this.lastStyleProps === this.props) {
|
|
161
|
-
return this.lastStyle;
|
|
162
|
-
}
|
|
163
|
-
this.lastStyleProps = this.props;
|
|
164
|
-
|
|
165
|
-
const myStyle = this.getMyStyle();
|
|
166
|
-
|
|
167
|
-
const styleProps = this.getStyleProps(myStyle);
|
|
168
|
-
const hash = this.computeStyleHash(myStyle, styleProps);
|
|
169
|
-
|
|
170
|
-
let item = stylesCache.get(hash);
|
|
171
|
-
if (item) {
|
|
172
|
-
/* When an existing style is used, detach from its current position
|
|
173
|
-
* and move of one step in the linked-list. The goal is to keep the less
|
|
174
|
-
* used style in front of the list (head).
|
|
175
|
-
*/
|
|
176
|
-
const nextItem = item.next;
|
|
177
|
-
if (nextItem) {
|
|
178
|
-
item.detach();
|
|
179
|
-
nextItem.append(item);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
this.lastStyle = item.style;
|
|
183
|
-
return item.style;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const jsStyles = myStyle.func(this.context.theme, styleProps);
|
|
187
|
-
const newStyle = {
|
|
188
|
-
classNames: injectCSS(jsStyles),
|
|
189
|
-
props: jsStyles,
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
/* Limit the style cache to 2048 entries. The less used item is deleted
|
|
193
|
-
* when the limit is reached.
|
|
194
|
-
*/
|
|
195
|
-
if (stylesCache.size > 2048) {
|
|
196
|
-
item = stylesCacheUses.head;
|
|
197
|
-
item.detach();
|
|
198
|
-
stylesCache.delete(item.hash);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/* Create a new linked-list item and add this one at the end of the list.
|
|
202
|
-
* Here, it's still not possible to be sure that this style will be often
|
|
203
|
-
* used. Anyway, if it's not used anymore, it will move one-by-one to the
|
|
204
|
-
* front of the list.
|
|
205
|
-
*/
|
|
206
|
-
item = new LinkedList.Item();
|
|
207
|
-
item.hash = hash;
|
|
208
|
-
item.style = newStyle;
|
|
209
|
-
stylesCacheUses.append(item);
|
|
210
|
-
|
|
211
|
-
stylesCache.set(hash, item);
|
|
212
|
-
|
|
213
|
-
this.lastStyle = newStyle;
|
|
214
|
-
return newStyle;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
set styles(stylesDef) {
|
|
218
|
-
const myStyleFunc = stylesDef.default;
|
|
219
|
-
const s = {
|
|
220
|
-
hasThemeParam: myStyleFunc.length > 0,
|
|
221
|
-
hasPropsParam: myStyleFunc.length > 1,
|
|
222
|
-
propNamesUsed: stylesDef.propNames,
|
|
223
|
-
mapProps: stylesDef.mapProps,
|
|
224
|
-
func: myStyleFunc,
|
|
225
|
-
};
|
|
226
|
-
if (!this._styleDefs) {
|
|
227
|
-
this._styleDefs = [s];
|
|
228
|
-
} else {
|
|
229
|
-
this._styleDefs.push(s);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
getStyleProps(myStyle) {
|
|
234
|
-
if (!myStyle.hasPropsParam) {
|
|
235
|
-
return null;
|
|
236
|
-
}
|
|
237
|
-
let propNamesUsed = myStyle.propNamesUsed;
|
|
238
|
-
if (!propNamesUsed) {
|
|
239
|
-
throw new Error(`propNames is not defined in styles.js of ${this.name}`);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
let styleProps = {};
|
|
243
|
-
propNamesUsed.forEach((p) => {
|
|
244
|
-
styleProps[p] = this.props[p];
|
|
245
|
-
});
|
|
246
|
-
if (myStyle.mapProps) {
|
|
247
|
-
styleProps = myStyle.mapProps(styleProps, this.context.theme);
|
|
248
|
-
}
|
|
249
|
-
return styleProps;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
computeStyleHash(myStyle, styleProps) {
|
|
253
|
-
let hashProps = '';
|
|
254
|
-
if (myStyle.hasPropsParam) {
|
|
255
|
-
hashProps = fasterStringify(styleProps);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
let hashTheme = '';
|
|
259
|
-
if (myStyle.hasThemeParam) {
|
|
260
|
-
hashTheme = this.context.theme.cacheName;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
return `${this.name}${hashTheme}${hashProps}`;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
importStyleDefinition(widgetName) {
|
|
267
|
-
let myStyleFunc = stylesImporter(widgetName);
|
|
268
|
-
if (!myStyleFunc) {
|
|
269
|
-
return null;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
return {
|
|
273
|
-
hasThemeParam: myStyleFunc.length > 0,
|
|
274
|
-
hasPropsParam: myStyleFunc.length > 1,
|
|
275
|
-
propNamesUsed: stylesImporter(widgetName, 'propNames'),
|
|
276
|
-
mapProps: stylesImporter(widgetName, 'mapProps'),
|
|
277
|
-
func: myStyleFunc,
|
|
278
|
-
};
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// Get style definition for this widget and merge it with the style definition of inherited widget
|
|
282
|
-
getMergedStyleDefinition() {
|
|
283
|
-
let styleDefs = this._styleDefs;
|
|
284
|
-
if (!styleDefs) {
|
|
285
|
-
styleDefs = this._names.map(this.importStyleDefinition);
|
|
286
|
-
}
|
|
287
|
-
if (styleDefs.every((styleDef) => !styleDef)) {
|
|
288
|
-
throw new Error(`No styles.js file for component '${this.name}'`);
|
|
289
|
-
}
|
|
290
|
-
styleDefs = styleDefs.filter((styleDef) => styleDef);
|
|
291
|
-
|
|
292
|
-
if (styleDefs.length === 1) {
|
|
293
|
-
return styleDefs[0];
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
styleDefs.reverse();
|
|
297
|
-
|
|
298
|
-
const propNamesUsedList = styleDefs
|
|
299
|
-
.map((styleDef) => styleDef.propNamesUsed)
|
|
300
|
-
.filter((propNamesUsed) => propNamesUsed)
|
|
301
|
-
.flat();
|
|
302
|
-
|
|
303
|
-
let propNamesUsed;
|
|
304
|
-
if (propNamesUsedList.length > 0) {
|
|
305
|
-
propNamesUsed = new Set(propNamesUsedList);
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
const mapPropsList = styleDefs
|
|
309
|
-
.map((styleDef) => styleDef.mapProps)
|
|
310
|
-
.filter((mapProps) => mapProps);
|
|
311
|
-
const funcList = styleDefs.map((styleDef) => styleDef.func);
|
|
312
|
-
|
|
313
|
-
return {
|
|
314
|
-
hasThemeParam: styleDefs.some((styleDef) => styleDef.hasThemeParam),
|
|
315
|
-
hasPropsParam: styleDefs.some((styleDef) => styleDef.hasPropsParam),
|
|
316
|
-
propNamesUsed,
|
|
317
|
-
mapProps: (props, theme) =>
|
|
318
|
-
mapPropsList.reduce((p, mapProps) => mapProps(p, theme), props),
|
|
319
|
-
func: (theme, props) =>
|
|
320
|
-
funcList.reduce(
|
|
321
|
-
(styles, func) => func.bind({styles})(theme, props),
|
|
322
|
-
{}
|
|
323
|
-
),
|
|
324
|
-
};
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
// Get style definition for this widget from cache or import it
|
|
328
|
-
getMyStyle() {
|
|
329
|
-
let myStyle = myStyleCache[this.name];
|
|
330
|
-
if (myStyle) {
|
|
331
|
-
return myStyle;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
myStyle = this.getMergedStyleDefinition();
|
|
335
|
-
myStyleCache[this.name] = myStyle;
|
|
336
|
-
|
|
337
|
-
return myStyle;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
read(key) {
|
|
341
|
-
return this.props[key];
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
componentDidCatch(error, info) {
|
|
345
|
-
this.reportError(error, info);
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
componentDidMount() {
|
|
349
|
-
/* HACK: flush explicitly all aphrodite styles in order to remove the
|
|
350
|
-
* flickers and bugs in some systems where the styles are not applied.
|
|
351
|
-
* Note that this API should not be called directly because it's an
|
|
352
|
-
* internal function of aphrodite.
|
|
353
|
-
*/
|
|
354
|
-
throttle250(() => flushToStyleTag());
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
shouldComponentUpdate(newProps, newState) {
|
|
358
|
-
return (
|
|
359
|
-
!shallowEqualShredder(this.props, newProps) ||
|
|
360
|
-
!shallowEqualShredder(this.state, newState)
|
|
361
|
-
);
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
componentWillUnmount() {
|
|
365
|
-
debounce500(() => this.rawDispatch(widgetsActions.collect()));
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
///////////STATE MGMT:
|
|
369
|
-
static withRoute(path, watchedParams, watchedSearchs, watchHash) {
|
|
370
|
-
return connect(
|
|
371
|
-
(state) => {
|
|
372
|
-
const router = new Shredder(state.router);
|
|
373
|
-
const location = router.get('location');
|
|
374
|
-
if (!location) {
|
|
375
|
-
return {};
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
const pathName = router.get('location.pathname');
|
|
379
|
-
const search = router.get('location.search');
|
|
380
|
-
|
|
381
|
-
const match = matchPath(pathName, {
|
|
382
|
-
path,
|
|
383
|
-
exact: false,
|
|
384
|
-
strict: false,
|
|
385
|
-
});
|
|
386
|
-
|
|
387
|
-
let withSearch = null;
|
|
388
|
-
|
|
389
|
-
if (Array.isArray(watchedSearchs)) {
|
|
390
|
-
for (const s of watchedSearchs) {
|
|
391
|
-
if (!withSearch) {
|
|
392
|
-
withSearch = {};
|
|
393
|
-
}
|
|
394
|
-
withSearch[s] = Widget.GetParameter(search, s);
|
|
395
|
-
}
|
|
396
|
-
} else {
|
|
397
|
-
withSearch = {
|
|
398
|
-
[watchedSearchs]: Widget.GetParameter(search, watchedSearchs),
|
|
399
|
-
};
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
let withHash = null;
|
|
403
|
-
if (watchHash) {
|
|
404
|
-
withHash = {hash: router.get('location.hash')};
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
if (Array.isArray(watchedParams)) {
|
|
408
|
-
const params = {};
|
|
409
|
-
for (const p of watchedParams) {
|
|
410
|
-
params[p] = !match ? null : match.params[p];
|
|
411
|
-
}
|
|
412
|
-
return {
|
|
413
|
-
isDisplayed: !!match,
|
|
414
|
-
...params,
|
|
415
|
-
...withSearch,
|
|
416
|
-
...withHash,
|
|
417
|
-
};
|
|
418
|
-
} else {
|
|
419
|
-
return {
|
|
420
|
-
isDisplayed: !!match,
|
|
421
|
-
[watchedParams]: !match ? null : match.params[watchedParams],
|
|
422
|
-
...withSearch,
|
|
423
|
-
...withHash,
|
|
424
|
-
};
|
|
425
|
-
}
|
|
426
|
-
},
|
|
427
|
-
null,
|
|
428
|
-
null,
|
|
429
|
-
{
|
|
430
|
-
pure: true,
|
|
431
|
-
areOwnPropsEqual: shallowEqualShredder,
|
|
432
|
-
areStatePropsEqual: shallowEqualShredder,
|
|
433
|
-
areMergedPropsEqual: shallowEqualShredder,
|
|
434
|
-
}
|
|
435
|
-
);
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
static WithRoute(component, watchedParams, watchedSearchs, watchHash) {
|
|
439
|
-
return (path) => {
|
|
440
|
-
return Widget.withRoute(
|
|
441
|
-
path,
|
|
442
|
-
watchedParams,
|
|
443
|
-
watchedSearchs,
|
|
444
|
-
watchHash
|
|
445
|
-
)(component);
|
|
446
|
-
};
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
static wire(connectId, wires) {
|
|
450
|
-
const useProps = !connectId;
|
|
451
|
-
return connect(
|
|
452
|
-
(state, props) => {
|
|
453
|
-
if (useProps) {
|
|
454
|
-
connectId = props.id;
|
|
455
|
-
}
|
|
456
|
-
let mapState = {};
|
|
457
|
-
if (state.backend) {
|
|
458
|
-
if (wires) {
|
|
459
|
-
const shredded = new Shredder(state.backend);
|
|
460
|
-
if (!shredded.has(connectId)) {
|
|
461
|
-
return {_no_props_: true, id: null};
|
|
462
|
-
}
|
|
463
|
-
Object.keys(wires).forEach((wire) => {
|
|
464
|
-
const val = shredded.get(`${connectId}.${wires[wire]}`, null);
|
|
465
|
-
if (val) {
|
|
466
|
-
if (val._isSuperReaper6000) {
|
|
467
|
-
mapState[wire] = val.state;
|
|
468
|
-
} else {
|
|
469
|
-
mapState[wire] = val;
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
});
|
|
473
|
-
}
|
|
474
|
-
return mapState;
|
|
475
|
-
}
|
|
476
|
-
if (this.isForm) {
|
|
477
|
-
mapState.initialValues = mapState;
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
return {};
|
|
481
|
-
},
|
|
482
|
-
null,
|
|
483
|
-
null,
|
|
484
|
-
{
|
|
485
|
-
pure: true,
|
|
486
|
-
areOwnPropsEqual: shallowEqualShredder,
|
|
487
|
-
areStatePropsEqual: shallowEqualShredder,
|
|
488
|
-
areMergedPropsEqual: shallowEqualShredder,
|
|
489
|
-
}
|
|
490
|
-
);
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
static Wired(component) {
|
|
494
|
-
if (!component) {
|
|
495
|
-
throw new Error('You must provide a component!');
|
|
496
|
-
}
|
|
497
|
-
return (id) => Widget.wire(id, component.wiring)(component);
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
static shred(state) {
|
|
501
|
-
return new Shredder(state);
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
static connect(...args) {
|
|
505
|
-
return _connect(...args);
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
static connectWidget(...args) {
|
|
509
|
-
return connectWidget(...args);
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
static connectBackend(...args) {
|
|
513
|
-
return connectBackend(...args);
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
withState(mapProps, path) {
|
|
517
|
-
return connect(
|
|
518
|
-
(state) => {
|
|
519
|
-
const s = new Shredder(state.backend);
|
|
520
|
-
if (isFunction(mapProps)) {
|
|
521
|
-
return Object.assign(mapProps(s.get(`${this.props.id}${path}`)));
|
|
522
|
-
} else {
|
|
523
|
-
return {
|
|
524
|
-
[mapProps]: s.get(`${this.props.id}${path}`),
|
|
525
|
-
};
|
|
526
|
-
}
|
|
527
|
-
},
|
|
528
|
-
null,
|
|
529
|
-
null,
|
|
530
|
-
{
|
|
531
|
-
pure: true,
|
|
532
|
-
areOwnPropsEqual: shallowEqualShredder,
|
|
533
|
-
areStatePropsEqual: shallowEqualShredder,
|
|
534
|
-
areMergedPropsEqual: shallowEqualShredder,
|
|
535
|
-
}
|
|
536
|
-
);
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
WithState(component, mapProps) {
|
|
540
|
-
return (path) => {
|
|
541
|
-
return this.withState(mapProps, path)(component);
|
|
542
|
-
};
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
withModel(model, mapProps, fullPath) {
|
|
546
|
-
return connect(
|
|
547
|
-
(state) => {
|
|
548
|
-
const s = new Shredder({
|
|
549
|
-
backend: state.backend,
|
|
550
|
-
widgets: state.widgets,
|
|
551
|
-
network: state.network,
|
|
552
|
-
});
|
|
553
|
-
let parentModel = '';
|
|
554
|
-
if (!fullPath) {
|
|
555
|
-
parentModel = `backend.${this.props.id}`;
|
|
556
|
-
}
|
|
557
|
-
if (!mapProps) {
|
|
558
|
-
return {
|
|
559
|
-
defaultValue: s.get(`${parentModel}${model}`),
|
|
560
|
-
model,
|
|
561
|
-
};
|
|
562
|
-
} else {
|
|
563
|
-
if (isFunction(mapProps)) {
|
|
564
|
-
return Object.assign(
|
|
565
|
-
{model},
|
|
566
|
-
mapProps(s.get(`${parentModel}${model}`))
|
|
567
|
-
);
|
|
568
|
-
} else {
|
|
569
|
-
return {
|
|
570
|
-
[mapProps]: s.get(`${parentModel}${model}`),
|
|
571
|
-
model,
|
|
572
|
-
};
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
},
|
|
576
|
-
null,
|
|
577
|
-
null,
|
|
578
|
-
{
|
|
579
|
-
pure: true,
|
|
580
|
-
areOwnPropsEqual: shallowEqualShredder,
|
|
581
|
-
areStatePropsEqual: shallowEqualShredder,
|
|
582
|
-
areMergedPropsEqual: shallowEqualShredder,
|
|
583
|
-
forwardRef: true,
|
|
584
|
-
}
|
|
585
|
-
);
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
WithModel(component, mapProps, useEntityId) {
|
|
589
|
-
return (model) => {
|
|
590
|
-
if (isFunction(model)) {
|
|
591
|
-
model = model();
|
|
592
|
-
}
|
|
593
|
-
if (useEntityId) {
|
|
594
|
-
model = `backend.${this.props.entityId}${model}`;
|
|
595
|
-
}
|
|
596
|
-
// Optional choice
|
|
597
|
-
if (model.indexOf('||') !== -1) {
|
|
598
|
-
const choices = model.split('||');
|
|
599
|
-
const first = this.getModelValue(choices[0], useEntityId);
|
|
600
|
-
if (first) {
|
|
601
|
-
return this.withModel(choices[0], mapProps)(component);
|
|
602
|
-
}
|
|
603
|
-
const second = choices[0].replace().replace(/[^\.]+$/, choices[1]);
|
|
604
|
-
return this.withModel(second, mapProps)(component);
|
|
605
|
-
}
|
|
606
|
-
// Look for data in collections
|
|
607
|
-
const collectionInfo = model.match(/\[(.*)\]/);
|
|
608
|
-
if (collectionInfo) {
|
|
609
|
-
if (collectionInfo.length === 2) {
|
|
610
|
-
const itemId = collectionInfo[1];
|
|
611
|
-
//Full collection case
|
|
612
|
-
if (itemId.length === 0) {
|
|
613
|
-
const path = model.replace('[]', '');
|
|
614
|
-
const coll = this.getModelValue(path, useEntityId);
|
|
615
|
-
return (props) => {
|
|
616
|
-
return (
|
|
617
|
-
<div>
|
|
618
|
-
{coll.map((v, k) => {
|
|
619
|
-
const Item = this.withModel(
|
|
620
|
-
`${path}.${k}`,
|
|
621
|
-
mapProps,
|
|
622
|
-
useEntityId
|
|
623
|
-
)(component);
|
|
624
|
-
return <Item key={k} {...props} />;
|
|
625
|
-
})}
|
|
626
|
-
</div>
|
|
627
|
-
);
|
|
628
|
-
};
|
|
629
|
-
}
|
|
630
|
-
// With entity id case
|
|
631
|
-
if (
|
|
632
|
-
itemId.match(
|
|
633
|
-
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
|
|
634
|
-
)
|
|
635
|
-
) {
|
|
636
|
-
const i = model.indexOf('[');
|
|
637
|
-
const prePath = model.substring(1, i);
|
|
638
|
-
const finalPath = this.getEntityPathInCollection(
|
|
639
|
-
prePath,
|
|
640
|
-
itemId
|
|
641
|
-
)(
|
|
642
|
-
useEntityId
|
|
643
|
-
? this.getModelValue(this.props.entityId, useEntityId)
|
|
644
|
-
: this.getModelValue('')
|
|
645
|
-
);
|
|
646
|
-
|
|
647
|
-
return this.withModel(finalPath, mapProps, useEntityId)(component);
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
// Std
|
|
653
|
-
return this.withModel(model, mapProps, useEntityId)(component);
|
|
654
|
-
};
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
///////// High-level model mapper API
|
|
658
|
-
|
|
659
|
-
getWidgetToEntityMapper(component, mapProps) {
|
|
660
|
-
return this.WithModel(component, mapProps, true);
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
getWidgetToFormMapper(component, mapProps) {
|
|
664
|
-
return this.WithModel(component, mapProps, false);
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
getPluginToEntityMapper(component, pluginName, mapProps) {
|
|
668
|
-
const WiredPlugin = Widget.Wired(component)(
|
|
669
|
-
`${pluginName}@${this.props.id}`
|
|
670
|
-
);
|
|
671
|
-
return this.WithModel(WiredPlugin, mapProps, true);
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
getPluginToFormMapper(component, pluginName, mapProps) {
|
|
675
|
-
const pluginPath = `${pluginName}@${this.props.id}`;
|
|
676
|
-
const WiredPlugin = Widget.Wired(component)(pluginPath);
|
|
677
|
-
return this.withModel(`backend.${pluginPath}`, mapProps, true)(WiredPlugin);
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
wirePluginToForm(component, pluginName) {
|
|
681
|
-
const WiredPlugin = Widget.Wired(component)(
|
|
682
|
-
`${pluginName}@${this.props.id}`
|
|
683
|
-
);
|
|
684
|
-
return WiredPlugin;
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
mapWidgetToBackend(component, mapProps, path) {
|
|
688
|
-
return this.withModel(`backend.${path}`, mapProps, true)(component);
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
mapWidgetToEntityPlugin(component, mapProps, pluginName, path) {
|
|
692
|
-
path = `${pluginName}@${this.props.entityId}${path}`;
|
|
693
|
-
return this.withModel(`backend.${path}`, mapProps, true)(component);
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
mapWidgetToFormPlugin(component, mapProps, pluginName, path) {
|
|
697
|
-
path = `${pluginName}@${this.props.id}${path}`;
|
|
698
|
-
return this.withModel(`backend.${path}`, mapProps, true)(component);
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
mapWidget(component, mapProps, path) {
|
|
702
|
-
return this.withModel(path, mapProps, true)(component);
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
buildCollectionLoader(ids, FinalComp, FallbackComp) {
|
|
706
|
-
let Loader = (props) => {
|
|
707
|
-
const loaded = ids.reduce((loaded, id) => {
|
|
708
|
-
return props[id] === true;
|
|
709
|
-
}, false);
|
|
710
|
-
if (loaded) {
|
|
711
|
-
return <FinalComp collection={this.getCollection(ids)} />;
|
|
712
|
-
} else {
|
|
713
|
-
return FallbackComp ? <FallbackComp /> : null;
|
|
714
|
-
}
|
|
715
|
-
};
|
|
716
|
-
|
|
717
|
-
ids.map((id) => {
|
|
718
|
-
Loader = this.mapWidget(
|
|
719
|
-
Loader,
|
|
720
|
-
(item) => {
|
|
721
|
-
return {
|
|
722
|
-
[id]: item !== null && item !== undefined,
|
|
723
|
-
};
|
|
724
|
-
},
|
|
725
|
-
`backend.${id}.id`
|
|
726
|
-
);
|
|
727
|
-
});
|
|
728
|
-
|
|
729
|
-
return <Loader />;
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
getCollection(ids) {
|
|
733
|
-
return ids.map((id) => {
|
|
734
|
-
return this.getEntityById(id);
|
|
735
|
-
});
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
buildLoader(branch, Loaded, FallbackComp) {
|
|
739
|
-
const Loader = (props) => {
|
|
740
|
-
if (props.loaded) {
|
|
741
|
-
return <Loaded />;
|
|
742
|
-
} else {
|
|
743
|
-
return FallbackComp ? <FallbackComp /> : null;
|
|
744
|
-
}
|
|
745
|
-
};
|
|
746
|
-
|
|
747
|
-
const Renderer = this.mapWidget(
|
|
748
|
-
Loader,
|
|
749
|
-
(entityId) => {
|
|
750
|
-
if (!entityId) {
|
|
751
|
-
return {loaded: false};
|
|
752
|
-
} else {
|
|
753
|
-
return {loaded: true};
|
|
754
|
-
}
|
|
755
|
-
},
|
|
756
|
-
`backend.${branch}.id`
|
|
757
|
-
);
|
|
758
|
-
return <Renderer />;
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
///////////GOBLIN BUS:
|
|
762
|
-
get registry() {
|
|
763
|
-
return this.getState().commands.get('registry');
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
canDo(cmd) {
|
|
767
|
-
if (!this.registry[cmd]) {
|
|
768
|
-
return false;
|
|
769
|
-
}
|
|
770
|
-
const state = this.getState().backend;
|
|
771
|
-
const clientSessionId = state
|
|
772
|
-
.get(this.context.labId)
|
|
773
|
-
.get('clientSessionId');
|
|
774
|
-
const loginSession = state.get(`login-session@${clientSessionId}`);
|
|
775
|
-
if (loginSession && this.registry[cmd] !== true) {
|
|
776
|
-
const rank = loginSession.get('rank');
|
|
777
|
-
if (this.registry[cmd][rank] && this.registry[cmd][rank] === true) {
|
|
778
|
-
return false;
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
return true;
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
cmd(cmd, args) {
|
|
785
|
-
if (!this.registry[cmd]) {
|
|
786
|
-
throw new Error(
|
|
787
|
-
`Command ${cmd} not implemented or authorized by the zepplin firewall`
|
|
788
|
-
);
|
|
789
|
-
}
|
|
790
|
-
const state = this.getState().backend;
|
|
791
|
-
|
|
792
|
-
if (args && !args.labId) {
|
|
793
|
-
args.labId = this.context.labId;
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
if (args && !args.clientSessionId) {
|
|
797
|
-
args.clientSessionId = state.get(args.labId).get('clientSessionId');
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
if (args && !args.desktopId) {
|
|
801
|
-
args.desktopId = this.context.desktopId;
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
const loginSession = state.get(`login-session@${args.clientSessionId}`);
|
|
805
|
-
if (loginSession && this.registry[cmd] !== true) {
|
|
806
|
-
const rank = loginSession.get('rank');
|
|
807
|
-
if (this.registry[cmd][rank] && this.registry[cmd][rank] === true) {
|
|
808
|
-
console.warn(
|
|
809
|
-
'%cGoblins Warning',
|
|
810
|
-
'font-weight: bold;',
|
|
811
|
-
`Command will be blocked`
|
|
812
|
-
);
|
|
813
|
-
return;
|
|
814
|
-
}
|
|
815
|
-
}
|
|
816
|
-
const action = {
|
|
817
|
-
type: 'QUEST',
|
|
818
|
-
cmd,
|
|
819
|
-
data: args,
|
|
820
|
-
_xcraftIPC: true,
|
|
821
|
-
};
|
|
822
|
-
this.context.dispatch(action);
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
reportError(error, info) {
|
|
826
|
-
const desktopId = this.context.desktopId
|
|
827
|
-
? this.context.desktopId
|
|
828
|
-
: this.props.desktopId;
|
|
829
|
-
this.doFor(this.context.labId, 'when-ui-crash', {desktopId, error, info});
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
do(action, args) {
|
|
833
|
-
return this.doAs(this.name, action, args);
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
doAs(service, action, args) {
|
|
837
|
-
const id = this.props.id || this.context.id;
|
|
838
|
-
if (!id) {
|
|
839
|
-
console.error(`${this.name} is not a connected widget (need an id)`);
|
|
840
|
-
return;
|
|
841
|
-
}
|
|
842
|
-
this.cmd(`${service}.${action}`, Object.assign({id}, args));
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
doFor(serviceId, action, args) {
|
|
846
|
-
const service = serviceId.split('@')[0];
|
|
847
|
-
this.cmd(`${service}.${action}`, Object.assign({id: serviceId}, args));
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
/**
|
|
851
|
-
* Do backend quest or dispatch frontend action given the model
|
|
852
|
-
*
|
|
853
|
-
* @param {String} model - A path in the state.
|
|
854
|
-
* @param {String} name - The quest or action name.
|
|
855
|
-
* @param {Object} args - The arguments.
|
|
856
|
-
*/
|
|
857
|
-
doDispatch(model, name, args) {
|
|
858
|
-
const [root, id] = model.split('.');
|
|
859
|
-
if (root === 'backend') {
|
|
860
|
-
this.doFor(id, name, args);
|
|
861
|
-
} else if (root === 'widgets') {
|
|
862
|
-
this.dispatchTo(id, {
|
|
863
|
-
...args,
|
|
864
|
-
type: name,
|
|
865
|
-
});
|
|
866
|
-
} else {
|
|
867
|
-
throw new Error(`Model path starting with '${root}' is not supported.`);
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
|
|
871
|
-
/**
|
|
872
|
-
* Dispatch an action in the frontend reducer for this widget.
|
|
873
|
-
*
|
|
874
|
-
* @param {Object} action - Redux action.
|
|
875
|
-
* @param {String} name - (optional) Reducer name.
|
|
876
|
-
*/
|
|
877
|
-
dispatch(action, name) {
|
|
878
|
-
this.dispatchTo(this.widgetId, action, name || this.name);
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
/**
|
|
882
|
-
* Dispatch an action in the frontend reducer for a specified widget.
|
|
883
|
-
*
|
|
884
|
-
* A `reducer.js` file must be present in a widget folder that matches
|
|
885
|
-
* the `name` parameter or the name specified in the `id` parameter,
|
|
886
|
-
* after the `$`.
|
|
887
|
-
*
|
|
888
|
-
* Possible values for `id`:
|
|
889
|
-
* `backendId` (example: desktop@111)
|
|
890
|
-
* `backendId$name` (exemple: workitem@222$hinter)
|
|
891
|
-
* `backendId$name@id` (exemple: workitem@222$hinter@333)
|
|
892
|
-
* `$name` (no backend id, must be manually collected in componentWillUnmount)
|
|
893
|
-
* `$name@id` (same as above)
|
|
894
|
-
*
|
|
895
|
-
* @param {String} id - Destination widget id.
|
|
896
|
-
* @param {Object} action - Redux action.
|
|
897
|
-
* @param {String} name - (optional) Reducer name.
|
|
898
|
-
*/
|
|
899
|
-
dispatchTo(id, action, name) {
|
|
900
|
-
if (typeof action !== 'function') {
|
|
901
|
-
if (!action.type) {
|
|
902
|
-
throw new Error(
|
|
903
|
-
`Cannot dispatch a widget action without a type. Action: ${JSON.stringify(
|
|
904
|
-
action
|
|
905
|
-
)}. Widget name: ${this.name}`
|
|
906
|
-
);
|
|
907
|
-
}
|
|
908
|
-
action._id = id;
|
|
909
|
-
if (name) {
|
|
910
|
-
action._type = name;
|
|
911
|
-
}
|
|
912
|
-
action.type = `@widgets_${action.type}`;
|
|
913
|
-
}
|
|
914
|
-
this.rawDispatch(action);
|
|
915
|
-
}
|
|
916
|
-
|
|
917
|
-
/**
|
|
918
|
-
* Dispatch a payload to the desktop cache reducer.
|
|
919
|
-
*
|
|
920
|
-
* This cache is useful for storing provisory values in the session (desktop)
|
|
921
|
-
* in order to restore a specific state when a widget is re-mount again.
|
|
922
|
-
* It's very useful in the case of the ScrollableContainer for example, where
|
|
923
|
-
* the last scroll position is dispatched in the cache, and restored with the
|
|
924
|
-
* next mount of this component. It's not possible to use the widget reducer
|
|
925
|
-
* associated to the ScrollableContainer because in this case, after the
|
|
926
|
-
* unmount, the reducer is collected.
|
|
927
|
-
*
|
|
928
|
-
* The cache has a limit, you must always consider that the values stored
|
|
929
|
-
* can be lost at any time.
|
|
930
|
-
*
|
|
931
|
-
* @param {string} id - Widget's id.
|
|
932
|
-
* @param {Object} payload - Payload to store.
|
|
933
|
-
*/
|
|
934
|
-
dispatchToCache(id, payload) {
|
|
935
|
-
this.dispatchTo(
|
|
936
|
-
this.context.desktopId,
|
|
937
|
-
{
|
|
938
|
-
type: 'WIDGET_CACHE',
|
|
939
|
-
widgetId: id,
|
|
940
|
-
value: payload,
|
|
941
|
-
},
|
|
942
|
-
'desktop'
|
|
943
|
-
);
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
rawDispatch(action) {
|
|
947
|
-
this.context.store.dispatch(action);
|
|
948
|
-
}
|
|
949
|
-
|
|
950
|
-
///////////NAVIGATION:
|
|
951
|
-
|
|
952
|
-
nav(route, frontOnly) {
|
|
953
|
-
if (frontOnly) {
|
|
954
|
-
this.context.dispatch(push(route));
|
|
955
|
-
} else {
|
|
956
|
-
this.doFor(this.context.labId, 'nav', {route});
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
static GetParameter(search, name) {
|
|
961
|
-
return getParameter(search, name);
|
|
962
|
-
}
|
|
963
|
-
|
|
964
|
-
setBackendValue(path, value) {
|
|
965
|
-
this.rawDispatch({
|
|
966
|
-
type: 'FIELD-CHANGED',
|
|
967
|
-
path,
|
|
968
|
-
value,
|
|
969
|
-
});
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
setModelValue(path, value, useEntity) {
|
|
973
|
-
let fullPath = 'backend.' + this.props.id + path;
|
|
974
|
-
if (useEntity) {
|
|
975
|
-
fullPath = 'backend.' + this.props.entityId + path;
|
|
976
|
-
}
|
|
977
|
-
this.rawDispatch({
|
|
978
|
-
type: 'FIELD-CHANGED',
|
|
979
|
-
path: fullPath,
|
|
980
|
-
value,
|
|
981
|
-
});
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
setFormValue(path, value) {
|
|
985
|
-
this.setModelValue(path, value);
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
setEntityValue(path, value) {
|
|
989
|
-
this.setModelValue(path, value, true);
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
backendHasBranch(branch) {
|
|
993
|
-
return this.getState().backend.has(branch);
|
|
994
|
-
}
|
|
995
|
-
|
|
996
|
-
getModelValue(model, fullPath) {
|
|
997
|
-
const storeState = this.getState();
|
|
998
|
-
const state = new Shredder({
|
|
999
|
-
backend: storeState.backend,
|
|
1000
|
-
widgets: storeState.widgets,
|
|
1001
|
-
network: storeState.network,
|
|
1002
|
-
});
|
|
1003
|
-
if (fullPath) {
|
|
1004
|
-
if (isFunction(model)) {
|
|
1005
|
-
model = model(state.get(model));
|
|
1006
|
-
}
|
|
1007
|
-
if (!model.startsWith('backend.')) {
|
|
1008
|
-
model = 'backend.' + model;
|
|
1009
|
-
}
|
|
1010
|
-
return state.get(model);
|
|
1011
|
-
} else {
|
|
1012
|
-
const parentModel = this.context.model || `backend.${this.props.id}`;
|
|
1013
|
-
if (isFunction(model)) {
|
|
1014
|
-
model = model(state.get(parentModel));
|
|
1015
|
-
}
|
|
1016
|
-
return state.get(`${parentModel}${model}`);
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
1019
|
-
|
|
1020
|
-
getBackendValue(fullpath) {
|
|
1021
|
-
const storeState = this.getState();
|
|
1022
|
-
const state = new Shredder({
|
|
1023
|
-
backend: storeState.backend,
|
|
1024
|
-
widgets: storeState.widgets,
|
|
1025
|
-
network: storeState.network,
|
|
1026
|
-
});
|
|
1027
|
-
return state.get(fullpath);
|
|
1028
|
-
}
|
|
1029
|
-
|
|
1030
|
-
getFormValue(path) {
|
|
1031
|
-
return this.getModelValue(path);
|
|
1032
|
-
}
|
|
1033
|
-
|
|
1034
|
-
getFormPluginValue(pluginName, path) {
|
|
1035
|
-
const state = new Shredder(this.getState().backend);
|
|
1036
|
-
return state.get(`${pluginName}@${this.props.id}${path}`);
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
|
-
getEntityPluginValue(pluginName, path) {
|
|
1040
|
-
const state = new Shredder(this.getState().backend);
|
|
1041
|
-
return state.get(`${pluginName}@${this.props.entityId}${path}`);
|
|
1042
|
-
}
|
|
1043
|
-
|
|
1044
|
-
getEntityValue(model) {
|
|
1045
|
-
if (isFunction(model)) {
|
|
1046
|
-
const state = new Shredder(this.getState());
|
|
1047
|
-
model = model(state.get(`backend.${this.props.entityId}`));
|
|
1048
|
-
return this.getModelValue(model, true);
|
|
1049
|
-
} else {
|
|
1050
|
-
return this.getModelValue(`${this.props.entityId}${model}`, true);
|
|
1051
|
-
}
|
|
1052
|
-
}
|
|
1053
|
-
|
|
1054
|
-
getEntityById(entityId) {
|
|
1055
|
-
const state = new Shredder(this.getState().backend);
|
|
1056
|
-
return state.get(entityId);
|
|
1057
|
-
}
|
|
1058
|
-
|
|
1059
|
-
getEntityPathInCollection(collectionPath, id, entityPath) {
|
|
1060
|
-
return (entity) => {
|
|
1061
|
-
const item = entity
|
|
1062
|
-
.get(collectionPath)
|
|
1063
|
-
.find((pack) => pack.get('id') === id);
|
|
1064
|
-
|
|
1065
|
-
return entityPath
|
|
1066
|
-
? `.${collectionPath}[${item.key}].${entityPath}`
|
|
1067
|
-
: `.${collectionPath}[${item.key}]`;
|
|
1068
|
-
};
|
|
1069
|
-
}
|
|
1070
|
-
|
|
1071
|
-
getState() {
|
|
1072
|
-
return this.context.store.getState();
|
|
1073
|
-
}
|
|
1074
|
-
|
|
1075
|
-
getSchema(path) {
|
|
1076
|
-
return Widget.getSchema(this.getState(), path);
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
|
-
getWidgetState() {
|
|
1080
|
-
const widgetId = this.widgetId;
|
|
1081
|
-
if (!widgetId) {
|
|
1082
|
-
throw new Error('Cannot resolve widget state without a valid id');
|
|
1083
|
-
}
|
|
1084
|
-
return this.getState().widgets.get(widgetId);
|
|
1085
|
-
}
|
|
1086
|
-
|
|
1087
|
-
getWidgetCacheState(widgetId) {
|
|
1088
|
-
if (!widgetId) {
|
|
1089
|
-
throw new Error('Cannot resolve widget cache state without a valid id');
|
|
1090
|
-
}
|
|
1091
|
-
return this.getState().widgets.getIn([
|
|
1092
|
-
this.context.desktopId,
|
|
1093
|
-
'widgetsCache',
|
|
1094
|
-
widgetId,
|
|
1095
|
-
]);
|
|
1096
|
-
}
|
|
1097
|
-
|
|
1098
|
-
getBackendState() {
|
|
1099
|
-
if (!this.props.id) {
|
|
1100
|
-
throw new Error('Cannot resolve backend state without a valid id');
|
|
1101
|
-
}
|
|
1102
|
-
return this.getState().backend.get(this.props.id);
|
|
1103
|
-
}
|
|
1104
|
-
|
|
1105
|
-
getRouting() {
|
|
1106
|
-
return new Shredder(this.context.store.getState().router);
|
|
1107
|
-
}
|
|
1108
|
-
|
|
1109
|
-
getSelectionState(target) {
|
|
1110
|
-
if (target.type !== 'text') {
|
|
1111
|
-
return null;
|
|
1112
|
-
}
|
|
1113
|
-
return {
|
|
1114
|
-
ss: target.selectionStart,
|
|
1115
|
-
se: target.selectionEnd,
|
|
1116
|
-
sd: target.selectionDirection,
|
|
1117
|
-
};
|
|
1118
|
-
}
|
|
1119
|
-
|
|
1120
|
-
getHinterType(hinterId) {
|
|
1121
|
-
let type = hinterId;
|
|
1122
|
-
if (!type || type === '') {
|
|
1123
|
-
return null;
|
|
1124
|
-
}
|
|
1125
|
-
const index = hinterId.indexOf('@');
|
|
1126
|
-
if (index !== -1) {
|
|
1127
|
-
type = hinterId.substr(0, index);
|
|
1128
|
-
}
|
|
1129
|
-
return type;
|
|
1130
|
-
}
|
|
1131
|
-
|
|
1132
|
-
getHash() {
|
|
1133
|
-
return this.getRouting().get('location.hash');
|
|
1134
|
-
}
|
|
1135
|
-
|
|
1136
|
-
getNearestId() {
|
|
1137
|
-
return this.props.id || this.context.nearestParentId;
|
|
1138
|
-
}
|
|
1139
|
-
|
|
1140
|
-
static copyTextToClipboard(text) {
|
|
1141
|
-
const textField = document.createElement('textarea');
|
|
1142
|
-
textField.innerText = text;
|
|
1143
|
-
document.body.appendChild(textField);
|
|
1144
|
-
textField.select();
|
|
1145
|
-
document.execCommand('copy');
|
|
1146
|
-
textField.remove();
|
|
1147
|
-
}
|
|
1148
|
-
|
|
1149
|
-
getUserSettings() {
|
|
1150
|
-
return Widget.getUserSession(new Shredder(this.getState()));
|
|
1151
|
-
}
|
|
1152
|
-
|
|
1153
|
-
setUserSettings(questName, payload) {
|
|
1154
|
-
const state = this.getState().backend;
|
|
1155
|
-
const serviceId = state.get(window.labId).get('clientSessionId');
|
|
1156
|
-
payload.id = serviceId;
|
|
1157
|
-
const service = serviceId.split('@', 1)[0];
|
|
1158
|
-
this.cmd(`${service}.${questName}`, payload);
|
|
1159
|
-
}
|
|
1160
|
-
|
|
1161
|
-
static getUserSession(state) {
|
|
1162
|
-
return state.get(
|
|
1163
|
-
`backend.${state.get(`backend.${window.labId}.clientSessionId`)}`
|
|
1164
|
-
);
|
|
1165
|
-
}
|
|
1166
|
-
|
|
1167
|
-
static getLoginSession(state) {
|
|
1168
|
-
const clientSessionId = state.get(
|
|
1169
|
-
`backend.${window.labId}.clientSessionId`,
|
|
1170
|
-
null
|
|
1171
|
-
);
|
|
1172
|
-
if (!clientSessionId) {
|
|
1173
|
-
return null;
|
|
1174
|
-
}
|
|
1175
|
-
const loginSession = state.get(
|
|
1176
|
-
`backend.login-session@${clientSessionId}`,
|
|
1177
|
-
null
|
|
1178
|
-
);
|
|
1179
|
-
if (!loginSession) {
|
|
1180
|
-
return null;
|
|
1181
|
-
} else {
|
|
1182
|
-
return loginSession;
|
|
1183
|
-
}
|
|
1184
|
-
}
|
|
1185
|
-
|
|
1186
|
-
static getSchema(state, path) {
|
|
1187
|
-
if (state && state.backend) {
|
|
1188
|
-
const backend = new Shredder(state.backend);
|
|
1189
|
-
if (!path) {
|
|
1190
|
-
return backend.get(`workshop.schema`, null);
|
|
1191
|
-
} else {
|
|
1192
|
-
return backend.get(`workshop.schema.${path}`, null);
|
|
1193
|
-
}
|
|
1194
|
-
}
|
|
1195
|
-
return null;
|
|
1196
|
-
}
|
|
1197
|
-
}
|
|
1198
|
-
|
|
1199
|
-
Widget.propTypes = {
|
|
1200
|
-
id: PropTypes.string,
|
|
1201
|
-
entityId: PropTypes.string,
|
|
1202
|
-
hinter: PropTypes.string,
|
|
1203
|
-
};
|
|
1204
|
-
|
|
1205
|
-
export default Widget;
|
|
1
|
+
//T:2019-02-27
|
|
2
|
+
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import _ from 'lodash';
|
|
5
|
+
import {connect} from 'react-redux';
|
|
6
|
+
import PropTypes from 'prop-types';
|
|
7
|
+
import Shredder from 'xcraft-core-shredder';
|
|
8
|
+
import LinkedList from 'linked-list';
|
|
9
|
+
import {push} from 'connected-react-router/immutable';
|
|
10
|
+
import {matchPath} from 'react-router';
|
|
11
|
+
import fasterStringify from 'faster-stable-stringify';
|
|
12
|
+
import {StyleSheet as Aphrodite, flushToStyleTag} from 'aphrodite/no-important';
|
|
13
|
+
import traverse from 'traverse';
|
|
14
|
+
import importer from 'goblin_importer';
|
|
15
|
+
import shallowEqualShredder from './utils/shallowEqualShredder';
|
|
16
|
+
import _connect from './utils/connect';
|
|
17
|
+
import connectWidget from './utils/connectWidget';
|
|
18
|
+
import connectBackend from './utils/connectBackend';
|
|
19
|
+
import * as widgetsActions from './utils/widgets-actions';
|
|
20
|
+
import {getParameter} from '../../lib/helpers.js';
|
|
21
|
+
|
|
22
|
+
const stylesImporter = importer('styles');
|
|
23
|
+
const reducerImporter = importer('reducer');
|
|
24
|
+
|
|
25
|
+
let stylesCache = new Map();
|
|
26
|
+
let stylesCacheUses = new LinkedList();
|
|
27
|
+
let myStyleCache = {};
|
|
28
|
+
|
|
29
|
+
export function clearStylesCache() {
|
|
30
|
+
stylesCache = new Map();
|
|
31
|
+
stylesCacheUses = new LinkedList();
|
|
32
|
+
myStyleCache = {};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const debounce500 = _.debounce((fct) => fct(), 500);
|
|
36
|
+
const throttle250 = _.throttle((fct) => fct(), 250);
|
|
37
|
+
|
|
38
|
+
function isFunction(functionToCheck) {
|
|
39
|
+
var getType = {};
|
|
40
|
+
return (
|
|
41
|
+
functionToCheck &&
|
|
42
|
+
getType.toString.call(functionToCheck) === '[object Function]'
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// See https://github.com/Khan/aphrodite/issues/319#issuecomment-393857964
|
|
47
|
+
const {StyleSheet, css} = Aphrodite.extend([
|
|
48
|
+
{
|
|
49
|
+
selectorHandler: (selector, baseSelector, generateSubtreeStyles) => {
|
|
50
|
+
const nestedTags = [];
|
|
51
|
+
const selectors = selector.split(',');
|
|
52
|
+
_.each(selectors, (subselector, key) => {
|
|
53
|
+
if (selector[0] === '&') {
|
|
54
|
+
const tag = key === 0 ? subselector.slice(1) : subselector;
|
|
55
|
+
const nestedTag = generateSubtreeStyles(
|
|
56
|
+
`${baseSelector}${tag}`.replace(/ +(?= )/g, '')
|
|
57
|
+
);
|
|
58
|
+
nestedTags.push(nestedTag);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
return _.isEmpty(nestedTags) ? null : _.flattenDeep(nestedTags);
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
]);
|
|
65
|
+
|
|
66
|
+
const injectCSS = (classes) => {
|
|
67
|
+
traverse(classes).forEach(function (style) {
|
|
68
|
+
if (style === undefined || style === null) {
|
|
69
|
+
this.delete();
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const sheet = StyleSheet.create(classes);
|
|
74
|
+
Object.keys(sheet).forEach((key) => (sheet[key] = css(sheet[key])));
|
|
75
|
+
return sheet;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
function getWidgetName(constructorName) {
|
|
79
|
+
return constructorName.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
class Widget extends React.Component {
|
|
83
|
+
constructor() {
|
|
84
|
+
super(...arguments);
|
|
85
|
+
this._names = this._getInheritedNames();
|
|
86
|
+
this._name = this._names[0];
|
|
87
|
+
|
|
88
|
+
const reducer = reducerImporter(this._name);
|
|
89
|
+
if (reducer) {
|
|
90
|
+
const widgetId = this.widgetId;
|
|
91
|
+
if (widgetId) {
|
|
92
|
+
this.dispatch({type: 'WIDGETS_CREATE'});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
_getInheritedNames() {
|
|
98
|
+
let p = this;
|
|
99
|
+
const names = new Set();
|
|
100
|
+
while ((p = Object.getPrototypeOf(p))) {
|
|
101
|
+
const constructorName = p.constructor.name;
|
|
102
|
+
if (constructorName === 'Widget') {
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
const widgetName = getWidgetName(constructorName);
|
|
106
|
+
names.add(widgetName);
|
|
107
|
+
}
|
|
108
|
+
return [...names];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
//? static get propTypes() {
|
|
112
|
+
//? return {
|
|
113
|
+
//? id: PropTypes.string,
|
|
114
|
+
//? entityId: PropTypes.string,
|
|
115
|
+
//? hinter: PropTypes.string,
|
|
116
|
+
//? };
|
|
117
|
+
//? }
|
|
118
|
+
|
|
119
|
+
static get childContextTypes() {
|
|
120
|
+
return this.contextTypes;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
getChildContext() {
|
|
124
|
+
if (this.props.id) {
|
|
125
|
+
return {...this.context, ...{nearestParentId: this.props.id}};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return this.context;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
static get contextTypes() {
|
|
132
|
+
return {
|
|
133
|
+
labId: PropTypes.string,
|
|
134
|
+
dispatch: PropTypes.func,
|
|
135
|
+
store: PropTypes.object,
|
|
136
|
+
theme: PropTypes.object,
|
|
137
|
+
model: PropTypes.any,
|
|
138
|
+
register: PropTypes.func,
|
|
139
|
+
id: PropTypes.string,
|
|
140
|
+
desktopId: PropTypes.string,
|
|
141
|
+
contextId: PropTypes.string,
|
|
142
|
+
entityId: PropTypes.string,
|
|
143
|
+
dragControllerId: PropTypes.string,
|
|
144
|
+
dragServiceId: PropTypes.string,
|
|
145
|
+
readonly: PropTypes.any,
|
|
146
|
+
themeContextName: PropTypes.string,
|
|
147
|
+
nearestParentId: PropTypes.string,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
get name() {
|
|
152
|
+
return this._name;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
get widgetId() {
|
|
156
|
+
return this.props.widgetId || this.props.id;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
get styles() {
|
|
160
|
+
if (this.lastStyleProps === this.props) {
|
|
161
|
+
return this.lastStyle;
|
|
162
|
+
}
|
|
163
|
+
this.lastStyleProps = this.props;
|
|
164
|
+
|
|
165
|
+
const myStyle = this.getMyStyle();
|
|
166
|
+
|
|
167
|
+
const styleProps = this.getStyleProps(myStyle);
|
|
168
|
+
const hash = this.computeStyleHash(myStyle, styleProps);
|
|
169
|
+
|
|
170
|
+
let item = stylesCache.get(hash);
|
|
171
|
+
if (item) {
|
|
172
|
+
/* When an existing style is used, detach from its current position
|
|
173
|
+
* and move of one step in the linked-list. The goal is to keep the less
|
|
174
|
+
* used style in front of the list (head).
|
|
175
|
+
*/
|
|
176
|
+
const nextItem = item.next;
|
|
177
|
+
if (nextItem) {
|
|
178
|
+
item.detach();
|
|
179
|
+
nextItem.append(item);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
this.lastStyle = item.style;
|
|
183
|
+
return item.style;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const jsStyles = myStyle.func(this.context.theme, styleProps);
|
|
187
|
+
const newStyle = {
|
|
188
|
+
classNames: injectCSS(jsStyles),
|
|
189
|
+
props: jsStyles,
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
/* Limit the style cache to 2048 entries. The less used item is deleted
|
|
193
|
+
* when the limit is reached.
|
|
194
|
+
*/
|
|
195
|
+
if (stylesCache.size > 2048) {
|
|
196
|
+
item = stylesCacheUses.head;
|
|
197
|
+
item.detach();
|
|
198
|
+
stylesCache.delete(item.hash);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/* Create a new linked-list item and add this one at the end of the list.
|
|
202
|
+
* Here, it's still not possible to be sure that this style will be often
|
|
203
|
+
* used. Anyway, if it's not used anymore, it will move one-by-one to the
|
|
204
|
+
* front of the list.
|
|
205
|
+
*/
|
|
206
|
+
item = new LinkedList.Item();
|
|
207
|
+
item.hash = hash;
|
|
208
|
+
item.style = newStyle;
|
|
209
|
+
stylesCacheUses.append(item);
|
|
210
|
+
|
|
211
|
+
stylesCache.set(hash, item);
|
|
212
|
+
|
|
213
|
+
this.lastStyle = newStyle;
|
|
214
|
+
return newStyle;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
set styles(stylesDef) {
|
|
218
|
+
const myStyleFunc = stylesDef.default;
|
|
219
|
+
const s = {
|
|
220
|
+
hasThemeParam: myStyleFunc.length > 0,
|
|
221
|
+
hasPropsParam: myStyleFunc.length > 1,
|
|
222
|
+
propNamesUsed: stylesDef.propNames,
|
|
223
|
+
mapProps: stylesDef.mapProps,
|
|
224
|
+
func: myStyleFunc,
|
|
225
|
+
};
|
|
226
|
+
if (!this._styleDefs) {
|
|
227
|
+
this._styleDefs = [s];
|
|
228
|
+
} else {
|
|
229
|
+
this._styleDefs.push(s);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
getStyleProps(myStyle) {
|
|
234
|
+
if (!myStyle.hasPropsParam) {
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
let propNamesUsed = myStyle.propNamesUsed;
|
|
238
|
+
if (!propNamesUsed) {
|
|
239
|
+
throw new Error(`propNames is not defined in styles.js of ${this.name}`);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
let styleProps = {};
|
|
243
|
+
propNamesUsed.forEach((p) => {
|
|
244
|
+
styleProps[p] = this.props[p];
|
|
245
|
+
});
|
|
246
|
+
if (myStyle.mapProps) {
|
|
247
|
+
styleProps = myStyle.mapProps(styleProps, this.context.theme);
|
|
248
|
+
}
|
|
249
|
+
return styleProps;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
computeStyleHash(myStyle, styleProps) {
|
|
253
|
+
let hashProps = '';
|
|
254
|
+
if (myStyle.hasPropsParam) {
|
|
255
|
+
hashProps = fasterStringify(styleProps);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
let hashTheme = '';
|
|
259
|
+
if (myStyle.hasThemeParam) {
|
|
260
|
+
hashTheme = this.context.theme.cacheName;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return `${this.name}${hashTheme}${hashProps}`;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
importStyleDefinition(widgetName) {
|
|
267
|
+
let myStyleFunc = stylesImporter(widgetName);
|
|
268
|
+
if (!myStyleFunc) {
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return {
|
|
273
|
+
hasThemeParam: myStyleFunc.length > 0,
|
|
274
|
+
hasPropsParam: myStyleFunc.length > 1,
|
|
275
|
+
propNamesUsed: stylesImporter(widgetName, 'propNames'),
|
|
276
|
+
mapProps: stylesImporter(widgetName, 'mapProps'),
|
|
277
|
+
func: myStyleFunc,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Get style definition for this widget and merge it with the style definition of inherited widget
|
|
282
|
+
getMergedStyleDefinition() {
|
|
283
|
+
let styleDefs = this._styleDefs;
|
|
284
|
+
if (!styleDefs) {
|
|
285
|
+
styleDefs = this._names.map(this.importStyleDefinition);
|
|
286
|
+
}
|
|
287
|
+
if (styleDefs.every((styleDef) => !styleDef)) {
|
|
288
|
+
throw new Error(`No styles.js file for component '${this.name}'`);
|
|
289
|
+
}
|
|
290
|
+
styleDefs = styleDefs.filter((styleDef) => styleDef);
|
|
291
|
+
|
|
292
|
+
if (styleDefs.length === 1) {
|
|
293
|
+
return styleDefs[0];
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
styleDefs.reverse();
|
|
297
|
+
|
|
298
|
+
const propNamesUsedList = styleDefs
|
|
299
|
+
.map((styleDef) => styleDef.propNamesUsed)
|
|
300
|
+
.filter((propNamesUsed) => propNamesUsed)
|
|
301
|
+
.flat();
|
|
302
|
+
|
|
303
|
+
let propNamesUsed;
|
|
304
|
+
if (propNamesUsedList.length > 0) {
|
|
305
|
+
propNamesUsed = new Set(propNamesUsedList);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const mapPropsList = styleDefs
|
|
309
|
+
.map((styleDef) => styleDef.mapProps)
|
|
310
|
+
.filter((mapProps) => mapProps);
|
|
311
|
+
const funcList = styleDefs.map((styleDef) => styleDef.func);
|
|
312
|
+
|
|
313
|
+
return {
|
|
314
|
+
hasThemeParam: styleDefs.some((styleDef) => styleDef.hasThemeParam),
|
|
315
|
+
hasPropsParam: styleDefs.some((styleDef) => styleDef.hasPropsParam),
|
|
316
|
+
propNamesUsed,
|
|
317
|
+
mapProps: (props, theme) =>
|
|
318
|
+
mapPropsList.reduce((p, mapProps) => mapProps(p, theme), props),
|
|
319
|
+
func: (theme, props) =>
|
|
320
|
+
funcList.reduce(
|
|
321
|
+
(styles, func) => func.bind({styles})(theme, props),
|
|
322
|
+
{}
|
|
323
|
+
),
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Get style definition for this widget from cache or import it
|
|
328
|
+
getMyStyle() {
|
|
329
|
+
let myStyle = myStyleCache[this.name];
|
|
330
|
+
if (myStyle) {
|
|
331
|
+
return myStyle;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
myStyle = this.getMergedStyleDefinition();
|
|
335
|
+
myStyleCache[this.name] = myStyle;
|
|
336
|
+
|
|
337
|
+
return myStyle;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
read(key) {
|
|
341
|
+
return this.props[key];
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
componentDidCatch(error, info) {
|
|
345
|
+
this.reportError(error, info);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
componentDidMount() {
|
|
349
|
+
/* HACK: flush explicitly all aphrodite styles in order to remove the
|
|
350
|
+
* flickers and bugs in some systems where the styles are not applied.
|
|
351
|
+
* Note that this API should not be called directly because it's an
|
|
352
|
+
* internal function of aphrodite.
|
|
353
|
+
*/
|
|
354
|
+
throttle250(() => flushToStyleTag());
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
shouldComponentUpdate(newProps, newState) {
|
|
358
|
+
return (
|
|
359
|
+
!shallowEqualShredder(this.props, newProps) ||
|
|
360
|
+
!shallowEqualShredder(this.state, newState)
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
componentWillUnmount() {
|
|
365
|
+
debounce500(() => this.rawDispatch(widgetsActions.collect()));
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
///////////STATE MGMT:
|
|
369
|
+
static withRoute(path, watchedParams, watchedSearchs, watchHash) {
|
|
370
|
+
return connect(
|
|
371
|
+
(state) => {
|
|
372
|
+
const router = new Shredder(state.router);
|
|
373
|
+
const location = router.get('location');
|
|
374
|
+
if (!location) {
|
|
375
|
+
return {};
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const pathName = router.get('location.pathname');
|
|
379
|
+
const search = router.get('location.search');
|
|
380
|
+
|
|
381
|
+
const match = matchPath(pathName, {
|
|
382
|
+
path,
|
|
383
|
+
exact: false,
|
|
384
|
+
strict: false,
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
let withSearch = null;
|
|
388
|
+
|
|
389
|
+
if (Array.isArray(watchedSearchs)) {
|
|
390
|
+
for (const s of watchedSearchs) {
|
|
391
|
+
if (!withSearch) {
|
|
392
|
+
withSearch = {};
|
|
393
|
+
}
|
|
394
|
+
withSearch[s] = Widget.GetParameter(search, s);
|
|
395
|
+
}
|
|
396
|
+
} else {
|
|
397
|
+
withSearch = {
|
|
398
|
+
[watchedSearchs]: Widget.GetParameter(search, watchedSearchs),
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
let withHash = null;
|
|
403
|
+
if (watchHash) {
|
|
404
|
+
withHash = {hash: router.get('location.hash')};
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (Array.isArray(watchedParams)) {
|
|
408
|
+
const params = {};
|
|
409
|
+
for (const p of watchedParams) {
|
|
410
|
+
params[p] = !match ? null : match.params[p];
|
|
411
|
+
}
|
|
412
|
+
return {
|
|
413
|
+
isDisplayed: !!match,
|
|
414
|
+
...params,
|
|
415
|
+
...withSearch,
|
|
416
|
+
...withHash,
|
|
417
|
+
};
|
|
418
|
+
} else {
|
|
419
|
+
return {
|
|
420
|
+
isDisplayed: !!match,
|
|
421
|
+
[watchedParams]: !match ? null : match.params[watchedParams],
|
|
422
|
+
...withSearch,
|
|
423
|
+
...withHash,
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
},
|
|
427
|
+
null,
|
|
428
|
+
null,
|
|
429
|
+
{
|
|
430
|
+
pure: true,
|
|
431
|
+
areOwnPropsEqual: shallowEqualShredder,
|
|
432
|
+
areStatePropsEqual: shallowEqualShredder,
|
|
433
|
+
areMergedPropsEqual: shallowEqualShredder,
|
|
434
|
+
}
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
static WithRoute(component, watchedParams, watchedSearchs, watchHash) {
|
|
439
|
+
return (path) => {
|
|
440
|
+
return Widget.withRoute(
|
|
441
|
+
path,
|
|
442
|
+
watchedParams,
|
|
443
|
+
watchedSearchs,
|
|
444
|
+
watchHash
|
|
445
|
+
)(component);
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
static wire(connectId, wires) {
|
|
450
|
+
const useProps = !connectId;
|
|
451
|
+
return connect(
|
|
452
|
+
(state, props) => {
|
|
453
|
+
if (useProps) {
|
|
454
|
+
connectId = props.id;
|
|
455
|
+
}
|
|
456
|
+
let mapState = {};
|
|
457
|
+
if (state.backend) {
|
|
458
|
+
if (wires) {
|
|
459
|
+
const shredded = new Shredder(state.backend);
|
|
460
|
+
if (!shredded.has(connectId)) {
|
|
461
|
+
return {_no_props_: true, id: null};
|
|
462
|
+
}
|
|
463
|
+
Object.keys(wires).forEach((wire) => {
|
|
464
|
+
const val = shredded.get(`${connectId}.${wires[wire]}`, null);
|
|
465
|
+
if (val) {
|
|
466
|
+
if (val._isSuperReaper6000) {
|
|
467
|
+
mapState[wire] = val.state;
|
|
468
|
+
} else {
|
|
469
|
+
mapState[wire] = val;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
return mapState;
|
|
475
|
+
}
|
|
476
|
+
if (this.isForm) {
|
|
477
|
+
mapState.initialValues = mapState;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
return {};
|
|
481
|
+
},
|
|
482
|
+
null,
|
|
483
|
+
null,
|
|
484
|
+
{
|
|
485
|
+
pure: true,
|
|
486
|
+
areOwnPropsEqual: shallowEqualShredder,
|
|
487
|
+
areStatePropsEqual: shallowEqualShredder,
|
|
488
|
+
areMergedPropsEqual: shallowEqualShredder,
|
|
489
|
+
}
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
static Wired(component) {
|
|
494
|
+
if (!component) {
|
|
495
|
+
throw new Error('You must provide a component!');
|
|
496
|
+
}
|
|
497
|
+
return (id) => Widget.wire(id, component.wiring)(component);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
static shred(state) {
|
|
501
|
+
return new Shredder(state);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
static connect(...args) {
|
|
505
|
+
return _connect(...args);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
static connectWidget(...args) {
|
|
509
|
+
return connectWidget(...args);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
static connectBackend(...args) {
|
|
513
|
+
return connectBackend(...args);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
withState(mapProps, path) {
|
|
517
|
+
return connect(
|
|
518
|
+
(state) => {
|
|
519
|
+
const s = new Shredder(state.backend);
|
|
520
|
+
if (isFunction(mapProps)) {
|
|
521
|
+
return Object.assign(mapProps(s.get(`${this.props.id}${path}`)));
|
|
522
|
+
} else {
|
|
523
|
+
return {
|
|
524
|
+
[mapProps]: s.get(`${this.props.id}${path}`),
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
},
|
|
528
|
+
null,
|
|
529
|
+
null,
|
|
530
|
+
{
|
|
531
|
+
pure: true,
|
|
532
|
+
areOwnPropsEqual: shallowEqualShredder,
|
|
533
|
+
areStatePropsEqual: shallowEqualShredder,
|
|
534
|
+
areMergedPropsEqual: shallowEqualShredder,
|
|
535
|
+
}
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
WithState(component, mapProps) {
|
|
540
|
+
return (path) => {
|
|
541
|
+
return this.withState(mapProps, path)(component);
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
withModel(model, mapProps, fullPath) {
|
|
546
|
+
return connect(
|
|
547
|
+
(state) => {
|
|
548
|
+
const s = new Shredder({
|
|
549
|
+
backend: state.backend,
|
|
550
|
+
widgets: state.widgets,
|
|
551
|
+
network: state.network,
|
|
552
|
+
});
|
|
553
|
+
let parentModel = '';
|
|
554
|
+
if (!fullPath) {
|
|
555
|
+
parentModel = `backend.${this.props.id}`;
|
|
556
|
+
}
|
|
557
|
+
if (!mapProps) {
|
|
558
|
+
return {
|
|
559
|
+
defaultValue: s.get(`${parentModel}${model}`),
|
|
560
|
+
model,
|
|
561
|
+
};
|
|
562
|
+
} else {
|
|
563
|
+
if (isFunction(mapProps)) {
|
|
564
|
+
return Object.assign(
|
|
565
|
+
{model},
|
|
566
|
+
mapProps(s.get(`${parentModel}${model}`))
|
|
567
|
+
);
|
|
568
|
+
} else {
|
|
569
|
+
return {
|
|
570
|
+
[mapProps]: s.get(`${parentModel}${model}`),
|
|
571
|
+
model,
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
},
|
|
576
|
+
null,
|
|
577
|
+
null,
|
|
578
|
+
{
|
|
579
|
+
pure: true,
|
|
580
|
+
areOwnPropsEqual: shallowEqualShredder,
|
|
581
|
+
areStatePropsEqual: shallowEqualShredder,
|
|
582
|
+
areMergedPropsEqual: shallowEqualShredder,
|
|
583
|
+
forwardRef: true,
|
|
584
|
+
}
|
|
585
|
+
);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
WithModel(component, mapProps, useEntityId) {
|
|
589
|
+
return (model) => {
|
|
590
|
+
if (isFunction(model)) {
|
|
591
|
+
model = model();
|
|
592
|
+
}
|
|
593
|
+
if (useEntityId) {
|
|
594
|
+
model = `backend.${this.props.entityId}${model}`;
|
|
595
|
+
}
|
|
596
|
+
// Optional choice
|
|
597
|
+
if (model.indexOf('||') !== -1) {
|
|
598
|
+
const choices = model.split('||');
|
|
599
|
+
const first = this.getModelValue(choices[0], useEntityId);
|
|
600
|
+
if (first) {
|
|
601
|
+
return this.withModel(choices[0], mapProps)(component);
|
|
602
|
+
}
|
|
603
|
+
const second = choices[0].replace().replace(/[^\.]+$/, choices[1]);
|
|
604
|
+
return this.withModel(second, mapProps)(component);
|
|
605
|
+
}
|
|
606
|
+
// Look for data in collections
|
|
607
|
+
const collectionInfo = model.match(/\[(.*)\]/);
|
|
608
|
+
if (collectionInfo) {
|
|
609
|
+
if (collectionInfo.length === 2) {
|
|
610
|
+
const itemId = collectionInfo[1];
|
|
611
|
+
//Full collection case
|
|
612
|
+
if (itemId.length === 0) {
|
|
613
|
+
const path = model.replace('[]', '');
|
|
614
|
+
const coll = this.getModelValue(path, useEntityId);
|
|
615
|
+
return (props) => {
|
|
616
|
+
return (
|
|
617
|
+
<div>
|
|
618
|
+
{coll.map((v, k) => {
|
|
619
|
+
const Item = this.withModel(
|
|
620
|
+
`${path}.${k}`,
|
|
621
|
+
mapProps,
|
|
622
|
+
useEntityId
|
|
623
|
+
)(component);
|
|
624
|
+
return <Item key={k} {...props} />;
|
|
625
|
+
})}
|
|
626
|
+
</div>
|
|
627
|
+
);
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
// With entity id case
|
|
631
|
+
if (
|
|
632
|
+
itemId.match(
|
|
633
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
|
|
634
|
+
)
|
|
635
|
+
) {
|
|
636
|
+
const i = model.indexOf('[');
|
|
637
|
+
const prePath = model.substring(1, i);
|
|
638
|
+
const finalPath = this.getEntityPathInCollection(
|
|
639
|
+
prePath,
|
|
640
|
+
itemId
|
|
641
|
+
)(
|
|
642
|
+
useEntityId
|
|
643
|
+
? this.getModelValue(this.props.entityId, useEntityId)
|
|
644
|
+
: this.getModelValue('')
|
|
645
|
+
);
|
|
646
|
+
|
|
647
|
+
return this.withModel(finalPath, mapProps, useEntityId)(component);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// Std
|
|
653
|
+
return this.withModel(model, mapProps, useEntityId)(component);
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
///////// High-level model mapper API
|
|
658
|
+
|
|
659
|
+
getWidgetToEntityMapper(component, mapProps) {
|
|
660
|
+
return this.WithModel(component, mapProps, true);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
getWidgetToFormMapper(component, mapProps) {
|
|
664
|
+
return this.WithModel(component, mapProps, false);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
getPluginToEntityMapper(component, pluginName, mapProps) {
|
|
668
|
+
const WiredPlugin = Widget.Wired(component)(
|
|
669
|
+
`${pluginName}@${this.props.id}`
|
|
670
|
+
);
|
|
671
|
+
return this.WithModel(WiredPlugin, mapProps, true);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
getPluginToFormMapper(component, pluginName, mapProps) {
|
|
675
|
+
const pluginPath = `${pluginName}@${this.props.id}`;
|
|
676
|
+
const WiredPlugin = Widget.Wired(component)(pluginPath);
|
|
677
|
+
return this.withModel(`backend.${pluginPath}`, mapProps, true)(WiredPlugin);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
wirePluginToForm(component, pluginName) {
|
|
681
|
+
const WiredPlugin = Widget.Wired(component)(
|
|
682
|
+
`${pluginName}@${this.props.id}`
|
|
683
|
+
);
|
|
684
|
+
return WiredPlugin;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
mapWidgetToBackend(component, mapProps, path) {
|
|
688
|
+
return this.withModel(`backend.${path}`, mapProps, true)(component);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
mapWidgetToEntityPlugin(component, mapProps, pluginName, path) {
|
|
692
|
+
path = `${pluginName}@${this.props.entityId}${path}`;
|
|
693
|
+
return this.withModel(`backend.${path}`, mapProps, true)(component);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
mapWidgetToFormPlugin(component, mapProps, pluginName, path) {
|
|
697
|
+
path = `${pluginName}@${this.props.id}${path}`;
|
|
698
|
+
return this.withModel(`backend.${path}`, mapProps, true)(component);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
mapWidget(component, mapProps, path) {
|
|
702
|
+
return this.withModel(path, mapProps, true)(component);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
buildCollectionLoader(ids, FinalComp, FallbackComp) {
|
|
706
|
+
let Loader = (props) => {
|
|
707
|
+
const loaded = ids.reduce((loaded, id) => {
|
|
708
|
+
return props[id] === true;
|
|
709
|
+
}, false);
|
|
710
|
+
if (loaded) {
|
|
711
|
+
return <FinalComp collection={this.getCollection(ids)} />;
|
|
712
|
+
} else {
|
|
713
|
+
return FallbackComp ? <FallbackComp /> : null;
|
|
714
|
+
}
|
|
715
|
+
};
|
|
716
|
+
|
|
717
|
+
ids.map((id) => {
|
|
718
|
+
Loader = this.mapWidget(
|
|
719
|
+
Loader,
|
|
720
|
+
(item) => {
|
|
721
|
+
return {
|
|
722
|
+
[id]: item !== null && item !== undefined,
|
|
723
|
+
};
|
|
724
|
+
},
|
|
725
|
+
`backend.${id}.id`
|
|
726
|
+
);
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
return <Loader />;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
getCollection(ids) {
|
|
733
|
+
return ids.map((id) => {
|
|
734
|
+
return this.getEntityById(id);
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
buildLoader(branch, Loaded, FallbackComp) {
|
|
739
|
+
const Loader = (props) => {
|
|
740
|
+
if (props.loaded) {
|
|
741
|
+
return <Loaded />;
|
|
742
|
+
} else {
|
|
743
|
+
return FallbackComp ? <FallbackComp /> : null;
|
|
744
|
+
}
|
|
745
|
+
};
|
|
746
|
+
|
|
747
|
+
const Renderer = this.mapWidget(
|
|
748
|
+
Loader,
|
|
749
|
+
(entityId) => {
|
|
750
|
+
if (!entityId) {
|
|
751
|
+
return {loaded: false};
|
|
752
|
+
} else {
|
|
753
|
+
return {loaded: true};
|
|
754
|
+
}
|
|
755
|
+
},
|
|
756
|
+
`backend.${branch}.id`
|
|
757
|
+
);
|
|
758
|
+
return <Renderer />;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
///////////GOBLIN BUS:
|
|
762
|
+
get registry() {
|
|
763
|
+
return this.getState().commands.get('registry');
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
canDo(cmd) {
|
|
767
|
+
if (!this.registry[cmd]) {
|
|
768
|
+
return false;
|
|
769
|
+
}
|
|
770
|
+
const state = this.getState().backend;
|
|
771
|
+
const clientSessionId = state
|
|
772
|
+
.get(this.context.labId)
|
|
773
|
+
.get('clientSessionId');
|
|
774
|
+
const loginSession = state.get(`login-session@${clientSessionId}`);
|
|
775
|
+
if (loginSession && this.registry[cmd] !== true) {
|
|
776
|
+
const rank = loginSession.get('rank');
|
|
777
|
+
if (this.registry[cmd][rank] && this.registry[cmd][rank] === true) {
|
|
778
|
+
return false;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
return true;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
cmd(cmd, args) {
|
|
785
|
+
if (!this.registry[cmd]) {
|
|
786
|
+
throw new Error(
|
|
787
|
+
`Command ${cmd} not implemented or authorized by the zepplin firewall`
|
|
788
|
+
);
|
|
789
|
+
}
|
|
790
|
+
const state = this.getState().backend;
|
|
791
|
+
|
|
792
|
+
if (args && !args.labId) {
|
|
793
|
+
args.labId = this.context.labId;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
if (args && !args.clientSessionId) {
|
|
797
|
+
args.clientSessionId = state.get(args.labId).get('clientSessionId');
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
if (args && !args.desktopId) {
|
|
801
|
+
args.desktopId = this.context.desktopId;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
const loginSession = state.get(`login-session@${args.clientSessionId}`);
|
|
805
|
+
if (loginSession && this.registry[cmd] !== true) {
|
|
806
|
+
const rank = loginSession.get('rank');
|
|
807
|
+
if (this.registry[cmd][rank] && this.registry[cmd][rank] === true) {
|
|
808
|
+
console.warn(
|
|
809
|
+
'%cGoblins Warning',
|
|
810
|
+
'font-weight: bold;',
|
|
811
|
+
`Command will be blocked`
|
|
812
|
+
);
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
const action = {
|
|
817
|
+
type: 'QUEST',
|
|
818
|
+
cmd,
|
|
819
|
+
data: args,
|
|
820
|
+
_xcraftIPC: true,
|
|
821
|
+
};
|
|
822
|
+
this.context.dispatch(action);
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
reportError(error, info) {
|
|
826
|
+
const desktopId = this.context.desktopId
|
|
827
|
+
? this.context.desktopId
|
|
828
|
+
: this.props.desktopId;
|
|
829
|
+
this.doFor(this.context.labId, 'when-ui-crash', {desktopId, error, info});
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
do(action, args) {
|
|
833
|
+
return this.doAs(this.name, action, args);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
doAs(service, action, args) {
|
|
837
|
+
const id = this.props.id || this.context.id;
|
|
838
|
+
if (!id) {
|
|
839
|
+
console.error(`${this.name} is not a connected widget (need an id)`);
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
this.cmd(`${service}.${action}`, Object.assign({id}, args));
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
doFor(serviceId, action, args) {
|
|
846
|
+
const service = serviceId.split('@')[0];
|
|
847
|
+
this.cmd(`${service}.${action}`, Object.assign({id: serviceId}, args));
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
/**
|
|
851
|
+
* Do backend quest or dispatch frontend action given the model
|
|
852
|
+
*
|
|
853
|
+
* @param {String} model - A path in the state.
|
|
854
|
+
* @param {String} name - The quest or action name.
|
|
855
|
+
* @param {Object} args - The arguments.
|
|
856
|
+
*/
|
|
857
|
+
doDispatch(model, name, args) {
|
|
858
|
+
const [root, id] = model.split('.');
|
|
859
|
+
if (root === 'backend') {
|
|
860
|
+
this.doFor(id, name, args);
|
|
861
|
+
} else if (root === 'widgets') {
|
|
862
|
+
this.dispatchTo(id, {
|
|
863
|
+
...args,
|
|
864
|
+
type: name,
|
|
865
|
+
});
|
|
866
|
+
} else {
|
|
867
|
+
throw new Error(`Model path starting with '${root}' is not supported.`);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
/**
|
|
872
|
+
* Dispatch an action in the frontend reducer for this widget.
|
|
873
|
+
*
|
|
874
|
+
* @param {Object} action - Redux action.
|
|
875
|
+
* @param {String} name - (optional) Reducer name.
|
|
876
|
+
*/
|
|
877
|
+
dispatch(action, name) {
|
|
878
|
+
this.dispatchTo(this.widgetId, action, name || this.name);
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
/**
|
|
882
|
+
* Dispatch an action in the frontend reducer for a specified widget.
|
|
883
|
+
*
|
|
884
|
+
* A `reducer.js` file must be present in a widget folder that matches
|
|
885
|
+
* the `name` parameter or the name specified in the `id` parameter,
|
|
886
|
+
* after the `$`.
|
|
887
|
+
*
|
|
888
|
+
* Possible values for `id`:
|
|
889
|
+
* `backendId` (example: desktop@111)
|
|
890
|
+
* `backendId$name` (exemple: workitem@222$hinter)
|
|
891
|
+
* `backendId$name@id` (exemple: workitem@222$hinter@333)
|
|
892
|
+
* `$name` (no backend id, must be manually collected in componentWillUnmount)
|
|
893
|
+
* `$name@id` (same as above)
|
|
894
|
+
*
|
|
895
|
+
* @param {String} id - Destination widget id.
|
|
896
|
+
* @param {Object} action - Redux action.
|
|
897
|
+
* @param {String} name - (optional) Reducer name.
|
|
898
|
+
*/
|
|
899
|
+
dispatchTo(id, action, name) {
|
|
900
|
+
if (typeof action !== 'function') {
|
|
901
|
+
if (!action.type) {
|
|
902
|
+
throw new Error(
|
|
903
|
+
`Cannot dispatch a widget action without a type. Action: ${JSON.stringify(
|
|
904
|
+
action
|
|
905
|
+
)}. Widget name: ${this.name}`
|
|
906
|
+
);
|
|
907
|
+
}
|
|
908
|
+
action._id = id;
|
|
909
|
+
if (name) {
|
|
910
|
+
action._type = name;
|
|
911
|
+
}
|
|
912
|
+
action.type = `@widgets_${action.type}`;
|
|
913
|
+
}
|
|
914
|
+
this.rawDispatch(action);
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
/**
|
|
918
|
+
* Dispatch a payload to the desktop cache reducer.
|
|
919
|
+
*
|
|
920
|
+
* This cache is useful for storing provisory values in the session (desktop)
|
|
921
|
+
* in order to restore a specific state when a widget is re-mount again.
|
|
922
|
+
* It's very useful in the case of the ScrollableContainer for example, where
|
|
923
|
+
* the last scroll position is dispatched in the cache, and restored with the
|
|
924
|
+
* next mount of this component. It's not possible to use the widget reducer
|
|
925
|
+
* associated to the ScrollableContainer because in this case, after the
|
|
926
|
+
* unmount, the reducer is collected.
|
|
927
|
+
*
|
|
928
|
+
* The cache has a limit, you must always consider that the values stored
|
|
929
|
+
* can be lost at any time.
|
|
930
|
+
*
|
|
931
|
+
* @param {string} id - Widget's id.
|
|
932
|
+
* @param {Object} payload - Payload to store.
|
|
933
|
+
*/
|
|
934
|
+
dispatchToCache(id, payload) {
|
|
935
|
+
this.dispatchTo(
|
|
936
|
+
this.context.desktopId,
|
|
937
|
+
{
|
|
938
|
+
type: 'WIDGET_CACHE',
|
|
939
|
+
widgetId: id,
|
|
940
|
+
value: payload,
|
|
941
|
+
},
|
|
942
|
+
'desktop'
|
|
943
|
+
);
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
rawDispatch(action) {
|
|
947
|
+
this.context.store.dispatch(action);
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
///////////NAVIGATION:
|
|
951
|
+
|
|
952
|
+
nav(route, frontOnly) {
|
|
953
|
+
if (frontOnly) {
|
|
954
|
+
this.context.dispatch(push(route));
|
|
955
|
+
} else {
|
|
956
|
+
this.doFor(this.context.labId, 'nav', {route});
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
static GetParameter(search, name) {
|
|
961
|
+
return getParameter(search, name);
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
setBackendValue(path, value) {
|
|
965
|
+
this.rawDispatch({
|
|
966
|
+
type: 'FIELD-CHANGED',
|
|
967
|
+
path,
|
|
968
|
+
value,
|
|
969
|
+
});
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
setModelValue(path, value, useEntity) {
|
|
973
|
+
let fullPath = 'backend.' + this.props.id + path;
|
|
974
|
+
if (useEntity) {
|
|
975
|
+
fullPath = 'backend.' + this.props.entityId + path;
|
|
976
|
+
}
|
|
977
|
+
this.rawDispatch({
|
|
978
|
+
type: 'FIELD-CHANGED',
|
|
979
|
+
path: fullPath,
|
|
980
|
+
value,
|
|
981
|
+
});
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
setFormValue(path, value) {
|
|
985
|
+
this.setModelValue(path, value);
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
setEntityValue(path, value) {
|
|
989
|
+
this.setModelValue(path, value, true);
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
backendHasBranch(branch) {
|
|
993
|
+
return this.getState().backend.has(branch);
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
getModelValue(model, fullPath) {
|
|
997
|
+
const storeState = this.getState();
|
|
998
|
+
const state = new Shredder({
|
|
999
|
+
backend: storeState.backend,
|
|
1000
|
+
widgets: storeState.widgets,
|
|
1001
|
+
network: storeState.network,
|
|
1002
|
+
});
|
|
1003
|
+
if (fullPath) {
|
|
1004
|
+
if (isFunction(model)) {
|
|
1005
|
+
model = model(state.get(model));
|
|
1006
|
+
}
|
|
1007
|
+
if (!model.startsWith('backend.')) {
|
|
1008
|
+
model = 'backend.' + model;
|
|
1009
|
+
}
|
|
1010
|
+
return state.get(model);
|
|
1011
|
+
} else {
|
|
1012
|
+
const parentModel = this.context.model || `backend.${this.props.id}`;
|
|
1013
|
+
if (isFunction(model)) {
|
|
1014
|
+
model = model(state.get(parentModel));
|
|
1015
|
+
}
|
|
1016
|
+
return state.get(`${parentModel}${model}`);
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
getBackendValue(fullpath) {
|
|
1021
|
+
const storeState = this.getState();
|
|
1022
|
+
const state = new Shredder({
|
|
1023
|
+
backend: storeState.backend,
|
|
1024
|
+
widgets: storeState.widgets,
|
|
1025
|
+
network: storeState.network,
|
|
1026
|
+
});
|
|
1027
|
+
return state.get(fullpath);
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
getFormValue(path) {
|
|
1031
|
+
return this.getModelValue(path);
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
getFormPluginValue(pluginName, path) {
|
|
1035
|
+
const state = new Shredder(this.getState().backend);
|
|
1036
|
+
return state.get(`${pluginName}@${this.props.id}${path}`);
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
getEntityPluginValue(pluginName, path) {
|
|
1040
|
+
const state = new Shredder(this.getState().backend);
|
|
1041
|
+
return state.get(`${pluginName}@${this.props.entityId}${path}`);
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
getEntityValue(model) {
|
|
1045
|
+
if (isFunction(model)) {
|
|
1046
|
+
const state = new Shredder(this.getState());
|
|
1047
|
+
model = model(state.get(`backend.${this.props.entityId}`));
|
|
1048
|
+
return this.getModelValue(model, true);
|
|
1049
|
+
} else {
|
|
1050
|
+
return this.getModelValue(`${this.props.entityId}${model}`, true);
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
getEntityById(entityId) {
|
|
1055
|
+
const state = new Shredder(this.getState().backend);
|
|
1056
|
+
return state.get(entityId);
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
getEntityPathInCollection(collectionPath, id, entityPath) {
|
|
1060
|
+
return (entity) => {
|
|
1061
|
+
const item = entity
|
|
1062
|
+
.get(collectionPath)
|
|
1063
|
+
.find((pack) => pack.get('id') === id);
|
|
1064
|
+
|
|
1065
|
+
return entityPath
|
|
1066
|
+
? `.${collectionPath}[${item.key}].${entityPath}`
|
|
1067
|
+
: `.${collectionPath}[${item.key}]`;
|
|
1068
|
+
};
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
getState() {
|
|
1072
|
+
return this.context.store.getState();
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
getSchema(path) {
|
|
1076
|
+
return Widget.getSchema(this.getState(), path);
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
getWidgetState() {
|
|
1080
|
+
const widgetId = this.widgetId;
|
|
1081
|
+
if (!widgetId) {
|
|
1082
|
+
throw new Error('Cannot resolve widget state without a valid id');
|
|
1083
|
+
}
|
|
1084
|
+
return this.getState().widgets.get(widgetId);
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
getWidgetCacheState(widgetId) {
|
|
1088
|
+
if (!widgetId) {
|
|
1089
|
+
throw new Error('Cannot resolve widget cache state without a valid id');
|
|
1090
|
+
}
|
|
1091
|
+
return this.getState().widgets.getIn([
|
|
1092
|
+
this.context.desktopId,
|
|
1093
|
+
'widgetsCache',
|
|
1094
|
+
widgetId,
|
|
1095
|
+
]);
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
getBackendState() {
|
|
1099
|
+
if (!this.props.id) {
|
|
1100
|
+
throw new Error('Cannot resolve backend state without a valid id');
|
|
1101
|
+
}
|
|
1102
|
+
return this.getState().backend.get(this.props.id);
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
getRouting() {
|
|
1106
|
+
return new Shredder(this.context.store.getState().router);
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
getSelectionState(target) {
|
|
1110
|
+
if (target.type !== 'text') {
|
|
1111
|
+
return null;
|
|
1112
|
+
}
|
|
1113
|
+
return {
|
|
1114
|
+
ss: target.selectionStart,
|
|
1115
|
+
se: target.selectionEnd,
|
|
1116
|
+
sd: target.selectionDirection,
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
getHinterType(hinterId) {
|
|
1121
|
+
let type = hinterId;
|
|
1122
|
+
if (!type || type === '') {
|
|
1123
|
+
return null;
|
|
1124
|
+
}
|
|
1125
|
+
const index = hinterId.indexOf('@');
|
|
1126
|
+
if (index !== -1) {
|
|
1127
|
+
type = hinterId.substr(0, index);
|
|
1128
|
+
}
|
|
1129
|
+
return type;
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
getHash() {
|
|
1133
|
+
return this.getRouting().get('location.hash');
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
getNearestId() {
|
|
1137
|
+
return this.props.id || this.context.nearestParentId;
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
static copyTextToClipboard(text) {
|
|
1141
|
+
const textField = document.createElement('textarea');
|
|
1142
|
+
textField.innerText = text;
|
|
1143
|
+
document.body.appendChild(textField);
|
|
1144
|
+
textField.select();
|
|
1145
|
+
document.execCommand('copy');
|
|
1146
|
+
textField.remove();
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
getUserSettings() {
|
|
1150
|
+
return Widget.getUserSession(new Shredder(this.getState()));
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
setUserSettings(questName, payload) {
|
|
1154
|
+
const state = this.getState().backend;
|
|
1155
|
+
const serviceId = state.get(window.labId).get('clientSessionId');
|
|
1156
|
+
payload.id = serviceId;
|
|
1157
|
+
const service = serviceId.split('@', 1)[0];
|
|
1158
|
+
this.cmd(`${service}.${questName}`, payload);
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
static getUserSession(state) {
|
|
1162
|
+
return state.get(
|
|
1163
|
+
`backend.${state.get(`backend.${window.labId}.clientSessionId`)}`
|
|
1164
|
+
);
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
static getLoginSession(state) {
|
|
1168
|
+
const clientSessionId = state.get(
|
|
1169
|
+
`backend.${window.labId}.clientSessionId`,
|
|
1170
|
+
null
|
|
1171
|
+
);
|
|
1172
|
+
if (!clientSessionId) {
|
|
1173
|
+
return null;
|
|
1174
|
+
}
|
|
1175
|
+
const loginSession = state.get(
|
|
1176
|
+
`backend.login-session@${clientSessionId}`,
|
|
1177
|
+
null
|
|
1178
|
+
);
|
|
1179
|
+
if (!loginSession) {
|
|
1180
|
+
return null;
|
|
1181
|
+
} else {
|
|
1182
|
+
return loginSession;
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
static getSchema(state, path) {
|
|
1187
|
+
if (state && state.backend) {
|
|
1188
|
+
const backend = new Shredder(state.backend);
|
|
1189
|
+
if (!path) {
|
|
1190
|
+
return backend.get(`workshop.schema`, null);
|
|
1191
|
+
} else {
|
|
1192
|
+
return backend.get(`workshop.schema.${path}`, null);
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
return null;
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
Widget.propTypes = {
|
|
1200
|
+
id: PropTypes.string,
|
|
1201
|
+
entityId: PropTypes.string,
|
|
1202
|
+
hinter: PropTypes.string,
|
|
1203
|
+
};
|
|
1204
|
+
|
|
1205
|
+
export default Widget;
|