Merge pull request #7411 from 2betop/crud-reload

fix: 修复crud 中 drawer 动作后不刷新问题   Close: #6903
This commit is contained in:
hsm-lv 2023-07-10 11:09:33 +08:00 committed by GitHub
commit 1314d93c30
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 701 additions and 125 deletions

63
.vscode/launch.json vendored
View File

@ -1,71 +1,18 @@
{
// 使 IntelliSense
//
// 访: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"version": "1.0.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Jest All",
"name": "Jest: current file",
//"env": { "NODE_ENV": "test" },
"program": "${workspaceFolder}/node_modules/.bin/jest",
"args": ["--runInBand"],
"sourceMaps": true,
"args": ["${fileBasenameNoExtension}"],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"disableOptimisticBPs": true,
"runtimeExecutable": "/usr/local/bin/node",
"windows": {
"program": "${workspaceFolder}/node_modules/jest/bin/jest",
"program": "${workspaceFolder}/node_modules/jest/bin/jest"
}
},
{
"type": "node",
"request": "launch",
"name": "Jest Current File",
"program": "${workspaceFolder}/node_modules/.bin/jest",
"args": [
"--no-cache",
"--runInBand",
"${relativeFile}"
],
"sourceMaps": true,
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"disableOptimisticBPs": true,
"runtimeExecutable": "/usr/local/bin/node",
"windows": {
"program": "${workspaceFolder}/node_modules/jest/bin/jest",
}
},
{
"type": "node",
"request": "launch",
"name": "Jest Current File Specified test",
"program": "${workspaceFolder}/node_modules/.bin/jest",
"args": [
"--no-cache",
"--runInBand",
"${relativeFile}",
"-t",
"${input:testName}"
],
"sourceMaps": true,
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"disableOptimisticBPs": true,
"runtimeExecutable": "/usr/local/bin/node",
"windows": {
"program": "${workspaceFolder}/node_modules/jest/bin/jest",
}
}
],
"inputs": [
{
"id": "testName",
"type": "promptString",
"default": "",
"description": "Run only tests with a name that matches the regex pattern."
}
]
}

View File

@ -160,7 +160,12 @@ export class RootRenderer extends React.Component<RootRendererProps> {
window.open(mailto);
} else if (action.actionType === 'dialog') {
store.setCurrentAction(action);
store.openDialog(ctx, undefined, action.callback, delegate);
store.openDialog(
ctx,
undefined,
action.callback,
delegate || (this.context as any)
);
} else if (action.actionType === 'drawer') {
store.setCurrentAction(action);
store.openDrawer(ctx, undefined, undefined, delegate);
@ -323,9 +328,14 @@ export class RootRenderer extends React.Component<RootRendererProps> {
actionType: 'dialog',
dialog: dialog
});
store.openDialog(ctx, undefined, confirmed => {
store.openDialog(
ctx,
undefined,
confirmed => {
resolve(confirmed);
});
},
this.context as any
);
});
}

View File

