@xh/hoist 80.0.0-SNAPSHOT.1767647247785 → 80.0.0-SNAPSHOT.1767973504648
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 +24 -4
- package/admin/tabs/cluster/instances/services/DetailsPanel.ts +3 -3
- package/admin/tabs/cluster/instances/services/ServiceModel.ts +2 -2
- package/appcontainer/AppContainerModel.ts +5 -5
- package/appcontainer/AppStateModel.ts +1 -1
- package/appcontainer/FeedbackDialogModel.ts +3 -5
- package/build/types/appcontainer/AppContainerModel.d.ts +2 -2
- package/build/types/cmp/grid/GridModel.d.ts +2 -2
- package/build/types/cmp/layout/Tags.d.ts +2 -0
- package/build/types/cmp/tab/TabContainerModel.d.ts +1 -1
- package/build/types/core/XH.d.ts +2 -1
- package/build/types/desktop/cmp/button/FieldsetCollapseButton.d.ts +10 -0
- package/build/types/desktop/cmp/dash/canvas/DashCanvasModel.d.ts +46 -6
- package/build/types/desktop/cmp/dash/canvas/widgetwell/DashCanvasWidgetWell.d.ts +17 -0
- package/build/types/desktop/cmp/dash/canvas/widgetwell/DashCanvasWidgetWellModel.d.ts +11 -0
- package/build/types/desktop/cmp/form/CollapsibleFieldset.d.ts +11 -0
- package/cmp/grid/GridModel.ts +2 -2
- package/cmp/layout/Tags.ts +2 -0
- package/cmp/tab/TabContainerModel.ts +1 -1
- package/cmp/viewmanager/ViewManagerModel.ts +2 -2
- package/core/XH.ts +12 -4
- package/desktop/appcontainer/AppContainer.ts +1 -1
- package/desktop/appcontainer/LoginPanel.ts +2 -2
- package/desktop/cmp/button/FieldsetCollapseButton.ts +44 -0
- package/desktop/cmp/dash/canvas/DashCanvas.ts +21 -4
- package/desktop/cmp/dash/canvas/DashCanvasModel.ts +140 -24
- package/desktop/cmp/dash/canvas/widgetwell/DashCanvasWidgetWell.scss +26 -0
- package/desktop/cmp/dash/canvas/widgetwell/DashCanvasWidgetWell.ts +126 -0
- package/desktop/cmp/dash/canvas/widgetwell/DashCanvasWidgetWellModel.ts +65 -0
- package/desktop/cmp/form/CollapsibleFieldset.scss +14 -0
- package/desktop/cmp/form/CollapsibleFieldset.ts +65 -0
- package/desktop/cmp/viewmanager/ViewManager.ts +2 -2
- package/mobile/appcontainer/AppContainer.ts +1 -1
- package/mobile/appcontainer/LoginPanel.ts +2 -2
- package/package.json +3 -3
- package/tsconfig.tsbuildinfo +1 -1
- package/utils/impl/MenuItems.ts +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
## 80.0.0-SNAPSHOT - unreleased
|
|
4
4
|
|
|
5
|
+
### 💥 Breaking Changes
|
|
6
|
+
|
|
7
|
+
* Completed the refactoring away from `loadModel` to `loadObserver` started in v79:
|
|
8
|
+
* Renamed `XH.appLoadModel` to `XH.appLoadObserver`. The prior getter remains as an alias but is
|
|
9
|
+
deprecated and scheduled for removal in v82.
|
|
10
|
+
* Renamed `AppContainerModel.loadModel` to `loadObserver`. This is primarily an internal model,
|
|
11
|
+
so there is no deprecated alias. Any app usages should swap to `XH.appLoadObserver`.
|
|
12
|
+
* Removed additional references to deprecated `loadModel` within Hoist itself.
|
|
13
|
+
|
|
14
|
+
### 🎁 New Features
|
|
15
|
+
|
|
16
|
+
* DashCanvas:
|
|
17
|
+
* supports dragging and dropping widgets in from an external container.
|
|
18
|
+
* supports new compacting strategy: 'wrap'
|
|
19
|
+
* new elementFactory tags: `fieldset`, `legend`
|
|
20
|
+
|
|
21
|
+
### 📚 Libraries
|
|
22
|
+
|
|
23
|
+
* react-grid-layout `2.1 → 2.2.2`
|
|
24
|
+
|
|
5
25
|
## 79.0.0 - 2026-01-05
|
|
6
26
|
|
|
7
27
|
### 💥 Breaking Changes
|
|
@@ -69,8 +89,8 @@ this release, but is not strictly required.
|
|
|
69
89
|
|
|
70
90
|
### 📚 Libraries
|
|
71
91
|
|
|
72
|
-
* @blueprintjs/core: `5.10
|
|
73
|
-
* @blueprintjs/datetime: `5.3
|
|
92
|
+
* @blueprintjs/core: `5.10 → 6.3`
|
|
93
|
+
* @blueprintjs/datetime: `5.3 → 6.0`
|
|
74
94
|
* react-grid-layout `1.5 → 2.1`
|
|
75
95
|
|
|
76
96
|
## 78.1.4 - 2025-12-05
|
|
@@ -856,8 +876,8 @@ build. That said, we *strongly* recommend taking these same changes into your ap
|
|
|
856
876
|
### 📚 Libraries
|
|
857
877
|
|
|
858
878
|
* @azure/msal-browser `3.17 → 3.23`
|
|
859
|
-
* mobx `6.9
|
|
860
|
-
* mobx-react-lite `3.4
|
|
879
|
+
* mobx `6.9 → 6.13`,
|
|
880
|
+
* mobx-react-lite `3.4 → 4.0`,
|
|
861
881
|
|
|
862
882
|
## 67.0.0 - 2024-09-03
|
|
863
883
|
|
|
@@ -35,9 +35,9 @@ export const detailsPanel = hoistCmp.factory({
|
|
|
35
35
|
|
|
36
36
|
const stats = hoistCmp.factory<DetailsModel>({
|
|
37
37
|
render({model}) {
|
|
38
|
-
const {stats, lastLoadException,
|
|
38
|
+
const {stats, lastLoadException, loadObserver} = model;
|
|
39
39
|
|
|
40
|
-
if (!
|
|
40
|
+
if (!loadObserver.isPending && lastLoadException) {
|
|
41
41
|
return errorMessage({
|
|
42
42
|
error: lastLoadException,
|
|
43
43
|
detailsFn: e => XH.exceptionHandler.showExceptionDetails(e)
|
|
@@ -46,7 +46,7 @@ const stats = hoistCmp.factory<DetailsModel>({
|
|
|
46
46
|
|
|
47
47
|
return isEmpty(stats)
|
|
48
48
|
? placeholder(
|
|
49
|
-
...(
|
|
49
|
+
...(loadObserver.isPending
|
|
50
50
|
? []
|
|
51
51
|
: [Icon.questionCircle(), 'This service does not report any admin stats.'])
|
|
52
52
|
)
|
|
@@ -91,7 +91,7 @@ export class ServiceModel extends BaseInstanceModel {
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
async clearCachesAsync(entireCluster: boolean) {
|
|
94
|
-
const {gridModel, instanceName,
|
|
94
|
+
const {gridModel, instanceName, loadObserver} = this,
|
|
95
95
|
{selectedRecords} = gridModel;
|
|
96
96
|
|
|
97
97
|
if (isEmpty(selectedRecords)) return;
|
|
@@ -125,7 +125,7 @@ export class ServiceModel extends BaseInstanceModel {
|
|
|
125
125
|
instance: entireCluster ? null : instanceName,
|
|
126
126
|
names: selectedRecords.map(it => it.data.name)
|
|
127
127
|
}
|
|
128
|
-
}).linkTo(
|
|
128
|
+
}).linkTo(loadObserver);
|
|
129
129
|
await this.refreshAsync();
|
|
130
130
|
XH.successToast('Service caches cleared.');
|
|
131
131
|
} catch (e) {
|
|
@@ -76,7 +76,7 @@ export class AppContainerModel extends HoistModel {
|
|
|
76
76
|
//------------
|
|
77
77
|
// Sub-models
|
|
78
78
|
//------------
|
|
79
|
-
@managed
|
|
79
|
+
@managed appLoadObserver = TaskObserver.trackAll();
|
|
80
80
|
@managed appStateModel = new AppStateModel();
|
|
81
81
|
@managed pageStateModel = new PageStateModel();
|
|
82
82
|
@managed routerModel = new RouterModel();
|
|
@@ -250,7 +250,7 @@ export class AppContainerModel extends HoistModel {
|
|
|
250
250
|
|
|
251
251
|
// init all models other than Router
|
|
252
252
|
const models = [
|
|
253
|
-
this.
|
|
253
|
+
this.appLoadObserver,
|
|
254
254
|
this.appStateModel,
|
|
255
255
|
this.pageStateModel,
|
|
256
256
|
this.routerModel,
|
|
@@ -271,7 +271,7 @@ export class AppContainerModel extends HoistModel {
|
|
|
271
271
|
];
|
|
272
272
|
models.forEach((m: any) => m.init?.());
|
|
273
273
|
|
|
274
|
-
this.
|
|
274
|
+
this.bindInitSequenceToAppLoadObserver();
|
|
275
275
|
|
|
276
276
|
this.setDocTitle();
|
|
277
277
|
|
|
@@ -359,10 +359,10 @@ export class AppContainerModel extends HoistModel {
|
|
|
359
359
|
this.appStateModel.setAppState(nextState);
|
|
360
360
|
}
|
|
361
361
|
|
|
362
|
-
private
|
|
362
|
+
private bindInitSequenceToAppLoadObserver() {
|
|
363
363
|
const terminalStates: AppState[] = ['RUNNING', 'SUSPENDED', 'LOAD_FAILED', 'ACCESS_DENIED'],
|
|
364
364
|
loadingPromise = mobxWhen(() => terminalStates.includes(this.appStateModel.state));
|
|
365
|
-
loadingPromise.linkTo(this.
|
|
365
|
+
loadingPromise.linkTo(this.appLoadObserver);
|
|
366
366
|
}
|
|
367
367
|
|
|
368
368
|
private setViewportContent(content: string) {
|
|
@@ -57,7 +57,7 @@ export class AppStateModel extends HoistModel {
|
|
|
57
57
|
this.setAppState('SUSPENDED');
|
|
58
58
|
XH.webSocketService.shutdown();
|
|
59
59
|
Timer.cancelAll();
|
|
60
|
-
XH.appContainerModel.
|
|
60
|
+
XH.appContainerModel.appLoadObserver.clear();
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
checkAccess(): boolean {
|
|
@@ -51,20 +51,18 @@ export class FeedbackDialogModel extends HoistModel {
|
|
|
51
51
|
* Submit the feedback entry to the activity tracking system.
|
|
52
52
|
*/
|
|
53
53
|
async submitAsync() {
|
|
54
|
-
const {message} = this
|
|
55
|
-
{trackService} = XH;
|
|
56
|
-
|
|
54
|
+
const {message} = this;
|
|
57
55
|
if (!message) this.hide();
|
|
58
56
|
|
|
59
57
|
try {
|
|
60
|
-
trackService.track({
|
|
58
|
+
XH.trackService.track({
|
|
61
59
|
category: 'Feedback',
|
|
62
60
|
message: 'User submitted feedback',
|
|
63
61
|
data: {
|
|
64
62
|
userMessage: this.message
|
|
65
63
|
}
|
|
66
64
|
});
|
|
67
|
-
trackService.pushPendingAsync().linkTo(XH.
|
|
65
|
+
await XH.trackService.pushPendingAsync().linkTo(XH.appLoadObserver);
|
|
68
66
|
XH.successToast('Thank you - your feedback has been sent.');
|
|
69
67
|
this.hide();
|
|
70
68
|
} catch (e) {
|
|
@@ -23,7 +23,7 @@ export declare class AppContainerModel extends HoistModel {
|
|
|
23
23
|
private initCalled;
|
|
24
24
|
appSpec: AppSpec;
|
|
25
25
|
appModel: HoistAppModel;
|
|
26
|
-
|
|
26
|
+
appLoadObserver: TaskObserver;
|
|
27
27
|
appStateModel: AppStateModel;
|
|
28
28
|
pageStateModel: PageStateModel;
|
|
29
29
|
routerModel: RouterModel;
|
|
@@ -87,7 +87,7 @@ export declare class AppContainerModel extends HoistModel {
|
|
|
87
87
|
private startRouter;
|
|
88
88
|
private startOptionsDialog;
|
|
89
89
|
private setAppState;
|
|
90
|
-
private
|
|
90
|
+
private bindInitSequenceToAppLoadObserver;
|
|
91
91
|
private setViewportContent;
|
|
92
92
|
private getViewportContent;
|
|
93
93
|
}
|
|
@@ -553,9 +553,9 @@ export declare class GridModel extends HoistModel {
|
|
|
553
553
|
autosizeAsync(overrideOpts?: Omit<GridAutosizeOptions, 'mode'>): Promise<void>;
|
|
554
554
|
/**
|
|
555
555
|
* Begin an inline editing session.
|
|
556
|
-
* @param record - StoreRecord/ID to edit. If unspecified, the first selected StoreRecord
|
|
556
|
+
* @param opts.record - StoreRecord/ID to edit. If unspecified, the first selected StoreRecord
|
|
557
557
|
* will be used, if any, or the first overall StoreRecord in the grid.
|
|
558
|
-
* @param colId - ID of column on which to start editing. If unspecified, the first
|
|
558
|
+
* @param opts.colId - ID of column on which to start editing. If unspecified, the first
|
|
559
559
|
* editable column will be used.
|
|
560
560
|
*/
|
|
561
561
|
beginEditAsync(opts?: {
|
|
@@ -10,6 +10,7 @@ export declare const a: import("@xh/hoist/core").ElementFactory<import("react").
|
|
|
10
10
|
export declare const br: import("@xh/hoist/core").ElementFactory<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLBRElement>, HTMLBRElement>>;
|
|
11
11
|
export declare const code: import("@xh/hoist/core").ElementFactory<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLElement>, HTMLElement>>;
|
|
12
12
|
export declare const div: import("@xh/hoist/core").ElementFactory<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>>;
|
|
13
|
+
export declare const fieldset: import("@xh/hoist/core").ElementFactory<import("react").DetailedHTMLProps<import("react").FieldsetHTMLAttributes<HTMLFieldSetElement>, HTMLFieldSetElement>>;
|
|
13
14
|
export declare const form: import("@xh/hoist/core").ElementFactory<import("react").DetailedHTMLProps<import("react").FormHTMLAttributes<HTMLFormElement>, HTMLFormElement>>;
|
|
14
15
|
export declare const hr: import("@xh/hoist/core").ElementFactory<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLHRElement>, HTMLHRElement>>;
|
|
15
16
|
export declare const h1: import("@xh/hoist/core").ElementFactory<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>>;
|
|
@@ -17,6 +18,7 @@ export declare const h2: import("@xh/hoist/core").ElementFactory<import("react")
|
|
|
17
18
|
export declare const h3: import("@xh/hoist/core").ElementFactory<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>>;
|
|
18
19
|
export declare const h4: import("@xh/hoist/core").ElementFactory<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>>;
|
|
19
20
|
export declare const label: import("@xh/hoist/core").ElementFactory<import("react").DetailedHTMLProps<import("react").LabelHTMLAttributes<HTMLLabelElement>, HTMLLabelElement>>;
|
|
21
|
+
export declare const legend: import("@xh/hoist/core").ElementFactory<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLLegendElement>, HTMLLegendElement>>;
|
|
20
22
|
export declare const li: import("@xh/hoist/core").ElementFactory<import("react").DetailedHTMLProps<import("react").LiHTMLAttributes<HTMLLIElement>, HTMLLIElement>>;
|
|
21
23
|
export declare const nav: import("@xh/hoist/core").ElementFactory<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLElement>, HTMLElement>>;
|
|
22
24
|
export declare const ol: import("@xh/hoist/core").ElementFactory<import("react").DetailedHTMLProps<import("react").OlHTMLAttributes<HTMLOListElement>, HTMLOListElement>>;
|
|
@@ -70,7 +70,7 @@ export declare class TabContainerModel extends HoistModel {
|
|
|
70
70
|
protected lastActiveTabId: string;
|
|
71
71
|
/**
|
|
72
72
|
* @param config - TabContainer configuration.
|
|
73
|
-
* @param
|
|
73
|
+
* @param depth - Depth in hierarchy of nested TabContainerModels. Not for application use.
|
|
74
74
|
*/
|
|
75
75
|
constructor({ tabs, defaultTabId, route, track, renderMode, refreshMode, persistWith, emptyText, xhImpl, switcher }: TabContainerConfig, depth?: number);
|
|
76
76
|
/** Set/replace all tabs within the container. */
|
package/build/types/core/XH.d.ts
CHANGED
|
@@ -73,8 +73,9 @@ export declare class XHApi {
|
|
|
73
73
|
webSocketService: WebSocketService;
|
|
74
74
|
/**
|
|
75
75
|
* Tracks globally loading promises.
|
|
76
|
-
* Apps should link any async operations that should mask the entire viewport to this
|
|
76
|
+
* Apps should link any async operations that should mask the entire viewport to this observer.
|
|
77
77
|
*/
|
|
78
|
+
get appLoadObserver(): TaskObserver;
|
|
78
79
|
get appLoadModel(): TaskObserver;
|
|
79
80
|
/** Root level application model. */
|
|
80
81
|
get appModel(): HoistAppModel;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ReactElement, type ReactNode } from 'react';
|
|
2
|
+
import { HoistProps } from '@xh/hoist/core';
|
|
3
|
+
export interface FieldsetCollapseButtonProps extends HoistProps {
|
|
4
|
+
icon?: ReactElement;
|
|
5
|
+
text: ReactNode;
|
|
6
|
+
clickHandler?: (boolean: any) => void;
|
|
7
|
+
collapsed?: boolean;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare const FieldsetCollapseButton: import("react").FC<FieldsetCollapseButtonProps>, fieldsetCollapseButton: import("@xh/hoist/core").ElementFactory<FieldsetCollapseButtonProps>;
|
|
@@ -14,11 +14,12 @@ export interface DashCanvasConfig extends DashConfig<DashCanvasViewSpec, DashCan
|
|
|
14
14
|
*/
|
|
15
15
|
rowHeight?: number;
|
|
16
16
|
/**
|
|
17
|
-
* Whether views should "compact" vertically or
|
|
17
|
+
* Whether views should "compact" vertically, horizontally or wrap
|
|
18
18
|
* to condense space. Default `true` defaults to vertical compaction.
|
|
19
|
+
* Use `wrap` with caution. It only works well if all items are 1 row high.
|
|
19
20
|
* See react-grid-layout docs for more information.
|
|
20
|
-
|
|
21
|
-
compact?: boolean | 'vertical' | 'horizontal';
|
|
21
|
+
*/
|
|
22
|
+
compact?: boolean | 'vertical' | 'horizontal' | 'wrap';
|
|
22
23
|
/** Between items [x,y] in pixels. Default `[10, 10]`. */
|
|
23
24
|
margin?: [number, number];
|
|
24
25
|
/** Padding inside the container [x, y] in pixels. Defaults to same as `margin`. */
|
|
@@ -29,6 +30,31 @@ export interface DashCanvasConfig extends DashConfig<DashCanvasViewSpec, DashCan
|
|
|
29
30
|
* Whether a grid background should be shown. Default false.
|
|
30
31
|
*/
|
|
31
32
|
showGridBackground?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Whether the canvas should accept drag-and-drop of views from outside
|
|
35
|
+
* the canvas. Default false.
|
|
36
|
+
*/
|
|
37
|
+
allowsDrop?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Optional callback to invoke after a view is successfully dropped onto the canvas.
|
|
40
|
+
*/
|
|
41
|
+
onDropDone?: (viewModel: DashCanvasViewModel) => void;
|
|
42
|
+
/**
|
|
43
|
+
* Optional callback to invoke when an item is dragged over the canvas. This may be used to
|
|
44
|
+
* customize how the size of the dropping placeholder is calculated. The callback should
|
|
45
|
+
* return an object with optional properties indicating the desired width, height (in grid units),
|
|
46
|
+
* and offset (in pixels) of the dropping placeholder. The method's signature is the same as
|
|
47
|
+
* the `onDropDragOver` prop of ReactGridLayout.
|
|
48
|
+
* Returning `false` will prevent the dropping placeholder from being shown, and prevents a drop.
|
|
49
|
+
* Returning `void` will use the default behavior, which is to size the placeholder as per the
|
|
50
|
+
* `dropConfig.defaultItem` specification.
|
|
51
|
+
*/
|
|
52
|
+
onDropDragOver?: (e: DragEvent) => OnDropDragOverResult;
|
|
53
|
+
/**
|
|
54
|
+
* Whether an overlay with an Add View button should be rendered
|
|
55
|
+
* when the canvas is empty. Default true.
|
|
56
|
+
*/
|
|
57
|
+
showAddViewButtonWhenEmpty?: boolean;
|
|
32
58
|
}
|
|
33
59
|
export interface DashCanvasItemState {
|
|
34
60
|
layout: DashCanvasItemLayout;
|
|
@@ -42,6 +68,12 @@ export interface DashCanvasItemLayout {
|
|
|
42
68
|
w: number;
|
|
43
69
|
h: number;
|
|
44
70
|
}
|
|
71
|
+
export type OnDropDragOverResult = {
|
|
72
|
+
w?: number;
|
|
73
|
+
h?: number;
|
|
74
|
+
dragOffsetX?: number;
|
|
75
|
+
dragOffsetY?: number;
|
|
76
|
+
} | false | void;
|
|
45
77
|
/**
|
|
46
78
|
* Model for {@link DashCanvas}, managing all configurable options for the component and publishing
|
|
47
79
|
* the observable state of its current widgets and their layout.
|
|
@@ -51,12 +83,17 @@ export declare class DashCanvasModel extends DashModel<DashCanvasViewSpec, DashC
|
|
|
51
83
|
}> {
|
|
52
84
|
columns: number;
|
|
53
85
|
rowHeight: number;
|
|
54
|
-
compact: 'vertical' | 'horizontal';
|
|
86
|
+
compact: 'vertical' | 'horizontal' | 'wrap';
|
|
55
87
|
margin: [number, number];
|
|
56
88
|
containerPadding: [number, number];
|
|
57
89
|
showGridBackground: boolean;
|
|
58
90
|
rglHeight: number;
|
|
91
|
+
showAddViewButtonWhenEmpty: boolean;
|
|
92
|
+
DROPPING_ELEM_ID: string;
|
|
59
93
|
maxRows: number;
|
|
94
|
+
allowsDrop: boolean;
|
|
95
|
+
onDropDone: (viewModel: DashCanvasViewModel) => void;
|
|
96
|
+
draggedInView: DashCanvasItemState;
|
|
60
97
|
/** Current number of rows in canvas */
|
|
61
98
|
get rows(): number;
|
|
62
99
|
get isEmpty(): boolean;
|
|
@@ -65,7 +102,7 @@ export declare class DashCanvasModel extends DashModel<DashCanvasViewSpec, DashC
|
|
|
65
102
|
isResizing: boolean;
|
|
66
103
|
private isLoadingState;
|
|
67
104
|
get rglLayout(): any[];
|
|
68
|
-
constructor({ viewSpecs, viewSpecDefaults, initialState, layoutLocked, contentLocked, renameLocked, persistWith, emptyText, addViewButtonText, columns, rowHeight, compact, margin, maxRows, containerPadding, extraMenuItems, showGridBackground }: DashCanvasConfig);
|
|
105
|
+
constructor({ viewSpecs, viewSpecDefaults, initialState, layoutLocked, contentLocked, renameLocked, persistWith, emptyText, addViewButtonText, columns, rowHeight, compact, margin, maxRows, containerPadding, extraMenuItems, showGridBackground, showAddViewButtonWhenEmpty, allowsDrop, onDropDone, onDropDragOver }: DashCanvasConfig);
|
|
69
106
|
/** Removes all views from the canvas */
|
|
70
107
|
clear(): void;
|
|
71
108
|
/**
|
|
@@ -103,6 +140,10 @@ export declare class DashCanvasModel extends DashModel<DashCanvasViewSpec, DashC
|
|
|
103
140
|
renameView(id: string): void;
|
|
104
141
|
/** Scrolls a DashCanvasView into view. */
|
|
105
142
|
ensureViewVisible(id: string): void;
|
|
143
|
+
onDrop(rglLayout: LayoutItem[], layoutItem: LayoutItem, evt: Event): void;
|
|
144
|
+
setDraggedInView(view?: DashCanvasItemState): void;
|
|
145
|
+
onDropDragOver(evt: DragEvent): OnDropDragOverResult;
|
|
146
|
+
getViewsBySpecId(id: any): DashCanvasViewModel[];
|
|
106
147
|
getPersistableState(): PersistableState<{
|
|
107
148
|
state: DashCanvasItemState[];
|
|
108
149
|
}>;
|
|
@@ -123,6 +164,5 @@ export declare class DashCanvasModel extends DashModel<DashCanvasViewSpec, DashC
|
|
|
123
164
|
private setViewLayout;
|
|
124
165
|
private getSpec;
|
|
125
166
|
private hasSpec;
|
|
126
|
-
private getViewsBySpecId;
|
|
127
167
|
private getNextAvailablePosition;
|
|
128
168
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { HoistProps } from '@xh/hoist/core';
|
|
2
|
+
import { DashCanvasModel } from '@xh/hoist/desktop/cmp/dash';
|
|
3
|
+
import './DashCanvasWidgetWell.scss';
|
|
4
|
+
export interface DashCanvasWidgetWellProps extends HoistProps {
|
|
5
|
+
/** DashCanvasModel for which this widget well should allow the user to add views from. */
|
|
6
|
+
dashCanvasModel?: DashCanvasModel;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Widget Well from which to add items to a DashCanvas by drag-and-drop.
|
|
10
|
+
*
|
|
11
|
+
* Available view specs are listed in their defined order,
|
|
12
|
+
* grouped by their 'groupName' property if present.
|
|
13
|
+
*
|
|
14
|
+
* Typically, an app developer would place this inside a collapsible panel to the side of
|
|
15
|
+
* a DashCanvas.
|
|
16
|
+
*/
|
|
17
|
+
export declare const DashCanvasWidgetWell: import("react").FC<DashCanvasWidgetWellProps>, dashCanvasWidgetWell: import("@xh/hoist/core").ElementFactory<DashCanvasWidgetWellProps>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { DragEvent } from 'react';
|
|
2
|
+
import { DashCanvasModel } from '@xh/hoist/desktop/cmp/dash';
|
|
3
|
+
import { HoistModel } from '@xh/hoist/core';
|
|
4
|
+
import '@xh/hoist/desktop/register';
|
|
5
|
+
export declare class DashCanvasWidgetWellModel extends HoistModel {
|
|
6
|
+
dashCanvasModel: DashCanvasModel;
|
|
7
|
+
constructor();
|
|
8
|
+
onLinked(): void;
|
|
9
|
+
onDragStart(evt: DragEvent<HTMLDivElement>): void;
|
|
10
|
+
onDragEnd(evt: DragEvent<HTMLDivElement>): void;
|
|
11
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type FieldsetHTMLAttributes, type ReactElement, type ReactNode } from 'react';
|
|
2
|
+
import { HoistProps } from '@xh/hoist/core';
|
|
3
|
+
import './CollapsibleFieldset.scss';
|
|
4
|
+
export interface CollapsibleFieldsetProps extends FieldsetHTMLAttributes<HTMLFieldSetElement>, HoistProps {
|
|
5
|
+
icon?: ReactElement;
|
|
6
|
+
label: ReactNode;
|
|
7
|
+
clickHandler?: () => void;
|
|
8
|
+
collapsed?: boolean;
|
|
9
|
+
hideItemCount?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare const CollapsibleFieldset: import("react").FC<CollapsibleFieldsetProps>, collapsibleFieldset: import("@xh/hoist/core").ElementFactory<CollapsibleFieldsetProps>;
|
package/cmp/grid/GridModel.ts
CHANGED
|
@@ -1442,9 +1442,9 @@ export class GridModel extends HoistModel {
|
|
|
1442
1442
|
|
|
1443
1443
|
/**
|
|
1444
1444
|
* Begin an inline editing session.
|
|
1445
|
-
* @param record - StoreRecord/ID to edit. If unspecified, the first selected StoreRecord
|
|
1445
|
+
* @param opts.record - StoreRecord/ID to edit. If unspecified, the first selected StoreRecord
|
|
1446
1446
|
* will be used, if any, or the first overall StoreRecord in the grid.
|
|
1447
|
-
* @param colId - ID of column on which to start editing. If unspecified, the first
|
|
1447
|
+
* @param opts.colId - ID of column on which to start editing. If unspecified, the first
|
|
1448
1448
|
* editable column will be used.
|
|
1449
1449
|
*/
|
|
1450
1450
|
async beginEditAsync(opts: {record?: StoreRecordOrId; colId?: string} = {}) {
|
package/cmp/layout/Tags.ts
CHANGED
|
@@ -29,6 +29,7 @@ export const a = elementFactory('a');
|
|
|
29
29
|
export const br = elementFactory('br');
|
|
30
30
|
export const code = elementFactory('code');
|
|
31
31
|
export const div = elementFactory('div');
|
|
32
|
+
export const fieldset = elementFactory('fieldset');
|
|
32
33
|
export const form = elementFactory('form');
|
|
33
34
|
export const hr = elementFactory('hr');
|
|
34
35
|
export const h1 = elementFactory('h1');
|
|
@@ -36,6 +37,7 @@ export const h2 = elementFactory('h2');
|
|
|
36
37
|
export const h3 = elementFactory('h3');
|
|
37
38
|
export const h4 = elementFactory('h4');
|
|
38
39
|
export const label = elementFactory('label');
|
|
40
|
+
export const legend = elementFactory('legend');
|
|
39
41
|
export const li = elementFactory('li');
|
|
40
42
|
export const nav = elementFactory('nav');
|
|
41
43
|
export const ol = elementFactory('ol');
|
|
@@ -119,7 +119,7 @@ export class TabContainerModel extends HoistModel {
|
|
|
119
119
|
|
|
120
120
|
/**
|
|
121
121
|
* @param config - TabContainer configuration.
|
|
122
|
-
* @param
|
|
122
|
+
* @param depth - Depth in hierarchy of nested TabContainerModels. Not for application use.
|
|
123
123
|
*/
|
|
124
124
|
constructor(
|
|
125
125
|
{
|
|
@@ -281,8 +281,8 @@ export class ViewManagerModel<T = PlainObject> extends HoistModel {
|
|
|
281
281
|
|
|
282
282
|
/** True if any async tasks are pending. */
|
|
283
283
|
get isLoading(): boolean {
|
|
284
|
-
const {
|
|
285
|
-
return
|
|
284
|
+
const {loadObserver, saveTask, selectTask} = this;
|
|
285
|
+
return loadObserver.isPending || saveTask.isPending || selectTask.isPending;
|
|
286
286
|
}
|
|
287
287
|
|
|
288
288
|
/**
|
package/core/XH.ts
CHANGED
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
WebSocketService,
|
|
32
32
|
ClientHealthService
|
|
33
33
|
} from '@xh/hoist/svc';
|
|
34
|
-
import {getLogLevel, setLogLevel, LogLevel} from '@xh/hoist/utils/js';
|
|
34
|
+
import {getLogLevel, setLogLevel, LogLevel, apiDeprecated} from '@xh/hoist/utils/js';
|
|
35
35
|
import {camelCase, flatten, isString, uniqueId} from 'lodash';
|
|
36
36
|
import {Router, State} from 'router5';
|
|
37
37
|
import {CancelFn} from 'router5/types/types/base';
|
|
@@ -164,10 +164,18 @@ export class XHApi {
|
|
|
164
164
|
//----------------------------
|
|
165
165
|
/**
|
|
166
166
|
* Tracks globally loading promises.
|
|
167
|
-
* Apps should link any async operations that should mask the entire viewport to this
|
|
167
|
+
* Apps should link any async operations that should mask the entire viewport to this observer.
|
|
168
168
|
*/
|
|
169
|
+
get appLoadObserver(): TaskObserver {
|
|
170
|
+
return this.acm.appLoadObserver;
|
|
171
|
+
}
|
|
172
|
+
|
|
169
173
|
get appLoadModel(): TaskObserver {
|
|
170
|
-
|
|
174
|
+
apiDeprecated('XH.appLoadModel', {
|
|
175
|
+
v: 'v82',
|
|
176
|
+
msg: 'Use XH.appLoadObserver instead.'
|
|
177
|
+
});
|
|
178
|
+
return this.appLoadObserver;
|
|
171
179
|
}
|
|
172
180
|
|
|
173
181
|
/** Root level application model. */
|
|
@@ -439,7 +447,7 @@ export class XHApi {
|
|
|
439
447
|
*/
|
|
440
448
|
@action
|
|
441
449
|
reloadApp(opts?: ReloadAppOptions | string) {
|
|
442
|
-
never().linkTo(this.
|
|
450
|
+
never().linkTo(this.appLoadObserver);
|
|
443
451
|
|
|
444
452
|
opts = isString(opts) ? {path: opts} : (opts ?? {});
|
|
445
453
|
|
|
@@ -171,7 +171,7 @@ const appContainerView = hoistCmp.factory({
|
|
|
171
171
|
});
|
|
172
172
|
|
|
173
173
|
const appLoadMask = hoistCmp.factory<AppContainerModel>(({model}) =>
|
|
174
|
-
mask({bind: model.
|
|
174
|
+
mask({bind: model.appLoadObserver, spinner: true})
|
|
175
175
|
);
|
|
176
176
|
|
|
177
177
|
const suspendedView = hoistCmp.factory(() => viewport(suspendPanel(), appLoadMask()));
|
|
@@ -25,7 +25,7 @@ export const loginPanel = hoistCmp.factory({
|
|
|
25
25
|
|
|
26
26
|
render({model}) {
|
|
27
27
|
const {loginMessage} = XH.appSpec,
|
|
28
|
-
{
|
|
28
|
+
{loadObserver, warning, isValid, loginInProgress} = model;
|
|
29
29
|
|
|
30
30
|
const onKeyDown = ev => {
|
|
31
31
|
if (ev.key === 'Enter') model.submitAsync();
|
|
@@ -40,7 +40,7 @@ export const loginPanel = hoistCmp.factory({
|
|
|
40
40
|
icon: Icon.login(),
|
|
41
41
|
className: 'xh-login',
|
|
42
42
|
width: 300,
|
|
43
|
-
mask:
|
|
43
|
+
mask: loadObserver,
|
|
44
44
|
items: [
|
|
45
45
|
vspacer(10),
|
|
46
46
|
form(
|
|
@@ -0,0 +1,44 @@
|
|
|
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 © 2026 Extremely Heavy Industries Inc.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {type ReactElement, type ReactNode, useState} from 'react';
|
|
9
|
+
import {hoistCmp, HoistProps} from '@xh/hoist/core';
|
|
10
|
+
import {button} from '@xh/hoist/desktop/cmp/button';
|
|
11
|
+
import {legend} from '@xh/hoist/cmp/layout';
|
|
12
|
+
import {Icon} from '@xh/hoist/icon/Icon';
|
|
13
|
+
|
|
14
|
+
export interface FieldsetCollapseButtonProps extends HoistProps {
|
|
15
|
+
icon?: ReactElement;
|
|
16
|
+
text: ReactNode;
|
|
17
|
+
clickHandler?: (boolean) => void;
|
|
18
|
+
collapsed?: boolean;
|
|
19
|
+
disabled?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const [FieldsetCollapseButton, fieldsetCollapseButton] =
|
|
23
|
+
hoistCmp.withFactory<FieldsetCollapseButtonProps>({
|
|
24
|
+
displayName: 'FieldsetCollapseButton',
|
|
25
|
+
model: false,
|
|
26
|
+
render({icon, text, clickHandler, collapsed, disabled}) {
|
|
27
|
+
const [isCollapsed, setIsCollapsed] = useState<boolean>(collapsed === true);
|
|
28
|
+
|
|
29
|
+
return legend(
|
|
30
|
+
button({
|
|
31
|
+
text,
|
|
32
|
+
icon,
|
|
33
|
+
rightIcon: isCollapsed ? Icon.angleDown() : Icon.angleUp(),
|
|
34
|
+
outlined: false,
|
|
35
|
+
disabled,
|
|
36
|
+
onClick: () => {
|
|
37
|
+
const val = !isCollapsed;
|
|
38
|
+
setIsCollapsed(val);
|
|
39
|
+
clickHandler?.(val);
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
@@ -10,7 +10,7 @@ import ReactGridLayout, {
|
|
|
10
10
|
useContainerWidth,
|
|
11
11
|
getCompactor
|
|
12
12
|
} from 'react-grid-layout';
|
|
13
|
-
import {GridBackground, type GridBackgroundProps} from 'react-grid-layout/extras';
|
|
13
|
+
import {GridBackground, type GridBackgroundProps, wrapCompactor} from 'react-grid-layout/extras';
|
|
14
14
|
import composeRefs from '@seznam/compose-react-refs';
|
|
15
15
|
import {div, vbox, vspacer} from '@xh/hoist/cmp/layout';
|
|
16
16
|
import {
|
|
@@ -62,7 +62,11 @@ export const [DashCanvas, dashCanvas] = hoistCmp.withFactory<DashCanvasProps>({
|
|
|
62
62
|
render({className, model, rglOptions, testId}, ref) {
|
|
63
63
|
const isDraggable = !model.layoutLocked,
|
|
64
64
|
isResizable = !model.layoutLocked,
|
|
65
|
-
{width, containerRef, mounted} = useContainerWidth()
|
|
65
|
+
{width, containerRef, mounted} = useContainerWidth(),
|
|
66
|
+
defaultDroppedItemDims = {
|
|
67
|
+
w: Math.floor(model.columns / 3),
|
|
68
|
+
h: Math.floor(model.columns / 3)
|
|
69
|
+
};
|
|
66
70
|
|
|
67
71
|
return refreshContextView({
|
|
68
72
|
model: model.refreshContextModel,
|
|
@@ -98,7 +102,20 @@ export const [DashCanvas, dashCanvas] = hoistCmp.withFactory<DashCanvasProps>({
|
|
|
98
102
|
resizeConfig: {
|
|
99
103
|
enabled: isResizable
|
|
100
104
|
},
|
|
101
|
-
|
|
105
|
+
dropConfig: {
|
|
106
|
+
enabled: model.contentLocked ? false : model.allowsDrop,
|
|
107
|
+
defaultItem: defaultDroppedItemDims,
|
|
108
|
+
onDragOver: (evt: DragEvent) => model.onDropDragOver(evt)
|
|
109
|
+
},
|
|
110
|
+
onDrop: (
|
|
111
|
+
layout: LayoutItem[],
|
|
112
|
+
layoutItem: LayoutItem,
|
|
113
|
+
evt: Event
|
|
114
|
+
) => model.onDrop(layout, layoutItem, evt),
|
|
115
|
+
compactor:
|
|
116
|
+
model.compact === 'wrap'
|
|
117
|
+
? wrapCompactor
|
|
118
|
+
: getCompactor(model.compact, false, false),
|
|
102
119
|
onLayoutChange: (layout: LayoutItem[]) =>
|
|
103
120
|
model.onRglLayoutChange(layout),
|
|
104
121
|
onResizeStart: () => (model.isResizing = true),
|
|
@@ -116,7 +133,7 @@ export const [DashCanvas, dashCanvas] = hoistCmp.withFactory<DashCanvasProps>({
|
|
|
116
133
|
),
|
|
117
134
|
width
|
|
118
135
|
}),
|
|
119
|
-
emptyContainerOverlay({omit: !mounted})
|
|
136
|
+
emptyContainerOverlay({omit: !mounted || !model.showAddViewButtonWhenEmpty})
|
|
120
137
|
],
|
|
121
138
|
[TEST_ID]: testId
|
|
122
139
|
})
|