mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
feat: CB-873 redesign driver properties
This commit is contained in:
@@ -29,6 +29,10 @@
|
||||
</g>
|
||||
</symbol>
|
||||
|
||||
<symbol id="add_sm" viewBox="-3 -3 14 14">
|
||||
<path fill="currentColor" fill-rule="evenodd" clip-rule="evenodd" d="M5 0H3v3H0v2h3v3h2V5h3V3H5V0z"/>
|
||||
</symbol>
|
||||
|
||||
<symbol id="arrow" viewBox="0 0 16 16">
|
||||
<path class="icon-fill" d="M8.245 10.66L4 6.4155L5.41567 5.001L8.245 7.831L11.0743 5L12.49 6.4155L8.245 10.66Z" />
|
||||
</symbol>
|
||||
|
||||
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
@@ -13,6 +13,7 @@ import { use } from 'reshadow';
|
||||
|
||||
import { useStyles, composes } from '@cloudbeaver/core-theming';
|
||||
|
||||
import { Icon } from './Icons';
|
||||
import { Loader } from './Loader/Loader';
|
||||
import { useObjectRef } from './useObjectRef';
|
||||
|
||||
@@ -21,6 +22,12 @@ const buttonStyles = composes(
|
||||
Button {
|
||||
composes: theme-button from global;
|
||||
}
|
||||
button-label {
|
||||
composes: theme-button__label from global;
|
||||
}
|
||||
button-icon {
|
||||
composes: theme-button__icon from global;
|
||||
}
|
||||
ripple {
|
||||
composes: theme-button_ripple from global;
|
||||
}
|
||||
@@ -92,6 +99,8 @@ type ButtonProps = (
|
||||
& React.LinkHTMLAttributes<HTMLLinkElement | HTMLButtonElement>
|
||||
) & {
|
||||
loading?: boolean;
|
||||
icon?: string;
|
||||
viewBox?: string;
|
||||
mod?: Array<keyof typeof buttonMod>;
|
||||
tag?: 'button' | 'a';
|
||||
href?: string;
|
||||
@@ -103,6 +112,8 @@ type ButtonProps = (
|
||||
|
||||
export const Button: React.FC<ButtonProps> = observer(function Button({
|
||||
children,
|
||||
icon,
|
||||
viewBox,
|
||||
mod,
|
||||
tag = 'button',
|
||||
disabled = false,
|
||||
@@ -139,8 +150,9 @@ export const Button: React.FC<ButtonProps> = observer(function Button({
|
||||
const Button = tag;
|
||||
return styled(useStyles(buttonStyles, ...(mod || []).map(mod => buttonMod[mod])))(
|
||||
<Button {...rest} disabled={disabled} {...use({ loading })} className={className} onClick={state.click}>
|
||||
<ripple as="div" />
|
||||
<button-label as='div'>{children}</button-label>
|
||||
<ripple />
|
||||
{icon && <button-icon><Icon name={icon} viewBox={viewBox} /></button-icon>}
|
||||
<button-label as='span'>{children}</button-label>
|
||||
<Loader small />
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -71,6 +71,11 @@ export const BASE_CONTAINERS_STYLES = composes(
|
||||
margin-right: 25%;
|
||||
}
|
||||
|
||||
&[box] {
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&[center] {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ interface Props extends IContainerProps {
|
||||
className?: string;
|
||||
form?: boolean;
|
||||
center?: boolean;
|
||||
box?: boolean;
|
||||
}
|
||||
|
||||
export const Group: React.FC<Props> = function Group({ children, className }) {
|
||||
|
||||
@@ -6,8 +6,9 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { computed } from 'mobx';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useCallback } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import styled from 'reshadow';
|
||||
|
||||
import { useTranslate } from '@cloudbeaver/core-localization';
|
||||
@@ -21,22 +22,25 @@ import { PROPERTIES_TABLE_STYLES } from './styles';
|
||||
|
||||
type PropertiesState = Record<string, string>;
|
||||
|
||||
interface PropertiesTableProps {
|
||||
interface Props {
|
||||
properties: IProperty[];
|
||||
propertiesState?: PropertiesState;
|
||||
readOnly?: boolean;
|
||||
onKeyChange?: (id: string, name: string) => void;
|
||||
onChange?: (state: PropertiesState) => void;
|
||||
onAdd?: () => void;
|
||||
onRemove?: (id: string) => void;
|
||||
onRemove?: (property: IProperty) => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const PropertiesTable = observer(function PropertiesTable(props: PropertiesTableProps) {
|
||||
export const PropertiesTable = observer(function PropertiesTable(props: Props) {
|
||||
const { className, onAdd, readOnly, propertiesState } = props;
|
||||
const translate = useTranslate();
|
||||
const propsRef = useObjectRef({ ...props });
|
||||
|
||||
const sortedProperties = useMemo(() => computed(() => propsRef.properties.slice().sort(
|
||||
(a, b) => (a?.displayName ?? '').localeCompare(b?.displayName ?? ''))), [propsRef.properties]);
|
||||
|
||||
const changeName = useCallback((id: string, key: string) => {
|
||||
const { properties, propertiesState, onKeyChange } = propsRef;
|
||||
const property = properties.find(property => property.id === id);
|
||||
@@ -92,9 +96,8 @@ export const PropertiesTable = observer(function PropertiesTable(props: Properti
|
||||
}
|
||||
|
||||
if (onRemove) {
|
||||
onRemove(id);
|
||||
onRemove(property);
|
||||
}
|
||||
properties.splice(properties.indexOf(property), 1);
|
||||
}, []);
|
||||
|
||||
const isKeyUnique = useCallback(
|
||||
@@ -102,24 +105,30 @@ export const PropertiesTable = observer(function PropertiesTable(props: Properti
|
||||
[]
|
||||
);
|
||||
|
||||
const alphabetOrderProperties = propsRef.properties.slice().sort(
|
||||
(a, b) => (a?.displayName ?? '').localeCompare(b?.displayName ?? ''));
|
||||
|
||||
return styled(useStyles(PROPERTIES_TABLE_STYLES))(
|
||||
<properties as="div" className={className}>
|
||||
<properties-header as="div">
|
||||
<properties-header-name as="div">
|
||||
<properties className={className}>
|
||||
<properties-header>
|
||||
<properties-header-name>
|
||||
{translate('block_properties_table_name')}
|
||||
</properties-header-name>
|
||||
<properties-header-value as="div">
|
||||
<properties-header-value>
|
||||
{translate('block_properties_table_value')}
|
||||
</properties-header-value>
|
||||
<properties-header-right as="div">
|
||||
{onAdd && !readOnly && <Button type='button' mod={['outlined']} onClick={() => onAdd()}>{translate('block_properties_table_add')}</Button>}
|
||||
</properties-header-right>
|
||||
</properties-header>
|
||||
<properties-list as="div">
|
||||
{alphabetOrderProperties.map(property => (
|
||||
<properties-list>
|
||||
{onAdd && !readOnly && (
|
||||
<properties-header-add>
|
||||
<Button
|
||||
icon='add_sm'
|
||||
viewBox="0 0 18 18"
|
||||
type='button'
|
||||
onClick={() => onAdd()}
|
||||
>
|
||||
{translate('block_properties_table_add')}
|
||||
</Button>
|
||||
</properties-header-add>
|
||||
)}
|
||||
{sortedProperties.get().map(property => (
|
||||
<PropertyItem
|
||||
key={property.id}
|
||||
property={property}
|
||||
|
||||
@@ -13,10 +13,7 @@ import { composes } from '@cloudbeaver/core-theming';
|
||||
export const PROPERTIES_TABLE_STYLES = composes(
|
||||
css`
|
||||
properties-header {
|
||||
composes: theme-background-secondary theme-text-on-secondary from global;
|
||||
}
|
||||
properties-header-name, properties-header-value {
|
||||
composes: theme-background-surface theme-text-on-surface from global;
|
||||
composes: theme-background-surface theme-text-on-surface theme-border-color-secondary from global;
|
||||
}
|
||||
`,
|
||||
css`
|
||||
@@ -28,10 +25,7 @@ export const PROPERTIES_TABLE_STYLES = composes(
|
||||
properties-header {
|
||||
box-sizing: border-box;
|
||||
display: inline-flex;
|
||||
padding: 2px 0;
|
||||
position: sticky;
|
||||
z-index: 1;
|
||||
top: 0;
|
||||
border-bottom: solid 1px;
|
||||
}
|
||||
properties-header-name, properties-header-value {
|
||||
composes: theme-typography--caption from global;
|
||||
@@ -40,22 +34,25 @@ export const PROPERTIES_TABLE_STYLES = composes(
|
||||
flex: 1;
|
||||
padding: 4px 36px;
|
||||
}
|
||||
properties-header-value, properties-header-right {
|
||||
margin-left: 2px;
|
||||
}
|
||||
properties-header-name {
|
||||
flex: 0 0 auto;
|
||||
width: 300px;
|
||||
}
|
||||
properties-header-right {
|
||||
flex: 0 0 auto;
|
||||
properties-header-add {
|
||||
flex: 0 0 100%;
|
||||
padding: 8px 28px;
|
||||
|
||||
& Button {
|
||||
text-transform: initial;
|
||||
}
|
||||
}
|
||||
|
||||
properties-list {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-bottom: 24px;
|
||||
overflow: auto;
|
||||
max-height: 600px;
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export default [
|
||||
['block_properties_table_name', 'Name'],
|
||||
['block_properties_table_value', 'Value'],
|
||||
['block_properties_table_add', 'Add'],
|
||||
['block_properties_table_add', 'Add property'],
|
||||
];
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export default [
|
||||
['block_properties_table_name', 'Название'],
|
||||
['block_properties_table_value', 'Значение'],
|
||||
['block_properties_table_add', 'Добавить'],
|
||||
['block_properties_table_add', 'Добавить параметр'],
|
||||
];
|
||||
|
||||
+30
-20
@@ -11,7 +11,7 @@ import { observer } from 'mobx-react-lite';
|
||||
import { useMemo, useState } from 'react';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { IProperty, Loader, PropertiesTable, TabContainerPanelComponent, useMapResource, useTab } from '@cloudbeaver/core-blocks';
|
||||
import { BASE_CONTAINERS_STYLES, ColoredContainer, Group, IProperty, Loader, PropertiesTable, TabContainerPanelComponent, useMapResource, useTab } from '@cloudbeaver/core-blocks';
|
||||
import { useStyles } from '@cloudbeaver/core-theming';
|
||||
import { uuid } from '@cloudbeaver/core-utils';
|
||||
|
||||
@@ -19,14 +19,17 @@ import { DBDriverResource } from '../../DBDriverResource';
|
||||
import type { IConnectionFormProps } from '../IConnectionFormProps';
|
||||
|
||||
const styles = css`
|
||||
properties {
|
||||
display: flex;
|
||||
ColoredContainer {
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
}
|
||||
center {
|
||||
margin: auto;
|
||||
Group {
|
||||
max-height: 100%;
|
||||
}
|
||||
PropertiesTable {
|
||||
padding-top: 8px;
|
||||
max-height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -34,7 +37,7 @@ export const DriverProperties: TabContainerPanelComponent<IConnectionFormProps>
|
||||
tabId,
|
||||
state: formState,
|
||||
}) {
|
||||
const style = useStyles(styles);
|
||||
const style = useStyles(styles, BASE_CONTAINERS_STYLES);
|
||||
const { selected } = useTab(tabId);
|
||||
|
||||
const [state] = useState(() => {
|
||||
@@ -50,7 +53,11 @@ export const DriverProperties: TabContainerPanelComponent<IConnectionFormProps>
|
||||
});
|
||||
}
|
||||
|
||||
return { propertiesList, add };
|
||||
function remove(property: IProperty) {
|
||||
propertiesList.splice(propertiesList.indexOf(property), 1);
|
||||
}
|
||||
|
||||
return { propertiesList, add, remove };
|
||||
});
|
||||
|
||||
const driver = useMapResource(
|
||||
@@ -87,17 +94,20 @@ export const DriverProperties: TabContainerPanelComponent<IConnectionFormProps>
|
||||
])), [driver.data]);
|
||||
|
||||
return styled(style)(
|
||||
<properties as="div">
|
||||
<Loader state={driver}>
|
||||
{() => (
|
||||
<PropertiesTable
|
||||
properties={joinedProperties.get()}
|
||||
propertiesState={formState.config.properties}
|
||||
readOnly={formState.readonly}
|
||||
onAdd={state.add}
|
||||
/>
|
||||
)}
|
||||
</Loader>
|
||||
</properties>
|
||||
<Loader state={driver}>
|
||||
{() => styled(style)(
|
||||
<ColoredContainer parent>
|
||||
<Group box keepSize large>
|
||||
<PropertiesTable
|
||||
properties={joinedProperties.get()}
|
||||
propertiesState={formState.config.properties}
|
||||
readOnly={formState.readonly}
|
||||
onAdd={state.add}
|
||||
onRemove={state.remove}
|
||||
/>
|
||||
</Group>
|
||||
</ColoredContainer>
|
||||
)}
|
||||
</Loader>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -15,12 +15,18 @@
|
||||
|
||||
.theme-button {
|
||||
composes: mdc-button;
|
||||
height: 28px;
|
||||
height: 32px;
|
||||
|
||||
&[disabled] {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.theme-button__label {
|
||||
composes: mdc-button__label;
|
||||
}
|
||||
.theme-button__icon {
|
||||
composes: mdc-button__icon;
|
||||
}
|
||||
.theme-button_ripple {
|
||||
composes: mdc-button__ripple;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user