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,41 @@
|
|
|
1
|
+
import React, { Fragment, useContext, useState } from 'react';
|
|
2
|
+
import { Uploader } from './uploader'
|
|
3
|
+
import { Button, Text } from '../html';
|
|
4
|
+
import { SiteContext, Dialog } from '../site';
|
|
5
|
+
import { UploadFile } from './UploadFile';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Upload Dialog
|
|
9
|
+
*/
|
|
10
|
+
export const UploadDialog = ({ label, target, accept, onSuccess, onOK, onError, onClose, onActionClose = true }) => {
|
|
11
|
+
|
|
12
|
+
const site = useContext(SiteContext);
|
|
13
|
+
const [file, setFile] = useState();
|
|
14
|
+
const [errors, setErrors] = useState([])
|
|
15
|
+
|
|
16
|
+
function onComplete(uploads) {
|
|
17
|
+
setFile(uploads[0]);
|
|
18
|
+
if (onSuccess) onSuccess(uploads[0])
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function onAction(action) {
|
|
22
|
+
if (action === 'CLOSE' || onActionClose === true) {
|
|
23
|
+
site.closeDialog();
|
|
24
|
+
onClose()
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const actions = (
|
|
29
|
+
<Fragment>
|
|
30
|
+
<Button label="CLOSE" action={() => onAction("CLOSE")} />
|
|
31
|
+
</Fragment>
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
const title = <Text use="headline6">{label}</Text>
|
|
35
|
+
return (
|
|
36
|
+
<Dialog title={title} open={true} onAction={onAction} actions={actions}>
|
|
37
|
+
{file ? <UploadFile file={file} /> : <Uploader label={label} accept={accept} target={target} onComplete={onComplete} />}
|
|
38
|
+
{errors.map(error => <Text use="overline" tag="div" className="error">{error}</Text>)}
|
|
39
|
+
</Dialog>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React, { Fragment } from 'react'
|
|
2
|
+
import { Icon, Text } from 'ywana-core7'
|
|
3
|
+
import { LinearProgress } from 'rmwc'
|
|
4
|
+
import './uploader.css'
|
|
5
|
+
import '@rmwc/linear-progress/styles';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Upload File
|
|
9
|
+
*/
|
|
10
|
+
export const UploadFile = ({ file, state, progress, error }) => {
|
|
11
|
+
|
|
12
|
+
const STATES = { 'IDLE': 0, 'RUNNING': 1, 'SUCCESS': 2, 'ERROR': 3 }
|
|
13
|
+
|
|
14
|
+
const icon = error ? <Icon icon="image" className="error"/> : <Icon icon="image" />
|
|
15
|
+
return state !== STATES.IDLE ? (
|
|
16
|
+
<Fragment>
|
|
17
|
+
|
|
18
|
+
<div className="upload-file">
|
|
19
|
+
{icon}
|
|
20
|
+
<Text use="body1" tag="label">{file.fileName}</Text>
|
|
21
|
+
{state === STATES.RUNNING ? <LinearProgress progress={progress} /> : ''}
|
|
22
|
+
{state === STATES.RUNNING ? <Icon icon="close" clickable /> : ''}
|
|
23
|
+
{state === STATES.SUCCESS ? <Icon icon="done" /> : ''}
|
|
24
|
+
{state === STATES.ERROR ? <Icon icon="error" /> : ''}
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
{ error ? <div className="error"><Text use="overline">{error}</Text></div> : ''}
|
|
28
|
+
|
|
29
|
+
</Fragment>
|
|
30
|
+
) : ''
|
|
31
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './uploader'
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
.uploader {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.demo-uploader {
|
|
7
|
+
width: 40rem;
|
|
8
|
+
height: 30rem;
|
|
9
|
+
margin: 5rem auto;
|
|
10
|
+
flex-direction: column;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/***************** Upload Area ***********************/
|
|
14
|
+
|
|
15
|
+
.upload-area6 {
|
|
16
|
+
flex: 1;
|
|
17
|
+
margin: 0 0 1rem 0;
|
|
18
|
+
border: dashed 2px var(--divider-color);
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-direction: column;
|
|
21
|
+
align-items: center;
|
|
22
|
+
justify-content: center;
|
|
23
|
+
border-radius: 1rem;
|
|
24
|
+
min-height: 10rem;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.upload-area6>label {
|
|
28
|
+
font-size: 1.1rem;
|
|
29
|
+
font-weight: 500;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.upload-area6.drag-over {
|
|
33
|
+
background-color: rgba(200,200,200,.1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/***************** Upload File ***********************/
|
|
37
|
+
|
|
38
|
+
.upload-file {
|
|
39
|
+
display: flex;
|
|
40
|
+
align-items: center;
|
|
41
|
+
min-height: 4rem;
|
|
42
|
+
padding: 0 1rem;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.upload-file>label {
|
|
46
|
+
padding: 0 1rem;
|
|
47
|
+
min-width: 40%;
|
|
48
|
+
flex: 1;
|
|
49
|
+
overflow: hidden;
|
|
50
|
+
text-overflow: ellipsis;
|
|
51
|
+
white-space: nowrap;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.error {
|
|
55
|
+
color: red;
|
|
56
|
+
text-align: center
|
|
57
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import React, { useState, useMemo } from 'react'
|
|
2
|
+
import ResumableJS from 'resumablejs'
|
|
3
|
+
import { UploadArea } from './UploadArea'
|
|
4
|
+
import { UploadFile } from './UploadFile'
|
|
5
|
+
import './uploader.css'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Uploader
|
|
9
|
+
*/
|
|
10
|
+
export const Uploader = ({ label, target, accept, className, onSuccess, onError, onComplete, errors = [] }) => {
|
|
11
|
+
|
|
12
|
+
const STATES = { 'IDLE': 0, 'RUNNING': 1, 'SUCCESS': 2, 'ERROR': 3, 'COMPLETED': 4 }
|
|
13
|
+
|
|
14
|
+
const resumable = useMemo(() => {
|
|
15
|
+
const config = {
|
|
16
|
+
target: target,
|
|
17
|
+
chunkSize: 1 * 1024 * 1024,
|
|
18
|
+
simultaneousUploads: 1,
|
|
19
|
+
testChunks: false,
|
|
20
|
+
throttleProgressCallbacks: 1,
|
|
21
|
+
fileType: accept
|
|
22
|
+
}
|
|
23
|
+
const resumable = new ResumableJS(config)
|
|
24
|
+
resumable.on('fileAdded', onFileAdded)
|
|
25
|
+
resumable.on('fileProgress', onFileProgress)
|
|
26
|
+
resumable.on('fileSuccess', onFileSuccess)
|
|
27
|
+
resumable.on('fileError', onFileError)
|
|
28
|
+
resumable.on('complete', onAllComplete)
|
|
29
|
+
return resumable
|
|
30
|
+
}, [])
|
|
31
|
+
|
|
32
|
+
const [progress, setProgress] = useState(0)
|
|
33
|
+
const [state, setState] = useState(STATES.IDLE)
|
|
34
|
+
const [error, setError] = useState()
|
|
35
|
+
const [files, setFiles] = useState([])
|
|
36
|
+
|
|
37
|
+
function onFileAdded(file) {
|
|
38
|
+
files.push(file)
|
|
39
|
+
setFiles(files)
|
|
40
|
+
resumable.upload()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function onFileProgress(file) {
|
|
44
|
+
const progress = file.progress()
|
|
45
|
+
setProgress(progress)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function onFileSuccess(file, message) {
|
|
49
|
+
setState(STATES.SUCCESS)
|
|
50
|
+
if (onSuccess) onSuccess(file, message)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function onFileError(file, message) {
|
|
54
|
+
setError(message)
|
|
55
|
+
setState(STATES.ERROR)
|
|
56
|
+
if (onError) onError(file, message)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function onAllComplete() {
|
|
60
|
+
setState(STATES.IDLE)
|
|
61
|
+
if (onComplete) onComplete(files)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<div className={`uploader ${className}`}>
|
|
66
|
+
<UploadArea resumable={resumable} state={state} label={label} error={error}/>
|
|
67
|
+
</div>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
.kanban {
|
|
2
|
+
display: flex;
|
|
3
|
+
min-height: 30rem;
|
|
4
|
+
background-color: var(--background-color);
|
|
5
|
+
overflow-x: auto;
|
|
6
|
+
max-height: 90vh;
|
|
7
|
+
border-top: solid 1px var(--divider-color);
|
|
8
|
+
border-left: solid 1px var(--divider-color);
|
|
9
|
+
border-bottom: solid 1px var(--divider-color);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.kanban-column {
|
|
13
|
+
flex: 1;
|
|
14
|
+
min-width: 20rem;
|
|
15
|
+
display: flex;
|
|
16
|
+
flex-direction: column;
|
|
17
|
+
border-right: solid 1px var(--divider-color);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.kanban-column.disabled {
|
|
21
|
+
pointer-events: none;
|
|
22
|
+
opacity: 0.7;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.kanban-column.dragOver {
|
|
26
|
+
background-color: rgba(100,100,100,.1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.kanban-column.min {
|
|
30
|
+
flex: 0;
|
|
31
|
+
min-width: 4rem;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.kanban-column>header {
|
|
35
|
+
height: 5rem;
|
|
36
|
+
border-bottom: solid 1px var(--divider-color);
|
|
37
|
+
display: flex;
|
|
38
|
+
align-items: flex-start;
|
|
39
|
+
padding: 1rem;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.kanban-column>header>.title {
|
|
43
|
+
padding: 0 1rem;
|
|
44
|
+
flex: 1;
|
|
45
|
+
display: flex;
|
|
46
|
+
flex-direction: column;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.kanban-column>header>i, .kanban-column>footer>button {
|
|
50
|
+
color: rgba(100, 100, 100, .6);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.kanban-column>main {
|
|
54
|
+
flex: 1;
|
|
55
|
+
overflow: auto;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.kanban-column.min>main {
|
|
59
|
+
min-height: 10rem;
|
|
60
|
+
padding: 1rem;
|
|
61
|
+
-webkit-writing-mode: vertical-rl;
|
|
62
|
+
writing-mode: vertical-rl;
|
|
63
|
+
color: var(--primary-color-light);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.kanban-card {
|
|
67
|
+
margin: 2rem;
|
|
68
|
+
box-shadow: var(--shadow1);
|
|
69
|
+
background-color: var(--paper-color);
|
|
70
|
+
border-radius: 5px;
|
|
71
|
+
min-height: 10rem;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.kanban-card.red {
|
|
75
|
+
border-left: solid 5px red;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.secondary-text {
|
|
79
|
+
color: rgba(100, 100, 100, .8);
|
|
80
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
import { Icon, Text } from '../../html'
|
|
3
|
+
import './Kanban.css'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Kanban
|
|
7
|
+
*/
|
|
8
|
+
export const Kanban = ({ children }) => {
|
|
9
|
+
return (
|
|
10
|
+
<div className="kanban">
|
|
11
|
+
{children}
|
|
12
|
+
</div>
|
|
13
|
+
)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Kanban Column
|
|
18
|
+
*/
|
|
19
|
+
export const KanbanColumn = ({ id, accept = [], icon, title, subtitle, badge, children, minified = false, disabled = false }) => {
|
|
20
|
+
|
|
21
|
+
const [min, setMin] = useState(minified)
|
|
22
|
+
|
|
23
|
+
function toggle() {
|
|
24
|
+
setMin(!min)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return min ? (
|
|
28
|
+
<div className={`kanban-column ${id} min`}>
|
|
29
|
+
<main>
|
|
30
|
+
{title}
|
|
31
|
+
</main>
|
|
32
|
+
<footer>
|
|
33
|
+
<Icon icon="toggle_off" onIcon="toggle_on" clickable action={toggle} />
|
|
34
|
+
</footer>
|
|
35
|
+
</div>
|
|
36
|
+
) : (
|
|
37
|
+
<div className={`kanban-column ${id} ${disabled ? 'disabled' : ''}`}>
|
|
38
|
+
<header>
|
|
39
|
+
<Icon icon={icon} />
|
|
40
|
+
<div className="title">
|
|
41
|
+
<Text use="headline6">{title}</Text>
|
|
42
|
+
<Text className="secondary-text" use="body2">{subtitle}</Text>
|
|
43
|
+
</div>
|
|
44
|
+
<div className="badge" >{badge}</div>
|
|
45
|
+
</header>
|
|
46
|
+
<main>
|
|
47
|
+
{children}
|
|
48
|
+
</main>
|
|
49
|
+
<footer>
|
|
50
|
+
<Icon icon="toggle_off" onIcon="toggle_on" clickable action={toggle} />
|
|
51
|
+
</footer>
|
|
52
|
+
</div>
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Kanban Card
|
|
58
|
+
*/
|
|
59
|
+
export const KanbanCard = ({ id, type, color, children }) => {
|
|
60
|
+
return (
|
|
61
|
+
<div className={`kanban-card ${color}`} >
|
|
62
|
+
{children}
|
|
63
|
+
</div>
|
|
64
|
+
)
|
|
65
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
.login-box {
|
|
2
|
+
background-color: var(--paper-color);
|
|
3
|
+
display: grid;
|
|
4
|
+
grid-template-areas:
|
|
5
|
+
"header header"
|
|
6
|
+
"main main"
|
|
7
|
+
"footer footer";
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.login-box > header {
|
|
11
|
+
grid-area: header;
|
|
12
|
+
display: flex;
|
|
13
|
+
flex: 1;
|
|
14
|
+
flex-direction: column;
|
|
15
|
+
justify-content: space-around;
|
|
16
|
+
align-items: center;
|
|
17
|
+
padding: 1rem;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.login-box > header > img {
|
|
21
|
+
max-width: 90%;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.login-box > header > .title {
|
|
25
|
+
font-size: 2rem;
|
|
26
|
+
font-weight: 600;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.login-box > main {
|
|
30
|
+
grid-area: main;
|
|
31
|
+
padding: 1rem 1rem 0 1rem;
|
|
32
|
+
display: flex;
|
|
33
|
+
flex-direction: column;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.login-box .message {
|
|
37
|
+
display: flex;
|
|
38
|
+
justify-content: center;
|
|
39
|
+
align-items: center;
|
|
40
|
+
padding-top: 1rem;
|
|
41
|
+
color: var(--danger-color-light);
|
|
42
|
+
font-weight: 500;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.load-box {
|
|
46
|
+
color: var(--primary-color-text);
|
|
47
|
+
background-color: var(--primary-color);
|
|
48
|
+
display: flex;
|
|
49
|
+
align-items: center;
|
|
50
|
+
justify-content: center;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.load-box .icon {
|
|
54
|
+
animation: rotation 2s infinite linear;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@keyframes rotation-counterclock {
|
|
58
|
+
from {
|
|
59
|
+
transform: rotate(359deg);
|
|
60
|
+
}
|
|
61
|
+
to {
|
|
62
|
+
transform: rotate(0deg);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@keyframes rotation {
|
|
67
|
+
from {
|
|
68
|
+
transform: rotate(0deg);
|
|
69
|
+
}
|
|
70
|
+
to {
|
|
71
|
+
transform: rotate(359deg);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.login-box > footer {
|
|
76
|
+
grid-area: footer;
|
|
77
|
+
padding: 1rem;
|
|
78
|
+
display: flex;
|
|
79
|
+
flex-direction: column;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
@keyframes fadeIn {
|
|
83
|
+
from {
|
|
84
|
+
opacity: 0;
|
|
85
|
+
}
|
|
86
|
+
to {
|
|
87
|
+
opacity: 1;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
import { Icon , Button, Text, TextField } from '../../html'
|
|
3
|
+
import './LoginBox.css'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Login Box
|
|
7
|
+
*/
|
|
8
|
+
export const LoginBox = ({
|
|
9
|
+
logo, title,
|
|
10
|
+
userLabel = "User",
|
|
11
|
+
passwordLabel = "Password",
|
|
12
|
+
loginLabel = "Log In", onOK,
|
|
13
|
+
forgotLabel = "Forgot Password ?", onForgot,
|
|
14
|
+
message,
|
|
15
|
+
loading
|
|
16
|
+
}) => {
|
|
17
|
+
|
|
18
|
+
const [user, setUser] = useState('')
|
|
19
|
+
const [password, setPassword] = useState('')
|
|
20
|
+
|
|
21
|
+
function canOK() {
|
|
22
|
+
return user && user.length > 0 && password && password.length > 0
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function ok(forcedPwd) {
|
|
26
|
+
if (onOK && canOK()) onOK(user, forcedPwd || password)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function canForgot() {
|
|
30
|
+
return user && user.length > 0
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function forgot() {
|
|
34
|
+
if (onForgot) onForgot(user)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function tx(txt) {
|
|
38
|
+
return <Text>{txt}</Text>
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function changeUser(id, value) {
|
|
42
|
+
setUser(value)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function changePassword(id, value) {
|
|
46
|
+
setPassword(value)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<div className="login-box">
|
|
51
|
+
<header>
|
|
52
|
+
{logo ? <img src={logo} /> : ''}
|
|
53
|
+
<div className="title"><Text>{title}</Text></div>
|
|
54
|
+
</header>
|
|
55
|
+
<main>
|
|
56
|
+
<TextField label={tx(userLabel)} value={user} onChange={changeUser} onEnter={ok} outlined />
|
|
57
|
+
<TextField label={tx(passwordLabel)} value={password} onChange={changePassword} onEnter={ok} type="password" outlined />
|
|
58
|
+
</main>
|
|
59
|
+
<footer>
|
|
60
|
+
{ onForgot ? <Button label={tx(forgotLabel)} action={forgot} disabled={!canForgot()}/> : null }
|
|
61
|
+
{ loading ? <div className="load-box"><Icon icon="refresh" /></div> : <Button label={tx(loginLabel)} action={ok} disabled={!canOK()} raised /> }
|
|
62
|
+
{ message ? <div className="message"><Text>{message}</Text></div> : null}
|
|
63
|
+
</footer>
|
|
64
|
+
</div>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
.reset-password-box {
|
|
2
|
+
background-color: var(--paper-color);
|
|
3
|
+
display: grid;
|
|
4
|
+
grid-template-areas: "header header" "main main" "footer footer";
|
|
5
|
+
animation: fadeIn 2s;
|
|
6
|
+
padding: .5rem;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.reset-password-box>header {
|
|
10
|
+
grid-area: header;
|
|
11
|
+
display: flex;
|
|
12
|
+
flex: 1;
|
|
13
|
+
flex-direction: column;
|
|
14
|
+
justify-content: space-around;
|
|
15
|
+
align-items: center;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.reset-password-box>main {
|
|
19
|
+
grid-area: main;
|
|
20
|
+
display: flex;
|
|
21
|
+
flex-direction: column;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.reset-password-box>main>label {
|
|
25
|
+
margin: 1rem 0 0 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.reset-password-box .error {
|
|
29
|
+
font-weight: 600;
|
|
30
|
+
color: red;
|
|
31
|
+
padding: 1rem;
|
|
32
|
+
text-align: center;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.reset-password-box>footer {
|
|
36
|
+
grid-area: footer;
|
|
37
|
+
margin-top: .5rem;
|
|
38
|
+
display: flex;
|
|
39
|
+
justify-content: space-between;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.reset-password-box>footer>button {
|
|
43
|
+
min-width: 6rem;
|
|
44
|
+
max-height: 3rem;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@keyframes fadeIn {
|
|
48
|
+
from { opacity: 0; }
|
|
49
|
+
to { opacity: 1; }
|
|
50
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react'
|
|
2
|
+
import { TextField, Text, Button } from '../../html'
|
|
3
|
+
import './ResetPasswordBox.css'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Reset Password
|
|
7
|
+
*/
|
|
8
|
+
export const ResetPasswordBox = ({ logo, title, children, onOK, onClose }) => {
|
|
9
|
+
|
|
10
|
+
const [form, setForm ] = useState({})
|
|
11
|
+
const [error, setError] = useState()
|
|
12
|
+
|
|
13
|
+
function canOK() {
|
|
14
|
+
return form.password1 && form.password2
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function changeField(id, value) {
|
|
18
|
+
const next = Object.assign({}, form, {[id]: value})
|
|
19
|
+
setForm(next)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function ok() {
|
|
23
|
+
if (onOK && canOK()) {
|
|
24
|
+
if (form.password1 === form.password2 ) {
|
|
25
|
+
setError(null)
|
|
26
|
+
onOK(form)
|
|
27
|
+
} else {
|
|
28
|
+
setError("New Password and Confirm New Password do not match")
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function close() {
|
|
34
|
+
if (onClose) onClose()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<div className="reset-password-box">
|
|
39
|
+
<header>
|
|
40
|
+
{logo ? <img src={logo} /> : null}
|
|
41
|
+
{title ? <div className="title"><Text>{title}</Text></div> : null}
|
|
42
|
+
{ children }
|
|
43
|
+
</header>
|
|
44
|
+
<main>
|
|
45
|
+
<Text use="headline6">Change Password</Text>
|
|
46
|
+
<TextField id="password1" outlined icon="lock" type="password" label="New Password" lapse={100} onChange={changeField} onEnter={ok}/>
|
|
47
|
+
<TextField id="password2" outlined icon="lock" type="password" label="Confirm New Password" lapse={100} onChange={changeField} onEnter={ok}/>
|
|
48
|
+
{ error ? <div className="error">{error}</div> : null}
|
|
49
|
+
</main>
|
|
50
|
+
<footer>
|
|
51
|
+
<Button label="Close" action={close}/>
|
|
52
|
+
<Button label="OK" raised disabled={!canOK()} action={ok}/>
|
|
53
|
+
</footer>
|
|
54
|
+
</div>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
.viewer {
|
|
2
|
+
width: 100vw;
|
|
3
|
+
height: 100vh;
|
|
4
|
+
background-color: rgba(0, 0, 0, .75);
|
|
5
|
+
display: grid;
|
|
6
|
+
grid-template-columns: 1fr auto;
|
|
7
|
+
grid-template-rows: 4rem 1fr;
|
|
8
|
+
grid-template-areas: "header aside" "main aside";
|
|
9
|
+
color: #FFF;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.viewer>header {
|
|
13
|
+
grid-area: header;
|
|
14
|
+
background: linear-gradient(to bottom, rgba(0, 0, 0, 0.65) 0%, transparent 100%);
|
|
15
|
+
background-image: linear-gradient(rgba(0, 0, 0, 0.65) 0%, transparent 100%);
|
|
16
|
+
background-position-x: initial;
|
|
17
|
+
background-position-y: initial;
|
|
18
|
+
background-size: initial;
|
|
19
|
+
background-repeat-x: initial;
|
|
20
|
+
background-repeat-y: initial;
|
|
21
|
+
background-attachment: initial;
|
|
22
|
+
background-origin: initial;
|
|
23
|
+
background-clip: initial;
|
|
24
|
+
background-color: initial;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.viewer>main {
|
|
28
|
+
grid-area: main;
|
|
29
|
+
display: flex;
|
|
30
|
+
flex-direction: column;
|
|
31
|
+
justify-content: center;
|
|
32
|
+
align-items: center;
|
|
33
|
+
overflow: auto;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.viewer>main>.resizer {
|
|
37
|
+
width: 100%;
|
|
38
|
+
overflow: hidden;
|
|
39
|
+
position: relative;
|
|
40
|
+
display: flex;
|
|
41
|
+
justify-content: center;
|
|
42
|
+
align-items: center;
|
|
43
|
+
padding: 1rem;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.viewer>main>.resizer img {
|
|
47
|
+
width: 100%;
|
|
48
|
+
object-fit: fill;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@media (min-width: 800px) {
|
|
52
|
+
.viewer>main>.resizer {
|
|
53
|
+
width: 800px;
|
|
54
|
+
padding: 0;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.viewer>aside {
|
|
59
|
+
min-width: 22rem;
|
|
60
|
+
grid-area: aside;
|
|
61
|
+
display: none;
|
|
62
|
+
background-color: rgb(50, 50, 50);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.viewer>aside.open {
|
|
66
|
+
grid-area: aside;
|
|
67
|
+
display: flex;
|
|
68
|
+
flex-direction: column;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.viewer>aside .property>label {
|
|
72
|
+
color: #AAA;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.viewer>footer {
|
|
76
|
+
width: 15rem;
|
|
77
|
+
position: absolute;
|
|
78
|
+
bottom: 1rem;
|
|
79
|
+
left: calc(50vw - 4rem);
|
|
80
|
+
border-radius: 3px;
|
|
81
|
+
z-index: 100;
|
|
82
|
+
display: flex;
|
|
83
|
+
justify-content: space-between;
|
|
84
|
+
align-items: center;
|
|
85
|
+
padding: .5rem;
|
|
86
|
+
background-color: rgba(0, 0, 0, .75);
|
|
87
|
+
}
|