diff options
| author | Jules Laplace <julescarbon@gmail.com> | 2020-09-26 14:56:02 +0200 |
|---|---|---|
| committer | Jules Laplace <julescarbon@gmail.com> | 2020-09-26 14:56:02 +0200 |
| commit | a17b76ac75f506f5da6fe8adf9c36632b60d4226 (patch) | |
| tree | abb0af0c4409b830dea2ef808c146223ee973933 /frontend/common | |
| parent | 2231a6e1c05b07bb7ec5906716aedec93d02429c (diff) | |
refactor to use app-rooted js imports
Diffstat (limited to 'frontend/common')
22 files changed, 0 insertions, 2298 deletions
diff --git a/frontend/common/app.css b/frontend/common/app.css deleted file mode 100644 index d9f9946..0000000 --- a/frontend/common/app.css +++ /dev/null @@ -1,428 +0,0 @@ -* { box-sizing: border-box; } -html, body { - margin: 0; - padding: 0; - width: 100%; - height: 100%; -} -body { - background: #000; - color: #ddd; - overflow: hidden; - font-family: 'Roboto', sans-serif; - font-size: 0.875rem; - height: 100%; - width: 100%; -} -.gray { - color: #888; -} - -/* layout */ - -.container { - height: 100%; - width: 100%; -} -.app { - /*display: flex;*/ - height: 100%; - width: 100%; -} -.app > div { - display: flex; - flex-direction: column; - height: 100%; - width: 100%; -} -.app .body { - display: flex; - flex-direction: column; - flex-grow: 1; - position: relative; - height: 100%; - width: 100%; -} - -.row { - display: flex; - flex-direction: row; - justify-content: flex-start; - align-items: flex-start; -} -.row > div { - margin-right: 1.5rem; -} -.row > div:last-child { - margin-right: 0; -} - - -.row.menubar { - justify-content: flex-end; -} -.menubar > :first-child { - flex: 1; -} - -/* lists */ - -ul { - margin: 0.75rem 0; -} -li { - line-height: 1.5; -} - -/* header */ - -header { - height: 3.125rem; - font-size: 0.875rem; - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; - background: rgba(16,32,64,0.5); - color: white; - z-index: 50; -} -header b { - font-weight: 900; -} -header a { - color: rgba(255,255,255,0.95); - text-decoration: none; - font-size: 0.875rem; - font-weight: 500; -} -header > div:first-child { - display: flex; - justify-content: flex-start; - align-items: center; - padding-left: 1.5rem; -} -header > div:last-child { - padding-right: 1.5rem; -} -header > div > button { - padding: 0.25rem; - margin: 0 0 0 0.5rem; - background: #000; - border-color: #888; - color: #888; -} -header > div > button:hover { - border-color: #fff; - color: #fff; -} -header .vcat-btn { - font-size: 0.875rem; - padding-left: 0.5rem; - letter-spacing: 0.0625rem; -} -header > div:last-child a { - padding: 0.5rem; -} -header .btn-link:focus, -header .btn-link:hover, -header .btn-link:active, -header a:focus, -header a:hover, -header a:active { - text-decoration: none; - color: white; -} -header a:focus, -header a:hover, -header a:active { - color: white; -} -.menuToggle { - width: 1.625rem; - height: 1.625rem; - cursor: pointer; - line-height: 1; -} -header a.navbar-brand { - font-size: .8rem; -} - -header .username { - cursor: pointer; -} - -/* headings */ - -h1 { - color: #eee; - margin-bottom: 1.25rem; - font-size: 1.5rem; - font-weight: normal; -} -div:first-child > h1:first-child, -.menuButtons + div > h1:first-child { - margin-top: 0; -} -h2 { - color: #eee; - font-size: 1.25rem; - font-weight: normal; -} -h3 { - color: #eee; - margin-top: 0; - margin-bottom: 1.25rem; - font-size: 1.0rem; - font-weight: normal; -} -p { - margin: 1.25rem 0; - line-height: 1.5; -} -.byline { - color: #888; - font-size: 0.75rem; - margin-top: 0.25rem; - margin-bottom: 1.25rem; -} - -/* links */ - -b { - color: #fff; -} -a { - text-decoration: underline; - color: #8df; -} - -/* menu button */ - -.menuButtons { - width: 2.5rem; - min-height: 18rem; -} -.menuButton { - position: relative; - text-align: center; - text-transform: uppercase; - font-size: 0.625rem; - color: #333; - text-decoration: none; - cursor: pointer; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - width: 2.5rem; - margin-bottom: 0.75rem; -} -.menuButton .icon { - background-color: #fff; - width: 2.5rem; - height: 2.5rem; - border: 1px solid; - border-color: #888; - margin-bottom: 0.3rem; - display: flex; - justify-content: center; - align-items: center; - transition: border-color 0.1s; - border-radius: 0.125rem; -} -.menuButton svg { - width: 80%; - fill: #888; -} -.menuButton:hover .icon { - background-color: #eef; - border-color: #000; -} -.menuButton:hover svg { - fill: #000; -} - -.menuButton.small { - width: 2.0rem; - margin-bottom: 0; - margin-right: 0.1875rem; -} -.menuButton.small .icon { - border: 0; - border-color: #888; - margin-bottom: 0; - width: 2.0rem; - height: 2.0rem; - border-radius: 0.0625rem; -} -.menuButton.small svg { - fill: #888; - width: 1.75rem; - height: 1.75rem; -} -.menuButton.small .icon:hover { - border-color: #11f; - background-color: #11f; -} -.menuButton.small:hover svg { - fill: #fff; -} -.results.th .menuButton.small { - width: 1.5rem; -} -.results.th .menuButton.small .icon { - width: 1.5rem; - height: 1.5rem; -} -.results.th .menuButton.small svg { - width: 1.25rem; - height: 1.25rem; -} - -.menuButton.small.active .icon { - border-color: #11f; - background-color: #11f; -} -.menuButton.small.active svg { - fill: #fff; -} -.menuButton.small.active:hover .icon { - border-color: #fff; - background-color: #11f; -} -.menuButton.small.active:hover svg { - fill: #fff; -} - -/* rows - like a table */ - -.rows { - width: 100%; -} -.rows .row { - width: 100%; -} -.rows .row:nth-of-type(2n+1) { - background: #f8f8f8; -} -.rows .row:nth-of-type(2n+2) { - background: #eeeeee; -} -.rows .row:hover { - background: #d8d8d8; -} -.rows .row > div, -.rows .row a > div { - padding: 0.75rem; - overflow: hidden; - text-overflow: ellipsis; - margin: 0; -} -.rows .row div.title { - width: 10rem; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; -} -.rows .row div.string, -.rows .row div.str { - min-width: 6rem; -} -.rows .row div.bool { - width: 4rem; - overflow: visible; - text-align: center; -} -.rows .row div.color { - width: 4rem; - overflow: visible; - text-align: center; -} -.rows .row div.date { - min-width: 10rem; -} -.rows .row div.int, -.rows .row div.float { - text-align: right; - min-width: 6rem; -} -.rows .row.heading div.int, -.rows .row.heading div.float { - text-align: center; -} -.rows .row div.text { - flex: 1; - max-width: 20rem; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -.rows .row.heading div { - text-transform: capitalize; - font-weight: bold; - background: #f8f8f8; -} -.rows .row.heading:hover { - background: #f8f8f8; -} - -/* misc ui */ - -pre, code, .license { - font-family: Menlo, monospace; - font-size: 0.75rem; - line-height: 2; -} -.swatch { - display: inline-block; - width: 0.75rem; - height: 0.75rem; - border: 1px solid #333; -} -.dot { - display: inline-block; - width: 0.5rem; - height: 0.5rem; - border-radius: 50%; -} -.light { - color: #888; -} -.pill { - display: inline-block; - padding: 0.125rem; - width: 2.5rem; - text-align: center; - border-radius: 0.5rem; - font-size: 0.75rem; - text-transform: uppercase; - font-weight: bold; -} -.pill.yes { - background: #11f; - color: #fff; -} -.pill.no { - color: #ccc; - border: 1px solid; -} - -/* columns (of tags) */ - -.form .columnCells { - padding-top: 0.25rem -} -.columnCells .column { - margin-top: 0.25rem; -} -.columnCells .column > div { - max-width: 100%; - padding: 0 0.375rem 0.375rem 0; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -.columnCells .selected { - color: #11f; -}
\ No newline at end of file diff --git a/frontend/common/copyToClipboardButton.component.js b/frontend/common/copyToClipboardButton.component.js deleted file mode 100644 index cfe7103..0000000 --- a/frontend/common/copyToClipboardButton.component.js +++ /dev/null @@ -1,24 +0,0 @@ -import React, { Component } from 'react'; -import { writeToClipboard } from '../util' - -export default class CopyToClipboardButton extends Component { - state = { - copied: false, - } - - handleClick() { - writeToClipboard(this.props.data) - this.setState({ copied: true }) - } - - render() { - return ( - <button - className={this.state.copied ? 'copyButton copied' : 'copyButton'} - onClick={this.handleClick.bind(this)} - > - {this.state.copied ? 'Copied!' : 'Copy'} - </button> - ) - } -} diff --git a/frontend/common/fonts.css b/frontend/common/fonts.css deleted file mode 100644 index c782885..0000000 --- a/frontend/common/fonts.css +++ /dev/null @@ -1,55 +0,0 @@ -@font-face { - font-family: 'Roboto'; - src: url('/static/fonts/Roboto-Bold.ttf') format('truetype'); - font-weight: bold; -} -@font-face { - font-family: 'Roboto'; - src: url('/static/fonts/Roboto-BoldItalic.ttf') format('truetype'); - font-weight: bold; - font-style: italic; -} -/* -@font-face { - font-family: 'Roboto'; - src: url('/static/fonts/Roboto-Light.ttf') format('truetype'); - font-weight: 100; -} -@font-face { - font-family: 'Roboto'; - src: url('/static/fonts/Roboto-LightItalic.ttf') format('truetype'); - font-weight: 100; -} -*/ -@font-face { - font-family: 'Roboto'; - src: url('/static/fonts/Roboto-Medium.ttf') format('truetype'); - font-weight: 300; -} -@font-face { - font-family: 'Roboto'; - src: url('/static/fonts/Roboto-MediumItalic.ttf') format('truetype'); - font-style: italic; - font-weight: 300; -} -@font-face { - font-family: 'Roboto'; - src: url('/static/fonts/Roboto-Regular.ttf') format('truetype'); -} -@font-face { - font-family: 'Roboto'; - src: url('/static/fonts/Roboto-Italic.ttf') format('truetype'); - font-style: italic; -} -/* -@font-face { - font-family: 'Roboto'; - src: url('/static/fonts/Roboto-Thin.ttf') format('truetype'); - font-weight: 100; -} -@font-face { - font-family: 'Roboto'; - src: url('/static/fonts/Roboto-ThinItalic.ttf') format('truetype'); - font-weight: 100; -} -*/
\ No newline at end of file diff --git a/frontend/common/form.component.js b/frontend/common/form.component.js deleted file mode 100644 index f3775a2..0000000 --- a/frontend/common/form.component.js +++ /dev/null @@ -1,220 +0,0 @@ -import React, { Component } from 'react'; -import { courtesyS } from '../util' - -export const TextInput = props => ( - <label className={props.error ? 'error' : 'text'}> - {props.title && <span>{props.title}</span>} - <input - type="text" - required={props.required} - onChange={props.onChange} - name={props.name} - value={props.data[props.name]} - placeholder={props.placeholder} - autoComplete={props.autoComplete} - /> - </label> -) - -export const LabelDescription = props => ( - <label className={'text description'}> - <span>{props.title}</span> - <span>{props.children}</span> - </label> -) - -export const NumberInput = props => ( - <label className={props.error ? 'error' : 'text'}> - <span>{props.title}</span> - <input - type="number" - required={props.required} - onChange={props.onChange} - name={props.name} - value={props.data[props.name]} - min={props.min} - max={props.max} - step={props.step || 1} - /> - </label> -) - -export const ColorInput = props => ( - <label className={props.error ? 'error color' : 'text color'}> - <span>{props.title}</span> - <input - type="color" - required={props.required} - onChange={props.onChange} - name={props.name} - value={props.data[props.name]} - /> - <input - type="text" - required={props.required} - onChange={props.onChange} - name={props.name} - value={props.data[props.name]} - /> - </label> -) - -export const TextArea = props => ( - <label className={props.error ? 'textarea error' : 'textarea'}> - {props.title && <span>{props.title}</span>} - <textarea - onChange={props.onChange} - name={props.name} - value={props.data[props.name]} - /> - </label> -) - -export const Checkbox = props => ( - <label className="checkbox"> - <input - type="checkbox" - name={props.name} - value={1} - checked={props.checked} - onChange={(e) => props.onChange(props.name, e.target.checked)} - /> - <span>{props.label}</span> - </label> -) - -export const Radio = props => { - return ( - <label className="radio"> - <input - type="radio" - name={props.name} - value={props.value} - checked={props.value === props.currentValue} - onChange={() => props.onChange(props.name, props.value)} - /> - <span>{props.label}</span> - </label> - ) -} - -export class Select extends Component { - state = { - focused: false, - } - - render() { - const { name, selected, options, defaultOption, title, loading, onChange, className } = this.props - if (loading) { - return <label className='select'><div>Loading...</div></label> - } - const { focused } = this.state - return ( - <label> - {title && <span>{title}</span>} - <div className={(focused ? 'select focus' : 'select') + " " + (className || "")}> - <div>{(options.find(opt => opt.name === selected) || {label: defaultOption}).label}</div> - <select - onFocus={() => this.setState({ focused: true })} - onBlur={() => this.setState({ focused: false })} - onChange={e => { - onChange(name, e.target.value) - // this.setState({ focused: false }) - }} - value={selected || "__default__"} - > - {!selected && defaultOption && <option value="__default__">{defaultOption}</option>} - {options.map((option, i) => ( - <option - key={option.name} - value={option.name} - disabled={option.disabled} - >{option.label}</option> - ))} - </select> - </div> - </label> - ) - } -} - -export class FileInputField extends Component { - state = { - count: 0, - } - - handleChange(files) { - const { multiple, onChange } = this.props - if (!files) { - this.setState({ count: 0 }) - } else { - this.setState({ count: multiple ? files.length : 0 }) - } - onChange(files) - } - - render() { - const { error, title, label, required, multiple, mime, name } = this.props - return ( - <label className={error ? 'error' : 'text fileInput'}> - <span>{title}</span> - <div className="row"> - <button> - {label || "Choose files"} - <FileInput - mime={mime} - multiple={multiple} - onChange={this.handleChange.bind(this)} - /> - </button> - {!!this.state.count && <span>{courtesyS(this.state.count, "file")}{" selected"}</span>} - </div> - </label> - ) - } -} - -export class FileInput extends Component { - handleChange(e) { - let { multiple, mime } = this.props - if (!mime) { - mime = "image/" - } - const files = e.dataTransfer ? e.dataTransfer.files : e.target.files - let i - let file, selectedFiles = [] - for (i = 0; i < files.length; i++) { - file = files[i] - if (file && file.type.indexOf(mime) === 0) { - if (multiple) { - selectedFiles.push(file) - } else { - break - } - } - } - if (multiple && selectedFiles.length) { - this.props.onChange(selectedFiles) - } else if (!multiple && file) { - this.props.onChange(file) - } else { - this.props.onChange() - } - } - - render() { - return ( - <input type="file" multiple={!!this.props.multiple} onChange={this.handleChange.bind(this)} /> - ) - } -} - -export const SubmitButton = (props) => ( - <label> - <span></span> - <button - className={props.className ? "submit " + props.className : "submit"} - onClick={props.onClick} - >{props.title}</button> - </label> -) diff --git a/frontend/common/form.css b/frontend/common/form.css deleted file mode 100644 index dbfa01f..0000000 --- a/frontend/common/form.css +++ /dev/null @@ -1,323 +0,0 @@ -/* label */ - -label { - display: flex; - min-width: 10rem; - flex-direction: row; - justify-content: flex-start; - align-items: center; - cursor: pointer; -} - -.label { - display: flex; - min-width: 10rem; - flex-direction: row; - justify-content: flex-start; - align-items: flex-start; - cursor: pointer; -} - -/* form (stock forms) */ - -form label, -form .label { - width: 100%; - margin-bottom: 0.5rem; -} -form label span, -form .label > span { - display: inline-block; - min-width: 8rem; - padding: 0.5rem 0; -} -form label.textarea { - align-items: flex-start; -} -form input[type="checkbox"] { - margin: -0.0625rem 0.625rem 0 8rem; -} - -/* form errors */ - -form .error span { - color: #f11; -} -form .error input[type=text], -form .error input[type=number], -form .error input[type=password] { - border-color: #f11; -} - -/* form field descriptions */ - -form label.description { - cursor: normal; - font-size: small; - color: #ddd; -} -form label.description span { - padding-top: 0; -} - -/* text input */ - -input[type=text], -input[type=number], -input[type=password] { - padding: 0.5rem; - border: 1px solid #ddd; - color: #fff; - background: #111; - font-family: 'Roboto', sans-serif; - font-size: 0.875rem; - width: 15rem; - border-radius: 0.125rem; -} - -input[type=text]:focus, -input[type=number]:focus, -input[type=password]:focus { - border: 1px solid #84f; - background: #000; -} - -textarea { - width: 20rem; - height: 10rem; - padding: 0.5rem; - border: 1px solid #ddd; - font-family: 'Roboto', sans-serif; - background: #111; - color: #fff; - font-size: 0.875rem; - border-radius: 0.125rem; -} -textarea:focus { - border: 1px solid #84f; - background: #000; -} - -/* checkbox */ - -input[type=checkbox] { - position: relative; - display: block; - width: 0.75rem; - height: 0.75rem; - margin-right: 0.625rem; - cursor: pointer; - outline: 0; -} -input[type=checkbox] + span { - font-size: 0.825rem; - text-transform: uppercase; - color: #444; -} -input[type=checkbox]:hover + span { - color: #000; -} -input[type=checkbox]:focus + span { - color: #84f; -} -input[type="checkbox"]:checked + span { - color: #000; -} -input[type="checkbox"]:focus:checked + span { - color: #84f; -} - -input[type="checkbox"]:after { - position: relative; - display: block; - left: 0; - width: 0.75rem; - height: 0.75rem; - border: 0.0625rem solid #ddd; - content: ""; - background-color: #fff; - background-repeat: no-repeat; - background-position: center; - cursor: pointer; - transition: background-color 0.1s; - border-radius: 0.125rem; -} -input[type=checkbox]:focus:after { - border-color: #84f; -} -input[type="checkbox"]:checked:after { - border-color: #84f; - background-color: #84f; - background-image: url(/static/img/check.svg); - background-size: cover; -} - -/* select */ - -.select { - position: relative; - width: 9rem; - min-width: auto; - background: #111; - border-radius: 0.125rem; - border: 1px solid #ddd; - padding: 0.5rem; - overflow: hidden; - text-overflow: ellipsis; - margin-right: 1.25rem; - cursor: pointer; -} -.select select { - position: absolute; - top: 0; left: 0; - width: 100%; height: 100%; - opacity: 0; - cursor: pointer; -} -.select:after { - content: ''; - position: absolute; - top: 50%; - right: 0.375rem; - transform: translateY(-0.125rem); - width: 0; - height: 0; - border-left: 0.375rem solid transparent; - border-right: 0.375rem solid transparent; - border-top: 0.375rem solid #ddd; -} -.select.focus { - border-color: #84f; - background: #000; -} -.select.focus:after { - border-top-color: #84f; -} -.select:hover { - background-color: #000; -} -.select div { - width: calc(100% - 1.025rem); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.select.wide { - width: 20rem; -} - -/* button */ - -button { - position: relative; - background: #333; - border-radius: 0.125rem; - color: #ddd; - border: 1px solid; - padding: 0.5rem; - overflow: hidden; - text-overflow: ellipsis; - font-family: 'Roboto', sans-serif; - font-size: 0.875rem; - cursor: pointer; - /*text-transform: uppercase;*/ - transition: all 0.1s; -} -button:hover { - background-color: #000; - border-color: #fff; -} -button.process { - padding-left: 1.5rem; -} -button.process:after { - content: ''; - position: absolute; - top: 50%; - left: 0.625rem; - transform: translateY(-0.375rem); - width: 0; - height: 0; - border-top: 0.375rem solid transparent; - border-bottom: 0.375rem solid transparent; - border-left: 0.375rem solid #888; -} -button.process:focus:after { - border-left-color: #84f; -} -button:focus { - background: #000; - border-color: #fff; - color: #fff; - outline: 0; -} -button:disabled { - background: #eee; - color: #888; - border-color: #bbb; - pointer-events: none; -} -button:disabled:after { - border-left-color: #aaa; -} -.buttons button { - margin-right: 0.75rem; -} -button.submit { - border-color: #d8f; - color: #fff; - background: #111; -} -button.submit:focus, -button.submit:hover { - border-color: #fff; - color: #fff; - background: #222; -} -button.submit.destroy { - background-color: rgba(16,16,16,0.5); - border-color: #b11; - color: #d11; -} -button.submit.destroy:focus, -button.submit.destroy:hover { - background: #000; - border-color: #f33; - color: #f33; -} - -/* file upload, should always be inside a container */ - -input[type=file] { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - opacity: 0; - cursor: pointer; -} -::-webkit-file-upload-button { cursor: pointer; } -.fileInput > .row { - align-items: center; -} -.fileInput > .row > span { - padding-left: 1rem; -} - -/* copy button */ - -.copyButton { - border-color: transparent; - color: #84f; - font-size: 0.675rem; - padding: 0.25rem; - margin-left: 0.25rem; -} -.desktop .copyButton:hover { - border-color: #84f; -} -.copyButton.copied { - color: #84f; -}
\ No newline at end of file diff --git a/frontend/common/header.component.js b/frontend/common/header.component.js deleted file mode 100644 index 9e96e80..0000000 --- a/frontend/common/header.component.js +++ /dev/null @@ -1,41 +0,0 @@ -import React from 'react' -// import { bindActionCreators } from 'redux' -import { connect } from 'react-redux' -import { Link } from 'react-router-dom' -import { session } from '../session' - -function Header(props) { - return ( - <header> - <div> - <Link to="/" className="logo"><b>{props.site.siteTitle}</b></Link> - </div> - <div> - <span className='username' onClick={() => changeUsername()}> - {' → '}{props.username} - </span> - </div> - </header> - ) -} - -const changeUsername = () => { - const username = prompt("Please enter your username:", session('username')) - if (username && username.length) { - session.set('username', username) - document.querySelector('Header div span').innerText = ' → ' + username // very naughty - } -} - - -const mapStateToProps = (state) => ({ - // auth: state.auth, - site: state.site, - username: session.get('username'), - // isAuthenticated: state.auth.isAuthenticated, -}) - -const mapDispatchToProps = (dispatch) => ({ -}) - -export default connect(mapStateToProps, mapDispatchToProps)(Header) diff --git a/frontend/common/imageCrop.component.js b/frontend/common/imageCrop.component.js deleted file mode 100644 index 9cae850..0000000 --- a/frontend/common/imageCrop.component.js +++ /dev/null @@ -1,41 +0,0 @@ -import React, { Component } from 'react'; -import { cropImage } from '../util' - -export default class ImageCrop extends Component { - state = { - cropURL: null - } - - componentDidMount() { - const { url, crop } = this.props - this.crop(url, crop) - } - - componentDidUpdate(prevProps) { - const { url, crop } = this.props - if (this.props.crop !== prevProps.crop) { - cropImage(url, crop).then(canvas =>{ - const cropURL = canvas.toDataURL('image/jpeg', 0.8) - this.setState({ cropURL }) - }) - } - } - - crop(url, crop) { - cropImage(url, crop).then(canvas =>{ - const cropURL = canvas.toDataURL('image/jpeg', 0.8) - this.setState({ cropURL }) - }) - } - - - render() { - const { cropURL } = this.state - if (!cropURL) { - return null - } - return ( - <img src={cropURL} className='preview' /> - ) - } -} diff --git a/frontend/common/index.js b/frontend/common/index.js deleted file mode 100644 index 5c0dc50..0000000 --- a/frontend/common/index.js +++ /dev/null @@ -1,32 +0,0 @@ -export { default as Header } from './header.component' -export { - MenuButton, SmallMenuButton, MenuRoute, -} from './menubutton.component' -export { - Select, Checkbox, Radio, FileInput, FileInputField, - TextInput, NumberInput, TextArea, SubmitButton, - LabelDescription, ColorInput, -} from './form.component' -export { - Swatch, Dot, Columns, Statistic, Detections, Progress -} from './miscellaneous.component' -export { default as TableIndex } from './tableIndex.component' -export { Loader } from './loader.component' -export { - TableObject, TableArray, TableTuples, - TableRow, TableCell -} from './table.component' -export { default as CopyToClipboardButton } from './copyToClipboardButton.component' -export { default as ImageCrop } from './imageCrop.component' -export { Modal } from './modal.component' -export { default as UploadImage } from './uploadImage.component' -export { default as Slider } from './slider.component' - -import './fonts.css' -import './app.css' -import './form.css' -import './loader.css' -import './table.css' -import './modal.css' -import './miscellaneous.css' -import './upload.css' diff --git a/frontend/common/loader.component.js b/frontend/common/loader.component.js deleted file mode 100644 index f0a0c69..0000000 --- a/frontend/common/loader.component.js +++ /dev/null @@ -1,16 +0,0 @@ -import React, { Component } from 'react'; - -import './loader.css' - -const Loader = () => ( - <div> - <div className='circular-loader color'> - <div className="stroke"> - <div className="stroke-left"></div> - <div className="stroke-right"></div> - </div> - </div> - </div> -) - -export { Loader } diff --git a/frontend/common/loader.css b/frontend/common/loader.css deleted file mode 100644 index f047e8e..0000000 --- a/frontend/common/loader.css +++ /dev/null @@ -1,125 +0,0 @@ - -@keyframes L_circle_rotate { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(360deg); - } -} -@keyframes L_stroke_rotate { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(1080deg); - } -} -@keyframes L_stroke_fix { - 0% { - transform: rotate(0deg); - } - 50% { - transform: rotate(135deg); - } - 100% { - transform: rotate(270deg); - } -} -@keyframes L_stroke_left_grow { - 0% { - transform: rotate(-5deg); - } - 50% { - transform: rotate(-140deg); - } - 100% { - transform: rotate(-5deg); - } -} -@keyframes L_stroke_right_grow { - 0% { - transform: rotate(5deg); - } - 50% { - transform: rotate(140deg); - } - 100% { - transform: rotate(5deg); - } -} -.circular-loader .stroke::before, .circular-loader .stroke-right::before, .circular-loader .stroke-left::before { - content: ""; - display: block; -} - -.circular-loader, .circular-loader .stroke, .circular-loader .stroke::before, .circular-loader .stroke-right, .circular-loader .stroke-right::before, .circular-loader .stroke-left, .circular-loader .stroke-left::before { - width: 2em; - height: 2em; - box-sizing: border-box; - border-radius: 50%; -} - -.circular-loader .stroke::before, .circular-loader .stroke-right::before, .circular-loader .stroke-left::before { - border-style: solid; - border-width: 0.21429em; - border-color: #778; -} - -.circular-loader .stroke-right, .circular-loader .stroke-left::before { - position: absolute; - clip: rect(0 2em 2em 1em); -} -.circular-loader .stroke-right::before, .circular-loader .stroke-left { - position: absolute; - clip: rect(0 1em 2em 0); -} -.circular-loader .stroke::before { - position: absolute; - clip: rect(0 1.05em 1em 0.95em); -} - -/**/ -.circular-loader { - animation: L_circle_rotate 1568.23529ms linear infinite both; -} -.circular-loader .stroke::before, -.circular-loader .stroke-right, .circular-loader .stroke-left { - animation: L_stroke_fix 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both; -} -.circular-loader .stroke { - animation: L_stroke_rotate 5332ms steps(4) infinite both; -} -.circular-loader .stroke-right::before { - animation: L_stroke_right_grow 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both; -} -.circular-loader .stroke-left::before { - animation: L_stroke_left_grow 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both; -} -@keyframes color_K { - 0%, 15% { - border-color: #44444f; - } - 25%, 40% { - border-color: #bbbbc7; - } - 50%, 65% { - border-color: #66666f; - } - 75%, 90% { - border-color: #ccccd4; - } - 100% { - border-color: #44444f; - } -} -.circular-loader.color .stroke::before { - animation: L_stroke_fix 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both, color_K 5332ms linear infinite both; -} -.circular-loader.color .stroke-right::before { - animation: L_stroke_right_grow 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both, color_K 5332ms linear infinite both; -} -.circular-loader.color .stroke-left::before { - animation: L_stroke_left_grow 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both, color_K 5332ms linear infinite both; -} - diff --git a/frontend/common/menubutton.component.js b/frontend/common/menubutton.component.js deleted file mode 100644 index d4c2e31..0000000 --- a/frontend/common/menubutton.component.js +++ /dev/null @@ -1,128 +0,0 @@ -import React, { Component } from 'react' -import { Route, Link } from 'react-router-dom' -import { history } from '../store' - -const icons = { - upload: { - title: 'Upload', - image: '/static/img/add.svg', - // svg: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>, - // svg: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" d="M9,16V10H5L12,3L19,10H15V16H9M5,20V18H19V20H5Z" /></svg> - // svg: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9,10V16H15V10H19L12,3L5,10H9M12,5.8L14.2,8H13V14H11V8H9.8L12,5.8M19,18H5V20H19V18Z" /></svg> - svg: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" d="M0,0h24v24H0V0z"/><path d="M19,12v7H5v-7H3v7c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2v-7H19z M11,6.83L8.41,9.41L7,8l5-5l5,5l-1.41,1.41L13,6.83v9.67h-2 V6.83z"/></svg>, - }, - new: { - title: 'New', - image: '/static/img/add.svg', - svg: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>, - // svg: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" d="M9,16V10H5L12,3L19,10H15V16H9M5,20V18H19V20H5Z" /></svg>, - // svg: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9,10V16H15V10H19L12,3L5,10H9M12,5.8L14.2,8H13V14H11V8H9.8L12,5.8M19,18H5V20H19V18Z" /></svg> - }, - save: { - title: 'Export', - image: '/static/img/save.svg', - svg: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M19 12v7H5v-7H3v7c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2v-7h-2zm-6 .67l2.59-2.58L17 11.5l-5 5-5-5 1.41-1.41L11 12.67V3h2v9.67z"/></svg>, - }, - saved: { - title: 'Saved', - image: '/static/img/folder.svg', - svg: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M9.17 6l2 2H20v10H4V6h5.17M10 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg>, - }, - recent: { - title: 'Recent', - image: '/static/img/history.svg', - svg: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M13 3c-4.97 0-9 4.03-9 9H1l3.89 3.89.07.14L9 12H6c0-3.87 3.13-7 7-7s7 3.13 7 7-3.13 7-7 7c-1.93 0-3.68-.79-4.94-2.06l-1.42 1.42C8.27 19.99 10.51 21 13 21c4.97 0 9-4.03 9-9s-4.03-9-9-9zm-1 5v5l4.25 2.52.77-1.28-3.52-2.09V8z"/></svg>, - }, - random: { - title: 'Random', - image: '/static/img/random.svg', - // svg: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M10.59 9.17L5.41 4 4 5.41l5.17 5.17 1.42-1.41zM14.5 4l2.04 2.04L4 18.59 5.41 20 17.96 7.46 20 9.5V4h-5.5zm.33 9.41l-1.41 1.41 3.13 3.13L14.5 20H20v-5.5l-2.04 2.04-3.13-3.13z"/></svg>, - svg: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V5h14v14z"/><circle cx="7.5" cy="16.5" r="1.5"/><circle cx="7.5" cy="7.5" r="1.5"/><circle cx="12" cy="12" r="1.5"/><circle cx="16.5" cy="16.5" r="1.5"/><circle cx="16.5" cy="7.5" r="1.5"/></svg> - }, - menu: { - title: 'Menu', - image: '/static/img/menu.svg', - svg: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"/></svg>, - }, - list: { - title: 'List', - image: '/static/img/view_list.svg', - svg: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path opacity=".87" fill="none" d="M0 0h24v24H0V0z"/><path d="M3 5v14h17V5H3zm4 2v2H5V7h2zm-2 6v-2h2v2H5zm0 2h2v2H5v-2zm13 2H9v-2h9v2zm0-4H9v-2h9v2zm0-4H9V7h9v2z"/></svg>, - }, - edit: { - title: 'Edit', - image: '/static/img/edit.svg', - svg: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M14.06 9.02l.92.92L5.92 19H5v-.92l9.06-9.06M17.66 3c-.25 0-.51.1-.7.29l-1.83 1.83 3.75 3.75 1.83-1.83c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.2-.2-.45-.29-.71-.29zm-3.6 3.19L3 17.25V21h3.75L17.81 9.94l-3.75-3.75z"/></svg> - }, - delete: { - title: 'Delete', - image: '/static/img/delete.svg', - svg: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M16 9v10H8V9h8m-1.5-6h-5l-1 1H5v2h14V4h-3.5l-1-1zM18 7H6v12c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7z"/></svg> - }, - back: { - title: 'Back', - image: '/static/img/back.svg', - svg: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path opacity=".87" fill="none" d="M0 0h24v24H0V0z"/><path d="M17.51 3.87L15.73 2.1 5.84 12l9.9 9.9 1.77-1.77L9.38 12l8.13-8.13z"/></svg>, - }, - image_search: { - title: 'Search', - image: '/static/img/image_search.svg', - svg: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M18 13v7H4V6h5.02c.05-.71.22-1.38.48-2H4c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2v-5l-2-2zm-1.5 5h-11l2.75-3.53 1.96 2.36 2.75-3.54zm2.8-9.11c.44-.7.7-1.51.7-2.39C20 4.01 17.99 2 15.5 2S11 4.01 11 6.5s2.01 4.5 4.49 4.5c.88 0 1.7-.26 2.39-.7L21 13.42 22.42 12 19.3 8.89zM15.5 9C14.12 9 13 7.88 13 6.5S14.12 4 15.5 4 18 5.12 18 6.5 16.88 9 15.5 9z"/><path fill="none" d="M0 0h24v24H0z"/></svg>, - }, - search: { - title: 'Search', - image: '/static/img/search.svg', - svg: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/><path d="M0 0h24v24H0z" fill="none"/></svg>, - }, - open_in_new: { - title: 'Open', - image: '/static/img/open_in_new.svg', - svg: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M19 19H5V5h7V3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-7h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z"/></svg>, - }, - test: { - title: 'Test', - image: '/static/img/fastfood.svg', - svg: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M18.06 22.99h1.66c.84 0 1.53-.64 1.63-1.46L23 5.05h-5V1h-1.97v4.05h-4.97l.3 2.34c1.71.47 3.31 1.32 4.27 2.26 1.44 1.42 2.43 2.89 2.43 5.29v8.05zM1 21.99V21h15.03v.99c0 .55-.45 1-1.01 1H2.01c-.56 0-1.01-.45-1.01-1zm15.03-7c0-8-15.03-8-15.03 0h15.03zM1.02 17h15v2h-15z"/><path fill="none" d="M0 0h24v24H0z"/></svg> - }, - // export: { - // title: 'Export', - // image: '/static/img/export.svg', - // svg: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" d="M0,0h24v24H0V0z"/><path d="M19,12v7H5v-7H3v7c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2v-7H19z M11,6.83L8.41,9.41L7,8l5-5l5,5l-1.41,1.41L13,6.83v9.67h-2 V6.83z"/></svg>, - // }, -} - -const goBack = () => history.goBack() - -export const MenuButton = ({ name, href, onClick, label, children, className }) => { - const { svg, title } = icons[name] - if (name === 'back') { - onClick = goBack - } - if (href) { - return ( - <Link to={href} className={className || 'menuButton'}> - <div className='icon'>{svg}</div> - {label === false ? "" : title} - {children} - </Link> - ) - } else { - return ( - <div className={className || 'menuButton'} onClick={onClick}> - <div className='icon'>{svg}</div> - {label === false ? "" : title} - {children} - </div> - ) - } -} - -export const SmallMenuButton = (props) => ( - <MenuButton {...props} label={false} className={props.active ? 'menuButton small active' : 'menuButton small'} /> -) - -export const MenuRoute = ({ component: Component, props, ...rest }) => ( - <Route {...rest} render={routeProps => ( - <Component {...routeProps} {...props} /> - )}/> -) diff --git a/frontend/common/miscellaneous.component.js b/frontend/common/miscellaneous.component.js deleted file mode 100644 index cf12ef5..0000000 --- a/frontend/common/miscellaneous.component.js +++ /dev/null @@ -1,71 +0,0 @@ -import React, { Component } from 'react'; -import { Link } from 'react-router-dom' -import { clamp, percent } from '../util' - -export const Swatch = ({ color }) => ( - <div - className='swatch' - style={{ backgroundColor: color ? 'rgb(' + color.join(',') + ')' : 'transparent' }} - /> -) - -export const Dot = ({ color }) => ( - <div - className='dot' - style={{ backgroundColor: color }} - /> -) - -export const Columns = ({ count, margin, width, object, children, className }) => { - if (!object || !object.length) object = children - if (!object || !object.length) return null - margin = margin || 380 - width = width || 250 - count = count || Math.floor((window.innerWidth - margin) / width) - let columns = [] - let len = object.length - let j = 0 - for (let i = 0; i < count; i++) { - let column_len = len * (i + 1) / count - let column = [] - for (; j < column_len; j++) { - column.push(<div key={j}>{object[j]}</div>) - } - columns.push(<div key={"col_" + i + "_" + j} className='column' style={{ width }}>{column}</div>) - if (j >= len) break - } - return ( - <div className={'row columnCells ' + className}> - {columns} - </div> - ) -} - -export const Statistic = ({ name, value, link }) => ( - <div className='statistic row'> - <div className='title'>{link ? <Link to={link}>{name}</Link> : name}</div> - <div className='int'>{value}</div> - </div> -) - -export const Detections = ({ detections, labels }) => ( - (detections || []).map(({ label, rect }, i) => ( - <div - className='rect' - key={i} - style={{ - left: percent(clamp(rect.x1)), - width: percent(clamp(rect.x2 - rect.x1, 0, Math.min(1.0, 1.0 - rect.x1))), - top: percent(clamp(rect.y1)), - height: percent(clamp(rect.y2 - rect.y1, 0, Math.min(1.0, 1.0 - rect.y1))), - }}> - {labels && <span>{label.replace(/_/g, ' ')}</span>} - </div> - ) -)) - -export const Progress = ({ current, total }) => ( - <div className='progress'> - <div className='bar' style={{ width: Math.round(100 * current / total) + '%' }} /> - </div> -) diff --git a/frontend/common/miscellaneous.css b/frontend/common/miscellaneous.css deleted file mode 100644 index 32c3e7b..0000000 --- a/frontend/common/miscellaneous.css +++ /dev/null @@ -1,18 +0,0 @@ - -/* Progress bar */ - -.progress { - background: #f8f8f8; - margin: 0.25rem 0; - height: 0.25rem; - width: 20rem; - position: relative; - box-shadow: 0 1px 3px rgba(0,0,0,0.2); - margin-bottom: 0.2rem; -} -.progress .bar { - position: absolute; - top: 0; left: 0; - height: 100%; - background: #11f; -} diff --git a/frontend/common/modal.component.js b/frontend/common/modal.component.js deleted file mode 100644 index 75c479c..0000000 --- a/frontend/common/modal.component.js +++ /dev/null @@ -1,9 +0,0 @@ -import React, { Component } from 'react' - -export const Modal = ({ visible, children }) => ( - <div className={visible ? "modal visible" : "modal"}> - <div> - {children} - </div> - </div> -) diff --git a/frontend/common/modal.css b/frontend/common/modal.css deleted file mode 100644 index 5e95a09..0000000 --- a/frontend/common/modal.css +++ /dev/null @@ -1,20 +0,0 @@ -.modal { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 100; - background: rgba(0,0,0,0.2); - display: none; -} -.modal.visible { - display: flex; - justify-content: center; - align-items: center; -} -.modal > div { - background: #fff; - padding: 1rem; - box-shadow: 0 2px 4px rgba(0,0,0,0.5); -} diff --git a/frontend/common/slider.component.js b/frontend/common/slider.component.js deleted file mode 100644 index 9d96b1e..0000000 --- a/frontend/common/slider.component.js +++ /dev/null @@ -1,120 +0,0 @@ -import React, { Component } from 'react' -import { default as throttle } from 'lodash.throttle' - -const SLIDER_THROTTLE_TIME = 1000 / 30 - -export default class Slider extends Component { - state = { - value: 0 - } - - constructor(props){ - super(props) - this.timeout = 0 - this.handleInput = this.handleInput.bind(this) - this.handleRange = this.handleRange.bind(this) - this.handleKeyDown = this.handleKeyDown.bind(this) - this.onChange = throttle(props.onChange, SLIDER_THROTTLE_TIME) - } - componentDidMount() { - let { value } = this.props - if (this.props.type === 'int') { - value = parseInt(value) - } - this.setState({ value }) - } - componentDidUpdate(prevProps) { - let { value } = this.props - if (prevProps.value !== value) { - if (this.props.type === 'int') { - value = parseInt(value) - } - this.setState({ value }) - } - } - handleInput(e){ - let { name } = this.props - let new_value = e.target.value - if (new_value === '') { - new_value = this.props.defaultValue || (this.props.max - this.props.min) / 2 - } - else if (this.props.type === 'int') { - new_value = parseInt(new_value) - } - else if (this.props.type === 'odd') { - new_value = parseInt(Math.floor(new_value / 2) * 2 + 1) - } - else { - new_value = parseFloat(new_value) - } - if (this.state.value !== new_value) { - this.setState({ value: new_value }) - this.props.onChange(this.props.name, new_value) - } - } - handleKeyDown(e) { - console.log(e.keyCode) - } - handleRange(e){ - let { value: new_value } = e.target - if (this.props.type === 'int') { - new_value = parseInt(new_value) - } - else if (this.props.type === 'odd') { - new_value = parseInt(Math.floor(new_value / 2) * 2 + 1) - } - else if (this.props.type === 'list') { - new_value = this.props.options[new_value] || this.props.options[0] - } - else { - new_value = parseFloat(new_value) - } - this.setState({ value: new_value }) - this.onChange(this.props.name, new_value) - } - render(){ - let { name, title } = this.props - let value = this.state.value - if (typeof value === 'undefined') { - value = this.props.min - } - let text_value = value - let step; - let min = this.props.min || 0 - let max = this.props.max || 0 - if (this.props.type === 'int') { - step = 1 - } else if (this.props.type === 'list') { - min = 0 - max = this.props.options.length - 1 - step = 1 - value = this.props.options.indexOf(value) - } else { - step = (this.props.max - this.props.min) / 100 - text_value = parseFloat(value).toFixed(2) - } - return ( - <label className={this.props.error ? 'slider error' : 'slider'}> - <span>{title}</span> - <input - type='number' - min={min} - max={max} - step={step} - value={text_value} - onKeyDown={this.handleKeyDown} - onChange={this.handleInput} - onBlur={this.handleInput} - /> - <input - type='range' - min={min} - max={max} - step={step} - value={value} - onChange={this.handleRange} - /> - </label> - ) - } -} diff --git a/frontend/common/table.component.js b/frontend/common/table.component.js deleted file mode 100644 index 8a74a79..0000000 --- a/frontend/common/table.component.js +++ /dev/null @@ -1,128 +0,0 @@ -import React from 'react' - -import { formatName } from '../util' - -const __HR__ = '__HR__' - -export function TableObject({ tag, object, order, summary }) { - if (!object) return null - if (object === 'loading') { - return <div className='tableObject loading'>{tag}{': Loading'}</div> - } - if (object.err) { - return <div className='tableObject error'>{tag}{' Error: '}{object.err}</div> - } - let objects = Object.keys(object) - if (order) { - const grouped = objects.reduce((a, b) => { - const index = order.indexOf(b) - if (index !== -1) { - a.order.push([index, b]) - } else { - a.alpha.push(b) - } - return a - }, { order: [], alpha: [] }) - objects = grouped.order - .sort((a, b) => a[0] - b[0]) - .map(([i, s]) => s) - if (!summary) { - objects = objects - // .concat([__HR__]) - .concat(grouped.alpha.sort()) - } - } else { - objects = objects.sort() - } - return ( - <div> - {tag && <h3 className='tt'>{tag}</h3>} - <table className={'tableObject ' + tag}> - <tbody> - {objects.map((key, i) => ( - <TableRow key={key + '_' + i} name={key} value={object[key]} /> - ))} - </tbody> - </table> - </div> - ) -} - -export function TableArray({ tag, list }) { - if (!list) return null - return ( - <div> - {tag && <h3>{tag}</h3>} - <table className={'tableArray ' + tag}> - <tbody> - {list.map((value, i) => ( - <tr key={tag + '_' + i}> - <TableCell value={value} /> - </tr> - ))} - </tbody> - </table> - </div> - ) -} - -export function TableTuples({ tag, list }) { - if (!list) return null - return ( - <div> - {tag && <h3>{tag}</h3>} - <table className={'tableTuples ' + tag}> - <tbody> - {list.map(([key, ...values], i) => ( - <tr key={tag + '_' + i}> - <th>{formatName(key)}</th> - {values.map((value, j) => ( - <TableCell key={i + '_' + j} value={value} /> - ))} - </tr> - ))} - </tbody> - </table> - </div> - ) -} - -export function TableRow({ name, value }) { - if (name === __HR__) { - return ( - <tr> - <th className='tr'> - <hr /> - </th> - </tr> - ) - } - return ( - <tr> - <th>{formatName(name)}</th> - <TableCell name={name} value={value} /> - </tr> - ) -} - -export function TableCell({ value }) { - if (value && typeof value === 'object') { - if (value._raw) { - value = value.value - } else if (value.length) { - value = <TableArray nested tag={''} list={value} /> - } else { - value = <TableObject nested tag={''} object={value} /> - } - } - if (typeof value === 'boolean') { - return <td>{value ? <Pill type='yes' /> : <Pill type='no' />}</td> - } - return ( - <td>{value}</td> - ) -} - -export const Pill = ({ color, type }) => ( - <div className={'pill ' + type} style={{ backgroundColor: color }}>{type}</div> -)
\ No newline at end of file diff --git a/frontend/common/table.css b/frontend/common/table.css deleted file mode 100644 index 4752e21..0000000 --- a/frontend/common/table.css +++ /dev/null @@ -1,96 +0,0 @@ -/* tables on metadata pages */ - -h3.tt { - margin: 1.5rem 0 0.25rem 0; - font-size: 1.25rem; -} -table { - border: 0; - margin: 0; - padding: 0; - border-spacing: 0; - line-height: 1.5; - color: #666; -} -.tableObject td, -.tableObject th { - padding: 0.1875rem; - vertical-align: top; -} -.tableObject hr { - width: 100%; - color: transparent; - border: 0; - border-bottom: 1px solid #bbb; - align: left; - margin: 3px 0; - padding: 0; -} -.tableObject th, -.tableTuples th { - min-width: 8rem; - text-align: left; - text-transform: capitalize; - padding-left: 0; - padding-right: 0.625rem; - font-weight: 300; - color: #333; -} -.tableTuples td { - text-align: right; -} -.tableObject td { - font-weight: normal; - color: #000; -} -.tableObject .tableObject { - border: 0.0625rem solid #ddd; -} -.tableArray { - border: 0.0625rem solid #ddd; - border-spacing: 0; -} -.tableArray td { - border-bottom: 0.0625rem solid #ddd; -} - -.rows .tableRow { - flex-wrap: wrap; -} -.rows .row > div.galleryRow { - padding: 0 0.75rem 0.75rem 0.75rem; -} -.rows .row > div.galleryRow > div { - display: flex; - flex-direction: row; - max-height: 100px; - flex-wrap: wrap; - max-width: 100%; - overflow: hidden; -} -.galleryRow .thumbnail { - height: 100px; - margin-right: 0.75rem; -} -/* -.gray { - font-size: 12px; - color: #888; - display: block; -} -.sha256.heading { - margin: 20px 0 0px; -} -.gray span { - padding-right: 5px; -} -.gray { - margin-bottom: 10px; -} -.gray a { - color: #666; -} -*/ -.tableIndex { - width: 100%; -}
\ No newline at end of file diff --git a/frontend/common/tableIndex.component.js b/frontend/common/tableIndex.component.js deleted file mode 100644 index a34a9e9..0000000 --- a/frontend/common/tableIndex.component.js +++ /dev/null @@ -1,129 +0,0 @@ -import React, { Component } from 'react' -import { Link } from 'react-router-dom' -import { connect } from 'react-redux' - -import { formatDateTime } from '../util' -import { Loader, Swatch, Dot } from '../common' - -/* - <TableIndex - title="Collections" - actions={actions.collection} - data={data.collection.index} - fields={[ - { name: 'title', type: 'title', link: row => '/collection/' + row.id + '/show/' }, - { name: 'username', type: 'string' }, - { name: 'date', type: 'date' }, - { name: 'notes', type: 'text' }, - ]} - /> -*/ - -export default class TableIndex extends Component { - componentDidMount() { - this.props.actions && this.props.actions.index() - } - - render() { - const { data, els, title, fields, noHeadings, notFoundMessage } = this.props - if (data.loading) { - return <Loader /> - } - if (!els && (!data.lookup || !data.order.length)) { - return ( - <div> - <h1>{title}</h1> - <p className='gray'> - {notFoundMessage || ("No " + title)} - </p> - </div> - ) - } - return ( - <div className='tableIndex'> - <h1>{title}</h1> - <div className='rows'> - {!noHeadings && <RowHeadings fields={fields} />} - {els - ? els.map(el => <Row key={el.id} row={el} fields={fields} />) - : data.order.map(id => <Row key={id} row={data.lookup[id]} fields={fields} />) - } - </div> - </div> - ) - } -} - -const RowHeadings = ({fields}) => { - return ( - <div className='row heading'> - {fields.map(field => { - if (field.type === 'gallery') return - let css = {} - if (field.width) { - css = { width: field.width, maxWidth: 'none', flex: 'none', } - } - if (field.flex) { - css.flex = field.flex - } - return <div key={field.name} className={field.type} style={css}>{(field.title || field.name).replace(/_/g, ' ')}</div> - })} - </div> - ) -} - -const Row = ({ row, fields }) => { - return ( - <div className='row tableRow'> - {fields.map(field => { - let value = field.valueFn ? field.valueFn(row) : row[field.name] - let css = {} - if (field.type === 'date' && (row.updated_at || row.created_at || value)) { - // value = (value || "").split('.')[0] - value = formatDateTime(row.updated_at || row.created_at || value) - } else if (field.type === 'text') { - value = String(value || "").trim().split('\n')[0].replace(/^#+/, '').substr(0, 100) - } else if (field.type === 'color') { - value = <Swatch color={value} /> - } else if (field.type === 'bool') { - value = <Dot color={value ? '#11f' : '#fff'} /> - } else if (field.type === 'str') { - value = String(value || "").replace(/_/g, ' ') - } else if (field.type === 'gallery') { - return <GalleryRow key={field.name} media={value} /> - } - if (field.width) { - css = { width: field.width, maxWidth: 'none', flex: 'none', } - } - if (field.flex) { - css.flex = field.flex - } - let className - if (field.style) { - className = field.type + ' ' + field.style - } else { - className = field.type - } - value = <div title={value} key={field.name} className={className} style={css}>{value}</div> - if (field.link) { - return <Link key={field.name} to={field.link(row)}>{value}</Link> - } - return value - })} - </div> - ) -} - -const GalleryRow = ({ media }) => { - return ( - <div className='galleryRow'> - <div> - {media.map(img => ( - <Link to={"/media/id/" + img.id + "/"} key={img.url}> - <img src={img.url} className='thumbnail' /> - </Link> - ))} - </div> - </div> - ) -} diff --git a/frontend/common/upload.css b/frontend/common/upload.css deleted file mode 100644 index 719f98c..0000000 --- a/frontend/common/upload.css +++ /dev/null @@ -1,26 +0,0 @@ -/* drag-and-drop */ - -.dragCurtain { - display: none; - pointer-events: none; - justify-content: center; - align-items: center; - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: rgba(0,0,0,0.5); - z-index: 1; -} -.dragCurtain div { - color: #11f; - background: white; - border: 2px solid #11f; - padding: 2rem; - font-size: 1.5rem; - font-weight: bold; -} -.dragging .dragCurtain { - display: flex; -} diff --git a/frontend/common/upload.helpers.js b/frontend/common/upload.helpers.js deleted file mode 100644 index f26e2cc..0000000 --- a/frontend/common/upload.helpers.js +++ /dev/null @@ -1,174 +0,0 @@ -import ExifReader from 'exifreader' - -function base64ToUint8Array(string, start, finish) { - start = start || 0 - finish = finish || string.length - // atob that shit - const binary = atob(string) - const buffer = new Uint8Array(binary.length) - for (let i = start; i < finish; i++) { - buffer[i] = binary.charCodeAt(i) - } - return buffer -} - -function getOrientation(uri) { - // Split off the base64 data - const base64String = uri.split(',')[1] - // Read off first 128KB, which is all we need to - // get the EXIF data - const arr = base64ToUint8Array(base64String, 0, 2 ** 17) - try { - const tags = ExifReader.load(arr.buffer) - // console.log(tags) - if (typeof tags.Orientation == 'number') { - return tags.Orientation - } - return tags.Orientation.value - } catch (err) { - return 1 - } -} - -function applyRotation(canvas, ctx, deg) { - const radians = deg * (Math.PI / 180) - if (deg === 90) { - ctx.translate(canvas.width, 0) - } else if (deg === 180) { - ctx.translate(canvas.width, canvas.height) - } else if (deg === 270) { - ctx.translate(0, canvas.height) - } - ctx.rotate(radians) -} - -/** - * Mapping from EXIF orientation values to data - * regarding the rotation and mirroring necessary to - * render the canvas correctly - * Derived from: - * http://www.daveperrett.com/articles/2012/07/28/exif-orientation-handling-is-a-ghetto/ - */ -const orientationToTransform = { - 1: { rotation: 0, mirror: false }, - 2: { rotation: 0, mirror: true }, - 3: { rotation: 180, mirror: false }, - 4: { rotation: 180, mirror: true }, - 5: { rotation: 90, mirror: true }, - 6: { rotation: 90, mirror: false }, - 7: { rotation: 270, mirror: true }, - 8: { rotation: 270, mirror: false } -} - -function applyOrientationCorrection(canvas, ctx, uri) { - const orientation = getOrientation(uri) - // Only apply transform if there is some non-normal orientation - if (orientation && orientation !== 1) { - console.log(orientation) - const transform = orientationToTransform[orientation] - const { rotation } = transform - const flipAspect = rotation === 90 || rotation === 270 - if (flipAspect) { - // Fancy schmancy swap algo - canvas.width = canvas.height + canvas.width - canvas.height = canvas.width - canvas.height - canvas.width -= canvas.height - } - if (rotation > 0) { - applyRotation(canvas, ctx, rotation) - } - } -} - -function getScale(width, height, viewportWidth, viewportHeight, fillViewport) { - function fitHorizontal() { - return viewportWidth / width - } - function fitVertical() { - return viewportHeight / height - } - fillViewport = !!fillViewport - const landscape = (width / height) > (viewportWidth / viewportHeight) - if (landscape) { - if (fillViewport) { - return fitVertical() - } - if (width > viewportWidth) { - return fitHorizontal() - } - } else { - if (fillViewport) { - return fitHorizontal() - } - if (height > viewportHeight) { - return fitVertical() - } - } - return 1 -} - -export function renderToCanvas(img, options) { - if (!img) return null - options = options || {} - - // Canvas max size for any side - const maxSide = options.maxSide || 0 - const canvas = document.createElement('canvas') - const ctx = canvas.getContext('2d') - const initialScale = options.scale || 1 - /* - // constrain - // Scale to needed to constrain canvas to max size - let scale = getScale(img.naturalWidth * initialScale, img.naturalHeight * initialScale, maxSide, maxSide, true) - // console.log(scale) - // Still need to apply the user defined scale - scale *= initialScale - canvas.width = Math.round(img.naturalWidth * scale) - canvas.height = Math.round(img.naturalHeight * scale) - */ - const { naturalWidth, naturalHeight } = img - if (maxSide > 0) { - if (naturalWidth > naturalHeight) { - canvas.width = Math.min(maxSide, naturalWidth) - canvas.height = naturalHeight * canvas.width / naturalWidth - } else { - canvas.height = Math.min(maxSide, naturalHeight) - canvas.width = naturalWidth * canvas.height / naturalHeight - } - } else { - canvas.width = naturalWidth - canvas.height = naturalHeight - } - const { correctOrientation } = options - const jpeg = !!img.src.match(/data:image\/jpeg|\.jpeg$|\.jpg$/i) - const hasDataURI = !!img.src.match(/^data:/) - - ctx.save() - - // Can only correct orientation on JPEGs represented as dataURIs - // for the time being - if (correctOrientation && jpeg && hasDataURI) { - applyOrientationCorrection(canvas, ctx, img.src) - } - // Resize image if too large - // if (scale !== 1) { - // ctx.scale(scale, scale) - // } - - ctx.drawImage(img, 0, 0, canvas.width, canvas.height) - ctx.restore() - - return canvas -} - -export function renderThumbnail(img) { - const resized = renderToCanvas(img, { correctOrientation: true }) - // const canvas = document.createElement('canvas') // document.querySelector('#user_photo_canvas') - // const ctx = canvas.getContext('2d') - // ctx.fillStyle = 'black' - // ctx.fillRect(0, 0, MAX_SIDE, MAX_SIDE) - // const xOffset = (MAX_SIDE - resized.width) / 2 - // const yOffset = (MAX_SIDE - resized.height) / 2 - // ctx.drawImage(resized, xOffset, yOffset, resized.width, resized.height) - return resized -} diff --git a/frontend/common/uploadImage.component.js b/frontend/common/uploadImage.component.js deleted file mode 100644 index 3ae41c8..0000000 --- a/frontend/common/uploadImage.component.js +++ /dev/null @@ -1,74 +0,0 @@ -import React, { Component } from 'react' - -import { renderThumbnail } from './upload.helpers' - -export default class UploadImageComponent extends Component { - constructor(props) { - super(props) - document.body.addEventListener("dragover", this.dragOver.bind(this)) - document.body.addEventListener("dragleave", this.dragLeave.bind(this)) - document.body.addEventListener("drop", this.upload.bind(this)) - } - - dragOver(e) { - e.stopPropagation() - e.preventDefault() - document.body.className = 'dragging' - } - - dragLeave(e) { - e.stopPropagation() - e.preventDefault() - document.body.className = '' - } - - upload(e) { - e.preventDefault() - document.body.className = '' - const files = e.dataTransfer ? e.dataTransfer.files : e.target.files - let i - let file - for (i = 0; i < files.length; i++) { - file = files[i] - if (file && file.type.match('image.*')) break - } - if (!file) { - console.log('No file specified') - return - } - const fr = new FileReader() - fr.onload = fileReaderEvent => { - fr.onload = null - const img = new Image() - img.onload = () => { - img.onload = null - this.resizeAndUpload(file, img) - } - img.src = fileReaderEvent.target.result - } - fr.readAsDataURL(file) - } - - resizeAndUpload(file, img) { - const canvas = renderThumbnail(img, this.props) - canvas.toBlob(blob => { - this.props.onUpload({ file, img, canvas, blob, freshen: true }) - }, 'image/jpeg', this.props.quality || 80) - } - - render() { - return ( - <div className='uploadButton'> - <input - type="file" - accept="image/*" - onChange={this.upload.bind(this)} - required - /> - <div className='dragCurtain'> - <div className='dragLabel'>Drop image here</div> - </div> - </div> - ) - } -} |
