@onehat/ui 0.3.7 → 0.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onehat/ui",
3
- "version": "0.3.7",
3
+ "version": "0.3.9",
4
4
  "description": "Base UI for OneHat apps",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -25,14 +25,17 @@
25
25
  },
26
26
  "license": "UNLICENSED",
27
27
  "dependencies": {
28
- "@onehat/data": "^1.19.0",
28
+ "@gluestack-style/react": "^0.2.38",
29
+ "@gluestack-ui/themed": "^0.1.21",
29
30
  "@hookform/resolvers": "^3.3.1",
30
31
  "@k-renwick/colour-mixer": "^1.2.1",
32
+ "@onehat/data": "^1.19.0",
31
33
  "@reduxjs/toolkit": "^1.9.5",
32
- "js-cookie": "^3.0.5",
33
34
  "inflector-js": "^1.0.1",
35
+ "js-cookie": "^3.0.5",
34
36
  "native-base": "^3.4.28",
35
37
  "react-hook-form": "^7.45.0",
38
+ "react-native-svg": "^13.13.0",
36
39
  "react-redux": "^8.1.2",
37
40
  "yup": "^1.2.0"
38
41
  },
@@ -42,10 +45,10 @@
42
45
  "react": "*",
43
46
  "react-color": "^2.19.3",
44
47
  "react-datetime": "^3.2.0",
45
- "react-draggable": "^4.4.5",
46
- "react-native-draggable": "^3.3.0",
47
48
  "react-dom": "*",
48
- "react-native": "*"
49
+ "react-draggable": "^4.4.5",
50
+ "react-native": "*",
51
+ "react-native-draggable": "^3.3.0"
49
52
  },
