mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
Merge remote-tracking branch 'origin/devel' into devel
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -54,7 +54,7 @@ export interface IAdministrationItemSubItem {
|
||||
|
||||
export interface IConfigurationWizardItemOptions {
|
||||
description: string;
|
||||
onValidate?: () => boolean;
|
||||
onValidate?: () => Promise<boolean> | boolean;
|
||||
isDisabled?: () => boolean;
|
||||
isHidden?: () => boolean;
|
||||
isDone?: () => boolean;
|
||||
|
||||
+15
-14
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+17
-2
@@ -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,11 +55,22 @@ export const ServerConfigurationForm = observer(function ServerConfigurationForm
|
||||
onChange,
|
||||
onSave,
|
||||
}: Props) {
|
||||
const service = useService(ServerConfigurationService);
|
||||
const translate = useTranslate();
|
||||
const [focusedRef] = useFocus({ focusFirstChild: true });
|
||||
const [focusedRef] = useFocus<HTMLFormElement>({ 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))(
|
||||
<SubmittingForm onSubmit={onSave} name='server_config' ref={focusedRef as React.RefObject<HTMLFormElement>}>
|
||||
<SubmittingForm onSubmit={onSave} name='server_config' ref={focusedRef}>
|
||||
<box as="div">
|
||||
<box-element as='div'>
|
||||
<group as="div">
|
||||
|
||||
+22
-3
@@ -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<boolean>;
|
||||
|
||||
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<boolean>) => {
|
||||
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
|
||||
|
||||
-1
@@ -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')}
|
||||
</Button>
|
||||
|
||||
@@ -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<HTMLDivElement>({ onFocus, onBlur });
|
||||
const nodeChildren = useChildren();
|
||||
const isEnabled = usePermission(EPermission.public);
|
||||
|
||||
@@ -69,7 +69,7 @@ export const NavigationTree = observer(function NavigationTree() {
|
||||
}
|
||||
|
||||
return styled(navigationTreeStyles)(
|
||||
<tree as="div" tabIndex={0} ref={ref as React.RefObject<HTMLDivElement>}>
|
||||
<tree as="div" tabIndex={0} ref={ref}>
|
||||
{nodeChildren.children.map(id => (
|
||||
<NavigationTreeNode key={id} id={id} parentId={ROOT_NODE_PATH}/>
|
||||
))}
|
||||
|
||||
+2
-2
@@ -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<HTMLDivElement>({ 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<HTMLDivElement>}
|
||||
ref={ref}
|
||||
>
|
||||
{navigation.tabIdList.map(tabId => (
|
||||
<TabPanel key={tabId} tabId={tabId}>
|
||||
|
||||
@@ -16,18 +16,18 @@ type FocusOptions = {
|
||||
onBlur?: () => void;
|
||||
}
|
||||
|
||||
export function useFocus({
|
||||
export function useFocus<T extends HTMLElement>({
|
||||
focusFirstChild,
|
||||
onFocus,
|
||||
onBlur,
|
||||
}: FocusOptions): [React.RefObject<HTMLElement>, boolean] {
|
||||
}: FocusOptions): [React.RefObject<T>, boolean] {
|
||||
const [focus, setFocus] = useState(false);
|
||||
const reference = useRef<HTMLElement>(null);
|
||||
const reference = useRef<T>(null);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (reference.current !== null && focusFirstChild) {
|
||||
const firstFocusable = reference.current
|
||||
.querySelector<HTMLElement>('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
|
||||
.querySelector<T>('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
|
||||
|
||||
if (firstFocusable) {
|
||||
firstFocusable.focus();
|
||||
|
||||
+2
-2
@@ -39,10 +39,10 @@ export const ConnectionForm = observer(function ConnectionForm({
|
||||
controller,
|
||||
}: ConnectionFormProps) {
|
||||
const translate = useTranslate();
|
||||
const [focusedRef] = useFocus({ focusFirstChild: true });
|
||||
const [focusedRef] = useFocus<HTMLDivElement>({ focusFirstChild: true });
|
||||
|
||||
return styled(useStyles(styles, formStyles))(
|
||||
<connection-form as='div' ref={focusedRef as React.RefObject<HTMLDivElement>}>
|
||||
<connection-form as='div' ref={focusedRef}>
|
||||
<box as="div">
|
||||
<box-element as='div'>
|
||||
<group as="div">
|
||||
|
||||
@@ -61,7 +61,7 @@ export const DatabaseAuthDialog = observer(function DatabaseAuthDialog({
|
||||
rejectDialog,
|
||||
}: DialogComponentProps<string, null>) {
|
||||
const connection = useConnectionInfo(payload);
|
||||
const [focusedRef] = useFocus({ focusFirstChild: true });
|
||||
const [focusedRef] = useFocus<HTMLFormElement>({ 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())
|
||||
? <Loader />
|
||||
: (
|
||||
<SubmittingForm onSubmit={controller.login} ref={focusedRef as React.RefObject<HTMLFormElement>}>
|
||||
<SubmittingForm onSubmit={controller.login} ref={focusedRef}>
|
||||
<ObjectPropertyInfoForm
|
||||
prefix={`auth_${connection.connectionInfo?.id || ''} `}
|
||||
autofillToken={`section-${connection.connectionInfo?.id || ''} section-auth`}
|
||||
|
||||
@@ -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<T> implements IExecutor<T> {
|
||||
private defaultData?: T | null
|
||||
) { }
|
||||
|
||||
async execute(data: T) {
|
||||
async execute(data: T): Promise<IContextProvider<T>> {
|
||||
if ((data === undefined || data === null) && this.defaultData !== undefined && this.defaultData !== null) {
|
||||
data = this.defaultData;
|
||||
}
|
||||
@@ -29,13 +29,14 @@ export class Executor<T> implements IExecutor<T> {
|
||||
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<T>) {
|
||||
@@ -43,8 +44,16 @@ export class Executor<T> implements IExecutor<T> {
|
||||
return this;
|
||||
}
|
||||
|
||||
removeHandler(handler: IExecutorHandler<T>) {
|
||||
this.handlers = this.handlers.filter(h => h === handler);
|
||||
}
|
||||
|
||||
addPostHandler(handler: IExecutorHandler<T>) {
|
||||
this.postHandlers.push(handler);
|
||||
return this;
|
||||
}
|
||||
|
||||
removePostHandler(handler: IExecutorHandler<T>) {
|
||||
this.postHandlers = this.postHandlers.filter(h => h === handler);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,13 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { IContextProvider } from './ExecutionContext';
|
||||
import { IExecutorHandler } from './IExecutorHandler';
|
||||
|
||||
export interface IExecutor<T> {
|
||||
execute(data: T): Promise<void>;
|
||||
execute(data: T): Promise<IContextProvider<T>>;
|
||||
addHandler(handler: IExecutorHandler<T>): void;
|
||||
removeHandler(handler: IExecutorHandler<T>): void;
|
||||
addPostHandler(handler: IExecutorHandler<T>): void;
|
||||
removePostHandler(handler: IExecutorHandler<T>): void;
|
||||
}
|
||||
|
||||
+2
-2
@@ -138,7 +138,7 @@ export const UserForm = observer(function UserForm({
|
||||
}: Props) {
|
||||
const translate = useTranslate();
|
||||
const controller = useController(UserFormController, userId, onCancel);
|
||||
const [focusedRef] = useFocus({ focusFirstChild: true });
|
||||
const [focusedRef] = useFocus<HTMLFormElement>({ focusFirstChild: true });
|
||||
|
||||
const handleLoginChange = useCallback(
|
||||
(value: string) => controller.credentials.login = value,
|
||||
@@ -186,7 +186,7 @@ export const UserForm = observer(function UserForm({
|
||||
</Button>
|
||||
</TabList>
|
||||
<content-box as='div'>
|
||||
<SubmittingForm onSubmit={controller.save} ref={focusedRef as React.RefObject<HTMLFormElement>}>
|
||||
<SubmittingForm onSubmit={controller.save} ref={focusedRef}>
|
||||
<TabPanel tabId='info'>
|
||||
<flex-box as="div">
|
||||
<flex-box-element as='div'>
|
||||
|
||||
+2
-2
@@ -27,13 +27,13 @@ export const AuthProviderForm = observer(function AuthProviderForm({
|
||||
credentials,
|
||||
authenticate,
|
||||
}: Props) {
|
||||
const [elementRef] = useFocus({ focusFirstChild: true });
|
||||
const [elementRef] = useFocus<HTMLDivElement>({ focusFirstChild: true });
|
||||
const handleChange = useCallback((key: string, value: string) => {
|
||||
credentials[key] = value;
|
||||
}, [credentials]);
|
||||
|
||||
return styled(useStyles(formStyles))(
|
||||
<login-form as='div' ref={elementRef as React.RefObject<HTMLDivElement>}>
|
||||
<login-form as='div' ref={elementRef}>
|
||||
{provider.credentialParameters.map(parameter => parameter.user && (
|
||||
<group as="div" key={parameter.id}>
|
||||
<InputField
|
||||
|
||||
+2
-2
@@ -31,11 +31,11 @@ export const ConnectionForm = observer(function ConnectionForm({
|
||||
controller,
|
||||
}: ConnectionFormProps) {
|
||||
const translate = useTranslate();
|
||||
const [focusedRef] = useFocus({ focusFirstChild: true });
|
||||
const [focusedRef] = useFocus<HTMLDivElement>({ focusFirstChild: true });
|
||||
|
||||
return styled(useStyles(formStyles))(
|
||||
<connection-form as='div'>
|
||||
<connection-type as="div" ref={focusedRef as React.RefObject<HTMLDivElement>}>
|
||||
<connection-type as="div" ref={focusedRef}>
|
||||
<Radio
|
||||
name="type"
|
||||
id="custom"
|
||||
|
||||
+2
-2
@@ -45,7 +45,7 @@ const styles = css`
|
||||
export const ConnectionDialog = observer(function ConnectionDialog({
|
||||
rejectDialog,
|
||||
}: DialogComponentProps<null, null>) {
|
||||
const [focusedRef] = useFocus({ focusFirstChild: true });
|
||||
const [focusedRef] = useFocus<HTMLFormElement>({ 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')}
|
||||
</center>
|
||||
) : (
|
||||
<SubmittingForm onSubmit={controller.onConnect} ref={focusedRef as React.RefObject<HTMLFormElement>}>
|
||||
<SubmittingForm onSubmit={controller.onConnect} ref={focusedRef}>
|
||||
<ObjectPropertyInfoForm
|
||||
prefix={`auth_${controller.template?.id || ''}`}
|
||||
autofillToken={`section-${controller.template?.id || ''} section-auth`}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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) {
|
||||
@@ -247,12 +252,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[] {
|
||||
|
||||
@@ -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 (
|
||||
<div style={{ display: 'flex', width: '100%' }}>
|
||||
{!props.data && <Loader small/>}
|
||||
{props.data && props.rowIndex + 1}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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 (
|
||||
<div style={{ paddingLeft: '10px', lineHeight: '25px' }}>
|
||||
<span>Loading...</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+1
@@ -71,6 +71,7 @@ export const PlainTextEditor = forwardRef<Partial<ICellEditorComp>, ICellEditorP
|
||||
|
||||
const handleChange = useCallback((newValue: string) => {
|
||||
value.current = newValue;
|
||||
context.editCellValue(props.rowIndex, props.column.getColId(), newValue, true);
|
||||
forceUpdate(value.current);
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user