@equinor/fusion-framework-dev-portal 11.0.3 → 11.0.5
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/dist/main.js +14779 -14675
- package/package.json +48 -43
- package/CHANGELOG.md +0 -1070
- package/dev-server.ts +0 -109
- package/src/AppLoader.tsx +0 -135
- package/src/BookmarkSideSheet.tsx +0 -47
- package/src/ContextSelector/ContextSelector.tsx +0 -85
- package/src/ContextSelector/index.ts +0 -1
- package/src/ContextSelector/useContextResolver.ts +0 -337
- package/src/EquinorLoader.tsx +0 -33
- package/src/ErrorViewer.tsx +0 -26
- package/src/FusionLogo.tsx +0 -76
- package/src/Header.tsx +0 -94
- package/src/HeaderActions.tsx +0 -47
- package/src/PersonSideSheet/index.tsx +0 -65
- package/src/PersonSideSheet/sheets/FeatureSheetContent.tsx +0 -52
- package/src/PersonSideSheet/sheets/FeatureTogglerApp.tsx +0 -39
- package/src/PersonSideSheet/sheets/FeatureTogglerPortal.tsx +0 -33
- package/src/PersonSideSheet/sheets/LandingSheetContent.tsx +0 -64
- package/src/PersonSideSheet/sheets/index.ts +0 -3
- package/src/PersonSideSheet/sheets/roles/ClaimableRole.test.tsx +0 -83
- package/src/PersonSideSheet/sheets/roles/ClaimableRole.tsx +0 -229
- package/src/PersonSideSheet/sheets/roles/RolesApi.test.ts +0 -62
- package/src/PersonSideSheet/sheets/roles/RolesApi.ts +0 -112
- package/src/PersonSideSheet/sheets/roles/RolesSheetContent.test.tsx +0 -86
- package/src/PersonSideSheet/sheets/roles/RolesSheetContent.tsx +0 -207
- package/src/PersonSideSheet/sheets/roles/index.ts +0 -1
- package/src/PersonSideSheet/sheets/styled.tsx +0 -33
- package/src/PersonSideSheet/sheets/types.ts +0 -14
- package/src/Router.tsx +0 -83
- package/src/configure.ts +0 -186
- package/src/get-app-tag-from-url.ts +0 -17
- package/src/globals.d.ts +0 -32
- package/src/main.tsx +0 -43
- package/src/resources/svg.ts +0 -7
- package/src/version.ts +0 -2
- package/tsconfig.json +0 -79
- package/tsconfig.tsbuildinfo +0 -1
- package/vite.config.ts +0 -15
- package/vitest.config.ts +0 -12
|
@@ -1,337 +0,0 @@
|
|
|
1
|
-
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
2
|
-
import { useFramework } from '@equinor/fusion-framework-react';
|
|
3
|
-
import { useCurrentApp } from '@equinor/fusion-framework-react/app';
|
|
4
|
-
import type { AppModule } from '@equinor/fusion-framework-module-app';
|
|
5
|
-
import type { NavigationModule } from '@equinor/fusion-framework-module-navigation';
|
|
6
|
-
import type {
|
|
7
|
-
ContextItem,
|
|
8
|
-
ContextModule,
|
|
9
|
-
IContextProvider,
|
|
10
|
-
} from '@equinor/fusion-framework-module-context';
|
|
11
|
-
import { useObservableState, useObservableSubscription } from '@equinor/fusion-observable/react';
|
|
12
|
-
import '@equinor/fusion-framework-app';
|
|
13
|
-
import { ChipElement } from '@equinor/fusion-wc-chip';
|
|
14
|
-
ChipElement;
|
|
15
|
-
|
|
16
|
-
import { EMPTY, catchError, lastValueFrom, map, of } from 'rxjs';
|
|
17
|
-
|
|
18
|
-
import type {
|
|
19
|
-
ContextResult,
|
|
20
|
-
ContextResultItem,
|
|
21
|
-
ContextResolver,
|
|
22
|
-
} from '@equinor/fusion-react-context-selector';
|
|
23
|
-
import type { AppModulesInstance } from '@equinor/fusion-framework-app';
|
|
24
|
-
import type { QueryClientError } from '@equinor/fusion-query/client';
|
|
25
|
-
import type { FusionContextSearchError } from '@equinor/fusion-framework-module-context/errors.js';
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Capitalizes the first letter of a string and lowercases the rest.
|
|
29
|
-
*
|
|
30
|
-
* @param string - The input string to capitalize.
|
|
31
|
-
* @returns The capitalized string.
|
|
32
|
-
*/
|
|
33
|
-
function capitalizeFirstLetter(string: string): string {
|
|
34
|
-
return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase();
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Converts a {@link ContextItem} graphic field to the shape expected by `ContextResultItem`.
|
|
39
|
-
*
|
|
40
|
-
* @param graphic - The graphic value from a context item (string, SVG object, or undefined).
|
|
41
|
-
* @returns An object with `graphic` and `graphicType` properties, or an empty object.
|
|
42
|
-
*/
|
|
43
|
-
function convertGraphic(
|
|
44
|
-
graphic: ContextItem['graphic'],
|
|
45
|
-
): Pick<ContextResultItem, 'graphic' | 'graphicType'> {
|
|
46
|
-
// No graphic provided means nothing to render
|
|
47
|
-
if (graphic === undefined) {
|
|
48
|
-
return {};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// A string graphic is either an inline SVG/HTML markup or an EDS icon name
|
|
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'];
|
|
55
|
-
return {
|
|
56
|
-
graphicType: graphic.startsWith('<') ? 'inline-html' : edsGraphicType,
|
|
57
|
-
graphic: graphic,
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// A structured SVG graphic object maps directly to the inline-svg type
|
|
62
|
-
if (graphic.type === 'svg') {
|
|
63
|
-
return {
|
|
64
|
-
graphicType: 'inline-svg',
|
|
65
|
-
graphic: graphic.content,
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return {
|
|
70
|
-
graphicType: 'inline-html',
|
|
71
|
-
graphic: graphic.content,
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Converts a {@link ContextItem} meta field to the shape expected by `ContextResultItem`.
|
|
77
|
-
*
|
|
78
|
-
* @param meta - The meta value from a context item (string, SVG object, or undefined).
|
|
79
|
-
* @returns An object with `meta` and `metaType` properties, or an empty object.
|
|
80
|
-
*/
|
|
81
|
-
function convertMeta(meta: ContextItem['meta']): Pick<ContextResultItem, 'metaType' | 'meta'> {
|
|
82
|
-
// No meta provided means nothing to render
|
|
83
|
-
if (meta === undefined) {
|
|
84
|
-
return {};
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// A string meta is either an inline SVG/HTML markup or an EDS icon name
|
|
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'];
|
|
91
|
-
return {
|
|
92
|
-
metaType: meta.startsWith('<') ? 'inline-html' : edsMetaType,
|
|
93
|
-
meta: meta,
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// A structured SVG meta object maps directly to the inline-svg type
|
|
98
|
-
if (meta.type === 'svg') {
|
|
99
|
-
return {
|
|
100
|
-
metaType: 'inline-svg',
|
|
101
|
-
meta: meta.content,
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return {
|
|
106
|
-
metaType: 'inline-html',
|
|
107
|
-
meta: meta.content,
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Maps an array of context items to `ContextResult` for the context selector dropdown.
|
|
113
|
-
*
|
|
114
|
-
* Applies custom rendering for `EquinorTask` (shows inactive state chip) and
|
|
115
|
-
* `OrgChart` (shows list icon and inactive state chip) context types.
|
|
116
|
-
*
|
|
117
|
-
* @param src - Array of context items from the context query.
|
|
118
|
-
* @returns Mapped array of `ContextResultItem` objects for the selector UI.
|
|
119
|
-
*/
|
|
120
|
-
const mapper = (src: ContextItem<{ taskState?: string; state?: string }>[]): ContextResult => {
|
|
121
|
-
// Map each raw context item to its ContextResultItem shape, applying per-type overrides
|
|
122
|
-
return src.map((i) => {
|
|
123
|
-
// Layer the base fields with graphic and meta overrides derived from the item
|
|
124
|
-
const baseResult = {
|
|
125
|
-
id: i.id,
|
|
126
|
-
title: i.title,
|
|
127
|
-
subTitle: i.subTitle ?? i.type.id,
|
|
128
|
-
...convertGraphic(i.graphic),
|
|
129
|
-
...convertMeta(i.meta),
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
// Displays the status of the EquinorTask if it is not 'active'
|
|
133
|
-
const isEquinorTaskInactive = !!(
|
|
134
|
-
i.value.taskState && i.value.taskState.toLowerCase() !== 'active'
|
|
135
|
-
);
|
|
136
|
-
// Only override the meta chip for EquinorTask items that are inactive
|
|
137
|
-
if (i.type.id === 'EquinorTask' && isEquinorTaskInactive) {
|
|
138
|
-
baseResult.meta = `<fwc-chip disabled variant="outlined" value="${i.value.taskState}" />`;
|
|
139
|
-
baseResult.metaType = 'inline-html';
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// OrgChart items always get a fixed icon plus an optional inactive-state chip
|
|
143
|
-
if (i.type.id === 'OrgChart') {
|
|
144
|
-
// Org charts should always have 'list' icon
|
|
145
|
-
baseResult.graphic = 'list';
|
|
146
|
-
// 'eds' isn't part of the ContextResultItem['graphicType'] union upstream, so cast explicitly
|
|
147
|
-
baseResult.graphicType = 'eds' as unknown as ContextResultItem['graphicType'];
|
|
148
|
-
|
|
149
|
-
// Displays the org chart status if it is not 'active'
|
|
150
|
-
const isOrgChartInactive = !!(i.value.state && i.value.state.toLowerCase() !== 'active');
|
|
151
|
-
// Only override the meta chip when the org chart is inactive
|
|
152
|
-
if (isOrgChartInactive) {
|
|
153
|
-
baseResult.meta = `<fwc-chip disabled variant="outlined" value="${capitalizeFirstLetter(i.value.state ?? '')}" />`;
|
|
154
|
-
baseResult.metaType = 'inline-html';
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
return baseResult;
|
|
159
|
-
});
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Creates a single `ContextResultItem` with sensible defaults.
|
|
164
|
-
*
|
|
165
|
-
* Used to generate placeholder or error entries in the context selector dropdown.
|
|
166
|
-
*
|
|
167
|
-
* @param props - Partial properties to merge into the default item shape.
|
|
168
|
-
* @returns A complete `ContextResultItem` with defaults for `id` and `title`.
|
|
169
|
-
*/
|
|
170
|
-
const singleItem = (props: Partial<ContextResultItem>): ContextResultItem => {
|
|
171
|
-
// Layer the caller-supplied props on top of the placeholder defaults
|
|
172
|
-
return Object.assign({ id: 'no-such-item', title: 'Change me' }, props);
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Hook that creates a context resolver, tracks the current context provider,
|
|
177
|
-
* and provides the currently selected context for the {@link ContextSelector}.
|
|
178
|
-
*
|
|
179
|
-
* Observes the current application's module instances. When the app exposes a
|
|
180
|
-
* context module, the hook wires up a search resolver that queries context items
|
|
181
|
-
* and maps results to `ContextResultItem`. It also handles error display and
|
|
182
|
-
* minimum query length enforcement.
|
|
183
|
-
*
|
|
184
|
-
* @see {@link https://equinor.github.io/fusion-react-components/?path=/docs/data-contextselector--component | ContextSelector Storybook}
|
|
185
|
-
* @returns An object containing the `resolver` for the selector, the current `provider`, and `currentContext` items.
|
|
186
|
-
*/
|
|
187
|
-
export const useContextResolver = (): {
|
|
188
|
-
resolver: ContextResolver | null;
|
|
189
|
-
provider: IContextProvider | null;
|
|
190
|
-
currentContext: ContextResult;
|
|
191
|
-
} => {
|
|
192
|
-
/* Framework modules */
|
|
193
|
-
const framework = useFramework<[AppModule, NavigationModule]>();
|
|
194
|
-
|
|
195
|
-
const { currentApp } = useCurrentApp();
|
|
196
|
-
|
|
197
|
-
/** App module collection instance */
|
|
198
|
-
const instance$ = useMemo(() => currentApp?.instance$ || EMPTY, [currentApp]);
|
|
199
|
-
|
|
200
|
-
/* context provider state */
|
|
201
|
-
const [provider, setProvider] = useState<IContextProvider | null>(null);
|
|
202
|
-
|
|
203
|
-
/* Current context observable */
|
|
204
|
-
const { value: currentContext } = useObservableState(
|
|
205
|
-
useMemo(() => provider?.currentContext$ || EMPTY, [provider]),
|
|
206
|
-
);
|
|
207
|
-
|
|
208
|
-
const preselected: ContextResult = useMemo(() => {
|
|
209
|
-
return currentContext ? mapper([currentContext]) : [];
|
|
210
|
-
}, [currentContext]);
|
|
211
|
-
|
|
212
|
-
/** callback function when current app instance changes */
|
|
213
|
-
const onContextProviderChange = useCallback((modules: AppModulesInstance) => {
|
|
214
|
-
/** try to get the context module from the app module instance */
|
|
215
|
-
const contextProvider = (modules as AppModulesInstance<[ContextModule]>).context;
|
|
216
|
-
// Only set a provider when the loaded app actually exposes a context module
|
|
217
|
-
if (contextProvider) {
|
|
218
|
-
setProvider(contextProvider);
|
|
219
|
-
} else {
|
|
220
|
-
setProvider(null);
|
|
221
|
-
}
|
|
222
|
-
}, []);
|
|
223
|
-
|
|
224
|
-
/** clear the app provider */
|
|
225
|
-
const clearContextProvider = useCallback(() => {
|
|
226
|
-
setProvider(null);
|
|
227
|
-
}, []);
|
|
228
|
-
|
|
229
|
-
/** observe changes to app modules and clear / set the context provider on change */
|
|
230
|
-
useObservableSubscription(instance$, onContextProviderChange, clearContextProvider);
|
|
231
|
-
useEffect(
|
|
232
|
-
() =>
|
|
233
|
-
framework.modules.event.addEventListener('onReactAppLoaded', (e) => {
|
|
234
|
-
console.debug('useContextResolver::onReactAppLoaded', 'using legacy register hack method');
|
|
235
|
-
return onContextProviderChange(e.detail.modules);
|
|
236
|
-
}),
|
|
237
|
-
[framework, onContextProviderChange],
|
|
238
|
-
);
|
|
239
|
-
|
|
240
|
-
const processError = useCallback((err: Error): ContextResult => {
|
|
241
|
-
// Unwrap query-client errors to get at the underlying cause
|
|
242
|
-
if (err.name === 'QueryClientError') {
|
|
243
|
-
return processError((err as QueryClientError).cause as Error);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// Render context-search errors with their own title/description
|
|
247
|
-
if (err.name === 'FusionContextSearchError') {
|
|
248
|
-
const error = err as FusionContextSearchError;
|
|
249
|
-
return [
|
|
250
|
-
singleItem({
|
|
251
|
-
id: error.name,
|
|
252
|
-
title: error.title,
|
|
253
|
-
subTitle: error.description,
|
|
254
|
-
graphic: 'error_outlined',
|
|
255
|
-
isDisabled: true,
|
|
256
|
-
}),
|
|
257
|
-
];
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
return [
|
|
261
|
-
singleItem({
|
|
262
|
-
title: 'Unexpected error occurred',
|
|
263
|
-
subTitle: 'Please try again or report the issue in Services@Equinor',
|
|
264
|
-
graphic: 'error_outlined',
|
|
265
|
-
isDisabled: true,
|
|
266
|
-
}),
|
|
267
|
-
];
|
|
268
|
-
}, []);
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* set resolver for ContextSelector
|
|
272
|
-
* @return ContextResolver
|
|
273
|
-
*/
|
|
274
|
-
const minLength = 2;
|
|
275
|
-
const resolver = useMemo(
|
|
276
|
-
(): ContextResolver | null =>
|
|
277
|
-
provider && {
|
|
278
|
-
searchQuery: async (search: string): Promise<ContextResult> => {
|
|
279
|
-
// Avoid firing a search query until the minimum character threshold is met
|
|
280
|
-
if (search.length < minLength) {
|
|
281
|
-
return [
|
|
282
|
-
singleItem({
|
|
283
|
-
// TODO(#5064): make as enum if used for checks, or type
|
|
284
|
-
id: 'min-length',
|
|
285
|
-
title: `Type ${minLength - search.length} more chars to search`,
|
|
286
|
-
isDisabled: true,
|
|
287
|
-
}),
|
|
288
|
-
];
|
|
289
|
-
}
|
|
290
|
-
try {
|
|
291
|
-
const query$ = provider.queryContext(search);
|
|
292
|
-
return lastValueFrom(
|
|
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
|
-
}),
|
|
321
|
-
),
|
|
322
|
-
);
|
|
323
|
-
/** this should NEVER happen! */
|
|
324
|
-
} catch (e) {
|
|
325
|
-
const err = e as Error;
|
|
326
|
-
console.error('PORTAL::ContextResolver', `unhandled error for [${search}]`, e);
|
|
327
|
-
return processError(err);
|
|
328
|
-
}
|
|
329
|
-
},
|
|
330
|
-
initialResult: preselected,
|
|
331
|
-
},
|
|
332
|
-
[provider, preselected, processError],
|
|
333
|
-
);
|
|
334
|
-
return { resolver, provider, currentContext: preselected };
|
|
335
|
-
};
|
|
336
|
-
|
|
337
|
-
export default useContextResolver;
|
package/src/EquinorLoader.tsx
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import type React from 'react';
|
|
2
|
-
import { StarProgress } from '@equinor/fusion-react-progress-indicator';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Full-viewport loading indicator displaying the Equinor star spinner.
|
|
6
|
-
*
|
|
7
|
-
* Used as a fallback while the Fusion Framework or an application is initializing.
|
|
8
|
-
*
|
|
9
|
-
* @param props.text - Status message displayed below the spinner.
|
|
10
|
-
* @param props.children - Optional additional content rendered inside the spinner.
|
|
11
|
-
* @returns A centered full-screen loading overlay.
|
|
12
|
-
*/
|
|
13
|
-
export const EquinorLoader = ({
|
|
14
|
-
children,
|
|
15
|
-
text,
|
|
16
|
-
}: React.PropsWithChildren<{ readonly text: string }>): React.ReactElement => {
|
|
17
|
-
return (
|
|
18
|
-
<div
|
|
19
|
-
style={{
|
|
20
|
-
display: 'flex',
|
|
21
|
-
justifyContent: 'center',
|
|
22
|
-
alignItems: 'center',
|
|
23
|
-
width: '100vw',
|
|
24
|
-
height: '100vh',
|
|
25
|
-
overflow: 'hidden',
|
|
26
|
-
}}
|
|
27
|
-
>
|
|
28
|
-
<StarProgress text={text}>{children}</StarProgress>
|
|
29
|
-
</div>
|
|
30
|
-
);
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
export default EquinorLoader;
|
package/src/ErrorViewer.tsx
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { Typography } from '@equinor/eds-core-react';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Recursively renders an error and its causal chain.
|
|
5
|
-
*
|
|
6
|
-
* Displays the error message and stack trace for each error in the `cause`
|
|
7
|
-
* chain, providing full visibility into nested failures during app loading.
|
|
8
|
-
*
|
|
9
|
-
* @param props.error - The error to display, including any nested `cause` errors.
|
|
10
|
-
* @returns A bordered section showing the error message, stack trace, and any nested causes.
|
|
11
|
-
*/
|
|
12
|
-
export const ErrorViewer = ({ error }: { readonly error: Error }) => {
|
|
13
|
-
return (
|
|
14
|
-
<>
|
|
15
|
-
<div style={{ marginTop: 20, border: '1px solid' }}>
|
|
16
|
-
<Typography variant="h4" color="warning">
|
|
17
|
-
{error.message}
|
|
18
|
-
</Typography>
|
|
19
|
-
<section style={{ padding: 10 }}>{error.stack && <pre>{error.stack}</pre>}</section>
|
|
20
|
-
</div>
|
|
21
|
-
{error.cause && <ErrorViewer error={error.cause as Error} />}
|
|
22
|
-
</>
|
|
23
|
-
);
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
export default ErrorViewer;
|
package/src/FusionLogo.tsx
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { useId } from 'react';
|
|
2
|
-
import type { SVGProps } from 'react';
|
|
3
|
-
|
|
4
|
-
/** Props for the {@link FusionLogo} component. */
|
|
5
|
-
type FusionLogoProps = Omit<SVGProps<SVGSVGElement>, 'viewBox'> & {
|
|
6
|
-
/** Uniform scale multiplier applied via CSS transform. Defaults to `1`. */
|
|
7
|
-
readonly scale?: number;
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Inline SVG rendering of the Fusion logo.
|
|
12
|
-
*
|
|
13
|
-
* Uses unique gradient IDs per instance so multiple logos can coexist on the
|
|
14
|
-
* same page without gradient collisions.
|
|
15
|
-
*
|
|
16
|
-
* @param props.scale - Scale multiplier for the logo size.
|
|
17
|
-
* @param props.style - Additional inline styles merged with the computed transform.
|
|
18
|
-
* @returns An inline SVG element sized to `1em` height.
|
|
19
|
-
*/
|
|
20
|
-
export const FusionLogo = ({ scale = 1, style }: FusionLogoProps) => {
|
|
21
|
-
const paint0Id = useId();
|
|
22
|
-
const paint1Id = useId();
|
|
23
|
-
|
|
24
|
-
return (
|
|
25
|
-
<svg viewBox="0 0 50 35" style={{ height: '1em', ...style, transform: `scale(${scale})` }}>
|
|
26
|
-
<title>Fusion Logo</title>
|
|
27
|
-
<path
|
|
28
|
-
d="M0 2V23.1776L7.05405 16.1235V7.05405H16.1235L23.1776 0H2C0.895431 0 0 0.89543 0 2Z"
|
|
29
|
-
transform="translate(50 17.5) scale(0.92727 1.06779) rotate(135)"
|
|
30
|
-
fill={`url(#${paint0Id})`}
|
|
31
|
-
/>
|
|
32
|
-
<path
|
|
33
|
-
d="M0 2V23.1776L7.05405 16.1235V7.05405H16.1235L23.1776 0H2C0.895431 0 0 0.89543 0 2Z"
|
|
34
|
-
transform="translate(0 17.5) scale(0.92727 1.06779) rotate(-45)"
|
|
35
|
-
fill={`url(#${paint1Id})`}
|
|
36
|
-
/>
|
|
37
|
-
<path
|
|
38
|
-
d="M9.61965 36.6972L2.60087 29.6784L1.96135 22.3809L8.42623 22.9069L9.61965 36.6972Z"
|
|
39
|
-
transform="translate(33.8887 34.9863) scale(0.92727 -1.06779) rotate(45)"
|
|
40
|
-
fill="#990025"
|
|
41
|
-
/>
|
|
42
|
-
<path
|
|
43
|
-
d="M7.05434 7.05434L0 0L1.21096 13.8183L7.68846 14.3818L7.05434 7.05434Z"
|
|
44
|
-
transform="translate(33.8887 34.9863) scale(0.92727 -1.06779) rotate(45)"
|
|
45
|
-
fill="#990025"
|
|
46
|
-
/>
|
|
47
|
-
<path
|
|
48
|
-
d="M0 0L2.49398 29.5715L9.61965 36.6972L7.01878 7.01878L0 0Z"
|
|
49
|
-
transform="translate(33.8887 0.015625) scale(0.92727 1.06779) rotate(45)"
|
|
50
|
-
fill="#FF1243"
|
|
51
|
-
/>
|
|
52
|
-
<defs>
|
|
53
|
-
<linearGradient
|
|
54
|
-
id={paint0Id}
|
|
55
|
-
x2="1"
|
|
56
|
-
gradientUnits="userSpaceOnUse"
|
|
57
|
-
gradientTransform="matrix(-13.5478 9.01983 -12.9578 -13.5478 18.0677 6.77391)"
|
|
58
|
-
>
|
|
59
|
-
<stop offset="0.508287" stopColor="#DC002E" />
|
|
60
|
-
<stop offset="0.508387" stopColor="#FF1243" />
|
|
61
|
-
</linearGradient>
|
|
62
|
-
<linearGradient
|
|
63
|
-
id={paint1Id}
|
|
64
|
-
x2="1"
|
|
65
|
-
gradientUnits="userSpaceOnUse"
|
|
66
|
-
gradientTransform="matrix(-13.5478 9.01983 -12.9578 -13.5478 18.0677 6.77391)"
|
|
67
|
-
>
|
|
68
|
-
<stop offset="0.508287" stopColor="#DC002E" />
|
|
69
|
-
<stop offset="0.508387" stopColor="#FF1243" />
|
|
70
|
-
</linearGradient>
|
|
71
|
-
</defs>
|
|
72
|
-
</svg>
|
|
73
|
-
);
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
export default FusionLogo;
|
package/src/Header.tsx
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import { useCallback, useId, useState } from 'react';
|
|
2
|
-
import { ContextSelector } from './ContextSelector';
|
|
3
|
-
import { FusionLogo } from './FusionLogo';
|
|
4
|
-
|
|
5
|
-
import styled from 'styled-components';
|
|
6
|
-
import { add, menu, tag } from '@equinor/eds-icons';
|
|
7
|
-
import { Icon, TopBar } from '@equinor/eds-core-react';
|
|
8
|
-
Icon.add({ menu, add, tag });
|
|
9
|
-
|
|
10
|
-
import { useCurrentUser } from '@equinor/fusion-framework-react/hooks';
|
|
11
|
-
import { useCurrentApp, useCurrentAppModule } from '@equinor/fusion-framework-react/app';
|
|
12
|
-
|
|
13
|
-
import type { BookmarkModule } from '@equinor/fusion-framework-react-module-bookmark';
|
|
14
|
-
|
|
15
|
-
import { BookmarkProvider } from '@equinor/fusion-framework-react-components-bookmark';
|
|
16
|
-
|
|
17
|
-
import PersonAvatarElement from '@equinor/fusion-wc-person/avatar';
|
|
18
|
-
PersonAvatarElement; // Register the custom element - prevent tree-shaking
|
|
19
|
-
|
|
20
|
-
import { PersonSideSheet } from './PersonSideSheet';
|
|
21
|
-
|
|
22
|
-
import { BookmarkSideSheet } from './BookmarkSideSheet';
|
|
23
|
-
|
|
24
|
-
import { HeaderActions } from './HeaderActions';
|
|
25
|
-
|
|
26
|
-
const Styled = {
|
|
27
|
-
Title: styled.div`
|
|
28
|
-
display: flex;
|
|
29
|
-
align-items: center;
|
|
30
|
-
gap: 0.75rem;
|
|
31
|
-
font-size: 1rem;
|
|
32
|
-
font-weight: 500;
|
|
33
|
-
`,
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Portal top bar header containing the Fusion logo, context selector, and action buttons.
|
|
38
|
-
*
|
|
39
|
-
* Composes the bookmark provider with the current app and user so bookmark
|
|
40
|
-
* and person side sheets can operate in context. Provides the sticky top bar
|
|
41
|
-
* layout used across all portal pages.
|
|
42
|
-
*/
|
|
43
|
-
export const Header = () => {
|
|
44
|
-
const currentUser = useCurrentUser();
|
|
45
|
-
const topBarId = useId();
|
|
46
|
-
const [isPersonSheetOpen, setIsPersonSheetOpen] = useState(false);
|
|
47
|
-
|
|
48
|
-
const [isBookmarkOpen, setIsBookmarkOpen] = useState(false);
|
|
49
|
-
const onBookmarkClose = useCallback(() => {
|
|
50
|
-
setIsBookmarkOpen(false);
|
|
51
|
-
}, []);
|
|
52
|
-
|
|
53
|
-
const { currentApp } = useCurrentApp();
|
|
54
|
-
|
|
55
|
-
const { module: bookmarkProvider } = useCurrentAppModule<BookmarkModule>('bookmark');
|
|
56
|
-
|
|
57
|
-
return (
|
|
58
|
-
<BookmarkProvider
|
|
59
|
-
provider={bookmarkProvider ?? undefined}
|
|
60
|
-
currentApp={
|
|
61
|
-
currentApp
|
|
62
|
-
? { appKey: currentApp.appKey, name: currentApp.manifest?.displayName }
|
|
63
|
-
: undefined
|
|
64
|
-
}
|
|
65
|
-
currentUser={
|
|
66
|
-
currentUser ? { id: currentUser.localAccountId, name: currentUser.name } : undefined
|
|
67
|
-
}
|
|
68
|
-
>
|
|
69
|
-
<TopBar id={topBarId} sticky={false} style={{ padding: '0 1em', height: 48 }}>
|
|
70
|
-
<TopBar.Header>
|
|
71
|
-
<Styled.Title>
|
|
72
|
-
<FusionLogo />
|
|
73
|
-
<span>Fusion Framework CLI</span>
|
|
74
|
-
</Styled.Title>
|
|
75
|
-
</TopBar.Header>
|
|
76
|
-
<HeaderActions
|
|
77
|
-
userAzureId={currentUser?.localAccountId}
|
|
78
|
-
toggleBookmark={setIsBookmarkOpen}
|
|
79
|
-
togglePerson={setIsPersonSheetOpen}
|
|
80
|
-
/>
|
|
81
|
-
<TopBar.CustomContent>
|
|
82
|
-
<ContextSelector />
|
|
83
|
-
</TopBar.CustomContent>
|
|
84
|
-
{/* since buttons are 40px but have 48px click bounds */}
|
|
85
|
-
</TopBar>
|
|
86
|
-
<BookmarkSideSheet isOpen={isBookmarkOpen} onClose={onBookmarkClose} />
|
|
87
|
-
<PersonSideSheet
|
|
88
|
-
azureId={currentUser?.localAccountId}
|
|
89
|
-
isOpen={isPersonSheetOpen}
|
|
90
|
-
onClose={() => setIsPersonSheetOpen(!isPersonSheetOpen)}
|
|
91
|
-
/>
|
|
92
|
-
</BookmarkProvider>
|
|
93
|
-
);
|
|
94
|
-
};
|
package/src/HeaderActions.tsx
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { tag } from '@equinor/eds-icons';
|
|
2
|
-
import { Button, Icon, TopBar } from '@equinor/eds-core-react';
|
|
3
|
-
|
|
4
|
-
import PersonAvatarElement from '@equinor/fusion-wc-person/avatar';
|
|
5
|
-
PersonAvatarElement;
|
|
6
|
-
|
|
7
|
-
import { useBookmarkComponentContext } from '@equinor/fusion-framework-react-components-bookmark';
|
|
8
|
-
|
|
9
|
-
/** Props for the {@link HeaderActions} component. */
|
|
10
|
-
interface HeaderActionProps {
|
|
11
|
-
/** Azure AD object ID of the current user, used for the person avatar. */
|
|
12
|
-
readonly userAzureId?: string;
|
|
13
|
-
/** Toggle callback for the bookmark side sheet open/close state. */
|
|
14
|
-
readonly toggleBookmark: (open: (status: boolean) => boolean) => void;
|
|
15
|
-
/** Toggle callback for the person settings side sheet open/close state. */
|
|
16
|
-
readonly togglePerson: (open: (status: boolean) => boolean) => void;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Action buttons displayed in the portal top bar header.
|
|
21
|
-
*
|
|
22
|
-
* Renders a bookmark toggle button (disabled when no bookmark provider is
|
|
23
|
-
* available) and a person-avatar button that opens the user settings sheet.
|
|
24
|
-
*
|
|
25
|
-
* @param props - {@link HeaderActionProps}
|
|
26
|
-
*/
|
|
27
|
-
export const HeaderActions = (props: HeaderActionProps) => {
|
|
28
|
-
const { toggleBookmark, togglePerson, userAzureId } = props;
|
|
29
|
-
|
|
30
|
-
const bookmarkContext = useBookmarkComponentContext();
|
|
31
|
-
|
|
32
|
-
return (
|
|
33
|
-
<TopBar.Actions style={{ minWidth: 48, minHeight: 48 }}>
|
|
34
|
-
<Button
|
|
35
|
-
onClick={() => toggleBookmark((x) => !x)}
|
|
36
|
-
variant="ghost_icon"
|
|
37
|
-
disabled={!bookmarkContext.provider}
|
|
38
|
-
title={bookmarkContext.provider ? 'Bookmarks' : 'Bookmarks not available, enable in app'}
|
|
39
|
-
>
|
|
40
|
-
<Icon data={tag} />
|
|
41
|
-
</Button>
|
|
42
|
-
<Button onClick={() => togglePerson((x) => !x)} variant="ghost_icon">
|
|
43
|
-
<fwc-person-avatar size="small" azureId={userAzureId} clickable={false} />
|
|
44
|
-
</Button>
|
|
45
|
-
</TopBar.Actions>
|
|
46
|
-
);
|
|
47
|
-
};
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { useCallback, useMemo, useState } from 'react';
|
|
2
|
-
import { SideSheet } from '@equinor/fusion-react-side-sheet';
|
|
3
|
-
import PersonListItem from '@equinor/fusion-wc-person/list-item';
|
|
4
|
-
PersonListItem;
|
|
5
|
-
|
|
6
|
-
import { Divider } from '@equinor/eds-core-react';
|
|
7
|
-
|
|
8
|
-
import { LandingSheetContent, FeatureSheetContent, RolesSheetContent } from './sheets';
|
|
9
|
-
|
|
10
|
-
/** Props for the {@link PersonSideSheet} component. */
|
|
11
|
-
type PersonSideSheetProps = {
|
|
12
|
-
/** Azure AD object ID of the user to display in the side sheet. */
|
|
13
|
-
readonly azureId?: string;
|
|
14
|
-
/** Whether the side sheet is currently visible. */
|
|
15
|
-
readonly isOpen: boolean;
|
|
16
|
-
/** Callback invoked when the user dismisses the side sheet. */
|
|
17
|
-
onClose(): void;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Side sheet overlay that displays user settings and feature toggles.
|
|
22
|
-
*
|
|
23
|
-
* Contains a person list item for the current user and navigable sub-sheets
|
|
24
|
-
* for viewing and toggling application and portal feature flags.
|
|
25
|
-
*
|
|
26
|
-
* @param props - {@link PersonSideSheetProps}
|
|
27
|
-
*/
|
|
28
|
-
export const PersonSideSheet = ({ azureId, isOpen, onClose }: PersonSideSheetProps) => {
|
|
29
|
-
const [currentSheet, setCurrentSheet] = useState<string>('landing');
|
|
30
|
-
|
|
31
|
-
const Component = useMemo(() => {
|
|
32
|
-
// Pick the sub-sheet component matching the currently navigated-to sheet
|
|
33
|
-
switch (currentSheet) {
|
|
34
|
-
case 'features':
|
|
35
|
-
return FeatureSheetContent;
|
|
36
|
-
case 'roles':
|
|
37
|
-
return RolesSheetContent;
|
|
38
|
-
default:
|
|
39
|
-
return LandingSheetContent;
|
|
40
|
-
}
|
|
41
|
-
}, [currentSheet]);
|
|
42
|
-
|
|
43
|
-
const navigateCallback = useCallback((sheet: string) => {
|
|
44
|
-
setCurrentSheet(sheet ?? 'landing');
|
|
45
|
-
}, []);
|
|
46
|
-
|
|
47
|
-
return (
|
|
48
|
-
<SideSheet isOpen={isOpen} onClose={onClose} isDismissable={true}>
|
|
49
|
-
<SideSheet.Title title="User settings" />
|
|
50
|
-
<SideSheet.SubTitle subTitle={'Settings for your user in Fusion portal'} />
|
|
51
|
-
<SideSheet.Actions />
|
|
52
|
-
<SideSheet.Content>
|
|
53
|
-
<section style={{ paddingLeft: '0.5em' }}>
|
|
54
|
-
<div>
|
|
55
|
-
<fwc-person-list-item azureId={azureId} />
|
|
56
|
-
</div>
|
|
57
|
-
<Divider />
|
|
58
|
-
<Component azureId={azureId} sheet={currentSheet} navigate={navigateCallback} />
|
|
59
|
-
</section>
|
|
60
|
-
</SideSheet.Content>
|
|
61
|
-
</SideSheet>
|
|
62
|
-
);
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
export default PersonSideSheet;
|