From 609a870c1912a78804ca8ef2c382f8635ca2a46f Mon Sep 17 00:00:00 2001 From: Wroud Date: Sun, 3 May 2020 20:24:18 +0300 Subject: [PATCH] feat(core): add PropertiesTable block --- webapp/packages/core/assets/locales/en.js | 6 +- webapp/packages/core/assets/locales/ru.js | 8 +- .../DriverPropertyValueSelector.tsx | 0 .../src/blocks/PropertiesTable/IProperty.ts | 15 ++ .../PropertiesTable/PropertiesTable.tsx | 129 ++++++++++++++++++ .../blocks/PropertiesTable/PropertyItem.tsx} | 22 +-- .../core/src/blocks/PropertiesTable/index.ts | 2 + .../core/src/blocks/PropertiesTable/styles.ts | 60 ++++++++ webapp/packages/core/src/blocks/index.ts | 1 + .../ConnectionFormDialog.tsx | 2 +- .../DriverProperties/DriverProperties.tsx | 101 +++----------- .../DriverPropertiesController.ts | 66 ++------- 12 files changed, 261 insertions(+), 151 deletions(-) rename webapp/packages/{custom-connection-plugin/src/CustomConnection/ConnectionFormDialog/DriverProperties => core/src/blocks/PropertiesTable}/DriverPropertyValueSelector.tsx (100%) create mode 100644 webapp/packages/core/src/blocks/PropertiesTable/IProperty.ts create mode 100644 webapp/packages/core/src/blocks/PropertiesTable/PropertiesTable.tsx rename webapp/packages/{custom-connection-plugin/src/CustomConnection/ConnectionFormDialog/DriverProperties/DriverProperty.tsx => core/src/blocks/PropertiesTable/PropertyItem.tsx} (88%) create mode 100644 webapp/packages/core/src/blocks/PropertiesTable/index.ts create mode 100644 webapp/packages/core/src/blocks/PropertiesTable/styles.ts diff --git a/webapp/packages/core/assets/locales/en.js b/webapp/packages/core/assets/locales/en.js index 8a0b9f813b..db69cd5c76 100644 --- a/webapp/packages/core/assets/locales/en.js +++ b/webapp/packages/core/assets/locales/en.js @@ -17,9 +17,6 @@ module.exports = { customConnection_connectionType_url: 'URL', customConnection_options: 'Options', customConnection_properties: 'Driver Properties', - customConnection_properties_name: 'Name', - customConnection_properties_value: 'Value', - customConnection_properties_add: 'Add', customConnection_custom_host: 'Host', customConnection_custom_obligatory: '(obligatory)', customConnection_custom_port: 'Port', @@ -39,6 +36,9 @@ module.exports = { ui_errors_details: 'Details', ui_search: 'Search...', ui_data_saving_error: 'Error when saving changes', + block_properties_table_name: 'Name', + block_properties_table_value: 'Value', + block_properties_table_add: 'Add', app_root_session_expired_title: 'Session expired', app_root_session_expired_message: 'Session has been expired. Do you want to reload?', diff --git a/webapp/packages/core/assets/locales/ru.js b/webapp/packages/core/assets/locales/ru.js index 3471459063..f5692a43f2 100644 --- a/webapp/packages/core/assets/locales/ru.js +++ b/webapp/packages/core/assets/locales/ru.js @@ -16,9 +16,6 @@ module.exports = { customConnection_connectionType_url: 'URL', customConnection_options: 'Подключение', customConnection_properties: 'Параметры драйвера', - customConnection_properties_name: 'Название', - customConnection_properties_value: 'Значение', - customConnection_properties_add: 'Добавить', customConnection_custom_host: 'Хост', customConnection_custom_obligatory: '(обязательное)', customConnection_custom_port: 'Порт', @@ -32,12 +29,15 @@ module.exports = { ui_stepper_back: 'Назад', ui_processing_loading: 'Загрузка...', - ui_processing_cancel: 'Отменить...', + ui_processing_cancel: 'Отменить', ui_processing_retry: 'Повторить', ui_error_message: 'Ошибка:', ui_errors_details: 'Информация', ui_search: 'Поиск...', ui_data_saving_error: 'Ошибка при сохранении изменений', + block_properties_name: 'Название', + block_properties_value: 'Значение', + block_properties_add: 'Добавить', app_root_session_expired_title: 'Сесстия истекла', app_root_session_expired_message: 'Сессия истекла. Хотите перезагрузить страницу?', diff --git a/webapp/packages/custom-connection-plugin/src/CustomConnection/ConnectionFormDialog/DriverProperties/DriverPropertyValueSelector.tsx b/webapp/packages/core/src/blocks/PropertiesTable/DriverPropertyValueSelector.tsx similarity index 100% rename from webapp/packages/custom-connection-plugin/src/CustomConnection/ConnectionFormDialog/DriverProperties/DriverPropertyValueSelector.tsx rename to webapp/packages/core/src/blocks/PropertiesTable/DriverPropertyValueSelector.tsx diff --git a/webapp/packages/core/src/blocks/PropertiesTable/IProperty.ts b/webapp/packages/core/src/blocks/PropertiesTable/IProperty.ts new file mode 100644 index 0000000000..3323e610ea --- /dev/null +++ b/webapp/packages/core/src/blocks/PropertiesTable/IProperty.ts @@ -0,0 +1,15 @@ +/* + * cloudbeaver - Cloud Database Manager + * Copyright (C) 2020 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ + +export interface IProperty { + id: string; + name: string; + defaultValue?: string; + description?: string; + validValues?: string[]; +} diff --git a/webapp/packages/core/src/blocks/PropertiesTable/PropertiesTable.tsx b/webapp/packages/core/src/blocks/PropertiesTable/PropertiesTable.tsx new file mode 100644 index 0000000000..c9e6e4468c --- /dev/null +++ b/webapp/packages/core/src/blocks/PropertiesTable/PropertiesTable.tsx @@ -0,0 +1,129 @@ +/* + * cloudbeaver - Cloud Database Manager + * Copyright (C) 2020 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, useLocalStore } from 'mobx-react'; +import { useCallback } from 'react'; +import styled from 'reshadow'; + +import { Button } from '@dbeaver/core/blocks'; +import { useTranslate } from '@dbeaver/core/localization'; +import { useStyles } from '@dbeaver/core/theming'; + +import { IProperty } from './IProperty'; +import { PropertyItem } from './PropertyItem'; +import { PROPERTIES_TABLE_STYLES } from './styles'; + + +type PropertiesState = { + [key: string]: string; +} + +type PropertiesTableProps = { + properties: IProperty[]; + propertiesState?: PropertiesState; + onNameChange?(id: string, name: string): void; + onChange?(state: PropertiesState): void; + onAdd?(): void; + onRemove?(id: string): void; + className?: string; +} + +export const PropertiesTable = observer(function PropertiesTable({ + propertiesState, + properties, + onNameChange, + onChange, + onAdd, + onRemove, + className, +}: PropertiesTableProps) { + const translate = useTranslate(); + const state = useLocalStore(() => (propertiesState || {})); + const changeName = useCallback((id: string, name: string) => { + const property = properties.find(property => property.id === id); + + if (!property) { + return; + } + + if (state[property.name] !== undefined) { + state[name] = state[property.name]; + delete state[property.name]; + } + + if (onNameChange) { + onNameChange(property.name, name); + } + property.name = name; + }, [properties]); + + const changeValue = useCallback((id: string, value: string) => { + const property = properties.find(property => property.id === id); + + if (!property) { + return; + } + + state[property.name] = value; + + if (onChange) { + onChange(state); + } + }, [properties, onChange]); + + const removeProperty = useCallback((id: string) => { + const property = properties.find(property => property.id === id); + + if (!property) { + return; + } + + if (state[property.name] !== undefined) { + delete state[property.name]; + } + + if (onRemove) { + onRemove(id); + } + properties.splice(properties.indexOf(property), 1); + }, [properties, onRemove]); + + const isNameUnique = useCallback( + (name: string) => properties.filter(property => property.name === name).length === 1, + [] + ); + + return styled(useStyles(PROPERTIES_TABLE_STYLES))( + + + + {translate('block_properties_table_name')} + + + {translate('block_properties_table_value')} + + + {onAdd && } + + + + {properties.map(property => ( + + ))} + + + ); +}); diff --git a/webapp/packages/custom-connection-plugin/src/CustomConnection/ConnectionFormDialog/DriverProperties/DriverProperty.tsx b/webapp/packages/core/src/blocks/PropertiesTable/PropertyItem.tsx similarity index 88% rename from webapp/packages/custom-connection-plugin/src/CustomConnection/ConnectionFormDialog/DriverProperties/DriverProperty.tsx rename to webapp/packages/core/src/blocks/PropertiesTable/PropertyItem.tsx index e46c98c35d..b2f68a2bc0 100644 --- a/webapp/packages/custom-connection-plugin/src/CustomConnection/ConnectionFormDialog/DriverProperties/DriverProperty.tsx +++ b/webapp/packages/core/src/blocks/PropertiesTable/PropertyItem.tsx @@ -15,8 +15,8 @@ import styled, { css, use } from 'reshadow'; import { ShadowInput, Icon } from '@dbeaver/core/blocks'; import { composes, useStyles } from '@dbeaver/core/theming'; -import { DriverPropertyInfoWithStaticId } from './DriverPropertiesController'; import { DriverPropertyValueSelector } from './DriverPropertyValueSelector'; +import { IProperty } from './IProperty'; const styles = composes( css` @@ -104,8 +104,8 @@ const styles = composes( ` ); -type DriverPropertyProps = { - property: DriverPropertyInfoWithStaticId; +type PropertyItemProps = { + property: IProperty; value?: string; onNameChange(staticId: string, newId: string): void; onValueChange(staticId: string, value: string): void; @@ -113,24 +113,24 @@ type DriverPropertyProps = { error?: boolean; } -export const DriverProperty = observer(function DriverProperty({ +export const PropertyItem = observer(function PropertyItem({ property, value, onNameChange, onValueChange, onRemove, error, -}: DriverPropertyProps) { - const isEditable = !property.displayName; +}: PropertyItemProps) { + const isEditable = property.name !== property.id; const edited = value !== undefined && value !== property.defaultValue; const [focus, setFocus] = useState(false); const nameInputRef = useRef(null); - const handleNameChange = useCallback((value: string) => onNameChange(property.staticId, value), [property.staticId]); + const handleNameChange = useCallback((value: string) => onNameChange(property.id, value), [property]); const handleValueChange = useCallback( - (value: string) => onValueChange(property.staticId, value), - [property.staticId] + (value: string) => onValueChange(property.id, value), + [property] ); - const handleRemove = useCallback(() => onRemove(property.staticId), [property.staticId]); + const handleRemove = useCallback(() => onRemove(property.id), [property]); useEffect(() => { if (nameInputRef.current && isEditable) { @@ -149,7 +149,7 @@ export const DriverProperty = observer(function DriverProperty({ readOnly={!isEditable} autoComplete='none' > - {property.displayName || property.id} + {property.name || property.id} diff --git a/webapp/packages/core/src/blocks/PropertiesTable/index.ts b/webapp/packages/core/src/blocks/PropertiesTable/index.ts new file mode 100644 index 0000000000..3733bb82d4 --- /dev/null +++ b/webapp/packages/core/src/blocks/PropertiesTable/index.ts @@ -0,0 +1,2 @@ +export * from './PropertiesTable'; +export * from './IProperty'; diff --git a/webapp/packages/core/src/blocks/PropertiesTable/styles.ts b/webapp/packages/core/src/blocks/PropertiesTable/styles.ts new file mode 100644 index 0000000000..dfb8bdfacd --- /dev/null +++ b/webapp/packages/core/src/blocks/PropertiesTable/styles.ts @@ -0,0 +1,60 @@ +/* + * cloudbeaver - Cloud Database Manager + * Copyright (C) 2020 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'; + +import { composes } from '@dbeaver/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; + } + `, + css` + properties { + display: flex; + flex: 1; + flex-direction: column; + } + properties-header { + box-sizing: border-box; + display: inline-flex; + padding: 5px 1px; + position: sticky; + z-index: 1; + top: 0; + } + properties-header-name, properties-header-value { + composes: theme-typography--caption from global; + text-transform: uppercase; + box-sizing: border-box; + flex: 1; + padding: 4px 36px; + margin: 0px 1px; + } + properties-header-name { + flex: 0 0 auto; + width: 300px; + } + properties-header-right { + flex: 0 0 auto; + margin: 0px 1px; + } + + properties-list { + flex: 1; + display: flex; + flex-direction: column; + padding-bottom: 24px; + } + ` +); diff --git a/webapp/packages/core/src/blocks/index.ts b/webapp/packages/core/src/blocks/index.ts index af8cf51a50..00fd243969 100644 --- a/webapp/packages/core/src/blocks/index.ts +++ b/webapp/packages/core/src/blocks/index.ts @@ -16,3 +16,4 @@ export * from './useClipboard'; export * from './SubmittingForm'; export * from './SanitizedHTML/SanitizedHTML'; export * from './ItemList'; +export * from './PropertiesTable'; diff --git a/webapp/packages/custom-connection-plugin/src/CustomConnection/ConnectionFormDialog/ConnectionFormDialog.tsx b/webapp/packages/custom-connection-plugin/src/CustomConnection/ConnectionFormDialog/ConnectionFormDialog.tsx index e9dfbb2a40..0ef2030383 100644 --- a/webapp/packages/custom-connection-plugin/src/CustomConnection/ConnectionFormDialog/ConnectionFormDialog.tsx +++ b/webapp/packages/custom-connection-plugin/src/CustomConnection/ConnectionFormDialog/ConnectionFormDialog.tsx @@ -144,7 +144,7 @@ export const ConnectionFormDialog = observer( diff --git a/webapp/packages/custom-connection-plugin/src/CustomConnection/ConnectionFormDialog/DriverProperties/DriverProperties.tsx b/webapp/packages/custom-connection-plugin/src/CustomConnection/ConnectionFormDialog/DriverProperties/DriverProperties.tsx index 0996dfb804..b759af3c49 100644 --- a/webapp/packages/custom-connection-plugin/src/CustomConnection/ConnectionFormDialog/DriverProperties/DriverProperties.tsx +++ b/webapp/packages/custom-connection-plugin/src/CustomConnection/ConnectionFormDialog/DriverProperties/DriverProperties.tsx @@ -11,76 +11,36 @@ import { useEffect } from 'react'; import styled, { css } from 'reshadow'; import { DBDriver } from '@dbeaver/core/app'; -import { Button, Loader } from '@dbeaver/core/blocks'; +import { Loader, PropertiesTable } from '@dbeaver/core/blocks'; import { useController } from '@dbeaver/core/di'; -import { useTranslate } from '@dbeaver/core/localization'; -import { useStyles, composes } from '@dbeaver/core/theming'; +import { useStyles } from '@dbeaver/core/theming'; -import { DriverPropertiesController, DriverPropertyState } from './DriverPropertiesController'; -import { DriverProperty } from './DriverProperty'; +import { DriverPropertiesController } from './DriverPropertiesController'; -const 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; - } - `, - css` - properties { - display: flex; - flex: 1; - flex-direction: column; - } - properties-header { - box-sizing: border-box; - display: inline-flex; - padding: 5px 1px; - position: sticky; - z-index: 1; - top: 0; - } - properties-header-name, properties-header-value { - composes: theme-typography--caption from global; - text-transform: uppercase; - box-sizing: border-box; - flex: 1; - padding: 4px 36px; - margin: 0px 1px; - } - properties-header-name { - flex: 0 0 auto; - width: 300px; - } - properties-header-right { - flex: 0 0 auto; - margin: 0px 1px; - } +const styles = css` + properties { + display: flex; + flex: 1; + flex-direction: column; + } +`; - properties-list { - flex: 1; - display: flex; - flex-direction: column; - padding-bottom: 24px; - } - ` -); +type DriverPropertyState = { + [key: string]: string; +} type DriverPropertiesProps = { driver: DBDriver; - properties: DriverPropertyState; + state: DriverPropertyState; isSelected: boolean; } export const DriverProperties = observer(function DriverProperties({ driver, - properties, + state, isSelected, }: DriverPropertiesProps) { - const translate = useTranslate(); - const controller = useController(DriverPropertiesController, driver, properties); + const controller = useController(DriverPropertiesController, driver); useEffect(() => { if (isSelected) { @@ -90,32 +50,13 @@ export const DriverProperties = observer(function DriverProperties({ return styled(useStyles(styles))( - - - {translate('customConnection_properties_name')} - - - {translate('customConnection_properties_value')} - - - - - {controller.isLoading && } {!controller.isLoading && ( - - {controller.driverProperties.map(property => ( - 1} - /> - ))} - + )} ); diff --git a/webapp/packages/custom-connection-plugin/src/CustomConnection/ConnectionFormDialog/DriverProperties/DriverPropertiesController.ts b/webapp/packages/custom-connection-plugin/src/CustomConnection/ConnectionFormDialog/DriverProperties/DriverPropertiesController.ts index 68181fd295..b0a268c68d 100644 --- a/webapp/packages/custom-connection-plugin/src/CustomConnection/ConnectionFormDialog/DriverProperties/DriverPropertiesController.ts +++ b/webapp/packages/custom-connection-plugin/src/CustomConnection/ConnectionFormDialog/DriverProperties/DriverPropertiesController.ts @@ -6,9 +6,10 @@ * you may not use this file except in compliance with the License. */ -import { observable, action } from 'mobx'; +import { observable } from 'mobx'; import { DBDriver } from '@dbeaver/core/app'; +import { IProperty } from '@dbeaver/core/blocks'; import { injectable, IInitializableController } from '@dbeaver/core/di'; import { NotificationService } from '@dbeaver/core/eventsLog'; import { DriverPropertyInfo } from '@dbeaver/core/sdk'; @@ -32,70 +33,25 @@ export class DriverPropertiesController implements IInitializableController { @observable driver!: DBDriver @observable hasDetails = false @observable responseMessage: string | null = null - @observable driverProperties = observable([]) - @observable state: DriverPropertyState = {} + @observable driverProperties = observable([]) private loaded = false; constructor(private customConnectionService: CustomConnectionService, private notificationService: NotificationService) { } - init(driver: DBDriver, state: DriverPropertyState) { + init(driver: DBDriver) { this.driver = driver; - this.state = state; - } - - getPropertyIdCount(propertyId: string): number { - return this.driverProperties.filter(property => property.id === propertyId).length; } onAddProperty = () => { this.driverProperties.unshift({ - staticId: uuid(), - id: 'property', + id: uuid(), + name: 'property', + defaultValue: '', }); } - onValueChange = (staticId: string, value: string) => { - const property = this.driverProperties.find(property => property.staticId === staticId); - - if (!property) { - return; - } - - this.state[property.id] = value; - } - - @action - onNameChange = (staticId: string, newId: string) => { - const property = this.driverProperties.find(property => property.staticId === staticId); - - if (!property) { - return; - } - - if (this.state[property.id] !== undefined) { - this.state[newId] = this.state[property.id]; - delete this.state[property.id]; - } - - property.id = newId; - } - - @action - onRemove = (staticId: string) => { - const property = this.driverProperties.find(property => property.staticId === staticId); - - if (!property) { - return; - } - - if (this.state[property.id] !== undefined) { - delete this.state[property.id]; - } - this.driverProperties.remove(property); - } - async loadDriverProperties() { if (this.isLoading || this.loaded) { return; @@ -103,7 +59,13 @@ export class DriverPropertiesController implements IInitializableController { this.isLoading = true; try { const driverProperties = await this.customConnectionService.loadDriverProperties(this.driver.id); - this.driverProperties = observable(driverProperties.map(property => ({ ...property, staticId: property.id }))); + this.driverProperties = observable(driverProperties.map(property => ({ + id: property.displayName!, + name: property.displayName!, + defaultValue: property.defaultValue, + description: property.description, + validValues: property.validValues, + }))); this.loaded = true; } catch (exception) { this.notificationService.logException(exception, 'Can\'t load driver properties');