mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-22 08:46:00 +00:00
import using drag & drop
This commit is contained in:
@@ -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>
|
||||
|
||||
53
packages/web/src/DragAndDropFileTarget.js
Normal file
53
packages/web/src/DragAndDropFileTarget.js
Normal 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>
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 & 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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
79
packages/web/src/utility/UploadsProvider.js
Normal file
79
packages/web/src/utility/UploadsProvider.js
Normal 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 };
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user