ywana-core8 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/dist/index.cjs +2 -0
  2. package/dist/index.cjs.map +1 -0
  3. package/dist/index.css +2 -0
  4. package/dist/index.css.map +1 -0
  5. package/dist/index.modern.js +2 -0
  6. package/dist/index.modern.js.map +1 -0
  7. package/dist/index.umd.js +2 -0
  8. package/dist/index.umd.js.map +1 -0
  9. package/package.json +27 -0
  10. package/publish.sh +5 -0
  11. package/src/css/fonts.css +162 -0
  12. package/src/css/html.css +36 -0
  13. package/src/css/theme.css +89 -0
  14. package/src/css/theme_dark.css +85 -0
  15. package/src/css/theme_light.css +87 -0
  16. package/src/domain/CollectionPage.css +34 -0
  17. package/src/domain/CollectionPage.js +346 -0
  18. package/src/domain/ContentEditor.css +174 -0
  19. package/src/domain/ContentEditor.js +425 -0
  20. package/src/domain/ContentForm.js +74 -0
  21. package/src/domain/ContentType.js +187 -0
  22. package/src/domain/CreateContentDialog.js +59 -0
  23. package/src/domain/EditContentDialog.js +50 -0
  24. package/src/domain/TablePage.css +29 -0
  25. package/src/domain/TablePage.js +395 -0
  26. package/src/domain/index.js +5 -0
  27. package/src/fonts/Assistant-Bold.ttf +0 -0
  28. package/src/fonts/Assistant-ExtraBold.ttf +0 -0
  29. package/src/fonts/Assistant-ExtraLight.ttf +0 -0
  30. package/src/fonts/Assistant-Light.ttf +0 -0
  31. package/src/fonts/Assistant-Medium.ttf +0 -0
  32. package/src/fonts/Assistant-Regular.ttf +0 -0
  33. package/src/fonts/Assistant-SemiBold.ttf +0 -0
  34. package/src/fonts/Assistant-VariableFont_wght.ttf +0 -0
  35. package/src/html/button.css +79 -0
  36. package/src/html/button.js +26 -0
  37. package/src/html/checkbox.css +51 -0
  38. package/src/html/checkbox.js +33 -0
  39. package/src/html/chip.css +63 -0
  40. package/src/html/chip.js +39 -0
  41. package/src/html/form.css +17 -0
  42. package/src/html/form.js +80 -0
  43. package/src/html/header.css +64 -0
  44. package/src/html/header.js +30 -0
  45. package/src/html/icon.css +53 -0
  46. package/src/html/icon.js +21 -0
  47. package/src/html/index.js +18 -0
  48. package/src/html/list.css +72 -0
  49. package/src/html/list.js +78 -0
  50. package/src/html/menu.css +76 -0
  51. package/src/html/menu.js +80 -0
  52. package/src/html/progress.css +20 -0
  53. package/src/html/progress.js +27 -0
  54. package/src/html/property.css +18 -0
  55. package/src/html/property.js +16 -0
  56. package/src/html/radio.css +50 -0
  57. package/src/html/radio.js +25 -0
  58. package/src/html/section.css +6 -0
  59. package/src/html/section.js +31 -0
  60. package/src/html/tab.css +45 -0
  61. package/src/html/tab.js +68 -0
  62. package/src/html/table.css +56 -0
  63. package/src/html/table.js +186 -0
  64. package/src/html/text.js +20 -0
  65. package/src/html/textfield-outlined.css +52 -0
  66. package/src/html/textfield.css +130 -0
  67. package/src/html/textfield.js +99 -0
  68. package/src/html/tokenfield.css +51 -0
  69. package/src/html/tokenfield.js +74 -0
  70. package/src/html/tree.css +63 -0
  71. package/src/html/tree.js +49 -0
  72. package/src/http/client.js +62 -0
  73. package/src/http/index.js +2 -0
  74. package/src/http/session.js +39 -0
  75. package/src/index.js +9 -0
  76. package/src/site/details.css +58 -0
  77. package/src/site/dialog.css +63 -0
  78. package/src/site/dialog.js +43 -0
  79. package/src/site/index.js +3 -0
  80. package/src/site/layouts.css +27 -0
  81. package/src/site/page.css +44 -0
  82. package/src/site/page.js +36 -0
  83. package/src/site/site.css +85 -0
  84. package/src/site/site.js +234 -0
  85. package/src/site/siteContext.js +4 -0
  86. package/src/site/workspace.js +57 -0
  87. package/src/upload/UploadArea.js +64 -0
  88. package/src/upload/UploadDialog.js +41 -0
  89. package/src/upload/UploadFile.js +31 -0
  90. package/src/upload/index.js +1 -0
  91. package/src/upload/uploader.css +57 -0
  92. package/src/upload/uploader.js +69 -0
  93. package/src/widgets/index.js +4 -0
  94. package/src/widgets/kanban/Kanban.css +80 -0
  95. package/src/widgets/kanban/Kanban.js +65 -0
  96. package/src/widgets/login/LoginBox.css +89 -0
  97. package/src/widgets/login/LoginBox.js +66 -0
  98. package/src/widgets/login/ResetPasswordBox.css +50 -0
  99. package/src/widgets/login/ResetPasswordBox.js +56 -0
  100. package/src/widgets/viewer/Viewer.css +87 -0
  101. package/src/widgets/viewer/Viewer.js +47 -0
