Skip to content

Commit 2632b90

Browse files
authored
feat: warn about unsaved changes in editor (#1620)
1 parent 122f3c5 commit 2632b90

File tree

23 files changed

+427
-365
lines changed

23 files changed

+427
-365
lines changed

package-lock.json

+11
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@
119119
"devDependencies": {
120120
"@commitlint/cli": "^19.3.0",
121121
"@commitlint/config-conventional": "^19.2.2",
122+
"@ebay/nice-modal-react": "^1.2.13",
122123
"@gravity-ui/browserslist-config": "^4.3.0",
123124
"@gravity-ui/eslint-config": "^3.2.0",
124125
"@gravity-ui/prettier-config": "^1.1.0",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.confirmation-dialog {
2+
&__message {
3+
white-space: pre-wrap;
4+
}
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import * as NiceModal from '@ebay/nice-modal-react';
2+
import type {ButtonView} from '@gravity-ui/uikit';
3+
import {Dialog} from '@gravity-ui/uikit';
4+
5+
import {cn} from '../../utils/cn';
6+
7+
import {confirmationDialogKeyset} from './i18n';
8+
9+
import './ConfirmationDialog.scss';
10+
11+
const block = cn('confirmation-dialog');
12+
13+
interface CommonDialogProps {
14+
caption?: string;
15+
message?: React.ReactNode;
16+
body?: React.ReactNode;
17+
18+
progress?: boolean;
19+
textButtonCancel?: string;
20+
textButtonApply?: string;
21+
buttonApplyView?: ButtonView;
22+
className?: string;
23+
onConfirm?: () => void;
24+
}
25+
26+
interface ConfirmationDialogNiceModalProps extends CommonDialogProps {
27+
onClose?: () => void;
28+
}
29+
30+
interface ConfirmationDialogProps extends CommonDialogProps {
31+
onClose: () => void;
32+
open: boolean;
33+
children?: React.ReactNode;
34+
}
35+
36+
export const CONFIRMATION_DIALOG = 'confirmation-dialog';
37+
function ConfirmationDialog({
38+
caption = '',
39+
children,
40+
onConfirm,
41+
onClose,
42+
progress,
43+
textButtonApply,
44+
textButtonCancel,
45+
buttonApplyView = 'normal',
46+
className,
47+
open,
48+
}: ConfirmationDialogProps) {
49+
return (
50+
<Dialog
51+
className={block(null, className)}
52+
size="s"
53+
onClose={onClose}
54+
disableOutsideClick
55+
open={open}
56+
>
57+
<Dialog.Header caption={caption} />
58+
<Dialog.Body>{children}</Dialog.Body>
59+
<Dialog.Footer
60+
onClickButtonApply={onConfirm}
61+
propsButtonApply={{view: buttonApplyView}}
62+
textButtonApply={textButtonApply}
63+
textButtonCancel={textButtonCancel ?? confirmationDialogKeyset('action_cancel')}
64+
onClickButtonCancel={onClose}
65+
loading={progress}
66+
/>
67+
</Dialog>
68+
);
69+
}
70+
71+
export const ConfirmationDialogNiceModal = NiceModal.create(
72+
(props: ConfirmationDialogNiceModalProps) => {
73+
const modal = NiceModal.useModal();
74+
75+
const handleClose = () => {
76+
modal.hide();
77+
modal.remove();
78+
};
79+
80+
return (
81+
<ConfirmationDialog
82+
{...props}
83+
onConfirm={async () => {
84+
await props.onConfirm?.();
85+
modal.resolve(true);
86+
handleClose();
87+
}}
88+
onClose={() => {
89+
props.onClose?.();
90+
modal.resolve(false);
91+
handleClose();
92+
}}
93+
open={modal.visible}
94+
/>
95+
);
96+
},
97+
);
98+
99+
NiceModal.register(CONFIRMATION_DIALOG, ConfirmationDialogNiceModal);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"action_cancel": "Cancel"
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import {registerKeysets} from '../../../utils/i18n';
2+
3+
import en from './en.json';
4+
5+
const COMPONENT = 'ydb-confirmation-dialog';
6+
7+
export const confirmationDialogKeyset = registerKeysets(COMPONENT, {en});

src/containers/App/Providers.tsx

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React from 'react';
22

3+
import * as NiceModal from '@ebay/nice-modal-react';
34
import {ThemeProvider} from '@gravity-ui/uikit';
45
import type {Store} from '@reduxjs/toolkit';
56
import type {History} from 'history';
@@ -34,9 +35,11 @@ export function Providers({
3435
<Router history={history}>
3536
<QueryParamProvider adapter={ReactRouter5Adapter}>
3637
<Theme>
37-
<ComponentsProvider registry={componentsRegistry}>
38-
{children}
39-
</ComponentsProvider>
38+
<NiceModal.Provider>
39+
<ComponentsProvider registry={componentsRegistry}>
40+
{children}
41+
</ComponentsProvider>
42+
</NiceModal.Provider>
4043
</Theme>
4144
</QueryParamProvider>
4245
</Router>

src/containers/Tenant/Diagnostics/TenantOverview/TenantCpu/TopQueries.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
TENANT_QUERY_TABS_ID,
1616
} from '../../../../../store/reducers/tenant/constants';
1717
import {useAutoRefreshInterval, useTypedDispatch} from '../../../../../utils/hooks';
18+
import {useChangeInputWithConfirmation} from '../../../../../utils/hooks/withConfirmation/useChangeInputWithConfirmation';
1819
import {parseQueryErrorToString} from '../../../../../utils/query';
1920
import {TenantTabsGroups, getTenantPath} from '../../../TenantPages';
2021
import {
@@ -48,7 +49,7 @@ export function TopQueries({tenantName}: TopQueriesProps) {
4849
const loading = isFetching && currentData === undefined;
4950
const data = currentData?.resultSets?.[0]?.result || [];
5051

51-
const handleRowClick = React.useCallback(
52+
const applyRowClick = React.useCallback(
5253
(row: any) => {
5354
const {QueryText: input} = row;
5455

@@ -67,6 +68,8 @@ export function TopQueries({tenantName}: TopQueriesProps) {
6768
[dispatch, history, location],
6869
);
6970

71+
const handleRowClick = useChangeInputWithConfirmation(applyRowClick);
72+
7073
const title = getSectionTitle({
7174
entity: i18n('queries'),
7275
postfix: i18n('by-cpu-time', {executionPeriod: i18n('executed-last-hour')}),

src/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
} from '../../../../store/reducers/tenant/constants';
2121
import {cn} from '../../../../utils/cn';
2222
import {useTypedDispatch, useTypedSelector} from '../../../../utils/hooks';
23+
import {useChangeInputWithConfirmation} from '../../../../utils/hooks/withConfirmation/useChangeInputWithConfirmation';
2324
import {TenantTabsGroups, getTenantPath} from '../../TenantPages';
2425

2526
import {RunningQueriesData} from './RunningQueriesData';
@@ -68,7 +69,7 @@ export const TopQueries = ({tenantName}: TopQueriesProps) => {
6869

6970
const filters = useTypedSelector((state) => state.executeTopQueries);
7071

71-
const onRowClick = React.useCallback(
72+
const applyRowClick = React.useCallback(
7273
(input: string) => {
7374
dispatch(changeUserInput({input}));
7475

@@ -85,6 +86,8 @@ export const TopQueries = ({tenantName}: TopQueriesProps) => {
8586
[dispatch, history, location],
8687
);
8788

89+
const onRowClick = useChangeInputWithConfirmation(applyRowClick);
90+
8891
const handleTextSearchUpdate = (text: string) => {
8992
dispatch(setTopQueriesFilters({text}));
9093
};

src/containers/Tenant/ObjectSummary/SchemaTree/SchemaTree.tsx

+9-1
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,19 @@ import React from 'react';
66
import {NavigationTree} from 'ydb-ui-components';
77

88
import {useCreateDirectoryFeatureAvailable} from '../../../../store/reducers/capabilities/hooks';
9+
import {selectUserInput} from '../../../../store/reducers/executeQuery';
910
import {schemaApi} from '../../../../store/reducers/schema/schema';
1011
import {tableSchemaDataApi} from '../../../../store/reducers/tableSchemaData';
1112
import type {GetTableSchemaDataParams} from '../../../../store/reducers/tableSchemaData';
1213
import type {EPathType, TEvDescribeSchemeResult} from '../../../../types/api/schema';
1314
import {wait} from '../../../../utils';
1415
import {SECOND_IN_MS} from '../../../../utils/constants';
15-
import {useQueryExecutionSettings, useTypedDispatch} from '../../../../utils/hooks';
16+
import {
17+
useQueryExecutionSettings,
18+
useTypedDispatch,
19+
useTypedSelector,
20+
} from '../../../../utils/hooks';
21+
import {getConfirmation} from '../../../../utils/hooks/withConfirmation/useChangeInputWithConfirmation';
1622
import {getSchemaControls} from '../../utils/controls';
1723
import {isChildlessPathType, mapPathTypeToNavigationTreeType} from '../../utils/schema';
1824
import {getActions} from '../../utils/schemaActions';
@@ -33,6 +39,7 @@ export function SchemaTree(props: SchemaTreeProps) {
3339
const createDirectoryFeatureAvailable = useCreateDirectoryFeatureAvailable();
3440
const {rootPath, rootName, rootType, currentPath, onActivePathUpdate} = props;
3541
const dispatch = useTypedDispatch();
42+
const input = useTypedSelector(selectUserInput);
3643
const [getTableSchemaDataMutation] = tableSchemaDataApi.useGetTableSchemaDataMutation();
3744

3845
const getTableSchemaDataPromise = React.useCallback(
@@ -144,6 +151,7 @@ export function SchemaTree(props: SchemaTreeProps) {
144151
? handleOpenCreateDirectoryDialog
145152
: undefined,
146153
getTableSchemaDataPromise,
154+
getConfirmation: input ? getConfirmation : undefined,
147155
},
148156
rootPath,
149157
)}

src/containers/Tenant/Query/NewSQL/NewSQL.tsx

+15-1
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,28 @@
1+
import React from 'react';
2+
13
import {ChevronDown} from '@gravity-ui/icons';
24
import {Button, DropdownMenu} from '@gravity-ui/uikit';
35

6+
import {changeUserInput} from '../../../../store/reducers/executeQuery';
47
import {useTypedDispatch} from '../../../../utils/hooks';
8+
import {useChangeInputWithConfirmation} from '../../../../utils/hooks/withConfirmation/useChangeInputWithConfirmation';
59
import {bindActions} from '../../utils/newSQLQueryActions';
610

711
import i18n from './i18n';
812

913
export function NewSQL() {
1014
const dispatch = useTypedDispatch();
11-
const actions = bindActions(dispatch);
15+
16+
const insertTemplate = React.useCallback(
17+
(input: string) => {
18+
dispatch(changeUserInput({input}));
19+
},
20+
[dispatch],
21+
);
22+
23+
const onTemplateClick = useChangeInputWithConfirmation(insertTemplate);
24+
25+
const actions = bindActions(onTemplateClick);
1226

1327
const items = [
1428
{

src/containers/Tenant/Query/QueriesHistory/QueriesHistory.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import type {QueryInHistory} from '../../../../types/store/executeQuery';
1515
import {cn} from '../../../../utils/cn';
1616
import {formatDateTime} from '../../../../utils/dataFormatters/dataFormatters';
1717
import {useTypedDispatch, useTypedSelector} from '../../../../utils/hooks';
18+
import {useChangeInputWithConfirmation} from '../../../../utils/hooks/withConfirmation/useChangeInputWithConfirmation';
1819
import {formatToMs, parseUsToMs} from '../../../../utils/timeParsers';
1920
import {MAX_QUERY_HEIGHT, QUERY_TABLE_SETTINGS} from '../../utils/constants';
2021
import i18n from '../i18n';
@@ -36,11 +37,13 @@ function QueriesHistory({changeUserInput}: QueriesHistoryProps) {
3637
const filter = useTypedSelector(selectQueriesHistoryFilter);
3738
const reversedHistory = [...queriesHistory].reverse();
3839

39-
const onQueryClick = (query: QueryInHistory) => {
40+
const applyQueryClick = (query: QueryInHistory) => {
4041
changeUserInput({input: query.queryText});
4142
dispatch(setQueryTab(TENANT_QUERY_TABS_ID.newQuery));
4243
};
4344

45+
const onQueryClick = useChangeInputWithConfirmation(applyQueryClick);
46+
4447
const onChangeFilter = (value: string) => {
4548
dispatch(setQueryHistoryFilter(value));
4649
};

0 commit comments

Comments
 (0)