goblin-laboratory 4.11.2 → 4.12.0
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/lib/index.js +5 -0
- package/package.json +2 -2
- package/widgets/connect-helpers/map-c.js +42 -7
- package/widgets/connect-helpers/pick-c.js +13 -0
- package/widgets/connect-helpers/with-c.js +72 -27
- package/widgets/desktop-events.js +39 -0
- package/widgets/index-browsers.js +3 -0
- package/widgets/index-electron-ws.js +3 -0
- package/widgets/index-electron.js +4 -0
- package/widgets/renderer.js +5 -0
- package/widgets/widget/index.js +13 -7
- package/widgets/with-route/matchPath.js +67 -0
- package/widgets/with-route/with-route.js +1 -1
package/lib/index.js
CHANGED
|
@@ -34,6 +34,11 @@ class Channel {
|
|
|
34
34
|
this._send('DISPATCH_IN_APP', {action, _data: action});
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
sendEvent(topic, data) {
|
|
38
|
+
data = {topic, data};
|
|
39
|
+
this._send('NEW_EVENT', {data, _data: data});
|
|
40
|
+
}
|
|
41
|
+
|
|
37
42
|
beginRender(labId, tokens) {
|
|
38
43
|
this._send('BEGIN_RENDER', {labId, tokens, _data: labId});
|
|
39
44
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "goblin-laboratory",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.12.0",
|
|
4
4
|
"description": "Laboratory",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
@@ -43,12 +43,12 @@
|
|
|
43
43
|
"immutable": "4.0.0-rc.14",
|
|
44
44
|
"linked-list": "^1.0.4",
|
|
45
45
|
"obj-to-css": "^1.0.1",
|
|
46
|
+
"path-to-regexp": "^1.9.0",
|
|
46
47
|
"prettier": "2.0.4",
|
|
47
48
|
"prop-types": "^15.5.10",
|
|
48
49
|
"react": "^17.0.1",
|
|
49
50
|
"react-dom": "^17.0.1",
|
|
50
51
|
"react-redux": "^8.1.3",
|
|
51
|
-
"react-router": "^5.0.0",
|
|
52
52
|
"redux-thunk": "^2.3.0",
|
|
53
53
|
"safe-stable-stringify": "^2.5.0",
|
|
54
54
|
"xcraft-core-shredder": "^5.3.0",
|
|
@@ -1,16 +1,51 @@
|
|
|
1
1
|
import C, {ConnectedProp} from './c.js';
|
|
2
2
|
|
|
3
|
+
function mergeInFuncs(inFunc1, inFunc2) {
|
|
4
|
+
if (inFunc1) {
|
|
5
|
+
if (inFunc2) {
|
|
6
|
+
return (...args) => inFunc2(inFunc1(...args));
|
|
7
|
+
}
|
|
8
|
+
return inFunc1;
|
|
9
|
+
}
|
|
10
|
+
return inFunc2;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function mergeOutFuncs(outFunc1, outFunc2, inFunc1) {
|
|
14
|
+
if (outFunc1) {
|
|
15
|
+
if (outFunc2) {
|
|
16
|
+
if (outFunc1.length > 1 || outFunc2.length > 1) {
|
|
17
|
+
if (inFunc1) {
|
|
18
|
+
return (newValue, oldValue, ...oldValues) =>
|
|
19
|
+
outFunc1(
|
|
20
|
+
outFunc2(newValue, inFunc1(oldValue, ...oldValues)),
|
|
21
|
+
oldValue,
|
|
22
|
+
...oldValues
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
return (newValue, oldValue, ...oldValues) =>
|
|
26
|
+
outFunc1(
|
|
27
|
+
outFunc2(newValue, oldValue, ...oldValues),
|
|
28
|
+
oldValue,
|
|
29
|
+
...oldValues
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
return (newValue) => outFunc1(outFunc2(newValue));
|
|
33
|
+
}
|
|
34
|
+
return outFunc1;
|
|
35
|
+
}
|
|
36
|
+
if (outFunc2 && outFunc2.length > 1 && inFunc1) {
|
|
37
|
+
return (newValue, oldValue, ...oldValues) =>
|
|
38
|
+
outFunc2(newValue, inFunc1(oldValue, ...oldValues));
|
|
39
|
+
}
|
|
40
|
+
return outFunc2;
|
|
41
|
+
}
|
|
42
|
+
|
|
3
43
|
export default function mapC(value, inFunc, outFunc) {
|
|
4
44
|
if (value instanceof ConnectedProp) {
|
|
5
45
|
return C(
|
|
6
46
|
value.path,
|
|
7
|
-
value.inFunc
|
|
8
|
-
value.outFunc
|
|
9
|
-
? value.outFunc.length > 1 || outFunc.length > 1
|
|
10
|
-
? (newValue, ...oldValues) =>
|
|
11
|
-
value.outFunc(outFunc(newValue, ...oldValues), ...oldValues)
|
|
12
|
-
: (newValue) => value.outFunc(outFunc(newValue))
|
|
13
|
-
: outFunc
|
|
47
|
+
mergeInFuncs(value.inFunc, inFunc),
|
|
48
|
+
mergeOutFuncs(value.outFunc, outFunc, value.inFunc)
|
|
14
49
|
);
|
|
15
50
|
}
|
|
16
51
|
return inFunc(value);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import C from './c.js';
|
|
2
|
+
|
|
3
|
+
export default function pickC(...fields) {
|
|
4
|
+
for (const field of fields) {
|
|
5
|
+
if (field.lastIndexOf('.') !== 0) {
|
|
6
|
+
throw new Error(`Unsupported field ${field}`);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
const fieldNames = fields.map((field) => field.slice(1));
|
|
10
|
+
return C(fields, (...values) =>
|
|
11
|
+
Object.fromEntries(fieldNames.map((name, i) => [name, values[i]]))
|
|
12
|
+
);
|
|
13
|
+
}
|
|
@@ -27,13 +27,13 @@ function isShredderOrImmutable(obj) {
|
|
|
27
27
|
* ```
|
|
28
28
|
* And then the prop "value" can be connected to the state using:
|
|
29
29
|
* ```javascript
|
|
30
|
-
* <
|
|
30
|
+
* <TextField
|
|
31
31
|
* value={C('.age')}
|
|
32
32
|
* />
|
|
33
33
|
* ```
|
|
34
34
|
* Two functions can be applied, when reading and writing to the state:
|
|
35
35
|
* ```javascript
|
|
36
|
-
* <
|
|
36
|
+
* <TextField
|
|
37
37
|
* value={C('.age', age => age + '', age => Number(age))}
|
|
38
38
|
* />
|
|
39
39
|
* ```
|
|
@@ -53,8 +53,24 @@ function isShredderOrImmutable(obj) {
|
|
|
53
53
|
*
|
|
54
54
|
* It is possible to connect a prop to multiple values in the state:
|
|
55
55
|
* ```javascript
|
|
56
|
-
* <
|
|
57
|
-
*
|
|
56
|
+
* <Label
|
|
57
|
+
* text={C(['.firstname', '.lastname'], (firstname, lastname) => `${firstname} ${lastname}`)}
|
|
58
|
+
* />
|
|
59
|
+
* ```
|
|
60
|
+
*
|
|
61
|
+
* When multiple values are connected, the write action is "patch"
|
|
62
|
+
* and the output function must return a patch object:
|
|
63
|
+
* ```javascript
|
|
64
|
+
* <TextFieldTyped
|
|
65
|
+
* type="date"
|
|
66
|
+
* value={C(
|
|
67
|
+
* ['.year', '.month', '.day'],
|
|
68
|
+
* (year, month, day) => `${year}-${month}-${day}`,
|
|
69
|
+
* (value) => {
|
|
70
|
+
* const [year, month, day] = value.split('-');
|
|
71
|
+
* return {year, month, day};
|
|
72
|
+
* }
|
|
73
|
+
* )}
|
|
58
74
|
* />
|
|
59
75
|
* ```
|
|
60
76
|
*
|
|
@@ -79,18 +95,17 @@ export default function withC(Component, dispatchProps = {}, {modelProp} = {}) {
|
|
|
79
95
|
const ConnectedPropsMapper = (props) => {
|
|
80
96
|
let {_connectedProps, _connectedProp, _model, ...otherProps} = props;
|
|
81
97
|
const newProps = {};
|
|
82
|
-
for (const prop of _connectedProps) {
|
|
98
|
+
for (const {name, prop, fullPath} of _connectedProps) {
|
|
83
99
|
const inFunc = prop.inFunc;
|
|
84
100
|
if (inFunc) {
|
|
85
|
-
const name = prop.name;
|
|
86
101
|
if (name === '_connectedProp') {
|
|
87
|
-
if (Array.isArray(
|
|
102
|
+
if (Array.isArray(fullPath)) {
|
|
88
103
|
_connectedProp = inFunc(..._connectedProp);
|
|
89
104
|
} else {
|
|
90
105
|
_connectedProp = inFunc(_connectedProp);
|
|
91
106
|
}
|
|
92
107
|
} else {
|
|
93
|
-
if (Array.isArray(
|
|
108
|
+
if (Array.isArray(fullPath)) {
|
|
94
109
|
newProps[name] = inFunc(...props[name]);
|
|
95
110
|
} else {
|
|
96
111
|
newProps[name] = inFunc(props[name]);
|
|
@@ -104,10 +119,10 @@ export default function withC(Component, dispatchProps = {}, {modelProp} = {}) {
|
|
|
104
119
|
}
|
|
105
120
|
if (modelProp) {
|
|
106
121
|
const connectedModelProp = _connectedProps.find(
|
|
107
|
-
(
|
|
122
|
+
({name}) => name === modelProp
|
|
108
123
|
);
|
|
109
124
|
if (connectedModelProp) {
|
|
110
|
-
const path = connectedModelProp.path;
|
|
125
|
+
const path = connectedModelProp.prop.path;
|
|
111
126
|
const model = Array.isArray(path) ? path[0] : path;
|
|
112
127
|
return (
|
|
113
128
|
<WithModel model={model}>
|
|
@@ -124,8 +139,7 @@ export default function withC(Component, dispatchProps = {}, {modelProp} = {}) {
|
|
|
124
139
|
const ConnectedComponent = Widget.connect(
|
|
125
140
|
(state, props) => {
|
|
126
141
|
const newProps = {};
|
|
127
|
-
for (const
|
|
128
|
-
const fullPath = prop.fullPath;
|
|
142
|
+
for (const {name, fullPath} of props._connectedProps) {
|
|
129
143
|
let value;
|
|
130
144
|
if (Array.isArray(fullPath)) {
|
|
131
145
|
value = fullPath.map((p) => state.get(p));
|
|
@@ -134,7 +148,7 @@ export default function withC(Component, dispatchProps = {}, {modelProp} = {}) {
|
|
|
134
148
|
} else {
|
|
135
149
|
value = state.get(fullPath);
|
|
136
150
|
}
|
|
137
|
-
newProps[
|
|
151
|
+
newProps[name] = value;
|
|
138
152
|
}
|
|
139
153
|
return newProps;
|
|
140
154
|
},
|
|
@@ -163,7 +177,32 @@ export default function withC(Component, dispatchProps = {}, {modelProp} = {}) {
|
|
|
163
177
|
}
|
|
164
178
|
|
|
165
179
|
handlePropChange(propName, value) {
|
|
166
|
-
const
|
|
180
|
+
const propPath = this.props[propName].path;
|
|
181
|
+
|
|
182
|
+
if (Array.isArray(propPath)) {
|
|
183
|
+
const [root, id, ...pathArray] = (
|
|
184
|
+
this.props._model || this.context.model
|
|
185
|
+
).split('.');
|
|
186
|
+
if (pathArray.length > 0) {
|
|
187
|
+
throw new Error(`Patch with a path is not supported`);
|
|
188
|
+
}
|
|
189
|
+
if (root === 'backend') {
|
|
190
|
+
this.doFor(id, 'patch', {patch: value});
|
|
191
|
+
return;
|
|
192
|
+
} else if (root === 'widgets') {
|
|
193
|
+
this.dispatchTo(id, {
|
|
194
|
+
type: 'PATCH',
|
|
195
|
+
patch: value,
|
|
196
|
+
});
|
|
197
|
+
return;
|
|
198
|
+
} else {
|
|
199
|
+
throw new Error(
|
|
200
|
+
`Model path starting with '${root}' is not supported.`
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const path = this.addContextToPath(propPath);
|
|
167
206
|
if (!path) {
|
|
168
207
|
throw new Error(`Path is not defined`);
|
|
169
208
|
}
|
|
@@ -203,22 +242,22 @@ export default function withC(Component, dispatchProps = {}, {modelProp} = {}) {
|
|
|
203
242
|
|
|
204
243
|
for (const name of this.connectedPropNames) {
|
|
205
244
|
const prop = this.props[name];
|
|
206
|
-
prop.name = name;
|
|
207
245
|
|
|
208
246
|
// Add context to path
|
|
247
|
+
let fullPath;
|
|
209
248
|
if (Array.isArray(prop.path)) {
|
|
210
249
|
// Handle array of paths (for extra arguments to inFunc)
|
|
211
|
-
|
|
250
|
+
fullPath = prop.path.map(this.addContextToPath);
|
|
212
251
|
} else {
|
|
213
|
-
|
|
252
|
+
fullPath = this.addContextToPath(prop.path);
|
|
214
253
|
}
|
|
215
254
|
|
|
216
|
-
if (
|
|
255
|
+
if (fullPath === null || fullPath === undefined) {
|
|
217
256
|
// No path, the prop will receive 'undefined'
|
|
218
257
|
undefinedProps[name] = undefined;
|
|
219
258
|
} else {
|
|
220
259
|
// There is a path, add the prop to the list of connected props
|
|
221
|
-
connectedProps.push(prop);
|
|
260
|
+
connectedProps.push({name, prop, fullPath});
|
|
222
261
|
|
|
223
262
|
// Setup a dispatch prop to change the prop value
|
|
224
263
|
if (name in dispatchProps) {
|
|
@@ -226,16 +265,22 @@ export default function withC(Component, dispatchProps = {}, {modelProp} = {}) {
|
|
|
226
265
|
const outFunc = prop.outFunc;
|
|
227
266
|
if (outFunc) {
|
|
228
267
|
if (outFunc.length > 1) {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
268
|
+
if (Array.isArray(fullPath)) {
|
|
269
|
+
onChangeProps[dispatchPropName] = (value) => {
|
|
270
|
+
const currentValues = fullPath.map((path) =>
|
|
271
|
+
this.getState(path)
|
|
272
|
+
);
|
|
273
|
+
this.handlePropChange(
|
|
274
|
+
name,
|
|
275
|
+
outFunc(value, ...currentValues)
|
|
276
|
+
);
|
|
277
|
+
};
|
|
234
278
|
} else {
|
|
235
|
-
|
|
279
|
+
onChangeProps[dispatchPropName] = (value) => {
|
|
280
|
+
const currentValue = this.getState(fullPath);
|
|
281
|
+
this.handlePropChange(name, outFunc(value, currentValue));
|
|
282
|
+
};
|
|
236
283
|
}
|
|
237
|
-
onChangeProps[dispatchPropName] = (value) =>
|
|
238
|
-
this.handlePropChange(name, outFunc(value, ...currentValues));
|
|
239
284
|
} else {
|
|
240
285
|
onChangeProps[dispatchPropName] = (value) =>
|
|
241
286
|
this.handlePropChange(name, outFunc(value));
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
class DesktopEvents {
|
|
2
|
+
#events = new Map();
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Subscribe to a topic.
|
|
6
|
+
* @param {string} topic
|
|
7
|
+
* @param {Function} callback
|
|
8
|
+
* @returns {Function} unsub
|
|
9
|
+
*/
|
|
10
|
+
sub(topic, callback) {
|
|
11
|
+
/** @type {Set} */
|
|
12
|
+
let callbacks = this.#events.get(topic);
|
|
13
|
+
if (!callbacks) {
|
|
14
|
+
callbacks = new Set();
|
|
15
|
+
this.#events.set(topic, callbacks);
|
|
16
|
+
}
|
|
17
|
+
callbacks.add(callback);
|
|
18
|
+
/* unsub */
|
|
19
|
+
return () => {
|
|
20
|
+
callbacks.delete(callback);
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
emit(topic, data) {
|
|
25
|
+
const callbacks = this.#events.get(topic);
|
|
26
|
+
if (!callbacks) {
|
|
27
|
+
return; /* discard event */
|
|
28
|
+
}
|
|
29
|
+
for (const callback of callbacks) {
|
|
30
|
+
try {
|
|
31
|
+
callback(data);
|
|
32
|
+
} catch (ex) {
|
|
33
|
+
console.error(ex.stack || ex.message || ex);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export default new DesktopEvents();
|
|
@@ -52,6 +52,9 @@ class BrowsersRenderer extends Renderer {
|
|
|
52
52
|
case 'NEW_BACKEND_STATE':
|
|
53
53
|
this.newBackendState(data.transitState);
|
|
54
54
|
break;
|
|
55
|
+
case 'NEW_EVENT':
|
|
56
|
+
this.emitEvent(data.topic, data.data);
|
|
57
|
+
break;
|
|
55
58
|
case 'BEGIN_RENDER':
|
|
56
59
|
super.main(data.labId);
|
|
57
60
|
//persist for future handshaking
|
|
@@ -56,6 +56,10 @@ class ElectronRenderer extends Renderer {
|
|
|
56
56
|
this.newBackendState(transitState)
|
|
57
57
|
);
|
|
58
58
|
|
|
59
|
+
ipcRenderer.on('NEW_EVENT', (event, data) =>
|
|
60
|
+
this.emitEvent(data.topic, data.data)
|
|
61
|
+
);
|
|
62
|
+
|
|
59
63
|
ipcRenderer.on('BEGIN_RENDER', () => {
|
|
60
64
|
return super.main(labId);
|
|
61
65
|
});
|
package/widgets/renderer.js
CHANGED
|
@@ -2,6 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import ReactDOM from 'react-dom';
|
|
3
3
|
import Root from 'goblin-laboratory/widgets/root';
|
|
4
4
|
import configureStore from 'goblin-laboratory/widgets/store/store';
|
|
5
|
+
import desktopEvents from './desktop-events.js';
|
|
5
6
|
|
|
6
7
|
class Renderer {
|
|
7
8
|
constructor(send, options = {}) {
|
|
@@ -51,6 +52,10 @@ class Renderer {
|
|
|
51
52
|
);
|
|
52
53
|
}
|
|
53
54
|
|
|
55
|
+
emitEvent(topic, data) {
|
|
56
|
+
desktopEvents.emit(topic, data);
|
|
57
|
+
}
|
|
58
|
+
|
|
54
59
|
main(labId) {
|
|
55
60
|
//PUT LABID IN WINDOW STATE
|
|
56
61
|
//USEFULL IN SOME CONNECT()
|
package/widgets/widget/index.js
CHANGED
|
@@ -2,16 +2,17 @@ import React from 'react';
|
|
|
2
2
|
import _ from 'lodash';
|
|
3
3
|
import PropTypes from 'prop-types';
|
|
4
4
|
import Shredder from 'xcraft-core-shredder';
|
|
5
|
-
import {flushToStyleTag} from 'aphrodite/no-important';
|
|
5
|
+
import {flushToStyleTag} from 'aphrodite/no-important.js';
|
|
6
6
|
import importer from 'goblin_importer';
|
|
7
|
-
import shallowEqualShredder from './utils/shallowEqualShredder';
|
|
8
|
-
import _connect from './utils/connect';
|
|
9
|
-
import connectWidget from './utils/connectWidget';
|
|
10
|
-
import connectBackend from './utils/connectBackend';
|
|
11
|
-
import * as widgetsActions from './utils/widgets-actions';
|
|
7
|
+
import shallowEqualShredder from './utils/shallowEqualShredder.js';
|
|
8
|
+
import _connect from './utils/connect.js';
|
|
9
|
+
import connectWidget from './utils/connectWidget.js';
|
|
10
|
+
import connectBackend from './utils/connectBackend.js';
|
|
11
|
+
import * as widgetsActions from './utils/widgets-actions.js';
|
|
12
12
|
import mergeStyleDefinitions from './style/merge-style-definitions.js';
|
|
13
13
|
import buildStyle from './style/build-style.js';
|
|
14
14
|
import joinModels from '../connect-helpers/join-models.js';
|
|
15
|
+
import desktopEvents from '../desktop-events.js';
|
|
15
16
|
|
|
16
17
|
const stylesImporter = importer('styles');
|
|
17
18
|
const reducerImporter = importer('reducer');
|
|
@@ -143,6 +144,10 @@ class Widget extends React.Component {
|
|
|
143
144
|
return this.props.id || this.context.nearestParentId;
|
|
144
145
|
}
|
|
145
146
|
|
|
147
|
+
get events() {
|
|
148
|
+
return desktopEvents;
|
|
149
|
+
}
|
|
150
|
+
|
|
146
151
|
// Styles
|
|
147
152
|
|
|
148
153
|
get styles() {
|
|
@@ -311,7 +316,8 @@ class Widget extends React.Component {
|
|
|
311
316
|
do(action, args) {
|
|
312
317
|
return this.doAs(this.name, action, args);
|
|
313
318
|
}
|
|
314
|
-
/**
|
|
319
|
+
/**
|
|
320
|
+
* @deprecated Replace by doFor.
|
|
315
321
|
* It's possible to have a mismatch between service name and serviceId.
|
|
316
322
|
* Prefer to use doFor with the service id.
|
|
317
323
|
*/
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import pathToRegexp from 'path-to-regexp';
|
|
2
|
+
|
|
3
|
+
const cache = {};
|
|
4
|
+
const cacheLimit = 10000;
|
|
5
|
+
let cacheCount = 0;
|
|
6
|
+
|
|
7
|
+
function compilePath(path, options) {
|
|
8
|
+
const cacheKey = `${options.end}${options.strict}${options.sensitive}`;
|
|
9
|
+
const pathCache = cache[cacheKey] || (cache[cacheKey] = {});
|
|
10
|
+
|
|
11
|
+
if (pathCache[path]) return pathCache[path];
|
|
12
|
+
|
|
13
|
+
const keys = [];
|
|
14
|
+
const regexp = pathToRegexp(path, keys, options);
|
|
15
|
+
const result = {regexp, keys};
|
|
16
|
+
|
|
17
|
+
if (cacheCount < cacheLimit) {
|
|
18
|
+
pathCache[path] = result;
|
|
19
|
+
cacheCount++;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Public API for matching a URL pathname to a path.
|
|
27
|
+
*/
|
|
28
|
+
function matchPath(pathname, options = {}) {
|
|
29
|
+
if (typeof options === 'string' || Array.isArray(options)) {
|
|
30
|
+
options = {path: options};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const {path, exact = false, strict = false, sensitive = false} = options;
|
|
34
|
+
|
|
35
|
+
const paths = [].concat(path);
|
|
36
|
+
|
|
37
|
+
return paths.reduce((matched, path) => {
|
|
38
|
+
if (!path && path !== '') return null;
|
|
39
|
+
if (matched) return matched;
|
|
40
|
+
|
|
41
|
+
const {regexp, keys} = compilePath(path, {
|
|
42
|
+
end: exact,
|
|
43
|
+
strict,
|
|
44
|
+
sensitive,
|
|
45
|
+
});
|
|
46
|
+
const match = regexp.exec(pathname);
|
|
47
|
+
|
|
48
|
+
if (!match) return null;
|
|
49
|
+
|
|
50
|
+
const [url, ...values] = match;
|
|
51
|
+
const isExact = pathname === url;
|
|
52
|
+
|
|
53
|
+
if (exact && !isExact) return null;
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
path, // the path used to match
|
|
57
|
+
url: path === '/' && url === '' ? '/' : url, // the matched portion of the URL
|
|
58
|
+
isExact, // whether or not we matched exactly
|
|
59
|
+
params: keys.reduce((memo, key, index) => {
|
|
60
|
+
memo[key.name] = values[index];
|
|
61
|
+
return memo;
|
|
62
|
+
}, {}),
|
|
63
|
+
};
|
|
64
|
+
}, null);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export default matchPath;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {connect} from 'react-redux';
|
|
2
2
|
import Shredder from 'xcraft-core-shredder';
|
|
3
|
-
import {matchPath} from 'react-router';
|
|
4
3
|
import {getParameter} from '../../lib/helpers.js';
|
|
5
4
|
import shallowEqualShredder from '../widget/utils/shallowEqualShredder.js';
|
|
5
|
+
import matchPath from './matchPath.js';
|
|
6
6
|
|
|
7
7
|
export function withRoute(path, watchedParams, watchedSearchs, watchHash) {
|
|
8
8
|
return connect(
|