diff --git a/client/assets/css/reset.scss b/client/assets/css/reset.scss
index 9402e71d..c18d1a5f 100644
--- a/client/assets/css/reset.scss
+++ b/client/assets/css/reset.scss
@@ -83,6 +83,7 @@ a {
}
select {
+ -webkit-appearance: none;
-moz-appearance: none;
}
@@ -100,8 +101,7 @@ button::-moz-focus-inner {
border: 0;
}
-input,
-textarea {
+input, textarea, select {
transition: border 0.2s;
outline: none;
}
@@ -109,9 +109,9 @@ textarea {
input[type="checkbox"] {
position: relative;
top: 1px;
- margin: 0;
- padding: 0;
vertical-align: top;
+ margin-top: auto;
+ margin-bottom: auto;
}
.no-select {
diff --git a/client/components/alert.js b/client/components/alert.js
index 7edafc48..5d142995 100644
--- a/client/components/alert.js
+++ b/client/components/alert.js
@@ -14,13 +14,16 @@ export class ModalAlert extends Popup {
alert.subscribe((Component, okCallback) => {
this.setState({
appear: true,
- value: Component
+ value: Component,
+ fn: okCallback
});
});
}
onSubmit(e){
- this.setState({appear: false});
+ this.setState({appear: false}, () => {
+ requestAnimationFrame(() => this.state.fn())
+ });
}
modalContentBody(){
diff --git a/client/components/button.scss b/client/components/button.scss
index 369648e7..2ff373c5 100644
--- a/client/components/button.scss
+++ b/client/components/button.scss
@@ -23,6 +23,8 @@ button{
background: var(--emphasis);
color: white
}
- &.transparent{
+ &.dark{
+ background: var(--dark);
+ color: white;
}
}
diff --git a/client/components/container.js b/client/components/container.js
index e010c367..b43c8cad 100644
--- a/client/components/container.js
+++ b/client/components/container.js
@@ -1,6 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
-
import './container.scss';
export class Container extends React.Component {
diff --git a/client/components/decorator.js b/client/components/decorator.js
index 39042931..84b3dca1 100644
--- a/client/components/decorator.js
+++ b/client/components/decorator.js
@@ -1,8 +1,8 @@
import React from 'react';
import { Link } from 'react-router-dom';
-import { browserHistory } from 'react-router'
+import { browserHistory, Redirect } from 'react-router';
-import { Session } from '../model/';
+import { Session, Admin } from '../model/';
import { Container, Loader, Icon } from '../components/';
import { memory, currentShare } from '../helpers/';
@@ -47,7 +47,6 @@ export function LoggedInOnly(WrappedComponent){
};
}
-
export function ErrorPage(WrappedComponent){
return class extends React.Component {
constructor(props){
@@ -89,3 +88,11 @@ export function ErrorPage(WrappedComponent){
}
};
}
+
+export const LoadingPage = (props) => {
+ return (
+
+
+
+ );
+};
diff --git a/client/components/formbuilder.js b/client/components/formbuilder.js
new file mode 100644
index 00000000..d8e68f22
--- /dev/null
+++ b/client/components/formbuilder.js
@@ -0,0 +1,170 @@
+import React from 'react';
+import { Input, Select, Enabler } from './';
+import { FormObjToJSON, bcrypt_password, format } from '../helpers/';
+
+import "./formbuilder.scss";
+
+export class FormBuilder extends React.Component {
+ constructor(props){
+ super(props);
+ }
+
+ section(struct, key, level = 0){
+ if(struct == null) struct = "";
+ const isALeaf = function(struct){
+ if("label" in struct && "type" in struct &&
+ "value" in struct && "default" in struct){
+ return true;
+ }
+ return false;
+ };
+
+ if(Array.isArray(struct)) return null;
+ else if(isALeaf(struct) === false){
+ if(level <= 1){
+ return (
+
+ {
+ key ?
{ format(key) }
: ""
+ }
+ {
+ Object.keys(struct).map((key, index) => {
+ return (
+
+ { this.section(struct[key], key, level + 1) }
+
+ );
+ })
+ }
+
+ );
+ }
+ return (
+
+
+
+ );
+ }
+
+ let id = {};
+ let target = [];
+ if(struct.id !== undefined){
+ id.id = this.props.idx === undefined ? struct.id : struct.id + "_" + this.props.idx;
+ }
+ if(struct.type === "enable"){
+ target = struct.target.map((target) => {
+ return this.props.idx === undefined ? target : target + "_" + this.props.idx;
+ });
+ }
+
+ const onChange = function(e, fn){
+ struct.value = e;
+ if(typeof fn === "function"){
+ fn(struct);
+ }
+ this.props.onChange.call(
+ this,
+ FormObjToJSON(this.props.form)
+ );
+ };
+ return ( );
+ }
+
+ render(){
+ return this.section(this.props.form || {});
+ }
+}
+
+
+const FormElement = (props) => {
+ const id = props.id !== undefined ? {id: props.id} : {};
+ let struct = props.params;
+ let $input = ( props.onChange(e.target.value)} {...id} name={props.name} type="text" defaultValue={struct.value} placeholder={struct.placeholder} /> );
+ switch(props.params["type"]){
+ case "text":
+ const onTextChange = (value) => {
+ if(value === ""){
+ value = null;
+ }
+ props.onChange(value);
+ };
+ $input = ( onTextChange(e.target.value)} {...id} name={props.name} type="text" value={struct.value || ""} placeholder={struct.placeholder}/> );
+ break;
+ case "number":
+ const onNumberChange = (value) => {
+ value = value === "" ? null : parseInt(value);
+ props.onChange(value);
+ };
+ $input = ( onNumberChange(e.target.value)} {...id} name={props.name} type="number" value={struct.value || ""} placeholder={struct.placeholder} /> );
+ break;
+ case "password":
+ const onPasswordChange = (value) => {
+ if(value === ""){
+ value = null;
+ }
+ props.onChange(value);
+ };
+ $input = ( onPasswordChange(e.target.value)} {...id} name={props.name} type="password" value={struct.value || ""} placeholder={struct.placeholder} /> );
+ break;
+ case "bcrypt":
+ const onBcryptChange = (value) => {
+ if(value === ""){
+ return props.onChange(null);
+ }
+ bcrypt_password(value).then((hash) => {
+ props.onChange(hash);
+ });
+ };
+ $input = ( onBcryptChange(e.target.value)} {...id} name={props.name} type="password" value={struct.value || ""} placeholder={struct.placeholder} /> );
+ break;
+ case "hidden":
+ $input = ( );
+ break;
+ case "boolean":
+ $input = ( props.onChange(e.target.checked)} {...id} name={props.name} type="checkbox" checked={struct.value === null ? !!struct.default : struct.value} /> );
+ break;
+ case "select":
+ $input = (