You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@plc4x.apache.org by cd...@apache.org on 2024/01/19 22:16:27 UTC

(plc4x) branch feature/new-ui-tool updated: feat: Worked on implementing the dialog for editing devices.

This is an automated email from the ASF dual-hosted git repository.

cdutz pushed a commit to branch feature/new-ui-tool
in repository https://gitbox.apache.org/repos/asf/plc4x.git


The following commit(s) were added to refs/heads/feature/new-ui-tool by this push:
     new d1d3f714fb feat: Worked on implementing the dialog for editing devices.
d1d3f714fb is described below

commit d1d3f714fb71e78229f4cfc2854e6f7bfa34d4c1
Author: Christofer Dutz <cd...@apache.org>
AuthorDate: Fri Jan 19 23:16:20 2024 +0100

    feat: Worked on implementing the dialog for editing devices.
---
 .gitignore                                         |  6 +-
 plc4j/tools/ui/frontend/frontend/package.json      |  2 +-
 .../frontend/src/components/DeviceDialog.tsx       | 94 ++++++++++++++++++++++
 .../frontend/src/components/NavigationTree.tsx     | 69 +++++++++++++---
 .../src/generated/plc4j-tools-ui-frontend.ts       |  2 +-
 .../ui/frontend/frontend/src/model/TreeItemData.ts |  4 +
 .../ui/frontend/frontend/src/pages/Inspect.tsx     |  2 +
 .../{model/TreeItemData.ts => utils/Counter.ts}    | 19 ++---
 8 files changed, 168 insertions(+), 30 deletions(-)

diff --git a/.gitignore b/.gitignore
index c356315c88..84dfdc5d9a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -55,7 +55,7 @@ buildNumber.properties
 *.tmp
 *.bak
 *.swp
-*~.nib
+#*~.nib
 local.properties
 .settings/
 .loadpath
@@ -75,7 +75,7 @@ local.properties
 .worksheet
 
 # Emacs backup files
-*~
+#*~
 .scannerwork
 
 spock-reports
@@ -119,7 +119,7 @@ gradle-app.setting
 # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
 # gradle/wrapper/gradle-wrapper.properties
 
-exclude ruby specifics:
+#exclude ruby specifics:
 
 *.gem
 *.rbc
diff --git a/plc4j/tools/ui/frontend/frontend/package.json b/plc4j/tools/ui/frontend/frontend/package.json
index 0f83ca022c..8174c491e9 100644
--- a/plc4j/tools/ui/frontend/frontend/package.json
+++ b/plc4j/tools/ui/frontend/frontend/package.json
@@ -15,7 +15,7 @@
     "@mdi/js": "^7.3.67",
     "@mdi/react": "^1.6.1",
     "@mui/icons-material": "^5.14.19",
-    "@mui/material": "^5.14.20",
+    "@mui/material": "^5.15.5",
     "@reduxjs/toolkit": "^2.0.1",
     "axios": "^1.6.2",
     "primeflex": "^3.3.1",
