Merge branch 'devel' of https://github.com/dbeaver/cloudbeaver into devel

This commit is contained in:
Wroud
2021-04-21 16:02:54 +03:00
23 changed files with 472 additions and 256 deletions
@@ -16,17 +16,20 @@ import { CheckboxMarkup, CheckboxMod } from './CheckboxMarkup';
import { CheckboxOnChangeEvent, useCheckboxState } from './useCheckboxState';
export interface CheckboxBaseProps {
label?: string;
mod?: CheckboxMod[];
ripple?: boolean;
indeterminate?: boolean;
style?: ComponentStyle;
}
export type CheckboxInputProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'type' | 'value' | 'checked' | 'id' | 'style'> & ILayoutSizeProps;
export type CheckboxInputProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'type' | 'value' | 'defaultValue' | 'checked' | 'defaultChecked' | 'style'> & ILayoutSizeProps & {
value?: string;
defaultValue?: string;
defaultChecked?: boolean;
label?: string;
};
export interface ICheckboxControlledProps extends CheckboxInputProps {
value?: string;
state?: never;
checked?: boolean;
onChange?: CheckboxOnChangeEvent<string | undefined>;
@@ -34,7 +37,6 @@ export interface ICheckboxControlledProps extends CheckboxInputProps {
}
export interface ICheckboxObjectProps<TKey extends string> extends CheckboxInputProps {
value?: string;
state: Partial<Record<TKey, boolean | null | string | string[]>>;
checked?: never;
onChange?: CheckboxOnChangeEvent<TKey>;
@@ -50,9 +52,11 @@ export interface CheckboxType {
export const Checkbox: CheckboxType = observer(function Checkbox({
name,
value,
defaultValue,
state,
label,
checked,
defaultChecked,
children,
mod,
ripple,
@@ -61,7 +65,15 @@ export const Checkbox: CheckboxType = observer(function Checkbox({
onChange,
...rest
}: CheckboxBaseProps & (ICheckboxControlledProps | ICheckboxObjectProps<any>)) {
const checkboxState = useCheckboxState({ value, checked, state, name, onChange });
const checkboxState = useCheckboxState({
value,
defaultValue,
checked,
defaultChecked,
state,
name,
onChange,
});
if (autoHide && !isControlPresented(name, state)) {
return null;
@@ -71,7 +83,6 @@ export const Checkbox: CheckboxType = observer(function Checkbox({
<CheckboxMarkup
{...rest}
name={name}
id={value || name}
checked={checkboxState.checked}
label={label}
className={className}
@@ -110,7 +110,7 @@ interface ICheckboxMarkupProps extends Omit<React.InputHTMLAttributes<HTMLInputE
}
export const CheckboxMarkup: React.FC<ICheckboxMarkupProps> = function CheckboxMarkup({
label, className, title, mod = ['primary'], ripple = true, style, ...rest
id, label, className, title, mod = ['primary'], ripple = true, style, ...rest
}) {
return styled(
useStyles(
@@ -123,7 +123,7 @@ export const CheckboxMarkup: React.FC<ICheckboxMarkupProps> = function CheckboxM
)(
<checkbox-container className={className} title={title} as='div'>
<checkbox as='div'>
<checkbox-input as='input' type='checkbox' {...rest} />
<checkbox-input as='input' type='checkbox' {...rest} id={id || rest.name} />
<checkbox-background as='div'>
<checkbox-checkmark as='svg' viewBox='0 0 24 24'>
<checkbox-checkmark-path as='path' fill='none' d='M1.73,12.91 8.1,19.28 22.79,4.59' />
@@ -134,7 +134,7 @@ export const CheckboxMarkup: React.FC<ICheckboxMarkupProps> = function CheckboxM
<checkbox-ripple as='div' />
)}
</checkbox>
{label && rest.id && <checkbox-label as='label' htmlFor={rest.id}>{label}</checkbox-label>}
{label && (id || rest.name) && <checkbox-label as='label' htmlFor={id || rest.name}>{label}</checkbox-label>}
</checkbox-container>
);
};
@@ -46,7 +46,7 @@ export const FieldCheckboxNew: CheckboxType = function FieldCheckboxNew({
return styled(styles)(
<field className={className} as="div">
<Checkbox {...(rest as CheckboxBaseProps & ICheckboxControlledProps)} />
<field-label htmlFor={rest.value || rest.name} title={rest.title} as="label">{children}</field-label>
<field-label htmlFor={rest.id || rest.name} title={rest.title} as="label">{children}</field-label>
</field>
);
};
@@ -79,7 +79,6 @@ const switchState = {
interface IBaseProps {
mod?: Array<keyof typeof switchMod>;
label?: string;
description?: string;
}
@@ -91,10 +90,12 @@ interface SwitchType {
export const Switch: SwitchType = observer(function Switch({
name,
value,
defaultValue,
label,
description,
state,
checked,
defaultChecked,
className,
children,
onChange,
@@ -103,7 +104,15 @@ export const Switch: SwitchType = observer(function Switch({
disabled,
...rest
}: IBaseProps & (ICheckboxControlledProps | ICheckboxObjectProps<any>)) {
const checkboxState = useCheckboxState({ value, checked, state, name, onChange });
const checkboxState = useCheckboxState({
value,
defaultValue,
checked,
defaultChecked,
state,
name,
onChange,
});
const styles = useStyles(
baseFormControlStyles,
switchStyles,
@@ -95,9 +95,11 @@ interface SwitchType {
export const SwitchNew: SwitchType = observer(function SwitchNew({
name,
value,
defaultValue,
description,
state,
checked,
defaultChecked,
className,
children,
onChange,
@@ -106,7 +108,15 @@ export const SwitchNew: SwitchType = observer(function SwitchNew({
disabled,
...rest
}: IBaseProps & (ICheckboxControlledProps | ICheckboxObjectProps<any>)) {
const checkboxState = useCheckboxState({ value, checked, state, name, onChange });
const checkboxState = useCheckboxState({
value,
defaultValue,
checked,
defaultChecked,
state,
name,
onChange,
});
const styles = useStyles(
baseFormControlStylesNew,
switchStyles,
@@ -11,11 +11,13 @@ import { useContext, useState } from 'react';
import { useObjectRef } from '../../useObjectRef';
import { FormContext } from '../FormContext';
export type CheckboxOnChangeEvent<T> =(value: boolean, name: T) => void;
export type CheckboxOnChangeEvent<T> = (value: boolean, name: T) => void;
export type CheckboxStateOptions<TKey extends string> = {
value: string | undefined;
defaultValue: string | undefined;
checked: boolean | undefined;
defaultChecked: boolean | undefined;
} & (
{
state: undefined;
@@ -30,23 +32,24 @@ export type CheckboxStateOptions<TKey extends string> = {
);
interface ICheckboxState {
checked: boolean;
checked: boolean | undefined;
change: (event: React.ChangeEvent<HTMLInputElement>) => void;
}
export function useCheckboxState<TKey extends string>(options: CheckboxStateOptions<TKey>): ICheckboxState {
const [count, refresh] = useState(0);
const context = useContext(FormContext);
const optionsRef = useObjectRef({ ...options, context, count });
const controlledValue = options.value ?? options.defaultValue ?? undefined;
const optionsRef = useObjectRef({ ...options, context, count, value: controlledValue });
const { state, name, value } = optionsRef;
let checked = optionsRef.checked ?? false;
let checked = optionsRef.checked ?? optionsRef.defaultChecked ?? undefined;
if (state !== undefined && name !== undefined) {
if (state !== undefined && name !== undefined && name in state) {
const currentState = state[name as TKey];
if (typeof value === 'string' && Array.isArray(currentState)) {
checked = currentState.includes(value);
if (typeof value === 'string') {
checked = Array.isArray(currentState) ? currentState.includes(value) : currentState === value;
} else if (typeof currentState === 'string') {
checked = currentState.toLowerCase() === 'true';
} else {
@@ -54,7 +57,7 @@ export function useCheckboxState<TKey extends string>(options: CheckboxStateOpti
}
}
return useObjectRef({
return useObjectRef<ICheckboxState>({
checked,
change(event: React.ChangeEvent<HTMLInputElement>) {
const { state, name, value, onChange, count, context } = optionsRef;
@@ -63,13 +66,16 @@ export function useCheckboxState<TKey extends string>(options: CheckboxStateOpti
if (state !== undefined && name !== undefined) {
const currentState = state[name as TKey];
if (typeof value === 'string' && Array.isArray(currentState)) {
const elementIndex = currentState.indexOf(value);
if (checked && elementIndex === -1) {
currentState.push(value);
} else if (elementIndex !== -1) {
currentState.splice(elementIndex, 1);
if (typeof value === 'string') {
if (Array.isArray(currentState)) {
const elementIndex = currentState.indexOf(value);
if (checked && elementIndex === -1) {
currentState.push(value);
} else if (elementIndex !== -1) {
currentState.splice(elementIndex, 1);
}
} else {
state[name as TKey] = value;
}
} else {
state[name as TKey] = checked;
@@ -97,10 +97,11 @@ const styles = composes(
`
);
type BaseProps<TKey, TValue> = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'onSelect' | 'name' | 'value'> & ILayoutSizeProps & {
type BaseProps<TKey, TValue> = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'onSelect' | 'name' | 'value' | 'defaultValue'> & ILayoutSizeProps & {
propertyName?: string;
items: TValue[];
searchable?: boolean;
defaultValue?: string;
keySelector: (item: TValue) => TKey;
valueSelector: (item: TValue) => string;
onSwitch?: (state: boolean) => void;
@@ -129,6 +130,7 @@ interface ComboboxType {
export const ComboboxNew: ComboboxType = observer(function ComboboxNew({
value: controlledValue,
defaultValue,
name,
state,
propertyName,
@@ -154,14 +156,10 @@ export const ComboboxNew: ComboboxType = observer(function ComboboxNew({
gutter: 4,
});
const [searchValue, setSearchValue] = useState('');
let value: string | number | readonly string[] | undefined = controlledValue;
let value: string | number | readonly string[] | undefined = controlledValue ?? defaultValue ?? undefined;
if (state) {
if (name in state) {
value = state[name];
} else if (rest.defaultValue !== undefined) {
value = rest.defaultValue;
}
if (state && name !== undefined && name in state) {
value = state[name];
}
const selectedItem = items.find(item => keySelector(item) === value);
@@ -27,7 +27,6 @@ type ControlledProps = BaseProps & {
name?: string;
value?: string;
onChange?: (value: string, name?: string) => any;
state?: never;
autoHide?: never;
};
@@ -37,7 +36,6 @@ type ObjectProps<TKey extends keyof TState, TState> = BaseProps & {
state: TState;
onChange?: (value: string, name: TKey) => any;
autoHide?: boolean;
value?: never;
};
@@ -49,6 +47,7 @@ interface InputFieldType {
export const InputField: InputFieldType = observer(function InputField({
name,
value: valueControlled,
defaultValue,
required,
state,
children,
@@ -76,9 +75,13 @@ export const InputField: InputFieldType = observer(function InputField({
}
}, [state, name, context, onChange]);
const value = state ? state[name] : valueControlled;
let value: any = valueControlled ?? defaultValue ?? undefined;
if (autoHide && !isControlPresented(name, state)) {
if (state && name !== undefined && name in state) {
value = state[name];
}
if (autoHide && !isControlPresented(name, state, defaultValue)) {
return null;
}
@@ -64,6 +64,7 @@ export const InputFieldNew: InputFieldType = observer(function InputFieldNew({
name,
style,
value: valueControlled,
defaultValue,
required,
state,
mapState,
@@ -96,11 +97,15 @@ export const InputFieldNew: InputFieldType = observer(function InputFieldNew({
}
}, [state, name, context, onChange]);
if (autoHide && !isControlPresented(name, state)) {
if (autoHide && !isControlPresented(name, state, defaultValue)) {
return null;
}
let value = state ? state[name] : valueControlled;
let value: any = valueControlled ?? defaultValue ?? undefined;
if (state && name !== undefined && name in state) {
value = state[name];
}
if (mapState) {
value = mapState(value);
@@ -6,9 +6,14 @@
* you may not use this file except in compliance with the License.
*/
export function isControlPresented(name: string | undefined, state: any): boolean {
export function isControlPresented(
name: string | undefined, state: any, defaultValue?: string | number | readonly string[]
): boolean {
if (state !== undefined && name !== undefined) {
return name in state && state[name] !== null;
if (name in state) {
return state[name] !== null;
}
return defaultValue !== undefined;
}
return true;
}
@@ -14,13 +14,13 @@ import { InputField } from '@cloudbeaver/core-blocks';
import type { ObjectPropertyInfo } from '@cloudbeaver/core-sdk';
import { useStyles } from '@cloudbeaver/core-theming';
import { FieldCheckbox } from '../FormControls/Checkboxes/FieldCheckbox';
import { Combobox } from '../FormControls/Combobox';
import { FormFieldDescription } from '../FormControls/FormFieldDescription';
import { FormGroup } from '../FormControls/FormGroup';
import { isControlPresented } from '../FormControls/isControlPresented';
import { Link } from '../Link';
import { TextPlaceholder } from '../TextPlaceholder';
import { FieldCheckbox } from '../../FormControls/Checkboxes/FieldCheckbox';
import { Combobox } from '../../FormControls/Combobox';
import { FormFieldDescription } from '../../FormControls/FormFieldDescription';
import { FormGroup } from '../../FormControls/FormGroup';
import { isControlPresented } from '../../FormControls/isControlPresented';
import { Link } from '../../Link';
import { TextPlaceholder } from '../../TextPlaceholder';
import { formStyles } from './formStyles';
const RESERVED_KEYWORDS = ['no', 'off', 'new-password'];
@@ -80,6 +80,7 @@ const RenderField: React.FC<RenderFieldProps> = observer(function RenderField({
if (checkbox) {
return (
<FieldCheckbox
id={property.id}
name={property.id!}
state={state}
label={property.displayName}
@@ -0,0 +1,79 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 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 { observer } from 'mobx-react-lite';
import React, { useCallback } from 'react';
import type { ObjectPropertyInfo } from '@cloudbeaver/core-sdk';
import type { ILayoutSizeProps } from '../../Containers/ILayoutSizeProps';
import { TextPlaceholder } from '../../TextPlaceholder';
import { RenderField } from './RenderField';
interface ObjectPropertyFormProps extends ILayoutSizeProps {
properties: ObjectPropertyInfo[];
state?: Record<string, any>;
category?: string | null;
editable?: boolean;
autofillToken?: string;
className?: string;
disabled?: boolean;
readOnly?: boolean;
autoHide?: boolean;
showRememberTip?: boolean;
onFocus?: (name: string) => void;
}
export const ObjectPropertyInfoFormNew: React.FC<ObjectPropertyFormProps> = observer(function ObjectPropertyInfoFormNew({
properties,
state,
category,
editable = true,
className,
autofillToken = '',
disabled,
readOnly,
autoHide,
showRememberTip,
onFocus,
}) {
const handleFocus = useCallback((e: React.FocusEvent<HTMLInputElement>) => {
if (onFocus) {
onFocus(e.target.name);
}
}, [onFocus]);
if (properties.length === 0) {
return <TextPlaceholder>Properties empty</TextPlaceholder>;
}
return (
<>
{properties.map(property => {
if (category !== undefined && property.category !== category) {
return null;
}
return (
<RenderField
key={property.id}
className={className}
property={property}
state={state}
editable={editable}
autofillToken={autofillToken}
disabled={disabled}
readOnly={readOnly}
autoHide={autoHide}
showRememberTip={showRememberTip}
onFocus={handleFocus}
/>
);
})}
</>
);
});
@@ -0,0 +1,212 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 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 { observer } from 'mobx-react-lite';
import type { ObjectPropertyInfo } from '@cloudbeaver/core-sdk';
import { FieldCheckboxNew } from '../../FormControls/Checkboxes/FieldCheckboxNew';
import { ComboboxNew } from '../../FormControls/ComboboxNew';
import { FormFieldDescriptionNew } from '../../FormControls/FormFieldDescriptionNew';
import { InputFieldNew } from '../../FormControls/InputFieldNew';
import { isControlPresented } from '../../FormControls/isControlPresented';
import { Link } from '../../Link';
const RESERVED_KEYWORDS = ['no', 'off', 'new-password'];
interface RenderFieldProps {
property: ObjectPropertyInfo;
state?: Record<string, any>;
editable?: boolean;
autofillToken?: string;
disabled?: boolean;
readOnly?: boolean;
autoHide?: boolean;
showRememberTip?: boolean;
onFocus?: (event: React.FocusEvent<HTMLInputElement>) => void;
className?: string;
}
type ControlType = 'checkbox' | 'combobox' | 'link' | 'input';
function getControlTypeFor(property: ObjectPropertyInfo): ControlType {
const dataType = property.dataType?.toLowerCase();
if (dataType === 'boolean') {
return 'checkbox';
} else if (property.validValues && property.validValues.length > 0) {
return 'combobox';
} else if (property.features.includes('href')) {
return 'link';
}
return 'input';
}
function getValue(value: any, controlType: ControlType) {
const checkbox = controlType === 'checkbox';
if (value === null || value === undefined) {
return checkbox ? false : '';
}
if (typeof value === 'string') {
return checkbox ? value.toLowerCase() === 'true' : value;
}
return value.displayName || value.value || JSON.stringify(value);
}
export const RenderField: React.FC<RenderFieldProps> = observer(function RenderField({
property,
state,
editable = true,
autofillToken = '',
disabled,
readOnly,
autoHide,
showRememberTip,
onFocus,
className,
}) {
const controltype = getControlTypeFor(property);
const password = property.features.includes('password');
const value = getValue(property.value, controltype);
const defaultValue = getValue(property.defaultValue, controltype);
let description: string | undefined;
if (controltype === 'link') {
return (
<FormFieldDescriptionNew label={property.displayName} className={className}>
<Link href={state?.[property.id!]} target='_blank' rel='noopener noreferrer'>{property.description}</Link>
</FormFieldDescriptionNew>
);
}
if (!editable) {
if (autoHide && !isControlPresented(property.id!, state)) {
return null;
}
return (
<FormFieldDescriptionNew title={property.description} label={property.displayName} className={className}>
{state?.[property.id!]}
</FormFieldDescriptionNew>
);
}
if (showRememberTip && password && property.value) {
description = 'Password saved';
}
if (controltype === 'checkbox') {
if (state !== undefined) {
return (
<FieldCheckboxNew
id={property.id}
name={property.id!}
state={state}
defaultChecked={defaultValue}
title={property.description}
disabled={disabled || readOnly}
className={className}
>
{property.displayName ?? ''}
</FieldCheckboxNew>
);
}
return (
<FieldCheckboxNew
id={property.id}
name={property.id!}
checked={value}
defaultChecked={defaultValue}
title={property.description}
disabled={disabled || readOnly}
className={className}
>
{property.displayName ?? ''}
</FieldCheckboxNew>
);
}
if (controltype === 'combobox') {
if (state !== undefined) {
return (
<ComboboxNew
name={property.id!}
state={state}
items={property.validValues!}
keySelector={value => value}
valueSelector={value => value}
defaultValue={defaultValue}
title={property.description}
disabled={disabled}
className={className}
>
{property.displayName ?? ''}
</ComboboxNew>
);
}
return (
<ComboboxNew
name={property.id!}
items={property.validValues!}
keySelector={value => value}
valueSelector={value => value}
defaultValue={defaultValue}
title={property.description}
disabled={disabled}
className={className}
>
{property.displayName ?? ''}
</ComboboxNew>
);
}
if (state !== undefined) {
return (
<InputFieldNew
type={password ? 'password' : 'text'}
title={property.description}
name={property.id!}
state={state}
defaultValue={defaultValue}
description={description}
disabled={disabled}
readOnly={readOnly}
autoHide={autoHide}
autoComplete={RESERVED_KEYWORDS.includes(autofillToken) ? autofillToken : `${autofillToken} ${property.id}`}
mod='surface'
className={className}
onFocus={onFocus}
>
{property.displayName}
</InputFieldNew>
);
}
return (
<InputFieldNew
type={password ? 'password' : 'text'}
title={property.description}
name={property.id!}
value={value}
defaultValue={defaultValue}
description={description}
disabled={disabled}
readOnly={readOnly}
autoComplete={RESERVED_KEYWORDS.includes(autofillToken) ? autofillToken : `${autofillToken} ${property.id}`}
mod='surface'
className={className}
onFocus={onFocus}
>
{property.displayName}
</InputFieldNew>
);
});
@@ -0,0 +1,42 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 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 { computed, IComputedValue } from 'mobx';
import { useMemo } from 'react';
import type { ObjectPropertyInfo } from '@cloudbeaver/core-sdk';
interface ICategoriesInfo {
categories: string[];
isUncategorizedExists: boolean;
}
export function useObjectPropertyCategories(properties: ObjectPropertyInfo[]) {
const categories: IComputedValue<ICategoriesInfo> = useMemo(() => computed(() => {
const result: ICategoriesInfo = {
categories: [],
isUncategorizedExists: false,
};
for (const property of properties) {
const category = property.category;
if (!category) {
result.isUncategorizedExists = true;
continue;
}
if (!result.categories.includes(category)) {
result.categories.push(category);
}
}
return result;
}), [properties]);
return categories.get();
}
@@ -1,185 +0,0 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 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 { observer } from 'mobx-react-lite';
import { useCallback } from 'react';
import type { ObjectPropertyInfo } from '@cloudbeaver/core-sdk';
import type { ILayoutSizeProps } from '../Containers/ILayoutSizeProps';
import { FieldCheckboxNew } from '../FormControls/Checkboxes/FieldCheckboxNew';
import { ComboboxNew } from '../FormControls/ComboboxNew';
import { FormFieldDescriptionNew } from '../FormControls/FormFieldDescriptionNew';
import { InputFieldNew } from '../FormControls/InputFieldNew';
import { isControlPresented } from '../FormControls/isControlPresented';
import { Link } from '../Link';
import { TextPlaceholder } from '../TextPlaceholder';
const RESERVED_KEYWORDS = ['no', 'off', 'new-password'];
interface RenderFieldProps {
property: ObjectPropertyInfo;
state: Record<string, any>;
editable?: boolean;
autofillToken?: string;
disabled?: boolean;
readOnly?: boolean;
autoHide?: boolean;
showRememberTip?: boolean;
onFocus?: (event: React.FocusEvent<HTMLInputElement>) => void;
className?: string;
}
const RenderField: React.FC<RenderFieldProps> = observer(function RenderField({
property,
state,
editable = true,
autofillToken = '',
disabled,
readOnly,
autoHide,
showRememberTip,
onFocus,
className,
}) {
const href = property.features.includes('href');
const password = property.features.includes('password');
const checkbox = property.dataType === 'Boolean';
const combobox = property.validValues && property.validValues.length > 0;
let description: string | undefined;
if (href) {
return (
<FormFieldDescriptionNew label={property.displayName} className={className}>
<Link href={state[property.id!]} target='_blank' rel='noopener noreferrer'>{property.description}</Link>
</FormFieldDescriptionNew>
);
}
if (!editable) {
if (autoHide && !isControlPresented(property.id!, state)) {
return null;
}
return (
<FormFieldDescriptionNew title={property.description} label={property.displayName} className={className}>
{state[property.id!]}
</FormFieldDescriptionNew>
);
}
if (showRememberTip && password && property.value) {
description = 'Password saved';
}
if (checkbox) {
return (
<FieldCheckboxNew
name={property.id!}
state={state}
title={property.description}
disabled={disabled || readOnly}
className={className}
>
{property.displayName ?? ''}
</FieldCheckboxNew>
);
}
if (combobox) {
return (
<ComboboxNew
name={property.id!}
state={state}
items={property.validValues!}
keySelector={value => value}
valueSelector={value => value}
defaultValue={property.defaultValue}
title={property.description}
disabled={disabled}
className={className}
>
{property.displayName ?? ''}
</ComboboxNew>
);
}
return (
<InputFieldNew
type={password ? 'password' : 'text'}
title={property.description}
name={property.id!}
state={state}
description={description}
disabled={disabled}
readOnly={readOnly}
autoHide={autoHide}
autoComplete={RESERVED_KEYWORDS.includes(autofillToken) ? autofillToken : `${autofillToken} ${property.id}`}
mod='surface'
className={className}
onFocus={onFocus}
>
{property.displayName}
</InputFieldNew>
);
});
interface ObjectPropertyFormProps extends ILayoutSizeProps {
properties: ObjectPropertyInfo[] | undefined;
state: Record<string, string | number>;
editable?: boolean;
autofillToken?: string;
className?: string;
disabled?: boolean;
readOnly?: boolean;
autoHide?: boolean;
showRememberTip?: boolean;
onFocus?: (name: string) => void;
}
export const ObjectPropertyInfoFormNew: React.FC<ObjectPropertyFormProps> = observer(function ObjectPropertyInfoFormNew({
properties,
state,
editable = true,
className,
autofillToken = '',
disabled,
readOnly,
autoHide,
showRememberTip,
onFocus,
}) {
const handleFocus = useCallback((e: React.FocusEvent<HTMLInputElement>) => {
if (onFocus) {
onFocus(e.target.name);
}
}, [onFocus]);
if (!properties || properties.length === 0) {
return <TextPlaceholder>Properties empty</TextPlaceholder>;
}
return (
<>
{properties.map(property => (
<RenderField
key={property.id}
className={className}
property={property}
state={state}
editable={editable}
autofillToken={autofillToken}
disabled={disabled}
readOnly={readOnly}
autoHide={autoHide}
showRememberTip={showRememberTip}
onFocus={handleFocus}
/>
))}
</>
);
});
+3 -2
View File
@@ -4,8 +4,9 @@ export * from './ItemList';
export * from './layout/AppLogo';
export * from './layout/TopAppBar';
export * from './Loader/Loader';
export * from './ObjectPropertyInfoForm/ObjectPropertyInfoForm';
export * from './ObjectPropertyInfoForm/ObjectPropertyInfoFormNew';
export * from './ObjectPropertyInfo/ObjectPropertyInfoForm/ObjectPropertyInfoForm';
export * from './ObjectPropertyInfo/ObjectPropertyInfoForm/ObjectPropertyInfoFormNew';
export * from './ObjectPropertyInfo/useObjectPropertyCategories';
export * from './Placeholder/Placeholder';
export * from './Placeholder/PlaceholderContainer';
@@ -205,8 +205,8 @@ export const Options: TabContainerPanelComponent<IConnectionFormProps> = observe
)}
{admin && originLocal && (
<FieldCheckboxNew
id={config.connectionId}
name="template"
value={config.connectionId}
state={config}
disabled={edit || disabled}
readOnly={readonly}
@@ -227,7 +227,7 @@ export const Options: TabContainerPanelComponent<IConnectionFormProps> = observe
</Group>
</Container>
<Container medium gap>
{(authModel && !driver.data?.anonymousAccess) && (
{(authModel && !driver.data?.anonymousAccess && properties) && (
<Group form gap>
<GroupTitle>{translate('connections_connection_edit_authentication')}</GroupTitle>
<Container wrap gap>
@@ -243,8 +243,8 @@ export const Options: TabContainerPanelComponent<IConnectionFormProps> = observe
</Container>
{credentialsSavingEnabled && (
<FieldCheckboxNew
id={config.connectionId + 'authNeeded'}
name="saveCredentials"
value={config.connectionId + 'authNeeded'}
state={config}
disabled={disabled || readonly}
>{translate('connections_connection_edit_save_credentials')}
@@ -161,8 +161,8 @@ export const SSH: TabContainerPanelComponent<IConnectionFormProps> = observer(fu
</Container>
{credentialsSavingEnabled && (
<FieldCheckboxNew
id={SSH_TUNNEL_ID + ' savePassword'}
name="savePassword"
value={SSH_TUNNEL_ID + ' savePassword'}
state={state}
disabled={disabled || !enabled || readonly}
>{translate('connections_connection_edit_save_credentials')}
@@ -122,8 +122,8 @@ export const DatabaseAuthDialog = observer(function DatabaseAuthDialog({
{credentialsSavingEnabled && (
<FormGroup>
<FieldCheckbox
id={connection.connectionInfo?.id || 'DBAuthSaveCredentials'}
name="saveCredentials"
value={connection.connectionInfo?.id || 'DBAuthSaveCredentials'}
label={translate('connections_connection_edit_save_credentials')}
disabled={controller.isAuthenticating}
state={controller.config}
@@ -98,7 +98,7 @@ export const UserInfo: TabContainerPanelComponent<IUserFormProps> = observer(fun
{controller.roles.map((role, i) => (
<FormGroup key={role.roleId}>
<FieldCheckbox
value={role.roleId}
id={role.roleId}
name='role'
label={role.roleName || role.roleId}
checked={!!controller.credentials.roles.get(role.roleId)}
@@ -118,8 +118,8 @@ export const ConnectionDialog = observer(function ConnectionDialog({
{credentialsSavingEnabled && (
<FormGroup>
<FieldCheckbox
id={controller.template?.id || 'DBAuthSaveCredentials'}
name="saveCredentials"
value={controller.template?.id || 'DBAuthSaveCredentials'}
label={translate('connections_connection_edit_save_credentials')}
disabled={controller.isConnecting}
state={controller.config}
@@ -11,39 +11,58 @@ import type { PropsWithChildren } from 'react';
import styled from 'reshadow';
import { useDatabaseObjectInfo } from '@cloudbeaver/core-app';
import { ColoredContainer, Group, Loader, TextPlaceholder } from '@cloudbeaver/core-blocks';
import { ColoredContainer, Loader, TextPlaceholder, useObjectPropertyCategories, GroupTitle, ObjectPropertyInfoFormNew, Group } from '@cloudbeaver/core-blocks';
import { BASE_CONTAINERS_STYLES } from '@cloudbeaver/core-blocks';
import { useTranslate } from '@cloudbeaver/core-localization';
import type { ObjectPropertyInfo } from '@cloudbeaver/core-sdk';
import { useStyles } from '@cloudbeaver/core-theming';
import { ObjectProperty } from './ObjectProperty';
type ObjectPropertiesProps = PropsWithChildren<{
objectId: string;
}>;
const emptyArray: ObjectPropertyInfo[] = [];
export const ObjectProperties = observer(function ObjectProperties({
objectId,
}: ObjectPropertiesProps) {
const translate = useTranslate();
const { dbObject, isLoading } = useDatabaseObjectInfo(objectId);
const styles = useStyles(BASE_CONTAINERS_STYLES);
const { categories, isUncategorizedExists } = useObjectPropertyCategories(dbObject?.properties ?? emptyArray);
const properties = dbObject?.properties;
if (!dbObject?.properties && isLoading) {
if (!properties && isLoading) {
return <Loader />;
}
if (!dbObject?.properties || dbObject.properties.length === 0) {
if (!properties || properties.length === 0) {
return <TextPlaceholder>{translate('plugin_object_viewer_table_no_items')}</TextPlaceholder>;
}
return styled(styles)(
<ColoredContainer overflow parent>
<Group gap large>
{dbObject.properties.map(v => (
<ObjectProperty key={v.id} objectProperty={v} small />
))}
</Group>
<ColoredContainer overflow parent gap>
{isUncategorizedExists && (
<Group gap large>
<ObjectPropertyInfoFormNew
properties={properties}
category={null}
small
readOnly
/>
</Group>
)}
{categories.map(category => (
<Group key={category} gap large>
<GroupTitle>{category}</GroupTitle>
<ObjectPropertyInfoFormNew
properties={properties}
category={category}
small
readOnly
/>
</Group>
))}
</ColoredContainer>
);
});