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
|
+
}
|