@guillotinaweb/react-gmi 0.20.1 → 0.22.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.
@@ -1,4 +1,4 @@
1
- import React, { useState, useRef, useEffect, useCallback, createContext, forwardRef, createElement, Fragment, useContext, useReducer, Component } from 'react';
1
+ import React, { createContext, useState, useCallback, useRef, useEffect, forwardRef, createElement, Fragment, useContext, useReducer } from 'react';
2
2
  import usePortal from 'react-useportal';
3
3
  import PropTypes from 'prop-types';
4
4
  import jwt_decode from 'jwt-decode';
@@ -36,74 +36,140 @@ function _objectWithoutPropertiesLoose(source, excluded) {
36
36
  return target;
37
37
  }
38
38
 
39
- class RestClient {
40
- constructor(url, auth) {
41
- this.auth = auth;
42
- this.url = url;
39
+ const AuthContext = createContext({});
40
+ const ClientContext = createContext({});
41
+ const TraversalContext = createContext({});
42
+
43
+ class Traversal {
44
+ constructor(_ref) {
45
+ let {
46
+ flash
47
+ } = _ref,
48
+ props = _objectWithoutPropertiesLoose(_ref, ["flash"]);
49
+
50
+ Object.assign(this, props);
51
+ if (typeof flash === 'function') this.flash = flash;
43
52
  }
44
53
 
45
- async request(path, data, headers) {
46
- data = data || {};
47
- data.headers = headers || this.getHeaders();
48
- return await fetch(`${this.url}${path}`, data);
54
+ refresh({
55
+ transparent = false
56
+ } = {}) {
57
+ this.dispatch({
58
+ type: 'REFRESH',
59
+ payload: {
60
+ transparent
61
+ }
62
+ });
49
63
  }
50
64
 
51
- getHeaders() {
52
- const authToken = this.auth.getToken();
53
- if (!authToken) return {};
54
- return {
55
- Accept: 'application/json',
56
- 'Content-Type': 'application/json',
57
- Authorization: 'Bearer ' + authToken
58
- };
65
+ get path() {
66
+ return this.state.path;
59
67
  }
60
68
 
61
- async post(path, data) {
62
- return await this.request(path, {
63
- method: 'post',
64
- body: JSON.stringify(data)
69
+ get pathPrefix() {
70
+ return this.state.path.slice(1);
71
+ }
72
+
73
+ get context() {
74
+ return this.state.context;
75
+ }
76
+
77
+ get containerPath() {
78
+ return this.client.getContainerFromPath(this.path);
79
+ }
80
+
81
+ apply(data) {
82
+ // apply a optimistic update to context
83
+ this.dispatch({
84
+ type: 'APPLY',
85
+ payload: data
65
86
  });
66
87
  }
67
88
 
68
- async get(path) {
69
- return await this.request(path, {});
89
+ flash(message, type) {
90
+ this.dispatch({
91
+ type: 'SET_FLASH',
92
+ payload: {
93
+ flash: {
94
+ message,
95
+ type
96
+ }
97
+ }
98
+ });
99
+ window.scrollTo({
100
+ top: 0,
101
+ left: 0,
102
+ behavior: 'smooth'
103
+ });
70
104
  }
71
105
 
72
- async put(path, data) {
73
- return await this.request(path, {
74
- method: 'put',
75
- body: JSON.stringify(data)
106
+ clearFlash() {
107
+ this.dispatch({
108
+ type: 'CLEAR_FLASH'
76
109
  });
77
110
  }
78
111
 
79
- async patch(path, data) {
80
- return await this.request(path, {
81
- method: 'PATCH',
82
- body: JSON.stringify(data)
112
+ doAction(action, params) {
113
+ this.dispatch({
114
+ type: 'SET_ACTION',
115
+ payload: {
116
+ action,
117
+ params
118
+ }
83
119
  });
84
120
  }
85
121
 
86
- async upload(path, data) {
87
- const headers = this.getHeaders();
88
- delete headers['Content-Type'];
89
- headers['Content-Type'] = data['content-type'];
90
- headers['X-UPLOAD-FILENAME'] = data.filename;
91
- headers['Content-Encoding'] = 'base64';
92
- return await this.request(path, {
93
- method: 'PATCH',
94
- body: data.data
95
- }, headers);
122
+ cancelAction() {
123
+ this.dispatch({
124
+ type: 'CLEAR_ACTION'
125
+ });
96
126
  }
97
127
 
98
- async delete(path, data = undefined) {
99
- return await this.request(path, {
100
- method: 'delete',
101
- body: JSON.stringify(data)
128
+ hasPerm(permission) {
129
+ return this.state.permissions[permission] === true;
130
+ }
131
+
132
+ filterTabs(tabs, tabsPermissions) {
133
+ const result = {};
134
+ Object.keys(tabs).forEach(item => {
135
+ const perm = tabsPermissions[item];
136
+
137
+ if (perm && this.hasPerm(perm)) {
138
+ result[item] = tabs[item];
139
+ } else if (!perm) {
140
+ result[item] = tabs[item];
141
+ }
102
142
  });
143
+ return result;
103
144
  }
104
145
 
105
146
  }
106
147
 
148
+ function TraversalProvider(_ref2) {
149
+ let {
150
+ children
151
+ } = _ref2,
152
+ props = _objectWithoutPropertiesLoose(_ref2, ["children"]);
153
+
154
+ return /*#__PURE__*/React.createElement(TraversalContext.Provider, {
155
+ value: new Traversal(props)
156
+ }, children);
157
+ }
158
+ function useTraversal() {
159
+ return React.useContext(TraversalContext);
160
+ }
161
+ function ClientProvider({
162
+ children,
163
+ client
164
+ }) {
165
+ return /*#__PURE__*/React.createElement(ClientContext.Provider, {
166
+ value: client
167
+ }, children);
168
+ }
169
+ function useGuillotinaClient() {
170
+ return React.useContext(ClientContext);
171
+ }
172
+
107
173
  const classnames = classNames => {
108
174
  return classNames.filter(Boolean).join(' ').trim();
109
175
  };
@@ -153,1604 +219,1567 @@ function sleep(ms) {
153
219
  });
154
220
  }
155
221
 
156
- const Icon = ({
157
- icon,
158
- className,
159
- align
160
- }) => {
161
- const addClass = className ? className.split(' ') : [className];
162
- align = align || 'is-right';
163
- return /*#__PURE__*/React.createElement("span", {
164
- className: classnames(['icon', align, ...addClass])
165
- }, /*#__PURE__*/React.createElement("i", {
166
- className: icon
167
- }));
168
- };
222
+ const noop$1 = () => {};
169
223
 
170
- const Loading = (_ref) => {
171
- let rest = _extends({}, _ref);
224
+ const Button = (_ref) => {
225
+ let {
226
+ children,
227
+ className = 'is-primary',
228
+ onClick,
229
+ type = 'submit',
230
+ loading = false,
231
+ disabled = false,
232
+ dataTest
233
+ } = _ref,
234
+ rest = _objectWithoutPropertiesLoose(_ref, ["children", "className", "onClick", "type", "loading", "disabled", "dataTest"]);
172
235
 
173
- return /*#__PURE__*/React.createElement("div", _extends({
174
- className: "progress-line"
175
- }, rest));
236
+ let css = [].concat('button', ...className.split(' '));
237
+ if (loading) css = css.concat('is-loading');
238
+ if (disabled) onClick = noop$1;
239
+ return /*#__PURE__*/React.createElement("p", {
240
+ className: "control"
241
+ }, /*#__PURE__*/React.createElement("button", _extends({
242
+ type: type,
243
+ className: classnames(css),
244
+ onClick: onClick,
245
+ disabled: disabled
246
+ }, rest, {
247
+ "data-test": dataTest
248
+ }), children));
176
249
  };
177
250
 
178
- const Tag = ({
179
- name,
180
- onRemove,
181
- size: _size = 'is-medium',
182
- color: _color = 'is-warning'
183
- }) => /*#__PURE__*/React.createElement("span", {
184
- className: classnames(['tag', _color, _size])
185
- }, name, onRemove !== undefined && /*#__PURE__*/React.createElement("button", {
186
- className: "delete is-small",
187
- onClick: () => onRemove()
188
- }));
189
-
190
- function Delete({
191
- onClick,
192
- className,
193
- children
194
- }) {
195
- return /*#__PURE__*/React.createElement("button", {
196
- type: "button",
197
- onClick: onClick,
198
- className: `delete ${className}`,
199
- "data-test": "btnDeleteTest"
200
- }, children);
251
+ function Modal(props) {
252
+ const {
253
+ isActive,
254
+ setActive,
255
+ children
256
+ } = props;
257
+ const {
258
+ Portal
259
+ } = usePortal();
260
+ const css = 'modal ' + (isActive ? 'is-active ' : '') + props.className;
261
+ return /*#__PURE__*/React.createElement(Portal, null, /*#__PURE__*/React.createElement("div", {
262
+ className: css
263
+ }, /*#__PURE__*/React.createElement("div", {
264
+ className: "modal-background",
265
+ onClick: () => setActive(false)
266
+ }), /*#__PURE__*/React.createElement("div", {
267
+ className: "modal-content"
268
+ }, /*#__PURE__*/React.createElement("div", {
269
+ className: "box"
270
+ }, children)), /*#__PURE__*/React.createElement("button", {
271
+ className: "modal-close is-large",
272
+ "aria-label": "close",
273
+ onClick: () => setActive(false)
274
+ })));
201
275
  }
202
-
203
- function Table({
204
- headers,
205
- children,
206
- className
276
+ function Confirm({
277
+ message,
278
+ onCancel,
279
+ onConfirm,
280
+ loading
207
281
  }) {
208
- className = className ? className.split(' ') : ' is-full is-fullwidth is-narrow'.split(' ');
209
- return /*#__PURE__*/React.createElement("table", {
210
- className: classnames(['table', ...className])
211
- }, /*#__PURE__*/React.createElement("thead", null, /*#__PURE__*/React.createElement("tr", null, headers && headers.map((item, idx) => /*#__PURE__*/React.createElement("th", {
212
- key: item + idx
213
- }, item)))), /*#__PURE__*/React.createElement("tbody", null, children));
214
- }
282
+ const setActive = () => onCancel();
215
283
 
216
- function Notification({
217
- isColor = '',
218
- children
284
+ return /*#__PURE__*/React.createElement(Modal, {
285
+ isActive: true,
286
+ setActive: setActive,
287
+ className: "confirm"
288
+ }, /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("h1", {
289
+ className: "title is-size-5"
290
+ }, message || 'Are you Sure?'), /*#__PURE__*/React.createElement("div", {
291
+ className: "level",
292
+ style: {
293
+ marginTop: 50
294
+ }
295
+ }, /*#__PURE__*/React.createElement("div", {
296
+ className: "level-left"
297
+ }), /*#__PURE__*/React.createElement("div", {
298
+ className: "level-right"
299
+ }, /*#__PURE__*/React.createElement("button", {
300
+ className: "button is-danger",
301
+ onClick: () => onCancel(),
302
+ "data-test": "btnCancelModalTest"
303
+ }, "Cancel"), "\xA0\xA0", /*#__PURE__*/React.createElement(Button, {
304
+ loading: loading,
305
+ className: "is-success",
306
+ onClick: () => onConfirm(),
307
+ dataTest: "btnConfirmModalTest"
308
+ }, "Confirm")))));
309
+ } // @todo Improve it... Replacing the inputText to a tree
310
+
311
+ function PathTree({
312
+ title,
313
+ defaultPath,
314
+ children,
315
+ onConfirm,
316
+ onCancel
219
317
  }) {
220
- return /*#__PURE__*/React.createElement("div", {
221
- className: 'notification is-' + isColor,
222
- "data-test": "notificationTest"
223
- }, children);
318
+ return /*#__PURE__*/React.createElement(Modal, {
319
+ isActive: true,
320
+ setActive: onCancel
321
+ }, /*#__PURE__*/React.createElement("h1", null, title), /*#__PURE__*/React.createElement("form", {
322
+ onSubmit: e => {
323
+ e.preventDefault();
324
+ onConfirm(e.target[0].value, e.target);
325
+ }
326
+ }, /*#__PURE__*/React.createElement("small", {
327
+ style: {
328
+ display: 'block',
329
+ marginTop: 20
330
+ }
331
+ }, `Example: /folder (without /db/container on front)`), /*#__PURE__*/React.createElement("input", {
332
+ className: "input mb-3",
333
+ defaultValue: defaultPath,
334
+ type: "text",
335
+ "data-test": "inputPathTreeTest"
336
+ }), children, /*#__PURE__*/React.createElement("div", {
337
+ className: "level-right mt-3"
338
+ }, /*#__PURE__*/React.createElement("button", {
339
+ type: "button",
340
+ className: "button is-danger",
341
+ onClick: onCancel,
342
+ "data-test": "btnCancelModalTest"
343
+ }, "Cancel"), "\xA0\xA0", /*#__PURE__*/React.createElement("button", {
344
+ type: "submit",
345
+ className: "button is-success",
346
+ "data-test": "btnConfirmModalTest"
347
+ }, "Confirm"))));
224
348
  }
225
349
 
226
- // https://github.com/molefrog/wouter
227
-
228
- const setURLParams = p => window.history['pushState'](0, 0, (window.history.pathname || '') + '?' + p.toString().replace(/%2F/g, '/'));
350
+ function useSetState(initialState) {
351
+ const [state, set] = useState(initialState);
352
+ const setState = useCallback(patch => {
353
+ set(prevState => Object.assign({}, prevState, patch instanceof Function ? patch(prevState) : patch));
354
+ }, [set]);
355
+ return [state, setState];
356
+ }
229
357
 
230
- const clean = to => {
231
- const current = new URLSearchParams();
232
- Object.keys(to).forEach(_key => current.set(_key, to[_key]));
233
- setURLParams(current);
358
+ const initial = {
359
+ loading: undefined,
360
+ isError: false,
361
+ errorMessage: undefined,
362
+ result: undefined,
363
+ response: undefined
234
364
  };
235
365
 
236
- const useLocation = () => {
237
- const [path, update] = useState(currentSearchParams());
238
- const prevPath = useRef(path);
239
- useEffect(() => {
240
- patchHistoryEvents(); // this function checks if the location has been changed since the
241
- // last render and updates the state only when needed.
242
- // unfortunately, we can't rely on `path` value here, since it can be stale,
243
- // that's why we store the last pathname in a ref.
244
-
245
- const checkForUpdates = () => {
246
- const pathname = currentSearchParams();
247
- prevPath.current !== pathname && update(prevPath.current = pathname);
248
- };
249
-
250
- const events = ['popstate', 'pushState', 'replaceState'];
251
- events.map(e => window.addEventListener(e, checkForUpdates)); // it's possible that an update has occurred between render and the effect handler,
252
- // so we run additional check on mount to catch these updates. Based on:
253
- // https://gist.github.com/bvaughn/e25397f70e8c65b0ae0d7c90b731b189
254
-
255
- checkForUpdates();
256
- return () => events.map(e => window.removeEventListener(e, checkForUpdates));
257
- }, []); // the 2nd argument of the `useLocation` return value is a function
258
- // that allows to perform a navigation.
259
- //
260
- // the function reference should stay the same between re-renders, so that
261
- // it can be passed down as an element prop without any performance concerns.
262
-
263
- const navigate = useCallback((to, replace) => {
264
- if (replace) {
265
- clean(to);
266
- return;
267
- }
268
-
269
- let current = new URLSearchParams(path.toString());
270
- Object.keys(to).forEach(_key => current.set(_key, to[_key]));
271
- setURLParams(current);
272
- }, [path]);
273
- const remove = useCallback(param => {
274
- let current = new URLSearchParams(path.toString());
275
- current.delete(param);
276
- setURLParams(current);
277
- }, [path]);
278
- return [path, navigate, remove];
279
- }; // While History API does have `popstate` event, the only
280
- // proper way to listen to changes via `push/replaceState`
281
- // is to monkey-patch these methods.
282
- //
283
- // See https://stackoverflow.com/a/4585031
366
+ const getErrorMessage = (dataError, defaultValue) => {
367
+ if (dataError && dataError.details) {
368
+ return dataError.details;
369
+ } else if (dataError && dataError.reason) {
370
+ return dataError.reason;
371
+ }
284
372
 
285
- let patched = 0;
373
+ return defaultValue;
374
+ };
286
375
 
287
- const patchHistoryEvents = () => {
288
- if (patched) return;
289
- ['pushState', 'replaceState'].map(type => {
290
- const original = window.history[type];
376
+ const processResponse = async (res, ready_body = true) => {
377
+ if (res.status < 400) return {
378
+ isError: false,
379
+ loading: false,
380
+ result: ready_body ? await res.json() : res.status,
381
+ response: res
382
+ };else return {
383
+ isError: true,
384
+ loading: false,
385
+ errorMessage: getErrorMessage(await res.json(), res.status),
386
+ response: res
387
+ };
388
+ };
291
389
 
292
- window.history[type] = function () {
293
- const result = original.apply(this, arguments);
294
- const event = new Event(type);
295
- event.arguments = arguments;
296
- dispatchEvent(event);
297
- return result;
298
- };
390
+ const patch = (setState, Ctx) => async (data, endpoint, body = false) => {
391
+ setState({
392
+ loading: true
299
393
  });
300
- return patched = 1;
301
- };
394
+ let newState = {};
302
395
 
303
- const currentSearchParams = (path = window.location.search) => new URLSearchParams(path);
396
+ try {
397
+ const path = endpoint ? `${Ctx.path}${endpoint}` : Ctx.path;
398
+ const res = await Ctx.client.patch(path, data);
399
+ newState = await processResponse(res, body);
400
+ } catch (e) {
401
+ console.error('Error', e);
402
+ newState = {
403
+ isError: true,
404
+ errorMessage: 'unhandled exception'
405
+ };
406
+ }
304
407
 
305
- function Link(_ref) {
306
- let {
307
- aRef,
308
- model,
309
- children
310
- } = _ref,
311
- props = _objectWithoutPropertiesLoose(_ref, ["aRef", "model", "children"]);
408
+ setState(newState);
409
+ return newState;
410
+ };
312
411
 
313
- const [path, navigate] = useLocation();
314
- const aStyle = {
315
- textDecoration: 'none',
316
- color: 'currentColor'
317
- };
412
+ const del = (setState, Ctx) => async (data, endpoint, body = false) => {
413
+ setState({
414
+ loading: true
415
+ });
416
+ let newState = {};
318
417
 
319
- function onClick(e) {
320
- e.stopPropagation();
321
- if (actAsLink(e)) return;
322
- e.preventDefault();
323
- navigate({
324
- path: model.path
325
- }, true);
326
- if (props.onClick) props.onClick(e);
418
+ try {
419
+ const path = endpoint ? `${Ctx.path}${endpoint}` : Ctx.path;
420
+ const res = await Ctx.client.delete(path, data);
421
+ newState = await processResponse(res, body);
422
+ } catch (e) {
423
+ console.error('Error', e);
424
+ newState = {
425
+ isError: true,
426
+ errorMessage: 'unhandled exception'
427
+ };
327
428
  }
328
429
 
329
- return /*#__PURE__*/React.createElement("a", _extends({}, props, {
330
- ref: aRef,
331
- href: `?${path}${model.id}/`,
332
- style: aStyle,
333
- onClick: onClick
334
- }), children);
335
- }
430
+ setState(newState);
431
+ return newState;
432
+ };
336
433
 
337
- function actAsLink(e) {
338
- return e.ctrlKey || e.metaKey || e.altKey || e.shiftKey || e.button !== 0;
339
- }
434
+ const post = (setState, Ctx) => async (data, endpoint, body = true) => {
435
+ setState({
436
+ loading: true
437
+ });
438
+ let newState = {};
340
439
 
341
- function TdLink(_ref) {
342
- let {
343
- model,
344
- children
345
- } = _ref,
346
- props = _objectWithoutPropertiesLoose(_ref, ["model", "children"]);
440
+ try {
441
+ const path = endpoint ? `${Ctx.path}${endpoint}` : Ctx.path;
442
+ const res = await Ctx.client.post(path, data);
443
+ newState = await processResponse(res, body);
444
+ } catch (e) {
445
+ console.error('Error', e);
446
+ newState = {
447
+ isError: true,
448
+ errorMessage: 'unhandled exception'
449
+ };
450
+ }
347
451
 
348
- const link = useRef();
452
+ setState(newState);
453
+ return newState;
454
+ };
349
455
 
350
- function onClick() {
351
- link.current.click();
352
- }
456
+ const get = (setState, Ctx) => async endpoint => {
457
+ setState({
458
+ loading: true
459
+ });
460
+ const path = endpoint ? `${Ctx.path}${endpoint}` : Ctx.path;
461
+ const req = await Ctx.client.get(path);
462
+ const data = await req.json();
463
+ setState({
464
+ loading: false,
465
+ result: data,
466
+ response: req
467
+ });
468
+ return data;
469
+ };
353
470
 
354
- return /*#__PURE__*/React.createElement("td", _extends({}, props, {
355
- onClick: onClick
356
- }), /*#__PURE__*/React.createElement(Link, {
357
- model: model,
358
- aRef: link
359
- }, children));
471
+ function useCrudContext() {
472
+ const Ctx = useTraversal();
473
+ const [state, setState] = useSetState(initial);
474
+ return _extends({}, state, {
475
+ Ctx,
476
+ patch: patch(setState, Ctx),
477
+ del: del(setState, Ctx),
478
+ post: post(setState, Ctx),
479
+ get: get(setState, Ctx)
480
+ });
360
481
  }
361
482
 
362
- const SEP = '=';
363
- const DEFAULT_FIELD = 'title__in';
364
- const CLEANER = '||';
365
- function parser(qs, defaultField = DEFAULT_FIELD) {
366
- let lastKey = undefined;
367
- qs.trim();
368
-
369
- if (qs.includes('"')) {
370
- qs = qs.replace(/"(\w+) (\w+)"/, `$1${CLEANER}$2`);
371
- }
483
+ function AddItem(props) {
484
+ const Ctx = useTraversal();
485
+ const {
486
+ post,
487
+ loading
488
+ } = useCrudContext();
489
+ const {
490
+ type
491
+ } = props;
492
+ const {
493
+ getForm
494
+ } = Ctx.registry;
495
+ const Form = getForm(type);
372
496
 
373
- qs = qs.split(' ');
374
- return qs.map(part => {
375
- if (part.includes(CLEANER)) {
376
- part = part.replace('||', ' ');
377
- }
497
+ const setActive = () => {
498
+ Ctx.cancelAction();
499
+ };
378
500
 
379
- if (!part.includes(SEP)) {
380
- return !lastKey ? [defaultField, part] : [lastKey, part];
381
- }
501
+ async function doSubmit(data) {
502
+ const form = Object.assign({}, {
503
+ '@type': type
504
+ }, data.formData ? data.formData : data);
505
+ const {
506
+ isError,
507
+ errorMessage
508
+ } = await post(form);
382
509
 
383
- const [key, val] = part.split(SEP);
384
- lastKey = key;
385
- return [key, val];
386
- });
387
- }
388
- function buildQs(parsedQuery) {
389
- return parsedQuery.map(([key, val]) => `${encodeURIComponent(key)}=${encodeURIComponent(val)}`).join('&');
390
- }
510
+ if (!isError) {
511
+ Ctx.flash('Content created!', 'success');
512
+ } else {
513
+ Ctx.flash(`An error has ocurred: ${errorMessage}`, 'danger');
514
+ }
391
515
 
392
- let cacheTypes = {};
393
- let cacheSchemas = {};
394
- class GuillotinaClient {
395
- constructor(rest, isContainer) {
396
- this.rest = rest;
397
- this.isContainer = isContainer;
516
+ Ctx.cancelAction();
517
+ Ctx.refresh();
398
518
  }
399
519
 
400
- async getContext(path) {
401
- switch (path) {
402
- case '/':
403
- return await this.rest.get('');
404
-
405
- default:
406
- if (path.startsWith('/')) {
407
- path = path.substring(1);
408
- }
520
+ return /*#__PURE__*/React.createElement(Modal, {
521
+ isActive: true,
522
+ setActive: setActive
523
+ }, /*#__PURE__*/React.createElement(Form, {
524
+ loading: loading,
525
+ onSubmit: doSubmit,
526
+ onError: err => console.log(err),
527
+ actionName: 'Add ' + type,
528
+ title: 'Add ' + type,
529
+ type: type,
530
+ dataTest: `formAdd${type}Test`
531
+ }));
532
+ }
409
533
 
410
- return await this.rest.get(path);
411
- }
534
+ const Permissions = ['guillotina.AddContent', 'guillotina.ModifyContent', 'guillotina.ViewContent', 'guillotina.DeleteContent', 'guillotina.AccessContent', 'guillotina.SeePermissions', 'guillotina.ChangePermissions', 'guillotina.MoveContent', 'guillotina.DuplicateContent', 'guillotina.ReadConfiguration', 'guillotina.RegisterConfigurations', 'guillotina.WriteConfiguration', 'guillotina.ManageAddons', 'guillotina.swagger.View'];
535
+ const Config = {
536
+ DisabledTypes: ['UserManager', 'GroupManager'],
537
+ PageSize: 10,
538
+ DelayActions: 200,
539
+ Permissions: Permissions,
540
+ SearchEngine: 'PostreSQL',
541
+ // Elasticsearch
542
+ fieldHaveDeleteButton: schema => {
543
+ return (schema == null ? void 0 : schema.widget) === 'file' || (schema == null ? void 0 : schema.widget) === 'select' || (schema == null ? void 0 : schema.type) === 'array';
412
544
  }
545
+ };
546
+ let calculated = Object.assign({}, Config);
413
547
 
414
- async get(path) {
415
- if (path.startsWith('/')) {
416
- path = path.slice(1);
548
+ const addConfig = (additional, original) => {
549
+ const rest = Object.assign({}, original);
550
+ Object.keys(additional).forEach(item => {
551
+ if (typeof Config[item] === 'object' && Array.isArray(Config[item])) {
552
+ rest[item] = [].concat(Config[item], additional[item]);
553
+ } else if (typeof Config[item] === 'object') {
554
+ rest[item] = Object.assign({}, Config[item], additional[item]);
555
+ } else {
556
+ rest[item] = additional[item];
417
557
  }
558
+ });
559
+ return rest;
560
+ };
418
561
 
419
- return await this.rest.get(path);
420
- }
562
+ function useConfig(cfg) {
563
+ const ref = React.useRef();
421
564
 
422
- getQueryParamsPostresql({
423
- start = 0,
424
- pageSize = 10,
425
- withDepth = true
426
- }) {
427
- let result = [];
428
- result = [...parser(start.toString(), 'b_start'), ...parser(pageSize.toString(), 'b_size')];
565
+ if (cfg && !ref.current) {
566
+ console.log('cfg', cfg);
567
+ calculated = addConfig(cfg, calculated);
568
+ ref.current = calculated;
569
+ } // if (cfg && !ref.current) {
570
+ // ref.current = addConfig(cfg, ref.current);
571
+ // calculated = ref.current
572
+ // console.log("Current config", cfg)
573
+ // }
429
574
 
430
- if (withDepth) {
431
- var _parser;
432
575
 
433
- result = [...result, ...((_parser = parser('1', 'depth')) != null ? _parser : [])];
434
- }
576
+ return calculated;
577
+ }
435
578
 
436
- return result;
579
+ const getId = item => {
580
+ if (item['@id']) {
581
+ return item['@id'];
437
582
  }
438
583
 
439
- getQueryParamsElasticsearch({
440
- start = 0,
441
- pageSize = 10,
442
- path,
443
- withDepth = true
444
- }) {
445
- let result = [];
446
- let containerPath = getContainerFromPath(path);
584
+ return item['@absolute_url'];
585
+ };
447
586
 
448
- if (containerPath.startsWith('/')) {
449
- containerPath = containerPath.slice(1);
450
- }
587
+ function RemoveItems(props) {
588
+ const Ctx = useTraversal();
589
+ const cfg = useConfig();
590
+ const [loading, setLoading] = React.useState(false);
591
+ const {
592
+ items = []
593
+ } = props;
594
+ const last = items[items.length - 1]['@name'];
595
+ const itemsNames = items.map(item => item['@name']).join(', ').replace(`, ${last}`, ` and ${last}`);
451
596
 
452
- let objectPath = path.replace(containerPath, '');
597
+ async function removeItems() {
598
+ let errors = [];
599
+ setLoading(true);
600
+ const actions = items.map(async item => {
601
+ console.log(item);
602
+ const res = await Ctx.client.delete(getId(item));
453
603
 
454
- if (objectPath.endsWith('/')) {
455
- objectPath = objectPath.slice(0, -1);
604
+ if (!res.ok) {
605
+ const err = await res.json();
606
+ errors.push(err);
607
+ }
608
+ }); // this sleep is here, to let elasticsearch, wait for
609
+ // index our operations... (will work 99% of use cases)
610
+
611
+ actions.push(sleep(cfg.DelayActions));
612
+ await Promise.all(actions);
613
+
614
+ if (errors.length === 0) {
615
+ Ctx.flash(`Items removed!`, 'success');
616
+ Ctx.refresh();
617
+ } else {
618
+ const errorstr = errors.map(err => JSON.stringify(err)).join('\n');
619
+ Ctx.flash(`Something went wrong!! ${errorstr}`, 'danger');
456
620
  }
457
621
 
458
- result = [...parser(start.toString(), '_from'), ...parser(pageSize.toString(), 'size')];
622
+ setLoading(false);
623
+ Ctx.cancelAction();
624
+ }
459
625
 
460
- if (withDepth) {
461
- var _parser2;
626
+ return /*#__PURE__*/React.createElement(Confirm, {
627
+ loading: loading,
628
+ onCancel: () => Ctx.cancelAction(),
629
+ onConfirm: removeItems,
630
+ message: `Are you sure to remove: ${itemsNames}?`
631
+ });
632
+ }
462
633
 
463
- result = [...result, ...((_parser2 = parser('1', 'depth')) != null ? _parser2 : [])];
464
- }
634
+ const Checkbox = (_ref) => {
635
+ let {
636
+ id,
637
+ className,
638
+ classNameInput,
639
+ loading,
640
+ disabled,
641
+ indeterminate = false,
642
+ value = false,
643
+ children,
644
+ placeholder,
645
+ onChange,
646
+ dataTest
647
+ } = _ref,
648
+ rest = _objectWithoutPropertiesLoose(_ref, ["id", "className", "classNameInput", "loading", "disabled", "indeterminate", "value", "color", "backgroundColor", "borderColor", "children", "placeholder", "onChange", "dataTest"]);
465
649
 
466
- if (objectPath !== '') {
467
- result = [...result, ...parser(objectPath, 'path__wildcard')];
650
+ const inputRef = useRef(null);
651
+ const [state, setState] = React.useState(value);
652
+ useEffect(() => {
653
+ if (inputRef.current) {
654
+ inputRef.current.indeterminate = indeterminate;
468
655
  }
656
+ }, [indeterminate]);
469
657
 
470
- return result;
471
- }
658
+ const updateState = ev => {
659
+ setState(ev.target.checked);
660
+ onChange(ev.target.checked);
661
+ };
472
662
 
473
- getItemsColumn() {
474
- const smallcss = {
475
- width: 25
476
- };
477
- const mediumcss = {
478
- width: 120
479
- };
480
- return [{
481
- label: '',
482
- child: m => /*#__PURE__*/React.createElement("td", {
483
- style: smallcss
484
- }, /*#__PURE__*/React.createElement(Icon, {
485
- icon: m.icon
486
- }))
487
- }, {
488
- label: 'type',
489
- child: m => /*#__PURE__*/React.createElement(TdLink, {
490
- style: smallcss,
491
- model: m
492
- }, /*#__PURE__*/React.createElement("span", {
493
- className: "tag"
494
- }, m.type))
495
- }, {
496
- label: 'id/name',
497
- child: (m, navigate, search) => /*#__PURE__*/React.createElement(TdLink, {
498
- model: m
499
- }, m.name, search && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("br", null), /*#__PURE__*/React.createElement("span", {
500
- className: "is-size-7 tag is-light"
501
- }, m.path)))
502
- }, {
503
- label: 'created',
504
- child: m => /*#__PURE__*/React.createElement("td", {
505
- style: mediumcss,
506
- className: "is-size-7 is-vcentered"
507
- }, m.created)
508
- }, {
509
- label: 'modified',
510
- child: m => /*#__PURE__*/React.createElement("td", {
511
- style: mediumcss,
512
- className: "is-size-7 is-vcentered"
513
- }, m.updated)
514
- }];
515
- } // BBB API changes. Compat G5 and G6
663
+ return /*#__PURE__*/React.createElement("div", {
664
+ className: "field"
665
+ }, /*#__PURE__*/React.createElement("label", {
666
+ htmlFor: id,
667
+ className: classnames(['checkbox', className])
668
+ }, /*#__PURE__*/React.createElement("input", _extends({
669
+ ref: inputRef,
670
+ disabled: disabled || loading,
671
+ id: id,
672
+ type: "checkbox",
673
+ className: classnames(['checkbox', classNameInput]),
674
+ checked: state,
675
+ onChange: updateState,
676
+ "data-test": dataTest
677
+ }, rest)), children || placeholder));
678
+ };
516
679
 
680
+ const ErrorZone = ({
681
+ children,
682
+ id,
683
+ className
684
+ }) => {
685
+ return /*#__PURE__*/React.createElement("p", {
686
+ className: classnames(['help is-danger', className]),
687
+ id: id
688
+ }, children);
689
+ };
517
690
 
518
- applyCompat(data) {
519
- data.member = data.items;
520
- data.items_count = data.items_total;
521
- return data;
522
- }
691
+ ErrorZone.propTypes = {
692
+ children: PropTypes.node,
693
+ id: PropTypes.string,
694
+ className: PropTypes.string
695
+ };
523
696
 
524
- async search(path, params, container = false, prepare = true) {
525
- if (path.startsWith('/')) {
526
- path = path.slice(1);
697
+ const applyValidators = (value, validators) => {
698
+ let validation = Array.isArray(validators) ? validators : [validators];
699
+ let result = true;
700
+ validation.forEach(func => {
701
+ if (func(value) === false) {
702
+ result = false;
527
703
  }
704
+ });
705
+ return result;
706
+ };
528
707
 
529
- if (container) {
530
- path = getContainerFromPath(path);
531
- }
708
+ const useInput = (onChange, value, validator) => {
709
+ let [state, setState] = React.useState({
710
+ hasError: false,
711
+ value: value
712
+ });
532
713
 
533
- let query = prepare ? toQueryString(params) : params;
534
- const url = `${path}@search?${query}`;
535
- let res = await this.rest.get(url);
536
- let data = await res.json();
537
- return this.applyCompat(data);
538
- }
714
+ const onUpdate = ev => {
715
+ const value = ev && ev.target ? ev.target.value : ev ? ev : '';
716
+ setState({
717
+ value,
718
+ hasError: false
719
+ });
720
+ if (onChange) onChange(value);
721
+ };
539
722
 
540
- async canido(path, permissions) {
541
- if (path.startsWith('/')) {
542
- path = path.slice(1);
543
- }
723
+ const onBlur = () => {
724
+ const hasError = applyValidators(state.value, validator) === false;
725
+ if (hasError) setState({
726
+ value: state.value,
727
+ hasError
728
+ });
729
+ };
544
730
 
545
- if (Array.isArray(permissions)) {
546
- permissions.join(',');
731
+ const onFocus = () => {
732
+ if (state.hasError) {
733
+ setState({
734
+ value: state.value,
735
+ hasError: false
736
+ });
547
737
  }
738
+ };
548
739
 
549
- const url = `${path}@canido?permissions=${permissions}`;
550
- return await this.rest.get(url);
551
- }
740
+ React.useEffect(() => {
741
+ setState({
742
+ value,
743
+ hasError: false
744
+ });
745
+ }, [value]);
746
+ return {
747
+ onChange: onUpdate,
748
+ onFocus,
749
+ onBlur,
750
+ state
751
+ };
752
+ };
552
753
 
553
- async createObject(path, data) {
554
- return await this.rest.post(path, data);
555
- }
754
+ // From github.com/protonmail/proton-shared
556
755
 
557
- cleanPath(path) {
558
- let url = path.split('/').slice(3);
559
- return `${url.join('/')}`;
560
- }
756
+ /* eslint-disable no-useless-escape */
757
+ const REGEX_EMAIL = /(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))/i;
758
+ const REGEX_URL = /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[\-;:&=\+\$,\w]+@)?[A-Za-z0-9\.\-]+|(?:www\.|[\-;:&=\+\$,\w]+@)[A-Za-z0-9\.\-]+)((?:\/[\+~%\/\.\w\-_]*)?\??(?:[\-\+=&;%@\.\w_]*)#?(?:[\.\!\/\\\w]*))?)/;
759
+ const REGEX_HEX_COLOR = /^#([a-f0-9]{3,4}|[a-f0-9]{4}(?:[a-f0-9]{2}){1,2})\b$/i;
760
+ const REGEX_NUMBER = /^\d+$/;
761
+ const isEmpty = (value = '') => !value.length;
762
+ const maxLength = (value = '', limit = 0) => value.length <= limit;
763
+ const minLength = (value = '', limit = 0) => value.length >= limit;
764
+ const isEmail = (value = '') => REGEX_EMAIL.test(value);
765
+ const isURL = (value = '') => REGEX_URL.test(value);
766
+ const isHexColor = (value = '') => REGEX_HEX_COLOR.test(value);
767
+ const isNumber = (value = '') => REGEX_NUMBER.test(value);
768
+ const notEmpty = value => !!value.length;
561
769
 
562
- async delete(path, data) {
563
- console.log('path', path, this.cleanPath(path));
564
- return await this.rest.delete(this.cleanPath(path), data);
565
- }
770
+ const noop$2 = () => true;
771
+ /** @type any */
566
772
 
567
- async create(path, data) {
568
- if (path.startsWith('/')) {
569
- path = path.substring(1);
570
- }
571
773
 
572
- return await this.rest.post(path, data);
573
- }
774
+ const Input = React.forwardRef((_ref, ref) => {
775
+ let {
776
+ icon,
777
+ iconPosition = 'has-icons-right',
778
+ error,
779
+ errorZoneClassName,
780
+ autoComplete = 'off',
781
+ className = '',
782
+ widget = 'input',
783
+ type = 'text',
784
+ loading = false,
785
+ required = false,
786
+ id,
787
+ placeholder,
788
+ value,
789
+ autofocus = false,
790
+ onChange,
791
+ validator = noop$2,
792
+ errorMessage,
793
+ dataTest = 'testInput'
794
+ } = _ref,
795
+ rest = _objectWithoutPropertiesLoose(_ref, ["icon", "iconPosition", "error", "errorZoneClassName", "autoComplete", "className", "widget", "type", "onPressEnter", "isSubmitted", "loading", "required", "id", "placeholder", "value", "autofocus", "onChange", "validator", "errorMessage", "dataTest"]);
574
796
 
575
- async post(path, data) {
576
- return await this.create(path, data);
797
+ if (required) {
798
+ validator = Array.isArray(validator) ? validator.push(notEmpty) : [validator, notEmpty];
577
799
  }
578
800
 
579
- async patch(path, data) {
580
- if (path.startsWith('/')) {
581
- path = path.substring(1);
582
- }
801
+ const _useInput = useInput(onChange, value != null ? value : '', validator),
802
+ {
803
+ state
804
+ } = _useInput,
805
+ handlers = _objectWithoutPropertiesLoose(_useInput, ["state"]);
583
806
 
584
- return await this.rest.patch(path, data);
585
- }
807
+ const [uid] = useState(generateUID('input'));
808
+ const [mounted, setMounted] = useState(false); // eslint-disable-next-line
586
809
 
587
- async upload(path, file) {
588
- if (path.startsWith('/')) {
589
- path = path.substring(1);
810
+ ref = ref || React.useRef();
811
+ useEffect(() => {
812
+ setMounted(true);
813
+ }, []);
814
+ useEffect(() => {
815
+ if (autofocus && !error) {
816
+ ref.current.focus();
590
817
  }
818
+ }, [mounted, autofocus, ref, error]);
819
+ const theError = state.hasError ? errorMessage || 'invalid field' : '';
820
+ const statusClasses = state.hasError ? 'is-danger' : '';
821
+
822
+ const cssControl = () => icon ? ['control', iconPosition] : ['control'];
823
+
824
+ return /*#__PURE__*/React.createElement("div", {
825
+ className: "field"
826
+ }, id && placeholder ? /*#__PURE__*/React.createElement("label", {
827
+ className: "label",
828
+ htmlFor: id
829
+ }, placeholder) : null, /*#__PURE__*/React.createElement("div", {
830
+ className: classnames(cssControl())
831
+ }, /*#__PURE__*/React.createElement("input", _extends({
832
+ className: classnames([widget, className, statusClasses]),
833
+ "aria-invalid": theError,
834
+ "aria-describedby": uid,
835
+ id: id,
836
+ ref: ref,
837
+ type: type,
838
+ value: state.value,
839
+ placeholder: placeholder,
840
+ autoComplete: autoComplete,
841
+ disabled: loading || rest.disabled,
842
+ required: required,
843
+ "data-test": dataTest
844
+ }, handlers, rest)), icon && icon), /*#__PURE__*/React.createElement(ErrorZone, {
845
+ className: errorZoneClassName,
846
+ id: uid
847
+ }, state.hasError ? theError : ''));
848
+ });
849
+ Input.propTypes = {
850
+ icon: PropTypes.node,
851
+ iconPosition: PropTypes.arrayOf(PropTypes.oneOf(['has-icons-left', 'has-icons-right', ''])),
852
+ error: PropTypes.string,
853
+ errorZoneClassName: PropTypes.string,
854
+ autoComplete: PropTypes.string,
855
+ autoFocus: PropTypes.bool,
856
+ className: PropTypes.string,
857
+ disabled: PropTypes.bool,
858
+ loading: PropTypes.bool,
859
+ isSubmitted: PropTypes.bool,
860
+ id: PropTypes.string,
861
+ name: PropTypes.string,
862
+ onChange: PropTypes.func,
863
+ onKeyDown: PropTypes.func,
864
+ onKeyUp: PropTypes.func,
865
+ onPressEnter: PropTypes.func,
866
+ placeholder: PropTypes.string,
867
+ readOnly: PropTypes.bool,
868
+ required: PropTypes.bool,
869
+ type: PropTypes.string,
870
+ value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool])
871
+ };
872
+
873
+ const Icon = ({
874
+ icon,
875
+ className,
876
+ align
877
+ }) => {
878
+ const addClass = className ? className.split(' ') : [className];
879
+ align = align || 'is-right';
880
+ return /*#__PURE__*/React.createElement("span", {
881
+ className: classnames(['icon', align, ...addClass])
882
+ }, /*#__PURE__*/React.createElement("i", {
883
+ className: icon
884
+ }));
885
+ };
886
+
887
+ const EmailInput = (_ref) => {
888
+ let {
889
+ value = '',
890
+ dataTest
891
+ } = _ref,
892
+ rest = _objectWithoutPropertiesLoose(_ref, ["value", "dataTest"]);
893
+
894
+ return /*#__PURE__*/React.createElement(Input, _extends({
895
+ type: "email",
896
+ validator: isEmail,
897
+ errorMessage: "Email address invalid",
898
+ value: value,
899
+ "data-test": dataTest,
900
+ icon: /*#__PURE__*/React.createElement(Icon, {
901
+ icon: "fas fa-envelope"
902
+ })
903
+ }, rest));
904
+ };
591
905
 
592
- return await this.rest.upload(path, file);
593
- }
906
+ const Form = (_ref) => {
907
+ let {
908
+ children,
909
+ className = '',
910
+ onSubmit = noop,
911
+ onReset = noop,
912
+ autoComplete = 'off',
913
+ title,
914
+ error,
915
+ dataTest
916
+ } = _ref,
917
+ rest = _objectWithoutPropertiesLoose(_ref, ["children", "className", "onSubmit", "onReset", "autoComplete", "title", "error", "dataTest"]);
594
918
 
595
- async download(path) {
596
- if (path.startsWith('/')) {
597
- path = path.substring(1);
598
- }
919
+ const handleSubmit = event => {
920
+ event.preventDefault();
921
+ onSubmit(event);
922
+ };
599
923
 
600
- return await this.rest.get(path);
601
- }
924
+ return /*#__PURE__*/React.createElement("div", {
925
+ "data-test": dataTest
926
+ }, title && /*#__PURE__*/React.createElement("div", {
927
+ className: "level"
928
+ }, /*#__PURE__*/React.createElement("h1", {
929
+ className: "title is-size-4"
930
+ }, title)), error && /*#__PURE__*/React.createElement("div", {
931
+ className: "notification is-danger"
932
+ }, error), /*#__PURE__*/React.createElement("form", _extends({
933
+ onSubmit: handleSubmit,
934
+ onReset: onReset,
935
+ autoComplete: autoComplete,
936
+ className: classnames(['form', className])
937
+ }, rest), children));
938
+ };
939
+ Form.propTypes = {
940
+ children: PropTypes.node.isRequired,
941
+ className: PropTypes.string,
942
+ onSubmit: PropTypes.func,
943
+ onReset: PropTypes.func,
944
+ autoComplete: PropTypes.string
945
+ };
602
946
 
603
- async getTypeSchema(path, name) {
604
- if (!cacheSchemas[name]) {
605
- let url = getContainerFromPath(path); // todo: handle db case (only addable containers)
947
+ const PasswordInput = (_ref) => {
948
+ let {
949
+ value = ''
950
+ } = _ref,
951
+ rest = _objectWithoutPropertiesLoose(_ref, ["value"]);
606
952
 
607
- const res = await this.rest.get(`${url}@types/${name}`);
608
- cacheSchemas[name] = await res.json();
609
- }
953
+ return /*#__PURE__*/React.createElement(Input, _extends({
954
+ value: value,
955
+ type: "password"
956
+ }, rest));
957
+ };
610
958
 
611
- return cacheSchemas[name];
612
- }
959
+ const formComponents = {
960
+ string: Input,
961
+ password: PasswordInput,
962
+ boolean: Checkbox,
963
+ email: EmailInput
964
+ };
965
+ function FormBuilder(_ref) {
966
+ let {
967
+ schema,
968
+ formData,
969
+ onSubmit,
970
+ actionName,
971
+ children,
972
+ exclude = [],
973
+ remotes = [],
974
+ submitButton = true
975
+ } = _ref,
976
+ rest = _objectWithoutPropertiesLoose(_ref, ["schema", "formData", "title", "onSubmit", "actionName", "children", "exclude", "remotes", "submitButton"]);
613
977
 
614
- async getAddons(path) {
615
- return await this.rest.get(`${path}@addons`);
616
- }
978
+ const ref = React.useRef();
979
+ const {
980
+ properties,
981
+ required
982
+ } = schema;
983
+ const values = Object.assign({}, formData || {}); // build initial state
617
984
 
618
- async installAddon(path, key) {
619
- return await this.rest.post(`${path}@addons`, {
620
- id: key
621
- });
622
- }
985
+ let initialState = {};
986
+ const fields = Object.keys(properties).filter(x => !exclude.includes(x));
987
+ fields.forEach(element => {
988
+ initialState[element] = values[element] || undefined;
989
+ }); // Register remotes
623
990
 
624
- async removeAddon(path, key) {
625
- return await this.rest.delete(`${path}@addons`, {
626
- id: key
991
+ if (!ref.current) {
992
+ ref.current = {};
993
+ Object.keys(remotes).forEach(item => ref.current[item] = remotes[item]);
994
+ } else {
995
+ // apply remote changes
996
+ Object.keys(remotes).forEach(key => {
997
+ if (JSON.stringify(ref.current[key]) !== JSON.stringify(remotes[key])) {
998
+ ref.current[key] = remotes[key];
999
+ }
627
1000
  });
628
1001
  }
629
1002
 
630
- async getGroups(path) {
631
- const endpoint = `${getContainerFromPath(path)}@groups`;
632
- return await this.rest.get(endpoint);
633
- }
1003
+ ref.current = ref.current || {};
634
1004
 
635
- async getUsers(path) {
636
- const endpoint = `${getContainerFromPath(path)}@users`;
637
- return await this.rest.get(endpoint);
638
- }
1005
+ const onUpdate = field => ev => {
1006
+ ref.current[field] = ev.target ? ev.target.value : ev.value || ev;
1007
+ };
639
1008
 
640
- async getPrincipals(path) {
641
- const groups = this.getGroups(path);
642
- const users = this.getUsers(path);
643
- const [gr, usr] = await Promise.all([groups, users]);
644
- return {
645
- groups: gr.ok ? await gr.json() : [],
646
- users: usr.ok ? await usr.json() : []
1009
+ const GetTag = ({
1010
+ field
1011
+ }) => {
1012
+ const Tag = formComponents[properties[field].widget || properties[field].type];
1013
+ const props = {
1014
+ label: properties[field].title,
1015
+ value: initialState[field],
1016
+ onChange: onUpdate(field),
1017
+ placeholder: properties[field].title || '',
1018
+ id: generateUID(),
1019
+ dataTest: `${field}TestInput`
647
1020
  };
648
- }
649
-
650
- async getRoles(path) {
651
- const endpoint = `${getContainerFromPath(path)}@available-roles`;
652
- return await this.rest.get(endpoint);
653
- }
654
-
655
- async getAllPermissions(path) {
656
- // paths used to query the API always has to start without a "/"
657
- if (path.startsWith('/')) {
658
- path = path.slice(1);
659
- }
660
-
661
- if (!path.endsWith('/')) {
662
- path = `${path}/`;
663
- }
664
-
665
- const req = await this.rest.get(path + '@all_permissions');
666
- const resp = await req.json();
667
- const permissions = Array.from(new Set(extractPermissions(resp)));
668
- return permissions;
669
- }
670
1021
 
671
- async getTypes(path) {
672
- if (path.startsWith('/')) {
673
- path = path.slice(1);
1022
+ if (required.includes(field)) {
1023
+ props.required = true;
1024
+ props.placeholder += ' *';
674
1025
  }
675
1026
 
676
- if (!cacheTypes[path]) {
677
- const types = await this.rest.get(path + '@addable-types');
1027
+ Tag.displayName = `${field}Field`;
1028
+ return /*#__PURE__*/React.createElement(Tag, props);
1029
+ };
678
1030
 
679
- if (types.status === 401 || types.status === 404) {
680
- cacheTypes[path] = [];
681
- } else {
682
- const res = await types.json();
683
- cacheTypes[path] = res;
684
- }
685
- }
1031
+ const children_ = React.Children.map(children, child => React.cloneElement(child, {
1032
+ onChange: onUpdate
1033
+ }));
686
1034
 
687
- return cacheTypes[path];
688
- }
1035
+ const changes = () => {
1036
+ onSubmit(ref.current, values);
1037
+ };
689
1038
 
1039
+ return /*#__PURE__*/React.createElement(Form, _extends({
1040
+ onSubmit: changes
1041
+ }, rest), fields.map(field => /*#__PURE__*/React.createElement(GetTag, {
1042
+ field: field,
1043
+ key: field
1044
+ })), children_, submitButton && /*#__PURE__*/React.createElement(Button, null, actionName));
690
1045
  }
691
- function getClient(url, auth, isContainer = false) {
692
- return new GuillotinaClient(new RestClient(url, auth), isContainer);
693
- }
694
- const getContainerFromPath = path => {
695
- if (path.startsWith('/')) {
696
- path = path.substring(1);
697
- }
698
-
699
- let parts = path.split('/');
700
- return `${parts[0]}/${parts[1]}/`;
701
- };
702
- const lightFileReader = async file => {
703
- return new Promise(resolve => {
704
- const reader = new FileReader();
705
- reader.readAsArrayBuffer(file);
706
1046
 
707
- reader.onloadend = e => {
708
- const fileData = e.target.result;
709
- resolve({
710
- filename: file.name.normalize('NFD').replace(/[\u0300-\u036f]/g, ''),
711
- data: fileData,
712
- 'content-type': file.type
713
- });
714
- };
715
- });
716
- };
1047
+ /** @type any */
717
1048
 
718
- const extractPermissions = data => {
719
- let result = [];
1049
+ const Select = React.forwardRef((_ref, ref) => {
1050
+ let {
1051
+ options,
1052
+ error,
1053
+ errorZoneClassName,
1054
+ size = 1,
1055
+ placeholder,
1056
+ id,
1057
+ className = '',
1058
+ classWrap = '',
1059
+ multiple = false,
1060
+ loading = false,
1061
+ onChange,
1062
+ appendDefault = false,
1063
+ style = {},
1064
+ dataTest
1065
+ } = _ref,
1066
+ rest = _objectWithoutPropertiesLoose(_ref, ["options", "error", "errorZoneClassName", "size", "placeholder", "id", "className", "classWrap", "multiple", "loading", "isSubmitted", "onChange", "appendDefault", "style", "dataTest"]);
720
1067
 
721
- if (typeof data !== 'object') ; else if (!Array.isArray(data) && data.permission) {
722
- result = result.concat([data.permission]);
723
- } else if (!Array.isArray(data)) {
724
- Object.keys(data).map(key => result = result.concat(extractPermissions(data[key])));
725
- } else if (Array.isArray(data)) {
726
- data.map(item => result = result.concat(extractPermissions(item)));
727
- }
1068
+ const [uid] = useState(generateUID('select'));
728
1069
 
729
- return result;
730
- };
1070
+ const onUpdate = ev => {
1071
+ if (ev.target.value === '') {
1072
+ onChange({
1073
+ target: {
1074
+ value: undefined
1075
+ }
1076
+ });
1077
+ } else {
1078
+ onChange(ev);
1079
+ }
1080
+ };
731
1081
 
732
- const AuthContext = createContext({});
733
- const ClientContext = createContext({});
734
- const TraversalContext = createContext({});
1082
+ if (appendDefault) {
1083
+ options = [{
1084
+ text: 'Choose..',
1085
+ value: ''
1086
+ }].concat(options);
1087
+ }
735
1088
 
736
- class Traversal {
737
- constructor(_ref) {
1089
+ const statusClasses = error ? 'is-danger' : '';
1090
+ const cssWrap = ['select', statusClasses, multiple ? 'is-multiple' : '', classWrap];
1091
+ return /*#__PURE__*/React.createElement("div", {
1092
+ className: "field"
1093
+ }, id && placeholder ? /*#__PURE__*/React.createElement("label", {
1094
+ className: "label",
1095
+ htmlFor: id
1096
+ }, placeholder) : null, /*#__PURE__*/React.createElement("div", {
1097
+ className: classnames(cssWrap)
1098
+ }, /*#__PURE__*/React.createElement("select", _extends({
1099
+ className: classnames(['', className]),
1100
+ size: size,
1101
+ multiple: multiple,
1102
+ disabled: loading || rest.disabled,
1103
+ onChange: onUpdate
1104
+ }, rest, {
1105
+ ref: ref,
1106
+ style: style,
1107
+ "data-test": dataTest
1108
+ }), options.map((_ref2, index) => {
738
1109
  let {
739
- flash
740
- } = _ref,
741
- props = _objectWithoutPropertiesLoose(_ref, ["flash"]);
1110
+ text
1111
+ } = _ref2,
1112
+ rest = _objectWithoutPropertiesLoose(_ref2, ["text"]);
742
1113
 
743
- Object.assign(this, props);
744
- if (typeof flash === 'function') this.flash = flash;
745
- }
1114
+ return /*#__PURE__*/React.createElement("option", _extends({
1115
+ key: index.toString()
1116
+ }, rest), text);
1117
+ }))), error && /*#__PURE__*/React.createElement(ErrorZone, {
1118
+ className: errorZoneClassName,
1119
+ id: uid
1120
+ }, error ? error : ''));
1121
+ });
1122
+ Select.propTypes = {
1123
+ error: PropTypes.string,
1124
+ disabled: PropTypes.bool,
1125
+ loading: PropTypes.bool,
1126
+ isSubmitted: PropTypes.bool,
1127
+ size: PropTypes.number,
1128
+ onChange: PropTypes.func,
1129
+ options: PropTypes.arrayOf(PropTypes.object),
1130
+ multiple: PropTypes.bool,
1131
+ className: PropTypes.string
1132
+ };
746
1133
 
747
- refresh({
748
- transparent = false
749
- } = {}) {
750
- this.dispatch({
751
- type: 'REFRESH',
752
- payload: {
753
- transparent
754
- }
755
- });
1134
+ class RestClient {
1135
+ constructor(url, container, auth) {
1136
+ this.auth = auth;
1137
+ this.url = url;
1138
+ this.container = container;
756
1139
  }
757
1140
 
758
- get path() {
759
- return this.state.path;
760
- }
1141
+ async request(path, data, headers) {
1142
+ if (path.indexOf(this.url) !== -1) {
1143
+ path = path.replace(this.url, '');
1144
+ }
761
1145
 
762
- get pathPrefix() {
763
- return this.state.path.slice(1);
764
- }
1146
+ if (this.container !== '/' && path.indexOf(this.container) === -1) {
1147
+ path = `${this.container}${path}`;
1148
+ }
765
1149
 
766
- get context() {
767
- return this.state.context;
768
- }
1150
+ if (!path.startsWith('/')) {
1151
+ path = `/${path}`;
1152
+ }
769
1153
 
770
- get containerPath() {
771
- return getContainerFromPath(this.path);
1154
+ data = data || {};
1155
+ data.headers = headers || this.getHeaders();
1156
+ return await fetch(`${this.url}${path}`, data);
772
1157
  }
773
1158
 
774
- apply(data) {
775
- // apply a optimistic update to context
776
- this.dispatch({
777
- type: 'APPLY',
778
- payload: data
779
- });
1159
+ getHeaders() {
1160
+ const authToken = this.auth.getToken();
1161
+ if (!authToken) return {};
1162
+ return {
1163
+ Accept: 'application/json',
1164
+ 'Content-Type': 'application/json',
1165
+ Authorization: 'Bearer ' + authToken
1166
+ };
780
1167
  }
781
1168
 
782
- flash(message, type) {
783
- this.dispatch({
784
- type: 'SET_FLASH',
785
- payload: {
786
- flash: {
787
- message,
788
- type
789
- }
790
- }
791
- });
792
- window.scrollTo({
793
- top: 0,
794
- left: 0,
795
- behavior: 'smooth'
1169
+ async post(path, data) {
1170
+ return await this.request(path, {
1171
+ method: 'post',
1172
+ body: JSON.stringify(data)
796
1173
  });
797
1174
  }
798
1175
 
799
- clearFlash() {
800
- this.dispatch({
801
- type: 'CLEAR_FLASH'
802
- });
1176
+ async get(path) {
1177
+ return await this.request(path, {});
803
1178
  }
804
1179
 
805
- doAction(action, params) {
806
- this.dispatch({
807
- type: 'SET_ACTION',
808
- payload: {
809
- action,
810
- params
811
- }
1180
+ async put(path, data) {
1181
+ return await this.request(path, {
1182
+ method: 'put',
1183
+ body: JSON.stringify(data)
812
1184
  });
813
1185
  }
814
1186
 
815
- cancelAction() {
816
- this.dispatch({
817
- type: 'CLEAR_ACTION'
1187
+ async patch(path, data) {
1188
+ return await this.request(path, {
1189
+ method: 'PATCH',
1190
+ body: JSON.stringify(data)
818
1191
  });
819
1192
  }
820
1193
 
821
- hasPerm(permission) {
822
- return this.state.permissions[permission] === true;
823
- }
824
-
825
- filterTabs(tabs, tabsPermissions) {
826
- const result = {};
827
- Object.keys(tabs).forEach(item => {
828
- const perm = tabsPermissions[item];
829
-
830
- if (perm && this.hasPerm(perm)) {
831
- result[item] = tabs[item];
832
- } else if (!perm) {
833
- result[item] = tabs[item];
834
- }
835
- });
836
- return result;
1194
+ async upload(path, data) {
1195
+ const headers = this.getHeaders();
1196
+ delete headers['Content-Type'];
1197
+ headers['Content-Type'] = data['content-type'];
1198
+ headers['X-UPLOAD-FILENAME'] = data.filename;
1199
+ headers['Content-Encoding'] = 'base64';
1200
+ return await this.request(path, {
1201
+ method: 'PATCH',
1202
+ body: data.data
1203
+ }, headers);
837
1204
  }
838
1205
 
839
- }
840
-
841
- function TraversalProvider(_ref2) {
842
- let {
843
- children
844
- } = _ref2,
845
- props = _objectWithoutPropertiesLoose(_ref2, ["children"]);
846
-
847
- return /*#__PURE__*/React.createElement(TraversalContext.Provider, {
848
- value: new Traversal(props)
849
- }, children);
850
- }
851
- function useTraversal() {
852
- return React.useContext(TraversalContext);
853
- }
854
- function ClientProvider({
855
- children,
856
- client
857
- }) {
858
- return /*#__PURE__*/React.createElement(ClientContext.Provider, {
859
- value: client
860
- }, children);
861
- }
862
- function useGuillotinaClient() {
863
- return React.useContext(ClientContext);
864
- }
865
-
866
- const noop$1 = () => {};
867
-
868
- const Button = (_ref) => {
869
- let {
870
- children,
871
- className = 'is-primary',
872
- onClick,
873
- type = 'submit',
874
- loading = false,
875
- disabled = false,
876
- dataTest
877
- } = _ref,
878
- rest = _objectWithoutPropertiesLoose(_ref, ["children", "className", "onClick", "type", "loading", "disabled", "dataTest"]);
879
-
880
- let css = [].concat('button', ...className.split(' '));
881
- if (loading) css = css.concat('is-loading');
882
- if (disabled) onClick = noop$1;
883
- return /*#__PURE__*/React.createElement("p", {
884
- className: "control"
885
- }, /*#__PURE__*/React.createElement("button", _extends({
886
- type: type,
887
- className: classnames(css),
888
- onClick: onClick,
889
- disabled: disabled
890
- }, rest, {
891
- "data-test": dataTest
892
- }), children));
893
- };
894
-
895
- function Modal(props) {
896
- const {
897
- isActive,
898
- setActive,
899
- children
900
- } = props;
901
- const {
902
- Portal
903
- } = usePortal();
904
- const css = 'modal ' + (isActive ? 'is-active ' : '') + props.className;
905
- return /*#__PURE__*/React.createElement(Portal, null, /*#__PURE__*/React.createElement("div", {
906
- className: css
907
- }, /*#__PURE__*/React.createElement("div", {
908
- className: "modal-background",
909
- onClick: () => setActive(false)
910
- }), /*#__PURE__*/React.createElement("div", {
911
- className: "modal-content"
912
- }, /*#__PURE__*/React.createElement("div", {
913
- className: "box"
914
- }, children)), /*#__PURE__*/React.createElement("button", {
915
- className: "modal-close is-large",
916
- "aria-label": "close",
917
- onClick: () => setActive(false)
918
- })));
919
- }
920
- function Confirm({
921
- message,
922
- onCancel,
923
- onConfirm,
924
- loading
925
- }) {
926
- const setActive = () => onCancel();
1206
+ async delete(path, data = undefined) {
1207
+ return await this.request(path, {
1208
+ method: 'delete',
1209
+ body: JSON.stringify(data)
1210
+ });
1211
+ }
927
1212
 
928
- return /*#__PURE__*/React.createElement(Modal, {
929
- isActive: true,
930
- setActive: setActive,
931
- className: "confirm"
932
- }, /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("h1", {
933
- className: "title is-size-5"
934
- }, message || 'Are you Sure?'), /*#__PURE__*/React.createElement("div", {
935
- className: "level",
936
- style: {
937
- marginTop: 50
938
- }
939
- }, /*#__PURE__*/React.createElement("div", {
940
- className: "level-left"
941
- }), /*#__PURE__*/React.createElement("div", {
942
- className: "level-right"
943
- }, /*#__PURE__*/React.createElement("button", {
944
- className: "button is-danger",
945
- onClick: () => onCancel()
946
- }, "Cancel"), "\xA0\xA0", /*#__PURE__*/React.createElement(Button, {
947
- loading: loading,
948
- className: "is-success",
949
- onClick: () => onConfirm(),
950
- dataTest: "btnConfirmModalTest"
951
- }, "Confirm")))));
952
- } // @todo Improve it... Replacing the inputText to a tree
1213
+ }
953
1214
 
954
- function PathTree({
955
- title,
956
- defaultPath,
957
- children,
958
- onConfirm,
959
- onCancel
1215
+ const Loading = (_ref) => {
1216
+ let rest = _extends({}, _ref);
1217
+
1218
+ return /*#__PURE__*/React.createElement("div", _extends({
1219
+ className: "progress-line"
1220
+ }, rest));
1221
+ };
1222
+
1223
+ const Tag = ({
1224
+ name,
1225
+ onRemove,
1226
+ size: _size = 'is-medium',
1227
+ color: _color = 'is-warning'
1228
+ }) => /*#__PURE__*/React.createElement("span", {
1229
+ className: classnames(['tag', _color, _size])
1230
+ }, name, onRemove !== undefined && /*#__PURE__*/React.createElement("button", {
1231
+ className: "delete is-small",
1232
+ onClick: () => onRemove()
1233
+ }));
1234
+
1235
+ function Delete({
1236
+ onClick,
1237
+ className,
1238
+ children
960
1239
  }) {
961
- return /*#__PURE__*/React.createElement(Modal, {
962
- isActive: true,
963
- setActive: onCancel
964
- }, /*#__PURE__*/React.createElement("h1", null, title), /*#__PURE__*/React.createElement("form", {
965
- onSubmit: e => {
966
- e.preventDefault();
967
- onConfirm(e.target[0].value, e.target);
968
- }
969
- }, /*#__PURE__*/React.createElement("input", {
970
- className: "input",
971
- placeholder: "/folder (without /db/container on front)",
972
- style: {
973
- margin: '20px 0'
974
- },
975
- defaultValue: defaultPath,
976
- type: "text"
977
- }), children, /*#__PURE__*/React.createElement("div", {
978
- className: "level-right"
979
- }, /*#__PURE__*/React.createElement("button", {
1240
+ return /*#__PURE__*/React.createElement("button", {
980
1241
  type: "button",
981
- className: "button is-danger",
982
- onClick: onCancel
983
- }, "Cancel"), "\xA0\xA0", /*#__PURE__*/React.createElement("button", {
984
- type: "submit",
985
- className: "button is-success"
986
- }, "Confirm"))));
1242
+ onClick: onClick,
1243
+ className: `delete ${className}`,
1244
+ "data-test": "btnDeleteTest"
1245
+ }, children);
987
1246
  }
988
1247
 
989
- function useSetState(initialState) {
990
- const [state, set] = useState(initialState);
991
- const setState = useCallback(patch => {
992
- set(prevState => Object.assign({}, prevState, patch instanceof Function ? patch(prevState) : patch));
993
- }, [set]);
994
- return [state, setState];
1248
+ function Table({
1249
+ headers,
1250
+ children,
1251
+ className
1252
+ }) {
1253
+ className = className ? className.split(' ') : ' is-full is-fullwidth is-narrow'.split(' ');
1254
+ return /*#__PURE__*/React.createElement("table", {
1255
+ className: classnames(['table', ...className])
1256
+ }, /*#__PURE__*/React.createElement("thead", null, /*#__PURE__*/React.createElement("tr", null, headers && headers.map((item, idx) => /*#__PURE__*/React.createElement("th", {
1257
+ key: item + idx
1258
+ }, item)))), /*#__PURE__*/React.createElement("tbody", null, children));
995
1259
  }
996
1260
 
997
- const initial = {
998
- loading: undefined,
999
- isError: false,
1000
- errorMessage: undefined,
1001
- result: undefined,
1002
- response: undefined
1003
- };
1261
+ function Notification({
1262
+ isColor = '',
1263
+ children
1264
+ }) {
1265
+ return /*#__PURE__*/React.createElement("div", {
1266
+ className: 'notification is-' + isColor,
1267
+ "data-test": "notificationTest"
1268
+ }, children);
1269
+ }
1004
1270
 
1005
- const getErrorMessage = (dataError, defaultValue) => {
1006
- if (dataError && dataError.details) {
1007
- return dataError.details;
1008
- } else if (dataError && dataError.reason) {
1009
- return dataError.reason;
1010
- }
1271
+ // https://github.com/molefrog/wouter
1011
1272
 
1012
- return defaultValue;
1013
- };
1273
+ const setURLParams = p => window.history['pushState'](0, 0, (window.history.pathname || '') + '?' + p.toString().replace(/%2F/g, '/'));
1014
1274
 
1015
- const processResponse = async (res, ready_body = true) => {
1016
- if (res.status < 400) return {
1017
- isError: false,
1018
- loading: false,
1019
- result: ready_body ? await res.json() : res.status,
1020
- response: res
1021
- };else return {
1022
- isError: true,
1023
- loading: false,
1024
- errorMessage: getErrorMessage(await res.json(), res.status),
1025
- response: res
1026
- };
1275
+ const clean = to => {
1276
+ const current = new URLSearchParams();
1277
+ Object.keys(to).forEach(_key => current.set(_key, to[_key]));
1278
+ setURLParams(current);
1027
1279
  };
1028
1280
 
1029
- const patch = (setState, Ctx) => async (data, endpoint, body = false) => {
1030
- setState({
1031
- loading: true
1032
- });
1033
- let newState = {};
1281
+ const useLocation = () => {
1282
+ const [path, update] = useState(currentSearchParams());
1283
+ const prevPath = useRef(path);
1284
+ useEffect(() => {
1285
+ patchHistoryEvents(); // this function checks if the location has been changed since the
1286
+ // last render and updates the state only when needed.
1287
+ // unfortunately, we can't rely on `path` value here, since it can be stale,
1288
+ // that's why we store the last pathname in a ref.
1034
1289
 
1035
- try {
1036
- const path = endpoint ? `${Ctx.path}${endpoint}` : Ctx.path;
1037
- const res = await Ctx.client.patch(path, data);
1038
- newState = await processResponse(res, body);
1039
- } catch (e) {
1040
- console.error('Error', e);
1041
- newState = {
1042
- isError: true,
1043
- errorMessage: 'unhandled exception'
1290
+ const checkForUpdates = () => {
1291
+ const pathname = currentSearchParams();
1292
+ prevPath.current !== pathname && update(prevPath.current = pathname);
1044
1293
  };
1045
- }
1046
-
1047
- setState(newState);
1048
- return newState;
1049
- };
1050
-
1051
- const del = (setState, Ctx) => async (data, endpoint, body = false) => {
1052
- setState({
1053
- loading: true
1054
- });
1055
- let newState = {};
1056
1294
 
1057
- try {
1058
- const path = endpoint ? `${Ctx.path}${endpoint}` : Ctx.path;
1059
- const res = await Ctx.client.delete(path, data);
1060
- newState = await processResponse(res, body);
1061
- } catch (e) {
1062
- console.error('Error', e);
1063
- newState = {
1064
- isError: true,
1065
- errorMessage: 'unhandled exception'
1066
- };
1067
- }
1295
+ const events = ['popstate', 'pushState', 'replaceState'];
1296
+ events.map(e => window.addEventListener(e, checkForUpdates)); // it's possible that an update has occurred between render and the effect handler,
1297
+ // so we run additional check on mount to catch these updates. Based on:
1298
+ // https://gist.github.com/bvaughn/e25397f70e8c65b0ae0d7c90b731b189
1068
1299
 
1069
- setState(newState);
1070
- return newState;
1071
- };
1300
+ checkForUpdates();
1301
+ return () => events.map(e => window.removeEventListener(e, checkForUpdates));
1302
+ }, []); // the 2nd argument of the `useLocation` return value is a function
1303
+ // that allows to perform a navigation.
1304
+ //
1305
+ // the function reference should stay the same between re-renders, so that
1306
+ // it can be passed down as an element prop without any performance concerns.
1072
1307
 
1073
- const post = (setState, Ctx) => async (data, endpoint, body = true) => {
1074
- setState({
1075
- loading: true
1076
- });
1077
- let newState = {};
1308
+ const navigate = useCallback((to, replace) => {
1309
+ if (replace) {
1310
+ clean(to);
1311
+ return;
1312
+ }
1078
1313
 
1079
- try {
1080
- const path = endpoint ? `${Ctx.path}${endpoint}` : Ctx.path;
1081
- const res = await Ctx.client.post(path, data);
1082
- newState = await processResponse(res, body);
1083
- } catch (e) {
1084
- console.error('Error', e);
1085
- newState = {
1086
- isError: true,
1087
- errorMessage: 'unhandled exception'
1088
- };
1089
- }
1314
+ let current = new URLSearchParams(path.toString());
1315
+ Object.keys(to).forEach(_key => current.set(_key, to[_key]));
1316
+ setURLParams(current);
1317
+ }, [path]);
1318
+ const remove = useCallback(param => {
1319
+ let current = new URLSearchParams(path.toString());
1320
+ current.delete(param);
1321
+ setURLParams(current);
1322
+ }, [path]);
1323
+ return [path, navigate, remove];
1324
+ }; // While History API does have `popstate` event, the only
1325
+ // proper way to listen to changes via `push/replaceState`
1326
+ // is to monkey-patch these methods.
1327
+ //
1328
+ // See https://stackoverflow.com/a/4585031
1090
1329
 
1091
- setState(newState);
1092
- return newState;
1093
- };
1330
+ let patched = 0;
1094
1331
 
1095
- const get = (setState, Ctx) => async endpoint => {
1096
- setState({
1097
- loading: true
1098
- });
1099
- const path = endpoint ? `${Ctx.path}${endpoint}` : Ctx.path;
1100
- const req = await Ctx.client.get(path);
1101
- const data = await req.json();
1102
- setState({
1103
- loading: false,
1104
- result: data,
1105
- response: req
1332
+ const patchHistoryEvents = () => {
1333
+ if (patched) return;
1334
+ ['pushState', 'replaceState'].map(type => {
1335
+ const original = window.history[type];
1336
+
1337
+ window.history[type] = function () {
1338
+ const result = original.apply(this, arguments);
1339
+ const event = new Event(type);
1340
+ event.arguments = arguments;
1341
+ dispatchEvent(event);
1342
+ return result;
1343
+ };
1106
1344
  });
1107
- return data;
1345
+ return patched = 1;
1108
1346
  };
1109
1347
 
1110
- function useCrudContext() {
1111
- const Ctx = useTraversal();
1112
- const [state, setState] = useSetState(initial);
1113
- return _extends({}, state, {
1114
- Ctx,
1115
- patch: patch(setState, Ctx),
1116
- del: del(setState, Ctx),
1117
- post: post(setState, Ctx),
1118
- get: get(setState, Ctx)
1119
- });
1120
- }
1348
+ const currentSearchParams = (path = window.location.search) => new URLSearchParams(path);
1121
1349
 
1122
- function AddItem(props) {
1123
- const Ctx = useTraversal();
1124
- const {
1125
- post,
1126
- loading
1127
- } = useCrudContext();
1128
- const {
1129
- type
1130
- } = props;
1131
- const {
1132
- getForm
1133
- } = Ctx.registry;
1134
- const Form = getForm(type);
1350
+ function Link(_ref) {
1351
+ let {
1352
+ aRef,
1353
+ model,
1354
+ children
1355
+ } = _ref,
1356
+ props = _objectWithoutPropertiesLoose(_ref, ["aRef", "model", "children"]);
1135
1357
 
1136
- const setActive = () => {
1137
- Ctx.cancelAction();
1358
+ const [path, navigate] = useLocation();
1359
+ const aStyle = {
1360
+ textDecoration: 'none',
1361
+ color: 'currentColor'
1138
1362
  };
1139
1363
 
1140
- async function doSubmit(data) {
1141
- const form = Object.assign({}, {
1142
- '@type': type
1143
- }, data.formData ? data.formData : data);
1144
- const {
1145
- isError,
1146
- errorMessage
1147
- } = await post(form);
1364
+ function onClick(e) {
1365
+ e.stopPropagation();
1366
+ if (actAsLink(e)) return;
1367
+ e.preventDefault();
1368
+ navigate({
1369
+ path: model.path
1370
+ }, true);
1371
+ if (props.onClick) props.onClick(e);
1372
+ }
1148
1373
 
1149
- if (!isError) {
1150
- Ctx.flash('Content created!', 'success');
1151
- } else {
1152
- Ctx.flash(`An error has ocurred: ${errorMessage}`, 'danger');
1153
- }
1374
+ return /*#__PURE__*/React.createElement("a", _extends({}, props, {
1375
+ ref: aRef,
1376
+ href: `?${path}${model.id}/`,
1377
+ style: aStyle,
1378
+ onClick: onClick
1379
+ }), children);
1380
+ }
1154
1381
 
1155
- Ctx.cancelAction();
1156
- Ctx.refresh();
1382
+ function actAsLink(e) {
1383
+ return e.ctrlKey || e.metaKey || e.altKey || e.shiftKey || e.button !== 0;
1384
+ }
1385
+
1386
+ function TdLink(_ref) {
1387
+ let {
1388
+ model,
1389
+ children
1390
+ } = _ref,
1391
+ props = _objectWithoutPropertiesLoose(_ref, ["model", "children"]);
1392
+
1393
+ const link = useRef();
1394
+
1395
+ function onClick() {
1396
+ link.current.click();
1157
1397
  }
1158
1398
 
1159
- return /*#__PURE__*/React.createElement(Modal, {
1160
- isActive: true,
1161
- setActive: setActive
1162
- }, /*#__PURE__*/React.createElement(Form, {
1163
- loading: loading,
1164
- onSubmit: doSubmit,
1165
- onError: err => console.log(err),
1166
- actionName: 'Add ' + type,
1167
- title: 'Add ' + type,
1168
- type: type,
1169
- dataTest: `formAdd${type}Test`
1170
- }));
1399
+ return /*#__PURE__*/React.createElement("td", _extends({}, props, {
1400
+ onClick: onClick
1401
+ }), /*#__PURE__*/React.createElement(Link, {
1402
+ model: model,
1403
+ aRef: link
1404
+ }, children));
1171
1405
  }
1172
1406
 
1173
- const Permissions = ['guillotina.AddContent', 'guillotina.ModifyContent', 'guillotina.ViewContent', 'guillotina.DeleteContent', 'guillotina.AccessContent', 'guillotina.SeePermissions', 'guillotina.ChangePermissions', 'guillotina.MoveContent', 'guillotina.DuplicateContent', 'guillotina.ReadConfiguration', 'guillotina.RegisterConfigurations', 'guillotina.WriteConfiguration', 'guillotina.ManageAddons', 'guillotina.swagger.View'];
1174
- const Config = {
1175
- DisabledTypes: ['UserManager', 'GroupManager'],
1176
- PageSize: 10,
1177
- DelayActions: 200,
1178
- Permissions: Permissions,
1179
- SearchEngine: 'PostreSQL' // Elasticsearch
1407
+ const SEP = '=';
1408
+ const DEFAULT_FIELD = 'title__in';
1409
+ const CLEANER = '||';
1410
+ function parser(qs, defaultField = DEFAULT_FIELD) {
1411
+ let lastKey = undefined;
1412
+ qs.trim();
1180
1413
 
1181
- };
1182
- let calculated = Object.assign({}, Config);
1414
+ if (qs.includes('"')) {
1415
+ qs = qs.replace(/"(\w+) (\w+)"/, `$1${CLEANER}$2`);
1416
+ }
1183
1417
 
1184
- const addConfig = (additional, original) => {
1185
- const rest = Object.assign({}, original);
1186
- Object.keys(additional).forEach(item => {
1187
- if (typeof Config[item] === 'object' && Array.isArray(Config[item])) {
1188
- rest[item] = [].concat(Config[item], additional[item]);
1189
- } else if (typeof Config[item] === 'object') {
1190
- rest[item] = Object.assign({}, Config[item], additional[item]);
1191
- } else {
1192
- rest[item] = additional[item];
1418
+ qs = qs.split(' ');
1419
+ return qs.map(part => {
1420
+ if (part.includes(CLEANER)) {
1421
+ part = part.replace('||', ' ');
1193
1422
  }
1194
- });
1195
- return rest;
1196
- };
1197
1423
 
1198
- function useConfig(cfg) {
1199
- const ref = React.useRef();
1424
+ if (!part.includes(SEP)) {
1425
+ return !lastKey ? [defaultField, part] : [lastKey, part];
1426
+ }
1200
1427
 
1201
- if (cfg && !ref.current) {
1202
- console.log('cfg', cfg);
1203
- calculated = addConfig(cfg, calculated);
1204
- ref.current = calculated;
1205
- } // if (cfg && !ref.current) {
1206
- // ref.current = addConfig(cfg, ref.current);
1207
- // calculated = ref.current
1208
- // console.log("Current config", cfg)
1209
- // }
1428
+ const [key, val] = part.split(SEP);
1429
+ lastKey = key;
1430
+ return [key, val];
1431
+ });
1432
+ }
1433
+ function buildQs(parsedQuery) {
1434
+ return parsedQuery.map(([key, val]) => `${encodeURIComponent(key)}=${encodeURIComponent(val)}`).join('&');
1435
+ }
1210
1436
 
1437
+ let cacheTypes = {};
1438
+ let cacheSchemas = {};
1439
+ class GuillotinaClient {
1440
+ constructor(rest, pathContainsContainer) {
1441
+ this.getContainerFromPath = path => {
1442
+ if (this.pathContainsContainer) {
1443
+ if (path.startsWith('/')) {
1444
+ path = path.substring(1);
1445
+ }
1211
1446
 
1212
- return calculated;
1213
- }
1447
+ let parts = path.split('/');
1448
+ return `${parts[0]}/${parts[1]}/`;
1449
+ }
1214
1450
 
1215
- const getId = item => {
1216
- if (item['@id']) {
1217
- return item['@id'];
1218
- }
1451
+ return '';
1452
+ };
1219
1453
 
1220
- return item['@absolute_url'];
1221
- };
1454
+ this.clearContainerFromPath = path => {
1455
+ if (this.pathContainsContainer) {
1456
+ return `/${this.cleanPath(path)}`;
1457
+ }
1222
1458
 
1223
- function RemoveItems(props) {
1224
- const Ctx = useTraversal();
1225
- const cfg = useConfig();
1226
- const [loading, setLoading] = React.useState(false);
1227
- const {
1228
- items = []
1229
- } = props;
1230
- const last = items[items.length - 1]['@name'];
1231
- const itemsNames = items.map(item => item['@name']).join(', ').replace(`, ${last}`, ` and ${last}`);
1459
+ return path;
1460
+ };
1232
1461
 
1233
- async function removeItems() {
1234
- let errors = [];
1235
- setLoading(true);
1236
- const actions = items.map(async item => {
1237
- console.log(item);
1238
- const res = await Ctx.client.delete(getId(item));
1462
+ this.rest = rest;
1463
+ this.pathContainsContainer = pathContainsContainer;
1464
+ }
1239
1465
 
1240
- if (!res.ok) {
1241
- const err = await res.json();
1242
- errors.push(err);
1243
- }
1244
- }); // this sleep is here, to let elasticsearch, wait for
1245
- // index our operations... (will work 99% of use cases)
1466
+ async getContext(path) {
1467
+ switch (path) {
1468
+ case '/':
1469
+ return await this.rest.get('');
1246
1470
 
1247
- actions.push(sleep(cfg.DelayActions));
1248
- await Promise.all(actions);
1471
+ default:
1472
+ if (path.startsWith('/')) {
1473
+ path = path.substring(1);
1474
+ }
1249
1475
 
1250
- if (errors.length === 0) {
1251
- Ctx.flash(`Items removed!`, 'success');
1252
- Ctx.refresh();
1253
- } else {
1254
- const errorstr = errors.map(err => JSON.stringify(err)).join('\n');
1255
- Ctx.flash(`Something went wrong!! ${errorstr}`, 'danger');
1476
+ return await this.rest.get(path);
1256
1477
  }
1257
-
1258
- setLoading(false);
1259
- Ctx.cancelAction();
1260
1478
  }
1261
1479
 
1262
- return /*#__PURE__*/React.createElement(Confirm, {
1263
- loading: loading,
1264
- onCancel: () => Ctx.cancelAction(),
1265
- onConfirm: removeItems,
1266
- message: `Are you sure to remove: ${itemsNames}?`
1267
- });
1268
- }
1269
-
1270
- const Checkbox = (_ref) => {
1271
- let {
1272
- id,
1273
- className,
1274
- classNameInput,
1275
- loading,
1276
- disabled,
1277
- indeterminate = false,
1278
- value = false,
1279
- children,
1280
- placeholder,
1281
- onChange,
1282
- dataTest
1283
- } = _ref,
1284
- rest = _objectWithoutPropertiesLoose(_ref, ["id", "className", "classNameInput", "loading", "disabled", "indeterminate", "value", "color", "backgroundColor", "borderColor", "children", "placeholder", "onChange", "dataTest"]);
1480
+ async get(path) {
1481
+ if (path.startsWith('/')) {
1482
+ path = path.slice(1);
1483
+ }
1285
1484
 
1286
- const inputRef = useRef(null);
1287
- const [state, setState] = React.useState(value);
1288
- useEffect(() => {
1289
- if (inputRef.current) {
1290
- inputRef.current.indeterminate = indeterminate;
1485
+ return await this.rest.get(path);
1486
+ }
1487
+
1488
+ getQueryParamsPostresql({
1489
+ start = 0,
1490
+ pageSize = 10,
1491
+ withDepth = true
1492
+ }) {
1493
+ let result = [];
1494
+ result = [...parser(start.toString(), 'b_start'), ...parser(pageSize.toString(), 'b_size')];
1495
+
1496
+ if (withDepth) {
1497
+ var _parser;
1498
+
1499
+ result = [...result, ...((_parser = parser('1', 'depth')) != null ? _parser : [])];
1291
1500
  }
1292
- }, [indeterminate]);
1293
1501
 
1294
- const updateState = ev => {
1295
- setState(ev.target.checked);
1296
- onChange(ev.target.checked);
1297
- };
1502
+ return result;
1503
+ }
1298
1504
 
1299
- return /*#__PURE__*/React.createElement("div", {
1300
- className: "field"
1301
- }, /*#__PURE__*/React.createElement("label", {
1302
- htmlFor: id,
1303
- className: classnames(['checkbox', className])
1304
- }, /*#__PURE__*/React.createElement("input", _extends({
1305
- ref: inputRef,
1306
- disabled: disabled || loading,
1307
- id: id,
1308
- type: "checkbox",
1309
- className: classnames(['checkbox', classNameInput]),
1310
- checked: state,
1311
- onChange: updateState,
1312
- "data-test": dataTest
1313
- }, rest)), children || placeholder));
1314
- };
1505
+ getQueryParamsElasticsearch({
1506
+ start = 0,
1507
+ pageSize = 10,
1508
+ path,
1509
+ withDepth = true
1510
+ }) {
1511
+ let result = [];
1512
+ let containerPath = this.getContainerFromPath(path);
1315
1513
 
1316
- const ErrorZone = ({
1317
- children,
1318
- id,
1319
- className
1320
- }) => {
1321
- return /*#__PURE__*/React.createElement("p", {
1322
- className: classnames(['help is-danger', className]),
1323
- id: id
1324
- }, children);
1325
- };
1514
+ if (containerPath.startsWith('/')) {
1515
+ containerPath = containerPath.slice(1);
1516
+ }
1326
1517
 
1327
- ErrorZone.propTypes = {
1328
- children: PropTypes.node,
1329
- id: PropTypes.string,
1330
- className: PropTypes.string
1331
- };
1518
+ let objectPath = path.replace(containerPath, '');
1332
1519
 
1333
- const applyValidators = (value, validators) => {
1334
- let validation = Array.isArray(validators) ? validators : [validators];
1335
- let result = true;
1336
- validation.forEach(func => {
1337
- if (func(value) === false) {
1338
- result = false;
1520
+ if (objectPath.endsWith('/')) {
1521
+ objectPath = objectPath.slice(0, -1);
1339
1522
  }
1340
- });
1341
- return result;
1342
- };
1343
1523
 
1344
- const useInput = (onChange, value, validator) => {
1345
- let [state, setState] = React.useState({
1346
- hasError: false,
1347
- value: value
1348
- });
1524
+ result = [...parser(start.toString(), '_from'), ...parser(pageSize.toString(), 'size')];
1349
1525
 
1350
- const onUpdate = ev => {
1351
- const value = ev && ev.target ? ev.target.value : ev ? ev : '';
1352
- setState({
1353
- value,
1354
- hasError: false
1355
- });
1356
- if (onChange) onChange(value);
1357
- };
1526
+ if (withDepth) {
1527
+ var _parser2;
1358
1528
 
1359
- const onBlur = () => {
1360
- const hasError = applyValidators(state.value, validator) === false;
1361
- if (hasError) setState({
1362
- value: state.value,
1363
- hasError
1364
- });
1365
- };
1529
+ result = [...result, ...((_parser2 = parser('1', 'depth')) != null ? _parser2 : [])];
1530
+ }
1366
1531
 
1367
- const onFocus = () => {
1368
- if (state.hasError) {
1369
- setState({
1370
- value: state.value,
1371
- hasError: false
1372
- });
1532
+ if (objectPath !== '') {
1533
+ result = [...result, ...parser(objectPath, 'path__wildcard')];
1373
1534
  }
1374
- };
1375
1535
 
1376
- React.useEffect(() => {
1377
- setState({
1378
- value,
1379
- hasError: false
1380
- });
1381
- }, [value]);
1382
- return {
1383
- onChange: onUpdate,
1384
- onFocus,
1385
- onBlur,
1386
- state
1387
- };
1388
- };
1536
+ return result;
1537
+ }
1389
1538
 
1390
- // From github.com/protonmail/proton-shared
1539
+ getItemsColumn() {
1540
+ const smallcss = {
1541
+ width: 25
1542
+ };
1543
+ const mediumcss = {
1544
+ width: 120
1545
+ };
1546
+ return [{
1547
+ label: '',
1548
+ child: m => /*#__PURE__*/React.createElement("td", {
1549
+ style: smallcss
1550
+ }, /*#__PURE__*/React.createElement(Icon, {
1551
+ icon: m.icon
1552
+ }))
1553
+ }, {
1554
+ label: 'type',
1555
+ child: m => /*#__PURE__*/React.createElement(TdLink, {
1556
+ style: smallcss,
1557
+ model: m
1558
+ }, /*#__PURE__*/React.createElement("span", {
1559
+ className: "tag"
1560
+ }, m.type))
1561
+ }, {
1562
+ label: 'id/name',
1563
+ child: (m, navigate, search) => /*#__PURE__*/React.createElement(TdLink, {
1564
+ model: m
1565
+ }, m.name, search && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("br", null), /*#__PURE__*/React.createElement("span", {
1566
+ className: "is-size-7 tag is-light"
1567
+ }, m.path)))
1568
+ }, {
1569
+ label: 'created',
1570
+ child: m => /*#__PURE__*/React.createElement("td", {
1571
+ style: mediumcss,
1572
+ className: "is-size-7 is-vcentered"
1573
+ }, m.created)
1574
+ }, {
1575
+ label: 'modified',
1576
+ child: m => /*#__PURE__*/React.createElement("td", {
1577
+ style: mediumcss,
1578
+ className: "is-size-7 is-vcentered"
1579
+ }, m.updated)
1580
+ }];
1581
+ } // BBB API changes. Compat G5 and G6
1391
1582
 
1392
- /* eslint-disable no-useless-escape */
1393
- const REGEX_EMAIL = /(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))/i;
1394
- const REGEX_URL = /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[\-;:&=\+\$,\w]+@)?[A-Za-z0-9\.\-]+|(?:www\.|[\-;:&=\+\$,\w]+@)[A-Za-z0-9\.\-]+)((?:\/[\+~%\/\.\w\-_]*)?\??(?:[\-\+=&;%@\.\w_]*)#?(?:[\.\!\/\\\w]*))?)/;
1395
- const REGEX_HEX_COLOR = /^#([a-f0-9]{3,4}|[a-f0-9]{4}(?:[a-f0-9]{2}){1,2})\b$/i;
1396
- const REGEX_NUMBER = /^\d+$/;
1397
- const isEmpty = (value = '') => !value.length;
1398
- const maxLength = (value = '', limit = 0) => value.length <= limit;
1399
- const minLength = (value = '', limit = 0) => value.length >= limit;
1400
- const isEmail = (value = '') => REGEX_EMAIL.test(value);
1401
- const isURL = (value = '') => REGEX_URL.test(value);
1402
- const isHexColor = (value = '') => REGEX_HEX_COLOR.test(value);
1403
- const isNumber = (value = '') => REGEX_NUMBER.test(value);
1404
- const notEmpty = value => !!value.length;
1405
1583
 
1406
- const noop$2 = () => true;
1407
- /** @type any */
1584
+ applyCompat(data) {
1585
+ data.member = data.items;
1586
+ data.items_count = data.items_total;
1587
+ return data;
1588
+ }
1408
1589
 
1590
+ async search(path, params, container = false, prepare = true) {
1591
+ if (path.startsWith('/')) {
1592
+ path = path.slice(1);
1593
+ }
1409
1594
 
1410
- const Input = React.forwardRef((_ref, ref) => {
1411
- let {
1412
- icon,
1413
- iconPosition = 'has-icons-right',
1414
- error,
1415
- errorZoneClassName,
1416
- autoComplete = 'off',
1417
- className = '',
1418
- widget = 'input',
1419
- type = 'text',
1420
- loading = false,
1421
- required = false,
1422
- id,
1423
- placeholder,
1424
- value,
1425
- autofocus = false,
1426
- onChange,
1427
- validator = noop$2,
1428
- errorMessage,
1429
- dataTest = 'testInput'
1430
- } = _ref,
1431
- rest = _objectWithoutPropertiesLoose(_ref, ["icon", "iconPosition", "error", "errorZoneClassName", "autoComplete", "className", "widget", "type", "onPressEnter", "isSubmitted", "loading", "required", "id", "placeholder", "value", "autofocus", "onChange", "validator", "errorMessage", "dataTest"]);
1595
+ if (container) {
1596
+ path = this.getContainerFromPath(path);
1597
+ }
1432
1598
 
1433
- if (required) {
1434
- validator = Array.isArray(validator) ? validator.push(notEmpty) : [validator, notEmpty];
1599
+ let query = prepare ? toQueryString(params) : params;
1600
+ const url = `${path}@search?${query}`;
1601
+ let res = await this.rest.get(url);
1602
+ let data = await res.json();
1603
+ return this.applyCompat(data);
1435
1604
  }
1436
1605
 
1437
- const _useInput = useInput(onChange, value != null ? value : '', validator),
1438
- {
1439
- state
1440
- } = _useInput,
1441
- handlers = _objectWithoutPropertiesLoose(_useInput, ["state"]);
1606
+ async canido(path, permissions) {
1607
+ if (path.startsWith('/')) {
1608
+ path = path.slice(1);
1609
+ }
1610
+
1611
+ if (Array.isArray(permissions)) {
1612
+ permissions.join(',');
1613
+ }
1614
+
1615
+ const url = `${path}@canido?permissions=${permissions}`;
1616
+ return await this.rest.get(url);
1617
+ }
1618
+
1619
+ async createObject(path, data) {
1620
+ return await this.rest.post(path, data);
1621
+ }
1622
+
1623
+ cleanPath(path) {
1624
+ let url = path.split('/').slice(3);
1625
+ return `${url.join('/')}`;
1626
+ }
1442
1627
 
1443
- const [uid] = useState(generateUID('input'));
1444
- const [mounted, setMounted] = useState(false); // eslint-disable-next-line
1628
+ async delete(path, data) {
1629
+ return await this.rest.delete(path, data);
1630
+ }
1445
1631
 
1446
- ref = ref || React.useRef();
1447
- useEffect(() => {
1448
- setMounted(true);
1449
- }, []);
1450
- useEffect(() => {
1451
- if (autofocus && !error) {
1452
- ref.current.focus();
1632
+ async create(path, data) {
1633
+ if (path.startsWith('/')) {
1634
+ path = path.substring(1);
1453
1635
  }
1454
- }, [mounted, autofocus, ref, error]);
1455
- const theError = state.hasError ? errorMessage || 'invalid field' : '';
1456
- const statusClasses = state.hasError ? 'is-danger' : '';
1457
1636
 
1458
- const cssControl = () => icon ? ['control', iconPosition] : ['control'];
1637
+ return await this.rest.post(path, data);
1638
+ }
1459
1639
 
1460
- return /*#__PURE__*/React.createElement("div", {
1461
- className: "field"
1462
- }, id && placeholder ? /*#__PURE__*/React.createElement("label", {
1463
- className: "label",
1464
- htmlFor: id
1465
- }, placeholder) : null, /*#__PURE__*/React.createElement("div", {
1466
- className: classnames(cssControl())
1467
- }, /*#__PURE__*/React.createElement("input", _extends({
1468
- className: classnames([widget, className, statusClasses]),
1469
- "aria-invalid": theError,
1470
- "aria-describedby": uid,
1471
- id: id,
1472
- ref: ref,
1473
- type: type,
1474
- value: state.value,
1475
- placeholder: placeholder,
1476
- autoComplete: autoComplete,
1477
- disabled: loading || rest.disabled,
1478
- required: required,
1479
- "data-test": dataTest
1480
- }, handlers, rest)), icon && icon), /*#__PURE__*/React.createElement(ErrorZone, {
1481
- className: errorZoneClassName,
1482
- id: uid
1483
- }, state.hasError ? theError : ''));
1484
- });
1485
- Input.propTypes = {
1486
- icon: PropTypes.node,
1487
- iconPosition: PropTypes.arrayOf(PropTypes.oneOf(['has-icons-left', 'has-icons-right', ''])),
1488
- error: PropTypes.string,
1489
- errorZoneClassName: PropTypes.string,
1490
- autoComplete: PropTypes.string,
1491
- autoFocus: PropTypes.bool,
1492
- className: PropTypes.string,
1493
- disabled: PropTypes.bool,
1494
- loading: PropTypes.bool,
1495
- isSubmitted: PropTypes.bool,
1496
- id: PropTypes.string,
1497
- name: PropTypes.string,
1498
- onChange: PropTypes.func,
1499
- onKeyDown: PropTypes.func,
1500
- onKeyUp: PropTypes.func,
1501
- onPressEnter: PropTypes.func,
1502
- placeholder: PropTypes.string,
1503
- readOnly: PropTypes.bool,
1504
- required: PropTypes.bool,
1505
- type: PropTypes.string,
1506
- value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool])
1507
- };
1640
+ async post(path, data) {
1641
+ return await this.create(path, data);
1642
+ }
1508
1643
 
1509
- const EmailInput = (_ref) => {
1510
- let {
1511
- value = '',
1512
- dataTest
1513
- } = _ref,
1514
- rest = _objectWithoutPropertiesLoose(_ref, ["value", "dataTest"]);
1644
+ async patch(path, data) {
1645
+ if (path.startsWith('/')) {
1646
+ path = path.substring(1);
1647
+ }
1515
1648
 
1516
- return /*#__PURE__*/React.createElement(Input, _extends({
1517
- type: "email",
1518
- validator: isEmail,
1519
- errorMessage: "Email address invalid",
1520
- value: value,
1521
- "data-test": dataTest,
1522
- icon: /*#__PURE__*/React.createElement(Icon, {
1523
- icon: "fas fa-envelope"
1524
- })
1525
- }, rest));
1526
- };
1649
+ return await this.rest.patch(path, data);
1650
+ }
1527
1651
 
1528
- const Form = (_ref) => {
1529
- let {
1530
- children,
1531
- className = '',
1532
- onSubmit = noop,
1533
- onReset = noop,
1534
- autoComplete = 'off',
1535
- title,
1536
- error,
1537
- dataTest
1538
- } = _ref,
1539
- rest = _objectWithoutPropertiesLoose(_ref, ["children", "className", "onSubmit", "onReset", "autoComplete", "title", "error", "dataTest"]);
1652
+ async upload(path, file) {
1653
+ if (path.startsWith('/')) {
1654
+ path = path.substring(1);
1655
+ }
1540
1656
 
1541
- const handleSubmit = event => {
1542
- event.preventDefault();
1543
- onSubmit(event);
1544
- };
1657
+ return await this.rest.upload(path, file);
1658
+ }
1545
1659
 
1546
- return /*#__PURE__*/React.createElement("div", {
1547
- "data-test": dataTest
1548
- }, title && /*#__PURE__*/React.createElement("div", {
1549
- className: "level"
1550
- }, /*#__PURE__*/React.createElement("h1", {
1551
- className: "title is-size-4"
1552
- }, title)), error && /*#__PURE__*/React.createElement("div", {
1553
- className: "notification is-danger"
1554
- }, error), /*#__PURE__*/React.createElement("form", _extends({
1555
- onSubmit: handleSubmit,
1556
- onReset: onReset,
1557
- autoComplete: autoComplete,
1558
- className: classnames(['form', className])
1559
- }, rest), children));
1560
- };
1561
- Form.propTypes = {
1562
- children: PropTypes.node.isRequired,
1563
- className: PropTypes.string,
1564
- onSubmit: PropTypes.func,
1565
- onReset: PropTypes.func,
1566
- autoComplete: PropTypes.string
1567
- };
1660
+ async download(path) {
1661
+ if (path.startsWith('/')) {
1662
+ path = path.substring(1);
1663
+ }
1568
1664
 
1569
- const PasswordInput = (_ref) => {
1570
- let {
1571
- value = ''
1572
- } = _ref,
1573
- rest = _objectWithoutPropertiesLoose(_ref, ["value"]);
1665
+ return await this.rest.get(path);
1666
+ }
1574
1667
 
1575
- return /*#__PURE__*/React.createElement(Input, _extends({
1576
- value: value,
1577
- type: "password"
1578
- }, rest));
1579
- };
1668
+ async getTypeSchema(path, name) {
1669
+ if (!cacheSchemas[name]) {
1670
+ let url = this.getContainerFromPath(path); // todo: handle db case (only addable containers)
1580
1671
 
1581
- const formComponents = {
1582
- string: Input,
1583
- password: PasswordInput,
1584
- boolean: Checkbox,
1585
- email: EmailInput
1586
- };
1587
- function FormBuilder(_ref) {
1588
- let {
1589
- schema,
1590
- formData,
1591
- onSubmit,
1592
- actionName,
1593
- children,
1594
- exclude = [],
1595
- remotes = [],
1596
- submitButton = true
1597
- } = _ref,
1598
- rest = _objectWithoutPropertiesLoose(_ref, ["schema", "formData", "title", "onSubmit", "actionName", "children", "exclude", "remotes", "submitButton"]);
1672
+ const res = await this.rest.get(`${url}@types/${name}`);
1673
+ cacheSchemas[name] = await res.json();
1674
+ }
1599
1675
 
1600
- const ref = React.useRef();
1601
- const {
1602
- properties,
1603
- required
1604
- } = schema;
1605
- const values = Object.assign({}, formData || {}); // build initial state
1676
+ return cacheSchemas[name];
1677
+ }
1606
1678
 
1607
- let initialState = {};
1608
- const fields = Object.keys(properties).filter(x => !exclude.includes(x));
1609
- fields.forEach(element => {
1610
- initialState[element] = values[element] || undefined;
1611
- }); // Register remotes
1679
+ async getAddons(path) {
1680
+ return await this.rest.get(`${path}@addons`);
1681
+ }
1612
1682
 
1613
- if (!ref.current) {
1614
- ref.current = {};
1615
- Object.keys(remotes).forEach(item => ref.current[item] = remotes[item]);
1616
- } else {
1617
- // apply remote changes
1618
- Object.keys(remotes).forEach(key => {
1619
- if (JSON.stringify(ref.current[key]) !== JSON.stringify(remotes[key])) {
1620
- ref.current[key] = remotes[key];
1621
- }
1683
+ async installAddon(path, key) {
1684
+ return await this.rest.post(`${path}@addons`, {
1685
+ id: key
1622
1686
  });
1623
1687
  }
1624
1688
 
1625
- ref.current = ref.current || {};
1689
+ async removeAddon(path, key) {
1690
+ return await this.rest.delete(`${path}@addons`, {
1691
+ id: key
1692
+ });
1693
+ }
1626
1694
 
1627
- const onUpdate = field => ev => {
1628
- ref.current[field] = ev.target ? ev.target.value : ev.value || ev;
1629
- };
1695
+ async getGroups(path) {
1696
+ const endpoint = `${this.getContainerFromPath(path)}@groups`;
1697
+ return await this.rest.get(endpoint);
1698
+ }
1630
1699
 
1631
- const GetTag = ({
1632
- field
1633
- }) => {
1634
- const Tag = formComponents[properties[field].widget || properties[field].type];
1635
- const props = {
1636
- label: properties[field].title,
1637
- value: initialState[field],
1638
- onChange: onUpdate(field),
1639
- placeholder: properties[field].title || '',
1640
- id: generateUID(),
1641
- dataTest: `${field}TestInput`
1700
+ async getUsers(path) {
1701
+ const endpoint = `${this.getContainerFromPath(path)}@users`;
1702
+ return await this.rest.get(endpoint);
1703
+ }
1704
+
1705
+ async getPrincipals(path) {
1706
+ const groups = this.getGroups(path);
1707
+ const users = this.getUsers(path);
1708
+ const [gr, usr] = await Promise.all([groups, users]);
1709
+ return {
1710
+ groups: gr.ok ? await gr.json() : [],
1711
+ users: usr.ok ? await usr.json() : []
1642
1712
  };
1713
+ }
1714
+
1715
+ async getRoles(path) {
1716
+ const endpoint = `${this.getContainerFromPath(path)}@available-roles`;
1717
+ return await this.rest.get(endpoint);
1718
+ }
1719
+
1720
+ async getAllPermissions(path) {
1721
+ // paths used to query the API always has to start without a "/"
1722
+ if (path.startsWith('/')) {
1723
+ path = path.slice(1);
1724
+ }
1725
+
1726
+ const req = await this.rest.get(path + '@all_permissions');
1727
+ const resp = await req.json();
1728
+ const permissions = Array.from(new Set(extractPermissions(resp)));
1729
+ return permissions;
1730
+ }
1643
1731
 
1644
- if (required.includes(field)) {
1645
- props.required = true;
1646
- props.placeholder += ' *';
1732
+ async getTypes(path) {
1733
+ if (path.startsWith('/')) {
1734
+ path = path.slice(1);
1647
1735
  }
1648
1736
 
1649
- Tag.displayName = `${field}Field`;
1650
- return /*#__PURE__*/React.createElement(Tag, props);
1651
- };
1737
+ if (!cacheTypes[path]) {
1738
+ const types = await this.rest.get(path + '@addable-types');
1652
1739
 
1653
- const children_ = React.Children.map(children, child => React.cloneElement(child, {
1654
- onChange: onUpdate
1655
- }));
1740
+ if (types.status === 401 || types.status === 404) {
1741
+ cacheTypes[path] = [];
1742
+ } else {
1743
+ const res = await types.json();
1744
+ cacheTypes[path] = res;
1745
+ }
1746
+ }
1656
1747
 
1657
- const changes = () => {
1658
- onSubmit(ref.current, values);
1659
- };
1748
+ return cacheTypes[path];
1749
+ }
1660
1750
 
1661
- return /*#__PURE__*/React.createElement(Form, _extends({
1662
- onSubmit: changes
1663
- }, rest), fields.map(field => /*#__PURE__*/React.createElement(GetTag, {
1664
- field: field,
1665
- key: field
1666
- })), children_, submitButton && /*#__PURE__*/React.createElement(Button, null, actionName));
1667
1751
  }
1752
+ function getClient(url, container, auth) {
1753
+ return new GuillotinaClient(new RestClient(url, container, auth), container === '/');
1754
+ }
1755
+ const lightFileReader = async file => {
1756
+ return new Promise(resolve => {
1757
+ const reader = new FileReader();
1758
+ reader.readAsArrayBuffer(file);
1668
1759
 
1669
- /** @type any */
1670
-
1671
- const Select = React.forwardRef((_ref, ref) => {
1672
- let {
1673
- options,
1674
- error,
1675
- errorZoneClassName,
1676
- size = 1,
1677
- placeholder,
1678
- id,
1679
- className = '',
1680
- classWrap = '',
1681
- multiple = false,
1682
- loading = false,
1683
- onChange,
1684
- appendDefault = false,
1685
- style = {},
1686
- dataTest
1687
- } = _ref,
1688
- rest = _objectWithoutPropertiesLoose(_ref, ["options", "error", "errorZoneClassName", "size", "placeholder", "id", "className", "classWrap", "multiple", "loading", "isSubmitted", "onChange", "appendDefault", "style", "dataTest"]);
1689
-
1690
- const [uid] = useState(generateUID('select'));
1691
-
1692
- const onUpdate = ev => {
1693
- if (ev.target.value === '') {
1694
- onChange({
1695
- target: {
1696
- value: undefined
1697
- }
1760
+ reader.onloadend = e => {
1761
+ const fileData = e.target.result;
1762
+ resolve({
1763
+ filename: file.name.normalize('NFD').replace(/[\u0300-\u036f]/g, ''),
1764
+ data: fileData,
1765
+ 'content-type': file.type
1698
1766
  });
1699
- } else {
1700
- onChange(ev);
1701
- }
1702
- };
1767
+ };
1768
+ });
1769
+ };
1703
1770
 
1704
- if (appendDefault) {
1705
- options = [{
1706
- text: 'Choose..',
1707
- value: ''
1708
- }].concat(options);
1709
- }
1771
+ const extractPermissions = data => {
1772
+ let result = [];
1710
1773
 
1711
- const statusClasses = error ? 'is-danger' : '';
1712
- const cssWrap = ['select', statusClasses, multiple ? 'is-multiple' : '', classWrap];
1713
- return /*#__PURE__*/React.createElement("div", {
1714
- className: "field"
1715
- }, id && placeholder ? /*#__PURE__*/React.createElement("label", {
1716
- className: "label",
1717
- htmlFor: id
1718
- }, placeholder) : null, /*#__PURE__*/React.createElement("div", {
1719
- className: classnames(cssWrap)
1720
- }, /*#__PURE__*/React.createElement("select", _extends({
1721
- className: classnames(['', className]),
1722
- size: size,
1723
- multiple: multiple,
1724
- disabled: loading || rest.disabled,
1725
- onChange: onUpdate
1726
- }, rest, {
1727
- ref: ref,
1728
- style: style,
1729
- "data-test": dataTest
1730
- }), options.map((_ref2, index) => {
1731
- let {
1732
- text
1733
- } = _ref2,
1734
- rest = _objectWithoutPropertiesLoose(_ref2, ["text"]);
1774
+ if (typeof data !== 'object') ; else if (!Array.isArray(data) && data.permission) {
1775
+ result = result.concat([data.permission]);
1776
+ } else if (!Array.isArray(data)) {
1777
+ Object.keys(data).map(key => result = result.concat(extractPermissions(data[key])));
1778
+ } else if (Array.isArray(data)) {
1779
+ data.map(item => result = result.concat(extractPermissions(item)));
1780
+ }
1735
1781
 
1736
- return /*#__PURE__*/React.createElement("option", _extends({
1737
- key: index.toString()
1738
- }, rest), text);
1739
- }))), error && /*#__PURE__*/React.createElement(ErrorZone, {
1740
- className: errorZoneClassName,
1741
- id: uid
1742
- }, error ? error : ''));
1743
- });
1744
- Select.propTypes = {
1745
- error: PropTypes.string,
1746
- disabled: PropTypes.bool,
1747
- loading: PropTypes.bool,
1748
- isSubmitted: PropTypes.bool,
1749
- size: PropTypes.number,
1750
- onChange: PropTypes.func,
1751
- options: PropTypes.arrayOf(PropTypes.object),
1752
- multiple: PropTypes.bool,
1753
- className: PropTypes.string
1782
+ return result;
1754
1783
  };
1755
1784
 
1756
1785
  function FileUpload(_ref) {
@@ -2147,7 +2176,7 @@ function Dropdown(_ref) {
2147
2176
  // eslint-disable-next-line jsx-a11y/anchor-is-valid
2148
2177
  React.createElement("a", {
2149
2178
  className: disabled ? 'dropdown-item is-active' : 'dropdown-item',
2150
- "data-test": `dropdownItemTest-${option.text.toLowerCase()}`,
2179
+ "data-test": `dropdownItemTest-${option.value.toLowerCase()}`,
2151
2180
  key: option.text,
2152
2181
  onClick: disabled ? undefined : () => onChange(option.value),
2153
2182
  style: disabled ? {
@@ -2161,57 +2190,6 @@ function Dropdown(_ref) {
2161
2190
  }))));
2162
2191
  }
2163
2192
 
2164
- const plain = ['string', 'number', 'boolean'];
2165
- function RenderField({
2166
- value,
2167
- Widget
2168
- }) {
2169
- if (value === null || value === undefined) return '';
2170
-
2171
- if (Widget) {
2172
- return /*#__PURE__*/React.createElement(Widget, {
2173
- value: value
2174
- });
2175
- }
2176
-
2177
- const type = typeof value;
2178
-
2179
- if (plain.includes(type)) {
2180
- return value;
2181
- }
2182
-
2183
- if (type === 'object') {
2184
- if (Array.isArray(value)) {
2185
- return value.map(item => /*#__PURE__*/React.createElement("div", {
2186
- key: item
2187
- }, /*#__PURE__*/React.createElement(RenderField, {
2188
- value: item
2189
- })));
2190
- }
2191
-
2192
- return Object.keys(value).map(key => /*#__PURE__*/React.createElement(FieldValue, {
2193
- field: key,
2194
- value: value[key],
2195
- key: key
2196
- }));
2197
- }
2198
-
2199
- return /*#__PURE__*/React.createElement("p", null, "No render for ", JSON.stringify(value));
2200
- }
2201
-
2202
- const FieldValue = ({
2203
- field,
2204
- value
2205
- }) => /*#__PURE__*/React.createElement("div", {
2206
- className: "field"
2207
- }, /*#__PURE__*/React.createElement("div", {
2208
- className: "label"
2209
- }, field), /*#__PURE__*/React.createElement("div", {
2210
- className: "value"
2211
- }, /*#__PURE__*/React.createElement(RenderField, {
2212
- value: value
2213
- })));
2214
-
2215
2193
  const formatDate = str => {
2216
2194
  const d = new Date(str);
2217
2195
  const minutes = d.getMinutes() < 10 ? `0${d.getMinutes()}` : d.getMinutes();
@@ -2234,74 +2212,7 @@ function getNewId(id = '') {
2234
2212
  return `${suffix}${num + 1}`;
2235
2213
  });
2236
2214
  }
2237
-
2238
- const DownloadField = ({
2239
- value
2240
- }) => {
2241
- const Ctx = useTraversal();
2242
- const {
2243
- data,
2244
- field
2245
- } = value;
2246
-
2247
- const getField = async downloadFile => {
2248
- const endpoint = `${Ctx.path}@download/${field}`;
2249
- const res = await Ctx.client.download(endpoint);
2250
- const text = await res.blob();
2251
- const blob = new Blob([text], {
2252
- type: data.content_type
2253
- });
2254
- const url = window.URL.createObjectURL(blob); // Create blob link to download
2255
-
2256
- const link = document.createElement('a');
2257
- link.href = url;
2258
-
2259
- if (downloadFile) {
2260
- link.setAttribute('download', `${data.filename}`);
2261
- } else {
2262
- link.setAttribute('target', `_blank`);
2263
- }
2264
-
2265
- document.body.appendChild(link);
2266
- link.click();
2267
- setTimeout(function () {
2268
- var _link$parentNode;
2269
-
2270
- // For Firefox it is necessary to delay revoking the ObjectURL
2271
- window.URL.revokeObjectURL(url);
2272
- (_link$parentNode = link.parentNode) == null ? void 0 : _link$parentNode.removeChild(link);
2273
- }, 100);
2274
- };
2275
-
2276
- return /*#__PURE__*/createElement("div", {
2277
- className: "field"
2278
- }, /*#__PURE__*/createElement("div", {
2279
- className: "label"
2280
- }, data.filename), /*#__PURE__*/createElement("div", {
2281
- className: "columns"
2282
- }, /*#__PURE__*/createElement("div", {
2283
- className: "column"
2284
- }, /*#__PURE__*/createElement("button", {
2285
- className: "button is-small is-primary level-left",
2286
- onClick: async event => {
2287
- event.preventDefault();
2288
- event.stopPropagation();
2289
- getField(false);
2290
- }
2291
- }, "Open")), /*#__PURE__*/createElement("div", {
2292
- className: "column"
2293
- }, /*#__PURE__*/createElement("button", {
2294
- className: "button is-small is-primary level-right",
2295
- onClick: async event => {
2296
- event.preventDefault();
2297
- event.stopPropagation();
2298
- getField(true);
2299
- }
2300
- }, "Download"))));
2301
- };
2302
-
2303
- const DEFAULT_VALUE_EDITABLE_FIELD = 'Click to edit';
2304
- const DEFAULT_VALUE_NO_EDITABLE_FIELD = ' -- ';
2215
+
2305
2216
  function EditableField({
2306
2217
  field,
2307
2218
  value,
@@ -2318,36 +2229,17 @@ function EditableField({
2318
2229
  loading,
2319
2230
  Ctx
2320
2231
  } = useCrudContext();
2232
+ const {
2233
+ fieldHaveDeleteButton
2234
+ } = useConfig();
2321
2235
  const EditComponent = Ctx.registry.get('components', 'EditComponent');
2236
+ const RenderFieldComponent = Ctx.registry.get('components', 'RenderFieldComponent');
2322
2237
  useEffect(() => {
2323
2238
  if (isEdit && ref.current) {
2324
2239
  ref.current.focus();
2325
2240
  }
2326
2241
  });
2327
2242
  const canModified = modifyContent && !get$1(schema, 'readonly', false);
2328
- const haveDeleteBtn = (schema == null ? void 0 : schema.widget) === 'file' || (schema == null ? void 0 : schema.widget) === 'select' || (schema == null ? void 0 : schema.type) === 'array';
2329
-
2330
- const getRenderProps = () => {
2331
- const renderProps = {
2332
- value: val != null ? val : modifyContent ? DEFAULT_VALUE_EDITABLE_FIELD : DEFAULT_VALUE_NO_EDITABLE_FIELD
2333
- };
2334
-
2335
- if (val && (schema == null ? void 0 : schema.widget) === 'file') {
2336
- renderProps['value'] = {
2337
- data: val,
2338
- field: field
2339
- };
2340
- renderProps['Widget'] = DownloadField;
2341
- } else if ((schema == null ? void 0 : schema.type) === 'boolean') {
2342
- var _val$toString;
2343
-
2344
- renderProps['value'] = (_val$toString = val == null ? void 0 : val.toString()) != null ? _val$toString : renderProps['value'];
2345
- } else if (val && (schema == null ? void 0 : schema.type) === 'datetime') {
2346
- renderProps['value'] = new Date(val).toLocaleString();
2347
- }
2348
-
2349
- return renderProps;
2350
- };
2351
2243
 
2352
2244
  const saveField = async ev => {
2353
2245
  if (ev) ev.preventDefault();
@@ -2437,7 +2329,12 @@ function EditableField({
2437
2329
  setEdit(!!canModified);
2438
2330
  },
2439
2331
  "data-test": `editableFieldTest-${field}`
2440
- }, /*#__PURE__*/React.createElement(RenderField, getRenderProps()), canModified && /*#__PURE__*/React.createElement(Icon, {
2332
+ }, /*#__PURE__*/React.createElement(RenderFieldComponent, {
2333
+ schema: schema,
2334
+ field: field,
2335
+ val: val,
2336
+ modifyContent: modifyContent
2337
+ }), canModified && /*#__PURE__*/React.createElement(Icon, {
2441
2338
  icon: "fas fa-edit"
2442
2339
  })), isEdit && /*#__PURE__*/React.createElement("div", {
2443
2340
  className: "field",
@@ -2465,7 +2362,7 @@ function EditableField({
2465
2362
  className: "is-small",
2466
2363
  onClick: () => setEdit(false),
2467
2364
  dataTest: "editableFieldBtnCancelTest"
2468
- }, "Cancel")), !required && haveDeleteBtn && /*#__PURE__*/React.createElement("div", {
2365
+ }, "Cancel")), !required && fieldHaveDeleteButton(schema) && /*#__PURE__*/React.createElement("div", {
2469
2366
  className: "control"
2470
2367
  }, /*#__PURE__*/React.createElement(Button, {
2471
2368
  className: "is-small is-danger",
@@ -2474,6 +2371,155 @@ function EditableField({
2474
2371
  }, "Delete")))));
2475
2372
  }
2476
2373
 
2374
+ const DownloadField = ({
2375
+ value
2376
+ }) => {
2377
+ const Ctx = useTraversal();
2378
+ const {
2379
+ data,
2380
+ field
2381
+ } = value;
2382
+
2383
+ const getField = async downloadFile => {
2384
+ const endpoint = `${Ctx.path}@download/${field}`;
2385
+ const res = await Ctx.client.download(endpoint);
2386
+ const text = await res.blob();
2387
+ const blob = new Blob([text], {
2388
+ type: data.content_type
2389
+ });
2390
+ const url = window.URL.createObjectURL(blob); // Create blob link to download
2391
+
2392
+ const link = document.createElement('a');
2393
+ link.href = url;
2394
+
2395
+ if (downloadFile) {
2396
+ link.setAttribute('download', `${data.filename}`);
2397
+ } else {
2398
+ link.setAttribute('target', `_blank`);
2399
+ }
2400
+
2401
+ document.body.appendChild(link);
2402
+ link.click();
2403
+ setTimeout(function () {
2404
+ var _link$parentNode;
2405
+
2406
+ // For Firefox it is necessary to delay revoking the ObjectURL
2407
+ window.URL.revokeObjectURL(url);
2408
+ (_link$parentNode = link.parentNode) == null ? void 0 : _link$parentNode.removeChild(link);
2409
+ }, 100);
2410
+ };
2411
+
2412
+ return /*#__PURE__*/createElement("div", {
2413
+ className: "field"
2414
+ }, /*#__PURE__*/createElement("div", {
2415
+ className: "label"
2416
+ }, data.filename), /*#__PURE__*/createElement("div", {
2417
+ className: "columns"
2418
+ }, /*#__PURE__*/createElement("div", {
2419
+ className: "column"
2420
+ }, /*#__PURE__*/createElement("button", {
2421
+ className: "button is-small is-primary level-left",
2422
+ onClick: async event => {
2423
+ event.preventDefault();
2424
+ event.stopPropagation();
2425
+ getField(false);
2426
+ }
2427
+ }, "Open")), /*#__PURE__*/createElement("div", {
2428
+ className: "column"
2429
+ }, /*#__PURE__*/createElement("button", {
2430
+ className: "button is-small is-primary level-right",
2431
+ onClick: async event => {
2432
+ event.preventDefault();
2433
+ event.stopPropagation();
2434
+ getField(true);
2435
+ }
2436
+ }, "Download"))));
2437
+ };
2438
+
2439
+ const plain = ['string', 'number', 'boolean'];
2440
+ function RenderField({
2441
+ value,
2442
+ Widget
2443
+ }) {
2444
+ if (value === null || value === undefined) return '';
2445
+
2446
+ if (Widget) {
2447
+ return /*#__PURE__*/React.createElement(Widget, {
2448
+ value: value
2449
+ });
2450
+ }
2451
+
2452
+ const type = typeof value;
2453
+
2454
+ if (plain.includes(type)) {
2455
+ return value;
2456
+ }
2457
+
2458
+ if (type === 'object') {
2459
+ if (Array.isArray(value)) {
2460
+ return value.map(item => /*#__PURE__*/React.createElement("div", {
2461
+ key: item
2462
+ }, /*#__PURE__*/React.createElement(RenderField, {
2463
+ value: item
2464
+ })));
2465
+ }
2466
+
2467
+ return Object.keys(value).map(key => /*#__PURE__*/React.createElement(FieldValue, {
2468
+ field: key,
2469
+ value: value[key],
2470
+ key: key
2471
+ }));
2472
+ }
2473
+
2474
+ return /*#__PURE__*/React.createElement("p", null, "No render for ", JSON.stringify(value));
2475
+ }
2476
+
2477
+ const FieldValue = ({
2478
+ field,
2479
+ value
2480
+ }) => /*#__PURE__*/React.createElement("div", {
2481
+ className: "field"
2482
+ }, /*#__PURE__*/React.createElement("div", {
2483
+ className: "label"
2484
+ }, field), /*#__PURE__*/React.createElement("div", {
2485
+ className: "value"
2486
+ }, /*#__PURE__*/React.createElement(RenderField, {
2487
+ value: value
2488
+ })));
2489
+
2490
+ const DEFAULT_VALUE_EDITABLE_FIELD = 'Click to edit';
2491
+ const DEFAULT_VALUE_NO_EDITABLE_FIELD = ' -- ';
2492
+ function RenderFieldComponent({
2493
+ schema,
2494
+ field,
2495
+ val,
2496
+ modifyContent
2497
+ }) {
2498
+ const getRenderProps = () => {
2499
+ const renderProps = {
2500
+ value: val != null ? val : modifyContent ? DEFAULT_VALUE_EDITABLE_FIELD : DEFAULT_VALUE_NO_EDITABLE_FIELD
2501
+ };
2502
+
2503
+ if (val && (schema == null ? void 0 : schema.widget) === 'file') {
2504
+ renderProps['value'] = {
2505
+ data: val,
2506
+ field: field
2507
+ };
2508
+ renderProps['Widget'] = DownloadField;
2509
+ } else if ((schema == null ? void 0 : schema.type) === 'boolean') {
2510
+ var _val$toString;
2511
+
2512
+ renderProps['value'] = (_val$toString = val == null ? void 0 : val.toString()) != null ? _val$toString : renderProps['value'];
2513
+ } else if (val && (schema == null ? void 0 : schema.type) === 'datetime') {
2514
+ renderProps['value'] = new Date(val).toLocaleString();
2515
+ }
2516
+
2517
+ return renderProps;
2518
+ };
2519
+
2520
+ return /*#__PURE__*/React.createElement(RenderField, getRenderProps());
2521
+ }
2522
+
2477
2523
  function IAttachment({
2478
2524
  properties,
2479
2525
  values
@@ -2916,7 +2962,9 @@ function ItemsActionsProvider({
2916
2962
  * and it select/unselect all items of the page.
2917
2963
  */
2918
2964
 
2919
- function AllItemsCheckbox() {
2965
+ function AllItemsCheckbox({
2966
+ dataTest
2967
+ }) {
2920
2968
  const {
2921
2969
  onSelectAllItems,
2922
2970
  selected
@@ -2927,7 +2975,8 @@ function AllItemsCheckbox() {
2927
2975
  style: {
2928
2976
  marginLeft: 2
2929
2977
  },
2930
- value: selected.all
2978
+ value: selected.all,
2979
+ dataTest: dataTest
2931
2980
  });
2932
2981
  }
2933
2982
  /**
@@ -2935,7 +2984,8 @@ function AllItemsCheckbox() {
2935
2984
  */
2936
2985
 
2937
2986
  function ItemCheckbox({
2938
- item
2987
+ item,
2988
+ dataTest
2939
2989
  }) {
2940
2990
  const {
2941
2991
  selected,
@@ -2946,7 +2996,8 @@ function ItemCheckbox({
2946
2996
  return /*#__PURE__*/React.createElement(Checkbox, {
2947
2997
  key: value,
2948
2998
  onChange: () => onSelectOneItem(item),
2949
- value: value
2999
+ value: value,
3000
+ dataTest: dataTest
2950
3001
  });
2951
3002
  }
2952
3003
  /**
@@ -2970,7 +3021,9 @@ function ItemsActionsDropdown() {
2970
3021
  onChange: onAction,
2971
3022
  optionDisabledWhen: o => actions[o.value].perms.some(perm => !traversal.hasPerm(perm)),
2972
3023
  options: options
2973
- }, "Choose action...");
3024
+ }, /*#__PURE__*/React.createElement("div", {
3025
+ "data-test": "btnChooseActionTest"
3026
+ }, "Choose action..."));
2974
3027
  }
2975
3028
 
2976
3029
  /* eslint jsx-a11y/anchor-is-valid: "off" */
@@ -3190,7 +3243,8 @@ function RItem({
3190
3243
  }, /*#__PURE__*/React.createElement("td", {
3191
3244
  style: smallcss
3192
3245
  }, /*#__PURE__*/React.createElement(ItemCheckbox, {
3193
- item: item
3246
+ item: item,
3247
+ dataTest: "itemCheckboxRowTest"
3194
3248
  })), columns.map(i => /*#__PURE__*/React.createElement(React.Fragment, {
3195
3249
  key: i.label
3196
3250
  }, i.child(model, link, search))), /*#__PURE__*/React.createElement("td", {
@@ -4734,7 +4788,7 @@ function CopyItems(props) {
4734
4788
 
4735
4789
  return /*#__PURE__*/React.createElement(PathTree, {
4736
4790
  title: "Copy to...",
4737
- defaultPath: `/${Ctx.path.split(getContainerFromPath(Ctx.path))[1]}`,
4791
+ defaultPath: Ctx.client.clearContainerFromPath(Ctx.path),
4738
4792
  onConfirm: copyItems,
4739
4793
  onCancel: () => Ctx.cancelAction()
4740
4794
  }, items.map(item => /*#__PURE__*/React.createElement(React.Fragment, {
@@ -4747,11 +4801,14 @@ function CopyItems(props) {
4747
4801
  }, `New id for "${item.id}" copy`), /*#__PURE__*/React.createElement("input", {
4748
4802
  type: "text",
4749
4803
  className: "input",
4804
+ "data-test": `inputCopyIdTest-${item['@name']}`,
4750
4805
  defaultValue: getNewId(item.id)
4751
4806
  }))), "\xA0");
4752
4807
  }
4753
4808
 
4754
4809
  function CopyItem(props) {
4810
+ var _item$parent$Name;
4811
+
4755
4812
  const Ctx = useTraversal();
4756
4813
  const {
4757
4814
  post
@@ -4782,7 +4839,7 @@ function CopyItem(props) {
4782
4839
 
4783
4840
  return /*#__PURE__*/React.createElement(PathTree, {
4784
4841
  title: "Copy to...",
4785
- defaultPath: `/${item['parent']['@name']}`,
4842
+ defaultPath: `/${(_item$parent$Name = item['parent']['@name']) != null ? _item$parent$Name : '/'}`,
4786
4843
  onConfirm: copyItem,
4787
4844
  onCancel: () => Ctx.cancelAction()
4788
4845
  }, /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("small", {
@@ -4793,6 +4850,7 @@ function CopyItem(props) {
4793
4850
  }, `New id for "${item['@name']}" copy`), /*#__PURE__*/React.createElement("input", {
4794
4851
  type: "text",
4795
4852
  className: "input",
4853
+ "data-test": `inputCopyIdTest-${item['@name']}`,
4796
4854
  defaultValue: getNewId(item['@name'])
4797
4855
  })));
4798
4856
  }
@@ -5196,7 +5254,7 @@ function GroupCtx() {
5196
5254
  }, /*#__PURE__*/React.createElement("h3", {
5197
5255
  className: "title is-size-6"
5198
5256
  }, "Users"), /*#__PURE__*/React.createElement("p", null, "Add a User"), /*#__PURE__*/React.createElement(SearchInput, {
5199
- path: getContainerFromPath(Ctx.path),
5257
+ path: Ctx.containerPath,
5200
5258
  qs: [...searchParsed, ...sortParsed],
5201
5259
  traversal: Ctx,
5202
5260
  onChange: addUser,
@@ -5310,7 +5368,8 @@ function Path() {
5310
5368
  key: indx
5311
5369
  }, /*#__PURE__*/React.createElement("a", {
5312
5370
  href: path,
5313
- onClick: onClick
5371
+ onClick: onClick,
5372
+ "data-test": `breadcrumbItemTest-home`
5314
5373
  }, /*#__PURE__*/React.createElement("span", {
5315
5374
  className: "icon"
5316
5375
  }, /*#__PURE__*/React.createElement("i", {
@@ -5473,7 +5532,8 @@ let registry = {
5473
5532
  properties: {},
5474
5533
  components: {
5475
5534
  Path: Path,
5476
- EditComponent: EditComponent
5535
+ EditComponent: EditComponent,
5536
+ RenderFieldComponent: RenderFieldComponent
5477
5537
  },
5478
5538
  searchEngineQueryParamsFunction: {
5479
5539
  PostreSQL: 'getQueryParamsPostresql',
@@ -5815,132 +5875,113 @@ const ERRORS = {
5815
5875
  failed_to_fetch: 'Failed to fetch data: Backend not running?',
5816
5876
  invalid_credentials: 'Failed! Invalid credentials'
5817
5877
  };
5818
- class Login extends Component {
5819
- constructor(props) {
5820
- var _this;
5821
-
5822
- super(props);
5823
- _this = this;
5824
- this.state = {
5825
- username: '',
5826
- password: '',
5827
- loading: undefined,
5828
- schema: '',
5829
- errors: undefined
5830
- };
5831
- this.ref = React.createRef();
5832
-
5833
- this.doLogin = async function (ev) {
5834
- ev.preventDefault();
5835
-
5836
- _this.setState({
5837
- loading: true,
5838
- errors: undefined
5839
- });
5840
-
5841
- const {
5842
- username,
5843
- password,
5844
- schema
5845
- } = _this.state;
5846
-
5847
- if (schema !== '') {
5848
- _this.props.auth.setAccount(schema);
5849
- }
5850
-
5851
- const auth = _this.props.auth;
5852
- const res = await auth.login(username, password);
5878
+ const initialState$4 = {
5879
+ username: '',
5880
+ password: '',
5881
+ loading: undefined,
5882
+ errors: undefined
5883
+ };
5884
+ const Login = ({
5885
+ currentSchema,
5886
+ setCurrentSchema,
5887
+ schemas,
5888
+ auth,
5889
+ onLogin
5890
+ }) => {
5891
+ const [state, setState] = useSetState(initialState$4);
5892
+ const inputRef = useRef(null);
5893
+ useEffect(() => {
5894
+ if (inputRef) {
5895
+ inputRef.current.focus();
5896
+ }
5897
+ }, [inputRef]);
5853
5898
 
5854
- if (!res) {
5855
- _this.setState({
5856
- errors: auth.errors,
5857
- loading: false
5858
- });
5899
+ const doLogin = async ev => {
5900
+ ev.preventDefault();
5901
+ setState({
5902
+ loading: true,
5903
+ errors: undefined
5904
+ });
5905
+ const {
5906
+ username,
5907
+ password
5908
+ } = state;
5859
5909
 
5860
- return;
5861
- }
5910
+ if (currentSchema !== '') {
5911
+ auth.setAccount(currentSchema);
5912
+ }
5862
5913
 
5863
- if (_this.props.onLogin) {
5864
- _this.props.onLogin();
5865
- }
5866
- };
5914
+ const res = await auth.login(username, password);
5867
5915
 
5868
- if (props.schemas) {
5869
- this.state.schema = props.schemas[0];
5916
+ if (!res) {
5917
+ setState({
5918
+ errors: auth.errors,
5919
+ loading: false
5920
+ });
5921
+ return;
5870
5922
  }
5871
- }
5872
-
5873
- componentDidMount() {
5874
- this.ref.current.focus();
5875
- }
5876
5923
 
5877
- render() {
5878
- let {
5879
- password,
5880
- username,
5881
- errors
5882
- } = this.state;
5883
- const {
5884
- schemas
5885
- } = this.props;
5886
- return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("form", {
5887
- className: "login__form",
5888
- action: "",
5889
- onSubmit: this.doLogin,
5890
- "data-test": "formLoginTest"
5891
- }, /*#__PURE__*/React.createElement("div", {
5892
- className: "field"
5893
- }, /*#__PURE__*/React.createElement("label", {
5894
- className: "label"
5895
- }, "Username:"), /*#__PURE__*/React.createElement("input", {
5896
- type: "text",
5897
- className: "input",
5898
- placeholder: "Username",
5899
- onChange: e => this.setState({
5900
- username: e.target.value
5901
- }),
5902
- value: username,
5903
- ref: this.ref,
5904
- "data-test": "inputUsernameLoginTest"
5905
- })), /*#__PURE__*/React.createElement("div", {
5906
- className: "field"
5907
- }, /*#__PURE__*/React.createElement("label", {
5908
- className: "label"
5909
- }, "Password:"), /*#__PURE__*/React.createElement("input", {
5910
- type: "password",
5911
- className: "input",
5912
- onChange: e => this.setState({
5913
- password: e.target.value
5914
- }),
5915
- value: password,
5916
- "data-test": "inputPasswordLoginTest"
5917
- })), schemas && /*#__PURE__*/React.createElement("div", {
5918
- className: "field"
5919
- }, /*#__PURE__*/React.createElement("label", {
5920
- className: "label"
5921
- }, "Schema:"), /*#__PURE__*/React.createElement("div", {
5922
- className: "select"
5923
- }, /*#__PURE__*/React.createElement("select", {
5924
- onChange: e => this.setState({
5925
- schema: e.target.value
5926
- })
5927
- }, schemas.map(s => /*#__PURE__*/React.createElement("option", {
5928
- value: s,
5929
- key: s
5930
- }, s))))), /*#__PURE__*/React.createElement("div", {
5931
- className: "field"
5932
- }, /*#__PURE__*/React.createElement("button", {
5933
- className: "button is-warning",
5934
- type: "submit",
5935
- "data-test": "btnLoginTest"
5936
- }, "Login")), /*#__PURE__*/React.createElement("div", {
5937
- className: "field"
5938
- }, errors && /*#__PURE__*/React.createElement("p", {
5939
- className: "has-text-danger"
5940
- }, ERRORS[errors] || 'Generic error'))));
5941
- }
5924
+ if (onLogin) {
5925
+ onLogin();
5926
+ }
5927
+ };
5942
5928
 
5943
- }
5929
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("form", {
5930
+ className: "login__form",
5931
+ action: "",
5932
+ onSubmit: doLogin,
5933
+ "data-test": "formLoginTest"
5934
+ }, /*#__PURE__*/React.createElement("div", {
5935
+ className: "field"
5936
+ }, /*#__PURE__*/React.createElement("label", {
5937
+ className: "label"
5938
+ }, "Username:"), /*#__PURE__*/React.createElement("input", {
5939
+ type: "text",
5940
+ className: "input",
5941
+ placeholder: "Username",
5942
+ onChange: e => setState({
5943
+ username: e.target.value
5944
+ }),
5945
+ value: state.username,
5946
+ ref: inputRef,
5947
+ "data-test": "inputUsernameLoginTest"
5948
+ })), /*#__PURE__*/React.createElement("div", {
5949
+ className: "field"
5950
+ }, /*#__PURE__*/React.createElement("label", {
5951
+ className: "label"
5952
+ }, "Password:"), /*#__PURE__*/React.createElement("input", {
5953
+ type: "password",
5954
+ className: "input",
5955
+ placeholder: "Password",
5956
+ onChange: e => setState({
5957
+ password: e.target.value
5958
+ }),
5959
+ value: state.password,
5960
+ "data-test": "inputPasswordLoginTest"
5961
+ })), schemas && schemas.length > 1 && /*#__PURE__*/React.createElement("div", {
5962
+ className: "field"
5963
+ }, /*#__PURE__*/React.createElement("label", {
5964
+ className: "label"
5965
+ }, "Schema:"), /*#__PURE__*/React.createElement("div", {
5966
+ className: "select"
5967
+ }, /*#__PURE__*/React.createElement("select", {
5968
+ "data-test": "selectSchemaTest",
5969
+ onChange: e => setCurrentSchema(e.target.value)
5970
+ }, schemas.map(s => /*#__PURE__*/React.createElement("option", {
5971
+ value: s,
5972
+ key: s
5973
+ }, s))))), /*#__PURE__*/React.createElement("div", {
5974
+ className: "field"
5975
+ }, /*#__PURE__*/React.createElement("button", {
5976
+ className: "button is-warning",
5977
+ type: "submit",
5978
+ "data-test": "btnLoginTest"
5979
+ }, "Login")), /*#__PURE__*/React.createElement("div", {
5980
+ className: "field"
5981
+ }, state.errors && /*#__PURE__*/React.createElement("p", {
5982
+ className: "has-text-danger"
5983
+ }, ERRORS[state.errors] || 'Generic error'))));
5984
+ };
5944
5985
 
5945
5986
  const ignoreFiels = [];
5946
5987
  const extraFields = ['title'];
@@ -6219,5 +6260,5 @@ class Auth {
6219
6260
 
6220
6261
  }
6221
6262
 
6222
- export { ACTIONS_OBJECT, AddItem, AddPermission, AllItemsCheckbox, ApplicationCtx, Auth, AuthContext, BaseForm, BehaviorNotImplemented, BehaviorsView, Button, Checkbox, ClientContext, ClientProvider, Config, Confirm, ContainerCtx, ContextToolbar, CreateButton, CreateContainer, DEFAULT_VALUE_EDITABLE_FIELD, DEFAULT_VALUE_NO_EDITABLE_FIELD, DatabaseCtx, Delete, DownloadField, EditableField, EmailInput, FileUpload, Flash, FolderCtx, Form, FormBuilder, GroupCtx, GroupToolbar, GroupsCtx, Guillotina, GuillotinaClient, IAttachment, IDublinCore, IMultiAttachment, Icon, Input, InputList, Item, ItemCheckbox, ItemCtx, ItemModel, ItemTitle, ItemsActionsDropdown, ItemsActionsProvider, Layout, Link, Loading, Login, Modal, NotAllowed, Notification, Pagination, PanelActions, PanelAddons, PanelBehaviors, PanelItems, PanelNotImplemented, PanelPermissions, PanelProperties, PasswordInput, Path, PathTree, PermissionPrinperm, PermissionPrinrole, PermissionRoleperm, Permissions, PropertiesButtonView, PropertiesView, REGEX_EMAIL, REGEX_HEX_COLOR, REGEX_NUMBER, REGEX_URL, RItem, RemoveItems, RenderField, RequiredFieldsForm, RestClient, SearchInput, SearchLabels, Select, Sharing, Table, TabsPanel, Tag, TagsWidget, TdLink, Textarea, TraversalContext, TraversalProvider, UserCtx, UserForm, UsersCtx, UsersToolbar, actions, base64ToArrayBuffer, buildQs, classnames, defaultComponent, formatDate, generateUID, get$1 as get, getClient, getContainerFromPath, getNewId, isEmail, isEmpty, isHexColor, isNumber, isURL, lightFileReader, maxLength, minLength, noop, notEmpty, parser, sleep, stringToSlug, toQueryString, useConfig, useCrudContext, useGuillotinaClient, useLocation, useRegistry, useRemoteField, useTraversal };
6263
+ export { ACTIONS_OBJECT, AddItem, AddPermission, AllItemsCheckbox, ApplicationCtx, Auth, AuthContext, BaseForm, BehaviorNotImplemented, BehaviorsView, Button, Checkbox, ClientContext, ClientProvider, Config, Confirm, ContainerCtx, ContextToolbar, CreateButton, CreateContainer, DEFAULT_VALUE_EDITABLE_FIELD, DEFAULT_VALUE_NO_EDITABLE_FIELD, DatabaseCtx, Delete, DownloadField, EditableField, EmailInput, FileUpload, Flash, FolderCtx, Form, FormBuilder, GroupCtx, GroupToolbar, GroupsCtx, Guillotina, GuillotinaClient, IAttachment, IDublinCore, IMultiAttachment, Icon, Input, InputList, Item, ItemCheckbox, ItemCtx, ItemModel, ItemTitle, ItemsActionsDropdown, ItemsActionsProvider, Layout, Link, Loading, Login, Modal, NotAllowed, Notification, Pagination, PanelActions, PanelAddons, PanelBehaviors, PanelItems, PanelNotImplemented, PanelPermissions, PanelProperties, PasswordInput, Path, PathTree, PermissionPrinperm, PermissionPrinrole, PermissionRoleperm, Permissions, PropertiesButtonView, PropertiesView, REGEX_EMAIL, REGEX_HEX_COLOR, REGEX_NUMBER, REGEX_URL, RItem, RemoveItems, RenderField, RenderFieldComponent, RequiredFieldsForm, RestClient, SearchInput, SearchLabels, Select, Sharing, Table, TabsPanel, Tag, TagsWidget, TdLink, Textarea, TraversalContext, TraversalProvider, UserCtx, UserForm, UsersCtx, UsersToolbar, actions, base64ToArrayBuffer, buildQs, classnames, defaultComponent, formatDate, generateUID, get$1 as get, getClient, getNewId, isEmail, isEmpty, isHexColor, isNumber, isURL, lightFileReader, maxLength, minLength, noop, notEmpty, parser, sleep, stringToSlug, toQueryString, useConfig, useCrudContext, useGuillotinaClient, useLocation, useRegistry, useRemoteField, useTraversal };
6223
6264
  //# sourceMappingURL=react-gmi.modern.js.map