@journeyapps-labs/reactor-mod-data-browser 3.5.0 → 3.6.1
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 +22 -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/SchemaModelObject.d.ts +3 -0
- package/dist/@types/core/delete-schema-models.d.ts +8 -0
- package/dist/@types/core/query/AbstractQuery.d.ts +3 -1
- 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-definitions/CreateModelAction.js +1 -1
- package/dist/actions/schema-definitions/CreateModelAction.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 +23 -1
- package/dist/core/AbstractConnection.js.map +1 -1
- package/dist/core/SchemaModelDefinition.js +2 -2
- package/dist/core/SchemaModelDefinition.js.map +1 -1
- package/dist/core/SchemaModelObject.js +52 -9
- package/dist/core/SchemaModelObject.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/AbstractQuery.js +48 -10
- package/dist/core/query/AbstractQuery.js.map +1 -1
- package/dist/core/query/query-simple/SimpleQueryColumns.js +0 -2
- package/dist/core/query/query-simple/SimpleQueryColumns.js.map +1 -1
- package/dist/core/query/widgets/SmartFilterWidget.js +1 -1
- package/dist/core/query/widgets/SmartFilterWidget.js.map +1 -1
- package/dist/entities/SchemaModelObjectEntityDefinition.js +5 -0
- package/dist/entities/SchemaModelObjectEntityDefinition.js.map +1 -1
- package/dist/forms/SchemaModelForm.js +9 -3
- package/dist/forms/SchemaModelForm.js.map +1 -1
- package/dist/panels/_shared/SharedModelPanelFactory.js +4 -2
- package/dist/panels/_shared/SharedModelPanelFactory.js.map +1 -1
- package/dist/panels/model/ModelPanelWidget.js +15 -11
- 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 +13 -2
- package/dist/panels/query/TableControlsWidget.js.map +1 -1
- package/dist/panels/query/table-controls/ChangesControlsWidget.js +1 -1
- package/dist/panels/query/table-controls/ChangesControlsWidget.js.map +1 -1
- package/dist/panels/query/table-controls/PageControlsWidget.js +8 -5
- package/dist/panels/query/table-controls/PageControlsWidget.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 +75 -75
- package/dist-module/bundle.js.map +1 -1
- package/package.json +8 -8
- package/src/DataBrowserModule.ts +2 -0
- package/src/actions/schema-definitions/CreateModelAction.ts +1 -1
- package/src/actions/schema-model/DeleteSchemaModelAction.ts +43 -0
- package/src/core/AbstractConnection.ts +23 -1
- package/src/core/SchemaModelDefinition.ts +3 -5
- package/src/core/SchemaModelObject.ts +30 -7
- package/src/core/delete-schema-models.ts +131 -0
- package/src/core/query/AbstractQuery.ts +16 -2
- package/src/core/query/query-simple/SimpleQueryColumns.tsx +0 -2
- package/src/core/query/widgets/SmartFilterWidget.tsx +1 -1
- package/src/entities/SchemaModelObjectEntityDefinition.ts +4 -0
- package/src/forms/SchemaModelForm.tsx +7 -2
- package/src/panels/_shared/SharedModelPanelFactory.tsx +4 -2
- package/src/panels/model/ModelPanelWidget.tsx +18 -11
- 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 +26 -2
- package/src/panels/query/table-controls/ChangesControlsWidget.tsx +1 -1
- package/src/panels/query/table-controls/PageControlsWidget.tsx +7 -5
- 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.1",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"typings": "./dist/@types/index",
|
|
6
6
|
"publishConfig": {
|
|
@@ -15,23 +15,23 @@
|
|
|
15
15
|
"license": "Apache-2.0",
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"@emotion/styled": "^11.14.1",
|
|
18
|
-
"@fortawesome/react-fontawesome": "^3.3.
|
|
18
|
+
"@fortawesome/react-fontawesome": "^3.3.1",
|
|
19
19
|
"@journeyapps-labs/client-backend-v4": "1.0.0",
|
|
20
20
|
"@journeyapps-labs/common-ioc": "^1.0.1",
|
|
21
|
-
"@journeyapps-labs/common-sdk": "^1.0.
|
|
21
|
+
"@journeyapps-labs/common-sdk": "^1.0.4",
|
|
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",
|
|
29
29
|
"async": "^3.2.6",
|
|
30
|
-
"lodash": "^4.
|
|
30
|
+
"lodash": "^4.18.1",
|
|
31
31
|
"mobx": "^6.15.0",
|
|
32
32
|
"mobx-react": "^9.2.1",
|
|
33
|
-
"react": "19.2.
|
|
34
|
-
"uuid": "^
|
|
33
|
+
"react": "19.2.5",
|
|
34
|
+
"uuid": "^14.0.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@journeyapps-labs/lib-reactor-builder": "3.0.2",
|
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
|
+
}
|
|
@@ -90,7 +90,29 @@ export abstract class AbstractConnection extends BaseObserver<AbstractConnection
|
|
|
90
90
|
}
|
|
91
91
|
await batch.execute();
|
|
92
92
|
for (let model of models) {
|
|
93
|
-
model.reload();
|
|
93
|
+
await model.reload();
|
|
94
|
+
model.clearEdits();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async batchDelete(models: SchemaModelObject[]) {
|
|
99
|
+
if (models.length === 0) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const database = await this.getConnection();
|
|
103
|
+
let batch = new database.Batch();
|
|
104
|
+
for (let model of models) {
|
|
105
|
+
if (!model?.model?.persisted) {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
batch.destroy(model.model);
|
|
109
|
+
}
|
|
110
|
+
await batch.execute();
|
|
111
|
+
for (let model of models) {
|
|
112
|
+
if (!model?.model?.persisted) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
model.definition.cache.delete(model.id);
|
|
94
116
|
}
|
|
95
117
|
}
|
|
96
118
|
|
|
@@ -6,13 +6,11 @@ import { SchemaModelObject } from './SchemaModelObject';
|
|
|
6
6
|
import { LifecycleModel } from '@journeyapps-labs/lib-reactor-data-layer';
|
|
7
7
|
import { BaseObserver } from '@journeyapps-labs/common-utils';
|
|
8
8
|
import { queue, QueueObject } from 'async';
|
|
9
|
-
import {
|
|
10
|
-
import { V4Index } from '@journeyapps-labs/client-backend-v4';
|
|
11
|
-
import { action, observable } from 'mobx';
|
|
9
|
+
import { observable } from 'mobx';
|
|
12
10
|
import { IndexModel } from './IndexModel';
|
|
13
11
|
import { TypeEngine } from '../forms/TypeEngine';
|
|
14
|
-
import { STANDARD_MODEL_FIELD_LABELS, StandardModelFields
|
|
15
|
-
import {
|
|
12
|
+
import { idVariable, STANDARD_MODEL_FIELD_LABELS, StandardModelFields } from './query/StandardModelFields';
|
|
13
|
+
import { SchemaFieldOrderingPreference, SchemaFieldOrderValue } from '../preferences/SchemaOrderingPreferences';
|
|
16
14
|
|
|
17
15
|
export interface SchemaModelDefinitionListener {
|
|
18
16
|
resolved: (event: { object: SchemaModelObject }) => any;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ApiObjectData, DatabaseAdapter, DatabaseObject } from '@journeyapps/db';
|
|
2
2
|
import { SchemaModelDefinition } from './SchemaModelDefinition';
|
|
3
|
-
import { action, observable } from 'mobx';
|
|
3
|
+
import { action, observable, runInAction } from 'mobx';
|
|
4
|
+
import { inject, NotificationStore, NotificationType, VisorStore } from '@journeyapps-labs/reactor-mod';
|
|
4
5
|
|
|
5
6
|
export interface SchemaModelObjectOptions {
|
|
6
7
|
definition: SchemaModelDefinition;
|
|
@@ -9,6 +10,12 @@ export interface SchemaModelObjectOptions {
|
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
export class SchemaModelObject {
|
|
13
|
+
@inject(VisorStore)
|
|
14
|
+
accessor visorStore: VisorStore;
|
|
15
|
+
|
|
16
|
+
@inject(NotificationStore)
|
|
17
|
+
accessor notificationStore: NotificationStore;
|
|
18
|
+
|
|
12
19
|
@observable
|
|
13
20
|
accessor data: ApiObjectData;
|
|
14
21
|
|
|
@@ -31,30 +38,42 @@ export class SchemaModelObject {
|
|
|
31
38
|
async applyPatches() {
|
|
32
39
|
if (!this.model) {
|
|
33
40
|
const collection = await this.definition.getCollection();
|
|
34
|
-
|
|
41
|
+
runInAction(() => {
|
|
42
|
+
this.model = collection.create();
|
|
43
|
+
});
|
|
35
44
|
}
|
|
36
45
|
for (let entry of this.patch.entries()) {
|
|
37
46
|
if (this.definition.definition.belongsTo[entry[0]]) {
|
|
38
|
-
this.model[entry[0]](entry[1]
|
|
47
|
+
this.model[entry[0]](entry[1]?.model || null);
|
|
39
48
|
} else {
|
|
40
49
|
this.model[entry[0]] = entry[1];
|
|
41
50
|
}
|
|
42
51
|
}
|
|
43
|
-
this.
|
|
52
|
+
this.clearEdits();
|
|
44
53
|
}
|
|
45
54
|
|
|
46
55
|
async save() {
|
|
47
|
-
await this.definition.
|
|
56
|
+
await this.visorStore.wrap(`Saving ${this.definition.definition.label}`, async () => {
|
|
57
|
+
await this.definition.connection.batchSave([this]);
|
|
58
|
+
});
|
|
59
|
+
this.notificationStore.showNotification({
|
|
60
|
+
title: 'Model updated',
|
|
61
|
+
description: `${this.definition.definition.label} was updated`,
|
|
62
|
+
type: NotificationType.SUCCESS
|
|
63
|
+
});
|
|
48
64
|
}
|
|
49
65
|
|
|
66
|
+
@action
|
|
50
67
|
clearEdits() {
|
|
51
68
|
this.patch.clear();
|
|
52
69
|
}
|
|
53
70
|
|
|
71
|
+
@action
|
|
54
72
|
revert(field: string) {
|
|
55
73
|
this.patch.delete(field);
|
|
56
74
|
}
|
|
57
75
|
|
|
76
|
+
@action
|
|
58
77
|
set(field: string, value: any) {
|
|
59
78
|
if (this.model?.[field] === value) {
|
|
60
79
|
this.patch.delete(field);
|
|
@@ -72,7 +91,11 @@ export class SchemaModelObject {
|
|
|
72
91
|
}
|
|
73
92
|
|
|
74
93
|
async reload() {
|
|
75
|
-
await this.definition.load(this.id);
|
|
94
|
+
const model = await this.definition.load(this.id);
|
|
95
|
+
if (model && model !== this) {
|
|
96
|
+
this.setData(model.data);
|
|
97
|
+
this.definition.cache.set(this.id, this);
|
|
98
|
+
}
|
|
76
99
|
}
|
|
77
100
|
|
|
78
101
|
get definition(): SchemaModelDefinition {
|
|
@@ -80,6 +103,6 @@ export class SchemaModelObject {
|
|
|
80
103
|
}
|
|
81
104
|
|
|
82
105
|
get id() {
|
|
83
|
-
return this.data
|
|
106
|
+
return this.data?.id;
|
|
84
107
|
}
|
|
85
108
|
}
|
|
@@ -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
|
+
};
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { Page } from './Page';
|
|
2
2
|
import { AbstractConnection } from '../AbstractConnection';
|
|
3
3
|
import { v4 } from 'uuid';
|
|
4
|
-
import { TableColumn } from '@journeyapps-labs/reactor-mod';
|
|
4
|
+
import { inject, NotificationStore, NotificationType, TableColumn, VisorStore } from '@journeyapps-labs/reactor-mod';
|
|
5
5
|
import { SchemaModelObject } from '../SchemaModelObject';
|
|
6
6
|
|
|
7
7
|
export abstract class AbstractQuery {
|
|
8
|
+
@inject(VisorStore)
|
|
9
|
+
accessor visorStore: VisorStore;
|
|
10
|
+
|
|
11
|
+
@inject(NotificationStore)
|
|
12
|
+
accessor notificationStore: NotificationStore;
|
|
13
|
+
|
|
8
14
|
id: string;
|
|
9
15
|
|
|
10
16
|
constructor(
|
|
@@ -15,7 +21,15 @@ export abstract class AbstractQuery {
|
|
|
15
21
|
}
|
|
16
22
|
|
|
17
23
|
async batchSave() {
|
|
18
|
-
|
|
24
|
+
const dirtyObjects = this.getDirtyObjects();
|
|
25
|
+
await this.visorStore.wrap(`Saving ${dirtyObjects.length} models`, async () => {
|
|
26
|
+
await this.connection.batchSave(dirtyObjects);
|
|
27
|
+
});
|
|
28
|
+
this.notificationStore.showNotification({
|
|
29
|
+
title: 'Models updated',
|
|
30
|
+
description: `${dirtyObjects.length} ${dirtyObjects.length === 1 ? 'model was' : 'models were'} updated`,
|
|
31
|
+
type: NotificationType.SUCCESS
|
|
32
|
+
});
|
|
19
33
|
}
|
|
20
34
|
|
|
21
35
|
abstract getDirtyObjects(): SchemaModelObject[];
|
|
@@ -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
|
}
|
|
@@ -73,7 +73,7 @@ export const SmartFilterWidget: React.FC<SmartFilterWidgetProps> = (props) => {
|
|
|
73
73
|
active={isActive}
|
|
74
74
|
{...setupTooltipProps({
|
|
75
75
|
tooltip: getFilterTooltip(props.filter),
|
|
76
|
-
tooltipPos: TooltipPosition.
|
|
76
|
+
tooltipPos: TooltipPosition.BOTTOM_RIGHT
|
|
77
77
|
})}
|
|
78
78
|
onClick={async (event) => {
|
|
79
79
|
const filter = await props.setupFilter({
|
|
@@ -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
|
}
|
|
@@ -81,7 +81,7 @@ export class TypedBinding extends Binding {
|
|
|
81
81
|
super({
|
|
82
82
|
...options,
|
|
83
83
|
resolve: async () => {
|
|
84
|
-
return options.model?.model[options.name];
|
|
84
|
+
return options.model?.model?.[options.name];
|
|
85
85
|
}
|
|
86
86
|
});
|
|
87
87
|
this.handler = this.typeEngine.getHandler(options.variable.type);
|
|
@@ -110,6 +110,7 @@ export class SchemaModelForm extends FormModel {
|
|
|
110
110
|
.map((entry) => {
|
|
111
111
|
if (entry.type === OrderedSchemaFieldType.BELONGS_TO) {
|
|
112
112
|
const relationship = entry.object;
|
|
113
|
+
const idVariable = options.definition.getBelongsToIdVariableForRelationship(relationship.name);
|
|
113
114
|
const definition = options.definition.connection.getSchemaModelDefinitionByName(
|
|
114
115
|
relationship.foreignType.name
|
|
115
116
|
);
|
|
@@ -130,7 +131,11 @@ export class SchemaModelForm extends FormModel {
|
|
|
130
131
|
model: this.options.object,
|
|
131
132
|
input: entity,
|
|
132
133
|
resolve: () => {
|
|
133
|
-
if (
|
|
134
|
+
if (idVariable && options.object?.model) {
|
|
135
|
+
const objectId = options.object.model[idVariable.name];
|
|
136
|
+
return objectId ? definition.resolve(objectId) : null;
|
|
137
|
+
}
|
|
138
|
+
if (!options.object?.data?.belongs_to?.[relationship.name]) {
|
|
134
139
|
return null;
|
|
135
140
|
}
|
|
136
141
|
return definition.resolve(options.object.data.belongs_to[relationship.name]);
|
|
@@ -36,8 +36,10 @@ export class SharedModelPanelModel extends ReactorPanelModel {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
async decodeEntities(data: ReturnType<this['encodeEntities']>) {
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
const definition = data.definition;
|
|
40
|
+
const model = definition ? data.model || (await definition.generateNewModelObject()) : null;
|
|
41
|
+
this.definition = definition;
|
|
42
|
+
this.model = model;
|
|
41
43
|
}
|
|
42
44
|
}
|
|
43
45
|
|
|
@@ -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;
|
|
@@ -40,7 +42,7 @@ export const ModelPanelWidget: React.FC<QueryPanelWidgetProps> = observer((props
|
|
|
40
42
|
const _theme = ioc.get(ThemeStore).getCurrentTheme(theme);
|
|
41
43
|
|
|
42
44
|
useEffect(() => {
|
|
43
|
-
if (!props.model.definition) {
|
|
45
|
+
if (!props.model.definition || !props.model.model) {
|
|
44
46
|
return;
|
|
45
47
|
}
|
|
46
48
|
let _form = new SchemaModelForm({
|
|
@@ -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',
|
|
@@ -88,8 +95,8 @@ export const ModelPanelWidget: React.FC<QueryPanelWidgetProps> = observer((props
|
|
|
88
95
|
label="Save"
|
|
89
96
|
icon="save"
|
|
90
97
|
iconColor={_theme.status.success}
|
|
91
|
-
action={() => {
|
|
92
|
-
props.model.model.save();
|
|
98
|
+
action={async () => {
|
|
99
|
+
await props.model.model.save();
|
|
93
100
|
}}
|
|
94
101
|
/>
|
|
95
102
|
<PanelButtonWidget
|
|
@@ -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
|
/>
|