remove web

This commit is contained in:
Jan Prochazka
2021-02-20 19:15:11 +01:00
parent dd7db5904c
commit daf9e9d18b
240 changed files with 0 additions and 22572 deletions

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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 &amp; 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>
);
}

View File

@@ -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>
);
}

View File

@@ -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`;
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>;
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
)}
</>
);
}

View File

@@ -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>
);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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 };
}