Skip to content

Commit a90e92b

Browse files
committed
feat(App): drag and drop adds file to CreateModal
- adjust `CreateDataset` to take a filePath. We pass that filePath through our `validName` function to create a name that will work with Qri. When a filePath is given, we display all the values in the modal that we know about. Eventually this modal will be removed in favor of desktop making smart assumptions, but before we can do that we need to understand how users can set or change those assumptions, ie the dataset name and default directory locations
1 parent 2f5a954 commit a90e92b

File tree

5 files changed

+73
-24
lines changed

5 files changed

+73
-24
lines changed

app/components/App.tsx

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ import { Action } from 'redux'
44
import { CSSTransition } from 'react-transition-group'
55
import { HashRouter as Router } from 'react-router-dom'
66
import { ipcRenderer } from 'electron'
7+
import path from 'path'
8+
9+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
10+
import { faFileMedical } from '@fortawesome/free-solid-svg-icons'
711

812
// import components
913
import Toast from './Toast'
@@ -21,8 +25,9 @@ import RoutesContainer from '../containers/RoutesContainer'
2125
// import models
2226
import { ApiAction } from '../store/api'
2327
import { Modal, ModalType, HideModal } from '../models/modals'
24-
import { Toast as IToast, Selections } from '../models/store'
28+
import { Toast as IToast, Selections, ToastType } from '../models/store'
2529
import { Dataset } from '../models/dataset'
30+
import { ToastTypes } from './chrome/Toast'
2631

2732
export const QRI_CLOUD_ROOT = 'https://qri.cloud'
2833

