resizable widgets

This commit is contained in:
Jan Prochazka
2020-11-05 12:17:34 +01:00
parent fd9fa0c95a
commit 7888cf6714
6 changed files with 114 additions and 40 deletions

View File

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

View File

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

View File

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

View File

@@ -14,6 +14,7 @@ const SplitterMainBase = styled.div`
bottom: 0;
`;
// @ts-ignore
const VerticalMainContainer = styled(SplitterMainBase)`
flex: 1;
display: flex;

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

View File

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