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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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