import export- cancelable, better design

This commit is contained in:
Jan Prochazka 2020-11-15 18:55:42 +01:00
parent eaf45d8768
commit 801bf05a31
5 changed files with 110 additions and 43 deletions

View File

@ -6,8 +6,10 @@ const iconNames = {
'icon plus-box': 'mdi mdi-plus-box-outline', 'icon plus-box': 'mdi mdi-plus-box-outline',
'icon invisible-box': 'mdi mdi-minus-box-outline icon-invisible', 'icon invisible-box': 'mdi mdi-minus-box-outline icon-invisible',
'icon cloud-upload': 'mdi mdi-cloud-upload', 'icon cloud-upload': 'mdi mdi-cloud-upload',
'icon import': 'mdi mdi-file-upload', 'icon import': 'mdi mdi-application-import',
'icon export': 'mdi mdi-application-export',
'icon new-connection': 'mdi mdi-database-plus', 'icon new-connection': 'mdi mdi-database-plus',
'icon tables': 'mdi mdi-table-multiple',
'icon database': 'mdi mdi-database', 'icon database': 'mdi mdi-database',
'icon server': 'mdi mdi-server', 'icon server': 'mdi mdi-server',

View File

@ -36,6 +36,10 @@ const Wrapper = styled.div`
display: flex; display: flex;
`; `;
const SourceListWrapper = styled.div`
margin: 10px;
`;
const Column = styled.div` const Column = styled.div`
margin: 10px; margin: 10px;
flex: 1; flex: 1;
@ -77,6 +81,12 @@ const ArrowWrapper = styled.div`
align-self: center; align-self: center;
`; `;
const Title = styled.div`
font-size: 20px;
text-align: center;
margin: 10px 0px;
`;
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'] });
@ -194,8 +204,16 @@ function SourceTargetConfig({
const archiveFiles = useArchiveFiles({ folder: values[archiveFolderField] }); const archiveFiles = useArchiveFiles({ folder: values[archiveFolderField] });
return ( return (
<Column> <Column>
{direction == 'source' && <Label theme={theme}>Source configuration</Label>} {direction == 'source' && (
{direction == 'target' && <Label theme={theme}>Target configuration</Label>} <Title theme={theme}>
<FontIcon icon="icon import" /> Source configuration
</Title>
)}
{direction == 'target' && (
<Title theme={theme}>
<FontIcon icon="icon export" /> Target configuration
</Title>
)}
<FormReactSelect options={types.filter((x) => x.directions.includes(direction))} name={storageTypeField} /> <FormReactSelect options={types.filter((x) => x.directions.includes(direction))} name={storageTypeField} />
{(storageType == 'database' || storageType == 'query') && ( {(storageType == 'database' || storageType == 'query') && (
<> <>
@ -396,45 +414,51 @@ export default function ImportExportConfigurator({ uploadedFile = undefined, onC
schemaNameField="targetSchemaName" schemaNameField="targetSchemaName"
/> />
</Wrapper> </Wrapper>
<TableControl rows={sourceList || []}> <SourceListWrapper>
<TableColumn fieldName="source" header="Source" formatter={(row) => <SourceName name={row} />} /> <Title>
<TableColumn <FontIcon icon="icon tables" /> Map source tables/files
fieldName="action" </Title>
header="Action" <TableControl rows={sourceList || []}>
formatter={(row) => ( <TableColumn fieldName="source" header="Source" formatter={(row) => <SourceName name={row} />} />
<SelectField <TableColumn
options={getActionOptions(row, values, targetDbinfo)} fieldName="action"
value={values[`actionType_${row}`] || getActionOptions(row, values, targetDbinfo)[0].value} header="Action"
onChange={(e) => setFieldValue(`actionType_${row}`, e.target.value)} formatter={(row) => (
/> <SelectField
)} options={getActionOptions(row, values, targetDbinfo)}
/> value={values[`actionType_${row}`] || getActionOptions(row, values, targetDbinfo)[0].value}
<TableColumn onChange={(e) => setFieldValue(`actionType_${row}`, e.target.value)}
fieldName="target"
header="Target"
formatter={(row) => (
<TextField
value={getTargetName(row, values)}
onChange={(e) => setFieldValue(`targetName_${row}`, e.target.value)}
/>
)}
/>
<TableColumn
fieldName="preview"
header="Preview"
formatter={(row) =>
supportsPreview ? (
<CheckboxField
checked={previewSource == row}
onChange={(e) => {
if (e.target.checked) setPreviewSource(row);
else setPreviewSource(null);
}}
/> />
) : null )}
} />
/> <TableColumn
</TableControl> fieldName="target"
header="Target"
formatter={(row) => (
<TextField
value={getTargetName(row, values)}
onChange={(e) => setFieldValue(`targetName_${row}`, e.target.value)}
/>
)}
/>
<TableColumn
fieldName="preview"
header="Preview"
formatter={(row) =>
supportsPreview ? (
<CheckboxField
checked={previewSource == row}
onChange={(e) => {
if (e.target.checked) setPreviewSource(row);
else setPreviewSource(null);
}}
/>
) : null
}
/>
</TableControl>
{(sourceList || []).length == 0 && <ErrorInfo message="No source tables/files" icon="img alert" />}
</SourceListWrapper>
</Container> </Container>
); );
} }

View File

@ -18,6 +18,9 @@ import SocketMessagesView from '../query/SocketMessagesView';
import RunnerOutputFiles from '../query/RunnerOuputFiles'; import RunnerOutputFiles from '../query/RunnerOuputFiles';
import useTheme from '../theme/useTheme'; import useTheme from '../theme/useTheme';
import PreviewDataGrid from '../impexp/PreviewDataGrid'; import PreviewDataGrid from '../impexp/PreviewDataGrid';
import useSocket from '../utility/SocketProvider';
import LoadingInfo from '../widgets/LoadingInfo';
import { FontIcon } from '../icons';
const headerHeight = '60px'; const headerHeight = '60px';
const footerHeight = '60px'; const footerHeight = '60px';
@ -115,8 +118,25 @@ export default function ImportExportModal({
const theme = useTheme(); const theme = useTheme();
const [previewReader, setPreviewReader] = React.useState(0); const [previewReader, setPreviewReader] = React.useState(0);
const targetArchiveFolder = importToArchive ? `import-${moment().format('YYYY-MM-DD-hh-mm-ss')}` : archive; const targetArchiveFolder = importToArchive ? `import-${moment().format('YYYY-MM-DD-hh-mm-ss')}` : archive;
const socket = useSocket();
const [busy, setBusy] = React.useState(false);
const handleRunnerDone = React.useCallback(() => {
setBusy(false);
}, []);
React.useEffect(() => {
if (runnerId && socket) {
socket.on(`runner-done-${runnerId}`, handleRunnerDone);
return () => {
socket.off(`runner-done-${runnerId}`, handleRunnerDone);
};
}
}, [runnerId, socket]);
const handleExecute = async (values) => { const handleExecute = async (values) => {
if (busy) return;
const script = await createImpExpScript(values); const script = await createImpExpScript(values);
setExecuteNumber((num) => num + 1); setExecuteNumber((num) => num + 1);
@ -125,6 +145,13 @@ export default function ImportExportModal({
const resp = await axios.post('runners/start', { script }); const resp = await axios.post('runners/start', { script });
runid = resp.data.runid; runid = resp.data.runid;
setRunnerId(runid); setRunnerId(runid);
setBusy(true);
};
const handleCancel = () => {
axios.post('runners/cancel', {
runid: runnerId,
});
}; };
return ( return (
@ -140,7 +167,7 @@ export default function ImportExportModal({
}} }}
> >
<StyledForm> <StyledForm>
<ModalHeader modalState={modalState}>Import/Export</ModalHeader> <ModalHeader modalState={modalState}>Import/Export {busy && <FontIcon icon="icon loading" />}</ModalHeader>
<Wrapper> <Wrapper>
<ContentWrapper theme={theme}> <ContentWrapper theme={theme}>
<ImportExportConfigurator uploadedFile={uploadedFile} onChangePreview={setPreviewReader} /> <ImportExportConfigurator uploadedFile={uploadedFile} onChangePreview={setPreviewReader} />
@ -166,7 +193,11 @@ export default function ImportExportModal({
</Wrapper> </Wrapper>
<Footer theme={theme}> <Footer theme={theme}>
<FooterButtons> <FooterButtons>
<FormStyledButton type="submit" value="Run" /> {busy ? (
<FormStyledButton type="button" value="Cancel" onClick={handleCancel} />
) : (
<FormStyledButton type="submit" value="Run" />
)}
<GenerateSctriptButton modalState={modalState} /> <GenerateSctriptButton modalState={modalState} />
<FormStyledButton type="button" value="Close" onClick={modalState.close} /> <FormStyledButton type="button" value="Close" onClick={modalState.close} />
</FooterButtons> </FooterButtons>

View File

@ -6,6 +6,7 @@ import TableControl, { TableColumn } from '../utility/TableControl';
import formatFileSize from '../utility/formatFileSize'; import formatFileSize from '../utility/formatFileSize';
import resolveApi from '../utility/resolveApi'; import resolveApi from '../utility/resolveApi';
import getElectron from '../utility/getElectron'; import getElectron from '../utility/getElectron';
import ErrorInfo from '../widgets/ErrorInfo';
export default function RunnerOutputFiles({ runnerId, executeNumber }) { export default function RunnerOutputFiles({ runnerId, executeNumber }) {
const socket = useSocket(); const socket = useSocket();
@ -31,6 +32,10 @@ export default function RunnerOutputFiles({ runnerId, executeNumber }) {
const electron = getElectron(); const electron = getElectron();
if (!files || files.length == 0) {
return <ErrorInfo message="No output files" icon="img alert" />;
}
return ( return (
<TableControl rows={files}> <TableControl rows={files}>
<TableColumn fieldName="name" header="Name" /> <TableColumn fieldName="name" header="Name" />

View File

@ -2,6 +2,7 @@ import _ from 'lodash';
import React from 'react'; import React from 'react';
import MessagesView from './MessagesView'; import MessagesView from './MessagesView';
import useSocket from '../utility/SocketProvider'; import useSocket from '../utility/SocketProvider';
import ErrorInfo from '../widgets/ErrorInfo';
export default function SocketMessagesView({ export default function SocketMessagesView({
eventName, eventName,
@ -41,6 +42,10 @@ export default function SocketMessagesView({
} }
}, [eventName, socket]); }, [eventName, socket]);
if (!displayedMessages || displayedMessages.length == 0) {
return <ErrorInfo message="No messages" icon="img alert" />;
}
return ( return (
<MessagesView <MessagesView
items={displayedMessages} items={displayedMessages}