import using drag & drop

This commit is contained in:
Jan Prochazka
2020-11-07 21:52:35 +01:00
parent c65806fd89
commit 6fb314c414
8 changed files with 228 additions and 58 deletions

View File

@@ -2,6 +2,21 @@ const path = require('path');
const { uploadsdir } = require('../utility/directories'); const { uploadsdir } = require('../utility/directories');
const uuidv1 = require('uuid/v1'); const uuidv1 = require('uuid/v1');
const extensions = [
{
ext: '.xlsx',
type: 'excel',
},
{
ext: '.jsonl',
type: 'jsonl',
},
{
ext: '.csv',
type: 'csv',
},
];
module.exports = { module.exports = {
upload_meta: { upload_meta: {
method: 'post', method: 'post',
@@ -16,10 +31,21 @@ module.exports = {
const uploadName = uuidv1(); const uploadName = uuidv1();
const filePath = path.join(uploadsdir(), uploadName); const filePath = path.join(uploadsdir(), uploadName);
console.log(`Uploading file ${data.name}, size=${data.size}`); console.log(`Uploading file ${data.name}, size=${data.size}`);
let storageType = null;
let shortName = data.name;
for (const { ext, type } of extensions) {
if (data.name.endsWith(ext)) {
storageType = type;
shortName = data.name.slice(0, -ext.length);
}
}
data.mv(filePath, () => { data.mv(filePath, () => {
res.json({ res.json({
originalName: data.name, originalName: data.name,
shortName,
storageType,
uploadName, uploadName,
filePath,
}); });
}); });
}, },

View File

@@ -13,6 +13,7 @@ import {
import { SocketProvider } from './utility/SocketProvider'; import { SocketProvider } from './utility/SocketProvider';
import ConnectionsPinger from './utility/ConnectionsPinger'; import ConnectionsPinger from './utility/ConnectionsPinger';
import { ModalLayerProvider } from './modals/showModal'; import { ModalLayerProvider } from './modals/showModal';
import UploadsProvider from './utility/UploadsProvider';
function App() { function App() {
return ( return (
@@ -26,7 +27,9 @@ function App() {
<ConnectionsPinger> <ConnectionsPinger>
<ModalLayerProvider> <ModalLayerProvider>
<CurrentArchiveProvider> <CurrentArchiveProvider>
<Screen /> <UploadsProvider>
<Screen />
</UploadsProvider>
</CurrentArchiveProvider> </CurrentArchiveProvider>
</ModalLayerProvider> </ModalLayerProvider>
</ConnectionsPinger> </ConnectionsPinger>

View File

@@ -0,0 +1,53 @@
import React from 'react';
import styled from 'styled-components';
const TargetStyled = styled.div`
position: fixed;
display: flex;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: #aaaaff;
align-items: center;
justify-content: space-around;
z-index: 1000;
`;
const InfoBox = styled.div``;
const IconWrapper = styled.div`
display: flex;
justify-content: space-around;
font-size: 50px;
margin-bottom: 20px;
`;
const InfoWrapper = styled.div`
display: flex;
justify-content: space-around;
margin-top: 10px;
`;
const TitleWrapper = styled.div`
font-size: 30px;
display: flex;
justify-content: space-around;
`;
export default function DragAndDropFileTarget({ isDragActive, inputProps }) {
return (
!!isDragActive && (
<TargetStyled>
<InfoBox>
<IconWrapper>
<i className="fas fa-cloud-upload-alt" />
</IconWrapper>
<TitleWrapper>Drop the files to upload to DbGate</TitleWrapper>
<InfoWrapper>Supported file types: csv, MS Excel, json-lines</InfoWrapper>
</InfoBox>
<input {...inputProps} />
</TargetStyled>
)
);
}

View File

@@ -3,7 +3,6 @@
import React from 'react'; import React from 'react';
import theme from './theme'; import theme from './theme';
import styled from 'styled-components'; import styled from 'styled-components';
import { useDropzone } from 'react-dropzone';
import TabsPanel from './TabsPanel'; import TabsPanel from './TabsPanel';
import TabContent from './TabContent'; import TabContent from './TabContent';
import WidgetIconPanel from './widgets/WidgetIconPanel'; import WidgetIconPanel from './widgets/WidgetIconPanel';
@@ -13,7 +12,8 @@ import ToolBar from './widgets/Toolbar';
import StatusBar from './widgets/StatusBar'; import StatusBar from './widgets/StatusBar';
import { useSplitterDrag, HorizontalSplitHandle } from './widgets/Splitter'; import { useSplitterDrag, HorizontalSplitHandle } from './widgets/Splitter';
import { ModalLayer } from './modals/showModal'; import { ModalLayer } from './modals/showModal';
import resolveApi from './utility/resolveApi'; import DragAndDropFileTarget from './DragAndDropFileTarget';
import { useUploadsZone } from './utility/UploadsProvider';
const BodyDiv = styled.div` const BodyDiv = styled.div`
position: fixed; position: fixed;
@@ -98,15 +98,6 @@ const ScreenHorizontalSplitHandle = styled(HorizontalSplitHandle)`
bottom: ${theme.statusBar.height}px; bottom: ${theme.statusBar.height}px;
`; `;
const DragAndDropTarget = styled.div`
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: red;
`;
export default function Screen() { export default function Screen() {
const currentWidget = useCurrentWidget(); const currentWidget = useCurrentWidget();
const leftPanelWidth = useLeftPanelWidth(); const leftPanelWidth = useLeftPanelWidth();
@@ -117,42 +108,7 @@ export default function Screen() {
const toolbarPortalRef = React.useRef(); const toolbarPortalRef = React.useRef();
const onSplitDown = useSplitterDrag('clientX', (diff) => setLeftPanelWidth((v) => v + diff)); const onSplitDown = useSplitterDrag('clientX', (diff) => setLeftPanelWidth((v) => v + diff));
const onDrop = React.useCallback((files) => { const { getRootProps, getInputProps, isDragActive } = useUploadsZone();
// Do something with the files
console.log('FILES', files);
files.forEach(async (file) => {
if (parseInt(file.size, 10) >= 4 * 1024 * 1024) {
// to big file
return;
}
const formData = new FormData();
formData.append('data', file);
const fetchOptions = {
method: 'POST',
body: formData,
};
const apiBase = resolveApi();
const resp = await fetch(`${apiBase}/uploads/upload`, fetchOptions);
const event = await resp.json();
return event;
// const reader = new FileReader();
// reader.onabort = () => console.log('file reading was aborted');
// reader.onerror = () => console.log('file reading has failed');
// reader.onload = () => {
// // Do whatever you want with the file contents
// const binaryStr = reader.result;
// console.log(binaryStr);
// };
// reader.readAsArrayBuffer(file);
});
}, []);
const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop });
return ( return (
<div {...getRootProps()}> <div {...getRootProps()}>
@@ -184,12 +140,7 @@ export default function Screen() {
</StausBarContainer> </StausBarContainer>
<ModalLayer /> <ModalLayer />
{!!isDragActive && ( <DragAndDropFileTarget inputProps={getInputProps()} isDragActive={isDragActive} />
<DragAndDropTarget>
Drop the files here ...
<input {...getInputProps()} />{' '}
</DragAndDropTarget>
)}
</div> </div>
); );
} }

View File

@@ -22,6 +22,7 @@ import getAsArray from '../utility/getAsArray';
import axios from '../utility/axios'; import axios from '../utility/axios';
import LoadingInfo from '../widgets/LoadingInfo'; import LoadingInfo from '../widgets/LoadingInfo';
import SqlEditor from '../sqleditor/SqlEditor'; import SqlEditor from '../sqleditor/SqlEditor';
import { useUploadsProvider } from '../utility/UploadsProvider';
const Container = styled.div` const Container = styled.div`
max-height: 50vh; max-height: 50vh;
@@ -62,6 +63,11 @@ const SqlWrapper = styled.div`
width: 20vw; width: 20vw;
`; `;
const DragWrapper = styled.div`
padding: 10px;
background: #ddd;
`;
function getFileFilters(storageType) { function getFileFilters(storageType) {
const res = []; const res = [];
if (storageType == 'csv') res.push({ name: 'CSV files', extensions: ['csv'] }); if (storageType == 'csv') res.push({ name: 'CSV files', extensions: ['csv'] });
@@ -141,7 +147,7 @@ function FilesInput() {
if (electron) { if (electron) {
return <ElectronFilesInput />; return <ElectronFilesInput />;
} }
return <ErrorInfo message="Import files is currently implemented only for electron client" />; return <DragWrapper>Drag &amp; drop imported files here</DragWrapper>;
} }
function SourceTargetConfig({ function SourceTargetConfig({
@@ -287,12 +293,43 @@ function SourceName({ name }) {
); );
} }
export default function ImportExportConfigurator() { export default function ImportExportConfigurator({ uploadedFile = undefined }) {
const { values, setFieldValue } = useFormikContext(); const { values, setFieldValue } = useFormikContext();
const targetDbinfo = useDatabaseInfo({ conid: values.targetConnectionId, database: values.targetDatabaseName }); const targetDbinfo = useDatabaseInfo({ conid: values.targetConnectionId, database: values.targetDatabaseName });
const sourceConnectionInfo = useConnectionInfo({ conid: values.sourceConnectionId }); const sourceConnectionInfo = useConnectionInfo({ conid: values.sourceConnectionId });
const { engine: sourceEngine } = sourceConnectionInfo || {}; const { engine: sourceEngine } = sourceConnectionInfo || {};
const { sourceList } = values; const { sourceList } = values;
const { uploadListener, setUploadListener } = useUploadsProvider();
const handleUpload = React.useCallback(
(file) => {
addFilesToSourceList(
[
{
full: file.filePath,
name: file.shortName,
},
],
values,
setFieldValue
);
// setFieldValue('sourceList', [...(sourceList || []), file.originalName]);
},
[setFieldValue, sourceList]
);
React.useEffect(() => {
setUploadListener(() => handleUpload);
return () => {
setUploadListener(null);
};
}, [handleUpload]);
React.useEffect(() => {
if (uploadedFile) {
handleUpload(uploadedFile);
}
}, []);
return ( return (
<Container> <Container>

View File

@@ -38,7 +38,7 @@ function GenerateSctriptButton({ modalState }) {
return <FormStyledButton type="button" value="Generate script" onClick={handleGenerateScript} />; return <FormStyledButton type="button" value="Generate script" onClick={handleGenerateScript} />;
} }
export default function ImportExportModal({ modalState, initialValues }) { export default function ImportExportModal({ modalState, initialValues, uploadedFile = undefined }) {
const [executeNumber, setExecuteNumber] = React.useState(0); const [executeNumber, setExecuteNumber] = React.useState(0);
const [runnerId, setRunnerId] = React.useState(null); const [runnerId, setRunnerId] = React.useState(null);
const archive = useCurrentArchive(); const archive = useCurrentArchive();
@@ -69,7 +69,7 @@ export default function ImportExportModal({ modalState, initialValues }) {
<Form> <Form>
<ModalHeader modalState={modalState}>Import/Export</ModalHeader> <ModalHeader modalState={modalState}>Import/Export</ModalHeader>
<ModalContent> <ModalContent>
<ImportExportConfigurator /> <ImportExportConfigurator uploadedFile={uploadedFile} />
</ModalContent> </ModalContent>
<ModalFooter> <ModalFooter>
<FormStyledButton type="submit" value="Run" /> <FormStyledButton type="submit" value="Run" />

View File

@@ -0,0 +1,79 @@
import React from 'react';
import { useDropzone } from 'react-dropzone';
import ImportExportModal from '../modals/ImportExportModal';
import useShowModal from '../modals/showModal';
import resolveApi from './resolveApi';
const UploadsContext = React.createContext(null);
export default function UploadsProvider({ children }) {
const [uploadListener, setUploadListener] = React.useState(null);
return <UploadsContext.Provider value={{ uploadListener, setUploadListener }}>{children}</UploadsContext.Provider>;
}
export function useUploadsProvider() {
return React.useContext(UploadsContext);
}
export function useUploadsZone() {
const { uploadListener } = useUploadsProvider();
const showModal = useShowModal();
const onDrop = React.useCallback(
(files) => {
files.forEach(async (file) => {
if (parseInt(file.size, 10) >= 4 * 1024 * 1024) {
// to big file
return;
}
const formData = new FormData();
formData.append('data', file);
const fetchOptions = {
method: 'POST',
body: formData,
};
const apiBase = resolveApi();
const resp = await fetch(`${apiBase}/uploads/upload`, fetchOptions);
const fileData = await resp.json();
if (uploadListener) {
uploadListener(fileData);
} else {
if (['csv', 'excel', 'jsonl'].includes(fileData.storageType)) {
showModal((modalState) => (
<ImportExportModal
uploadedFile={fileData}
modalState={modalState}
initialValues={{
sourceStorageType: fileData.storageType,
// sourceConnectionId: data.conid,
// sourceDatabaseName: data.database,
// sourceSchemaName: data.schemaName,
// sourceList: [data.pureName],
}}
/>
));
}
}
// const reader = new FileReader();
// reader.onabort = () => console.log('file reading was aborted');
// reader.onerror = () => console.log('file reading has failed');
// reader.onload = () => {
// // Do whatever you want with the file contents
// const binaryStr = reader.result;
// console.log(binaryStr);
// };
// reader.readAsArrayBuffer(file);
});
},
[uploadListener]
);
const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop });
return { getRootProps, getInputProps, isDragActive };
}

View File

@@ -8,6 +8,8 @@ import { useConfig } from '../utility/metadataLoaders';
import { useSetOpenedTabs, useOpenedTabs } from '../utility/globalState'; import { useSetOpenedTabs, useOpenedTabs } from '../utility/globalState';
import { openNewTab } from '../utility/common'; import { openNewTab } from '../utility/common';
import useNewFreeTable from '../freetable/useNewFreeTable'; import useNewFreeTable from '../freetable/useNewFreeTable';
import ImportExportModal from '../modals/ImportExportModal';
import useShowModal from '../modals/showModal';
const ToolbarContainer = styled.div` const ToolbarContainer = styled.div`
display: flex; display: flex;
@@ -22,6 +24,7 @@ export default function ToolBar({ toolbarPortalRef }) {
const toolbar = config.toolbar || []; const toolbar = config.toolbar || [];
const setOpenedTabs = useSetOpenedTabs(); const setOpenedTabs = useSetOpenedTabs();
const openedTabs = useOpenedTabs(); const openedTabs = useOpenedTabs();
const showModal = useShowModal();
React.useEffect(() => { React.useEffect(() => {
window['dbgate_createNewConnection'] = modalState.open; window['dbgate_createNewConnection'] = modalState.open;
@@ -29,6 +32,21 @@ export default function ToolBar({ toolbarPortalRef }) {
window['dbgate_closeAll'] = () => setOpenedTabs([]); window['dbgate_closeAll'] = () => setOpenedTabs([]);
}); });
const showImport = () => {
showModal((modalState) => (
<ImportExportModal
modalState={modalState}
initialValues={{
sourceStorageType: 'csv',
// sourceConnectionId: data.conid,
// sourceDatabaseName: data.database,
// sourceSchemaName: data.schemaName,
// sourceList: [data.pureName],
}}
/>
));
};
function openTabFromButton(button) { function openTabFromButton(button) {
if (openedTabs.find((x) => x.tabComponent == 'InfoPageTab' && x.props && x.props.page == button.page)) { if (openedTabs.find((x) => x.tabComponent == 'InfoPageTab' && x.props && x.props.page == button.page)) {
setOpenedTabs((tabs) => setOpenedTabs((tabs) =>
@@ -79,6 +97,9 @@ export default function ToolBar({ toolbarPortalRef }) {
<ToolbarButton onClick={newFreeTable} icon="fas fa-table"> <ToolbarButton onClick={newFreeTable} icon="fas fa-table">
Free table editor Free table editor
</ToolbarButton> </ToolbarButton>
<ToolbarButton onClick={showImport} icon="fas fa-file-upload">
Import data
</ToolbarButton>
<ToolbarContainer ref={toolbarPortalRef}></ToolbarContainer> <ToolbarContainer ref={toolbarPortalRef}></ToolbarContainer>
</ToolbarContainer> </ToolbarContainer>
); );