@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.
Files changed (78) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/@types/actions/schema-model/DeleteSchemaModelAction.d.ts +12 -0
  3. package/dist/@types/core/AbstractConnection.d.ts +1 -0
  4. package/dist/@types/core/SchemaModelObject.d.ts +3 -0
  5. package/dist/@types/core/delete-schema-models.d.ts +8 -0
  6. package/dist/@types/core/query/AbstractQuery.d.ts +3 -1
  7. package/dist/@types/forms/types/shared/ui.d.ts +6 -0
  8. package/dist/@types/panels/query/PageResultsWidget.d.ts +5 -1
  9. package/dist/@types/panels/query/QueryPanelFactory.d.ts +8 -0
  10. package/dist/@types/panels/query/TableControlsWidget.d.ts +2 -0
  11. package/dist/@types/panels/query/table-controls/SelectionControlsWidget.d.ts +6 -0
  12. package/dist/DataBrowserModule.js +2 -0
  13. package/dist/DataBrowserModule.js.map +1 -1
  14. package/dist/actions/schema-definitions/CreateModelAction.js +1 -1
  15. package/dist/actions/schema-definitions/CreateModelAction.js.map +1 -1
  16. package/dist/actions/schema-model/DeleteSchemaModelAction.js +27 -0
  17. package/dist/actions/schema-model/DeleteSchemaModelAction.js.map +1 -0
  18. package/dist/core/AbstractConnection.js +23 -1
  19. package/dist/core/AbstractConnection.js.map +1 -1
  20. package/dist/core/SchemaModelDefinition.js +2 -2
  21. package/dist/core/SchemaModelDefinition.js.map +1 -1
  22. package/dist/core/SchemaModelObject.js +52 -9
  23. package/dist/core/SchemaModelObject.js.map +1 -1
  24. package/dist/core/delete-schema-models.js +101 -0
  25. package/dist/core/delete-schema-models.js.map +1 -0
  26. package/dist/core/query/AbstractQuery.js +48 -10
  27. package/dist/core/query/AbstractQuery.js.map +1 -1
  28. package/dist/core/query/query-simple/SimpleQueryColumns.js +0 -2
  29. package/dist/core/query/query-simple/SimpleQueryColumns.js.map +1 -1
  30. package/dist/core/query/widgets/SmartFilterWidget.js +1 -1
  31. package/dist/core/query/widgets/SmartFilterWidget.js.map +1 -1
  32. package/dist/entities/SchemaModelObjectEntityDefinition.js +5 -0
  33. package/dist/entities/SchemaModelObjectEntityDefinition.js.map +1 -1
  34. package/dist/forms/SchemaModelForm.js +9 -3
  35. package/dist/forms/SchemaModelForm.js.map +1 -1
  36. package/dist/panels/_shared/SharedModelPanelFactory.js +4 -2
  37. package/dist/panels/_shared/SharedModelPanelFactory.js.map +1 -1
  38. package/dist/panels/model/ModelPanelWidget.js +15 -11
  39. package/dist/panels/model/ModelPanelWidget.js.map +1 -1
  40. package/dist/panels/query/PageResultsWidget.js +26 -6
  41. package/dist/panels/query/PageResultsWidget.js.map +1 -1
  42. package/dist/panels/query/QueryPanelFactory.js +39 -4
  43. package/dist/panels/query/QueryPanelFactory.js.map +1 -1
  44. package/dist/panels/query/QueryPanelWidget.js +12 -2
  45. package/dist/panels/query/QueryPanelWidget.js.map +1 -1
  46. package/dist/panels/query/TableControlsWidget.js +13 -2
  47. package/dist/panels/query/TableControlsWidget.js.map +1 -1
  48. package/dist/panels/query/table-controls/ChangesControlsWidget.js +1 -1
  49. package/dist/panels/query/table-controls/ChangesControlsWidget.js.map +1 -1
  50. package/dist/panels/query/table-controls/PageControlsWidget.js +8 -5
  51. package/dist/panels/query/table-controls/PageControlsWidget.js.map +1 -1
  52. package/dist/panels/query/table-controls/SelectionControlsWidget.js +11 -0
  53. package/dist/panels/query/table-controls/SelectionControlsWidget.js.map +1 -0
  54. package/dist/tsconfig.tsbuildinfo +1 -1
  55. package/dist-module/bundle.js +75 -75
  56. package/dist-module/bundle.js.map +1 -1
  57. package/package.json +8 -8
  58. package/src/DataBrowserModule.ts +2 -0
  59. package/src/actions/schema-definitions/CreateModelAction.ts +1 -1
  60. package/src/actions/schema-model/DeleteSchemaModelAction.ts +43 -0
  61. package/src/core/AbstractConnection.ts +23 -1
  62. package/src/core/SchemaModelDefinition.ts +3 -5
  63. package/src/core/SchemaModelObject.ts +30 -7
  64. package/src/core/delete-schema-models.ts +131 -0
  65. package/src/core/query/AbstractQuery.ts +16 -2
  66. package/src/core/query/query-simple/SimpleQueryColumns.tsx +0 -2
  67. package/src/core/query/widgets/SmartFilterWidget.tsx +1 -1
  68. package/src/entities/SchemaModelObjectEntityDefinition.ts +4 -0
  69. package/src/forms/SchemaModelForm.tsx +7 -2
  70. package/src/panels/_shared/SharedModelPanelFactory.tsx +4 -2
  71. package/src/panels/model/ModelPanelWidget.tsx +18 -11
  72. package/src/panels/query/PageResultsWidget.tsx +45 -8
  73. package/src/panels/query/QueryPanelFactory.tsx +23 -0
  74. package/src/panels/query/QueryPanelWidget.tsx +14 -0
  75. package/src/panels/query/TableControlsWidget.tsx +26 -2
  76. package/src/panels/query/table-controls/ChangesControlsWidget.tsx +1 -1
  77. package/src/panels/query/table-controls/PageControlsWidget.tsx +7 -5
  78. 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.5.0",
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.0",
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.3",
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.4.0",
25
- "@journeyapps-labs/reactor-mod-editor": "2.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.17.23",
30
+ "lodash": "^4.18.1",
31
31
  "mobx": "^6.15.0",
