Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import { useTranslate } from '../localization/useTranslate.js';
import { useState } from 'react';

export interface ConfirmationDialogPayload {

Check failure on line 25 in webapp/packages/core-blocks/src/CommonDialog/ConfirmationDialog.tsx

View workflow job for this annotation

GitHub Actions / Frontend / Lint

Interface name `ConfirmationDialogPayload` must match the RegExp: /^I[A-Z]/u
icon?: string;
size?: CommonDialogWrapperSize;
title: string;
Expand All @@ -37,9 +37,15 @@
confirmActionText?: TLocalizationToken;
cancelActionText?: TLocalizationToken;
extraActionText?: TLocalizationToken;
/**
* Async action run when the confirm button is pressed.
* While it runs the confirm button shows a loader and is disabled.
* The dialog resolves only if it returns `true`.
*/
onConfirm?: () => Promise<boolean>;
Comment thread
sergeyteleshev marked this conversation as resolved.
}

export interface ConfirmationDialogResult {

Check failure on line 48 in webapp/packages/core-blocks/src/CommonDialog/ConfirmationDialog.tsx

View workflow job for this annotation

GitHub Actions / Frontend / Lint

Interface name `ConfirmationDialogResult` must match the RegExp: /^I[A-Z]/u
isExtraAction?: boolean;
skipConfirmations: boolean;
}
Expand Down Expand Up @@ -67,10 +73,25 @@
confirmActionText,
cancelActionText,
extraActionText,
onConfirm,
} = payload;
const [skipConfirmations, setSkipConfirmations] = useState(false);

function resolve() {
async function resolve() {
if (onConfirm) {
let success = false;
try {
success = await onConfirm();
} catch {
success = false;
}

if (!success) {
rejectDialog({ skipConfirmations });
return;
}
}

resolveDialog({
skipConfirmations,
});
Expand Down Expand Up @@ -116,7 +137,7 @@
<Translate token={extraActionText || 'ui_no'} />
</Button>
)}
<Button type="button" className="tw:shrink-0" onClick={resolve}>
<Button type="button" className="tw:shrink-0" loader onClick={resolve}>
<Translate token={confirmActionText || 'ui_processing_ok'} />
</Button>
</CommonDialogFooter>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2026 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 { type CommonDialogService, DialogueStateResult } from '@cloudbeaver/core-dialogs';
import type { TLocalizationToken } from '@cloudbeaver/core-localization';

import { ConfirmationDialog } from './ConfirmationDialog.js';

export interface IUnsavedChangesProvider {
readonly isChanged: boolean;
readonly isSaving?: boolean;
/** Returns whether the save succeeded. `undefined` means there was nothing to save and navigation may proceed. */
save(): Promise<boolean> | undefined;
reset(): void;
title?: string;
subTitle?: string;
message?: TLocalizationToken;
}

/**
* Shows a unified "Save / Don't save / Cancel" dialog when the provider has unsaved changes.
*
* @returns `true` when navigation may proceed (saved successfully or discarded), `false` to block it.
*/
export async function confirmUnsavedChanges(commonDialogService: CommonDialogService, provider: IUnsavedChangesProvider): Promise<boolean> {
if (!provider.isChanged) {
return true;
}

if (provider.isSaving) {
return false;
}

const { status, result } = await commonDialogService.open(ConfirmationDialog, {
title: provider.title ?? 'ui_save_reminder',
subTitle: provider.subTitle,
message: provider.message ?? 'ui_changes_might_be_lost',
confirmActionText: 'ui_processing_save',
extraActionText: 'ui_processing_dont_save',
cancelActionText: 'ui_processing_cancel',
showExtraAction: true,
onConfirm: async () => (await provider.save()) ?? true,
});

if (status === DialogueStateResult.Resolved) {
return true;
}

if (result?.isExtraAction) {
provider.reset();
return true;
}

return false;
}
1 change: 1 addition & 0 deletions webapp/packages/core-blocks/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@

export * from './AuthenticationProviderLoader.js';
export * from './useAuthenticationAction.js';
export * from './CommonDialog/CommonDialog/CommonDialogBody.js';

Check failure on line 16 in webapp/packages/core-blocks/src/index.ts

View workflow job for this annotation

GitHub Actions / Frontend / Lint

Don't import/export .tsx files from .ts files directly, use React.lazy()
export * from './CommonDialog/CommonDialog/CommonDialogFooter.js';

Check failure on line 17 in webapp/packages/core-blocks/src/index.ts

View workflow job for this annotation

GitHub Actions / Frontend / Lint

Don't import/export .tsx files from .ts files directly, use React.lazy()
export * from './CommonDialog/CommonDialog/CommonDialogHeader.js';

Check failure on line 18 in webapp/packages/core-blocks/src/index.ts

