Skip to content

Commit

Permalink
Merge branch 'devel' into fix/cb-5658/pagination-resolving
Browse files Browse the repository at this point in the history
  • Loading branch information
Wroud authored Sep 17, 2024
2 parents 5bd6122 + 2d1bde2 commit bb6e6e3
Show file tree
Hide file tree
Showing 11 changed files with 68 additions and 130 deletions.
9 changes: 4 additions & 5 deletions webapp/packages/core-ui/src/Form/Components/BaseForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*/
import { observer } from 'mobx-react-lite';

import { Button, Container, Form, s, StatusMessage, useAutoLoad, useForm, useS, useTranslate } from '@cloudbeaver/core-blocks';
import { Button, Container, Form, getComputed, s, StatusMessage, useForm, useS, useTranslate } from '@cloudbeaver/core-blocks';
import { getFirstException } from '@cloudbeaver/core-utils';

import { TabList } from '../../Tabs/TabList';
Expand All @@ -22,7 +22,8 @@ export const BaseForm = observer<IBaseFormProps<any>>(function BaseForm({ servic
const translate = useTranslate();

const editing = state.mode === FormMode.Edit;
const changed = state.isChanged();
const changed = state.isChanged;
const error = getComputed(() => getFirstException(state.exception));

const form = useForm({
async onSubmit() {
Expand All @@ -36,15 +37,13 @@ export const BaseForm = observer<IBaseFormProps<any>>(function BaseForm({ servic
},
});

useAutoLoad(BaseForm, state);

return (
<Form context={form} disabled={state.isDisabled} contents focusFirstChild>
<TabsState container={service.parts} localState={state.parts} formState={state}>
<Container compact parent noWrap vertical>
<Container className={s(styles, { bar: true })} gap keepSize noWrap>
<Container fill>
<StatusMessage exception={getFirstException(state.exception)} type={state.statusType} message={state.statusMessage} />
<StatusMessage exception={error} type={state.statusType} message={state.statusMessage} />
<TabList className={s(styles, { tabList: true })} underline big />
</Container>
<Container keepSize noWrap center gap compact>
Expand Down
22 changes: 16 additions & 6 deletions webapp/packages/core-ui/src/Form/FormPart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { action, makeObservable, observable, toJS } from 'mobx';
import { action, computed, makeObservable, observable, toJS } from 'mobx';

import { executorHandlerFilter, ExecutorInterrupter, type IExecutionContextProvider } from '@cloudbeaver/core-executor';
import { isObjectsEqual } from '@cloudbeaver/core-utils';
Expand All @@ -16,6 +16,7 @@ import type { IFormState } from './IFormState';
export abstract class FormPart<TPartState, TFormState = any> implements IFormPart<TPartState> {
state: TPartState;
initialState: TPartState;
isSaving: boolean;

exception: Error | null;
promise: Promise<any> | null;
Expand All @@ -29,6 +30,7 @@ export abstract class FormPart<TPartState, TFormState = any> implements IFormPar
) {
this.initialState = initialState;
this.state = toJS(this.initialState);
this.isSaving = false;

this.exception = null;
this.promise = null;
Expand All @@ -37,7 +39,6 @@ export abstract class FormPart<TPartState, TFormState = any> implements IFormPar
this.loading = false;

this.formState.submitTask.addHandler(executorHandlerFilter(() => this.isLoaded(), this.save.bind(this)));
this.formState.configureTask.addHandler(executorHandlerFilter(() => this.isLoaded(), this.configure.bind(this)));
this.formState.formatTask.addHandler(executorHandlerFilter(() => this.isLoaded(), this.format.bind(this)));
this.formState.validationTask.addHandler(executorHandlerFilter(() => this.isLoaded(), this.validate.bind(this)));

Expand All @@ -46,12 +47,19 @@ export abstract class FormPart<TPartState, TFormState = any> implements IFormPar
state: observable,
exception: observable.ref,
promise: observable.ref,
isSaving: observable.ref,
loaded: observable,
loading: observable,
setInitialState: action,
isDisabled: computed,
isChanged: computed,
});
}

get isDisabled(): boolean {
return this.isSaving || this.isLoading();
}

isLoading(): boolean {
return this.loading;
}
Expand All @@ -68,7 +76,7 @@ export abstract class FormPart<TPartState, TFormState = any> implements IFormPar
return this.exception !== null;
}

isChanged(): boolean {
get isChanged(): boolean {
if (!this.loaded || this.initialState === this.state) {
return false;
}
Expand All @@ -85,10 +93,12 @@ export abstract class FormPart<TPartState, TFormState = any> implements IFormPar
try {
await this.loader();

if (!this.isChanged()) {
if (!this.isChanged) {
return;
}

this.isSaving = true;

await this.saveChanges(data, contexts);
if (ExecutorInterrupter.isInterrupted(contexts)) {
return;
Expand All @@ -100,6 +110,7 @@ export abstract class FormPart<TPartState, TFormState = any> implements IFormPar
this.exception = exception;
throw exception;
} finally {
this.isSaving = false;
this.loading = false;
}
}
Expand Down Expand Up @@ -136,7 +147,7 @@ export abstract class FormPart<TPartState, TFormState = any> implements IFormPar
protected setInitialState(initialState: TPartState) {
this.initialState = initialState;

if (this.isChanged()) {
if (this.isChanged) {
return;
}

Expand All @@ -147,7 +158,6 @@ export abstract class FormPart<TPartState, TFormState = any> implements IFormPar
this.state = state;
}

protected configure(data: IFormState<TFormState>, contexts: IExecutionContextProvider<IFormState<TFormState>>): void | Promise<void> {}
protected format(data: IFormState<TFormState>, contexts: IExecutionContextProvider<IFormState<TFormState>>): void | Promise<void> {}
protected validate(data: IFormState<TFormState>, contexts: IExecutionContextProvider<IFormState<TFormState>>): void | Promise<void> {}

Expand Down
115 changes: 29 additions & 86 deletions webapp/packages/core-ui/src/Form/FormState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { DataContext, dataContextAddDIProvider, DataContextGetter, type IDataCon
import type { IServiceProvider } from '@cloudbeaver/core-di';
import type { ENotificationType } from '@cloudbeaver/core-events';
import { Executor, ExecutorInterrupter, IExecutionContextProvider, type IExecutor } from '@cloudbeaver/core-executor';
import { isLoadableStateHasException, MetadataMap, uuid } from '@cloudbeaver/core-utils';
import { isArraysEqual, isNotNullDefined, MetadataMap, uuid } from '@cloudbeaver/core-utils';
import { DATA_CONTEXT_LOADABLE_STATE, loadableStateContext } from '@cloudbeaver/core-view';

import { DATA_CONTEXT_FORM_STATE } from './DATA_CONTEXT_FORM_STATE';
Expand All @@ -25,23 +25,24 @@ export class FormState<TState> implements IFormState<TState> {
mode: FormMode;
parts: MetadataMap<string, IFormPart<any>>;
state: TState;
isSaving: boolean;

statusMessage: string | string[] | null;
statusType: ENotificationType | null;
exception: Error | (Error | null)[] | null;

promise: Promise<any> | null;

get isDisabled(): boolean {
return this.isSaving || this.isLoading();
return this.partsValues.some(part => part.isSaving || part?.isLoading?.());
}

get isSaving(): boolean {
return this.partsValues.some(part => part.isSaving);
}

readonly id: string;
readonly service: FormBaseService<TState, any>;
readonly dataContext: IDataContext;

readonly configureTask: IExecutor<IFormState<TState>>;
readonly formStateTask: IExecutor<TState>;
readonly fillDefaultConfigTask: IExecutor<IFormState<TState>>;
readonly submitTask: IExecutor<IFormState<TState>>;
Expand All @@ -56,17 +57,12 @@ export class FormState<TState> implements IFormState<TState> {
this.mode = FormMode.Create;
this.parts = new MetadataMap<string, any>();
this.state = state;
this.isSaving = false;

this.statusMessage = null;
this.statusType = null;
this.exception = null;

this.promise = null;

this.configureTask = new Executor(this as IFormState<TState>, () => true);
this.configureTask.addCollection(service.onConfigure);

this.formStateTask = new Executor<TState>(state, () => true);
this.formStateTask.addCollection(service.onState).addPostHandler(this.updateFormState.bind(this));

Expand All @@ -90,42 +86,43 @@ export class FormState<TState> implements IFormState<TState> {
mode: observable,
parts: observable.ref,
promise: observable.ref,
exception: observable.ref,
isSaving: observable.ref,
state: observable,
isSaving: computed,
exception: computed,
isDisabled: computed,
setMode: action,
setPartsState: action,
setException: action,
setState: action,
isChanged: computed,
partsValues: computed<IFormPart<any>[]>({
equals: isArraysEqual,
}),
isError: computed,
isCancelled: computed,
});
}

isLoading(): boolean {
return this.promise !== null || this.dataContext.get(DATA_CONTEXT_LOADABLE_STATE)!.loaders.some(loader => loader.isLoading());
}

isLoaded(): boolean {
if (this.promise) {
return false;
}
return this.dataContext.get(DATA_CONTEXT_LOADABLE_STATE)!.loaders.every(loader => loader.isLoaded());
get partsValues() {
return Array.from(this.parts.values());
}

isError(): boolean {
return this.dataContext.get(DATA_CONTEXT_LOADABLE_STATE)!.loaders.some(loader => loader.isError());
get exception(): Error | (Error | null)[] | null {
return this.partsValues
.map(part => part?.exception)
.flat()
.filter(isNotNullDefined);
}

isOutdated(): boolean {
return this.dataContext.get(DATA_CONTEXT_LOADABLE_STATE)!.loaders.some(loader => loader.isOutdated?.() === true);
get isError(): boolean {
return this.partsValues.some(part => part.isError());
}

isCancelled(): boolean {
return this.dataContext.get(DATA_CONTEXT_LOADABLE_STATE)!.loaders.some(loader => loader.isCancelled?.() === true);
get isCancelled(): boolean {
return this.partsValues.some(part => part?.isCancelled?.());
}

isChanged(): boolean {
return Array.from(this.parts.values()).some(part => part.isChanged());
get isChanged(): boolean {
return this.partsValues.some(part => part.isChanged);
}

getPart<T extends IFormPart<any>>(getter: DataContextGetter<T>, init: (context: IDataContext, id: string) => T): T {
Expand All @@ -140,50 +137,6 @@ export class FormState<TState> implements IFormState<TState> {
}) as T;
}

async load(refresh?: boolean): Promise<void> {
if (this.promise !== null) {
return this.promise;
}

if (this.isLoaded() && !this.isOutdated() && !refresh) {
return;
}

this.promise = (async () => {
try {
await this.configureTask.execute(this);

const loaders = this.dataContext.get(DATA_CONTEXT_LOADABLE_STATE)!.loaders;

for (const loader of loaders) {
if (isLoadableStateHasException(loader)) {
continue;
}

if (!loader.isLoaded() || loader.isOutdated?.() === true) {
try {
await loader.load();
} catch {
return;
}
}
}

await this.fillDefaultConfigTask.execute(this);
this.exception = null;
} catch (exception: any) {
this.exception = exception;
throw exception;
} finally {
this.promise = null;
}
})();
}

async reload(): Promise<void> {
await this.load(true);
}

cancel(): void {
const loaders = this.dataContext.get(DATA_CONTEXT_LOADABLE_STATE)!.loaders;

Expand All @@ -210,32 +163,22 @@ export class FormState<TState> implements IFormState<TState> {
return this;
}

setException(exception: Error | (Error | null)[] | null): this {
this.exception = exception;
return this;
}

setState(state: TState): this {
this.state = state;
return this;
}

async save(): Promise<boolean> {
try {
this.isSaving = true;
const context = await this.submitTask.execute(this);

if (ExecutorInterrupter.isInterrupted(context)) {
return false;
}

this.exception = null;
return true;
} catch (exception: any) {
this.exception = exception;
} finally {
this.isSaving = false;
}
} catch (exception: any) {}

return false;
}

Expand Down
4 changes: 3 additions & 1 deletion webapp/packages/core-ui/src/Form/IFormPart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ import type { ILoadableState } from '@cloudbeaver/core-utils';
export interface IFormPart<TState> extends ILoadableState {
readonly state: TState;
readonly initialState: TState;
isSaving: boolean;
readonly isDisabled: boolean;

isChanged(): boolean;
readonly isChanged: boolean;

load(): Promise<void>;
reset(): void;
Expand Down
Loading

0 comments on commit bb6e6e3

Please sign in to comment.