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.
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.css +2 -0
- package/dist/index.css.map +1 -0
- package/dist/index.modern.js +2 -0
- package/dist/index.modern.js.map +1 -0
- package/dist/index.umd.js +2 -0
- package/dist/index.umd.js.map +1 -0
- package/package.json +27 -0
- package/publish.sh +5 -0
- package/src/css/fonts.css +162 -0
- package/src/css/html.css +36 -0
- package/src/css/theme.css +89 -0
- package/src/css/theme_dark.css +85 -0
- package/src/css/theme_light.css +87 -0
- package/src/domain/CollectionPage.css +34 -0
- package/src/domain/CollectionPage.js +346 -0
- package/src/domain/ContentEditor.css +174 -0
- package/src/domain/ContentEditor.js +425 -0
- package/src/domain/ContentForm.js +74 -0
- package/src/domain/ContentType.js +187 -0
- package/src/domain/CreateContentDialog.js +59 -0
- package/src/domain/EditContentDialog.js +50 -0
- package/src/domain/TablePage.css +29 -0
- package/src/domain/TablePage.js +395 -0
- package/src/domain/index.js +5 -0
- package/src/fonts/Assistant-Bold.ttf +0 -0
- package/src/fonts/Assistant-ExtraBold.ttf +0 -0
- package/src/fonts/Assistant-ExtraLight.ttf +0 -0
- package/src/fonts/Assistant-Light.ttf +0 -0
- package/src/fonts/Assistant-Medium.ttf +0 -0
- package/src/fonts/Assistant-Regular.ttf +0 -0
- package/src/fonts/Assistant-SemiBold.ttf +0 -0
- package/src/fonts/Assistant-VariableFont_wght.ttf +0 -0
- package/src/html/button.css +79 -0
- package/src/html/button.js +26 -0
- package/src/html/checkbox.css +51 -0
- package/src/html/checkbox.js +33 -0
- package/src/html/chip.css +63 -0
- package/src/html/chip.js +39 -0
- package/src/html/form.css +17 -0
- package/src/html/form.js +80 -0
- package/src/html/header.css +64 -0
- package/src/html/header.js +30 -0
- package/src/html/icon.css +53 -0
- package/src/html/icon.js +21 -0
- package/src/html/index.js +18 -0
- package/src/html/list.css +72 -0
- package/src/html/list.js +78 -0
- package/src/html/menu.css +76 -0
- package/src/html/menu.js +80 -0
- package/src/html/progress.css +20 -0
- package/src/html/progress.js +27 -0
- package/src/html/property.css +18 -0
- package/src/html/property.js +16 -0
- package/src/html/radio.css +50 -0
- package/src/html/radio.js +25 -0
- package/src/html/section.css +6 -0
- package/src/html/section.js +31 -0
- package/src/html/tab.css +45 -0
- package/src/html/tab.js +68 -0
- package/src/html/table.css +56 -0
- package/src/html/table.js +186 -0
- package/src/html/text.js +20 -0
- package/src/html/textfield-outlined.css +52 -0
- package/src/html/textfield.css +130 -0
- package/src/html/textfield.js +99 -0
- package/src/html/tokenfield.css +51 -0
- package/src/html/tokenfield.js +74 -0
- package/src/html/tree.css +63 -0
- package/src/html/tree.js +49 -0
- package/src/http/client.js +62 -0
- package/src/http/index.js +2 -0
- package/src/http/session.js +39 -0
- package/src/index.js +9 -0
- package/src/site/details.css +58 -0
- package/src/site/dialog.css +63 -0
- package/src/site/dialog.js +43 -0
- package/src/site/index.js +3 -0
- package/src/site/layouts.css +27 -0
- package/src/site/page.css +44 -0
- package/src/site/page.js +36 -0
- package/src/site/site.css +85 -0
- package/src/site/site.js +234 -0
- package/src/site/siteContext.js +4 -0
- package/src/site/workspace.js +57 -0
- package/src/upload/UploadArea.js +64 -0
- package/src/upload/UploadDialog.js +41 -0
- package/src/upload/UploadFile.js +31 -0
- package/src/upload/index.js +1 -0
- package/src/upload/uploader.css +57 -0
- package/src/upload/uploader.js +69 -0
- package/src/widgets/index.js +4 -0
- package/src/widgets/kanban/Kanban.css +80 -0
- package/src/widgets/kanban/Kanban.js +65 -0
- package/src/widgets/login/LoginBox.css +89 -0
- package/src/widgets/login/LoginBox.js +66 -0
- package/src/widgets/login/ResetPasswordBox.css +50 -0
- package/src/widgets/login/ResetPasswordBox.js +56 -0
- package/src/widgets/viewer/Viewer.css +87 -0
- 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
|
+
|
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
|
+
}
|