From 05ef023752abdb4f1e072332496dc7c6eaff8965 Mon Sep 17 00:00:00 2001
From: herbert <herbert.eiselt@highstreet-technologies.com>
Date: Sat, 1 Feb 2020 16:00:00 +0100
Subject: SDN-R add updated odlux

Updates all odlux framework and app components.

Issue-ID: SDNC-1032
Signed-off-by: herbert <herbert.eiselt@highstreet-technologies.com>
Change-Id: I13c520489fd40d05b7fd5215f5941af6238e9cae
---
 .../src/views/configurationApplication.tsx         | 412 ++++++++++++++-------
 1 file changed, 288 insertions(+), 124 deletions(-)

(limited to 'sdnr/wt/odlux/apps/configurationApp/src/views/configurationApplication.tsx')

diff --git a/sdnr/wt/odlux/apps/configurationApp/src/views/configurationApplication.tsx b/sdnr/wt/odlux/apps/configurationApp/src/views/configurationApplication.tsx
index 24a4af8b2..9c92ceb6b 100644
--- a/sdnr/wt/odlux/apps/configurationApp/src/views/configurationApplication.tsx
+++ b/sdnr/wt/odlux/apps/configurationApp/src/views/configurationApplication.tsx
@@ -16,7 +16,7 @@
  * ============LICENSE_END==========================================================================
  */
 
-import * as React from 'react';
+import React from 'react';
 import { RouteComponentProps, withRouter } from 'react-router-dom';
 
 import { WithStyles, withStyles, createStyles, Theme } from '@material-ui/core/styles';
@@ -26,11 +26,12 @@ import { IApplicationStoreState } from "../../../../framework/src/store/applicat
 import MaterialTable, { ColumnModel, ColumnType, MaterialTableCtorType } from "../../../../framework/src/components/material-table";
 import { Loader } from "../../../../framework/src/components/material-ui/loader";
 
-import { SetSelectedValue, splitVPath, updateDataActionAsyncCreator } from "../actions/deviceActions";
-import { ViewSpecification, isViewElementString, isViewElementNumber, isViewElementBoolean, isViewElementObjectOrList, isViewElementSelection } from "../models/uiModels";
+import { SetSelectedValue, splitVPath, updateDataActionAsyncCreator, updateViewActionAsyncCreator } from "../actions/deviceActions";
+import { ViewSpecification, isViewElementString, isViewElementNumber, isViewElementBoolean, isViewElementObjectOrList, isViewElementSelection, isViewElementChoise, ViewElement, ViewElementChoise, isViewElementUnion } from "../models/uiModels";
 
 import Fab from '@material-ui/core/Fab';
 import AddIcon from '@material-ui/icons/Add';
+import ArrowBack from '@material-ui/icons/ArrowBack';
 import RemoveIcon from '@material-ui/icons/RemoveCircleOutline';
 import SaveIcon from '@material-ui/icons/Save';
 import EditIcon from '@material-ui/icons/Edit';
@@ -38,7 +39,7 @@ 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";
@@ -47,6 +48,13 @@ import Breadcrumbs from "@material-ui/core/Breadcrumbs";
 import Link from "@material-ui/core/Link";
 import FormHelperText from '@material-ui/core/FormHelperText';
 
