@xh/hoist 76.0.0 → 76.1.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 +55 -39
- package/appcontainer/ImpersonationBarModel.ts +8 -3
- package/build/types/appcontainer/ImpersonationBarModel.d.ts +2 -0
- package/build/types/cmp/chart/ChartModel.d.ts +2 -0
- package/build/types/core/model/HoistModel.d.ts +1 -1
- package/build/types/data/cube/Query.d.ts +1 -0
- package/build/types/desktop/cmp/dash/DashViewModel.d.ts +9 -2
- package/build/types/desktop/cmp/dash/container/DashContainerModel.d.ts +1 -0
- package/build/types/desktop/cmp/grid/impl/filter/headerfilter/HeaderFilterModel.d.ts +4 -2
- package/build/types/desktop/cmp/grid/impl/filter/headerfilter/values/ValuesTabModel.d.ts +10 -3
- package/cmp/chart/ChartModel.ts +8 -2
- package/cmp/dataview/DataView.ts +1 -1
- package/core/model/HoistModel.ts +1 -1
- package/data/cube/Query.ts +2 -0
- package/data/cube/View.ts +18 -12
- package/desktop/appcontainer/AppContainer.ts +15 -10
- package/desktop/appcontainer/ImpersonationBar.ts +17 -6
- package/desktop/appcontainer/VersionBar.ts +5 -2
- package/desktop/cmp/dash/DashViewModel.ts +14 -2
- package/desktop/cmp/dash/canvas/impl/DashCanvasView.ts +3 -3
- package/desktop/cmp/dash/container/DashContainerModel.ts +22 -15
- package/desktop/cmp/grid/impl/filter/headerfilter/HeaderFilter.ts +0 -1
- package/desktop/cmp/grid/impl/filter/headerfilter/HeaderFilterModel.ts +12 -4
- package/desktop/cmp/grid/impl/filter/headerfilter/values/ValuesTab.scss +12 -4
- package/desktop/cmp/grid/impl/filter/headerfilter/values/ValuesTab.ts +8 -2
- package/desktop/cmp/grid/impl/filter/headerfilter/values/ValuesTabModel.ts +42 -10
- package/desktop/cmp/viewmanager/dialog/ManageDialog.ts +1 -1
- package/mobile/appcontainer/ImpersonationBar.ts +1 -1
- package/package.json +5 -5
- package/styles/vars.scss +2 -2
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -338,7 +338,7 @@ export class DashContainerModel
|
|
|
338
338
|
renameView(id: string) {
|
|
339
339
|
const view = this.getItemByViewModel(id);
|
|
340
340
|
if (!view) return;
|
|
341
|
-
this.showTitleForm(view.tab.element);
|
|
341
|
+
this.showTitleForm(view.tab.element, this.getViewModel(id));
|
|
342
342
|
}
|
|
343
343
|
|
|
344
344
|
onResize() {
|
|
@@ -528,10 +528,10 @@ export class DashContainerModel
|
|
|
528
528
|
|
|
529
529
|
const $el = item.tab.element, // Note: this is a jquery element
|
|
530
530
|
stack = item.parent,
|
|
531
|
-
$titleEl = $el
|
|
531
|
+
$titleEl = this.getTitleElement($el),
|
|
532
532
|
iconSelector = 'svg.svg-inline--fa',
|
|
533
533
|
viewSpec = this.getViewSpec(item.config.component),
|
|
534
|
-
{icon
|
|
534
|
+
{icon} = viewModel;
|
|
535
535
|
|
|
536
536
|
$el.off('contextmenu').contextmenu(e => {
|
|
537
537
|
const index = stack.contentItems.indexOf(item);
|
|
@@ -551,14 +551,9 @@ export class DashContainerModel
|
|
|
551
551
|
}
|
|
552
552
|
}
|
|
553
553
|
|
|
554
|
-
if (title) {
|
|
555
|
-
const currentTitle = $titleEl.text();
|
|
556
|
-
if (currentTitle !== title) $titleEl.text(title);
|
|
557
|
-
}
|
|
558
|
-
|
|
559
554
|
if (viewSpec.allowRename) {
|
|
560
555
|
this.insertTitleForm($el, viewModel);
|
|
561
|
-
$titleEl.off('dblclick').dblclick(() => this.showTitleForm($el));
|
|
556
|
+
$titleEl.off('dblclick').dblclick(() => this.showTitleForm($el, viewModel));
|
|
562
557
|
}
|
|
563
558
|
});
|
|
564
559
|
}
|
|
@@ -568,7 +563,7 @@ export class DashContainerModel
|
|
|
568
563
|
if ($el.find(formSelector).length) return;
|
|
569
564
|
|
|
570
565
|
// Create and insert form
|
|
571
|
-
const $titleEl = $el
|
|
566
|
+
const $titleEl = this.getTitleElement($el);
|
|
572
567
|
$titleEl.after(`<form class="title-form"><input type="text"/></form>`);
|
|
573
568
|
|
|
574
569
|
// Attach listeners
|
|
@@ -579,7 +574,6 @@ export class DashContainerModel
|
|
|
579
574
|
$formEl.submit(() => {
|
|
580
575
|
const title = $inputEl.val();
|
|
581
576
|
if (title.length) {
|
|
582
|
-
$titleEl.text(title);
|
|
583
577
|
viewModel.title = title;
|
|
584
578
|
}
|
|
585
579
|
|
|
@@ -588,12 +582,11 @@ export class DashContainerModel
|
|
|
588
582
|
});
|
|
589
583
|
}
|
|
590
584
|
|
|
591
|
-
private showTitleForm($tabEl) {
|
|
585
|
+
private showTitleForm($tabEl, viewModel: DashViewModel) {
|
|
592
586
|
if (this.renameLocked) return;
|
|
593
587
|
|
|
594
|
-
const $
|
|
595
|
-
|
|
596
|
-
currentTitle = $titleEl.text();
|
|
588
|
+
const $inputEl = $tabEl.find('.title-form input').first(),
|
|
589
|
+
currentTitle = viewModel.title;
|
|
597
590
|
|
|
598
591
|
$tabEl.addClass('show-title-form');
|
|
599
592
|
$inputEl.val(currentTitle);
|
|
@@ -647,6 +640,16 @@ export class DashContainerModel
|
|
|
647
640
|
containerModel: this
|
|
648
641
|
});
|
|
649
642
|
|
|
643
|
+
model.addReaction({
|
|
644
|
+
track: () => model.fullTitle,
|
|
645
|
+
run: () => {
|
|
646
|
+
const item = this.getItemByViewModel(id),
|
|
647
|
+
$titleEl = this.getTitleElement(item.tab.element);
|
|
648
|
+
|
|
649
|
+
$titleEl.text(model.fullTitle);
|
|
650
|
+
}
|
|
651
|
+
});
|
|
652
|
+
|
|
650
653
|
this.addViewModel(model);
|
|
651
654
|
return modelLookupContextProvider({
|
|
652
655
|
value: this.modelLookupContext,
|
|
@@ -662,6 +665,10 @@ export class DashContainerModel
|
|
|
662
665
|
return ret;
|
|
663
666
|
}
|
|
664
667
|
|
|
668
|
+
private getTitleElement($el) {
|
|
669
|
+
return $el.find('.lm_title').first();
|
|
670
|
+
}
|
|
671
|
+
|
|
665
672
|
@action
|
|
666
673
|
private destroyGoldenLayout() {
|
|
667
674
|
XH.safeDestroy(this.goldenLayout);
|
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
* Copyright © 2025 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import {Column, GridFilterFieldSpec, GridFilterModel, GridModel} from '@xh/hoist/cmp/grid';
|
|
8
9
|
import {TabContainerModel} from '@xh/hoist/cmp/tab';
|
|
9
|
-
import {HoistModel,
|
|
10
|
+
import {HoistModel, lookup, managed} from '@xh/hoist/core';
|
|
10
11
|
import {
|
|
11
12
|
CompoundFilter,
|
|
12
13
|
FieldFilter,
|
|
@@ -19,12 +20,11 @@ import {
|
|
|
19
20
|
import {action, computed} from '@xh/hoist/mobx';
|
|
20
21
|
import {wait} from '@xh/hoist/promise';
|
|
21
22
|
import {isEmpty} from 'lodash';
|
|
22
|
-
import {
|
|
23
|
+
import {ColumnHeaderFilterModel} from '../ColumnHeaderFilterModel';
|
|
23
24
|
import {customTab} from './custom/CustomTab';
|
|
24
25
|
import {CustomTabModel} from './custom/CustomTabModel';
|
|
25
26
|
import {valuesTab} from './values/ValuesTab';
|
|
26
27
|
import {ValuesTabModel} from './values/ValuesTabModel';
|
|
27
|
-
import {ColumnHeaderFilterModel} from '../ColumnHeaderFilterModel';
|
|
28
28
|
|
|
29
29
|
export class HeaderFilterModel extends HoistModel {
|
|
30
30
|
override xhImpl = true;
|
|
@@ -46,8 +46,16 @@ export class HeaderFilterModel extends HoistModel {
|
|
|
46
46
|
return this.fieldSpec.field;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
get gridModel(): GridModel {
|
|
50
|
+
return this.filterModel.gridModel;
|
|
51
|
+
}
|
|
52
|
+
|
|
49
53
|
get store(): Store {
|
|
50
|
-
return this.
|
|
54
|
+
return this.gridModel.store;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
get column(): Column {
|
|
58
|
+
return this.parent.column;
|
|
51
59
|
}
|
|
52
60
|
|
|
53
61
|
get fieldType(): FieldType {
|
|
@@ -1,24 +1,32 @@
|
|
|
1
1
|
.xh-values-filter-tab {
|
|
2
|
-
|
|
3
|
-
padding: 5px 7px;
|
|
2
|
+
&__filter-controls {
|
|
4
3
|
border-bottom: 1px solid var(--xh-grid-header-border-color);
|
|
4
|
+
padding: 5px 7px;
|
|
5
5
|
row-gap: 5px;
|
|
6
|
+
|
|
6
7
|
.bp5-control-indicator {
|
|
7
8
|
font-size: 1em;
|
|
8
9
|
}
|
|
10
|
+
|
|
9
11
|
label {
|
|
10
12
|
font-size: var(--xh-grid-compact-header-font-size-px);
|
|
11
13
|
color: var(--xh-grid-header-text-color);
|
|
12
14
|
cursor: pointer;
|
|
13
15
|
}
|
|
16
|
+
|
|
17
|
+
&__sort-icon {
|
|
18
|
+
border-left: var(--xh-menu-border);
|
|
19
|
+
padding-left: var(--xh-pad-half-px);
|
|
20
|
+
color: var(--xh-grid-header-text-color);
|
|
21
|
+
}
|
|
14
22
|
}
|
|
15
23
|
|
|
16
24
|
&__hidden-values-message {
|
|
17
|
-
display: flex;
|
|
18
|
-
padding: var(--xh-pad-half-px);
|
|
19
25
|
background-color: var(--xh-bg-alt);
|
|
20
26
|
border-top: var(--xh-border-solid);
|
|
21
27
|
color: var(--xh-text-color-muted);
|
|
28
|
+
display: flex;
|
|
29
|
+
padding: var(--xh-pad-half-px);
|
|
22
30
|
|
|
23
31
|
.xh-icon {
|
|
24
32
|
margin-right: var(--xh-pad-half-px);
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import {isEmpty} from 'lodash';
|
|
8
8
|
import {grid} from '@xh/hoist/cmp/grid';
|
|
9
|
-
import {div, hframe, placeholder, label, vbox, vframe} from '@xh/hoist/cmp/layout';
|
|
9
|
+
import {div, hframe, placeholder, label, vbox, vframe, filler} from '@xh/hoist/cmp/layout';
|
|
10
10
|
import {storeFilterField} from '@xh/hoist/cmp/store';
|
|
11
11
|
import {XH, hoistCmp, uses} from '@xh/hoist/core';
|
|
12
12
|
import {button} from '@xh/hoist/desktop/cmp/button';
|
|
@@ -60,7 +60,7 @@ const storeFilterSelect = hoistCmp.factory<ValuesTabModel>(({model}) => {
|
|
|
60
60
|
addToFilterId = XH.genId();
|
|
61
61
|
|
|
62
62
|
return vbox({
|
|
63
|
-
className: '
|
|
63
|
+
className: 'xh-values-filter-tab__filter-controls',
|
|
64
64
|
items: [
|
|
65
65
|
hframe(
|
|
66
66
|
checkbox({
|
|
@@ -73,6 +73,12 @@ const storeFilterSelect = hoistCmp.factory<ValuesTabModel>(({model}) => {
|
|
|
73
73
|
label({
|
|
74
74
|
htmlFor: selectAllId,
|
|
75
75
|
item: `(Select All${filterText ? ' Search Results' : ''})`
|
|
76
|
+
}),
|
|
77
|
+
filler(),
|
|
78
|
+
div({
|
|
79
|
+
className: 'xh-values-filter-tab__filter-controls__sort-icon',
|
|
80
|
+
item: model.sortIcon,
|
|
81
|
+
onClick: () => model.toggleSort()
|
|
76
82
|
})
|
|
77
83
|
),
|
|
78
84
|
hframe({
|
|
@@ -7,26 +7,27 @@
|
|
|
7
7
|
import {GridFilterModel, GridModel} from '@xh/hoist/cmp/grid';
|
|
8
8
|
import {HoistModel, managed} from '@xh/hoist/core';
|
|
9
9
|
import {FieldFilterSpec} from '@xh/hoist/data';
|
|
10
|
-
import {HeaderFilterModel} from '../HeaderFilterModel';
|
|
11
10
|
import {checkbox} from '@xh/hoist/desktop/cmp/input';
|
|
11
|
+
import {Icon} from '@xh/hoist/icon';
|
|
12
12
|
import {action, bindable, computed, makeObservable, observable} from '@xh/hoist/mobx';
|
|
13
13
|
import {castArray, difference, flatten, isEmpty, map, partition, uniq, without} from 'lodash';
|
|
14
|
+
import {HeaderFilterModel} from '../HeaderFilterModel';
|
|
14
15
|
|
|
15
16
|
export class ValuesTabModel extends HoistModel {
|
|
16
17
|
override xhImpl = true;
|
|
17
18
|
|
|
18
19
|
headerFilterModel: HeaderFilterModel;
|
|
19
20
|
|
|
20
|
-
/** Checkbox grid to display enumerated set of values */
|
|
21
|
-
@managed
|
|
21
|
+
/** Checkbox grid to display enumerated set of values. */
|
|
22
|
+
@managed gridModel: GridModel;
|
|
22
23
|
|
|
23
|
-
/** List of currently checked values
|
|
24
|
+
/** List of currently checked values. */
|
|
24
25
|
@observable.ref pendingValues: any[] = [];
|
|
25
26
|
|
|
26
|
-
/** Bound search term for `StoreFilterField
|
|
27
|
+
/** Bound search term for `StoreFilterField`. */
|
|
27
28
|
@bindable filterText: string = null;
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
/**
|
|
30
31
|
* Merge current filter with pendingValues on commit.
|
|
31
32
|
* Used when commitOnChange is false.
|
|
32
33
|
*/
|
|
@@ -80,12 +81,26 @@ export class ValuesTabModel extends HoistModel {
|
|
|
80
81
|
return this.values.length < this.valueCount;
|
|
81
82
|
}
|
|
82
83
|
|
|
84
|
+
get sortIcon() {
|
|
85
|
+
const {sort, abs} = this.gridModel.sortBy[0];
|
|
86
|
+
if (sort === 'asc') {
|
|
87
|
+
if (abs) return Icon.sortAbsAsc();
|
|
88
|
+
return Icon.sortAsc();
|
|
89
|
+
}
|
|
90
|
+
if (sort === 'desc') {
|
|
91
|
+
if (abs) return Icon.sortAbsDesc();
|
|
92
|
+
return Icon.sortDesc();
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
83
97
|
constructor(headerFilterModel: HeaderFilterModel) {
|
|
84
98
|
super();
|
|
85
99
|
makeObservable(this);
|
|
86
100
|
|
|
87
101
|
this.headerFilterModel = headerFilterModel;
|
|
88
102
|
this.gridModel = this.createGridModel();
|
|
103
|
+
this.initGridSortBy();
|
|
89
104
|
|
|
90
105
|
this.addReaction(
|
|
91
106
|
{
|
|
@@ -125,6 +140,13 @@ export class ValuesTabModel extends HoistModel {
|
|
|
125
140
|
: without(this.pendingValues, ...values);
|
|
126
141
|
}
|
|
127
142
|
|
|
143
|
+
@action
|
|
144
|
+
toggleSort() {
|
|
145
|
+
const {colId, sort, abs} = this.gridModel.sortBy.find(it => it.colId === 'value'),
|
|
146
|
+
newSort = sort === 'asc' ? 'desc' : 'asc';
|
|
147
|
+
this.gridModel.setSortBy({colId, sort: newSort, abs});
|
|
148
|
+
}
|
|
149
|
+
|
|
128
150
|
toggleAllRecsChecked() {
|
|
129
151
|
const setAllToChecked = !this.allVisibleRecsChecked,
|
|
130
152
|
values = this.gridModel.store.records.map(it => it.get('value'));
|
|
@@ -244,13 +266,22 @@ export class ValuesTabModel extends HoistModel {
|
|
|
244
266
|
this.gridModel.loadData(data);
|
|
245
267
|
}
|
|
246
268
|
|
|
269
|
+
private initGridSortBy() {
|
|
270
|
+
const {gridModel: srcGridModel, column} = this.headerFilterModel,
|
|
271
|
+
srcColGridSorter = srcGridModel.sortBy.find(it => it.colId === column.colId);
|
|
272
|
+
|
|
273
|
+
this.gridModel.setSortBy({
|
|
274
|
+
colId: 'value',
|
|
275
|
+
sort: srcColGridSorter?.sort ?? 'asc',
|
|
276
|
+
abs: srcColGridSorter?.abs ?? false
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
247
280
|
private createGridModel() {
|
|
248
281
|
const {BLANK_PLACEHOLDER} = GridFilterModel,
|
|
249
282
|
{headerFilterModel, fieldSpec} = this,
|
|
250
|
-
{fieldType} = headerFilterModel,
|
|
251
|
-
renderer =
|
|
252
|
-
fieldSpec.renderer ??
|
|
253
|
-
(fieldType !== 'tags' ? this.headerFilterModel.parent.column.renderer : null);
|
|
283
|
+
{fieldType, column} = headerFilterModel,
|
|
284
|
+
renderer = fieldSpec.renderer ?? (fieldType !== 'tags' ? column.renderer : null);
|
|
254
285
|
|
|
255
286
|
return new GridModel({
|
|
256
287
|
store: {
|
|
@@ -301,6 +332,7 @@ export class ValuesTabModel extends HoistModel {
|
|
|
301
332
|
{
|
|
302
333
|
field: 'value',
|
|
303
334
|
align: 'left',
|
|
335
|
+
tooltip: true,
|
|
304
336
|
comparator: (v1, v2, sortDir, abs, {defaultComparator}) => {
|
|
305
337
|
const mul = sortDir === 'desc' ? -1 : 1;
|
|
306
338
|
if (v1 === BLANK_PLACEHOLDER) return 1 * mul;
|
|
@@ -36,7 +36,7 @@ export const impersonationBar = hoistCmp.factory({
|
|
|
36
36
|
enableFilter: true,
|
|
37
37
|
enableFullscreen: true,
|
|
38
38
|
placeholder: 'Select a user to impersonate...',
|
|
39
|
-
createMessageFn: q => `Impersonate ${q}`,
|
|
39
|
+
createMessageFn: q => `Impersonate new user "${q}"`,
|
|
40
40
|
onCommit: model.onCommit
|
|
41
41
|
}),
|
|
42
42
|
button({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xh/hoist",
|
|
3
|
-
"version": "76.
|
|
3
|
+
"version": "76.1.0",
|
|
4
4
|
"description": "Hoist add-on for building and deploying React Applications.",
|
|
5
5
|
"repository": "github:xh/hoist-react",
|
|
6
6
|
"homepage": "https://xh.io",
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
]
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@auth0/auth0-spa-js": "~2.
|
|
32
|
-
"@azure/msal-browser": "~4.
|
|
31
|
+
"@auth0/auth0-spa-js": "~2.7.0",
|
|
32
|
+
"@azure/msal-browser": "~4.25.0",
|
|
33
33
|
"@blueprintjs/core": "^5.10.5",
|
|
34
34
|
"@blueprintjs/datetime": "^5.3.7",
|
|
35
35
|
"@blueprintjs/datetime2": "^2.3.7",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"codemirror": "~5.65.0",
|
|
49
49
|
"core-js": "3.x",
|
|
50
50
|
"debounce-promise": "~3.1.0",
|
|
51
|
-
"dompurify": "~3.
|
|
51
|
+
"dompurify": "~3.3.0",
|
|
52
52
|
"downloadjs": "~1.4.7",
|
|
53
53
|
"fast-deep-equal": "~3.1.1",
|
|
54
54
|
"filesize": "~11.0.2",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"jwt-decode": "~4.0.0",
|
|
59
59
|
"lodash": "~4.17.21",
|
|
60
60
|
"lodash-inflection": "~1.5.0",
|
|
61
|
-
"mobx": "~6.
|
|
61
|
+
"mobx": "~6.15.0",
|
|
62
62
|
"mobx-react-lite": "~4.1.0",
|
|
63
63
|
"moment": "~2.30.1",
|
|
64
64
|
"numbro": "~2.5.0",
|
package/styles/vars.scss
CHANGED
|
@@ -522,8 +522,8 @@ body {
|
|
|
522
522
|
--xh-zone-grid-label-color: var(--zone-grid-label-color, inherit);
|
|
523
523
|
|
|
524
524
|
// Grid column-header-based filter popover (desktop only)
|
|
525
|
-
--xh-grid-filter-popover-height-px: var(--grid-filter-popover-height-px,
|
|
526
|
-
--xh-grid-filter-popover-width-px: var(--grid-filter-popover-width-px,
|
|
525
|
+
--xh-grid-filter-popover-height-px: var(--grid-filter-popover-height-px, 400px);
|
|
526
|
+
--xh-grid-filter-popover-width-px: var(--grid-filter-popover-width-px, 280px);
|
|
527
527
|
|
|
528
528
|
// Dark Grid
|
|
529
529
|
&.xh-dark {
|