50
53
  "devDependencies": {
51
54
  "@babel/core": "^7.22.1",
@@ -25,6 +25,9 @@ export default function Viewer(props) {
25
25
  // withData
26
26
  record,
27
27
 
28
+ // parent container
29
+ selectorSelected,
30
+
28
31
  // withEditor
29
32
  onEditMode,
30
33
  onClose,
@@ -39,7 +42,7 @@ export default function Viewer(props) {
39
42
  let {
40
43
  type,
41
44
  title = null,
42
- selectorId,
45
+ selectorId = null,
43
46
  ...propsToPass
44
47
  } = item;
45
48
  const
@@ -89,7 +89,7 @@ function Form(props) {
89
89
  onDelete,
90
90
  editorStateRef,
91
91
 
92
- // DataMgt
92
+ // parent container
93
93
  selectorId,
94
94
  selectorSelected,
95
95
 
@@ -115,10 +115,10 @@ function Form(props) {
115
115
  // resetField,
116
116
  // setError,
117
117
  // clearErrors,
118
- // setValue,
118
+ setValue: formSetValue,
119
119
  // setFocus,
120
- getValues,
121
- getFieldState,
120
+ getValues: formGetValues,
121
+ // getFieldState,
122
122
  // trigger,
123
123
  } = useForm({
124
124
  mode: 'onChange', // onChange | onBlur | onSubmit | onTouched | all
@@ -204,6 +204,7 @@ function Form(props) {
204
204
  if (_.isPlainObject(editor)) {
205
205
  const {
206
206
  type,
207
+ onChange: onEditorChange,
207
208
  ...p
208
209
  } = editor;
209
210
  editorProps = p;
@@ -217,6 +218,9 @@ function Form(props) {
217
218
  value={value}
218
219
  setValue={(newValue) => {
219
220
  onChange(newValue);
221
+ if (onEditorChange) {
222
+ onEditorChange(newValue, formSetValue, formGetValues, formState);
223
+ }
220
224
  }}
221
225
  onBlur={onBlur}
222
226
  selectorId={selectorId}
@@ -256,6 +260,7 @@ function Form(props) {
256
260
  isEditable = true,
257
261
  label,
258
262
  items,
263
+ onChange: onEditorChange,
259
264
  ...propsToPass
260
265
  } = item;
261
266
  let editorTypeProps = {};
@@ -363,10 +368,10 @@ function Form(props) {
363
368
  let element = <Element
364
369
  name={name}
365
370
  value={value}
366
- onChangeValue={(value) => {
367
- onChange(value); // form onChange handler
368
- if (propsToPass.onChange) {
369
- propsToPass.onChange(value); // item onChange handler
371
+ onChangeValue={(newValue) => {
372
+ onChange(newValue);
373
+ if (onEditorChange) {
374
+ onEditorChange(newValue, formSetValue, formGetValues, formState);
370
375
  }
371
376
  }}
372
377
  onBlur={onBlur}
@@ -419,7 +424,7 @@ function Form(props) {
419
424
  Element = getComponentFromType(type),
420
425
  element = <Element
421
426
  selectorId={selectorId}
422
- selectorSelected={selectorId ? record : selectorSelected}
427
+ selectorSelected={selectorSelected || record}
423
428
  flex={1}
424
429
  {...propsToPass}
425
430
  />;
@@ -30,7 +30,7 @@ export default function withEditor(WrappedComponent, isTree = false) {
30
30
  },
31
31
  record,
32
32
 
33
- // DataMgt
33
+ // parent container
34
34
  selectorId,
35
35
  selectorSelected,
36
36
 
@@ -63,7 +63,7 @@ export default function withPresetButtons(WrappedComponent, isGrid = false) {
63
63
  selection,
64
64
  setSelection,
65
65
 
66
- // DataMgt
66
+ // parent container
67
67
  selectorId,
68
68
  selectorSelected,
69
69
  } = props,
@@ -6,6 +6,7 @@ const
6
6
  FOCUS = '#ffd';
7
7
 
8
8
  const defaults = {
9
+ ATTACHMENTS_MAX_FILESIZE: 1024 * 1024 * 5, // 5MB
9
10
  FILTER_LABEL_FONTSIZE: DEFAULT_FONTSIZE,
10
11
  FORM_ANCILLARY_TITLE_FONTSIZE: 22,
11
12
  FORM_COLOR_READOUT_FONTSIZE: DEFAULT_FONTSIZE,
@@ -1,25 +1,31 @@
1
- import React, { useState, useEffect, useRef, } from 'react';
1
+ import { useState, useEffect, } from 'react';
2
2
  import {
3
3
  Box,
4
- Icon,
4
+ Button,
5
+ Column,
5
6
  Row,
6
- Text,
7
- Tooltip,
8
7
  } from 'native-base';
9
8
  import {
10
9
  CURRENT_MODE,
11
10
  UI_MODE_WEB,
12
11
  UI_MODE_REACT_NATIVE,
13
- } from '../../../Constants/UiModes.js';
14
- import UiGlobals from '../../../UiGlobals.js';
12
+ } from '../../Constants/UiModes.js';
13
+ import UiGlobals from '../../UiGlobals.js';
15
14
  import {
16
15
  FILE_MODE_IMAGE,
17
16
  FILE_MODE_FILE,
18
- } from '../../../Constants/File.js';
17
+ } from '../../Constants/File.js';
19
18
  import { Avatar, Dropzone, FileMosaic, FileCard, FileInputButton, } from "@files-ui/react";
20
19
  import withData from '../../Components/Hoc/withData.js';
21
20
  import _ from 'lodash';
22
21
 
22
+ const
23
+ EXPANDED_MAX = 100,
24
+ COLLAPSED_MAX = 2;
25
+
26
+ // Note this component uploads only one file per server request---
27
+ // it doesn't upload multiple files simultaneously.
28
+
23
29
  function AttachmentsElement(props) {
24
30
 
25
31
  if (CURRENT_MODE !== UI_MODE_WEB) {
@@ -30,93 +36,217 @@ function AttachmentsElement(props) {
30
36
  canCrud = true,
31
37
  _dropZone = {},
32
38
  _fileMosaic = {},
33
- accept = '*', // 'image/*'
39
+ accept, // 'image/*'
34
40
  maxFiles = null,
35
- maxFileSize = 28 * 1024,
36
41
  disabled = false,
37
42
  clickable = true,
38
- isImageOnly = false,
43
+
44
+ // parentContainer
45
+ selectorSelected,
39
46
 
40
47
  // withData
41
48
  Repository,
42
49
 
43
50
  } = props,
44
51
  styles = UiGlobals.styles,
45
- WhichFile = isImageOnly ? Avatar : FileMosaic,
46
- onDelete = (a,b,c,d,e) => {
52
+ model = selectorSelected?.repository.name,
53
+ modelid = selectorSelected?.id,
54
+ [isReady, setIsReady] = useState(false),
55
+ [isUploading, setIsUploading] = useState(false),
56
+ [showAll, setShowAll] = useState(false),
57
+ [files, setFiles] = useState([]),
58
+ buildFiles = () => {
59
+ const files = _.map(Repository.entities, (entity) => {
60
+ return {
61
+ id: entity.id, // string | number The identifier of the file
62
+ // file: null, // File The file object obtained from client drop or selection
63
+ name: entity.attachments__filename, // string The name of the file
64
+ type: entity.attachments__mimetype, // string The file mime type.
65
+ size: entity.attachments__size, // number The size of the file in bytes.
66
+ // valid: null, // boolean If present, it will show a valid or rejected message ("valid", "denied"). By default valid is undefined.
67
+ // errors: null, // string[] The list of errors according to the validation criteria or the result of the given custom validation function.
68
+ // uploadStatus: null, // UPLOADSTATUS The current upload status. (e.g. "uploading").
69
+ // uploadMessage: null, // string A message that shows the result of the upload process.
70
+ imageUrl: entity.attachments__uri, // string A string representation or web url of the image that will be set to the "src" prop of an <img/> tag. If given, the component will use this image source instead of reading the image file.
71
+ downloadUrl: entity.attachments__uri, // string The url to be used to perform a GET request in order to download the file. If defined, the download icon will be shown.
72
+ // progress: null, // number The current percentage of upload progress. This value will have a higher priority over the upload progress value calculated inside the component.
73
+ // extraUploadData: null, // Record<string, any> The additional data that will be sent to the server when files are uploaded individually
74
+ // extraData: null, // Object Any kind of extra data that could be needed.
75
+ // serverResponse: null, // ServerResponse The upload response from server.
76
+ // xhr: null, // XMLHttpRequest A reference to the XHR object that allows the upload, progress and abort events.
77
+ };
78
+ });
79
+ setFiles(files);
80
+ },
81
+ toggleShowAll = () => {
82
+ setShowAll(!showAll);
83
+ },
84
+ onDropzoneChange = (files) => {
85
+ setFiles(files);
86
+ _.each(files, (file) => {
87
+ file.extraUploadData = {
88
+ model,
89
+ modelid,
90
+ };
91
+ });
92
+ },
93
+ onUploadStart = (files) => {
94
+ setIsUploading(true);
95
+ },
96
+ onUploadFinish = (files) => {
97
+ let isDoneUploading = true;
98
+
99
+ _.each(files, (file) => {
100
+ if (!file.xhr || file.xhr.status !== 200) {
101
+ isDoneUploading = false;
102
+ return false; // break
103
+ }
104
+ });
105
+
106
+ if (isDoneUploading) {
107
+ setIsUploading(false);
108
+ Repository.reload();
109
+ }
110
+ },
111
+ onFileDelete = (id) => {
112
+ Repository.deleteById(id);
113
+ };
114
+
115
+ useEffect(() => {
116
+
117
+ if (!model) {
118
+ return () => {};
119
+ }
120
+
121
+ (async () => {
47
122
 
123
+ // Load Repository
124
+ const filters = [
125
+ {
126
+ name: 'model',
127
+ value: model,
128
+ },
129
+ {
130
+ name: 'modelid',
131
+ value: modelid,
132
+ },
133
+ ];
134
+ if (accept) {
135
+ let name,
136
+ mimetypes;
137
+ if (_.isString(accept)) {
138
+ name = 'mimetype LIKE';
139
+ mimetypes = accept.replace('*', '%');
140
+ } else if (_.isArray(accept)) {
141
+ name = 'mimetype IN';
142
+ mimetypes = accept;
143
+ }
144
+ filters.push({
145
+ name,
146
+ value: mimetypes,
147
+ });
148
+ }
149
+ Repository.filter(filters);
150
+ Repository.setPageSize(showAll ? EXPANDED_MAX : COLLAPSED_MAX);
151
+ await Repository.load();
152
+
153
+ buildFiles();
154
+
155
+ if (!isReady) {
156
+ setIsReady(true);
157
+ }
158
+
159
+ })();
160
+
161
+ Repository.on('load', buildFiles);
162
+ return () => {
163
+ Repository.off('load', buildFiles);
48
164
  };
165
+ }, [model, modelid, showAll]);
166
+
167
+ if (!isReady) {
168
+ return null;
169
+ }
170
+
49
171
 
50
172
  if (canCrud) {
51
- return <Dropzone
52
- value={files}
53
- onChange={updateFiles}
54
- accept={accept}
55
- maxFiles={maxFiles}
56
- maxFileSize={maxFileSize}
57
- validator={() => {}}
58
- autoClean={true}
59
- uploadConfig={{
60
- url: Repository.api.baseURL + Repository.name + '/uploadAttachments',
61
- method: 'POST',
62
- headers: Repository.headers,
63
- autoUpload: true,
64
- }}
65
- onUploadFinish={handleFinishUpload}
66
- background={styles.ATTACHMENTS_BG}
67
- color={styles.ATTACHMENTS_COLOR}
68
- minHeight={150}
69
- clickable={clickable}
70
- {..._dropZone}
71
- >
72
- {files.map((file) => {
73
- // const ExtFile = {
74
- // id string | number The identifier of the file
75
- // file File The file object obtained from client drop or selection
76
- // name string The name of the file
77
- // type string The file mime type.
78
- // size number The size of the file in bytes.
79
- // valid boolean If present, it will show a valid or rejected message ("valid", "denied"). By default valid is undefined.
80
- // errors string[] The list of errors according to the validation criteria or the result of the given custom validation function.
81
- // uploadStatus UPLOADSTATUS The current upload status. (e.g. "uploading").
82
- // uploadMessage string A message that shows the result of the upload process.
83
- // imageUrl string A string representation or web url of the image that will be set to the "src" prop of an <img/> tag. If given, the component will use this image source instead of reading the image file.
84
- // downloadUrl string The url to be used to perform a GET request in order to download the file. If defined, the download icon will be shown.
85
- // progress number The current percentage of upload progress. This value will have a higher priority over the upload progress value calculated inside the component.
86
- // extraUploadData Record<string, any> The additional data that will be sent to the server when files are uploaded individually
87
- // extraData Object Any kind of extra data that could be needed.
88
- // serverResponse ServerResponse The upload response from server.
89
- // xhr XMLHttpRequest A reference to the XHR object that allows the upload, progress and abort events.
90
- // };
91
- return <WhichFile
92
- key={file.id}
93
- {...file}
94
- backgroundBlurImage={false}
95
- onDelete={onDelete}
96
- info
97
- {..._fileMosaic}
98
- />;
99
- })}
100
- </Dropzone>;
173
+ _fileMosaic.onDelete = onFileDelete;
174
+ }
175
+ let content = <Column
176
+ w="100%"
177
+ // minHeight={50}
178
+ p={2}
179
+ background={styles.ATTACHMENTS_BG}
180
+ >
181
+ <Row flexWrap="wrap">
182
+ {files.map((file) => {
183
+ return <Box
184
+ key={file.id}
185
+ marginRight={4}
186
+ >
187
+ <FileMosaic
188
+ {...file}
189
+ backgroundBlurImage={false}
190
+ {..._fileMosaic}
191
+ />
192
+ </Box>;
193
+ })}
194
+ </Row>
195
+ {Repository.total <= COLLAPSED_MAX ? null :
196
+ <Button
197
+ onPress={toggleShowAll}
198
+ marginTop={4}
199
+ _text={{
200
+ color: 'trueGray.600',
201
+ fontStyle: 'italic',
202
+ textAlign: 'left',
203
+ width: '100%',
204
+ }}
205
+ variant="ghost"
206
+ >{'Show ' + (showAll ? ' Less' : ' All ' + Repository.total)}</Button>}
207
+ </Column>;
208
+
209
+ if (canCrud) {
210
+ content = <Dropzone
211
+ value={files}
212
+ onChange={onDropzoneChange}
213
+ accept={accept}
214
+ maxFiles={maxFiles}
215
+ maxFileSize={styles.ATTACHMENTS_MAX_FILESIZE}
216
+ autoClean={true}
217
+ uploadConfig={{
218
+ url: Repository.api.baseURL + Repository.name + '/uploadAttachment',
219
+ method: 'POST',
220
+ headers: Repository.headers,
221
+ autoUpload: true,
222
+ }}
223
+ headerConfig={{
224
+ deleteFiles: false,
225
+ }}
226
+ onUploadStart={onUploadStart}
227
+ onUploadFinish={onUploadFinish}
228
+ background={styles.ATTACHMENTS_BG}
229
+ color={styles.ATTACHMENTS_COLOR}
230
+ minHeight={150}
231
+ footer={false}
232
+ clickable={clickable}
233
+ {..._dropZone}
234
+ >
235
+ {content}
236
+ </Dropzone>;
101
237
 
102
238
  }
103
-
104
- return <Row
105
- flex={1}
106
- minHeight={150}
107
- background={styles.ATTACHMENTS_BG}
108
- color={styles.ATTACHMENTS_COLOR}
109
- >
110
- {files.map((file) => {
111
- return <WhichFile
112
- key={file.id}
113
- {...file}
114
- onDelete={removeFile}
115
- info
116
- {..._fileMosaic}
117
- />;
118
- })}
119
- </Row>;
239
+ return content;
240
+ }
241
+
242
+ function withAdditionalProps(WrappedComponent) {
243
+ return (props) => {
244
+ return <WrappedComponent
245
+ model="Attachments"
246
+ uniqueRepository={true}
247
+ {...props}
248
+ />;
249
+ };
120
250
  }
121
251
 
122
- export default withData(AttachmentsElement);
252
+ export default withAdditionalProps(withData(AttachmentsElement));