+import { UIElementReference } from '../components/uiElementReference';
+import { UiElementNumber } from '../components/uiElementNumber';
+import { UiElementString } from '../components/uiElementString';
+import { UiElementBoolean } from '../components/uiElementBoolean';
+import { UiElementSelection } from '../components/uiElementSelection';
+import { UIElementUnion } from '../components/uiElementUnion';
+
 const styles = (theme: Theme) => createStyles({
   header: {
     "display": "flex",
@@ -97,6 +105,16 @@ const styles = (theme: Theme) => createStyles({
       },
     },
   },
+  section: {
+    padding: "15px",
+    borderBottom: `2px solid ${theme.palette.divider}`,
+  },
+  viewElements: {
+    width: 485, marginLeft: 20, marginRight: 20
+  },
+  verificationElements: {
+    width: 485, marginLeft: 20, marginRight: 20
+  }
 });
 
 const mapProps = (state: IApplicationStoreState) => ({
@@ -115,6 +133,7 @@ const mapProps = (state: IApplicationStoreState) => ({
 const mapDispatch = (dispatcher: IDispatcher) => ({
   onValueSelected: (value: any) => dispatcher.dispatch(new SetSelectedValue(value)),
   onUpdateData: (vPath: string, data: any) => dispatcher.dispatch(updateDataActionAsyncCreator(vPath, data)),
+  reloadView: (vPath: string) => dispatcher.dispatch(updateViewActionAsyncCreator(vPath)),
 });
 
 const SelectElementTable = MaterialTable as MaterialTableCtorType<{ [key: string]: any }>;
@@ -126,6 +145,7 @@ type ConfigurationApplicationComponentState = {
   editMode: boolean;
   canEdit: boolean;
   viewData: { [key: string]: any } | null;
+  choises: { [path: string]: { selectedCase: string, data: { [property: string]: any } } };
 }
 
 const OldProps = Symbol("OldProps");
@@ -134,27 +154,61 @@ class ConfigurationApplicationComponent extends React.Component<ConfigurationApp
   /**
    *
    */
-  constructor (props: ConfigurationApplicationComponentProps) {
+  constructor(props: ConfigurationApplicationComponentProps) {
     super(props);
 
     this.state = {
       isNew: false,
       canEdit: false,
       editMode: false,
-      viewData: null
+      viewData: null,
+      choises: {},
     }
   }
 
-  static getDerivedStateFromProps(nextProps: ConfigurationApplicationComponentProps, prevState: ConfigurationApplicationComponentState & { [OldProps]: ConfigurationApplicationComponentProps  }) {
+  static getDerivedStateFromProps(nextProps: ConfigurationApplicationComponentProps, prevState: ConfigurationApplicationComponentState & { [OldProps]: ConfigurationApplicationComponentProps }) {
 
     if (!prevState || !prevState[OldProps] || (prevState[OldProps].viewData !== nextProps.viewData)) {
-      const isNew: boolean = nextProps.vPath?.endsWith("[]") || false;
+      const isNew: boolean = nextProps.vPath ?.endsWith("[]") || false;
       const state = {
         ...prevState,
         isNew: isNew,
         editMode: isNew,
         viewData: nextProps.viewData || null,
         [OldProps]: nextProps,
+        choises: nextProps.viewSpecification && Object.keys(nextProps.viewSpecification.elements).reduce((acc, cur) => {
+          const elm = nextProps.viewSpecification.elements[cur];
+          if (isViewElementChoise(elm)) {
+            const caseKeys = Object.keys(elm.cases);
+
+            // find the right case for this choise, use the first one with data, at least use index 0
+            const selectedCase = caseKeys.find(key => {
+              const caseElm = elm.cases[key];
+              return Object.keys(caseElm.elements).some(caseElmKey => {
+                const caseElmElm = caseElm.elements[caseElmKey];
+                return nextProps.viewData[caseElmElm.label] != null || nextProps.viewData[caseElmElm.id] != null;
+              });
+            }) || caseKeys[0];
+
+            // extract all data of the active case
+            const caseElements = elm.cases[selectedCase].elements;
+            const data = Object.keys(caseElements).reduce((dataAcc, dataCur) => {
+              const dataElm = caseElements[dataCur];
+              if (nextProps.viewData[dataElm.label] !== undefined) {
+                dataAcc[dataElm.label] = nextProps.viewData[dataElm.label];
+              } else if (nextProps.viewData[dataElm.id] !== undefined) {
+                dataAcc[dataElm.id] = nextProps.viewData[dataElm.id];
+              }
+              return dataAcc;
+            }, {} as { [name: string]: any });
+
+            acc[elm.id] = {
+              selectedCase,
+              data,
+            };
+          }
+          return acc;
+        }, {} as { [path: string]: { selectedCase: string, data: { [property: string]: any } } }) || {}
       }
       return state;
     }
@@ -174,123 +228,203 @@ class ConfigurationApplicationComponent extends React.Component<ConfigurationApp
     });
   }
 
-  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;
-        }
+  private renderUIElement = (uiElement: ViewElement, viewData: { [key: string]: any }, keyProperty: string | undefined, editMode: boolean, isNew: boolean) => {
+    const isKey = (uiElement.label === keyProperty);
+    const canEdit = editMode && (isNew || (uiElement.config && !isKey));
+    if (isViewElementSelection(uiElement)) {
+
+      return <UiElementSelection
+        key={uiElement.id}
+        inputValue={viewData[uiElement.id] || ''}
+        value={uiElement}
+        readOnly={!canEdit}
+        disabled={editMode && !canEdit}
+        onChange={(e) => { this.changeValueFor(uiElement.id, e) }}
+      />
+
+    } else if (isViewElementBoolean(uiElement)) {
+      return <UiElementBoolean
+        key={uiElement.id}
+        inputValue={viewData[uiElement.id] || ''}
+        value={uiElement}
+        readOnly={!canEdit}
+        disabled={editMode && !canEdit}
+        onChange={(e) => { this.changeValueFor(uiElement.id, e) }} />
+
+    } else if (isViewElementString(uiElement)) {
+      return <UiElementString
+        key={uiElement.id}
+        inputValue={viewData[uiElement.id] || ''}
+        value={uiElement}
+        isKey={isKey}
+        readOnly={!canEdit}
+        disabled={editMode && !canEdit}
+        onChange={(e) => { this.changeValueFor(uiElement.id, e) }} />
+
+    } else if (isViewElementNumber(uiElement)) {
+      return <UiElementNumber
+        key={uiElement.id}
+        value={uiElement}
+        inputValue={viewData[uiElement.id] == null ? '' : viewData[uiElement.id]}
+        readOnly={!canEdit}
+        disabled={editMode && !canEdit}
+        onChange={(e) => { this.changeValueFor(uiElement.id, e) }} />
+    } else if (isViewElementUnion(uiElement)) {
+      return <UIElementUnion
+        key={uiElement.id}
+        isKey={false}
+        value={uiElement}
+        inputValue={viewData[uiElement.id] == null ? '' : viewData[uiElement.id]}
+        readOnly={!canEdit}
+        disabled={editMode && !canEdit}
+        onChange={(e) => { this.changeValueFor(uiElement.id, e) }} />
+    }
+    else {
+      if (process.env.NODE_ENV !== "production") {
+        console.error(`Unknown element type - ${(uiElement as any).uiType} in ${(uiElement as any).id}.`)
+      }
+      return null;
+    }
+  };
 
-        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
-                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 key={option.key} title={option.description} value={option.value}>{option.key}</MenuItem>))}
-              </Select>
-              <FormHelperText>{error}</FormHelperText>
-            </FormControl>)
-            : 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
-                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
-          );
-        } else if (isViewElementString(uiElement)) {
-          return (
-            <Tooltip key={uiElement.id} title={uiElement.description || ''}>
-              <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>
-          );
-        } else if (isViewElementNumber(uiElement)) {
-          return (
-            <Tooltip key={uiElement.id} title={uiElement.description || ''}>
-              <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>
-          );
-        } else if (isViewElementObjectOrList(uiElement)) {
-          return (
-            <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>
-          );
-        } else {
-          if (process.env.NODE_ENV !== "production") {
-            console.error(`Unknown type - ${(uiElement as any).uiType} in ${(uiElement as any).id}.`)
+  // private renderUIReference = (uiElement: ViewElement, viewData: { [key: string]: any }, keyProperty: string | undefined, editMode: boolean, isNew: boolean) => {
+  //   const isKey = (uiElement.label === keyProperty);
+  //   const canEdit = editMode && (isNew || (uiElement.config && !isKey));
+  //   if (isViewElementObjectOrList(uiElement)) {
+  //     return (
+  //       <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>
+  //     );
+  //   } else {
+  //     if (process.env.NODE_ENV !== "production") {
+  //       console.error(`Unknown reference type - ${(uiElement as any).uiType} in ${(uiElement as any).id}.`)
+  //     }
+  //     return null;
+  //   }
+  // };
+
+  private renderUIChoise = (uiElement: ViewElementChoise, viewData: { [key: string]: any }, keyProperty: string | undefined, editMode: boolean, isNew: boolean) => {
+    const isKey = (uiElement.label === keyProperty);
+
+    const currentChoise = this.state.choises[uiElement.id];
+    const currentCase = currentChoise && uiElement.cases[currentChoise.selectedCase];
+
+    const canEdit = editMode && (isNew || (uiElement.config && !isKey));
+    if (isViewElementChoise(uiElement)) {
+      const subElements = currentCase ?.elements;
+      return (
+        <>
+          <FormControl key={uiElement.id} style={{ width: 485, marginLeft: 20, marginRight: 20 }}>
+            <InputLabel htmlFor={`select-${uiElement.id}`} >{uiElement.label}</InputLabel>
+            <Select
+              required={!!uiElement.mandatory}
+              onChange={(e) => {
+                if (currentChoise.selectedCase === e.target.value) {
+                  return; // nothing changed
+                }
+                this.setState({ choises: { ...this.state.choises, [uiElement.id]: { ...this.state.choises[uiElement.id], selectedCase: e.target.value as string } } });
+              }}
+              readOnly={!canEdit}
+              disabled={editMode && !canEdit}
+              value={this.state.choises[uiElement.id].selectedCase}
+              inputProps={{
+                name: uiElement.id,
+                id: `select-${uiElement.id}`,
+              }}
+            >
+              {
+                Object.keys(uiElement.cases).map(caseKey => {
+                  const caseElm = uiElement.cases[caseKey];
+                  return (
+                    <MenuItem key={caseElm.id} title={caseElm.description} value={caseKey}>{caseElm.label}</MenuItem>
+                  );
+                })
+              }
+            </Select>
+          </FormControl>
+          {subElements
+            ? Object.keys(subElements).map(elmKey => {
+              const elm = subElements[elmKey];
+              return this.renderUIElement(elm, viewData, keyProperty, editMode, isNew);
+            })
+            : <h3>Invalid Choise</h3>
           }
-          return null;
+        </>
+      );
+    } else {
+      if (process.env.NODE_ENV !== "production") {
+        console.error(`Unknown type - ${(uiElement as any).uiType} in ${(uiElement as any).id}.`)
+      }
+      return null;
+    }
+  };
+
+  private renderUIView = (viewSpecification: ViewSpecification, viewData: { [key: string]: any }, keyProperty: string | undefined, editMode: boolean, isNew: boolean) => {
+    const { classes } = this.props;
+
+    const orderFunc = (vsA: ViewElement, vsB: ViewElement) => {
+      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;
+    };
+
+    const sections = Object.keys(viewSpecification.elements).reduce((acc, cur) => {
+      const elm = viewSpecification.elements[cur];
+      if (isViewElementObjectOrList(elm)) {
+        acc.references.push(elm);
+      } else if (isViewElementChoise(elm)) {
+        acc.choises.push(elm);
+      } else {
+        acc.elements.push(elm);
+      }
+      return acc;
+    }, { elements: [] as ViewElement[], references: [] as ViewElement[], choises: [] as ViewElementChoise[] });
+
+    sections.elements = sections.elements.sort(orderFunc);
+
+    return (
+      <>
+        <div className={classes.section} />
+        {sections.elements.length > 0
+          ? (
+            <div className={classes.section}>
+              {sections.elements.map(element => this.renderUIElement(element, viewData, keyProperty, editMode, isNew))}
+            </div>
+          ) : null
+        }
+        {sections.references.length > 0
+          ? (
+            <div className={classes.section}>
+              {sections.references.map(element => (
+                <UIElementReference key={element.id} element={element} disabled={editMode} onOpenReference={(elm) => { this.navigate(`/${elm.id}`) }} />
+              ))}
+            </div>
+          ) : null
         }
-      })
+        {sections.choises.length > 0
+          ? (
+            <div className={classes.section}>
+              {sections.choises.map(element => this.renderUIChoise(element, viewData, keyProperty, editMode, isNew))}
+            </div>
+          ) : null
+        }
+      </>
     );
   };
 