View workflow job for this annotation

GitHub Actions / Frontend / Lint

Don't import/export .tsx files from .ts files directly, use React.lazy()
export * from './CommonDialog/CommonDialog/CommonDialogWrapper.js';

Check failure on line 19 in webapp/packages/core-blocks/src/index.ts

View workflow job for this annotation

GitHub Actions / Frontend / Lint

Don't import/export .tsx files from .ts files directly, use React.lazy()
export * from './CommonDialog/confirmUnsavedChanges.js';

Check failure on line 20 in webapp/packages/core-blocks/src/index.ts

View workflow job for this annotation

GitHub Actions / Frontend / Lint

Don't import/export .tsx files from .ts files directly, use React.lazy()
export * from './CommonDialog/ConfirmationDialog.js';

Check failure on line 21 in webapp/packages/core-blocks/src/index.ts

View workflow job for this annotation

GitHub Actions / Frontend / Lint

Don't import/export .tsx files from .ts files directly, use React.lazy()
export { default as ConfirmationDialogStyles } from './CommonDialog/ConfirmationDialog.module.css';
export * from './CommonDialog/ConfirmationDialogDelete.js';

Check failure on line 23 in webapp/packages/core-blocks/src/index.ts

View workflow job for this annotation

GitHub Actions / Frontend / Lint

Don't import/export .tsx files from .ts files directly, use React.lazy()
export * from './CommonDialog/RenameDialog.js';

Check failure on line 24 in webapp/packages/core-blocks/src/index.ts

View workflow job for this annotation

GitHub Actions / Frontend / Lint

