mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-19 11:56:00 +00:00
remove web
This commit is contained in:
@@ -1,82 +0,0 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { AppObjectList } from '../appobj/AppObjectList';
|
||||
import { useCurrentArchive, useSetCurrentArchive } from '../utility/globalState';
|
||||
import { SearchBoxWrapper, WidgetsInnerContainer } from './WidgetStyles';
|
||||
import WidgetColumnBar, { WidgetColumnBarItem } from './WidgetColumnBar';
|
||||
import { useArchiveFiles, useArchiveFolders } from '../utility/metadataLoaders';
|
||||
import ArchiveFolderAppObject from '../appobj/ArchiveFolderAppObject';
|
||||
import ArchiveFileAppObject from '../appobj/ArchiveFileAppObject';
|
||||
import SearchInput from './SearchInput';
|
||||
import InlineButton from './InlineButton';
|
||||
import axios from '../utility/axios';
|
||||
|
||||
function ArchiveFolderList() {
|
||||
const folders = useArchiveFolders();
|
||||
const [filter, setFilter] = React.useState('');
|
||||
|
||||
const setArchive = useSetCurrentArchive();
|
||||
|
||||
const handleRefreshFolders = () => {
|
||||
axios.post('archive/refresh-folders', {});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<SearchBoxWrapper>
|
||||
<SearchInput placeholder="Search archive folders" filter={filter} setFilter={setFilter} />
|
||||
<InlineButton onClick={handleRefreshFolders}>Refresh</InlineButton>
|
||||
</SearchBoxWrapper>
|
||||
<WidgetsInnerContainer>
|
||||
<AppObjectList
|
||||
list={_.sortBy(folders, 'name')}
|
||||
AppObjectComponent={ArchiveFolderAppObject}
|
||||
onObjectClick={archive => setArchive(archive.name)}
|
||||
filter={filter}
|
||||
/>
|
||||
</WidgetsInnerContainer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function ArchiveFilesList() {
|
||||
const folder = useCurrentArchive();
|
||||
const files = useArchiveFiles({ folder });
|
||||
const [filter, setFilter] = React.useState('');
|
||||
const handleRefreshFiles = () => {
|
||||
axios.post('archive/refresh-files', { folder });
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<SearchBoxWrapper>
|
||||
<SearchInput placeholder="Search archive files" filter={filter} setFilter={setFilter} />
|
||||
<InlineButton onClick={handleRefreshFiles}>Refresh</InlineButton>
|
||||
</SearchBoxWrapper>
|
||||
<WidgetsInnerContainer>
|
||||
<AppObjectList
|
||||
list={(files || []).map(file => ({
|
||||
fileName: file.name,
|
||||
folderName: folder,
|
||||
}))}
|
||||
filter={filter}
|
||||
AppObjectComponent={ArchiveFileAppObject}
|
||||
/>
|
||||
</WidgetsInnerContainer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function ArchiveWidget() {
|
||||
return (
|
||||
<WidgetColumnBar>
|
||||
<WidgetColumnBarItem title="Archive folders" name="folders" height="50%">
|
||||
<ArchiveFolderList />
|
||||
</WidgetColumnBarItem>
|
||||
<WidgetColumnBarItem title="Archive files" name="files">
|
||||
<ArchiveFilesList />
|
||||
</WidgetColumnBarItem>
|
||||
</WidgetColumnBar>
|
||||
);
|
||||
}
|
||||
@@ -1,196 +0,0 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { AppObjectList } from '../appobj/AppObjectList';
|
||||
import ConnectionAppObject from '../appobj/ConnectionAppObject';
|
||||
import DatabaseAppObject from '../appobj/DatabaseAppObject';
|
||||
import { useSetCurrentDatabase, useCurrentDatabase, useOpenedConnections } from '../utility/globalState';
|
||||
import InlineButton from './InlineButton';
|
||||
import DatabaseObjectAppObject from '../appobj/DatabaseObjectAppObject';
|
||||
import {
|
||||
// useSqlObjectList,
|
||||
useDatabaseList,
|
||||
useConnectionList,
|
||||
useServerStatus,
|
||||
useDatabaseStatus,
|
||||
useDatabaseInfo,
|
||||
useConfig,
|
||||
} from '../utility/metadataLoaders';
|
||||
import { SearchBoxWrapper, WidgetsInnerContainer } from './WidgetStyles';
|
||||
import axios from '../utility/axios';
|
||||
import LoadingInfo from './LoadingInfo';
|
||||
import SearchInput from './SearchInput';
|
||||
import ErrorInfo from './ErrorInfo';
|
||||
import WidgetColumnBar, { WidgetColumnBarItem } from './WidgetColumnBar';
|
||||
import ToolbarButton from './ToolbarButton';
|
||||
import useShowModal from '../modals/showModal';
|
||||
import ConnectionModal from '../modals/ConnectionModal';
|
||||
import SubColumnParamList from '../appobj/SubColumnParamList';
|
||||
import { ChevronExpandIcon } from '../icons';
|
||||
|
||||
function SubDatabaseList({ data }) {
|
||||
const setDb = useSetCurrentDatabase();
|
||||
const handleDatabaseClick = database => {
|
||||
setDb({
|
||||
...database,
|
||||
connection: data,
|
||||
});
|
||||
};
|
||||
const { _id } = data;
|
||||
const databases = useDatabaseList({ conid: _id });
|
||||
return (
|
||||
<AppObjectList
|
||||
list={(databases || []).map(db => ({ ...db, connection: data }))}
|
||||
AppObjectComponent={DatabaseAppObject}
|
||||
// makeAppObj={databaseAppObject({ boldCurrentDatabase: true })}
|
||||
onObjectClick={handleDatabaseClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function ConnectionList() {
|
||||
const connections = useConnectionList();
|
||||
const serverStatus = useServerStatus();
|
||||
const openedConnections = useOpenedConnections();
|
||||
const connectionsWithStatus =
|
||||
connections && serverStatus ? connections.map(conn => ({ ...conn, status: serverStatus[conn._id] })) : connections;
|
||||
const showModal = useShowModal();
|
||||
|
||||
const handleRefreshConnections = () => {
|
||||
for (const conid of openedConnections) {
|
||||
axios.post('server-connections/refresh', { conid });
|
||||
}
|
||||
};
|
||||
|
||||
const showNewConnection = () => {
|
||||
showModal(modalState => <ConnectionModal modalState={modalState} />);
|
||||
};
|
||||
|
||||
const [filter, setFilter] = React.useState('');
|
||||
return (
|
||||
<>
|
||||
<SearchBoxWrapper>
|
||||
<SearchInput placeholder="Search connection" filter={filter} setFilter={setFilter} />
|
||||
<InlineButton onClick={handleRefreshConnections}>Refresh</InlineButton>
|
||||
</SearchBoxWrapper>
|
||||
|
||||
<WidgetsInnerContainer>
|
||||
<AppObjectList
|
||||
list={_.sortBy(connectionsWithStatus, ({ displayName, server }) =>
|
||||
(displayName || server || '').toUpperCase()
|
||||
)}
|
||||
AppObjectComponent={ConnectionAppObject}
|
||||
// makeAppObj={connectionAppObject({ boldCurrentDatabase: true })}
|
||||
SubItems={SubDatabaseList}
|
||||
filter={filter}
|
||||
isExpandable={data => openedConnections.includes(data._id)}
|
||||
expandOnClick
|
||||
/>
|
||||
{connections && connections.length == 0 && (
|
||||
<ToolbarButton icon="icon new-connection" onClick={showNewConnection}>
|
||||
Add new connection
|
||||
</ToolbarButton>
|
||||
)}
|
||||
</WidgetsInnerContainer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function SqlObjectList({ conid, database }) {
|
||||
const objects = useDatabaseInfo({ conid, database });
|
||||
const status = useDatabaseStatus({ conid, database });
|
||||
|
||||
const handleRefreshDatabase = () => {
|
||||
axios.post('database-connections/refresh', { conid, database });
|
||||
};
|
||||
|
||||
const [filter, setFilter] = React.useState('');
|
||||
const objectList = _.flatten(
|
||||
['tables', 'views', 'procedures', 'functions'].map(objectTypeField =>
|
||||
_.sortBy(
|
||||
((objects || {})[objectTypeField] || []).map(obj => ({ ...obj, objectTypeField })),
|
||||
['schemaName', 'pureName']
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
const inputRef = React.useRef(null);
|
||||
|
||||
if (status && status.name == 'error') {
|
||||
return (
|
||||
<WidgetsInnerContainer>
|
||||
<ErrorInfo message={status.message} icon="img error" />
|
||||
<InlineButton onClick={handleRefreshDatabase}>Refresh</InlineButton>
|
||||
</WidgetsInnerContainer>
|
||||
);
|
||||
}
|
||||
|
||||
if (objectList.length == 0 && status && status.name != 'pending' && objects) {
|
||||
return (
|
||||
<WidgetsInnerContainer>
|
||||
<ErrorInfo
|
||||
message={`Database ${database} is empty or structure is not loaded, press Refresh button to reload structure`}
|
||||
icon="img alert"
|
||||
/>
|
||||
<InlineButton onClick={handleRefreshDatabase}>Refresh</InlineButton>
|
||||
</WidgetsInnerContainer>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<SearchBoxWrapper>
|
||||
<SearchInput inputRef={inputRef} placeholder="Search tables or objects" filter={filter} setFilter={setFilter} />
|
||||
<InlineButton onClick={handleRefreshDatabase}>Refresh</InlineButton>
|
||||
</SearchBoxWrapper>
|
||||
<WidgetsInnerContainer>
|
||||
{(status && status.name == 'pending') || !objects ? (
|
||||
<LoadingInfo message="Loading database structure" />
|
||||
) : (
|
||||
<AppObjectList
|
||||
list={objectList.map(x => ({ ...x, conid, database }))}
|
||||
AppObjectComponent={DatabaseObjectAppObject}
|
||||
groupFunc={data => _.startCase(data.objectTypeField)}
|
||||
filter={filter}
|
||||
SubItems={SubColumnParamList}
|
||||
isExpandable={data => data.objectTypeField == 'tables' || data.objectTypeField == 'views'}
|
||||
ExpandIconComponent={ChevronExpandIcon}
|
||||
/>
|
||||
)}
|
||||
</WidgetsInnerContainer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function SqlObjectListWrapper() {
|
||||
const db = useCurrentDatabase();
|
||||
|
||||
if (!db) {
|
||||
return (
|
||||
<WidgetsInnerContainer>
|
||||
<ErrorInfo message="Database not selected" icon="img alert" />
|
||||
</WidgetsInnerContainer>
|
||||
);
|
||||
}
|
||||
const { name, connection } = db;
|
||||
|
||||
return <SqlObjectList conid={connection._id} database={name} />;
|
||||
// return <div>tables of {db && db.name}</div>
|
||||
// return <div>tables of {JSON.stringify(db)}</div>
|
||||
}
|
||||
|
||||
export default function DatabaseWidget() {
|
||||
const config = useConfig();
|
||||
return (
|
||||
<WidgetColumnBar>
|
||||
{!config.singleDatabase && (
|
||||
<WidgetColumnBarItem title="Connections" name="connections" height="50%">
|
||||
<ConnectionList />
|
||||
</WidgetColumnBarItem>
|
||||
)}
|
||||
<WidgetColumnBarItem title="Tables, views, functions" name="dbObjects">
|
||||
<SqlObjectListWrapper />
|
||||
</WidgetColumnBarItem>
|
||||
</WidgetColumnBar>
|
||||
);
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
import React from 'react';
|
||||
import { FontIcon } from '../icons';
|
||||
import { useShowMenu } from '../modals/showMenu';
|
||||
import InlineButton from './InlineButton';
|
||||
|
||||
export default function DropDownButton({ children }) {
|
||||
const buttonRef = React.useRef(null);
|
||||
const showMenu = useShowMenu();
|
||||
|
||||
const handleShowMenu = () => {
|
||||
const rect = buttonRef.current.getBoundingClientRect();
|
||||
showMenu(rect.left, rect.bottom, children);
|
||||
};
|
||||
|
||||
return (
|
||||
<InlineButton buttonRef={buttonRef} onClick={handleShowMenu} square>
|
||||
<FontIcon icon="icon chevron-down" />
|
||||
</InlineButton>
|
||||
);
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import styled from 'styled-components';
|
||||
import { FontIcon } from '../icons';
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 10px;
|
||||
`;
|
||||
|
||||
const Icon = styled.div`
|
||||
font-size: 20pt;
|
||||
margin: 10px;
|
||||
`;
|
||||
|
||||
const ContainerSmall = styled.div`
|
||||
display: flex;
|
||||
margin-right: 10px;
|
||||
`;
|
||||
|
||||
export default function ErrorInfo({ message, icon = 'img error', isSmall = false }) {
|
||||
if (isSmall) {
|
||||
return (
|
||||
<ContainerSmall>
|
||||
<FontIcon icon={icon} />
|
||||
{message}
|
||||
</ContainerSmall>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Container>
|
||||
<Icon>
|
||||
<FontIcon icon={icon} />
|
||||
</Icon>
|
||||
{message}
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { AppObjectList } from '../appobj/AppObjectList';
|
||||
import { useOpenedTabs } from '../utility/globalState';
|
||||
import ClosedTabAppObject from '../appobj/ClosedTabAppObject';
|
||||
import { WidgetsInnerContainer } from './WidgetStyles';
|
||||
import WidgetColumnBar, { WidgetColumnBarItem } from './WidgetColumnBar';
|
||||
import { useFavorites } from '../utility/metadataLoaders';
|
||||
import useHasPermission from '../utility/useHasPermission';
|
||||
import { FavoriteFileAppObject } from '../appobj/FavoriteFileAppObject';
|
||||
|
||||
function ClosedTabsList() {
|
||||
const tabs = useOpenedTabs();
|
||||
|
||||
return (
|
||||
<>
|
||||
<WidgetsInnerContainer>
|
||||
<AppObjectList
|
||||
list={_.sortBy(
|
||||
tabs.filter(x => x.closedTime),
|
||||
x => -x.closedTime
|
||||
)}
|
||||
AppObjectComponent={ClosedTabAppObject}
|
||||
/>
|
||||
</WidgetsInnerContainer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function FavoritesList() {
|
||||
const favorites = useFavorites();
|
||||
|
||||
return (
|
||||
<>
|
||||
<WidgetsInnerContainer>
|
||||
<AppObjectList list={favorites} AppObjectComponent={FavoriteFileAppObject} />
|
||||
</WidgetsInnerContainer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function FavoritesWidget() {
|
||||
const hasPermission = useHasPermission();
|
||||
return (
|
||||
<WidgetColumnBar>
|
||||
{hasPermission('files/favorites/read') && (
|
||||
<WidgetColumnBarItem title="Favorites" name="favorites" height="20%">
|
||||
<FavoritesList />
|
||||
</WidgetColumnBarItem>
|
||||
)}
|
||||
<WidgetColumnBarItem title="Recently closed tabs" name="closedTabs">
|
||||
<ClosedTabsList />
|
||||
</WidgetColumnBarItem>
|
||||
</WidgetColumnBar>
|
||||
);
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { AppObjectList } from '../appobj/AppObjectList';
|
||||
import { WidgetsInnerContainer } from './WidgetStyles';
|
||||
import { SavedFileAppObject } from '../appobj/SavedFileAppObject';
|
||||
import WidgetColumnBar, { WidgetColumnBarItem } from './WidgetColumnBar';
|
||||
import { useFiles } from '../utility/metadataLoaders';
|
||||
|
||||
function SavedFilesList() {
|
||||
const sqlFiles = useFiles({ folder: 'sql' });
|
||||
const shellFiles = useFiles({ folder: 'shell' });
|
||||
const markdownFiles = useFiles({ folder: 'markdown' });
|
||||
const chartFiles = useFiles({ folder: 'charts' });
|
||||
const queryFiles = useFiles({ folder: 'query' });
|
||||
|
||||
const files = [
|
||||
...(sqlFiles || []),
|
||||
...(shellFiles || []),
|
||||
...(markdownFiles || []),
|
||||
...(chartFiles || []),
|
||||
...(queryFiles || []),
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<WidgetsInnerContainer>
|
||||
<AppObjectList
|
||||
list={files}
|
||||
AppObjectComponent={SavedFileAppObject}
|
||||
groupFunc={data => _.startCase(data.folder)}
|
||||
/>
|
||||
</WidgetsInnerContainer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function FilesWidget() {
|
||||
return (
|
||||
<WidgetColumnBar>
|
||||
<WidgetColumnBarItem title="Saved files" name="files">
|
||||
<SavedFilesList />
|
||||
</WidgetColumnBarItem>
|
||||
</WidgetColumnBar>
|
||||
);
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import dimensions from '../theme/dimensions';
|
||||
import useTheme from '../theme/useTheme';
|
||||
|
||||
// const StyledInputBase=styled.input``;
|
||||
// const StyledLabelBase=styled.label``;
|
||||
|
||||
const makeStyle = base => styled(base)`
|
||||
// height: ${dimensions.toolBar.height - 5}px;
|
||||
border: 1px solid ${props => props.theme.button_background2};
|
||||
padding: 5px;
|
||||
margin: 2px;
|
||||
width: 100px;
|
||||
background-color: ${props => props.theme.button_background};
|
||||
color: ${props => props.theme.button_font1};
|
||||
border-radius: 2px;
|
||||
|
||||
${props =>
|
||||
!props.disabled &&
|
||||
`
|
||||
&:hover {
|
||||
background-color: ${props.theme.button_background2};
|
||||
}
|
||||
|
||||
&:active:hover {
|
||||
background-color: ${props.theme.button_background3};
|
||||
}
|
||||
`}
|
||||
|
||||
${props =>
|
||||
props.disabled &&
|
||||
`
|
||||
background-color: ${props.theme.button_background3};
|
||||
color: ${props.theme.button_font3} ;
|
||||
`}
|
||||
`;
|
||||
|
||||
const ButtonInput = makeStyle(styled.input``);
|
||||
const FormStyledLabelBase = makeStyle(styled.label``);
|
||||
export const FormStyledLabel = styled(FormStyledLabelBase)``;
|
||||
|
||||
export default function FormStyledButton({
|
||||
onClick = undefined,
|
||||
type = 'button',
|
||||
value,
|
||||
disabled = undefined,
|
||||
...other
|
||||
}) {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<ButtonInput
|
||||
type={type}
|
||||
theme={theme}
|
||||
onClick={
|
||||
onClick
|
||||
? () => {
|
||||
if (!disabled && onClick) onClick();
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
disabled={disabled}
|
||||
value={value}
|
||||
{...other}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export const StyledThemedLink = styled.a`
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
color: ${props => props.theme.main_background_blue[7]};
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
`;
|
||||
@@ -1,74 +0,0 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import useTheme from '../theme/useTheme';
|
||||
|
||||
const ButtonDiv = styled.div`
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
${props => props.theme.inlinebtn_background} 5%,
|
||||
${props => props.theme.inlinebtn_background3} 100%
|
||||
);
|
||||
background-color: ${props => props.theme.inlinebtn_background};
|
||||
border: 1px solid ${props => props.theme.inlinebtn_background3};
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
vertical-align: middle;
|
||||
color: ${props => (props.disabled ? props.theme.inlinebtn_font3 : props.theme.inlinebtn_font1)};
|
||||
font-size: 12px;
|
||||
padding: 3px;
|
||||
margin: 0;
|
||||
text-decoration: none;
|
||||
${props =>
|
||||
!props.disabled &&
|
||||
`
|
||||
&:hover {
|
||||
border: 1px solid ${props.theme.inlinebtn_font2};
|
||||
}
|
||||
&:active:hover {
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
${props.theme.inlinebtn_background3} 5%,
|
||||
${props.theme.inlinebtn_background} 100%
|
||||
);
|
||||
background-color: ${props.theme.inlinebtn_background3};
|
||||
}
|
||||
`}
|
||||
display: flex;
|
||||
|
||||
${props =>
|
||||
props.square &&
|
||||
`
|
||||
width: 18px;
|
||||
`}
|
||||
`;
|
||||
|
||||
const InnerDiv = styled.div`
|
||||
margin: auto;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
export default function InlineButton({
|
||||
children,
|
||||
onClick = undefined,
|
||||
buttonRef = undefined,
|
||||
disabled = undefined,
|
||||
square = false,
|
||||
}) {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<ButtonDiv
|
||||
className="buttonLike"
|
||||
ref={buttonRef}
|
||||
onClick={() => {
|
||||
if (!disabled && onClick) onClick();
|
||||
}}
|
||||
disabled={disabled}
|
||||
square={square}
|
||||
theme={theme}
|
||||
>
|
||||
<InnerDiv>{children}</InnerDiv>
|
||||
</ButtonDiv>
|
||||
);
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { FontIcon } from '../icons';
|
||||
import dimensions from '../theme/dimensions';
|
||||
import useTheme from '../theme/useTheme';
|
||||
import { useForm } from '../utility/FormProvider';
|
||||
|
||||
const ButtonDiv = styled.div`
|
||||
padding: 5px 15px;
|
||||
color: ${props => props.theme.main_font1};
|
||||
border: 1px solid ${props => props.theme.border};
|
||||
width: 120px;
|
||||
height: 60px;
|
||||
background-color: ${props => props.theme.toolbar_background};
|
||||
|
||||
${props =>
|
||||
!props.disabled &&
|
||||
`
|
||||
&:hover {
|
||||
background-color: ${props.theme.toolbar_background2} ;
|
||||
}
|
||||
|
||||
&:active:hover {
|
||||
background-color: ${props.theme.toolbar_background3};
|
||||
}
|
||||
`}
|
||||
|
||||
${props =>
|
||||
props.disabled &&
|
||||
`
|
||||
color: ${props.theme.main_font3};
|
||||
`}
|
||||
`;
|
||||
|
||||
const IconDiv = styled.div`
|
||||
font-size: 30px;
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
const ButtonDivInner = styled.div`
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
export default function LargeButton({ children, onClick, icon = undefined, disabled = undefined }) {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<ButtonDiv
|
||||
theme={theme}
|
||||
onClick={() => {
|
||||
if (!disabled && onClick) onClick();
|
||||
}}
|
||||
disabled={disabled}
|
||||
>
|
||||
<IconDiv>
|
||||
<FontIcon icon={icon} />
|
||||
</IconDiv>
|
||||
<ButtonDivInner>{children}</ButtonDivInner>
|
||||
</ButtonDiv>
|
||||
);
|
||||
}
|
||||
|
||||
export function LargeFormButton({ children, onClick, icon = undefined, disabled = undefined }) {
|
||||
const { values } = useForm();
|
||||
return (
|
||||
<LargeButton icon={icon} disabled={disabled} onClick={() => onClick(values)}>
|
||||
{children}
|
||||
</LargeButton>
|
||||
);
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import styled from 'styled-components';
|
||||
import { FontIcon } from '../icons';
|
||||
import useTheme from '../theme/useTheme';
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 10px;
|
||||
`;
|
||||
|
||||
const Spinner = styled.div`
|
||||
font-size: 20pt;
|
||||
margin: 10px;
|
||||
`;
|
||||
|
||||
const LoadingInfoWrapper = styled.div`
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
`;
|
||||
const LoadingInfoBox = styled.div`
|
||||
background-color: ${props => props.theme.main_background2};
|
||||
padding: 10px;
|
||||
border: 1px solid gray;
|
||||
`;
|
||||
|
||||
export default function LoadingInfo({ message, wrapper = false }) {
|
||||
const theme = useTheme();
|
||||
const core = (
|
||||
<Container>
|
||||
<Spinner>
|
||||
<FontIcon icon="icon loading" />
|
||||
</Spinner>
|
||||
{message}
|
||||
</Container>
|
||||
);
|
||||
if (wrapper) {
|
||||
return (
|
||||
<LoadingInfoWrapper>
|
||||
<LoadingInfoBox theme={theme}>{core}</LoadingInfoBox>
|
||||
</LoadingInfoWrapper>
|
||||
);
|
||||
} else {
|
||||
return core;
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
import { SearchBoxWrapper, WidgetsInnerContainer } from './WidgetStyles';
|
||||
import WidgetColumnBar, { WidgetColumnBarItem } from './WidgetColumnBar';
|
||||
import { useInstalledPlugins } from '../utility/metadataLoaders';
|
||||
import SearchInput from './SearchInput';
|
||||
import useFetch from '../utility/useFetch';
|
||||
import PluginsList from '../plugins/PluginsList';
|
||||
|
||||
function InstalledPluginsList() {
|
||||
const plugins = useInstalledPlugins();
|
||||
|
||||
return (
|
||||
<WidgetsInnerContainer>
|
||||
<PluginsList plugins={plugins} />
|
||||
</WidgetsInnerContainer>
|
||||
);
|
||||
}
|
||||
|
||||
function AvailablePluginsList() {
|
||||
const [filter, setFilter] = React.useState('');
|
||||
const [search, setSearch] = React.useState('');
|
||||
|
||||
const plugins = useFetch({
|
||||
url: 'plugins/search',
|
||||
params: {
|
||||
filter: search,
|
||||
},
|
||||
defaultValue: [],
|
||||
});
|
||||
|
||||
const setDebouncedFilter = React.useRef(
|
||||
// @ts-ignore
|
||||
_.debounce(value => setSearch(value), 500)
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
// @ts-ignore
|
||||
setDebouncedFilter.current(filter);
|
||||
}, [filter]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SearchBoxWrapper>
|
||||
<SearchInput placeholder="Search extensions on web" filter={filter} setFilter={setFilter} />
|
||||
</SearchBoxWrapper>
|
||||
<WidgetsInnerContainer>
|
||||
<PluginsList plugins={plugins} />
|
||||
</WidgetsInnerContainer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function PluginsWidget() {
|
||||
return (
|
||||
<WidgetColumnBar>
|
||||
<WidgetColumnBarItem title="Installed extensions" name="installed" height="50%">
|
||||
<InstalledPluginsList />
|
||||
</WidgetColumnBarItem>
|
||||
<WidgetColumnBarItem title="Available extensions" name="all">
|
||||
<AvailablePluginsList />
|
||||
</WidgetColumnBarItem>
|
||||
</WidgetColumnBar>
|
||||
);
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import keycodes from '../utility/keycodes';
|
||||
|
||||
const StyledInput = styled.input`
|
||||
flex: 1;
|
||||
min-width: 10px;
|
||||
width: 10px;
|
||||
`;
|
||||
|
||||
export default function SearchInput({ placeholder, filter, setFilter, inputRef = undefined }) {
|
||||
const handleKeyDown = ev => {
|
||||
if (ev.keyCode == keycodes.escape) {
|
||||
setFilter('');
|
||||
}
|
||||
};
|
||||
return (
|
||||
<StyledInput
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
placeholder={placeholder}
|
||||
value={filter}
|
||||
onChange={e => setFilter(e.target.value)}
|
||||
onFocus={e => e.target.select()}
|
||||
onKeyDown={handleKeyDown}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,169 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import useDimensions from '../utility/useDimensions';
|
||||
import dimensions from '../theme/dimensions';
|
||||
import useTheme from '../theme/useTheme';
|
||||
|
||||
const SplitterMainBase = styled.div`
|
||||
flex: 1;
|
||||
display: flex;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
`;
|
||||
|
||||
// @ts-ignore
|
||||
const VerticalMainContainer = styled(SplitterMainBase)`
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
const HorizontalMainContainer = styled(SplitterMainBase)`
|
||||
flex: 1;
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
export const VerticalSplitHandle = styled.div`
|
||||
background-color: ${props => props.theme.border};
|
||||
height: ${dimensions.splitter.thickness}px;
|
||||
cursor: row-resize;
|
||||
&:hover {
|
||||
background-color: ${props => props.theme.border_background2};
|
||||
}
|
||||
`;
|
||||
|
||||
export const HorizontalSplitHandle = styled.div`
|
||||
background-color: ${props => props.theme.border};
|
||||
width: ${dimensions.splitter.thickness}px;
|
||||
cursor: col-resize;
|
||||
&:hover {
|
||||
background-color: ${props => props.theme.border_background2};
|
||||
}
|
||||
`;
|
||||
|
||||
const ChildContainer1 = styled.div`
|
||||
// flex: 0 0 50%;
|
||||
// flex-basis: 100px;
|
||||
// flex-grow: 1;
|
||||
display: flex;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
const ChildContainer2 = styled.div`
|
||||
flex: 1;
|
||||
// flex: 0 0 50%;
|
||||
// flex-basis: 100px;
|
||||
// flex-grow: 1;
|
||||
display: flex;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
export function useSplitterDrag(axes, onResize) {
|
||||
const [resizeStart, setResizeStart] = React.useState(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (resizeStart != null) {
|
||||
const handleResizeMove = e => {
|
||||
e.preventDefault();
|
||||
let diff = e[axes] - resizeStart;
|
||||
setResizeStart(e[axes]);
|
||||
onResize(diff);
|
||||
};
|
||||
const handleResizeEnd = e => {
|
||||
e.preventDefault();
|
||||
setResizeStart(null);
|
||||
};
|
||||
|
||||
document.addEventListener('mousemove', handleResizeMove, true);
|
||||
document.addEventListener('mouseup', handleResizeEnd, true);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('mousemove', handleResizeMove, true);
|
||||
document.removeEventListener('mouseup', handleResizeEnd, true);
|
||||
};
|
||||
}
|
||||
}, [resizeStart]);
|
||||
|
||||
const handleResizeDown = e => {
|
||||
setResizeStart(e[axes]);
|
||||
};
|
||||
|
||||
return handleResizeDown;
|
||||
}
|
||||
|
||||
function SplitterCore({
|
||||
children,
|
||||
eventField,
|
||||
dimensionField,
|
||||
styleField,
|
||||
Handle,
|
||||
Main,
|
||||
initialValue = undefined,
|
||||
size = undefined,
|
||||
setSize = undefined,
|
||||
}) {
|
||||
const theme = useTheme();
|
||||
const childrenArray = _.isArray(children) ? children : [children];
|
||||
if (childrenArray.length !== 1 && childrenArray.length != 2) {
|
||||
throw new Error('Splitter must have 1 or 2 children');
|
||||
}
|
||||
const [refNode, dimensions] = useDimensions();
|
||||
const [mySize, setMySize] = React.useState(0);
|
||||
const size1 = size === undefined ? mySize : size;
|
||||
const setSize1 = setSize === undefined ? setMySize : setSize;
|
||||
|
||||
React.useEffect(() => {
|
||||
if (_.isString(initialValue) && initialValue.endsWith('px')) setSize1(parseInt(initialValue.slice(0, -2)));
|
||||
else if (_.isString(initialValue) && initialValue.endsWith('%'))
|
||||
setSize1((dimensions[dimensionField] * parseFloat(initialValue.slice(0, -1))) / 100);
|
||||
else setSize1(dimensions[dimensionField] / 2);
|
||||
}, [dimensions]);
|
||||
|
||||
const handleResizeDown = useSplitterDrag(eventField, diff => setSize1(v => v + diff));
|
||||
|
||||
const isSplitter = !!childrenArray[1];
|
||||
|
||||
return (
|
||||
<Main ref={refNode}>
|
||||
<ChildContainer1 style={isSplitter ? { [styleField]: size1 } : { flex: '1' }}>{childrenArray[0]}</ChildContainer1>
|
||||
{isSplitter && <Handle onMouseDown={handleResizeDown} theme={theme} />}
|
||||
{isSplitter && <ChildContainer2>{childrenArray[1]}</ChildContainer2>}
|
||||
</Main>
|
||||
);
|
||||
}
|
||||
|
||||
export function VerticalSplitter({ children, ...other }) {
|
||||
return (
|
||||
<SplitterCore
|
||||
eventField="clientY"
|
||||
dimensionField="height"
|
||||
styleField="height"
|
||||
Handle={VerticalSplitHandle}
|
||||
Main={VerticalMainContainer}
|
||||
{...other}
|
||||
>
|
||||
{children}
|
||||
</SplitterCore>
|
||||
);
|
||||
}
|
||||
|
||||
export function HorizontalSplitter({ children, ...other }) {
|
||||
return (
|
||||
<SplitterCore
|
||||
eventField="clientX"
|
||||
dimensionField="width"
|
||||
styleField="width"
|
||||
Handle={HorizontalSplitHandle}
|
||||
Main={HorizontalMainContainer}
|
||||
{...other}
|
||||
>
|
||||
{children}
|
||||
</SplitterCore>
|
||||
);
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { FontIcon } from '../icons';
|
||||
import useTheme from '../theme/useTheme';
|
||||
|
||||
import { useCurrentDatabase } from '../utility/globalState';
|
||||
import { useDatabaseStatus } from '../utility/metadataLoaders';
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
color: ${props => props.theme.statusbar_font1};
|
||||
align-items: stretch;
|
||||
justify-content: space-between;
|
||||
`;
|
||||
|
||||
export const StatusBarItem = styled.div`
|
||||
padding: 2px 10px;
|
||||
`;
|
||||
|
||||
const ErrorWrapper = styled.span`
|
||||
color: ${props =>
|
||||
// @ts-ignore
|
||||
props.theme.statusbar_font_red[5]};
|
||||
`;
|
||||
|
||||
const InfoWrapper = styled.span`
|
||||
color: ${props =>
|
||||
// @ts-ignore
|
||||
props.theme.statusbar_font_green[5]};
|
||||
`;
|
||||
|
||||
const StatusbarContainer = styled.div`
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
export default function StatusBar({ statusbarPortalRef }) {
|
||||
const { name, connection } = useCurrentDatabase() || {};
|
||||
const status = useDatabaseStatus(connection ? { conid: connection._id, database: name } : {});
|
||||
const { displayName, server, user, engine } = connection || {};
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Container theme={theme}>
|
||||
<StatusbarContainer>
|
||||
{name && (
|
||||
<StatusBarItem>
|
||||
<FontIcon icon="icon database" /> {name}
|
||||
</StatusBarItem>
|
||||
)}
|
||||
{(displayName || server) && (
|
||||
<StatusBarItem>
|
||||
<FontIcon icon="icon server" /> {displayName || server}
|
||||
</StatusBarItem>
|
||||
)}
|
||||
|
||||
{user && (
|
||||
<StatusBarItem>
|
||||
<FontIcon icon="icon account" /> {user}
|
||||
</StatusBarItem>
|
||||
)}
|
||||
|
||||
{connection && status && (
|
||||
<StatusBarItem>
|
||||
{status.name == 'pending' && (
|
||||
<>
|
||||
<FontIcon icon="icon loading" /> Loading
|
||||
</>
|
||||
)}
|
||||
{status.name == 'ok' && (
|
||||
<>
|
||||
<InfoWrapper theme={theme}>
|
||||
<FontIcon icon="icon ok" />
|
||||
</InfoWrapper>{' '}
|
||||
Connected
|
||||
</>
|
||||
)}
|
||||
{status.name == 'error' && (
|
||||
<>
|
||||
<ErrorWrapper theme={theme}>
|
||||
<FontIcon icon="icon error" />
|
||||
</ErrorWrapper>{' '}
|
||||
Error
|
||||
</>
|
||||
)}
|
||||
</StatusBarItem>
|
||||
)}
|
||||
{!connection && (
|
||||
<StatusBarItem>
|
||||
<>
|
||||
<FontIcon icon="icon disconnected" /> Not connected
|
||||
</>
|
||||
</StatusBarItem>
|
||||
)}
|
||||
</StatusbarContainer>
|
||||
<StatusbarContainer ref={statusbarPortalRef}></StatusbarContainer>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
import styled from 'styled-components';
|
||||
import useTheme from '../theme/useTheme';
|
||||
import dimensions from '../theme/dimensions';
|
||||
|
||||
const TabItem = styled.div`
|
||||
border-right: 1px solid ${props => props.theme.border};
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: ${props => props.theme.tabs_font_hover};
|
||||
}
|
||||
background-color: ${props =>
|
||||
// @ts-ignore
|
||||
props.selected ? props.theme.tabs_background1 : 'inherit'};
|
||||
`;
|
||||
|
||||
const TabNameWrapper = styled.span`
|
||||
margin-left: 5px;
|
||||
`;
|
||||
|
||||
// visibility: ${(props) =>
|
||||
// // @ts-ignore
|
||||
// props.tabVisible ? 'visible' : 'none'};
|
||||
|
||||
const TabContainer = styled.div`
|
||||
${props =>
|
||||
// @ts-ignore
|
||||
!props.isInline &&
|
||||
`
|
||||
position: absolute;
|
||||
display: flex;
|
||||
left: 0;
|
||||
right: 0
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
`}
|
||||
|
||||
${props =>
|
||||
// @ts-ignore
|
||||
!props.tabVisible && (props.isInline ? `display:none` : `visibility: hidden;`)}
|
||||
`;
|
||||
|
||||
const TabsContainer = styled.div`
|
||||
display: flex;
|
||||
height: ${dimensions.tabsPanel.height}px;
|
||||
right: 0;
|
||||
background-color: ${props => props.theme.tabs_background2};
|
||||
`;
|
||||
|
||||
const TabContentContainer = styled.div`
|
||||
flex: 1;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const MainContainer = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
export function TabPage({ key, label, children }) {
|
||||
return children;
|
||||
}
|
||||
|
||||
export function TabControl({ children, activePageIndex = undefined, activePageLabel = undefined, isInline = false }) {
|
||||
const [value, setValue] = React.useState(0);
|
||||
|
||||
// const [mountedTabs, setMountedTabs] = React.useState({});
|
||||
|
||||
const childrenArray = (_.isArray(children) ? _.flatten(children) : [children]).filter(x => x);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (activePageIndex != null) setValue(activePageIndex);
|
||||
}, [activePageIndex]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (activePageLabel != null) {
|
||||
const pageIndex = _.findIndex(childrenArray, x => x.props.label == activePageLabel);
|
||||
if (pageIndex >= 0) setValue(pageIndex);
|
||||
}
|
||||
}, [activePageLabel]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (childrenArray.length > 0 && (value < 0 || value >= childrenArray.length)) {
|
||||
setValue(0);
|
||||
}
|
||||
});
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
// // cleanup closed tabs
|
||||
// if (_.difference(_.keys(mountedTabs), _.map(childrenArray, 'props.key')).length > 0) {
|
||||
// setMountedTabs(_.pickBy(mountedTabs, (v, k) => childrenArray.find((x) => x.props.key == k)));
|
||||
// }
|
||||
|
||||
return (
|
||||
<MainContainer>
|
||||
<TabsContainer theme={theme}>
|
||||
{childrenArray
|
||||
.filter(x => x.props)
|
||||
.map((tab, index) => (
|
||||
// @ts-ignore
|
||||
<TabItem key={index} onClick={() => setValue(index)} selected={value == index} theme={theme}>
|
||||
<TabNameWrapper>{tab.props.label}</TabNameWrapper>
|
||||
</TabItem>
|
||||
))}
|
||||
</TabsContainer>
|
||||
<TabContentContainer>
|
||||
{childrenArray.map((tab, index) => {
|
||||
const tabVisible = index == value;
|
||||
return (
|
||||
<TabContainer
|
||||
// @ts-ignore
|
||||
tabVisible={tabVisible}
|
||||
isInline={isInline}
|
||||
key={tab.props.key}
|
||||
>
|
||||
{childrenArray[index] && childrenArray[index].props.children}
|
||||
</TabContainer>
|
||||
);
|
||||
})}
|
||||
</TabContentContainer>
|
||||
</MainContainer>
|
||||
);
|
||||
}
|
||||
@@ -1,183 +0,0 @@
|
||||
import React from 'react';
|
||||
import useModalState from '../modals/useModalState';
|
||||
import ConnectionModal from '../modals/ConnectionModal';
|
||||
import { DropDownMenuItem } from '../modals/DropDownMenu';
|
||||
import styled from 'styled-components';
|
||||
import ToolbarButton, { ToolbarButtonExternalImage, ToolbarDropDownButton } from './ToolbarButton';
|
||||
import useNewQuery, { useNewQueryDesign } from '../query/useNewQuery';
|
||||
import { useConfig, useFavorites } from '../utility/metadataLoaders';
|
||||
import {
|
||||
useSetOpenedTabs,
|
||||
useOpenedTabs,
|
||||
useCurrentTheme,
|
||||
useSetCurrentTheme,
|
||||
useCurrentDatabase,
|
||||
} from '../utility/globalState';
|
||||
import useNewFreeTable from '../freetable/useNewFreeTable';
|
||||
import ImportExportModal from '../modals/ImportExportModal';
|
||||
import useShowModal from '../modals/showModal';
|
||||
import useExtensions from '../utility/useExtensions';
|
||||
import { getDefaultFileFormat } from '../utility/fileformats';
|
||||
import getElectron from '../utility/getElectron';
|
||||
import AboutModal from '../modals/AboutModal';
|
||||
import useOpenNewTab from '../utility/useOpenNewTab';
|
||||
import tabs from '../tabs';
|
||||
import FavoriteModal from '../modals/FavoriteModal';
|
||||
import { useOpenFavorite } from '../appobj/FavoriteFileAppObject';
|
||||
import ErrorMessageModal from '../modals/ErrorMessageModal';
|
||||
import useOpenElectronFile from '../utility/useOpenElectronFile';
|
||||
|
||||
const ToolbarContainer = styled.div`
|
||||
display: flex;
|
||||
user-select: none;
|
||||
`;
|
||||
|
||||
export default function ToolBar({ toolbarPortalRef }) {
|
||||
const newQuery = useNewQuery();
|
||||
const newQueryDesign = useNewQueryDesign();
|
||||
const newFreeTable = useNewFreeTable();
|
||||
const config = useConfig();
|
||||
const currentDatabase = useCurrentDatabase();
|
||||
// const toolbar = config.toolbar || [];
|
||||
const setOpenedTabs = useSetOpenedTabs();
|
||||
const openedTabs = useOpenedTabs();
|
||||
const openNewTab = useOpenNewTab();
|
||||
const showModal = useShowModal();
|
||||
const currentTheme = useCurrentTheme();
|
||||
const setCurrentTheme = useSetCurrentTheme();
|
||||
const extensions = useExtensions();
|
||||
const electron = getElectron();
|
||||
const favorites = useFavorites();
|
||||
const openFavorite = useOpenFavorite();
|
||||
const openElectronFile = useOpenElectronFile();
|
||||
|
||||
const currentTab = openedTabs.find(x => x.selected);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (config.runAsPortal == false) {
|
||||
window['dbgate_createNewConnection'] = showNewConnection;
|
||||
}
|
||||
window['dbgate_newQuery'] = newQuery;
|
||||
window['dbgate_closeAll'] = () => setOpenedTabs([]);
|
||||
window['dbgate_showAbout'] = showAbout;
|
||||
window['dbgate_openFile'] = openElectronFile;
|
||||
});
|
||||
|
||||
const showAbout = () => {
|
||||
showModal(modalState => <AboutModal modalState={modalState} />);
|
||||
};
|
||||
|
||||
const showImport = () => {
|
||||
showModal(modalState => (
|
||||
<ImportExportModal
|
||||
modalState={modalState}
|
||||
importToArchive
|
||||
initialValues={{
|
||||
sourceStorageType: getDefaultFileFormat(extensions).storageType,
|
||||
// sourceConnectionId: data.conid,
|
||||
// sourceDatabaseName: data.database,
|
||||
// sourceSchemaName: data.schemaName,
|
||||
// sourceList: [data.pureName],
|
||||
}}
|
||||
/>
|
||||
));
|
||||
};
|
||||
|
||||
const showNewConnection = () => {
|
||||
showModal(modalState => <ConnectionModal modalState={modalState} />);
|
||||
};
|
||||
|
||||
const switchTheme = () => {
|
||||
if (currentTheme == 'light') setCurrentTheme('dark');
|
||||
else setCurrentTheme('light');
|
||||
};
|
||||
|
||||
const newMarkdown = () => {
|
||||
openNewTab({
|
||||
title: 'Page #',
|
||||
tabComponent: 'MarkdownEditorTab',
|
||||
icon: 'img markdown',
|
||||
});
|
||||
};
|
||||
|
||||
const addToFavorite = () => {
|
||||
showModal(modalState => <FavoriteModal modalState={modalState} savingTab={currentTab} />);
|
||||
};
|
||||
|
||||
const newShell = () => {
|
||||
openNewTab({
|
||||
title: 'Shell #',
|
||||
icon: 'img shell',
|
||||
tabComponent: 'ShellTab',
|
||||
});
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
const { hash } = document.location;
|
||||
const openFavoriteName = hash && hash.startsWith('#favorite=') ? hash.substring('#favorite='.length) : null;
|
||||
const openTabdata = hash && hash.startsWith('#tabdata=') ? hash.substring('#tabdata='.length) : null;
|
||||
if (openFavoriteName) {
|
||||
const open = (favorites || []).find(x => x.urlPath == openFavoriteName);
|
||||
if (open) {
|
||||
openFavorite(open);
|
||||
window.history.replaceState(null, null, ' ');
|
||||
}
|
||||
} else if (openTabdata) {
|
||||
try {
|
||||
const json = JSON.parse(decodeURIComponent(openTabdata));
|
||||
openFavorite(json);
|
||||
window.history.replaceState(null, null, ' ');
|
||||
} catch (err) {
|
||||
showModal(modalState => <ErrorMessageModal modalState={modalState} message={err.message} />);
|
||||
}
|
||||
} else if (!openedTabs.find(x => x.closedTime == null)) {
|
||||
for (const fav of (favorites || []).filter(x => x.openOnStartup)) {
|
||||
openFavorite(fav);
|
||||
}
|
||||
}
|
||||
}, [favorites]);
|
||||
|
||||
return (
|
||||
<ToolbarContainer>
|
||||
{!electron && (
|
||||
<ToolbarButtonExternalImage
|
||||
// eslint-disable-next-line
|
||||
image={`${process.env.PUBLIC_URL}/logo192.png`}
|
||||
onClick={showAbout}
|
||||
/>
|
||||
)}
|
||||
{(favorites || [])
|
||||
.filter(x => x.showInToolbar)
|
||||
.map(x => (
|
||||
<ToolbarButton key={x.file} onClick={() => openFavorite(x)} icon={x.icon || 'icon favorite'}>
|
||||
{x.title}
|
||||
</ToolbarButton>
|
||||
))}
|
||||
<ToolbarDropDownButton icon="icon add" text="New">
|
||||
{config.runAsPortal == false && <DropDownMenuItem onClick={showNewConnection}>Connection</DropDownMenuItem>}
|
||||
<DropDownMenuItem onClick={() => newQuery()}>SQL query</DropDownMenuItem>
|
||||
{!!currentDatabase && <DropDownMenuItem onClick={() => newQueryDesign()}>Query designer</DropDownMenuItem>}
|
||||
<DropDownMenuItem onClick={() => newFreeTable()}>Free table editor</DropDownMenuItem>
|
||||
<DropDownMenuItem onClick={() => newMarkdown()}>Markdown page</DropDownMenuItem>
|
||||
<DropDownMenuItem onClick={() => newShell()}>JavaScript shell script</DropDownMenuItem>
|
||||
</ToolbarDropDownButton>
|
||||
|
||||
<ToolbarButton onClick={showImport} icon="icon import">
|
||||
Import data
|
||||
</ToolbarButton>
|
||||
{!!currentTab &&
|
||||
tabs[currentTab.tabComponent] &&
|
||||
tabs[currentTab.tabComponent].allowAddToFavorites &&
|
||||
tabs[currentTab.tabComponent].allowAddToFavorites(currentTab.props) && (
|
||||
<ToolbarButton onClick={addToFavorite} icon="icon share">
|
||||
Share
|
||||
</ToolbarButton>
|
||||
)}
|
||||
<ToolbarButton onClick={switchTheme} icon="icon theme">
|
||||
{currentTheme == 'dark' ? 'Light mode' : 'Dark mode'}
|
||||
</ToolbarButton>
|
||||
|
||||
<ToolbarContainer ref={toolbarPortalRef}></ToolbarContainer>
|
||||
</ToolbarContainer>
|
||||
);
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { FontIcon } from '../icons';
|
||||
import { useShowMenu } from '../modals/showMenu';
|
||||
import dimensions from '../theme/dimensions';
|
||||
import useTheme from '../theme/useTheme';
|
||||
|
||||
const ButtonDiv = styled.div`
|
||||
padding: 5px 15px;
|
||||
// margin: 2px;
|
||||
color: ${props => props.theme.main_font1};
|
||||
border: 0;
|
||||
border-right: 1px solid ${props => props.theme.border};
|
||||
height: ${dimensions.toolBar.height}px;
|
||||
|
||||
${props =>
|
||||
!props.disabled &&
|
||||
`
|
||||
&:hover {
|
||||
background-color: ${props.theme.toolbar_background2} ;
|
||||
}
|
||||
|
||||
&:active:hover {
|
||||
background-color: ${props.theme.toolbar_background3};
|
||||
}
|
||||
`}
|
||||
|
||||
${props =>
|
||||
props.disabled &&
|
||||
`
|
||||
color: ${props.theme.main_font3};
|
||||
`}
|
||||
`;
|
||||
|
||||
const StyledIconSpan = styled.span`
|
||||
margin-right: 5px;
|
||||
// font-size: 18px;
|
||||
color: ${props => (props.disabled ? props.theme.main_font3 : props.theme.main_font_link)};
|
||||
`;
|
||||
|
||||
const ButtonDivInner = styled.div`
|
||||
position: relative;
|
||||
top: ${props => props.patchY}px;
|
||||
white-space: nowrap;
|
||||
`;
|
||||
|
||||
const ButtonExternalImage = styled.img`
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
`;
|
||||
|
||||
export default function ToolbarButton({ children, onClick, icon = undefined, disabled = undefined, patchY = 2 }) {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<ButtonDiv
|
||||
theme={theme}
|
||||
onClick={() => {
|
||||
if (!disabled && onClick) onClick();
|
||||
}}
|
||||
disabled={disabled}
|
||||
>
|
||||
<ButtonDivInner patchY={patchY}>
|
||||
{icon && (
|
||||
<StyledIconSpan disabled={disabled} theme={theme}>
|
||||
<FontIcon icon={icon} />
|
||||
</StyledIconSpan>
|
||||
)}
|
||||
{children}
|
||||
</ButtonDivInner>
|
||||
</ButtonDiv>
|
||||
);
|
||||
}
|
||||
|
||||
export function ToolbarButtonExternalImage({ image, onClick, disabled = undefined }) {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<ButtonDiv
|
||||
theme={theme}
|
||||
onClick={() => {
|
||||
if (!disabled && onClick) onClick();
|
||||
}}
|
||||
disabled={disabled}
|
||||
>
|
||||
<ButtonExternalImage src={image} />
|
||||
</ButtonDiv>
|
||||
);
|
||||
}
|
||||
|
||||
export function ToolbarDropDownButton({ children, icon = undefined, text, disabled = undefined, patchY = 2 }) {
|
||||
const theme = useTheme();
|
||||
const showMenu = useShowMenu();
|
||||
const buttonRef = React.useRef(null);
|
||||
|
||||
return (
|
||||
<ButtonDiv
|
||||
theme={theme}
|
||||
onClick={() => {
|
||||
if (disabled) return;
|
||||
|
||||
const rect = buttonRef.current.getBoundingClientRect();
|
||||
showMenu(rect.left, rect.bottom, children);
|
||||
}}
|
||||
disabled={disabled}
|
||||
>
|
||||
<ButtonDivInner patchY={patchY} ref={buttonRef}>
|
||||
{icon && (
|
||||
<StyledIconSpan disabled={disabled} theme={theme}>
|
||||
<FontIcon icon={icon} />
|
||||
</StyledIconSpan>
|
||||
)}
|
||||
{text}
|
||||
<FontIcon icon="icon chevron-down" />
|
||||
</ButtonDivInner>
|
||||
</ButtonDiv>
|
||||
);
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
import {
|
||||
SearchBoxWrapper,
|
||||
WidgetsInnerContainer,
|
||||
WidgetsMainContainer,
|
||||
WidgetsOuterContainer,
|
||||
WidgetTitle,
|
||||
} from './WidgetStyles';
|
||||
import { VerticalSplitHandle, useSplitterDrag } from './Splitter';
|
||||
import useDimensions from '../utility/useDimensions';
|
||||
import useTheme from '../theme/useTheme';
|
||||
|
||||
export function WidgetColumnBarItem({ title, children, name, height = undefined, collapsed = false }) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
function WidgetContainer({ widget, visible, splitterVisible, parentHeight, initialSize = undefined }) {
|
||||
const [size, setSize] = React.useState(null);
|
||||
const theme = useTheme();
|
||||
|
||||
const handleResizeDown = useSplitterDrag('clientY', diff => setSize(v => v + diff));
|
||||
|
||||
React.useEffect(() => {
|
||||
if (_.isString(initialSize) && initialSize.endsWith('px')) setSize(parseInt(initialSize.slice(0, -2)));
|
||||
else if (_.isString(initialSize) && initialSize.endsWith('%'))
|
||||
setSize((parentHeight * parseFloat(initialSize.slice(0, -1))) / 100);
|
||||
else setSize(parentHeight / 3);
|
||||
}, [parentHeight]);
|
||||
|
||||
if (!visible) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<WidgetsOuterContainer style={splitterVisible ? { height: size } : null}>
|
||||
{widget.props.children}
|
||||
</WidgetsOuterContainer>
|
||||
{splitterVisible && <VerticalSplitHandle onMouseDown={handleResizeDown} theme={theme} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function WidgetColumnBar({ children, onChangeCollapsedWidgets = undefined }) {
|
||||
const childArray = _.isArray(children) ? children : [children];
|
||||
const [refNode, dimensions] = useDimensions();
|
||||
const [collapsedWidgets, setCollapsedWidgets] = React.useState(() =>
|
||||
childArray.filter(x => x && x.props.collapsed).map(x => x.props.name)
|
||||
);
|
||||
const toggleCollapsed = name => {
|
||||
// skip collapse last uncollapsed item
|
||||
if (!childArray.find(x => x.props && x.props.name != name && !collapsedWidgets.includes(x.props.name))) return;
|
||||
|
||||
if (collapsedWidgets.includes(name)) setCollapsedWidgets(collapsedWidgets.filter(x => x != name));
|
||||
else setCollapsedWidgets([...collapsedWidgets, name]);
|
||||
};
|
||||
React.useEffect(() => {
|
||||
if (onChangeCollapsedWidgets) onChangeCollapsedWidgets(collapsedWidgets);
|
||||
}, [onChangeCollapsedWidgets, collapsedWidgets]);
|
||||
|
||||
return (
|
||||
<WidgetsMainContainer ref={refNode}>
|
||||
{childArray.map((widget, index) => {
|
||||
if (!widget) return null;
|
||||
return (
|
||||
<React.Fragment key={widget.props.name}>
|
||||
<WidgetTitle onClick={() => toggleCollapsed(widget.props.name)}>{widget.props.title}</WidgetTitle>
|
||||
<WidgetContainer
|
||||
parentHeight={dimensions && dimensions.height}
|
||||
visible={!collapsedWidgets.includes(widget.props.name)}
|
||||
widget={widget}
|
||||
initialSize={widget.props.height}
|
||||
splitterVisible={!!childArray.slice(index + 1).find(x => x && !collapsedWidgets.includes(x.props.name))}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</WidgetsMainContainer>
|
||||
);
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useCurrentWidget } from '../utility/globalState';
|
||||
import ArchiveWidget from './ArchiveWidget';
|
||||
import DatabaseWidget from './DatabaseWidget';
|
||||
import FavoritesWidget from './FavoritesWidget';
|
||||
import FilesWidget from './FilesWidget';
|
||||
import PluginsWidget from './PluginsWidget';
|
||||
|
||||
function WidgetContainerCore() {
|
||||
const currentWidget = useCurrentWidget();
|
||||
if (currentWidget === 'database') return <DatabaseWidget />;
|
||||
if (currentWidget === 'file') return <FilesWidget />;
|
||||
if (currentWidget === 'archive') return <ArchiveWidget />;
|
||||
if (currentWidget === 'plugins') return <PluginsWidget />;
|
||||
if (currentWidget === 'favorites') return <FavoritesWidget />;
|
||||
return null;
|
||||
}
|
||||
|
||||
const WidgetContainer = React.memo(WidgetContainerCore);
|
||||
|
||||
export default WidgetContainer;
|
||||
@@ -1,93 +0,0 @@
|
||||
import React from 'react';
|
||||
import dimensions from '../theme/dimensions';
|
||||
import styled from 'styled-components';
|
||||
import { useCurrentWidget, useSetCurrentWidget } from '../utility/globalState';
|
||||
import { FontIcon } from '../icons';
|
||||
import useTheme from '../theme/useTheme';
|
||||
|
||||
const IconWrapper = styled.div`
|
||||
color: ${props => props.theme.widget_font2};
|
||||
font-size: ${dimensions.widgetMenu.iconFontSize};
|
||||
height: ${dimensions.widgetMenu.iconSize}px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
${props =>
|
||||
// @ts-ignore
|
||||
props.isSelected &&
|
||||
`
|
||||
background-color: ${props.theme.widget_background3};
|
||||
// position: relative;
|
||||
// border-left: 3px solid ${props.theme.widget_font1};
|
||||
// left: -3px;
|
||||
color: ${props.theme.widget_font1};
|
||||
`}
|
||||
|
||||
&:hover {
|
||||
color: ${props => props.theme.widget_font1};
|
||||
}
|
||||
`;
|
||||
|
||||
export default function WidgetIconPanel() {
|
||||
const theme = useTheme();
|
||||
const widgets = [
|
||||
{
|
||||
icon: 'icon database',
|
||||
name: 'database',
|
||||
title: 'Database connections',
|
||||
},
|
||||
// {
|
||||
// icon: 'fa-table',
|
||||
// name: 'table',
|
||||
// },
|
||||
{
|
||||
icon: 'icon file',
|
||||
name: 'file',
|
||||
title: 'Closed tabs & Saved SQL files',
|
||||
},
|
||||
{
|
||||
icon: 'icon archive',
|
||||
name: 'archive',
|
||||
title: 'Archive (saved tabular data)',
|
||||
},
|
||||
{
|
||||
icon: 'icon plugin',
|
||||
name: 'plugins',
|
||||
title: 'Extensions & Plugins',
|
||||
},
|
||||
{
|
||||
icon: 'icon favorite',
|
||||
name: 'favorites',
|
||||
title: 'Favorites',
|
||||
},
|
||||
// {
|
||||
// icon: 'fa-cog',
|
||||
// name: 'settings',
|
||||
// },
|
||||
// {
|
||||
// icon: 'fa-check',
|
||||
// name: 'settings',
|
||||
// },
|
||||
];
|
||||
|
||||
const currentWidget = useCurrentWidget();
|
||||
const setCurrentWidget = useSetCurrentWidget();
|
||||
|
||||
return (
|
||||
<>
|
||||
{widgets.map(({ icon, name, title }) => (
|
||||
<IconWrapper
|
||||
key={icon}
|
||||
// @ts-ignore
|
||||
isSelected={name === currentWidget}
|
||||
onClick={() => setCurrentWidget(name === currentWidget ? null : name)}
|
||||
title={title}
|
||||
theme={theme}
|
||||
>
|
||||
<FontIcon icon={icon} />
|
||||
</IconWrapper>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import useTheme from '../theme/useTheme';
|
||||
// import theme from '../theme';
|
||||
import { useLeftPanelWidth } from '../utility/globalState';
|
||||
|
||||
export const SearchBoxWrapper = styled.div`
|
||||
display: flex;
|
||||
margin-bottom: 5px;
|
||||
`;
|
||||
|
||||
export const WidgetsMainContainer = styled.div`
|
||||
position: relative;
|
||||
display: flex;
|
||||
// flex-flow: column wrap;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
user-select: none;
|
||||
`;
|
||||
|
||||
const StyledWidgetsOuterContainer = styled.div`
|
||||
overflow: hidden;
|
||||
// width: ${props => props.leftPanelWidth}px;
|
||||
position: relative;
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
export function WidgetsOuterContainer({ children, style = undefined, refNode = undefined }) {
|
||||
// const leftPanelWidth = useLeftPanelWidth();
|
||||
return (
|
||||
<StyledWidgetsOuterContainer
|
||||
ref={refNode}
|
||||
// leftPanelWidth={leftPanelWidth}
|
||||
style={{
|
||||
...style,
|
||||
flex: style && (style.height != null || style.width != null) ? undefined : '1 1 0',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</StyledWidgetsOuterContainer>
|
||||
);
|
||||
}
|
||||
|
||||
export const StyledWidgetsInnerContainer = styled.div`
|
||||
flex: 1 1;
|
||||
overflow-x: auto;
|
||||
overflow-y: auto;
|
||||
width: ${props => props.leftPanelWidth}px;
|
||||
`;
|
||||
|
||||
export function WidgetsInnerContainer({ children }) {
|
||||
const leftPanelWidth = useLeftPanelWidth();
|
||||
return <StyledWidgetsInnerContainer leftPanelWidth={leftPanelWidth}>{children}</StyledWidgetsInnerContainer>;
|
||||
}
|
||||
|
||||
// export const Input = styled.input`
|
||||
// flex: 1;
|
||||
// min-width: 90px;
|
||||
// `;
|
||||
|
||||
const StyledWidgetTitle = styled.div`
|
||||
padding: 5px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
background-color: ${props => props.theme.title_background};
|
||||
&:hover {
|
||||
background-color: ${props => props.theme.title_background2};
|
||||
}
|
||||
border: 1px solid ${props => props.theme.border};
|
||||
`;
|
||||
|
||||
export function WidgetTitle({ children, inputRef = undefined, onClick = undefined }) {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<StyledWidgetTitle
|
||||
theme={theme}
|
||||
onClick={() => {
|
||||
if (inputRef && inputRef.current) inputRef.current.focus();
|
||||
if (onClick) onClick();
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</StyledWidgetTitle>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user