32
32
  "mobx-react": "^9.2.1",
33
- "react": "19.2.4",
34
- "uuid": "^13.0.0"
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",
@@ -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
 
@@ -20,7 +20,7 @@ export class CreateModelAction extends EntityAction<SchemaModelDefinition> {
20
20
  super({
21
21
  id: CreateModelAction.ID,
22
22
  name: 'Create schema model',
23
- icon: 'search',
23
+ icon: 'plus',
24
24
  target: DataBrowserEntities.SCHEMA_MODEL_DEFINITION
25
25
  });
26
26
  }
@@ -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 { v4 } from 'uuid';
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, idVariable } from './query/StandardModelFields';
15
- import { SchemaFieldOrderValue, SchemaFieldOrderingPreference } from '../preferences/SchemaOrderingPreferences';
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
- this.model = collection.create();
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].model);
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.patch.clear();
52
+ this.clearEdits();
44
53
  }
45
54
 
46
55
  async save() {
47
- await this.definition.connection.batchSave([this]);
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.id;
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
- await this.connection.batchSave(this.getDirtyObjects());
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.BOTTOM
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 (!options.object.data.belongs_to[relationship.name]) {
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
- this.definition = data.definition;
40
- this.model = data.model || (await data.definition.generateNewModelObject());
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 { themed, ioc, ScrollableDivCss, System, TableWidget, LoadingPanelWidget } from '@journeyapps-labs/reactor-mod';
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
- <TableWidget
47
- onContextMenu={(event, row) => {
48
- system
49
- .getDefinition<SchemaModelObject>(DataBrowserEntities.SCHEMA_MODEL_OBJECT)
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
  />