Don't import/export .tsx files from .ts files directly, use React.lazy()
export * from './CommonDialog/DialogsPortal.js';
export * from './ExportImageDialog/ExportImageDialogLazy.js';
export * from './ExportImageDialog/ExportImageFormats.js';
Expand Down
1 change: 1 addition & 0 deletions webapp/packages/core-localization/src/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export default [
['ui_processing_ok', 'Ok'],
['ui_processing_create', 'Create'],
['ui_processing_save', 'Save'],
['ui_processing_dont_save', "Don't save"],
['ui_processing_saving', 'Saving...'],
['ui_processing_do_you_want_to_proceed', 'Do you want to proceed?'],
['ui_processing_saved', 'Saved'],
Expand Down
1 change: 1 addition & 0 deletions webapp/packages/core-localization/src/locales/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export default [
['ui_processing_ok', 'Ok'],
['ui_processing_create', 'Créer'],
['ui_processing_save', 'Sauvegarder'],
['ui_processing_dont_save', 'Ne pas sauvegarder'],
['ui_processing_saved', 'Sauvegardé'],
['ui_processing_stop', 'Arrêter'],
['ui_processing_skip', 'Passer'],
Expand Down
2 changes: 2 additions & 0 deletions webapp/packages/core-localization/src/locales/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export default [
['ui_processing_ok', 'Ok'],
['ui_processing_create', 'Crea'],
['ui_processing_save', 'Salva'],
['ui_processing_dont_save', 'Non salvare'],
['ui_processing_saving', 'Saving...'],
['ui_processing_do_you_want_to_proceed', 'Do you want to proceed?'],
['ui_processing_saved', 'Saved'],
Expand Down Expand Up @@ -95,6 +96,7 @@ export default [
['ui_no_items_placeholder', 'Non ci sono ancora elementi.'],
['ui_search_no_result_placeholder', 'Nessun risultato trovato.'],
['ui_save_reminder', 'Ci sono modifiche non salvate.'],
['ui_changes_might_be_lost', 'Le tue modifiche potrebbero andare perse'],
['ui_yes', 'Sì'],
['ui_no', 'No'],
['ui_select_all', 'Select all'],
Expand Down
1 change: 1 addition & 0 deletions webapp/packages/core-localization/src/locales/ru.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export default [
['ui_processing_ok', 'Принять'],
['ui_processing_create', 'Создать'],
['ui_processing_save', 'Сохранить'],
['ui_processing_dont_save', 'Не сохранять'],
['ui_processing_saving', 'Сохранение...'],
['ui_processing_do_you_want_to_proceed', 'Хотите продолжить?'],
['ui_processing_saved', 'Сохранено'],
Expand Down
1 change: 1 addition & 0 deletions webapp/packages/core-localization/src/locales/vi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export default [
['ui_processing_ok', 'Đồng ý'],
['ui_processing_create', 'Tạo'],
['ui_processing_save', 'Lưu'],
['ui_processing_dont_save', 'Không lưu'],
['ui_processing_saving', 'Đang lưu...'],
['ui_processing_do_you_want_to_proceed', 'Bạn có muốn tiếp tục không?'],
['ui_processing_saved', 'Đã lưu'],
Expand Down
1 change: 1 addition & 0 deletions webapp/packages/core-localization/src/locales/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export default [
['ui_processing_ok', '好'],
['ui_processing_create', '创建'],
['ui_processing_save', '保存'],
['ui_processing_dont_save', '不保存'],
['ui_processing_saving', '保存中...'],
['ui_processing_do_you_want_to_proceed', '是否继续?'],
['ui_processing_saved', '已保存'],
Expand Down
1 change: 1 addition & 0 deletions webapp/packages/core-ui/src/Form/IFormState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export interface IFormState<TState> {
isError: boolean;
isCancelled: boolean;
isChanged: boolean;
isSaving: boolean;
isReadOnly: boolean;

save(providedContext?: IExecutionContext<IFormState<TState>>): Promise<boolean>;
Expand Down
18 changes: 11 additions & 7 deletions webapp/packages/core-ui/src/Tabs/TabsState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import { TabProvider, useStoreState, useTabStore } from '@dbeaver/ui-kit';
import { action, observable } from 'mobx';
import { observer } from 'mobx-react-lite';
import { useEffect, useMemo, useState } from 'react';
import { useEffect, useLayoutEffect, useMemo, useState } from 'react';

import { useAutoLoad, useExecutor, useObjectRef, useObservableRef } from '@cloudbeaver/core-blocks';
import { useDataContext } from '@cloudbeaver/core-data-context';
Expand All @@ -28,7 +28,7 @@
/** Default selected tab id */
selectedId?: string;
orientation?: 'horizontal' | 'vertical';
/** Provide a tab Id to control tabs state */
/** Provide a tab Id to fully control the selected tab: the selection always mirrors `currentTabId`. */
currentTabId?: string | null;
container?: ITabsContainer<T, any>;
localState?: MetadataMap<string, any>;
Expand Down Expand Up @@ -64,7 +64,7 @@
...rest
}: TabsStateProps<T>): React.ReactElement | null {
const context = useDataContext();
const props = useMemo(() => rest as any as T, [...Object.values(rest)]);

Check warning on line 67 in webapp/packages/core-ui/src/Tabs/TabsState.tsx

View workflow job for this annotation

GitHub Actions / Frontend / Lint

React Hook useMemo has a spread element in its dependency array. This means we can't statically verify whether you've passed the correct dependencies

Check warning on line 67 in webapp/packages/core-ui/src/Tabs/TabsState.tsx

View workflow job for this annotation

GitHub Actions / Frontend / Lint

React Hook useMemo has a missing dependency: 'rest'. Either include it or remove the dependency array

let displayed: string[] = [];

Expand Down Expand Up @@ -109,15 +109,16 @@
selected,
store,
tabList,
currentTabId,
},
);

useEffect(() => {
if (isNotNullDefined(currentTabId)) {
useLayoutEffect(() => {
Comment thread
sergeyteleshev marked this conversation as resolved.
if (isNotNullDefined(currentTabId) && selected !== currentTabId) {
dynamic.store.setSelectedId(currentTabId);
dynamic.selectedId = currentTabId;
}
}, [currentTabId]);
}, [currentTabId, selected]);

Check warning on line 121 in webapp/packages/core-ui/src/Tabs/TabsState.tsx

View workflow job for this annotation

GitHub Actions / Frontend / Lint

React Hook useLayoutEffect has a missing dependency: 'dynamic'. Either include it or remove the dependency array

useEffect(() => {
if (displayed.length > 0 && autoSelect) {
Expand All @@ -128,7 +129,7 @@
dynamic.store.setSelectedId(displayed[0]);
}
}
}, [displayed, autoSelect]);

Check warning on line 132 in webapp/packages/core-ui/src/Tabs/TabsState.tsx

View workflow job for this annotation

GitHub Actions / Frontend / Lint

React Hook useEffect has a missing dependency: 'dynamic.store'. Either include it or remove the dependency array

useExecutor({
executor: openExecutor,
Expand All @@ -139,6 +140,9 @@
ExecutorInterrupter.interrupt(contexts);
return;
}
if (isNotNullDefined(dynamic.currentTabId)) {
return;
}
dynamic.selectedId = data.tabId;
if (dynamic.store.getState().selectedId !== data.tabId) {
dynamic.store.setSelectedId(data.tabId);
Expand All @@ -159,15 +163,15 @@
const currentSelectedId = selected;

useEffect(() => {
if (!isNotNullDefined(currentSelectedId) || dynamic.selectedId === currentSelectedId) {
if (isNotNullDefined(currentTabId) || !isNotNullDefined(currentSelectedId) || dynamic.selectedId === currentSelectedId) {
return;
}

openExecutor.execute({
tabId: currentSelectedId,
props,
});
}, [currentSelectedId]);
}, [currentSelectedId, currentTabId]);

Check warning on line 174 in webapp/packages/core-ui/src/Tabs/TabsState.tsx

View workflow job for this annotation

GitHub Actions / Frontend / Lint

React Hook useEffect has missing dependencies: 'dynamic.selectedId', 'openExecutor', and 'props'. Either include them or remove the dependency array

const value = useObservableRef<ITabsContext<T>>(
() => ({
Expand Down
2 changes: 1 addition & 1 deletion webapp/packages/core-ui/src/module.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2025 DBeaver Corp and others
* Copyright (C) 2020-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
* you may not use this file except in compliance with the License.
*/
import { AdministrationItemService, AdministrationItemType, ConfigurationWizardService } from '@cloudbeaver/core-administration';
import { ConfirmationDialog, importLazyComponent } from '@cloudbeaver/core-blocks';
import { confirmUnsavedChanges, importLazyComponent } from '@cloudbeaver/core-blocks';
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
import { CommonDialogService, DialogueStateResult } from '@cloudbeaver/core-dialogs';
import { CommonDialogService } from '@cloudbeaver/core-dialogs';
import { SessionDataResource } from '@cloudbeaver/core-root';
import { formValidationContext } from '@cloudbeaver/core-ui';
import { getFirstException } from '@cloudbeaver/core-utils';
Expand Down Expand Up @@ -104,7 +104,7 @@
onLoad: () => {
this.serverConfigurationFormStateManager.create();
},
onDeActivate: (configurationWizard, administration, nextAdministrationItem) => {

Check warning on line 107 in webapp/packages/plugin-administration/src/ConfigurationWizard/ConfigurationWizardPagesBootstrapService.ts

View workflow job for this annotation

GitHub Actions / Frontend / Lint

'nextAdministrationItem' is defined but never used

Check warning on line 107 in webapp/packages/plugin-administration/src/ConfigurationWizard/ConfigurationWizardPagesBootstrapService.ts

View workflow job for this annotation

GitHub Actions / Frontend / Lint

'administration' is defined but never used
// so onFinish can be called with all required data from the form during easy config mode
if (configurationWizard) {
return;
Expand All @@ -112,25 +112,15 @@

this.serverConfigurationFormStateManager.destroy();
},
canDeActivate: async configurationWizard => {
canDeActivate: configurationWizard => {
const state = this.serverConfigurationFormStateManager.formState;

if (state?.isSaving) {
return false;
}

if (!configurationWizard && state?.isChanged) {
const { status } = await this.commonDialogService.open(ConfirmationDialog, {
title: 'ui_save_reminder',
message: 'ui_are_you_sure',
});

if (status === DialogueStateResult.Rejected) {
return false;
}
}

return true;
return confirmUnsavedChanges(this.commonDialogService, {
isChanged: !configurationWizard && !!state?.isChanged,
isSaving: state?.isSaving,
save: () => this.serverConfigurationFormStateManager.save(),
reset: () => state?.reset(),
});
},
getContentComponent: () => ServerConfigurationPage,
getDrawerComponent: () => ServerConfigurationDrawerItem,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2025 DBeaver Corp and others
* Copyright (C) 2020-2026 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 { TeamsResource } from '@cloudbeaver/core-authentication';
import { ColoredContainer, ConfirmationDialog, GroupBack, GroupTitle, Text, useExecutor, useResource, useTranslate } from '@cloudbeaver/core-blocks';
import {
ColoredContainer,
confirmUnsavedChanges,
GroupBack,
GroupTitle,
Text,
useExecutor,
useResource,
useTranslate,
} from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { CommonDialogService, DialogueStateResult } from '@cloudbeaver/core-dialogs';
import { CommonDialogService } from '@cloudbeaver/core-dialogs';
import { ExecutorInterrupter } from '@cloudbeaver/core-executor';
import { FormMode } from '@cloudbeaver/core-ui';

Expand All @@ -35,16 +44,14 @@ export const TeamEdit = observer<Props>(function TeamEdit({ item }) {
executor: teamsTableOptionsPanelService.onClose,
handlers: [
async function closeHandler(event, contexts) {
if (formState.isChanged && event === 'before') {
const { status } = await commonDialogService.open(ConfirmationDialog, {
title: 'ui_save_reminder',
message: 'ui_are_you_sure',
confirmActionText: 'ui_yes',
});
if (event !== 'before') {
return;
}

const confirmed = await confirmUnsavedChanges(commonDialogService, formState);

if (status === DialogueStateResult.Rejected) {
ExecutorInterrupter.interrupt(contexts);
}
if (!confirmed) {
ExecutorInterrupter.interrupt(contexts);
}
},
],
Expand Down
Loading
Loading