@xh/hoist 73.0.0-SNAPSHOT.1738098319236 → 73.0.0-SNAPSHOT.1738169109530
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/admin/jsonsearch/JsonSearchPanel.ts +300 -0
- package/admin/jsonsearch/impl/JsonSearchPanelImplModel.ts +161 -0
- package/admin/tabs/general/config/ConfigPanel.ts +30 -4
- package/admin/tabs/userData/jsonblob/JsonBlobPanel.ts +47 -10
- package/admin/tabs/userData/prefs/UserPreferencePanel.ts +45 -15
- package/build/types/admin/jsonsearch/JsonSearchPanel.d.ts +18 -0
- package/build/types/admin/jsonsearch/impl/JsonSearchPanelImplModel.d.ts +34 -0
- package/package.json +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file belongs to Hoist, an application development toolkit
|
|
3
|
+
* developed by Extremely Heavy Industries (www.xh.io | info@xh.io)
|
|
4
|
+
*
|
|
5
|
+
* Copyright © 2025 Extremely Heavy Industries Inc.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {startCase} from 'lodash';
|
|
9
|
+
import {toolbar, toolbarSep} from '@xh/hoist/desktop/cmp/toolbar';
|
|
10
|
+
import {errorMessage} from '@xh/hoist/cmp/error';
|
|
11
|
+
import {grid, GridConfig, gridCountLabel} from '@xh/hoist/cmp/grid';
|
|
12
|
+
import {
|
|
13
|
+
a,
|
|
14
|
+
box,
|
|
15
|
+
filler,
|
|
16
|
+
fragment,
|
|
17
|
+
h4,
|
|
18
|
+
hframe,
|
|
19
|
+
label,
|
|
20
|
+
li,
|
|
21
|
+
span,
|
|
22
|
+
ul,
|
|
23
|
+
vbox
|
|
24
|
+
} from '@xh/hoist/cmp/layout';
|
|
25
|
+
import {hoistCmp, HoistProps, SelectOption, useLocalModel} from '@xh/hoist/core';
|
|
26
|
+
import {button} from '@xh/hoist/desktop/cmp/button';
|
|
27
|
+
import {buttonGroupInput, jsonInput, select, textInput} from '@xh/hoist/desktop/cmp/input';
|
|
28
|
+
import {panel} from '@xh/hoist/desktop/cmp/panel';
|
|
29
|
+
import {Icon} from '@xh/hoist/icon';
|
|
30
|
+
import {dialog, popover} from '@xh/hoist/kit/blueprint';
|
|
31
|
+
import {clipboardButton} from '@xh/hoist/desktop/cmp/clipboard';
|
|
32
|
+
import {JsonSearchPanelImplModel} from './impl/JsonSearchPanelImplModel';
|
|
33
|
+
|
|
34
|
+
export interface JsonSearchButtonProps extends HoistProps {
|
|
35
|
+
/** Name of the type of Json Documents being searched. This appears in the dialog title. */
|
|
36
|
+
subjectName: string;
|
|
37
|
+
|
|
38
|
+
/** Url to endpoint for searching for matching JSON documents */
|
|
39
|
+
docSearchUrl: string;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Config for GridModel used to display search results.
|
|
43
|
+
*/
|
|
44
|
+
gridModelConfig: GridConfig;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Names of fields that can be used to group by.
|
|
48
|
+
*/
|
|
49
|
+
groupByOptions: Array<SelectOption | any>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const [JsonSearchButton, jsonSearchButton] = hoistCmp.withFactory<JsonSearchButtonProps>({
|
|
53
|
+
displayName: 'JsonSearchPanel',
|
|
54
|
+
|
|
55
|
+
render() {
|
|
56
|
+
const impl = useLocalModel(JsonSearchPanelImplModel);
|
|
57
|
+
|
|
58
|
+
return fragment(
|
|
59
|
+
button({
|
|
60
|
+
icon: Icon.json(),
|
|
61
|
+
text: 'JSON Search',
|
|
62
|
+
onClick: () => impl.toggleSearchIsOpen()
|
|
63
|
+
}),
|
|
64
|
+
jsonSearchDialog({
|
|
65
|
+
omit: !impl.isOpen,
|
|
66
|
+
model: impl
|
|
67
|
+
})
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const jsonSearchDialog = hoistCmp.factory<JsonSearchPanelImplModel>({
|
|
73
|
+
displayName: 'JsonSearchPanel',
|
|
74
|
+
|
|
75
|
+
render({model}) {
|
|
76
|
+
const {error, subjectName} = model;
|
|
77
|
+
|
|
78
|
+
return dialog({
|
|
79
|
+
title: `${subjectName} Json Search`,
|
|
80
|
+
style: {
|
|
81
|
+
width: '90vw',
|
|
82
|
+
height: '90vh'
|
|
83
|
+
},
|
|
84
|
+
icon: Icon.json(),
|
|
85
|
+
isOpen: true,
|
|
86
|
+
className: 'xh-admin-diff-detail',
|
|
87
|
+
onClose: () => model.toggleSearchIsOpen(),
|
|
88
|
+
item: panel({
|
|
89
|
+
tbar: searchTbar(),
|
|
90
|
+
item: panel({
|
|
91
|
+
mask: model.docLoadTask,
|
|
92
|
+
items: [
|
|
93
|
+
errorMessage({
|
|
94
|
+
error,
|
|
95
|
+
title: error?.name ? startCase(error.name) : undefined
|
|
96
|
+
}),
|
|
97
|
+
hframe({
|
|
98
|
+
omit: error,
|
|
99
|
+
items: [
|
|
100
|
+
panel({
|
|
101
|
+
item: grid({model: model.gridModel}),
|
|
102
|
+
modelConfig: {
|
|
103
|
+
side: 'left',
|
|
104
|
+
defaultSize: '30%',
|
|
105
|
+
collapsible: true,
|
|
106
|
+
defaultCollapsed: false,
|
|
107
|
+
resizable: true
|
|
108
|
+
}
|
|
109
|
+
}),
|
|
110
|
+
panel({
|
|
111
|
+
mask: model.nodeLoadTask,
|
|
112
|
+
tbar: readerTbar(),
|
|
113
|
+
bbar: nodeBbar({
|
|
114
|
+
omit: model.readerContentType !== 'matches',
|
|
115
|
+
model
|
|
116
|
+
}),
|
|
117
|
+
item: jsonInput({
|
|
118
|
+
model,
|
|
119
|
+
bind: 'readerContent',
|
|
120
|
+
flex: 1,
|
|
121
|
+
width: '100%',
|
|
122
|
+
readonly: true,
|
|
123
|
+
showCopyButton: true
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
|
+
]
|
|
127
|
+
})
|
|
128
|
+
]
|
|
129
|
+
})
|
|
130
|
+
})
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const searchTbar = hoistCmp.factory<JsonSearchPanelImplModel>(({model}) => {
|
|
136
|
+
return toolbar(
|
|
137
|
+
pathField({model}),
|
|
138
|
+
helpButton(),
|
|
139
|
+
toolbarSep(),
|
|
140
|
+
span('Group by:'),
|
|
141
|
+
select({
|
|
142
|
+
bind: 'groupBy',
|
|
143
|
+
options: model.groupByOptions,
|
|
144
|
+
width: 160,
|
|
145
|
+
enableFilter: false
|
|
146
|
+
}),
|
|
147
|
+
toolbarSep(),
|
|
148
|
+
gridCountLabel({
|
|
149
|
+
gridModel: model.gridModel,
|
|
150
|
+
unit: 'document'
|
|
151
|
+
})
|
|
152
|
+
);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const pathField = hoistCmp.factory<JsonSearchPanelImplModel>({
|
|
156
|
+
render({model}) {
|
|
157
|
+
return textInput({
|
|
158
|
+
bind: 'path',
|
|
159
|
+
autoFocus: true,
|
|
160
|
+
commitOnChange: true,
|
|
161
|
+
leftIcon: Icon.search(),
|
|
162
|
+
enableClear: true,
|
|
163
|
+
placeholder:
|
|
164
|
+
"JSON Path - e.g. $..[?(@.colId == 'trader')] - type a path and hit ENTER to search",
|
|
165
|
+
width: null,
|
|
166
|
+
flex: 1,
|
|
167
|
+
onKeyDown: e => {
|
|
168
|
+
if (e.key === 'Enter') model.loadJsonDocsAsync();
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const helpButton = hoistCmp.factory({
|
|
175
|
+
model: false,
|
|
176
|
+
render() {
|
|
177
|
+
return popover({
|
|
178
|
+
item: button({
|
|
179
|
+
icon: Icon.questionCircle(),
|
|
180
|
+
outlined: true
|
|
181
|
+
}),
|
|
182
|
+
content: vbox({
|
|
183
|
+
style: {
|
|
184
|
+
padding: '0px 20px 10px 20px'
|
|
185
|
+
},
|
|
186
|
+
items: [
|
|
187
|
+
h4('Sample Queries'),
|
|
188
|
+
ul({
|
|
189
|
+
items: queryExamples.map(({query, explanation}) =>
|
|
190
|
+
li({
|
|
191
|
+
key: query,
|
|
192
|
+
items: [
|
|
193
|
+
span({
|
|
194
|
+
className:
|
|
195
|
+
'xh-border xh-pad-half xh-bg-alt xh-font-family-mono',
|
|
196
|
+
item: query
|
|
197
|
+
}),
|
|
198
|
+
' ',
|
|
199
|
+
clipboardButton({
|
|
200
|
+
text: null,
|
|
201
|
+
icon: Icon.copy(),
|
|
202
|
+
getCopyText: () => query,
|
|
203
|
+
successMessage: 'Query copied to clipboard.'
|
|
204
|
+
}),
|
|
205
|
+
' ',
|
|
206
|
+
explanation
|
|
207
|
+
]
|
|
208
|
+
})
|
|
209
|
+
)
|
|
210
|
+
}),
|
|
211
|
+
a({
|
|
212
|
+
href: 'https://github.com/json-path/JsonPath?tab=readme-ov-file#operators',
|
|
213
|
+
target: '_blank',
|
|
214
|
+
item: 'Path Syntax Docs & More Examples'
|
|
215
|
+
})
|
|
216
|
+
]
|
|
217
|
+
})
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
const readerTbar = hoistCmp.factory<JsonSearchPanelImplModel>(({model}) => {
|
|
223
|
+
return toolbar({
|
|
224
|
+
items: [
|
|
225
|
+
buttonGroupInput({
|
|
226
|
+
model,
|
|
227
|
+
bind: 'readerContentType',
|
|
228
|
+
minimal: true,
|
|
229
|
+
outlined: true,
|
|
230
|
+
disabled: !model.selectedRecord,
|
|
231
|
+
items: [
|
|
232
|
+
button({
|
|
233
|
+
text: 'Document',
|
|
234
|
+
value: 'document'
|
|
235
|
+
}),
|
|
236
|
+
button({
|
|
237
|
+
text: 'Matches',
|
|
238
|
+
value: 'matches'
|
|
239
|
+
})
|
|
240
|
+
]
|
|
241
|
+
}),
|
|
242
|
+
filler(),
|
|
243
|
+
box({
|
|
244
|
+
omit: !model.matchingNodeCount,
|
|
245
|
+
item: `${model.matchingNodeCount} ${model.matchingNodeCount === 1 ? 'match' : 'matches'}`
|
|
246
|
+
})
|
|
247
|
+
]
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
const nodeBbar = hoistCmp.factory<JsonSearchPanelImplModel>(({model}) => {
|
|
252
|
+
return toolbar(
|
|
253
|
+
label('Path Format:'),
|
|
254
|
+
buttonGroupInput({
|
|
255
|
+
model,
|
|
256
|
+
bind: 'pathFormat',
|
|
257
|
+
minimal: true,
|
|
258
|
+
outlined: true,
|
|
259
|
+
disabled: !model.selectedRecord,
|
|
260
|
+
items: [
|
|
261
|
+
button({
|
|
262
|
+
text: 'XPath',
|
|
263
|
+
value: 'XPath'
|
|
264
|
+
}),
|
|
265
|
+
button({
|
|
266
|
+
text: 'JSONPath',
|
|
267
|
+
value: 'JSONPath'
|
|
268
|
+
})
|
|
269
|
+
]
|
|
270
|
+
})
|
|
271
|
+
);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
const queryExamples = [
|
|
275
|
+
{
|
|
276
|
+
query: '$',
|
|
277
|
+
explanation: 'Return the root object'
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
query: '$..*',
|
|
281
|
+
explanation: 'Return all nodes, recursively'
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
query: '$..[?(@.colId && @.width && @.hidden != true)]',
|
|
285
|
+
explanation:
|
|
286
|
+
'Find all nodes with a property "colId" and a property "width" and a property "hidden" not equal to true'
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
query: '$..[?(@.colId && @.width)]',
|
|
290
|
+
explanation: 'Find all nodes with a property "colId" and a property "width"'
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
query: "$..[?(@.colId == 'trader')]",
|
|
294
|
+
explanation: 'Find all nodes with a property "colId" equal to "trader"'
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
query: '$..grid[?(@.version == 1)]',
|
|
298
|
+
explanation: 'Find all grid nodes with a property "version" equal to 1'
|
|
299
|
+
}
|
|
300
|
+
];
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file belongs to Hoist, an application development toolkit
|
|
3
|
+
* developed by Extremely Heavy Industries (www.xh.io | info@xh.io)
|
|
4
|
+
*
|
|
5
|
+
* Copyright © 2025 Extremely Heavy Industries Inc.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {GridModel} from '@xh/hoist/cmp/grid';
|
|
9
|
+
import {GroupingChooserModel} from '@xh/hoist/cmp/grouping';
|
|
10
|
+
import {HoistModel, managed, TaskObserver, XH} from '@xh/hoist/core';
|
|
11
|
+
import {action, bindable, makeObservable, observable} from '@xh/hoist/mobx';
|
|
12
|
+
import {isEmpty, zipWith} from 'lodash';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @internal
|
|
16
|
+
*/
|
|
17
|
+
export class JsonSearchPanelImplModel extends HoistModel {
|
|
18
|
+
override xhImpl = true;
|
|
19
|
+
|
|
20
|
+
private matchingNodesUrl = 'jsonSearch/getMatchingNodes';
|
|
21
|
+
|
|
22
|
+
@managed gridModel: GridModel;
|
|
23
|
+
@managed groupingChooserModel: GroupingChooserModel;
|
|
24
|
+
@managed docLoadTask: TaskObserver = TaskObserver.trackLast();
|
|
25
|
+
@managed nodeLoadTask: TaskObserver = TaskObserver.trackLast();
|
|
26
|
+
|
|
27
|
+
@observable groupBy: string = null;
|
|
28
|
+
@observable isOpen: boolean = false;
|
|
29
|
+
|
|
30
|
+
@bindable.ref error = null;
|
|
31
|
+
@bindable path: string = '';
|
|
32
|
+
@bindable readerContentType: 'document' | 'matches' = 'matches';
|
|
33
|
+
@bindable pathFormat: 'XPath' | 'JSONPath' = 'XPath';
|
|
34
|
+
@bindable readerContent: string = '';
|
|
35
|
+
@bindable matchingNodeCount: number = 0;
|
|
36
|
+
|
|
37
|
+
get subjectName(): string {
|
|
38
|
+
return this.componentProps.subjectName;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
get docSearchUrl(): string {
|
|
42
|
+
return this.componentProps.docSearchUrl;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
get selectedRecord() {
|
|
46
|
+
return this.gridModel.selectedRecord;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
get gridModelConfig() {
|
|
50
|
+
return this.componentProps.gridModelConfig;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
get groupByOptions() {
|
|
54
|
+
return [...this.componentProps.groupByOptions, {value: null, label: 'None'}];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@action
|
|
58
|
+
toggleSearchIsOpen() {
|
|
59
|
+
this.isOpen = !this.isOpen;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
constructor() {
|
|
63
|
+
super();
|
|
64
|
+
makeObservable(this);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
override onLinked() {
|
|
68
|
+
this.gridModel = new GridModel({
|
|
69
|
+
...this.gridModelConfig,
|
|
70
|
+
selModel: 'single'
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
this.addReaction(
|
|
74
|
+
{
|
|
75
|
+
track: () => this.path,
|
|
76
|
+
run: path => {
|
|
77
|
+
if (isEmpty(path)) {
|
|
78
|
+
this.error = null;
|
|
79
|
+
this.gridModel.clear();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
track: () => [this.selectedRecord, this.readerContentType, this.pathFormat],
|
|
85
|
+
run: () => this.loadreaderContentAsync(),
|
|
86
|
+
debounce: 300
|
|
87
|
+
}
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async loadJsonDocsAsync() {
|
|
92
|
+
if (isEmpty(this.path)) {
|
|
93
|
+
this.error = null;
|
|
94
|
+
this.gridModel.clear();
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
const data = await XH.fetchJson({
|
|
100
|
+
url: this.docSearchUrl,
|
|
101
|
+
params: {path: this.path}
|
|
102
|
+
}).linkTo(this.docLoadTask);
|
|
103
|
+
|
|
104
|
+
this.error = null;
|
|
105
|
+
this.gridModel.loadData(data);
|
|
106
|
+
this.gridModel.selectFirstAsync();
|
|
107
|
+
} catch (e) {
|
|
108
|
+
this.gridModel.clear();
|
|
109
|
+
this.error = e;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private async loadreaderContentAsync() {
|
|
114
|
+
if (!this.selectedRecord) {
|
|
115
|
+
this.matchingNodeCount = 0;
|
|
116
|
+
this.readerContent = '';
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const {json} = this.selectedRecord.data;
|
|
121
|
+
|
|
122
|
+
if (this.readerContentType === 'document') {
|
|
123
|
+
this.readerContent = JSON.stringify(JSON.parse(json), null, 2);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
let nodes = await XH.fetchJson({
|
|
128
|
+
url: this.matchingNodesUrl,
|
|
129
|
+
params: {
|
|
130
|
+
path: this.path,
|
|
131
|
+
json
|
|
132
|
+
}
|
|
133
|
+
}).linkTo(this.nodeLoadTask);
|
|
134
|
+
|
|
135
|
+
this.matchingNodeCount = nodes.paths.length;
|
|
136
|
+
nodes = zipWith(nodes.paths, nodes.values, (path: string, value) => {
|
|
137
|
+
return {
|
|
138
|
+
path: this.pathFormat === 'XPath' ? this.convertToXPath(path) : path,
|
|
139
|
+
value
|
|
140
|
+
};
|
|
141
|
+
});
|
|
142
|
+
this.readerContent = JSON.stringify(nodes, null, 2);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private convertToXPath(JSONPath: string): string {
|
|
146
|
+
return JSONPath.replaceAll(/^\$\['?/g, '/')
|
|
147
|
+
.replaceAll(/^\$/g, '')
|
|
148
|
+
.replaceAll(/'?]\['?/g, '/')
|
|
149
|
+
.replaceAll(/'?]$/g, '');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
@action
|
|
153
|
+
private setGroupBy(groupBy: string) {
|
|
154
|
+
this.groupBy = groupBy;
|
|
155
|
+
|
|
156
|
+
// Always select first when regrouping.
|
|
157
|
+
const groupByArr = groupBy ? groupBy.split(',') : [];
|
|
158
|
+
this.gridModel.setGroupBy(groupByArr);
|
|
159
|
+
this.gridModel.preSelectFirstAsync();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -4,10 +4,14 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Copyright © 2025 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
|
+
import * as AdminCol from '@xh/hoist/admin/columns';
|
|
8
|
+
import * as Col from '@xh/hoist/admin/columns/Rest';
|
|
9
|
+
import {jsonSearchButton} from '@xh/hoist/admin/jsonsearch/JsonSearchPanel';
|
|
7
10
|
import {fragment} from '@xh/hoist/cmp/layout';
|
|
8
11
|
import {creates, hoistCmp} from '@xh/hoist/core';
|
|
9
12
|
import {button} from '@xh/hoist/desktop/cmp/button';
|
|
10
13
|
import {restGrid} from '@xh/hoist/desktop/cmp/rest';
|
|
14
|
+
import {toolbarSep} from '@xh/hoist/desktop/cmp/toolbar';
|
|
11
15
|
import {Icon} from '@xh/hoist/icon';
|
|
12
16
|
import {differ} from '../../../differ/Differ';
|
|
13
17
|
import {regroupDialog} from '../../../regroup/RegroupDialog';
|
|
@@ -20,13 +24,35 @@ export const configPanel = hoistCmp.factory({
|
|
|
20
24
|
return fragment(
|
|
21
25
|
restGrid({
|
|
22
26
|
testId: 'config',
|
|
23
|
-
extraToolbarItems: () =>
|
|
24
|
-
|
|
27
|
+
extraToolbarItems: () => [
|
|
28
|
+
button({
|
|
25
29
|
icon: Icon.diff(),
|
|
26
30
|
text: 'Compare w/ Remote',
|
|
27
31
|
onClick: () => model.openDiffer()
|
|
28
|
-
})
|
|
29
|
-
|
|
32
|
+
}),
|
|
33
|
+
toolbarSep(),
|
|
34
|
+
jsonSearchButton({
|
|
35
|
+
subjectName: 'Config',
|
|
36
|
+
docSearchUrl: 'jsonSearch/searchConfigs',
|
|
37
|
+
gridModelConfig: {
|
|
38
|
+
sortBy: ['groupName', 'name', 'owner'],
|
|
39
|
+
columns: [
|
|
40
|
+
{
|
|
41
|
+
field: {name: 'owner', type: 'string'},
|
|
42
|
+
width: 200
|
|
43
|
+
},
|
|
44
|
+
{...AdminCol.groupName},
|
|
45
|
+
{...AdminCol.name},
|
|
46
|
+
{
|
|
47
|
+
field: {name: 'json', type: 'string'},
|
|
48
|
+
hidden: true
|
|
49
|
+
},
|
|
50
|
+
{...Col.lastUpdated}
|
|
51
|
+
]
|
|
52
|
+
},
|
|
53
|
+
groupByOptions: ['owner', 'groupName', 'name']
|
|
54
|
+
})
|
|
55
|
+
]
|
|
30
56
|
}),
|
|
31
57
|
differ({omit: !model.differModel}),
|
|
32
58
|
regroupDialog()
|
|
@@ -5,10 +5,15 @@
|
|
|
5
5
|
* Copyright © 2025 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import
|
|
8
|
+
import * as Col from '@xh/hoist/admin/columns/Rest';
|
|
9
|
+
import * as AdminCol from '@xh/hoist/admin/columns';
|
|
10
|
+
import {hframe} from '@xh/hoist/cmp/layout';
|
|
9
11
|
import {creates, hoistCmp} from '@xh/hoist/core';
|
|
10
12
|
import {button} from '@xh/hoist/desktop/cmp/button';
|
|
13
|
+
import {jsonSearchButton} from '@xh/hoist/admin/jsonsearch/JsonSearchPanel';
|
|
14
|
+
import {panel} from '@xh/hoist/desktop/cmp/panel';
|
|
11
15
|
import {restGrid} from '@xh/hoist/desktop/cmp/rest';
|
|
16
|
+
import {toolbarSep} from '@xh/hoist/desktop/cmp/toolbar';
|
|
12
17
|
import {Icon} from '@xh/hoist/icon';
|
|
13
18
|
import {differ} from '../../../differ/Differ';
|
|
14
19
|
import {JsonBlobModel} from './JsonBlobModel';
|
|
@@ -17,15 +22,47 @@ export const jsonBlobPanel = hoistCmp.factory({
|
|
|
17
22
|
model: creates(JsonBlobModel),
|
|
18
23
|
|
|
19
24
|
render({model}) {
|
|
20
|
-
return
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
return hframe(
|
|
26
|
+
panel({
|
|
27
|
+
item: restGrid({
|
|
28
|
+
extraToolbarItems: () => [
|
|
29
|
+
button({
|
|
30
|
+
icon: Icon.diff(),
|
|
31
|
+
text: 'Compare w/ Remote',
|
|
32
|
+
onClick: () => model.openDiffer()
|
|
33
|
+
}),
|
|
34
|
+
toolbarSep(),
|
|
35
|
+
jsonSearchButton({
|
|
36
|
+
subjectName: 'JSON Blob',
|
|
37
|
+
docSearchUrl: 'jsonSearch/searchBlobs',
|
|
38
|
+
gridModelConfig: {
|
|
39
|
+
sortBy: ['type', 'name', 'owner'],
|
|
40
|
+
columns: [
|
|
41
|
+
{
|
|
42
|
+
field: {name: 'token', type: 'string'},
|
|
43
|
+
hidden: true,
|
|
44
|
+
width: 100
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
field: {name: 'type', type: 'string'},
|
|
48
|
+
width: 200
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
field: {name: 'owner', type: 'string'},
|
|
52
|
+
width: 200
|
|
53
|
+
},
|
|
54
|
+
{...AdminCol.name},
|
|
55
|
+
{
|
|
56
|
+
field: {name: 'json', type: 'string'},
|
|
57
|
+
hidden: true
|
|
58
|
+
},
|
|
59
|
+
{...Col.lastUpdated}
|
|
60
|
+
]
|
|
61
|
+
},
|
|
62
|
+
groupByOptions: ['owner', 'type', 'name']
|
|
63
|
+
})
|
|
64
|
+
]
|
|
65
|
+
})
|
|
29
66
|
}),
|
|
30
67
|
differ({omit: !model.differModel})
|
|
31
68
|
);
|
|
@@ -4,32 +4,62 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Copyright © 2025 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
|
+
|
|
8
|
+
import * as Col from '@xh/hoist/admin/columns/Rest';
|
|
9
|
+
import * as AdminCol from '@xh/hoist/admin/columns';
|
|
7
10
|
import {prefEditorDialog} from '@xh/hoist/admin/tabs/userData/prefs/editor/PrefEditorDialog';
|
|
8
11
|
import {UserPreferenceModel} from '@xh/hoist/admin/tabs/userData/prefs/UserPreferenceModel';
|
|
12
|
+
import {hframe} from '@xh/hoist/cmp/layout';
|
|
9
13
|
import {creates, hoistCmp} from '@xh/hoist/core';
|
|
10
14
|
import {button} from '@xh/hoist/desktop/cmp/button';
|
|
15
|
+
import {jsonSearchButton} from '@xh/hoist/admin/jsonsearch/JsonSearchPanel';
|
|
11
16
|
import {panel} from '@xh/hoist/desktop/cmp/panel';
|
|
12
17
|
import {restGrid} from '@xh/hoist/desktop/cmp/rest';
|
|
18
|
+
import {toolbarSep} from '@xh/hoist/desktop/cmp/toolbar';
|
|
13
19
|
import {Icon} from '@xh/hoist/icon';
|
|
14
20
|
|
|
15
21
|
export const userPreferencePanel = hoistCmp.factory({
|
|
16
22
|
model: creates(UserPreferenceModel),
|
|
17
23
|
|
|
18
24
|
render({model}) {
|
|
19
|
-
return
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
25
|
+
return hframe(
|
|
26
|
+
panel({
|
|
27
|
+
items: [
|
|
28
|
+
restGrid({
|
|
29
|
+
extraToolbarItems: () => [
|
|
30
|
+
button({
|
|
31
|
+
icon: Icon.gear(),
|
|
32
|
+
text: 'Configure',
|
|
33
|
+
onClick: () => (model.showEditorDialog = true)
|
|
34
|
+
}),
|
|
35
|
+
toolbarSep(),
|
|
36
|
+
jsonSearchButton({
|
|
37
|
+
subjectName: 'User Preference',
|
|
38
|
+
docSearchUrl: 'jsonSearch/searchUserPreferences',
|
|
39
|
+
gridModelConfig: {
|
|
40
|
+
sortBy: ['groupName', 'name', 'owner'],
|
|
41
|
+
columns: [
|
|
42
|
+
{
|
|
43
|
+
field: {name: 'owner', type: 'string'},
|
|
44
|
+
width: 200
|
|
45
|
+
},
|
|
46
|
+
{...AdminCol.groupName},
|
|
47
|
+
{...AdminCol.name},
|
|
48
|
+
{
|
|
49
|
+
field: {name: 'json', type: 'string'},
|
|
50
|
+
hidden: true
|
|
51
|
+
},
|
|
52
|
+
{...Col.lastUpdated}
|
|
53
|
+
]
|
|
54
|
+
},
|
|
55
|
+
groupByOptions: ['owner', 'groupName', 'name']
|
|
56
|
+
})
|
|
57
|
+
]
|
|
58
|
+
}),
|
|
59
|
+
prefEditorDialog()
|
|
60
|
+
],
|
|
61
|
+
mask: 'onLoad'
|
|
62
|
+
})
|
|
63
|
+
);
|
|
34
64
|
}
|
|
35
65
|
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import { GridConfig } from '@xh/hoist/cmp/grid';
|
|
3
|
+
import { HoistProps, SelectOption } from '@xh/hoist/core';
|
|
4
|
+
export interface JsonSearchButtonProps extends HoistProps {
|
|
5
|
+
/** Name of the type of Json Documents being searched. This appears in the dialog title. */
|
|
6
|
+
subjectName: string;
|
|
7
|
+
/** Url to endpoint for searching for matching JSON documents */
|
|
8
|
+
docSearchUrl: string;
|
|
9
|
+
/**
|
|
10
|
+
* Config for GridModel used to display search results.
|
|
11
|
+
*/
|
|
12
|
+
gridModelConfig: GridConfig;
|
|
13
|
+
/**
|
|
14
|
+
* Names of fields that can be used to group by.
|
|
15
|
+
*/
|
|
16
|
+
groupByOptions: Array<SelectOption | any>;
|
|
17
|
+
}
|
|
18
|
+
export declare const JsonSearchButton: import("react").FC<JsonSearchButtonProps>, jsonSearchButton: import("@xh/hoist/core").ElementFactory<JsonSearchButtonProps>;
|