mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-19 16:36:00 +00:00
resizable widgets
This commit is contained in:
@@ -12,6 +12,7 @@ import {
|
||||
WidgetsOuterContainer,
|
||||
WidgetTitle,
|
||||
} from './WidgetStyles';
|
||||
import WidgetColumnBar, { WidgetColumnBarItem } from './WidgetColumnBar';
|
||||
import savedSqlFileAppObject from '../appobj/savedSqlFileAppObject';
|
||||
import { useArchiveFiles, useArchiveFolders } from '../utility/metadataLoaders';
|
||||
import archiveFolderAppObject from '../appobj/archiveFolderAppObject';
|
||||
@@ -22,7 +23,6 @@ import axios from '../utility/axios';
|
||||
|
||||
function ArchiveFolderList() {
|
||||
const folders = useArchiveFolders();
|
||||
const inputRef = React.useRef(null);
|
||||
const [filter, setFilter] = React.useState('');
|
||||
|
||||
const setArchive = useSetCurrentArchive();
|
||||
@@ -33,9 +33,8 @@ function ArchiveFolderList() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<WidgetTitle inputRef={inputRef}>Archive folder</WidgetTitle>
|
||||
<SearchBoxWrapper>
|
||||
<SearchInput inputRef={inputRef} placeholder="Search archive folders" filter={filter} setFilter={setFilter} />
|
||||
<SearchInput placeholder="Search archive folders" filter={filter} setFilter={setFilter} />
|
||||
<InlineButton onClick={handleRefreshFolders}>Refresh</InlineButton>
|
||||
</SearchBoxWrapper>
|
||||
<WidgetsInnerContainer>
|
||||
@@ -53,7 +52,6 @@ function ArchiveFolderList() {
|
||||
function ArchiveFilesList() {
|
||||
const folder = useCurrentArchive();
|
||||
const files = useArchiveFiles({ folder });
|
||||
const inputRef = React.useRef(null);
|
||||
const [filter, setFilter] = React.useState('');
|
||||
const handleRefreshFiles = () => {
|
||||
axios.post('archive/refresh-files', { folder });
|
||||
@@ -61,9 +59,8 @@ function ArchiveFilesList() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<WidgetTitle inputRef={inputRef}>Archive files</WidgetTitle>
|
||||
<SearchBoxWrapper>
|
||||
<SearchInput inputRef={inputRef} placeholder="Search archive files" filter={filter} setFilter={setFilter} />
|
||||
<SearchInput placeholder="Search archive files" filter={filter} setFilter={setFilter} />
|
||||
<InlineButton onClick={handleRefreshFiles}>Refresh</InlineButton>
|
||||
</SearchBoxWrapper>
|
||||
<WidgetsInnerContainer>
|
||||
@@ -82,13 +79,13 @@ function ArchiveFilesList() {
|
||||
|
||||
export default function ArchiveWidget() {
|
||||
return (
|
||||
<WidgetsMainContainer>
|
||||
<WidgetsOuterContainer>
|
||||
<WidgetColumnBar>
|
||||
<WidgetColumnBarItem title="Archive folders" name="folders" height="50%">
|
||||
<ArchiveFolderList />
|
||||
</WidgetsOuterContainer>
|
||||
<WidgetsOuterContainer>
|
||||
</WidgetColumnBarItem>
|
||||
<WidgetColumnBarItem title="Archive files" name="files">
|
||||
<ArchiveFilesList />
|
||||
</WidgetsOuterContainer>
|
||||
</WidgetsMainContainer>
|
||||
</WidgetColumnBarItem>
|
||||
</WidgetColumnBar>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import axios from '../utility/axios';
|
||||
import LoadingInfo from './LoadingInfo';
|
||||
import SearchInput from './SearchInput';
|
||||
import ErrorInfo from './ErrorInfo';
|
||||
import WidgetColumnBar, { WidgetColumnBarItem } from './WidgetColumnBar';
|
||||
|
||||
function SubDatabaseList({ data }) {
|
||||
const setDb = useSetCurrentDatabase();
|
||||
@@ -60,14 +61,12 @@ function ConnectionList() {
|
||||
axios.post('server-connections/refresh', { conid });
|
||||
}
|
||||
};
|
||||
const inputRef = React.useRef(null);
|
||||
|
||||
const [filter, setFilter] = React.useState('');
|
||||
return (
|
||||
<>
|
||||
<WidgetTitle inputRef={inputRef}>Connections</WidgetTitle>
|
||||
<SearchBoxWrapper>
|
||||
<SearchInput placeholder="Search connection" filter={filter} setFilter={setFilter} inputRef={inputRef} />
|
||||
<SearchInput placeholder="Search connection" filter={filter} setFilter={setFilter} />
|
||||
<InlineButton onClick={handleRefreshConnections}>Refresh</InlineButton>
|
||||
</SearchBoxWrapper>
|
||||
|
||||
@@ -103,7 +102,6 @@ function SqlObjectList({ conid, database }) {
|
||||
const inputRef = React.useRef(null);
|
||||
return (
|
||||
<>
|
||||
<WidgetTitle inputRef={inputRef}>Tables, views, functions</WidgetTitle>
|
||||
<SearchBoxWrapper>
|
||||
<SearchInput inputRef={inputRef} placeholder="Search tables or objects" filter={filter} setFilter={setFilter} />
|
||||
<InlineButton onClick={handleRefreshDatabase}>Refresh</InlineButton>
|
||||
@@ -128,12 +126,7 @@ function SqlObjectListWrapper() {
|
||||
const db = useCurrentDatabase();
|
||||
|
||||
if (!db) {
|
||||
return (
|
||||
<>
|
||||
<WidgetTitle>Tables, views, functions</WidgetTitle>
|
||||
<ErrorInfo message="Database not selected" icon="fas fa-exclamation-circle blue" />
|
||||
</>
|
||||
);
|
||||
return <ErrorInfo message="Database not selected" icon="fas fa-exclamation-circle blue" />;
|
||||
}
|
||||
const { name, connection } = db;
|
||||
|
||||
@@ -144,13 +137,13 @@ function SqlObjectListWrapper() {
|
||||
|
||||
export default function DatabaseWidget() {
|
||||
return (
|
||||
<WidgetsMainContainer>
|
||||
<WidgetsOuterContainer>
|
||||
<WidgetColumnBar>
|
||||
<WidgetColumnBarItem title="Connections" name="connections" height="50%">
|
||||
<ConnectionList />
|
||||
</WidgetsOuterContainer>
|
||||
<WidgetsOuterContainer>
|
||||
</WidgetColumnBarItem>
|
||||
<WidgetColumnBarItem title="Tables, views, functions" name="dbObjects">
|
||||
<SqlObjectListWrapper />
|
||||
</WidgetsOuterContainer>
|
||||
</WidgetsMainContainer>
|
||||
</WidgetColumnBarItem>
|
||||
</WidgetColumnBar>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,13 +13,13 @@ import {
|
||||
WidgetTitle,
|
||||
} from './WidgetStyles';
|
||||
import savedSqlFileAppObject from '../appobj/savedSqlFileAppObject';
|
||||
import WidgetColumnBar, { WidgetColumnBarItem } from './WidgetColumnBar';
|
||||
|
||||
function ClosedTabsList() {
|
||||
const tabs = useOpenedTabs();
|
||||
|
||||
return (
|
||||
<>
|
||||
<WidgetTitle>Recently closed tabs</WidgetTitle>
|
||||
<WidgetsInnerContainer>
|
||||
<AppObjectList
|
||||
list={_.sortBy(
|
||||
@@ -38,7 +38,6 @@ function SavedSqlFilesList() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<WidgetTitle>Saved SQL files</WidgetTitle>
|
||||
<WidgetsInnerContainer>
|
||||
<AppObjectList list={files} makeAppObj={savedSqlFileAppObject()} />
|
||||
</WidgetsInnerContainer>
|
||||
@@ -48,13 +47,13 @@ function SavedSqlFilesList() {
|
||||
|
||||
export default function FilesWidget() {
|
||||
return (
|
||||
<WidgetsMainContainer>
|
||||
<WidgetsOuterContainer>
|
||||
<WidgetColumnBar>
|
||||
<WidgetColumnBarItem title="Recently closed tabs" name="closedTabs" height="50%">
|
||||
<ClosedTabsList />
|
||||
</WidgetsOuterContainer>
|
||||
<WidgetsOuterContainer>
|
||||
</WidgetColumnBarItem>
|
||||
<WidgetColumnBarItem title="Saved SQL files" name="sqlFiles">
|
||||
<SavedSqlFilesList />
|
||||
</WidgetsOuterContainer>
|
||||
</WidgetsMainContainer>
|
||||
</WidgetColumnBarItem>
|
||||
</WidgetColumnBar>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ const SplitterMainBase = styled.div`
|
||||
bottom: 0;
|
||||
`;
|
||||
|
||||
// @ts-ignore
|
||||
const VerticalMainContainer = styled(SplitterMainBase)`
|
||||
flex: 1;
|
||||
display: flex;
|
||||
|
||||
72
packages/web/src/widgets/WidgetColumnBar.js
Normal file
72
packages/web/src/widgets/WidgetColumnBar.js
Normal file
@@ -0,0 +1,72 @@
|
||||
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';
|
||||
|
||||
export function WidgetColumnBarItem({ title, children, name, height = undefined }) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
function WidgetContainer({ widget, visible, splitterVisible, parentHeight, initialSize = undefined }) {
|
||||
const [size, setSize] = React.useState(null);
|
||||
|
||||
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} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function WidgetColumnBar({ children }) {
|
||||
const childArray = _.isArray(children) ? children : [children];
|
||||
const [refNode, dimensions] = useDimensions();
|
||||
const [collapsedWidgets, setCollapsedWidgets] = React.useState(() =>
|
||||
childArray.filter((x) => x.props.collapsed).map((x) => x.props.key)
|
||||
);
|
||||
const toggleCollapsed = (name) => {
|
||||
if (collapsedWidgets.includes(name)) setCollapsedWidgets(collapsedWidgets.filter((x) => x != name));
|
||||
else setCollapsedWidgets([...collapsedWidgets, name]);
|
||||
};
|
||||
|
||||
return (
|
||||
<WidgetsMainContainer ref={refNode}>
|
||||
{childArray.map((widget, index) => {
|
||||
if (!widget) return null;
|
||||
return (
|
||||
<>
|
||||
<WidgetTitle onClick={() => toggleCollapsed(widget.props.name)}>{widget.props.title}</WidgetTitle>
|
||||
<WidgetContainer
|
||||
parentHeight={dimensions && dimensions.height}
|
||||
visible={!collapsedWidgets.includes(widget.props.name)}
|
||||
widget={widget}
|
||||
key={widget.props.name}
|
||||
initialSize={widget.props.height}
|
||||
splitterVisible={!!childArray.slice(index + 1).find((x) => !collapsedWidgets.includes(x.props.name))}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
})}
|
||||
</WidgetsMainContainer>
|
||||
);
|
||||
}
|
||||
@@ -19,7 +19,6 @@ export const WidgetsMainContainer = styled.div`
|
||||
`;
|
||||
|
||||
const StyledWidgetsOuterContainer = styled.div`
|
||||
flex: 1 1 0;
|
||||
overflow: hidden;
|
||||
width: ${(props) => props.leftPanelWidth}px;
|
||||
position: relative;
|
||||
@@ -27,9 +26,20 @@ const StyledWidgetsOuterContainer = styled.div`
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
export function WidgetsOuterContainer({ children }) {
|
||||
export function WidgetsOuterContainer({ children, style = undefined, refNode = undefined }) {
|
||||
const leftPanelWidth = useLeftPanelWidth();
|
||||
return <StyledWidgetsOuterContainer leftPanelWidth={leftPanelWidth}>{children}</StyledWidgetsOuterContainer>;
|
||||
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`
|
||||
@@ -53,14 +63,16 @@ const StyledWidgetTitle = styled.div`
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
background-color: gray;
|
||||
border: 1px solid #aaa;
|
||||
// background-color: #CEC;
|
||||
`;
|
||||
|
||||
export function WidgetTitle({ children, inputRef = undefined }) {
|
||||
export function WidgetTitle({ children, inputRef = undefined, onClick = undefined }) {
|
||||
return (
|
||||
<StyledWidgetTitle
|
||||
onClick={() => {
|
||||
if (inputRef && inputRef.current) inputRef.current.focus();
|
||||
if (onClick) onClick();
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
Reference in New Issue
Block a user