-  private renderUIElementList(listSpecification: ViewSpecification, listKeyProperty: string, listData: { [key: string]: any }[]) {
+  private renderUIViewList(listSpecification: ViewSpecification, listKeyProperty: string, listData: { [key: string]: any }[]) {
     const listElements = listSpecification.elements;
 
     const navigate = (path: string) => {
@@ -381,11 +515,41 @@ class ConfigurationApplicationComponent extends React.Component<ConfigurationApp
             }
           </Breadcrumbs>
         </div>
+        {this.state.editMode && (
+          <Fab color="secondary" aria-label="edit" className={this.props.classes.fab} onClick={async () => {
+            this.props.vPath && await this.props.reloadView(this.props.vPath);
+            this.setState({ editMode: false });
+          }} ><ArrowBack /></Fab>
+        ) || null}
         { /* 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);
+
+                // ensure only active choises will be contained
+                const choiseKeys = Object.keys(viewSpecification.elements).filter(elmKey => isViewElementChoise(viewSpecification.elements[elmKey]));
+                const elementsToRemove = choiseKeys.reduce((acc, cur) => {
+                  const choise = viewSpecification.elements[cur] as ViewElementChoise;
+                  const selectedCase = this.state.choises[cur].selectedCase;
+                  Object.keys(choise.cases).forEach(caseKey => {
+                    if (caseKey === selectedCase) return;
+                    const caseElements = choise.cases[caseKey].elements;
+                    Object.keys(caseElements).forEach(caseElementKey => {
+                      acc.push(caseElements[caseElementKey]);
+                    });
+                  });
+                  return acc;
+                }, [] as ViewElement[]);
+
+                const viewData = this.state.viewData;
+                const resultingViewData = viewData && Object.keys(viewData).reduce((acc, cur) => {
+                  if (!elementsToRemove.some(elm => elm.label === cur || elm.id === cur)) {
+                    acc[cur] = viewData[cur];
+                  }
+                  return acc;
+                }, {} as { [key: string]: any });
+
+                this.props.onUpdateData(this.props.vPath!, resultingViewData);
               }
               this.setState({ editMode: !editMode });
             }}>
@@ -431,10 +595,10 @@ class ConfigurationApplicationComponent extends React.Component<ConfigurationApp
 
     return (
       <div>
-        { this.renderBreadCrumps() }
-        { displayAsList && viewData instanceof Array
-            ? this.renderUIElementList(viewSpecification, keyProperty!, viewData)
-            : this.renderUIElement(viewSpecification, viewData!, keyProperty, editMode, isNew)
+        {this.renderBreadCrumps()}
+        {displayAsList && viewData instanceof Array
+          ? this.renderUIViewList(viewSpecification, keyProperty!, viewData)
+          : this.renderUIView(viewSpecification, viewData!, keyProperty, editMode, isNew)
         }
       </div >
     );
-- 
cgit 1.2.3-korg

