@journeyapps-labs/reactor-mod-data-browser 3.4.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 +20 -0
- package/dist/@types/actions/schema-model/DeleteSchemaModelAction.d.ts +12 -0
- package/dist/@types/core/AbstractConnection.d.ts +2 -0
- package/dist/@types/core/SchemaModelDefinition.d.ts +20 -0
- package/dist/@types/core/delete-schema-models.d.ts +8 -0
- package/dist/@types/entities/SchemaModelTreePresenterComponent.d.ts +10 -0
- package/dist/@types/forms/types/shared/ui.d.ts +6 -0
- package/dist/@types/index.d.ts +1 -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/@types/preferences/QueryControlPreferences.d.ts +14 -2
- package/dist/@types/preferences/SchemaOrderingPreferences.d.ts +22 -0
- package/dist/DataBrowserModule.js +4 -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 +32 -3
- package/dist/core/AbstractConnection.js.map +1 -1
- package/dist/core/SchemaModelDefinition.js +73 -17
- package/dist/core/SchemaModelDefinition.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 +55 -50
- package/dist/core/query/query-simple/SimpleQueryColumns.js.map +1 -1
- package/dist/core/query/widgets/IDCellDisplayWidget.js +9 -4
- package/dist/core/query/widgets/IDCellDisplayWidget.js.map +1 -1
- package/dist/entities/SchemaModelDefinitionEntityDefinition.js +3 -10
- package/dist/entities/SchemaModelDefinitionEntityDefinition.js.map +1 -1
- package/dist/entities/SchemaModelObjectEntityDefinition.js +5 -0
- package/dist/entities/SchemaModelObjectEntityDefinition.js.map +1 -1
- package/dist/entities/SchemaModelTreePresenterComponent.js +23 -0
- package/dist/entities/SchemaModelTreePresenterComponent.js.map +1 -0
- package/dist/forms/SchemaModelForm.js +31 -29
- package/dist/forms/SchemaModelForm.js.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.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 +25 -10
- 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/preferences/QueryControlPreferences.js +45 -1
- package/dist/preferences/QueryControlPreferences.js.map +1 -1
- package/dist/preferences/SchemaOrderingPreferences.js +76 -0
- package/dist/preferences/SchemaOrderingPreferences.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist-module/bundle.js +74 -74
- package/dist-module/bundle.js.map +1 -1
- package/package.json +7 -6
- package/src/DataBrowserModule.ts +4 -0
- package/src/actions/schema-model/DeleteSchemaModelAction.ts +43 -0
- package/src/core/AbstractConnection.ts +33 -3
- package/src/core/SchemaModelDefinition.ts +97 -17
- package/src/core/delete-schema-models.ts +131 -0
- package/src/core/query/query-simple/SimpleQueryColumns.tsx +81 -74
- package/src/core/query/widgets/IDCellDisplayWidget.tsx +17 -4
- package/src/entities/SchemaModelDefinitionEntityDefinition.ts +3 -13
- package/src/entities/SchemaModelObjectEntityDefinition.ts +4 -0
- package/src/entities/SchemaModelTreePresenterComponent.ts +31 -0
- package/src/forms/SchemaModelForm.tsx +46 -41
- package/src/index.ts +1 -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 +38 -22
- package/src/panels/query/TableControlsWidget.tsx +4 -0
- package/src/panels/query/table-controls/SelectionControlsWidget.tsx +34 -0
- package/src/preferences/QueryControlPreferences.ts +51 -2
- package/src/preferences/SchemaOrderingPreferences.ts +82 -0
|
@@ -3,9 +3,8 @@ import {
|
|
|
3
3
|
EntityActionHandlerComponent,
|
|
4
4
|
EntityDefinition,
|
|
5
5
|
EntityDescriberComponent,
|
|
6
|
-
inject,
|
|
7
6
|
InlineEntityEncoderComponent,
|
|
8
|
-
|
|
7
|
+
inject,
|
|
9
8
|
SimpleParentEntitySearchEngine
|
|
10
9
|
} from '@journeyapps-labs/reactor-mod';
|
|
11
10
|
import { DataBrowserEntities } from '../entities';
|
|
@@ -14,6 +13,7 @@ import { AbstractConnection } from '../core/AbstractConnection';
|
|
|
14
13
|
import { SchemaModelDefinition } from '../core/SchemaModelDefinition';
|
|
15
14
|
import { QuerySchemaModelAction } from '../actions/schema-definitions/QuerySchemaModelAction';
|
|
16
15
|
import { IndexModel } from '../core/IndexModel';
|
|
16
|
+
import { SchemaModelTreePresenterComponent } from './SchemaModelTreePresenterComponent';
|
|
17
17
|
|
|
18
18
|
export interface SchemaModelDefinitionEntityDefinitionEncoded {
|
|
19
19
|
connection_id: string;
|
|
@@ -77,17 +77,7 @@ export class SchemaModelDefinitionEntityDefinition extends EntityDefinition<Sche
|
|
|
77
77
|
})
|
|
78
78
|
);
|
|
79
79
|
|
|
80
|
-
this.registerComponent(
|
|
81
|
-
new InlineTreePresenterComponent({
|
|
82
|
-
loadChildrenAsNodesAreOpened: true,
|
|
83
|
-
cacheTreeEntities: true,
|
|
84
|
-
augmentTreeNodeProps: () => {
|
|
85
|
-
return {
|
|
86
|
-
openOnSingleClick: false
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
})
|
|
90
|
-
);
|
|
80
|
+
this.registerComponent(new SchemaModelTreePresenterComponent({ loadChildrenAsNodesAreOpened: true }));
|
|
91
81
|
|
|
92
82
|
this.registerComponent(
|
|
93
83
|
new SimpleParentEntitySearchEngine<AbstractConnection, SchemaModelDefinition>({
|
|
@@ -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
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CachedEntityTreePresenterContext,
|
|
3
|
+
EntityReactorNode,
|
|
4
|
+
EntityTreePresenterComponent,
|
|
5
|
+
ReactorTreeEntity
|
|
6
|
+
} from '@journeyapps-labs/reactor-mod';
|
|
7
|
+
import { SchemaModelDefinition } from '../core/SchemaModelDefinition';
|
|
8
|
+
import { SchemaModelOrderValue, SchemaModelOrderingPreference } from '../preferences/SchemaOrderingPreferences';
|
|
9
|
+
|
|
10
|
+
class SchemaModelTreePresenterContext extends CachedEntityTreePresenterContext<SchemaModelDefinition> {
|
|
11
|
+
getSortedEntities(entities: SchemaModelDefinition[]) {
|
|
12
|
+
if (SchemaModelOrderingPreference.getValue() === SchemaModelOrderValue.AS_DEFINED_IN_SCHEMA) {
|
|
13
|
+
return entities;
|
|
14
|
+
}
|
|
15
|
+
return super.getSortedEntities(entities);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
protected doGenerateTreeNode(entity: SchemaModelDefinition, options): ReactorTreeEntity {
|
|
19
|
+
return new EntityReactorNode({
|
|
20
|
+
entity,
|
|
21
|
+
definition: this.definition,
|
|
22
|
+
events: options?.events
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class SchemaModelTreePresenterComponent extends EntityTreePresenterComponent<SchemaModelDefinition> {
|
|
28
|
+
protected _generateContext() {
|
|
29
|
+
return new SchemaModelTreePresenterContext(this);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -7,6 +7,7 @@ import { DirtyWrapperInput } from './inputs/DirtyWrapperInput';
|
|
|
7
7
|
import { TypeEngine, TypeHandler } from './TypeEngine';
|
|
8
8
|
import { autorun, IReactionDisposer } from 'mobx';
|
|
9
9
|
import { Variable } from '@journeyapps/db';
|
|
10
|
+
import { OrderedSchemaFieldType } from '../core/SchemaModelDefinition';
|
|
10
11
|
|
|
11
12
|
export interface SchemaModelFormOptions {
|
|
12
13
|
definition: SchemaModelDefinition;
|
|
@@ -104,52 +105,56 @@ export class SchemaModelForm extends FormModel {
|
|
|
104
105
|
constructor(protected options: SchemaModelFormOptions) {
|
|
105
106
|
super();
|
|
106
107
|
this.bindings = new Set<Binding>();
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
return new Binding({
|
|
119
|
-
name: relationship.name,
|
|
120
|
-
model: this.options.object,
|
|
121
|
-
input: entity,
|
|
122
|
-
resolve: () => {
|
|
123
|
-
if (!options.object.data.belongs_to[relationship.name]) {
|
|
108
|
+
options.definition
|
|
109
|
+
.getOrderedFieldsAndRelationships()
|
|
110
|
+
.map((entry) => {
|
|
111
|
+
if (entry.type === OrderedSchemaFieldType.BELONGS_TO) {
|
|
112
|
+
const relationship = entry.object;
|
|
113
|
+
const definition = options.definition.connection.getSchemaModelDefinitionByName(
|
|
114
|
+
relationship.foreignType.name
|
|
115
|
+
);
|
|
116
|
+
if (!definition) {
|
|
124
117
|
return null;
|
|
125
118
|
}
|
|
126
|
-
|
|
119
|
+
|
|
120
|
+
const entity = new EntityInput({
|
|
121
|
+
name: relationship.name,
|
|
122
|
+
entityType: DataBrowserEntities.SCHEMA_MODEL_OBJECT,
|
|
123
|
+
parent: definition,
|
|
124
|
+
label: relationship.name,
|
|
125
|
+
value: null
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
return new Binding({
|
|
129
|
+
name: relationship.name,
|
|
130
|
+
model: this.options.object,
|
|
131
|
+
input: entity,
|
|
132
|
+
resolve: () => {
|
|
133
|
+
if (!options.object.data.belongs_to[relationship.name]) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
return definition.resolve(options.object.data.belongs_to[relationship.name]);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
127
139
|
}
|
|
128
|
-
});
|
|
129
|
-
})
|
|
130
|
-
.filter((f) => !!f)
|
|
131
|
-
.forEach((binding) => {
|
|
132
|
-
this.bindings.add(binding);
|
|
133
|
-
this.addInput(new DirtyWrapperInput(binding.input, options.object));
|
|
134
|
-
});
|
|
135
140
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
141
|
+
const attribute = entry.object;
|
|
142
|
+
let field = this.typeEngine.getHandler(attribute.type)?.generateField({
|
|
143
|
+
name: attribute.name,
|
|
144
|
+
label: attribute.label,
|
|
145
|
+
type: attribute.type
|
|
146
|
+
});
|
|
147
|
+
if (!field) {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
145
150
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
151
|
+
return new TypedBinding({
|
|
152
|
+
variable: attribute,
|
|
153
|
+
model: this.options.object,
|
|
154
|
+
input: field,
|
|
155
|
+
name: attribute.name
|
|
156
|
+
});
|
|
157
|
+
})
|
|
153
158
|
.filter((binding) => !!binding)
|
|
154
159
|
.forEach((binding) => {
|
|
155
160
|
this.bindings.add(binding);
|
package/src/index.ts
CHANGED
|
@@ -15,6 +15,7 @@ export * from './entities/QueryEntityDefinition';
|
|
|
15
15
|
export * from './entities/ConnectionEntityDefinition';
|
|
16
16
|
export * from './entities/ConnectionFactoryEntityDefinition';
|
|
17
17
|
export * from './entities/SchemaModelDefinitionEntityDefinition';
|
|
18
|
+
export * from './entities/SchemaModelTreePresenterComponent';
|
|
18
19
|
export * from './entities/SchemaModelObjectEntityDefinition';
|
|
19
20
|
|
|
20
21
|
export * from './panels/query/QueryPanelFactory';
|
|
@@ -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
|
}
|
|
@@ -8,6 +8,8 @@ import { Page } from '../../core/query/Page';
|
|
|
8
8
|
import { PageResultsWidget } from './PageResultsWidget';
|
|
9
9
|
import { TableControlsWidget } from './TableControlsWidget';
|
|
10
10
|
import { autorun } from 'mobx';
|
|
11
|
+
import { TableControlsPositionPreference, TableControlsPositionValue } from '../../preferences/QueryControlPreferences';
|
|
12
|
+
import { deleteSchemaModels } from '../../core/delete-schema-models';
|
|
11
13
|
|
|
12
14
|
export interface QueryPanelWidgetProps {
|
|
13
15
|
model: QueryPanelModel;
|
|
@@ -93,6 +95,27 @@ export const QueryPanelWidget: React.FC<QueryPanelWidgetProps> = observer((props
|
|
|
93
95
|
|
|
94
96
|
const activePage =
|
|
95
97
|
props.model.current_page_data || (props.model.query ? props.model.query.getPage(props.model.current_page) : null);
|
|
98
|
+
const controlsPosition = TableControlsPositionPreference.getValue();
|
|
99
|
+
|
|
100
|
+
const controls = (
|
|
101
|
+
<TableControlsWidget
|
|
102
|
+
query={props.model.query}
|
|
103
|
+
current_page={activePage}
|
|
104
|
+
loading={loading}
|
|
105
|
+
selectedCount={props.model.selected_models.length}
|
|
106
|
+
onDeleteSelected={async () => {
|
|
107
|
+
await deleteSchemaModels({
|
|
108
|
+
models: props.model.selected_models
|
|
109
|
+
});
|
|
110
|
+
}}
|
|
111
|
+
onLoadSavedQuery={async (id) => {
|
|
112
|
+
await props.model.loadSavedQuery(id);
|
|
113
|
+
}}
|
|
114
|
+
goToPage={(index) => {
|
|
115
|
+
props.model.current_page = index;
|
|
116
|
+
}}
|
|
117
|
+
/>
|
|
118
|
+
);
|
|
96
119
|
|
|
97
120
|
return (
|
|
98
121
|
<LoadingPanelWidget loading={!props.model.query || !activePage}>
|
|
@@ -101,35 +124,28 @@ export const QueryPanelWidget: React.FC<QueryPanelWidgetProps> = observer((props
|
|
|
101
124
|
<S.Container>
|
|
102
125
|
<BorderLayoutWidget
|
|
103
126
|
top={
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
onLoadSavedQuery={async (id) => {
|
|
109
|
-
await props.model.loadSavedQuery(id);
|
|
110
|
-
}}
|
|
111
|
-
goToPage={(index) => {
|
|
112
|
-
props.model.current_page = index;
|
|
113
|
-
}}
|
|
114
|
-
/>
|
|
127
|
+
controlsPosition === TableControlsPositionValue.TOP ||
|
|
128
|
+
controlsPosition === TableControlsPositionValue.BOTH
|
|
129
|
+
? controls
|
|
130
|
+
: null
|
|
115
131
|
}
|
|
116
132
|
bottom={
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
onLoadSavedQuery={async (id) => {
|
|
122
|
-
await props.model.loadSavedQuery(id);
|
|
123
|
-
}}
|
|
124
|
-
goToPage={(index) => {
|
|
125
|
-
props.model.current_page = index;
|
|
126
|
-
}}
|
|
127
|
-
/>
|
|
133
|
+
controlsPosition === TableControlsPositionValue.BOTTOM ||
|
|
134
|
+
controlsPosition === TableControlsPositionValue.BOTH
|
|
135
|
+
? controls
|
|
136
|
+
: null
|
|
128
137
|
}
|
|
129
138
|
>
|
|
130
139
|
<PageResultsWidget
|
|
131
140
|
query={props.model.query}
|
|
132
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
|
+
}}
|
|
133
149
|
scrollTop={props.model.table_scroll_top}
|
|
134
150
|
scrollLeft={props.model.table_scroll_left}
|
|
135
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
|
+
};
|
|
@@ -1,9 +1,49 @@
|
|
|
1
|
-
import { BooleanSetting, PrefsStore } from '@journeyapps-labs/reactor-mod';
|
|
1
|
+
import { BooleanSetting, PrefsStore, SetSetting, ioc } from '@journeyapps-labs/reactor-mod';
|
|
2
2
|
|
|
3
3
|
export enum QueryControlPreferences {
|
|
4
4
|
SHOW_SORT_CONTROLS = 'databrowser/query-controls/show-sort-controls',
|
|
5
5
|
SHOW_FILTER_CONTROLS = 'databrowser/query-controls/show-filter-controls',
|
|
6
|
-
FILTER_NULL_FIELDS_IN_RELATIONSHIP_PEEK = 'databrowser/query-controls/filter-null-fields-in-relationship-peek'
|
|
6
|
+
FILTER_NULL_FIELDS_IN_RELATIONSHIP_PEEK = 'databrowser/query-controls/filter-null-fields-in-relationship-peek',
|
|
7
|
+
SHOW_ID_VALUE_IN_ID_COLUMN = 'databrowser/query-controls/show-id-value-in-id-column',
|
|
8
|
+
TABLE_CONTROLS_POSITION = 'databrowser/query-controls/table-controls-position'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export enum TableControlsPositionValue {
|
|
12
|
+
TOP = 'top',
|
|
13
|
+
BOTTOM = 'bottom',
|
|
14
|
+
BOTH = 'both'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class TableControlsPositionPreference extends SetSetting {
|
|
18
|
+
static KEY = QueryControlPreferences.TABLE_CONTROLS_POSITION;
|
|
19
|
+
|
|
20
|
+
constructor() {
|
|
21
|
+
super({
|
|
22
|
+
key: TableControlsPositionPreference.KEY,
|
|
23
|
+
name: 'Table controls position',
|
|
24
|
+
category: 'Query Controls',
|
|
25
|
+
value: TableControlsPositionValue.BOTH,
|
|
26
|
+
options: [
|
|
27
|
+
{
|
|
28
|
+
key: TableControlsPositionValue.TOP,
|
|
29
|
+
label: 'Top'
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
key: TableControlsPositionValue.BOTTOM,
|
|
33
|
+
label: 'Bottom'
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
key: TableControlsPositionValue.BOTH,
|
|
37
|
+
label: 'Both'
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
static getValue(): TableControlsPositionValue {
|
|
44
|
+
return ioc.get(PrefsStore).getPreference<TableControlsPositionPreference>(TableControlsPositionPreference.KEY)
|
|
45
|
+
.value as TableControlsPositionValue;
|
|
46
|
+
}
|
|
7
47
|
}
|
|
8
48
|
|
|
9
49
|
export const registerQueryControlPreferences = (prefsStore: PrefsStore) => {
|
|
@@ -31,4 +71,13 @@ export const registerQueryControlPreferences = (prefsStore: PrefsStore) => {
|
|
|
31
71
|
category: 'Query Controls'
|
|
32
72
|
})
|
|
33
73
|
);
|
|
74
|
+
prefsStore.registerPreference(
|
|
75
|
+
new BooleanSetting({
|
|
76
|
+
key: QueryControlPreferences.SHOW_ID_VALUE_IN_ID_COLUMN,
|
|
77
|
+
checked: true,
|
|
78
|
+
name: 'Show ID value in ID column',
|
|
79
|
+
category: 'Query Controls'
|
|
80
|
+
})
|
|
81
|
+
);
|
|
82
|
+
prefsStore.registerPreference(new TableControlsPositionPreference());
|
|
34
83
|
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { ioc, PrefsStore, SetSetting } from '@journeyapps-labs/reactor-mod';
|
|
2
|
+
|
|
3
|
+
export enum SchemaModelOrderValue {
|
|
4
|
+
ALPHABETICAL = 'alphabetical',
|
|
5
|
+
AS_DEFINED_IN_SCHEMA = 'as_defined_in_schema'
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export class SchemaModelOrderingPreference extends SetSetting {
|
|
9
|
+
static KEY = 'databrowser/schema/model-order';
|
|
10
|
+
|
|
11
|
+
constructor() {
|
|
12
|
+
super({
|
|
13
|
+
key: SchemaModelOrderingPreference.KEY,
|
|
14
|
+
name: 'Schema model order',
|
|
15
|
+
category: 'Schema',
|
|
16
|
+
value: SchemaModelOrderValue.AS_DEFINED_IN_SCHEMA,
|
|
17
|
+
options: [
|
|
18
|
+
{
|
|
19
|
+
key: SchemaModelOrderValue.AS_DEFINED_IN_SCHEMA,
|
|
20
|
+
label: 'As defined in schema'
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
key: SchemaModelOrderValue.ALPHABETICAL,
|
|
24
|
+
label: 'Alphabetical'
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static getValue(): SchemaModelOrderValue {
|
|
31
|
+
return ioc.get(PrefsStore).getPreference<SchemaModelOrderingPreference>(SchemaModelOrderingPreference.KEY)
|
|
32
|
+
.value as SchemaModelOrderValue;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export enum SchemaFieldOrderValue {
|
|
37
|
+
ALPHABETICAL = 'alphabetical',
|
|
38
|
+
BELONGS_TO_FIRST = 'belongs_to_first',
|
|
39
|
+
BELONGS_TO_LAST = 'belongs_to_last',
|
|
40
|
+
AS_DEFINED_IN_SCHEMA = 'as_defined_in_schema'
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export class SchemaFieldOrderingPreference extends SetSetting {
|
|
44
|
+
static KEY = 'databrowser/schema/field-order';
|
|
45
|
+
|
|
46
|
+
constructor() {
|
|
47
|
+
super({
|
|
48
|
+
key: SchemaFieldOrderingPreference.KEY,
|
|
49
|
+
name: 'Schema field order',
|
|
50
|
+
category: 'Schema',
|
|
51
|
+
value: SchemaFieldOrderValue.AS_DEFINED_IN_SCHEMA,
|
|
52
|
+
options: [
|
|
53
|
+
{
|
|
54
|
+
key: SchemaFieldOrderValue.AS_DEFINED_IN_SCHEMA,
|
|
55
|
+
label: 'As defined in schema'
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
key: SchemaFieldOrderValue.ALPHABETICAL,
|
|
59
|
+
label: 'Alphabetical'
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
key: SchemaFieldOrderValue.BELONGS_TO_FIRST,
|
|
63
|
+
label: 'Belongs-to first'
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
key: SchemaFieldOrderValue.BELONGS_TO_LAST,
|
|
67
|
+
label: 'Belongs-to last'
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
static getValue(): SchemaFieldOrderValue {
|
|
74
|
+
return ioc.get(PrefsStore).getPreference<SchemaFieldOrderingPreference>(SchemaFieldOrderingPreference.KEY)
|
|
75
|
+
.value as SchemaFieldOrderValue;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export const registerSchemaOrderingPreferences = (prefsStore: PrefsStore) => {
|
|
80
|
+
prefsStore.registerPreference(new SchemaModelOrderingPreference());
|
|
81
|
+
prefsStore.registerPreference(new SchemaFieldOrderingPreference());
|
|
82
|
+
};
|