diff --git a/plc4j/tools/ui/frontend/frontend/src/components/DeviceDialog.tsx b/plc4j/tools/ui/frontend/frontend/src/components/DeviceDialog.tsx
new file mode 100644
index 0000000000..3954427297
--- /dev/null
+++ b/plc4j/tools/ui/frontend/frontend/src/components/DeviceDialog.tsx
@@ -0,0 +1,94 @@
+import {Device} from "../generated/plc4j-tools-ui-frontend.ts";
+import {Dialog} from "primereact/dialog";
+import {Button} from "primereact/button";
+import {InputText} from "primereact/inputtext";
+import withWidth from "@mui/material/Hidden/withWidth";
+import {DataTable} from "primereact/datatable";
+import {Column} from "primereact/column";
+
+interface ConnectionDialogProps {
+    device: Device
+    visible: boolean
+
+    onSave: (device:Device) => void
+    onCancel: () => void
+}
+
+interface TableEntry {
+    key: string
+    value: string
+}
+
+type Dictionary = { [index: string]: string }
+
+function mapToTableEntry(map : Dictionary):TableEntry[] {
+    let tableEntries = [] as TableEntry[]
+    for(const key in map) {
+        const value = map[key]
+        tableEntries = [...tableEntries, {key: key, value: value}]
+    }
+    return tableEntries
+}
+
+export default function DeviceDialog({device, visible, onSave, onCancel}: ConnectionDialogProps) {
+    function handleSave() {
+        onSave(device)
+    }
+    function handleCancel() {
+        onCancel()
+    }
+
+    return (
+        <Dialog visible={visible} modal style={{width: '60rem'}} draggable={true} resizable={true} onHide={() => {
+        }}>
+            <div className="formgrid grid">
+                <label htmlFor="name" className="col-12 mb-2 md:col-2 md:mb-0">Device Name</label>
+                <div className="col-12 md:col-10">
+                    <InputText id="name" value={device.name}/>
+                </div>
+            </div>
+            <div className="formgrid grid">
+                <label htmlFor="protocol" className="col-12 mb-2 md:col-2 md:mb-0">Protocol</label>
+                <div className="col-12 md:col-10">
+                    <InputText id="protocol" value={device.protocolCode}/>
+                </div>
+            </div>
+            <div className="formgrid grid">
+                <label htmlFor="transportCode" className="col-12 mb-2 md:col-2 md:mb-0">Transport Type</label>
+                <div className="col-12 md:col-10">
+                    <InputText id="transportCode" value={device.transportCode}/>
+                </div>
+            </div>
+            <div className="formgrid grid">
+                <label htmlFor="transportUrl" className="col-12 mb-2 md:col-2 md:mb-0">Transport URL</label>
+                <div className="col-12 md:col-10">
+                    <InputText id="transportUrl" value={device.transportUrl}/>
+                </div>
+            </div>
+            <div className="formgrid grid">
+                <label htmlFor="options" className="col-12 mb-2 md:col-2 md:mb-0">Options</label>
+                <div className="col-12 md:col-10">
+                    <DataTable id="options" value={mapToTableEntry(device.options)} tableStyle={{minWidth: '50rem'}}>
+                        <Column field="key" header="Name"/>
+                        <Column field="value" header="Value"/>
+                    </DataTable>
+                </div>
+            </div>
+            <div className="formgrid grid">
+                <label htmlFor="attributes" className="col-12 mb-2 md:col-2 md:mb-0">Attributes</label>
+                <div className="col-12 md:col-10">
+                    <DataTable id="attributes" value={mapToTableEntry(device.attributes)} tableStyle={{minWidth: '50rem'}}>
+                        <Column field="key" header="Name"/>
+                        <Column field="value" header="Value"/>
+                    </DataTable>
+                </div>
+            </div>
+            <div className="formgrid grid">
+                <div className="field col">
+                    <Button label={"Save"} onClick={handleSave}/>
+                    <Button label={"Cancel"} onClick={handleCancel}/>
+                </div>
+            </div>
+        </Dialog>
+    )
+}
\ No newline at end of file
diff --git a/plc4j/tools/ui/frontend/frontend/src/components/NavigationTree.tsx b/plc4j/tools/ui/frontend/frontend/src/components/NavigationTree.tsx
index c3b72b0fe8..299e0000c3 100644
--- a/plc4j/tools/ui/frontend/frontend/src/components/NavigationTree.tsx
+++ b/plc4j/tools/ui/frontend/frontend/src/components/NavigationTree.tsx
@@ -22,11 +22,13 @@ import {Tree} from "primereact/tree";
 import {TreeNode} from "primereact/treenode";
 import {IconType} from "primereact/utils";
 import 'primeicons/primeicons.css';
-import {useRef} from "react";
+import {useRef, useState} from "react";
 import {ContextMenu} from "primereact/contextmenu";
 import {MenuItem} from "primereact/menuitem";
-import {RestApplicationClient} from "../generated/plc4j-tools-ui-frontend.ts";
+import {Device, RestApplicationClient} from "../generated/plc4j-tools-ui-frontend.ts";
 import axios from "axios";
