WIP Create DragAndDrop component

Co-authored-by: Alex Paxton <thealexpaxton@gmail.com>
pull/3559/head
Iris Scholten 2018-05-31 18:53:42 -07:00
parent 44cef03127
commit 3b6c973ee8
8 changed files with 368 additions and 127 deletions

View File

@ -7,13 +7,14 @@ import React, {
ReactElement, ReactElement,
} from 'react' } from 'react'
import WriteDataFooter from 'src/data_explorer/components/WriteDataFooter' import WriteDataFooter from 'src/data_explorer/components/WriteDataFooter'
import DragAndDrop from 'src/shared/components/DragAndDrop'
interface Props { interface Props {
handleCancelFile: (e: MouseEvent<HTMLButtonElement>) => void handleCancelFile: (e: MouseEvent<HTMLButtonElement>) => void
handleEdit: (e: ChangeEvent<HTMLTextAreaElement>) => void handleEdit: (e: ChangeEvent<HTMLTextAreaElement>) => void
handleKeyUp: (e: KeyboardEvent<HTMLTextAreaElement>) => void handleKeyUp: (e: KeyboardEvent<HTMLTextAreaElement>) => void
handleFile: (drop: boolean) => (e: DragEvent<HTMLInputElement>) => void handleFile: (drop: boolean) => (e: DragEvent<HTMLInputElement>) => void
handleSubmit: (e: MouseEvent<HTMLButtonElement>) => void handleSubmit: (uploadContent: string) => void
inputContent: string inputContent: string
uploadContent: string uploadContent: string
fileName: string fileName: string
@ -33,9 +34,9 @@ class WriteDataBody extends PureComponent<Props> {
) )
} }
private handleFile = (e: any): void => { // private handleFile = (e: any): void => {
this.props.handleFile(false)(e) // this.props.handleFile(false)(e)
} // }
private get input(): JSX.Element { private get input(): JSX.Element {
const {isManual} = this.props const {isManual} = this.props
@ -43,7 +44,13 @@ class WriteDataBody extends PureComponent<Props> {
return this.textarea return this.textarea
} }
return this.dragArea // return this.dragArea
return (
<DragAndDrop
handleSubmit={this.props.handleSubmit}
fileTypesToAccept="text/*, application/gzip"
/>
)
} }
private get textarea(): ReactElement<HTMLTextAreaElement> { private get textarea(): ReactElement<HTMLTextAreaElement> {
@ -62,86 +69,80 @@ class WriteDataBody extends PureComponent<Props> {
) )
} }
private get dragArea(): ReactElement<HTMLDivElement> { // private get dragArea(): ReactElement<HTMLDivElement> {
const {fileInput, handleFileOpen} = this.props // const {fileInput, handleFileOpen} = this.props
return ( // return (
<div className={this.dragAreaClass} onClick={handleFileOpen}> // <div className={this.dragAreaClass} onClick={handleFileOpen}>
{this.dragAreaHeader} // {this.dragAreaHeader}
<div className={this.infoClass} /> // <div className={this.infoClass} />
<input // <input
type="file" // type="file"
ref={fileInput} // ref={fileInput}
className="write-data-form--upload" // className="write-data-form--upload"
accept="text/*, application/gzip" // accept="text/*, application/gzip"
onChange={this.handleFile} // onChange={this.handleFile}
/> // />
{this.buttons} // {this.buttons}
</div> // </div>
) // )
} // }
private get dragAreaHeader(): ReactElement<HTMLHeadElement> { // private get dragAreaHeader(): ReactElement<HTMLHeadElement> {
const {uploadContent, fileName} = this.props // const {uploadContent, fileName} = this.props
if (uploadContent) { // if (uploadContent) {
return <h3 className="write-data-form--filepath_selected">{fileName}</h3> // return <h3 className="write-data-form--filepath_selected">{fileName}</h3>
} // }
return ( // return (
<h3 className="write-data-form--filepath_empty"> // <h3 className="write-data-form--filepath_empty">
Drop a file here or click to upload // Drop a file here or click to upload
</h3> // </h3>
) // )
} // }
private get infoClass(): string { // private get infoClass(): string {
const {uploadContent} = this.props // const {uploadContent} = this.props
if (uploadContent) { // if (uploadContent) {
return 'write-data-form--graphic write-data-form--graphic_success' // return 'write-data-form--graphic write-data-form--graphic_success'
} // }
return 'write-data-form--graphic' // return 'write-data-form--graphic'
} // }
private get buttons(): ReactElement<HTMLSpanElement> | null { // private get buttons(): ReactElement<HTMLSpanElement> | null {
const {uploadContent, handleSubmit, handleCancelFile} = this.props // const {uploadContent, handleSubmit, handleCancelFile} = this.props
if (!uploadContent) { // if (!uploadContent) {
return null // return null
} // }
return ( // return (
<span className="write-data-form--file-submit"> // <span className="write-data-form--file-submit">
<button className="btn btn-md btn-success" onClick={handleSubmit}> // <button className="btn btn-md btn-success" onClick={handleSubmit}>
Write this File // Write this File
</button> // </button>
<button className="btn btn-md btn-default" onClick={handleCancelFile}> // <button className="btn btn-md btn-default" onClick={handleCancelFile}>
Cancel // Cancel
</button> // </button>
</span> // </span>
) // )
} // }
private get dragAreaClass(): string { // private get dragAreaClass(): string {
const {uploadContent} = this.props // const {uploadContent} = this.props
if (uploadContent) { // if (uploadContent) {
return 'write-data-form--file' // return 'write-data-form--file'
} // }
return 'write-data-form--file write-data-form--file_active' // return 'write-data-form--file write-data-form--file_active'
} // }
private get footer(): JSX.Element | null { private get footer(): JSX.Element | null {
const { const {isUploading, isManual, inputContent, uploadContent} = this.props
isUploading,
isManual,
inputContent,
handleSubmit,
uploadContent,
} = this.props
if (!isManual) { if (!isManual) {
return null return null
@ -152,11 +153,15 @@ class WriteDataBody extends PureComponent<Props> {
isUploading={isUploading} isUploading={isUploading}
isManual={isManual} isManual={isManual}
inputContent={inputContent} inputContent={inputContent}
handleSubmit={handleSubmit} handleSubmit={this.handleSubmit}
uploadContent={uploadContent} uploadContent={uploadContent}
/> />
) )
} }
private handleSubmit = (): void => {
this.props.handleSubmit('')
}
} }
export default WriteDataBody export default WriteDataBody

