mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
Merge branch 'devel' of https://github.com/dbeaver/cloudbeaver into devel
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
+8
-7
@@ -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}
|
||||
+79
@@ -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}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
});
|
||||
+212
@@ -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}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
});
|
||||
@@ -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}
|
||||
|
||||
+1
-1
@@ -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)}
|
||||
|
||||
+1
-1
@@ -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}
|
||||
|
||||
+30
-11
@@ -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>
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user