aboutsummaryrefslogtreecommitdiffstats
path: root/sdnr/wt-odlux/odlux/framework/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'sdnr/wt-odlux/odlux/framework/src/components')
-rw-r--r--sdnr/wt-odlux/odlux/framework/src/components/errorDisplay.tsx131
-rw-r--r--sdnr/wt-odlux/odlux/framework/src/components/icons/menuIcon.tsx29
-rw-r--r--sdnr/wt-odlux/odlux/framework/src/components/logo.tsx103
-rw-r--r--sdnr/wt-odlux/odlux/framework/src/components/material-table/columnModel.ts56
-rw-r--r--sdnr/wt-odlux/odlux/framework/src/components/material-table/index.tsx707
-rw-r--r--sdnr/wt-odlux/odlux/framework/src/components/material-table/showColumnDialog.tsx188
-rw-r--r--sdnr/wt-odlux/odlux/framework/src/components/material-table/tableFilter.tsx113
-rw-r--r--sdnr/wt-odlux/odlux/framework/src/components/material-table/tableHead.tsx127
-rw-r--r--sdnr/wt-odlux/odlux/framework/src/components/material-table/tableToolbar.tsx191
-rw-r--r--sdnr/wt-odlux/odlux/framework/src/components/material-table/utilities.ts357
-rw-r--r--sdnr/wt-odlux/odlux/framework/src/components/material-ui/index.ts22
-rw-r--r--sdnr/wt-odlux/odlux/framework/src/components/material-ui/listItemLink.tsx83
-rw-r--r--sdnr/wt-odlux/odlux/framework/src/components/material-ui/loader.tsx49
-rw-r--r--sdnr/wt-odlux/odlux/framework/src/components/material-ui/panel.tsx76
-rw-r--r--sdnr/wt-odlux/odlux/framework/src/components/material-ui/snackDisplay.tsx74
-rw-r--r--sdnr/wt-odlux/odlux/framework/src/components/material-ui/toggleButton.tsx181
-rw-r--r--sdnr/wt-odlux/odlux/framework/src/components/material-ui/toggleButtonGroup.tsx40
-rw-r--r--sdnr/wt-odlux/odlux/framework/src/components/material-ui/treeView.tsx380
-rw-r--r--sdnr/wt-odlux/odlux/framework/src/components/navigationMenu.tsx218
-rw-r--r--sdnr/wt-odlux/odlux/framework/src/components/objectDump/index.tsx205
-rw-r--r--sdnr/wt-odlux/odlux/framework/src/components/routing/appFrame.tsx55
-rw-r--r--sdnr/wt-odlux/odlux/framework/src/components/settings/general.tsx110
-rw-r--r--sdnr/wt-odlux/odlux/framework/src/components/titleBar.tsx233
23 files changed, 3728 insertions, 0 deletions
diff --git a/sdnr/wt-odlux/odlux/framework/src/components/errorDisplay.tsx b/sdnr/wt-odlux/odlux/framework/src/components/errorDisplay.tsx
new file mode 100644
index 000000000..d41d82687
--- /dev/null
+++ b/sdnr/wt-odlux/odlux/framework/src/components/errorDisplay.tsx
@@ -0,0 +1,131 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.
+ * =================================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ * ============LICENSE_END==========================================================================
+ */
+import React from 'react';
+import { Theme } from '@mui/material/styles';
+
+import { WithStyles } from '@mui/styles';
+import withStyles from '@mui/styles/withStyles';
+import createStyles from '@mui/styles/createStyles';
+
+import Modal from '@mui/material/Modal';
+import Button from '@mui/material/Button';
+import Card from '@mui/material/Card';
+import CardActions from '@mui/material/CardActions';
+import CardContent from '@mui/material/CardContent';
+import Typography from '@mui/material/Typography';
+
+import { ClearErrorInfoAction, RemoveErrorInfoAction } from '../actions/errorActions';
+
+import { connect, Connect } from '../flux/connect';
+
+const styles = (theme: Theme) => createStyles({
+ modal: {
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "center",
+ },
+ paper: {
+ width: theme.spacing(50),
+ backgroundColor: theme.palette.background.paper,
+ boxShadow: theme.shadows[5],
+ padding: theme.spacing(4),
+ },
+ card: {
+ minWidth: 275,
+ },
+ bullet: {
+ display: 'inline-block',
+ margin: '0 2px',
+ transform: 'scale(0.8)',
+ },
+ title: {
+ marginBottom: 16,
+ fontSize: 14,
+ },
+ pos: {
+ marginBottom: 12,
+ },
+});
+
+type ErrorDisplayProps = WithStyles<typeof styles> & Connect;
+
+// function getModalStyle() {
+// const top = 50 + rand();
+// const left = 50 + rand();
+
+// return {
+// top: `${ top }%`,
+// left: `${ left }%`,
+// transform: `translate(-${ top }%, -${ left }%)`,
+// };
+// }
+
+/**
+ * Represents a component for formatting and displaying errors.
+ */
+class ErrorDisplayComponent extends React.Component<ErrorDisplayProps> {
+ constructor(props: ErrorDisplayProps) {
+ super(props);
+ }
+
+ render(): JSX.Element {
+ const { classes, state } = this.props;
+ const errorInfo = state.framework.applicationState.errors.length && state.framework.applicationState.errors[state.framework.applicationState.errors.length - 1];
+
+ return (
+ <Modal className={classes.modal}
+ aria-labelledby="simple-modal-title"
+ aria-describedby="simple-modal-description"
+ open={state.framework.applicationState.errors && state.framework.applicationState.errors.length > 0}
+ onClose={() => this.props.dispatch(new ClearErrorInfoAction())}
+ >
+ {errorInfo &&
+ <div className={classes.paper}>
+ <Card className={classes.card}>
+ <CardContent>
+ <Typography className={classes.title} color="textSecondary">
+ {errorInfo.title != null ? errorInfo.title : "Something went wrong."}
+ </Typography>
+ <Typography variant="h5" component="h2">
+ {errorInfo.error && errorInfo.error.toString()}
+ </Typography>
+ <Typography className={classes.pos} color="textSecondary">
+ {errorInfo.message && errorInfo.message.toString()}
+ </Typography>
+ <Typography component="p">
+ {errorInfo.info && errorInfo.info.componentStack && errorInfo.info.componentStack.split('\n').map(line => {
+ return [line, <br />];
+ })}
+ {errorInfo.info && errorInfo.info.extra && errorInfo.info.extra.split('\n').map(line => {
+ return [line, <br />];
+ })}
+ </Typography>
+ </CardContent>
+ <CardActions>
+ <Button color="inherit" size="small" onClick={() => this.props.dispatch(new RemoveErrorInfoAction(errorInfo))} >Close</Button>
+ </CardActions>
+ </Card>
+ </div> || <div></div>
+ }
+ </Modal>
+ );
+ }
+}
+
+export const ErrorDisplay = withStyles(styles)(connect()(ErrorDisplayComponent));
+export default ErrorDisplay; \ No newline at end of file
diff --git a/sdnr/wt-odlux/odlux/framework/src/components/icons/menuIcon.tsx b/sdnr/wt-odlux/odlux/framework/src/components/icons/menuIcon.tsx
new file mode 100644
index 000000000..0d7d734c9
--- /dev/null
+++ b/sdnr/wt-odlux/odlux/framework/src/components/icons/menuIcon.tsx
@@ -0,0 +1,29 @@
+import React from 'react';
+
+type MenuIconPropsBase = {
+ className?: string;
+ size?: number | string;
+};
+
+type MenuIconPropsWithColor = MenuIconPropsBase & {
+ color: string;
+};
+
+type MenuIconProps = MenuIconPropsBase | MenuIconPropsWithColor;
+
+const MenuIcon = (props: MenuIconProps) => {
+ const { className, size = '30px' } = props;
+ const color = 'color' in props ? props.color : '#36A9E1';
+
+ return (
+ <svg className={className} width={size} height={size} viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink">
+ <path fill={color} d="M4,10h24c1.104,0,2-0.896,2-2s-0.896-2-2-2H4C2.896,6,2,6.896,2,8S2.896,10,4,10z" />
+ <path fill={color} d="M28,14H4c-1.104,0-2,0.896-2,2 s0.896,2,2,2h24c1.104,0,2-0.896,2-2S29.104,14,28,14z" />
+ <path fill={color} d="M28,22H4c-1.104,0-2,0.896-2,2s0.896,2,2,2h24c1.104,0,2-0.896,2-2 S29.104,22,28,22z" />
+ </svg>
+ );
+};
+
+MenuIcon.defaultName = 'MenuIcon';
+
+export default MenuIcon; \ No newline at end of file
diff --git a/sdnr/wt-odlux/odlux/framework/src/components/logo.tsx b/sdnr/wt-odlux/odlux/framework/src/components/logo.tsx
new file mode 100644
index 000000000..f2bb1f575
--- /dev/null
+++ b/sdnr/wt-odlux/odlux/framework/src/components/logo.tsx
@@ -0,0 +1,103 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.
+ * =================================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ * ============LICENSE_END==========================================================================
+ */
+/******************************************************************************
+ * Copyright 2018 highstreet technologies GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *****************************************************************************/
+
+import * as React from 'react';
+import { withRouter, RouteComponentProps } from 'react-router-dom';
+
+import { Theme } from '@mui/material/styles'; // infra for styling
+
+
+import { WithStyles } from '@mui/styles';
+import withStyles from '@mui/styles/withStyles';
+import createStyles from '@mui/styles/createStyles';
+
+
+const defaultLogo = require('../assets/icons/ht.Connect.svg');
+
+const styles = (theme: Theme) => createStyles({
+ headerLogo: {
+ backgroundImage: "url(" + (theme.design && theme.design.url || defaultLogo) + ")",
+ backgroundColor: theme.palette.primary.main,
+ backgroundRepeat: "no-repeat",
+ backgroundSize: "auto " + (theme.design && theme.design.logoHeight || 70) + "px",
+ height: theme.design && theme.design.logoHeight || 70,
+ width: theme.design ? theme.design.width / theme.design.height * theme.design.logoHeight : 220
+ }
+});
+
+type LogoProps = RouteComponentProps<{ id: string }> & WithStyles<typeof styles>;
+interface ILogoState {
+ windowWidth: number
+}
+
+class LogoComponent extends React.Component<LogoProps, ILogoState> {
+
+ private hideLogoWhenWindowWidthIsLower: number = 800;
+
+ constructor(props: LogoProps) {
+ super(props);
+ this.state = {
+ windowWidth: 0
+ };
+ this.updateWindowDimensions = this.updateWindowDimensions.bind(this);
+ }
+
+ componentDidMount(): void {
+ this.updateWindowDimensions();
+ window.addEventListener('resize', this.updateWindowDimensions);
+ };
+ componentWillUnmount(): void {
+ window.removeEventListener('resize', this.updateWindowDimensions);
+ };
+ updateWindowDimensions(): void {
+ this.setState({ windowWidth: window.innerWidth });
+ }
+
+ render(): JSX.Element {
+ let div: JSX.Element = <div />;
+ if (this.state.windowWidth >= this.hideLogoWhenWindowWidthIsLower) {
+ div = <div className={this.props.classes.headerLogo} />;
+ } else {
+ console.info([
+ "Logo hidden, because browser window width (",
+ this.state.windowWidth,
+ "px) is lower threshold (",
+ this.hideLogoWhenWindowWidthIsLower,
+ "px)."].join(''));
+ }
+ return div;
+ }
+}
+
+export const Logo = withStyles(styles)(withRouter(LogoComponent));
+export default Logo;
diff --git a/sdnr/wt-odlux/odlux/framework/src/components/material-table/columnModel.ts b/sdnr/wt-odlux/odlux/framework/src/components/material-table/columnModel.ts
new file mode 100644
index 000000000..3ed313497
--- /dev/null
+++ b/sdnr/wt-odlux/odlux/framework/src/components/material-table/columnModel.ts
@@ -0,0 +1,56 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.
+ * =================================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ * ============LICENSE_END==========================================================================
+ */
+
+import * as React from 'react';
+
+export enum ColumnType {
+ text,
+ numeric,
+ boolean,
+ date,
+ custom
+}
+
+type CustomControl<TData> = {
+ className?: string;
+ style?: React.CSSProperties;
+ rowData: TData;
+}
+
+export type ColumnModel<TData> = {
+ title?: string;
+ disablePadding?: boolean;
+ width?: string | number ;
+ className?: string;
+ hide?: boolean;
+ style?: React.CSSProperties;
+ align?: 'inherit' | 'left' | 'center' | 'right' | 'justify';
+ disableSorting?: boolean;
+ disableFilter?: boolean;
+} & ({
+ property: string;
+ type: ColumnType.custom;
+ customControl: React.ComponentType<CustomControl<TData>>;
+} | {
+ property: keyof TData;
+ type: ColumnType.boolean;
+ labels?: { "true": string, "false": string };
+} | {
+ property: keyof TData;
+ type?: ColumnType.numeric | ColumnType.text | ColumnType.date;
+}); \ No newline at end of file
diff --git a/sdnr/wt-odlux/odlux/framework/src/components/material-table/index.tsx b/sdnr/wt-odlux/odlux/framework/src/components/material-table/index.tsx
new file mode 100644
index 000000000..c1a5005d4
--- /dev/null
+++ b/sdnr/wt-odlux/odlux/framework/src/components/material-table/index.tsx
@@ -0,0 +1,707 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.
+ * =================================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ * ============LICENSE_END==========================================================================
+ */
+import * as React from 'react';
+import { Theme } from '@mui/material/styles';
+
+import { WithStyles } from '@mui/styles';
+import withStyles from '@mui/styles/withStyles';
+import createStyles from '@mui/styles/createStyles';
+
+import Table from '@mui/material/Table';
+import TableBody from '@mui/material/TableBody';
+import TableCell from '@mui/material/TableCell';
+import TableContainer from '@mui/material/TableContainer';
+import TablePagination from '@mui/material/TablePagination';
+import TableRow from '@mui/material/TableRow';
+import Paper from '@mui/material/Paper';
+import Checkbox from '@mui/material/Checkbox';
+
+import { TableToolbar } from './tableToolbar';
+import { EnhancedTableHead } from './tableHead';
+import { EnhancedTableFilter } from './tableFilter';
+
+import { ColumnModel, ColumnType } from './columnModel';
+import { Menu, Typography } from '@mui/material';
+import { DistributiveOmit } from '@mui/types';
+
+import makeStyles from '@mui/styles/makeStyles';
+
+import { SvgIconProps } from '@mui/material/SvgIcon';
+
+import { DividerTypeMap } from '@mui/material/Divider';
+import { MenuItemProps } from '@mui/material/MenuItem';
+import { flexbox } from '@mui/system';
+import { RowDisabled } from './utilities';
+import { toAriaLabel } from '../../utilities/yangHelper';
+export { ColumnModel, ColumnType } from './columnModel';
+
+type propType = string | number | null | undefined | (string | number)[];
+type dataType = { [prop: string]: propType };
+type resultType<TData = dataType> = { page: number, total: number, rows: TData[] };
+
+export type DataCallback<TData = dataType> = (page?: number, rowsPerPage?: number, orderBy?: string | null, order?: 'asc' | 'desc' | null, filter?: { [property: string]: string }) => resultType<TData> | Promise<resultType<TData>>;
+
+function regExpEscape(s: string) {
+ return s.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&');
+}
+
+function wildcardCheck(input: string, pattern: string) {
+ if (!pattern) return true;
+ const regex = new RegExp(
+ (!pattern.startsWith('*') ? '^' : '') +
+ pattern.split(/\*+/).map(p => p.split(/\?+/).map(regExpEscape).join('.')).join('.*') +
+ (!pattern.endsWith('*') ? '$' : '')
+ );
+ return input.match(regex) !== null && input.match(regex)!.length >= 1;
+}
+
+function desc(a: dataType, b: dataType, orderBy: string) {
+ if ((b[orderBy] || "") < (a[orderBy] || "")) {
+ return -1;
+ }
+ if ((b[orderBy] || "") > (a[orderBy] || "")) {
+ return 1;
+ }
+ return 0;
+}
+
+function stableSort(array: dataType[], cmp: (a: dataType, b: dataType) => number) {
+ const stabilizedThis = array.map((el, index) => [el, index]) as [dataType, number][];
+ stabilizedThis.sort((a, b) => {
+ const order = cmp(a[0], b[0]);
+ if (order !== 0) return order;
+ return a[1] - b[1];
+ });
+ return stabilizedThis.map(el => el[0]);
+}
+
+function getSorting(order: 'asc' | 'desc' | null, orderBy: string) {
+ return order === 'desc' ? (a: dataType, b: dataType) => desc(a, b, orderBy) : (a: dataType, b: dataType) => -desc(a, b, orderBy);
+}
+
+const styles = (theme: Theme) => createStyles({
+ root: {
+ width: '100%',
+ overflow: "hidden",
+ marginTop: theme.spacing(3),
+ position: "relative",
+ boxSizing: "border-box",
+ display: "flex",
+ flexDirection: "column",
+ },
+ container: {
+ flex: "1 1 100%"
+ },
+ pagination: {
+ overflow: "hidden",
+ minHeight: "52px"
+ }
+});
+
+const useTableRowExtStyles = makeStyles((theme: Theme) => createStyles({
+ disabled: {
+ color: "rgba(180, 180, 180, 0.7)",
+ },
+}));
+
+type GetStatelessComponentProps<T> = T extends (props: infer P & { children?: React.ReactNode }) => any ? P : any;
+type TableRowExtProps = GetStatelessComponentProps<typeof TableRow> & { disabled: boolean };
+const TableRowExt : React.FC<TableRowExtProps> = (props) => {
+ const [disabled, setDisabled] = React.useState(true);
+ const classes = useTableRowExtStyles();
+
+ const onMouseDown = (ev: React.MouseEvent<HTMLElement>) => {
+ if (ev.button ===1){
+ setDisabled(!disabled);
+ ev.preventDefault();
+ ev.stopPropagation();
+ } else if (props.disabled && disabled) {
+ ev.preventDefault();
+ ev.stopPropagation();
+ }
+ };
+
+ return (
+ <TableRow {...{...props, color: props.disabled && disabled ? '#a0a0a0' : undefined , className: props.disabled && disabled ? classes.disabled : '', onMouseDown, onContextMenu: props.disabled && disabled ? onMouseDown : props.onContextMenu } } />
+ );
+};
+
+export type MaterialTableComponentState<TData = {}> = {
+ order: 'asc' | 'desc';
+ orderBy: string | null;
+ selected: any[] | null;
+ rows: TData[];
+ total: number;
+ page: number;
+ rowsPerPage: number;
+ loading: boolean;
+ showFilter: boolean;
+ hiddenColumns: string[];
+ filter: { [property: string]: string };
+};
+
+export type TableApi = { forceRefresh?: () => Promise<void> };
+
+type MaterialTableComponentBaseProps<TData> = WithStyles<typeof styles> & {
+ className?: string;
+ columns: ColumnModel<TData>[];
+ idProperty: keyof TData | ((data: TData) => React.Key);
+
+ //Note: used to save settings as well. Must be unique across apps. Null tableIds will not get saved to the settings
+ tableId: string | null;
+ isPopup?: boolean;
+ title?: string;
+ stickyHeader?: boolean;
+ allowHtmlHeader?: boolean;
+ defaultSortOrder?: 'asc' | 'desc';
+ defaultSortColumn?: keyof TData;
+ enableSelection?: boolean;
+ disableSorting?: boolean;
+ disableFilter?: boolean;
+ customActionButtons?: { icon: React.ComponentType<SvgIconProps>, tooltip?: string, ariaLabel: string, onClick: () => void, disabled?: boolean }[];
+ onHandleClick?(event: React.MouseEvent<HTMLTableRowElement>, rowData: TData): void;
+ createContextMenu?: (row: TData) => React.ReactElement<MenuItemProps | DividerTypeMap<{}, "hr">, React.ComponentType<MenuItemProps | DividerTypeMap<{}, "hr">>>[];
+};
+
+type MaterialTableComponentPropsWithRows<TData = {}> = MaterialTableComponentBaseProps<TData> & { rows: TData[]; asynchronus?: boolean; };
+type MaterialTableComponentPropsWithRequestData<TData = {}> = MaterialTableComponentBaseProps<TData> & { onRequestData: DataCallback; tableApi?: TableApi; };
+type MaterialTableComponentPropsWithExternalState<TData = {}> = MaterialTableComponentBaseProps<TData> & MaterialTableComponentState & {
+ onToggleFilter: () => void;
+ onFilterChanged: (property: string, filterTerm: string) => void;
+ onHandleChangePage: (page: number) => void;
+ onHandleChangeRowsPerPage: (rowsPerPage: number | null) => void;
+ onHandleRequestSort: (property: string) => void;
+ onHideColumns : (columnNames: string[]) => void
+ onShowColumns: (columnNames: string[]) => void
+};
+
+type MaterialTableComponentProps<TData = {}> =
+ MaterialTableComponentPropsWithRows<TData> |
+ MaterialTableComponentPropsWithRequestData<TData> |
+ MaterialTableComponentPropsWithExternalState<TData>;
+
+function isMaterialTableComponentPropsWithRows(props: MaterialTableComponentProps): props is MaterialTableComponentPropsWithRows {
+ return (props as MaterialTableComponentPropsWithRows).rows !== undefined && (props as MaterialTableComponentPropsWithRows).rows instanceof Array;
+}
+
+function isMaterialTableComponentPropsWithRequestData(props: MaterialTableComponentProps): props is MaterialTableComponentPropsWithRequestData {
+ return (props as MaterialTableComponentPropsWithRequestData).onRequestData !== undefined && (props as MaterialTableComponentPropsWithRequestData).onRequestData instanceof Function;
+}
+
+function isMaterialTableComponentPropsWithRowsAndRequestData(props: MaterialTableComponentProps): props is MaterialTableComponentPropsWithExternalState {
+ const propsWithExternalState = (props as MaterialTableComponentPropsWithExternalState)
+ return propsWithExternalState.onFilterChanged instanceof Function ||
+ propsWithExternalState.onHandleChangePage instanceof Function ||
+ propsWithExternalState.onHandleChangeRowsPerPage instanceof Function ||
+ propsWithExternalState.onToggleFilter instanceof Function ||
+ propsWithExternalState.onHideColumns instanceof Function ||
+ propsWithExternalState.onHandleRequestSort instanceof Function
+}
+
+// get settings in here!
+
+
+class MaterialTableComponent<TData extends {} = {}> extends React.Component<MaterialTableComponentProps, MaterialTableComponentState & { contextMenuInfo: { index: number; mouseX?: number; mouseY?: number }; }> {
+
+ constructor(props: MaterialTableComponentProps) {
+ super(props);
+
+
+ const page = isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.page : 0;
+ const rowsPerPage = isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.rowsPerPage || 10 : 10;
+
+ this.state = {
+ contextMenuInfo: { index: -1 },
+ filter: isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.filter || {} : {},
+ showFilter: isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.showFilter : false,
+ loading: isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.loading : false,
+ order: isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.order : this.props.defaultSortOrder || 'asc',
+ orderBy: isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.orderBy : this.props.defaultSortColumn || null,
+ selected: isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.selected : null,
+ rows: isMaterialTableComponentPropsWithRows(this.props) && this.props.rows.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) || [],
+ total: isMaterialTableComponentPropsWithRows(this.props) && this.props.rows.length || 0,
+ hiddenColumns: isMaterialTableComponentPropsWithRowsAndRequestData(this.props) && this.props.hiddenColumns || [],
+ page,
+ rowsPerPage,
+ };
+
+ if (isMaterialTableComponentPropsWithRequestData(this.props)) {
+ this.update();
+
+ if (this.props.tableApi) {
+ this.props.tableApi.forceRefresh = () => this.update();
+ }
+ }
+ }
+ render(): JSX.Element {
+ const { classes, columns, allowHtmlHeader } = this.props;
+ const { rows, total: rowCount, order, orderBy, selected, rowsPerPage, page, showFilter, filter } = this.state;
+ const emptyRows = rowsPerPage - Math.min(rowsPerPage, rowCount - page * rowsPerPage);
+ const getId = typeof this.props.idProperty !== "function" ? (data: TData) => ((data as { [key: string]: any })[this.props.idProperty as any as string] as string | number) : this.props.idProperty;
+ const toggleFilter = isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.onToggleFilter : () => { !this.props.disableFilter && this.setState({ showFilter: !showFilter }, this.update) }
+
+ const hideColumns = isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.onHideColumns : (data: string[]) => { const newArray = [...new Set([...this.state.hiddenColumns, ...data])]; this.setState({hiddenColumns:newArray}); }
+ const showColumns = isMaterialTableComponentPropsWithRowsAndRequestData(this.props) ? this.props.onShowColumns : (data: string[]) => { const newArray = this.state.hiddenColumns.filter(el=> !data.includes(el)); this.setState({hiddenColumns:newArray}); }
+
+ const allColumnsHidden = this.props.columns.length === this.state.hiddenColumns.length;
+ return (
+ <Paper className={this.props.className ? `${classes.root} ${this.props.className}` : classes.root}>
+ <TableContainer className={classes.container}>
+ <TableToolbar tableId={this.props.tableId} numSelected={selected && selected.length} title={this.props.title} customActionButtons={this.props.customActionButtons} onExportToCsv={this.exportToCsv}
+ onToggleFilter={toggleFilter}
+ columns={columns}
+ onHideColumns={hideColumns}
+ onShowColumns={showColumns} />
+ <Table padding="normal" aria-label={this.props.tableId ? this.props.tableId : 'tableTitle'} stickyHeader={this.props.stickyHeader || false} >
+ <EnhancedTableHead
+ allowHtmlHeader={allowHtmlHeader || false}
+ columns={columns}
+ numSelected={selected && selected.length}
+ order={order}
+ orderBy={orderBy}
+ onSelectAllClick={this.handleSelectAllClick}
+ onRequestSort={this.onHandleRequestSort}
+ rowCount={rows.length}
+ enableSelection={this.props.enableSelection}
+ hiddenColumns={this.state.hiddenColumns}
+ />
+ <TableBody>
+ {showFilter && <EnhancedTableFilter columns={columns} hiddenColumns={this.state.hiddenColumns} filter={filter} onFilterChanged={this.onFilterChanged} enableSelection={this.props.enableSelection} /> || null}
+
+ {allColumnsHidden ? <Typography variant="body1" textAlign="center">All columns of this table are hidden.</Typography> :
+
+ rows // may need ordering here
+ .map((entry: TData & { [RowDisabled]?: boolean, [kex: string]: any }, index) => {
+ const entryId = getId(entry);
+ const contextMenu = (this.props.createContextMenu && this.state.contextMenuInfo.index === index && this.props.createContextMenu(entry)) || null;
+ const isSelected = this.isSelected(entryId) || this.state.contextMenuInfo.index === index;
+ return (
+ <TableRowExt
+ hover
+ onClick={event => {
+ if (this.props.createContextMenu) {
+ this.setState({
+ contextMenuInfo: {
+ index: -1
+ }
+ });
+ }
+ this.handleClick(event, entry, entryId);
+ }}
+ onContextMenu={event => {
+ if (this.props.createContextMenu) {
+ event.preventDefault();
+ event.stopPropagation();
+ this.setState({ contextMenuInfo: { index, mouseX: event.clientX - 2, mouseY: event.clientY - 4 } });
+ }
+ }}
+ role="checkbox"
+ aria-checked={isSelected}
+ aria-label="table-row"
+ tabIndex={-1}
+ key={entryId}
+ selected={isSelected}
+ disabled={entry[RowDisabled] || false}
+ >
+ {this.props.enableSelection
+ ? <TableCell padding="checkbox" style={{ width: "50px", color: entry[RowDisabled] || false ? "inherit" : undefined } }>
+ <Checkbox color='secondary' checked={isSelected} />
+ </TableCell>
+ : null
+ }
+ {
+
+ this.props.columns.map(
+ col => {
+ const style = col.width ? { width: col.width } : {};
+ const tableCell = (
+
+ <TableCell style={ entry[RowDisabled] || false ? { ...style, color: "inherit" } : style } aria-label={col.title? toAriaLabel(col.title) : toAriaLabel(col.property)} key={col.property} align={col.type === ColumnType.numeric && !col.align ? "right" : col.align} >
+ {col.type === ColumnType.custom && col.customControl
+ ? <col.customControl className={col.className} style={col.style} rowData={entry} />
+ : col.type === ColumnType.boolean
+ ? <span className={col.className} style={col.style}>{col.labels ? col.labels[entry[col.property] ? "true" : "false"] : String(entry[col.property])}</span>
+ : <span className={col.className} style={col.style}>{String(entry[col.property])}</span>
+ }
+ </TableCell>
+ );
+
+ //show column if...
+ const showColumn = !this.state.hiddenColumns.includes(col.property);
+ return showColumn && tableCell
+ }
+ )
+ }
+ {<Menu open={!!contextMenu} onClose={() => this.setState({ contextMenuInfo: { index: -1 } })} anchorReference="anchorPosition" keepMounted
+ anchorPosition={this.state.contextMenuInfo.mouseY != null && this.state.contextMenuInfo.mouseX != null ? { top: this.state.contextMenuInfo.mouseY, left: this.state.contextMenuInfo.mouseX } : undefined}>
+ {contextMenu}
+ </Menu> || null}
+ </TableRowExt>
+ );
+ })}
+ {emptyRows > 0 && (
+ <TableRow style={{ height: 49 * emptyRows }}>
+ <TableCell colSpan={this.props.columns.length} />
+ </TableRow>
+ )}
+ </TableBody>
+ </Table>
+ </TableContainer>
+ <TablePagination className={classes.pagination}
+ rowsPerPageOptions={[5, 10, 20, 50]}
+ component="div"
+ count={rowCount}
+ rowsPerPage={rowsPerPage}
+ page={page}
+ aria-label={this.props.isPopup ? "popup-table-pagination-footer" : "table-pagination-footer" }
+ backIconButtonProps={{
+ 'aria-label': this.props.isPopup ? 'popup-previous-page' : 'previous-page',
+ }}
+ nextIconButtonProps={{
+ 'aria-label': this.props.isPopup ? 'popup-next-page': 'next-page',
+ }}
+ onPageChange={this.onHandleChangePage}
+ onRowsPerPageChange={this.onHandleChangeRowsPerPage}
+ />
+ </Paper>
+ );
+ }
+
+ static getDerivedStateFromProps(props: MaterialTableComponentProps, state: MaterialTableComponentState & { _rawRows: {}[] }): MaterialTableComponentState & { _rawRows: {}[] } {
+
+ if (isMaterialTableComponentPropsWithRowsAndRequestData(props)) {
+ return {
+ ...state,
+ rows: props.rows,
+ total: props.total,
+ orderBy: props.orderBy,
+ order: props.order,
+ filter: props.filter,
+ loading: props.loading,
+ showFilter: props.showFilter,
+ page: props.page,
+ hiddenColumns: props.hiddenColumns,
+ rowsPerPage: props.rowsPerPage
+ }
+ } else if (isMaterialTableComponentPropsWithRows(props) && props.asynchronus && state._rawRows !== props.rows) {
+ const newState = MaterialTableComponent.updateRows(props, state);
+ return {
+ ...state,
+ ...newState,
+ _rawRows: props.rows || []
+ };
+ }
+ return state;
+ }
+
+ private static updateRows(props: MaterialTableComponentPropsWithRows, state: MaterialTableComponentState): { rows: {}[], total: number, page: number } {
+
+ let data = [...(props.rows as dataType[] || [])];
+ const columns = props.columns;
+
+ const { page, rowsPerPage, order, orderBy, filter } = state;
+
+ try {
+ if (state.showFilter) {
+ Object.keys(filter).forEach(prop => {
+ const column = columns.find(c => c.property === prop);
+ const filterExpression = filter[prop];
+
+ if (!column) throw new Error("Filter for not existing column found.");
+
+ if (filterExpression != null) {
+ data = data.filter((val) => {
+ const dataValue = val[prop];
+
+ if (dataValue != null) {
+
+ if (column.type === ColumnType.boolean) {
+
+ const boolDataValue = JSON.parse(String(dataValue).toLowerCase());
+ const boolFilterExpression = JSON.parse(String(filterExpression).toLowerCase());
+ return boolDataValue == boolFilterExpression;
+
+ } else if (column.type === ColumnType.text) {
+
+ const valueAsString = String(dataValue);
+ const filterExpressionAsString = String(filterExpression).trim();
+ if (filterExpressionAsString.length === 0) return true;
+ return wildcardCheck(valueAsString, filterExpressionAsString);
+
+ } else if (column.type === ColumnType.numeric){
+
+ const valueAsNumber = Number(dataValue);
+ const filterExpressionAsString = String(filterExpression).trim();
+ if (filterExpressionAsString.length === 0 || isNaN(valueAsNumber)) return true;
+
+ if (filterExpressionAsString.startsWith('>=')) {
+ return valueAsNumber >= Number(filterExpressionAsString.substring(2).trim());
+ } else if (filterExpressionAsString.startsWith('<=')) {
+ return valueAsNumber <= Number(filterExpressionAsString.substring(2).trim());
+ } else if (filterExpressionAsString.startsWith('>')) {
+ return valueAsNumber > Number(filterExpressionAsString.substring(1).trim());
+ } else if (filterExpressionAsString.startsWith('<')) {
+ return valueAsNumber < Number(filterExpressionAsString.substring(1).trim());
+ }
+ } else if (column.type === ColumnType.date){
+ const valueAsString = String(dataValue);
+
+ const convertToDate = (valueAsString: string) => {
+ // time value needs to be padded
+ const hasTimeValue = /T\d{2,2}/.test(valueAsString);
+ const indexCollon = valueAsString.indexOf(':');
+ if (hasTimeValue && (indexCollon === -1 || indexCollon >= valueAsString.length-2)) {
+ valueAsString = indexCollon === -1
+ ? valueAsString + ":00"
+ : indexCollon === valueAsString.length-1
+ ? valueAsString + "00"
+ : valueAsString += "0"
+ }
+ return new Date(Date.parse(valueAsString));
+ };
+
+ // @ts-ignore
+ const valueAsDate = new Date(Date.parse(dataValue));
+ const filterExpressionAsString = String(filterExpression).trim();
+
+ if (filterExpressionAsString.startsWith('>=')) {
+ return valueAsDate >= convertToDate(filterExpressionAsString.substring(2).trim());
+ } else if (filterExpressionAsString.startsWith('<=')) {
+ return valueAsDate <= convertToDate(filterExpressionAsString.substring(2).trim());
+ } else if (filterExpressionAsString.startsWith('>')) {
+ return valueAsDate > convertToDate(filterExpressionAsString.substring(1).trim());
+ } else if (filterExpressionAsString.startsWith('<')) {
+ return valueAsDate < convertToDate(filterExpressionAsString.substring(1).trim());
+ }
+
+
+ if (filterExpressionAsString.length === 0) return true;
+ return wildcardCheck(valueAsString, filterExpressionAsString);
+
+ }
+ }
+
+ return (dataValue == filterExpression)
+ });
+ };
+ });
+ }
+
+ const rowCount = data.length;
+
+ if (page > 0 && rowsPerPage * page > rowCount) { //if result is smaller than the currently shown page, new search and repaginate
+ let newPage = Math.floor(rowCount / rowsPerPage);
+ return {
+ rows: data,
+ total: rowCount,
+ page: newPage
+ };
+ } else {
+ data = (orderBy && order
+ ? stableSort(data, getSorting(order, orderBy))
+ : data).slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage);
+
+ return {
+ rows: data,
+ total: rowCount,
+ page: page
+ };
+ }
+
+
+ } catch (e) {
+ console.error(e);
+ return {
+ rows: [],
+ total: 0,
+ page: page
+ }
+ }
+ }
+
+ private async update() {
+ if (isMaterialTableComponentPropsWithRequestData(this.props)) {
+ const response = await Promise.resolve(
+ this.props.onRequestData(
+ this.state.page, this.state.rowsPerPage, this.state.orderBy, this.state.order, this.state.showFilter && this.state.filter || {})
+ );
+ this.setState(response);
+ } else {
+ let updateResult = MaterialTableComponent.updateRows(this.props, this.state);
+ this.setState(updateResult);
+ }
+ }
+
+ private onFilterChanged = (property: string, filterTerm: string) => {
+ if (isMaterialTableComponentPropsWithRowsAndRequestData(this.props)) {
+ this.props.onFilterChanged(property, filterTerm);
+ return;
+ }
+ if (this.props.disableFilter) return;
+ const colDefinition = this.props.columns && this.props.columns.find(col => col.property === property);
+ if (colDefinition && colDefinition.disableFilter) return;
+
+ const filter = { ...this.state.filter, [property]: filterTerm };
+ this.setState({
+ filter
+ }, this.update);
+ };
+
+ private onHandleRequestSort = (event: React.SyntheticEvent, property: string) => {
+ if (isMaterialTableComponentPropsWithRowsAndRequestData(this.props)) {
+ this.props.onHandleRequestSort(property);
+ return;
+ }
+ if (this.props.disableSorting) return;
+ const colDefinition = this.props.columns && this.props.columns.find(col => col.property === property);
+ if (colDefinition && colDefinition.disableSorting) return;
+
+ const orderBy = this.state.orderBy === property && this.state.order === 'desc' ? null : property;
+ const order = this.state.orderBy === property && this.state.order === 'asc' ? 'desc' : 'asc';
+ this.setState({
+ order,
+ orderBy
+ }, this.update);
+ };
+
+ handleSelectAllClick: () => {};
+
+ private onHandleChangePage = (event: any | null, page: number) => {
+ if (isMaterialTableComponentPropsWithRowsAndRequestData(this.props)) {
+ this.props.onHandleChangePage(page);
+ return;
+ }
+ this.setState({
+ page
+ }, this.update);
+ };
+
+ private onHandleChangeRowsPerPage = (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
+ if (isMaterialTableComponentPropsWithRowsAndRequestData(this.props)) {
+ this.props.onHandleChangeRowsPerPage(+(event && event.target.value));
+ return;
+ }
+ const rowsPerPage = +(event && event.target.value);
+ if (rowsPerPage && rowsPerPage > 0) {
+ this.setState({
+ rowsPerPage
+ }, this.update);
+ }
+ };
+
+ private isSelected(id: string | number): boolean {
+ let selected = this.state.selected || [];
+ const selectedIndex = selected.indexOf(id);
+ return (selectedIndex > -1);
+ }
+
+ private handleClick(event: any, rowData: TData, id: string | number): void {
+ if (this.props.onHandleClick instanceof Function) {
+ this.props.onHandleClick(event, rowData);
+ return;
+ }
+ if (!this.props.enableSelection) {
+ return;
+ }
+ let selected = this.state.selected || [];
+ const selectedIndex = selected.indexOf(id);
+ if (selectedIndex > -1) {
+ selected = [
+ ...selected.slice(0, selectedIndex),
+ ...selected.slice(selectedIndex + 1)
+ ];
+ } else {
+ selected = [
+ ...selected,
+ id
+ ];
+ }
+ this.setState({
+ selected
+ });
+ }
+
+
+ private exportToCsv = async () => {
+ let file;
+ let data: dataType[] | null = null;
+ let csv: string[] = [];
+
+ if (isMaterialTableComponentPropsWithRequestData(this.props)) {
+ // table with extra request handler
+ this.setState({ loading: true });
+ const result = await Promise.resolve(
+ this.props.onRequestData(0, 1000, this.state.orderBy, this.state.order, this.state.showFilter && this.state.filter || {})
+ );
+ data = result.rows;
+ this.setState({ loading: true });
+ } else if (isMaterialTableComponentPropsWithRowsAndRequestData(this.props)) {
+ // table with generated handlers note: exports data shown on current page
+ data = this.props.rows;
+ }
+ else {
+ // table with local data
+ data = MaterialTableComponent.updateRows(this.props, this.state).rows;
+ }
+
+ if (data && data.length > 0) {
+ csv.push(this.props.columns.map(col => col.title || col.property).join(',') + "\r\n");
+ this.state.rows && this.state.rows.forEach((row: any) => {
+ csv.push(this.props.columns.map(col => row[col.property]).join(',') + "\r\n");
+ });
+ const properties = { type: "text/csv;charset=utf-8" }; // Specify the file's mime-type.
+ try {
+ // Specify the filename using the File constructor, but ...
+ file = new File(csv, "export.csv", properties);
+ } catch (e) {
+ // ... fall back to the Blob constructor if that isn't supported.
+ file = new Blob(csv, properties);
+ }
+ }
+ if (!file) return;
+ var reader = new FileReader();
+ reader.onload = function (e) {
+ const dataUri = reader.result as any;
+ const link = document.createElement("a");
+ if (typeof link.download === 'string') {
+ link.href = dataUri;
+ link.download = "export.csv";
+
+ //Firefox requires the link to be in the body
+ document.body.appendChild(link);
+
+ //simulate click
+ link.click();
+
+ //remove the link when done
+ document.body.removeChild(link);
+ } else {
+ window.open(dataUri);
+ }
+ }
+ reader.readAsDataURL(file);
+
+ // const url = URL.createObjectURL(file);
+ // window.location.replace(url);
+ }
+}
+
+export type MaterialTableCtorType<TData extends {} = {}> = new () => React.Component<DistributiveOmit<MaterialTableComponentProps<TData>, 'classes'>>;
+
+export const MaterialTable = withStyles(styles)(MaterialTableComponent);
+export default MaterialTable; \ No newline at end of file
diff --git a/sdnr/wt-odlux/odlux/framework/src/components/material-table/showColumnDialog.tsx b/sdnr/wt-odlux/odlux/framework/src/components/material-table/showColumnDialog.tsx
new file mode 100644
index 000000000..ab0d465e7
--- /dev/null
+++ b/sdnr/wt-odlux/odlux/framework/src/components/material-table/showColumnDialog.tsx
@@ -0,0 +1,188 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2022 highstreet technologies GmbH Intellectual Property. All rights reserved.
+ * =================================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ * ============LICENSE_END==========================================================================
+ */
+
+import React from 'react';
+import { Button, FormControlLabel, Popover, Switch, Typography } from '@mui/material';
+import { connect, Connect, IDispatcher } from '../../flux/connect';
+
+import { ColumnModel } from './columnModel';
+import { IApplicationStoreState } from '../../store/applicationStore';
+import { TableSettingsColumn } from '../../models/settings';
+import { updateTableSettings } from '../../actions/settingsAction';
+
+const mapStateToProps = (state: IApplicationStoreState) => ({
+ settings: state.framework.applicationState.settings,
+ settingsDoneLoading: state.framework.applicationState.settings.isInitialLoadDone
+});
+
+const mapDispatchToProps = (dispatcher: IDispatcher) => ({
+ saveToSettings: (tableName: string, columns: TableSettingsColumn[]) => dispatcher.dispatch(updateTableSettings(tableName, columns))
+})
+
+type DialogProps = {
+ columns: ColumnModel<{}>[],
+ settingsName: string | null,
+ anchorEl: HTMLElement | null;
+ hideColumns: (columnNames: string[]) => void
+ showColumns: (columnNames: string[]) => void
+ onClose(): void
+
+} & Connect<typeof mapStateToProps, typeof mapDispatchToProps>;
+
+ //TODO: figure out why everything gets triggered twice...
+
+const ShowColumnsDialog: React.FunctionComponent<DialogProps> = (props) => {
+
+ const savedSettings = props.settingsName && props.settings.tables[props.settingsName];
+
+ const [checkedColumns, setCheckedColumns] = React.useState<{ property: string, display: boolean, title: string | undefined }[]>([]);
+
+ const open = Boolean(props.anchorEl);
+ const allColumnNames = props.columns.map(e => e.property);
+
+ React.useEffect(() => {
+
+ createHideShowSelection();
+
+ }, []);
+
+ React.useEffect(() => {
+
+ createHideShowSelection();
+
+ }, [props.settings.isInitialLoadDone]);
+
+
+ const createHideShowSelection = () => {
+ let columns = props.columns.map(e => { return { property: e.property, display: !Boolean(e.hide), title: e.title } });
+
+
+ if (savedSettings) {
+
+ if (columns.length !== savedSettings.columns.length) {
+ console.error("saved column length does not match current column length. Maybe a settings entry got wrongly overridden?")
+ }
+
+ //overwrite column data with settings
+ savedSettings?.columns.forEach(el => {
+ let foundIndex = columns.findIndex(e => e.property == el.property);
+ if (columns[foundIndex] !== undefined)
+ columns[foundIndex].display = el.displayed;
+ });
+
+ } else {
+ console.warn("No settingsName set, changes will not be saved.")
+ }
+
+ setCheckedColumns(columns);
+
+ const hideColumns = columns.filter(el => !el.display).map(e => e.property);
+ props.hideColumns(hideColumns);
+ }
+
+
+ const handleChange = (propertyName: string, checked: boolean) => {
+ if (!checked) {
+ props.hideColumns([propertyName]);
+ } else {
+ props.showColumns([propertyName])
+
+ }
+
+ let updatedList = checkedColumns.map(item => {
+ if (item.property == propertyName) {
+ return { ...item, display: checked };
+ }
+ return item;
+ });
+
+ setCheckedColumns(updatedList);
+ };
+
+ const onHideAll = () => {
+
+ switchCheckedColumns(false);
+ props.hideColumns(allColumnNames);
+ }
+
+ const onShowAll = () => {
+
+ switchCheckedColumns(true);
+ props.showColumns(allColumnNames);
+ }
+
+ const onClose = () => {
+
+ const tableColumns: TableSettingsColumn[] = checkedColumns.map(el => {
+ return {
+ property: el.property,
+ displayed: el.display
+ }
+ });
+
+ if (props.settingsName) {
+ props.saveToSettings(props.settingsName, tableColumns);
+ }
+ props.onClose();
+
+ }
+
+ const switchCheckedColumns = (changeToValue: boolean) => {
+ let updatedList = checkedColumns.map(item => {
+ return { ...item, display: changeToValue };
+ });
+
+ setCheckedColumns(updatedList);
+
+ }
+
+ return (<Popover open={open} onClose={onClose}
+ anchorEl={props.anchorEl}
+ anchorOrigin={{
+ vertical: 'top',
+ horizontal: 'left',
+ }} >
+ <div>
+ <Typography fontWeight={600} style={{ margin: 10 }} >Hide / Show Columns</Typography>
+ </div>
+ <div style={{ display: "flex", flexDirection: "column", margin: 10 }}>
+ {
+ checkedColumns?.map((el, i) => {
+
+ return <>
+
+ <FormControlLabel
+ value="end"
+ key={"hide-show-column-"+i}
+ aria-label={"hide-or-show-column-button"}
+ control={<Switch color="secondary" checked={el.display} onChange={e => handleChange(el.property, e.target.checked)} />}
+ label={el.title || el.property}
+ labelPlacement="end"
+ />
+ </>
+ })
+ }
+ <div style={{ display: "flex", flexDirection: "row", justifyContent: "space-between" }}>
+ <Button color="secondary" aria-label="hide-all-columns-button" onClick={(e) => onHideAll()}>Hide all</Button>
+ <Button color="secondary" aria-label="show-all-columns-button" onClick={(e) => onShowAll()}>Show all</Button>
+ </div>
+ </div>
+ </Popover>)
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(ShowColumnsDialog);
diff --git a/sdnr/wt-odlux/odlux/framework/src/components/material-table/tableFilter.tsx b/sdnr/wt-odlux/odlux/framework/src/components/material-table/tableFilter.tsx
new file mode 100644
index 000000000..1b9136844
--- /dev/null
+++ b/sdnr/wt-odlux/odlux/framework/src/components/material-table/tableFilter.tsx
@@ -0,0 +1,113 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.
+ * =================================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ * ============LICENSE_END==========================================================================
+ */
+
+import * as React from 'react';
+import { ColumnModel, ColumnType } from './columnModel';
+import { Theme } from '@mui/material/styles';
+
+
+import { WithStyles } from '@mui/styles';
+import withStyles from '@mui/styles/withStyles';
+import createStyles from '@mui/styles/createStyles';
+
+
+import TableCell from '@mui/material/TableCell';
+import TableRow from '@mui/material/TableRow';
+import Input from '@mui/material/Input';
+import { Select, FormControl, InputLabel, MenuItem, SelectChangeEvent } from '@mui/material';
+import { toAriaLabel } from '../../utilities/yangHelper';
+
+
+const styles = (theme: Theme) => createStyles({
+ container: {
+ display: 'flex',
+ flexWrap: 'wrap',
+ },
+ input: {
+ margin: theme.spacing(1),
+ },
+ numberInput: {
+ float: "right"
+ }
+});
+
+interface IEnhancedTableFilterComponentProps extends WithStyles<typeof styles> {
+ onFilterChanged: (property: string, filterTerm: string) => void;
+ filter: { [property: string]: string };
+ columns: ColumnModel<{}>[];
+ hiddenColumns: string[];
+ enableSelection?: boolean;
+}
+
+class EnhancedTableFilterComponent extends React.Component<IEnhancedTableFilterComponentProps> {
+ createSelectFilterHandler = (property: string) => (event: SelectChangeEvent<HTMLSelectElement | string>) => {
+ this.props.onFilterChanged && this.props.onFilterChanged(property, event.target.value as string);
+ };
+ createInputFilterHandler = (property: string) => (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
+ this.props.onFilterChanged && this.props.onFilterChanged(property, event.currentTarget.value);
+ };
+
+
+ render() {
+ const { columns, filter, classes } = this.props;
+ return (
+ <TableRow>
+ {this.props.enableSelection
+ ? <TableCell padding="checkbox" style={{ width: "50px" }}>
+ </TableCell>
+ : null
+ }
+ {columns.map((col, ind) => {
+ const style = col.width ? { width: col.width } : {};
+ const tableCell = (
+ <TableCell
+ className={col.type === ColumnType.numeric ? classes.numberInput : ''}
+ key={col.property}
+ padding={col.disablePadding ? 'none' : 'normal'}
+ style={style}
+ >
+ {col.disableFilter || (col.type === ColumnType.custom)
+ ? null
+ : (col.type === ColumnType.boolean)
+ ? <Select variant="standard" className={classes.input} aria-label={col.title ? toAriaLabel(col.title as string) + '-filter' : `${ind + 1}-filter`}
+ value={filter[col.property] !== undefined ? filter[col.property] : ''}
+ onChange={this.createSelectFilterHandler(col.property)}
+ inputProps={{ name: `${col.property}-bool`, id: `${col.property}-bool` }} >
+ <MenuItem value={undefined} aria-label="none-value" >
+ <em>None</em>
+ </MenuItem>
+ <MenuItem aria-label="true-value" value={true as any as string}>{col.labels ? col.labels["true"] : "true"}</MenuItem>
+ <MenuItem aria-label="false-value" value={false as any as string}>{col.labels ? col.labels["false"] : "false"}</MenuItem>
+ </Select>
+ : <Input className={classes.input}
+ inputProps={{ 'aria-label': col.title ? toAriaLabel(col.title as string) + '-filter' : `${ind + 1}-filter` }}
+ value={filter[col.property] || ''}
+ onChange={this.createInputFilterHandler(col.property)} />}
+ </TableCell>
+ );
+
+ const showColumn = !this.props.hiddenColumns.includes(col.property);
+
+ return showColumn && tableCell;
+ }, this)}
+ </TableRow>
+ );
+ }
+}
+
+export const EnhancedTableFilter = withStyles(styles)(EnhancedTableFilterComponent); \ No newline at end of file
diff --git a/sdnr/wt-odlux/odlux/framework/src/components/material-table/tableHead.tsx b/sdnr/wt-odlux/odlux/framework/src/components/material-table/tableHead.tsx
new file mode 100644
index 000000000..d6f7b7def
--- /dev/null
+++ b/sdnr/wt-odlux/odlux/framework/src/components/material-table/tableHead.tsx
@@ -0,0 +1,127 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.
+ * =================================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ * ============LICENSE_END==========================================================================
+ */
+
+import * as React from 'react';
+import { ColumnModel, ColumnType } from './columnModel';
+import { Theme } from '@mui/material/styles';
+
+import { WithStyles } from '@mui/styles';
+import withStyles from '@mui/styles/withStyles';
+import createStyles from '@mui/styles/createStyles';
+
+import TableSortLabel from '@mui/material/TableSortLabel';
+import TableCell from '@mui/material/TableCell';
+import TableHead from '@mui/material/TableHead';
+import TableRow from '@mui/material/TableRow';
+import Checkbox from '@mui/material/Checkbox';
+import Tooltip from '@mui/material/Tooltip';
+
+const styles = (theme: Theme) => createStyles({
+ header: {
+ backgroundColor: "#fafafa",
+ position: "sticky",
+ top: 0
+ }
+});
+
+
+type styles_header = WithStyles<typeof styles>;
+
+interface IEnhancedTableHeadComponentProps extends styles_header {
+ numSelected: number | null;
+ onRequestSort: (event: React.SyntheticEvent, property: string) => void;
+ onSelectAllClick: () => void;
+ order: 'asc' | 'desc';
+ orderBy: string | null;
+ rowCount: number;
+ columns: ColumnModel<{}>[];
+ hiddenColumns: string[];
+ enableSelection?: boolean;
+ allowHtmlHeader?: boolean;
+}
+
+class EnhancedTableHeadComponent extends React.Component<IEnhancedTableHeadComponentProps> {
+ createSortHandler = (property: string) => (event: React.SyntheticEvent) => {
+ this.props.onRequestSort(event, property);
+ };
+
+ render() {
+ const { onSelectAllClick, order, orderBy, numSelected, rowCount, columns } = this.props;
+ const {classes} = this.props;
+
+ return (
+ <TableHead>
+ <TableRow>
+ { this.props.enableSelection
+ ? <TableCell padding="checkbox" style={ { width: "50px" } } className= {classes.header} >
+ <Checkbox
+ indeterminate={ numSelected && numSelected > 0 && numSelected < rowCount || undefined }
+ checked={ numSelected === rowCount }
+ onChange={ onSelectAllClick }
+ />
+ </TableCell>
+ : null
+ }
+ { columns.map(col => {
+ const style = col.width ? { width: col.width } : {};
+ const tableCell = (
+ <TableCell className= {classes.header}
+ key={ col.property }
+ align={ col.type === ColumnType.numeric ? 'right' : 'left' }
+ padding={ col.disablePadding ? 'none' : 'normal' }
+ sortDirection={ orderBy === (col.property) ? order : false }
+ style={ style }
+ >
+ { col.disableSorting || (col.type === ColumnType.custom)
+ ? <TableSortLabel
+ active={ false }
+ direction={ undefined }
+ >
+ { col.title || col.property }
+ </TableSortLabel>
+ : <Tooltip disableInteractive
+ title="Sort"
+ placement={ col.type === ColumnType.numeric ? 'bottom-end' : 'bottom-start' }
+ enterDelay={ 300 }
+ >
+ <TableSortLabel
+ active={ orderBy === col.property }
+ direction={ order || undefined }
+ onClick={ this.createSortHandler(col.property) }
+ >
+ {
+ this.props.allowHtmlHeader ? <div className="content" dangerouslySetInnerHTML={{__html: col.title || col.property}}></div>
+ : (col.title || col.property )
+ }
+ </TableSortLabel>
+ </Tooltip> }
+ </TableCell>
+ );
+
+ //show column if...
+ const showColumn = !this.props.hiddenColumns.includes(col.property);
+
+ return showColumn && tableCell;
+ }, this) }
+ </TableRow>
+ </TableHead>
+ );
+ }
+}
+
+export const EnhancedTableHead = withStyles(styles)(EnhancedTableHeadComponent); \ No newline at end of file
diff --git a/sdnr/wt-odlux/odlux/framework/src/components/material-table/tableToolbar.tsx b/sdnr/wt-odlux/odlux/framework/src/components/material-table/tableToolbar.tsx
new file mode 100644
index 000000000..143b802a4
--- /dev/null
+++ b/sdnr/wt-odlux/odlux/framework/src/components/material-table/tableToolbar.tsx
@@ -0,0 +1,191 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.
+ * =================================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ * ============LICENSE_END==========================================================================
+ */
+import * as React from 'react';
+import { Theme, lighten } from '@mui/material/styles';
+
+import { WithStyles } from '@mui/styles';
+import withStyles from '@mui/styles/withStyles';
+import createStyles from '@mui/styles/createStyles';
+
+import IconButton from '@mui/material/IconButton';
+import Tooltip from '@mui/material/Tooltip';
+import Toolbar from '@mui/material/Toolbar';
+import Typography from '@mui/material/Typography';
+import DeleteIcon from '@mui/icons-material/Delete';
+import MoreIcon from '@mui/icons-material/MoreVert';
+import FilterListIcon from '@mui/icons-material/FilterList';
+import MenuItem from '@mui/material/MenuItem';
+import Menu from '@mui/material/Menu';
+import { SvgIconProps } from '@mui/material/SvgIcon';
+import { Button } from '@mui/material';
+import { ColumnModel } from './columnModel';
+import ShowColumnsDialog from './showColumnDialog'
+
+const styles = (theme: Theme) => createStyles({
+ root: {
+ paddingRight: theme.spacing(1),
+ },
+ highlight:
+ theme.palette.mode === 'light'
+ ? {
+ color: theme.palette.secondary.main,
+ backgroundColor: lighten(theme.palette.secondary.light, 0.85),
+ }
+ : {
+ color: theme.palette.text.primary,
+ backgroundColor: theme.palette.secondary.dark,
+ },
+ spacer: {
+ flex: '1 1 100%',
+ },
+ actions: {
+ color: theme.palette.text.secondary,
+ display: "flex",
+ flex: "auto",
+ flexDirection: "row"
+ },
+ title: {
+ flex: '0 0 auto',
+ },
+ menuButton: {
+ marginLeft: -12,
+ marginRight: 20,
+ },
+});
+
+interface ITableToolbarComponentProps extends WithStyles<typeof styles> {
+ numSelected: number | null;
+ title?: string;
+ tableId: string | null;
+ customActionButtons?: { icon: React.ComponentType<SvgIconProps>, tooltip?: string, ariaLabel: string, onClick: () => void, disabled?: boolean }[];
+ columns: ColumnModel<{}>[];
+ onHideColumns: (columnNames: string[]) => void
+ onShowColumns: (columnNames: string[]) => void
+ onToggleFilter: () => void;
+ onExportToCsv: () => void;
+}
+
+class TableToolbarComponent extends React.Component<ITableToolbarComponentProps, { anchorEl: EventTarget & HTMLElement | null, anchorElDialog: HTMLElement | null }> {
+
+
+ constructor(props: ITableToolbarComponentProps) {
+ super(props);
+
+ this.state = {
+ anchorEl: null,
+ anchorElDialog: null
+ };
+ }
+
+ private handleMenu = (event: React.MouseEvent<HTMLElement>) => {
+ this.setState({ anchorEl: event.currentTarget });
+ };
+
+ private handleClose = () => {
+ this.setState({ anchorEl: null });
+ };
+
+ private showColumnsDialog = (event: React.MouseEvent<HTMLElement>) =>{
+ this.setState({ anchorElDialog: this.state.anchorEl });
+ }
+
+ private onCloseDialog = () =>{
+ this.setState({ anchorElDialog: null });
+
+ }
+
+ render() {
+ const { numSelected, classes } = this.props;
+ const open = !!this.state.anchorEl;
+ const buttonPrefix = this.props.tableId !== null ? this.props.tableId : 'table';
+ return (
+ <>
+ <Toolbar className={`${classes.root} ${numSelected && numSelected > 0 ? classes.highlight : ''} `} >
+ <div className={classes.title}>
+ {numSelected && numSelected > 0 ? (
+ <Typography color="inherit" variant="subtitle1">
+ {numSelected} selected
+ </Typography>
+ ) : (
+ <Typography variant="h5" id="tableTitle">
+ {this.props.title || null}
+ </Typography>
+ )}
+ </div>
+ <div className={classes.spacer} />
+ <div className={classes.actions}>
+ {this.props.customActionButtons
+ ? this.props.customActionButtons.map((action, ind) => (
+ <Tooltip disableInteractive key={`custom-action-${ind}`} title={action.tooltip || ''}>
+ <IconButton
+ disabled={action.disabled}
+ aria-label={`${buttonPrefix}-${action.ariaLabel}-button`}
+ onClick={() => action.onClick()}
+ size="large">
+ <action.icon />
+ </IconButton>
+ </Tooltip>
+ ))
+ : null}
+ {numSelected && numSelected > 0 ? (
+ <Tooltip disableInteractive title="Delete">
+ <IconButton aria-label={`${buttonPrefix}-delete-button`} size="large">
+ <DeleteIcon />
+ </IconButton>
+ </Tooltip>
+ ) : (
+ <Tooltip disableInteractive title="Filter list">
+ <IconButton
+ aria-label={`${buttonPrefix}-filter-list-button`}
+ onClick={() => { this.props.onToggleFilter && this.props.onToggleFilter() }}
+ size="large">
+ <FilterListIcon />
+ </IconButton>
+ </Tooltip>
+ )}
+ <Tooltip disableInteractive title="Actions">
+ <IconButton
+ color="inherit"
+ aria-label={`${buttonPrefix}-additional-actions-button`}
+ aria-owns={open ? 'menu-appbar' : undefined}
+ aria-haspopup="true"
+ onClick={this.handleMenu}
+ size="large">
+ <MoreIcon />
+ </IconButton>
+ </Tooltip>
+ <Menu id="menu-appbar" anchorEl={this.state.anchorEl} anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
+ transformOrigin={{ vertical: 'top', horizontal: 'right' }} open={open} onClose={this.handleClose} >
+ <MenuItem aria-label="export-table-as-csv" onClick={(e) =>{ this.props.onExportToCsv(); this.handleClose()}}>Export as CSV</MenuItem>
+ <MenuItem aria-label="hide-show-table-columns" onClick={(e) =>{ this.showColumnsDialog(e); this.handleClose()}}>Hide/show columns</MenuItem>
+ </Menu>
+ </div>
+ </Toolbar>
+ <ShowColumnsDialog
+ anchorEl={this.state.anchorElDialog}
+ onClose={this.onCloseDialog}
+ settingsName={this.props.tableId}
+ columns={this.props.columns}
+ hideColumns={this.props.onHideColumns}
+ showColumns={this.props.onShowColumns} />
+ </>
+ );
+ }
+}
+
+export const TableToolbar = withStyles(styles)(TableToolbarComponent); \ No newline at end of file
diff --git a/sdnr/wt-odlux/odlux/framework/src/components/material-table/utilities.ts b/sdnr/wt-odlux/odlux/framework/src/components/material-table/utilities.ts
new file mode 100644
index 000000000..e2fda7647
--- /dev/null
+++ b/sdnr/wt-odlux/odlux/framework/src/components/material-table/utilities.ts
@@ -0,0 +1,357 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.
+ * =================================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ * ============LICENSE_END==========================================================================
+ */
+import { Action, IActionHandler } from '../../flux/action';
+import { Dispatch } from '../../flux/store';
+
+import { AddErrorInfoAction } from '../../actions/errorActions';
+import { IApplicationStoreState } from '../../store/applicationStore';
+
+export const RowDisabled = Symbol("RowDisabled");
+import { DataCallback } from ".";
+
+export interface IExternalTableState<TData> {
+ order: 'asc' | 'desc';
+ orderBy: string | null;
+ selected: any[] | null;
+ hiddenColumns: string[]
+ rows: (TData & { [RowDisabled]?: boolean })[];
+ total: number;
+ page: number;
+ rowsPerPage: number;
+ loading: boolean;
+ showFilter: boolean;
+ filter: { [property: string]: string };
+ preFilter: { [property: string]: string };
+}
+
+export type ExternalMethodes<TData> = {
+ reloadAction: (dispatch: Dispatch, getAppState: () => IApplicationStoreState) => Promise<void | AddErrorInfoAction>;
+ createActions: (dispatch: Dispatch, skipRefresh?: boolean) => {
+ onRefresh: () => void;
+ onHandleRequestSort: (orderBy: string) => void;
+ onHandleExplicitRequestSort: (property: string, sortOrder: "asc" | "desc") => void;
+ onToggleFilter: (refresh?: boolean | undefined) => void;
+ onFilterChanged: (property: string, filterTerm: string) => void;
+ onHandleChangePage: (page: number) => void;
+ onHandleChangeRowsPerPage: (rowsPerPage: number | null) => void;
+ onHideColumns: (columnName: string[]) => void;
+ onShowColumns: (columnName: string[]) => void;
+ onClearFilters: () => void;
+ },
+ createPreActions: (dispatch: Dispatch, skipRefresh?: boolean) => {
+ onPreFilterChanged: (preFilter: {
+ [key: string]: string;
+ }) => void;
+ };
+ createProperties: (state: IApplicationStoreState) => IExternalTableState<TData>;
+ actionHandler: IActionHandler<IExternalTableState<TData>, Action>;
+}
+
+
+/** Create an actionHandler and actions for external table states. */
+export function createExternal<TData>(callback: DataCallback<TData>, selectState: (appState: IApplicationStoreState) => IExternalTableState<TData>) : ExternalMethodes<TData> ;
+export function createExternal<TData>(callback: DataCallback<TData>, selectState: (appState: IApplicationStoreState) => IExternalTableState<TData>, disableRow: (data: TData) => boolean) : ExternalMethodes<TData>;
+export function createExternal<TData>(callback: DataCallback<TData>, selectState: (appState: IApplicationStoreState) => IExternalTableState<TData>, disableRow?: (data: TData) => boolean) : ExternalMethodes<TData> {
+
+ //#region Actions
+ abstract class TableAction extends Action { }
+
+
+ class RequestSortAction extends TableAction {
+ constructor(public orderBy: string) {
+ super();
+ }
+ }
+
+ class RequestExplicitSortAction extends TableAction {
+ constructor(public propertyName: string, public sortOrder: "asc" | "desc") {
+ super();
+ }
+ }
+
+ class SetSelectedAction extends TableAction {
+ constructor(public selected: TData[] | null) {
+ super();
+ }
+ }
+
+ class SetPageAction extends TableAction {
+ constructor(public page: number) {
+ super();
+ }
+ }
+
+ class SetRowsPerPageAction extends TableAction {
+ constructor(public rowsPerPage: number) {
+ super();
+ }
+ }
+
+ class SetPreFilterChangedAction extends TableAction {
+ constructor(public preFilter: { [key: string]: string }) {
+ super();
+ }
+ }
+
+ class SetFilterChangedAction extends TableAction {
+ constructor(public filter: { [key: string]: string }) {
+ super();
+ }
+ }
+
+ class SetShowFilterAction extends TableAction {
+ constructor(public show: boolean) {
+ super();
+ }
+ }
+
+ class RefreshAction extends TableAction {
+ constructor() {
+ super();
+ }
+ }
+
+ class SetResultAction extends TableAction {
+ constructor(public result: { page: number, total: number, rows: TData[] }) {
+ super();
+ }
+ }
+
+ class HideColumnsAction extends TableAction{
+ constructor(public property: string[]){
+ super();
+ }
+ }
+
+ class ShowColumnsAction extends TableAction{
+ constructor(public property: string[]){
+ super();
+ }
+ }
+
+ // #endregion
+
+ //#region Action Handler
+ const externalTableStateInit: IExternalTableState<TData> = {
+ order: 'asc',
+ orderBy: null,
+ selected: null,
+ hiddenColumns:[],
+ rows: [],
+ total: 0,
+ page: 0,
+ rowsPerPage: 10,
+ loading: false,
+ showFilter: false,
+ filter: {},
+ preFilter: {}
+ };
+
+ const externalTableStateActionHandler: IActionHandler<IExternalTableState<TData>> = (state = externalTableStateInit, action) => {
+ if (!(action instanceof TableAction)) return state;
+ if (action instanceof RefreshAction) {
+ state = {
+ ...state,
+ loading: true
+ }
+ } else if (action instanceof SetResultAction) {
+ state = {
+ ...state,
+ loading: false,
+ rows: disableRow
+ ? action.result.rows.map((row: TData) => ({...row, [RowDisabled]: disableRow(row) }))
+ : action.result.rows,
+ total: action.result.total,
+ page: action.result.page,
+ }
+ } else if (action instanceof RequestSortAction) {
+ state = {
+ ...state,
+ loading: true,
+ orderBy: state.orderBy === action.orderBy && state.order === 'desc' ? null : action.orderBy,
+ order: state.orderBy === action.orderBy && state.order === 'asc' ? 'desc' : 'asc',
+ }
+ } else if (action instanceof RequestExplicitSortAction) {
+ state = {
+ ...state,
+ loading: true,
+ orderBy: action.propertyName,
+ order: action.sortOrder
+ }
+ }
+ else if (action instanceof SetShowFilterAction) {
+ state = {
+ ...state,
+ loading: true,
+ showFilter: action.show
+ }
+ } else if (action instanceof SetPreFilterChangedAction) {
+ state = {
+ ...state,
+ loading: true,
+ preFilter: action.preFilter
+ }
+ } else if (action instanceof SetFilterChangedAction) {
+ state = {
+ ...state,
+ loading: true,
+ filter: action.filter
+ }
+ } else if (action instanceof SetPageAction) {
+ state = {
+ ...state,
+ loading: true,
+ page: action.page
+ }
+ } else if (action instanceof SetRowsPerPageAction) {
+ state = {
+ ...state,
+ loading: true,
+ rowsPerPage: action.rowsPerPage
+ }
+ }
+ else if (action instanceof HideColumnsAction){
+
+ //merge arrays, remove duplicates
+ const newArray = [...new Set([...state.hiddenColumns, ...action.property])]
+ state = {...state, hiddenColumns: newArray};
+ }
+ else if(action instanceof ShowColumnsAction){
+
+ const newArray = state.hiddenColumns.filter(el=> !action.property.includes(el));
+ state = {...state, hiddenColumns: newArray};
+ }
+
+ return state;
+ }
+
+ //const createTableAction(tableAction)
+
+ //#endregion
+ const reloadAction = (dispatch: Dispatch, getAppState: () => IApplicationStoreState) => {
+ dispatch(new RefreshAction());
+ const ownState = selectState(getAppState());
+ const filter = { ...ownState.preFilter, ...(ownState.showFilter && ownState.filter || {}) };
+ return Promise.resolve(callback(ownState.page, ownState.rowsPerPage, ownState.orderBy, ownState.order, filter)).then(result => {
+
+ if (ownState.page > 0 && ownState.rowsPerPage * ownState.page > result.total) { //if result is smaller than the currently shown page, new search and repaginate
+
+ let newPage = Math.floor(result.total / ownState.rowsPerPage);
+
+ Promise.resolve(callback(newPage, ownState.rowsPerPage, ownState.orderBy, ownState.order, filter)).then(result1 => {
+ dispatch(new SetResultAction(result1));
+ });
+
+
+ } else {
+ dispatch(new SetResultAction(result));
+ }
+
+
+ }).catch(error => dispatch(new AddErrorInfoAction(error)));
+ };
+
+ const createPreActions = (dispatch: Dispatch, skipRefresh: boolean = false) => {
+ return {
+ onPreFilterChanged: (preFilter: { [key: string]: string }) => {
+ dispatch(new SetPreFilterChangedAction(preFilter));
+ (!skipRefresh) && dispatch(reloadAction);
+ }
+ };
+ }
+
+ const createActions = (dispatch: Dispatch, skipRefresh: boolean = false) => {
+ return {
+ onRefresh: () => {
+ dispatch(reloadAction);
+ },
+ onHandleRequestSort: (orderBy: string) => {
+ dispatch((dispatch: Dispatch) => {
+ dispatch(new RequestSortAction(orderBy));
+ (!skipRefresh) && dispatch(reloadAction);
+ });
+ },
+ onHandleExplicitRequestSort: (property: string, sortOrder: "asc" | "desc") => {
+ dispatch((dispatch: Dispatch) => {
+ dispatch(new RequestExplicitSortAction(property, sortOrder));
+ (!skipRefresh) && dispatch(reloadAction);
+ });
+ },
+ onToggleFilter: (refresh?: boolean) => {
+ dispatch((dispatch: Dispatch, getAppState: () => IApplicationStoreState) => {
+ const { showFilter } = selectState(getAppState());
+ dispatch(new SetShowFilterAction(!showFilter));
+ if (!skipRefresh && (refresh === undefined || refresh))
+ dispatch(reloadAction);
+ });
+ },
+ onFilterChanged: (property: string, filterTerm: string) => {
+ dispatch((dispatch: Dispatch, getAppState: () => IApplicationStoreState) => {
+ let { filter } = selectState(getAppState());
+ filter = { ...filter, [property]: filterTerm };
+ dispatch(new SetFilterChangedAction(filter));
+ (!skipRefresh) && dispatch(reloadAction);
+ });
+ },
+ onHandleChangePage: (page: number) => {
+ dispatch((dispatch: Dispatch) => {
+ dispatch(new SetPageAction(page));
+ (!skipRefresh) && dispatch(reloadAction);
+ });
+ },
+ onHandleChangeRowsPerPage: (rowsPerPage: number | null) => {
+ dispatch((dispatch: Dispatch) => {
+ dispatch(new SetRowsPerPageAction(rowsPerPage || 10));
+ (!skipRefresh) && dispatch(reloadAction);
+ });
+ },
+ onHideColumns: (columnName: string[]) =>{
+ dispatch((dispatch: Dispatch) => {
+ dispatch(new HideColumnsAction(columnName));
+ })
+ },
+ onShowColumns: (columnName: string[]) =>{
+ dispatch((dispatch: Dispatch) => {
+ dispatch(new ShowColumnsAction(columnName));
+ })
+ },
+ onClearFilters: () => {
+ dispatch((dispatch: Dispatch) => {
+ let filter = { };
+ dispatch(new SetFilterChangedAction(filter));
+ });
+ },
+ // selected:
+ };
+ };
+
+ const createProperties = (state: IApplicationStoreState) => {
+ return {
+ ...selectState(state)
+ }
+ }
+
+ return {
+ reloadAction: reloadAction,
+ createActions: createActions,
+ createProperties: createProperties,
+ createPreActions: createPreActions,
+ actionHandler: externalTableStateActionHandler,
+ }
+}
+
diff --git a/sdnr/wt-odlux/odlux/framework/src/components/material-ui/index.ts b/sdnr/wt-odlux/odlux/framework/src/components/material-ui/index.ts
new file mode 100644
index 000000000..096e4439d
--- /dev/null
+++ b/sdnr/wt-odlux/odlux/framework/src/components/material-ui/index.ts
@@ -0,0 +1,22 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.
+ * =================================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ * ============LICENSE_END==========================================================================
+ */
+export { ListItemLink } from './listItemLink';
+export { Panel } from './panel';
+export { ToggleButton, ToggleButtonClassKey } from './toggleButton';
+export { TreeView, TreeItem, TreeViewCtorType} from './treeView';
+export { Loader } from './loader';
diff --git a/sdnr/wt-odlux/odlux/framework/src/components/material-ui/listItemLink.tsx b/sdnr/wt-odlux/odlux/framework/src/components/material-ui/listItemLink.tsx
new file mode 100644
index 000000000..626cb8978
--- /dev/null
+++ b/sdnr/wt-odlux/odlux/framework/src/components/material-ui/listItemLink.tsx
@@ -0,0 +1,83 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.
+ * =================================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ * ============LICENSE_END==========================================================================
+ */
+import * as React from 'react';
+import { NavLink, Link, Route } from 'react-router-dom';
+
+import ListItem from '@mui/material/ListItem';
+import ListItemIcon from '@mui/material/ListItemIcon';
+import ListItemText from '@mui/material/ListItemText';
+
+import { Theme } from '@mui/material/styles';
+import { WithStyles } from '@mui/styles';
+import withStyles from '@mui/styles/withStyles';
+import createStyles from '@mui/styles/createStyles';
+import { toAriaLabel } from '../../utilities/yangHelper';
+import { IconType } from '../../models/iconDefinition';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+
+const styles = (theme: Theme) => createStyles({
+ active: {
+ backgroundColor: theme.palette.action.selected
+ }
+});
+
+export interface IListItemLinkProps extends WithStyles<typeof styles> {
+ icon: IconType | null;
+ primary: string | React.ComponentType;
+ secondary?: React.ComponentType;
+ to: string;
+ exact?: boolean;
+ external?: boolean;
+}
+
+export const ListItemLink = withStyles(styles)((props: IListItemLinkProps) => {
+ const { icon, primary: Primary, secondary: Secondary, classes, to, exact = false, external=false } = props;
+ const renderLink = (itemProps: any): JSX.Element => (
+ props.external ? <a target="_blank" href={to} { ...itemProps }></a> :
+ <NavLink exact={ exact } to={ to } activeClassName={ classes.active } { ...itemProps } />);
+
+ const customIconHeight = 22;
+ const ariaLabel = typeof Primary === 'string' ? toAriaLabel("link-to-"+Primary) : toAriaLabel("link-to-"+Primary.displayName);
+
+ //create menu icon, either using an faIcon or a link to a custom svg icon
+ //moved to one place for easier usage
+ const listItemIcon = icon && ( typeof icon === 'string' ? <img height={customIconHeight} src={icon} /> : <FontAwesomeIcon icon={icon} /> );
+
+ return (
+ <>
+ <ListItem button component={ renderLink } aria-label={ariaLabel}>
+ { icon
+ ? <ListItemIcon>{ listItemIcon }</ListItemIcon>
+ : null
+ }
+ { typeof Primary === 'string'
+ ? <ListItemText primary={ Primary } style={{ padding: 0 }} />
+ : <Primary />
+ }
+ </ListItem>
+ { Secondary
+ ? <Route exact={ exact } path={ to } component={ Secondary } />
+ : null
+ }
+ </>
+ );
+ }
+);
+
+export default ListItemLink;
+
diff --git a/sdnr/wt-odlux/odlux/framework/src/components/material-ui/loader.tsx b/sdnr/wt-odlux/odlux/framework/src/components/material-ui/loader.tsx
new file mode 100644
index 000000000..bd523e1f4
--- /dev/null
+++ b/sdnr/wt-odlux/odlux/framework/src/components/material-ui/loader.tsx
@@ -0,0 +1,49 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.
+ * =================================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ * ============LICENSE_END==========================================================================
+ */
+
+
+import * as React from "react";
+
+import { Theme } from '@mui/material/styles';
+
+import { WithStyles } from '@mui/styles';
+import withStyles from '@mui/styles/withStyles';
+import createStyles from '@mui/styles/createStyles';
+
+const styles = (theme: Theme) => createStyles({
+ "@keyframes spin": {
+ "0%": { transform: "rotate(0deg)" },
+ "100%": { transform: "rotate(360deg)" },
+ },
+ loader: {
+ border: `16px solid ${theme.palette.grey.A200}`,
+ borderTop: `16px solid ${theme.palette.secondary.main}`,
+ borderRadius: "50%",
+ width: "120px",
+ height: "120px",
+ animation: "$spin 2s linear infinite",
+ }
+});
+
+const LoaderComponent: React.FC<WithStyles<typeof styles>> = (props) => {
+ return (
+ <div className={props.classes.loader} />
+ );
+};
+
+export const Loader = withStyles(styles)(LoaderComponent);
diff --git a/sdnr/wt-odlux/odlux/framework/src/components/material-ui/panel.tsx b/sdnr/wt-odlux/odlux/framework/src/components/material-ui/panel.tsx
new file mode 100644
index 000000000..6d192d2f0
--- /dev/null
+++ b/sdnr/wt-odlux/odlux/framework/src/components/material-ui/panel.tsx
@@ -0,0 +1,76 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.
+ * =================================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ * ============LICENSE_END==========================================================================
+ */
+import * as React from 'react';
+
+import { Theme } from '@mui/material/styles';
+
+import { WithStyles } from '@mui/styles';
+import withStyles from '@mui/styles/withStyles';
+import createStyles from '@mui/styles/createStyles';
+
+import { Accordion, AccordionSummary, AccordionDetails, Typography, AccordionActions } from '@mui/material';
+
+import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
+import { SvgIconProps } from '@mui/material/SvgIcon';
+
+const styles = (theme: Theme) => createStyles({
+ accordion: {
+ // background: theme.palette.secondary.dark,
+ // color: theme.palette.primary.contrastText
+ },
+ detail: {
+ // background: theme.palette.background.paper,
+ // color: theme.palette.text.primary,
+ position: "relative",
+ flexDirection: 'column'
+ },
+ text: {
+ // color: theme.palette.common.white,
+ // fontSize: "1rem"
+ },
+});
+
+type PanalProps = WithStyles<typeof styles> & {
+ activePanel: string | null,
+ panelId: string,
+ title: string,
+ customActionButtons?: JSX.Element[];
+ onToggle: (panelId: string | null) => void;
+}
+
+const PanelComponent: React.SFC<PanalProps> = (props) => {
+ const { classes, activePanel, onToggle } = props;
+ return (
+ <Accordion className={classes.accordion} expanded={activePanel === props.panelId} onChange={() => onToggle(props.panelId)} >
+ <AccordionSummary expandIcon={<ExpandMoreIcon />}>
+ <Typography className={classes.text} >{props.title}</Typography>
+ </AccordionSummary>
+ <AccordionDetails className={classes.detail}>
+ {props.children}
+ </AccordionDetails>
+ {props.customActionButtons
+ ? <AccordionActions>
+ {props.customActionButtons}
+ </AccordionActions>
+ : null}
+ </Accordion>
+ );
+};
+
+export const Panel = withStyles(styles)(PanelComponent);
+export default Panel; \ No newline at end of file
diff --git a/sdnr/wt-odlux/odlux/framework/src/components/material-ui/snackDisplay.tsx b/sdnr/wt-odlux/odlux/framework/src/components/material-ui/snackDisplay.tsx
new file mode 100644
index 000000000..437784ce5
--- /dev/null
+++ b/sdnr/wt-odlux/odlux/framework/src/components/material-ui/snackDisplay.tsx
@@ -0,0 +1,74 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.
+ * =================================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ * ============LICENSE_END==========================================================================
+ */
+import * as React from 'react';
+
+import { IApplicationStoreState } from '../../store/applicationStore';
+import { Connect, connect, IDispatcher } from '../../flux/connect';
+import { RemoveSnackbarNotification } from '../../actions/snackbarActions';
+
+import { WithSnackbarProps, withSnackbar } from 'notistack';
+
+const mapProps = (state: IApplicationStoreState) => ({
+ notifications: state.framework.applicationState.snackBars
+});
+
+const mapDispatch = (dispatcher: IDispatcher) => ({
+ removeSnackbar: (key: number) => {
+ dispatcher.dispatch(new RemoveSnackbarNotification(key));
+ }
+});
+
+type DisplaySnackbarsComponentProps = Connect<typeof mapProps, typeof mapDispatch> & WithSnackbarProps;
+
+class DisplaySnackbarsComponent extends React.Component<DisplaySnackbarsComponentProps> {
+ private displayed: number[] = [];
+
+ private storeDisplayed = (id: number) => {
+ this.displayed = [...this.displayed, id];
+ };
+
+ public shouldComponentUpdate({ notifications: newSnacks = [] }: DisplaySnackbarsComponentProps) {
+
+ const { notifications: currentSnacks } = this.props;
+ let notExists = false;
+ for (let i = 0; i < newSnacks.length; i++) {
+ if (notExists) continue;
+ notExists = notExists || !currentSnacks.filter(({ key }) => newSnacks[i].key === key).length;
+ }
+ return notExists;
+ }
+
+ componentDidUpdate() {
+ const { notifications = [] } = this.props;
+
+ notifications.forEach(notification => {
+ if (this.displayed.includes(notification.key)) return;
+ const options = notification.options || {};
+ this.props.enqueueSnackbar(notification.message, options);
+ this.storeDisplayed(notification.key);
+ this.props.removeSnackbar(notification.key);
+ });
+ }
+
+ render() {
+ return null;
+ }
+}
+
+const DisplayStackbars = withSnackbar(connect(mapProps, mapDispatch)(DisplaySnackbarsComponent));
+export default DisplayStackbars; \ No newline at end of file
diff --git a/sdnr/wt-odlux/odlux/framework/src/components/material-ui/toggleButton.tsx b/sdnr/wt-odlux/odlux/framework/src/components/material-ui/toggleButton.tsx
new file mode 100644
index 000000000..54f14a7e0
--- /dev/null
+++ b/sdnr/wt-odlux/odlux/framework/src/components/material-ui/toggleButton.tsx
@@ -0,0 +1,181 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.
+ * =================================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ * ============LICENSE_END==========================================================================
+ */
+
+import * as React from 'react';
+import classNames from 'classnames';
+import { Theme, alpha } from '@mui/material/styles';
+import { WithStyles } from '@mui/styles';
+import withStyles from '@mui/styles/withStyles';
+import createStyles from '@mui/styles/createStyles';
+import ButtonBase from '@mui/material/ButtonBase';
+
+
+export const styles = (theme: Theme) => createStyles({
+ /* Styles applied to the root element. */
+ root: {
+ ...theme.typography.button,
+ height: 32,
+ minWidth: 48,
+ margin: 0,
+ padding: `${theme.spacing(1 - 4)} ${theme.spacing(1.5)}`,
+ borderRadius: 2,
+ willChange: 'opacity',
+ color: alpha(theme.palette.action.active, 0.38),
+ '&:hover': {
+ textDecoration: 'none',
+ // Reset on mouse devices
+ backgroundColor: alpha(theme.palette.text.primary, 0.12),
+ '@media (hover: none)': {
+ backgroundColor: 'transparent',
+ },
+ '&.Mui-disabled': {
+ backgroundColor: 'transparent',
+ },
+ },
+ '&:not(:first-child)': {
+ borderTopLeftRadius: 0,
+ borderBottomLeftRadius: 0,
+ },
+ '&:not(:last-child)': {
+ borderTopRightRadius: 0,
+ borderBottomRightRadius: 0,
+ },
+ },
+ /* Styles applied to the root element if `disabled={true}`. */
+ disabled: {
+ color: alpha(theme.palette.action.disabled, 0.12),
+ },
+ /* Styles applied to the root element if `selected={true}`. */
+ selected: {
+ color: theme.palette.action.active,
+ '&:after': {
+ content: '""',
+ display: 'block',
+ position: 'absolute',
+ overflow: 'hidden',
+ borderRadius: 'inherit',
+ width: '100%',
+ height: '100%',
+ left: 0,
+ top: 0,
+ pointerEvents: 'none',
+ zIndex: 0,
+ backgroundColor: 'currentColor',
+ opacity: 0.38,
+ },
+ '& + &:before': {
+ content: '""',
+ display: 'block',
+ position: 'absolute',
+ overflow: 'hidden',
+ width: 1,
+ height: '100%',
+ left: 0,
+ top: 0,
+ pointerEvents: 'none',
+ zIndex: 0,
+ backgroundColor: 'currentColor',
+ opacity: 0.12,
+ },
+ },
+ /* Styles applied to the `label` wrapper element. */
+ label: {
+ width: '100%',
+ display: 'inherit',
+ alignItems: 'inherit',
+ justifyContent: 'inherit',
+ },
+});
+
+export type ToggleButtonClassKey = 'disabled' | 'root' | 'label' | 'selected';
+
+interface IToggleButtonProps extends WithStyles<typeof styles> {
+ className?: string;
+ component?: React.ReactType<IToggleButtonProps>;
+ disabled?: boolean;
+ disableFocusRipple?: boolean;
+ disableRipple?: boolean;
+ selected?: boolean;
+ type?: string;
+ value?: any;
+ onClick?: (event: React.FormEvent<HTMLElement>, value?: any) => void;
+ onChange?: (event: React.FormEvent<HTMLElement>, value?: any) => void;
+}
+
+class ToggleButtonComponent extends React.Component<IToggleButtonProps> {
+ handleChange = (event: React.FormEvent<HTMLElement>) => {
+ const { onChange, onClick, value } = this.props;
+
+ event.stopPropagation();
+ if (onClick) {
+ onClick(event, value);
+ if (event.isDefaultPrevented()) {
+ return;
+ }
+ }
+
+ if (onChange) {
+ onChange(event, value);
+ }
+ event.preventDefault();
+ };
+
+ render() {
+ const {
+ children,
+ className: classNameProp,
+ classes,
+ disableFocusRipple,
+ disabled,
+ selected,
+ ...other
+ } = this.props;
+
+ const className = classNames(
+ classes.root,
+ {
+ [classes.disabled]: disabled,
+ [classes.selected]: selected,
+ },
+ classNameProp,
+ );
+
+ return (
+ <ButtonBase
+ className={className}
+ disabled={disabled}
+ focusRipple={!disableFocusRipple}
+ onClick={this.handleChange}
+ href="#"
+ {...other}
+ >
+ <span className={classes.label}>{children}</span>
+ </ButtonBase>
+ );
+ }
+ public static defaultProps = {
+ disabled: false,
+ disableFocusRipple: false,
+ disableRipple: false,
+ };
+
+ public static muiName = 'ToggleButton';
+}
+
+export const ToggleButton = withStyles(styles, { name: 'MuiToggleButton' })(ToggleButtonComponent);
+export default ToggleButton; \ No newline at end of file
diff --git a/sdnr/wt-odlux/odlux/framework/src/components/material-ui/toggleButtonGroup.tsx b/sdnr/wt-odlux/odlux/framework/src/components/material-ui/toggleButtonGroup.tsx
new file mode 100644
index 000000000..bdabe0d56
--- /dev/null
+++ b/sdnr/wt-odlux/odlux/framework/src/components/material-ui/toggleButtonGroup.tsx
@@ -0,0 +1,40 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.
+ * =================================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ * ============LICENSE_END==========================================================================
+ */
+import * as React from 'react';
+import classNames from 'classnames';
+import { Theme } from '@mui/material/styles';
+
+import { WithStyles } from '@mui/styles';
+import withStyles from '@mui/styles/withStyles';
+import createStyles from '@mui/styles/createStyles';
+
+export const styles = (theme: Theme) => createStyles({
+ /* Styles applied to the root element. */
+ root: {
+ transition: theme.transitions.create('background,box-shadow'),
+ background: 'transparent',
+ borderRadius: 2,
+ overflow: 'hidden',
+ },
+ /* Styles applied to the root element if `selected={true}` or `selected="auto" and `value` set. */
+ selected: {
+ background: theme.palette.background.paper,
+ boxShadow: theme.shadows[2],
+ },
+});
+
diff --git a/sdnr/wt-odlux/odlux/framework/src/components/material-ui/treeView.tsx b/sdnr/wt-odlux/odlux/framework/src/components/material-ui/treeView.tsx
new file mode 100644
index 000000000..5c23909c4
--- /dev/null
+++ b/sdnr/wt-odlux/odlux/framework/src/components/material-ui/treeView.tsx
@@ -0,0 +1,380 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.
+ * =================================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ * ============LICENSE_END==========================================================================
+ */
+import * as React from 'react';
+import { Theme } from '@mui/material/styles';
+
+import { makeStyles, WithStyles, WithTheme } from '@mui/styles';
+import withStyles from '@mui/styles/withStyles';
+import createStyles from '@mui/styles/createStyles';
+
+import { List, ListItem, TextField, ListItemText, ListItemIcon, Typography } from '@mui/material';
+import { DistributiveOmit } from '@mui/types';
+
+import withTheme from '@mui/styles/withTheme';
+
+import { SvgIconProps } from '@mui/material/SvgIcon';
+import FileIcon from '@mui/icons-material/InsertDriveFile';
+import CloseIcon from '@mui/icons-material/ExpandLess';
+import OpenIcon from '@mui/icons-material/ExpandMore';
+import FolderIcon from '@mui/icons-material/Folder';
+
+declare module '@mui/styles/defaultTheme' {
+ // eslint-disable-next-line @typescript-eslint/no-empty-interface (remove this line if you don't have the rule enabled)
+ interface DefaultTheme extends Theme {}
+}
+
+const styles = (theme: Theme) => createStyles({
+ root: {
+ padding: 0,
+ paddingBottom: 8,
+ paddingTop: 8,
+ },
+ search: {
+ padding: `0px ${theme.spacing(1)}`
+ }
+});
+
+export enum SearchMode {
+ OnKeyDown = 1,
+ OnEnter =2
+}
+
+export type TreeItem<TData = { }> = {
+ disabled?: boolean;
+ icon?: React.ComponentType<SvgIconProps>;
+ iconClass?: string;
+ content: string;
+ contentClass?: string;
+ children?: TreeItem<TData>[];
+ value?: TData;
+}
+
+export type ExternalTreeItem<TData = {}> = TreeItem<TData> & {
+ isMatch?: boolean;
+}
+
+
+type TreeViewComponentState<TData = { }> = {
+ /** All indices of all expanded Items */
+ expandedItems: ExternalTreeItem<TData>[];
+ /** The index of the active iten or undefined if no item is active. */
+ activeItem?: ExternalTreeItem<TData>;
+ /** The search term or undefined if search is currently not active. */
+ searchTerm?: string;
+ searchTermValue?: string;
+}
+
+type TreeViewComponentBaseProps<TData = {}> = WithTheme & WithStyles<typeof styles> & {
+ className?: string;
+ items: TreeItem<TData>[];
+ useFolderIcons?: boolean;
+ enableSearchBar?: boolean;
+ autoExpandFolder?: boolean;
+ style?: React.CSSProperties;
+ itemHeight?: number;
+ depthOffset?: number;
+ searchMode?: SearchMode;
+
+}
+
+type TreeViewComponentWithInternalStateProps<TData = { }> = TreeViewComponentBaseProps<TData> & {
+ initialSearchTerm? : string;
+ onItemClick?: (item: TreeItem<TData>) => void;
+ onFolderClick?: (item: TreeItem<TData>) => void;
+}
+
+type TreeViewComponentWithExternalSearchProps<TData = {}> = TreeViewComponentBaseProps<TData> & {
+ items: ExternalTreeItem<TData>[];
+ initialSearchTerm? : string;
+ searchTerm: string;
+ onSearch: (searchTerm: string) => void;
+ onItemClick?: (item: TreeItem<TData>) => void;
+ onFolderClick?: (item: TreeItem<TData>) => void;
+}
+
+type TreeViewComponentWithExternalStateProps<TData = {}> = TreeViewComponentBaseProps<TData> & TreeViewComponentState<TData> & {
+ items: ExternalTreeItem<TData>[];
+ initialSearchTerm? : string;
+ searchTerm: string;
+ onSearch: (searchTerm: string) => void;
+ onItemClick: (item: TreeItem<TData>) => void;
+ onFolderClick: (item: TreeItem<TData>) => void;
+}
+
+type TreeViewComponentProps<TData = { }> =
+ | TreeViewComponentWithInternalStateProps<TData>
+ | TreeViewComponentWithExternalSearchProps<TData>
+ | TreeViewComponentWithExternalStateProps<TData>;
+
+function isTreeViewComponentWithExternalSearchProps(props: TreeViewComponentProps): props is TreeViewComponentWithExternalSearchProps {
+ const propsWithExternalState = (props as TreeViewComponentWithExternalStateProps)
+ return (
+ propsWithExternalState.onSearch instanceof Function &&
+ propsWithExternalState.onFolderClick === undefined &&
+ propsWithExternalState.expandedItems === undefined &&
+ propsWithExternalState.searchTerm !== undefined
+ );
+}
+
+function isTreeViewComponentWithExternalStateProps(props: TreeViewComponentProps): props is TreeViewComponentWithExternalStateProps {
+ const propsWithExternalState = (props as TreeViewComponentWithExternalStateProps)
+ return (
+ propsWithExternalState.onSearch instanceof Function &&
+ propsWithExternalState.onFolderClick instanceof Function &&
+ propsWithExternalState.expandedItems !== undefined &&
+ propsWithExternalState.searchTerm !== undefined
+ );
+}
+
+class TreeViewComponent<TData = { }> extends React.Component<TreeViewComponentProps<TData>, TreeViewComponentState<TData>> {
+
+ /**
+ * Initializes a new instance.
+ */
+ constructor(props: TreeViewComponentProps<TData>) {
+ super(props);
+
+ this.state = {
+ expandedItems: [],
+ activeItem: undefined,
+ searchTerm: undefined,
+ searchTermValue: props.initialSearchTerm
+ };
+ }
+
+ render(): JSX.Element {
+ this.itemIndex = 0;
+ const { searchTerm , searchTermValue} = this.state;
+ const { children, items, enableSearchBar } = this.props;
+
+ return (
+ <div className={this.props.className ? `${this.props.classes.root} ${this.props.className}` : this.props.classes.root} style={this.props.style}>
+ {children}
+ {enableSearchBar && <TextField variant="standard" label={"Search"} inputProps={{'aria-label': 'treeview-searchfield'}} fullWidth={true} className={this.props.classes.search} value={searchTermValue} onKeyDown={this.onSearchKeyDown} onChange={this.onChangeSearchText} /> || null}
+ {enableSearchBar && (searchTerm === undefined || searchTerm.length===0 )&& <Typography style={{marginTop:'10px'}}>Please search for an inventory identifier or use *.</Typography>}
+ <List>
+ {this.renderItems(items, searchTerm && searchTerm.toLowerCase())}
+ </List>
+ </div>
+ );
+ }
+
+ private itemIndex: number = 0;
+ private renderItems = (items: TreeItem<TData>[], searchTerm: string | undefined, depth: number = 1, forceRender: boolean = true) => {
+
+ return items.reduce((acc, item) => {
+
+ const children = item.children; // this.props.childrenProperty && ((item as any)[this.props.childrenProperty] as TData[]);
+ const childrenJsx = children && this.renderItems(children, searchTerm, depth + 1, this.state.expandedItems.indexOf(item) > -1);
+
+ const expanded = !isTreeViewComponentWithExternalStateProps(this.props) && searchTerm
+ ? childrenJsx && childrenJsx.length > 0
+ : !children
+ ? false
+ : this.state.expandedItems.indexOf(item) > -1;
+ const isFolder = children !== undefined;
+
+ const itemJsx = this.renderItem(item, searchTerm, depth, isFolder, expanded || false, forceRender);
+ itemJsx && acc.push(itemJsx);
+
+ if (isFolder && expanded && childrenJsx) {
+ acc.push(...childrenJsx);
+ }
+ return acc;
+
+ }, [] as JSX.Element[]);
+ }
+ private renderItem = (item: ExternalTreeItem<TData> , searchTerm: string | undefined, depth: number, isFolder: boolean, expanded: boolean, forceRender: boolean): JSX.Element | null => {
+ const styles = {
+ item: {
+ paddingLeft: (((this.props.depthOffset || 0) + depth) * Number(this.props.theme.spacing(3).replace("px", ''))),
+ backgroundColor: this.state.activeItem === item ? this.props.theme.palette.action.selected : undefined,
+ height: this.props.itemHeight || undefined,
+ cursor: item.disabled ? 'not-allowed' : 'pointer',
+ color: item.disabled ? this.props.theme.palette.text.disabled : this.props.theme.palette.text.primary,
+ overflow: 'hidden',
+ transform: 'translateZ(0)',
+ }
+ };
+
+ const text = item.content || ''; // need to keep track of search
+ const search_array = searchTerm?.split("*");
+ const index = search_array?.findIndex(function (_str: String) {
+ return _str.length > 0;
+ }) || 0;
+ const firstSearchSubString = search_array ? search_array[index] : "";
+ const matchIndex = firstSearchSubString ? text.toLowerCase().indexOf(firstSearchSubString) : -1;
+
+ const hasStarInSearch = search_array ? search_array.length > 1 : false;
+ const isSearchStringWithStar = hasStarInSearch && firstSearchSubString?.length > 0 || false;
+
+ const searchTermLength = firstSearchSubString && firstSearchSubString.length || 0;
+
+ const handleClickCreator = (isIcon: boolean) => (event: React.SyntheticEvent) => {
+ if (item.disabled) return;
+ event.preventDefault();
+ event.stopPropagation();
+ if (isFolder && (this.props.autoExpandFolder || isIcon)) {
+ this.props.onFolderClick ? this.props.onFolderClick(item) : this.onFolderClick(item);
+ } else {
+ this.props.onItemClick ? this.props.onItemClick(item) : this.onItemClick(item);
+ }
+ };
+
+ return ((searchTerm && (matchIndex > -1 || expanded || (!isTreeViewComponentWithExternalStateProps(this.props) && item.isMatch || depth === 1)) || !searchTerm || forceRender)
+ ? (
+ <ListItem key={`tree-list-${this.itemIndex++}`} aria-label="tree-view-item" style={styles.item} onClick={handleClickCreator(false)} button >
+
+ { // display the left icon
+ (this.props.useFolderIcons && <ListItemIcon>{isFolder ? <FolderIcon /> : <FileIcon />}</ListItemIcon>) ||
+ (item.icon && (<ListItemIcon className={ item.iconClass }><item.icon /></ListItemIcon>))}
+
+
+ { // highlight search result
+ isSearchStringWithStar && matchIndex > -1
+ ? <ListItemText className={item.contentClass} primary={(
+ <span
+ style={{
+ display: 'inline-block',
+ backgroundColor: 'rgba(255,235,59,0.5)',
+ padding: '3px',
+ }}
+ >
+ {text}
+ </span>)} />
+ : matchIndex > -1
+ ? <ListItemText className={item.contentClass} primary={(<span>
+ {text.substring(0, matchIndex)}
+ <span
+ style={{
+ display: 'inline-block',
+ backgroundColor: 'rgba(255,235,59,0.5)',
+ padding: '3px',
+ }}
+ >
+ {text.substring(matchIndex, matchIndex + searchTermLength)}
+ </span>
+ {text.substring(matchIndex + searchTermLength)}
+ </span>)} />
+ : <ListItemText className={item.contentClass} primary={(
+ <span style={item.isMatch ? {
+ display: 'inline-block',
+ padding: '3px',
+ } : undefined}>
+ {text} </span>
+ )} />
+ }
+
+ { // display the right icon, depending on the state
+ !isFolder ? null : expanded ? (<OpenIcon onClick={handleClickCreator(true)} />) : (<CloseIcon onClick={handleClickCreator(true)} />)}
+ </ListItem>
+ )
+ : null
+ );
+ }
+
+ private onFolderClick = (item: TreeItem<TData>) => {
+ // toggle items with children
+ if (this.state.searchTerm) return;
+ const indexOfItemToToggle = this.state.expandedItems.indexOf(item);
+ if (indexOfItemToToggle === -1) {
+ this.setState({
+ expandedItems: [...this.state.expandedItems, item],
+ });
+ } else {
+ this.setState({
+ expandedItems: [
+ ...this.state.expandedItems.slice(0, indexOfItemToToggle),
+ ...this.state.expandedItems.slice(indexOfItemToToggle + 1),
+ ]
+ });
+ }
+ };
+
+ private onItemClick = (item: TreeItem<TData>) => {
+ // activate items without children
+ this.setState({
+ activeItem: item,
+ });
+ };
+
+ private onSearchKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
+ const enterMode = this.props.searchMode === SearchMode.OnEnter;
+
+ if (enterMode && event.keyCode === 13) {
+ event.preventDefault();
+ event.stopPropagation();
+
+ enterMode && this.setState({
+ searchTerm: this.state.searchTermValue
+ });
+
+ if (isTreeViewComponentWithExternalSearchProps(this.props) || isTreeViewComponentWithExternalStateProps(this.props)) {
+ this.props.onSearch(this.state.searchTermValue || "");
+ }
+ }
+ }
+
+ private onChangeSearchText = (event: React.ChangeEvent<HTMLInputElement>) => {
+ event.preventDefault();
+ event.stopPropagation();
+
+ const keyDownMode = (!this.props.searchMode || this.props.searchMode === SearchMode.OnKeyDown);
+
+ this.setState(keyDownMode
+ ? {
+ searchTerm: event.target.value,
+ searchTermValue: event.target.value,
+ } as any : {
+ searchTermValue: event.target.value,
+ }) as any;
+
+ if ((isTreeViewComponentWithExternalSearchProps(this.props) || isTreeViewComponentWithExternalStateProps(this.props)) && keyDownMode) {
+ this.props.onSearch(event.target.value);
+ }
+ };
+
+ static getDerivedStateFromProps(props: TreeViewComponentProps, state: TreeViewComponentState): TreeViewComponentState {
+ if (isTreeViewComponentWithExternalStateProps(props)) {
+ return {
+ ...state,
+ expandedItems: props.expandedItems || [],
+ activeItem: props.activeItem,
+ searchTerm: props.searchTerm
+ };
+ } else if (isTreeViewComponentWithExternalSearchProps(props)) {
+ return {
+ ...state,
+ searchTerm: props.searchTerm,
+ };
+ }
+ return state;
+ }
+
+ public static defaultProps = {
+ useFolderIcons: false,
+ enableSearchBar: false,
+ autoExpandFolder: false,
+ depthOffset: 0
+ }
+}
+
+export type TreeViewCtorType<TData = { }> = new () => React.Component<DistributiveOmit<TreeViewComponentProps<TData>, 'theme'|'classes'>>;
+
+export const TreeView = withTheme(withStyles(styles)(TreeViewComponent));
+export default TreeView; \ No newline at end of file
diff --git a/sdnr/wt-odlux/odlux/framework/src/components/navigationMenu.tsx b/sdnr/wt-odlux/odlux/framework/src/components/navigationMenu.tsx
new file mode 100644
index 000000000..bcc191a89
--- /dev/null
+++ b/sdnr/wt-odlux/odlux/framework/src/components/navigationMenu.tsx
@@ -0,0 +1,218 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.
+ * =================================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ * ============LICENSE_END==========================================================================
+ */
+import React from 'react';
+import { Theme } from '@mui/material/styles';
+import classNames from 'classnames';
+
+import { WithStyles } from '@mui/styles';
+import withStyles from '@mui/styles/withStyles';
+import createStyles from '@mui/styles/createStyles';
+
+import Drawer from '@mui/material/Drawer';
+import List from '@mui/material/List';
+
+import Divider from '@mui/material/Divider';
+
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faProjectDiagram } from '@fortawesome/free-solid-svg-icons';
+
+import ListItemLink from '../components/material-ui/listItemLink';
+
+import { connect, Connect } from '../flux/connect';
+import { MenuAction } from '../actions/menuAction';
+import { transportPCEUrl } from '../app';
+
+const aboutIcon = require('../assets/icons/About.svg');
+const homeIcon = require('../assets/icons/Home.svg');
+const loginIcon = require('../assets/icons/User.svg');
+const settingsIcon = require('../assets/icons/Tools.svg');
+
+const drawerWidth = 240;
+
+const extraLinks = (window as any)._odluxExtraLinks as [string, string][];
+
+const styles = (theme: Theme) => createStyles({
+ drawerPaper: {
+ position: 'relative',
+ width: drawerWidth,
+ },
+ toolbar: theme.mixins.toolbar as any,
+
+ drawerOpen: {
+ width: drawerWidth,
+ transition: theme.transitions.create('width', {
+ easing: theme.transitions.easing.sharp,
+ duration: theme.transitions.duration.enteringScreen,
+ }),
+ },
+ drawerClose: {
+ transition: theme.transitions.create('width', {
+ easing: theme.transitions.easing.sharp,
+ duration: theme.transitions.duration.leavingScreen,
+ }),
+ overflowX: 'hidden',
+ width: theme.spacing(7),
+ [theme.breakpoints.up('sm')]: {
+ width: theme.spacing(9),
+ },
+ },
+ drawer: {
+
+ },
+ menu: {
+ flex: "1 0 0%",
+ },
+ optLinks: {
+ borderTop: "2px solid #cfcfcf",
+ display: "flex",
+ flexDirection: "row",
+ flexWrap: "wrap",
+ justifyContent: "space-around"
+ },
+ link: {
+ margin: theme.spacing(1)+1,
+ fontSize: theme.typography.fontSize-2,
+ },
+});
+
+const tabletWidthBreakpoint = 768;
+
+export const NavigationMenu = withStyles(styles)(connect()(({ classes, state, dispatch }: WithStyles<typeof styles> & Connect & Connect) => {
+ const { user } = state.framework.authenticationState;
+ const isOpen = state.framework.applicationState.isMenuOpen;
+ const closedByUser = state.framework.applicationState.isMenuClosedByUser;
+ const transportUrl = state.framework.applicationState.transportpceUrl;
+
+ const [responsive, setResponsive] = React.useState(false);
+
+ //collapse menu on mount if necessary
+ React.useEffect(()=>{
+
+ if(isOpen && window.innerWidth <= tabletWidthBreakpoint){
+
+ setResponsive(true);
+ dispatch(new MenuAction(false));
+ }
+
+ },[]);
+
+ React.useEffect(() => {
+
+ function handleResize() {
+ if (user && user.isValid) {
+ if (window.innerWidth < tabletWidthBreakpoint && !responsive) {
+ setResponsive(true);
+ if (!closedByUser) {
+ dispatch(new MenuAction(false));
+ }
+
+ } else if (window.innerWidth > tabletWidthBreakpoint && responsive) {
+ setResponsive(false);
+ if (!closedByUser) {
+ dispatch(new MenuAction(true));
+ }
+
+ }
+ }
+ }
+ window.addEventListener("resize", handleResize);
+
+
+ return () => {
+ window.removeEventListener("resize", handleResize);
+ }
+ })
+
+ React.useEffect(()=>{
+ // trigger a resize if menu changed in case elements have to re-arrange
+ window.dispatchEvent(new Event('menu-resized'));
+ }, [isOpen])
+
+ let menuItems = state.framework.applicationRegistration && Object.keys(state.framework.applicationRegistration).map(key => {
+ const reg = state.framework.applicationRegistration[key];
+ return reg && (
+ <ListItemLink
+ key={reg.name}
+ to={reg.path || `/${reg.name}`}
+ primary={reg.menuEntry || reg.name}
+ secondary={reg.subMenuEntry}
+ icon={reg.icon || null} />
+ ) || null;
+ }) || null;
+
+ if(transportUrl.length>0){
+
+ const transportPCELink = <ListItemLink
+ key={"transportPCE"}
+ to={transportUrl}
+ primary={"TransportPCE"}
+ icon={faProjectDiagram}
+ external />;
+
+ const linkFound = menuItems.find(obj => obj.key === "microwave");
+
+ if (linkFound) {
+ const index = menuItems.indexOf(linkFound);
+ menuItems.splice(index + 1, 0, transportPCELink);
+ } else {
+ menuItems.push(transportPCELink);
+ }
+ }
+
+
+ return (
+ <Drawer
+ variant="permanent"
+ className={
+ classNames(classes.drawer, {
+ [classes.drawerOpen]: isOpen,
+ [classes.drawerClose]: !isOpen
+ })
+ }
+ classes={{
+ paper: classes.drawerPaper,
+ }}
+ >
+ {user && user.isValid && <>
+ <div className={classes.toolbar} />
+ { /* https://fiffty.github.io/react-treeview-mui/ */}
+ <List className={classes.menu} component="nav">
+ <ListItemLink exact to="/" primary="Home" icon={homeIcon} />
+ <Divider />
+ {
+ menuItems
+ }
+ <Divider />
+ <ListItemLink to="/about" primary="About" icon={aboutIcon} />
+ {(false && process.env.NODE_ENV === "development")
+ ? <>
+ <Divider />
+ <ListItemLink to="/test" primary="Test" icon={settingsIcon} />
+ </>
+ : null
+ }
+ </List>
+ {isOpen && extraLinks && <div className={classes.optLinks}>
+ {extraLinks.map(linkInfo => (<a className={classes.link} href={linkInfo[1]}>{linkInfo[0]}</a>))}
+ </div> || null}
+ </> || null
+ }
+ </Drawer>)
+}));
+
+export default NavigationMenu; \ No newline at end of file
diff --git a/sdnr/wt-odlux/odlux/framework/src/components/objectDump/index.tsx b/sdnr/wt-odlux/odlux/framework/src/components/objectDump/index.tsx
new file mode 100644
index 000000000..10a0547be
--- /dev/null
+++ b/sdnr/wt-odlux/odlux/framework/src/components/objectDump/index.tsx
@@ -0,0 +1,205 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.
+ * =================================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ * ============LICENSE_END==========================================================================
+ */
+
+import * as React from "react";
+import makeStyles from '@mui/styles/makeStyles';
+
+export const getTypeName = (obj: any): string => {
+ if (obj == null) {
+ return obj === undefined ? "Undefined" : "Null";
+ }
+ return Object.prototype.toString.call(obj).slice(8, -1);
+};
+
+const isObjectLike = (obj: any) => {
+ return typeof obj === "object" && obj !== null;
+};
+
+const isBoolean = (obj: any) => {
+ return obj === true || obj === false ||
+ (isObjectLike(obj) && getTypeName(obj) === "Boolean");
+};
+
+const isNumber = (obj: any) => {
+ return typeof obj === "number" ||
+ (isObjectLike(obj) && getTypeName(obj) === "Number");
+};
+
+const isString = (obj: any) => {
+ return typeof obj === "string" ||
+ (isObjectLike(obj) && getTypeName(obj) === "String");
+};
+
+const isNull = (obj: any) => {
+ return obj === null;
+};
+
+const isDate = (obj: any): boolean => {
+ return isObjectLike(obj) && (obj instanceof Date);
+};
+
+const useSimpleTableStyles = makeStyles({
+ root: {
+ },
+ table: {
+ fontFamily: "verdana, arial, helvetica, sans-serif",
+ borderSpacing: "3px",
+ borderCollapse: "separate",
+ marginLeft: "30px"
+ },
+ label: {
+ cursor: "pointer",
+ },
+ th: {
+ textAlign: "left",
+ color: "white",
+ padding: "5px",
+ backgroundColor: "#cccccc",
+
+ },
+ td: {
+ verticalAlign: "top",
+ padding: "0.5rem 1rem",
+ border: "2px solid #DDD"
+ },
+ object: {
+ },
+ objectTh: {
+ backgroundColor: "#cccccc",
+ },
+ objectTd: {
+ padding: "0.5rem 1rem",
+ border: "2px solid #DDD"
+ },
+ chevron: {
+ '&:before': {
+ borderStyle: 'solid',
+ borderWidth: '0.25em 0.25em 0 0',
+ content: '\'\'',
+ display: 'inline-block',
+ height: '0.45em',
+ left: '0.15em',
+ position: 'relative',
+ top: '0.15em',
+ transform: 'rotate(-45deg)',
+ transition: 'all 0.3s',
+ verticalAlign: 'top',
+ width: '0.45em',
+ }
+
+ },
+ right: {
+ '&:before': {
+ left: '0',
+ transform: 'rotate(45deg)',
+ }
+ },
+ bottom: {
+ '&:before': {
+ left: '0',
+ transform: 'rotate(135deg)',
+ }
+ },
+});
+
+
+type SimpleTableProps = {
+ classNameTh?: string;
+ label?: JSX.Element | string | null;
+ cols?: number;
+ expand?: boolean;
+ ariaLabel?: string;
+}
+
+const SimpleTable: React.FC<SimpleTableProps> = (props) => {
+ const { label = '', cols = 2, expand = true, classNameTh, children } = props;
+ const [isExpanded, setIsExpanded] = React.useState(expand);
+
+ const classes = useSimpleTableStyles();
+
+ React.useEffect(() => {
+ setIsExpanded(expand);
+ }, [expand]);
+
+ const handleClick = () => {
+ setIsExpanded(!isExpanded);
+ };
+
+ return (
+ <table className={`${classes.root} ${classes.table}`} aria-label={props.ariaLabel? props.ariaLabel+'-table' : 'table'}>
+ {label && (<thead>
+ <tr aria-label={props.ariaLabel? props.ariaLabel+'-title-row' : 'title row'}>
+ <th className={`${classes.th} ${classes.label} ${classNameTh ? classNameTh : ''}`} colSpan={cols} onClick={handleClick}>
+ <span className={`${classes.chevron} ${isExpanded ? classes.bottom : classes.right }`}></span> { label }
+ </th>
+ </tr>
+ </thead>) || null
+ }
+ {isExpanded && <tbody >{children}</tbody> || null}
+ </table>
+ );
+};
+
+
+type ObjectRendererProps = {
+ className?: string;
+ label?: JSX.Element | string | null;
+ expand?: boolean;
+ object: { [key: string]: any };
+ ariaLabel?: string;
+};
+
+const ObjectRenderer: React.FC<ObjectRendererProps> = (props) => {
+ const { object, className, label = 'Object', expand = true } = props;
+ const classes = useSimpleTableStyles();
+
+ return (
+ <SimpleTable ariaLabel={props.ariaLabel} classNameTh={classes.objectTh} label={getTypeName(object) || label} expand={expand}>
+ {
+ Object.keys(object).map(key => {
+ return (
+ <tr key={String(key)} aria-label={props.ariaLabel? props.ariaLabel+'-row': 'row'}>
+ <td className={`${classes.td} ${classes.objectTd}`} aria-label="object-title">{String(key)} </td>
+ <td className={`${classes.td}`} aria-label="object-details">{renderObject(object[key], "sub-element")}</td>
+ </tr>
+ );
+ })
+ }
+ </SimpleTable>
+ );
+};
+
+
+type ArrayRendererProps = {
+ label?: JSX.Element | string | null;
+ extraRenderer?: { [label: string]: React.ComponentType<{ label?: JSX.Element | string | null; object: any; }> };
+ description?: string;
+ object: any;
+};
+
+const ArrayRenderer: React.FC<ArrayRendererProps> = (props) => {
+
+ return null;
+};
+
+export const renderObject = (object: any, ariaLabel?: string): JSX.Element | string => {
+ if (isString(object) || isNumber(object) || isBoolean(object)) {
+ return String(object);
+ }
+ return <ObjectRenderer object={object} ariaLabel={ariaLabel} />;
+};
diff --git a/sdnr/wt-odlux/odlux/framework/src/components/routing/appFrame.tsx b/sdnr/wt-odlux/odlux/framework/src/components/routing/appFrame.tsx
new file mode 100644
index 000000000..aa22f17f4
--- /dev/null
+++ b/sdnr/wt-odlux/odlux/framework/src/components/routing/appFrame.tsx
@@ -0,0 +1,55 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.
+ * =================================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ * ============LICENSE_END==========================================================================
+ */
+import React from 'react';
+
+import { connect, Connect } from '../../flux/connect';
+
+import { SetTitleAction } from '../../actions/titleActions';
+import { AddErrorInfoAction } from '../../actions/errorActions';
+
+import { IconType } from '../../models/iconDefinition';
+
+export interface IAppFrameProps {
+ title: string;
+ icon?: IconType;
+ appId?: string
+}
+
+/**
+ * Represents a component to wich will embed each single app providing the
+ * functionality to update the title and implement an exeprion border.
+ */
+export class AppFrame extends React.Component<IAppFrameProps & Connect> {
+
+ public render(): JSX.Element {
+ return (
+ <div style={{ flex: "1", overflow: "hidden", display: "flex", flexDirection: "column" }}>
+ { this.props.children }
+ </div>
+ )
+ }
+
+ public componentDidMount() {
+ this.props.dispatch(new SetTitleAction(this.props.title, this.props.icon, this.props.appId));
+ }
+ public componentDidCatch(error: Error | null, info: object) {
+ this.props.dispatch(new AddErrorInfoAction({ error, info }));
+ }
+}
+
+export default connect()(AppFrame); \ No newline at end of file
diff --git a/sdnr/wt-odlux/odlux/framework/src/components/settings/general.tsx b/sdnr/wt-odlux/odlux/framework/src/components/settings/general.tsx
new file mode 100644
index 000000000..ffd516ba1
--- /dev/null
+++ b/sdnr/wt-odlux/odlux/framework/src/components/settings/general.tsx
@@ -0,0 +1,110 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2021 highstreet technologies GmbH Intellectual Property. All rights reserved.
+ * =================================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ * ============LICENSE_END==========================================================================
+ */
+
+import React from 'react';
+import { Button, FormControlLabel, Switch, Typography } from '@mui/material';
+import makeStyles from '@mui/styles/makeStyles';
+import { SettingsComponentProps } from '../../models/settings';
+import { connect, Connect, IDispatcher } from '../../flux/connect';
+import { IApplicationStoreState } from '../../store/applicationStore';
+import { getGeneralSettingsAction, SetGeneralSettingsAction, updateGeneralSettingsAction } from '../../actions/settingsAction';
+import { sendMessage, SettingsMessage } from '../../services/broadcastService';
+
+
+type props = Connect<typeof mapProps, typeof mapDispatch> & SettingsComponentProps;
+
+const mapProps = (state: IApplicationStoreState) => ({
+ settings: state.framework.applicationState.settings,
+ user: state.framework.authenticationState.user?.user
+
+});
+
+const mapDispatch = (dispatcher: IDispatcher) => ({
+
+ updateSettings :(activateNotifications: boolean) => dispatcher.dispatch(updateGeneralSettingsAction(activateNotifications)),
+ getSettings: () =>dispatcher.dispatch(getGeneralSettingsAction()),
+ });
+
+const styles = makeStyles({
+ sectionMargin: {
+ marginTop: "30px",
+ marginBottom: "15px"
+ },
+ elementMargin: {
+ marginLeft: "10px"
+ },
+ buttonPosition:{
+ position: "absolute",
+ right: "32%"
+ }
+ });
+
+const General : React.FunctionComponent<props> = (props) =>{
+
+const classes = styles();
+
+const [areWebsocketsEnabled, setWebsocketsEnabled] = React.useState(props.settings.general.areNotificationsEnabled || false);
+
+React.useEffect(()=>{
+ props.getSettings();
+},[]);
+
+React.useEffect(()=>{
+ if(props.settings.general.areNotificationsEnabled!==null)
+ setWebsocketsEnabled(props.settings.general.areNotificationsEnabled)
+},[props.settings]);
+
+const onWebsocketsChange = (event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>, newValue: boolean) =>{
+ setWebsocketsEnabled(newValue);
+ }
+
+const onSave = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) =>{
+
+ e.preventDefault();
+ const message: SettingsMessage = {key: 'general', enableNotifications: areWebsocketsEnabled, user: props.user!};
+ sendMessage(message, "odlux_settings");
+ props.updateSettings(areWebsocketsEnabled);
+ props.onClose();
+}
+
+const onCancel = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) =>{
+ e.preventDefault();
+ props.onClose();
+
+}
+
+
+ return <div>
+ <Typography className={classes.sectionMargin} variant="body1" style={{ fontWeight: "bold" }} gutterBottom>
+ Enable Notifications
+ </Typography>
+ <FormControlLabel style={{ padding:5}}
+ value="end"
+ control={<Switch color="secondary" aria-label="enable-notifications-button" aria-checked={areWebsocketsEnabled} checked={areWebsocketsEnabled} onChange={onWebsocketsChange} />}
+ label="Enable Notifications"
+ labelPlacement="end"
+ />
+ <div className={classes.buttonPosition}>
+ <Button aria-label="cancel-button" className={classes.elementMargin} variant="contained" color="primary" onClick={onCancel}>Cancel</Button>
+ <Button aria-label="save-button" className={classes.elementMargin} variant="contained" color="secondary" onClick={onSave}>Save</Button>
+ </div>
+ </div>
+}
+
+export const GeneralUserSettings = connect(mapProps, mapDispatch)(General);
+export default GeneralUserSettings; \ No newline at end of file
diff --git a/sdnr/wt-odlux/odlux/framework/src/components/titleBar.tsx b/sdnr/wt-odlux/odlux/framework/src/components/titleBar.tsx
new file mode 100644
index 000000000..40c0fc736
--- /dev/null
+++ b/sdnr/wt-odlux/odlux/framework/src/components/titleBar.tsx
@@ -0,0 +1,233 @@
+/**
+ * ============LICENSE_START========================================================================
+ * ONAP : ccsdk feature sdnr wt odlux
+ * =================================================================================================
+ * Copyright (C) 2019 highstreet technologies GmbH Intellectual Property. All rights reserved.
+ * =================================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ * ============LICENSE_END==========================================================================
+ */
+import React from 'react';
+import { withRouter, RouteComponentProps } from 'react-router-dom';
+
+import { Theme } from '@mui/material/styles';
+import { WithStyles } from '@mui/styles';
+import withStyles from '@mui/styles/withStyles';
+import createStyles from '@mui/styles/createStyles';
+import AppBar from '@mui/material/AppBar';
+import Toolbar from '@mui/material/Toolbar';
+import Typography from '@mui/material/Typography';
+import Button from '@mui/material/Button';
+import IconButton from '@mui/material/IconButton';
+import AccountCircle from '@mui/icons-material/AccountCircle';
+import MenuItem from '@mui/material/MenuItem';
+import Menu from '@mui/material/Menu';
+
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faBan } from '@fortawesome/free-solid-svg-icons';
+import { faDotCircle } from '@fortawesome/free-solid-svg-icons';
+
+import { logoutUser } from '../actions/authentication';
+import { PushAction, ReplaceAction } from '../actions/navigationActions';
+
+import { connect, Connect, IDispatcher } from '../flux/connect';
+import { MenuAction, MenuClosedByUser } from '../actions/menuAction';
+
+import MenuIcon from './icons/menuIcon';
+import Logo from './logo';
+
+const styles = (theme: Theme) => createStyles({
+ appBar: {
+ zIndex: theme.zIndex.drawer + 1,
+ },
+ grow: {
+ flexGrow: 1,
+ },
+ menuButton: {
+ marginLeft: -12,
+ marginRight: 20,
+ },
+ icon: {
+ marginLeft: 16,
+ marginRight: 8,
+ marginBottom: -2,
+ },
+ connected: {
+ color: "green"
+ },
+ notConnected: {
+ color: "red"
+ },
+ notificationInfo: {
+ marginLeft: 5
+ }
+});
+
+const mapDispatch = (dispatcher: IDispatcher) => {
+ return {
+ logout: () => {
+ dispatcher.dispatch(logoutUser());
+ dispatcher.dispatch(new ReplaceAction("/login"));
+ },
+ openSettings : () =>{
+ dispatcher.dispatch(new PushAction("/settings"));
+ },
+ toggleMainMenu: (value: boolean, value2: boolean) => {
+ dispatcher.dispatch(new MenuAction(value));
+ dispatcher.dispatch(new MenuClosedByUser(value2))
+ }
+ }
+};
+
+type TitleBarProps = RouteComponentProps<{}> & WithStyles<typeof styles> & Connect<undefined, typeof mapDispatch>
+
+class TitleBarComponent extends React.Component<TitleBarProps, { anchorEl: HTMLElement | null }> {
+
+ constructor(props: TitleBarProps) {
+ super(props);
+ this.state = {
+ anchorEl: null
+ }
+
+ }
+ render(): JSX.Element {
+ const { classes, state, history, location } = this.props;
+ const open = !!this.state.anchorEl;
+ let toolbarElements: Array<JSX.Element>;
+ toolbarElements = [];
+
+ // create notificationInfo element
+ const notificationInfo = state.framework.applicationState.isWebsocketAvailable != undefined ?
+ (state.framework.applicationState.isWebsocketAvailable ?
+ <Typography aria-label="notifications-are-active" variant="body1" className={classes.notificationInfo}>Notifications <FontAwesomeIcon className={classes.connected} icon={faDotCircle} /> |</Typography> : <Typography aria-label="notifications-are-inactive" variant="body1" className={classes.notificationInfo}>Notifications <FontAwesomeIcon className={classes.notConnected} icon={faBan} /> |</Typography>)
+ : <Typography variant="body1" aria-label="notifications-are-not-available" className={classes.notificationInfo}>Notifications N/A |</Typography>;
+
+
+ // add notificationInfo element before help
+ if (state.framework.applicationRegistration) {
+ let isNotificationInfoAdded = false;
+ Object.keys(state.framework.applicationRegistration).map(key => {
+ const reg = state.framework.applicationRegistration[key];
+ if (reg && reg.statusBarElement) {
+ if (key === "help") {
+ isNotificationInfoAdded = true;
+ toolbarElements.push(notificationInfo);
+ }
+ toolbarElements.push(<reg.statusBarElement key={key} />);
+ }
+ });
+
+ // add notificationInfo in case help wasn't found
+ if (!isNotificationInfoAdded) {
+ toolbarElements.push(notificationInfo);
+ }
+ }
+
+ const stateIcon = state.framework.applicationState.icon;
+ const customIconHeight = 22;
+ const icon = !stateIcon
+ ? null
+ : (typeof stateIcon === 'string'
+ ? <img className={classes.icon} height={customIconHeight} src={stateIcon} />
+ : <FontAwesomeIcon className={classes.icon} icon={stateIcon} />)
+
+
+ return (
+ <AppBar enableColorOnDark position="absolute" className={classes.appBar}>
+ <Toolbar>
+ <IconButton
+ className={classes.menuButton}
+ color="inherit"
+ aria-label="Menu"
+ onClick={this.toggleMainMenu}
+ size="large">
+ <MenuIcon />
+ </IconButton>
+ <Logo />
+ <Typography variant="h6" color="inherit" >
+ {icon}
+ {state.framework.applicationState.title}
+ </Typography>
+ <div className={classes.grow}></div>
+ {
+ // render toolbar
+ toolbarElements.map((item) => {
+ return item
+ })
+ }
+
+ {state.framework.authenticationState.user
+ ? (<div>
+ <Button aria-label="current user menu button"
+ aria-owns={open ? 'menu-appbar' : undefined}
+ aria-haspopup="true"
+ onClick={this.openMenu}
+ color="inherit"
+ >
+ <AccountCircle />
+ {state.framework.authenticationState.user.user}
+ </Button>
+ <Menu
+ id="menu-appbar"
+ anchorEl={this.state.anchorEl}
+ anchorOrigin={{
+ vertical: 'top',
+ horizontal: 'right',
+ }}
+ transformOrigin={{
+ vertical: 'top',
+ horizontal: 'right',
+ }}
+ open={open}
+ onClose={this.closeMenu}
+ >
+ {/* <MenuItem onClick={ this.closeMenu }>Profile</MenuItem> */}
+ <MenuItem
+ aria-label="settings-button"
+ onClick={ () =>{
+ this.props.openSettings();
+ this.closeMenu(); }}>Settings</MenuItem>
+ <MenuItem
+ aria-label="logout-button"
+ onClick={() => {
+ this.props.logout();
+ this.closeMenu();
+ }}>Logout</MenuItem>
+ </Menu>
+ </div>)
+ : (<Button onClick={() => { history.push('/login') }} color="inherit" disabled={location.pathname == "/login"}>Login</Button>)}
+ </Toolbar>
+ </AppBar>
+ );
+ };
+
+ private toggleMainMenu = (event: React.MouseEvent<HTMLElement>) => {
+ console.log(this.props);
+ if (this.props.state.framework.authenticationState.user && this.props.state.framework.authenticationState.user.isValid) {
+ const isMainMenuOpen = this.props.state.framework.applicationState.isMenuOpen
+ const isClosedByUser = this.props.state.framework.applicationState.isMenuClosedByUser
+ this.props.toggleMainMenu(!isMainMenuOpen, !isClosedByUser);
+ }
+ }
+
+ private openMenu = (event: React.MouseEvent<HTMLElement>) => {
+ this.setState({ anchorEl: event.currentTarget });
+ };
+
+ private closeMenu = () => {
+ this.setState({ anchorEl: null });
+ };
+}
+
+//todo: ggf. https://github.com/acdlite/recompose verwenden zur Vereinfachung
+
+export const TitleBar = withStyles(styles)(withRouter(connect(undefined, mapDispatch)(TitleBarComponent)));
+export default TitleBar; \ No newline at end of file