View File

@ -1,4 +1,4 @@
import React, {PureComponent, MouseEvent} from 'react' import React, {PureComponent} from 'react'
import { import {
WRITE_DATA_DOCS_LINK, WRITE_DATA_DOCS_LINK,
DATA_IMPORT_DOCS_LINK, DATA_IMPORT_DOCS_LINK,
@ -12,7 +12,8 @@ interface Props {
isUploading: boolean isUploading: boolean
uploadContent: string uploadContent: string
inputContent: string inputContent: string
handleSubmit: (e: MouseEvent<HTMLButtonElement>) => void // handleSubmit: (e: MouseEvent<HTMLButtonElement>) => void
handleSubmit: () => void
} }
class WriteDataFooter extends PureComponent<Props> { class WriteDataFooter extends PureComponent<Props> {

View File

@ -1,10 +1,9 @@
import React, { import React, {
PureComponent, PureComponent,
DragEvent, // DragEvent,
ChangeEvent, ChangeEvent,
KeyboardEvent, KeyboardEvent,
} from 'react' } from 'react'
import classnames from 'classnames'
import OnClickOutside from 'src/shared/components/OnClickOutside' import OnClickOutside from 'src/shared/components/OnClickOutside'
import WriteDataBody from 'src/data_explorer/components/WriteDataBody' import WriteDataBody from 'src/data_explorer/components/WriteDataBody'
@ -13,7 +12,7 @@ import WriteDataHeader from 'src/data_explorer/components/WriteDataHeader'
import {OVERLAY_TECHNOLOGY} from 'src/shared/constants/classNames' import {OVERLAY_TECHNOLOGY} from 'src/shared/constants/classNames'
import {ErrorHandling} from 'src/shared/decorators/errors' import {ErrorHandling} from 'src/shared/decorators/errors'
import {Source, DropdownItem} from 'src/types' import {Source, DropdownItem} from 'src/types'
let dragCounter = 0 // let dragCounter = 0
interface Props { interface Props {
source: Source source: Source
@ -54,17 +53,9 @@ class WriteDataForm extends PureComponent<Props, State> {
public render() { public render() {
const {onClose, errorThrown, source} = this.props const {onClose, errorThrown, source} = this.props
const {dragClass} = this.state
return ( return (
<div <div className={OVERLAY_TECHNOLOGY}>
onDrop={this.handleFile(true)}
onDragOver={this.handleDragOver}
onDragEnter={this.handleDragEnter}
onDragExit={this.handleDragLeave}
onDragLeave={this.handleDragLeave}
className={classnames(OVERLAY_TECHNOLOGY, dragClass)}
>
<div className="write-data-form"> <div className="write-data-form">
<WriteDataHeader <WriteDataHeader
{...this.state} {...this.state}
@ -105,21 +96,23 @@ class WriteDataForm extends PureComponent<Props, State> {
} }
} }
private handleSubmit = async () => { private handleSubmit = async (uploadContent: string) => {
const {onClose, source, writeLineProtocol} = this.props console.log('submit!', uploadContent)
const {inputContent, uploadContent, selectedDatabase, isManual} = this.state
const content = isManual ? inputContent : uploadContent
this.setState({isUploading: true})
try { // const {onClose, source, writeLineProtocol} = this.props
await writeLineProtocol(source, selectedDatabase, content) // const {inputContent, uploadContent, selectedDatabase, isManual} = this.state
this.setState({isUploading: false}) // const content = isManual ? inputContent : uploadContent
onClose() // this.setState({isUploading: true})
window.location.reload()
} catch (error) { // try {
this.setState({isUploading: false}) // await writeLineProtocol(source, selectedDatabase, content)
console.error(error.data.error) // this.setState({isUploading: false})
} // onClose()
// window.location.reload()
// } catch (error) {
// this.setState({isUploading: false})
// console.error(error.data.error)
// }
} }
private handleEdit = (e: ChangeEvent<HTMLTextAreaElement>): void => { private handleEdit = (e: ChangeEvent<HTMLTextAreaElement>): void => {
@ -159,24 +152,24 @@ class WriteDataForm extends PureComponent<Props, State> {
this.fileInput.value = '' this.fileInput.value = ''
} }
private handleDragOver = (e: DragEvent<HTMLDivElement>) => { // private handleDragOver = (e: DragEvent<HTMLDivElement>) => {
e.preventDefault() // e.preventDefault()
e.stopPropagation() // e.stopPropagation()
} // }
private handleDragEnter = (e: DragEvent<HTMLDivElement>): void => { // private handleDragEnter = (e: DragEvent<HTMLDivElement>): void => {
dragCounter += 1 // dragCounter += 1
e.preventDefault() // e.preventDefault()
this.setState({dragClass: 'drag-over'}) // this.setState({dragClass: 'drag-over'})
} // }
private handleDragLeave = (e: DragEvent<HTMLDivElement>): void => { // private handleDragLeave = (e: DragEvent<HTMLDivElement>): void => {
dragCounter -= 1 // dragCounter -= 1
e.preventDefault() // e.preventDefault()
if (dragCounter === 0) { // if (dragCounter === 0) {
this.setState({dragClass: 'drag-none'}) // this.setState({dragClass: 'drag-none'})
} // }
} // }
private handleFileOpen = (): void => { private handleFileOpen = (): void => {
const {uploadContent} = this.state const {uploadContent} = this.state

View File

@ -32,7 +32,10 @@ class WriteDataHeader extends PureComponent<Props> {
database={selectedDatabase} database={selectedDatabase}
onErrorThrown={errorThrown} onErrorThrown={errorThrown}
/> />
<ul className="nav nav-tablist nav-tablist-sm"> <ul
className="nav nav-tablist nav-tablist-sm"
style={{position: 'relative', zIndex: 9010}}
>
<li onClick={this.handleToggleOff} className={this.fileUploadClass}> <li onClick={this.handleToggleOff} className={this.fileUploadClass}>
File Upload File Upload
</li> </li>
@ -45,7 +48,10 @@ class WriteDataHeader extends PureComponent<Props> {
</li> </li>
</ul> </ul>
</div> </div>
<div className="page-header__right"> <div
className="page-header__right"
style={{position: 'relative', zIndex: 9010}}
>
<span className="page-header__dismiss" onClick={onClose} /> <span className="page-header__dismiss" onClick={onClose} />
</div> </div>
</div> </div>

View File

@ -0,0 +1,212 @@
import React, {PureComponent, ReactElement, DragEvent} from 'react'
interface Props {
fileTypesToAccept?: string
containerClass?: string
handleSubmit: (uploadContent: string) => void
}
interface State {
inputContent: string | null
uploadContent: string
fileName: string
progress: string
dragClass: string
isUploading: boolean
}
let dragCounter = 0
class DragAndDrop extends PureComponent<Props, State> {
private fileInput: HTMLInputElement
constructor(props: Props) {
super(props)
this.state = {
inputContent: null,
uploadContent: '',
fileName: '',
progress: '',
dragClass: 'drag-none',
isUploading: false,
}
}
public render() {
return (
<div className="drag-and-drop">
{/* (Invisible, covers entire screen)
This div handles drag only*/}
<div
onDrop={this.handleFile(true)}
onDragOver={this.handleDragOver}
onDragEnter={this.handleDragEnter}
onDragExit={this.handleDragLeave}
onDragLeave={this.handleDragLeave}
className="drag-and-drop--dropzone"
/>
{/* visible form, handles drag & click */}
{this.dragArea}
</div>
)
}
private get dragArea(): ReactElement<HTMLDivElement> {
return (
<div
className={this.dragAreaClass}
onClick={this.handleFileOpen}
onDrop={this.handleFile(true)}
onDragOver={this.handleDragOver}
onDragEnter={this.handleDragEnter}
onDragExit={this.handleDragLeave}
onDragLeave={this.handleDragLeave}
>
{this.dragAreaHeader}
<div className={this.infoClass} />
<input
type="file"
ref={r => (this.fileInput = r)}
className="write-data-form--upload"
accept={this.fileTypesToAccept}
onChange={this.handleFile(false)}
/>
{this.buttons}
</div>
)
}
private get fileTypesToAccept(): string {
const {fileTypesToAccept} = this.props
if (!fileTypesToAccept) {
return '*'
}
return fileTypesToAccept
}
private get infoClass(): string {
const {uploadContent} = this.state
if (uploadContent) {
return 'write-data-form--graphic write-data-form--graphic_success'
}
return 'write-data-form--graphic'
}
private get dragAreaClass(): string {
const {uploadContent} = this.state
if (uploadContent) {
return 'drag-and-drop--form'
}
return 'drag-and-drop--form active'
}
private get dragAreaHeader(): ReactElement<HTMLHeadElement> {
const {uploadContent, fileName} = this.state
if (uploadContent) {
return <h3 className="drag-and-drop--header selected">{fileName}</h3>
}
return (
<h3 className="drag-and-drop--header empty">
Drop a file here or click to upload
</h3>
)
}
private get buttons(): ReactElement<HTMLSpanElement> | null {
const {uploadContent} = this.state
if (!uploadContent) {
return null
}
return (
<span className="drag-and-drop--buttons">
<button className="btn btn-md btn-success" onClick={this.handleSubmit}>
Write this File
</button>
<button
className="btn btn-md btn-default"
onClick={this.handleCancelFile}
>
Cancel
</button>
</span>
)
}
private handleSubmit = () => {
const {handleSubmit} = this.props
const {uploadContent} = this.state
handleSubmit(uploadContent)
}
private handleFile = (drop: boolean) => (e: any): void => {
let file
if (drop) {
file = e.dataTransfer.files[0]
this.setState({
dragClass: 'drag-none',
})
} else {
file = e.currentTarget.files[0]
}
if (!file) {
return
}
e.preventDefault()
e.stopPropagation()
const reader = new FileReader()
reader.readAsText(file)
reader.onload = loadEvent => {
this.setState({
uploadContent: loadEvent.target.result,
fileName: file.name,
})
}
}
private handleFileOpen = (): void => {
const {uploadContent} = this.state
if (uploadContent === '') {
this.fileInput.click()
}
}
private handleCancelFile = (): void => {
this.setState({uploadContent: ''})
this.fileInput.value = ''
}
private handleDragOver = (e: DragEvent<HTMLDivElement>) => {
e.preventDefault()
e.stopPropagation()
}
private handleDragEnter = (e: DragEvent<HTMLDivElement>): void => {
dragCounter += 1
e.preventDefault()
this.setState({dragClass: 'drag-over'})
}
private handleDragLeave = (e: DragEvent<HTMLDivElement>): void => {
dragCounter -= 1
e.preventDefault()
if (dragCounter === 0) {
this.setState({dragClass: 'drag-none'})
}
}
}
export default DragAndDrop

View File

@ -41,6 +41,7 @@
@import 'components/color-dropdown'; @import 'components/color-dropdown';
@import 'components/custom-time-range'; @import 'components/custom-time-range';
@import 'components/customize-fields'; @import 'components/customize-fields';
@import 'components/drag-and-drop';
@import 'components/dygraphs'; @import 'components/dygraphs';
@import 'components/fancy-scrollbars'; @import 'components/fancy-scrollbars';
@import 'components/fancy-table'; @import 'components/fancy-table';

View File

@ -0,0 +1,21 @@
/*
Drag and Drop Styles
------------------------------------------------------------------------------
*/
$drag-and-drop--z-dropzone: 9000;
$drag-and-drop--z-form: 9010;
.drag-and-drop--dropzone {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: $drag-and-drop--z-dropzone;
}
.drag-and-drop--form {
position: relative;
z-index: $drag-and-drop--z-form;
}

View File

@ -9,14 +9,14 @@ $write-data--drag-overlay-z: 1;
$write-data--max-width: 960px; $write-data--max-width: 960px;
$write-data--gutter: 30px; $write-data--gutter: 30px;
$write-data--margin: 18px; $write-data--margin: 18px;
$write-data--input-height: calc(90vh - 48px - 60px - 36px); // Heights of everything but input height $write-data--input-height: calc(
90vh - 48px - 60px - 36px
); // Heights of everything but input height
$write-data--transition: opacity 0.4s ease; $write-data--transition: opacity 0.4s ease;
.write-data-form { .write-data-form {
max-width: $write-data--max-width; max-width: $write-data--max-width;
margin: 0 auto; margin: 0 auto;
position: relative;
z-index: $write-data--form-z;
} }
.write-data-form--header { .write-data-form--header {
height: $chronograf-page-header-height; height: $chronograf-page-header-height;
@ -40,7 +40,7 @@ $write-data--transition: opacity 0.4s ease;
.write-data-form--body { .write-data-form--body {
padding: $write-data--margin $write-data--gutter; padding: $write-data--margin $write-data--gutter;
border-radius: 0 0 $radius $radius; border-radius: 0 0 $radius $radius;
@include gradient-v($g2-kevlar,$g0-obsidian); @include gradient-v($g2-kevlar, $g0-obsidian);
} }
textarea.form-control.write-data-form--input { textarea.form-control.write-data-form--input {
height: $write-data--input-height; height: $write-data--input-height;
@ -76,9 +76,7 @@ textarea.form-control.write-data-form--input {
border: 2px solid $g4-onyx; border: 2px solid $g4-onyx;
margin-bottom: 48px; margin-bottom: 48px;
border-radius: 3px; border-radius: 3px;
transition: transition: background-color 0.25s ease, border-color 0.25s ease;
background-color 0.25s ease,
border-color 0.25s ease;
> p { > p {
color: $g12-forge; color: $g12-forge;
@ -100,7 +98,7 @@ textarea.form-control.write-data-form--input {
} }
} }
input[type="file"].write-data-form--upload { input[type='file'].write-data-form--upload {
display: none; display: none;
} }
.write-data-form--filepath_selected, .write-data-form--filepath_selected,
@ -113,8 +111,12 @@ input[type="file"].write-data-form--upload {
text-overflow: ellipsis; text-overflow: ellipsis;
margin: 0 0 30px 0; margin: 0 0 30px 0;
} }
.write-data-form--filepath_empty {color: $g12-forge;} .write-data-form--filepath_empty {
.write-data-form--filepath_selected {color: $c-rainforest;} color: $g12-forge;
}
.write-data-form--filepath_selected {
color: $c-rainforest;
}
.write-data-form--file-submit { .write-data-form--file-submit {
margin-top: 30px; margin-top: 30px;