diff options
Diffstat (limited to 'sdnr/wt/odlux/apps/configurationApp/src/views/configurationApplication.tsx')
-rw-r--r-- | sdnr/wt/odlux/apps/configurationApp/src/views/configurationApplication.tsx | 626 |
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 |