@xh/hoist 69.0.0-SNAPSHOT.1727923437594 → 69.0.0-SNAPSHOT.1728035585537
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 +14 -0
- package/appcontainer/AppContainerModel.ts +5 -3
- package/appcontainer/AppStateModel.ts +32 -38
- package/build/types/appcontainer/AppContainerModel.d.ts +1 -1
- package/build/types/appcontainer/AppStateModel.d.ts +3 -0
- package/build/types/core/types/AppState.d.ts +4 -2
- package/core/types/AppState.ts +8 -3
- package/desktop/appcontainer/AppContainer.ts +3 -1
- package/desktop/cmp/panel/impl/dragger/DraggerModel.ts +3 -1
- package/mobile/appcontainer/AppContainer.ts +3 -1
- package/package.json +1 -1
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
## 69.0.0-SNAPSHOT - unreleased
|
|
4
4
|
|
|
5
|
+
### 🐞 Bug Fixes
|
|
6
|
+
|
|
7
|
+
* Added a workaround for a bug where Panel drag resizing was broken in Safari.
|
|
8
|
+
|
|
9
|
+
### 💥 Breaking Changes (upgrade difficulty: 🟢 LOW )
|
|
10
|
+
* The `INITIALIZING` AppState has been replaced with more fine-grained states (see below). This
|
|
11
|
+
is not expected to affect any applications.
|
|
12
|
+
|
|
13
|
+
### 🎁 New Features
|
|
14
|
+
|
|
15
|
+
* Added new AppStates `AUTHENTICATING`, `INITIALIZING_HOIST`, and `INITIALIZING_APP` to support
|
|
16
|
+
more granular tracking and timing of app startup lifecycle.
|
|
17
|
+
* Improved the default "Loaded App" activity tracking entry with more granular data on load timing.
|
|
18
|
+
|
|
5
19
|
## 68.1.0 - 2024-09-27
|
|
6
20
|
|
|
7
21
|
### 🎁 New Features
|
|
@@ -99,7 +99,7 @@ export class AppContainerModel extends HoistModel {
|
|
|
99
99
|
@managed userAgentModel = new UserAgentModel();
|
|
100
100
|
|
|
101
101
|
/**
|
|
102
|
-
* Message shown on spinner while the application is in
|
|
102
|
+
* Message shown on spinner while the application is in a pre-running state.
|
|
103
103
|
* Update within `AppModel.initAsync()` to relay app-specific initialization status.
|
|
104
104
|
*/
|
|
105
105
|
@bindable initializingLoadMaskMessage: ReactNode;
|
|
@@ -108,7 +108,7 @@ export class AppContainerModel extends HoistModel {
|
|
|
108
108
|
* Main entry point. Initialize and render application code.
|
|
109
109
|
*/
|
|
110
110
|
renderApp<T extends HoistAppModel>(appSpec: AppSpec<T>) {
|
|
111
|
-
// Remove the
|
|
111
|
+
// Remove the preload exception handler installed by preflight.js
|
|
112
112
|
window.onerror = null;
|
|
113
113
|
const spinner = document.getElementById('xh-preload-spinner');
|
|
114
114
|
if (spinner) spinner.style.display = 'none';
|
|
@@ -180,6 +180,7 @@ export class AppContainerModel extends HoistModel {
|
|
|
180
180
|
await installServicesAsync([FetchService]);
|
|
181
181
|
|
|
182
182
|
// Check auth, locking out, or showing login if possible
|
|
183
|
+
this.setAppState('AUTHENTICATING');
|
|
183
184
|
XH.authModel = new this.appSpec.authModelClass();
|
|
184
185
|
const isAuthenticated = await XH.authModel.completeAuthAsync();
|
|
185
186
|
if (!isAuthenticated) {
|
|
@@ -206,6 +207,7 @@ export class AppContainerModel extends HoistModel {
|
|
|
206
207
|
*/
|
|
207
208
|
@action
|
|
208
209
|
async completeInitAsync() {
|
|
210
|
+
this.setAppState('INITIALIZING_HOIST');
|
|
209
211
|
try {
|
|
210
212
|
// Install identity service and confirm access
|
|
211
213
|
await installServicesAsync(IdentityService);
|
|
@@ -215,7 +217,6 @@ export class AppContainerModel extends HoistModel {
|
|
|
215
217
|
}
|
|
216
218
|
|
|
217
219
|
// Complete initialization process
|
|
218
|
-
this.setAppState('INITIALIZING');
|
|
219
220
|
await installServicesAsync([ConfigService, LocalStorageService]);
|
|
220
221
|
await installServicesAsync(TrackService);
|
|
221
222
|
await installServicesAsync([EnvironmentService, PrefService, JsonBlobService]);
|
|
@@ -272,6 +273,7 @@ export class AppContainerModel extends HoistModel {
|
|
|
272
273
|
// Delay to workaround hot-reload styling issues in dev.
|
|
273
274
|
await wait(XH.isDevelopmentMode ? 300 : 1);
|
|
274
275
|
|
|
276
|
+
this.setAppState('INITIALIZING_APP');
|
|
275
277
|
const modelClass: any = this.appSpec.modelClass;
|
|
276
278
|
this.appModel = modelClass.instance = new modelClass();
|
|
277
279
|
await this.appModel.initAsync();
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
* Copyright © 2024 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
7
|
import {AppState, AppSuspendData, HoistModel, XH} from '@xh/hoist/core';
|
|
8
|
-
import {action, makeObservable, observable
|
|
8
|
+
import {action, makeObservable, observable} from '@xh/hoist/mobx';
|
|
9
9
|
import {Timer} from '@xh/hoist/utils/async';
|
|
10
10
|
import {getClientDeviceInfo} from '@xh/hoist/utils/js';
|
|
11
|
-
import {isBoolean, isString} from 'lodash';
|
|
11
|
+
import {camelCase, isBoolean, isString, mapKeys} from 'lodash';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Support for Core Hoist Application state and loading.
|
|
@@ -24,6 +24,10 @@ export class AppStateModel extends HoistModel {
|
|
|
24
24
|
suspendData: AppSuspendData;
|
|
25
25
|
accessDeniedMessage: string = 'Access Denied';
|
|
26
26
|
|
|
27
|
+
private timings: Record<AppState, number> = {} as Record<AppState, number>;
|
|
28
|
+
private loadStarted: number = window['_xhLoadTimestamp']; // set in index.html
|
|
29
|
+
private lastStateChangeTime: number = this.loadStarted;
|
|
30
|
+
|
|
27
31
|
constructor() {
|
|
28
32
|
super();
|
|
29
33
|
makeObservable(this);
|
|
@@ -33,10 +37,14 @@ export class AppStateModel extends HoistModel {
|
|
|
33
37
|
|
|
34
38
|
@action
|
|
35
39
|
setAppState(nextState: AppState) {
|
|
36
|
-
if (this.state
|
|
37
|
-
|
|
38
|
-
}
|
|
40
|
+
if (this.state === nextState) return;
|
|
41
|
+
|
|
42
|
+
const {state, timings, lastStateChangeTime} = this,
|
|
43
|
+
now = Date.now();
|
|
44
|
+
timings[state] = (timings[state] ?? 0) + now - lastStateChangeTime;
|
|
45
|
+
this.lastStateChangeTime = now;
|
|
39
46
|
this.state = nextState;
|
|
47
|
+
this.logDebug(`AppState change`, `${state} → ${nextState}`);
|
|
40
48
|
}
|
|
41
49
|
|
|
42
50
|
suspendApp(suspendData: AppSuspendData) {
|
|
@@ -70,39 +78,25 @@ export class AppStateModel extends HoistModel {
|
|
|
70
78
|
// Implementation
|
|
71
79
|
//------------------
|
|
72
80
|
private trackLoad() {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
XH.
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
},
|
|
93
|
-
logData: ['appVersion', 'appBuild'],
|
|
94
|
-
omit: !XH.appSpec.trackAppLoad
|
|
95
|
-
});
|
|
96
|
-
disposer();
|
|
97
|
-
break;
|
|
98
|
-
case 'LOGIN_REQUIRED':
|
|
99
|
-
loginStarted = now;
|
|
100
|
-
break;
|
|
101
|
-
default:
|
|
102
|
-
if (loginStarted) loginElapsed = now - loginStarted;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
);
|
|
81
|
+
const {timings, loadStarted} = this;
|
|
82
|
+
this.addReaction({
|
|
83
|
+
when: () => this.state === 'RUNNING',
|
|
84
|
+
run: () =>
|
|
85
|
+
XH.track({
|
|
86
|
+
category: 'App',
|
|
87
|
+
message: `Loaded ${XH.clientAppCode}`,
|
|
88
|
+
elapsed: Date.now() - loadStarted - (timings.LOGIN_REQUIRED ?? 0),
|
|
89
|
+
data: {
|
|
90
|
+
appVersion: XH.appVersion,
|
|
91
|
+
appBuild: XH.appBuild,
|
|
92
|
+
locationHref: window.location.href,
|
|
93
|
+
timings: mapKeys(timings, (v, k) => camelCase(k)),
|
|
94
|
+
...getClientDeviceInfo()
|
|
95
|
+
},
|
|
96
|
+
logData: ['appVersion', 'appBuild'],
|
|
97
|
+
omit: !XH.appSpec.trackAppLoad
|
|
98
|
+
})
|
|
99
|
+
});
|
|
106
100
|
}
|
|
107
101
|
|
|
108
102
|
//---------------------
|
|
@@ -42,7 +42,7 @@ export declare class AppContainerModel extends HoistModel {
|
|
|
42
42
|
themeModel: ThemeModel;
|
|
43
43
|
userAgentModel: UserAgentModel;
|
|
44
44
|
/**
|
|
45
|
-
* Message shown on spinner while the application is in
|
|
45
|
+
* Message shown on spinner while the application is in a pre-running state.
|
|
46
46
|
* Update within `AppModel.initAsync()` to relay app-specific initialization status.
|
|
47
47
|
*/
|
|
48
48
|
initializingLoadMaskMessage: ReactNode;
|
|
@@ -10,6 +10,9 @@ export declare class AppStateModel extends HoistModel {
|
|
|
10
10
|
lastActivityMs: number;
|
|
11
11
|
suspendData: AppSuspendData;
|
|
12
12
|
accessDeniedMessage: string;
|
|
13
|
+
private timings;
|
|
14
|
+
private loadStarted;
|
|
15
|
+
private lastStateChangeTime;
|
|
13
16
|
constructor();
|
|
14
17
|
setAppState(nextState: AppState): void;
|
|
15
18
|
suspendApp(suspendData: AppSuspendData): void;
|
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export declare const AppState: Readonly<{
|
|
5
5
|
PRE_AUTH: "PRE_AUTH";
|
|
6
|
+
AUTHENTICATING: "AUTHENTICATING";
|
|
6
7
|
LOGIN_REQUIRED: "LOGIN_REQUIRED";
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
INITIALIZING_HOIST: "INITIALIZING_HOIST";
|
|
9
|
+
INITIALIZING_APP: "INITIALIZING_APP";
|
|
9
10
|
RUNNING: "RUNNING";
|
|
10
11
|
SUSPENDED: "SUSPENDED";
|
|
11
12
|
LOAD_FAILED: "LOAD_FAILED";
|
|
13
|
+
ACCESS_DENIED: "ACCESS_DENIED";
|
|
12
14
|
}>;
|
|
13
15
|
export type AppState = (typeof AppState)[keyof typeof AppState];
|
|
14
16
|
export interface AppSuspendData {
|
package/core/types/AppState.ts
CHANGED
|
@@ -9,13 +9,18 @@
|
|
|
9
9
|
* Enumeration of possible App States
|
|
10
10
|
*/
|
|
11
11
|
export const AppState = Object.freeze({
|
|
12
|
+
// Main Flow
|
|
12
13
|
PRE_AUTH: 'PRE_AUTH',
|
|
14
|
+
AUTHENTICATING: 'AUTHENTICATING',
|
|
13
15
|
LOGIN_REQUIRED: 'LOGIN_REQUIRED',
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
INITIALIZING_HOIST: 'INITIALIZING_HOIST',
|
|
17
|
+
INITIALIZING_APP: 'INITIALIZING_APP',
|
|
16
18
|
RUNNING: 'RUNNING',
|
|
17
19
|
SUSPENDED: 'SUSPENDED',
|
|
18
|
-
|
|
20
|
+
|
|
21
|
+
// Terminal Error States.
|
|
22
|
+
LOAD_FAILED: 'LOAD_FAILED',
|
|
23
|
+
ACCESS_DENIED: 'ACCESS_DENIED'
|
|
19
24
|
});
|
|
20
25
|
|
|
21
26
|
// eslint-disable-next-line
|
|
@@ -104,7 +104,9 @@ export const AppContainer = hoistCmp({
|
|
|
104
104
|
function viewForState({model}) {
|
|
105
105
|
switch (XH.appState) {
|
|
106
106
|
case 'PRE_AUTH':
|
|
107
|
-
case '
|
|
107
|
+
case 'AUTHENTICATING':
|
|
108
|
+
case 'INITIALIZING_HOIST':
|
|
109
|
+
case 'INITIALIZING_APP':
|
|
108
110
|
return viewport(
|
|
109
111
|
mask({spinner: true, isDisplayed: true, message: model.initializingLoadMaskMessage})
|
|
110
112
|
);
|
|
@@ -254,7 +254,9 @@ export class DraggerModel extends HoistModel {
|
|
|
254
254
|
}
|
|
255
255
|
|
|
256
256
|
private isValidMouseEvent(e) {
|
|
257
|
-
|
|
257
|
+
// Note: We fall back to deprecated 'which' to work around a Safari issue where `buttons`
|
|
258
|
+
// was not being set. We may be able to remove in the future.
|
|
259
|
+
return (e.buttons && e.buttons !== 0) || (e.which && e.which !== 0);
|
|
258
260
|
}
|
|
259
261
|
|
|
260
262
|
private isValidTouchEvent(e) {
|
|
@@ -89,7 +89,9 @@ export const AppContainer = hoistCmp({
|
|
|
89
89
|
function viewForState({model}) {
|
|
90
90
|
switch (XH.appState) {
|
|
91
91
|
case 'PRE_AUTH':
|
|
92
|
-
case '
|
|
92
|
+
case 'AUTHENTICATING':
|
|
93
|
+
case 'INITIALIZING_HOIST':
|
|
94
|
+
case 'INITIALIZING_APP':
|
|
93
95
|
return viewport(
|
|
94
96
|
mask({spinner: true, isDisplayed: true, message: model.initializingLoadMaskMessage})
|
|
95
97
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xh/hoist",
|
|
3
|
-
"version": "69.0.0-SNAPSHOT.
|
|
3
|
+
"version": "69.0.0-SNAPSHOT.1728035585537",
|
|
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",
|