mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-19 20:06:00 +00:00
remove web
This commit is contained in:
@@ -1,93 +0,0 @@
|
||||
import React from 'react';
|
||||
import ModalBase from './ModalBase';
|
||||
import ModalHeader from './ModalHeader';
|
||||
import ModalContent from './ModalContent';
|
||||
import ModalFooter from './ModalFooter';
|
||||
import { useConfig } from '../utility/metadataLoaders';
|
||||
import FormStyledButton from '../widgets/FormStyledButton';
|
||||
import moment from 'moment';
|
||||
import styled from 'styled-components';
|
||||
import getElectron from '../utility/getElectron';
|
||||
import useTheme from '../theme/useTheme';
|
||||
import { StyledThemedLink } from '../widgets/FormStyledButton';
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
const TextContainer = styled.div``;
|
||||
|
||||
const StyledLine = styled.div`
|
||||
margin: 5px;
|
||||
`;
|
||||
|
||||
const StyledValue = styled.span`
|
||||
font-weight: bold;
|
||||
`;
|
||||
|
||||
function Line({ label, children }) {
|
||||
return (
|
||||
<StyledLine>
|
||||
{label}: <StyledValue>{children}</StyledValue>
|
||||
</StyledLine>
|
||||
);
|
||||
}
|
||||
|
||||
function Link({ label, children, href }) {
|
||||
const electron = getElectron();
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<StyledLine>
|
||||
{label}:{' '}
|
||||
{electron ? (
|
||||
<StyledThemedLink theme={theme} onClick={() => electron.shell.openExternal(href)}>
|
||||
{children}
|
||||
</StyledThemedLink>
|
||||
) : (
|
||||
<StyledThemedLink theme={theme} href={href} target="_blank" rel="noopener noreferrer">
|
||||
{children}
|
||||
</StyledThemedLink>
|
||||
)}
|
||||
</StyledLine>
|
||||
);
|
||||
}
|
||||
|
||||
export default function AboutModal({ modalState }) {
|
||||
const config = useConfig();
|
||||
const { version, buildTime } = config || {};
|
||||
return (
|
||||
<ModalBase modalState={modalState}>
|
||||
<ModalHeader modalState={modalState}>About DbGate</ModalHeader>
|
||||
<ModalContent>
|
||||
<Container>
|
||||
<img
|
||||
// eslint-disable-next-line
|
||||
src={`${process.env.PUBLIC_URL}/logo192.png`}
|
||||
/>
|
||||
<TextContainer>
|
||||
<Line label="Version">{version}</Line>
|
||||
<Line label="Build date">{moment(buildTime).format('YYYY-MM-DD')}</Line>
|
||||
<Link label="Web" href="https://dbgate.org">
|
||||
dbgate.org
|
||||
</Link>
|
||||
<Link label="Source codes" href="https://github.com/dbgate/dbgate/">
|
||||
github
|
||||
</Link>
|
||||
<Link label="Docker container" href="https://hub.docker.com/r/dbgate/dbgate">
|
||||
docker hub
|
||||
</Link>
|
||||
<Link label="Online demo" href="https://demo.dbgate.org">
|
||||
demo.dbgate.org
|
||||
</Link>
|
||||
<Link label="Search plugins" href="https://www.npmjs.com/search?q=keywords:dbgateplugin">
|
||||
npmjs.com
|
||||
</Link>
|
||||
</TextContainer>
|
||||
</Container>
|
||||
</ModalContent>
|
||||
<ModalFooter>
|
||||
<FormStyledButton value="Close" onClick={() => modalState.close()} />
|
||||
</ModalFooter>
|
||||
</ModalBase>
|
||||
);
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
import React from 'react';
|
||||
import ModalBase from './ModalBase';
|
||||
import { FormButton, FormSubmit, FormTextField } from '../utility/forms';
|
||||
import ModalHeader from './ModalHeader';
|
||||
import ModalContent from './ModalContent';
|
||||
import ModalFooter from './ModalFooter';
|
||||
import FormStyledButton from '../widgets/FormStyledButton';
|
||||
import { FormProvider } from '../utility/FormProvider';
|
||||
|
||||
export default function ChangeDownloadUrlModal({ modalState, url = '', onConfirm = undefined }) {
|
||||
// const textFieldRef = React.useRef(null);
|
||||
// React.useEffect(() => {
|
||||
// if (textFieldRef.current) textFieldRef.current.focus();
|
||||
// }, [textFieldRef.current]);
|
||||
|
||||
// const handleSubmit = () => async (values) => {
|
||||
// onConfirm(values.url);
|
||||
// modalState.close();
|
||||
// };
|
||||
|
||||
const handleSubmit = React.useCallback(
|
||||
async values => {
|
||||
onConfirm(values.url);
|
||||
modalState.close();
|
||||
},
|
||||
[modalState, onConfirm]
|
||||
);
|
||||
return (
|
||||
<ModalBase modalState={modalState}>
|
||||
<ModalHeader modalState={modalState}>Download imported file from web</ModalHeader>
|
||||
<FormProvider initialValues={{ url }}>
|
||||
<ModalContent>
|
||||
<FormTextField label="URL" name="url" style={{ width: '30vw' }} focused />
|
||||
</ModalContent>
|
||||
<ModalFooter>
|
||||
<FormSubmit value="OK" onClick={handleSubmit} />
|
||||
<FormStyledButton value="Cancel" onClick={() => modalState.close()} />
|
||||
</ModalFooter>
|
||||
</FormProvider>
|
||||
</ModalBase>
|
||||
);
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
import React from 'react';
|
||||
import ModalBase from './ModalBase';
|
||||
import FormStyledButton from '../widgets/FormStyledButton';
|
||||
import ModalFooter from './ModalFooter';
|
||||
import ModalContent from './ModalContent';
|
||||
import { FormSubmit } from '../utility/forms';
|
||||
import { FormProvider } from '../utility/FormProvider';
|
||||
|
||||
export default function ConfirmModal({ message, modalState, onConfirm }) {
|
||||
return (
|
||||
<FormProvider>
|
||||
<ModalBase modalState={modalState}>
|
||||
<ModalContent>{message}</ModalContent>
|
||||
|
||||
<ModalFooter>
|
||||
<FormSubmit
|
||||
value="OK"
|
||||
onClick={() => {
|
||||
modalState.close();
|
||||
onConfirm();
|
||||
}}
|
||||
/>
|
||||
<FormStyledButton type="button" value="Close" onClick={modalState.close} />
|
||||
</ModalFooter>
|
||||
</ModalBase>
|
||||
</FormProvider>
|
||||
);
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
import React from 'react';
|
||||
import ModalBase from './ModalBase';
|
||||
import FormStyledButton from '../widgets/FormStyledButton';
|
||||
import SqlEditor from '../sqleditor/SqlEditor';
|
||||
import styled from 'styled-components';
|
||||
import keycodes from '../utility/keycodes';
|
||||
import ModalHeader from './ModalHeader';
|
||||
import ModalContent from './ModalContent';
|
||||
import ModalFooter from './ModalFooter';
|
||||
|
||||
const SqlWrapper = styled.div`
|
||||
position: relative;
|
||||
height: 30vh;
|
||||
width: 40vw;
|
||||
`;
|
||||
|
||||
export default function ConfirmSqlModal({ modalState, sql, engine, onConfirm }) {
|
||||
const handleKeyDown = (data, hash, keyString, keyCode, event) => {
|
||||
if (keyCode == keycodes.enter) {
|
||||
event.preventDefault();
|
||||
modalState.close();
|
||||
onConfirm();
|
||||
}
|
||||
};
|
||||
return (
|
||||
<ModalBase modalState={modalState}>
|
||||
<ModalHeader modalState={modalState}>Save changes</ModalHeader>
|
||||
<ModalContent>
|
||||
<SqlWrapper>
|
||||
<SqlEditor value={sql} engine={engine} focusOnCreate onKeyDown={handleKeyDown} readOnly />
|
||||
</SqlWrapper>
|
||||
</ModalContent>
|
||||
|
||||
<ModalFooter>
|
||||
<FormStyledButton
|
||||
value="OK"
|
||||
onClick={() => {
|
||||
modalState.close();
|
||||
onConfirm();
|
||||
}}
|
||||
/>
|
||||
<FormStyledButton type="button" value="Close" onClick={modalState.close} />
|
||||
</ModalFooter>
|
||||
</ModalBase>
|
||||
);
|
||||
}
|
||||
@@ -1,349 +0,0 @@
|
||||
import React from 'react';
|
||||
import axios from '../utility/axios';
|
||||
import ModalBase from './ModalBase';
|
||||
import {
|
||||
FormButton,
|
||||
FormTextField,
|
||||
FormSelectField,
|
||||
FormSubmit,
|
||||
FormPasswordField,
|
||||
FormCheckboxField,
|
||||
FormElectronFileSelector,
|
||||
} from '../utility/forms';
|
||||
import ModalHeader from './ModalHeader';
|
||||
import ModalFooter from './ModalFooter';
|
||||
import ModalContent from './ModalContent';
|
||||
import useExtensions from '../utility/useExtensions';
|
||||
import LoadingInfo from '../widgets/LoadingInfo';
|
||||
import { FontIcon } from '../icons';
|
||||
import { FormProvider, useForm } from '../utility/FormProvider';
|
||||
import { TabControl, TabPage } from '../widgets/TabControl';
|
||||
import { usePlatformInfo } from '../utility/metadataLoaders';
|
||||
import getElectron from '../utility/getElectron';
|
||||
import { FormFieldTemplateLarge, FormRowLarge } from '../utility/formStyle';
|
||||
import styled from 'styled-components';
|
||||
import { FlexCol3, FlexCol6, FlexCol9 } from '../utility/flexGrid';
|
||||
// import FormikForm from '../utility/FormikForm';
|
||||
|
||||
const FlexContainer = styled.div`
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
const TestResultContainer = styled.div`
|
||||
margin-left: 10px;
|
||||
align-self: center;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
`;
|
||||
|
||||
const ButtonsContainer = styled.div`
|
||||
flex-shrink: 0;
|
||||
`;
|
||||
|
||||
const AgentInfoWrap = styled.div`
|
||||
margin-left: 20px;
|
||||
margin-bottom: 20px;
|
||||
`;
|
||||
|
||||
function DriverFields({ extensions }) {
|
||||
const { values, setFieldValue } = useForm();
|
||||
const { authType, engine } = values;
|
||||
const driver = extensions.drivers.find(x => x.engine == engine);
|
||||
// const { authTypes } = driver || {};
|
||||
const [authTypes, setAuthTypes] = React.useState(null);
|
||||
const currentAuthType = authTypes && authTypes.find(x => x.name == authType);
|
||||
|
||||
const loadAuthTypes = async () => {
|
||||
const resp = await axios.post('plugins/auth-types', { engine });
|
||||
setAuthTypes(resp.data);
|
||||
if (resp.data && !currentAuthType) {
|
||||
setFieldValue('authType', resp.data[0].name);
|
||||
}
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
setAuthTypes(null);
|
||||
loadAuthTypes();
|
||||
}, [values.engine]);
|
||||
|
||||
if (!driver) return null;
|
||||
const disabledFields = (currentAuthType ? currentAuthType.disabledFields : null) || [];
|
||||
|
||||
return (
|
||||
<>
|
||||
{!!authTypes && (
|
||||
<FormSelectField label="Authentication" name="authType">
|
||||
{authTypes.map(auth => (
|
||||
<option value={auth.name} key={auth.name}>
|
||||
{auth.title}
|
||||
</option>
|
||||
))}
|
||||
</FormSelectField>
|
||||
)}
|
||||
<FormRowLarge>
|
||||
<FlexCol9
|
||||
//@ts-ignore
|
||||
marginRight={5}
|
||||
>
|
||||
<FormTextField
|
||||
label="Server"
|
||||
name="server"
|
||||
disabled={disabledFields.includes('server')}
|
||||
templateProps={{ noMargin: true }}
|
||||
/>
|
||||
</FlexCol9>
|
||||
<FlexCol3>
|
||||
<FormTextField
|
||||
label="Port"
|
||||
name="port"
|
||||
disabled={disabledFields.includes('port')}
|
||||
templateProps={{ noMargin: true }}
|
||||
placeholder={driver && driver.defaultPort}
|
||||
/>
|
||||
</FlexCol3>
|
||||
</FormRowLarge>
|
||||
<FormRowLarge>
|
||||
<FlexCol6
|
||||
//@ts-ignore
|
||||
marginRight={5}
|
||||
>
|
||||
<FormTextField
|
||||
label="User"
|
||||
name="user"
|
||||
disabled={disabledFields.includes('user')}
|
||||
templateProps={{ noMargin: true }}
|
||||
/>
|
||||
</FlexCol6>
|
||||
<FlexCol6>
|
||||
<FormPasswordField
|
||||
label="Password"
|
||||
name="password"
|
||||
disabled={disabledFields.includes('password')}
|
||||
templateProps={{ noMargin: true }}
|
||||
/>
|
||||
</FlexCol6>
|
||||
</FormRowLarge>
|
||||
|
||||
{!disabledFields.includes('password') && (
|
||||
<FormSelectField label="Password mode" name="passwordMode">
|
||||
<option value="saveEncrypted">Save and encrypt</option>
|
||||
<option value="saveRaw">Save raw (UNSAFE!!)</option>
|
||||
</FormSelectField>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function SshTunnelFields() {
|
||||
const { values, setFieldValue } = useForm();
|
||||
const { useSshTunnel, sshMode, sshPort, sshKeyfile } = values;
|
||||
const platformInfo = usePlatformInfo();
|
||||
const electron = getElectron();
|
||||
|
||||
React.useEffect(() => {
|
||||
if (useSshTunnel && !sshMode) {
|
||||
setFieldValue('sshMode', 'userPassword');
|
||||
}
|
||||
if (useSshTunnel && !sshPort) {
|
||||
setFieldValue('sshPort', '22');
|
||||
}
|
||||
if (useSshTunnel && sshMode == 'keyFile' && !sshKeyfile) {
|
||||
setFieldValue('sshKeyfile', platformInfo.defaultKeyFile);
|
||||
}
|
||||
}, [useSshTunnel, sshMode]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormCheckboxField label="Use SSH tunnel" name="useSshTunnel" />
|
||||
<FormRowLarge>
|
||||
<FlexCol9
|
||||
//@ts-ignore
|
||||
marginRight={5}
|
||||
>
|
||||
<FormTextField label="Host" name="sshHost" disabled={!useSshTunnel} templateProps={{ noMargin: true }} />
|
||||
</FlexCol9>
|
||||
<FlexCol3>
|
||||
<FormTextField label="Port" name="sshPort" disabled={!useSshTunnel} templateProps={{ noMargin: true }} />
|
||||
</FlexCol3>
|
||||
</FormRowLarge>
|
||||
<FormTextField label="Bastion host (Jump host)" name="sshBastionHost" disabled={!useSshTunnel} />
|
||||
|
||||
<FormSelectField label="SSH Authentication" name="sshMode" disabled={!useSshTunnel}>
|
||||
<option value="userPassword">Username & password</option>
|
||||
<option value="agent">SSH agent</option>
|
||||
{!!electron && <option value="keyFile">Key file</option>}
|
||||
</FormSelectField>
|
||||
|
||||
{sshMode != 'userPassword' && <FormTextField label="Login" name="sshLogin" disabled={!useSshTunnel} />}
|
||||
|
||||
{sshMode == 'userPassword' && (
|
||||
<FormRowLarge>
|
||||
<FlexCol6
|
||||
//@ts-ignore
|
||||
marginRight={5}
|
||||
>
|
||||
<FormTextField label="Login" name="sshLogin" disabled={!useSshTunnel} templateProps={{ noMargin: true }} />
|
||||
</FlexCol6>
|
||||
<FlexCol6>
|
||||
<FormPasswordField
|
||||
label="Password"
|
||||
name="sshPassword"
|
||||
disabled={!useSshTunnel}
|
||||
templateProps={{ noMargin: true }}
|
||||
/>
|
||||
</FlexCol6>
|
||||
</FormRowLarge>
|
||||
)}
|
||||
|
||||
{sshMode == 'keyFile' && (
|
||||
<FormRowLarge>
|
||||
<FlexCol6
|
||||
//@ts-ignore
|
||||
marginRight={5}
|
||||
>
|
||||
<FormElectronFileSelector
|
||||
label="Private key file"
|
||||
name="sshKeyfile"
|
||||
disabled={!useSshTunnel}
|
||||
templateProps={{ noMargin: true }}
|
||||
/>
|
||||
</FlexCol6>
|
||||
<FlexCol6>
|
||||
<FormPasswordField
|
||||
label="Key file passphrase"
|
||||
name="sshKeyfilePassword"
|
||||
disabled={!useSshTunnel}
|
||||
templateProps={{ noMargin: true }}
|
||||
/>
|
||||
</FlexCol6>
|
||||
</FormRowLarge>
|
||||
)}
|
||||
|
||||
{useSshTunnel && sshMode == 'agent' && (
|
||||
<AgentInfoWrap>
|
||||
{platformInfo.sshAuthSock ? (
|
||||
<div>
|
||||
<FontIcon icon="img ok" /> SSH Agent found
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<FontIcon icon="img error" /> SSH Agent not found
|
||||
</div>
|
||||
)}
|
||||
</AgentInfoWrap>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function SslFields() {
|
||||
const { values } = useForm();
|
||||
const { useSsl } = values;
|
||||
const electron = getElectron();
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormCheckboxField label="Use SSL" name="useSsl" />
|
||||
<FormElectronFileSelector label="CA Cert (optional)" name="sslCaFile" disabled={!useSsl || !electron} />
|
||||
<FormElectronFileSelector label="Certificate (optional)" name="sslCertFile" disabled={!useSsl || !electron} />
|
||||
<FormElectronFileSelector label="Key file (optional)" name="sslKeyFile" disabled={!useSsl || !electron} />
|
||||
<FormCheckboxField label="Reject unauthorized" name="sslRejectUnauthorized" disabled={!useSsl} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function ConnectionModal({ modalState, connection = undefined }) {
|
||||
const [sqlConnectResult, setSqlConnectResult] = React.useState(null);
|
||||
const extensions = useExtensions();
|
||||
const [isTesting, setIsTesting] = React.useState(false);
|
||||
const testIdRef = React.useRef(0);
|
||||
|
||||
const handleTest = async values => {
|
||||
setIsTesting(true);
|
||||
testIdRef.current += 1;
|
||||
const testid = testIdRef.current;
|
||||
const resp = await axios.post('connections/test', values);
|
||||
if (testIdRef.current != testid) return;
|
||||
|
||||
setIsTesting(false);
|
||||
setSqlConnectResult(resp.data);
|
||||
};
|
||||
|
||||
const handleCancel = async () => {
|
||||
testIdRef.current += 1; // invalidate current test
|
||||
setIsTesting(false);
|
||||
};
|
||||
|
||||
const handleSubmit = async values => {
|
||||
axios.post('connections/save', values);
|
||||
modalState.close();
|
||||
};
|
||||
return (
|
||||
<ModalBase modalState={modalState}>
|
||||
<ModalHeader modalState={modalState}>{connection ? 'Edit connection' : 'Add connection'}</ModalHeader>
|
||||
<FormProvider
|
||||
initialValues={connection || { server: 'localhost', engine: 'mssql@dbgate-plugin-mssql' }}
|
||||
template={FormFieldTemplateLarge}
|
||||
>
|
||||
<ModalContent noPadding>
|
||||
<TabControl isInline>
|
||||
<TabPage label="Main" key="main">
|
||||
<FormSelectField label="Database engine" name="engine">
|
||||
<option value="(select driver)"></option>
|
||||
{extensions.drivers.map(driver => (
|
||||
<option value={driver.engine} key={driver.engine}>
|
||||
{driver.title}
|
||||
</option>
|
||||
))}
|
||||
{/* <option value="mssql">Microsoft SQL Server</option>
|
||||
<option value="mysql">MySQL</option>
|
||||
<option value="postgres">Postgre SQL</option> */}
|
||||
</FormSelectField>
|
||||
<DriverFields extensions={extensions} />
|
||||
<FormTextField label="Display name" name="displayName" />
|
||||
</TabPage>
|
||||
<TabPage label="SSH Tunnel" key="sshTunnel">
|
||||
<SshTunnelFields />
|
||||
</TabPage>
|
||||
<TabPage label="SSL" key="ssl">
|
||||
<SslFields />
|
||||
</TabPage>
|
||||
</TabControl>
|
||||
</ModalContent>
|
||||
|
||||
<ModalFooter>
|
||||
<FlexContainer>
|
||||
<ButtonsContainer>
|
||||
{isTesting ? (
|
||||
<FormButton value="Cancel" onClick={handleCancel} />
|
||||
) : (
|
||||
<FormButton value="Test" onClick={handleTest} />
|
||||
)}
|
||||
|
||||
<FormSubmit value="Save" onClick={handleSubmit} />
|
||||
</ButtonsContainer>
|
||||
|
||||
<TestResultContainer>
|
||||
{!isTesting && sqlConnectResult && sqlConnectResult.msgtype == 'connected' && (
|
||||
<div>
|
||||
Connected: <FontIcon icon="img ok" /> {sqlConnectResult.version}
|
||||
</div>
|
||||
)}
|
||||
{!isTesting && sqlConnectResult && sqlConnectResult.msgtype == 'error' && (
|
||||
<div>
|
||||
Connect failed: <FontIcon icon="img error" /> {sqlConnectResult.error}
|
||||
</div>
|
||||
)}
|
||||
{isTesting && (
|
||||
<div>
|
||||
<FontIcon icon="icon loading" /> Testing connection
|
||||
</div>
|
||||
)}
|
||||
</TestResultContainer>
|
||||
</FlexContainer>
|
||||
</ModalFooter>
|
||||
</FormProvider>
|
||||
</ModalBase>
|
||||
);
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import React from 'react';
|
||||
import axios from '../utility/axios';
|
||||
import ModalBase from './ModalBase';
|
||||
import { FormButton, FormSubmit, FormTextField } from '../utility/forms';
|
||||
import ModalHeader from './ModalHeader';
|
||||
import ModalContent from './ModalContent';
|
||||
import ModalFooter from './ModalFooter';
|
||||
import { FormProvider } from '../utility/FormProvider';
|
||||
|
||||
export default function CreateDatabaseModal({ modalState, conid }) {
|
||||
const handleSubmit = async values => {
|
||||
const { name } = values;
|
||||
axios.post('server-connections/create-database', { conid, name });
|
||||
|
||||
modalState.close();
|
||||
};
|
||||
return (
|
||||
<ModalBase modalState={modalState}>
|
||||
<ModalHeader modalState={modalState}>Create database</ModalHeader>
|
||||
<FormProvider initialValues={{ name: 'newdb' }}>
|
||||
<ModalContent>
|
||||
<FormTextField label="Database name" name="name" />
|
||||
</ModalContent>
|
||||
<ModalFooter>
|
||||
<FormSubmit value="Create" onClick={handleSubmit} />
|
||||
</ModalFooter>
|
||||
</FormProvider>
|
||||
</ModalBase>
|
||||
);
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { sleep } from '../utility/common';
|
||||
import useDocumentClick from '../utility/useDocumentClick';
|
||||
import { useHideMenu } from './showMenu';
|
||||
|
||||
const ContextMenuStyled = styled.ul`
|
||||
position: absolute;
|
||||
list-style: none;
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.15);
|
||||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
|
||||
padding: 5px 0;
|
||||
margin: 2px 0 0;
|
||||
font-size: 14px;
|
||||
text-align: left;
|
||||
min-width: 160px;
|
||||
z-index: 1050;
|
||||
cursor: default;
|
||||
`;
|
||||
|
||||
const KeyTextSpan = styled.span`
|
||||
font-style: italic;
|
||||
font-weight: bold;
|
||||
text-align: right;
|
||||
margin-left: 16px;
|
||||
`;
|
||||
|
||||
const StyledLink = styled.a`
|
||||
padding: 3px 20px;
|
||||
line-height: 1.42;
|
||||
display: block;
|
||||
white-space: nop-wrap;
|
||||
color: #262626;
|
||||
|
||||
&:hover {
|
||||
background-color: #f5f5f5;
|
||||
text-decoration: none;
|
||||
color: #262626;
|
||||
}
|
||||
`;
|
||||
|
||||
export const DropDownMenuDivider = styled.li`
|
||||
margin: 9px 0px 9px 0px;
|
||||
border-top: 1px solid #f2f2f2;
|
||||
border-bottom: 1px solid #fff;
|
||||
`;
|
||||
|
||||
export function DropDownMenuItem({ children, keyText = undefined, onClick }) {
|
||||
const handleMouseEnter = () => {
|
||||
// if (this.context.parentMenu) this.context.parentMenu.closeSubmenu();
|
||||
};
|
||||
|
||||
return (
|
||||
<li onMouseEnter={handleMouseEnter}>
|
||||
<StyledLink onClick={onClick}>
|
||||
{children}
|
||||
{keyText && <KeyTextSpan>{keyText}</KeyTextSpan>}
|
||||
</StyledLink>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
export function ContextMenu({ left, top, children }) {
|
||||
const hideMenu = useHideMenu();
|
||||
useDocumentClick(async () => {
|
||||
await sleep(0);
|
||||
hideMenu();
|
||||
});
|
||||
const menuRef = React.useRef(null);
|
||||
React.useEffect(() => {
|
||||
if (menuRef.current) fixPopupPlacement(menuRef.current);
|
||||
}, [menuRef.current]);
|
||||
return (
|
||||
<ContextMenuStyled ref={menuRef} style={{ left: `${left}px`, top: `${top}px` }}>
|
||||
{children}
|
||||
</ContextMenuStyled>
|
||||
);
|
||||
}
|
||||
|
||||
function getElementOffset(element) {
|
||||
var de = document.documentElement;
|
||||
var box = element.getBoundingClientRect();
|
||||
var top = box.top + window.pageYOffset - de.clientTop;
|
||||
var left = box.left + window.pageXOffset - de.clientLeft;
|
||||
return { top: top, left: left };
|
||||
}
|
||||
|
||||
function fixPopupPlacement(element) {
|
||||
const { width, height } = element.getBoundingClientRect();
|
||||
let offset = getElementOffset(element);
|
||||
|
||||
let newLeft = null;
|
||||
let newTop = null;
|
||||
|
||||
if (offset.left + width > window.innerWidth) {
|
||||
newLeft = offset.left - width;
|
||||
}
|
||||
if (offset.top + height > window.innerHeight) {
|
||||
newTop = offset.top - height;
|
||||
}
|
||||
|
||||
if (newLeft != null) element.style.left = `${newLeft}px`;
|
||||
if (newTop != null) element.style.top = `${newTop}px`;
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
import React from 'react';
|
||||
import ModalBase from './ModalBase';
|
||||
import FormStyledButton from '../widgets/FormStyledButton';
|
||||
import styled from 'styled-components';
|
||||
import ModalHeader from './ModalHeader';
|
||||
import ModalFooter from './ModalFooter';
|
||||
import ModalContent from './ModalContent';
|
||||
import { FontIcon } from '../icons';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
display:flex
|
||||
align-items:center
|
||||
`;
|
||||
|
||||
const IconWrapper = styled.div`
|
||||
margin-right: 10px;
|
||||
font-size: 20pt;
|
||||
`;
|
||||
|
||||
export default function ErrorMessageModal({ modalState, title = 'Error', message }) {
|
||||
return (
|
||||
<ModalBase modalState={modalState}>
|
||||
<ModalHeader modalState={modalState}>{title}</ModalHeader>
|
||||
<ModalContent>
|
||||
<Wrapper>
|
||||
<IconWrapper>
|
||||
<FontIcon icon="img error" />
|
||||
</IconWrapper>
|
||||
{message}
|
||||
</Wrapper>
|
||||
</ModalContent>
|
||||
<ModalFooter>
|
||||
<FormStyledButton type="button" value="Close" onClick={modalState.close} />
|
||||
</ModalFooter>
|
||||
</ModalBase>
|
||||
);
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
import React from 'react';
|
||||
import ModalBase from './ModalBase';
|
||||
import {
|
||||
FormTextField,
|
||||
FormSubmit,
|
||||
FormButton,
|
||||
FormCheckboxField,
|
||||
FormFieldTemplate,
|
||||
FormCondition,
|
||||
FormSelectField,
|
||||
} from '../utility/forms';
|
||||
import ModalHeader from './ModalHeader';
|
||||
import ModalContent from './ModalContent';
|
||||
import ModalFooter from './ModalFooter';
|
||||
import { FormProvider, useForm } from '../utility/FormProvider';
|
||||
import axios from '../utility/axios';
|
||||
import uuidv1 from 'uuid/v1';
|
||||
import { FontIcon } from '../icons';
|
||||
import useHasPermission from '../utility/useHasPermission';
|
||||
import _ from 'lodash';
|
||||
import getElectron from '../utility/getElectron';
|
||||
import { copyTextToClipboard } from '../utility/clipboard';
|
||||
import localforage from 'localforage';
|
||||
|
||||
function FontIconPreview() {
|
||||
const { values } = useForm();
|
||||
return <FontIcon icon={values.icon} />;
|
||||
}
|
||||
|
||||
export default function FavoriteModal({ modalState, editingData = undefined, savingTab = undefined }) {
|
||||
const hasPermission = useHasPermission();
|
||||
const electron = getElectron();
|
||||
const savedProperties = ['title', 'icon', 'showInToolbar', 'openOnStartup', 'urlPath'];
|
||||
const initialValues = React.useMemo(() => {
|
||||
if (savingTab) {
|
||||
return {
|
||||
title: savingTab.title,
|
||||
icon: savingTab.icon,
|
||||
urlPath: _.kebabCase(_.deburr(savingTab.title)),
|
||||
};
|
||||
}
|
||||
if (editingData) {
|
||||
return _.pick(editingData, savedProperties);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const savedFile = savingTab && savingTab.props && savingTab.props.savedFile;
|
||||
|
||||
const canWriteFavorite = hasPermission('files/favorites/write');
|
||||
|
||||
const getTabSaveData = async values => {
|
||||
const tabdata = {};
|
||||
const skipEditor = !!savedFile && values.whatToSave != 'content';
|
||||
|
||||
const re = new RegExp(`tabdata_(.*)_${savingTab.tabid}`);
|
||||
for (const key of await localforage.keys()) {
|
||||
const match = key.match(re);
|
||||
if (!match) continue;
|
||||
if (skipEditor && match[1] == 'editor') continue;
|
||||
tabdata[match[1]] = await localforage.getItem(key);
|
||||
}
|
||||
for (const key in localStorage) {
|
||||
const match = key.match(re);
|
||||
if (!match) continue;
|
||||
if (skipEditor && match[1] == 'editor') continue;
|
||||
tabdata[match[1]] = JSON.parse(localStorage.getItem(key));
|
||||
}
|
||||
console.log('tabdata', tabdata, skipEditor, savingTab.tabid);
|
||||
|
||||
return {
|
||||
props:
|
||||
values.whatToSave == 'content' && savingTab.props
|
||||
? _.omit(savingTab.props, ['savedFile', 'savedFormat', 'savedFolder'])
|
||||
: savingTab.props,
|
||||
tabComponent: savingTab.tabComponent,
|
||||
tabdata,
|
||||
..._.pick(values, savedProperties),
|
||||
};
|
||||
};
|
||||
|
||||
const saveTab = async values => {
|
||||
const data = await getTabSaveData(values);
|
||||
|
||||
axios.post('files/save', {
|
||||
folder: 'favorites',
|
||||
file: uuidv1(),
|
||||
format: 'json',
|
||||
data,
|
||||
});
|
||||
};
|
||||
|
||||
const saveFile = async values => {
|
||||
const oldDataResp = await axios.post('files/load', {
|
||||
folder: 'favorites',
|
||||
file: editingData.file,
|
||||
format: 'json',
|
||||
});
|
||||
|
||||
axios.post('files/save', {
|
||||
folder: 'favorites',
|
||||
file: editingData.file,
|
||||
format: 'json',
|
||||
data: {
|
||||
...oldDataResp.data,
|
||||
...values,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleSubmit = async values => {
|
||||
modalState.close();
|
||||
if (savingTab) {
|
||||
saveTab(values);
|
||||
}
|
||||
if (editingData) {
|
||||
saveFile(values);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCopyLink = async values => {
|
||||
const tabdata = await getTabSaveData(values);
|
||||
copyTextToClipboard(`${document.location.origin}#tabdata=${encodeURIComponent(JSON.stringify(tabdata))}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalBase modalState={modalState}>
|
||||
<ModalHeader modalState={modalState}>{editingData ? 'Edit favorite' : 'Share / add to favorites'}</ModalHeader>
|
||||
<FormProvider initialValues={initialValues}>
|
||||
<ModalContent>
|
||||
<FormTextField label="Title" name="title" focused />
|
||||
<FormTextField label="Icon" name="icon" />
|
||||
<FormFieldTemplate label="Icon preview" type="icon">
|
||||
<FontIconPreview />
|
||||
</FormFieldTemplate>
|
||||
<FormTextField label="URL path" name="urlPath" />
|
||||
{!!savingTab && !electron && canWriteFavorite && (
|
||||
<FormCheckboxField label="Share as link" name="shareAsLink" />
|
||||
)}
|
||||
<FormCondition condition={values => !values.shareAsLink && canWriteFavorite}>
|
||||
<FormCheckboxField label="Show in toolbar" name="showInToolbar" />
|
||||
<FormCheckboxField label="Open on startup" name="openOnStartup" />
|
||||
</FormCondition>
|
||||
{!!savingTab && !!savedFile && (
|
||||
<FormSelectField label="What to save" name="whatToSave">
|
||||
<option value="fileName">Link to file</option>
|
||||
<option value="content">Content</option>
|
||||
</FormSelectField>
|
||||
)}
|
||||
</ModalContent>
|
||||
<ModalFooter>
|
||||
<FormCondition condition={values => !values.shareAsLink && canWriteFavorite}>
|
||||
<FormSubmit value="OK" onClick={handleSubmit} />
|
||||
</FormCondition>
|
||||
<FormCondition condition={values => values.shareAsLink || !canWriteFavorite}>
|
||||
<FormButton value="Copy link" onClick={handleCopyLink} />
|
||||
</FormCondition>
|
||||
<FormButton value="Cancel" onClick={() => modalState.close()} />
|
||||
</ModalFooter>
|
||||
</FormProvider>
|
||||
</ModalBase>
|
||||
);
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
import React from 'react';
|
||||
import ModalBase from './ModalBase';
|
||||
import FormStyledButton from '../widgets/FormStyledButton';
|
||||
import styled from 'styled-components';
|
||||
import ModalHeader from './ModalHeader';
|
||||
import ModalFooter from './ModalFooter';
|
||||
import ModalContent from './ModalContent';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
const OptionsWrapper = styled.div`
|
||||
margin-left: 10px;
|
||||
`;
|
||||
|
||||
function RadioGroupItem({ text, value, defaultChecked = undefined, setMode }) {
|
||||
return (
|
||||
<div>
|
||||
<input
|
||||
type="radio"
|
||||
name="multiple_values_option"
|
||||
id={`multiple_values_option_${value}`}
|
||||
defaultChecked={defaultChecked}
|
||||
onClick={() => setMode(value)}
|
||||
/>
|
||||
<label htmlFor={`multiple_values_option_${value}`}>{text}</label>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function FilterMultipleValuesModal({ modalState, onFilter }) {
|
||||
const editorRef = React.useRef(null);
|
||||
const [text, setText] = React.useState('');
|
||||
const [mode, setMode] = React.useState('is');
|
||||
React.useEffect(() => {
|
||||
setTimeout(() => {
|
||||
if (editorRef.current) editorRef.current.focus();
|
||||
}, 1);
|
||||
}, []);
|
||||
|
||||
const handleOk = () => {
|
||||
onFilter(mode, text);
|
||||
modalState.close();
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalBase modalState={modalState}>
|
||||
<ModalHeader modalState={modalState}>Filter multiple values</ModalHeader>
|
||||
<ModalContent>
|
||||
<Wrapper>
|
||||
<textarea rows={10} ref={editorRef} value={text} onChange={e => setText(e.target.value)} />
|
||||
<OptionsWrapper>
|
||||
<RadioGroupItem text="Is one of line" value="is" defaultChecked setMode={setMode} />
|
||||
<RadioGroupItem text="Is not one of line" value="is_not" setMode={setMode} />
|
||||
<RadioGroupItem text="Contains" value="contains" setMode={setMode} />
|
||||
<RadioGroupItem text="Begins" value="begins" setMode={setMode} />
|
||||
<RadioGroupItem text="Ends" value="ends" setMode={setMode} />
|
||||
</OptionsWrapper>
|
||||
</Wrapper>
|
||||
</ModalContent>
|
||||
<ModalFooter>
|
||||
<FormStyledButton type="button" value="OK" onClick={handleOk} />
|
||||
<FormStyledButton type="button" value="Close" onClick={modalState.close} />
|
||||
</ModalFooter>
|
||||
</ModalBase>
|
||||
);
|
||||
}
|
||||
@@ -1,249 +0,0 @@
|
||||
import React from 'react';
|
||||
import moment from 'moment';
|
||||
import ModalBase from './ModalBase';
|
||||
import FormStyledButton from '../widgets/FormStyledButton';
|
||||
import styled from 'styled-components';
|
||||
import ModalHeader from './ModalHeader';
|
||||
import ModalFooter from './ModalFooter';
|
||||
import ModalContent from './ModalContent';
|
||||
import ImportExportConfigurator from '../impexp/ImportExportConfigurator';
|
||||
import createImpExpScript from '../impexp/createImpExpScript';
|
||||
import { useCurrentArchive, useSetCurrentArchive, useSetCurrentWidget, useSetOpenedTabs } from '../utility/globalState';
|
||||
import RunnerOutputPane from '../query/RunnerOutputPane';
|
||||
import axios from '../utility/axios';
|
||||
import WidgetColumnBar, { WidgetColumnBarItem } from '../widgets/WidgetColumnBar';
|
||||
import SocketMessagesView from '../query/SocketMessagesView';
|
||||
import RunnerOutputFiles from '../query/RunnerOuputFiles';
|
||||
import useTheme from '../theme/useTheme';
|
||||
import PreviewDataGrid from '../impexp/PreviewDataGrid';
|
||||
import useSocket from '../utility/SocketProvider';
|
||||
import LoadingInfo from '../widgets/LoadingInfo';
|
||||
import { FontIcon } from '../icons';
|
||||
import LargeButton, { LargeFormButton } from '../widgets/LargeButton';
|
||||
import { getDefaultFileFormat } from '../utility/fileformats';
|
||||
import useExtensions from '../utility/useExtensions';
|
||||
import { FormProvider, useForm } from '../utility/FormProvider';
|
||||
import { FormTextField } from '../utility/forms';
|
||||
import useOpenNewTab from '../utility/useOpenNewTab';
|
||||
|
||||
const headerHeight = '60px';
|
||||
const footerHeight = '100px';
|
||||
|
||||
const OutputContainer = styled.div`
|
||||
position: relative;
|
||||
height: 150px;
|
||||
`;
|
||||
|
||||
const Wrapper = styled.div`
|
||||
display: flex;
|
||||
// flex: 1;
|
||||
|
||||
position: fixed;
|
||||
top: ${headerHeight};
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: ${footerHeight};
|
||||
`;
|
||||
|
||||
const WidgetColumnWrapper = styled.div`
|
||||
max-width: 40%;
|
||||
// flex-basis: 50%;
|
||||
// flow-grow: 0;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
border-left: 1px solid ${props => props.theme.border};
|
||||
`;
|
||||
|
||||
const FormWrapper = styled.div`
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
const ContentWrapper = styled.div`
|
||||
border-top: 1px solid ${props => props.theme.border};
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
`;
|
||||
|
||||
const Footer = styled.div`
|
||||
position: fixed;
|
||||
height: ${footerHeight};
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0px;
|
||||
background-color: ${props => props.theme.modalheader_background};
|
||||
|
||||
border-top: 1px solid ${props => props.theme.border};
|
||||
// padding: 15px;
|
||||
`;
|
||||
|
||||
const FooterButtons = styled.div`
|
||||
margin: 15px;
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
function GenerateSctriptButton({ modalState }) {
|
||||
const openNewTab = useOpenNewTab();
|
||||
const { values } = useForm();
|
||||
const extensions = useExtensions();
|
||||
|
||||
const handleGenerateScript = async () => {
|
||||
const code = await createImpExpScript(extensions, values);
|
||||
openNewTab(
|
||||
{
|
||||
title: 'Shell #',
|
||||
icon: 'img shell',
|
||||
tabComponent: 'ShellTab',
|
||||
},
|
||||
{ editor: code }
|
||||
);
|
||||
modalState.close();
|
||||
};
|
||||
|
||||
return (
|
||||
<LargeButton icon="img sql-file" onClick={handleGenerateScript}>
|
||||
Generate script
|
||||
</LargeButton>
|
||||
);
|
||||
}
|
||||
|
||||
export default function ImportExportModal({
|
||||
modalState,
|
||||
initialValues,
|
||||
uploadedFile = undefined,
|
||||
openedFile = undefined,
|
||||
importToArchive = false,
|
||||
}) {
|
||||
const [executeNumber, setExecuteNumber] = React.useState(0);
|
||||
const [runnerId, setRunnerId] = React.useState(null);
|
||||
const archive = useCurrentArchive();
|
||||
const theme = useTheme();
|
||||
const [previewReader, setPreviewReader] = React.useState(0);
|
||||
const targetArchiveFolder = importToArchive ? `import-${moment().format('YYYY-MM-DD-hh-mm-ss')}` : archive;
|
||||
const socket = useSocket();
|
||||
const refreshArchiveFolderRef = React.useRef(null);
|
||||
const setArchive = useSetCurrentArchive();
|
||||
const setCurrentWidget = useSetCurrentWidget();
|
||||
const extensions = useExtensions();
|
||||
|
||||
const [busy, setBusy] = React.useState(false);
|
||||
|
||||
const handleRunnerDone = React.useCallback(() => {
|
||||
setBusy(false);
|
||||
if (refreshArchiveFolderRef.current) {
|
||||
axios.post('archive/refresh-folders', {});
|
||||
axios.post('archive/refresh-files', { folder: refreshArchiveFolderRef.current });
|
||||
setArchive(refreshArchiveFolderRef.current);
|
||||
setCurrentWidget('archive');
|
||||
}
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (runnerId && socket) {
|
||||
socket.on(`runner-done-${runnerId}`, handleRunnerDone);
|
||||
return () => {
|
||||
socket.off(`runner-done-${runnerId}`, handleRunnerDone);
|
||||
};
|
||||
}
|
||||
}, [runnerId, socket]);
|
||||
|
||||
const handleExecute = async values => {
|
||||
if (busy) return;
|
||||
|
||||
setBusy(true);
|
||||
const script = await createImpExpScript(extensions, values);
|
||||
|
||||
setExecuteNumber(num => num + 1);
|
||||
|
||||
let runid = runnerId;
|
||||
const resp = await axios.post('runners/start', { script });
|
||||
runid = resp.data.runid;
|
||||
setRunnerId(runid);
|
||||
if (values.targetStorageType == 'archive') {
|
||||
refreshArchiveFolderRef.current = values.targetArchiveFolder;
|
||||
} else {
|
||||
refreshArchiveFolderRef.current = null;
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
axios.post('runners/cancel', {
|
||||
runid: runnerId,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalBase modalState={modalState} fullScreen isFlex>
|
||||
<FormProvider
|
||||
initialValues={{
|
||||
sourceStorageType: 'database',
|
||||
targetStorageType: importToArchive ? 'archive' : getDefaultFileFormat(extensions).storageType,
|
||||
sourceArchiveFolder: archive,
|
||||
targetArchiveFolder,
|
||||
...initialValues,
|
||||
}}
|
||||
>
|
||||
<FormWrapper>
|
||||
<ModalHeader modalState={modalState}>Import/Export {busy && <FontIcon icon="icon loading" />}</ModalHeader>
|
||||
<Wrapper>
|
||||
<ContentWrapper theme={theme}>
|
||||
<ImportExportConfigurator
|
||||
uploadedFile={uploadedFile}
|
||||
openedFile={openedFile}
|
||||
onChangePreview={setPreviewReader}
|
||||
/>
|
||||
</ContentWrapper>
|
||||
<WidgetColumnWrapper theme={theme}>
|
||||
<WidgetColumnBar>
|
||||
<WidgetColumnBarItem title="Output files" name="output" height="20%">
|
||||
<RunnerOutputFiles runnerId={runnerId} executeNumber={executeNumber} />
|
||||
</WidgetColumnBarItem>
|
||||
<WidgetColumnBarItem title="Messages" name="messages">
|
||||
<SocketMessagesView
|
||||
eventName={runnerId ? `runner-info-${runnerId}` : null}
|
||||
executeNumber={executeNumber}
|
||||
/>
|
||||
</WidgetColumnBarItem>
|
||||
{previewReader && (
|
||||
<WidgetColumnBarItem title="Preview" name="preview">
|
||||
<PreviewDataGrid reader={previewReader} />
|
||||
</WidgetColumnBarItem>
|
||||
)}
|
||||
<WidgetColumnBarItem title="Advanced configuration" name="config" collapsed>
|
||||
<FormTextField label="Schedule" name="schedule" />
|
||||
<FormTextField label="Start variable index" name="startVariableIndex" />
|
||||
</WidgetColumnBarItem>
|
||||
</WidgetColumnBar>
|
||||
</WidgetColumnWrapper>
|
||||
</Wrapper>
|
||||
<Footer theme={theme}>
|
||||
<FooterButtons>
|
||||
{busy ? (
|
||||
<LargeButton icon="icon close" onClick={handleCancel}>
|
||||
Cancel
|
||||
</LargeButton>
|
||||
) : (
|
||||
<LargeFormButton onClick={handleExecute} icon="icon run">
|
||||
Run
|
||||
</LargeFormButton>
|
||||
)}
|
||||
<GenerateSctriptButton modalState={modalState} />
|
||||
<LargeButton onClick={modalState.close} icon="icon close">
|
||||
Close
|
||||
</LargeButton>
|
||||
</FooterButtons>
|
||||
</Footer>
|
||||
</FormWrapper>
|
||||
</FormProvider>
|
||||
</ModalBase>
|
||||
);
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import React from 'react';
|
||||
import ModalBase from './ModalBase';
|
||||
import { FormTextField, FormSubmit, FormButton } from '../utility/forms';
|
||||
import ModalHeader from './ModalHeader';
|
||||
import ModalContent from './ModalContent';
|
||||
import ModalFooter from './ModalFooter';
|
||||
import { FormProvider } from '../utility/FormProvider';
|
||||
|
||||
export default function InputTextModal({ header, label, value, modalState, onConfirm }) {
|
||||
const handleSubmit = async values => {
|
||||
const { value } = values;
|
||||
modalState.close();
|
||||
onConfirm(value);
|
||||
};
|
||||
return (
|
||||
<ModalBase modalState={modalState}>
|
||||
<ModalHeader modalState={modalState}>{header}</ModalHeader>
|
||||
<FormProvider initialValues={{ value }}>
|
||||
<ModalContent>
|
||||
<FormTextField label={label} name="value" focused />
|
||||
</ModalContent>
|
||||
<ModalFooter>
|
||||
<FormButton value="Cancel" onClick={() => modalState.close()} />
|
||||
<FormSubmit value="OK" onClick={handleSubmit} />
|
||||
</ModalFooter>
|
||||
</FormProvider>
|
||||
</ModalBase>
|
||||
);
|
||||
}
|
||||
@@ -1,191 +0,0 @@
|
||||
import React from 'react';
|
||||
import ModalBase from './ModalBase';
|
||||
import FormStyledButton from '../widgets/FormStyledButton';
|
||||
import SqlEditor from '../sqleditor/SqlEditor';
|
||||
import styled from 'styled-components';
|
||||
import keycodes from '../utility/keycodes';
|
||||
import ModalHeader from './ModalHeader';
|
||||
import ModalContent from './ModalContent';
|
||||
import ModalFooter from './ModalFooter';
|
||||
import analyseQuerySources from '../sqleditor/analyseQuerySources';
|
||||
import TableControl, { TableColumn } from '../utility/TableControl';
|
||||
import { TextField } from '../utility/inputs';
|
||||
|
||||
const FlexLine = styled.div`
|
||||
display: flex;
|
||||
margin-bottom: 15px;
|
||||
`;
|
||||
|
||||
const FlexColumn = styled.div`
|
||||
margin: 5px;
|
||||
`;
|
||||
|
||||
const Label = styled.div`
|
||||
margin: 5px;
|
||||
`;
|
||||
|
||||
const SqlWrapper = styled.div`
|
||||
position: relative;
|
||||
height: 80px;
|
||||
width: 40vw;
|
||||
`;
|
||||
|
||||
const JOIN_TYPES = ['INNER JOIN', 'LEFT JOIN', 'RIGHT JOIN'];
|
||||
|
||||
export default function InsertJoinModal({ sql, modalState, engine, dbinfo, onInsert }) {
|
||||
const sources = React.useMemo(
|
||||
() => analyseQuerySources(sql, [...dbinfo.tables.map(x => x.pureName), ...dbinfo.views.map(x => x.pureName)]),
|
||||
[sql, dbinfo]
|
||||
);
|
||||
|
||||
const [sourceIndex, setSourceIndex] = React.useState(0);
|
||||
const [targetIndex, setTargetIndex] = React.useState(0);
|
||||
const [joinIndex, setJoinIndex] = React.useState(0);
|
||||
const [alias, setAlias] = React.useState('');
|
||||
const sourceRef = React.useRef(null);
|
||||
const targetRef = React.useRef(null);
|
||||
const aliasRef = React.useRef(null);
|
||||
const joinRef = React.useRef(null);
|
||||
|
||||
const targets = React.useMemo(() => {
|
||||
const source = sources[sourceIndex];
|
||||
if (!source) return [];
|
||||
/** @type {import('dbgate-types').TableInfo} */
|
||||
const table = dbinfo.tables.find(x => x.pureName == sources[sourceIndex].name);
|
||||
if (!table) return [];
|
||||
return [
|
||||
...table.foreignKeys.map(fk => ({
|
||||
baseColumns: fk.columns.map(x => x.columnName).join(', '),
|
||||
refTable: fk.refTableName,
|
||||
refColumns: fk.columns.map(x => x.refColumnName).join(', '),
|
||||
constraintName: fk.constraintName,
|
||||
columnMap: fk.columns,
|
||||
})),
|
||||
...table.dependencies.map(fk => ({
|
||||
baseColumns: fk.columns.map(x => x.refColumnName).join(', '),
|
||||
refTable: fk.pureName,
|
||||
refColumns: fk.columns.map(x => x.columnName).join(', '),
|
||||
constraintName: fk.constraintName,
|
||||
columnMap: fk.columns.map(x => ({
|
||||
columnName: x.refColumnName,
|
||||
refColumnName: x.columnName,
|
||||
})),
|
||||
})),
|
||||
];
|
||||
}, [sourceIndex, sources]);
|
||||
|
||||
const sqlPreview = React.useMemo(() => {
|
||||
const source = sources[sourceIndex];
|
||||
const target = targets[targetIndex];
|
||||
if (source && target) {
|
||||
return `${JOIN_TYPES[joinIndex]} ${target.refTable}${alias ? ` ${alias}` : ''} ON ${target.columnMap
|
||||
.map(col => `${source.name}.${col.columnName} = ${alias || target.refTable}.${col.refColumnName}`)
|
||||
.join(' AND ')}`;
|
||||
}
|
||||
return '';
|
||||
}, [joinIndex, sources, targets, sourceIndex, targetIndex, alias]);
|
||||
|
||||
const sourceKeyDown = React.useCallback(event => {
|
||||
if (event.keyCode == keycodes.enter || event.keyCode == keycodes.rightArrow) {
|
||||
targetRef.current.focus();
|
||||
}
|
||||
}, []);
|
||||
const targetKeyDown = React.useCallback(event => {
|
||||
if (event.keyCode == keycodes.leftArrow) {
|
||||
sourceRef.current.focus();
|
||||
}
|
||||
if (event.keyCode == keycodes.enter || event.keyCode == keycodes.rightArrow) {
|
||||
joinRef.current.focus();
|
||||
}
|
||||
}, []);
|
||||
const joinKeyDown = React.useCallback(event => {
|
||||
if (event.keyCode == keycodes.leftArrow) {
|
||||
targetRef.current.focus();
|
||||
}
|
||||
if (event.keyCode == keycodes.enter) {
|
||||
aliasRef.current.focus();
|
||||
}
|
||||
}, []);
|
||||
const aliasKeyDown = React.useCallback(
|
||||
event => {
|
||||
if (event.keyCode == keycodes.enter) {
|
||||
event.preventDefault();
|
||||
modalState.close();
|
||||
onInsert(sqlPreview);
|
||||
}
|
||||
},
|
||||
[onInsert, sqlPreview]
|
||||
);
|
||||
|
||||
return (
|
||||
<ModalBase modalState={modalState}>
|
||||
<ModalHeader modalState={modalState}>Insert join</ModalHeader>
|
||||
<ModalContent>
|
||||
<FlexLine>
|
||||
<FlexColumn>
|
||||
<Label>Existing table</Label>
|
||||
<TableControl
|
||||
rows={sources}
|
||||
focusOnCreate
|
||||
selectedIndex={sourceIndex}
|
||||
setSelectedIndex={setSourceIndex}
|
||||
onKeyDown={sourceKeyDown}
|
||||
tableRef={sourceRef}
|
||||
>
|
||||
<TableColumn fieldName="alias" header="Alias" />
|
||||
<TableColumn fieldName="name" header="Name" />
|
||||
</TableControl>
|
||||
</FlexColumn>
|
||||
<FlexColumn>
|
||||
<Label>New table</Label>
|
||||
<TableControl
|
||||
rows={targets}
|
||||
selectedIndex={targetIndex}
|
||||
setSelectedIndex={setTargetIndex}
|
||||
tableRef={targetRef}
|
||||
onKeyDown={targetKeyDown}
|
||||
>
|
||||
<TableColumn fieldName="baseColumns" header="Column from" />
|
||||
<TableColumn fieldName="refTable" header="Table to" />
|
||||
<TableColumn fieldName="refColumns" header="Column to" />
|
||||
{/* <TableColumn fieldName="constraintName" header="Foreign key" /> */}
|
||||
</TableControl>
|
||||
</FlexColumn>
|
||||
<FlexColumn>
|
||||
<Label>Join</Label>
|
||||
<TableControl
|
||||
rows={JOIN_TYPES.map(name => ({ name }))}
|
||||
selectedIndex={joinIndex}
|
||||
setSelectedIndex={setJoinIndex}
|
||||
tableRef={joinRef}
|
||||
onKeyDown={joinKeyDown}
|
||||
>
|
||||
<TableColumn fieldName="name" header="Join type" />
|
||||
</TableControl>
|
||||
<Label>Alias</Label>
|
||||
<TextField
|
||||
value={alias}
|
||||
onChange={e => setAlias(e.target.value)}
|
||||
editorRef={aliasRef}
|
||||
onKeyDown={aliasKeyDown}
|
||||
/>
|
||||
</FlexColumn>
|
||||
</FlexLine>
|
||||
<SqlWrapper>
|
||||
<SqlEditor value={sqlPreview} engine={engine} readOnly />
|
||||
</SqlWrapper>
|
||||
</ModalContent>
|
||||
|
||||
<ModalFooter>
|
||||
<FormStyledButton
|
||||
value="OK"
|
||||
onClick={() => {
|
||||
modalState.close();
|
||||
onInsert(sqlPreview);
|
||||
}}
|
||||
/>
|
||||
<FormStyledButton type="button" value="Close" onClick={modalState.close} />
|
||||
</ModalFooter>
|
||||
</ModalBase>
|
||||
);
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
import React from 'react';
|
||||
import Modal from 'react-modal';
|
||||
import styled from 'styled-components';
|
||||
import useTheme from '../theme/useTheme';
|
||||
import ErrorBoundary from '../utility/ErrorBoundary';
|
||||
|
||||
// const StyledModal = styled(Modal)`
|
||||
// position: absolute;
|
||||
// top: 40px;
|
||||
// left: 40px;
|
||||
// right: 40px;
|
||||
// bottom: 40px;
|
||||
// border: 1px solid #ccc;
|
||||
// background: #fff;
|
||||
// overflow: auto;
|
||||
// webkitoverflowscrolling: touch;
|
||||
// borderradius: 4px;
|
||||
// outline: none;
|
||||
// padding: 20px;
|
||||
// `;
|
||||
|
||||
const StyledModal = styled(Modal)`
|
||||
border: 1px solid ${props => props.theme.border};
|
||||
background: ${props => props.theme.modal_background};
|
||||
overflow: auto;
|
||||
webkitoverflowscrolling: touch;
|
||||
outline: none;
|
||||
|
||||
${props =>
|
||||
props.fullScreen &&
|
||||
`
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
// width: 100%;
|
||||
// height: 100%;
|
||||
`}
|
||||
|
||||
${props =>
|
||||
!props.fullScreen &&
|
||||
`
|
||||
border-radius: 10px;
|
||||
width: 50%;
|
||||
max-width: 900px;
|
||||
margin: auto;
|
||||
margin-top: 15vh;
|
||||
`}
|
||||
|
||||
// z-index:1200;
|
||||
|
||||
${props =>
|
||||
props.isFlex &&
|
||||
`
|
||||
display: flex;
|
||||
`}
|
||||
`;
|
||||
|
||||
const ModalContent = styled.div`
|
||||
padding: 20px;
|
||||
`;
|
||||
|
||||
export default function ModalBase({ modalState, children, isFlex = false, fullScreen = false }) {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<StyledModal
|
||||
theme={theme}
|
||||
isOpen={modalState.isOpen}
|
||||
onRequestClose={modalState.close}
|
||||
overlayClassName="RactModalOverlay"
|
||||
fullScreen={fullScreen}
|
||||
isFlex={isFlex}
|
||||
ariaHideApp={false}
|
||||
// style={{
|
||||
// overlay: {
|
||||
// backgroundColor: '#000',
|
||||
// opacity: 0.5,
|
||||
// zIndex: 1000,
|
||||
// },
|
||||
// zIndex: 1200,
|
||||
// }}
|
||||
>
|
||||
<ErrorBoundary>{children}</ErrorBoundary>
|
||||
</StyledModal>
|
||||
);
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import useTheme from '../theme/useTheme';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
border-bottom: 1px solid ${props => props.theme.border};
|
||||
border-top: 1px solid ${props => props.theme.border};
|
||||
${props =>
|
||||
// @ts-ignore
|
||||
!props.noPadding &&
|
||||
`
|
||||
padding: 15px;
|
||||
`}
|
||||
`;
|
||||
|
||||
export default function ModalContent({ children, noPadding = false }) {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Wrapper
|
||||
theme={theme}
|
||||
// @ts-ignore
|
||||
noPadding={noPadding}
|
||||
>
|
||||
{children}
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import useTheme from '../theme/useTheme';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
border-bottom: 1px solid ${props => props.theme.border};
|
||||
padding: 15px;
|
||||
background-color: ${props => props.theme.modalheader_background};
|
||||
`;
|
||||
|
||||
export default function ModalFooter({ children }) {
|
||||
const theme = useTheme();
|
||||
return <Wrapper theme={theme}>{children}</Wrapper>;
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { FontIcon } from '../icons';
|
||||
import useTheme from '../theme/useTheme';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
font-size: 15pt;
|
||||
padding: 15px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
background-color: ${props => props.theme.modalheader_background};
|
||||
`;
|
||||
|
||||
const CloseWrapper = styled.div`
|
||||
font-size: 12pt;
|
||||
&:hover {
|
||||
background-color: ${props => props.theme.modalheader_background2};
|
||||
}
|
||||
padding: 5px 10px;
|
||||
border-radius: 10px;
|
||||
`;
|
||||
|
||||
export default function ModalHeader({ children, modalState }) {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Wrapper theme={theme}>
|
||||
<div>{children}</div>
|
||||
<CloseWrapper onClick={modalState.close} theme={theme}>
|
||||
<FontIcon icon="icon close" />
|
||||
</CloseWrapper>
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
import React from 'react';
|
||||
import ModalBase from './ModalBase';
|
||||
import { FormTextField, FormSubmit, FormArchiveFolderSelect, FormFieldTemplate } from '../utility/forms';
|
||||
import styled from 'styled-components';
|
||||
import ModalHeader from './ModalHeader';
|
||||
import ModalContent from './ModalContent';
|
||||
import ModalFooter from './ModalFooter';
|
||||
import { FormProvider } from '../utility/FormProvider';
|
||||
|
||||
const SelectWrapper = styled.div`
|
||||
width: 150px;
|
||||
position: relative;
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
export default function SaveArchiveModal({ file = 'new-table', folder = 'default', modalState, onSave }) {
|
||||
const handleSubmit = async values => {
|
||||
const { file, folder } = values;
|
||||
modalState.close();
|
||||
if (onSave) onSave(folder, file);
|
||||
};
|
||||
return (
|
||||
<ModalBase modalState={modalState}>
|
||||
<ModalHeader modalState={modalState}>Save to archive</ModalHeader>
|
||||
<FormProvider initialValues={{ file, folder }}>
|
||||
<ModalContent>
|
||||
<FormFieldTemplate label="Folder" type="select">
|
||||
<SelectWrapper>
|
||||
<FormArchiveFolderSelect name="folder" />
|
||||
</SelectWrapper>
|
||||
</FormFieldTemplate>
|
||||
<FormTextField label="File name" name="file" />
|
||||
</ModalContent>
|
||||
<ModalFooter>
|
||||
<FormSubmit value="Save" onClick={handleSubmit} />
|
||||
</ModalFooter>
|
||||
</FormProvider>
|
||||
</ModalBase>
|
||||
);
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
import React from 'react';
|
||||
import axios from '../utility/axios';
|
||||
import ModalBase from './ModalBase';
|
||||
import { FormTextField, FormSubmit } from '../utility/forms';
|
||||
import ModalHeader from './ModalHeader';
|
||||
import ModalContent from './ModalContent';
|
||||
import ModalFooter from './ModalFooter';
|
||||
import { FormProvider } from '../utility/FormProvider';
|
||||
import FormStyledButton from '../widgets/FormStyledButton';
|
||||
import getElectron from '../utility/getElectron';
|
||||
|
||||
export default function SaveFileModal({
|
||||
data,
|
||||
folder,
|
||||
format,
|
||||
modalState,
|
||||
name,
|
||||
fileExtension,
|
||||
filePath,
|
||||
onSave = undefined,
|
||||
}) {
|
||||
const electron = getElectron();
|
||||
|
||||
const handleSubmit = async values => {
|
||||
const { name } = values;
|
||||
await axios.post('files/save', { folder, file: name, data, format });
|
||||
modalState.close();
|
||||
if (onSave) {
|
||||
onSave(name, {
|
||||
savedFile: name,
|
||||
savedFolder: folder,
|
||||
savedFilePath: null,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveToDisk = async filePath => {
|
||||
const path = window.require('path');
|
||||
const parsed = path.parse(filePath);
|
||||
// if (!parsed.ext) filePath += `.${fileExtension}`;
|
||||
|
||||
await axios.post('files/save-as', { filePath, data, format });
|
||||
modalState.close();
|
||||
|
||||
if (onSave) {
|
||||
onSave(parsed.name, {
|
||||
savedFile: null,
|
||||
savedFolder: null,
|
||||
savedFilePath: filePath,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalBase modalState={modalState}>
|
||||
<ModalHeader modalState={modalState}>Save file</ModalHeader>
|
||||
<FormProvider initialValues={{ name }}>
|
||||
<ModalContent>
|
||||
<FormTextField label="File name" name="name" focused />
|
||||
</ModalContent>
|
||||
<ModalFooter>
|
||||
<FormSubmit value="Save" onClick={handleSubmit} />
|
||||
{electron && (
|
||||
<FormStyledButton
|
||||
type="button"
|
||||
value="Save to disk"
|
||||
onClick={() => {
|
||||
const file = electron.remote.dialog.showSaveDialogSync(electron.remote.getCurrentWindow(), {
|
||||
filters: [
|
||||
{ name: `${fileExtension.toUpperCase()} files`, extensions: [fileExtension] },
|
||||
{ name: `All files`, extensions: ['*'] },
|
||||
],
|
||||
defaultPath: filePath || `${name}.${fileExtension}`,
|
||||
properties: ['showOverwriteConfirmation'],
|
||||
});
|
||||
if (file) {
|
||||
handleSaveToDisk(file);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</ModalFooter>
|
||||
</FormProvider>
|
||||
</ModalBase>
|
||||
);
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
import React from 'react';
|
||||
import axios from '../utility/axios';
|
||||
import { changeTab } from '../utility/common';
|
||||
import getElectron from '../utility/getElectron';
|
||||
import { useOpenedTabs, useSetOpenedTabs } from '../utility/globalState';
|
||||
import keycodes from '../utility/keycodes';
|
||||
import SaveFileToolbarButton from '../utility/SaveFileToolbarButton';
|
||||
import ToolbarPortal from '../utility/ToolbarPortal';
|
||||
import useHasPermission from '../utility/useHasPermission';
|
||||
import SaveFileModal from './SaveFileModal';
|
||||
import useModalState from './useModalState';
|
||||
|
||||
export default function SaveTabModal({
|
||||
data,
|
||||
folder,
|
||||
format,
|
||||
tabid,
|
||||
tabVisible,
|
||||
fileExtension,
|
||||
toolbarPortalRef = undefined,
|
||||
}) {
|
||||
const setOpenedTabs = useSetOpenedTabs();
|
||||
const openedTabs = useOpenedTabs();
|
||||
const saveFileModalState = useModalState();
|
||||
const hasPermission = useHasPermission();
|
||||
const canSave = hasPermission(`files/${folder}/write`);
|
||||
|
||||
const { savedFile, savedFilePath } = openedTabs.find(x => x.tabid == tabid).props || {};
|
||||
const onSave = (title, newProps) => {
|
||||
changeTab(tabid, setOpenedTabs, tab => ({
|
||||
...tab,
|
||||
title,
|
||||
props: {
|
||||
...tab.props,
|
||||
savedFormat: format,
|
||||
...newProps,
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
if (savedFile) {
|
||||
await axios.post('files/save', { folder, file: savedFile, data, format });
|
||||
}
|
||||
if (savedFilePath) {
|
||||
await axios.post('files/save-as', { filePath: savedFilePath, data, format });
|
||||
}
|
||||
};
|
||||
const handleSaveRef = React.useRef(handleSave);
|
||||
handleSaveRef.current = handleSave;
|
||||
|
||||
const handleKeyboard = React.useCallback(
|
||||
e => {
|
||||
if (e.keyCode == keycodes.s && e.ctrlKey) {
|
||||
e.preventDefault();
|
||||
if (e.shiftKey) {
|
||||
saveFileModalState.open();
|
||||
} else {
|
||||
if (savedFile || savedFilePath) handleSaveRef.current();
|
||||
else saveFileModalState.open();
|
||||
}
|
||||
}
|
||||
},
|
||||
[saveFileModalState]
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (tabVisible && canSave) {
|
||||
document.addEventListener('keydown', handleKeyboard);
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleKeyboard);
|
||||
};
|
||||
}
|
||||
}, [tabVisible, handleKeyboard, canSave]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const electron = getElectron();
|
||||
if (electron) {
|
||||
const { ipcRenderer } = electron;
|
||||
window['dbgate_tabExports'][tabid] = {
|
||||
save: handleSaveRef.current,
|
||||
saveAs: saveFileModalState.open,
|
||||
};
|
||||
ipcRenderer.send('update-menu');
|
||||
|
||||
return () => {
|
||||
delete window['dbgate_tabExports'][tabid];
|
||||
ipcRenderer.send('update-menu');
|
||||
};
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SaveFileModal
|
||||
data={data}
|
||||
folder={folder}
|
||||
format={format}
|
||||
modalState={saveFileModalState}
|
||||
name={savedFile || 'newFile'}
|
||||
filePath={savedFilePath}
|
||||
fileExtension={fileExtension}
|
||||
onSave={onSave}
|
||||
/>
|
||||
|
||||
{canSave && (
|
||||
<ToolbarPortal tabVisible={tabVisible} toolbarPortalRef={toolbarPortalRef}>
|
||||
<SaveFileToolbarButton
|
||||
saveAs={saveFileModalState.open}
|
||||
save={savedFile || savedFilePath ? handleSave : null}
|
||||
tabid={tabid}
|
||||
/>
|
||||
</ToolbarPortal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
import React from 'react';
|
||||
import ModalBase from './ModalBase';
|
||||
import FormStyledButton from '../widgets/FormStyledButton';
|
||||
import styled from 'styled-components';
|
||||
import { FormSubmit, FormSelectFieldRaw, FormRadioGroupItem, FormTextFieldRaw } from '../utility/forms';
|
||||
import ModalHeader from './ModalHeader';
|
||||
import ModalFooter from './ModalFooter';
|
||||
import ModalContent from './ModalContent';
|
||||
import { TextField } from '../utility/inputs';
|
||||
import { FormProvider } from '../utility/FormProvider';
|
||||
import { FormRow } from '../utility/formStyle';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
const OptionsWrapper = styled.div`
|
||||
margin-left: 10px;
|
||||
`;
|
||||
|
||||
function RadioGroupItem({ text, value, defaultChecked = undefined, setMode }) {
|
||||
return (
|
||||
<div>
|
||||
<input
|
||||
type="radio"
|
||||
name="multiple_values_option"
|
||||
id={`multiple_values_option_${value}`}
|
||||
defaultChecked={defaultChecked}
|
||||
onClick={() => setMode(value)}
|
||||
/>
|
||||
<label htmlFor={`multiple_values_option_${value}`}>{text}</label>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Select({ filterType, name }) {
|
||||
return (
|
||||
<FormSelectFieldRaw name={name}>
|
||||
{filterType == 'number' && (
|
||||
<>
|
||||
<option value="=">eqals</option>
|
||||
<option value="<>">does not equal</option>
|
||||
<option value="<">is smaller</option>
|
||||
<option value=">">is greater</option>
|
||||
<option value="<=">is smaller or equal</option>
|
||||
<option value=">=">is greater or equal</option>
|
||||
</>
|
||||
)}
|
||||
{filterType == 'string' && (
|
||||
<>
|
||||
<option value="+">contains</option>
|
||||
<option value="~">does not contain</option>
|
||||
<option value="^">begins with</option>
|
||||
<option value="!^">does not begin with</option>
|
||||
<option value="$">ends with</option>
|
||||
<option value="!$">does not end with</option>
|
||||
<option value="=">equals</option>
|
||||
<option value="<>">does not equal</option>
|
||||
<option value="<">is smaller</option>
|
||||
<option value=">">is greater</option>
|
||||
<option value="<=">is smaller or equal</option>
|
||||
<option value=">=">is greater or equal</option>
|
||||
</>
|
||||
)}
|
||||
{filterType == 'datetime' && (
|
||||
<>
|
||||
<option value="=">eqals</option>
|
||||
<option value="<>">does not equal</option>
|
||||
<option value="<">is before</option>
|
||||
<option value=">">is after</option>
|
||||
<option value="<=">is before or equal</option>
|
||||
<option value=">=">is after or equal</option>
|
||||
</>
|
||||
)}
|
||||
</FormSelectFieldRaw>
|
||||
);
|
||||
}
|
||||
|
||||
export default function SetFilterModal({ modalState, onFilter, filterType, condition1 }) {
|
||||
const editorRef = React.useRef(null);
|
||||
// const [condition1, setValue1] = React.useState(condition);
|
||||
// const [value2, setValue2] = React.useState('equals');
|
||||
// const [mode, setMode] = React.useState('and');
|
||||
React.useEffect(() => {
|
||||
setTimeout(() => {
|
||||
if (editorRef.current) editorRef.current.focus();
|
||||
}, 1);
|
||||
}, []);
|
||||
|
||||
const createTerm = (condition, value) => {
|
||||
if (!value) return null;
|
||||
if (filterType == 'string') return `${condition}"${value}"`;
|
||||
return `${condition}${value}`;
|
||||
};
|
||||
|
||||
const handleOk = values => {
|
||||
const { value1, condition1, value2, condition2, joinOperator } = values;
|
||||
const term1 = createTerm(condition1, value1);
|
||||
const term2 = createTerm(condition2, value2);
|
||||
if (term1 && term2) onFilter(`${term1}${joinOperator}${term2}`);
|
||||
else if (term1) onFilter(term1);
|
||||
else if (term2) onFilter(term2);
|
||||
modalState.close();
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalBase modalState={modalState}>
|
||||
<FormProvider initialValues={{ condition1, condition2: '=', joinOperator: ' ' }}>
|
||||
<ModalHeader modalState={modalState}>Set filter</ModalHeader>
|
||||
<ModalContent>
|
||||
<FormRow>Show rows where</FormRow>
|
||||
<FormRow>
|
||||
<Select filterType={filterType} name="condition1" />
|
||||
<FormTextFieldRaw name="value1" editorRef={editorRef} />
|
||||
</FormRow>
|
||||
<FormRow>
|
||||
<FormRadioGroupItem name="joinOperator" value=" " text="And" />
|
||||
<FormRadioGroupItem name="joinOperator" value="," text="Or" />
|
||||
</FormRow>
|
||||
<FormRow>
|
||||
<Select filterType={filterType} name="condition2" />
|
||||
<FormTextFieldRaw name="value2" />
|
||||
</FormRow>
|
||||
</ModalContent>
|
||||
<ModalFooter>
|
||||
<FormSubmit value="OK" onClick={handleOk} />
|
||||
<FormStyledButton type="button" value="Close" onClick={modalState.close} />
|
||||
</ModalFooter>
|
||||
</FormProvider>
|
||||
</ModalBase>
|
||||
);
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ContextMenu } from './DropDownMenu';
|
||||
import uuidv1 from 'uuid/v1';
|
||||
|
||||
const Context = React.createContext(null);
|
||||
|
||||
export function MenuLayerProvider({ children }) {
|
||||
const [menu, setMenu] = React.useState(null);
|
||||
return <Context.Provider value={[menu, setMenu]}>{children}</Context.Provider>;
|
||||
}
|
||||
|
||||
export function MenuLayer() {
|
||||
const [menu] = React.useContext(Context);
|
||||
return (
|
||||
<div>
|
||||
{menu != null && (
|
||||
<ContextMenu key={menu.menuid} left={menu.left} top={menu.top}>
|
||||
{menu.menu}
|
||||
</ContextMenu>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function useHideMenu() {
|
||||
const [, setMenu] = React.useContext(Context);
|
||||
return () => setMenu(null);
|
||||
}
|
||||
|
||||
export function useShowMenu() {
|
||||
const [, setMenu] = React.useContext(Context);
|
||||
const showMenu = (left, top, menu) => {
|
||||
const menuid = uuidv1();
|
||||
setMenu({ menuid, left, top, menu });
|
||||
};
|
||||
return showMenu;
|
||||
// const container = document.createElement('div');
|
||||
// document.body.appendChild(container);
|
||||
// ReactDOM.render(<ShowModalComponent renderModal={renderModal} container={container} />, container);
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import useModalState from './useModalState';
|
||||
|
||||
function ShowModalComponent({ renderModal, onClose }) {
|
||||
const modalState = useModalState(true);
|
||||
if (!modalState.isOpen) {
|
||||
onClose();
|
||||
}
|
||||
return renderModal(modalState);
|
||||
}
|
||||
|
||||
const Context = React.createContext(null);
|
||||
|
||||
export function ModalLayerProvider({ children }) {
|
||||
const [modals, setModals] = React.useState([]);
|
||||
return <Context.Provider value={[modals, setModals]}>{children}</Context.Provider>;
|
||||
}
|
||||
|
||||
export function ModalLayer() {
|
||||
const [modals, setModals] = React.useContext(Context);
|
||||
return (
|
||||
<div>
|
||||
{modals.map((modal, index) => (
|
||||
<ShowModalComponent key={index} renderModal={modal} onClose={() => setModals(x => x.filter(y => y != modal))} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function useShowModal() {
|
||||
const [modals, setModals] = React.useContext(Context);
|
||||
const showModal = renderModal => {
|
||||
setModals([...modals, renderModal]);
|
||||
};
|
||||
return showModal;
|
||||
// const container = document.createElement('div');
|
||||
// document.body.appendChild(container);
|
||||
// ReactDOM.render(<ShowModalComponent renderModal={renderModal} container={container} />, container);
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
export default function useModalState(isOpenDefault = false) {
|
||||
const [isOpen, setOpen] = React.useState(isOpenDefault);
|
||||
const close = () => setOpen(false);
|
||||
const open = () => setOpen(true);
|
||||
return { isOpen, open, close };
|
||||
}
|
||||
Reference in New Issue
Block a user