@equinor/fusion-framework-dev-portal 8.0.0 → 8.0.2
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 +100 -0
- package/dev-server.ts +4 -0
- package/dist/main.js +20575 -19971
- package/package.json +39 -39
- package/src/AppLoader.tsx +6 -19
- package/src/ContextSelector/ContextSelector.tsx +7 -0
- package/src/ContextSelector/useContextResolver.ts +53 -33
- package/src/Header.tsx +2 -2
- package/src/PersonSideSheet/index.tsx +1 -0
- package/src/PersonSideSheet/sheets/FeatureTogglerApp.tsx +1 -1
- package/src/PersonSideSheet/sheets/FeatureTogglerPortal.tsx +1 -1
- package/src/Router.tsx +0 -2
- package/src/{config.ts → configure.ts} +3 -0
- package/src/get-app-tag-from-url.ts +17 -0
- package/src/main.tsx +2 -2
- package/src/useAppContextNavigation.ts +2 -0
- package/src/version.ts +1 -1
- package/tsconfig.tsbuildinfo +1 -1
- /package/src/{BookMarkSideSheet.tsx → BookmarkSideSheet.tsx} +0 -0
- /package/src/{Header.Actions.tsx → HeaderActions.tsx} +0 -0
- /package/src/PersonSideSheet/sheets/{Styled.tsx → styled.tsx} +0 -0
- /package/src/resources/{fallback-photo.svg.ts → svg.ts} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@equinor/fusion-framework-dev-portal",
|
|
3
|
-
"version": "8.0.
|
|
3
|
+
"version": "8.0.2",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./dist/main.js",
|
|
@@ -35,47 +35,47 @@
|
|
|
35
35
|
"rxjs": "^7.8.1",
|
|
36
36
|
"styled-components": "^6.3.11",
|
|
37
37
|
"tsx": "^4.19.3",
|
|
38
|
-
"typescript": "^
|
|
38
|
+
"typescript": "^7.0.2",
|
|
39
39
|
"vite": "^8.0.0",
|
|
40
|
-
"@equinor/fusion-framework": "8.0.
|
|
41
|
-
"@equinor/fusion-framework-
|
|
42
|
-
"@equinor/fusion-framework-
|
|
43
|
-
"@equinor/fusion-framework-
|
|
44
|
-
"@equinor/fusion-framework-module-analytics": "3.0.
|
|
45
|
-
"@equinor/fusion-framework-module-app": "8.0.
|
|
46
|
-
"@equinor/fusion-framework-module-bookmark": "4.0.
|
|
47
|
-
"@equinor/fusion-framework-module-
|
|
48
|
-
"@equinor/fusion-framework-module-
|
|
49
|
-
"@equinor/fusion-framework-module-
|
|
50
|
-
"@equinor/fusion-framework-module-
|
|
51
|
-
"@equinor/fusion-framework-react": "8.0.
|
|
52
|
-
"@equinor/fusion-framework-module-
|
|
53
|
-
"@equinor/fusion-framework-react-components-bookmark": "2.0.
|
|
54
|
-
"@equinor/fusion-framework-react-components-people-provider": "2.0.
|
|
55
|
-
"@equinor/fusion-
|
|
56
|
-
"@equinor/fusion-
|
|
57
|
-
"@equinor/fusion-framework-react-
|
|
58
|
-
"@equinor/fusion-
|
|
40
|
+
"@equinor/fusion-framework": "8.0.11",
|
|
41
|
+
"@equinor/fusion-framework-dev-server": "2.0.16",
|
|
42
|
+
"@equinor/fusion-framework-app": "11.0.11",
|
|
43
|
+
"@equinor/fusion-framework-module-ag-grid": "37.0.1",
|
|
44
|
+
"@equinor/fusion-framework-module-analytics": "3.0.3",
|
|
45
|
+
"@equinor/fusion-framework-module-app": "8.0.3",
|
|
46
|
+
"@equinor/fusion-framework-module-bookmark": "4.0.3",
|
|
47
|
+
"@equinor/fusion-framework-module-feature-flag": "2.0.2",
|
|
48
|
+
"@equinor/fusion-framework-module-navigation": "7.0.6",
|
|
49
|
+
"@equinor/fusion-framework-module-telemetry": "7.0.1",
|
|
50
|
+
"@equinor/fusion-framework-module-context": "8.0.1",
|
|
51
|
+
"@equinor/fusion-framework-react": "8.0.1",
|
|
52
|
+
"@equinor/fusion-framework-module-services": "8.0.2",
|
|
53
|
+
"@equinor/fusion-framework-react-components-bookmark": "2.0.3",
|
|
54
|
+
"@equinor/fusion-framework-react-components-people-provider": "2.0.4",
|
|
55
|
+
"@equinor/fusion-framework-react-router": "2.4.0",
|
|
56
|
+
"@equinor/fusion-observable": "9.1.1",
|
|
57
|
+
"@equinor/fusion-framework-react-module-bookmark": "6.0.1",
|
|
58
|
+
"@equinor/fusion-query": "7.0.2"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
61
|
-
"@equinor/fusion-framework": "8.0.
|
|
62
|
-
"@equinor/fusion-framework-app": "11.0.
|
|
63
|
-
"@equinor/fusion-framework-
|
|
64
|
-
"@equinor/fusion-framework-
|
|
65
|
-
"@equinor/fusion-framework-module-analytics": "3.0.
|
|
66
|
-
"@equinor/fusion-framework-module-app": "8.0.
|
|
67
|
-
"@equinor/fusion-framework-module-bookmark": "4.0.
|
|
68
|
-
"@equinor/fusion-framework-module-
|
|
69
|
-
"@equinor/fusion-framework-module-
|
|
70
|
-
"@equinor/fusion-framework-module-
|
|
71
|
-
"@equinor/fusion-framework-module-
|
|
72
|
-
"@equinor/fusion-framework-module-
|
|
73
|
-
"@equinor/fusion-framework-react
|
|
74
|
-
"@equinor/fusion-framework-react": "
|
|
75
|
-
"@equinor/fusion-framework-react-components-people-provider": "2.0.
|
|
76
|
-
"@equinor/fusion-
|
|
77
|
-
"@equinor/fusion-
|
|
78
|
-
"@equinor/fusion-
|
|
61
|
+
"@equinor/fusion-framework": "8.0.11",
|
|
62
|
+
"@equinor/fusion-framework-app": "11.0.11",
|
|
63
|
+
"@equinor/fusion-framework-dev-server": "2.0.16",
|
|
64
|
+
"@equinor/fusion-framework-module-ag-grid": "37.0.1",
|
|
65
|
+
"@equinor/fusion-framework-module-analytics": "3.0.3",
|
|
66
|
+
"@equinor/fusion-framework-module-app": "8.0.3",
|
|
67
|
+
"@equinor/fusion-framework-module-bookmark": "4.0.3",
|
|
68
|
+
"@equinor/fusion-framework-module-context": "8.0.1",
|
|
69
|
+
"@equinor/fusion-framework-module-services": "8.0.2",
|
|
70
|
+
"@equinor/fusion-framework-module-feature-flag": "2.0.2",
|
|
71
|
+
"@equinor/fusion-framework-module-telemetry": "7.0.1",
|
|
72
|
+
"@equinor/fusion-framework-module-navigation": "7.0.6",
|
|
73
|
+
"@equinor/fusion-framework-react": "8.0.1",
|
|
74
|
+
"@equinor/fusion-framework-react-components-bookmark": "2.0.3",
|
|
75
|
+
"@equinor/fusion-framework-react-components-people-provider": "2.0.4",
|
|
76
|
+
"@equinor/fusion-query": "7.0.2",
|
|
77
|
+
"@equinor/fusion-observable": "9.1.1",
|
|
78
|
+
"@equinor/fusion-framework-react-module-bookmark": "6.0.1"
|
|
79
79
|
},
|
|
80
80
|
"peerDependenciesMeta": {
|
|
81
81
|
"@equinor/fusion-framework": {
|
package/src/AppLoader.tsx
CHANGED
|
@@ -12,24 +12,7 @@ import { AppManifestError } from '@equinor/fusion-framework-module-app/errors.js
|
|
|
12
12
|
import { ErrorViewer } from './ErrorViewer';
|
|
13
13
|
import type { AppModule } from '@equinor/fusion-framework-module-app';
|
|
14
14
|
import EquinorLoader from './EquinorLoader';
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* URL search-parameter key used to specify an app version tag.
|
|
18
|
-
*
|
|
19
|
-
* When present in the URL as `?$tag=<value>`, the portal loads that
|
|
20
|
-
* specific version of the application instead of the default.
|
|
21
|
-
*/
|
|
22
|
-
export const TAG = '$tag';
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Reads the application version tag from the current URL search parameters.
|
|
26
|
-
*
|
|
27
|
-
* @returns The tag string if the `$tag` search parameter is present, otherwise `null`.
|
|
28
|
-
*/
|
|
29
|
-
export const getAppTagFromUrl = (): string | null => {
|
|
30
|
-
const url = new URL(window.location.href);
|
|
31
|
-
return url.searchParams.get(TAG);
|
|
32
|
-
};
|
|
15
|
+
import { getAppTagFromUrl } from './get-app-tag-from-url';
|
|
33
16
|
|
|
34
17
|
/**
|
|
35
18
|
* Loads, initializes, and mounts a Fusion application by its key.
|
|
@@ -51,7 +34,7 @@ export const AppLoader = (props: { readonly appKey: string }) => {
|
|
|
51
34
|
const [loading, setLoading] = useState<boolean>(false);
|
|
52
35
|
const [error, setError] = useState<Error | undefined>();
|
|
53
36
|
|
|
54
|
-
// TODO change to `useCurrentApp`
|
|
37
|
+
// TODO(#5087): change to `useCurrentApp`
|
|
55
38
|
/** observe and use the current selected application from framework */
|
|
56
39
|
const { value: currentApp } = useObservableState(
|
|
57
40
|
useMemo(() => fusion.modules.app.current$, [fusion.modules.app]),
|
|
@@ -60,6 +43,7 @@ export const AppLoader = (props: { readonly appKey: string }) => {
|
|
|
60
43
|
useEffect(() => {
|
|
61
44
|
const tag = getAppTagFromUrl();
|
|
62
45
|
|
|
46
|
+
// A `$tag` URL param takes precedence over the plain appKey
|
|
63
47
|
if (tag) {
|
|
64
48
|
fusion.modules.app.setCurrentApp({ appKey, tag });
|
|
65
49
|
return;
|
|
@@ -90,6 +74,7 @@ export const AppLoader = (props: { readonly appKey: string }) => {
|
|
|
90
74
|
|
|
91
75
|
/** create a 'private' element for the application */
|
|
92
76
|
const el = document.createElement('div');
|
|
77
|
+
// Guard against rendering before the mounting ref has attached
|
|
93
78
|
if (!ref.current) {
|
|
94
79
|
throw Error('Missing application mounting point');
|
|
95
80
|
}
|
|
@@ -120,7 +105,9 @@ export const AppLoader = (props: { readonly appKey: string }) => {
|
|
|
120
105
|
return () => subscription.unsubscribe();
|
|
121
106
|
}, [fusion, currentApp]);
|
|
122
107
|
|
|
108
|
+
// Render an error view when initialization or manifest resolution failed
|
|
123
109
|
if (error) {
|
|
110
|
+
// Render a dedicated view for manifest-resolution failures
|
|
124
111
|
if (error.cause instanceof AppManifestError) {
|
|
125
112
|
return (
|
|
126
113
|
<div>
|
|
@@ -30,9 +30,14 @@ export const ContextSelector = (props: ContextSearchProps): ReactElement | null
|
|
|
30
30
|
/** callback handler for context selector, when context is changed or cleared */
|
|
31
31
|
const onContextSelect = useCallback(
|
|
32
32
|
(e: Event | ContextSelectEvent) => {
|
|
33
|
+
// Only react to selection/clear events when a provider is available to update
|
|
33
34
|
if (provider) {
|
|
35
|
+
// 'select' carries a chosen context item; anything else means the selector was cleared
|
|
34
36
|
if (e.type === 'select') {
|
|
37
|
+
// The 'select' event's `Event` type doesn't reflect the CustomEvent detail payload
|
|
38
|
+
// dispatched by the underlying context-selector element, so a cast is required here.
|
|
35
39
|
const ev = e as unknown as ContextSelectEvent;
|
|
40
|
+
// Only set a new context when the user actually chose an item
|
|
36
41
|
if (ev.nativeEvent.detail.selected.length) {
|
|
37
42
|
provider.contextClient.setCurrentContext(ev.nativeEvent.detail.selected[0].id);
|
|
38
43
|
}
|
|
@@ -49,11 +54,13 @@ export const ContextSelector = (props: ContextSearchProps): ReactElement | null
|
|
|
49
54
|
*/
|
|
50
55
|
const clearEvent = useMemo(() => new ContextClearEvent({ date: Date.now() }), []);
|
|
51
56
|
useEffect(() => {
|
|
57
|
+
// Notify listeners only when the context was cleared outside this selector
|
|
52
58
|
if (!selectedContextItem) {
|
|
53
59
|
document.dispatchEvent(clearEvent);
|
|
54
60
|
}
|
|
55
61
|
}, [clearEvent, selectedContextItem]);
|
|
56
62
|
|
|
63
|
+
// Nothing to render until a context resolver has been resolved
|
|
57
64
|
if (!resolver) return null;
|
|
58
65
|
|
|
59
66
|
return (
|
|
@@ -43,19 +43,22 @@ function capitalizeFirstLetter(string: string): string {
|
|
|
43
43
|
function convertGraphic(
|
|
44
44
|
graphic: ContextItem['graphic'],
|
|
45
45
|
): Pick<ContextResultItem, 'graphic' | 'graphicType'> {
|
|
46
|
+
// No graphic provided means nothing to render
|
|
46
47
|
if (graphic === undefined) {
|
|
47
48
|
return {};
|
|
48
49
|
}
|
|
49
50
|
|
|
51
|
+
// A string graphic is either an inline SVG/HTML markup or an EDS icon name
|
|
50
52
|
if (typeof graphic === 'string') {
|
|
53
|
+
// EDS icon names aren't part of the ContextResultItem['graphicType'] union upstream, so cast explicitly
|
|
54
|
+
const edsGraphicType = 'eds' as unknown as ContextResultItem['graphicType'];
|
|
51
55
|
return {
|
|
52
|
-
graphicType: graphic.startsWith('<')
|
|
53
|
-
? 'inline-html'
|
|
54
|
-
: ('eds' as unknown as ContextResultItem['graphicType']),
|
|
56
|
+
graphicType: graphic.startsWith('<') ? 'inline-html' : edsGraphicType,
|
|
55
57
|
graphic: graphic,
|
|
56
58
|
};
|
|
57
59
|
}
|
|
58
60
|
|
|
61
|
+
// A structured SVG graphic object maps directly to the inline-svg type
|
|
59
62
|
if (graphic.type === 'svg') {
|
|
60
63
|
return {
|
|
61
64
|
graphicType: 'inline-svg',
|
|
@@ -76,19 +79,22 @@ function convertGraphic(
|
|
|
76
79
|
* @returns An object with `meta` and `metaType` properties, or an empty object.
|
|
77
80
|
*/
|
|
78
81
|
function convertMeta(meta: ContextItem['meta']): Pick<ContextResultItem, 'metaType' | 'meta'> {
|
|
82
|
+
// No meta provided means nothing to render
|
|
79
83
|
if (meta === undefined) {
|
|
80
84
|
return {};
|
|
81
85
|
}
|
|
82
86
|
|
|
87
|
+
// A string meta is either an inline SVG/HTML markup or an EDS icon name
|
|
83
88
|
if (typeof meta === 'string') {
|
|
89
|
+
// EDS icon names aren't part of the ContextResultItem['metaType'] union upstream, so cast explicitly
|
|
90
|
+
const edsMetaType = 'eds' as unknown as ContextResultItem['metaType'];
|
|
84
91
|
return {
|
|
85
|
-
metaType: meta.startsWith('<')
|
|
86
|
-
? 'inline-html'
|
|
87
|
-
: ('eds' as unknown as ContextResultItem['metaType']),
|
|
92
|
+
metaType: meta.startsWith('<') ? 'inline-html' : edsMetaType,
|
|
88
93
|
meta: meta,
|
|
89
94
|
};
|
|
90
95
|
}
|
|
91
96
|
|
|
97
|
+
// A structured SVG meta object maps directly to the inline-svg type
|
|
92
98
|
if (meta.type === 'svg') {
|
|
93
99
|
return {
|
|
94
100
|
metaType: 'inline-svg',
|
|
@@ -112,7 +118,9 @@ function convertMeta(meta: ContextItem['meta']): Pick<ContextResultItem, 'metaTy
|
|
|
112
118
|
* @returns Mapped array of `ContextResultItem` objects for the selector UI.
|
|
113
119
|
*/
|
|
114
120
|
const mapper = (src: ContextItem<{ taskState?: string; state?: string }>[]): ContextResult => {
|
|
121
|
+
// Map each raw context item to its ContextResultItem shape, applying per-type overrides
|
|
115
122
|
return src.map((i) => {
|
|
123
|
+
// Layer the base fields with graphic and meta overrides derived from the item
|
|
116
124
|
const baseResult = {
|
|
117
125
|
id: i.id,
|
|
118
126
|
title: i.title,
|
|
@@ -125,18 +133,22 @@ const mapper = (src: ContextItem<{ taskState?: string; state?: string }>[]): Con
|
|
|
125
133
|
const isEquinorTaskInactive = !!(
|
|
126
134
|
i.value.taskState && i.value.taskState.toLowerCase() !== 'active'
|
|
127
135
|
);
|
|
136
|
+
// Only override the meta chip for EquinorTask items that are inactive
|
|
128
137
|
if (i.type.id === 'EquinorTask' && isEquinorTaskInactive) {
|
|
129
138
|
baseResult.meta = `<fwc-chip disabled variant="outlined" value="${i.value.taskState}" />`;
|
|
130
139
|
baseResult.metaType = 'inline-html';
|
|
131
140
|
}
|
|
132
141
|
|
|
142
|
+
// OrgChart items always get a fixed icon plus an optional inactive-state chip
|
|
133
143
|
if (i.type.id === 'OrgChart') {
|
|
134
144
|
// Org charts should always have 'list' icon
|
|
135
145
|
baseResult.graphic = 'list';
|
|
146
|
+
// 'eds' isn't part of the ContextResultItem['graphicType'] union upstream, so cast explicitly
|
|
136
147
|
baseResult.graphicType = 'eds' as unknown as ContextResultItem['graphicType'];
|
|
137
148
|
|
|
138
149
|
// Displays the org chart status if it is not 'active'
|
|
139
150
|
const isOrgChartInactive = !!(i.value.state && i.value.state.toLowerCase() !== 'active');
|
|
151
|
+
// Only override the meta chip when the org chart is inactive
|
|
140
152
|
if (isOrgChartInactive) {
|
|
141
153
|
baseResult.meta = `<fwc-chip disabled variant="outlined" value="${capitalizeFirstLetter(i.value.state ?? '')}" />`;
|
|
142
154
|
baseResult.metaType = 'inline-html';
|
|
@@ -156,6 +168,7 @@ const mapper = (src: ContextItem<{ taskState?: string; state?: string }>[]): Con
|
|
|
156
168
|
* @returns A complete `ContextResultItem` with defaults for `id` and `title`.
|
|
157
169
|
*/
|
|
158
170
|
const singleItem = (props: Partial<ContextResultItem>): ContextResultItem => {
|
|
171
|
+
// Layer the caller-supplied props on top of the placeholder defaults
|
|
159
172
|
return Object.assign({ id: 'no-such-item', title: 'Change me' }, props);
|
|
160
173
|
};
|
|
161
174
|
|
|
@@ -200,6 +213,7 @@ export const useContextResolver = (): {
|
|
|
200
213
|
const onContextProviderChange = useCallback((modules: AppModulesInstance) => {
|
|
201
214
|
/** try to get the context module from the app module instance */
|
|
202
215
|
const contextProvider = (modules as AppModulesInstance<[ContextModule]>).context;
|
|
216
|
+
// Only set a provider when the loaded app actually exposes a context module
|
|
203
217
|
if (contextProvider) {
|
|
204
218
|
setProvider(contextProvider);
|
|
205
219
|
} else {
|
|
@@ -224,10 +238,12 @@ export const useContextResolver = (): {
|
|
|
224
238
|
);
|
|
225
239
|
|
|
226
240
|
const processError = useCallback((err: Error): ContextResult => {
|
|
241
|
+
// Unwrap query-client errors to get at the underlying cause
|
|
227
242
|
if (err.name === 'QueryClientError') {
|
|
228
243
|
return processError((err as QueryClientError).cause as Error);
|
|
229
244
|
}
|
|
230
245
|
|
|
246
|
+
// Render context-search errors with their own title/description
|
|
231
247
|
if (err.name === 'FusionContextSearchError') {
|
|
232
248
|
const error = err as FusionContextSearchError;
|
|
233
249
|
return [
|
|
@@ -260,10 +276,11 @@ export const useContextResolver = (): {
|
|
|
260
276
|
(): ContextResolver | null =>
|
|
261
277
|
provider && {
|
|
262
278
|
searchQuery: async (search: string): Promise<ContextResult> => {
|
|
279
|
+
// Avoid firing a search query until the minimum character threshold is met
|
|
263
280
|
if (search.length < minLength) {
|
|
264
281
|
return [
|
|
265
282
|
singleItem({
|
|
266
|
-
// TODO
|
|
283
|
+
// TODO(#5064): make as enum if used for checks, or type
|
|
267
284
|
id: 'min-length',
|
|
268
285
|
title: `Type ${minLength - search.length} more chars to search`,
|
|
269
286
|
isDisabled: true,
|
|
@@ -271,34 +288,37 @@ export const useContextResolver = (): {
|
|
|
271
288
|
];
|
|
272
289
|
}
|
|
273
290
|
try {
|
|
291
|
+
const query$ = provider.queryContext(search);
|
|
274
292
|
return lastValueFrom(
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
293
|
+
query$
|
|
294
|
+
// Run the raw query results through the mapper, falling back to a no-results placeholder
|
|
295
|
+
.pipe(
|
|
296
|
+
map(mapper),
|
|
297
|
+
map((x) =>
|
|
298
|
+
x.length
|
|
299
|
+
? x
|
|
300
|
+
: [
|
|
301
|
+
singleItem({
|
|
302
|
+
// TODO(#5064): make as enum if used for checks, or type
|
|
303
|
+
id: 'no-results',
|
|
304
|
+
title: 'No results found',
|
|
305
|
+
graphic: 'info_circle',
|
|
306
|
+
isDisabled: true,
|
|
307
|
+
}),
|
|
308
|
+
],
|
|
309
|
+
),
|
|
310
|
+
/** handle failures */
|
|
311
|
+
catchError((err) => {
|
|
312
|
+
console.error(
|
|
313
|
+
'PORTAL::ContextResolver',
|
|
314
|
+
`failed to resolve context for query ${search}`,
|
|
315
|
+
err,
|
|
316
|
+
err.cause,
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
return of(processError(err));
|
|
320
|
+
}),
|
|
289
321
|
),
|
|
290
|
-
/** handle failures */
|
|
291
|
-
catchError((err) => {
|
|
292
|
-
console.error(
|
|
293
|
-
'PORTAL::ContextResolver',
|
|
294
|
-
`failed to resolve context for query ${search}`,
|
|
295
|
-
err,
|
|
296
|
-
err.cause,
|
|
297
|
-
);
|
|
298
|
-
|
|
299
|
-
return of(processError(err));
|
|
300
|
-
}),
|
|
301
|
-
),
|
|
302
322
|
);
|
|
303
323
|
/** this should NEVER happen! */
|
|
304
324
|
} catch (e) {
|
package/src/Header.tsx
CHANGED
|
@@ -19,9 +19,9 @@ PersonAvatarElement; // Register the custom element - prevent tree-shaking
|
|
|
19
19
|
|
|
20
20
|
import { PersonSideSheet } from './PersonSideSheet';
|
|
21
21
|
|
|
22
|
-
import { BookmarkSideSheet } from './
|
|
22
|
+
import { BookmarkSideSheet } from './BookmarkSideSheet';
|
|
23
23
|
|
|
24
|
-
import { HeaderActions } from './
|
|
24
|
+
import { HeaderActions } from './HeaderActions';
|
|
25
25
|
|
|
26
26
|
const Styled = {
|
|
27
27
|
Title: styled.div`
|
|
@@ -29,6 +29,7 @@ export const PersonSideSheet = ({ azureId, isOpen, onClose }: PersonSideSheetPro
|
|
|
29
29
|
const [currentSheet, setCurrentSheet] = useState<string>('landing');
|
|
30
30
|
|
|
31
31
|
const Component = useMemo(() => {
|
|
32
|
+
// Pick the sub-sheet component matching the currently navigated-to sheet
|
|
32
33
|
switch (currentSheet) {
|
|
33
34
|
case 'features':
|
|
34
35
|
return FeatureSheetContent;
|
|
@@ -2,7 +2,7 @@ import { useCurrentAppFeatures } from '@equinor/fusion-framework-react/feature-f
|
|
|
2
2
|
|
|
3
3
|
import { Typography, Switch } from '@equinor/eds-core-react';
|
|
4
4
|
|
|
5
|
-
import { Styled } from './
|
|
5
|
+
import { Styled } from './styled';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Feature toggle list for application-level feature flags.
|
|
@@ -2,7 +2,7 @@ import { useFrameworkFeatures } from '@equinor/fusion-framework-react/feature-fl
|
|
|
2
2
|
|
|
3
3
|
import { Typography, Switch } from '@equinor/eds-core-react';
|
|
4
4
|
|
|
5
|
-
import { Styled } from './
|
|
5
|
+
import { Styled } from './styled';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Feature toggle list for portal-level feature flags.
|
package/src/Router.tsx
CHANGED
|
@@ -51,7 +51,6 @@ const Root = () => {
|
|
|
51
51
|
/**
|
|
52
52
|
* Route component that extracts the `appKey` parameter and delegates to {@link AppLoader}.
|
|
53
53
|
*/
|
|
54
|
-
// eslint-disable-next-line react/no-multi-comp
|
|
55
54
|
const AppRoute = () => {
|
|
56
55
|
const { appKey } = useParams();
|
|
57
56
|
return appKey ? <AppLoader appKey={appKey} /> : null;
|
|
@@ -77,7 +76,6 @@ const routes = [
|
|
|
77
76
|
* Renders the application via `FusionRouter`. Observes context changes through
|
|
78
77
|
* {@link useAppContextNavigation} to keep the URL in sync.
|
|
79
78
|
*/
|
|
80
|
-
// eslint-disable-next-line react/no-multi-comp
|
|
81
79
|
export const Router = () => {
|
|
82
80
|
// observe the context changes and navigate when the context changes
|
|
83
81
|
useAppContextNavigation();
|
|
@@ -69,6 +69,7 @@ export const configure = async (config: FrameworkConfigurator) => {
|
|
|
69
69
|
enableNavigation(config, {
|
|
70
70
|
configure: (config) => {
|
|
71
71
|
config.setTelemetry(async (args) => {
|
|
72
|
+
// Only provide telemetry when the telemetry module was actually enabled
|
|
72
73
|
if (args.hasModule('telemetry')) {
|
|
73
74
|
return await args.requireInstance('telemetry');
|
|
74
75
|
}
|
|
@@ -85,8 +86,10 @@ export const configure = async (config: FrameworkConfigurator) => {
|
|
|
85
86
|
|
|
86
87
|
enableAnalytics(config, (builder) => {
|
|
87
88
|
builder.setAdapter('console', async (args) => {
|
|
89
|
+
// Only resolve the feature-flag-gated adapter when the featureFlag module is enabled
|
|
88
90
|
if (args.hasModule('featureFlag')) {
|
|
89
91
|
const featureFlagProvider = await args.requireInstance('featureFlag');
|
|
92
|
+
// Only log analytics to the console when the feature flag is explicitly enabled
|
|
90
93
|
if (featureFlagProvider.getFeature('fusionLogAnalytics')?.enabled) {
|
|
91
94
|
return new ConsoleAnalyticsAdapter();
|
|
92
95
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL search-parameter key used to specify an app version tag.
|
|
3
|
+
*
|
|
4
|
+
* When present in the URL as `?$tag=<value>`, the portal loads that
|
|
5
|
+
* specific version of the application instead of the default.
|
|
6
|
+
*/
|
|
7
|
+
const TAG = '$tag';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Reads the application version tag from the current URL search parameters.
|
|
11
|
+
*
|
|
12
|
+
* @returns The tag string if the `$tag` search parameter is present, otherwise `null`.
|
|
13
|
+
*/
|
|
14
|
+
export const getAppTagFromUrl = (): string | null => {
|
|
15
|
+
const url = new URL(window.location.href);
|
|
16
|
+
return url.searchParams.get(TAG);
|
|
17
|
+
};
|
package/src/main.tsx
CHANGED
|
@@ -6,10 +6,10 @@ import { ThemeProvider, theme } from '@equinor/fusion-react-styles';
|
|
|
6
6
|
import { PeopleResolverProvider } from '@equinor/fusion-framework-react-components-people-provider';
|
|
7
7
|
|
|
8
8
|
import { EquinorLoader } from './EquinorLoader';
|
|
9
|
-
import { configure } from './
|
|
9
|
+
import { configure } from './configure';
|
|
10
10
|
import { Router } from './Router';
|
|
11
11
|
|
|
12
|
-
import fallbackSvg from './resources/
|
|
12
|
+
import fallbackSvg from './resources/svg';
|
|
13
13
|
|
|
14
14
|
/** Fallback avatar image used when a person photo cannot be loaded. */
|
|
15
15
|
const fallbackImage = new Blob([fallbackSvg], { type: 'image/svg+xml' });
|
|
@@ -37,6 +37,7 @@ const generatePathname = (
|
|
|
37
37
|
context?: IContextProvider,
|
|
38
38
|
pathContextId?: string,
|
|
39
39
|
) => {
|
|
40
|
+
// Replace an existing context id segment in the url when one is present
|
|
40
41
|
if (pathContextId) {
|
|
41
42
|
// context id exists in the url, replace it with the new context id
|
|
42
43
|
const pathname =
|
|
@@ -110,6 +111,7 @@ export const useAppContextNavigation = () => {
|
|
|
110
111
|
/** context was cleared */
|
|
111
112
|
if (item === null) {
|
|
112
113
|
console.debug('🌍 Portal:', 'current context was cleared, navigating to root');
|
|
114
|
+
// Prefer the app's own navigation instance when it exists
|
|
113
115
|
if (appNavigation) {
|
|
114
116
|
appNavigation.replace('/');
|
|
115
117
|
} else {
|
package/src/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Generated by genversion.
|
|
2
|
-
export const version = '8.0.
|
|
2
|
+
export const version = '8.0.2';
|