Add manifest.json, icons and install button, flatten client/src
This commit is contained in:
parent
a219e689c1
commit
474afda9c2
105 changed files with 338 additions and 283 deletions
9
client/js/components/ui/Button.js
Normal file
9
client/js/components/ui/Button.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
import React from 'react';
|
||||
|
||||
const Button = ({ children, ...props }) => (
|
||||
<button type="button" {...props}>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
|
||||
export default Button;
|
18
client/js/components/ui/Checkbox.js
Normal file
18
client/js/components/ui/Checkbox.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
import React from 'react';
|
||||
import classnames from 'classnames';
|
||||
|
||||
const Checkbox = ({ name, label, topLabel, ...props }) => (
|
||||
<label
|
||||
className={classnames('checkbox', {
|
||||
'top-label': topLabel
|
||||
})}
|
||||
htmlFor={name}
|
||||
>
|
||||
{topLabel && label}
|
||||
<input type="checkbox" id={name} name={name} {...props} />
|
||||
<span />
|
||||
{!topLabel && label}
|
||||
</label>
|
||||
);
|
||||
|
||||
export default Checkbox;
|
107
client/js/components/ui/Editable.js
Normal file
107
client/js/components/ui/Editable.js
Normal file
|
@ -0,0 +1,107 @@
|
|||
import React, { PureComponent, createRef } from 'react';
|
||||
import { stringWidth } from 'utils';
|
||||
|
||||
export default class Editable extends PureComponent {
|
||||
static defaultProps = {
|
||||
editable: true
|
||||
};
|
||||
|
||||
inputEl = createRef();
|
||||
|
||||
state = {
|
||||
editing: false
|
||||
};
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (!prevState.editing && this.state.editing) {
|
||||
// eslint-disable-next-line react/no-did-update-set-state
|
||||
this.updateInputWidth(this.props.value);
|
||||
this.inputEl.current.focus();
|
||||
}
|
||||
}
|
||||
|
||||
getSnapshotBeforeUpdate(prevProps) {
|
||||
if (this.state.editing && prevProps.value !== this.props.value) {
|
||||
this.updateInputWidth(this.props.value);
|
||||
}
|
||||
}
|
||||
|
||||
updateInputWidth = value => {
|
||||
if (this.inputEl.current) {
|
||||
const style = window.getComputedStyle(this.inputEl.current);
|
||||
const padding = parseInt(style.paddingRight, 10);
|
||||
// Make sure the width is at least 1px so the caret always shows
|
||||
const width =
|
||||
stringWidth(value, `${style.fontSize} ${style.fontFamily}`) || 1;
|
||||
|
||||
this.setState({
|
||||
width: width + padding * 2,
|
||||
indent: padding
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
startEditing = () => {
|
||||
if (this.props.editable) {
|
||||
this.initialValue = this.props.value;
|
||||
this.setState({ editing: true });
|
||||
}
|
||||
};
|
||||
|
||||
stopEditing = () => {
|
||||
const { validate, value, onChange } = this.props;
|
||||
if (validate && !validate(value)) {
|
||||
onChange(this.initialValue);
|
||||
}
|
||||
this.setState({ editing: false });
|
||||
};
|
||||
|
||||
handleBlur = e => {
|
||||
const { onBlur } = this.props;
|
||||
this.stopEditing();
|
||||
if (onBlur) {
|
||||
onBlur(e.target.value);
|
||||
}
|
||||
};
|
||||
|
||||
handleChange = e => this.props.onChange(e.target.value);
|
||||
|
||||
handleKey = e => {
|
||||
if (e.key === 'Enter') {
|
||||
this.handleBlur(e);
|
||||
}
|
||||
};
|
||||
|
||||
handleFocus = e => {
|
||||
const val = e.target.value;
|
||||
e.target.value = '';
|
||||
e.target.value = val;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { children, className, value } = this.props;
|
||||
|
||||
const style = {
|
||||
width: this.state.width,
|
||||
textIndent: this.state.indent,
|
||||
paddingLeft: 0
|
||||
};
|
||||
|
||||
return this.state.editing ? (
|
||||
<input
|
||||
ref={this.inputEl}
|
||||
className={className}
|
||||
type="text"
|
||||
value={value}
|
||||
onBlur={this.handleBlur}
|
||||
onChange={this.handleChange}
|
||||
onKeyDown={this.handleKey}
|
||||
onFocus={this.handleFocus}
|
||||
style={style}
|
||||
spellCheck={false}
|
||||
/>
|
||||
) : (
|
||||
<div onClick={this.startEditing}>{children}</div>
|
||||
);
|
||||
}
|
||||
}
|
48
client/js/components/ui/FileInput.js
Normal file
48
client/js/components/ui/FileInput.js
Normal file
|
@ -0,0 +1,48 @@
|
|||
import React, { PureComponent } from 'react';
|
||||
import Button from 'components/ui/Button';
|
||||
|
||||
export default class FileInput extends PureComponent {
|
||||
static defaultProps = {
|
||||
type: 'text'
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.input = window.document.createElement('input');
|
||||
this.input.setAttribute('type', 'file');
|
||||
|
||||
this.input.addEventListener('change', e => {
|
||||
const file = e.target.files[0];
|
||||
const reader = new FileReader();
|
||||
const { onChange, type } = this.props;
|
||||
|
||||
reader.onload = () => {
|
||||
onChange(file.name, reader.result);
|
||||
};
|
||||
|
||||
switch (type) {
|
||||
case 'binary':
|
||||
reader.readAsArrayBuffer(file);
|
||||
break;
|
||||
|
||||
case 'text':
|
||||
reader.readAsText(file);
|
||||
break;
|
||||
|
||||
default:
|
||||
reader.readAsText(file);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
handleClick = () => this.input.click();
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Button className="input-file" onClick={this.handleClick}>
|
||||
{this.props.name}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
}
|
7
client/js/components/ui/Navicon.js
Normal file
7
client/js/components/ui/Navicon.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
import React from 'react';
|
||||
|
||||
const Navicon = ({ onClick }) => (
|
||||
<i className="icon-menu navicon" onClick={onClick} />
|
||||
);
|
||||
|
||||
export default Navicon;
|
87
client/js/components/ui/TextInput.js
Normal file
87
client/js/components/ui/TextInput.js
Normal file
|
@ -0,0 +1,87 @@
|
|||
import React, { PureComponent } from 'react';
|
||||
import { FastField } from 'formik';
|
||||
import classnames from 'classnames';
|
||||
import capitalize from 'lodash/capitalize';
|
||||
import Error from 'components/ui/formik/Error';
|
||||
|
||||
export default class TextInput extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.input = React.createRef();
|
||||
window.addEventListener('resize', this.handleResize);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('resize', this.handleResize);
|
||||
}
|
||||
|
||||
handleResize = () => {
|
||||
if (this.scroll) {
|
||||
this.scroll = false;
|
||||
this.scrollIntoView();
|
||||
}
|
||||
};
|
||||
|
||||
handleFocus = () => {
|
||||
this.scroll = true;
|
||||
setTimeout(() => {
|
||||
this.scroll = false;
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
scrollIntoView = () => {
|
||||
if (this.input.current.scrollIntoViewIfNeeded) {
|
||||
this.input.current.scrollIntoViewIfNeeded();
|
||||
} else {
|
||||
this.input.current.scrollIntoView();
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { name, label = capitalize(name), noError, ...props } = this.props;
|
||||
|
||||
return (
|
||||
<FastField
|
||||
name={name}
|
||||
render={({ field, form }) => {
|
||||
return (
|
||||
<>
|
||||
<div className="textinput">
|
||||
<input
|
||||
className={field.value && 'value'}
|
||||
type="text"
|
||||
name={name}
|
||||
autoCapitalize="off"
|
||||
autoCorrect="off"
|
||||
autoComplete="off"
|
||||
spellCheck="false"
|
||||
ref={this.input}
|
||||
onFocus={this.handleFocus}
|
||||
{...field}
|
||||
{...props}
|
||||
/>
|
||||
<span
|
||||
className={classnames('textinput-1', {
|
||||
value: field.value,
|
||||
error: form.touched[name] && form.errors[name]
|
||||
})}
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
<span
|
||||
className={classnames('textinput-2', {
|
||||
value: field.value,
|
||||
error: form.touched[name] && form.errors[name]
|
||||
})}
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
</div>
|
||||
{!noError && <Error name={name} />}
|
||||
</>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
27
client/js/components/ui/formik/Checkbox.js
Normal file
27
client/js/components/ui/formik/Checkbox.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
import React, { memo } from 'react';
|
||||
import { FastField } from 'formik';
|
||||
import Checkbox from 'components/ui/Checkbox';
|
||||
|
||||
const FormikCheckbox = ({ name, onChange, ...props }) => (
|
||||
<FastField
|
||||
name={name}
|
||||
render={({ field, form }) => {
|
||||
return (
|
||||
<Checkbox
|
||||
name={name}
|
||||
checked={field.value}
|
||||
onChange={e => {
|
||||
form.setFieldTouched(name, true);
|
||||
field.onChange(e);
|
||||
if (onChange) {
|
||||
onChange(e);
|
||||
}
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
export default memo(FormikCheckbox);
|
8
client/js/components/ui/formik/Error.js
Normal file
8
client/js/components/ui/formik/Error.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
import React from 'react';
|
||||
import { ErrorMessage } from 'formik';
|
||||
|
||||
const Error = props => (
|
||||
<ErrorMessage component="div" className="form-error" {...props} />
|
||||
);
|
||||
|
||||
export default Error;
|
0
client/js/components/ui/formik/TextInput.js
Normal file
0
client/js/components/ui/formik/TextInput.js
Normal file
Loading…
Add table
Add a link
Reference in a new issue