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

@@ -13,6 +13,7 @@ import {
import { SocketProvider } from './utility/SocketProvider';
import ConnectionsPinger from './utility/ConnectionsPinger';
import { ModalLayerProvider } from './modals/showModal';
import UploadsProvider from './utility/UploadsProvider';
function App() {
return (
@@ -26,7 +27,9 @@ function App() {
<ConnectionsPinger>
<ModalLayerProvider>
<CurrentArchiveProvider>
<Screen />
<UploadsProvider>
<Screen />
</UploadsProvider>
</CurrentArchiveProvider>
</ModalLayerProvider>
</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 theme from './theme';
import styled from 'styled-components';
import { useDropzone } from 'react-dropzone';
import TabsPanel from './TabsPanel';
import TabContent from './TabContent';
import WidgetIconPanel from './widgets/WidgetIconPanel';
@@ -13,7 +12,8 @@ import ToolBar from './widgets/Toolbar';
import StatusBar from './widgets/StatusBar';
import { useSplitterDrag, HorizontalSplitHandle } from './widgets/Splitter';
import { ModalLayer } from './modals/showModal';
import resolveApi from './utility/resolveApi';
import DragAndDropFileTarget from './DragAndDropFileTarget';
import { useUploadsZone } from './utility/UploadsProvider';
const BodyDiv = styled.div`
position: fixed;
@@ -98,15 +98,6 @@ const ScreenHorizontalSplitHandle = styled(HorizontalSplitHandle)`
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() {
const currentWidget = useCurrentWidget();
const leftPanelWidth = useLeftPanelWidth();
@@ -117,42 +108,7 @@ export default function Screen() {
const toolbarPortalRef = React.useRef();
const onSplitDown = useSplitterDrag('clientX', (diff) => setLeftPanelWidth((v) => v + diff));
const onDrop = React.useCallback((files) => {
// 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 });
const { getRootProps, getInputProps, isDragActive } = useUploadsZone();
return (
<div {...getRootProps()}>
@@ -184,12 +140,7 @@ export default function Screen() {
</StausBarContainer>
<ModalLayer />
{!!isDragActive && (
<DragAndDropTarget>
Drop the files here ...
<input {...getInputProps()} />{' '}
</DragAndDropTarget>
)}
<DragAndDropFileTarget inputProps={getInputProps()} isDragActive={isDragActive} />
</div>
);
}

View File

@@ -22,6 +22,7 @@ import getAsArray from '../utility/getAsArray';
import axios from '../utility/axios';
import LoadingInfo from '../widgets/LoadingInfo';
import SqlEditor from '../sqleditor/SqlEditor';
import { useUploadsProvider } from '../utility/UploadsProvider';
const Container = styled.div`
max-height: 50vh;
@@ -62,6 +63,11 @@ const SqlWrapper = styled.div`
width: 20vw;
`;
const DragWrapper = styled.div`
padding: 10px;
background: #ddd;
`;
function getFileFilters(storageType) {
const res = [];
if (storageType == 'csv') res.push({ name: 'CSV files', extensions: ['csv'] });
@@ -141,7 +147,7 @@ function FilesInput() {
if (electron) {
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({
@@ -287,12 +293,43 @@ function SourceName({ name }) {
);
}
export default function ImportExportConfigurator() {
export default function ImportExportConfigurator({ uploadedFile = undefined }) {
const { values, setFieldValue } = useFormikContext();
const targetDbinfo = useDatabaseInfo({ conid: values.targetConnectionId, database: values.targetDatabaseName });
const sourceConnectionInfo = useConnectionInfo({ conid: values.sourceConnectionId });
const { engine: sourceEngine } = sourceConnectionInfo || {};
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 (
<Container>

View File

@@ -38,7 +38,7 @@ function GenerateSctriptButton({ modalState }) {
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 [runnerId, setRunnerId] = React.useState(null);
const archive = useCurrentArchive();
@@ -69,7 +69,7 @@ export default function ImportExportModal({ modalState, initialValues }) {
<Form>
<ModalHeader modalState={modalState}>Import/Export</ModalHeader>
<ModalContent>
<ImportExportConfigurator />
<ImportExportConfigurator uploadedFile={uploadedFile} />
</ModalContent>
<ModalFooter>
<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 { openNewTab } from '../utility/common';
import useNewFreeTable from '../freetable/useNewFreeTable';
import ImportExportModal from '../modals/ImportExportModal';
import useShowModal from '../modals/showModal';
const ToolbarContainer = styled.div`
display: flex;
@@ -22,6 +24,7 @@ export default function ToolBar({ toolbarPortalRef }) {
const toolbar = config.toolbar || [];
const setOpenedTabs = useSetOpenedTabs();
const openedTabs = useOpenedTabs();
const showModal = useShowModal();
React.useEffect(() => {
window['dbgate_createNewConnection'] = modalState.open;
@@ -29,6 +32,21 @@ export default function ToolBar({ toolbarPortalRef }) {
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) {
if (openedTabs.find((x) => x.tabComponent == 'InfoPageTab' && x.props && x.props.page == button.page)) {
setOpenedTabs((tabs) =>
@@ -79,6 +97,9 @@ export default function ToolBar({ toolbarPortalRef }) {
<ToolbarButton onClick={newFreeTable} icon="fas fa-table">
Free table editor
</ToolbarButton>
<ToolbarButton onClick={showImport} icon="fas fa-file-upload">
Import data
</ToolbarButton>
<ToolbarContainer ref={toolbarPortalRef}></ToolbarContainer>
</ToolbarContainer>
);