@@ -43,6 +48,7 @@ export interface AppProps {
4348
datasetDirPath: string
4449
qriCloudAuthenticated: boolean
4550
toast: IToast
51+
openToast: (type: ToastType, message: string) => Action
4652
modal: Modal
4753
workingDataset: Dataset
4854
exportPath: string
@@ -171,6 +177,7 @@ class App extends React.Component<AppProps, AppState> {
171177
onSubmit={this.props.initDataset}
172178
onDismissed={async () => setModal(noModalObject)}
173179
setDatasetDirPath={this.props.setDatasetDirPath}
180+
filePath={modal.bodyPath ? modal.bodyPath : ''}
174181
/>
175182
)
176183
break
@@ -269,30 +276,41 @@ class App extends React.Component<AppProps, AppState> {
269276
event.stopPropagation()
270277
event.preventDefault()
271278
this.setState({ showDragDrop: true })
272-
console.log('enter')
273279
}}
274280
onDragOver={(event) => {
275281
event.stopPropagation()
276282
event.preventDefault()
277283
this.setState({ showDragDrop: true })
278-
console.log('over')
279284
}}
280285
onDragLeave={(event) => {
281286
event.stopPropagation()
282287
event.preventDefault()
283288
this.setState({ showDragDrop: false })
284-
console.log('leave')
285289
}}
286290
onDrop={(event) => {
287291
this.setState({ showDragDrop: false })
288-
console.log(event.dataTransfer.files[0].name)
289292
event.preventDefault()
290-
console.log('drop')
293+
const ext = path.extname(event.dataTransfer.files[0].path)
294+
this.props.closeToast()
295+
if (!(ext === '.csv' || ext === '.json')) {
296+
// open toast for 1 second
297+
this.props.openToast(ToastTypes.error, 'unsupported file format: only json and csv supported')
298+
setTimeout(() => this.props.closeToast(), 2500)
299+
return
300+
}
301+
this.props.setModal({
302+
type: ModalType.CreateDataset,
303+
bodyPath: event.dataTransfer.files[0].path
304+
})
291305
}}
292306
className='drag-drop'
293307
id='drag-drop'
294308
>
295-
DRAG AND DROP!
309+
<div className="inner">
310+
<div className="spacer">Create a new dataset!</div>
311+
<div className="icon"><FontAwesomeIcon size="5x" icon={faFileMedical} /></div>
312+
</div>
313+
<div className="footer">You can import csv and json files</div>
296314
</div>
297315
)
298316
}
@@ -311,7 +329,9 @@ class App extends React.Component<AppProps, AppState> {
311329
return (
312330
<div className='drag'
313331
onDragEnter={() => {
314-
this.setState({ showDragDrop: true })
332+
if (this.props.modal.type === ModalType.NoModal) {
333+
this.setState({ showDragDrop: true })
334+
}
315335
}}
316336
style={{
317337
height: '100%',

app/components/modals/CreateDataset.tsx

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,29 @@ interface CreateDatasetProps {
1818
onSubmit: (path: string, name: string, dir: string, mkdir: string) => Promise<ApiAction>
1919
datasetDirPath: string
2020
setDatasetDirPath: (path: string) => Action
21+
filePath: string
2122
}
2223

2324
const CreateDataset: React.FunctionComponent<CreateDatasetProps> = (props) => {
2425
const {
2526
onDismissed,
2627
onSubmit,
2728
datasetDirPath: persistedDatasetDirPath,
28-
setDatasetDirPath: saveDatasetDirPath
29+
setDatasetDirPath: saveDatasetDirPath,
30+
filePath: givenFilePath
2931
} = props
30-
const [datasetName, setDatasetName] = React.useState('')
32+
33+
const validName = (name: string): string => {
34+
// cast name to meet our specification
35+
// make lower case, snakecase, and remove invalid characters
36+
let coercedName = changeCase.lowerCase(name)
37+
coercedName = changeCase.snakeCase(name)
38+
return coercedName.replace(/^[^a-z0-9_]+$/g, '')
39+
}
40+
3141
const [datasetDirPath, setDatasetDirPath] = React.useState(persistedDatasetDirPath)
32-
const [filePath, setFilePath] = React.useState('')
42+
const [filePath, setFilePath] = React.useState(givenFilePath)
43+
const [datasetName, setDatasetName] = React.useState(validName(path.basename(filePath, path.extname(filePath))))
3344

3445
const [dismissable, setDismissable] = React.useState(true)
3546
const [buttonDisabled, setButtonDisabled] = React.useState(true)
@@ -78,16 +89,6 @@ const CreateDataset: React.FunctionComponent<CreateDatasetProps> = (props) => {
7889
}
7990
}
8091

81-
const tryToSetValidName = (name: string) => {
82-
// cast name to meet our specification
83-
// make lower case, snakecase, and remove invalid characters
84-
let coercedName = changeCase.lowerCase(name)
85-
coercedName = changeCase.snakeCase(name)
86-
coercedName = coercedName.replace(/^[^a-z0-9_]+$/g, '')
87-
88-
setDatasetName(coercedName)
89-
}
90-
9192
const showFilePicker = () => {
9293
const window = remote.getCurrentWindow()
9394
const filePath: string[] | undefined = remote.dialog.showOpenDialog(window, {
@@ -102,7 +103,9 @@ const CreateDataset: React.FunctionComponent<CreateDatasetProps> = (props) => {
102103
const basename = path.basename(selectedPath)
103104
const name = basename.split('.')[0]
104105

105-
name && datasetName === '' && tryToSetValidName(name)
106+
if (name && datasetName === '') {
107+
setDatasetName(validName(name))
108+
}
106109

107110
setFilePath(selectedPath)
108111
const isDataset = isQriDataset(selectedPath)

app/containers/AppContainer.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
import {
1717
acceptTOS,
1818
setQriCloudAuthenticated,
19+
openToast,
1920
closeToast,
2021
setModal,
2122
setDatasetDirPath,
@@ -66,6 +67,7 @@ const AppContainer = connect(
6667
addDataset: addDatasetAndFetch,
6768
initDataset: initDatasetAndFetch,
6869
linkDataset: linkDatasetAndFetch,
70+
openToast,
6971
closeToast,
7072
pingApi,
7173
setWorkingDataset,

app/reducers/ui.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { ipcRenderer } from 'electron'
44
import store from '../utils/localStore'
55
import { apiActionTypes } from '../utils/actionType'
66
import { SAVE_SUCC, SAVE_FAIL } from '../reducers/mutations'
7+
import { ModalType } from '../models/modals'
78

89
export const UI_TOGGLE_DATASET_LIST = 'UI_TOGGLE_DATASET_LIST'
910
export const UI_SET_SIDEBAR_WIDTH = 'UI_SET_SIDEBAR_WIDTH'
@@ -49,7 +50,8 @@ const initialState = {
4950
blockMenus: true,
5051
hideCommitNudge: store().getItem(hideCommitNudge) === 'true',
5152
datasetDirPath: store().getItem('datasetDirPath') || '',
52-
exportPath: store().getItem('exportPath') || ''
53+
exportPath: store().getItem('exportPath') || '',
54+
modal: { type: ModalType.NoModal }
5355
}
5456

5557
// send an event to electron to block menus on first load

app/scss/_drag-drop.scss

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,35 @@
11
.drag-drop {
2-
background: rgba($color: #000000, $alpha: .6);
2+
background: rgba($color: #ffffff, $alpha: .8);
33
position: absolute;
44
width: 100%;
55
height: 100%;
66
display: flex;
7+
flex-direction: column;
78
justify-content: center;
89
align-items: center;
910
top: 0;
1011
right: 0;
1112
left: 0;
1213
bottom: 0;
14+
15+
.inner {
16+
width: 80%;
17+
height: 70%;
18+
border: .5rem dashed rgba($color: #000000, $alpha: .4);
19+
border-radius: 5px;
20+
display: flex;
21+
flex-direction: column;
22+
justify-content:center;
23+
align-items: center;
24+
25+
.icon {
26+
color: rgba($color: #000000, $alpha: .4);
27+
}
28+
}
29+
.footer {
30+
position: relative;
31+
bottom: -5%;
32+
background: white;
33+
color: rgba($color: #000000, $alpha: 1);
34+
}
1335
}

0 commit comments

Comments
 (0)