summaryrefslogtreecommitdiffstats
path: root/sdnr/wt/odlux/apps/configurationApp/src/views/configurationApplication.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'sdnr/wt/odlux/apps/configurationApp/src/views/configurationApplication.tsx')
-rw-r--r--sdnr/wt/odlux/apps/configurationApp/src/views/configurationApplication.tsx626
1 files changed, 385 insertions, 241 deletions
diff --git a/sdnr/wt/odlux/apps/configurationApp/src/views/configurationApplication.tsx b/sdnr/wt/odlux/apps/configurationApp/src/views/configurationApplication.tsx
index d5189fcd0..24a4af8b2 100644
--- a/sdnr/wt/odlux/apps/configurationApp/src/views/configurationApplication.tsx
+++ b/sdnr/wt/odlux/apps/configurationApp/src/views/configurationApplication.tsx
@@ -15,306 +15,450 @@
* the License.
* ============LICENSE_END==========================================================================
*/
+
import * as React from 'react';
+import { RouteComponentProps, withRouter } from 'react-router-dom';
+
+import { WithStyles, withStyles, createStyles, Theme } from '@material-ui/core/styles';
-import { MaterialTable, MaterialTableCtorType } from '../../../../framework/src/components/material-table';
-import connect, { Connect, IDispatcher } from '../../../../framework/src/flux/connect';
-import { IApplicationStoreState } from '../../../../framework/src/store/applicationStore';
-import { IConnectAppStoreState } from '../../../connectApp/src/handlers/connectAppRootHandler';
-import { MountedNetworkElementType } from '../../../connectApp/src/models/mountedNetworkElements';
-import { NavigateToApplication } from '../../../../framework/src/actions/navigationActions';
-import { Dispatch } from '../../../../framework/src/flux/store';
+import connect, { IDispatcher, Connect } from "../../../../framework/src/flux/connect";
+import { IApplicationStoreState } from "../../../../framework/src/store/applicationStore";
+import MaterialTable, { ColumnModel, ColumnType, MaterialTableCtorType } from "../../../../framework/src/components/material-table";
+import { Loader } from "../../../../framework/src/components/material-ui/loader";
-import TextField from '@material-ui/core/TextField';
-import { Tooltip, Button, FormControl, InputLabel, Select, MenuItem, InputAdornment } from '@material-ui/core';
-import Link from '@material-ui/core/Link';
+import { SetSelectedValue, splitVPath, updateDataActionAsyncCreator } from "../actions/deviceActions";
+import { ViewSpecification, isViewElementString, isViewElementNumber, isViewElementBoolean, isViewElementObjectOrList, isViewElementSelection } from "../models/uiModels";
-import Table from '@material-ui/core/Table';
-import TableBody from '@material-ui/core/TableBody';
-import TableCell from '@material-ui/core/TableCell';
-import TableHead from '@material-ui/core/TableHead';
-import TableRow from '@material-ui/core/TableRow';
+import Fab from '@material-ui/core/Fab';
+import AddIcon from '@material-ui/icons/Add';
+import RemoveIcon from '@material-ui/icons/RemoveCircleOutline';
+import SaveIcon from '@material-ui/icons/Save';
+import EditIcon from '@material-ui/icons/Edit';
+import Tooltip from "@material-ui/core/Tooltip";
+import TextField from "@material-ui/core/TextField";
+import FormControl from "@material-ui/core/FormControl";
+import IconButton from "@material-ui/core/IconButton";
+import Button from "@material-ui/core/Button";
+import InputAdornment from "@material-ui/core/InputAdornment";
+import InputLabel from "@material-ui/core/InputLabel";
+import Select from "@material-ui/core/Select";
+import MenuItem from "@material-ui/core/MenuItem";
+import Breadcrumbs from "@material-ui/core/Breadcrumbs";
+import Link from "@material-ui/core/Link";
+import FormHelperText from '@material-ui/core/FormHelperText';
-import { ViewSpecification } from '../models/uiModels';
+const styles = (theme: Theme) => createStyles({
+ header: {
+ "display": "flex",
+ "justifyContent": "space-between",
+ },
+ leftButton: {
+ "justifyContent": "left"
+ },
+ outer: {
+ "flex": "1",
+ "heigh": "100%",
+ "display": "flex",
+ "alignItems": "center",
+ "justifyContent": "center",
+ },
+ inner: {
-const NetworkElementTable = MaterialTable as MaterialTableCtorType<MountedNetworkElementType>;
+ },
+ "icon": {
+ "marginRight": theme.spacing(0.5),
+ "width": 20,
+ "height": 20,
+ },
+ "fab": {
+ "margin": theme.spacing(1),
+ },
+ button: {
+ margin: 0,
+ padding: "6px 6px",
+ minWidth: 'unset'
+ },
+ readOnly: {
+ '& label.Mui-focused': {
+ color: 'green',
+ },
+ '& .MuiInput-underline:after': {
+ borderBottomColor: 'green',
+ },
+ '& .MuiOutlinedInput-root': {
+ '& fieldset': {
+ borderColor: 'red',
+ },
+ '&:hover fieldset': {
+ borderColor: 'yellow',
+ },
+ '&.Mui-focused fieldset': {
+ borderColor: 'green',
+ },
+ },
+ },
+});
const mapProps = (state: IApplicationStoreState) => ({
- ...state.configuration,
- avaliableDevices: state.connect.mountedNetworkElements.elements.filter(el => el.connectionStatus === "connected")
+ collectingData: state.configuration.valueSelector.collectingData,
+ listKeyProperty: state.configuration.valueSelector.keyProperty,
+ listSpecification: state.configuration.valueSelector.listSpecification,
+ listData: state.configuration.valueSelector.listData,
+ vPath: state.configuration.viewDescription.vPath,
+ nodeId: state.configuration.deviceDescription.nodeId,
+ viewData: state.configuration.viewDescription.viewData,
+ viewSpecification: state.configuration.viewDescription.viewSpecification,
+ displayAsList: state.configuration.viewDescription.displayAsList,
+ keyProperty: state.configuration.viewDescription.keyProperty,
});
-const mapDisp = (dispatcher: IDispatcher) => ({
- navigateTo: (viewId: string, index?: string | number) => dispatcher.dispatch((dispatch: Dispatch, getState: () => IApplicationStoreState) => {
- const { configuration: { nodeId, lpId, indexValues } } = getState();
- const newIndexValues = typeof index === 'number' && indexValues
- ? indexValues.split('/').slice(0, index).join("/")
- : indexValues
- ? `${indexValues}${index ? `/${index}` : ''}`
- : index;
- dispatch(new NavigateToApplication("configuration", `${nodeId}/${lpId}/${viewId}${newIndexValues ? `/${newIndexValues}` : ''}`));
-
- }),
- changeNode: (ndoeId: string) => dispatcher.dispatch((dispatch: Dispatch) => {
- dispatch(new NavigateToApplication("configuration", ndoeId));
- }),
- changeLp: (lpId: string) => dispatcher.dispatch((dispatch: Dispatch, getState: () => IApplicationStoreState) => {
- const { configuration: { nodeId } } = getState();
- dispatch(new NavigateToApplication("configuration", `${nodeId}/${lpId}`));
- })
+
+const mapDispatch = (dispatcher: IDispatcher) => ({
+ onValueSelected: (value: any) => dispatcher.dispatch(new SetSelectedValue(value)),
+ onUpdateData: (vPath: string, data: any) => dispatcher.dispatch(updateDataActionAsyncCreator(vPath, data)),
});
-type ConfigurationApplicationProps = Connect<typeof mapProps, typeof mapDisp>;
+const SelectElementTable = MaterialTable as MaterialTableCtorType<{ [key: string]: any }>;
-type ConfigurationApplicationState = {
+type ConfigurationApplicationComponentProps = RouteComponentProps & Connect<typeof mapProps, typeof mapDispatch> & WithStyles<typeof styles>;
+type ConfigurationApplicationComponentState = {
+ isNew: boolean;
+ editMode: boolean;
+ canEdit: boolean;
+ viewData: { [key: string]: any } | null;
}
-class ConfigurationApplicationComponent extends React.Component<ConfigurationApplicationProps, ConfigurationApplicationState> {
-
- render() {
- if (this.props.loading) {
- return (
- <h2>Collecting data from network element. Please wait ...</h2>
- );
- } else if (!this.props.nodeId) {
- return (
- <>
- <h2>Please select an network element to configure !</h2>
- <NetworkElementTable idProperty={"mountId"} rows={this.props.avaliableDevices} asynchronus
- onHandleClick={(evetm, rowData) => { this.props.changeNode(rowData.mountId) }} columns={
- [{ property:"mountId" }]
- } />
- </>
- );
- } else if (!this.props.lpId) {
- return this.props.coreModel && this.props.coreModel.ltp && this.props.coreModel.ltp.length
- ? (
- <>
- <h2>Please select an existing LP first !</h2>
- <ul>
- { this.props.coreModel.ltp.map(ltp => {
- return <li key={ltp.uuid}>
- <Link component="a" variant="body2" color="secondary" onClick={() => {
- this.props.changeLp(ltp.lp[0].uuid);
- }}>{ltp.lp[0].label[0].value}</Link>
- </li>
- }) || null}
- </ul>
- </>
- )
- : (
- <>
- <h2>No LTP / LP found !</h2>
- </>
- );
- } else if (!this.props.capability && !this.props.viewId) {
- return (
- <h2>Please select a capability or viewId first !</h2>
- );
+const OldProps = Symbol("OldProps");
+class ConfigurationApplicationComponent extends React.Component<ConfigurationApplicationComponentProps, ConfigurationApplicationComponentState> {
+
+ /**
+ *
+ */
+ constructor (props: ConfigurationApplicationComponentProps) {
+ super(props);
+
+ this.state = {
+ isNew: false,
+ canEdit: false,
+ editMode: false,
+ viewData: null
}
- const viewData = this.props.viewData;
- const viewSpecification = this.props.viewId
- ? this.props.viewSpecifications.find(d => d.id === this.props.viewId)
- : this.props.viewSpecifications.find(d => d.name === this.props.conditionalPackage);
-
- return viewSpecification
- ? (
- <>
- <hgroup style={{ marginBottom: 15 }}>
- <h2>{`${this.props.nodeId} - ${this.props.lpId}`}</h2>
- {this.createBreadCrump(viewSpecification.id, this.props.viewSpecifications)}
- </hgroup>
- <div style={{ display: "flex", flexWrap: "wrap", overflow: "auto" }}>
- {
+ }
- (this.props.viewData && this.props.viewData instanceof Array)
- ? this.renderUIList(viewSpecification, viewData as { [key: string]: string | number }[])
- : this.renderUIElement(viewSpecification, viewData as { [key: string]: string | number })
- }
- {/* { <pre>{JSON.stringify(this.props.viewData, null, 2)} </pre> } */}
+ static getDerivedStateFromProps(nextProps: ConfigurationApplicationComponentProps, prevState: ConfigurationApplicationComponentState & { [OldProps]: ConfigurationApplicationComponentProps }) {
- </div>
- </>
- )
- : <h2>View [{this.props.viewId || this.props.conditionalPackage}] Not Found ! {this.props.viewSpecifications.length} </h2>;
+ if (!prevState || !prevState[OldProps] || (prevState[OldProps].viewData !== nextProps.viewData)) {
+ const isNew: boolean = nextProps.vPath?.endsWith("[]") || false;
+ const state = {
+ ...prevState,
+ isNew: isNew,
+ editMode: isNew,
+ viewData: nextProps.viewData || null,
+ [OldProps]: nextProps,
+ }
+ return state;
+ }
+ return null;
}
- private static keyPropertyParser = /\$\$INDEX:(\d+):?([a-z\-]+)?\$\$$/;
- private renderUIList = (viewSpecification: ViewSpecification, viewData: { [key: string]: string | number }[]) => {
- const keyMatch = ConfigurationApplicationComponent.keyPropertyParser.exec(viewSpecification.dataPath);
- const keyProperty = keyMatch && keyMatch[2];
- return (
- <Table>
- <TableHead>
- <TableRow>
- {viewSpecification.elements.map(uiElement => {
- switch (uiElement.uiType) {
- case "number":
- return (
- <TableCell key={uiElement.id} align={"right"} >{uiElement.label}</TableCell>
- );
- case "selection":
- case "object":
- case "list":
- case "string":
- case "boolean":
- return (
- <TableCell key={uiElement.id} align={"left"} >{uiElement.label}</TableCell>
- );
- default:
- if (process.env.NODE_ENV !== "production") {
- console.error(`Unknown column type - ${(uiElement as any).uiType} in ${(uiElement as any).id}.`)
- }
- return null;
- }
- })
- }
- <TableCell align={"right"} >Actions</TableCell>
- </TableRow>
- </TableHead>
- <TableBody>
- {viewData.map((row, ind) => (
- <TableRow key={keyProperty && row[keyProperty] || ind}>
- {viewSpecification.elements.map(uiElement => {
- switch (uiElement.uiType) {
- case "string":
- case "number":
- return (
- <TableCell key={uiElement.id} component="td" scope="row" align={uiElement.uiType === "number" ? "right" : "left"}>{row[uiElement.id] == null ? "---" : row[uiElement.id] } </TableCell>
- );
- case "boolean":
- return (
- <TableCell key={uiElement.id} component="td" scope="row" align={"left"} >{row[uiElement.id] == null ? "---" : row[uiElement.id] ? uiElement.trueValue || 'True' : uiElement.falseValue || 'False'} </TableCell>
- );
- case "list":
- case "object":
- return (
- <TableCell key={uiElement.id} component="td" scope="row" align={"left"} >
- <Tooltip title={uiElement.description || ''}>
- <Link component="a" variant="body2" color="secondary" onClick={() => {
- this.props.navigateTo(uiElement.viewId, String(ind));
- }}>{uiElement.label}</Link>
- </Tooltip></TableCell>
- );
- case "selection":
- const option = row[uiElement.id] ? uiElement.options.find(opt => opt.key === row[uiElement.id]) : null;
- return (
- <TableCell key={uiElement.id} component="td" scope="row" align={"left"} >{option ? option.value : row[uiElement.id] == null ? "---" : row[uiElement.id] } </TableCell>
- );
- default:
- if (process.env.NODE_ENV !== "production") {
- console.error(`Unknown column type - ${(uiElement as any).uiType} in ${(uiElement as any).id}.`)
- }
- return null;
- }
- })}
- <TableCell align={"right"} ><Button onClick={() => {
- this.props.navigateTo(this.props.viewId || '', String(/*keyProperty && row[keyProperty] || */ ind));
- }} >Details</Button>
- </TableCell>
- </TableRow>
- ))}
- </TableBody>
- </Table>
- );
+ private navigate = (path: string) => {
+ this.props.history.push(`${this.props.match.url}${path}`);
}
- private renderUIElement = (viewSpecification: ViewSpecification, viewData: { [key: string]: string | number }) => (
- viewSpecification.elements.map(uiElement => {
- if (uiElement.leafrefPath) {
- return null;
+ private changeValueFor = (property: string, value: any) => {
+ this.setState({
+ viewData: {
+ ...this.state.viewData,
+ [property]: value
}
- switch (uiElement.uiType) {
- case "selection":
- return (viewData[uiElement.id] != null
+ });
+ }
+
+ private renderUIElement = (viewSpecification: ViewSpecification, viewData: { [key: string]: any }, keyProperty: string | undefined, editMode: boolean, isNew: boolean) => {
+ const elements = viewSpecification.elements;
+ return (
+ Object.keys(elements).sort((a, b) => {
+ const vsA = elements[a];
+ const vsB = elements[b];
+ if (keyProperty) {
+ // if (vsA.label === vsB.label) return 0;
+ if (vsA.label === keyProperty) return -1;
+ if (vsB.label === keyProperty) return +1;
+ }
+
+ if (vsA.uiType === vsB.uiType) return 0;
+ if (vsA.uiType !== "object" && vsB.uiType !== "object") return 0;
+ if (vsA.uiType === "object") return +1;
+ return -1;
+ }).map(key => {
+ const uiElement = elements[key];
+ const isKey = (uiElement.label === keyProperty);
+ const canEdit = editMode && (isNew || (uiElement.config && !isKey));
+ if (isViewElementSelection(uiElement)) {
+ let error = ""
+ const value = String(viewData[uiElement.id]).toLowerCase();
+ if (uiElement.mandatory && !!value) {
+ error = "Error";
+ }
+ return (canEdit || viewData[uiElement.id] != null
? (<FormControl key={uiElement.id} style={{ width: 485, marginLeft: 20, marginRight: 20 }}>
<InputLabel htmlFor={`select-${uiElement.id}`} >{uiElement.label}</InputLabel>
<Select
- readOnly={ true }
+ required={!!uiElement.mandatory}
+ error={!!error}
+ onChange={(e) => { this.changeValueFor(uiElement.id, e.target.value) }}
+ readOnly={!canEdit}
+ disabled={editMode && !canEdit}
value={(viewData[uiElement.id] || '').toString().toLowerCase()}
inputProps={{
name: uiElement.id,
id: `select-${uiElement.id}`,
}}
>
- {uiElement.options.map(option => (<MenuItem title={option.description} value={option.value}>{option.key}</MenuItem>))}
+ {uiElement.options.map(option => (<MenuItem key={option.key} title={option.description} value={option.value}>{option.key}</MenuItem>))}
</Select>
+ <FormHelperText>{error}</FormHelperText>
</FormControl>)
: null
);
- case "boolean":
- return (viewData[uiElement.id] != null
+ } else if (isViewElementBoolean(uiElement)) {
+ let error = ""
+ const value = String(viewData[uiElement.id]).toLowerCase();
+ if (uiElement.mandatory && value !== "true" && value !== "false") {
+ error = "Error";
+ }
+ return (canEdit || viewData[uiElement.id] != null
? (<FormControl key={uiElement.id} style={{ width: 485, marginLeft: 20, marginRight: 20 }}>
<InputLabel htmlFor={`select-${uiElement.id}`} >{uiElement.label}</InputLabel>
<Select
- readOnly={ true }
-
- value={String(viewData[uiElement.id]).toLowerCase()}
- inputProps={{
- name: uiElement.id,
- id: `select-${uiElement.id}`,
- }}
- >
- <MenuItem value={'true'}>{uiElement.trueValue || 'True'}</MenuItem>
- <MenuItem value={'false'}>{uiElement.falseValue || 'False'}</MenuItem>
-
- </Select>
+ required={!!uiElement.mandatory}
+ error={!!error}
+ onChange={(e) => { this.changeValueFor(uiElement.id, e.target.value) }}
+ readOnly={!canEdit}
+ disabled={editMode && !canEdit}
+ value={value}
+ inputProps={{
+ name: uiElement.id,
+ id: `select-${uiElement.id}`,
+ }}
+ >
+ <MenuItem value={'true'}>{uiElement.trueValue || 'True'}</MenuItem>
+ <MenuItem value={'false'}>{uiElement.falseValue || 'False'}</MenuItem>
+
+ </Select>
+ <FormHelperText>{error}</FormHelperText>
</FormControl>)
: null
);
- case "string":
+ } else if (isViewElementString(uiElement)) {
return (
<Tooltip key={uiElement.id} title={uiElement.description || ''}>
- <TextField InputProps={{ readOnly: true }} spellCheck={false} autoFocus margin="dense"
- id={uiElement.id} label={uiElement.label} type="text" value={viewData[uiElement.id] || ''} style={{ width: 485, marginLeft: 20, marginRight: 20 }} />
+ <TextField InputProps={{ readOnly: !canEdit, disabled: editMode && !canEdit }} spellCheck={false} autoFocus margin="dense"
+ id={uiElement.id} label={isKey ? "🔑 " + uiElement.label : uiElement.label} type="text" value={viewData[uiElement.id] || ''}
+ style={{ width: 485, marginLeft: 20, marginRight: 20 }}
+ onChange={(e) => { this.changeValueFor(uiElement.id, e.target.value) }}
+ />
</Tooltip>
);
- case "number":
+ } else if (isViewElementNumber(uiElement)) {
return (
<Tooltip key={uiElement.id} title={uiElement.description || ''}>
- <TextField InputProps={{ readOnly: true, startAdornment: uiElement.unit != null ? <InputAdornment position="start">{uiElement.unit}</InputAdornment> : undefined }} spellCheck={false} autoFocus margin="dense"
- id={uiElement.id} label={uiElement.label} type="text" value={viewData[uiElement.id] || ''} style={{ width: 485, marginLeft: 20, marginRight: 20 }} />
+ <TextField InputProps={{ readOnly: !canEdit, disabled: editMode && !canEdit, startAdornment: uiElement.units != null ? <InputAdornment position="start">{uiElement.units}</InputAdornment> : undefined }} spellCheck={false} autoFocus margin="dense"
+ id={uiElement.id} label={uiElement.label} type="text" value={viewData[uiElement.id] == null ? '' : viewData[uiElement.id]}
+ style={{ width: 485, marginLeft: 20, marginRight: 20 }}
+ onChange={(e) => { this.changeValueFor(uiElement.id, e.target.value) }}
+ />
</Tooltip>
);
- case "list":
- case "object":
+ } else if (isViewElementObjectOrList(uiElement)) {
return (
- <Tooltip key={uiElement.id} title={uiElement.description || ''}>
- <Link component="a" variant="body2" color="secondary" style={{ width: 485, marginLeft: 20, marginRight: 20 }} onClick={() => {
- this.props.navigateTo(uiElement.viewId);
- }}>{uiElement.label}</Link>
- </Tooltip>
+ <FormControl key={uiElement.id} style={{ width: 485, marginLeft: 20, marginRight: 20 }}>
+ <Tooltip title={uiElement.description || ''}>
+ <Button className={this.props.classes.leftButton} color="secondary" disabled={this.state.editMode} onClick={() => {
+ this.navigate(`/${uiElement.id}`);
+ }}>{uiElement.label}</Button>
+ </Tooltip>
+ </FormControl>
);
- default:
+ } else {
if (process.env.NODE_ENV !== "production") {
console.error(`Unknown type - ${(uiElement as any).uiType} in ${(uiElement as any).id}.`)
}
return null;
+ }
+ })
+ );
+ };
+
+ private renderUIElementList(listSpecification: ViewSpecification, listKeyProperty: string, listData: { [key: string]: any }[]) {
+ const listElements = listSpecification.elements;
+
+ const navigate = (path: string) => {
+ this.props.history.push(`${this.props.match.url}${path}`);
+ };
+
+ const addNewElementAction = {
+ icon: AddIcon, tooltip: 'Add', onClick: () => {
+ navigate("[]"); // empty key means new element
}
- })
- )
-
- private createBreadCrump = (viewId: string, viewSpecifications: ViewSpecification[]) => {
- const result: JSX.Element[] = [];
- const hasIndex = /\/\$\$INDEX:(\d+):?([a-z\-]+)?\$\$/i;
- let currentViewSpecification = viewSpecifications.find(s => s.id === viewId);
- let indexCounter = 0;
- while (currentViewSpecification != null) {
- const currentViewId = currentViewSpecification.id;
- const currentDataPathHasIndex = hasIndex.test(currentViewSpecification.dataPath);
- result.unshift((
- <span>
- <Link component="a" variant="body2" color="secondary" onClick={() => {
- this.props.navigateTo(currentViewId, currentDataPathHasIndex ? ++indexCounter : indexCounter);
- }}>{currentViewSpecification.name}</Link>
- {viewId === currentViewId ? null : " | "}
- </span>
- ));
- currentViewSpecification = viewSpecifications.find(s => s.id === (currentViewSpecification && currentViewSpecification.parentView || ''));
+ };
+
+ const { classes } = this.props;
+
+ return (
+ <SelectElementTable idProperty={listKeyProperty} rows={listData} customActionButtons={[addNewElementAction]} columns={
+ Object.keys(listElements).reduce<ColumnModel<{ [key: string]: any }>[]>((acc, cur) => {
+ const elm = listElements[cur];
+ if (elm.uiType !== "object" && listData.every(entry => entry[elm.label] != null)) {
+ if (elm.label !== listKeyProperty) {
+ acc.push({ property: elm.label, type: elm.uiType === "number" ? ColumnType.numeric : ColumnType.text });
+ } else {
+ acc.unshift({ property: elm.label, type: elm.uiType === "number" ? ColumnType.numeric : ColumnType.text });
+ }
+ }
+ return acc;
+ }, []).concat([{
+ property: "Actions", disableFilter: true, disableSorting: true, type: ColumnType.custom, customControl: (row => {
+ return (
+ <Tooltip title={"Remove"} >
+ <IconButton className={classes.button} onClick={event => {
+
+ }} >
+ <RemoveIcon />
+ </IconButton>
+ </Tooltip>
+ )
+ })
+ }])
+ } onHandleClick={(ev, row) => {
+ ev.preventDefault();
+ navigate(`[${row[listKeyProperty]}]`);
+ }} ></SelectElementTable>
+ );
+ }
+
+ private renderBreadCrumps() {
+ const { editMode } = this.state;
+ const { viewSpecification, displayAsList } = this.props;
+ const { vPath, match: { url, path }, nodeId } = this.props;
+ const pathParts = splitVPath(vPath!, /(?:([^\/\["]+)(?:\[([^\]]*)\])?)/g); // 1 = property / 2 = optional key
+ let lastPath = `/configuration`;
+ let basePath = `/configuration/${nodeId}`;
+ return (
+ <div className={this.props.classes.header}>
+ <div>
+ <Breadcrumbs aria-label="breadcrumb">
+ <Link color="inherit" href="#" onClick={(ev: React.MouseEvent<HTMLElement>) => {
+ ev.preventDefault();
+ this.props.history.push(lastPath);
+ }}>Back</Link>
+ <Link color="inherit" href="#" onClick={(ev: React.MouseEvent<HTMLElement>) => {
+ ev.preventDefault();
+ this.props.history.push(`/configuration/${nodeId}`);
+ }}><span>{nodeId}</span></Link>
+ {
+ pathParts.map(([prop, key], ind) => {
+ const path = `${basePath}/${prop}`;
+ const keyPath = key && `${basePath}/${prop}[${key}]`;
+ const ret = (
+ <span key={ind}>
+ <Link color="inherit" href="#" onClick={(ev: React.MouseEvent<HTMLElement>) => {
+ ev.preventDefault();
+ this.props.history.push(path);
+ }}><span>{prop.replace(/^[^:]+:/, "")}</span></Link>
+ {
+ keyPath && <Link color="inherit" href="#" onClick={(ev: React.MouseEvent<HTMLElement>) => {
+ ev.preventDefault();
+ this.props.history.push(keyPath);
+ }}>{`[${key}]`}</Link> || null
+ }
+ </span>
+ );
+ lastPath = basePath;
+ basePath = keyPath || path;
+ return ret;
+ })
+ }
+ </Breadcrumbs>
+ </div>
+ { /* do not show edit if this is a list or it can't be edited */
+ !displayAsList && viewSpecification.canEdit && (<div>
+ <Fab color="secondary" aria-label="edit" className={this.props.classes.fab} onClick={() => {
+ if (this.state.editMode) {
+ this.props.onUpdateData(this.props.vPath!, this.state.viewData);
+ }
+ this.setState({ editMode: !editMode });
+ }}>
+ {editMode
+ ? <SaveIcon />
+ : <EditIcon />
+ }
+ </Fab>
+ </div> || null)
+ }
+ </div>
+ );
+ }
+
+ private renderValueSelector() {
+ const { listKeyProperty, listSpecification, listData, onValueSelected } = this.props;
+ if (!listKeyProperty || !listSpecification) {
+ throw new Error("ListKex ot view not specified.");
}
- return result;
+
+ return (
+ <div>
+ <SelectElementTable idProperty={listKeyProperty} rows={listData} columns={
+ Object.keys(listSpecification.elements).reduce<ColumnModel<{ [key: string]: any }>[]>((acc, cur) => {
+ const elm = listSpecification.elements[cur];
+ if (elm.uiType !== "object" && listData.every(entry => entry[elm.label] != null)) {
+ if (elm.label !== listKeyProperty) {
+ acc.push({ property: elm.label, type: elm.uiType === "number" ? ColumnType.numeric : ColumnType.text });
+ } else {
+ acc.unshift({ property: elm.label, type: elm.uiType === "number" ? ColumnType.numeric : ColumnType.text });
+ }
+ }
+ return acc;
+ }, [])
+ } onHandleClick={(ev, row) => { ev.preventDefault(); onValueSelected(row); }} ></SelectElementTable>
+ </div>
+ );
+ }
+
+ private renderValueEditor() {
+ const { keyProperty, displayAsList, viewSpecification } = this.props;
+ const { viewData, editMode, isNew } = this.state;
+
+ return (
+ <div>
+ { this.renderBreadCrumps() }
+ { displayAsList && viewData instanceof Array
+ ? this.renderUIElementList(viewSpecification, keyProperty!, viewData)
+ : this.renderUIElement(viewSpecification, viewData!, keyProperty, editMode, isNew)
+ }
+ </div >
+ );
+ }
+
+ private renderCollectingData() {
+ return (
+ <div className={this.props.classes.outer}>
+ <div className={this.props.classes.inner}>
+ <Loader />
+ <h3>Collecting Data ...</h3>
+ </div>
+ </div>
+ );
+ }
+
+ render() {
+ return this.props.collectingData || !this.state.viewData
+ ? this.renderCollectingData()
+ : this.props.listSpecification
+ ? this.renderValueSelector()
+ : this.renderValueEditor();
}
}
-export const ConfigurationApplication = connect(mapProps, mapDisp)(ConfigurationApplicationComponent);
+export const ConfigurationApplication = withStyles(styles)(withRouter(connect(mapProps, mapDispatch)(ConfigurationApplicationComponent)));
export default ConfigurationApplication; \ No newline at end of file