Skip to content

Commit 11ff2a4

Browse files
authored
Merge pull request #936 from linuslundahl/feature/file-drop-to-editor
Add new FileDrop implementation.
2 parents ab736c1 + 82e6195 commit 11ff2a4

File tree

9 files changed

+330
-164
lines changed

9 files changed

+330
-164
lines changed

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,6 @@
138138
"react-dnd": "^2.5.1",
139139
"react-dnd-html5-backend": "^2.5.1",
140140
"react-dom": "^16.8.1",
141-
"react-dropzone": "^4.1.2",
142141
"react-icons": "^2.2.1",
143142
"react-redux": "^5.0.7",
144143
"react-suber": "1.0.4",
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
/*
2+
* Copyright (c) 2002-2019 "Neo4j,"
3+
* Neo4j Sweden AB [http://neo4j.com]
4+
*
5+
* This file is part of Neo4j.
6+
*
7+
* Neo4j is free software: you can redistribute it and/or modify
8+
* it under the terms of the GNU General Public License as published by
9+
* the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU General Public License
18+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
19+
*/
20+
21+
import React, { useState } from 'react'
22+
import { connect } from 'react-redux'
23+
import { withBus } from 'react-suber'
24+
import SVGInline from 'react-svg-inline'
25+
26+
import * as editor from 'shared/modules/editor/editorDuck'
27+
import { addFavorite } from 'shared/modules/favorites/favoritesDuck'
28+
import { parseGrass } from 'shared/services/grassUtils'
29+
import { updateGraphStyleData } from 'shared/modules/grass/grassDuck'
30+
import {
31+
showErrorMessage,
32+
executeCommand
33+
} from 'shared/modules/commands/commandsDuck'
34+
35+
import {
36+
StyledFileDrop,
37+
StyledFileDropInner,
38+
StyledFileDropContent,
39+
StyledFileDropActions,
40+
StyledFileDropActionButton
41+
} from './styled'
42+
import icon from 'icons/task-list-download.svg'
43+
44+
export function FileDrop (props) {
45+
const [fileHoverState, setFileHoverState] = useState(false)
46+
const [userSelect, setUserSelect] = useState(false)
47+
const [file, setFile] = useState(null)
48+
const { saveCypherToFavorites, importGrass, dispatchErrorMessage } = props
49+
50+
const resetState = () => {
51+
setFileHoverState(false)
52+
setUserSelect(false)
53+
setFile(null)
54+
}
55+
56+
const pasteInEditor = content => {
57+
props.bus && props.bus.send(editor.SET_CONTENT, editor.setContent(content))
58+
}
59+
60+
const fileLoader = (file, callback) => {
61+
const reader = new FileReader()
62+
reader.onload = fileEvent => {
63+
callback(fileEvent.target.result)
64+
resetState()
65+
}
66+
reader.onerror = () => {
67+
dispatchErrorMessage(`Something wen't wrong when reading the file`)
68+
resetState()
69+
}
70+
reader.readAsText(file)
71+
}
72+
73+
const handleDragOver = event => {
74+
event.stopPropagation()
75+
event.preventDefault()
76+
77+
if (!fileHoverState) {
78+
setFileHoverState(true)
79+
}
80+
}
81+
82+
const handleDragLeave = event => {
83+
// Check if we're leaving the browser window
84+
if (
85+
event.clientX <= 0 ||
86+
event.clientY <= 0 ||
87+
window.innerHeight < event.clientY ||
88+
window.innerWidth < event.clientX
89+
) {
90+
resetState()
91+
}
92+
}
93+
94+
const handleDrop = event => {
95+
const files = event.dataTransfer.files
96+
if (files.length === 1) {
97+
event.stopPropagation()
98+
event.preventDefault()
99+
100+
setFile(files[0])
101+
102+
const extension = ((files[0] || {}).name || '').split('.').pop()
103+
if (['cyp', 'cypher', 'cql', 'txt'].includes(extension)) {
104+
setUserSelect(true)
105+
} else if (extension === 'grass') {
106+
fileLoader(files[0], result => {
107+
importGrass(result)
108+
const action = executeCommand(':style')
109+
props.bus.send(action.type, action)
110+
})
111+
} else {
112+
dispatchErrorMessage(`'.${extension}' is not a valid file extension`)
113+
resetState()
114+
}
115+
}
116+
}
117+
118+
const className = ['filedrop']
119+
if (fileHoverState) {
120+
className.push('has-file-hovering')
121+
}
122+
123+
if (userSelect) {
124+
className.push('has-user-select')
125+
}
126+
127+
return (
128+
<StyledFileDrop
129+
className={className.join(' ')}
130+
onDragOver={handleDragOver}
131+
onDragLeave={handleDragLeave}
132+
onDrop={handleDrop}
133+
>
134+
{props.children}
135+
<StyledFileDropInner onClick={resetState}>
136+
<StyledFileDropContent>
137+
<SVGInline svg={icon} accessibilityLabel={'Import'} width={'14rem'} />
138+
{userSelect && (
139+
<StyledFileDropActions>
140+
<StyledFileDropActionButton
141+
className='filedrop-save-to-favorites'
142+
onClick={() => {
143+
fileLoader(file, saveCypherToFavorites)
144+
}}
145+
>
146+
Save to favorites
147+
</StyledFileDropActionButton>
148+
<StyledFileDropActionButton
149+
className='filedrop-paste-in-editor'
150+
onClick={() => {
151+
fileLoader(file, pasteInEditor)
152+
}}
153+
>
154+
Paste in editor
155+
</StyledFileDropActionButton>
156+
</StyledFileDropActions>
157+
)}
158+
</StyledFileDropContent>
159+
</StyledFileDropInner>
160+
</StyledFileDrop>
161+
)
162+
}
163+
164+
const mapDispatchToProps = dispatch => {
165+
return {
166+
saveCypherToFavorites: file => {
167+
dispatch(addFavorite(file))
168+
},
169+
importGrass: file => {
170+
const parsedGrass = parseGrass(file)
171+
if (parsedGrass) {
172+
dispatch(updateGraphStyleData(parsedGrass))
173+
} else {
174+
dispatch(showErrorMessage('Could not parse grass data'))
175+
}
176+
},
177+
dispatchErrorMessage: message => dispatch(showErrorMessage(message))
178+
}
179+
}
180+
181+
export default withBus(
182+
connect(
183+
null,
184+
mapDispatchToProps
185+
)(FileDrop)
186+
)
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Copyright (c) 2002-2019 "Neo4j,"
3+
* Neo4j Sweden AB [http://neo4j.com]
4+
*
5+
* This file is part of Neo4j.
6+
*
7+
* Neo4j is free software: you can redistribute it and/or modify
8+
* it under the terms of the GNU General Public License as published by
9+
* the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU General Public License
18+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
19+
*/
20+
21+
import styled from 'styled-components'
22+
23+
export const StyledFileDrop = styled.div``
24+
25+
export const StyledFileDropInner = styled.div`
26+
display: flex;
27+
flex-direction: column;
28+
justify-content: center;
29+
align-items: center;
30+
height: 100vh;
31+
width: 100vw;
32+
opacity: 0;
33+
overflow: hidden;
34+
position: absolute;
35+
left: 0;
36+
right: 0;
37+
top: 0;
38+
bottom: 0;
39+
pointer-events: none;
40+
41+
.has-file-hovering & {
42+
color: #fff;
43+
background-color: rgba(0, 0, 0, 0.6);
44+
opacity: 1;
45+
transition: opacity 0.2s ease-in-out;
46+
z-index: 1000;
47+
}
48+
49+
.has-user-select & {
50+
pointer-events: all;
51+
}
52+
`
53+
54+
export const StyledFileDropContent = styled.div`
55+
position: relative;
56+
`
57+
58+
export const StyledFileDropActions = styled.div`
59+
display: block;
60+
visibility: hidden;
61+
position: absolute;
62+
left: 50%;
63+
top: calc(100% + 1rem);
64+
transform: translateX(-50%);
65+
width: 100vw;
66+
text-align: center;
67+
pointer-events: none;
68+
69+
.has-user-select & {
70+
visibility: visible;
71+
pointer-events: all;
72+
}
73+
`
74+
75+
export const StyledFileDropActionButton = styled.button`
76+
border: 0;
77+
border-radius: 5px;
78+
color: #000;
79+
background-color: #fff;
80+
padding: 5px 10px;
81+
font-weight: 600;
82+
margin: 0 5px;
83+
`
Lines changed: 1 addition & 0 deletions
Loading

0 commit comments

Comments
 (0)