@journeyapps-labs/reactor-mod-data-browser 3.5.0 → 3.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/dist/@types/actions/schema-model/DeleteSchemaModelAction.d.ts +12 -0
- package/dist/@types/core/AbstractConnection.d.ts +1 -0
- package/dist/@types/core/delete-schema-models.d.ts +8 -0
- package/dist/@types/forms/types/shared/ui.d.ts +6 -0
- package/dist/@types/panels/query/PageResultsWidget.d.ts +5 -1
- package/dist/@types/panels/query/QueryPanelFactory.d.ts +8 -0
- package/dist/@types/panels/query/TableControlsWidget.d.ts +2 -0
- package/dist/@types/panels/query/table-controls/SelectionControlsWidget.d.ts +6 -0
- package/dist/DataBrowserModule.js +2 -0
- package/dist/DataBrowserModule.js.map +1 -1
- package/dist/actions/schema-model/DeleteSchemaModelAction.js +27 -0
- package/dist/actions/schema-model/DeleteSchemaModelAction.js.map +1 -0
- package/dist/core/AbstractConnection.js +21 -0
- package/dist/core/AbstractConnection.js.map +1 -1
- package/dist/core/delete-schema-models.js +101 -0
- package/dist/core/delete-schema-models.js.map +1 -0
- package/dist/core/query/query-simple/SimpleQueryColumns.js +0 -2
- package/dist/core/query/query-simple/SimpleQueryColumns.js.map +1 -1
- package/dist/entities/SchemaModelObjectEntityDefinition.js +5 -0
- package/dist/entities/SchemaModelObjectEntityDefinition.js.map +1 -1
- package/dist/panels/model/ModelPanelWidget.js +12 -8
- package/dist/panels/model/ModelPanelWidget.js.map +1 -1
- package/dist/panels/query/PageResultsWidget.js +26 -6
- package/dist/panels/query/PageResultsWidget.js.map +1 -1
- package/dist/panels/query/QueryPanelFactory.js +39 -4
- package/dist/panels/query/QueryPanelFactory.js.map +1 -1
- package/dist/panels/query/QueryPanelWidget.js +12 -2
- package/dist/panels/query/QueryPanelWidget.js.map +1 -1
- package/dist/panels/query/TableControlsWidget.js +2 -0
- package/dist/panels/query/TableControlsWidget.js.map +1 -1
- package/dist/panels/query/table-controls/SelectionControlsWidget.js +11 -0
- package/dist/panels/query/table-controls/SelectionControlsWidget.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist-module/bundle.js +56 -56
- package/dist-module/bundle.js.map +1 -1
- package/package.json +3 -3
- package/src/DataBrowserModule.ts +2 -0
- package/src/actions/schema-model/DeleteSchemaModelAction.ts +43 -0
- package/src/core/AbstractConnection.ts +21 -0
- package/src/core/delete-schema-models.ts +131 -0
- package/src/core/query/query-simple/SimpleQueryColumns.tsx +0 -2
- package/src/entities/SchemaModelObjectEntityDefinition.ts +4 -0
- package/src/panels/model/ModelPanelWidget.tsx +15 -8
- package/src/panels/query/PageResultsWidget.tsx +45 -8
- package/src/panels/query/QueryPanelFactory.tsx +23 -0
- package/src/panels/query/QueryPanelWidget.tsx +14 -0
- package/src/panels/query/TableControlsWidget.tsx +4 -0
- package/src/panels/query/table-controls/SelectionControlsWidget.tsx +34 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@journeyapps-labs/reactor-mod-data-browser",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.6.0",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"typings": "./dist/@types/index",
|
|
6
6
|
"publishConfig": {
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
"@journeyapps-labs/common-sdk": "^1.0.3",
|
|
22
22
|
"@journeyapps-labs/common-utils": "^1.0.1",
|
|
23
23
|
"@journeyapps-labs/lib-reactor-data-layer": "1.0.11",
|
|
24
|
-
"@journeyapps-labs/reactor-mod": "5.
|
|
25
|
-
"@journeyapps-labs/reactor-mod-editor": "2.2.
|
|
24
|
+
"@journeyapps-labs/reactor-mod": "5.6.0",
|
|
25
|
+
"@journeyapps-labs/reactor-mod-editor": "2.2.4",
|
|
26
26
|
"@journeyapps/db": "^8.1.1",
|
|
27
27
|
"@journeyapps/parser-schema": "^8.2.5",
|
|
28
28
|
"@projectstorm/react-workspaces-core": "4.2.3",
|
package/src/DataBrowserModule.ts
CHANGED
|
@@ -18,6 +18,7 @@ import { EditSchemaModelAction } from './actions/schema-model/EditSchemaModelAct
|
|
|
18
18
|
import { TypeEngine } from './forms/TypeEngine';
|
|
19
19
|
import { ViewSchemaModelAsJsonAction } from './actions/schema-model/ViewSchemaModelAsJsonAction';
|
|
20
20
|
import { ViewHasManyAction } from './actions/schema-model/ViewHasManyAction';
|
|
21
|
+
import { DeleteSchemaModelAction } from './actions/schema-model/DeleteSchemaModelAction';
|
|
21
22
|
import { ModelJsonPanelFactory } from './panels/model-json/ModelJsonPanelFactory';
|
|
22
23
|
import { SchemaModelIndexDefinition } from './entities/SchemaModelIndexDefinition';
|
|
23
24
|
import { SavedQueryStore } from './stores/SavedQueryStore';
|
|
@@ -52,6 +53,7 @@ export class DataBrowserModule extends AbstractReactorModule {
|
|
|
52
53
|
actionStore.registerAction(new EditSchemaModelAction());
|
|
53
54
|
actionStore.registerAction(new ViewSchemaModelAsJsonAction());
|
|
54
55
|
actionStore.registerAction(new ViewHasManyAction());
|
|
56
|
+
actionStore.registerAction(new DeleteSchemaModelAction());
|
|
55
57
|
actionStore.registerAction(new OpenSavedQueryAction());
|
|
56
58
|
actionStore.registerAction(new RemoveSavedQueryAction());
|
|
57
59
|
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ActionStore,
|
|
3
|
+
EntityAction,
|
|
4
|
+
EntityActionEvent,
|
|
5
|
+
ioc,
|
|
6
|
+
setupDeleteConfirmation
|
|
7
|
+
} from '@journeyapps-labs/reactor-mod';
|
|
8
|
+
import { DataBrowserEntities } from '../../entities';
|
|
9
|
+
import { SchemaModelObject } from '../../core/SchemaModelObject';
|
|
10
|
+
import { runDeleteSchemaModels } from '../../core/delete-schema-models';
|
|
11
|
+
import { ModelPanelModel } from '../../panels/model/ModelPanelFactory';
|
|
12
|
+
|
|
13
|
+
export interface DeleteSchemaModelActionEvent extends EntityActionEvent<SchemaModelObject> {
|
|
14
|
+
sourcePanel?: ModelPanelModel;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class DeleteSchemaModelAction extends EntityAction<SchemaModelObject, DeleteSchemaModelActionEvent> {
|
|
18
|
+
static ID = 'DELETE_SCHEMA_MODEL';
|
|
19
|
+
|
|
20
|
+
constructor() {
|
|
21
|
+
super({
|
|
22
|
+
id: DeleteSchemaModelAction.ID,
|
|
23
|
+
name: 'Delete schema model',
|
|
24
|
+
icon: 'trash',
|
|
25
|
+
target: DataBrowserEntities.SCHEMA_MODEL_OBJECT
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
setupDeleteConfirmation({
|
|
29
|
+
action: this
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async fireEvent(event: DeleteSchemaModelActionEvent): Promise<any> {
|
|
34
|
+
await runDeleteSchemaModels({
|
|
35
|
+
models: [event.targetEntity],
|
|
36
|
+
sourcePanel: event.sourcePanel
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
static get() {
|
|
41
|
+
return ioc.get(ActionStore).getActionByID<DeleteSchemaModelAction>(DeleteSchemaModelAction.ID);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -94,6 +94,27 @@ export abstract class AbstractConnection extends BaseObserver<AbstractConnection
|
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
async batchDelete(models: SchemaModelObject[]) {
|
|
98
|
+
if (models.length === 0) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const database = await this.getConnection();
|
|
102
|
+
let batch = new database.Batch();
|
|
103
|
+
for (let model of models) {
|
|
104
|
+
if (!model?.model?.persisted) {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
batch.destroy(model.model);
|
|
108
|
+
}
|
|
109
|
+
await batch.execute();
|
|
110
|
+
for (let model of models) {
|
|
111
|
+
if (!model?.model?.persisted) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
model.definition.cache.delete(model.id);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
97
118
|
getSchemaModelDefinitionByName(name: string) {
|
|
98
119
|
return this.schema_models.items.find((i) => i.definition.name === name);
|
|
99
120
|
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { CrudError } from '@journeyapps/db';
|
|
2
|
+
import {
|
|
3
|
+
DialogStore,
|
|
4
|
+
ioc,
|
|
5
|
+
NotificationStore,
|
|
6
|
+
NotificationType,
|
|
7
|
+
ReactorPanelModel,
|
|
8
|
+
WorkspaceStore
|
|
9
|
+
} from '@journeyapps-labs/reactor-mod';
|
|
10
|
+
import * as _ from 'lodash';
|
|
11
|
+
import { SchemaModelObject } from './SchemaModelObject';
|
|
12
|
+
import { QueryPanelModel } from '../panels/query/QueryPanelFactory';
|
|
13
|
+
import { ModelPanelModel } from '../panels/model/ModelPanelFactory';
|
|
14
|
+
|
|
15
|
+
export interface DeleteSchemaModelsOptions {
|
|
16
|
+
models: SchemaModelObject[];
|
|
17
|
+
sourcePanel?: ReactorPanelModel;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const normalizeModels = (models: SchemaModelObject[]) => {
|
|
21
|
+
return _.uniqBy(
|
|
22
|
+
models.filter((model) => model?.model?.persisted),
|
|
23
|
+
(model) => model.id
|
|
24
|
+
);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const getErrorMessage = (error: unknown) => {
|
|
28
|
+
if (error instanceof CrudError) {
|
|
29
|
+
return error.firstError()?.detail || error.message;
|
|
30
|
+
}
|
|
31
|
+
if (error instanceof Error) {
|
|
32
|
+
return error.message;
|
|
33
|
+
}
|
|
34
|
+
return 'An unknown error occurred while deleting the selected models.';
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const buildDeleteMarkdown = (models: SchemaModelObject[]) => {
|
|
38
|
+
if (models.length === 1) {
|
|
39
|
+
const model = models[0];
|
|
40
|
+
const display = model.data?.display || model.id;
|
|
41
|
+
return `Delete **${display}**?\n\nThis action cannot be undone.`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return `Delete **${models.length}** models?\n\nThis action cannot be undone.`;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const runDeleteSchemaModels = async (options: DeleteSchemaModelsOptions): Promise<boolean> => {
|
|
48
|
+
const models = normalizeModels(options.models);
|
|
49
|
+
|
|
50
|
+
if (models.length === 0) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const definition = models[0].definition;
|
|
55
|
+
const mismatchedModel = models.find((model) => model.definition.connection !== definition.connection);
|
|
56
|
+
if (mismatchedModel) {
|
|
57
|
+
ioc.get(NotificationStore).showNotification({
|
|
58
|
+
title: 'Cannot delete models',
|
|
59
|
+
description: 'Selected models must come from the same connection.',
|
|
60
|
+
type: NotificationType.ERROR
|
|
61
|
+
});
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
await definition.connection.batchDelete(models);
|
|
67
|
+
const sourcePanelID = options.sourcePanel?.id;
|
|
68
|
+
if (options.sourcePanel instanceof ModelPanelModel) {
|
|
69
|
+
options.sourcePanel.delete();
|
|
70
|
+
}
|
|
71
|
+
await Promise.all(
|
|
72
|
+
ioc
|
|
73
|
+
.get(WorkspaceStore)
|
|
74
|
+
.flatten(ioc.get(WorkspaceStore).getRoot())
|
|
75
|
+
.map(async (panel) => {
|
|
76
|
+
if (panel.id === sourcePanelID) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (panel instanceof QueryPanelModel) {
|
|
80
|
+
await panel.reloadQuery();
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (panel instanceof ModelPanelModel && models.some((model) => model.id === panel.model?.id)) {
|
|
84
|
+
panel.delete();
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
);
|
|
88
|
+
ioc.get(NotificationStore).showNotification({
|
|
89
|
+
title: models.length === 1 ? 'Model deleted' : 'Models deleted',
|
|
90
|
+
description:
|
|
91
|
+
models.length === 1
|
|
92
|
+
? 'The selected model has been deleted.'
|
|
93
|
+
: `${models.length} selected models have been deleted.`,
|
|
94
|
+
type: NotificationType.SUCCESS
|
|
95
|
+
});
|
|
96
|
+
return true;
|
|
97
|
+
} catch (error) {
|
|
98
|
+
ioc.get(NotificationStore).showNotification({
|
|
99
|
+
title: 'Delete failed',
|
|
100
|
+
description: getErrorMessage(error),
|
|
101
|
+
type: NotificationType.ERROR
|
|
102
|
+
});
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export const deleteSchemaModels = async (options: DeleteSchemaModelsOptions): Promise<boolean> => {
|
|
108
|
+
const models = normalizeModels(options.models);
|
|
109
|
+
|
|
110
|
+
if (models.length <= 1) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const confirmed = await ioc.get(DialogStore).showConfirmDialog({
|
|
115
|
+
title: 'Delete selected models',
|
|
116
|
+
markdown: buildDeleteMarkdown(models),
|
|
117
|
+
yesBtn: {
|
|
118
|
+
label: `Delete ${models.length} models`,
|
|
119
|
+
icon: 'trash'
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
if (!confirmed) {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return runDeleteSchemaModels({
|
|
128
|
+
...options,
|
|
129
|
+
models
|
|
130
|
+
});
|
|
131
|
+
};
|
|
@@ -112,7 +112,6 @@ export const buildSimpleQueryColumns = (options: BuildSimpleQueryColumnsOptions)
|
|
|
112
112
|
/>
|
|
113
113
|
),
|
|
114
114
|
noWrap: true,
|
|
115
|
-
shrink: true,
|
|
116
115
|
accessor: (cell, row: PageRow) => {
|
|
117
116
|
return (
|
|
118
117
|
<SmartBelongsToDisplayWidget
|
|
@@ -160,7 +159,6 @@ export const buildSimpleQueryColumns = (options: BuildSimpleQueryColumnsOptions)
|
|
|
160
159
|
/>
|
|
161
160
|
),
|
|
162
161
|
noWrap: true,
|
|
163
|
-
shrink: true,
|
|
164
162
|
accessor: (cell, row: PageRow) => {
|
|
165
163
|
return <SmartCellDisplayWidget name={attribute.name} row={row} />;
|
|
166
164
|
}
|
|
@@ -13,6 +13,7 @@ import { SchemaModelObject } from '../core/SchemaModelObject';
|
|
|
13
13
|
import { SchemaModelDefinition } from '../core/SchemaModelDefinition';
|
|
14
14
|
import { validate as validateUUID } from 'uuid';
|
|
15
15
|
import { ViewHasManyAction } from '../actions/schema-model/ViewHasManyAction';
|
|
16
|
+
import { DeleteSchemaModelAction } from '../actions/schema-model/DeleteSchemaModelAction';
|
|
16
17
|
|
|
17
18
|
export interface SchemaModelObjectEntityDefinitionEncoded {
|
|
18
19
|
connection_id: string;
|
|
@@ -98,6 +99,9 @@ export class SchemaModelObjectEntityDefinition extends EntityDefinition<SchemaMo
|
|
|
98
99
|
}
|
|
99
100
|
|
|
100
101
|
isActionAllowedForEntity(action: Action, entity: SchemaModelObject) {
|
|
102
|
+
if (action.id === DeleteSchemaModelAction.ID) {
|
|
103
|
+
return !!entity.model?.persisted;
|
|
104
|
+
}
|
|
101
105
|
if (action.id === ViewHasManyAction.ID) {
|
|
102
106
|
return Object.keys(entity.definition.definition.hasMany || {}).length > 0;
|
|
103
107
|
}
|
|
@@ -4,6 +4,7 @@ import { observer } from 'mobx-react';
|
|
|
4
4
|
import styled from '@emotion/styled';
|
|
5
5
|
import {
|
|
6
6
|
BorderLayoutWidget,
|
|
7
|
+
Btn,
|
|
7
8
|
ioc,
|
|
8
9
|
LoadingPanelWidget,
|
|
9
10
|
PANEL_CONTENT_PADDING,
|
|
@@ -15,6 +16,7 @@ import {
|
|
|
15
16
|
} from '@journeyapps-labs/reactor-mod';
|
|
16
17
|
import { SchemaModelForm } from '../../forms/SchemaModelForm';
|
|
17
18
|
import { ModelPanelModel } from './ModelPanelFactory';
|
|
19
|
+
import { DeleteSchemaModelAction } from '../../actions/schema-model/DeleteSchemaModelAction';
|
|
18
20
|
|
|
19
21
|
export interface QueryPanelWidgetProps {
|
|
20
22
|
model: ModelPanelModel;
|
|
@@ -55,16 +57,21 @@ export const ModelPanelWidget: React.FC<QueryPanelWidgetProps> = observer((props
|
|
|
55
57
|
|
|
56
58
|
let top = null;
|
|
57
59
|
if (props.model.model) {
|
|
60
|
+
const toolbarButtons: Btn[] = props.model.model.model?.persisted
|
|
61
|
+
? [
|
|
62
|
+
DeleteSchemaModelAction.get().representAsButton(
|
|
63
|
+
{
|
|
64
|
+
targetEntity: props.model.model,
|
|
65
|
+
sourcePanel: props.model
|
|
66
|
+
},
|
|
67
|
+
true
|
|
68
|
+
)
|
|
69
|
+
].filter((button): button is Btn => !!button)
|
|
70
|
+
: [];
|
|
71
|
+
|
|
58
72
|
top = (
|
|
59
73
|
<PanelToolbarWidget
|
|
60
|
-
btns={
|
|
61
|
-
[
|
|
62
|
-
// {
|
|
63
|
-
// label: 'Delete object',
|
|
64
|
-
// action: () => {}
|
|
65
|
-
// }
|
|
66
|
-
]
|
|
67
|
-
}
|
|
74
|
+
btns={toolbarButtons}
|
|
68
75
|
meta={[
|
|
69
76
|
{
|
|
70
77
|
label: 'ID',
|
|
@@ -1,15 +1,27 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { useEffect, useRef } from 'react';
|
|
3
|
-
import { Page } from '../../core/query/Page';
|
|
4
|
-
import {
|
|
3
|
+
import { Page, PageRow } from '../../core/query/Page';
|
|
4
|
+
import {
|
|
5
|
+
ComboBoxItem,
|
|
6
|
+
themed,
|
|
7
|
+
ioc,
|
|
8
|
+
ScrollableDivCss,
|
|
9
|
+
System,
|
|
10
|
+
MultiSelectChangeEvent,
|
|
11
|
+
MultiSelectTableWidget,
|
|
12
|
+
LoadingPanelWidget
|
|
13
|
+
} from '@journeyapps-labs/reactor-mod';
|
|
5
14
|
import { AbstractQuery } from '../../core/query/AbstractQuery';
|
|
6
15
|
import { observer } from 'mobx-react';
|
|
7
16
|
import { DataBrowserEntities } from '../../entities';
|
|
8
17
|
import { SchemaModelObject } from '../../core/SchemaModelObject';
|
|
18
|
+
import { deleteSchemaModels } from '../../core/delete-schema-models';
|
|
9
19
|
|
|
10
20
|
export interface PageResultsWidgetProps {
|
|
11
21
|
page: Page;
|
|
12
22
|
query: AbstractQuery;
|
|
23
|
+
selectedModels: SchemaModelObject[];
|
|
24
|
+
onSelectionChange: (event: MultiSelectChangeEvent<PageRow>) => void;
|
|
13
25
|
scrollTop: number;
|
|
14
26
|
scrollLeft: number;
|
|
15
27
|
onScroll: (offsets: { top: number; left: number }) => void;
|
|
@@ -18,8 +30,35 @@ export interface PageResultsWidgetProps {
|
|
|
18
30
|
export const PageResultsWidget: React.FC<PageResultsWidgetProps> = observer((props) => {
|
|
19
31
|
const system = ioc.get(System);
|
|
20
32
|
const rows = props.page.loading ? [] : props.page.asRows();
|
|
33
|
+
const selectedRowKeys = props.selectedModels.map((model) => model.id);
|
|
21
34
|
const ref = useRef<HTMLDivElement>(null);
|
|
22
35
|
|
|
36
|
+
const showContextMenu = (event: React.MouseEvent, row: PageRow) => {
|
|
37
|
+
const definition = system.getDefinition<SchemaModelObject>(DataBrowserEntities.SCHEMA_MODEL_OBJECT);
|
|
38
|
+
const isSelectedRow = props.selectedModels.some((model) => model.id === row.model.id);
|
|
39
|
+
|
|
40
|
+
const selectedItems: ComboBoxItem[] =
|
|
41
|
+
isSelectedRow && props.selectedModels.length > 1
|
|
42
|
+
? [
|
|
43
|
+
{
|
|
44
|
+
key: 'delete-selected',
|
|
45
|
+
title: `Delete selected [${props.selectedModels.length}]`,
|
|
46
|
+
group: 'Selected',
|
|
47
|
+
icon: 'trash',
|
|
48
|
+
action: async () => {
|
|
49
|
+
await deleteSchemaModels({
|
|
50
|
+
models: props.selectedModels
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
: [];
|
|
56
|
+
|
|
57
|
+
definition.showContextMenuForEntity(row.model, event, {
|
|
58
|
+
additionalItems: selectedItems
|
|
59
|
+
} as any);
|
|
60
|
+
};
|
|
61
|
+
|
|
23
62
|
useEffect(() => {
|
|
24
63
|
if (!ref.current) {
|
|
25
64
|
return;
|
|
@@ -43,12 +82,10 @@ export const PageResultsWidget: React.FC<PageResultsWidgetProps> = observer((pro
|
|
|
43
82
|
});
|
|
44
83
|
}}
|
|
45
84
|
>
|
|
46
|
-
<
|
|
47
|
-
onContextMenu={
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
.showContextMenuForEntity(row.model, event);
|
|
51
|
-
}}
|
|
85
|
+
<MultiSelectTableWidget
|
|
86
|
+
onContextMenu={showContextMenu}
|
|
87
|
+
selectedRowKeys={selectedRowKeys}
|
|
88
|
+
onSelectionChange={props.onSelectionChange}
|
|
52
89
|
rows={rows}
|
|
53
90
|
columns={props.query.getColumns()}
|
|
54
91
|
/>
|
|
@@ -10,6 +10,9 @@ import { SavedQueryStore } from '../../stores/SavedQueryStore';
|
|
|
10
10
|
import { AbstractConnection } from '../../core/AbstractConnection';
|
|
11
11
|
import { SharedConnectionPanelFactory } from '../_shared/SharedConnectionPanelFactory';
|
|
12
12
|
import { Page } from '../../core/query/Page';
|
|
13
|
+
import { SchemaModelObject } from '../../core/SchemaModelObject';
|
|
14
|
+
import { action } from 'mobx';
|
|
15
|
+
import * as _ from 'lodash';
|
|
13
16
|
|
|
14
17
|
export class QueryPanelModel extends ReactorPanelModel {
|
|
15
18
|
@inject(ConnectionStore)
|
|
@@ -24,6 +27,9 @@ export class QueryPanelModel extends ReactorPanelModel {
|
|
|
24
27
|
@observable.ref
|
|
25
28
|
accessor current_page_data: Page | null;
|
|
26
29
|
|
|
30
|
+
@observable.ref
|
|
31
|
+
accessor selected_models: SchemaModelObject[];
|
|
32
|
+
|
|
27
33
|
accessor table_scroll_top: number;
|
|
28
34
|
accessor table_scroll_left: number;
|
|
29
35
|
|
|
@@ -33,10 +39,26 @@ export class QueryPanelModel extends ReactorPanelModel {
|
|
|
33
39
|
this.query = query;
|
|
34
40
|
this.current_page = 0;
|
|
35
41
|
this.current_page_data = null;
|
|
42
|
+
this.selected_models = [];
|
|
36
43
|
this.table_scroll_top = 0;
|
|
37
44
|
this.table_scroll_left = 0;
|
|
38
45
|
}
|
|
39
46
|
|
|
47
|
+
@action clearSelection() {
|
|
48
|
+
this.selected_models = [];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@action mergeSelectionForPage(event: { page: Page; models: SchemaModelObject[] }) {
|
|
52
|
+
const pageRowKeys = new Set(event.page.asRows().map((row) => row.key));
|
|
53
|
+
const nextSelectedModels = this.selected_models.filter((model) => !pageRowKeys.has(model.id));
|
|
54
|
+
this.selected_models = _.uniqBy([...nextSelectedModels, ...event.models], (model) => model.id);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@action async reloadQuery() {
|
|
58
|
+
this.clearSelection();
|
|
59
|
+
await this.query.load();
|
|
60
|
+
}
|
|
61
|
+
|
|
40
62
|
isSerializable() {
|
|
41
63
|
return this.query instanceof AbstractSerializableQuery;
|
|
42
64
|
}
|
|
@@ -71,6 +93,7 @@ export class QueryPanelModel extends ReactorPanelModel {
|
|
|
71
93
|
}
|
|
72
94
|
this.current_page = 0;
|
|
73
95
|
this.current_page_data = null;
|
|
96
|
+
this.clearSelection();
|
|
74
97
|
this.query = query;
|
|
75
98
|
await query.load();
|
|
76
99
|
}
|
|
@@ -9,6 +9,7 @@ import { PageResultsWidget } from './PageResultsWidget';
|
|
|
9
9
|
import { TableControlsWidget } from './TableControlsWidget';
|
|
10
10
|
import { autorun } from 'mobx';
|
|
11
11
|
import { TableControlsPositionPreference, TableControlsPositionValue } from '../../preferences/QueryControlPreferences';
|
|
12
|
+
import { deleteSchemaModels } from '../../core/delete-schema-models';
|
|
12
13
|
|
|
13
14
|
export interface QueryPanelWidgetProps {
|
|
14
15
|
model: QueryPanelModel;
|
|
@@ -101,6 +102,12 @@ export const QueryPanelWidget: React.FC<QueryPanelWidgetProps> = observer((props
|
|
|
101
102
|
query={props.model.query}
|
|
102
103
|
current_page={activePage}
|
|
103
104
|
loading={loading}
|
|
105
|
+
selectedCount={props.model.selected_models.length}
|
|
106
|
+
onDeleteSelected={async () => {
|
|
107
|
+
await deleteSchemaModels({
|
|
108
|
+
models: props.model.selected_models
|
|
109
|
+
});
|
|
110
|
+
}}
|
|
104
111
|
onLoadSavedQuery={async (id) => {
|
|
105
112
|
await props.model.loadSavedQuery(id);
|
|
106
113
|
}}
|
|
@@ -132,6 +139,13 @@ export const QueryPanelWidget: React.FC<QueryPanelWidgetProps> = observer((props
|
|
|
132
139
|
<PageResultsWidget
|
|
133
140
|
query={props.model.query}
|
|
134
141
|
page={activePage}
|
|
142
|
+
selectedModels={props.model.selected_models}
|
|
143
|
+
onSelectionChange={(event) => {
|
|
144
|
+
props.model.mergeSelectionForPage({
|
|
145
|
+
page: activePage,
|
|
146
|
+
models: event.rows.map((row) => row.model)
|
|
147
|
+
});
|
|
148
|
+
}}
|
|
135
149
|
scrollTop={props.model.table_scroll_top}
|
|
136
150
|
scrollLeft={props.model.table_scroll_left}
|
|
137
151
|
onScroll={({ top, left }) => {
|
|
@@ -12,6 +12,7 @@ import { SortControlsWidget } from './table-controls/SortControlsWidget';
|
|
|
12
12
|
import { ChangesControlsWidget } from './table-controls/ChangesControlsWidget';
|
|
13
13
|
import { FilterControlsWidget } from './table-controls/FilterControlsWidget';
|
|
14
14
|
import { QueryControlPreferences } from '../../preferences/QueryControlPreferences';
|
|
15
|
+
import { SelectionControlsWidget } from './table-controls/SelectionControlsWidget';
|
|
15
16
|
|
|
16
17
|
export interface TableControlsWidgetProps {
|
|
17
18
|
current_page: Page;
|
|
@@ -20,6 +21,8 @@ export interface TableControlsWidgetProps {
|
|
|
20
21
|
query: AbstractQuery;
|
|
21
22
|
onLoadSavedQuery?: (id: string) => Promise<any> | any;
|
|
22
23
|
loading?: boolean;
|
|
24
|
+
selectedCount: number;
|
|
25
|
+
onDeleteSelected: () => Promise<void>;
|
|
23
26
|
}
|
|
24
27
|
|
|
25
28
|
export const TableControlsWidget: React.FC<TableControlsWidgetProps> = observer((props) => {
|
|
@@ -52,6 +55,7 @@ export const TableControlsWidget: React.FC<TableControlsWidgetProps> = observer(
|
|
|
52
55
|
{simpleQuery && showSortControls ? (
|
|
53
56
|
<SortControlsWidget simpleQuery={simpleQuery} goToPage={props.goToPage} />
|
|
54
57
|
) : null}
|
|
58
|
+
<SelectionControlsWidget selectedCount={props.selectedCount} onDeleteSelected={props.onDeleteSelected} />
|
|
55
59
|
<ChangesControlsWidget query={props.query} currentPage={props.current_page} />
|
|
56
60
|
{props.loading ? <S.Loading icon="spinner" spin={true} /> : null}
|
|
57
61
|
</S.Container>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
InputContainerWidget,
|
|
4
|
+
ioc,
|
|
5
|
+
PanelButtonMode,
|
|
6
|
+
PanelButtonWidget,
|
|
7
|
+
theme,
|
|
8
|
+
ThemeStore
|
|
9
|
+
} from '@journeyapps-labs/reactor-mod';
|
|
10
|
+
|
|
11
|
+
export interface SelectionControlsWidgetProps {
|
|
12
|
+
selectedCount: number;
|
|
13
|
+
onDeleteSelected: () => Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const SelectionControlsWidget: React.FC<SelectionControlsWidgetProps> = (props) => {
|
|
17
|
+
const _theme = ioc.get(ThemeStore).getCurrentTheme(theme);
|
|
18
|
+
|
|
19
|
+
if (props.selectedCount === 0) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<InputContainerWidget label="Selection">
|
|
25
|
+
<PanelButtonWidget
|
|
26
|
+
mode={PanelButtonMode.PRIMARY}
|
|
27
|
+
label={`Delete selected [${props.selectedCount}]`}
|
|
28
|
+
icon="trash"
|
|
29
|
+
iconColor={_theme.status.failed}
|
|
30
|
+
action={props.onDeleteSelected}
|
|
31
|
+
/>
|
|
32
|
+
</InputContainerWidget>
|
|
33
|
+
);
|
|
34
|
+
};
|