@ -58,6 +58,8 @@ export interface ScopedComponentType extends React.Component<RendererProps> {
}
export interface IScopedContext {
rendererType?: string;
component?: ScopedComponentType;
parent?: AliasIScopedContext;
children?: AliasIScopedContext[];
registerComponent: (component: ScopedComponentType) => void;
@ -69,6 +71,10 @@ export interface IScopedContext {
send: (target: string, ctx: RendererData) => void;
close: (target: string) => void;
closeById: (target: string) => void;
getComponentsByRefPath: (
session: string,
path: string
) => ScopedComponentType[];
}
type AliasIScopedContext = IScopedContext;
@ -78,14 +84,18 @@ export const ScopedContext = React.createContext(rootScopedContext);
function createScopedTools(
path?: string,
parent?: AliasIScopedContext,
env?: RendererEnv
env?: RendererEnv,
rendererType?: string
): IScopedContext {
const components: Array<ScopedComponentType> = [];
const self = {
const self: IScopedContext = {
rendererType,
component: undefined,
parent,
registerComponent(component: ScopedComponentType) {
// 不要把自己注册在自己的 Scoped 上,自己的 Scoped 是给子节点们注册的。
if (component.props.$path === path && parent) {
self.component = component;
return parent.registerComponent(component);
}
@ -404,7 +414,8 @@ export function HocScoped<
env: RendererEnv;
}
>(
ComposedComponent: React.ComponentType<T>
ComposedComponent: React.ComponentType<T>,
rendererType?: string
): React.ComponentType<
T & {
scopeRef?: (ref: any) => void;
@ -430,7 +441,8 @@ export function HocScoped<
this.scoped = createScopedTools(
this.props.$path,
context,
this.props.env
this.props.env,
rendererType
);
const scopeRef = props.scopeRef;

View File

@ -186,7 +186,7 @@ export function registerRenderer(config: RendererConfig): RendererConfig {
}
if (config.isolateScope) {
config.component = Scoped(config.component);
config.component = Scoped(config.component, config.type);
}
const idx = findIndex(

View File

@ -1146,7 +1146,12 @@ export default class Form extends React.Component<FormProps, object> {
action.target &&
this.reloadTarget(filterTarget(action.target, values), values);
} else if (action.actionType === 'dialog') {
store.openDialog(data, undefined, action.callback);
store.openDialog(
data,
undefined,
action.callback,
delegate || (this.context as any)
);
} else if (action.actionType === 'drawer') {
store.openDrawer(data);
} else if (isEffectiveApi(action.api || api, values)) {
@ -1275,7 +1280,12 @@ export default class Form extends React.Component<FormProps, object> {
return this.validate(true, throwErrors);
} else if (action.actionType === 'dialog') {
store.setCurrentAction(action);
store.openDialog(data, undefined, action.callback);
store.openDialog(
data,
undefined,
action.callback,
delegate || (this.context as any)
);
} else if (action.actionType === 'drawer') {
store.setCurrentAction(action);
store.openDrawer(data);
@ -1444,9 +1454,14 @@ export default class Form extends React.Component<FormProps, object> {
actionType: 'dialog',
dialog: dialog
});
store.openDialog(ctx, undefined, confirmed => {
store.openDialog(
ctx,
undefined,
confirmed => {
resolve(confirmed);
});
},
this.context as any
);
});
}

View File

@ -0,0 +1,531 @@
import {render, fireEvent, waitFor} from '@testing-library/react';
import '../../src';
import {render as amisRender} from '../../src';
import {makeEnv, wait} from '../helper';
jest.useRealTimers();
// 验证 crud 里面的 ajax 动作,结束后是否刷新 crud
test('CRUD reload ajax1', async () => {
let listApiCalledCount = 0;
const mockFetcher = jest.fn().mockImplementation((api: any) => {
if (/^\/api\/mock2\/sample\/\d+/.test(api.url)) {
return Promise.resolve({
data: {
status: 0
}
});
}
listApiCalledCount++;
return Promise.resolve({
data: {
status: 0,
data: {
items: [{id: 1, a: 'a1', b: 'b1'}]
}
}
});
});
const {container} = render(
amisRender(
{
type: 'page',
body: {
type: 'crud',
api: '/api/mock2/sample',
columns: [
{
name: 'id',
label: 'ID'
},
{
name: 'a',
label: 'A'
},
{
name: 'b',
label: 'B'
},
{
label: '操作',
type: 'operation',
buttons: [
{
label: '保存',
type: 'button',
actionType: 'ajax',
api: '/api/mock2/sample/${id}'
}
]
}
]
}
},
{},
makeEnv({fetcher: mockFetcher})
)
);
await wait(200);
const saveBtn = container.querySelectorAll('tbody>tr button')[0];
expect(saveBtn).toBeTruthy();
fireEvent.click(saveBtn);
await waitFor(() => {
expect(mockFetcher).toBeCalledTimes(3);
});
expect(listApiCalledCount).toBe(2);
});
// 如果配置了 reload none 则不刷新
test('CRUD reload ajax2', async () => {
let listApiCalledCount = 0;
const mockFetcher = jest.fn().mockImplementation((api: any) => {
if (/^\/api\/mock2\/sample\/\d+/.test(api.url)) {
return Promise.resolve({
data: {
status: 0
}
});
}
listApiCalledCount++;
return Promise.resolve({
data: {
status: 0,
data: {
items: [{id: 1, a: 'a1', b: 'b1'}]
}
}
});
});
const {container} = render(
amisRender(
{
type: 'page',
body: {
type: 'crud',
api: '/api/mock2/sample',
columns: [
{
name: 'id',
label: 'ID'
},
{
name: 'a',
label: 'A'
},
{
name: 'b',
label: 'B'
},
{
label: '操作',
type: 'operation',
buttons: [
{
label: '保存',
type: 'button',
actionType: 'ajax',
api: '/api/mock2/sample/${id}'
},
{
label: '保存2',
type: 'button',
actionType: 'ajax',
api: '/api/mock2/sample/${id}',
reload: 'none'
}
]
}
]
}
},
{},
makeEnv({fetcher: mockFetcher})
)
);
await wait(200);
const saveBtn1 = container.querySelectorAll('tbody>tr button')[0];
const saveBtn2 = container.querySelectorAll('tbody>tr button')[1];
expect(saveBtn1).toBeTruthy();
expect(saveBtn2).toBeTruthy();
fireEvent.click(saveBtn2);
await waitFor(() => {
expect(mockFetcher).toBeCalledTimes(2);
});
expect(listApiCalledCount).toBe(1);
fireEvent.click(saveBtn1);
await waitFor(() => {
expect(mockFetcher).toBeCalledTimes(4);
});
expect(listApiCalledCount).toBe(2);
});
// dialog 提交后应该刷新 crud
test('CRUD reload dialog1', async () => {
let listApiCalledCount = 0;
const mockFetcher = jest.fn().mockImplementation((api: any) => {
if (/^\/api\/mock2\/sample\/\d+/.test(api.url)) {
return Promise.resolve({
data: {
status: 0
}
});
}
listApiCalledCount++;
return Promise.resolve({
data: {
status: 0,
data: {
items: [{id: 1, a: 'a1', b: 'b1'}]
}
}
});
});
const {container, getByText}: any = render(
amisRender(
{
type: 'page',
body: {
type: 'crud',
api: '/api/mock2/sample',
columns: [
{
name: 'id',
label: 'ID'
},
{
name: 'a',
label: 'A'
},
{
name: 'b',
label: 'B'
},
{
label: '操作',
type: 'operation',
buttons: [
{
label: 'OpenDialog',
type: 'button',
actionType: 'dialog',
dialog: {
body: {
type: 'form',
api: '/api/mock2/sample/${id}',
body: [
{
type: 'input-text',
name: 'a',
label: 'A'
}
]
}
}
}
]
}
]
}
},
{},
makeEnv({fetcher: mockFetcher, getModalContainer: () => container})
)
);
await wait(200);
const saveBtn = container.querySelectorAll('tbody>tr button')[0];
expect(saveBtn).toBeTruthy();
fireEvent.click(saveBtn);
await wait(300);
expect(getByText('确认')).toBeInTheDocument();
fireEvent.click(getByText('取消'));
await wait(300);
fireEvent.click(saveBtn);
await wait(300);
expect(getByText('确认')).toBeInTheDocument();
fireEvent.click(getByText('确认'));
await wait(500);
expect(mockFetcher).toBeCalledTimes(3);
expect(listApiCalledCount).toBe(2);
});
// dialog 提交后如果配置列不刷新,则不刷新
test('CRUD reload dialog2', async () => {
let listApiCalledCount = 0;
const mockFetcher = jest.fn().mockImplementation((api: any) => {
if (/^\/api\/mock2\/sample\/\d+/.test(api.url)) {
return Promise.resolve({
data: {
status: 0
}
});
}
listApiCalledCount++;
return Promise.resolve({
data: {
status: 0,
data: {
items: [{id: 1, a: 'a1', b: 'b1'}]
}
}
});
});
const {container, getByText}: any = render(
amisRender(
{
type: 'page',
body: {
type: 'crud',
api: '/api/mock2/sample',
columns: [
{
name: 'id',
label: 'ID'
},
{
name: 'a',
label: 'A'
},
{
name: 'b',
label: 'B'
},
{
label: '操作',
type: 'operation',
buttons: [
{
label: 'OpenDialog',
type: 'button',
actionType: 'dialog',
reload: 'none',
dialog: {
body: {
type: 'form',
api: '/api/mock2/sample/${id}',
body: [
{
type: 'input-text',
name: 'a',
label: 'A'
}
]
}
}
}
]
}
]
}
},
{},
makeEnv({fetcher: mockFetcher, getModalContainer: () => container})
)
);
await wait(200);
const saveBtn = container.querySelectorAll('tbody>tr button')[0];
expect(saveBtn).toBeTruthy();
fireEvent.click(saveBtn);
await wait(300);
fireEvent.click(saveBtn);
await wait(300);
expect(getByText('确认')).toBeInTheDocument();
fireEvent.click(getByText('确认'));
await wait(500);
expect(mockFetcher).toBeCalledTimes(2);
expect(listApiCalledCount).toBe(1);
});
// drawer 提交后应该刷新 crud
test('CRUD reload drawer1', async () => {
let listApiCalledCount = 0;
const mockFetcher = jest.fn().mockImplementation((api: any) => {
if (/^\/api\/mock2\/sample\/\d+/.test(api.url)) {
return Promise.resolve({
data: {
status: 0
}
});
}
listApiCalledCount++;
return Promise.resolve({
data: {
status: 0,
data: {
items: [{id: 1, a: 'a1', b: 'b1'}]
}
}
});
});
const {container, getByText}: any = render(
amisRender(
{
type: 'page',
body: {
type: 'crud',
api: '/api/mock2/sample',
columns: [
{
name: 'id',
label: 'ID'
},
{
name: 'a',
label: 'A'
},
{
name: 'b',
label: 'B'
},
{
label: '操作',
type: 'operation',
buttons: [
{
label: 'openDrawer',
type: 'button',
actionType: 'drawer',
drawer: {
body: {
type: 'form',
api: '/api/mock2/sample/${id}',
body: [
{
type: 'input-text',
name: 'a',
label: 'A'
}
]
}
}
}
]
}
]
}
},
{},
makeEnv({fetcher: mockFetcher, getModalContainer: () => container})
)
);
await wait(200);
const saveBtn = container.querySelectorAll('tbody>tr button')[0];
expect(saveBtn).toBeTruthy();
fireEvent.click(saveBtn);
await wait(300);
expect(getByText('确认')).toBeInTheDocument();
fireEvent.click(getByText('取消'));
await wait(300);
fireEvent.click(saveBtn);
await wait(300);
expect(getByText('确认')).toBeInTheDocument();
fireEvent.click(getByText('确认'));
await wait(500);
expect(mockFetcher).toBeCalledTimes(3);
expect(listApiCalledCount).toBe(2);
});
// dialog 提交后如果配置列不刷新,则不刷新
test('CRUD reload drawer2', async () => {
let listApiCalledCount = 0;
const mockFetcher = jest.fn().mockImplementation((api: any) => {
if (/^\/api\/mock2\/sample\/\d+/.test(api.url)) {
return Promise.resolve({
data: {
status: 0
}
});
}
listApiCalledCount++;
return Promise.resolve({
data: {
status: 0,
data: {
items: [{id: 1, a: 'a1', b: 'b1'}]
}
}
});
});
const {container, getByText}: any = render(
amisRender(
{
type: 'page',
body: {
type: 'crud',
api: '/api/mock2/sample',
columns: [
{
name: 'id',
label: 'ID'
},
{
name: 'a',
label: 'A'
},
{
name: 'b',
label: 'B'
},
{
label: '操作',
type: 'operation',
buttons: [
{
label: 'openDrawer',
type: 'button',
actionType: 'drawer',
reload: 'none',
drawer: {
body: {
type: 'form',
api: '/api/mock2/sample/${id}',
body: [
{
type: 'input-text',
name: 'a',
label: 'A'
}
]
}
}
}
]
}
]
}
},
{},
makeEnv({fetcher: mockFetcher, getModalContainer: () => container})
)
);
await wait(200);
const saveBtn = container.querySelectorAll('tbody>tr button')[0];
expect(saveBtn).toBeTruthy();
fireEvent.click(saveBtn);
await wait(300);
fireEvent.click(saveBtn);
await wait(300);
expect(getByText('确认')).toBeInTheDocument();
fireEvent.click(getByText('确认'));
await wait(500);
expect(mockFetcher).toBeCalledTimes(2);
expect(listApiCalledCount).toBe(1);
});

View File

@ -702,7 +702,8 @@ export default class CRUD extends React.Component<CRUDProps, any> {
prevIndex: idx - 1,
index: idx
},
action.callback
action.callback,
delegate || (this.context as any)
);
} else if (action.actionType === 'ajax') {
store.setCurrentAction(action);
@ -1130,9 +1131,14 @@ export default class CRUD extends React.Component<CRUDProps, any> {
actionType: 'dialog',
dialog: dialog
});
store.openDialog(ctx, undefined, confirmed => {
store.openDialog(
ctx,
undefined,
confirmed => {
resolve(confirmed);
});
},
this.context as any
);
});
}

