mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
CB-3864 migrate tree to css modules (#2003)
* CB-3864 migrate tree to css modules * CB-3864 migrate extra components * CB-3864 review fixes * CB-3864 move editing and dragging props from base nav components * CB-3864 add styles to renderer * CB-3864 fix aws styles * CB-3864 remove padding in projects renderer * CB-3864 fix node hover effect * CB-3864 remove custom tags * CB-3864 remove config changes * CB-3864 review fixes * CB-3864 move NavigationNodeControl styles closer to the component * CB-3864 chore: code cleanup --------- Co-authored-by: Alexey <wrouds@gmail.com> Co-authored-by: EvgeniaBzzz <139753579+EvgeniaBzzz@users.noreply.github.com> Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com>
This commit is contained in:
co-authored by
Alexey
EvgeniaBzzz
mr-anton-t
parent
a2d9fddc5e
commit
571d6b7c0a
@@ -0,0 +1,10 @@
|
||||
.actionIconButton {
|
||||
composes: theme-form-element-radius theme-ripple from global;
|
||||
|
||||
padding: 4px !important;
|
||||
margin: 2px !important;
|
||||
width: 24px !important;
|
||||
height: 24px !important;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
.status {
|
||||
composes: theme-background-positive from global;
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
box-sizing: border-box;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
|
||||
&.connected {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@@ -5,32 +5,18 @@
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import styled, { css, use } from 'reshadow';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
const styles = css`
|
||||
status {
|
||||
composes: theme-background-positive from global;
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
box-sizing: border-box;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
|
||||
&[|connected] {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
`;
|
||||
import { s } from '../s';
|
||||
import { useS } from '../useS';
|
||||
import style from './ConnectionMark.m.css';
|
||||
|
||||
interface Props {
|
||||
connected: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const ConnectionMark: React.FC<Props> = function ConnectionMark({ connected, className }) {
|
||||
return styled(styles)(<status {...use({ connected })} className={className} />);
|
||||
};
|
||||
export const ConnectionMark: React.FC<Props> = observer(function ConnectionMark({ connected, className }) {
|
||||
const styles = useS(style);
|
||||
return <div className={s(styles, { status: true, connected }, className)} />;
|
||||
});
|
||||
|
||||
@@ -14,4 +14,4 @@
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
.node {
|
||||
box-sizing: border-box;
|
||||
width: inherit;
|
||||
min-width: 100%;
|
||||
}
|
||||
@@ -8,14 +8,14 @@
|
||||
import { computed, observable } from 'mobx';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { forwardRef } from 'react';
|
||||
import styled, { use } from 'reshadow';
|
||||
|
||||
import { getComputed } from '../../getComputed';
|
||||
import { s } from '../../s';
|
||||
import { useObjectRef } from '../../useObjectRef';
|
||||
import { useObservableRef } from '../../useObservableRef';
|
||||
import { useS } from '../../useS';
|
||||
import type { ITreeNodeState } from './ITreeNodeState';
|
||||
import style from './TreeNode.m.css';
|
||||
import { ITreeNodeContext, TreeNodeContext } from './TreeNodeContext';
|
||||
import { TREE_NODE_STYLES } from './TreeNodeStyles';
|
||||
|
||||
interface Props extends ITreeNodeState {
|
||||
className?: string;
|
||||
@@ -48,6 +48,7 @@ export const TreeNode = observer<Props, HTMLDivElement | null>(
|
||||
},
|
||||
ref,
|
||||
) {
|
||||
const styles = useS(style);
|
||||
const handlersRef = useObjectRef(handlers);
|
||||
|
||||
async function processAction(action: () => Promise<void>) {
|
||||
@@ -113,12 +114,10 @@ export const TreeNode = observer<Props, HTMLDivElement | null>(
|
||||
},
|
||||
);
|
||||
|
||||
const elementExpanded = getComputed(() => nodeContext.externalExpanded ?? nodeContext.expanded);
|
||||
|
||||
return styled(TREE_NODE_STYLES)(
|
||||
<node {...use({ expanded: elementExpanded })} ref={ref} className={className}>
|
||||
return (
|
||||
<div ref={ref} className={s(styles, { node: true }, className)}>
|
||||
<TreeNodeContext.Provider value={nodeContext}>{children}</TreeNodeContext.Provider>
|
||||
</node>,
|
||||
</div>
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
.treeNodeControl {
|
||||
composes: theme-ripple theme-ripple-selectable from global;
|
||||
box-sizing: border-box;
|
||||
height: 22px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 2px 0 2px 0;
|
||||
user-select: none;
|
||||
white-space: nowrap;
|
||||
position: initial !important;
|
||||
outline: none;
|
||||
|
||||
&::before {
|
||||
left: 0 !important;
|
||||
top: auto !important;
|
||||
height: inherit !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
& > * {
|
||||
margin-right: 0;
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
@@ -10,11 +10,14 @@ import React, { forwardRef, useContext } from 'react';
|
||||
|
||||
import { EventContext, EventStopPropagationFlag } from '@cloudbeaver/core-events';
|
||||
|
||||
import { s } from '../../s';
|
||||
import { useS } from '../../useS';
|
||||
import { EventTreeNodeClickFlag } from './EventTreeNodeClickFlag';
|
||||
import { EventTreeNodeExpandFlag } from './EventTreeNodeExpandFlag';
|
||||
import { EventTreeNodeSelectFlag } from './EventTreeNodeSelectFlag';
|
||||
import type { ITreeNodeState } from './ITreeNodeState';
|
||||
import { TreeNodeContext } from './TreeNodeContext';
|
||||
import style from './TreeNodeControl.m.css';
|
||||
|
||||
const KEY = {
|
||||
ENTER: 'Enter',
|
||||
@@ -25,15 +28,15 @@ interface Props extends ITreeNodeState {
|
||||
onClick?: (event: React.MouseEvent<HTMLDivElement>) => void;
|
||||
onMouseDown?: (event: React.MouseEvent<HTMLDivElement>) => void;
|
||||
className?: string;
|
||||
big?: boolean;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export const TreeNodeControl = observer<Props & React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>(
|
||||
forwardRef(function TreeNodeControl(
|
||||
{ title, group, disabled, loading, selected, expanded, externalExpanded, leaf, onClick, onMouseDown, className, children, big, ...rest },
|
||||
{ title, group, disabled, loading, selected, expanded, externalExpanded, leaf, onClick, onMouseDown, className, children, ...rest },
|
||||
ref,
|
||||
) {
|
||||
const styles = useS(style);
|
||||
const context = useContext(TreeNodeContext);
|
||||
|
||||
if (!context) {
|
||||
@@ -112,7 +115,7 @@ export const TreeNodeControl = observer<Props & React.HTMLAttributes<HTMLDivElem
|
||||
tabIndex={0}
|
||||
title={title}
|
||||
aria-selected={context.selected}
|
||||
className={className}
|
||||
className={s(styles, { treeNodeControl: true }, className)}
|
||||
onClick={handleClick}
|
||||
onMouseDown={handleMouseDown}
|
||||
onKeyDown={handleEnter}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
.treeNodeExpand {
|
||||
position: relative;
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
flex-shrink: 0;
|
||||
opacity: 0.5;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
transform: rotate(-90deg);
|
||||
&.small {
|
||||
display: block;
|
||||
}
|
||||
|
||||
&.expanded {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
cursor: pointer;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
@@ -7,24 +7,18 @@
|
||||
*/
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useContext } from 'react';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { EventContext } from '@cloudbeaver/core-events';
|
||||
|
||||
import { getComputed } from '../../getComputed';
|
||||
import { Icon } from '../../Icon';
|
||||
import { Loader } from '../../Loader/Loader';
|
||||
import { s } from '../../s';
|
||||
import { useS } from '../../useS';
|
||||
import { useStateDelay } from '../../useStateDelay';
|
||||
import { EventTreeNodeExpandFlag } from './EventTreeNodeExpandFlag';
|
||||
import { TreeNodeContext } from './TreeNodeContext';
|
||||
|
||||
const styles = css`
|
||||
Icon {
|
||||
cursor: pointer;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
import style from './TreeNodeExpand.m.css';
|
||||
|
||||
interface Props {
|
||||
leaf?: boolean;
|
||||
@@ -35,6 +29,7 @@ interface Props {
|
||||
}
|
||||
|
||||
export const TreeNodeExpand = observer<Props>(function TreeNodeExpand({ leaf, big, filterActive, disabled, className }) {
|
||||
const styles = useS(style);
|
||||
const context = useContext(TreeNodeContext);
|
||||
|
||||
if (!context) {
|
||||
@@ -90,10 +85,14 @@ export const TreeNodeExpand = observer<Props>(function TreeNodeExpand({ leaf, bi
|
||||
}
|
||||
}
|
||||
|
||||
return styled(styles)(
|
||||
<arrow className={className} onClick={handleExpand} onDoubleClick={handleDbClick}>
|
||||
return (
|
||||
<div
|
||||
className={s(styles, { treeNodeExpand: true, expanded: context.expanded, big }, className)}
|
||||
onClick={handleExpand}
|
||||
onDoubleClick={handleDbClick}
|
||||
>
|
||||
{loading && <Loader small fullSize />}
|
||||
{expandable && <Icon name={iconName} viewBox={viewBox} />}
|
||||
</arrow>,
|
||||
{expandable && <Icon name={iconName} className={s(styles, { icon: true })} viewBox={viewBox} />}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
.treeNodeIcon {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
flex-shrink: 0;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.staticImage {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
}
|
||||
@@ -5,30 +5,25 @@
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import type { ComponentStyle } from '@cloudbeaver/core-theming';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
import { s } from '../../s';
|
||||
import { StaticImage } from '../../StaticImage';
|
||||
import { useStyles } from '../../useStyles';
|
||||
|
||||
const styles = css`
|
||||
StaticImage {
|
||||
height: 100%;
|
||||
}
|
||||
`;
|
||||
import { useS } from '../../useS';
|
||||
import style from './TreeNodeIcon.m.css';
|
||||
|
||||
interface Props {
|
||||
icon?: string;
|
||||
style?: ComponentStyle;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const TreeNodeIcon: React.FC<React.PropsWithChildren<Props>> = function TreeNodeIcon({ icon, style, className, children }) {
|
||||
return styled(useStyles(styles, style))(
|
||||
<icon className={className}>
|
||||
{icon && <StaticImage icon={icon} />}
|
||||
export const TreeNodeIcon: React.FC<React.PropsWithChildren<Props>> = observer(function TreeNodeIcon({ icon, className, children }) {
|
||||
const styles = useS(style);
|
||||
|
||||
return (
|
||||
<div className={s(styles, { treeNodeIcon: true }, className)}>
|
||||
{icon && <StaticImage className={s(styles, { staticImage: true })} icon={icon} />}
|
||||
{children}
|
||||
</icon>,
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
.treeNodeName {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
padding-right: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 4px;
|
||||
}
|
||||
@@ -5,15 +5,22 @@
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
import { s } from '../../s';
|
||||
import { useS } from '../../useS';
|
||||
import style from './TreeNodeName.m.css';
|
||||
|
||||
interface Props extends React.HTMLAttributes<HTMLDivElement> {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const TreeNodeName: React.FC<Props> = function TreeNodeName({ className, children, ...rest }) {
|
||||
export const TreeNodeName: React.FC<Props> = observer(function TreeNodeName({ className, children, ...rest }) {
|
||||
const styles = useS(style);
|
||||
|
||||
return (
|
||||
<div className={className} {...rest}>
|
||||
<div className={s(styles, { treeNodeName: true }, className)} {...rest}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
.treeNodeNested {
|
||||
box-sizing: border-box;
|
||||
padding-left: 8px;
|
||||
display: none;
|
||||
|
||||
&.root {
|
||||
padding: 0;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
@@ -5,18 +5,27 @@
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { forwardRef } from 'react';
|
||||
|
||||
import { s } from '../../s';
|
||||
import { useS } from '../../useS';
|
||||
import style from './TreeNodeNested.m.css';
|
||||
|
||||
interface Props extends React.PropsWithChildren {
|
||||
expanded?: boolean;
|
||||
root?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const TreeNodeNested = forwardRef<HTMLDivElement, Props>(function TreeNodeNested({ className, children }, ref) {
|
||||
return (
|
||||
<div ref={ref} className={className}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
export const TreeNodeNested = observer(
|
||||
forwardRef<HTMLDivElement, Props>(function TreeNodeNested({ root, className, children }, ref) {
|
||||
const styles = useS(style);
|
||||
|
||||
return (
|
||||
<div ref={ref} className={s(styles, { treeNodeNested: true, root }, className)}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
.treeNodeNestedMessage {
|
||||
composes: theme-typography--caption from global;
|
||||
padding: 4px 12px;
|
||||
}
|
||||
@@ -5,12 +5,17 @@
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
import { s } from '../../s';
|
||||
import { useS } from '../../useS';
|
||||
import style from './TreeNodeNestedMessage.m.css';
|
||||
|
||||
interface Props {
|
||||
big?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const TreeNodeNestedMessage: React.FC<React.PropsWithChildren<Props>> = function TreeNodeNestedMessage({ className, children }) {
|
||||
return <div className={className}>{children}</div>;
|
||||
};
|
||||
export const TreeNodeNestedMessage: React.FC<React.PropsWithChildren<Props>> = observer(function TreeNodeNestedMessage({ className, children }) {
|
||||
const styles = useS(style);
|
||||
return <div className={s(styles, { treeNodeNestedMessage: true }, className)}>{children}</div>;
|
||||
});
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
.loader {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
@@ -7,21 +7,16 @@
|
||||
*/
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useContext } from 'react';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { EventContext } from '@cloudbeaver/core-events';
|
||||
|
||||
import { Checkbox } from '../../FormControls/Checkboxes/Checkbox';
|
||||
import { Loader } from '../../Loader/Loader';
|
||||
import { s } from '../../s';
|
||||
import { useS } from '../../useS';
|
||||
import { EventTreeNodeSelectFlag } from './EventTreeNodeSelectFlag';
|
||||
import { TreeNodeContext } from './TreeNodeContext';
|
||||
|
||||
const styles = css`
|
||||
Loader {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
`;
|
||||
import style from './TreeNodeSelect.m.css';
|
||||
|
||||
interface Props {
|
||||
group?: boolean;
|
||||
@@ -42,6 +37,7 @@ export const TreeNodeSelect = observer<Props>(function TreeNodeSelect({
|
||||
loadIndicator,
|
||||
className,
|
||||
}) {
|
||||
const styles = useS(style);
|
||||
const context = useContext(TreeNodeContext);
|
||||
|
||||
if (!context) {
|
||||
@@ -67,9 +63,13 @@ export const TreeNodeSelect = observer<Props>(function TreeNodeSelect({
|
||||
EventContext.set(event, EventTreeNodeSelectFlag);
|
||||
}
|
||||
|
||||
return styled(styles)(
|
||||
return (
|
||||
<div className={className} onClick={handleClick} onDoubleClick={handleDbClick}>
|
||||
{loading ? <Loader small /> : <Checkbox checked={selected} indeterminate={indeterminate} disabled={disabled} onChange={handleSelect} />}
|
||||
</div>,
|
||||
{loading ? (
|
||||
<Loader className={s(styles, { loader: true })} small />
|
||||
) : (
|
||||
<Checkbox checked={selected} indeterminate={indeterminate} disabled={disabled} onChange={handleSelect} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,188 +0,0 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2023 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { css } from 'reshadow';
|
||||
|
||||
export const TREE_NODE_STYLES = css`
|
||||
TreeNodeControl {
|
||||
composes: theme-ripple theme-ripple-selectable from global;
|
||||
}
|
||||
TreeNodeNestedMessage {
|
||||
composes: theme-text-text-hint-on-light from global;
|
||||
}
|
||||
node {
|
||||
box-sizing: border-box;
|
||||
width: inherit;
|
||||
min-width: 100%;
|
||||
|
||||
&[use|expanded] {
|
||||
& > TreeNodeNested {
|
||||
display: block;
|
||||
}
|
||||
|
||||
& > TreeNodeControl > TreeNodeExpand {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
}
|
||||
|
||||
& TreeNodeControl {
|
||||
box-sizing: border-box;
|
||||
height: 22px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 2px 0 2px 0;
|
||||
user-select: none;
|
||||
white-space: nowrap;
|
||||
position: initial;
|
||||
outline: none;
|
||||
|
||||
&::before {
|
||||
left: 0;
|
||||
top: auto;
|
||||
height: inherit;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&[|dragging] {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
&[|editing]::before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
& > * {
|
||||
margin-right: 0;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
& TreeNodeName {
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TreeNodeExpand {
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
flex-shrink: 0;
|
||||
opacity: 0.5;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
transform: rotate(-90deg);
|
||||
& [|size='small'] {
|
||||
display: block;
|
||||
}
|
||||
|
||||
& [|size='big'] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
NavigationNode[dragging] {
|
||||
opacity: 0.5 !important;
|
||||
}
|
||||
|
||||
TreeNodeIcon {
|
||||
box-sizing: border-box;
|
||||
flex-shrink: 0;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
TreeNodeName {
|
||||
box-sizing: border-box;
|
||||
padding-right: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
TreeNodeFilter {
|
||||
position: relative;
|
||||
min-width: 24px;
|
||||
min-height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
TreeNodeIcon,
|
||||
TreeNodeExpand,
|
||||
TreeNodeName {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
TreeNodeNested {
|
||||
box-sizing: border-box;
|
||||
padding-left: 8px;
|
||||
display: none;
|
||||
|
||||
&[root] {
|
||||
padding: 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
&[expanded] {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
TreeNodeNestedMessage {
|
||||
composes: theme-typography--caption from global;
|
||||
padding: 4px 12px;
|
||||
|
||||
&[big] {
|
||||
padding: 4px 24px;
|
||||
}
|
||||
}
|
||||
|
||||
tree-box[|big] {
|
||||
& TreeNodeNestedMessage {
|
||||
padding: 4px 24px;
|
||||
}
|
||||
}
|
||||
|
||||
node TreeNodeControl[big] {
|
||||
height: 46px;
|
||||
padding: 0px 16px;
|
||||
|
||||
& TreeNodeName {
|
||||
margin: 0;
|
||||
padding-right: 8px;
|
||||
padding-left: 8px;
|
||||
}
|
||||
& TreeNodeIcon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
& TreeNodeExpand {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
& TreeNodeExpand [|size='small'] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
& TreeNodeExpand [|size='big'] {
|
||||
display: block;
|
||||
}
|
||||
|
||||
& > * {
|
||||
margin-right: 8px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
& TreeNodeSelect {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
node TreeNodeControl[big] + TreeNodeNested {
|
||||
padding-left: 24px;
|
||||
}
|
||||
`;
|
||||
@@ -25,6 +25,7 @@ export * from './ConnectionImageWithMask/ConnectionImageWithMask';
|
||||
export { default as ConnectionImageWithMaskSvgStyles } from './ConnectionImageWithMask/ConnectionImageWithMaskSvg.m.css';
|
||||
|
||||
export * from './Menu/ACTION_ICON_BUTTON_STYLES';
|
||||
export { default as ActionIconButtonStyles } from './ActionIconButton.m.css';
|
||||
export * from './Menu/Menu';
|
||||
export { default as MenuStyles } from './Menu/Menu.m.css';
|
||||
export * from './Menu/MenuBarSmallItem';
|
||||
@@ -106,12 +107,13 @@ export * from './Tree/TreeNode/TreeNodeName';
|
||||
export * from './Tree/TreeNode/TreeNodeNested';
|
||||
export * from './Tree/TreeNode/TreeNodeNestedMessage';
|
||||
export * from './Tree/TreeNode/TreeNodeSelect';
|
||||
export * from './Tree/TreeNode/TreeNodeStyles';
|
||||
export * from './Button';
|
||||
export * from './ToolsPanel/ToolsAction';
|
||||
export * from './ToolsPanel/ToolsPanel';
|
||||
export { default as ToolsPanelStyles } from './ToolsPanel/ToolsPanel.m.css';
|
||||
export { default as ToolsActionStyles } from './ToolsPanel/ToolsAction.m.css';
|
||||
export { default as TreeNodeNestedMessageStyles } from './Tree/TreeNode/TreeNodeNestedMessage.m.css';
|
||||
export { default as TreeNodeStyles } from './Tree/TreeNode/TreeNode.m.css';
|
||||
export * from './FormControls/Checkboxes/Checkbox';
|
||||
export * from './FormControls/Checkboxes/FieldCheckbox';
|
||||
export * from './FormControls/Checkboxes/CheckboxMarkup';
|
||||
|
||||
@@ -242,6 +242,7 @@ module.exports = (env, argv) => {
|
||||
plugins: [
|
||||
new ForkTsCheckerWebpackPlugin({
|
||||
typescript: {
|
||||
typescriptPath: require.resolve('typescript'),
|
||||
configFile: resolve('tsconfig.json'),
|
||||
configOverwrite: {
|
||||
include: ['**/src/**/*.ts', '**/src/**/*.tsx'],
|
||||
|
||||
+5
-5
@@ -19,13 +19,13 @@ function DisplayErrorInfo({ error }: { error: IErrorInfo }) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<property className={s(styles, { property: true })}>
|
||||
{error.isHtml ? <Iframe srcDoc={error.message} /> : <message className={s(styles, { message: true })}>{error.message}</message>}
|
||||
</property>
|
||||
<div className={s(styles, { property: true })}>
|
||||
{error.isHtml ? <Iframe srcDoc={error.message} /> : <div className={s(styles, { message: true })}>{error.message}</div>}
|
||||
</div>
|
||||
{error.stackTrace && (
|
||||
<property className={s(styles, { property: true })}>
|
||||
<div className={s(styles, { property: true })}>
|
||||
<Textarea className={s(styles, { textarea: true })} value={error.stackTrace} readOnly embedded />
|
||||
</property>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
+5
-6
@@ -6,9 +6,8 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import styled from 'reshadow';
|
||||
|
||||
import { ACTION_ICON_BUTTON_STYLES, IconButton, s, TableColumnValue, TableItem, useS } from '@cloudbeaver/core-blocks';
|
||||
import { ActionIconButtonStyles, IconButton, s, TableColumnValue, TableItem, useS } from '@cloudbeaver/core-blocks';
|
||||
|
||||
import styles from './FiltersTableItem.m.css';
|
||||
|
||||
@@ -21,14 +20,14 @@ interface Props {
|
||||
}
|
||||
|
||||
export const FiltersTableItem = observer<Props>(function FiltersTableItem({ id, name, disabled, className, onDelete }) {
|
||||
const style = useS(styles);
|
||||
const style = useS(ActionIconButtonStyles, styles);
|
||||
|
||||
return styled(ACTION_ICON_BUTTON_STYLES)(
|
||||
return (
|
||||
<TableItem className={s(style, { tableItem: true }, className)} item={id} title={name} disabled={disabled} selectDisabled={disabled}>
|
||||
<TableColumnValue>{name}</TableColumnValue>
|
||||
<TableColumnValue className={s(style, { deleteColumnCell: true })} flex>
|
||||
<IconButton name="cross-bold" onClick={() => onDelete(id)} />
|
||||
<IconButton className={s(ActionIconButtonStyles, { actionIconButton: true })} name="cross-bold" onClick={() => onDelete(id)} />
|
||||
</TableColumnValue>
|
||||
</TableItem>,
|
||||
</TableItem>
|
||||
);
|
||||
});
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
.treeNodeControl {
|
||||
transition: opacity 0.3s ease;
|
||||
opacity: 1;
|
||||
|
||||
&.outdated {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.treeNodeControl:hover > .portal,
|
||||
.treeNodeControl[aria-selected='true'] > .portal,
|
||||
.portal:focus-within {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.treeNodeName {
|
||||
composes: theme-text-text-hint-on-light theme-typography--caption from global;
|
||||
height: 100%;
|
||||
max-width: 250px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.portal {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
margin-left: auto !important;
|
||||
margin-right: 8px !important;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.nameBox {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
+11
-47
@@ -9,7 +9,7 @@ import { observer } from 'mobx-react-lite';
|
||||
import React, { forwardRef, useContext } from 'react';
|
||||
import styled, { css, use } from 'reshadow';
|
||||
|
||||
import { getComputed, TREE_NODE_STYLES, TreeNodeContext, TreeNodeControl, TreeNodeName, useMouseContextMenu } from '@cloudbeaver/core-blocks';
|
||||
import { getComputed, s, TreeNodeContext, TreeNodeControl, TreeNodeName, useMouseContextMenu, useS } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { EventContext, EventStopPropagationFlag } from '@cloudbeaver/core-events';
|
||||
import { NavNodeInfoResource } from '@cloudbeaver/core-navigation-tree';
|
||||
@@ -23,47 +23,15 @@ import {
|
||||
NavTreeControlProps,
|
||||
TreeNodeMenuLoader,
|
||||
} from '@cloudbeaver/plugin-navigation-tree';
|
||||
import { ResourceManagerService } from '@cloudbeaver/plugin-resource-manager';
|
||||
|
||||
import { getRmProjectNodeId } from '../../NavNodes/getRmProjectNodeId';
|
||||
import { DATA_CONTEXT_RESOURCE_MANAGER_TREE_RESOURCE_TYPE_ID } from '../DATA_CONTEXT_RESOURCE_MANAGER_TREE_RESOURCE_TYPE_ID';
|
||||
import { ResourceManagerService } from '@cloudbeaver/plugin-resource-manager';
|
||||
|
||||
const styles = css`
|
||||
TreeNodeControl {
|
||||
transition: opacity 0.3s ease;
|
||||
opacity: 1;
|
||||
|
||||
&[|outdated] {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
TreeNodeControl:hover > portal,
|
||||
TreeNodeControl:global([aria-selected='true']) > portal,
|
||||
portal:focus-within {
|
||||
visibility: visible;
|
||||
}
|
||||
TreeNodeName {
|
||||
composes: theme-text-text-hint-on-light theme-typography--caption from global;
|
||||
height: 100%;
|
||||
max-width: 250px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
portal {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
margin-left: auto !important;
|
||||
margin-right: 8px !important;
|
||||
visibility: hidden;
|
||||
}
|
||||
name-box {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
`;
|
||||
import style from './NavigationNodeProjectControl.m.css';
|
||||
|
||||
export const NavigationNodeProjectControl: NavTreeControlComponent = observer<NavTreeControlProps, HTMLDivElement>(
|
||||
forwardRef(function NavigationNodeProjectControl({ node, dndElement, dndPlaceholder, className }, ref) {
|
||||
const styles = useS(style);
|
||||
const mouseContextMenu = useMouseContextMenu();
|
||||
const viewContext = useContext(CaptureViewContext);
|
||||
const elementsTreeContext = useContext(ElementsTreeContext);
|
||||
@@ -125,26 +93,22 @@ export const NavigationNodeProjectControl: NavTreeControlComponent = observer<Na
|
||||
return null;
|
||||
}
|
||||
|
||||
return styled(
|
||||
TREE_NODE_STYLES,
|
||||
styles,
|
||||
)(
|
||||
return (
|
||||
<TreeNodeControl
|
||||
ref={ref}
|
||||
onClick={handleClick}
|
||||
onContextMenu={handleContextMenuOpen}
|
||||
{...use({ outdated, dragging: dndElement })}
|
||||
className={className}
|
||||
className={s(styles, { treeNodeControl: true, outdated, }, className)}
|
||||
>
|
||||
<TreeNodeName title={name}>
|
||||
<name-box>{name}</name-box>
|
||||
<TreeNodeName title={name} className={s(styles, { treeNodeName: true })}>
|
||||
<div className={s(styles, { nameBox: true })}>{name}</div>
|
||||
</TreeNodeName>
|
||||
{!dndPlaceholder && (
|
||||
<portal onClick={handlePortalClick}>
|
||||
<div onClick={handlePortalClick} className={s(styles, { portal: true })}>
|
||||
<TreeNodeMenuLoader mouseContextMenu={mouseContextMenu} node={node} selected={selected} />
|
||||
</portal>
|
||||
</div>
|
||||
)}
|
||||
</TreeNodeControl>,
|
||||
</TreeNodeControl>
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
.projectNode {
|
||||
margin-top: 8px;
|
||||
|
||||
&.project:only-child,
|
||||
&.hideProjects {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
& > .navigationNodeNested {
|
||||
padding-left: 0 !important;
|
||||
}
|
||||
}
|
||||
+36
-35
@@ -7,9 +7,8 @@
|
||||
*/
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useContext } from 'react';
|
||||
import styled, { css, use } from 'reshadow';
|
||||
|
||||
import { getComputed, Translate, TREE_NODE_STYLES, TreeNodeNestedMessage } from '@cloudbeaver/core-blocks';
|
||||
import { getComputed, s, SContext, StyleRegistry, Translate, TreeNodeNestedMessage, useS } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import type { NavNodeInfoResource, ProjectsNavNodeService } from '@cloudbeaver/core-navigation-tree';
|
||||
import { ProjectsService } from '@cloudbeaver/core-projects';
|
||||
@@ -19,32 +18,33 @@ import {
|
||||
ElementsTreeContext,
|
||||
type IElementsTreeCustomRenderer,
|
||||
isDraggingInsideProject,
|
||||
NavigationNodeControlRendererStyles,
|
||||
NavigationNodeNestedStyles,
|
||||
type NavigationNodeRendererComponent,
|
||||
NavigationNodeRendererLoader,
|
||||
useNode,
|
||||
} from '@cloudbeaver/plugin-navigation-tree';
|
||||
|
||||
import { NavigationNodeProjectControl } from './NavigationNodeProjectControl';
|
||||
import type { ResourceManagerService } from '@cloudbeaver/plugin-resource-manager';
|
||||
|
||||
const nestedStyles = css`
|
||||
TreeNode {
|
||||
margin-top: 8px;
|
||||
import { NavigationNodeProjectControl } from './NavigationNodeProjectControl';
|
||||
import style from './NavigationTreeProjectsRendererRenderer.m.css';
|
||||
|
||||
&[|project]:only-child,
|
||||
&[|hideProjects] {
|
||||
margin-top: 0px;
|
||||
|
||||
& Control {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
& NavigationNodeNested {
|
||||
padding-left: 0 !important;
|
||||
}
|
||||
}
|
||||
`;
|
||||
const registry: StyleRegistry = [
|
||||
[
|
||||
NavigationNodeNestedStyles,
|
||||
{
|
||||
mode: 'append',
|
||||
styles: [style],
|
||||
},
|
||||
],
|
||||
[
|
||||
NavigationNodeControlRendererStyles,
|
||||
{
|
||||
mode: 'append',
|
||||
styles: [style],
|
||||
},
|
||||
],
|
||||
];
|
||||
|
||||
export function navigationTreeProjectsRendererRenderer(
|
||||
navNodeInfoResource: NavNodeInfoResource,
|
||||
@@ -88,6 +88,7 @@ const ProjectRenderer: NavigationNodeRendererComponent = observer(function Manag
|
||||
className,
|
||||
expanded,
|
||||
}) {
|
||||
const styles = useS(style);
|
||||
const projectsService = useService(ProjectsService);
|
||||
const elementsTreeContext = useContext(ElementsTreeContext);
|
||||
|
||||
@@ -105,26 +106,26 @@ const ProjectRenderer: NavigationNodeRendererComponent = observer(function Manag
|
||||
const singleProject = projectsService.activeProjects.length === 1;
|
||||
|
||||
if (!node) {
|
||||
return styled(TREE_NODE_STYLES)(
|
||||
return (
|
||||
<TreeNodeNestedMessage>
|
||||
<Translate token="app_navigationTree_node_not_found" />
|
||||
</TreeNodeNestedMessage>,
|
||||
</TreeNodeNestedMessage>
|
||||
);
|
||||
}
|
||||
|
||||
const project = node.nodeType === NAV_NODE_TYPE_RM_PROJECT && singleProject && !isDragging;
|
||||
|
||||
return styled(nestedStyles)(
|
||||
<NavigationNodeRendererLoader
|
||||
node={node}
|
||||
path={path}
|
||||
expanded={expanded}
|
||||
dragging={dragging}
|
||||
className={className}
|
||||
control={NavigationNodeProjectControl}
|
||||
style={nestedStyles}
|
||||
component={component}
|
||||
{...use({ hideProjects, project })}
|
||||
/>,
|
||||
return (
|
||||
<SContext registry={registry}>
|
||||
<NavigationNodeRendererLoader
|
||||
node={node}
|
||||
path={path}
|
||||
expanded={expanded}
|
||||
dragging={dragging}
|
||||
className={s(styles, { projectNode: true, hideProjects, project }, className)}
|
||||
control={NavigationNodeProjectControl}
|
||||
component={component}
|
||||
/>
|
||||
</SContext>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
.captureView {
|
||||
outline: none;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
}
|
||||
.elementsTreeLoader {
|
||||
min-width: 100%;
|
||||
width: max-content;
|
||||
}
|
||||
.loader {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
min-width: 240px;
|
||||
}
|
||||
.center {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
}
|
||||
.message {
|
||||
padding: 24px;
|
||||
box-sizing: border-box;
|
||||
max-width: 240px;
|
||||
text-align: center;
|
||||
margin: auto;
|
||||
}
|
||||
@@ -7,9 +7,8 @@
|
||||
*/
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useMemo } from 'react';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { getComputed, useResource, useUserData } from '@cloudbeaver/core-blocks';
|
||||
import { getComputed, s, useResource, useS, useUserData } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { NavNodeInfoResource, NavTreeResource, ProjectsNavNodeService } from '@cloudbeaver/core-navigation-tree';
|
||||
import { ProjectInfoResource, ProjectsService } from '@cloudbeaver/core-projects';
|
||||
@@ -25,6 +24,7 @@ import {
|
||||
NavigationTreeService,
|
||||
validateElementsTreeSettings,
|
||||
} from '@cloudbeaver/plugin-navigation-tree';
|
||||
import { ResourceManagerService } from '@cloudbeaver/plugin-resource-manager';
|
||||
|
||||
import { navigationTreeProjectFilter } from './ProjectsRenderer/navigationTreeProjectFilter';
|
||||
import { navigationTreeProjectSearchCompare } from './ProjectsRenderer/navigationTreeProjectSearchCompare';
|
||||
@@ -33,47 +33,16 @@ import { navigationTreeProjectsRendererRenderer } from './ProjectsRenderer/navig
|
||||
import { navigationTreeResourceTypeFilter } from './ProjectsRenderer/navigationTreeResourceTypeFilter';
|
||||
import { ProjectsSettingsPlaceholderElement } from './ProjectsRenderer/ProjectsSettingsForm';
|
||||
import { navigationTreeResourceExpandStateGetter } from './ResourceFolderRenderer/navigationTreeResourceExpandStateGetter';
|
||||
import style from './ResourceManagerTree.m.css';
|
||||
import { ResourceManagerTreeCaptureViewContext } from './ResourceManagerTreeCaptureViewContext';
|
||||
import { transformResourceNodeInfo } from './ResourceRenderer/transformResourceNodeInfo';
|
||||
import { ResourceManagerService } from '@cloudbeaver/plugin-resource-manager';
|
||||
|
||||
const styles = css`
|
||||
CaptureView {
|
||||
outline: none;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
}
|
||||
ElementsTreeLoader {
|
||||
min-width: 100%;
|
||||
width: max-content;
|
||||
}
|
||||
Loader {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
min-width: 240px;
|
||||
}
|
||||
center {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
}
|
||||
message {
|
||||
padding: 24px;
|
||||
box-sizing: border-box;
|
||||
max-width: 240px;
|
||||
text-align: center;
|
||||
margin: auto;
|
||||
}
|
||||
`;
|
||||
|
||||
interface Props extends React.PropsWithChildren {
|
||||
resourceTypeId?: string;
|
||||
}
|
||||
|
||||
export const ResourceManagerTree: React.FC<Props> = observer(function ResourceManagerTree({ resourceTypeId, children }) {
|
||||
const styles = useS(style);
|
||||
const root = RESOURCES_NODE_PATH;
|
||||
|
||||
const projectsNavNodeService = useService(ProjectsNavNodeService);
|
||||
@@ -131,8 +100,8 @@ export const ResourceManagerTree: React.FC<Props> = observer(function ResourceMa
|
||||
|
||||
const settingsElements = useMemo(() => [ProjectsSettingsPlaceholderElement], []);
|
||||
|
||||
return styled(styles)(
|
||||
<CaptureView view={navTreeService}>
|
||||
return (
|
||||
<CaptureView view={navTreeService} className={s(styles, { captureView: true })}>
|
||||
<ResourceManagerTreeCaptureViewContext resourceTypeId={resourceTypeId} />
|
||||
<ElementsTreeLoader
|
||||
root={root}
|
||||
@@ -145,15 +114,14 @@ export const ResourceManagerTree: React.FC<Props> = observer(function ResourceMa
|
||||
expandStateGetters={[projectsExpandStateGetter, resourceExpandStateGetter]}
|
||||
navNodeFilterCompare={navigationTreeProjectSearchCompare}
|
||||
settingsElements={settingsElements}
|
||||
emptyPlaceholder={() =>
|
||||
styled(styles)(
|
||||
<center>
|
||||
<message>{children}</message>
|
||||
</center>,
|
||||
)
|
||||
}
|
||||
className={s(styles, { elementsTreeLoader: true })}
|
||||
emptyPlaceholder={() => (
|
||||
<div className={s(styles, { center: true })}>
|
||||
<div className={s(styles, { message: true })}>{children}</div>
|
||||
</div>
|
||||
)}
|
||||
onOpen={node => navTreeService.navToNode(node.id, node.parentId)}
|
||||
/>
|
||||
</CaptureView>,
|
||||
</CaptureView>
|
||||
);
|
||||
});
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
.connectionGroup {
|
||||
composes: theme-text-text-hint-on-light theme-typography--caption from global;
|
||||
padding: 4px 12px;
|
||||
|
||||
&:not(:first-child) {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
+10
-20
@@ -5,12 +5,11 @@
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { Translate } from '@cloudbeaver/core-blocks';
|
||||
import { s, Translate, useS } from '@cloudbeaver/core-blocks';
|
||||
|
||||
import type { NavigationNodeRendererComponent } from '../ElementsTree/NavigationNodeComponent';
|
||||
import { NAVIGATION_TREE_CONNECTION_GROUPS } from './navigationTreeConnectionGroupFilter';
|
||||
import style from './NavigationTreeConnectionGroupRenderer.m.css';
|
||||
|
||||
export function navigationTreeConnectionGroupRenderer(nodeId: string): NavigationNodeRendererComponent | undefined {
|
||||
if (nodeId !== NAVIGATION_TREE_CONNECTION_GROUPS.manageable && nodeId !== NAVIGATION_TREE_CONNECTION_GROUPS.unmanageable) {
|
||||
@@ -24,28 +23,19 @@ export function navigationTreeConnectionGroupRenderer(nodeId: string): Navigatio
|
||||
return UnManageableGroup;
|
||||
}
|
||||
|
||||
const styles = css`
|
||||
connection-group {
|
||||
composes: theme-text-text-hint-on-light theme-typography--caption from global;
|
||||
padding: 4px 12px;
|
||||
|
||||
&:not(:first-child) {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const ManageableGroup: NavigationNodeRendererComponent = function ManageableGroup() {
|
||||
return styled(styles)(
|
||||
<connection-group>
|
||||
const styles = useS(style);
|
||||
return (
|
||||
<div className={s(styles, { connectionGroup: true })}>
|
||||
<Translate token="app_navigationTree_connection_group_user" />
|
||||
</connection-group>,
|
||||
</div>
|
||||
);
|
||||
};
|
||||
const UnManageableGroup: NavigationNodeRendererComponent = function UnManageableGroup() {
|
||||
return styled(styles)(
|
||||
<connection-group>
|
||||
const styles = useS(style);
|
||||
return (
|
||||
<div className={s(styles, { connectionGroup: true })}>
|
||||
<Translate token="app_navigationTree_connection_group_shared" />
|
||||
</connection-group>,
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
+17
-14
@@ -7,30 +7,31 @@
|
||||
*/
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import React, { forwardRef, useContext } from 'react';
|
||||
import styled, { use } from 'reshadow';
|
||||
|
||||
import {
|
||||
ConnectionImageWithMask,
|
||||
getComputed,
|
||||
Loader,
|
||||
TREE_NODE_STYLES,
|
||||
s,
|
||||
TreeNodeContext,
|
||||
TreeNodeControl,
|
||||
TreeNodeIcon,
|
||||
TreeNodeName,
|
||||
useMouseContextMenu,
|
||||
useS,
|
||||
} from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { EventContext, EventStopPropagationFlag } from '@cloudbeaver/core-events';
|
||||
import { EObjectFeature, NavNodeInfoResource, NavTreeResource } from '@cloudbeaver/core-navigation-tree';
|
||||
|
||||
import type { NavTreeControlComponent, NavTreeControlProps } from '../ElementsTree/NavigationNodeComponent';
|
||||
import { NAVIGATION_NODE_CONTROL_STYLES } from '../ElementsTree/NavigationTreeNode/NavigationNode/NAVIGATION_NODE_CONTROL_STYLES';
|
||||
import style from '../ElementsTree/NavigationTreeNode/NavigationNode/NavigationNodeControl.m.css';
|
||||
import { NavigationNodeExpand } from '../ElementsTree/NavigationTreeNode/NavigationNode/NavigationNodeExpand';
|
||||
import { TreeNodeMenuLoader } from '../ElementsTree/NavigationTreeNode/TreeNodeMenu/TreeNodeMenuLoader';
|
||||
|
||||
export const ConnectionNavNodeControl: NavTreeControlComponent = observer<NavTreeControlProps, HTMLDivElement>(
|
||||
forwardRef(function ConnectionNavNodeControl({ node, nodeInfo, dndElement, dndPlaceholder, className, onClick }, ref) {
|
||||
const styles = useS(style);
|
||||
const mouseContextMenu = useMouseContextMenu();
|
||||
const treeNodeContext = useContext(TreeNodeContext);
|
||||
const navNodeInfoResource = useService(NavNodeInfoResource);
|
||||
@@ -58,26 +59,28 @@ export const ConnectionNavNodeControl: NavTreeControlComponent = observer<NavTre
|
||||
treeNodeContext.select();
|
||||
}
|
||||
|
||||
return styled(
|
||||
TREE_NODE_STYLES,
|
||||
NAVIGATION_NODE_CONTROL_STYLES,
|
||||
)(
|
||||
<TreeNodeControl ref={ref} className={className} onClick={onClick} onContextMenu={handleContextMenuOpen} {...use({ dragging: dndElement })}>
|
||||
return (
|
||||
<TreeNodeControl
|
||||
ref={ref}
|
||||
className={s(styles, { treeNodeControl: true, dragging: !!dndElement }, className)}
|
||||
onClick={onClick}
|
||||
onContextMenu={handleContextMenuOpen}
|
||||
>
|
||||
<NavigationNodeExpand nodeId={node.id} />
|
||||
<TreeNodeIcon {...use({ connected })}>
|
||||
<TreeNodeIcon>
|
||||
<ConnectionImageWithMask icon={icon} connected={connected} maskId="tree-node-icon" />
|
||||
</TreeNodeIcon>
|
||||
<TreeNodeName title={title}>
|
||||
<TreeNodeName title={title} className={s(styles, { treeNodeName: true })}>
|
||||
<Loader suspense inline fullSize>
|
||||
<name-box>{name}</name-box>
|
||||
<div className={s(styles, { nameBox: true })}>{name}</div>
|
||||
</Loader>
|
||||
</TreeNodeName>
|
||||
{!dndPlaceholder && (
|
||||
<portal onClick={handlePortalClick}>
|
||||
<div className={s(styles, { portal: true })} onClick={handlePortalClick}>
|
||||
<TreeNodeMenuLoader mouseContextMenu={mouseContextMenu} node={node} selected={selected} />
|
||||
</portal>
|
||||
</div>
|
||||
)}
|
||||
</TreeNodeControl>,
|
||||
</TreeNodeControl>
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
+3
-4
@@ -6,9 +6,8 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import styled from 'reshadow';
|
||||
|
||||
import { Translate, TREE_NODE_STYLES, TreeNodeNestedMessage } from '@cloudbeaver/core-blocks';
|
||||
import { Translate, TreeNodeNestedMessage } from '@cloudbeaver/core-blocks';
|
||||
import { NAV_NODE_TYPE_CONNECTION } from '@cloudbeaver/core-connections';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { NavNodeInfoResource } from '@cloudbeaver/core-navigation-tree';
|
||||
@@ -42,10 +41,10 @@ const ConnectionRenderer: NavigationNodeRendererComponent = observer(function Ma
|
||||
const node = navNodeInfoResource.get(nodeId);
|
||||
|
||||
if (!node) {
|
||||
return styled(TREE_NODE_STYLES)(
|
||||
return (
|
||||
<TreeNodeNestedMessage>
|
||||
<Translate token="app_navigationTree_node_not_found" />
|
||||
</TreeNodeNestedMessage>,
|
||||
</TreeNodeNestedMessage>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+51
@@ -1,3 +1,54 @@
|
||||
.box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tree {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
|
||||
& .treeElements {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.treeBox {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
width: 250px;
|
||||
min-width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.folderExplorerPath {
|
||||
padding: 0 4px 8px 4px;
|
||||
}
|
||||
|
||||
.dropOutside {
|
||||
composes: theme-border-color-background from global;
|
||||
border: dashed 2px;
|
||||
border-radius: var(--theme-group-element-radius);
|
||||
margin: 12px;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
|
||||
&.bottom {
|
||||
order: 2;
|
||||
}
|
||||
|
||||
&:not(.showDropOutside) {
|
||||
display: none;
|
||||
}
|
||||
&.active {
|
||||
border-color: var(--theme-positive) !important;
|
||||
}
|
||||
}
|
||||
|
||||
+18
-78
@@ -7,8 +7,6 @@
|
||||
*/
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import styled, { css, use } from 'reshadow';
|
||||
import elementsTreeStyles from './ElementsTree.m.css';
|
||||
|
||||
import {
|
||||
EventTreeNodeClickFlag,
|
||||
@@ -19,18 +17,16 @@ import {
|
||||
PlaceholderElement,
|
||||
s,
|
||||
Translate,
|
||||
TREE_NODE_STYLES,
|
||||
TreeNodeNested,
|
||||
TreeNodeNestedMessage,
|
||||
useS,
|
||||
useStyles,
|
||||
} from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { EventContext, EventStopPropagationFlag } from '@cloudbeaver/core-events';
|
||||
import { EObjectFeature, type NavNode, NavNodeInfoResource, NavTreeResource, ROOT_NODE_PATH } from '@cloudbeaver/core-navigation-tree';
|
||||
import type { ComponentStyle } from '@cloudbeaver/core-theming';
|
||||
|
||||
import { useNavTreeDropBox } from '../useNavTreeDropBox';
|
||||
import style from './ElementsTree.m.css';
|
||||
import { ElementsTreeContentLoader } from './ElementsTreeContentLoader';
|
||||
import { ElementsTreeContext, IElementsTreeContext } from './ElementsTreeContext';
|
||||
import { elementsTreeNameFilter } from './elementsTreeNameFilter';
|
||||
@@ -46,66 +42,11 @@ import { useDropOutside } from './useDropOutside';
|
||||
import { IElementsTreeOptions, useElementsTree } from './useElementsTree';
|
||||
import { useElementsTreeFolderExplorer } from './useElementsTreeFolderExplorer';
|
||||
|
||||
const oldStyles = css`
|
||||
box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
tree {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
|
||||
& tree-elements {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
tree-box {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
width: 250px;
|
||||
min-width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
drop-outside {
|
||||
composes: theme-border-color-background from global;
|
||||
border: dashed 2px;
|
||||
border-radius: var(--theme-group-element-radius);
|
||||
margin: 12px;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
|
||||
&[|bottom] {
|
||||
order: 2;
|
||||
}
|
||||
|
||||
&:not([|showDropOutside]) {
|
||||
display: none;
|
||||
}
|
||||
&[|active] {
|
||||
border-color: var(--theme-positive) !important;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export interface ElementsTreeProps extends IElementsTreeOptions, React.PropsWithChildren {
|
||||
root?: string;
|
||||
selectionTree?: boolean;
|
||||
control?: NavTreeControlComponent;
|
||||
emptyPlaceholder?: React.FC;
|
||||
big?: boolean;
|
||||
style?: ComponentStyle;
|
||||
className?: string;
|
||||
settingsElements?: PlaceholderElement<IElementsTreeSettingsProps>[];
|
||||
navNodeFilterCompare?: NavNodeFilterCompareFn;
|
||||
@@ -127,8 +68,6 @@ export const ElementsTree = observer<ElementsTreeProps>(function ElementsTree({
|
||||
renderers = [],
|
||||
expandStateGetters,
|
||||
settingsElements,
|
||||
big,
|
||||
style,
|
||||
className,
|
||||
getChildren,
|
||||
loadChildren,
|
||||
@@ -142,7 +81,7 @@ export const ElementsTree = observer<ElementsTreeProps>(function ElementsTree({
|
||||
onSelect,
|
||||
onFilter,
|
||||
}) {
|
||||
const styles = useS(elementsTreeStyles);
|
||||
const styles = useS(style);
|
||||
const navTreeResource = useService(NavTreeResource);
|
||||
const navNodeInfoResource = useService(NavNodeInfoResource);
|
||||
const [treeRootRef, setTreeRootRef] = useState<HTMLDivElement | null>(null);
|
||||
@@ -217,22 +156,23 @@ export const ElementsTree = observer<ElementsTreeProps>(function ElementsTree({
|
||||
tree.resetSelection();
|
||||
}
|
||||
|
||||
return styled(useStyles(TREE_NODE_STYLES, oldStyles, style))(
|
||||
return (
|
||||
<>
|
||||
<ElementsTreeTools tree={tree} settingsElements={settingsElements} style={style} />
|
||||
<tree-box ref={setTreeRootRef} {...use({ big })}>
|
||||
<ElementsTreeTools tree={tree} settingsElements={settingsElements} />
|
||||
<div ref={setTreeRootRef} className={s(styles, { treeBox: true })}>
|
||||
<ElementsTreeContext.Provider value={context}>
|
||||
<box className={className}>
|
||||
<div className={s(styles, { box: true }, className)}>
|
||||
<FolderExplorer state={folderExplorer}>
|
||||
<tree ref={dropOutside.mouse.reference} onClick={handleClick}>
|
||||
<div ref={dropOutside.mouse.reference} className={s(styles, { tree: true })} onClick={handleClick}>
|
||||
{settings?.showFolderExplorerPath && (
|
||||
<FolderExplorerPath className={s(styles, { folderExplorerPath: true })} getName={getName} canSkip={canSkip} />
|
||||
)}
|
||||
<drop-outside
|
||||
<div
|
||||
ref={dndBox.setRef}
|
||||
{...use({
|
||||
className={s(styles, {
|
||||
dropOutside: true,
|
||||
showDropOutside: dropOutside.showDropOutside,
|
||||
active: dropOutside.zoneActive,
|
||||
active: !!dropOutside.zoneActive,
|
||||
bottom: dropOutside.bottom,
|
||||
})}
|
||||
>
|
||||
@@ -241,9 +181,9 @@ export const ElementsTree = observer<ElementsTreeProps>(function ElementsTree({
|
||||
<Translate token="app_navigationTree_drop_here" />
|
||||
</TreeNodeNestedMessage>
|
||||
</TreeNodeNested>
|
||||
</drop-outside>
|
||||
</div>
|
||||
<ElementsTreeContentLoader context={context} emptyPlaceholder={emptyPlaceholder} childrenState={tree}>
|
||||
<tree-elements>
|
||||
<div className={s(styles, { treeElements: true })}>
|
||||
<NavigationNodeNested
|
||||
ref={dropOutside.nestedRef}
|
||||
nodeId={root}
|
||||
@@ -251,13 +191,13 @@ export const ElementsTree = observer<ElementsTreeProps>(function ElementsTree({
|
||||
path={folderExplorer.state.path}
|
||||
root
|
||||
/>
|
||||
</tree-elements>
|
||||
</div>
|
||||
</ElementsTreeContentLoader>
|
||||
</tree>
|
||||
</div>
|
||||
</FolderExplorer>
|
||||
</box>
|
||||
</div>
|
||||
</ElementsTreeContext.Provider>
|
||||
</tree-box>
|
||||
</>,
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
+3
-4
@@ -6,9 +6,8 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import styled from 'reshadow';
|
||||
|
||||
import { getComputed, Loader, Translate, TREE_NODE_STYLES, TreeNodeNestedMessage } from '@cloudbeaver/core-blocks';
|
||||
import { getComputed, Loader, Translate, TreeNodeNestedMessage } from '@cloudbeaver/core-blocks';
|
||||
import type { ILoadableState } from '@cloudbeaver/core-utils';
|
||||
|
||||
import type { IElementsTreeContext } from './ElementsTreeContext';
|
||||
@@ -33,10 +32,10 @@ export const ElementsTreeContentLoader = observer<React.PropsWithChildren<Props>
|
||||
if (loading) {
|
||||
return <Loader />;
|
||||
} else if (context.tree.filtering) {
|
||||
return styled(TREE_NODE_STYLES)(
|
||||
return (
|
||||
<TreeNodeNestedMessage>
|
||||
<Translate token="app_navigationTree_node_no_results" filter={context.tree.filter} />
|
||||
</TreeNodeNestedMessage>,
|
||||
</TreeNodeNestedMessage>
|
||||
);
|
||||
} else if (context.folderExplorer.root === context.folderExplorer.state.folder) {
|
||||
return <>{Placeholder && <Placeholder />}</>;
|
||||
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
.filterBox {
|
||||
composes: theme-background-surface from global;
|
||||
padding: 8px 12px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
+6
-14
@@ -6,20 +6,12 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { Filter, useFocus, useStyles, useTranslate } from '@cloudbeaver/core-blocks';
|
||||
import { Filter, s, useFocus, useS, useTranslate } from '@cloudbeaver/core-blocks';
|
||||
import type { ComponentStyle } from '@cloudbeaver/core-theming';
|
||||
|
||||
import type { IElementsTree } from '../useElementsTree';
|
||||
|
||||
const filterStyle = css`
|
||||
filter-box {
|
||||
composes: theme-background-surface from global;
|
||||
padding: 8px 12px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
`;
|
||||
import styles from './ElementsTreeFilter.m.css';
|
||||
|
||||
interface Props {
|
||||
tree: IElementsTree;
|
||||
@@ -30,15 +22,15 @@ interface Props {
|
||||
export const ElementsTreeFilter = observer<Props>(function ElementsTreeFilter({ tree, style, className }) {
|
||||
const [focusedRef] = useFocus<HTMLDivElement>({ focusFirstChild: true });
|
||||
const translate = useTranslate();
|
||||
const styles = useStyles(filterStyle, style);
|
||||
const computedStyles = useS(styles, style);
|
||||
|
||||
if (!tree.settings?.filter) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return styled(styles)(
|
||||
<filter-box ref={focusedRef} className={className} as="div">
|
||||
return (
|
||||
<div ref={focusedRef} className={s(computedStyles, { filterBox: true }, className)}>
|
||||
<Filter placeholder={translate('app_navigationTree_search')} value={tree.filter} max onFilter={value => tree.setFilter(value as string)} />
|
||||
</filter-box>,
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
+9
-9
@@ -10,7 +10,7 @@ import React, { useState } from 'react';
|
||||
import styled from 'reshadow';
|
||||
|
||||
import {
|
||||
ACTION_ICON_BUTTON_STYLES,
|
||||
ActionIconButtonStyles,
|
||||
Fill,
|
||||
IconButton,
|
||||
IconButtonStyles,
|
||||
@@ -55,8 +55,8 @@ export const ElementsTreeTools = observer<React.PropsWithChildren<Props>>(functi
|
||||
const root = tree.root;
|
||||
const translate = useTranslate();
|
||||
const [opened, setOpen] = useState(false);
|
||||
const deprecatedStyles = useStyles(ACTION_ICON_BUTTON_STYLES, style);
|
||||
const styles = useS(ElementsTreeToolsStyles, ElementsTreeToolsIconButtonStyles);
|
||||
const deprecatedStyles = useStyles(style);
|
||||
const styles = useS(ElementsTreeToolsStyles, ElementsTreeToolsIconButtonStyles, ActionIconButtonStyles);
|
||||
|
||||
useCaptureViewContext(context => {
|
||||
context?.set(DATA_CONTEXT_NAV_TREE_ROOT, tree.baseRoot);
|
||||
@@ -67,13 +67,13 @@ export const ElementsTreeTools = observer<React.PropsWithChildren<Props>>(functi
|
||||
|
||||
return styled(deprecatedStyles)(
|
||||
<SContext registry={registry}>
|
||||
<tools className={s(styles, { tools: true })}>
|
||||
<actions className={s(styles, { actions: true })}>
|
||||
<div className={s(styles, { tools: true })}>
|
||||
<div className={s(styles, { actions: true })}>
|
||||
{tree.settings?.configurable && (
|
||||
<IconButton
|
||||
name="/icons/settings_cog_sm.svg"
|
||||
title={translate('ui_settings')}
|
||||
className={s(styles, { primary: true, opened })}
|
||||
className={s(styles, { primary: true, actionIconButton: true, opened })}
|
||||
img
|
||||
onClick={() => setOpen(!opened)}
|
||||
/>
|
||||
@@ -84,15 +84,15 @@ export const ElementsTreeTools = observer<React.PropsWithChildren<Props>>(functi
|
||||
name="/icons/refresh_sm.svg#root"
|
||||
title={translate('app_navigationTree_refresh')}
|
||||
disabled={loading}
|
||||
className={s(styles, { primary: true, loading })}
|
||||
className={s(styles, { primary: true, actionIconButton: true, loading })}
|
||||
img
|
||||
onClick={() => tree.refresh(root)}
|
||||
/>
|
||||
</actions>
|
||||
</div>
|
||||
{tree.settings && opened && <NavigationTreeSettings tree={tree} elements={settingsElements} style={style} />}
|
||||
<ElementsTreeFilter tree={tree} style={style} />
|
||||
{children}
|
||||
</tools>
|
||||
</div>
|
||||
</SContext>,
|
||||
);
|
||||
});
|
||||
|
||||
+1
@@ -17,6 +17,7 @@ export type NavTreeNodeComponent = React.FC<{
|
||||
path: string[];
|
||||
dragging?: boolean;
|
||||
expanded?: boolean;
|
||||
big?: boolean;
|
||||
className?: string;
|
||||
}>;
|
||||
|
||||
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
.treeNode {
|
||||
&.hovered ::before {
|
||||
opacity: 0.16;
|
||||
}
|
||||
|
||||
&.expanded > .navNodeNested {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
+8
-22
@@ -7,9 +7,8 @@
|
||||
*/
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useDeferredValue, useEffect } from 'react';
|
||||
import styled, { css, use } from 'reshadow';
|
||||
|
||||
import { getComputed, TreeNode, useMergeRefs, useStyles } from '@cloudbeaver/core-blocks';
|
||||
import { getComputed, s, TreeNode, useMergeRefs, useS } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { DATA_CONTEXT_NAV_NODE, DATA_CONTEXT_NAV_NODES, NavNodeManagerService } from '@cloudbeaver/core-navigation-tree';
|
||||
import { useDNDData } from '@cloudbeaver/core-ui';
|
||||
@@ -17,17 +16,12 @@ import { useDataContext } from '@cloudbeaver/core-view';
|
||||
|
||||
import { useNavTreeDropBox } from '../../useNavTreeDropBox';
|
||||
import type { NavigationNodeComponent } from '../NavigationNodeComponent';
|
||||
import style from './NavigationNode.m.css';
|
||||
import { DATA_ATTRIBUTE_NODE_EDITING } from './NavigationNode/DATA_ATTRIBUTE_NODE_EDITING';
|
||||
import { NavigationNodeNested } from './NavigationNode/NavigationNodeNested';
|
||||
import { NavigationNodeControlRenderer } from './NavigationNodeControlRenderer';
|
||||
import { useNavigationNode } from './useNavigationNode';
|
||||
|
||||
const styles = css`
|
||||
TreeNode[|hovered] ::before {
|
||||
opacity: 0.16;
|
||||
}
|
||||
`;
|
||||
|
||||
export const NavigationNode: NavigationNodeComponent = observer(function NavigationNode({
|
||||
node,
|
||||
component,
|
||||
@@ -35,8 +29,8 @@ export const NavigationNode: NavigationNodeComponent = observer(function Navigat
|
||||
control: externalControl,
|
||||
expanded: externalExpanded,
|
||||
className,
|
||||
style,
|
||||
}) {
|
||||
const styles = useS(style);
|
||||
const navNodeManagerService = useService(NavNodeManagerService);
|
||||
const navNode = useNavigationNode(node, path);
|
||||
const context = useDataContext();
|
||||
@@ -87,7 +81,7 @@ export const NavigationNode: NavigationNodeComponent = observer(function Navigat
|
||||
[],
|
||||
);
|
||||
|
||||
return styled(useStyles(style, styles))(
|
||||
return (
|
||||
<TreeNode
|
||||
ref={dndBox.setRef}
|
||||
group={navNode.group}
|
||||
@@ -99,23 +93,15 @@ export const NavigationNode: NavigationNodeComponent = observer(function Navigat
|
||||
showInFilter={navNode.showInFilter}
|
||||
externalExpanded={externalExpanded}
|
||||
leaf={navNode.leaf}
|
||||
className={className}
|
||||
className={s(styles, { treeNode: true, hovered: hasNodes, expanded: externalExpanded ?? navNode.expanded }, className)}
|
||||
onExpand={navNode.expand}
|
||||
onClick={navNode.click}
|
||||
onOpen={navNode.open}
|
||||
onSelect={navNode.select}
|
||||
{...use({ hovered: hasNodes })}
|
||||
>
|
||||
{/* <DNDPreview data={dndData} src="/icons/empty.svg" /> */}
|
||||
<NavigationNodeControlRenderer
|
||||
ref={controlRef}
|
||||
navNode={navNode}
|
||||
dragging={dndData.state.isDragging}
|
||||
control={externalControl}
|
||||
style={style}
|
||||
node={node}
|
||||
/>
|
||||
{expanded && <NavigationNodeNested nodeId={node.id} path={path} component={component} />}
|
||||
</TreeNode>,
|
||||
<NavigationNodeControlRenderer ref={controlRef} navNode={navNode} dragging={dndData.state.isDragging} control={externalControl} node={node} />
|
||||
{expanded && <NavigationNodeNested nodeId={node.id} path={path} component={component} className={s(styles, { navNodeNested: true })} />}
|
||||
</TreeNode>
|
||||
);
|
||||
});
|
||||
|
||||
-39
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2023 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { css } from 'reshadow';
|
||||
|
||||
export const NAVIGATION_NODE_CONTROL_STYLES = css`
|
||||
TreeNodeControl:hover > portal,
|
||||
TreeNodeControl:global([aria-selected='true']) > portal,
|
||||
portal:focus-within {
|
||||
visibility: visible;
|
||||
}
|
||||
TreeNodeName {
|
||||
height: 100%;
|
||||
max-width: 320px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
&[|editing] {
|
||||
padding: 0;
|
||||
overflow: visible;
|
||||
margin-left: 2px;
|
||||
}
|
||||
}
|
||||
portal {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
margin-left: auto !important;
|
||||
margin-right: 8px !important;
|
||||
visibility: hidden;
|
||||
}
|
||||
name-box {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
`;
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
.treeNodeControl.dragging {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.treeNodeControl.editing {
|
||||
&::before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
& .treeNodeName {
|
||||
padding: 0;
|
||||
overflow: visible;
|
||||
margin-left: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.treeNodeControl:hover > .portal,
|
||||
.treeNodeControl[aria-selected='true'] > .portal,
|
||||
.portal:focus-within {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.treeNodeName {
|
||||
height: 100%;
|
||||
max-width: 320px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.portal {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
margin-left: auto !important;
|
||||
margin-right: 8px !important;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.nameBox {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
+11
-14
@@ -8,12 +8,11 @@
|
||||
import { action, observable } from 'mobx';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import React, { forwardRef, useContext } from 'react';
|
||||
import styled, { use } from 'reshadow';
|
||||
|
||||
import {
|
||||
getComputed,
|
||||
Loader,
|
||||
TREE_NODE_STYLES,
|
||||
s,
|
||||
TreeNodeContext,
|
||||
TreeNodeControl,
|
||||
TreeNodeIcon,
|
||||
@@ -21,6 +20,7 @@ import {
|
||||
useMouseContextMenu,
|
||||
useObjectRef,
|
||||
useObservableRef,
|
||||
useS,
|
||||
} from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { EventContext, EventStopPropagationFlag } from '@cloudbeaver/core-events';
|
||||
@@ -29,7 +29,7 @@ import { getNodePlainName, type INodeActions, NavNodeInfoResource, NavTreeResour
|
||||
import type { NavTreeControlComponent, NavTreeControlProps } from '../../NavigationNodeComponent';
|
||||
import { TreeNodeMenuLoader } from '../TreeNodeMenu/TreeNodeMenuLoader';
|
||||
import { DATA_ATTRIBUTE_NODE_EDITING } from './DATA_ATTRIBUTE_NODE_EDITING';
|
||||
import { NAVIGATION_NODE_CONTROL_STYLES } from './NAVIGATION_NODE_CONTROL_STYLES';
|
||||
import style from './NavigationNodeControl.m.css';
|
||||
import { NavigationNodeExpand } from './NavigationNodeExpand';
|
||||
import { NavigationNodeEditorLoader } from './NavigationNodeLoaders';
|
||||
|
||||
@@ -49,6 +49,7 @@ interface IEditingState {
|
||||
|
||||
export const NavigationNodeControl: NavTreeControlComponent = observer<NavTreeControlProps, HTMLDivElement>(
|
||||
forwardRef(function NavigationNodeControl({ node, nodeInfo, dndElement, dndPlaceholder, className, onClick }, ref) {
|
||||
const styles = useS(style);
|
||||
const mouseContextMenu = useMouseContextMenu();
|
||||
const treeNodeContext = useContext(TreeNodeContext);
|
||||
const navNodeInfoResource = useService(NavNodeInfoResource);
|
||||
@@ -129,35 +130,31 @@ export const NavigationNodeControl: NavTreeControlComponent = observer<NavTreeCo
|
||||
|
||||
const attributes = { [DATA_ATTRIBUTE_NODE_EDITING]: editing };
|
||||
|
||||
return styled(
|
||||
TREE_NODE_STYLES,
|
||||
NAVIGATION_NODE_CONTROL_STYLES,
|
||||
)(
|
||||
return (
|
||||
<TreeNodeControl
|
||||
ref={ref}
|
||||
{...attributes}
|
||||
className={className}
|
||||
className={s(styles, { treeNodeControl: true, dragging: !!dndElement, editing }, className)}
|
||||
onClick={onClick}
|
||||
onContextMenu={handleContextMenuOpen}
|
||||
{...use({ editing, dragging: dndElement })}
|
||||
>
|
||||
<NavigationNodeExpand nodeId={node.id} />
|
||||
<TreeNodeIcon icon={icon} />
|
||||
<TreeNodeName title={title} {...use({ editing })}>
|
||||
<TreeNodeName title={title} className={s(styles, { treeNodeName: true })}>
|
||||
<Loader suspense inline fullSize>
|
||||
{editing ? (
|
||||
<NavigationNodeEditorLoader name={getNodePlainName(node)} disabled={saving} onSave={editingState.save} onClose={editingState.cancel} />
|
||||
) : (
|
||||
<name-box>{name}</name-box>
|
||||
<div className={s(styles, { nameBox: true })}>{name}</div>
|
||||
)}
|
||||
</Loader>
|
||||
</TreeNodeName>
|
||||
{!editing && !dndPlaceholder && (
|
||||
<portal onClick={handlePortalClick}>
|
||||
<div className={s(styles, { portal: true })} onClick={handlePortalClick}>
|
||||
<TreeNodeMenuLoader mouseContextMenu={mouseContextMenu} node={node} actions={nodeActions} selected={selected} />
|
||||
</portal>
|
||||
</div>
|
||||
)}
|
||||
</TreeNodeControl>,
|
||||
</TreeNodeControl>
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
.inlineEditor {
|
||||
height: 22px !important;
|
||||
|
||||
& input,
|
||||
& input[disabled],
|
||||
& input[readonly] {
|
||||
padding: 1px !important;
|
||||
}
|
||||
}
|
||||
+6
-15
@@ -7,22 +7,12 @@
|
||||
*/
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useState } from 'react';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { s, useS } from '@cloudbeaver/core-blocks';
|
||||
import { EventContext, EventStopPropagationFlag } from '@cloudbeaver/core-events';
|
||||
import { InlineEditor } from '@cloudbeaver/core-ui';
|
||||
|
||||
const styles = css`
|
||||
InlineEditor {
|
||||
height: 22px;
|
||||
|
||||
& input,
|
||||
& input[disabled],
|
||||
& input[readonly] {
|
||||
padding: 1px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
import style from './NavigationNodeEditor.m.css';
|
||||
|
||||
interface Props {
|
||||
name: string;
|
||||
@@ -32,6 +22,7 @@ interface Props {
|
||||
}
|
||||
|
||||
export const NavigationNodeEditor = observer<Props>(function NavigationNodeEditor({ name: initialName, disabled, onSave, onClose }) {
|
||||
const styles = useS(style);
|
||||
const [name, setName] = useState(initialName);
|
||||
const isNameChanged = initialName !== name;
|
||||
const isDisabledSave = disabled || !isNameChanged;
|
||||
@@ -44,13 +35,13 @@ export const NavigationNodeEditor = observer<Props>(function NavigationNodeEdito
|
||||
EventContext.set(event, EventStopPropagationFlag);
|
||||
}
|
||||
|
||||
return styled(styles)(
|
||||
return (
|
||||
<InlineEditor
|
||||
value={name}
|
||||
disabled={disabled}
|
||||
disableSave={isDisabledSave}
|
||||
controlsPosition="inside"
|
||||
style={styles}
|
||||
className={s(styles, { inlineEditor: true })}
|
||||
simple
|
||||
autofocus
|
||||
onChange={setName}
|
||||
@@ -59,6 +50,6 @@ export const NavigationNodeEditor = observer<Props>(function NavigationNodeEdito
|
||||
onReject={onClose}
|
||||
onBlur={onClose}
|
||||
onClick={stopPropagation}
|
||||
/>,
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
+2
-3
@@ -7,9 +7,8 @@
|
||||
*/
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useContext, useDeferredValue } from 'react';
|
||||
import styled from 'reshadow';
|
||||
|
||||
import { getComputed, TREE_NODE_STYLES, TreeNodeExpand } from '@cloudbeaver/core-blocks';
|
||||
import { getComputed, TreeNodeExpand } from '@cloudbeaver/core-blocks';
|
||||
|
||||
import { ElementsTreeContext } from '../../ElementsTreeContext';
|
||||
|
||||
@@ -27,5 +26,5 @@ export const NavigationNodeExpand = observer<Props>(function NavigationNodeExpan
|
||||
return null;
|
||||
}
|
||||
|
||||
return styled(TREE_NODE_STYLES)(<TreeNodeExpand filterActive={filterActive} />);
|
||||
return <TreeNodeExpand filterActive={filterActive} />;
|
||||
});
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
.navigationNodeNested {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
+7
-6
@@ -7,13 +7,13 @@
|
||||
*/
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { forwardRef, useContext, useDeferredValue, useMemo } from 'react';
|
||||
import styled from 'reshadow';
|
||||
|
||||
import { getComputed, TREE_NODE_STYLES, TreeNodeNested, TreeNodeNestedMessage, useTranslate } from '@cloudbeaver/core-blocks';
|
||||
import { getComputed, s, TreeNodeNested, TreeNodeNestedMessage, useS, useTranslate } from '@cloudbeaver/core-blocks';
|
||||
import { isArraysEqual } from '@cloudbeaver/core-utils';
|
||||
|
||||
import { ElementsTreeContext } from '../../ElementsTreeContext';
|
||||
import type { NavTreeNodeComponent } from '../../NavigationNodeComponent';
|
||||
import style from './NavigationNodeNested.m.css';
|
||||
|
||||
interface Props {
|
||||
nodeId?: string;
|
||||
@@ -25,6 +25,7 @@ interface Props {
|
||||
|
||||
export const NavigationNodeNested = observer(
|
||||
forwardRef<HTMLDivElement, Props>(function NavigationNodeNested({ nodeId, component, path, root, className }, ref) {
|
||||
const styles = useS(style);
|
||||
const treeContext = useContext(ElementsTreeContext);
|
||||
const translate = useTranslate();
|
||||
|
||||
@@ -50,11 +51,11 @@ export const NavigationNodeNested = observer(
|
||||
empty = useDeferredValue(empty);
|
||||
|
||||
if (nodeId !== undefined && rootFolder) {
|
||||
return styled(TREE_NODE_STYLES)(<NavigationNode nodeId={nodeId} path={path} expanded />);
|
||||
return <NavigationNode nodeId={nodeId} path={path} expanded />;
|
||||
}
|
||||
|
||||
return styled(TREE_NODE_STYLES)(
|
||||
<TreeNodeNested ref={ref} root={root} className={className}>
|
||||
return (
|
||||
<TreeNodeNested ref={ref} root={root} className={s(styles, { navigationNodeNested: true }, className)}>
|
||||
{children.map(child => (
|
||||
<NavigationNode key={child} nodeId={child} path={nextPath} />
|
||||
))}
|
||||
@@ -63,7 +64,7 @@ export const NavigationNodeNested = observer(
|
||||
{translate(nodeId === undefined ? 'app_navigationTree_connection_group_user' : 'app_navigationTree_node_empty')}
|
||||
</TreeNodeNestedMessage>
|
||||
)}
|
||||
</TreeNodeNested>,
|
||||
</TreeNodeNested>
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
.control {
|
||||
transition: opacity 0.3s ease;
|
||||
opacity: 1;
|
||||
|
||||
&.outdated {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
+13
-18
@@ -7,16 +7,15 @@
|
||||
*/
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { forwardRef, useCallback, useContext, useDeferredValue, useEffect, useRef, useState } from 'react';
|
||||
import styled, { css, use } from 'reshadow';
|
||||
|
||||
import { getComputed, TreeNodeContext, useMergeRefs, useObjectRef } from '@cloudbeaver/core-blocks';
|
||||
import { getComputed, s, TreeNodeContext, useMergeRefs, useObjectRef, useS } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { type NavNode, NavNodeInfoResource } from '@cloudbeaver/core-navigation-tree';
|
||||
import type { ComponentStyle } from '@cloudbeaver/core-theming';
|
||||
|
||||
import { ElementsTreeContext } from '../ElementsTreeContext';
|
||||
import type { NavTreeControlComponent } from '../NavigationNodeComponent';
|
||||
import { NavigationNodeControlLoader } from './NavigationNode/NavigationNodeLoaders';
|
||||
import style from './NavigationNodeControlRenderer.m.css';
|
||||
import type { INavigationNode } from './useNavigationNode';
|
||||
|
||||
interface Props {
|
||||
@@ -24,22 +23,11 @@ interface Props {
|
||||
navNode: INavigationNode;
|
||||
dragging?: boolean;
|
||||
control?: NavTreeControlComponent | undefined;
|
||||
style?: ComponentStyle;
|
||||
}
|
||||
|
||||
const styles = css`
|
||||
Control {
|
||||
transition: opacity 0.3s ease;
|
||||
opacity: 1;
|
||||
|
||||
&[|outdated] {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const NavigationNodeControlRenderer = observer<Props, HTMLDivElement>(
|
||||
forwardRef(function NavigationNodeControlRenderer({ node, navNode, dragging, control: externalControl, style }, ref) {
|
||||
forwardRef(function NavigationNodeControlRenderer({ node, navNode, dragging, control: externalControl }, ref) {
|
||||
const styles = useS(style);
|
||||
const elementRef = useRef<HTMLDivElement | null>(null);
|
||||
const [size, setSize] = useState(24);
|
||||
const contextRef = useObjectRef({
|
||||
@@ -108,8 +96,15 @@ export const NavigationNodeControlRenderer = observer<Props, HTMLDivElement>(
|
||||
treeNodeContext.select(event.ctrlKey || event.metaKey);
|
||||
}
|
||||
|
||||
return styled(styles)(
|
||||
<Control ref={mergedRef} {...use({ outdated })} nodeInfo={nodeInfo} dndElement={dragging} style={style} node={node} onClick={onClickHandler} />,
|
||||
return (
|
||||
<Control
|
||||
ref={mergedRef}
|
||||
className={s(styles, { control: true, outdated })}
|
||||
nodeInfo={nodeInfo}
|
||||
dndElement={dragging}
|
||||
node={node}
|
||||
onClick={onClickHandler}
|
||||
/>
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
+4
-5
@@ -7,16 +7,15 @@
|
||||
*/
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useContext } from 'react';
|
||||
import styled from 'reshadow';
|
||||
|
||||
import { getComputed, TreeNode, useObjectRef, useStyles } from '@cloudbeaver/core-blocks';
|
||||
import { getComputed, TreeNode, useObjectRef } from '@cloudbeaver/core-blocks';
|
||||
|
||||
import { ElementsTreeContext } from '../ElementsTreeContext';
|
||||
import type { NavigationNodeComponent } from '../NavigationNodeComponent';
|
||||
import { transformNodeInfo } from '../transformNodeInfo';
|
||||
import { NavigationNodeControlLoader } from './NavigationNode/NavigationNodeLoaders';
|
||||
|
||||
export const NavigationNodeDragged: NavigationNodeComponent = observer(function NavigationNode({ node, className, control: externalControl, style }) {
|
||||
export const NavigationNodeDragged: NavigationNodeComponent = observer(function NavigationNodeDragged({ node, className, control: externalControl }) {
|
||||
const contextRef = useObjectRef({
|
||||
context: useContext(ElementsTreeContext),
|
||||
});
|
||||
@@ -25,10 +24,10 @@ export const NavigationNodeDragged: NavigationNodeComponent = observer(function
|
||||
const Control = control || externalControl || NavigationNodeControlLoader;
|
||||
const nodeInfo = transformNodeInfo(node, contextRef.context?.tree.nodeInfoTransformers ?? []);
|
||||
|
||||
return styled(useStyles(style))(
|
||||
return (
|
||||
<TreeNode externalExpanded={false} className={className} leaf>
|
||||
{/* <DNDPreview data={dndData} src="/icons/empty.svg" /> */}
|
||||
<Control nodeInfo={nodeInfo} node={node} dndPlaceholder />
|
||||
</TreeNode>,
|
||||
</TreeNode>
|
||||
);
|
||||
});
|
||||
|
||||
+8
-6
@@ -7,9 +7,8 @@
|
||||
*/
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useContext } from 'react';
|
||||
import styled from 'reshadow';
|
||||
|
||||
import { Translate, TREE_NODE_STYLES, TreeNodeNestedMessage } from '@cloudbeaver/core-blocks';
|
||||
import { Translate, TreeNodeNestedMessage } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { NavNodeInfoResource } from '@cloudbeaver/core-navigation-tree';
|
||||
|
||||
@@ -19,7 +18,6 @@ import { NavigationNodeRendererLoader } from './NavigationNodeRendererLoader';
|
||||
|
||||
export const NavigationNodeElement: NavTreeNodeComponent = observer(function NavigationNodeElement({ nodeId, path, expanded, dragging, className }) {
|
||||
const context = useContext(ElementsTreeContext);
|
||||
const navNodeInfoResource = useService(NavNodeInfoResource);
|
||||
|
||||
if (context?.tree.renderers) {
|
||||
for (const renderer of context.tree.renderers) {
|
||||
@@ -40,17 +38,21 @@ export const NavigationNodeElement: NavTreeNodeComponent = observer(function Nav
|
||||
}
|
||||
}
|
||||
|
||||
return <NavigationNodeRenderer nodeId={nodeId} path={path} expanded={expanded} dragging={dragging} className={className} />;
|
||||
});
|
||||
|
||||
const NavigationNodeRenderer: NavTreeNodeComponent = observer(function NavigationNodeRenderer({ nodeId, path, expanded, dragging, className }) {
|
||||
const navNodeInfoResource = useService(NavNodeInfoResource);
|
||||
const node = navNodeInfoResource.get(nodeId);
|
||||
|
||||
if (!node) {
|
||||
return styled(TREE_NODE_STYLES)(
|
||||
return (
|
||||
<TreeNodeNestedMessage>
|
||||
<Translate token="app_navigationTree_node_not_found" />
|
||||
</TreeNodeNestedMessage>,
|
||||
</TreeNodeNestedMessage>
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: after node update reference can be lost and NavigationNode skip update
|
||||
return (
|
||||
<NavigationNodeRendererLoader
|
||||
node={node}
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
.contextMenu {
|
||||
padding: 0;
|
||||
height: 16px;
|
||||
|
||||
&:before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.selected svg,
|
||||
&:hover svg,
|
||||
&:focus svg {
|
||||
fill: #338fcc;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
cursor: pointer;
|
||||
width: 16px;
|
||||
height: 10px;
|
||||
fill: #dedede;
|
||||
}
|
||||
+13
-7
@@ -6,9 +6,8 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import styled, { use } from 'reshadow';
|
||||
|
||||
import { getComputed, Icon, IMouseContextMenu } from '@cloudbeaver/core-blocks';
|
||||
import { getComputed, Icon, IMouseContextMenu, s, useS } from '@cloudbeaver/core-blocks';
|
||||
import { ConnectionInfoResource } from '@cloudbeaver/core-connections';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { DATA_CONTEXT_NAV_NODE, type INodeActions, type NavNode } from '@cloudbeaver/core-navigation-tree';
|
||||
@@ -18,7 +17,7 @@ import { DATA_CONTEXT_CONNECTION } from '@cloudbeaver/plugin-connections';
|
||||
|
||||
import { MENU_NAV_TREE } from '../../MENU_NAV_TREE';
|
||||
import { DATA_CONTEXT_NAV_NODE_ACTIONS } from './DATA_CONTEXT_NAV_NODE_ACTIONS';
|
||||
import { treeNodeMenuStyles } from './treeNodeMenuStyles';
|
||||
import style from './TreeNodeMenu.m.css';
|
||||
|
||||
interface Props {
|
||||
node: NavNode;
|
||||
@@ -29,6 +28,7 @@ interface Props {
|
||||
}
|
||||
|
||||
export const TreeNodeMenu = observer<Props>(function TreeNodeMenu({ node, actions, selected, mouseContextMenu, onClose }) {
|
||||
const styles = useS(style);
|
||||
const connectionsInfoResource = useService(ConnectionInfoResource);
|
||||
const menu = useMenu({ menu: MENU_NAV_TREE });
|
||||
menu.context.set(DATA_CONTEXT_NAV_NODE, node);
|
||||
@@ -46,9 +46,15 @@ export const TreeNodeMenu = observer<Props>(function TreeNodeMenu({ node, action
|
||||
}
|
||||
}
|
||||
|
||||
return styled(treeNodeMenuStyles)(
|
||||
<ContextMenu menu={menu} mouseContextMenu={mouseContextMenu} onVisibleSwitch={handleVisibleSwitch} {...use({ selected })} modal>
|
||||
<Icon name="snack" viewBox="0 0 16 10" />
|
||||
</ContextMenu>,
|
||||
return (
|
||||
<ContextMenu
|
||||
menu={menu}
|
||||
className={s(styles, { contextMenu: true, selected })}
|
||||
mouseContextMenu={mouseContextMenu}
|
||||
modal
|
||||
onVisibleSwitch={handleVisibleSwitch}
|
||||
>
|
||||
<Icon className={s(styles, { icon: true })} name="snack" viewBox="0 0 16 10" />
|
||||
</ContextMenu>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
.tools {
|
||||
composes: theme-border-color-background from global;
|
||||
}
|
||||
|
||||
.tools > *:last-child:not(:first-child) {
|
||||
border-bottom: solid 1px;
|
||||
border-color: inherit;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
.captureView {
|
||||
outline: none;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.elementsTree {
|
||||
min-width: 100%;
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
.center {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
& .message {
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.message {
|
||||
box-sizing: border-box;
|
||||
max-width: 240px;
|
||||
padding: 24px;
|
||||
}
|
||||
@@ -7,14 +7,14 @@
|
||||
*/
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useMemo } from 'react';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { Translate, useUserData } from '@cloudbeaver/core-blocks';
|
||||
import { s, SContext, StyleRegistry, Translate, useS, useUserData } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { NavNodeInfoResource, NavTreeResource, ProjectsNavNodeService, ROOT_NODE_PATH } from '@cloudbeaver/core-navigation-tree';
|
||||
import { ProjectsService } from '@cloudbeaver/core-projects';
|
||||
import { CaptureView } from '@cloudbeaver/core-view';
|
||||
|
||||
import { ElementsTreeToolsStyles } from '../index';
|
||||
import { NavNodeViewService } from '../NodesManager/NavNodeView/NavNodeViewService';
|
||||
import { navigationTreeConnectionGroupFilter } from './ConnectionGroup/navigationTreeConnectionGroupFilter';
|
||||
import { navigationTreeConnectionGroupRenderer } from './ConnectionGroup/navigationTreeConnectionGroupRenderer';
|
||||
@@ -26,7 +26,9 @@ import {
|
||||
} from './ElementsTree/ElementsTreeTools/NavigationTreeSettings/createElementsTreeSettings';
|
||||
import { transformFilteredNodeInfo } from './ElementsTree/transformFilteredNodeInfo';
|
||||
import type { IElementsTreeSettings } from './ElementsTree/useElementsTree';
|
||||
import elementsTreeToolsStyles from './ElementsTreeTools.m.css';
|
||||
import { getNavigationTreeUserSettingsId } from './getNavigationTreeUserSettingsId';
|
||||
import style from './NavigationTree.m.css';
|
||||
import { navigationTreeDuplicateFilter } from './navigationTreeDuplicateIdFilter';
|
||||
import { NavigationTreeService } from './NavigationTreeService';
|
||||
import { navigationTreeProjectFilter } from './ProjectsRenderer/navigationTreeProjectFilter';
|
||||
@@ -36,49 +38,18 @@ import { navigationTreeProjectsRendererRenderer } from './ProjectsRenderer/navig
|
||||
import { ProjectsSettingsPlaceholderElement } from './ProjectsRenderer/ProjectsSettingsForm';
|
||||
import { useNavigationTree } from './useNavigationTree';
|
||||
|
||||
const navigationTreeStyles = css`
|
||||
CaptureView {
|
||||
outline: none;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
ElementsTree {
|
||||
min-width: 100%;
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
center {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
& message {
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
message {
|
||||
box-sizing: border-box;
|
||||
max-width: 240px;
|
||||
padding: 24px;
|
||||
}
|
||||
`;
|
||||
|
||||
const elementsTreeStyles = css`
|
||||
tools {
|
||||
composes: theme-border-color-background from global;
|
||||
}
|
||||
tools > *:last-child:not(:first-child) {
|
||||
border-bottom: solid 1px;
|
||||
border-color: inherit;
|
||||
}
|
||||
`;
|
||||
const registry: StyleRegistry = [
|
||||
[
|
||||
ElementsTreeToolsStyles,
|
||||
{
|
||||
mode: 'append',
|
||||
styles: [elementsTreeToolsStyles],
|
||||
},
|
||||
],
|
||||
];
|
||||
|
||||
export const NavigationTree = observer(function NavigationTree() {
|
||||
const styles = useS(style);
|
||||
const projectsNavNodeService = useService(ProjectsNavNodeService);
|
||||
const projectsService = useService(ProjectsService);
|
||||
const navTreeService = useService(NavigationTreeService);
|
||||
@@ -113,34 +84,34 @@ export const NavigationTree = observer(function NavigationTree() {
|
||||
|
||||
const settingsElements = useMemo(() => [ProjectsSettingsPlaceholderElement], []);
|
||||
|
||||
return styled(navigationTreeStyles)(
|
||||
<CaptureView view={navTreeService}>
|
||||
<ElementsTree
|
||||
root={root}
|
||||
localState={navTreeService.treeState}
|
||||
filters={[duplicateFilter, connectionGroupFilter, projectFilter]}
|
||||
renderers={[projectsRendererRenderer, navigationTreeConnectionGroupRenderer, connectionRenderer]}
|
||||
navNodeFilterCompare={navigationTreeProjectSearchCompare}
|
||||
nodeInfoTransformers={[transformFilteredNode]}
|
||||
expandStateGetters={[projectsExpandStateGetter]}
|
||||
settingsElements={settingsElements}
|
||||
emptyPlaceholder={() =>
|
||||
styled(navigationTreeStyles)(
|
||||
<center>
|
||||
<message>
|
||||
return (
|
||||
<SContext registry={registry}>
|
||||
<CaptureView view={navTreeService} className={s(styles, { captureView: true })}>
|
||||
<ElementsTree
|
||||
root={root}
|
||||
localState={navTreeService.treeState}
|
||||
filters={[duplicateFilter, connectionGroupFilter, projectFilter]}
|
||||
renderers={[projectsRendererRenderer, navigationTreeConnectionGroupRenderer, connectionRenderer]}
|
||||
navNodeFilterCompare={navigationTreeProjectSearchCompare}
|
||||
nodeInfoTransformers={[transformFilteredNode]}
|
||||
expandStateGetters={[projectsExpandStateGetter]}
|
||||
settingsElements={settingsElements}
|
||||
className={s(styles, { elementsTree: true })}
|
||||
emptyPlaceholder={() => (
|
||||
<div className={s(styles, { center: true })}>
|
||||
<div className={s(styles, { message: true })}>
|
||||
<Translate token="app_navigationTree_empty_placeholder" />
|
||||
</message>
|
||||
</center>,
|
||||
)
|
||||
}
|
||||
customSelect={handleSelect}
|
||||
customSelectReset={handleSelectReset}
|
||||
settings={settings}
|
||||
style={elementsTreeStyles}
|
||||
getChildren={navTreeService.getChildren}
|
||||
loadChildren={navTreeService.loadNestedNodes}
|
||||
onOpen={handleOpen}
|
||||
/>
|
||||
</CaptureView>,
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
customSelect={handleSelect}
|
||||
customSelectReset={handleSelectReset}
|
||||
settings={settings}
|
||||
getChildren={navTreeService.getChildren}
|
||||
loadChildren={navTreeService.loadNestedNodes}
|
||||
onOpen={handleOpen}
|
||||
/>
|
||||
</CaptureView>
|
||||
</SContext>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
@@ -6,25 +6,19 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { s, useS } from '@cloudbeaver/core-blocks';
|
||||
import type { TabContainerPanelComponent } from '@cloudbeaver/core-ui';
|
||||
|
||||
import { NavigationTreeLoader } from './NavigationTreeLoader';
|
||||
|
||||
const styles = css`
|
||||
container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
`;
|
||||
import style from './NavigationTreePanel.m.css';
|
||||
|
||||
export const NavigationTreePanel: TabContainerPanelComponent = observer(function NavigationTreePanel() {
|
||||
return styled(styles)(
|
||||
<container>
|
||||
const styles = useS(style);
|
||||
|
||||
return (
|
||||
<div className={s(styles, { container: true })}>
|
||||
<NavigationTreeLoader />
|
||||
</container>,
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
.treeNodeControl {
|
||||
transition: opacity 0.3s ease;
|
||||
opacity: 1;
|
||||
|
||||
&.outdated {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.treeNodeControl:hover > .portal,
|
||||
.treeNodeControl[aria-selected='true'] > .portal,
|
||||
.portal:focus-within {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.treeNodeName {
|
||||
composes: theme-text-text-hint-on-light theme-typography--caption from global;
|
||||
height: 100%;
|
||||
max-width: 250px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.portal {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
margin-left: auto !important;
|
||||
margin-right: 8px !important;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.nameBox {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
+10
-47
@@ -7,9 +7,8 @@
|
||||
*/
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import React, { forwardRef, useContext } from 'react';
|
||||
import styled, { css, use } from 'reshadow';
|
||||
|
||||
import { getComputed, TREE_NODE_STYLES, TreeNodeContext, TreeNodeControl, TreeNodeName, useMouseContextMenu } from '@cloudbeaver/core-blocks';
|
||||
import { getComputed, s, TreeNodeContext, TreeNodeControl, TreeNodeName, useMouseContextMenu, useS } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { EventContext, EventStopPropagationFlag } from '@cloudbeaver/core-events';
|
||||
import { NavNodeInfoResource } from '@cloudbeaver/core-navigation-tree';
|
||||
@@ -18,43 +17,11 @@ import { ElementsTreeContext } from '../ElementsTree/ElementsTreeContext';
|
||||
import type { NavTreeControlComponent, NavTreeControlProps } from '../ElementsTree/NavigationNodeComponent';
|
||||
import { isDraggingInsideProject } from '../ElementsTree/NavigationTreeNode/isDraggingInsideProject';
|
||||
import { TreeNodeMenuLoader } from '../ElementsTree/NavigationTreeNode/TreeNodeMenu/TreeNodeMenuLoader';
|
||||
|
||||
const styles = css`
|
||||
TreeNodeControl {
|
||||
transition: opacity 0.3s ease;
|
||||
opacity: 1;
|
||||
|
||||
&[|outdated] {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
TreeNodeControl:hover > portal,
|
||||
TreeNodeControl:global([aria-selected='true']) > portal,
|
||||
portal:focus-within {
|
||||
visibility: visible;
|
||||
}
|
||||
TreeNodeName {
|
||||
composes: theme-text-text-hint-on-light theme-typography--caption from global;
|
||||
height: 100%;
|
||||
max-width: 250px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
portal {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
margin-left: auto !important;
|
||||
margin-right: 8px !important;
|
||||
visibility: hidden;
|
||||
}
|
||||
name-box {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
`;
|
||||
import style from './NavigationNodeProjectControl.m.css';
|
||||
|
||||
export const NavigationNodeProjectControl: NavTreeControlComponent = observer<NavTreeControlProps, HTMLDivElement>(
|
||||
forwardRef(function NavigationNodeProjectControl({ node, dndElement, dndPlaceholder, className }, ref) {
|
||||
const styles = useS(style);
|
||||
const mouseContextMenu = useMouseContextMenu();
|
||||
const treeNodeContext = useContext(TreeNodeContext);
|
||||
const elementsTreeContext = useContext(ElementsTreeContext);
|
||||
@@ -92,27 +59,23 @@ export const NavigationNodeProjectControl: NavTreeControlComponent = observer<Na
|
||||
return null;
|
||||
}
|
||||
|
||||
return styled(
|
||||
TREE_NODE_STYLES,
|
||||
styles,
|
||||
)(
|
||||
return (
|
||||
<TreeNodeControl
|
||||
ref={ref}
|
||||
onClick={handleClick}
|
||||
onDoubleClick={handleDbClick}
|
||||
onContextMenu={handleContextMenuOpen}
|
||||
{...use({ outdated, dragging: dndElement })}
|
||||
className={className}
|
||||
className={s(styles, { treeNodeControl: true, outdated, dragging: isDragging }, className)}
|
||||
>
|
||||
<TreeNodeName title={node.name}>
|
||||
<name-box>{node.name}</name-box>
|
||||
<TreeNodeName className={s(styles, { treeNodeName: true })} title={node.name}>
|
||||
<div className={s(styles, { nameBox: true })}>{node.name}</div>
|
||||
</TreeNodeName>
|
||||
{!dndPlaceholder && (
|
||||
<portal onClick={handlePortalClick}>
|
||||
<div className={s(styles, { portal: true })} onClick={handlePortalClick}>
|
||||
<TreeNodeMenuLoader mouseContextMenu={mouseContextMenu} node={node} selected={selected} />
|
||||
</portal>
|
||||
</div>
|
||||
)}
|
||||
</TreeNodeControl>,
|
||||
</TreeNodeControl>
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
.projectNode {
|
||||
margin-top: 8px;
|
||||
|
||||
&.project:only-child,
|
||||
&.hideProjects {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
& > .navigationNodeNested {
|
||||
padding-left: 0 !important;
|
||||
}
|
||||
}
|
||||
+34
-34
@@ -7,13 +7,13 @@
|
||||
*/
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useContext } from 'react';
|
||||
import styled, { css, use } from 'reshadow';
|
||||
|
||||
import { getComputed, Translate, TREE_NODE_STYLES, TreeNodeNestedMessage } from '@cloudbeaver/core-blocks';
|
||||
import { getComputed, s, SContext, StyleRegistry, Translate, TreeNodeNestedMessage, useS } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import type { NavNodeInfoResource } from '@cloudbeaver/core-navigation-tree';
|
||||
import { NAV_NODE_TYPE_PROJECT, ProjectsService } from '@cloudbeaver/core-projects';
|
||||
|
||||
import { NavigationNodeControlRendererStyles, NavigationNodeNestedStyles } from '../../index';
|
||||
import { useNode } from '../../NodesManager/useNode';
|
||||
import { ElementsTreeContext } from '../ElementsTree/ElementsTreeContext';
|
||||
import type { NavigationNodeRendererComponent } from '../ElementsTree/NavigationNodeComponent';
|
||||
@@ -21,25 +21,24 @@ import { isDraggingInsideProject } from '../ElementsTree/NavigationTreeNode/isDr
|
||||
import { NavigationNodeRendererLoader } from '../ElementsTree/NavigationTreeNode/NavigationNodeRendererLoader';
|
||||
import type { IElementsTreeCustomRenderer } from '../ElementsTree/useElementsTree';
|
||||
import { NavigationNodeProjectControl } from './NavigationNodeProjectControl';
|
||||
import style from './NavigationTreeProjectsRendererRenderer.m.css';
|
||||
|
||||
const nestedStyles = css`
|
||||
TreeNode {
|
||||
margin-top: 8px;
|
||||
|
||||
&[|project]:only-child,
|
||||
&[|hideProjects] {
|
||||
margin-top: 0px;
|
||||
|
||||
& Control {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
& NavigationNodeNested {
|
||||
padding-left: 0 !important;
|
||||
}
|
||||
}
|
||||
`;
|
||||
const registry: StyleRegistry = [
|
||||
[
|
||||
NavigationNodeNestedStyles,
|
||||
{
|
||||
mode: 'append',
|
||||
styles: [style],
|
||||
},
|
||||
],
|
||||
[
|
||||
NavigationNodeControlRendererStyles,
|
||||
{
|
||||
mode: 'append',
|
||||
styles: [style],
|
||||
},
|
||||
],
|
||||
];
|
||||
|
||||
export function navigationTreeProjectsRendererRenderer(navNodeInfoResource: NavNodeInfoResource): IElementsTreeCustomRenderer {
|
||||
return nodeId => {
|
||||
@@ -61,6 +60,7 @@ const ProjectRenderer: NavigationNodeRendererComponent = observer(function Manag
|
||||
className,
|
||||
expanded,
|
||||
}) {
|
||||
const styles = useS(style);
|
||||
const projectsService = useService(ProjectsService);
|
||||
const elementsTreeContext = useContext(ElementsTreeContext);
|
||||
|
||||
@@ -78,26 +78,26 @@ const ProjectRenderer: NavigationNodeRendererComponent = observer(function Manag
|
||||
const hideProjects = elementsTreeContext?.tree.settings?.projects === false && !isDragging;
|
||||
|
||||
if (!node) {
|
||||
return styled(TREE_NODE_STYLES)(
|
||||
return (
|
||||
<TreeNodeNestedMessage>
|
||||
<Translate token="app_navigationTree_node_not_found" />
|
||||
</TreeNodeNestedMessage>,
|
||||
</TreeNodeNestedMessage>
|
||||
);
|
||||
}
|
||||
|
||||
const project = node.nodeType === NAV_NODE_TYPE_PROJECT && singleProject && !isDragging;
|
||||
|
||||
return styled(nestedStyles)(
|
||||
<NavigationNodeRendererLoader
|
||||
node={node}
|
||||
path={path}
|
||||
expanded={expanded}
|
||||
dragging={dragging}
|
||||
className={className}
|
||||
control={NavigationNodeProjectControl}
|
||||
style={nestedStyles}
|
||||
component={component}
|
||||
{...use({ hideProjects, project })}
|
||||
/>,
|
||||
return (
|
||||
<SContext registry={registry}>
|
||||
<NavigationNodeRendererLoader
|
||||
node={node}
|
||||
path={path}
|
||||
expanded={expanded}
|
||||
dragging={dragging}
|
||||
className={s(styles, { projectNode: true, hideProjects, project }, className)}
|
||||
control={NavigationNodeProjectControl}
|
||||
component={component}
|
||||
/>
|
||||
</SContext>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -28,6 +28,10 @@ export * from './NavigationTree/ElementsTree/ElementsTreeTools/ElementsTreeTools
|
||||
export * from './NavigationTree/ElementsTree/elementsTreeNameFilter';
|
||||
export * from './NavigationTree/NavigationTreeBootstrap';
|
||||
export * from './NavigationTree/NavigationTreeService';
|
||||
export { default as ElementsTreeToolsStyles } from './NavigationTree/ElementsTree/ElementsTreeTools/ElementsTreeTools.m.css';
|
||||
export { default as ElementsTreeFilterStyles } from './NavigationTree/ElementsTree/ElementsTreeTools/ElementsTreeFilter.m.css';
|
||||
export { default as NavigationNodeNestedStyles } from './NavigationTree/ElementsTree/NavigationTreeNode/NavigationNode/NavigationNodeNested.m.css';
|
||||
export { default as NavigationNodeControlRendererStyles } from './NavigationTree/ElementsTree/NavigationTreeNode/NavigationNodeControlRenderer.m.css';
|
||||
|
||||
export * from './NavigationTree/NavigationTreeLoader';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user