@vuu-ui/vuu-shell 0.5.13 → 0.5.15
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/package.json +10 -14
- package/src/ShellContextProvider.tsx +60 -0
- package/src/app-header/AppHeader.css +6 -0
- package/src/app-header/AppHeader.tsx +33 -0
- package/src/app-header/index.ts +1 -0
- package/src/app-palette/AppPalette.tsx +56 -0
- package/src/app-palette/index.ts +1 -0
- package/src/feature/ErrorBoundary.jsx +31 -0
- package/src/feature/Feature.tsx +52 -0
- package/src/feature/Loader.tsx +2 -0
- package/src/feature/css-module-loader.ts +6 -0
- package/src/feature/index.ts +1 -0
- package/src/get-layout-history.js +12 -0
- package/src/index.ts +5 -0
- package/src/login/LoginPanel.css +24 -0
- package/src/login/LoginPanel.tsx +63 -0
- package/src/login/index.ts +2 -0
- package/src/login/login-utils.ts +21 -0
- package/src/shell.css +15 -0
- package/src/shell.tsx +171 -0
- package/src/shellTypes.ts +18 -0
- package/src/use-force-render.js +6 -0
- package/src/use-layout-config.js +55 -0
- package/src/user-profile/UserPanel.css +26 -0
- package/src/user-profile/UserPanel.tsx +81 -0
- package/src/user-profile/UserProfile.css +3 -0
- package/src/user-profile/UserProfile.tsx +47 -0
- package/src/user-profile/index.ts +1 -0
- package/LICENSE +0 -201
- package/cjs/index.js +0 -2
- package/cjs/index.js.map +0 -7
- package/esm/index.js +0 -2
- package/esm/index.js.map +0 -7
- package/index.css +0 -2
- package/index.css.map +0 -7
package/package.json
CHANGED
|
@@ -1,26 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vuu-ui/vuu-shell",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.15",
|
|
4
4
|
"description": "VUU UI Shell",
|
|
5
|
+
"main": "src/index.ts",
|
|
5
6
|
"author": "heswell",
|
|
6
7
|
"license": "Apache-2.0",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "node ../../scripts/run-build.mjs"
|
|
10
|
+
},
|
|
7
11
|
"peerDependencies": {
|
|
8
12
|
"@salt-ds/core": "1.0.0",
|
|
9
13
|
"@salt-ds/icons": "1.0.0",
|
|
10
14
|
"@heswell/salt-lab": "1.0.0-alpha.0",
|
|
11
|
-
"@vuu-ui/vuu-data": "0.5.
|
|
12
|
-
"@vuu-ui/vuu-layout": "0.5.
|
|
13
|
-
"@vuu-ui/vuu-utils": "0.5.
|
|
15
|
+
"@vuu-ui/vuu-data": "0.5.15",
|
|
16
|
+
"@vuu-ui/vuu-layout": "0.5.15",
|
|
17
|
+
"@vuu-ui/vuu-utils": "0.5.15",
|
|
14
18
|
"classnames": "^2.2.6",
|
|
15
19
|
"react": "^17.0.2",
|
|
16
20
|
"react-dom": "^17.0.2"
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
"cjs",
|
|
20
|
-
"esm",
|
|
21
|
-
"index.css",
|
|
22
|
-
"index.css.map"
|
|
23
|
-
],
|
|
24
|
-
"module": "esm/index.js",
|
|
25
|
-
"main": "cjs/index.js"
|
|
26
|
-
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { MenuRpcResponse } from "@vuu-ui/vuu-data";
|
|
2
|
+
import { ColumnDescriptor } from "@vuu-ui/vuu-datagrid-types";
|
|
3
|
+
import { createContext, ReactElement, ReactNode, useContext } from "react";
|
|
4
|
+
|
|
5
|
+
export interface ShellContextProps {
|
|
6
|
+
getDefaultColumnConfig?: (
|
|
7
|
+
tableName: string,
|
|
8
|
+
columnName: string
|
|
9
|
+
) => Partial<ColumnDescriptor>;
|
|
10
|
+
handleRpcResponse?: (response: MenuRpcResponse) => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const defaultConfig = {};
|
|
14
|
+
|
|
15
|
+
const ShellContext = createContext<ShellContextProps>(defaultConfig);
|
|
16
|
+
|
|
17
|
+
export interface ShellProviderProps {
|
|
18
|
+
children: ReactNode;
|
|
19
|
+
value: ShellContextProps;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const Provider = ({
|
|
23
|
+
children,
|
|
24
|
+
context,
|
|
25
|
+
inheritedContext,
|
|
26
|
+
}: {
|
|
27
|
+
children: ReactNode;
|
|
28
|
+
context?: ShellContextProps;
|
|
29
|
+
inheritedContext?: ShellContextProps;
|
|
30
|
+
}) => {
|
|
31
|
+
// TODO functions provided at multiple levels must be merged
|
|
32
|
+
const mergedContext = {
|
|
33
|
+
...inheritedContext,
|
|
34
|
+
...context,
|
|
35
|
+
};
|
|
36
|
+
return (
|
|
37
|
+
<ShellContext.Provider value={mergedContext}>
|
|
38
|
+
{children}
|
|
39
|
+
</ShellContext.Provider>
|
|
40
|
+
);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const ShellContextProvider = ({
|
|
44
|
+
children,
|
|
45
|
+
value,
|
|
46
|
+
}: ShellProviderProps): ReactElement => {
|
|
47
|
+
return (
|
|
48
|
+
<ShellContext.Consumer>
|
|
49
|
+
{(context) => (
|
|
50
|
+
<Provider context={value} inheritedContext={context}>
|
|
51
|
+
{children}
|
|
52
|
+
</Provider>
|
|
53
|
+
)}
|
|
54
|
+
</ShellContext.Consumer>
|
|
55
|
+
);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const useShellContext = () => {
|
|
59
|
+
return useContext(ShellContext);
|
|
60
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { HTMLAttributes } from "react";
|
|
2
|
+
import { VuuUser } from "../shell";
|
|
3
|
+
import { UserProfile } from "../user-profile";
|
|
4
|
+
import "./AppHeader.css";
|
|
5
|
+
|
|
6
|
+
export interface AppHeaderProps extends HTMLAttributes<HTMLDivElement> {
|
|
7
|
+
layoutId: string;
|
|
8
|
+
loginUrl?: string;
|
|
9
|
+
onNavigate: (id: string) => void;
|
|
10
|
+
user: VuuUser;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const AppHeader = ({
|
|
14
|
+
layoutId,
|
|
15
|
+
loginUrl,
|
|
16
|
+
onNavigate,
|
|
17
|
+
user,
|
|
18
|
+
...htmlAttributes
|
|
19
|
+
}: AppHeaderProps) => {
|
|
20
|
+
return (
|
|
21
|
+
<header className="hwAppHeader" {...htmlAttributes}>
|
|
22
|
+
{/* <ToggleButton onChange={toggleColorScheme}>
|
|
23
|
+
theme
|
|
24
|
+
</ToggleButton> */}
|
|
25
|
+
<UserProfile
|
|
26
|
+
layoutId={layoutId}
|
|
27
|
+
loginUrl={loginUrl}
|
|
28
|
+
onNavigate={onNavigate}
|
|
29
|
+
user={user}
|
|
30
|
+
/>
|
|
31
|
+
</header>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './AppHeader';
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import cx from "classnames";
|
|
3
|
+
import {
|
|
4
|
+
ComponentRegistry,
|
|
5
|
+
isRegistered,
|
|
6
|
+
Palette,
|
|
7
|
+
PaletteItem,
|
|
8
|
+
} from "@vuu-ui/vuu-layout";
|
|
9
|
+
|
|
10
|
+
const getPaletteItems = (config) => {
|
|
11
|
+
const paletteItems = [];
|
|
12
|
+
|
|
13
|
+
config.forEach((configItem) => {
|
|
14
|
+
const { label, items = [] } = configItem;
|
|
15
|
+
paletteItems.push(
|
|
16
|
+
<div key={label} data-header>
|
|
17
|
+
{label}
|
|
18
|
+
</div>
|
|
19
|
+
);
|
|
20
|
+
items.forEach((paletteItem, i) => {
|
|
21
|
+
const { component, type, props, ...args } = paletteItem;
|
|
22
|
+
if (component) {
|
|
23
|
+
paletteItems.push(
|
|
24
|
+
<PaletteItem {...args} key={i}>
|
|
25
|
+
{component}
|
|
26
|
+
</PaletteItem>
|
|
27
|
+
);
|
|
28
|
+
} else if (type && isRegistered(type)) {
|
|
29
|
+
const Component = ComponentRegistry[type];
|
|
30
|
+
paletteItems.push(
|
|
31
|
+
<PaletteItem {...args} key={i}>
|
|
32
|
+
{React.createElement(Component, {
|
|
33
|
+
...props,
|
|
34
|
+
key: i,
|
|
35
|
+
})}
|
|
36
|
+
</PaletteItem>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
return paletteItems;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const AppPalette = ({ className, config, ...props }) => {
|
|
46
|
+
return (
|
|
47
|
+
<Palette
|
|
48
|
+
className={cx("TableList", className)}
|
|
49
|
+
orientation="vertical"
|
|
50
|
+
collapsibleHeaders
|
|
51
|
+
{...props}
|
|
52
|
+
>
|
|
53
|
+
{getPaletteItems(config)}
|
|
54
|
+
</Palette>
|
|
55
|
+
);
|
|
56
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './AppPalette';
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
// TODO
|
|
3
|
+
export class ErrorBoundary extends React.Component {
|
|
4
|
+
constructor(props) {
|
|
5
|
+
super(props);
|
|
6
|
+
this.state = { errorMessage: null };
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
static getDerivedStateFromError(error) {
|
|
10
|
+
// Update state so the next render will show the fallback UI.
|
|
11
|
+
return { errorMessage: error.message };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
componentDidCatch(error, errorInfo) {
|
|
15
|
+
// You can also log the error to an error reporting service
|
|
16
|
+
console.log(error, errorInfo);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
render() {
|
|
20
|
+
if (this.state.errorMessage) {
|
|
21
|
+
return (
|
|
22
|
+
<>
|
|
23
|
+
<h1>Something went wrong.</h1>
|
|
24
|
+
<p>{this.state.errorMessage}</p>
|
|
25
|
+
</>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return this.props.children;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import React, { Suspense } from "react";
|
|
2
|
+
import { registerComponent } from "@vuu-ui/vuu-layout";
|
|
3
|
+
import { ErrorBoundary } from "./ErrorBoundary";
|
|
4
|
+
import { Loader } from "./Loader";
|
|
5
|
+
import { importCSS } from "./css-module-loader";
|
|
6
|
+
|
|
7
|
+
export interface FeatureProps<Params extends object | undefined = undefined> {
|
|
8
|
+
height?: number;
|
|
9
|
+
url: string;
|
|
10
|
+
css?: string;
|
|
11
|
+
width?: number;
|
|
12
|
+
params: Params;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// const RawFeature = <Params extends object | undefined>({
|
|
16
|
+
function RawFeature<Params extends object | undefined>({
|
|
17
|
+
url,
|
|
18
|
+
css,
|
|
19
|
+
params,
|
|
20
|
+
...props
|
|
21
|
+
}: FeatureProps<Params>) {
|
|
22
|
+
if (css) {
|
|
23
|
+
// import(/* @vite-ignore */ css, { assert: { type: "css" } }).then(
|
|
24
|
+
// (cssModule) => {
|
|
25
|
+
// document.adoptedStyleSheets = [
|
|
26
|
+
// ...document.adoptedStyleSheets,
|
|
27
|
+
// cssModule.default,
|
|
28
|
+
// ];
|
|
29
|
+
// }
|
|
30
|
+
// );
|
|
31
|
+
// Polyfill until vite build supports import assertions
|
|
32
|
+
// Note: already fully supported in esbuild, so vite dev
|
|
33
|
+
importCSS(css).then((styleSheet) => {
|
|
34
|
+
document.adoptedStyleSheets = [
|
|
35
|
+
...document.adoptedStyleSheets,
|
|
36
|
+
styleSheet,
|
|
37
|
+
];
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
const LazyFeature = React.lazy(() => import(/* @vite-ignore */ url));
|
|
41
|
+
return (
|
|
42
|
+
<ErrorBoundary>
|
|
43
|
+
<Suspense fallback={<Loader />}>
|
|
44
|
+
<LazyFeature {...props} {...params} />
|
|
45
|
+
</Suspense>
|
|
46
|
+
</ErrorBoundary>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const Feature = React.memo(RawFeature);
|
|
51
|
+
Feature.displayName = "Feature";
|
|
52
|
+
registerComponent("Feature", Feature, "view");
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Feature';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const getLayoutHistory = async (user) => {
|
|
2
|
+
const history = await fetch(`api/vui/${user.username}`, {})
|
|
3
|
+
.then((response) => {
|
|
4
|
+
return response.ok ? response.json() : null;
|
|
5
|
+
})
|
|
6
|
+
.catch(() => {
|
|
7
|
+
// TODO we should set a layout with a warning here
|
|
8
|
+
console.log(`error getting history`);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
return history;
|
|
12
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
.vuuLoginPanel {
|
|
2
|
+
--hwTextInput-border: solid 1px #ccc;
|
|
3
|
+
--hwTextInput-height: 28px;
|
|
4
|
+
--hwTextInput-padding: 0 12px;
|
|
5
|
+
--hwTextInput-width: 100%;
|
|
6
|
+
--login-row-height: 60px;
|
|
7
|
+
align-content: center;
|
|
8
|
+
align-items: center;
|
|
9
|
+
border: solid 1px lightgray;
|
|
10
|
+
display: flex;
|
|
11
|
+
flex-direction: column;
|
|
12
|
+
gap: 24px;
|
|
13
|
+
justify-content: center;
|
|
14
|
+
justify-items: center;
|
|
15
|
+
margin: 0 auto;
|
|
16
|
+
padding: 48px 48px 24px 48px;
|
|
17
|
+
width: fit-content;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.vuuLoginPanel-login {
|
|
21
|
+
grid-column: 2/3;
|
|
22
|
+
align-self: end;
|
|
23
|
+
justify-self: end;
|
|
24
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { ChangeEvent, HTMLAttributes, useState } from "react";
|
|
2
|
+
import { Button } from "@salt-ds/core";
|
|
3
|
+
import { FormField, Input } from "@heswell/salt-lab";
|
|
4
|
+
|
|
5
|
+
import "./LoginPanel.css";
|
|
6
|
+
|
|
7
|
+
const classBase = "vuuLoginPanel";
|
|
8
|
+
|
|
9
|
+
export interface LoginPanelProps
|
|
10
|
+
extends Omit<HTMLAttributes<HTMLDivElement>, "onSubmit"> {
|
|
11
|
+
onSubmit: (username: string, password: string) => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const LoginPanel = ({ onSubmit }: LoginPanelProps) => {
|
|
15
|
+
const [username, setUserName] = useState("");
|
|
16
|
+
const [password, setPassword] = useState("");
|
|
17
|
+
|
|
18
|
+
const login = () => {
|
|
19
|
+
onSubmit(username, password);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const handleUsername = (
|
|
23
|
+
_event: ChangeEvent<HTMLInputElement>,
|
|
24
|
+
value: string
|
|
25
|
+
) => {
|
|
26
|
+
setUserName(value);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const handlePassword = (
|
|
30
|
+
_event: ChangeEvent<HTMLInputElement>,
|
|
31
|
+
value: string
|
|
32
|
+
) => {
|
|
33
|
+
setPassword(value);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const dataIsValid = username.trim() !== "" && password.trim() !== "";
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div className={classBase}>
|
|
40
|
+
<FormField label="Username" style={{ width: 200 }}>
|
|
41
|
+
<Input value={username} id="text-username" onChange={handleUsername} />
|
|
42
|
+
</FormField>
|
|
43
|
+
|
|
44
|
+
<FormField label="Password" style={{ width: 200 }}>
|
|
45
|
+
<Input
|
|
46
|
+
type="password"
|
|
47
|
+
value={password}
|
|
48
|
+
id="text-password"
|
|
49
|
+
onChange={handlePassword}
|
|
50
|
+
/>
|
|
51
|
+
</FormField>
|
|
52
|
+
|
|
53
|
+
<Button
|
|
54
|
+
className={`${classBase}-login`}
|
|
55
|
+
disabled={!dataIsValid}
|
|
56
|
+
onClick={login}
|
|
57
|
+
variant="cta"
|
|
58
|
+
>
|
|
59
|
+
Login
|
|
60
|
+
</Button>
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const getCookieValue = (name: string) =>
|
|
2
|
+
document.cookie
|
|
3
|
+
.split("; ")
|
|
4
|
+
.find((row) => row.startsWith(`${name}=`))
|
|
5
|
+
?.split("=")[1];
|
|
6
|
+
|
|
7
|
+
export const getAuthDetailsFromCookies = () => {
|
|
8
|
+
const username = getCookieValue("vuu-username");
|
|
9
|
+
const token = getCookieValue("vuu-auth-token");
|
|
10
|
+
return [username, token];
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const redirectToLogin = (loginUrl = "/login.html") => {
|
|
14
|
+
window.location.href = loginUrl;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const logout = (loginUrl?: string) => {
|
|
18
|
+
document.cookie = "vuu-username= ; expires = Thu, 01 Jan 1970 00:00:00 GMT";
|
|
19
|
+
document.cookie = "vuu-auth-token= ; expires = Thu, 01 Jan 1970 00:00:00 GMT";
|
|
20
|
+
redirectToLogin(loginUrl);
|
|
21
|
+
};
|
package/src/shell.css
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
.vuuShell {
|
|
2
|
+
background-color: var(--salt-container-primary-background, ivory);
|
|
3
|
+
height: var(--vuuShell-height, 100vh);
|
|
4
|
+
width: var(--vuuShell-width, 100vw);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.vuuShell-palette {
|
|
8
|
+
--vuuView-border: none;
|
|
9
|
+
--vuuView-margin: 0;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.vuuShell-warningPlaceholder {
|
|
13
|
+
background-color: var(--salt-container-background-high);
|
|
14
|
+
height: 100%;
|
|
15
|
+
}
|
package/src/shell.tsx
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { connectToServer } from "@vuu-ui/vuu-data";
|
|
2
|
+
import {
|
|
3
|
+
HTMLAttributes,
|
|
4
|
+
MouseEvent,
|
|
5
|
+
ReactElement,
|
|
6
|
+
ReactNode,
|
|
7
|
+
useCallback,
|
|
8
|
+
useEffect,
|
|
9
|
+
useRef,
|
|
10
|
+
useState,
|
|
11
|
+
} from "react";
|
|
12
|
+
import useLayoutConfig from "./use-layout-config";
|
|
13
|
+
import { ShellContextProvider } from "./ShellContextProvider";
|
|
14
|
+
import cx from "classnames";
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
Chest,
|
|
18
|
+
DraggableLayout,
|
|
19
|
+
Drawer,
|
|
20
|
+
FlexboxLayout as Flexbox,
|
|
21
|
+
LayoutProvider,
|
|
22
|
+
View,
|
|
23
|
+
} from "@vuu-ui/vuu-layout";
|
|
24
|
+
|
|
25
|
+
import { AppHeader } from "./app-header";
|
|
26
|
+
// import { AppPalette } from "./app-palette";
|
|
27
|
+
|
|
28
|
+
import { LayoutJSON } from "@vuu-ui/vuu-layout/src/layout-reducer";
|
|
29
|
+
import "./shell.css";
|
|
30
|
+
|
|
31
|
+
export type VuuUser = {
|
|
32
|
+
username: string;
|
|
33
|
+
token: string;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const warningLayout = {
|
|
37
|
+
type: "View",
|
|
38
|
+
props: {
|
|
39
|
+
style: { height: "calc(100% - 6px)" },
|
|
40
|
+
},
|
|
41
|
+
children: [
|
|
42
|
+
{
|
|
43
|
+
props: {
|
|
44
|
+
className: "vuuShell-warningPlaceholder",
|
|
45
|
+
},
|
|
46
|
+
type: "Placeholder",
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export interface ShellProps extends HTMLAttributes<HTMLDivElement> {
|
|
52
|
+
children?: ReactNode;
|
|
53
|
+
defaultLayout?: LayoutJSON;
|
|
54
|
+
leftSidePanel?: ReactElement;
|
|
55
|
+
loginUrl?: string;
|
|
56
|
+
// paletteConfig: any;
|
|
57
|
+
serverUrl?: string;
|
|
58
|
+
user: VuuUser;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const Shell = ({
|
|
62
|
+
children,
|
|
63
|
+
className,
|
|
64
|
+
defaultLayout = warningLayout,
|
|
65
|
+
leftSidePanel,
|
|
66
|
+
loginUrl,
|
|
67
|
+
serverUrl,
|
|
68
|
+
user,
|
|
69
|
+
...htmlAttributes
|
|
70
|
+
}: ShellProps) => {
|
|
71
|
+
const paletteView = useRef<HTMLDivElement>(null);
|
|
72
|
+
const [open, setOpen] = useState(false);
|
|
73
|
+
const layoutId = useRef("latest");
|
|
74
|
+
|
|
75
|
+
const [layout, setLayoutConfig, loadLayoutById] = useLayoutConfig(
|
|
76
|
+
user,
|
|
77
|
+
defaultLayout
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const handleLayoutChange = useCallback(
|
|
81
|
+
(layout) => {
|
|
82
|
+
setLayoutConfig(layout);
|
|
83
|
+
},
|
|
84
|
+
[setLayoutConfig]
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const handleDrawerClick = (e: MouseEvent<HTMLElement>) => {
|
|
88
|
+
const target = e.target as HTMLElement;
|
|
89
|
+
if (!paletteView.current?.contains(target)) {
|
|
90
|
+
setOpen(!open);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const handleNavigate = useCallback(
|
|
95
|
+
(id) => {
|
|
96
|
+
layoutId.current = id;
|
|
97
|
+
loadLayoutById(id);
|
|
98
|
+
},
|
|
99
|
+
[loadLayoutById]
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
if (serverUrl && user.token) {
|
|
104
|
+
connectToServer(serverUrl, user.token);
|
|
105
|
+
}
|
|
106
|
+
}, [serverUrl, user.token]);
|
|
107
|
+
|
|
108
|
+
const getDrawers = () => {
|
|
109
|
+
const drawers: ReactElement[] = [];
|
|
110
|
+
if (leftSidePanel) {
|
|
111
|
+
drawers.push(
|
|
112
|
+
<Drawer
|
|
113
|
+
key="left-panel"
|
|
114
|
+
onClick={handleDrawerClick}
|
|
115
|
+
open={open}
|
|
116
|
+
position="left"
|
|
117
|
+
inline
|
|
118
|
+
peekaboo
|
|
119
|
+
sizeOpen={200}
|
|
120
|
+
toggleButton="end"
|
|
121
|
+
>
|
|
122
|
+
<View
|
|
123
|
+
className="vuuShell-palette"
|
|
124
|
+
id="vw-app-palette"
|
|
125
|
+
key="app-palette"
|
|
126
|
+
ref={paletteView}
|
|
127
|
+
style={{ height: "100%" }}
|
|
128
|
+
>
|
|
129
|
+
{leftSidePanel}
|
|
130
|
+
</View>
|
|
131
|
+
</Drawer>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return drawers;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
return (
|
|
139
|
+
// ShellContext TBD
|
|
140
|
+
<ShellContextProvider value={undefined}>
|
|
141
|
+
<LayoutProvider layout={layout} onLayoutChange={handleLayoutChange}>
|
|
142
|
+
<DraggableLayout
|
|
143
|
+
className={cx("vuuShell", className)}
|
|
144
|
+
{...htmlAttributes}
|
|
145
|
+
>
|
|
146
|
+
<Flexbox
|
|
147
|
+
className="App"
|
|
148
|
+
style={{ flexDirection: "column", height: "100%", width: "100%" }}
|
|
149
|
+
>
|
|
150
|
+
<AppHeader
|
|
151
|
+
layoutId={layoutId.current}
|
|
152
|
+
loginUrl={loginUrl}
|
|
153
|
+
user={user}
|
|
154
|
+
onNavigate={handleNavigate}
|
|
155
|
+
/>
|
|
156
|
+
<Chest style={{ flex: 1 }}>
|
|
157
|
+
{getDrawers().concat(
|
|
158
|
+
<DraggableLayout
|
|
159
|
+
dropTarget
|
|
160
|
+
key="main-content"
|
|
161
|
+
style={{ width: "100%", height: "100%" }}
|
|
162
|
+
/>
|
|
163
|
+
)}
|
|
164
|
+
</Chest>
|
|
165
|
+
</Flexbox>
|
|
166
|
+
</DraggableLayout>
|
|
167
|
+
</LayoutProvider>
|
|
168
|
+
{children}
|
|
169
|
+
</ShellContextProvider>
|
|
170
|
+
);
|
|
171
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
declare global {
|
|
2
|
+
const vuuConfig: Promise<VuuConfig>;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export interface FeatureConfig {
|
|
6
|
+
name: string;
|
|
7
|
+
title: string;
|
|
8
|
+
url: string;
|
|
9
|
+
css?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type Features = {
|
|
13
|
+
[key: string]: FeatureConfig;
|
|
14
|
+
};
|
|
15
|
+
export interface VuuConfig {
|
|
16
|
+
features?: Features;
|
|
17
|
+
websocketUrl: string;
|
|
18
|
+
}
|