View File

@ -426,9 +426,14 @@ export default class Dialog extends React.Component<DialogProps> {
actionType: 'dialog',
dialog: dialog
});
store.openDialog(ctx, undefined, confirmed => {
store.openDialog(
ctx,
undefined,
confirmed => {
resolve(confirmed);
});
},
this.context as any
);
});
}
@ -898,7 +903,12 @@ export class DialogRenderer extends Dialog {
}
} else if (action.actionType === 'dialog') {
store.setCurrentAction(action);
store.openDialog(data, undefined, action.callback);
store.openDialog(
data,
undefined,
action.callback,
delegate || (this.context as any)
);
} else if (action.actionType === 'drawer') {
store.setCurrentAction(action);
store.openDrawer(data);
@ -1000,16 +1010,18 @@ export class DialogRenderer extends Dialog {
...rest: Array<any>
) {
super.handleDialogConfirm(values, action, ...rest);
const scoped = this.context as IScopedContext;
const store = this.props.store;
const scoped = store.getDialogScoped() || (this.context as IScopedContext);
const dialogAction = store.action as ActionObject;
const reload = action.reload ?? dialogAction.reload;
if (reload) {
scoped.reload(reload, store.data);
} else if (scoped.component?.reload) {
scoped.component.reload();
} else {
// 没有设置,则自动让页面中 crud 刷新。
scoped
(this.context as IScopedContext)
.getComponents()
.filter((item: any) => item.props.type === 'crud')
.forEach((item: any) => item.reload && item.reload());
@ -1022,19 +1034,20 @@ export class DialogRenderer extends Dialog {
...rest: Array<any>
) {
super.handleDrawerConfirm(values, action);
const scoped = this.context as IScopedContext;
const store = this.props.store;
const scoped = store.getDialogScoped() || (this.context as IScopedContext);
const drawerAction = store.action as ActionObject;
const reload = action.reload ?? drawerAction.reload;
// 稍等会,等动画结束。
setTimeout(() => {
if (drawerAction.reload) {
scoped.reload(drawerAction.reload, store.data);
} else if (action.reload) {
scoped.reload(action.reload, store.data);
if (reload) {
scoped.reload(reload, store.data);
} else if (scoped.component?.reload) {
scoped.component.reload();
} else {
// 没有设置,则自动让页面中 crud 刷新。
scoped
(this.context as IScopedContext)
.getComponents()
.filter((item: any) => item.props.type === 'crud')
.forEach((item: any) => item.reload && item.reload());

View File

@ -525,9 +525,14 @@ export default class Drawer extends React.Component<DrawerProps> {
actionType: 'dialog',
dialog: dialog
});
store.openDialog(ctx, undefined, confirmed => {
store.openDialog(
ctx,
undefined,
confirmed => {
resolve(confirmed);
});
},
this.context as any
);
});
}
@ -827,7 +832,12 @@ export class DrawerRenderer extends Drawer {
store.openDrawer(data);
} else if (action.actionType === 'dialog') {
store.setCurrentAction(action);
store.openDialog(data, undefined, action.callback);
store.openDialog(
data,
undefined,
action.callback,
delegate || (this.context as any)
);
} else if (action.actionType === 'reload') {
store.setCurrentAction(action);
action.target && scoped.reload(action.target, data);
@ -913,16 +923,19 @@ export class DrawerRenderer extends Drawer {
...rest: Array<any>
) {
super.handleDialogConfirm(values, action, ...rest);
const scoped = this.context as IScopedContext;
const store = this.props.store;
const scoped = store.getDialogScoped() || (this.context as IScopedContext);
const dialogAction = store.action as ActionObject;
const reload = action.reload ?? dialogAction.reload;
if (reload) {
scoped.reload(reload, store.data);
} else if (scoped.component?.reload) {
scoped.component.reload();
} else {
// 没有设置,则自动让页面中 crud 刷新。
scoped
(this.context as IScopedContext)
.getComponents()
.filter((item: any) => item.props.type === 'crud')
.forEach((item: any) => item.reload && item.reload());
@ -935,19 +948,20 @@ export class DrawerRenderer extends Drawer {
...rest: Array<any>
) {
super.handleDrawerConfirm(values, action);
const scoped = this.context as IScopedContext;
const store = this.props.store;
const scoped = store.getDialogScoped() || (this.context as IScopedContext);
const drawerAction = store.action as ActionObject;
const reload = action.reload ?? drawerAction.reload;
// 稍等会,等动画结束。
setTimeout(() => {
if (drawerAction.reload) {
scoped.reload(drawerAction.reload, store.data);
} else if (action.reload) {
scoped.reload(action.reload, store.data);
if (reload) {
scoped.reload(reload, store.data);
} else if (scoped.component?.reload) {
scoped.component.reload();
} else {
// 没有设置,则自动让页面中 crud 刷新。
scoped
(this.context as IScopedContext)
.getComponents()
.filter((item: any) => item.props.type === 'crud')
.forEach((item: any) => item.reload && item.reload());

View File

@ -484,7 +484,12 @@ export default class Page extends React.Component<PageProps> {
if (action.actionType === 'dialog') {
store.setCurrentAction(action);
store.openDialog(ctx, undefined, action.callback, delegate);
store.openDialog(
ctx,
undefined,
action.callback,
delegate || (this.context as any)
);
} else if (action.actionType === 'drawer') {
store.setCurrentAction(action);
store.openDrawer(ctx, undefined, undefined, delegate);
@ -664,9 +669,14 @@ export default class Page extends React.Component<PageProps> {
actionType: 'dialog',
dialog: dialog
});
store.openDialog(ctx, undefined, confirmed => {
store.openDialog(
ctx,
undefined,
confirmed => {
resolve(confirmed);
});
},
this.context as any
);
});
}
@ -1066,9 +1076,11 @@ export class PageRenderer extends Page {
if (reload) {
scoped.reload(reload, store.data);
} else if (scoped?.component?.reload) {
scoped.component.reload();
} else {
// 没有设置,则自动让页面中 crud 刷新。
scoped
(this.context as IScopedContext)
.getComponents()
.filter((item: any) => item.props.type === 'crud')
.forEach((item: any) => item.reload && item.reload());
@ -1091,9 +1103,10 @@ export class PageRenderer extends Page {
setTimeout(() => {
if (reload) {
scoped.reload(reload, store.data);
} else if (scoped?.component?.reload) {
scoped.component.reload();
} else {
// 没有设置,则自动让页面中 crud 刷新。
scoped
(this.context as IScopedContext)
.getComponents()
.filter((item: any) => item.props.type === 'crud')
.forEach((item: any) => item.reload && item.reload());

View File

@ -695,9 +695,14 @@ export default class Service extends React.Component<ServiceProps> {
actionType: 'dialog',
dialog: dialog
});
store.openDialog(ctx, undefined, confirmed => {
store.openDialog(
ctx,
undefined,
confirmed => {
resolve(confirmed);
});
},
this.context as any
);
});
}

View File

@ -650,7 +650,12 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
this.form.reset();
} else if (action.actionType === 'dialog') {
store.setCurrentAction(action);
store.openDialog(data, undefined, action.callback);
store.openDialog(
data,
undefined,
action.callback,
delegate || (this.context as any)
);
} else if (action.actionType === 'ajax') {
if (!action.api) {
return env.alert(`当 actionType 为 ajax 时,请设置 api 属性`);
@ -743,9 +748,14 @@ export default class Wizard extends React.Component<WizardProps, WizardState> {
actionType: 'dialog',
dialog: dialog
});
store.openDialog(ctx, undefined, confirmed => {
store.openDialog(
ctx,
undefined,
confirmed => {
resolve(confirmed);
});
},
this.context as any
);
});
}