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.
Files changed (62) hide show
  1. package/.editorconfig +9 -9
  2. package/.eslintrc.js +28 -28
  3. package/.zou-flow +3 -3
  4. package/README.md +107 -107
  5. package/carnotzet.js +10 -10
  6. package/config.js +13 -13
  7. package/laboratory.js +13 -13
  8. package/lib/.webpack-config.js +53 -53
  9. package/lib/carnotzet.js +118 -118
  10. package/lib/helpers.js +16 -16
  11. package/lib/index.js +66 -66
  12. package/package.json +47 -47
  13. package/widgets/connect-helpers/arrayEquals.js +5 -5
  14. package/widgets/connect-helpers/arraysEquals.js +24 -24
  15. package/widgets/connect-helpers/c.js +99 -99
  16. package/widgets/connect-helpers/join-models.js +16 -16
  17. package/widgets/connect-helpers/with-c.js +276 -276
  18. package/widgets/devtools.js +5 -5
  19. package/widgets/disconnect-overlay/styles.js +50 -50
  20. package/widgets/disconnect-overlay/widget.js +40 -40
  21. package/widgets/fields-view/widget.js +34 -34
  22. package/widgets/form/index.js +79 -79
  23. package/widgets/frame/widget.js +47 -47
  24. package/widgets/frontend-form/reducer.js +18 -18
  25. package/widgets/frontend-form/widget.js +15 -15
  26. package/widgets/importer/default.js +14 -14
  27. package/widgets/importer/importer.js +54 -53
  28. package/widgets/importer/index.js +4 -4
  29. package/widgets/index-browsers.js +195 -195
  30. package/widgets/index-electron-ws.js +153 -153
  31. package/widgets/index-electron.js +69 -69
  32. package/widgets/index.js +1 -1
  33. package/widgets/laboratory/service.js +542 -542
  34. package/widgets/laboratory/widget.js +98 -98
  35. package/widgets/maintenance/styles.js +38 -38
  36. package/widgets/maintenance/widget.js +65 -65
  37. package/widgets/props-binder/widget.js +48 -48
  38. package/widgets/renderer.js +85 -85
  39. package/widgets/root/index.js +54 -54
  40. package/widgets/searchkit/index.js +68 -68
  41. package/widgets/store/backend-reducer.js +116 -116
  42. package/widgets/store/commands-reducer.js +14 -14
  43. package/widgets/store/middlewares.js +171 -171
  44. package/widgets/store/network-reducer.js +23 -23
  45. package/widgets/store/root-reducer.js +35 -35
  46. package/widgets/store/store.js +40 -40
  47. package/widgets/store/widgets-reducer.js +95 -95
  48. package/widgets/theme-context/js-to-css.js +20 -20
  49. package/widgets/theme-context/widget.js +130 -130
  50. package/widgets/view/index.js +31 -31
  51. package/widgets/widget/index.js +1205 -1205
  52. package/widgets/widget/utils/connect.js +47 -47
  53. package/widgets/widget/utils/connectBackend.js +48 -48
  54. package/widgets/widget/utils/connectWidget.js +31 -31
  55. package/widgets/widget/utils/manifest.txt +134 -134
  56. package/widgets/widget/utils/shallowEqualShredder.js +36 -36
  57. package/widgets/widget/utils/widgets-actions.js +21 -21
  58. package/widgets/widget/utils/wrapMapStateToProps.js +26 -26
  59. package/widgets/with-desktop-id/widget.js +20 -20
  60. package/widgets/with-model/context.js +5 -5
  61. package/widgets/with-model/widget.js +42 -42
  62. package/widgets/with-workitem/widget.js +30 -30
@@ -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;