+import {Counter} from "../utils/Counter.ts";
+import DeviceDialog from "./DeviceDialog.tsx";
 
 type NavigationTreeProps = {
     treeItems: TreeItemData[];
@@ -35,6 +37,17 @@ type NavigationTreeProps = {
 const restClient = new RestApplicationClient(axios);
 
 export default function NavigationTree({treeItems}: NavigationTreeProps) {
+    const dialogDevice = useRef<Device>({
+        id: 0,
+        name: "",
+        protocolCode: "",
+        transportCode: "",
+        transportUrl: "",
+        options: {},
+        attributes: {},
+    });
+    const [showDeviceDialog, setShowDeviceDialog] = useState(false)
+
     const cm = useRef<ContextMenu>(null);
     const menu = [
         {
@@ -73,7 +86,8 @@ export default function NavigationTree({treeItems}: NavigationTreeProps) {
             disabled: false,
         } as MenuItem
     ] as MenuItem[]
-    function updateMenu(selectedItem:TreeItemData) {
+
+    function updateMenu(selectedItem: TreeItemData) {
         // Discover
         menu[0].disabled = !selectedItem.supportsDiscovery && selectedItem.type != "ROOT"
         menu[0].command = () => {
@@ -82,17 +96,45 @@ export default function NavigationTree({treeItems}: NavigationTreeProps) {
 
         // Add
         menu[1].disabled = selectedItem.type != "DRIVER"
+        menu[1].command = () => {
+            dialogDevice.current.id = 0;
+            dialogDevice.current.name = "";
+            dialogDevice.current.transportCode = "";
+            dialogDevice.current.transportUrl = "";
+            dialogDevice.current.protocolCode = selectedItem.id;
+            dialogDevice.current.options = {};
+            dialogDevice.current.attributes = {};
+            setShowDeviceDialog(true);
+        }
 
         // Edit
         menu[2].disabled = selectedItem.type != "DEVICE"
-
+        menu[2].command = () => {
+            console.log(JSON.stringify(selectedItem))
+            if(selectedItem.device) {
+                dialogDevice.current.id = selectedItem.device.id;
+                dialogDevice.current.name = selectedItem.device.name;
+                dialogDevice.current.transportCode = selectedItem.device.transportCode;
+                dialogDevice.current.transportUrl = selectedItem.device.transportUrl;
+                dialogDevice.current.protocolCode = selectedItem.device.protocolCode;
+                dialogDevice.current.options = selectedItem.device.options;
+                dialogDevice.current.attributes = selectedItem.device.attributes;
+                setShowDeviceDialog(true);
+            }
+        }
         // Delete
         menu[3].disabled = selectedItem.type != "DEVICE"
-
+        menu[3].command = () => {
+            // TODO: Ask for confirmation ...
+        }
         // Connect
         menu[4].disabled = selectedItem.type != "DEVICE"
+        menu[4].command = () => {
+            // TODO: Open a new tab with the connection ...
+        }
     }
-    function getIcon(curItem: TreeItemData):IconType<TreeNode> {
+
+    function getIcon(curItem: TreeItemData): IconType<TreeNode> {
         switch (curItem.type) {
             case "DRIVER":
                 return "pi pi-fw pi-folder-open"//"material-icons md-18 folder_open"
@@ -109,26 +151,27 @@ export default function NavigationTree({treeItems}: NavigationTreeProps) {
             // publish:   "Keyboard Double Arrow Up"
         }
     }
-    function createTreeNode(curItem:TreeItemData):TreeNode {
+
+    function createTreeNode(curItem: TreeItemData, keyGenerator: Counter): TreeNode {
         return {
-            key: curItem.type + '-' + curItem.id,
+            key: keyGenerator.getAndIncrement(),
             id: curItem.id,
             label: curItem.name,
             icon: getIcon(curItem),
             data: curItem,
-            children: curItem.children?.map(value => createTreeNode(value))
+            children: curItem.children?.map(value => createTreeNode(value, keyGenerator))
         }
     }
 
-    const treeNodes: TreeNode[] = treeItems.map(value => createTreeNode(value))
-    return(
+    const treeNodes: TreeNode[] = treeItems.map(value => createTreeNode(value, new Counter()))
+    return (
         <div>
+            <DeviceDialog device={dialogDevice.current} visible={showDeviceDialog} />
             <ContextMenu model={menu} ref={cm}/>
-
             <Tree value={treeNodes}
                   selectionMode="single"
                   onContextMenu={event => {
-                      if(cm.current) {
+                      if (cm.current) {
                           // Update the state of the menu (enabling/disabling some menu items)
                           updateMenu(event.node.data as TreeItemData)
                           //cm.current.props.model = menu
diff --git a/plc4j/tools/ui/frontend/frontend/src/generated/plc4j-tools-ui-frontend.ts b/plc4j/tools/ui/frontend/frontend/src/generated/plc4j-tools-ui-frontend.ts
index 71e59b4c25..07105c5f4e 100644
--- a/plc4j/tools/ui/frontend/frontend/src/generated/plc4j-tools-ui-frontend.ts
+++ b/plc4j/tools/ui/frontend/frontend/src/generated/plc4j-tools-ui-frontend.ts
@@ -19,7 +19,7 @@
  */
 /* tslint:disable */
 /* eslint-disable */
-// Generated using typescript-generator version 3.2.1263 on 2024-01-15 21:38:43.
+// Generated using typescript-generator version 3.2.1263 on 2024-01-19 18:01:48.
 
 export interface Device {
     id: number;
diff --git a/plc4j/tools/ui/frontend/frontend/src/model/TreeItemData.ts b/plc4j/tools/ui/frontend/frontend/src/model/TreeItemData.ts
index d015ebeb86..b361cedf01 100644
--- a/plc4j/tools/ui/frontend/frontend/src/model/TreeItemData.ts
+++ b/plc4j/tools/ui/frontend/frontend/src/model/TreeItemData.ts
@@ -17,12 +17,16 @@
  * under the License.
  */
 
+import {Device, Driver} from "../generated/plc4j-tools-ui-frontend.ts";
+
 export type TreeItemType = "ROOT" | "DRIVER" | "CONNECTION" | "DEVICE";
 
 export interface TreeItemData {
     id: string,
     name: string,
     type: TreeItemType,
+    driver?: Driver,
+    device?: Device,
     supportsDiscovery: boolean,
     supportsBrowsing: boolean,
     supportsReading: boolean,
diff --git a/plc4j/tools/ui/frontend/frontend/src/pages/Inspect.tsx b/plc4j/tools/ui/frontend/frontend/src/pages/Inspect.tsx
index 3cba7242a4..f00735425b 100644
--- a/plc4j/tools/ui/frontend/frontend/src/pages/Inspect.tsx
+++ b/plc4j/tools/ui/frontend/frontend/src/pages/Inspect.tsx
@@ -36,6 +36,7 @@ function getByDriverTree(driverList: Driver[], deviceList: Device[]):TreeItemDat
                 id: value.code,
                 name: value.name,
                 type: "DRIVER",
+                driver: value,
                 supportsDiscovery: value.supportsDiscovery,
                 supportsBrowsing: false,
                 supportsReading: false,
@@ -56,6 +57,7 @@ function getByDriverTree(driverList: Driver[], deviceList: Device[]):TreeItemDat
                     id: value.id?.toString(),
                     name: value.name,
                     type: "DEVICE",
+                    device: value,
                     // Even if most connections will support reading/writing, in order to really know, we need a
                     // connection first. As we're not offering any connection-actions here anyway we just set all
                     // to false.
diff --git a/plc4j/tools/ui/frontend/frontend/src/model/TreeItemData.ts b/plc4j/tools/ui/frontend/frontend/src/utils/Counter.ts
similarity index 67%
copy from plc4j/tools/ui/frontend/frontend/src/model/TreeItemData.ts
copy to plc4j/tools/ui/frontend/frontend/src/utils/Counter.ts
index d015ebeb86..4f44ad300c 100644
--- a/plc4j/tools/ui/frontend/frontend/src/model/TreeItemData.ts
+++ b/plc4j/tools/ui/frontend/frontend/src/utils/Counter.ts
@@ -17,17 +17,12 @@
  * under the License.
  */
 
-export type TreeItemType = "ROOT" | "DRIVER" | "CONNECTION" | "DEVICE";
+export class Counter {
+
+    private _value:number = 0;
+
+    public  getAndIncrement():number {
+        return this._value++
+    }
 
-export interface TreeItemData {
-    id: string,
-    name: string,
-    type: TreeItemType,
-    supportsDiscovery: boolean,
-    supportsBrowsing: boolean,
-    supportsReading: boolean,
-    supportsWriting: boolean,
-    supportsSubscribing: boolean,
-    supportsPublishing: boolean,
-    children?: readonly TreeItemData[]
 }