From 7e733bb44b38c316a862bac753ea8efa8d85776f Mon Sep 17 00:00:00 2001 From: Wroud Date: Tue, 1 Sep 2020 22:40:09 +0300 Subject: [PATCH 1/6] feat(core-executor): handlers remove function --- webapp/packages/core-executor/src/Executor.ts | 8 ++++++++ webapp/packages/core-executor/src/IExecutor.ts | 3 +++ 2 files changed, 11 insertions(+) diff --git a/webapp/packages/core-executor/src/Executor.ts b/webapp/packages/core-executor/src/Executor.ts index ee9c2a7603..3864d175c9 100644 --- a/webapp/packages/core-executor/src/Executor.ts +++ b/webapp/packages/core-executor/src/Executor.ts @@ -43,8 +43,16 @@ export class Executor implements IExecutor { return this; } + removeHandler(handler: IExecutorHandler) { + this.handlers = this.handlers.filter(h => h === handler); + } + addPostHandler(handler: IExecutorHandler) { this.postHandlers.push(handler); return this; } + + removePostHandler(handler: IExecutorHandler) { + this.postHandlers = this.postHandlers.filter(h => h === handler); + } } diff --git a/webapp/packages/core-executor/src/IExecutor.ts b/webapp/packages/core-executor/src/IExecutor.ts index 07b1a065ec..c0146a882c 100644 --- a/webapp/packages/core-executor/src/IExecutor.ts +++ b/webapp/packages/core-executor/src/IExecutor.ts @@ -11,4 +11,7 @@ import { IExecutorHandler } from './IExecutorHandler'; export interface IExecutor { execute(data: T): Promise; addHandler(handler: IExecutorHandler): void; + removeHandler(handler: IExecutorHandler): void; + addPostHandler(handler: IExecutorHandler): void; + removePostHandler(handler: IExecutorHandler): void; } From d1eb72d4890f46fc05183ec34f3dbcd1efb91235 Mon Sep 17 00:00:00 2001 From: Wroud Date: Tue, 1 Sep 2020 23:27:21 +0300 Subject: [PATCH 2/6] refactor(core-blocks): use generic for useFocus hook --- .../Pages/ServerConfiguration/ServerConfigurationForm.tsx | 4 ++-- .../core-app/src/NavigationTree/NavigationTree.tsx | 4 ++-- .../NavigationTabsBar/NavigationTabsBar.tsx | 4 ++-- webapp/packages/core-blocks/src/useFocus.ts | 8 ++++---- .../ConnectionsTable/ConnectionForm/ConnectionForm.tsx | 4 ++-- .../src/DatabaseAuthDialog/DatabaseAuthDialog.tsx | 4 ++-- .../src/Administration/Users/UserForm/UserForm.tsx | 4 ++-- .../src/Dialog/AuthProviderForm/AuthProviderForm.tsx | 4 ++-- .../ConnectionForm/ConnectionForm.tsx | 4 ++-- .../src/ConnectionDialog/ConnectionDialog.tsx | 4 ++-- 10 files changed, 22 insertions(+), 22 deletions(-) diff --git a/webapp/packages/core-administration/src/AdministrationScreen/ConfigurationWizard/Pages/ServerConfiguration/ServerConfigurationForm.tsx b/webapp/packages/core-administration/src/AdministrationScreen/ConfigurationWizard/Pages/ServerConfiguration/ServerConfigurationForm.tsx index 0eadab70a8..c36c7b4c90 100644 --- a/webapp/packages/core-administration/src/AdministrationScreen/ConfigurationWizard/Pages/ServerConfiguration/ServerConfigurationForm.tsx +++ b/webapp/packages/core-administration/src/AdministrationScreen/ConfigurationWizard/Pages/ServerConfiguration/ServerConfigurationForm.tsx @@ -52,10 +52,10 @@ export const ServerConfigurationForm = observer(function ServerConfigurationForm onSave, }: Props) { const translate = useTranslate(); - const [focusedRef] = useFocus({ focusFirstChild: true }); + const [focusedRef] = useFocus({ focusFirstChild: true }); return styled(useStyles(formStyles, boxStyles))( - }> + diff --git a/webapp/packages/core-app/src/NavigationTree/NavigationTree.tsx b/webapp/packages/core-app/src/NavigationTree/NavigationTree.tsx index f545a710ba..5b6f44f5e9 100644 --- a/webapp/packages/core-app/src/NavigationTree/NavigationTree.tsx +++ b/webapp/packages/core-app/src/NavigationTree/NavigationTree.tsx @@ -47,7 +47,7 @@ const navigationTreeStyles = css` export const NavigationTree = observer(function NavigationTree() { const navTreeService = useService(NavigationTreeService); const [onFocus, onBlur] = useActiveView(navTreeService.getView); - const [ref] = useFocus({ onFocus, onBlur }); + const [ref] = useFocus({ onFocus, onBlur }); const nodeChildren = useChildren(); const isEnabled = usePermission(EPermission.public); @@ -69,7 +69,7 @@ export const NavigationTree = observer(function NavigationTree() { } return styled(navigationTreeStyles)( - }> + {nodeChildren.children.map(id => ( ))} diff --git a/webapp/packages/core-app/src/shared/NavigationTabs/NavigationTabsBar/NavigationTabsBar.tsx b/webapp/packages/core-app/src/shared/NavigationTabs/NavigationTabsBar/NavigationTabsBar.tsx index 47eb312aa6..57da00ce7b 100644 --- a/webapp/packages/core-app/src/shared/NavigationTabs/NavigationTabsBar/NavigationTabsBar.tsx +++ b/webapp/packages/core-app/src/shared/NavigationTabs/NavigationTabsBar/NavigationTabsBar.tsx @@ -41,7 +41,7 @@ const stylesArray = [styles]; export const NavigationTabsBar = observer(function NavigationTabsBar() { const navigation = useService(NavigationTabsService); const [onFocus, onBlur] = useActiveView(navigation.getView); - const [ref] = useFocus({ onFocus, onBlur }); + const [ref] = useFocus({ onFocus, onBlur }); const handleSelect = useCallback((tabId: string) => navigation.selectTab(tabId), [navigation]); const handleClose = useCallback((tabId: string) => navigation.closeTab(tabId), [navigation]); @@ -62,7 +62,7 @@ export const NavigationTabsBar = observer(function NavigationTabsBar() { ))} style={stylesArray} tabIndex={0} - ref={ref as React.RefObject} + ref={ref} > {navigation.tabIdList.map(tabId => ( diff --git a/webapp/packages/core-blocks/src/useFocus.ts b/webapp/packages/core-blocks/src/useFocus.ts index fa078a81e0..47b0beaba1 100644 --- a/webapp/packages/core-blocks/src/useFocus.ts +++ b/webapp/packages/core-blocks/src/useFocus.ts @@ -16,18 +16,18 @@ type FocusOptions = { onBlur?: () => void; } -export function useFocus({ +export function useFocus({ focusFirstChild, onFocus, onBlur, -}: FocusOptions): [React.RefObject, boolean] { +}: FocusOptions): [React.RefObject, boolean] { const [focus, setFocus] = useState(false); - const reference = useRef(null); + const reference = useRef(null); useLayoutEffect(() => { if (reference.current !== null && focusFirstChild) { const firstFocusable = reference.current - .querySelector('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'); + .querySelector('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'); if (firstFocusable) { firstFocusable.focus(); diff --git a/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/ConnectionForm/ConnectionForm.tsx b/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/ConnectionForm/ConnectionForm.tsx index c0e6634d28..b85a2b3195 100644 --- a/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/ConnectionForm/ConnectionForm.tsx +++ b/webapp/packages/core-connections/src/Administration/Connections/ConnectionsTable/ConnectionForm/ConnectionForm.tsx @@ -39,10 +39,10 @@ export const ConnectionForm = observer(function ConnectionForm({ controller, }: ConnectionFormProps) { const translate = useTranslate(); - const [focusedRef] = useFocus({ focusFirstChild: true }); + const [focusedRef] = useFocus({ focusFirstChild: true }); return styled(useStyles(styles, formStyles))( - }> + diff --git a/webapp/packages/core-connections/src/DatabaseAuthDialog/DatabaseAuthDialog.tsx b/webapp/packages/core-connections/src/DatabaseAuthDialog/DatabaseAuthDialog.tsx index a8effa4255..81ecb71678 100644 --- a/webapp/packages/core-connections/src/DatabaseAuthDialog/DatabaseAuthDialog.tsx +++ b/webapp/packages/core-connections/src/DatabaseAuthDialog/DatabaseAuthDialog.tsx @@ -61,7 +61,7 @@ export const DatabaseAuthDialog = observer(function DatabaseAuthDialog({ rejectDialog, }: DialogComponentProps) { const connection = useConnectionInfo(payload); - const [focusedRef] = useFocus({ focusFirstChild: true }); + const [focusedRef] = useFocus({ focusFirstChild: true }); const { driver } = useDBDriver(connection.connectionInfo?.driverId || ''); const controller = useController(DBAuthDialogController, payload, rejectDialog); @@ -81,7 +81,7 @@ export const DatabaseAuthDialog = observer(function DatabaseAuthDialog({ {(!connection.isLoaded() || connection.isLoading()) ? : ( - }> + ({ focusFirstChild: true }); const handleLoginChange = useCallback( (value: string) => controller.credentials.login = value, @@ -186,7 +186,7 @@ export const UserForm = observer(function UserForm({ - }> + diff --git a/webapp/packages/plugin-authentication/src/Dialog/AuthProviderForm/AuthProviderForm.tsx b/webapp/packages/plugin-authentication/src/Dialog/AuthProviderForm/AuthProviderForm.tsx index 06974d6617..ac1ce255d2 100644 --- a/webapp/packages/plugin-authentication/src/Dialog/AuthProviderForm/AuthProviderForm.tsx +++ b/webapp/packages/plugin-authentication/src/Dialog/AuthProviderForm/AuthProviderForm.tsx @@ -27,13 +27,13 @@ export const AuthProviderForm = observer(function AuthProviderForm({ credentials, authenticate, }: Props) { - const [elementRef] = useFocus({ focusFirstChild: true }); + const [elementRef] = useFocus({ focusFirstChild: true }); const handleChange = useCallback((key: string, value: string) => { credentials[key] = value; }, [credentials]); return styled(useStyles(formStyles))( - }> + {provider.credentialParameters.map(parameter => parameter.user && ( ({ focusFirstChild: true }); return styled(useStyles(formStyles))( - }> + ) { - const [focusedRef] = useFocus({ focusFirstChild: true }); + const [focusedRef] = useFocus({ focusFirstChild: true }); const controller = useController(ConnectionController, rejectDialog); const translate = useTranslate(); let title = translate('basicConnection_connectionDialog_newConnection'); @@ -81,7 +81,7 @@ export const ConnectionDialog = observer(function ConnectionDialog({ {controller.isConnecting && translate('basicConnection_connectionDialog_connecting_message')} ) : ( - }> + Date: Tue, 1 Sep 2020 23:28:21 +0300 Subject: [PATCH 3/6] feat(core-executor): execute returns context --- webapp/packages/core-executor/src/Executor.ts | 7 ++++--- webapp/packages/core-executor/src/IExecutor.ts | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/webapp/packages/core-executor/src/Executor.ts b/webapp/packages/core-executor/src/Executor.ts index 3864d175c9..d9475eec21 100644 --- a/webapp/packages/core-executor/src/Executor.ts +++ b/webapp/packages/core-executor/src/Executor.ts @@ -6,7 +6,7 @@ * you may not use this file except in compliance with the License. */ -import { ExecutionContext } from './ExecutionContext'; +import { ExecutionContext, IContextProvider } from './ExecutionContext'; import { IExecutor } from './IExecutor'; import { IExecutorHandler } from './IExecutorHandler'; @@ -18,7 +18,7 @@ export class Executor implements IExecutor { private defaultData?: T | null ) { } - async execute(data: T) { + async execute(data: T): Promise> { if ((data === undefined || data === null) && this.defaultData !== undefined && this.defaultData !== null) { data = this.defaultData; } @@ -29,13 +29,14 @@ export class Executor implements IExecutor { const result = await handler(context, data); if (result === false) { - return; + return context; } } for (const handler of this.postHandlers) { await handler(context, data); } + return context; } addHandler(handler: IExecutorHandler) { diff --git a/webapp/packages/core-executor/src/IExecutor.ts b/webapp/packages/core-executor/src/IExecutor.ts index c0146a882c..88197768d7 100644 --- a/webapp/packages/core-executor/src/IExecutor.ts +++ b/webapp/packages/core-executor/src/IExecutor.ts @@ -6,10 +6,11 @@ * you may not use this file except in compliance with the License. */ +import { IContextProvider } from './ExecutionContext'; import { IExecutorHandler } from './IExecutorHandler'; export interface IExecutor { - execute(data: T): Promise; + execute(data: T): Promise>; addHandler(handler: IExecutorHandler): void; removeHandler(handler: IExecutorHandler): void; addPostHandler(handler: IExecutorHandler): void; From 08ef22b15c7e5547866a2fafb4b85510a0f48cfd Mon Sep 17 00:00:00 2001 From: Wroud Date: Tue, 1 Sep 2020 23:29:44 +0300 Subject: [PATCH 4/6] feat(core-administration): configuration forms validation handling --- .../packages/core-administration/package.json | 1 + .../AdministrationItem/IAdministrationItem.ts | 2 +- .../ConfigurationWizardService.ts | 29 ++++++++++--------- .../ServerConfigurationForm.tsx | 15 ++++++++++ .../ServerConfigurationService.ts | 25 ++++++++++++++-- .../ConfigurationWizard/WizardStepper.tsx | 1 - 6 files changed, 54 insertions(+), 19 deletions(-) diff --git a/webapp/packages/core-administration/package.json b/webapp/packages/core-administration/package.json index b91285872c..adf258a365 100644 --- a/webapp/packages/core-administration/package.json +++ b/webapp/packages/core-administration/package.json @@ -20,6 +20,7 @@ "@cloudbeaver/core-root": "^0.1.0", "@cloudbeaver/core-routing": "^0.1.0", "@cloudbeaver/core-theming": "^0.1.0", + "@cloudbeaver/core-executor": "^0.1.0", "react": "^16.x.x", "mobx": "^5.x.x", "mobx-react": "^6.x.x", diff --git a/webapp/packages/core-administration/src/AdministrationItem/IAdministrationItem.ts b/webapp/packages/core-administration/src/AdministrationItem/IAdministrationItem.ts index 2e7b9b2ea2..735c10e856 100644 --- a/webapp/packages/core-administration/src/AdministrationItem/IAdministrationItem.ts +++ b/webapp/packages/core-administration/src/AdministrationItem/IAdministrationItem.ts @@ -54,7 +54,7 @@ export interface IAdministrationItemSubItem { export interface IConfigurationWizardItemOptions { description: string; - onValidate?: () => boolean; + onValidate?: () => Promise | boolean; isDisabled?: () => boolean; isHidden?: () => boolean; isDone?: () => boolean; diff --git a/webapp/packages/core-administration/src/AdministrationScreen/ConfigurationWizard/ConfigurationWizardService.ts b/webapp/packages/core-administration/src/AdministrationScreen/ConfigurationWizard/ConfigurationWizardService.ts index a0292993c1..38daf0e41b 100644 --- a/webapp/packages/core-administration/src/AdministrationScreen/ConfigurationWizard/ConfigurationWizardService.ts +++ b/webapp/packages/core-administration/src/AdministrationScreen/ConfigurationWizard/ConfigurationWizardService.ts @@ -55,15 +55,6 @@ export class ConfigurationWizardService { }); } - @computed get canNext() { - if (this.currentStep?.configurationWizardOptions?.isDone - && !this.currentStep?.configurationWizardOptions?.isDone()) { - return false; - } - - return true; - } - @computed get nextStep() { return this.steps.find((item, index) => { if (index <= this.currentStepIndex) { @@ -112,27 +103,37 @@ export class ConfigurationWizardService { const step = this.getStep(name); if (!step) { - return; + return false; + } + + if (step.configurationWizardOptions?.onValidate) { + const isValid = await step.configurationWizardOptions.onValidate(); + if (!isValid) { + return false; + } } if (step.configurationWizardOptions?.onFinish) { await step.configurationWizardOptions.onFinish(); } + return true; } - next() { - if (!this.currentStep || !this.canNext) { + async next() { + if (!this.currentStep) { return; } - this.finishStep(this.currentStep.name); + if (!await this.finishStep(this.currentStep.name)) { + return; + } if (this.currentStepIndex + 1 < this.steps.length) { if (this.nextStep) { this.administrationScreenService.navigateToItem(this.nextStep.name); } } else { - this.finish(); + await this.finish(); } } diff --git a/webapp/packages/core-administration/src/AdministrationScreen/ConfigurationWizard/Pages/ServerConfiguration/ServerConfigurationForm.tsx b/webapp/packages/core-administration/src/AdministrationScreen/ConfigurationWizard/Pages/ServerConfiguration/ServerConfigurationForm.tsx index c36c7b4c90..d39f7d5086 100644 --- a/webapp/packages/core-administration/src/AdministrationScreen/ConfigurationWizard/Pages/ServerConfiguration/ServerConfigurationForm.tsx +++ b/webapp/packages/core-administration/src/AdministrationScreen/ConfigurationWizard/Pages/ServerConfiguration/ServerConfigurationForm.tsx @@ -7,15 +7,19 @@ */ import { observer } from 'mobx-react'; +import { useEffect } from 'react'; import styled, { css } from 'reshadow'; import { SubmittingForm, InputGroup, InputField, useFocus, Switch } from '@cloudbeaver/core-blocks'; +import { useService } from '@cloudbeaver/core-di'; import { useTranslate } from '@cloudbeaver/core-localization'; import { ServerConfigInput } from '@cloudbeaver/core-sdk'; import { useStyles } from '@cloudbeaver/core-theming'; +import { ServerConfigurationService } from './ServerConfigurationService'; + export const formStyles = css` SubmittingForm { flex: 1; @@ -51,9 +55,20 @@ export const ServerConfigurationForm = observer(function ServerConfigurationForm onChange, onSave, }: Props) { + const service = useService(ServerConfigurationService); const translate = useTranslate(); const [focusedRef] = useFocus({ focusFirstChild: true }); + useEffect(() => { + const validate = () => { + focusedRef.current?.checkValidity(); + focusedRef.current?.reportValidity(); + }; + service.validationTask.addHandler(validate); + + return () => service.validationTask.removeHandler(validate); + }, [service]); + return styled(useStyles(formStyles, boxStyles))( diff --git a/webapp/packages/core-administration/src/AdministrationScreen/ConfigurationWizard/Pages/ServerConfiguration/ServerConfigurationService.ts b/webapp/packages/core-administration/src/AdministrationScreen/ConfigurationWizard/Pages/ServerConfiguration/ServerConfigurationService.ts index 7b3870a25f..ed5bf7bd55 100644 --- a/webapp/packages/core-administration/src/AdministrationScreen/ConfigurationWizard/Pages/ServerConfiguration/ServerConfigurationService.ts +++ b/webapp/packages/core-administration/src/AdministrationScreen/ConfigurationWizard/Pages/ServerConfiguration/ServerConfigurationService.ts @@ -8,6 +8,7 @@ import { injectable } from '@cloudbeaver/core-di'; import { NotificationService } from '@cloudbeaver/core-events'; +import { IExecutor, Executor, IContextProvider } from '@cloudbeaver/core-executor'; import { GraphQLService } from '@cloudbeaver/core-sdk'; import { AdministrationScreenService } from '../../../AdministrationScreenService'; @@ -17,6 +18,7 @@ import { IServerConfigurationPageState } from './IServerConfigurationPageState'; export class ServerConfigurationService { readonly state: IServerConfigurationPageState; + readonly validationTask: IExecutor; constructor( private administrationScreenService: AdministrationScreenService, @@ -42,18 +44,35 @@ export class ServerConfigurationService { showUtilityObjects: false, }, })); + this.validationTask = new Executor(); } isDone() { return this.isFormFilled(); } - validate() { - return this.isFormFilled(); + async validate() { + const context = await this.validationTask.execute(true); + const state = await context.getContext(this.validationStatusContext); + + return state.getState(); + } + + validationStatusContext = (context: IContextProvider) => { + let state = this.isFormFilled(); + + const invalidate = () => { + state = false; + }; + const getState = () => state; + + return { + getState, + invalidate, + }; } private isFormFilled() { - return !!( this.state?.serverConfig.serverName && this.state.serverConfig.adminName diff --git a/webapp/packages/core-administration/src/AdministrationScreen/ConfigurationWizard/WizardStepper.tsx b/webapp/packages/core-administration/src/AdministrationScreen/ConfigurationWizard/WizardStepper.tsx index c7675faeea..fc42b83bbb 100644 --- a/webapp/packages/core-administration/src/AdministrationScreen/ConfigurationWizard/WizardStepper.tsx +++ b/webapp/packages/core-administration/src/AdministrationScreen/ConfigurationWizard/WizardStepper.tsx @@ -79,7 +79,6 @@ export const WizardStepper = observer(function WizardStepper() { type="button" mod={['unelevated']} onClick={() => service.next()} - disabled={!service.canNext} > {translate(service.currentStepIndex === service.steps.length - 1 ? 'ui_stepper_finish' : 'ui_stepper_next')} From 88174f86351d76d0856061159ede661307eb6877 Mon Sep 17 00:00:00 2001 From: Wroud Date: Wed, 2 Sep 2020 10:50:31 +0300 Subject: [PATCH 5/6] chore(plugin-data-spreadsheet): loading line animation CB-264 --- .../src/AgGridTable/AgGridTable.tsx | 4 ++-- .../src/AgGridTable/AgGridTableController.ts | 7 +------ .../src/AgGridTable/IndexCellRenderer.tsx | 19 +++++++++++++++++++ .../src/AgGridTable/LoadingCell.tsx | 15 --------------- 4 files changed, 22 insertions(+), 23 deletions(-) create mode 100644 webapp/packages/plugin-data-spreadsheet/src/AgGridTable/IndexCellRenderer.tsx delete mode 100644 webapp/packages/plugin-data-spreadsheet/src/AgGridTable/LoadingCell.tsx diff --git a/webapp/packages/plugin-data-spreadsheet/src/AgGridTable/AgGridTable.tsx b/webapp/packages/plugin-data-spreadsheet/src/AgGridTable/AgGridTable.tsx index 08f0aec3ed..e70464bd49 100644 --- a/webapp/packages/plugin-data-spreadsheet/src/AgGridTable/AgGridTable.tsx +++ b/webapp/packages/plugin-data-spreadsheet/src/AgGridTable/AgGridTable.tsx @@ -18,7 +18,7 @@ import { TableViewerModel } from '@cloudbeaver/plugin-data-viewer'; import { agGridStyles } from '../styles/styles'; import { AgGridTableController } from './AgGridTableController'; -import { LoadingCell } from './LoadingCell'; +import { IndexCellRenderer } from './IndexCellRenderer'; import { PlainTextEditor } from './PlainTextEditor/PlainTextEditor'; import { TableColumnHeader } from './TableColumnHeader/TableColumnHeader'; @@ -40,7 +40,7 @@ async function loader() { const agGridComponents = { agColumnHeader: TableColumnHeader, plainTextEditor: PlainTextEditor, - loadingCellRenderer: LoadingCell, + indexCellRenderer: IndexCellRenderer, }; export const AgGridTable = observer(function AgGridTable({ diff --git a/webapp/packages/plugin-data-spreadsheet/src/AgGridTable/AgGridTableController.ts b/webapp/packages/plugin-data-spreadsheet/src/AgGridTable/AgGridTableController.ts index a5e814d8bb..5190fd272b 100644 --- a/webapp/packages/plugin-data-spreadsheet/src/AgGridTable/AgGridTableController.ts +++ b/webapp/packages/plugin-data-spreadsheet/src/AgGridTable/AgGridTableController.ts @@ -247,12 +247,7 @@ export const INDEX_COLUMN_DEF: ColDef = { suppressMenu: true, editable: false, sortable: false, - cellRenderer: (params) => { - if (!params.data) { - return 'Loading...'; - } - return params.rowIndex + 1; - }, + cellRenderer: 'indexCellRenderer', }; function mapDataToColumns(columns?: IAgGridCol[]): ColDef[] { diff --git a/webapp/packages/plugin-data-spreadsheet/src/AgGridTable/IndexCellRenderer.tsx b/webapp/packages/plugin-data-spreadsheet/src/AgGridTable/IndexCellRenderer.tsx new file mode 100644 index 0000000000..092a27fd61 --- /dev/null +++ b/webapp/packages/plugin-data-spreadsheet/src/AgGridTable/IndexCellRenderer.tsx @@ -0,0 +1,19 @@ +/* + * 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 { CellClassParams } from '@ag-grid-community/core'; +import { Loader } from '@cloudbeaver/core-blocks'; + +export function IndexCellRenderer(props: CellClassParams) { + return ( +
+ {!props.data && } + {props.data && props.rowIndex + 1} +
+ ); +} diff --git a/webapp/packages/plugin-data-spreadsheet/src/AgGridTable/LoadingCell.tsx b/webapp/packages/plugin-data-spreadsheet/src/AgGridTable/LoadingCell.tsx deleted file mode 100644 index 9bd9b2a7d0..0000000000 --- a/webapp/packages/plugin-data-spreadsheet/src/AgGridTable/LoadingCell.tsx +++ /dev/null @@ -1,15 +0,0 @@ -/* - * 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 function LoadingCell() { - return ( -
- Loading... -
- ); -} From 6075dd8f71e0bae1c266e6d5af48432e625318b1 Mon Sep 17 00:00:00 2001 From: Wroud Date: Wed, 2 Sep 2020 11:01:26 +0300 Subject: [PATCH 6/6] feat(plugin-data-viewer): saving changes while editing CB-228 --- .../src/AgGridTable/AgGridContext.ts | 1 + .../src/AgGridTable/AgGridTableController.ts | 7 ++++++- .../src/AgGridTable/PlainTextEditor/PlainTextEditor.tsx | 1 + .../src/TableViewer/TableDataModel/TableEditor.ts | 6 ++++-- .../plugin-data-viewer/src/TableViewer/TableViewerModel.ts | 4 ++-- 5 files changed, 14 insertions(+), 5 deletions(-) diff --git a/webapp/packages/plugin-data-spreadsheet/src/AgGridTable/AgGridContext.ts b/webapp/packages/plugin-data-spreadsheet/src/AgGridTable/AgGridContext.ts index 9b040f4a74..0490c7ddb5 100644 --- a/webapp/packages/plugin-data-spreadsheet/src/AgGridTable/AgGridContext.ts +++ b/webapp/packages/plugin-data-spreadsheet/src/AgGridTable/AgGridContext.ts @@ -12,6 +12,7 @@ export type AgGridContext = { selection: TableSelection; isReadonly(): boolean; isCellEdited(rowId: number, column: string): boolean; + editCellValue(rowNumber: number, column: string, value: any, editing: boolean): void; revertCellValue(rowNumber: number, column: string): void; onEditSave(): void; onEditCancel(): void; diff --git a/webapp/packages/plugin-data-spreadsheet/src/AgGridTable/AgGridTableController.ts b/webapp/packages/plugin-data-spreadsheet/src/AgGridTable/AgGridTableController.ts index 5190fd272b..a041e58167 100644 --- a/webapp/packages/plugin-data-spreadsheet/src/AgGridTable/AgGridTableController.ts +++ b/webapp/packages/plugin-data-spreadsheet/src/AgGridTable/AgGridTableController.ts @@ -48,6 +48,7 @@ export class AgGridTableController implements IInitializableController, IDestruc selection: this.selection, isReadonly: () => this.gridModel.access === AccessMode.Readonly, isCellEdited: this.isCellEdited.bind(this), + editCellValue: this.editCellValue.bind(this), revertCellValue: this.revertCellValue.bind(this), onEditSave: this.onEditSave.bind(this), onEditCancel: this.onEditCancel.bind(this), @@ -158,8 +159,12 @@ export class AgGridTableController implements IInitializableController, IDestruc this.gridModel.revertCellValue(rowIndex, colId); } + private editCellValue(rowIndex: number, colId: string, value: any, editing: boolean) { + this.gridModel.onCellEditingStopped(rowIndex, colId, value, editing); + } + private handleCellEditingStopped(event: CellEditingStoppedEvent) { - this.gridModel.onCellEditingStopped(event.rowIndex, event.column.getColId(), event.value); + this.gridModel.onCellEditingStopped(event.rowIndex, event.column.getColId(), event.value, false); } private isCellEdited(rowIndex: number, column: string) { diff --git a/webapp/packages/plugin-data-spreadsheet/src/AgGridTable/PlainTextEditor/PlainTextEditor.tsx b/webapp/packages/plugin-data-spreadsheet/src/AgGridTable/PlainTextEditor/PlainTextEditor.tsx index 47cd1b1295..d039987838 100644 --- a/webapp/packages/plugin-data-spreadsheet/src/AgGridTable/PlainTextEditor/PlainTextEditor.tsx +++ b/webapp/packages/plugin-data-spreadsheet/src/AgGridTable/PlainTextEditor/PlainTextEditor.tsx @@ -71,6 +71,7 @@ export const PlainTextEditor = forwardRef, ICellEditorP const handleChange = useCallback((newValue: string) => { value.current = newValue; + context.editCellValue(props.rowIndex, props.column.getColId(), newValue, true); forceUpdate(value.current); }, []); diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableDataModel/TableEditor.ts b/webapp/packages/plugin-data-viewer/src/TableViewer/TableDataModel/TableEditor.ts index 9f20c1ca14..8d40659787 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/TableDataModel/TableEditor.ts +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TableDataModel/TableEditor.ts @@ -30,7 +30,7 @@ export class TableEditor { /** * this method doesn't modify TableRow but create a diff that can be applied later */ - editCellValue(rowId: number, columnKey: string, value: any) { + editCellValue(rowId: number, columnKey: string, value: any, editing: boolean) { const column = this.dataModel .getColumns() .find(column => column.name === columnKey); @@ -42,7 +42,9 @@ export class TableEditor { const newValue = this.getOrCreateEditedRow(rowId) .setValue(column.position, value); - this.rowsUpdateSubject.next([rowId]); + if (!editing) { + this.rowsUpdateSubject.next([rowId]); + } return newValue; } diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableViewerModel.ts b/webapp/packages/plugin-data-viewer/src/TableViewer/TableViewerModel.ts index 1d48a52130..f262fab860 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/TableViewerModel.ts +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TableViewerModel.ts @@ -322,12 +322,12 @@ export class TableViewerModel { } } - onCellEditingStopped(rowNumber: number, column: string, value: any) { + onCellEditingStopped(rowNumber: number, column: string, value: any, editing: boolean) { if (this.access === AccessMode.Readonly) { return; } - this.tableEditor.editCellValue(rowNumber, column, value); + this.tableEditor.editCellValue(rowNumber, column, value, editing); } onSortChanged(sorting: SortModel) {