goblin-gadgets 4.0.17 → 4.0.23

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/eslint.config.js CHANGED
@@ -15,7 +15,10 @@ module.exports = [
15
15
  languageOptions: {
16
16
  parser: babelParser,
17
17
  parserOptions: {
18
- requireConfigFile: false,
18
+ requireConfigFile: false, // Évite de devoir spécifier un fichier de configuration Babel
19
+ babelOptions: {
20
+ presets: ['@babel/preset-react'],
21
+ },
19
22
  ecmaFeatures: {
20
23
  jsx: true,
21
24
  },
@@ -36,7 +39,7 @@ module.exports = [
36
39
  'eqeqeq': 'error',
37
40
  'no-console': 'off',
38
41
  'react/display-name': 'off',
39
- '@babel/no-unused-expressions': 'error',
42
+ '@babel/no-unused-expressions': 'error', // Utilisation de règles spécifiques à Babel
40
43
  'no-unused-vars': [
41
44
  'error',
42
45
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "goblin-gadgets",
3
- "version": "4.0.17",
3
+ "version": "4.0.23",
4
4
  "description": "Gadgets library",
5
5
  "main": "./builders/builders.js",
6
6
  "scripts": {
@@ -29,7 +29,7 @@
29
29
  "goblin-theme": "^2.0.0",
30
30
  "goblin-laboratory": "^4.0.0",
31
31
  "leaflet": "1.2.0",
32
- "mocha": "^5.2.0",
32
+ "mocha": "^10.7.3",
33
33
  "monaco-editor": "^0.31.1",
34
34
  "mousetrap": "^1.6.1",
35
35
  "prettier": "2.0.4",
@@ -44,7 +44,7 @@
44
44
  "scroll-into-view-if-needed": "^1.4.0",
45
45
  "xcraft-core-shredder": "^5.0.0",
46
46
  "xcraft-dev-prettier": "^2.0.0",
47
- "xcraft-dev-rules": "^4.4.0"
47
+ "xcraft-dev-rules": "^4.4.2"
48
48
  },
49
49
  "prettier": "xcraft-dev-prettier"
50
50
  }
@@ -0,0 +1,13 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Retrieve the list of available commands.
5
+ *
6
+ * @returns {Object} The list and definitions of commands.
7
+ */
8
+ exports.xcraftCommands = function () {
9
+ return require(`./widgets/${require('path').basename(
10
+ __filename,
11
+ '.js'
12
+ )}/service.js`);
13
+ };
@@ -74,7 +74,6 @@ class LabelTextField extends Widget {
74
74
  width={this.props.labelWidth}
75
75
  disabled={this.props.disabled}
76
76
  wrap="no"
77
- justify="left"
78
77
  />
79
78
  );
80
79
  }
@@ -0,0 +1,248 @@
1
+ import {Unit} from 'goblin-theme';
2
+ const px = Unit.toPx;
3
+ const n = Unit.toValue;
4
+
5
+ /******************************************************************************/
6
+
7
+ export const propNames = [
8
+ 'visibility',
9
+ 'size',
10
+ 'origin',
11
+ 'attach',
12
+ 'width',
13
+ 'height',
14
+ 'heightStrategy',
15
+ 'marginTopBottom',
16
+ 'attachPoint',
17
+ 'transitionScope',
18
+ 'triangle',
19
+ 'triangleSize',
20
+ 'screenBackground',
21
+ ];
22
+
23
+ export default function styles(theme, props) {
24
+ const {
25
+ visibility,
26
+ size,
27
+ origin,
28
+ attach,
29
+ width,
30
+ height,
31
+ heightStrategy,
32
+ marginTopBottom,
33
+ attachPoint,
34
+ transitionScope = 'all',
35
+ triangle,
36
+ triangleSize = '16px',
37
+ screenBackground = 'rgba(255,255,255,0.8)',
38
+ } = props;
39
+
40
+ const showed = visibility === 'show';
41
+ const full = size === 'fullscreen';
42
+ const fitToContent = heightStrategy === 'fit-to-content';
43
+
44
+ let left = '0px';
45
+ let right = '0px';
46
+ let top = '0px';
47
+ let bottom = fitToContent ? null : '0px';
48
+
49
+ let marginLeft = 'calc(50% - 600px + 50px)';
50
+ let marginRight = 'calc(50% - 600px + 50px)';
51
+ let marginTop = '100px';
52
+ let marginBottom = '100px';
53
+ let maxHeight = fitToContent ? 'calc(100% - 200px)' : null;
54
+
55
+ if (width) {
56
+ const w = n(width);
57
+ marginLeft = `calc(50% - ${px(w / 2)})`;
58
+ marginRight = `calc(50% - ${px(w / 2)})`;
59
+ }
60
+
61
+ if (width && attach && attach.includes('left')) {
62
+ left = 'calc(50% - 600px + 100px)';
63
+ right = null;
64
+ marginLeft = null;
65
+ marginRight = null;
66
+ }
67
+
68
+ if (width && attach && attach.includes('right')) {
69
+ left = null;
70
+ right = 'calc(50% - 600px + 100px)';
71
+ marginLeft = null;
72
+ marginRight = null;
73
+ }
74
+
75
+ if (height && attach && attach.includes('top')) {
76
+ bottom = null;
77
+ marginTop = '100px';
78
+ marginBottom = null;
79
+ }
80
+
81
+ if (height && attach && attach.includes('bottom')) {
82
+ top = null;
83
+ marginBottom = '100px';
84
+ marginTop = null;
85
+ }
86
+
87
+ if (height && !attach) {
88
+ const h = n(height);
89
+ top = '50%';
90
+ bottom = '50%';
91
+ marginTop = px(-h / 2);
92
+ marginBottom = null;
93
+ }
94
+
95
+ if (marginTopBottom) {
96
+ marginTop = marginTopBottom;
97
+ marginBottom = marginTopBottom;
98
+ }
99
+
100
+ if (attachPoint) {
101
+ // Compute the attach point without overflow managment.
102
+ const al = px(attachPoint.x - n(width) / 2);
103
+ // Manages the overflow on the right.
104
+ const x = `min(${al}, 100vw - ${width} - 10px)`;
105
+ // Manages the overflow on the left.
106
+ const ml = `max(${x}, 10px)`;
107
+
108
+ // Cases where the popup goes out of the window are not handled.
109
+ switch (triangle) {
110
+ case 'bottom':
111
+ marginLeft = ml;
112
+ marginRight = null;
113
+ marginTop = px(attachPoint.y - n(height) - n(triangleSize));
114
+ marginBottom = null;
115
+ break;
116
+ case 'top':
117
+ marginLeft = ml;
118
+ marginRight = null;
119
+ marginTop = px(attachPoint.y + n(triangleSize));
120
+ marginBottom = null;
121
+ break;
122
+ case 'left':
123
+ marginLeft = px(attachPoint.x + n(triangleSize));
124
+ marginRight = null;
125
+ marginTop = px(attachPoint.y - n(height) / 2);
126
+ marginBottom = null;
127
+ break;
128
+ case 'right':
129
+ marginLeft = px(attachPoint.x - n(width) - n(triangleSize));
130
+ marginRight = null;
131
+ marginTop = px(attachPoint.y - n(height) / 2);
132
+ marginBottom = null;
133
+ break;
134
+ default:
135
+ console.error(
136
+ `popupContainer: triangle position '${triangle}' not supported`
137
+ );
138
+ }
139
+ }
140
+
141
+ if (full) {
142
+ marginLeft = null;
143
+ marginRight = null;
144
+ marginTop = null;
145
+ marginBottom = null;
146
+ }
147
+
148
+ const popupContainer = {
149
+ position: 'absolute',
150
+ inset: '0',
151
+ overflow: 'hidden',
152
+ backgroundColor: showed ? screenBackground : null,
153
+ transition: theme.transitions.openClosePopup,
154
+ opacity: showed ? 1 : 0,
155
+ pointerEvents: showed ? 'auto' : 'none',
156
+ };
157
+
158
+ const window = {
159
+ position: 'absolute',
160
+ left,
161
+ right,
162
+ top,
163
+ bottom,
164
+ marginLeft,
165
+ marginRight,
166
+ marginTop,
167
+ marginBottom,
168
+ width,
169
+ height,
170
+ maxHeight,
171
+ backgroundColor: '#fff',
172
+ borderRadius: full ? null : theme.shapes.popupRadius,
173
+ boxShadow: full ? null : '0px 0px 40px 0px rgba(0,0,0,0.5)',
174
+ display: 'flex',
175
+ flexDirection: 'column',
176
+ overflow: 'hidden',
177
+ transformOrigin: origin || 'bottom',
178
+ transition: `${transitionScope} ${theme.transitions.openClosePopup}`,
179
+ transform: showed ? null : 'scale(0.5)', // (*)
180
+ };
181
+
182
+ // (*) Ne pas faire:
183
+ // transform: showed ? 'scale(1)' : 'scale(0.5)',
184
+ // Cela génère un bug étrange lors du drag dans ColorPicker et AnalogClock !
185
+
186
+ /******************************************************************************/
187
+
188
+ const windowTriangle = {
189
+ ...window,
190
+ backgroundColor: null,
191
+ boxShadow: null,
192
+ overflow: null,
193
+ pointerEvents: 'none',
194
+ };
195
+
196
+ const triangleStyle = {
197
+ position: 'absolute',
198
+ width: '0px',
199
+ height: '0px',
200
+ boxSizing: 'border-box',
201
+ };
202
+
203
+ switch (triangle) {
204
+ case 'bottom':
205
+ triangleStyle.top = height;
206
+ triangleStyle.left = `calc(50% - ${triangleSize})`;
207
+ triangleStyle.borderTop = `${triangleSize} solid #fff`;
208
+ triangleStyle.borderBottom = `${triangleSize} solid transparent`;
209
+ triangleStyle.borderLeft = `${triangleSize} solid transparent`;
210
+ triangleStyle.borderRight = `${triangleSize} solid transparent`;
211
+ break;
212
+ case 'top':
213
+ triangleStyle.top = px(-2 * n(triangleSize));
214
+ triangleStyle.left = `calc(50% - ${triangleSize})`;
215
+ triangleStyle.borderTop = `${triangleSize} solid transparent`;
216
+ triangleStyle.borderBottom = `${triangleSize} solid #fff`;
217
+ triangleStyle.borderLeft = `${triangleSize} solid transparent`;
218
+ triangleStyle.borderRight = `${triangleSize} solid transparent`;
219
+ break;
220
+ case 'left':
221
+ triangleStyle.top = `calc(50% - ${triangleSize})`;
222
+ triangleStyle.left = px(-2 * n(triangleSize));
223
+ triangleStyle.borderTop = `${triangleSize} solid transparent`;
224
+ triangleStyle.borderBottom = `${triangleSize} solid transparent`;
225
+ triangleStyle.borderLeft = `${triangleSize} solid transparent`;
226
+ triangleStyle.borderRight = `${triangleSize} solid #fff`;
227
+ break;
228
+ case 'right':
229
+ triangleStyle.top = `calc(50% - ${triangleSize})`;
230
+ triangleStyle.left = width;
231
+ triangleStyle.borderTop = `${triangleSize} solid transparent`;
232
+ triangleStyle.borderBottom = `${triangleSize} solid transparent`;
233
+ triangleStyle.borderLeft = `${triangleSize} solid #fff`;
234
+ triangleStyle.borderRight = `${triangleSize} solid transparent`;
235
+ break;
236
+ }
237
+
238
+ /******************************************************************************/
239
+
240
+ return {
241
+ popupContainer,
242
+ window,
243
+ windowTriangle,
244
+ triangle: triangleStyle,
245
+ };
246
+ }
247
+
248
+ /******************************************************************************/
@@ -0,0 +1,69 @@
1
+ import React from 'react';
2
+ import * as styles from './styles';
3
+ import Widget from 'goblin-laboratory/widgets/widget';
4
+
5
+ /******************************************************************************/
6
+
7
+ function isInside(rect, x, y) {
8
+ if (rect && rect.left < rect.right && rect.top < rect.bottom) {
9
+ return (
10
+ x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom
11
+ );
12
+ } else {
13
+ return true;
14
+ }
15
+ }
16
+
17
+ /******************************************************************************/
18
+
19
+ export default class PopupContainer extends Widget {
20
+ constructor() {
21
+ super(...arguments);
22
+ this.styles = styles;
23
+ this.onBackgroundClick = this.onBackgroundClick.bind(this);
24
+ }
25
+
26
+ onBackgroundClick(e) {
27
+ if (!this.props.enableBackgroundClickForCancel) {
28
+ return;
29
+ }
30
+
31
+ const rect = this.divWindow.getBoundingClientRect();
32
+ if (!isInside(rect, e.clientX, e.clientY)) {
33
+ // If the mouse is outside the window, close it.
34
+ this.props.onClose();
35
+ }
36
+ }
37
+
38
+ renderTriangle() {
39
+ if (!this.props.triangle) {
40
+ return null;
41
+ }
42
+
43
+ return (
44
+ <div className={this.styles.classNames.windowTriangle}>
45
+ <div className={this.styles.classNames.triangle} />
46
+ </div>
47
+ );
48
+ }
49
+
50
+ render() {
51
+ return (
52
+ <div
53
+ className={this.styles.classNames.popupContainer}
54
+ onMouseDown={this.onBackgroundClick}
55
+ onTouchStart={this.onBackgroundClick}
56
+ >
57
+ <div
58
+ ref={(node) => (this.divWindow = node)}
59
+ className={this.styles.classNames.window}
60
+ >
61
+ {this.props.children}
62
+ </div>
63
+ {this.renderTriangle()}
64
+ </div>
65
+ );
66
+ }
67
+ }
68
+
69
+ /******************************************************************************/
@@ -0,0 +1,108 @@
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+ const goblinName = path.basename(module.parent.filename, '.js');
5
+ const Goblin = require('xcraft-core-goblin');
6
+
7
+ /******************************************************************************/
8
+
9
+ // Define initial logic values
10
+ const logicState = {
11
+ id: goblinName,
12
+ popup: null,
13
+ params: null,
14
+ };
15
+
16
+ /******************************************************************************/
17
+
18
+ const logicHandlers = {
19
+ create: (state, action) => {
20
+ return state.set('id', action.get('id'));
21
+ },
22
+
23
+ show: (state, action) => {
24
+ return state
25
+ .set('popup', action.get('popup'))
26
+ .set('params', action.get('params'));
27
+ },
28
+
29
+ hide: (state) => {
30
+ return state.set('popup', null).set('params', null);
31
+ },
32
+
33
+ setParams: (state, action) => {
34
+ return state.merge('params', action.get('params'));
35
+ },
36
+ };
37
+
38
+ /******************************************************************************/
39
+
40
+ Goblin.registerQuest(goblinName, 'create', async function (
41
+ quest,
42
+ id,
43
+ desktopId,
44
+ labId
45
+ ) {
46
+ quest.goblin.setX('desktopId', desktopId);
47
+ quest.goblin.setX('labId', labId);
48
+
49
+ await quest.warehouse.subscribe({
50
+ feed: desktopId,
51
+ branches: [id],
52
+ });
53
+
54
+ quest.do({id});
55
+ });
56
+
57
+ /******************************************************************************/
58
+
59
+ Goblin.registerQuest(goblinName, 'show', async function (quest, popup, params) {
60
+ await quest.me.showWindow();
61
+ quest.do({popup, params});
62
+ });
63
+
64
+ Goblin.registerQuest(goblinName, 'prompt', async function (
65
+ quest,
66
+ popup,
67
+ params
68
+ ) {
69
+ await quest.me.showWindow();
70
+ const result = await quest.sub.callAndWait(async function () {
71
+ await quest.me.show({popup, params});
72
+ }, `*::*<${popup}.done>`);
73
+
74
+ return result;
75
+ });
76
+
77
+ Goblin.registerQuest(goblinName, 'hide', function (quest, result) {
78
+ const state = quest.goblin.getState();
79
+ const popup = state.get('popup');
80
+ quest.evt(`<${popup}.done>`, result);
81
+ quest.do();
82
+ });
83
+
84
+ Goblin.registerQuest(goblinName, 'setParams', function (quest, params) {
85
+ quest.do({params});
86
+ });
87
+
88
+ Goblin.registerQuest(goblinName, 'showWindow', async function (quest) {
89
+ const labId = quest.goblin.getX('labId');
90
+ if (!labId) {
91
+ return;
92
+ }
93
+ const winId = `wm@${labId}`;
94
+ try {
95
+ const wmAPI = quest.getAPI(winId);
96
+ await wmAPI.moveToFront();
97
+ } catch (e) {
98
+ const err = e.stack || e.message || e;
99
+ quest.log.warn(
100
+ `Unable to call goblin-wm API ! showWindow works only in electron app ! ${err}`
101
+ );
102
+ }
103
+ });
104
+
105
+ Goblin.registerQuest(goblinName, 'delete', function (quest) {});
106
+
107
+ /******************************************************************************/
108
+ module.exports = Goblin.configure(goblinName, logicState, logicHandlers);
@@ -0,0 +1,112 @@
1
+ import React from 'react';
2
+ import Widget from 'goblin-laboratory/widgets/widget';
3
+ import PopupContainer from 'goblin-gadgets/widgets/popup-container/widget';
4
+
5
+ /******************************************************************************/
6
+
7
+ function getAttachPoint(rect, shift, mode) {
8
+ let x, y;
9
+
10
+ switch (mode.x) {
11
+ case 'left':
12
+ x = rect.get('left');
13
+ break;
14
+ case 'right':
15
+ x = rect.get('right');
16
+ break;
17
+ case 'center':
18
+ x = (rect.get('left') + rect.get('right')) / 2;
19
+ break;
20
+ }
21
+
22
+ switch (mode.y) {
23
+ case 'top':
24
+ y = rect.get('top');
25
+ break;
26
+ case 'bottom':
27
+ y = rect.get('bottom');
28
+ break;
29
+ case 'center':
30
+ y = (rect.gett('top') + rect.get('bottom')) / 2;
31
+ break;
32
+ }
33
+
34
+ x += shift.x;
35
+ y += shift.y;
36
+
37
+ return {x, y};
38
+ }
39
+
40
+ /******************************************************************************/
41
+
42
+ class PopupDispatcher extends Widget {
43
+ constructor() {
44
+ super(...arguments);
45
+ this.handleClose = this.handleClose.bind(this);
46
+ }
47
+
48
+ handleClose(item) {
49
+ this.doFor(this.props.id, 'hide', {popup: item.popup});
50
+ }
51
+ /******************************************************************************/
52
+
53
+ renderPopup(item, index) {
54
+ const {id, popup, params} = this.props;
55
+
56
+ const show = popup === item.popup;
57
+
58
+ if (params?.get('parentRect') && item.attachMode) {
59
+ item.attachPoint = getAttachPoint(
60
+ params.get('parentRect'),
61
+ item.attachShift,
62
+ item.attachMode
63
+ );
64
+ }
65
+
66
+ return (
67
+ <PopupContainer
68
+ key={index}
69
+ visibility={show ? 'show' : 'hidden'}
70
+ size={item.size}
71
+ origin={item.origin}
72
+ attach={item.attach}
73
+ width={item.width}
74
+ height={item.height}
75
+ heightStrategy={item.heightStrategy}
76
+ marginTopBottom={item.marginTopBottom}
77
+ screenBackground={item.screenBackground}
78
+ attachPoint={item.attachPoint}
79
+ triangle={item.triangle}
80
+ triangleSize="16px"
81
+ transitionScope={item.transitionScope}
82
+ enableBackgroundClickForCancel={item.enableBackgroundClickForCancel}
83
+ onClose={() => this.handleClose(item)}
84
+ >
85
+ {item.render({id, popup, params})}
86
+ </PopupContainer>
87
+ );
88
+ }
89
+
90
+ render() {
91
+ return this.props.popups.map((item, index) =>
92
+ this.renderPopup(item, index)
93
+ );
94
+ }
95
+ }
96
+
97
+ /******************************************************************************/
98
+
99
+ export default Widget.connect((state, props) => {
100
+ const serviceState = state.get(`backend.${props.id}`);
101
+ if (!serviceState) {
102
+ return {};
103
+ }
104
+
105
+ const popup = serviceState.get('popup');
106
+ const params = serviceState.get('params');
107
+
108
+ return {
109
+ popup,
110
+ params,
111
+ };
112
+ })(PopupDispatcher);
@@ -55,6 +55,7 @@ class TabNavigationLogic extends Elf.Spirit {
55
55
  }
56
56
 
57
57
  class TabNavigation extends Elf {
58
+ logic = Elf.getLogic(TabNavigationLogic);
58
59
  state = new TabNavigationState();
59
60
 
60
61
  /** @type {NavigationViews} */
@@ -72,14 +73,14 @@ class TabNavigation extends Elf {
72
73
  async create(id, desktopId, views) {
73
74
  this.desktopId = desktopId;
74
75
  this.views = views;
75
- this.do({id});
76
+ this.logic.create(id);
76
77
  const firstTab = Object.keys(views)[0];
77
78
  await this.setTab(firstTab);
78
79
  return this;
79
80
  }
80
81
 
81
82
  async change(path, newValue) {
82
- this.do({path, newValue});
83
+ this.logic.change(path, newValue);
83
84
  }
84
85
 
85
86
  async _loadService(tab) {
@@ -112,7 +113,7 @@ class TabNavigation extends Elf {
112
113
  const view = this.views[tab];
113
114
  const widget = view.widget;
114
115
  const widgetProps = view.widgetProps;
115
- this.do({tab, serviceId, widget, widgetProps});
116
+ this.logic.setTab(tab, serviceId, widget, widgetProps);
116
117
  }
117
118
 
118
119
  async switchTab(reverse) {