@@ -0,0 +1,425 @@
1
+ import React, { Fragment, useState } from 'react';
2
+ import { Button, CheckBox, DataTable, DropDown, Icon, Stack, Tab, Tabs, Text, TextField, Tree, TreeNode, TreeItem, TokenField, Property } from '../html';
3
+ import { Content, TYPES } from './ContentType';
4
+ import './ContentEditor.css';
5
+
6
+ /**
7
+ * Content Editor
8
+ */
9
+ export const ContentEditor = ({ content, filter, onChange }) => {
10
+
11
+ function change(id, value) {
12
+ const nextValue = Object.assign({}, content.value(), { [id]: value })
13
+ if (onChange) onChange(nextValue)
14
+ }
15
+
16
+ const sections = content.sections()
17
+
18
+ return (
19
+ <div className='content-editor'>
20
+ {sections.map((section) => {
21
+ const { title, fields } = section
22
+ return (
23
+ <section key={title}>
24
+ <header>{title}</header>
25
+ <main>
26
+ {fields
27
+ .filter(field => field.id !== 'id')
28
+ .filter(field => filter ? filter(field, content) : true)
29
+ .map((field) => <FieldEditor key={field.id} field={field} onChange={change} />)}
30
+ </main>
31
+ </section>
32
+ )
33
+ })}
34
+ </div>
35
+ )
36
+ }
37
+
38
+ /**
39
+ * Tabbed Content Editor
40
+ */
41
+ export const TabbedContentEditor = ({ content, filter, grouped = false, onChange }) => {
42
+
43
+ const [tab, setTab] = useState(0)
44
+
45
+ function change(id, value) {
46
+ const nextValue = Object.assign({}, content.value(), { [id]: value })
47
+ if (onChange) onChange(nextValue)
48
+ }
49
+
50
+ function changeTab(tab) {
51
+ setTab(tab)
52
+ }
53
+
54
+ function group(section) {
55
+ const { fields = [] } = section
56
+ const groups = fields.reduce((groups, field) => {
57
+ const groupName = field.group || ""
58
+ const group = groups[groupName] || { name: groupName, fields: [] }
59
+ group.fields.push(field)
60
+ groups[groupName] = group
61
+ return groups
62
+ }, {})
63
+ return Object.values(groups)
64
+ }
65
+
66
+ const sections = content.sections()
67
+
68
+ return (
69
+ <div className='content-editor tabbed'>
70
+ <Tabs selected={tab} onChange={changeTab}>
71
+ {sections.map(section => <Tab label={section.title} />)}
72
+ </Tabs>
73
+ <Stack selected={tab}>
74
+ {sections.map(section => {
75
+ const { title, fields } = section
76
+ return (
77
+ <section key={title}>
78
+ <nav>
79
+ &nbsp;
80
+ </nav>
81
+ <main>
82
+ {
83
+ grouped ?
84
+ group(section)
85
+ .map(group => {
86
+ return (
87
+ <Fragment>
88
+ {group.name.length > 0 ? <div className="group-caption">{group.name}</div> : null}
89
+ {
90
+ group.fields
91
+ .filter(field => field.id !== 'id')
92
+ .filter(field => filter ? filter(field) : true)
93
+ .map((field) => <FieldEditor key={field.id} field={field} onChange={change} content={content} />)
94
+ }
95
+ </Fragment>
96
+ )
97
+ })
98
+ :
99
+ fields
100
+ .filter(field => field.id !== 'id')
101
+ .filter(field => filter ? filter(field) : true)
102
+ .map((field) => <FieldEditor key={field.id} field={field} onChange={change} content={content} />)
103
+ }
104
+ </main>
105
+ </section>
106
+ )
107
+ })}
108
+ </Stack>
109
+ </div>
110
+ )
111
+ }
112
+
113
+ /**
114
+ * TreededContentEditor
115
+ */
116
+ export const TreededContentEditor = ({ content, filter, onChange }) => {
117
+
118
+
119
+ const value = content.value()
120
+ const nodes = Object.values(content.type).filter(field => field.type === TYPES.ARRAY)
121
+ const [selected, setSelected] = useState()
122
+
123
+ function select(index, field, node) {
124
+ const item = new Content(node.item, field)
125
+ setSelected({ index, item, node, value: value[node.id] })
126
+ }
127
+
128
+ function change(form) {
129
+ const nextNode = selected.value.slice()
130
+ nextNode[selected.index] = form
131
+ const nextContent = Object.assign({}, value, { [selected.node.id]: nextNode })
132
+ if (onChange) onChange(nextContent)
133
+ }
134
+
135
+ function add() {
136
+ //TODO
137
+ }
138
+
139
+ return (
140
+ <div className="content-editor treeded-editor">
141
+ <menu>
142
+ <header>
143
+ <Text use='caption'>Tree Editor</Text>
144
+ </header>
145
+ <Tree>
146
+ {nodes.map((node, index) => (
147
+ <TreeNode icon="folder" label={node.label} actions={[<Icon small icon="add" clickable action={add} />]}>
148
+ {
149
+ value[node.id] ? value[node.id].map((field, index) => <TreeItem icon={field.icon || "description"} label={field.name || field.label} onSelect={() => select(index, field, node)} />) : null
150
+ }
151
+ </TreeNode>
152
+ ))}
153
+ </Tree>
154
+ </menu>
155
+ <div>
156
+ {selected ? <TabbedContentEditor content={selected.item} onChange={change} /> : "select"}
157
+ </div>
158
+ </div>
159
+ )
160
+ }
161
+
162
+ /**
163
+ * FieldEditor
164
+ */
165
+ export const FieldEditor = ({ field, onChange, content, outlined = false }) => {
166
+ const { id, type, item, label, editable, options } = field
167
+
168
+ function change(id, value) {
169
+ if (onChange) onChange(id, value)
170
+ }
171
+
172
+ function renderField() {
173
+ const value1 = field.value ? field.value : field.default
174
+ switch (type) {
175
+ case TYPES.ENTITY:
176
+ return <EntityEditor field={field} value={value1} onChange={change} />
177
+ case TYPES.STRING:
178
+ return <StringEditor outlined={outlined} field={field} value={value1} onChange={change} content={content} />
179
+ case TYPES.BOOLEAN:
180
+ return <CheckBox outlined id={id} label={label} value={value1} onChange={change} />
181
+ case TYPES.DATE:
182
+ return <TextField outlined={outlined} id={id} type='date' label={label} value={value1} onChange={change} disabled={editable} />
183
+ case TYPES.NUMBER:
184
+ return <NumberEditor outlined={outlined} field={field} value={value1} onChange={change} />
185
+ case TYPES.ARRAY:
186
+ return item === TYPES.STRING ?
187
+ options ? <MultiSelectionEditor content={content} field={field} value={value1} onChange={change} /> : <ListEditor field={field} value={value1} onChange={change} />
188
+ : <CollectionEditor field={field} value={value1} onChange={change} />
189
+ default:
190
+ return <div>{label}</div>
191
+ }
192
+ }
193
+ return renderField()
194
+ }
195
+
196
+ /**
197
+ * Entity Editor
198
+ */
199
+ const EntityEditor = ({ field, value = {}, onChange }) => {
200
+ const { id, item, label } = field
201
+ const content = new Content(item, value)
202
+
203
+ function change(fid, value) {
204
+ const next = Object.assign({}, content.value(), { [fid]: value })
205
+ if (onChange) onChange(id, next)
206
+ }
207
+
208
+ const form = content.form()
209
+ const fields = Object.keys(form).map((key) => form[key])
210
+
211
+ return (
212
+ <div className='entity-editor'>
213
+ <header>
214
+ <Text use='caption'>{label}</Text>
215
+ </header>
216
+ <main>
217
+ {fields.map((field) => (
218
+ <FieldEditor key={field.id} field={field} onChange={change} />
219
+ ))}
220
+ </main>
221
+ </div>
222
+ )
223
+ }
224
+
225
+ /**
226
+ * String Editor
227
+ */
228
+ export const StringEditor = ({ field, value = '', onChange, content, outlined }) => {
229
+ const { id, label, options, editable = true, filter } = field
230
+
231
+ function change(id, value) {
232
+ if (onChange) onChange(id, value)
233
+ }
234
+
235
+ function buildOptions() {
236
+ const opts = typeof options === 'function' ? options(field, content) : options
237
+ return opts
238
+ }
239
+
240
+ return (
241
+ <div className='field-editor string-editor'>
242
+ {
243
+ editable ?
244
+ options ? (
245
+ <DropDown outlined={outlined} id={id} label={label} value={value} onChange={change} options={buildOptions()} />
246
+ ) : (
247
+ <TextField outlined={outlined} id={id} label={label} value={value} onChange={change} />
248
+ ) : (
249
+ <TextField outlined={outlined} id={id} label={label} value={value} onChange={change} readOnly={true} />
250
+ )
251
+ }
252
+ </div>
253
+ )
254
+ }
255
+
256
+ /**
257
+ * Number Editor
258
+ */
259
+ const NumberEditor = ({ field, value, onChange }) => {
260
+ const { id, label, editable = true } = field
261
+
262
+ function change(id, value) {
263
+ if (onChange) onChange(id, value)
264
+ }
265
+
266
+ const val = value || field.default
267
+ const min = field.min
268
+ const max = field.max
269
+ const disabled = !editable
270
+
271
+ return (
272
+ <div className='field-editor number-editor'>
273
+ <TextField outlined id={id} label={label} type='NUMBER' value={val} max={max} min={min} onChange={change} disabled={disabled} />
274
+ </div>
275
+ )
276
+ }
277
+
278
+ /**
279
+ * List Editor
280
+ */
281
+ export const ListEditor = ({ field, value = [], onChange }) => {
282
+
283
+ const { label } = field
284
+
285
+ function change(id, value) {
286
+ if (onChange) onChange(id, value)
287
+ }
288
+
289
+ return (
290
+ <div className="list-editor">
291
+ <TokenField id={field.id} label={label} init={value} onChange={change} />
292
+ </div>
293
+ )
294
+ }
295
+
296
+ /**
297
+ * Multi Selection Editor
298
+ */
299
+ export const MultiSelectionEditor = ({ field, value = [], content, onChange }) => {
300
+
301
+ const { id, label, options } = field
302
+
303
+ function change(v) {
304
+ const index = value.indexOf(v)
305
+ if (index >= 0) {
306
+ value.splice(index, 1)
307
+ } else {
308
+ value.push(v)
309
+ }
310
+ if (onChange) onChange(id, value)
311
+ }
312
+
313
+ function buildOptions() {
314
+ const opts = typeof options === 'function' ? options(field, content) : options
315
+ return opts
316
+ }
317
+
318
+ return (
319
+ <div className="multiselection-editor">
320
+ <label>{label}</label>
321
+ {buildOptions().map(option => {
322
+ const checked = value.includes(option.value)
323
+ return <CheckBox value={checked} label={option.label} onChange={() => change(option.value)} />
324
+ })}
325
+ </div>
326
+ )
327
+
328
+ }
329
+
330
+ /**
331
+ * Collection Editor
332
+ */
333
+ export const CollectionEditor = ({ field, value = [], onChange }) => {
334
+
335
+ const { id, item, label, Feeder, Renderer, Adder = true, editable = true } = field
336
+
337
+ function add(rows) {
338
+ if (onChange) {
339
+ const next = value.concat(rows)
340
+ onChange(id, next)
341
+ }
342
+ }
343
+
344
+ function remove(index) {
345
+ if (onChange) {
346
+ const next = value.filter((item, i) => i !== index)
347
+ onChange(id, next)
348
+ }
349
+ }
350
+
351
+ function change(index, cellId, cellValue) {
352
+ if (onChange) {
353
+ const next = value.slice()
354
+ next[index][cellId] = cellValue
355
+ onChange(id, next)
356
+ }
357
+ }
358
+
359
+ const columns = Object.values(item).map((item) => ({ ...item, onChange: change }))
360
+ columns.push({ id: 'actions', label: 'Actions' })
361
+
362
+ const rows = value.map((item, index) => ({
363
+ ...item,
364
+ id: index,
365
+ actions: [
366
+ <Icon icon='delete' clickable action={() => remove(index)} small />
367
+ ]
368
+ }))
369
+
370
+ const table = { columns, rows }
371
+
372
+ return (
373
+ <div className='collection-editor'>
374
+ <header>
375
+ <Text use='caption'>{label}</Text>
376
+ <div className="actions" >
377
+ {Feeder ? <Feeder onAdd={add} /> : null}
378
+ </div>
379
+ </header>
380
+ {Renderer ? <Renderer field={field} value={value} onRemove={remove} onChange={onChange} /> : <DataTable {...table} editable={editable} />}
381
+ <footer>
382
+ {Adder ? <CollectionAdder item={item} onAdd={add} /> : null}
383
+ </footer>
384
+ </div>
385
+ )
386
+ }
387
+
388
+ /**
389
+ * Collection Adder
390
+ */
391
+ const CollectionAdder = ({ item, onAdd }) => {
392
+
393
+ const [edit, setEdit] = useState(false)
394
+ const [form, setForm] = useState({})
395
+
396
+ function toggle() {
397
+ setEdit(!edit)
398
+ }
399
+
400
+ function change(index, cellId, cellValue) {
401
+ if (onAdd) {
402
+ const next = Object.assign({}, form, { [cellId]: cellValue })
403
+ setForm(next)
404
+ }
405
+ }
406
+
407
+ function save() {
408
+ if (onAdd) onAdd([form])
409
+ toggle()
410
+ }
411
+
412
+ const columns = Object.values(item).map((item) => ({ ...item, type: 'String', onChange: change }))
413
+ columns.push({ id: 'actions', label: '' })
414
+ const rows = [{
415
+ actions: [<Icon icon="done" clickable action={save} />, <Icon icon="close" clickable action={toggle} />]
416
+ }]
417
+ rows[0] = Object.assign({}, form, rows[0])
418
+ const table = { columns, rows }
419
+
420
+ return (
421
+ <div className="collection-adder">
422
+ {edit ? <DataTable {...table} editable={true} /> : <Button icon='add' label='Add' action={toggle} />}
423
+ </div>
424
+ )
425
+ }
@@ -0,0 +1,74 @@
1
+ import React from 'react';
2
+ import { TYPES } from './ContentType';
3
+ import { TextField, CheckBox, DropDown, Form } from '../html';
4
+
5
+ /**
6
+ * Content Form
7
+ */
8
+ export const ContentForm = ({ content, columns = 1, filter, rules, onChange }) => {
9
+
10
+ const form = content.form()
11
+ console.log(form)
12
+
13
+ const fields = Object.keys(form)
14
+ .map(key => form[key])
15
+ .filter( field => filter ? filter(field) : true)
16
+
17
+ console.log(fields)
18
+
19
+ return (
20
+ <Form className="content-form" columns={columns} outlined={true} onChange={onChange}>
21
+ { fields.map(field => <ContentFormField key={field.id} {...field} />)}
22
+ </Form>
23
+ )
24
+ }
25
+
26
+ /**
27
+ * Content Form Field
28
+ */
29
+ const ContentFormField = (props) => {
30
+ const { type, label } = props
31
+ switch (type) {
32
+ case TYPES.BOOLEAN:
33
+ return <CheckBox key={props.id} {...props} />
34
+ case TYPES.NUMBER:
35
+ return <NumberField key={props.id} {...props} />
36
+ default:
37
+ return <StringField key={props.id} {...props} />
38
+ }
39
+ }
40
+
41
+ /**
42
+ * String Editor
43
+ */
44
+ export const StringField = ({ id, type, label, options, textarea, required = false, value, hidden, onChange, outlined }) => {
45
+
46
+ const fieldTypes = {
47
+ [TYPES.NUMBER] : 'number',
48
+ [TYPES.DATE] : 'date',
49
+ }
50
+
51
+ function buildOptions() {
52
+ const opts = typeof options === 'function' ? options() : options
53
+ return opts
54
+ }
55
+
56
+ const fieldType = fieldTypes[type] || 'text'
57
+
58
+ if (options) return <DropDown key={id} id={id} label={label} value={value} onChange={onChange} options={buildOptions()} outlined={outlined} />
59
+ return <TextField key={id} id={id} type={fieldType} label={label} value={value} onChange={onChange} required={required} outlined={outlined} textarea={textarea}/>
60
+ }
61
+
62
+ /**
63
+ * Number Editor
64
+ */
65
+ export const NumberField = ({ id, label, options, required = false, value, onChange, outlined }) => {
66
+
67
+ function change(id, value) {
68
+ const num = !!+value ? +value : value
69
+ if (onChange) onChange(id, num)
70
+ }
71
+
72
+ if (options) return <DropDown key={id} id={id} label={label} value={value} onChange={change} options={options} outlined={outlined} />
73
+ return <TextField key={id} id={id} type="number" label={label} value={value} onChange={change} required={required} outlined={outlined} />
74
+ }
@@ -0,0 +1,187 @@
1
+ /**
2
+ * TYPES
3
+ */
4
+ export const TYPES = {
5
+ STRING: 'String',
6
+ NUMBER: 'Number',
7
+ DATE: 'Date',
8
+ BOOLEAN: 'Boolean',
9
+ ARRAY: 'Array',
10
+ ENTITY: 'Object',
11
+ FUNCTION: 'Function',
12
+ EMAIL: 'String'
13
+ }
14
+
15
+ /**
16
+ * CHECK
17
+ */
18
+ export const CHECK = Object.values(TYPES).reduce((obj, name) => {
19
+ obj['is' + name] = x => toString.call(x) == '[object ' + name + ']';
20
+ return obj;
21
+ }, {});
22
+
23
+ /**
24
+ * Content Type
25
+ */
26
+ export class ContentType {
27
+
28
+ constructor(schema) {
29
+ this._schema = schema
30
+ }
31
+
32
+ checkType({ type, item }, data) {
33
+ let valid = true
34
+ let validChildren = true
35
+ if (item) {
36
+ const child = new ContentType(item)
37
+ switch (type) {
38
+ case TYPES.ARRAY:
39
+ valid = CHECK['is' + type](data)
40
+ validChildren = data.every(element => child.validate(element))
41
+ break
42
+ case TYPES.ENTITY:
43
+ valid = CHECK['is' + type](data)
44
+ validChildren = child.validate(data)
45
+ break
46
+ case TYPES.NUMBER:
47
+ valid = CHECK['is' + type](data.value)
48
+ validChildren = true
49
+ break
50
+ default:
51
+ valid = true
52
+ validChildren = true;
53
+ }
54
+ }
55
+ return valid && validChildren
56
+ }
57
+
58
+ /**
59
+ * Validate entity data
60
+ * - Inspect required attributes only
61
+ * - Check attributes type
62
+ */
63
+ validate(data) {
64
+ const attributes = Object.entries(this._schema)
65
+ return attributes
66
+ .filter(([name, attr]) => attr.required === true)
67
+ .every(([name, attr]) => data[name] !== undefined ? this.checkType(attr, data[name]) : false)
68
+ }
69
+
70
+ /**
71
+ * Content Type Form
72
+ */
73
+ form(data) {
74
+
75
+ const entries = Object.entries(this._schema)
76
+
77
+ const form = entries.reduce((form, [id, field]) => {
78
+ form[id] = Object.assign({}, field, { value: data[id] })
79
+ return form
80
+ }, {})
81
+
82
+ return form
83
+ }
84
+
85
+ /**
86
+ * Content Type Sections
87
+ */
88
+ sections(data) {
89
+
90
+ const form = this.form(data)
91
+ const fields = Object.values(form)
92
+ const sections = fields.reduce((sections, field) => {
93
+ const { section } = field
94
+ const title = section ? section : ''
95
+ if (!sections.hasOwnProperty(title)) sections[title] = { title, fields: [] }
96
+ sections[title].fields.push(field)
97
+ return sections
98
+ }, {});
99
+
100
+ return Object.values(sections)
101
+ }
102
+
103
+ /**
104
+ * value
105
+ */
106
+ value(data) {
107
+
108
+ const fields = Object.entries(this._schema)
109
+ const next = fields.reduce((next, [name, field]) => {
110
+
111
+ const { type, item } = field
112
+ const entryData = data ? data[name] : null
113
+
114
+ switch (type) {
115
+
116
+ case TYPES.STRING:
117
+ next[name] = entryData || field.default
118
+ break;
119
+
120
+ case TYPES.NUMBER:
121
+ next[name] = entryData || field.default
122
+ break;
123
+
124
+ case TYPES.BOOLEAN:
125
+ next[name] = entryData || field.default
126
+ break
127
+
128
+ case TYPES.ENTITY:
129
+ const child1 = new ContentType(item)
130
+ next[name] = child1.value(entryData)
131
+ break;
132
+
133
+ case TYPES.ARRAY:
134
+ if (item === TYPES.STRING) {
135
+ next[name] = entryData ? entryData : []
136
+ } else {
137
+ const child2 = new ContentType(item)
138
+ next[name] = entryData ? entryData.map(data2 => child2.value(data2)) : []
139
+ }
140
+ break;
141
+
142
+ default:
143
+ next[name] = field
144
+ break;
145
+
146
+ }
147
+
148
+ return next
149
+
150
+ }, {})
151
+
152
+ return next
153
+ }
154
+ }
155
+
156
+
157
+ /**
158
+ * Content
159
+ */
160
+ export class Content {
161
+
162
+ constructor(type, value) {
163
+ this.type = type
164
+ this._type = new ContentType(type)
165
+ this._value = value
166
+ }
167
+
168
+ isValid() {
169
+ return this._type.validate(this._value)
170
+ }
171
+
172
+ value() {
173
+ return this._type.value(this._value)
174
+ }
175
+
176
+ form() {
177
+ return this._type.form(this._value)
178
+ }
179
+
180
+ sections() {
181
+ return this._type.sections(this._value)
182
+ }
183
+
184
+ update(value) {
185
+ this._value = value
186
+ }
187
+ }