@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
package/dev-server.ts
DELETED
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import dotenv from 'dotenv';
|
|
2
|
-
import { createDevServer, processServices } from '@equinor/fusion-framework-dev-server';
|
|
3
|
-
|
|
4
|
-
import { name } from './package.json' with { type: 'json' };
|
|
5
|
-
|
|
6
|
-
dotenv.config();
|
|
7
|
-
|
|
8
|
-
const clientId = process.env.FUSION_MSAL_CLIENT_ID;
|
|
9
|
-
const tenantId = process.env.FUSION_MSAL_TENANT_ID;
|
|
10
|
-
const serviceDiscoveryUrl = process.env.FUSION_SERVICE_DISCOVERY_URL;
|
|
11
|
-
const serviceScopes = JSON.parse(process.env.FUSION_SERVICE_SCOPE || '[]') as string[];
|
|
12
|
-
|
|
13
|
-
// Fail fast when any required MSAL/service-discovery env var is missing
|
|
14
|
-
if (!clientId) {
|
|
15
|
-
throw new Error('FUSION_MSAL_CLIENT_ID is not set');
|
|
16
|
-
}
|
|
17
|
-
// tenantId is equally required for MSAL configuration
|
|
18
|
-
if (!tenantId) {
|
|
19
|
-
throw new Error('FUSION_MSAL_TENANT_ID is not set');
|
|
20
|
-
}
|
|
21
|
-
// Service discovery URL is required to resolve backend service URIs
|
|
22
|
-
if (!serviceDiscoveryUrl) {
|
|
23
|
-
throw new Error('SERVICE_DISCOVERY_URL is not set');
|
|
24
|
-
}
|
|
25
|
-
// At least one service scope must be configured for auth requests
|
|
26
|
-
if (serviceScopes.length === 0) {
|
|
27
|
-
throw new Error('SERVICE_SCOPES is not set');
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
(async () => {
|
|
31
|
-
const devServer = await createDevServer({
|
|
32
|
-
spa: {
|
|
33
|
-
templateEnv: {
|
|
34
|
-
portal: {
|
|
35
|
-
id: name,
|
|
36
|
-
},
|
|
37
|
-
title: 'Fusion SPA',
|
|
38
|
-
serviceDiscovery: {
|
|
39
|
-
url: '/@fusion-services',
|
|
40
|
-
scopes: serviceScopes,
|
|
41
|
-
},
|
|
42
|
-
msal: {
|
|
43
|
-
clientId,
|
|
44
|
-
tenantId,
|
|
45
|
-
redirectUri: '/authentication/login-callback',
|
|
46
|
-
requiresAuth: 'true',
|
|
47
|
-
},
|
|
48
|
-
serviceWorker: {
|
|
49
|
-
resources: [
|
|
50
|
-
{
|
|
51
|
-
url: '/apps-proxy',
|
|
52
|
-
rewrite: '/@fusion-api/apps',
|
|
53
|
-
scopes: serviceScopes,
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
url: '/@fusion-api/apps',
|
|
57
|
-
scopes: serviceScopes,
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
url: '/help-proxy',
|
|
61
|
-
rewrite: '/@fusion-api/help',
|
|
62
|
-
scopes: serviceScopes,
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
url: '/portal-proxy',
|
|
66
|
-
rewrite: '/@fusion-api/portals',
|
|
67
|
-
scopes: serviceScopes,
|
|
68
|
-
},
|
|
69
|
-
],
|
|
70
|
-
},
|
|
71
|
-
},
|
|
72
|
-
},
|
|
73
|
-
api: {
|
|
74
|
-
serviceDiscoveryUrl,
|
|
75
|
-
processServices: (dataResponse, route) => {
|
|
76
|
-
const { data, routes } = processServices(dataResponse, route);
|
|
77
|
-
return {
|
|
78
|
-
data: data.concat({
|
|
79
|
-
key: 'portals',
|
|
80
|
-
name: 'Portal Service - MOCK',
|
|
81
|
-
uri: '/@fusion-api/portals',
|
|
82
|
-
}),
|
|
83
|
-
routes,
|
|
84
|
-
};
|
|
85
|
-
},
|
|
86
|
-
routes: [
|
|
87
|
-
{
|
|
88
|
-
match: `/portals/portals/${name}{@:tag}`,
|
|
89
|
-
middleware: async (req, res) => {
|
|
90
|
-
const url = new URL('/src/main.tsx', req.headers.referer);
|
|
91
|
-
res.writeHead(200, {
|
|
92
|
-
'content-type': 'application/json',
|
|
93
|
-
});
|
|
94
|
-
res.end(
|
|
95
|
-
JSON.stringify({
|
|
96
|
-
build: {
|
|
97
|
-
config: {},
|
|
98
|
-
entrypoint: url,
|
|
99
|
-
},
|
|
100
|
-
}),
|
|
101
|
-
);
|
|
102
|
-
},
|
|
103
|
-
},
|
|
104
|
-
],
|
|
105
|
-
},
|
|
106
|
-
});
|
|
107
|
-
await devServer.listen();
|
|
108
|
-
devServer.printUrls();
|
|
109
|
-
})();
|
package/src/AppLoader.tsx
DELETED
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
-
|
|
3
|
-
import { Subscription } from 'rxjs';
|
|
4
|
-
import { last } from 'rxjs/operators';
|
|
5
|
-
|
|
6
|
-
import { useFramework } from '@equinor/fusion-framework-react';
|
|
7
|
-
|
|
8
|
-
import { useObservableState } from '@equinor/fusion-observable/react';
|
|
9
|
-
|
|
10
|
-
import { AppManifestError } from '@equinor/fusion-framework-module-app/errors.js';
|
|
11
|
-
|
|
12
|
-
import { ErrorViewer } from './ErrorViewer';
|
|
13
|
-
import type { AppModule } from '@equinor/fusion-framework-module-app';
|
|
14
|
-
import EquinorLoader from './EquinorLoader';
|
|
15
|
-
import { getAppTagFromUrl } from './get-app-tag-from-url';
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Loads, initializes, and mounts a Fusion application by its key.
|
|
19
|
-
*
|
|
20
|
-
* Sets the current app on the framework's app module, observes initialization
|
|
21
|
-
* progress, and renders the app's script output into a private DOM element.
|
|
22
|
-
* Displays a loading spinner while the app initializes and an error view if
|
|
23
|
-
* manifest resolution or initialization fails.
|
|
24
|
-
*
|
|
25
|
-
* @param props.appKey - Unique key identifying the Fusion application to load.
|
|
26
|
-
*/
|
|
27
|
-
export const AppLoader = (props: { readonly appKey: string }) => {
|
|
28
|
-
const { appKey } = props;
|
|
29
|
-
const fusion = useFramework<[AppModule]>();
|
|
30
|
-
|
|
31
|
-
/** reference of application section/container */
|
|
32
|
-
const ref = useRef<HTMLElement>(null);
|
|
33
|
-
|
|
34
|
-
const [loading, setLoading] = useState<boolean>(false);
|
|
35
|
-
const [error, setError] = useState<Error | undefined>();
|
|
36
|
-
|
|
37
|
-
// TODO(#5087): change to `useCurrentApp`
|
|
38
|
-
/** observe and use the current selected application from framework */
|
|
39
|
-
const { value: currentApp } = useObservableState(
|
|
40
|
-
useMemo(() => fusion.modules.app.current$, [fusion.modules.app]),
|
|
41
|
-
);
|
|
42
|
-
|
|
43
|
-
useEffect(() => {
|
|
44
|
-
const tag = getAppTagFromUrl();
|
|
45
|
-
|
|
46
|
-
// A `$tag` URL param takes precedence over the plain appKey
|
|
47
|
-
if (tag) {
|
|
48
|
-
fusion.modules.app.setCurrentApp({ appKey, tag });
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
/** when appKey property change, assign it to current */
|
|
52
|
-
fusion.modules.app.setCurrentApp(appKey);
|
|
53
|
-
}, [appKey, fusion]);
|
|
54
|
-
|
|
55
|
-
useEffect(() => {
|
|
56
|
-
/** flag that application is loading */
|
|
57
|
-
setLoading(true);
|
|
58
|
-
|
|
59
|
-
/** clear previous errors */
|
|
60
|
-
setError(undefined);
|
|
61
|
-
|
|
62
|
-
/** create a teardown of load */
|
|
63
|
-
const subscription = new Subscription();
|
|
64
|
-
|
|
65
|
-
/** make sure that initialize is canceled and disposed if current app changes */
|
|
66
|
-
subscription.add(
|
|
67
|
-
currentApp
|
|
68
|
-
?.initialize()
|
|
69
|
-
.pipe(last())
|
|
70
|
-
.subscribe({
|
|
71
|
-
next: ({ manifest, script, config }) => {
|
|
72
|
-
/** generate basename for application */
|
|
73
|
-
const [basename] = window.location.pathname.match(/\/?apps\/[a-z|-]+(\/)?/g) ?? [''];
|
|
74
|
-
|
|
75
|
-
/** create a 'private' element for the application */
|
|
76
|
-
const el = document.createElement('div');
|
|
77
|
-
// Guard against rendering before the mounting ref has attached
|
|
78
|
-
if (!ref.current) {
|
|
79
|
-
throw Error('Missing application mounting point');
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
ref.current.appendChild(el);
|
|
83
|
-
|
|
84
|
-
/** extract render callback function from javascript module */
|
|
85
|
-
const render = script.renderApp ?? script.default;
|
|
86
|
-
|
|
87
|
-
/** add application teardown to current render effect teardown */
|
|
88
|
-
subscription.add(render(el, { fusion, env: { basename, config, manifest } }));
|
|
89
|
-
|
|
90
|
-
/** remove app element when application unmounts */
|
|
91
|
-
subscription.add(() => el.remove());
|
|
92
|
-
},
|
|
93
|
-
complete: () => {
|
|
94
|
-
/** flag that application is no longer loading */
|
|
95
|
-
setLoading(false);
|
|
96
|
-
},
|
|
97
|
-
error: (err) => {
|
|
98
|
-
/** set error if initialization of application fails */
|
|
99
|
-
setError(err);
|
|
100
|
-
},
|
|
101
|
-
}),
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
/** teardown application when hook unmounts */
|
|
105
|
-
return () => subscription.unsubscribe();
|
|
106
|
-
}, [fusion, currentApp]);
|
|
107
|
-
|
|
108
|
-
// Render an error view when initialization or manifest resolution failed
|
|
109
|
-
if (error) {
|
|
110
|
-
// Render a dedicated view for manifest-resolution failures
|
|
111
|
-
if (error.cause instanceof AppManifestError) {
|
|
112
|
-
return (
|
|
113
|
-
<div>
|
|
114
|
-
<h2>🔥 Failed to load application manifest 🤬</h2>
|
|
115
|
-
<h3>{error.cause.type}</h3>
|
|
116
|
-
<ErrorViewer error={error} />
|
|
117
|
-
</div>
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
return (
|
|
121
|
-
<div>
|
|
122
|
-
<h2>🔥 Failed to load application 🤬</h2>
|
|
123
|
-
<ErrorViewer error={error} />
|
|
124
|
-
</div>
|
|
125
|
-
);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
return (
|
|
129
|
-
<section id="app-section" ref={ref} style={{ display: 'contents' }}>
|
|
130
|
-
{loading && <EquinorLoader text="Loading Application" />}
|
|
131
|
-
</section>
|
|
132
|
-
);
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
export default AppLoader;
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { Button, Icon } from '@equinor/eds-core-react';
|
|
2
|
-
import { Bookmark } from '@equinor/fusion-framework-react-components-bookmark';
|
|
3
|
-
import { useBookmarkComponentContext } from '@equinor/fusion-framework-react-components-bookmark';
|
|
4
|
-
import { SideSheet } from '@equinor/fusion-react-side-sheet';
|
|
5
|
-
|
|
6
|
-
/** Props for the {@link BookmarkSideSheet} component. */
|
|
7
|
-
type BookmarkSideSheetProps = {
|
|
8
|
-
/** Whether the side sheet is currently visible. */
|
|
9
|
-
readonly isOpen: boolean;
|
|
10
|
-
/** Callback invoked when the user dismisses the side sheet. */
|
|
11
|
-
readonly onClose: VoidFunction;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Side sheet overlay for browsing and creating application bookmarks.
|
|
16
|
-
*
|
|
17
|
-
* Wraps the `Bookmark` component from `@equinor/fusion-framework-react-components-bookmark`
|
|
18
|
-
* in a dismissible side sheet with an "Add Bookmark" action button.
|
|
19
|
-
*
|
|
20
|
-
* @param props - {@link BookmarkSideSheetProps}
|
|
21
|
-
*/
|
|
22
|
-
export const BookmarkSideSheet = ({ isOpen, onClose }: BookmarkSideSheetProps) => {
|
|
23
|
-
const { provider, showCreateBookmark } = useBookmarkComponentContext();
|
|
24
|
-
|
|
25
|
-
return (
|
|
26
|
-
<SideSheet isOpen={isOpen} onClose={onClose} isDismissable={true} enableFullscreen={true}>
|
|
27
|
-
<SideSheet.Indicator color={'#258800'} />
|
|
28
|
-
<SideSheet.Title title="Bookmarks" />
|
|
29
|
-
<SideSheet.SubTitle subTitle={'Application bookmarks'} />
|
|
30
|
-
<SideSheet.Actions>
|
|
31
|
-
<Button
|
|
32
|
-
disabled={!provider?.canCreateBookmarks}
|
|
33
|
-
variant="ghost"
|
|
34
|
-
onClick={() => {
|
|
35
|
-
showCreateBookmark();
|
|
36
|
-
onClose();
|
|
37
|
-
}}
|
|
38
|
-
>
|
|
39
|
-
<Icon name="add" /> Add Bookmark
|
|
40
|
-
</Button>
|
|
41
|
-
</SideSheet.Actions>
|
|
42
|
-
<SideSheet.Content>
|
|
43
|
-
<Bookmark />
|
|
44
|
-
</SideSheet.Content>
|
|
45
|
-
</SideSheet>
|
|
46
|
-
);
|
|
47
|
-
};
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { useCallback, useEffect, useId, useMemo, type ReactElement } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
ContextProvider,
|
|
4
|
-
ContextSearch,
|
|
5
|
-
type ContextSearchProps,
|
|
6
|
-
type ContextSelectEvent,
|
|
7
|
-
ContextClearEvent,
|
|
8
|
-
} from '@equinor/fusion-react-context-selector';
|
|
9
|
-
import { useContextResolver } from './useContextResolver';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Context selector component wired to the current application's context module.
|
|
13
|
-
*
|
|
14
|
-
* Renders a search input with dropdown results from the Fusion context service.
|
|
15
|
-
* When the user selects a context item, it is set as the current context on the
|
|
16
|
-
* application's context provider. Clearing the selector resets the current context.
|
|
17
|
-
*
|
|
18
|
-
* @see {@link https://equinor.github.io/fusion-react-components/?path=/docs/data-contextselector--component | ContextSelector Storybook}
|
|
19
|
-
* @param props - Passthrough props for the underlying `ContextSearch` component.
|
|
20
|
-
* @returns The context selector element, or `null` if no context resolver is available.
|
|
21
|
-
*/
|
|
22
|
-
export const ContextSelector = (props: ContextSearchProps): ReactElement | null => {
|
|
23
|
-
const contextSelectorId = useId();
|
|
24
|
-
const {
|
|
25
|
-
resolver,
|
|
26
|
-
provider,
|
|
27
|
-
currentContext: [selectedContextItem],
|
|
28
|
-
} = useContextResolver();
|
|
29
|
-
|
|
30
|
-
/** callback handler for context selector, when context is changed or cleared */
|
|
31
|
-
const onContextSelect = useCallback(
|
|
32
|
-
(e: Event | ContextSelectEvent) => {
|
|
33
|
-
// Only react to selection/clear events when a provider is available to update
|
|
34
|
-
if (provider) {
|
|
35
|
-
// 'select' carries a chosen context item; anything else means the selector was cleared
|
|
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.
|
|
39
|
-
const ev = e as unknown as ContextSelectEvent;
|
|
40
|
-
// Only set a new context when the user actually chose an item
|
|
41
|
-
if (ev.nativeEvent.detail.selected.length) {
|
|
42
|
-
provider.contextClient.setCurrentContext(ev.nativeEvent.detail.selected[0].id);
|
|
43
|
-
}
|
|
44
|
-
} else {
|
|
45
|
-
provider.clearCurrentContext();
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
},
|
|
49
|
-
[provider],
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Clears context when ctx has been cleared outside the selector.
|
|
54
|
-
*/
|
|
55
|
-
const clearEvent = useMemo(() => new ContextClearEvent({ date: Date.now() }), []);
|
|
56
|
-
useEffect(() => {
|
|
57
|
-
// Notify listeners only when the context was cleared outside this selector
|
|
58
|
-
if (!selectedContextItem) {
|
|
59
|
-
document.dispatchEvent(clearEvent);
|
|
60
|
-
}
|
|
61
|
-
}, [clearEvent, selectedContextItem]);
|
|
62
|
-
|
|
63
|
-
// Nothing to render until a context resolver has been resolved
|
|
64
|
-
if (!resolver) return null;
|
|
65
|
-
|
|
66
|
-
return (
|
|
67
|
-
<div style={{ flex: 1, maxWidth: '480px' }}>
|
|
68
|
-
<ContextProvider resolver={resolver}>
|
|
69
|
-
<ContextSearch
|
|
70
|
-
id={contextSelectorId}
|
|
71
|
-
placeholder={props.placeholder ?? 'Search for context'}
|
|
72
|
-
initialText={props.initialText ?? 'Start typing to search'}
|
|
73
|
-
dropdownHeight={props.dropdownHeight ?? '300px'}
|
|
74
|
-
variant={props.variant ?? 'header'}
|
|
75
|
-
onSelect={(e: ContextSelectEvent) => onContextSelect(e)}
|
|
76
|
-
selectTextOnFocus={true}
|
|
77
|
-
previewItem={selectedContextItem}
|
|
78
|
-
onClearContext={onContextSelect}
|
|
79
|
-
/>
|
|
80
|
-
</ContextProvider>
|
|
81
|
-
</div>
|
|
82
|
-
);
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
export default ContextSelector;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { ContextSelector } from './ContextSelector';
|