From 2be9a0b7fa63eab00fbf3a02c093d7dec42fcd63 Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Wed, 13 Aug 2025 14:05:40 +0300 Subject: [PATCH 01/90] fix(665): REVERT LATER --- docker-compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index f1e85977d..66c379b31 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -45,8 +45,8 @@ services: - database environment: - sqlms.datasources.[0].name=byk - # - sqlms.datasources.[0].jdbcUrl=jdbc:postgresql://database:5432/services_db # For Local Use - - sqlms.datasources.[0].jdbcUrl=jdbc:postgresql://171.22.247.13:5435/services_db + - sqlms.datasources.[0].jdbcUrl=jdbc:postgresql://database:5432/services_db # For Local Use + # - sqlms.datasources.[0].jdbcUrl=jdbc:postgresql://171.22.247.13:5435/services_db - sqlms.datasources.[0].username=byk - sqlms.datasources.[0].password=01234 - logging.level.org.springframework.boot=INFO From 8726814495963f02d0ee8d1849a93dad1fa01916 Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Wed, 13 Aug 2025 14:22:46 +0300 Subject: [PATCH 02/90] fix(665): Install --- GUI/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/GUI/package.json b/GUI/package.json index 701c64edd..97738ce0b 100644 --- a/GUI/package.json +++ b/GUI/package.json @@ -33,6 +33,7 @@ "howler": "^2.2.4", "i18next": "^22.4.9", "i18next-browser-languagedetector": "^7.0.1", + "json-edit-react": "^1.28.2", "moment": "^2.29.4", "msw": "^0.49.2", "node-html-markdown": "^1.3.0", From bc26a896b3418992977427560338322340a0c5ea Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Wed, 13 Aug 2025 14:52:40 +0300 Subject: [PATCH 03/90] fix(665): drag-dorp poc lib 2 --- GUI/package.json | 1 + .../AssignBuilder/JsonEditorPoC.tsx | 182 ++++++++++++++++++ .../AssignBuilder/JsonFormsPoC.tsx | 121 ++++++++++++ .../FlowElementsPopup/AssignBuilder/index.tsx | 3 + 4 files changed, 307 insertions(+) create mode 100644 GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.tsx create mode 100644 GUI/src/components/FlowElementsPopup/AssignBuilder/JsonFormsPoC.tsx diff --git a/GUI/package.json b/GUI/package.json index 97738ce0b..0677183c6 100644 --- a/GUI/package.json +++ b/GUI/package.json @@ -34,6 +34,7 @@ "i18next": "^22.4.9", "i18next-browser-languagedetector": "^7.0.1", "json-edit-react": "^1.28.2", + "jsoneditor": "^10.3.0", "moment": "^2.29.4", "msw": "^0.49.2", "node-html-markdown": "^1.3.0", diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.tsx new file mode 100644 index 000000000..c451c4d84 --- /dev/null +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.tsx @@ -0,0 +1,182 @@ +import React, { useEffect, useRef, useState } from "react"; +// @ts-ignore +import JSONEditor from "jsoneditor"; +import "jsoneditor/dist/jsoneditor.css"; +import { getDragData } from "utils/component-util"; + +const JsonEditorPoC: React.FC = () => { + const editorRef = useRef(null); + const jsonEditorRef = useRef(null); + const [data, setData] = useState({ + name: "John Doe", + age: 30, + email: "john@example.com", + address: { + street: "123 Main St", + city: "Anytown", + zip: "12345", + }, + preferences: { + theme: "dark", + notifications: true, + }, + }); + + useEffect(() => { + if (editorRef.current && !jsonEditorRef.current) { + const editor = new JSONEditor(editorRef.current, { + mode: "tree", + modes: ["tree", "view", "form", "text", "code"], + onChangeJSON: (json: any) => { + console.log("JSON changed:", json); + setData(json); + }, + onError: (error: any) => { + console.error("JSON Editor error:", error); + }, + enableSort: true, + enableTransform: true, + search: true, + enableClipboard: true, + enableHistory: false, + enableNavigationBar: false, + enableStatusBar: false, + indentation: 2, + escapeUnicode: false, + sortObjectKeys: false, + colorPicker: true, + timestampTag: true, + language: "en", + }); + + editor.set(data); + jsonEditorRef.current = editor; + } + + return () => { + if (jsonEditorRef.current) { + jsonEditorRef.current.destroy(); + jsonEditorRef.current = null; + } + }; + }, []); + + useEffect(() => { + if (jsonEditorRef.current) { + jsonEditorRef.current.set(data); + } + }, [data]); + + const handleDragOver = (e: React.DragEvent) => { + e.preventDefault(); + }; + + const handleDrop = (e: React.DragEvent) => { + e.preventDefault(); + + try { + const dragData = getDragData(e); + if (dragData && jsonEditorRef.current) { + // Extract just the value from the drag data + const valueToReplace = dragData.value || dragData.data || dragData; + + // Get the element under the cursor + const element = document.elementFromPoint(e.clientX, e.clientY); + if (element) { + // Find the closest JSON editor node + const jsonNode = element.closest('.jsoneditor-value, .jsoneditor-field, .jsoneditor-string, .jsoneditor-number, .jsoneditor-boolean'); + + if (jsonNode) { + // Get the current JSON data + const currentData = jsonEditorRef.current.get(); + + // Try to find the path to the dropped node + const path = findNodePath(jsonNode, currentData); + + if (path) { + // Update the value at the specific path + const newData = updateValueAtPath(currentData, path, valueToReplace); + jsonEditorRef.current.set(newData); + setData(newData); + } + } + } + } + } catch (error) { + console.error("Error processing drop:", error); + } + }; + + // Helper function to find the path to a node in the JSON structure + const findNodePath = (node: Element, data: any): string | null => { + // This is a simplified approach - you might need to enhance this + // based on the actual DOM structure of jsoneditor + + // Look for data attributes or other identifiers + const fieldElement = node.closest('[data-path]'); + if (fieldElement) { + return fieldElement.getAttribute('data-path'); + } + + // Fallback: try to find by text content + const textContent = node.textContent?.trim(); + if (textContent) { + return findPathByValue(data, textContent); + } + + return null; + }; + + // Helper function to find path by value + const findPathByValue = (obj: any, value: string, currentPath = ''): string | null => { + for (const key in obj) { + const newPath = currentPath ? `${currentPath}.${key}` : key; + + if (obj[key] === value) { + return newPath; + } + + if (typeof obj[key] === 'object' && obj[key] !== null) { + const result = findPathByValue(obj[key], value, newPath); + if (result) return result; + } + } + return null; + }; + + // Helper function to update value at a specific path + const updateValueAtPath = (obj: any, path: string, newValue: any): any => { + const pathParts = path.split('.'); + const newObj = { ...obj }; + let current = newObj; + + // Navigate to the parent of the target + for (let i = 0; i < pathParts.length - 1; i++) { + if (current[pathParts[i]] === undefined) { + current[pathParts[i]] = {}; + } + current = current[pathParts[i]]; + } + + // Update the value at the target path + const lastPart = pathParts[pathParts.length - 1]; + current[lastPart] = newValue; + + return newObj; + }; + + return ( +
+ ); +}; + +export default JsonEditorPoC; diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonFormsPoC.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonFormsPoC.tsx new file mode 100644 index 000000000..b6fe5cb25 --- /dev/null +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonFormsPoC.tsx @@ -0,0 +1,121 @@ +import React, { useState } from "react"; +import { JsonForms } from "@jsonforms/react"; +import { materialRenderers, materialCells } from "@jsonforms/material-renderers"; + +const JsonFormsPoC: React.FC = () => { + const [data, setData] = useState({ + name: "John Doe", + age: 30, + email: "john@example.com", + address: { + street: "123 Main St", + city: "Anytown", + zip: "12345", + }, + preferences: { + theme: "dark", + notifications: true, + }, + }); + + // Simple schema - defines the structure and validation + const schema = { + type: "object", + properties: { + name: { + type: "string", + title: "Full Name", + }, + age: { + type: "number", + title: "Age", + minimum: 0, + maximum: 120, + }, + email: { + type: "string", + title: "Email Address", + format: "email", + }, + address: { + type: "object", + title: "Address", + properties: { + street: { type: "string", title: "Street" }, + city: { type: "string", title: "City" }, + zip: { type: "string", title: "ZIP Code" }, + }, + }, + preferences: { + type: "object", + title: "Preferences", + properties: { + theme: { + type: "string", + title: "Theme", + enum: ["light", "dark", "auto"], + }, + notifications: { + type: "boolean", + title: "Enable Notifications", + }, + }, + }, + }, + }; + + // UI schema - controls the layout and presentation + const uischema = { + type: "Control", + scope: "#", + options: { + showUnfocusedDescription: true, + }, + }; + + return ( +
+

@jsonforms/react PoC

+

This shows the basic visual style and form generation capabilities.

+ +
+ { + console.log("Data changed:", data); + console.log("Validation errors:", errors); + setData(data); + }} + /> +
+ +
+

Current Data:

+
+          {JSON.stringify(data, null, 2)}
+        
+
+
+ ); +}; + +export default JsonFormsPoC; diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/index.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/index.tsx index 347384138..f63aabea2 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/index.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/index.tsx @@ -5,6 +5,7 @@ import AssignElement from "./assignElement"; import { useAssignBuilder } from "./useAssignBuilder"; import "../styles.scss"; import { Assign } from "../../../types/assign"; +import JsonEditorPoC from "./JsonEditorPoC"; interface AssignBuilderProps { onChange: (group: Assign[]) => void; @@ -30,6 +31,8 @@ const AssignBuilder: React.FC = ({ onChange, seedGroup }) => {elements?.map((element) => ( ))} + + ); }; From c352f6762ac01b391035fa2742e0ff97163ead89 Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Wed, 13 Aug 2025 14:56:50 +0300 Subject: [PATCH 04/90] fix(665): drag-dorp poc lib 2 SIMPLER --- .../AssignBuilder/JsonEditorPoC.tsx | 158 ++---------------- 1 file changed, 14 insertions(+), 144 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.tsx index c451c4d84..145765aa1 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.tsx @@ -2,54 +2,29 @@ import React, { useEffect, useRef, useState } from "react"; // @ts-ignore import JSONEditor from "jsoneditor"; import "jsoneditor/dist/jsoneditor.css"; -import { getDragData } from "utils/component-util"; const JsonEditorPoC: React.FC = () => { const editorRef = useRef(null); const jsonEditorRef = useRef(null); - const [data, setData] = useState({ - name: "John Doe", - age: 30, - email: "john@example.com", - address: { - street: "123 Main St", - city: "Anytown", - zip: "12345", - }, - preferences: { - theme: "dark", - notifications: true, - }, - }); useEffect(() => { if (editorRef.current && !jsonEditorRef.current) { - const editor = new JSONEditor(editorRef.current, { - mode: "tree", - modes: ["tree", "view", "form", "text", "code"], - onChangeJSON: (json: any) => { - console.log("JSON changed:", json); - setData(json); + const editor = new JSONEditor(editorRef.current); + + editor.set({ + name: "John Doe", + age: 30, + email: "john@example.com", + address: { + street: "123 Main St", + city: "Anytown", + zip: "12345", }, - onError: (error: any) => { - console.error("JSON Editor error:", error); + preferences: { + theme: "dark", + notifications: true, }, - enableSort: true, - enableTransform: true, - search: true, - enableClipboard: true, - enableHistory: false, - enableNavigationBar: false, - enableStatusBar: false, - indentation: 2, - escapeUnicode: false, - sortObjectKeys: false, - colorPicker: true, - timestampTag: true, - language: "en", }); - - editor.set(data); jsonEditorRef.current = editor; } @@ -61,115 +36,10 @@ const JsonEditorPoC: React.FC = () => { }; }, []); - useEffect(() => { - if (jsonEditorRef.current) { - jsonEditorRef.current.set(data); - } - }, [data]); - - const handleDragOver = (e: React.DragEvent) => { - e.preventDefault(); - }; - - const handleDrop = (e: React.DragEvent) => { - e.preventDefault(); - - try { - const dragData = getDragData(e); - if (dragData && jsonEditorRef.current) { - // Extract just the value from the drag data - const valueToReplace = dragData.value || dragData.data || dragData; - - // Get the element under the cursor - const element = document.elementFromPoint(e.clientX, e.clientY); - if (element) { - // Find the closest JSON editor node - const jsonNode = element.closest('.jsoneditor-value, .jsoneditor-field, .jsoneditor-string, .jsoneditor-number, .jsoneditor-boolean'); - - if (jsonNode) { - // Get the current JSON data - const currentData = jsonEditorRef.current.get(); - - // Try to find the path to the dropped node - const path = findNodePath(jsonNode, currentData); - - if (path) { - // Update the value at the specific path - const newData = updateValueAtPath(currentData, path, valueToReplace); - jsonEditorRef.current.set(newData); - setData(newData); - } - } - } - } - } catch (error) { - console.error("Error processing drop:", error); - } - }; - - // Helper function to find the path to a node in the JSON structure - const findNodePath = (node: Element, data: any): string | null => { - // This is a simplified approach - you might need to enhance this - // based on the actual DOM structure of jsoneditor - - // Look for data attributes or other identifiers - const fieldElement = node.closest('[data-path]'); - if (fieldElement) { - return fieldElement.getAttribute('data-path'); - } - - // Fallback: try to find by text content - const textContent = node.textContent?.trim(); - if (textContent) { - return findPathByValue(data, textContent); - } - - return null; - }; - - // Helper function to find path by value - const findPathByValue = (obj: any, value: string, currentPath = ''): string | null => { - for (const key in obj) { - const newPath = currentPath ? `${currentPath}.${key}` : key; - - if (obj[key] === value) { - return newPath; - } - - if (typeof obj[key] === 'object' && obj[key] !== null) { - const result = findPathByValue(obj[key], value, newPath); - if (result) return result; - } - } - return null; - }; - - // Helper function to update value at a specific path - const updateValueAtPath = (obj: any, path: string, newValue: any): any => { - const pathParts = path.split('.'); - const newObj = { ...obj }; - let current = newObj; - - // Navigate to the parent of the target - for (let i = 0; i < pathParts.length - 1; i++) { - if (current[pathParts[i]] === undefined) { - current[pathParts[i]] = {}; - } - current = current[pathParts[i]]; - } - - // Update the value at the target path - const lastPart = pathParts[pathParts.length - 1]; - current[lastPart] = newValue; - - return newObj; - }; - return ( + return (
Date: Wed, 13 Aug 2025 15:07:16 +0300 Subject: [PATCH 05/90] Revert "fix(665): drag-dorp poc lib 2 SIMPLER" This reverts commit c352f6762ac01b391035fa2742e0ff97163ead89. --- .../AssignBuilder/JsonEditorPoC.tsx | 158 ++++++++++++++++-- 1 file changed, 144 insertions(+), 14 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.tsx index 145765aa1..c451c4d84 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.tsx @@ -2,29 +2,54 @@ import React, { useEffect, useRef, useState } from "react"; // @ts-ignore import JSONEditor from "jsoneditor"; import "jsoneditor/dist/jsoneditor.css"; +import { getDragData } from "utils/component-util"; const JsonEditorPoC: React.FC = () => { const editorRef = useRef(null); const jsonEditorRef = useRef(null); + const [data, setData] = useState({ + name: "John Doe", + age: 30, + email: "john@example.com", + address: { + street: "123 Main St", + city: "Anytown", + zip: "12345", + }, + preferences: { + theme: "dark", + notifications: true, + }, + }); useEffect(() => { if (editorRef.current && !jsonEditorRef.current) { - const editor = new JSONEditor(editorRef.current); - - editor.set({ - name: "John Doe", - age: 30, - email: "john@example.com", - address: { - street: "123 Main St", - city: "Anytown", - zip: "12345", + const editor = new JSONEditor(editorRef.current, { + mode: "tree", + modes: ["tree", "view", "form", "text", "code"], + onChangeJSON: (json: any) => { + console.log("JSON changed:", json); + setData(json); }, - preferences: { - theme: "dark", - notifications: true, + onError: (error: any) => { + console.error("JSON Editor error:", error); }, + enableSort: true, + enableTransform: true, + search: true, + enableClipboard: true, + enableHistory: false, + enableNavigationBar: false, + enableStatusBar: false, + indentation: 2, + escapeUnicode: false, + sortObjectKeys: false, + colorPicker: true, + timestampTag: true, + language: "en", }); + + editor.set(data); jsonEditorRef.current = editor; } @@ -36,10 +61,115 @@ const JsonEditorPoC: React.FC = () => { }; }, []); + useEffect(() => { + if (jsonEditorRef.current) { + jsonEditorRef.current.set(data); + } + }, [data]); + + const handleDragOver = (e: React.DragEvent) => { + e.preventDefault(); + }; + + const handleDrop = (e: React.DragEvent) => { + e.preventDefault(); + + try { + const dragData = getDragData(e); + if (dragData && jsonEditorRef.current) { + // Extract just the value from the drag data + const valueToReplace = dragData.value || dragData.data || dragData; + + // Get the element under the cursor + const element = document.elementFromPoint(e.clientX, e.clientY); + if (element) { + // Find the closest JSON editor node + const jsonNode = element.closest('.jsoneditor-value, .jsoneditor-field, .jsoneditor-string, .jsoneditor-number, .jsoneditor-boolean'); + + if (jsonNode) { + // Get the current JSON data + const currentData = jsonEditorRef.current.get(); + + // Try to find the path to the dropped node + const path = findNodePath(jsonNode, currentData); + + if (path) { + // Update the value at the specific path + const newData = updateValueAtPath(currentData, path, valueToReplace); + jsonEditorRef.current.set(newData); + setData(newData); + } + } + } + } + } catch (error) { + console.error("Error processing drop:", error); + } + }; + + // Helper function to find the path to a node in the JSON structure + const findNodePath = (node: Element, data: any): string | null => { + // This is a simplified approach - you might need to enhance this + // based on the actual DOM structure of jsoneditor + + // Look for data attributes or other identifiers + const fieldElement = node.closest('[data-path]'); + if (fieldElement) { + return fieldElement.getAttribute('data-path'); + } + + // Fallback: try to find by text content + const textContent = node.textContent?.trim(); + if (textContent) { + return findPathByValue(data, textContent); + } + + return null; + }; + + // Helper function to find path by value + const findPathByValue = (obj: any, value: string, currentPath = ''): string | null => { + for (const key in obj) { + const newPath = currentPath ? `${currentPath}.${key}` : key; + + if (obj[key] === value) { + return newPath; + } + + if (typeof obj[key] === 'object' && obj[key] !== null) { + const result = findPathByValue(obj[key], value, newPath); + if (result) return result; + } + } + return null; + }; + + // Helper function to update value at a specific path + const updateValueAtPath = (obj: any, path: string, newValue: any): any => { + const pathParts = path.split('.'); + const newObj = { ...obj }; + let current = newObj; + + // Navigate to the parent of the target + for (let i = 0; i < pathParts.length - 1; i++) { + if (current[pathParts[i]] === undefined) { + current[pathParts[i]] = {}; + } + current = current[pathParts[i]]; + } + + // Update the value at the target path + const lastPart = pathParts[pathParts.length - 1]; + current[lastPart] = newValue; + + return newObj; + }; - return ( + return (
Date: Wed, 13 Aug 2025 15:41:48 +0300 Subject: [PATCH 06/90] fix(665): clean --- .../AssignBuilder/JsonFormsPoC.tsx | 121 ------------------ 1 file changed, 121 deletions(-) delete mode 100644 GUI/src/components/FlowElementsPopup/AssignBuilder/JsonFormsPoC.tsx diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonFormsPoC.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonFormsPoC.tsx deleted file mode 100644 index b6fe5cb25..000000000 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonFormsPoC.tsx +++ /dev/null @@ -1,121 +0,0 @@ -import React, { useState } from "react"; -import { JsonForms } from "@jsonforms/react"; -import { materialRenderers, materialCells } from "@jsonforms/material-renderers"; - -const JsonFormsPoC: React.FC = () => { - const [data, setData] = useState({ - name: "John Doe", - age: 30, - email: "john@example.com", - address: { - street: "123 Main St", - city: "Anytown", - zip: "12345", - }, - preferences: { - theme: "dark", - notifications: true, - }, - }); - - // Simple schema - defines the structure and validation - const schema = { - type: "object", - properties: { - name: { - type: "string", - title: "Full Name", - }, - age: { - type: "number", - title: "Age", - minimum: 0, - maximum: 120, - }, - email: { - type: "string", - title: "Email Address", - format: "email", - }, - address: { - type: "object", - title: "Address", - properties: { - street: { type: "string", title: "Street" }, - city: { type: "string", title: "City" }, - zip: { type: "string", title: "ZIP Code" }, - }, - }, - preferences: { - type: "object", - title: "Preferences", - properties: { - theme: { - type: "string", - title: "Theme", - enum: ["light", "dark", "auto"], - }, - notifications: { - type: "boolean", - title: "Enable Notifications", - }, - }, - }, - }, - }; - - // UI schema - controls the layout and presentation - const uischema = { - type: "Control", - scope: "#", - options: { - showUnfocusedDescription: true, - }, - }; - - return ( -
-

@jsonforms/react PoC

-

This shows the basic visual style and form generation capabilities.

- -
- { - console.log("Data changed:", data); - console.log("Validation errors:", errors); - setData(data); - }} - /> -
- -
-

Current Data:

-
-          {JSON.stringify(data, null, 2)}
-        
-
-
- ); -}; - -export default JsonFormsPoC; From c45623146653f0bd531f7a588531df7b492610b0 Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Wed, 13 Aug 2025 15:52:52 +0300 Subject: [PATCH 07/90] fix(665): drag-dorp poc lib 2 + trans --- .../AssignBuilder/JsonEditorPoC.tsx | 146 +++++++++++++++--- 1 file changed, 125 insertions(+), 21 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.tsx index c451c4d84..62e74427f 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.tsx @@ -46,7 +46,109 @@ const JsonEditorPoC: React.FC = () => { sortObjectKeys: false, colorPicker: true, timestampTag: true, - language: "en", + language: "et", + languages: { + et: { + array: "Massiiv", + auto: "Automaatne", + appendText: "Lisa lõppu", + appendTitle: "Lisa uus väli tüübiga 'auto' selle välja järele (Ctrl+Shift+Ins)", + appendSubmenuTitle: "Vali lisatava välja tüüp", + appendTitleAuto: "Lisa uus väli tüübiga 'auto' (Ctrl+Shift+Ins)", + ascending: "Kasvav", + ascendingTitle: "Sorteeri selle ${type} alamvälju kasvavas järjekorras", + actionsMenu: "Klõpsa tegevuste menüü avamiseks (Ctrl+M)", + cannotParseFieldError: "Ei saa välja JSON-iks teisendada", + cannotParseValueError: "Ei saa väärtust JSON-iks teisendada", + collapseAll: "Ahenda kõik väljad", + compactTitle: "Tihenda JSON andmed, eemalda kõik tühikud (Ctrl+Shift+I)", + descending: "Kahanev", + descendingTitle: "Sorteeri selle ${type} alamvälju kahanevas järjekorras", + drag: "Lohista välja liigutamiseks (Alt+Shift+Nooled)", + duplicateKey: "dubleeritud võti", + duplicateText: "Dubleeri", + duplicateTitle: "Dubleeri valitud väljad (Ctrl+D)", + duplicateField: "Dubleeri see väli (Ctrl+D)", + duplicateFieldError: "Dubleeritud välja nimi", + empty: "tühi", + expandAll: "Laienda kõik väljad", + expandTitle: + "Klõpsa välja laiendamiseks/ahendamiseks (Ctrl+E). \n" + + "Ctrl+Klõps laiendab/ahendab koos kõigi alamväljadega.", + formatTitle: "Vorminda JSON andmed, korraliku taanduse ja reavahetustega (Ctrl+I)", + insert: "Lisa", + insertTitle: "Lisa uus väli tüübiga 'auto' enne seda välja (Ctrl+Ins)", + insertSub: "Vali lisatava välja tüüp", + object: "Objekt", + ok: "Ok", + redo: "Tee uuesti (Ctrl+Shift+Z)", + removeText: "Eemalda", + removeTitle: "Eemalda valitud väljad (Ctrl+Del)", + removeField: "Eemalda see väli (Ctrl+Del)", + repairTitle: + "Paranda JSON: paranda jutumärgid ja paomärgid, eemalda kommentaarid ja JSONP märgendid, teisenda JavaScript objektid JSON-iks.", + searchTitle: "Otsi välju ja väärtusi", + searchNextResultTitle: "Järgmine tulemus (Enter)", + searchPreviousResultTitle: "Eelmine tulemus (Shift + Enter)", + selectNode: "Vali sõlm...", + showAll: "näita kõik", + showMore: "näita rohkem", + showMoreStatus: "kuvatakse ${visibleChilds} ${totalChilds}-st elemendist.", + sort: "Sorteeri", + sortTitle: "Sorteeri selle ${type} alamvälju", + sortTitleShort: "Sorteeri sisu", + sortFieldLabel: "Väli:", + sortDirectionLabel: "Suund:", + sortFieldTitle: "Vali pesastatud väli, mille järgi massiivi või objekti sorteerida", + sortAscending: "Kasvav", + sortAscendingTitle: "Sorteeri valitud väli kasvavas järjekorras", + sortDescending: "Kahanev", + sortDescendingTitle: "Sorteeri valitud väli kahanevas järjekorras", + string: "Sõne", + transform: "Teisenda", + transformTitle: "Filtreeri, sorteeri või teisenda selle ${type} alamvälju", + transformTitleShort: "Filtreeri, sorteeri või teisenda sisu", + extract: "Eralda", + extractTitle: "Eralda see ${type}", + transformQueryTitle: "Sisesta JMESPath päring", + transformWizardLabel: "Nõustaja", + transformWizardFilter: "Filter", + transformWizardSortBy: "Sorteeri", + transformWizardSelectFields: "Vali väljad", + transformQueryLabel: "Päring", + transformPreviewLabel: "Eelvaade", + type: "Tüüp", + typeTitle: "Muuda selle välja tüüpi", + openUrl: "Ctrl+Klõps või Ctrl+Enter URL-i uues aknas avamiseks", + undo: "Võta viimane tegevus tagasi (Ctrl+Z)", + validationCannotMove: "Välja ei saa liigutada iseenda alamväljaks", + autoType: + 'Välja tüüp "auto". ' + + "Välja tüüp määratakse automaatselt väärtuse põhjal " + + "ja võib olla sõne, number, tõeväärtus või null.", + objectType: 'Välja tüüp "objekt". ' + "Objekt sisaldab järjestamata võti/väärtus paaride kogumit.", + arrayType: 'Välja tüüp "massiiv". ' + "Massiiv sisaldab järjestatud väärtuste kogumit.", + stringType: + 'Välja tüüp "sõne". ' + "Välja tüüpi ei määrata väärtuse põhjal, " + "vaid tagastatakse alati sõnena.", + modeEditorTitle: "Vaheta redaktori režiimi", + modeCodeText: "Kood", + modeCodeTitle: "Lülitu koodi esiletõstule", + modeFormText: "Vorm", + modeFormTitle: "Lülitu vormi redaktorile", + modeTextText: "Tekst", + modeTextTitle: "Lülitu lihtteksti redaktorile", + modeTreeText: "Puu", + modeTreeTitle: "Lülitu puu redaktorile", + modeViewText: "Vaade", + modeViewTitle: "Lülitu puu vaatele", + modePreviewText: "Eelvaade", + modePreviewTitle: "Lülitu eelvaate režiimile", + examples: "Näited", + default: "Vaikimisi", + containsInvalidProperties: "Sisaldab vigaseid omadusi", + containsInvalidItems: "Sisaldab vigaseid elemente", + }, + }, }); editor.set(data); @@ -73,26 +175,28 @@ const JsonEditorPoC: React.FC = () => { const handleDrop = (e: React.DragEvent) => { e.preventDefault(); - + try { const dragData = getDragData(e); if (dragData && jsonEditorRef.current) { // Extract just the value from the drag data const valueToReplace = dragData.value || dragData.data || dragData; - + // Get the element under the cursor const element = document.elementFromPoint(e.clientX, e.clientY); if (element) { // Find the closest JSON editor node - const jsonNode = element.closest('.jsoneditor-value, .jsoneditor-field, .jsoneditor-string, .jsoneditor-number, .jsoneditor-boolean'); - + const jsonNode = element.closest( + ".jsoneditor-value, .jsoneditor-field, .jsoneditor-string, .jsoneditor-number, .jsoneditor-boolean" + ); + if (jsonNode) { // Get the current JSON data const currentData = jsonEditorRef.current.get(); - + // Try to find the path to the dropped node const path = findNodePath(jsonNode, currentData); - + if (path) { // Update the value at the specific path const newData = updateValueAtPath(currentData, path, valueToReplace); @@ -111,32 +215,32 @@ const JsonEditorPoC: React.FC = () => { const findNodePath = (node: Element, data: any): string | null => { // This is a simplified approach - you might need to enhance this // based on the actual DOM structure of jsoneditor - + // Look for data attributes or other identifiers - const fieldElement = node.closest('[data-path]'); + const fieldElement = node.closest("[data-path]"); if (fieldElement) { - return fieldElement.getAttribute('data-path'); + return fieldElement.getAttribute("data-path"); } - + // Fallback: try to find by text content const textContent = node.textContent?.trim(); if (textContent) { return findPathByValue(data, textContent); } - + return null; }; // Helper function to find path by value - const findPathByValue = (obj: any, value: string, currentPath = ''): string | null => { + const findPathByValue = (obj: any, value: string, currentPath = ""): string | null => { for (const key in obj) { const newPath = currentPath ? `${currentPath}.${key}` : key; - + if (obj[key] === value) { return newPath; } - - if (typeof obj[key] === 'object' && obj[key] !== null) { + + if (typeof obj[key] === "object" && obj[key] !== null) { const result = findPathByValue(obj[key], value, newPath); if (result) return result; } @@ -146,10 +250,10 @@ const JsonEditorPoC: React.FC = () => { // Helper function to update value at a specific path const updateValueAtPath = (obj: any, path: string, newValue: any): any => { - const pathParts = path.split('.'); + const pathParts = path.split("."); const newObj = { ...obj }; let current = newObj; - + // Navigate to the parent of the target for (let i = 0; i < pathParts.length - 1; i++) { if (current[pathParts[i]] === undefined) { @@ -157,15 +261,15 @@ const JsonEditorPoC: React.FC = () => { } current = current[pathParts[i]]; } - + // Update the value at the target path const lastPart = pathParts[pathParts.length - 1]; current[lastPart] = newValue; - + return newObj; }; - return ( + return (
Date: Wed, 13 Aug 2025 16:09:53 +0300 Subject: [PATCH 08/90] fix(665): drag-dorp poc lib 3 BASIC --- .../AssignBuilder/JsonEditorPoC.tsx | 326 ++++-------------- .../FlowElementsPopup/AssignBuilder/index.tsx | 14 +- 2 files changed, 79 insertions(+), 261 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.tsx index 62e74427f..831adb97a 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.tsx @@ -1,158 +1,58 @@ import React, { useEffect, useRef, useState } from "react"; -// @ts-ignore -import JSONEditor from "jsoneditor"; -import "jsoneditor/dist/jsoneditor.css"; -import { getDragData } from "utils/component-util"; - -const JsonEditorPoC: React.FC = () => { +import { createJSONEditor, type Content } from "vanilla-jsoneditor"; +import { useTranslation } from "react-i18next"; +import { Track } from "components"; +import "../styles.scss"; + +interface JsonEditorPoCProps { + initialValue?: any; + onChange?: (value: any) => void; + readOnly?: boolean; +} + +const JsonEditorPoC: React.FC = ({ initialValue = {}, onChange, readOnly = false }) => { + const { t } = useTranslation(); const editorRef = useRef(null); - const jsonEditorRef = useRef(null); - const [data, setData] = useState({ - name: "John Doe", - age: 30, - email: "john@example.com", - address: { - street: "123 Main St", - city: "Anytown", - zip: "12345", - }, - preferences: { - theme: "dark", - notifications: true, - }, - }); + const jsonEditorRef = useRef(null); + const [isValid, setIsValid] = useState(true); + const [error, setError] = useState(null); useEffect(() => { if (editorRef.current && !jsonEditorRef.current) { - const editor = new JSONEditor(editorRef.current, { - mode: "tree", - modes: ["tree", "view", "form", "text", "code"], - onChangeJSON: (json: any) => { - console.log("JSON changed:", json); - setData(json); - }, - onError: (error: any) => { - console.error("JSON Editor error:", error); - }, - enableSort: true, - enableTransform: true, - search: true, - enableClipboard: true, - enableHistory: false, - enableNavigationBar: false, - enableStatusBar: false, - indentation: 2, - escapeUnicode: false, - sortObjectKeys: false, - colorPicker: true, - timestampTag: true, - language: "et", - languages: { - et: { - array: "Massiiv", - auto: "Automaatne", - appendText: "Lisa lõppu", - appendTitle: "Lisa uus väli tüübiga 'auto' selle välja järele (Ctrl+Shift+Ins)", - appendSubmenuTitle: "Vali lisatava välja tüüp", - appendTitleAuto: "Lisa uus väli tüübiga 'auto' (Ctrl+Shift+Ins)", - ascending: "Kasvav", - ascendingTitle: "Sorteeri selle ${type} alamvälju kasvavas järjekorras", - actionsMenu: "Klõpsa tegevuste menüü avamiseks (Ctrl+M)", - cannotParseFieldError: "Ei saa välja JSON-iks teisendada", - cannotParseValueError: "Ei saa väärtust JSON-iks teisendada", - collapseAll: "Ahenda kõik väljad", - compactTitle: "Tihenda JSON andmed, eemalda kõik tühikud (Ctrl+Shift+I)", - descending: "Kahanev", - descendingTitle: "Sorteeri selle ${type} alamvälju kahanevas järjekorras", - drag: "Lohista välja liigutamiseks (Alt+Shift+Nooled)", - duplicateKey: "dubleeritud võti", - duplicateText: "Dubleeri", - duplicateTitle: "Dubleeri valitud väljad (Ctrl+D)", - duplicateField: "Dubleeri see väli (Ctrl+D)", - duplicateFieldError: "Dubleeritud välja nimi", - empty: "tühi", - expandAll: "Laienda kõik väljad", - expandTitle: - "Klõpsa välja laiendamiseks/ahendamiseks (Ctrl+E). \n" + - "Ctrl+Klõps laiendab/ahendab koos kõigi alamväljadega.", - formatTitle: "Vorminda JSON andmed, korraliku taanduse ja reavahetustega (Ctrl+I)", - insert: "Lisa", - insertTitle: "Lisa uus väli tüübiga 'auto' enne seda välja (Ctrl+Ins)", - insertSub: "Vali lisatava välja tüüp", - object: "Objekt", - ok: "Ok", - redo: "Tee uuesti (Ctrl+Shift+Z)", - removeText: "Eemalda", - removeTitle: "Eemalda valitud väljad (Ctrl+Del)", - removeField: "Eemalda see väli (Ctrl+Del)", - repairTitle: - "Paranda JSON: paranda jutumärgid ja paomärgid, eemalda kommentaarid ja JSONP märgendid, teisenda JavaScript objektid JSON-iks.", - searchTitle: "Otsi välju ja väärtusi", - searchNextResultTitle: "Järgmine tulemus (Enter)", - searchPreviousResultTitle: "Eelmine tulemus (Shift + Enter)", - selectNode: "Vali sõlm...", - showAll: "näita kõik", - showMore: "näita rohkem", - showMoreStatus: "kuvatakse ${visibleChilds} ${totalChilds}-st elemendist.", - sort: "Sorteeri", - sortTitle: "Sorteeri selle ${type} alamvälju", - sortTitleShort: "Sorteeri sisu", - sortFieldLabel: "Väli:", - sortDirectionLabel: "Suund:", - sortFieldTitle: "Vali pesastatud väli, mille järgi massiivi või objekti sorteerida", - sortAscending: "Kasvav", - sortAscendingTitle: "Sorteeri valitud väli kasvavas järjekorras", - sortDescending: "Kahanev", - sortDescendingTitle: "Sorteeri valitud väli kahanevas järjekorras", - string: "Sõne", - transform: "Teisenda", - transformTitle: "Filtreeri, sorteeri või teisenda selle ${type} alamvälju", - transformTitleShort: "Filtreeri, sorteeri või teisenda sisu", - extract: "Eralda", - extractTitle: "Eralda see ${type}", - transformQueryTitle: "Sisesta JMESPath päring", - transformWizardLabel: "Nõustaja", - transformWizardFilter: "Filter", - transformWizardSortBy: "Sorteeri", - transformWizardSelectFields: "Vali väljad", - transformQueryLabel: "Päring", - transformPreviewLabel: "Eelvaade", - type: "Tüüp", - typeTitle: "Muuda selle välja tüüpi", - openUrl: "Ctrl+Klõps või Ctrl+Enter URL-i uues aknas avamiseks", - undo: "Võta viimane tegevus tagasi (Ctrl+Z)", - validationCannotMove: "Välja ei saa liigutada iseenda alamväljaks", - autoType: - 'Välja tüüp "auto". ' + - "Välja tüüp määratakse automaatselt väärtuse põhjal " + - "ja võib olla sõne, number, tõeväärtus või null.", - objectType: 'Välja tüüp "objekt". ' + "Objekt sisaldab järjestamata võti/väärtus paaride kogumit.", - arrayType: 'Välja tüüp "massiiv". ' + "Massiiv sisaldab järjestatud väärtuste kogumit.", - stringType: - 'Välja tüüp "sõne". ' + "Välja tüüpi ei määrata väärtuse põhjal, " + "vaid tagastatakse alati sõnena.", - modeEditorTitle: "Vaheta redaktori režiimi", - modeCodeText: "Kood", - modeCodeTitle: "Lülitu koodi esiletõstule", - modeFormText: "Vorm", - modeFormTitle: "Lülitu vormi redaktorile", - modeTextText: "Tekst", - modeTextTitle: "Lülitu lihtteksti redaktorile", - modeTreeText: "Puu", - modeTreeTitle: "Lülitu puu redaktorile", - modeViewText: "Vaade", - modeViewTitle: "Lülitu puu vaatele", - modePreviewText: "Eelvaade", - modePreviewTitle: "Lülitu eelvaate režiimile", - examples: "Näited", - default: "Vaikimisi", - containsInvalidProperties: "Sisaldab vigaseid omadusi", - containsInvalidItems: "Sisaldab vigaseid elemente", + // Initialize the JSON editor using the correct API + jsonEditorRef.current = createJSONEditor({ + target: editorRef.current, + props: { + content: { json: initialValue }, + readOnly, + onChange: (newContent: Content) => { + try { + // Handle different content formats from vanilla-jsoneditor + let jsonValue: any; + if ("json" in newContent) { + jsonValue = newContent.json; + } else if ("text" in newContent) { + jsonValue = JSON.parse(newContent.text); + } else { + jsonValue = newContent; + } + + setIsValid(true); + setError(null); + onChange?.(jsonValue); + } catch (err) { + setIsValid(false); + setError(err instanceof Error ? err.message : "Invalid JSON"); + } }, + mode: "tree", + navigationBar: true, + statusBar: true, + colorPicker: true, + search: true, + height: "400px", }, }); - - editor.set(data); - jsonEditorRef.current = editor; } return () => { @@ -161,125 +61,31 @@ const JsonEditorPoC: React.FC = () => { jsonEditorRef.current = null; } }; - }, []); + }, [initialValue, readOnly, onChange]); + // Update editor value when initialValue changes useEffect(() => { if (jsonEditorRef.current) { - jsonEditorRef.current.set(data); + jsonEditorRef.current.set({ json: initialValue }); } - }, [data]); - - const handleDragOver = (e: React.DragEvent) => { - e.preventDefault(); - }; - - const handleDrop = (e: React.DragEvent) => { - e.preventDefault(); - - try { - const dragData = getDragData(e); - if (dragData && jsonEditorRef.current) { - // Extract just the value from the drag data - const valueToReplace = dragData.value || dragData.data || dragData; - - // Get the element under the cursor - const element = document.elementFromPoint(e.clientX, e.clientY); - if (element) { - // Find the closest JSON editor node - const jsonNode = element.closest( - ".jsoneditor-value, .jsoneditor-field, .jsoneditor-string, .jsoneditor-number, .jsoneditor-boolean" - ); - - if (jsonNode) { - // Get the current JSON data - const currentData = jsonEditorRef.current.get(); - - // Try to find the path to the dropped node - const path = findNodePath(jsonNode, currentData); - - if (path) { - // Update the value at the specific path - const newData = updateValueAtPath(currentData, path, valueToReplace); - jsonEditorRef.current.set(newData); - setData(newData); - } - } - } - } - } catch (error) { - console.error("Error processing drop:", error); - } - }; - - // Helper function to find the path to a node in the JSON structure - const findNodePath = (node: Element, data: any): string | null => { - // This is a simplified approach - you might need to enhance this - // based on the actual DOM structure of jsoneditor - - // Look for data attributes or other identifiers - const fieldElement = node.closest("[data-path]"); - if (fieldElement) { - return fieldElement.getAttribute("data-path"); - } - - // Fallback: try to find by text content - const textContent = node.textContent?.trim(); - if (textContent) { - return findPathByValue(data, textContent); - } - - return null; - }; - - // Helper function to find path by value - const findPathByValue = (obj: any, value: string, currentPath = ""): string | null => { - for (const key in obj) { - const newPath = currentPath ? `${currentPath}.${key}` : key; - - if (obj[key] === value) { - return newPath; - } - - if (typeof obj[key] === "object" && obj[key] !== null) { - const result = findPathByValue(obj[key], value, newPath); - if (result) return result; - } - } - return null; - }; - - // Helper function to update value at a specific path - const updateValueAtPath = (obj: any, path: string, newValue: any): any => { - const pathParts = path.split("."); - const newObj = { ...obj }; - let current = newObj; - - // Navigate to the parent of the target - for (let i = 0; i < pathParts.length - 1; i++) { - if (current[pathParts[i]] === undefined) { - current[pathParts[i]] = {}; - } - current = current[pathParts[i]]; - } - - // Update the value at the target path - const lastPart = pathParts[pathParts.length - 1]; - current[lastPart] = newValue; - - return newObj; - }; + }, [initialValue]); return ( -
+ +
+

JSON Editor PoC

+ {!isValid && error &&
{error}
} +
+ +
+ ); }; diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/index.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/index.tsx index f63aabea2..9f52e804d 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/index.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/index.tsx @@ -32,7 +32,19 @@ const AssignBuilder: React.FC = ({ onChange, seedGroup }) => ))} - + {/* JSON Editor PoC */} + { + console.log("JSON Editor changed:", value); + }} + /> ); }; From 8401e463b4baa5494818ead50eee4e90f51286d2 Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Thu, 14 Aug 2025 10:31:19 +0300 Subject: [PATCH 09/90] Revert "fix(665): drag-dorp poc lib 3 BASIC" This reverts commit 89d5dd8b55be07b7ce36495d6385fd222605b1a7. --- .../AssignBuilder/JsonEditorPoC.tsx | 326 ++++++++++++++---- .../FlowElementsPopup/AssignBuilder/index.tsx | 14 +- 2 files changed, 261 insertions(+), 79 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.tsx index 831adb97a..62e74427f 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.tsx @@ -1,58 +1,158 @@ import React, { useEffect, useRef, useState } from "react"; -import { createJSONEditor, type Content } from "vanilla-jsoneditor"; -import { useTranslation } from "react-i18next"; -import { Track } from "components"; -import "../styles.scss"; - -interface JsonEditorPoCProps { - initialValue?: any; - onChange?: (value: any) => void; - readOnly?: boolean; -} - -const JsonEditorPoC: React.FC = ({ initialValue = {}, onChange, readOnly = false }) => { - const { t } = useTranslation(); +// @ts-ignore +import JSONEditor from "jsoneditor"; +import "jsoneditor/dist/jsoneditor.css"; +import { getDragData } from "utils/component-util"; + +const JsonEditorPoC: React.FC = () => { const editorRef = useRef(null); - const jsonEditorRef = useRef(null); - const [isValid, setIsValid] = useState(true); - const [error, setError] = useState(null); + const jsonEditorRef = useRef(null); + const [data, setData] = useState({ + name: "John Doe", + age: 30, + email: "john@example.com", + address: { + street: "123 Main St", + city: "Anytown", + zip: "12345", + }, + preferences: { + theme: "dark", + notifications: true, + }, + }); useEffect(() => { if (editorRef.current && !jsonEditorRef.current) { - // Initialize the JSON editor using the correct API - jsonEditorRef.current = createJSONEditor({ - target: editorRef.current, - props: { - content: { json: initialValue }, - readOnly, - onChange: (newContent: Content) => { - try { - // Handle different content formats from vanilla-jsoneditor - let jsonValue: any; - if ("json" in newContent) { - jsonValue = newContent.json; - } else if ("text" in newContent) { - jsonValue = JSON.parse(newContent.text); - } else { - jsonValue = newContent; - } - - setIsValid(true); - setError(null); - onChange?.(jsonValue); - } catch (err) { - setIsValid(false); - setError(err instanceof Error ? err.message : "Invalid JSON"); - } + const editor = new JSONEditor(editorRef.current, { + mode: "tree", + modes: ["tree", "view", "form", "text", "code"], + onChangeJSON: (json: any) => { + console.log("JSON changed:", json); + setData(json); + }, + onError: (error: any) => { + console.error("JSON Editor error:", error); + }, + enableSort: true, + enableTransform: true, + search: true, + enableClipboard: true, + enableHistory: false, + enableNavigationBar: false, + enableStatusBar: false, + indentation: 2, + escapeUnicode: false, + sortObjectKeys: false, + colorPicker: true, + timestampTag: true, + language: "et", + languages: { + et: { + array: "Massiiv", + auto: "Automaatne", + appendText: "Lisa lõppu", + appendTitle: "Lisa uus väli tüübiga 'auto' selle välja järele (Ctrl+Shift+Ins)", + appendSubmenuTitle: "Vali lisatava välja tüüp", + appendTitleAuto: "Lisa uus väli tüübiga 'auto' (Ctrl+Shift+Ins)", + ascending: "Kasvav", + ascendingTitle: "Sorteeri selle ${type} alamvälju kasvavas järjekorras", + actionsMenu: "Klõpsa tegevuste menüü avamiseks (Ctrl+M)", + cannotParseFieldError: "Ei saa välja JSON-iks teisendada", + cannotParseValueError: "Ei saa väärtust JSON-iks teisendada", + collapseAll: "Ahenda kõik väljad", + compactTitle: "Tihenda JSON andmed, eemalda kõik tühikud (Ctrl+Shift+I)", + descending: "Kahanev", + descendingTitle: "Sorteeri selle ${type} alamvälju kahanevas järjekorras", + drag: "Lohista välja liigutamiseks (Alt+Shift+Nooled)", + duplicateKey: "dubleeritud võti", + duplicateText: "Dubleeri", + duplicateTitle: "Dubleeri valitud väljad (Ctrl+D)", + duplicateField: "Dubleeri see väli (Ctrl+D)", + duplicateFieldError: "Dubleeritud välja nimi", + empty: "tühi", + expandAll: "Laienda kõik väljad", + expandTitle: + "Klõpsa välja laiendamiseks/ahendamiseks (Ctrl+E). \n" + + "Ctrl+Klõps laiendab/ahendab koos kõigi alamväljadega.", + formatTitle: "Vorminda JSON andmed, korraliku taanduse ja reavahetustega (Ctrl+I)", + insert: "Lisa", + insertTitle: "Lisa uus väli tüübiga 'auto' enne seda välja (Ctrl+Ins)", + insertSub: "Vali lisatava välja tüüp", + object: "Objekt", + ok: "Ok", + redo: "Tee uuesti (Ctrl+Shift+Z)", + removeText: "Eemalda", + removeTitle: "Eemalda valitud väljad (Ctrl+Del)", + removeField: "Eemalda see väli (Ctrl+Del)", + repairTitle: + "Paranda JSON: paranda jutumärgid ja paomärgid, eemalda kommentaarid ja JSONP märgendid, teisenda JavaScript objektid JSON-iks.", + searchTitle: "Otsi välju ja väärtusi", + searchNextResultTitle: "Järgmine tulemus (Enter)", + searchPreviousResultTitle: "Eelmine tulemus (Shift + Enter)", + selectNode: "Vali sõlm...", + showAll: "näita kõik", + showMore: "näita rohkem", + showMoreStatus: "kuvatakse ${visibleChilds} ${totalChilds}-st elemendist.", + sort: "Sorteeri", + sortTitle: "Sorteeri selle ${type} alamvälju", + sortTitleShort: "Sorteeri sisu", + sortFieldLabel: "Väli:", + sortDirectionLabel: "Suund:", + sortFieldTitle: "Vali pesastatud väli, mille järgi massiivi või objekti sorteerida", + sortAscending: "Kasvav", + sortAscendingTitle: "Sorteeri valitud väli kasvavas järjekorras", + sortDescending: "Kahanev", + sortDescendingTitle: "Sorteeri valitud väli kahanevas järjekorras", + string: "Sõne", + transform: "Teisenda", + transformTitle: "Filtreeri, sorteeri või teisenda selle ${type} alamvälju", + transformTitleShort: "Filtreeri, sorteeri või teisenda sisu", + extract: "Eralda", + extractTitle: "Eralda see ${type}", + transformQueryTitle: "Sisesta JMESPath päring", + transformWizardLabel: "Nõustaja", + transformWizardFilter: "Filter", + transformWizardSortBy: "Sorteeri", + transformWizardSelectFields: "Vali väljad", + transformQueryLabel: "Päring", + transformPreviewLabel: "Eelvaade", + type: "Tüüp", + typeTitle: "Muuda selle välja tüüpi", + openUrl: "Ctrl+Klõps või Ctrl+Enter URL-i uues aknas avamiseks", + undo: "Võta viimane tegevus tagasi (Ctrl+Z)", + validationCannotMove: "Välja ei saa liigutada iseenda alamväljaks", + autoType: + 'Välja tüüp "auto". ' + + "Välja tüüp määratakse automaatselt väärtuse põhjal " + + "ja võib olla sõne, number, tõeväärtus või null.", + objectType: 'Välja tüüp "objekt". ' + "Objekt sisaldab järjestamata võti/väärtus paaride kogumit.", + arrayType: 'Välja tüüp "massiiv". ' + "Massiiv sisaldab järjestatud väärtuste kogumit.", + stringType: + 'Välja tüüp "sõne". ' + "Välja tüüpi ei määrata väärtuse põhjal, " + "vaid tagastatakse alati sõnena.", + modeEditorTitle: "Vaheta redaktori režiimi", + modeCodeText: "Kood", + modeCodeTitle: "Lülitu koodi esiletõstule", + modeFormText: "Vorm", + modeFormTitle: "Lülitu vormi redaktorile", + modeTextText: "Tekst", + modeTextTitle: "Lülitu lihtteksti redaktorile", + modeTreeText: "Puu", + modeTreeTitle: "Lülitu puu redaktorile", + modeViewText: "Vaade", + modeViewTitle: "Lülitu puu vaatele", + modePreviewText: "Eelvaade", + modePreviewTitle: "Lülitu eelvaate režiimile", + examples: "Näited", + default: "Vaikimisi", + containsInvalidProperties: "Sisaldab vigaseid omadusi", + containsInvalidItems: "Sisaldab vigaseid elemente", }, - mode: "tree", - navigationBar: true, - statusBar: true, - colorPicker: true, - search: true, - height: "400px", }, }); + + editor.set(data); + jsonEditorRef.current = editor; } return () => { @@ -61,31 +161,125 @@ const JsonEditorPoC: React.FC = ({ initialValue = {}, onChan jsonEditorRef.current = null; } }; - }, [initialValue, readOnly, onChange]); + }, []); - // Update editor value when initialValue changes useEffect(() => { if (jsonEditorRef.current) { - jsonEditorRef.current.set({ json: initialValue }); + jsonEditorRef.current.set(data); } - }, [initialValue]); + }, [data]); + + const handleDragOver = (e: React.DragEvent) => { + e.preventDefault(); + }; + + const handleDrop = (e: React.DragEvent) => { + e.preventDefault(); + + try { + const dragData = getDragData(e); + if (dragData && jsonEditorRef.current) { + // Extract just the value from the drag data + const valueToReplace = dragData.value || dragData.data || dragData; + + // Get the element under the cursor + const element = document.elementFromPoint(e.clientX, e.clientY); + if (element) { + // Find the closest JSON editor node + const jsonNode = element.closest( + ".jsoneditor-value, .jsoneditor-field, .jsoneditor-string, .jsoneditor-number, .jsoneditor-boolean" + ); + + if (jsonNode) { + // Get the current JSON data + const currentData = jsonEditorRef.current.get(); + + // Try to find the path to the dropped node + const path = findNodePath(jsonNode, currentData); + + if (path) { + // Update the value at the specific path + const newData = updateValueAtPath(currentData, path, valueToReplace); + jsonEditorRef.current.set(newData); + setData(newData); + } + } + } + } + } catch (error) { + console.error("Error processing drop:", error); + } + }; + + // Helper function to find the path to a node in the JSON structure + const findNodePath = (node: Element, data: any): string | null => { + // This is a simplified approach - you might need to enhance this + // based on the actual DOM structure of jsoneditor + + // Look for data attributes or other identifiers + const fieldElement = node.closest("[data-path]"); + if (fieldElement) { + return fieldElement.getAttribute("data-path"); + } + + // Fallback: try to find by text content + const textContent = node.textContent?.trim(); + if (textContent) { + return findPathByValue(data, textContent); + } + + return null; + }; + + // Helper function to find path by value + const findPathByValue = (obj: any, value: string, currentPath = ""): string | null => { + for (const key in obj) { + const newPath = currentPath ? `${currentPath}.${key}` : key; + + if (obj[key] === value) { + return newPath; + } + + if (typeof obj[key] === "object" && obj[key] !== null) { + const result = findPathByValue(obj[key], value, newPath); + if (result) return result; + } + } + return null; + }; + + // Helper function to update value at a specific path + const updateValueAtPath = (obj: any, path: string, newValue: any): any => { + const pathParts = path.split("."); + const newObj = { ...obj }; + let current = newObj; + + // Navigate to the parent of the target + for (let i = 0; i < pathParts.length - 1; i++) { + if (current[pathParts[i]] === undefined) { + current[pathParts[i]] = {}; + } + current = current[pathParts[i]]; + } + + // Update the value at the target path + const lastPart = pathParts[pathParts.length - 1]; + current[lastPart] = newValue; + + return newObj; + }; return ( - -
-

JSON Editor PoC

- {!isValid && error &&
{error}
} -
- -
- +
); }; diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/index.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/index.tsx index 9f52e804d..f63aabea2 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/index.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/index.tsx @@ -32,19 +32,7 @@ const AssignBuilder: React.FC = ({ onChange, seedGroup }) => ))} - {/* JSON Editor PoC */} - { - console.log("JSON Editor changed:", value); - }} - /> + ); }; From cc759451ed747c04226b29e70a815918c1f6944f Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Thu, 14 Aug 2025 10:48:50 +0300 Subject: [PATCH 10/90] fix(665): drag-dorp poc lib 2 hover css --- .../AssignBuilder/JsonEditorPoC.module.scss | 12 ++++ .../AssignBuilder/JsonEditorPoC.scss | 12 ++++ .../AssignBuilder/JsonEditorPoC.tsx | 72 ++++++++++++------- 3 files changed, 72 insertions(+), 24 deletions(-) create mode 100644 GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.module.scss create mode 100644 GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.scss diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.module.scss b/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.module.scss new file mode 100644 index 000000000..f4561f5b4 --- /dev/null +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.module.scss @@ -0,0 +1,12 @@ +@import "src/styles/tools/spacing"; +@import "src/styles/tools/color"; +@import "src/styles/settings/variables/other"; +@import "src/styles/settings/variables/typography"; + +.dragHoverHighlight { + background-color: #e3f2fd !important; + border: 1px solid #2196f3 !important; + border-radius: 4px !important; + box-shadow: 0 0 8px rgba(33, 150, 243, 0.3) !important; + transition: all 0.2s ease-in-out !important; +} diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.scss b/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.scss new file mode 100644 index 000000000..f4561f5b4 --- /dev/null +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.scss @@ -0,0 +1,12 @@ +@import "src/styles/tools/spacing"; +@import "src/styles/tools/color"; +@import "src/styles/settings/variables/other"; +@import "src/styles/settings/variables/typography"; + +.dragHoverHighlight { + background-color: #e3f2fd !important; + border: 1px solid #2196f3 !important; + border-radius: 4px !important; + box-shadow: 0 0 8px rgba(33, 150, 243, 0.3) !important; + transition: all 0.2s ease-in-out !important; +} diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.tsx index 62e74427f..8589eda76 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.tsx @@ -3,10 +3,12 @@ import React, { useEffect, useRef, useState } from "react"; import JSONEditor from "jsoneditor"; import "jsoneditor/dist/jsoneditor.css"; import { getDragData } from "utils/component-util"; +import styles from "./JsonEditorPoC.module.scss"; const JsonEditorPoC: React.FC = () => { const editorRef = useRef(null); const jsonEditorRef = useRef(null); + const [hoveredElement, setHoveredElement] = useState(null); const [data, setData] = useState({ name: "John Doe", age: 30, @@ -27,25 +29,6 @@ const JsonEditorPoC: React.FC = () => { const editor = new JSONEditor(editorRef.current, { mode: "tree", modes: ["tree", "view", "form", "text", "code"], - onChangeJSON: (json: any) => { - console.log("JSON changed:", json); - setData(json); - }, - onError: (error: any) => { - console.error("JSON Editor error:", error); - }, - enableSort: true, - enableTransform: true, - search: true, - enableClipboard: true, - enableHistory: false, - enableNavigationBar: false, - enableStatusBar: false, - indentation: 2, - escapeUnicode: false, - sortObjectKeys: false, - colorPicker: true, - timestampTag: true, language: "et", languages: { et: { @@ -169,13 +152,56 @@ const JsonEditorPoC: React.FC = () => { } }, [data]); + // Cleanup effect to remove highlight when component unmounts + useEffect(() => { + return () => { + if (hoveredElement) { + hoveredElement.classList.remove(styles.dragHoverHighlight); + } + }; + }, [hoveredElement]); + const handleDragOver = (e: React.DragEvent) => { e.preventDefault(); + + // Get the element under the cursor + const element = document.elementFromPoint(e.clientX, e.clientY); + if (element) { + // Find the closest JSON editor node + const jsonNode = element.closest( + ".jsoneditor-value, .jsoneditor-field, .jsoneditor-string, .jsoneditor-number, .jsoneditor-boolean" + ); + + // Remove highlight from previously hovered element + if (hoveredElement && hoveredElement !== jsonNode) { + hoveredElement.classList.remove(styles.dragHoverHighlight); + } + + // Add highlight to currently hovered element + if (jsonNode && jsonNode !== hoveredElement) { + jsonNode.classList.add(styles.dragHoverHighlight); + setHoveredElement(jsonNode); + } + } + }; + + const handleDragLeave = (e: React.DragEvent) => { + // Remove highlight when leaving the drop zone + if (hoveredElement) { + hoveredElement.classList.remove(styles.dragHoverHighlight); + setHoveredElement(null); + } }; const handleDrop = (e: React.DragEvent) => { e.preventDefault(); + // Clean up highlight + if (hoveredElement) { + hoveredElement.classList.remove(styles.dragHoverHighlight); + setHoveredElement(null); + } + try { const dragData = getDragData(e); if (dragData && jsonEditorRef.current) { @@ -212,10 +238,7 @@ const JsonEditorPoC: React.FC = () => { }; // Helper function to find the path to a node in the JSON structure - const findNodePath = (node: Element, data: any): string | null => { - // This is a simplified approach - you might need to enhance this - // based on the actual DOM structure of jsoneditor - + const findNodePath = (node: Element, data: unknown): string | null => { // Look for data attributes or other identifiers const fieldElement = node.closest("[data-path]"); if (fieldElement) { @@ -252,7 +275,7 @@ const JsonEditorPoC: React.FC = () => { const updateValueAtPath = (obj: any, path: string, newValue: any): any => { const pathParts = path.split("."); const newObj = { ...obj }; - let current = newObj; + let current: any = newObj; // Navigate to the parent of the target for (let i = 0; i < pathParts.length - 1; i++) { @@ -273,6 +296,7 @@ const JsonEditorPoC: React.FC = () => {
Date: Thu, 14 Aug 2025 10:50:25 +0300 Subject: [PATCH 11/90] fix(665): to do --- .../FlowElementsPopup/AssignBuilder/JsonEditorPoC.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.tsx index 8589eda76..f23ad7eee 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useRef, useState } from "react"; +// todo why? // @ts-ignore import JSONEditor from "jsoneditor"; import "jsoneditor/dist/jsoneditor.css"; @@ -11,7 +12,7 @@ const JsonEditorPoC: React.FC = () => { const [hoveredElement, setHoveredElement] = useState(null); const [data, setData] = useState({ name: "John Doe", - age: 30, + age: "30", email: "john@example.com", address: { street: "123 Main St", From 4ff51f98d6973e2e19ca3599ffe3add0fbf1f0e7 Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Thu, 14 Aug 2025 10:53:00 +0300 Subject: [PATCH 12/90] fix(665): tyoes --- .../AssignBuilder/JsonEditorPoC.tsx | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.tsx index f23ad7eee..0993ecacd 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.tsx @@ -226,9 +226,9 @@ const JsonEditorPoC: React.FC = () => { if (path) { // Update the value at the specific path - const newData = updateValueAtPath(currentData, path, valueToReplace); + const newData = updateValueAtPath(currentData as Record, path, valueToReplace); jsonEditorRef.current.set(newData); - setData(newData); + setData(newData as typeof data); } } } @@ -239,7 +239,7 @@ const JsonEditorPoC: React.FC = () => { }; // Helper function to find the path to a node in the JSON structure - const findNodePath = (node: Element, data: unknown): string | null => { + const findNodePath = (node: Element, data: Record): string | null => { // Look for data attributes or other identifiers const fieldElement = node.closest("[data-path]"); if (fieldElement) { @@ -256,7 +256,7 @@ const JsonEditorPoC: React.FC = () => { }; // Helper function to find path by value - const findPathByValue = (obj: any, value: string, currentPath = ""): string | null => { + const findPathByValue = (obj: Record, value: string, currentPath = ""): string | null => { for (const key in obj) { const newPath = currentPath ? `${currentPath}.${key}` : key; @@ -265,7 +265,7 @@ const JsonEditorPoC: React.FC = () => { } if (typeof obj[key] === "object" && obj[key] !== null) { - const result = findPathByValue(obj[key], value, newPath); + const result = findPathByValue(obj[key] as Record, value, newPath); if (result) return result; } } @@ -273,17 +273,21 @@ const JsonEditorPoC: React.FC = () => { }; // Helper function to update value at a specific path - const updateValueAtPath = (obj: any, path: string, newValue: any): any => { + const updateValueAtPath = ( + obj: Record, + path: string, + newValue: unknown + ): Record => { const pathParts = path.split("."); const newObj = { ...obj }; - let current: any = newObj; + let current: Record = newObj; // Navigate to the parent of the target for (let i = 0; i < pathParts.length - 1; i++) { if (current[pathParts[i]] === undefined) { current[pathParts[i]] = {}; } - current = current[pathParts[i]]; + current = current[pathParts[i]] as Record; } // Update the value at the target path From 2ac18cba441a9845d4b3ac0302115b7bc3cacaed Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Thu, 14 Aug 2025 11:01:32 +0300 Subject: [PATCH 13/90] fix(665): rename --- .../AssignBuilder/JsonEditorPoC.scss | 12 ------------ ...nEditorPoC.module.scss => ObjectTree.module.scss} | 0 .../{JsonEditorPoC.tsx => ObjectTree.tsx} | 6 +++--- .../FlowElementsPopup/AssignBuilder/index.tsx | 4 ++-- 4 files changed, 5 insertions(+), 17 deletions(-) delete mode 100644 GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.scss rename GUI/src/components/FlowElementsPopup/AssignBuilder/{JsonEditorPoC.module.scss => ObjectTree.module.scss} (100%) rename GUI/src/components/FlowElementsPopup/AssignBuilder/{JsonEditorPoC.tsx => ObjectTree.tsx} (98%) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.scss b/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.scss deleted file mode 100644 index f4561f5b4..000000000 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.scss +++ /dev/null @@ -1,12 +0,0 @@ -@import "src/styles/tools/spacing"; -@import "src/styles/tools/color"; -@import "src/styles/settings/variables/other"; -@import "src/styles/settings/variables/typography"; - -.dragHoverHighlight { - background-color: #e3f2fd !important; - border: 1px solid #2196f3 !important; - border-radius: 4px !important; - box-shadow: 0 0 8px rgba(33, 150, 243, 0.3) !important; - transition: all 0.2s ease-in-out !important; -} diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.module.scss b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.module.scss similarity index 100% rename from GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.module.scss rename to GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.module.scss diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx similarity index 98% rename from GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.tsx rename to GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx index 0993ecacd..445537f5a 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/JsonEditorPoC.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx @@ -4,9 +4,9 @@ import React, { useEffect, useRef, useState } from "react"; import JSONEditor from "jsoneditor"; import "jsoneditor/dist/jsoneditor.css"; import { getDragData } from "utils/component-util"; -import styles from "./JsonEditorPoC.module.scss"; +import styles from "./ObjectTree.module.scss"; -const JsonEditorPoC: React.FC = () => { +const ObjectTree: React.FC = () => { const editorRef = useRef(null); const jsonEditorRef = useRef(null); const [hoveredElement, setHoveredElement] = useState(null); @@ -312,4 +312,4 @@ const JsonEditorPoC: React.FC = () => { ); }; -export default JsonEditorPoC; +export default ObjectTree; diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/index.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/index.tsx index f63aabea2..75d87680a 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/index.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/index.tsx @@ -5,7 +5,7 @@ import AssignElement from "./assignElement"; import { useAssignBuilder } from "./useAssignBuilder"; import "../styles.scss"; import { Assign } from "../../../types/assign"; -import JsonEditorPoC from "./JsonEditorPoC"; +import ObjectTree from "./ObjectTree"; interface AssignBuilderProps { onChange: (group: Assign[]) => void; @@ -32,7 +32,7 @@ const AssignBuilder: React.FC = ({ onChange, seedGroup }) => ))} - + ); }; From e98c1158df9f8ae0d30fe7a1a884fd5a77b3c453 Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Fri, 15 Aug 2025 09:01:46 +0300 Subject: [PATCH 14/90] fix(665): Clean up --- .../components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx index 445537f5a..865239c43 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx @@ -28,8 +28,6 @@ const ObjectTree: React.FC = () => { useEffect(() => { if (editorRef.current && !jsonEditorRef.current) { const editor = new JSONEditor(editorRef.current, { - mode: "tree", - modes: ["tree", "view", "form", "text", "code"], language: "et", languages: { et: { From c4bec5b720614f5c258cacbe48dc7824d170566e Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Fri, 15 Aug 2025 09:03:31 +0300 Subject: [PATCH 15/90] fix(665): Clean up --- .../AssignBuilder/ObjectTree.tsx | 114 +++++++++--------- 1 file changed, 55 insertions(+), 59 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx index 865239c43..cd2ae15cf 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx @@ -6,6 +6,61 @@ import "jsoneditor/dist/jsoneditor.css"; import { getDragData } from "utils/component-util"; import styles from "./ObjectTree.module.scss"; +// Helper function to find the path to a node in the JSON structure +const findNodePath = (node: Element, data: Record): string | null => { + // Look for data attributes or other identifiers + const fieldElement = node.closest("[data-path]"); + if (fieldElement) { + return fieldElement.getAttribute("data-path"); + } + + // Fallback: try to find by text content + const textContent = node.textContent?.trim(); + if (textContent) { + return findPathByValue(data, textContent); + } + + return null; +}; + +// Helper function to find path by value +const findPathByValue = (obj: Record, value: string, currentPath = ""): string | null => { + for (const key in obj) { + const newPath = currentPath ? `${currentPath}.${key}` : key; + + if (obj[key] === value) { + return newPath; + } + + if (typeof obj[key] === "object" && obj[key] !== null) { + const result = findPathByValue(obj[key] as Record, value, newPath); + if (result) return result; + } + } + return null; +}; + +// Helper function to update value at a specific path +const updateValueAtPath = (obj: Record, path: string, newValue: unknown): Record => { + const pathParts = path.split("."); + const newObj = { ...obj }; + let current: Record = newObj; + + // Navigate to the parent of the target + for (let i = 0; i < pathParts.length - 1; i++) { + if (current[pathParts[i]] === undefined) { + current[pathParts[i]] = {}; + } + current = current[pathParts[i]] as Record; + } + + // Update the value at the target path + const lastPart = pathParts[pathParts.length - 1]; + current[lastPart] = newValue; + + return newObj; +}; + const ObjectTree: React.FC = () => { const editorRef = useRef(null); const jsonEditorRef = useRef(null); @@ -236,65 +291,6 @@ const ObjectTree: React.FC = () => { } }; - // Helper function to find the path to a node in the JSON structure - const findNodePath = (node: Element, data: Record): string | null => { - // Look for data attributes or other identifiers - const fieldElement = node.closest("[data-path]"); - if (fieldElement) { - return fieldElement.getAttribute("data-path"); - } - - // Fallback: try to find by text content - const textContent = node.textContent?.trim(); - if (textContent) { - return findPathByValue(data, textContent); - } - - return null; - }; - - // Helper function to find path by value - const findPathByValue = (obj: Record, value: string, currentPath = ""): string | null => { - for (const key in obj) { - const newPath = currentPath ? `${currentPath}.${key}` : key; - - if (obj[key] === value) { - return newPath; - } - - if (typeof obj[key] === "object" && obj[key] !== null) { - const result = findPathByValue(obj[key] as Record, value, newPath); - if (result) return result; - } - } - return null; - }; - - // Helper function to update value at a specific path - const updateValueAtPath = ( - obj: Record, - path: string, - newValue: unknown - ): Record => { - const pathParts = path.split("."); - const newObj = { ...obj }; - let current: Record = newObj; - - // Navigate to the parent of the target - for (let i = 0; i < pathParts.length - 1; i++) { - if (current[pathParts[i]] === undefined) { - current[pathParts[i]] = {}; - } - current = current[pathParts[i]] as Record; - } - - // Update the value at the target path - const lastPart = pathParts[pathParts.length - 1]; - current[lastPart] = newValue; - - return newObj; - }; - return (
Date: Fri, 15 Aug 2025 09:05:04 +0300 Subject: [PATCH 16/90] fix(665): Clean up --- .../components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx index cd2ae15cf..d24d4ae22 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx @@ -239,7 +239,7 @@ const ObjectTree: React.FC = () => { } }; - const handleDragLeave = (e: React.DragEvent) => { + const handleDragLeave = () => { // Remove highlight when leaving the drop zone if (hoveredElement) { hoveredElement.classList.remove(styles.dragHoverHighlight); From e164cd0c9f6a65842e7b39c1aee28bfade1bff81 Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Fri, 15 Aug 2025 09:05:56 +0300 Subject: [PATCH 17/90] fix(665): Clean up --- .../components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx index d24d4ae22..de9eb02d7 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx @@ -67,7 +67,7 @@ const ObjectTree: React.FC = () => { const [hoveredElement, setHoveredElement] = useState(null); const [data, setData] = useState({ name: "John Doe", - age: "30", + age: 30, email: "john@example.com", address: { street: "123 Main St", From 9db3a26acecfa70f7172642fdb5d8bf888a92255 Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Fri, 15 Aug 2025 09:13:03 +0300 Subject: [PATCH 18/90] fix(665): PoC fix drop --- .../AssignBuilder/ObjectTree.tsx | 99 ++++++++++++++++--- 1 file changed, 85 insertions(+), 14 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx index de9eb02d7..e8cadf59d 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx @@ -8,18 +8,35 @@ import styles from "./ObjectTree.module.scss"; // Helper function to find the path to a node in the JSON structure const findNodePath = (node: Element, data: Record): string | null => { - // Look for data attributes or other identifiers - const fieldElement = node.closest("[data-path]"); - if (fieldElement) { - return fieldElement.getAttribute("data-path"); - } + console.log("Finding path for node:", node.className, node.textContent); - // Fallback: try to find by text content + // Try to find by text content (for values) const textContent = node.textContent?.trim(); if (textContent) { - return findPathByValue(data, textContent); + console.log("Trying to find path by text content:", textContent); + const path = findPathByValue(data, textContent); + console.log("Found path by text content:", path); + return path; } + console.log("No path found for node"); + return null; +}; + +// Helper function to find path by field name +const findPathByFieldName = (obj: Record, fieldName: string, currentPath = ""): string | null => { + for (const key in obj) { + const newPath = currentPath ? `${currentPath}.${key}` : key; + + if (key === fieldName) { + return newPath; + } + + if (typeof obj[key] === "object" && obj[key] !== null && !Array.isArray(obj[key])) { + const result = findPathByFieldName(obj[key] as Record, fieldName, newPath); + if (result) return result; + } + } return null; }; @@ -27,13 +44,34 @@ const findNodePath = (node: Element, data: Record): string | nu const findPathByValue = (obj: Record, value: string, currentPath = ""): string | null => { for (const key in obj) { const newPath = currentPath ? `${currentPath}.${key}` : key; + const objValue = obj[key]; + + // Handle different value types + if (objValue === value) { + return newPath; + } + + // Handle number comparison + if (typeof objValue === "number" && !isNaN(Number(value)) && objValue === Number(value)) { + return newPath; + } + + // Handle boolean comparison + if (typeof objValue === "boolean") { + const boolValue = value.toLowerCase() === "true" ? true : value.toLowerCase() === "false" ? false : null; + if (objValue === boolValue) { + return newPath; + } + } - if (obj[key] === value) { + // Handle null comparison + if (objValue === null && value.toLowerCase() === "null") { return newPath; } - if (typeof obj[key] === "object" && obj[key] !== null) { - const result = findPathByValue(obj[key] as Record, value, newPath); + // Recursively search in objects + if (typeof objValue === "object" && objValue !== null && !Array.isArray(objValue)) { + const result = findPathByValue(objValue as Record, value, newPath); if (result) return result; } } @@ -56,7 +94,32 @@ const updateValueAtPath = (obj: Record, path: string, newValue: // Update the value at the target path const lastPart = pathParts[pathParts.length - 1]; - current[lastPart] = newValue; + + // Try to preserve the original type if possible + const originalValue = current[lastPart]; + if (originalValue !== undefined) { + // Convert newValue to match the original type + if (typeof originalValue === "number" && typeof newValue === "string") { + const numValue = Number(newValue); + if (!isNaN(numValue)) { + current[lastPart] = numValue; + } else { + current[lastPart] = newValue; // Keep as string if conversion fails + } + } else if (typeof originalValue === "boolean" && typeof newValue === "string") { + if (newValue.toLowerCase() === "true") { + current[lastPart] = true; + } else if (newValue.toLowerCase() === "false") { + current[lastPart] = false; + } else { + current[lastPart] = newValue; // Keep as string if conversion fails + } + } else { + current[lastPart] = newValue; + } + } else { + current[lastPart] = newValue; + } return newObj; }; @@ -221,9 +284,9 @@ const ObjectTree: React.FC = () => { // Get the element under the cursor const element = document.elementFromPoint(e.clientX, e.clientY); if (element) { - // Find the closest JSON editor node + // Find the closest JSON editor node - improved selectors for all data types const jsonNode = element.closest( - ".jsoneditor-value, .jsoneditor-field, .jsoneditor-string, .jsoneditor-number, .jsoneditor-boolean" + ".jsoneditor-value, .jsoneditor-field, .jsoneditor-string, .jsoneditor-number, .jsoneditor-boolean, .jsoneditor-null, .jsoneditor-object, .jsoneditor-array" ); // Remove highlight from previously hovered element @@ -267,22 +330,30 @@ const ObjectTree: React.FC = () => { if (element) { // Find the closest JSON editor node const jsonNode = element.closest( - ".jsoneditor-value, .jsoneditor-field, .jsoneditor-string, .jsoneditor-number, .jsoneditor-boolean" + ".jsoneditor-value, .jsoneditor-field, .jsoneditor-string, .jsoneditor-number, .jsoneditor-boolean, .jsoneditor-null, .jsoneditor-object, .jsoneditor-array" ); if (jsonNode) { + console.log("Found JSON node:", jsonNode.className, jsonNode.textContent); + // Get the current JSON data const currentData = jsonEditorRef.current.get(); // Try to find the path to the dropped node const path = findNodePath(jsonNode, currentData); + console.log("Found path:", path, "Value to replace:", valueToReplace); if (path) { // Update the value at the specific path const newData = updateValueAtPath(currentData as Record, path, valueToReplace); jsonEditorRef.current.set(newData); setData(newData as typeof data); + console.log("Successfully updated data at path:", path); + } else { + console.log("Could not find path for node:", jsonNode); } + } else { + console.log("No JSON node found at drop position"); } } } From 43dd6d219f007c4a6300b70d542866089446f499 Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Fri, 15 Aug 2025 09:13:37 +0300 Subject: [PATCH 19/90] fix(665): To do --- .../components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx index e8cadf59d..3248c89dd 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx @@ -6,6 +6,7 @@ import "jsoneditor/dist/jsoneditor.css"; import { getDragData } from "utils/component-util"; import styles from "./ObjectTree.module.scss"; +// todo remove logs // Helper function to find the path to a node in the JSON structure const findNodePath = (node: Element, data: Record): string | null => { console.log("Finding path for node:", node.className, node.textContent); From dfe627c7eb3cee27b0666ddfecbdf7204d89a41a Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Fri, 15 Aug 2025 09:14:13 +0300 Subject: [PATCH 20/90] fix(665): Clean up --- .../AssignBuilder/ObjectTree.tsx | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx index 3248c89dd..82d0f8341 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx @@ -24,23 +24,6 @@ const findNodePath = (node: Element, data: Record): string | nu return null; }; -// Helper function to find path by field name -const findPathByFieldName = (obj: Record, fieldName: string, currentPath = ""): string | null => { - for (const key in obj) { - const newPath = currentPath ? `${currentPath}.${key}` : key; - - if (key === fieldName) { - return newPath; - } - - if (typeof obj[key] === "object" && obj[key] !== null && !Array.isArray(obj[key])) { - const result = findPathByFieldName(obj[key] as Record, fieldName, newPath); - if (result) return result; - } - } - return null; -}; - // Helper function to find path by value const findPathByValue = (obj: Record, value: string, currentPath = ""): string | null => { for (const key in obj) { From 5fd7a50097c907415972afdf77391020824b12a1 Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Fri, 15 Aug 2025 09:27:25 +0300 Subject: [PATCH 21/90] fix(665): Clean up --- .../AssignBuilder/ObjectTree.tsx | 29 ++++++------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx index 82d0f8341..9a385f7bc 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx @@ -30,26 +30,15 @@ const findPathByValue = (obj: Record, value: string, currentPat const newPath = currentPath ? `${currentPath}.${key}` : key; const objValue = obj[key]; - // Handle different value types - if (objValue === value) { - return newPath; - } - - // Handle number comparison - if (typeof objValue === "number" && !isNaN(Number(value)) && objValue === Number(value)) { - return newPath; - } - - // Handle boolean comparison - if (typeof objValue === "boolean") { - const boolValue = value.toLowerCase() === "true" ? true : value.toLowerCase() === "false" ? false : null; - if (objValue === boolValue) { - return newPath; - } - } - - // Handle null comparison - if (objValue === null && value.toLowerCase() === "null") { + // Check if values match (handling different types) + const isMatch = + objValue === value || + (typeof objValue === "number" && !isNaN(Number(value)) && objValue === Number(value)) || + (typeof objValue === "boolean" && + objValue === (value.toLowerCase() === "true" ? true : value.toLowerCase() === "false" ? false : null)) || + (objValue === null && value.toLowerCase() === "null"); + + if (isMatch) { return newPath; } From bed284ffd5abdf420a5c8d8cb3174e3c1b100ab8 Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Fri, 15 Aug 2025 09:30:35 +0300 Subject: [PATCH 22/90] fix(665): Clean up --- .../FlowElementsPopup/AssignBuilder/ObjectTree.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx index 9a385f7bc..c8a265eb1 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx @@ -9,18 +9,13 @@ import styles from "./ObjectTree.module.scss"; // todo remove logs // Helper function to find the path to a node in the JSON structure const findNodePath = (node: Element, data: Record): string | null => { - console.log("Finding path for node:", node.className, node.textContent); - // Try to find by text content (for values) const textContent = node.textContent?.trim(); if (textContent) { - console.log("Trying to find path by text content:", textContent); const path = findPathByValue(data, textContent); - console.log("Found path by text content:", path); return path; } - console.log("No path found for node"); return null; }; From 2799780fe809f0fb1f109481b985ffd63cbe1879 Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Fri, 15 Aug 2025 09:35:14 +0300 Subject: [PATCH 23/90] fix(665): PoC array drop --- .../AssignBuilder/ObjectTree.tsx | 170 ++++++++++++++---- 1 file changed, 137 insertions(+), 33 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx index c8a265eb1..5f27ad80b 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx @@ -20,7 +20,35 @@ const findNodePath = (node: Element, data: Record): string | nu }; // Helper function to find path by value -const findPathByValue = (obj: Record, value: string, currentPath = ""): string | null => { +const findPathByValue = (obj: Record | unknown[], value: string, currentPath = ""): string | null => { + // Handle arrays + if (Array.isArray(obj)) { + for (let i = 0; i < obj.length; i++) { + const newPath = currentPath ? `${currentPath}[${i}]` : `[${i}]`; + const objValue = obj[i]; + + // Check if values match (handling different types) + const isMatch = + objValue === value || + (typeof objValue === "number" && !isNaN(Number(value)) && objValue === Number(value)) || + (typeof objValue === "boolean" && + objValue === (value.toLowerCase() === "true" ? true : value.toLowerCase() === "false" ? false : null)) || + (objValue === null && value.toLowerCase() === "null"); + + if (isMatch) { + return newPath; + } + + // Recursively search in nested objects/arrays + if (typeof objValue === "object" && objValue !== null) { + const result = findPathByValue(objValue as Record | unknown[], value, newPath); + if (result) return result; + } + } + return null; + } + + // Handle objects for (const key in obj) { const newPath = currentPath ? `${currentPath}.${key}` : key; const objValue = obj[key]; @@ -37,9 +65,9 @@ const findPathByValue = (obj: Record, value: string, currentPat return newPath; } - // Recursively search in objects - if (typeof objValue === "object" && objValue !== null && !Array.isArray(objValue)) { - const result = findPathByValue(objValue as Record, value, newPath); + // Recursively search in nested objects/arrays + if (typeof objValue === "object" && objValue !== null) { + const result = findPathByValue(objValue as Record | unknown[], value, newPath); if (result) return result; } } @@ -47,51 +75,124 @@ const findPathByValue = (obj: Record, value: string, currentPat }; // Helper function to update value at a specific path -const updateValueAtPath = (obj: Record, path: string, newValue: unknown): Record => { - const pathParts = path.split("."); - const newObj = { ...obj }; - let current: Record = newObj; +const updateValueAtPath = ( + obj: Record | unknown[], + path: string, + newValue: unknown +): Record | unknown[] => { + // Parse path to handle both dot notation and array indices + const pathParts: (string | number)[] = []; + let currentPath = path; + + // Extract array indices and object keys + while (currentPath.length > 0) { + // First, check for array index at the beginning + const arrayMatch = currentPath.match(/^\[(\d+)\]/); + if (arrayMatch) { + pathParts.push(parseInt(arrayMatch[1])); + currentPath = currentPath.substring(arrayMatch[0].length); + continue; + } - // Navigate to the parent of the target - for (let i = 0; i < pathParts.length - 1; i++) { - if (current[pathParts[i]] === undefined) { - current[pathParts[i]] = {}; + // Then check for property name followed by array index + const propertyArrayMatch = currentPath.match(/^([^.\[\]]+)\[(\d+)\]/); + if (propertyArrayMatch) { + pathParts.push(propertyArrayMatch[1]); // property name + pathParts.push(parseInt(propertyArrayMatch[2])); // array index + currentPath = currentPath.substring(propertyArrayMatch[0].length); + continue; + } + + // Check for dot notation + const dotIndex = currentPath.indexOf("."); + if (dotIndex === -1) { + pathParts.push(currentPath); + break; + } else { + pathParts.push(currentPath.substring(0, dotIndex)); + currentPath = currentPath.substring(dotIndex + 1); } - current = current[pathParts[i]] as Record; } - // Update the value at the target path - const lastPart = pathParts[pathParts.length - 1]; + const newObj = Array.isArray(obj) ? [...obj] : { ...obj }; + let current: Record | unknown[] = newObj; - // Try to preserve the original type if possible - const originalValue = current[lastPart]; - if (originalValue !== undefined) { - // Convert newValue to match the original type - if (typeof originalValue === "number" && typeof newValue === "string") { - const numValue = Number(newValue); - if (!isNaN(numValue)) { - current[lastPart] = numValue; + // Navigate to the parent of the target + for (let i = 0; i < pathParts.length - 1; i++) { + const part = pathParts[i]; + if (typeof part === "number") { + // Array index + if (Array.isArray(current)) { + if (current[part] === undefined) { + current[part] = {}; + } + current = current[part] as Record | unknown[]; } else { - current[lastPart] = newValue; // Keep as string if conversion fails + // Convert object to array if needed + (current as Record)[part.toString()] = {}; + current = (current as Record)[part.toString()] as Record | unknown[]; } - } else if (typeof originalValue === "boolean" && typeof newValue === "string") { - if (newValue.toLowerCase() === "true") { - current[lastPart] = true; - } else if (newValue.toLowerCase() === "false") { - current[lastPart] = false; + } else { + // Object key + if (!Array.isArray(current)) { + if (current[part] === undefined) { + current[part] = {}; + } + current = current[part] as Record | unknown[]; } else { - current[lastPart] = newValue; // Keep as string if conversion fails + // This shouldn't happen with proper path parsing, but handle it gracefully + break; } - } else { - current[lastPart] = newValue; + } + } + + // Update the value at the target path + const lastPart = pathParts[pathParts.length - 1]; + + if (typeof lastPart === "number") { + // Array index + if (Array.isArray(current)) { + const originalValue = current[lastPart]; + current[lastPart] = convertValueToMatchType(originalValue, newValue); } } else { - current[lastPart] = newValue; + // Object key + if (!Array.isArray(current)) { + const originalValue = current[lastPart]; + current[lastPart] = convertValueToMatchType(originalValue, newValue); + } } return newObj; }; +// Helper function to convert value to match the original type +const convertValueToMatchType = (originalValue: unknown, newValue: unknown): unknown => { + if (originalValue === undefined) { + return newValue; + } + + // Convert newValue to match the original type + if (typeof originalValue === "number" && typeof newValue === "string") { + const numValue = Number(newValue); + if (!isNaN(numValue)) { + return numValue; + } else { + return newValue; // Keep as string if conversion fails + } + } else if (typeof originalValue === "boolean" && typeof newValue === "string") { + if (newValue.toLowerCase() === "true") { + return true; + } else if (newValue.toLowerCase() === "false") { + return false; + } else { + return newValue; // Keep as string if conversion fails + } + } else { + return newValue; + } +}; + const ObjectTree: React.FC = () => { const editorRef = useRef(null); const jsonEditorRef = useRef(null); @@ -109,11 +210,14 @@ const ObjectTree: React.FC = () => { theme: "dark", notifications: true, }, + hobbies: ["reading", "gaming", "coding"], + scores: [85, 92, 78], }); useEffect(() => { if (editorRef.current && !jsonEditorRef.current) { const editor = new JSONEditor(editorRef.current, { + modes: ["tree", "code"], language: "et", languages: { et: { From 63309b16d77d7538215afae73117d0a1431b075e Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Fri, 15 Aug 2025 09:42:57 +0300 Subject: [PATCH 24/90] fix(665): Clean up --- .../AssignBuilder/ObjectTree.tsx | 183 ++++++++++-------- 1 file changed, 99 insertions(+), 84 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx index 5f27ad80b..affa2493c 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx @@ -6,7 +6,6 @@ import "jsoneditor/dist/jsoneditor.css"; import { getDragData } from "utils/component-util"; import styles from "./ObjectTree.module.scss"; -// todo remove logs // Helper function to find the path to a node in the JSON structure const findNodePath = (node: Element, data: Record): string | null => { // Try to find by text content (for values) @@ -19,72 +18,70 @@ const findNodePath = (node: Element, data: Record): string | nu return null; }; -// Helper function to find path by value -const findPathByValue = (obj: Record | unknown[], value: string, currentPath = ""): string | null => { - // Handle arrays - if (Array.isArray(obj)) { - for (let i = 0; i < obj.length; i++) { - const newPath = currentPath ? `${currentPath}[${i}]` : `[${i}]`; - const objValue = obj[i]; +// Helper function to check if values match (handling different types) +const isValueMatch = (objValue: unknown, value: string): boolean => { + return ( + objValue === value || + (typeof objValue === "number" && !isNaN(Number(value)) && objValue === Number(value)) || + (typeof objValue === "boolean" && + objValue === (value.toLowerCase() === "true" ? true : value.toLowerCase() === "false" ? false : null)) || + (objValue === null && value.toLowerCase() === "null") + ); +}; - // Check if values match (handling different types) - const isMatch = - objValue === value || - (typeof objValue === "number" && !isNaN(Number(value)) && objValue === Number(value)) || - (typeof objValue === "boolean" && - objValue === (value.toLowerCase() === "true" ? true : value.toLowerCase() === "false" ? false : null)) || - (objValue === null && value.toLowerCase() === "null"); +// Helper function to search for value in a collection +const searchInCollection = ( + collection: Record | unknown[], + value: string, + currentPath: string, + isArray: boolean +): string | null => { + if (isArray) { + // Handle arrays + for (let i = 0; i < (collection as unknown[]).length; i++) { + const newPath = currentPath ? `${currentPath}[${i}]` : `[${i}]`; + const objValue = (collection as unknown[])[i]; - if (isMatch) { + if (isValueMatch(objValue, value)) { return newPath; } - // Recursively search in nested objects/arrays + // Recursively search in nested arrays if (typeof objValue === "object" && objValue !== null) { const result = findPathByValue(objValue as Record | unknown[], value, newPath); if (result) return result; } } - return null; - } + } else { + // Handle objects + for (const key in collection as Record) { + const newPath = currentPath ? `${currentPath}.${key}` : key; + const objValue = (collection as Record)[key]; - // Handle objects - for (const key in obj) { - const newPath = currentPath ? `${currentPath}.${key}` : key; - const objValue = obj[key]; - - // Check if values match (handling different types) - const isMatch = - objValue === value || - (typeof objValue === "number" && !isNaN(Number(value)) && objValue === Number(value)) || - (typeof objValue === "boolean" && - objValue === (value.toLowerCase() === "true" ? true : value.toLowerCase() === "false" ? false : null)) || - (objValue === null && value.toLowerCase() === "null"); - - if (isMatch) { - return newPath; - } + if (isValueMatch(objValue, value)) { + return newPath; + } - // Recursively search in nested objects/arrays - if (typeof objValue === "object" && objValue !== null) { - const result = findPathByValue(objValue as Record | unknown[], value, newPath); - if (result) return result; + // Recursively search in nested objects + if (typeof objValue === "object" && objValue !== null) { + const result = findPathByValue(objValue as Record | unknown[], value, newPath); + if (result) return result; + } } } return null; }; -// Helper function to update value at a specific path -const updateValueAtPath = ( - obj: Record | unknown[], - path: string, - newValue: unknown -): Record | unknown[] => { - // Parse path to handle both dot notation and array indices +// Helper function to find path by value +const findPathByValue = (obj: Record | unknown[], value: string, currentPath = ""): string | null => { + return searchInCollection(obj, value, currentPath, Array.isArray(obj)); +}; + +// Helper function to parse path into parts +const parsePath = (path: string): (string | number)[] => { const pathParts: (string | number)[] = []; let currentPath = path; - // Extract array indices and object keys while (currentPath.length > 0) { // First, check for array index at the beginning const arrayMatch = currentPath.match(/^\[(\d+)\]/); @@ -114,52 +111,70 @@ const updateValueAtPath = ( } } - const newObj = Array.isArray(obj) ? [...obj] : { ...obj }; - let current: Record | unknown[] = newObj; + return pathParts; +}; - // Navigate to the parent of the target - for (let i = 0; i < pathParts.length - 1; i++) { - const part = pathParts[i]; - if (typeof part === "number") { - // Array index - if (Array.isArray(current)) { - if (current[part] === undefined) { - current[part] = {}; - } - current = current[part] as Record | unknown[]; - } else { - // Convert object to array if needed - (current as Record)[part.toString()] = {}; - current = (current as Record)[part.toString()] as Record | unknown[]; +// Helper function to navigate to a specific path part +const navigateToPathPart = ( + current: Record | unknown[], + part: string | number +): Record | unknown[] => { + if (typeof part === "number") { + // Array index + if (Array.isArray(current)) { + if (current[part] === undefined) { + current[part] = {}; } + return current[part] as Record | unknown[]; } else { - // Object key - if (!Array.isArray(current)) { - if (current[part] === undefined) { - current[part] = {}; - } - current = current[part] as Record | unknown[]; - } else { - // This shouldn't happen with proper path parsing, but handle it gracefully - break; + // Convert object to array if needed + (current as Record)[part.toString()] = {}; + return (current as Record)[part.toString()] as Record | unknown[]; + } + } else { + // Object key + if (!Array.isArray(current)) { + if (current[part] === undefined) { + current[part] = {}; } + return current[part] as Record | unknown[]; + } else { + // This shouldn't happen with proper path parsing, but handle it gracefully + return current; } } +}; + +// Helper function to update value at a specific path +const updateValueAtPath = ( + obj: Record | unknown[], + path: string, + newValue: unknown +): Record | unknown[] => { + const pathParts = parsePath(path); + const newObj = Array.isArray(obj) ? [...obj] : { ...obj }; + let current: Record | unknown[] = newObj; + + // Navigate to the parent of the target + for (let i = 0; i < pathParts.length - 1; i++) { + current = navigateToPathPart(current, pathParts[i]); + } // Update the value at the target path const lastPart = pathParts[pathParts.length - 1]; - - if (typeof lastPart === "number") { - // Array index - if (Array.isArray(current)) { - const originalValue = current[lastPart]; - current[lastPart] = convertValueToMatchType(originalValue, newValue); - } - } else { - // Object key - if (!Array.isArray(current)) { - const originalValue = current[lastPart]; - current[lastPart] = convertValueToMatchType(originalValue, newValue); + const originalValue = + Array.isArray(current) && typeof lastPart === "number" + ? current[lastPart] + : !Array.isArray(current) && typeof lastPart === "string" + ? current[lastPart] + : undefined; + + if (originalValue !== undefined) { + const convertedValue = convertValueToMatchType(originalValue, newValue); + if (Array.isArray(current) && typeof lastPart === "number") { + current[lastPart] = convertedValue; + } else if (!Array.isArray(current) && typeof lastPart === "string") { + current[lastPart] = convertedValue; } } From 61f0130f26a3e3d1a53ae54e5f0fcfb8caee7b58 Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Fri, 15 Aug 2025 09:48:39 +0300 Subject: [PATCH 25/90] fix(665): Simplify --- .../AssignBuilder/ObjectTree.tsx | 32 +++---------------- 1 file changed, 4 insertions(+), 28 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx index affa2493c..e68203fb0 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx @@ -6,6 +6,8 @@ import "jsoneditor/dist/jsoneditor.css"; import { getDragData } from "utils/component-util"; import styles from "./ObjectTree.module.scss"; +// todo bug: open tree closes on drop + // Helper function to find the path to a node in the JSON structure const findNodePath = (node: Element, data: Record): string | null => { // Try to find by text content (for values) @@ -72,6 +74,7 @@ const searchInCollection = ( return null; }; +// todo inline // Helper function to find path by value const findPathByValue = (obj: Record | unknown[], value: string, currentPath = ""): string | null => { return searchInCollection(obj, value, currentPath, Array.isArray(obj)); @@ -170,7 +173,7 @@ const updateValueAtPath = ( : undefined; if (originalValue !== undefined) { - const convertedValue = convertValueToMatchType(originalValue, newValue); + const convertedValue = newValue; if (Array.isArray(current) && typeof lastPart === "number") { current[lastPart] = convertedValue; } else if (!Array.isArray(current) && typeof lastPart === "string") { @@ -181,33 +184,6 @@ const updateValueAtPath = ( return newObj; }; -// Helper function to convert value to match the original type -const convertValueToMatchType = (originalValue: unknown, newValue: unknown): unknown => { - if (originalValue === undefined) { - return newValue; - } - - // Convert newValue to match the original type - if (typeof originalValue === "number" && typeof newValue === "string") { - const numValue = Number(newValue); - if (!isNaN(numValue)) { - return numValue; - } else { - return newValue; // Keep as string if conversion fails - } - } else if (typeof originalValue === "boolean" && typeof newValue === "string") { - if (newValue.toLowerCase() === "true") { - return true; - } else if (newValue.toLowerCase() === "false") { - return false; - } else { - return newValue; // Keep as string if conversion fails - } - } else { - return newValue; - } -}; - const ObjectTree: React.FC = () => { const editorRef = useRef(null); const jsonEditorRef = useRef(null); From 2069e1c0676ff5b153ee0ecd5ea0053e09b4b8ee Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Fri, 15 Aug 2025 09:48:59 +0300 Subject: [PATCH 26/90] fix(665): Simplify --- .../FlowElementsPopup/AssignBuilder/ObjectTree.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx index e68203fb0..e0b5a2572 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx @@ -173,11 +173,10 @@ const updateValueAtPath = ( : undefined; if (originalValue !== undefined) { - const convertedValue = newValue; if (Array.isArray(current) && typeof lastPart === "number") { - current[lastPart] = convertedValue; + current[lastPart] = newValue; } else if (!Array.isArray(current) && typeof lastPart === "string") { - current[lastPart] = convertedValue; + current[lastPart] = newValue; } } From 26aaece6d65d3516b42237b6b8b71af519d1e48f Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Fri, 15 Aug 2025 10:09:52 +0300 Subject: [PATCH 27/90] fix(665): Simplify --- .../AssignBuilder/ObjectTree.tsx | 74 +++++++------------ 1 file changed, 28 insertions(+), 46 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx index e0b5a2572..b041c1718 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx @@ -117,37 +117,6 @@ const parsePath = (path: string): (string | number)[] => { return pathParts; }; -// Helper function to navigate to a specific path part -const navigateToPathPart = ( - current: Record | unknown[], - part: string | number -): Record | unknown[] => { - if (typeof part === "number") { - // Array index - if (Array.isArray(current)) { - if (current[part] === undefined) { - current[part] = {}; - } - return current[part] as Record | unknown[]; - } else { - // Convert object to array if needed - (current as Record)[part.toString()] = {}; - return (current as Record)[part.toString()] as Record | unknown[]; - } - } else { - // Object key - if (!Array.isArray(current)) { - if (current[part] === undefined) { - current[part] = {}; - } - return current[part] as Record | unknown[]; - } else { - // This shouldn't happen with proper path parsing, but handle it gracefully - return current; - } - } -}; - // Helper function to update value at a specific path const updateValueAtPath = ( obj: Record | unknown[], @@ -160,25 +129,38 @@ const updateValueAtPath = ( // Navigate to the parent of the target for (let i = 0; i < pathParts.length - 1; i++) { - current = navigateToPathPart(current, pathParts[i]); + const part = pathParts[i]; + + // Navigate to the next part of the path + if (typeof part === "number") { + // Array index + if (Array.isArray(current)) { + if (current[part] === undefined) { + current[part] = {}; + } + current = current[part] as Record | unknown[]; + } else { + // Convert object to array if needed + (current as Record)[part.toString()] = {}; + current = (current as Record)[part.toString()] as Record | unknown[]; + } + } else { + // Object key + if (!Array.isArray(current)) { + if (current[part] === undefined) { + current[part] = {}; + } + current = current[part] as Record | unknown[]; + } else { + // This shouldn't happen with proper path parsing, but handle it gracefully + break; + } + } } // Update the value at the target path const lastPart = pathParts[pathParts.length - 1]; - const originalValue = - Array.isArray(current) && typeof lastPart === "number" - ? current[lastPart] - : !Array.isArray(current) && typeof lastPart === "string" - ? current[lastPart] - : undefined; - - if (originalValue !== undefined) { - if (Array.isArray(current) && typeof lastPart === "number") { - current[lastPart] = newValue; - } else if (!Array.isArray(current) && typeof lastPart === "string") { - current[lastPart] = newValue; - } - } + (current as any)[lastPart] = newValue; return newObj; }; From 2980af1f0085415c3e6f570c40c58436a2b65266 Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Fri, 15 Aug 2025 10:12:25 +0300 Subject: [PATCH 28/90] fix(665): Simplify --- .../AssignBuilder/ObjectTree.tsx | 34 ++++--------------- 1 file changed, 6 insertions(+), 28 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx index b041c1718..b8003c66a 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx @@ -74,7 +74,7 @@ const searchInCollection = ( return null; }; -// todo inline +// todo inline? // Helper function to find path by value const findPathByValue = (obj: Record | unknown[], value: string, currentPath = ""): string | null => { return searchInCollection(obj, value, currentPath, Array.isArray(obj)); @@ -125,42 +125,20 @@ const updateValueAtPath = ( ): Record | unknown[] => { const pathParts = parsePath(path); const newObj = Array.isArray(obj) ? [...obj] : { ...obj }; - let current: Record | unknown[] = newObj; + let current: any = newObj; // Navigate to the parent of the target for (let i = 0; i < pathParts.length - 1; i++) { const part = pathParts[i]; - - // Navigate to the next part of the path - if (typeof part === "number") { - // Array index - if (Array.isArray(current)) { - if (current[part] === undefined) { - current[part] = {}; - } - current = current[part] as Record | unknown[]; - } else { - // Convert object to array if needed - (current as Record)[part.toString()] = {}; - current = (current as Record)[part.toString()] as Record | unknown[]; - } - } else { - // Object key - if (!Array.isArray(current)) { - if (current[part] === undefined) { - current[part] = {}; - } - current = current[part] as Record | unknown[]; - } else { - // This shouldn't happen with proper path parsing, but handle it gracefully - break; - } + if (current[part] === undefined) { + current[part] = {}; } + current = current[part]; } // Update the value at the target path const lastPart = pathParts[pathParts.length - 1]; - (current as any)[lastPart] = newValue; + current[lastPart] = newValue; return newObj; }; From 91dcaeb4adc09dcec4fabf85988ca670c4ab340a Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Fri, 15 Aug 2025 10:16:13 +0300 Subject: [PATCH 29/90] fix(665): Clean up --- .../AssignBuilder/ObjectTree.tsx | 64 +------------------ GUI/src/utils/object-util.ts | 63 ++++++++++++++++++ 2 files changed, 64 insertions(+), 63 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx index b8003c66a..50e5f3d1f 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx @@ -4,6 +4,7 @@ import React, { useEffect, useRef, useState } from "react"; import JSONEditor from "jsoneditor"; import "jsoneditor/dist/jsoneditor.css"; import { getDragData } from "utils/component-util"; +import { updateValueAtPath } from "utils/object-util"; import styles from "./ObjectTree.module.scss"; // todo bug: open tree closes on drop @@ -80,69 +81,6 @@ const findPathByValue = (obj: Record | unknown[], value: string return searchInCollection(obj, value, currentPath, Array.isArray(obj)); }; -// Helper function to parse path into parts -const parsePath = (path: string): (string | number)[] => { - const pathParts: (string | number)[] = []; - let currentPath = path; - - while (currentPath.length > 0) { - // First, check for array index at the beginning - const arrayMatch = currentPath.match(/^\[(\d+)\]/); - if (arrayMatch) { - pathParts.push(parseInt(arrayMatch[1])); - currentPath = currentPath.substring(arrayMatch[0].length); - continue; - } - - // Then check for property name followed by array index - const propertyArrayMatch = currentPath.match(/^([^.\[\]]+)\[(\d+)\]/); - if (propertyArrayMatch) { - pathParts.push(propertyArrayMatch[1]); // property name - pathParts.push(parseInt(propertyArrayMatch[2])); // array index - currentPath = currentPath.substring(propertyArrayMatch[0].length); - continue; - } - - // Check for dot notation - const dotIndex = currentPath.indexOf("."); - if (dotIndex === -1) { - pathParts.push(currentPath); - break; - } else { - pathParts.push(currentPath.substring(0, dotIndex)); - currentPath = currentPath.substring(dotIndex + 1); - } - } - - return pathParts; -}; - -// Helper function to update value at a specific path -const updateValueAtPath = ( - obj: Record | unknown[], - path: string, - newValue: unknown -): Record | unknown[] => { - const pathParts = parsePath(path); - const newObj = Array.isArray(obj) ? [...obj] : { ...obj }; - let current: any = newObj; - - // Navigate to the parent of the target - for (let i = 0; i < pathParts.length - 1; i++) { - const part = pathParts[i]; - if (current[part] === undefined) { - current[part] = {}; - } - current = current[part]; - } - - // Update the value at the target path - const lastPart = pathParts[pathParts.length - 1]; - current[lastPart] = newValue; - - return newObj; -}; - const ObjectTree: React.FC = () => { const editorRef = useRef(null); const jsonEditorRef = useRef(null); diff --git a/GUI/src/utils/object-util.ts b/GUI/src/utils/object-util.ts index 0ad281ee0..449a0aca7 100644 --- a/GUI/src/utils/object-util.ts +++ b/GUI/src/utils/object-util.ts @@ -92,3 +92,66 @@ export const getTypeColor = ( export const getKeyPathString = (keyPath: KeyPath) => { return keyPath.toReversed().join('"]["'); }; + +// Helper function to parse object path into parts +const parsePath = (path: string): (string | number)[] => { + const pathParts: (string | number)[] = []; + let currentPath = path; + + while (currentPath.length > 0) { + // First, check for array index at the beginning + const arrayMatch = currentPath.match(/^\[(\d+)\]/); + if (arrayMatch) { + pathParts.push(parseInt(arrayMatch[1])); + currentPath = currentPath.substring(arrayMatch[0].length); + continue; + } + + // Then check for property name followed by array index + const propertyArrayMatch = currentPath.match(/^([^.\[\]]+)\[(\d+)\]/); + if (propertyArrayMatch) { + pathParts.push(propertyArrayMatch[1]); // property name + pathParts.push(parseInt(propertyArrayMatch[2])); // array index + currentPath = currentPath.substring(propertyArrayMatch[0].length); + continue; + } + + // Check for dot notation + const dotIndex = currentPath.indexOf("."); + if (dotIndex === -1) { + pathParts.push(currentPath); + break; + } else { + pathParts.push(currentPath.substring(0, dotIndex)); + currentPath = currentPath.substring(dotIndex + 1); + } + } + + return pathParts; +}; + +// Helper function to update value at a specific object path +export const updateValueAtPath = ( + obj: Record | unknown[], + path: string, + newValue: unknown +): Record | unknown[] => { + const pathParts = parsePath(path); + const newObj = Array.isArray(obj) ? [...obj] : { ...obj }; + let current: any = newObj; + + // Navigate to the parent of the target + for (let i = 0; i < pathParts.length - 1; i++) { + const part = pathParts[i]; + if (current[part] === undefined) { + current[part] = {}; + } + current = current[part]; + } + + // Update the value at the target path + const lastPart = pathParts[pathParts.length - 1]; + current[lastPart] = newValue; + + return newObj; +}; From f57aa53f4b1d69aeca07631c6db4f514d6cb56a9 Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Fri, 15 Aug 2025 10:17:18 +0300 Subject: [PATCH 30/90] fix(665): Clean up --- .../FlowElementsPopup/AssignBuilder/ObjectTree.tsx | 8 -------- 1 file changed, 8 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx index 50e5f3d1f..3e9abeb94 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx @@ -294,26 +294,18 @@ const ObjectTree: React.FC = () => { ); if (jsonNode) { - console.log("Found JSON node:", jsonNode.className, jsonNode.textContent); - // Get the current JSON data const currentData = jsonEditorRef.current.get(); // Try to find the path to the dropped node const path = findNodePath(jsonNode, currentData); - console.log("Found path:", path, "Value to replace:", valueToReplace); if (path) { // Update the value at the specific path const newData = updateValueAtPath(currentData as Record, path, valueToReplace); jsonEditorRef.current.set(newData); setData(newData as typeof data); - console.log("Successfully updated data at path:", path); - } else { - console.log("Could not find path for node:", jsonNode); } - } else { - console.log("No JSON node found at drop position"); } } } From 2546ddc34ded136a4bda08761746350f918c01dd Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Fri, 15 Aug 2025 10:17:36 +0300 Subject: [PATCH 31/90] fix(665): Clean up --- .../components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx index 3e9abeb94..adb4b5de0 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx @@ -105,6 +105,7 @@ const ObjectTree: React.FC = () => { useEffect(() => { if (editorRef.current && !jsonEditorRef.current) { const editor = new JSONEditor(editorRef.current, { + // todo only tree modes: ["tree", "code"], language: "et", languages: { From 589a9ba204ce1b159cc40039ac4a82a6ac79638e Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Fri, 15 Aug 2025 10:22:05 +0300 Subject: [PATCH 32/90] fix(665): Clean up --- .../AssignBuilder/ObjectTree.tsx | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx index adb4b5de0..fc8c55038 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx @@ -4,7 +4,7 @@ import React, { useEffect, useRef, useState } from "react"; import JSONEditor from "jsoneditor"; import "jsoneditor/dist/jsoneditor.css"; import { getDragData } from "utils/component-util"; -import { updateValueAtPath } from "utils/object-util"; +import { isObject, updateValueAtPath } from "utils/object-util"; import styles from "./ObjectTree.module.scss"; // todo bug: open tree closes on drop @@ -14,7 +14,7 @@ const findNodePath = (node: Element, data: Record): string | nu // Try to find by text content (for values) const textContent = node.textContent?.trim(); if (textContent) { - const path = findPathByValue(data, textContent); + const path = searchInCollection(data, textContent, "", Array.isArray(data)); return path; } @@ -50,8 +50,13 @@ const searchInCollection = ( } // Recursively search in nested arrays - if (typeof objValue === "object" && objValue !== null) { - const result = findPathByValue(objValue as Record | unknown[], value, newPath); + if (isObject(objValue)) { + const result = searchInCollection( + objValue as Record | unknown[], + value, + newPath, + Array.isArray(objValue) + ); if (result) return result; } } @@ -66,8 +71,13 @@ const searchInCollection = ( } // Recursively search in nested objects - if (typeof objValue === "object" && objValue !== null) { - const result = findPathByValue(objValue as Record | unknown[], value, newPath); + if (isObject(objValue)) { + const result = searchInCollection( + objValue as Record | unknown[], + value, + newPath, + Array.isArray(objValue) + ); if (result) return result; } } @@ -76,10 +86,6 @@ const searchInCollection = ( }; // todo inline? -// Helper function to find path by value -const findPathByValue = (obj: Record | unknown[], value: string, currentPath = ""): string | null => { - return searchInCollection(obj, value, currentPath, Array.isArray(obj)); -}; const ObjectTree: React.FC = () => { const editorRef = useRef(null); From 90b1738f361d4464544b36dad3651026463b1887 Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Fri, 15 Aug 2025 10:22:14 +0300 Subject: [PATCH 33/90] fix(665): Clean up --- .../components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx index fc8c55038..14ac438aa 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx @@ -85,8 +85,6 @@ const searchInCollection = ( return null; }; -// todo inline? - const ObjectTree: React.FC = () => { const editorRef = useRef(null); const jsonEditorRef = useRef(null); From 5fb9c8ebf61777cdb01f606253b168d76a1aac98 Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Fri, 15 Aug 2025 10:26:39 +0300 Subject: [PATCH 34/90] fix(665): Clean up --- .../AssignBuilder/ObjectTree.tsx | 59 +++++++------------ 1 file changed, 21 insertions(+), 38 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx index 14ac438aa..2bf896a9d 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx @@ -39,49 +39,32 @@ const searchInCollection = ( currentPath: string, isArray: boolean ): string | null => { - if (isArray) { - // Handle arrays - for (let i = 0; i < (collection as unknown[]).length; i++) { - const newPath = currentPath ? `${currentPath}[${i}]` : `[${i}]`; - const objValue = (collection as unknown[])[i]; + const entries = isArray + ? (collection as unknown[]).map((value, index) => ({ key: index, value })) + : Object.entries(collection as Record).map(([key, value]) => ({ key, value })); - if (isValueMatch(objValue, value)) { - return newPath; - } - - // Recursively search in nested arrays - if (isObject(objValue)) { - const result = searchInCollection( - objValue as Record | unknown[], - value, - newPath, - Array.isArray(objValue) - ); - if (result) return result; - } - } - } else { - // Handle objects - for (const key in collection as Record) { - const newPath = currentPath ? `${currentPath}.${key}` : key; - const objValue = (collection as Record)[key]; + for (const { key, value: objValue } of entries) { + const newPath = currentPath + ? isArray + ? `${currentPath}[${key}]` + : `${currentPath}.${key}` + : isArray + ? `[${key}]` + : String(key); - if (isValueMatch(objValue, value)) { - return newPath; - } + if (isValueMatch(objValue, value)) return newPath; - // Recursively search in nested objects - if (isObject(objValue)) { - const result = searchInCollection( - objValue as Record | unknown[], - value, - newPath, - Array.isArray(objValue) - ); - if (result) return result; - } + if (isObject(objValue)) { + const result = searchInCollection( + objValue as Record | unknown[], + value, + newPath, + Array.isArray(objValue) + ); + if (result) return result; } } + return null; }; From d9119f9095aa7b36221e6f87f92289893061d46c Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Fri, 15 Aug 2025 10:30:58 +0300 Subject: [PATCH 35/90] fix(665): Fix types --- GUI/package.json | 1 + .../FlowElementsPopup/AssignBuilder/ObjectTree.tsx | 14 ++++++-------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/GUI/package.json b/GUI/package.json index 0677183c6..381694463 100644 --- a/GUI/package.json +++ b/GUI/package.json @@ -85,6 +85,7 @@ "@types/d3-hierarchy": "^3.1.7", "@types/d3-timer": "^3.0.2", "@types/howler": "^2.2.11", + "@types/jsoneditor": "^9.9.6", "@types/react": "^18.2.0", "@types/react-datepicker": "^4.8.0", "@types/react-dom": "^18.2.0", diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx index 2bf896a9d..94e05a34a 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx @@ -1,6 +1,4 @@ import React, { useEffect, useRef, useState } from "react"; -// todo why? -// @ts-ignore import JSONEditor from "jsoneditor"; import "jsoneditor/dist/jsoneditor.css"; import { getDragData } from "utils/component-util"; @@ -37,7 +35,7 @@ const searchInCollection = ( collection: Record | unknown[], value: string, currentPath: string, - isArray: boolean + isArray: boolean, ): string | null => { const entries = isArray ? (collection as unknown[]).map((value, index) => ({ key: index, value })) @@ -49,8 +47,8 @@ const searchInCollection = ( ? `${currentPath}[${key}]` : `${currentPath}.${key}` : isArray - ? `[${key}]` - : String(key); + ? `[${key}]` + : String(key); if (isValueMatch(objValue, value)) return newPath; @@ -59,7 +57,7 @@ const searchInCollection = ( objValue as Record | unknown[], value, newPath, - Array.isArray(objValue) + Array.isArray(objValue), ); if (result) return result; } @@ -234,7 +232,7 @@ const ObjectTree: React.FC = () => { if (element) { // Find the closest JSON editor node - improved selectors for all data types const jsonNode = element.closest( - ".jsoneditor-value, .jsoneditor-field, .jsoneditor-string, .jsoneditor-number, .jsoneditor-boolean, .jsoneditor-null, .jsoneditor-object, .jsoneditor-array" + ".jsoneditor-value, .jsoneditor-field, .jsoneditor-string, .jsoneditor-number, .jsoneditor-boolean, .jsoneditor-null, .jsoneditor-object, .jsoneditor-array", ); // Remove highlight from previously hovered element @@ -278,7 +276,7 @@ const ObjectTree: React.FC = () => { if (element) { // Find the closest JSON editor node const jsonNode = element.closest( - ".jsoneditor-value, .jsoneditor-field, .jsoneditor-string, .jsoneditor-number, .jsoneditor-boolean, .jsoneditor-null, .jsoneditor-object, .jsoneditor-array" + ".jsoneditor-value, .jsoneditor-field, .jsoneditor-string, .jsoneditor-number, .jsoneditor-boolean, .jsoneditor-null, .jsoneditor-object, .jsoneditor-array", ); if (jsonNode) { From 7e3b8472f482385dbd4427c0007efb06c410fe26 Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Fri, 15 Aug 2025 10:49:26 +0300 Subject: [PATCH 36/90] fix(665): Clean upo --- .../components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx index 94e05a34a..76837773b 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx @@ -5,8 +5,6 @@ import { getDragData } from "utils/component-util"; import { isObject, updateValueAtPath } from "utils/object-util"; import styles from "./ObjectTree.module.scss"; -// todo bug: open tree closes on drop - // Helper function to find the path to a node in the JSON structure const findNodePath = (node: Element, data: Record): string | null => { // Try to find by text content (for values) From 269c4e69aa86da5f3de8ccde2027f490e2548e9b Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Fri, 15 Aug 2025 11:34:29 +0300 Subject: [PATCH 37/90] fix(665): Trans --- .../AssignBuilder/ObjectTree.tsx | 108 ++---------------- GUI/src/i18n/en/common.json | 93 +++++++++++++++ GUI/src/i18n/et/common.json | 93 +++++++++++++++ 3 files changed, 193 insertions(+), 101 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx index 76837773b..08e9deccd 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; import JSONEditor from "jsoneditor"; import "jsoneditor/dist/jsoneditor.css"; import { getDragData } from "utils/component-util"; @@ -65,9 +66,13 @@ const searchInCollection = ( }; const ObjectTree: React.FC = () => { + const { t, i18n } = useTranslation(); const editorRef = useRef(null); const jsonEditorRef = useRef(null); const [hoveredElement, setHoveredElement] = useState(null); + + // Get all JSONEditor translations as an object + const jsonEditor = t("jsonEditor", { returnObjects: true }); const [data, setData] = useState({ name: "John Doe", age: 30, @@ -90,108 +95,9 @@ const ObjectTree: React.FC = () => { const editor = new JSONEditor(editorRef.current, { // todo only tree modes: ["tree", "code"], - language: "et", + language: i18n.language, languages: { - et: { - array: "Massiiv", - auto: "Automaatne", - appendText: "Lisa lõppu", - appendTitle: "Lisa uus väli tüübiga 'auto' selle välja järele (Ctrl+Shift+Ins)", - appendSubmenuTitle: "Vali lisatava välja tüüp", - appendTitleAuto: "Lisa uus väli tüübiga 'auto' (Ctrl+Shift+Ins)", - ascending: "Kasvav", - ascendingTitle: "Sorteeri selle ${type} alamvälju kasvavas järjekorras", - actionsMenu: "Klõpsa tegevuste menüü avamiseks (Ctrl+M)", - cannotParseFieldError: "Ei saa välja JSON-iks teisendada", - cannotParseValueError: "Ei saa väärtust JSON-iks teisendada", - collapseAll: "Ahenda kõik väljad", - compactTitle: "Tihenda JSON andmed, eemalda kõik tühikud (Ctrl+Shift+I)", - descending: "Kahanev", - descendingTitle: "Sorteeri selle ${type} alamvälju kahanevas järjekorras", - drag: "Lohista välja liigutamiseks (Alt+Shift+Nooled)", - duplicateKey: "dubleeritud võti", - duplicateText: "Dubleeri", - duplicateTitle: "Dubleeri valitud väljad (Ctrl+D)", - duplicateField: "Dubleeri see väli (Ctrl+D)", - duplicateFieldError: "Dubleeritud välja nimi", - empty: "tühi", - expandAll: "Laienda kõik väljad", - expandTitle: - "Klõpsa välja laiendamiseks/ahendamiseks (Ctrl+E). \n" + - "Ctrl+Klõps laiendab/ahendab koos kõigi alamväljadega.", - formatTitle: "Vorminda JSON andmed, korraliku taanduse ja reavahetustega (Ctrl+I)", - insert: "Lisa", - insertTitle: "Lisa uus väli tüübiga 'auto' enne seda välja (Ctrl+Ins)", - insertSub: "Vali lisatava välja tüüp", - object: "Objekt", - ok: "Ok", - redo: "Tee uuesti (Ctrl+Shift+Z)", - removeText: "Eemalda", - removeTitle: "Eemalda valitud väljad (Ctrl+Del)", - removeField: "Eemalda see väli (Ctrl+Del)", - repairTitle: - "Paranda JSON: paranda jutumärgid ja paomärgid, eemalda kommentaarid ja JSONP märgendid, teisenda JavaScript objektid JSON-iks.", - searchTitle: "Otsi välju ja väärtusi", - searchNextResultTitle: "Järgmine tulemus (Enter)", - searchPreviousResultTitle: "Eelmine tulemus (Shift + Enter)", - selectNode: "Vali sõlm...", - showAll: "näita kõik", - showMore: "näita rohkem", - showMoreStatus: "kuvatakse ${visibleChilds} ${totalChilds}-st elemendist.", - sort: "Sorteeri", - sortTitle: "Sorteeri selle ${type} alamvälju", - sortTitleShort: "Sorteeri sisu", - sortFieldLabel: "Väli:", - sortDirectionLabel: "Suund:", - sortFieldTitle: "Vali pesastatud väli, mille järgi massiivi või objekti sorteerida", - sortAscending: "Kasvav", - sortAscendingTitle: "Sorteeri valitud väli kasvavas järjekorras", - sortDescending: "Kahanev", - sortDescendingTitle: "Sorteeri valitud väli kahanevas järjekorras", - string: "Sõne", - transform: "Teisenda", - transformTitle: "Filtreeri, sorteeri või teisenda selle ${type} alamvälju", - transformTitleShort: "Filtreeri, sorteeri või teisenda sisu", - extract: "Eralda", - extractTitle: "Eralda see ${type}", - transformQueryTitle: "Sisesta JMESPath päring", - transformWizardLabel: "Nõustaja", - transformWizardFilter: "Filter", - transformWizardSortBy: "Sorteeri", - transformWizardSelectFields: "Vali väljad", - transformQueryLabel: "Päring", - transformPreviewLabel: "Eelvaade", - type: "Tüüp", - typeTitle: "Muuda selle välja tüüpi", - openUrl: "Ctrl+Klõps või Ctrl+Enter URL-i uues aknas avamiseks", - undo: "Võta viimane tegevus tagasi (Ctrl+Z)", - validationCannotMove: "Välja ei saa liigutada iseenda alamväljaks", - autoType: - 'Välja tüüp "auto". ' + - "Välja tüüp määratakse automaatselt väärtuse põhjal " + - "ja võib olla sõne, number, tõeväärtus või null.", - objectType: 'Välja tüüp "objekt". ' + "Objekt sisaldab järjestamata võti/väärtus paaride kogumit.", - arrayType: 'Välja tüüp "massiiv". ' + "Massiiv sisaldab järjestatud väärtuste kogumit.", - stringType: - 'Välja tüüp "sõne". ' + "Välja tüüpi ei määrata väärtuse põhjal, " + "vaid tagastatakse alati sõnena.", - modeEditorTitle: "Vaheta redaktori režiimi", - modeCodeText: "Kood", - modeCodeTitle: "Lülitu koodi esiletõstule", - modeFormText: "Vorm", - modeFormTitle: "Lülitu vormi redaktorile", - modeTextText: "Tekst", - modeTextTitle: "Lülitu lihtteksti redaktorile", - modeTreeText: "Puu", - modeTreeTitle: "Lülitu puu redaktorile", - modeViewText: "Vaade", - modeViewTitle: "Lülitu puu vaatele", - modePreviewText: "Eelvaade", - modePreviewTitle: "Lülitu eelvaate režiimile", - examples: "Näited", - default: "Vaikimisi", - containsInvalidProperties: "Sisaldab vigaseid omadusi", - containsInvalidItems: "Sisaldab vigaseid elemente", - }, + [i18n.language]: jsonEditor, }, }); diff --git a/GUI/src/i18n/en/common.json b/GUI/src/i18n/en/common.json index 652226fd1..045a276cd 100644 --- a/GUI/src/i18n/en/common.json +++ b/GUI/src/i18n/en/common.json @@ -470,5 +470,98 @@ "waiting-for-user-input": "Waiting for user input", "title": "Chat Integration", "input-placeholder": "Enter your message..." + }, + "jsonEditor": { + "array": "Array", + "auto": "Auto", + "appendText": "Append", + "appendTitle": "Add new field with type 'auto' after this field (Ctrl+Shift+Ins)", + "appendSubmenuTitle": "Select type of field to add", + "appendTitleAuto": "Add new field with type 'auto' (Ctrl+Shift+Ins)", + "ascending": "Ascending", + "ascendingTitle": "Sort this ${type} subfield in ascending order", + "actionsMenu": "Click to open actions menu (Ctrl+M)", + "cannotParseFieldError": "Cannot parse field to JSON", + "cannotParseValueError": "Cannot parse value to JSON", + "collapseAll": "Collapse all fields", + "compactTitle": "Compact JSON data, remove all spaces (Ctrl+Shift+I)", + "descending": "Descending", + "descendingTitle": "Sort this ${type} subfield in descending order", + "drag": "Drag to move field (Alt+Shift+Arrow)", + "duplicateKey": "duplicate key", + "duplicateText": "Duplicate", + "duplicateTitle": "Duplicate selected fields (Ctrl+D)", + "duplicateField": "Duplicate this field (Ctrl+D)", + "duplicateFieldError": "Duplicate field name", + "empty": "empty", + "expandAll": "Expand all fields", + "expandTitle": "Click to expand/collapse field (Ctrl+E).\nCtrl+Click to expand/collapse with all subfields.", + "formatTitle": "Format JSON data with proper indentation and line breaks (Ctrl+I)", + "insert": "Insert", + "insertTitle": "Add new field with type 'auto' before this field (Ctrl+Ins)", + "insertSub": "Select type of field to add", + "object": "Object", + "ok": "Ok", + "redo": "Redo (Ctrl+Shift+Z)", + "removeText": "Remove", + "removeTitle": "Remove selected fields (Ctrl+Del)", + "removeField": "Remove this field (Ctrl+Del)", + "repairTitle": "Repair JSON: fix quotes and brackets, remove comments and JSONP markup, convert JavaScript objects to JSON.", + "searchTitle": "Search fields and values", + "searchNextResultTitle": "Next result (Enter)", + "searchPreviousResultTitle": "Previous result (Shift + Enter)", + "selectNode": "Select node...", + "showAll": "show all", + "showMore": "show more", + "showMoreStatus": "showing ${visibleChilds} of ${totalChilds} items.", + "sort": "Sort", + "sortTitle": "Sort this ${type} subfield", + "sortTitleShort": "Sort content", + "sortFieldLabel": "Field:", + "sortDirectionLabel": "Direction:", + "sortFieldTitle": "Select nested field to sort array or object by", + "sortAscending": "Ascending", + "sortAscendingTitle": "Sort selected field in ascending order", + "sortDescending": "Descending", + "sortDescendingTitle": "Sort selected field in descending order", + "string": "String", + "transform": "Transform", + "transformTitle": "Filter, sort or transform this ${type} subfield", + "transformTitleShort": "Filter, sort or transform content", + "extract": "Extract", + "extractTitle": "Extract this ${type}", + "transformQueryTitle": "Enter JMESPath query", + "transformWizardLabel": "Wizard", + "transformWizardFilter": "Filter", + "transformWizardSortBy": "Sort by", + "transformWizardSelectFields": "Select fields", + "transformQueryLabel": "Query", + "transformPreviewLabel": "Preview", + "type": "Type", + "typeTitle": "Change type of this field", + "openUrl": "Ctrl+Click or Ctrl+Enter to open URL in new window", + "undo": "Undo last action (Ctrl+Z)", + "validationCannotMove": "Cannot move field to its own subfield", + "autoType": "Field type \"auto\". Field type is automatically determined based on the value and can be string, number, boolean or null.", + "objectType": "Field type \"object\". Object contains an unordered collection of key/value pairs.", + "arrayType": "Field type \"array\". Array contains an ordered collection of values.", + "stringType": "Field type \"string\". Field type is not determined based on the value, but always returned as string.", + "modeEditorTitle": "Switch editor mode", + "modeCodeText": "Code", + "modeCodeTitle": "Switch to code highlighting", + "modeFormText": "Form", + "modeFormTitle": "Switch to form editor", + "modeTextText": "Text", + "modeTextTitle": "Switch to plain text editor", + "modeTreeText": "Tree", + "modeTreeTitle": "Switch to tree editor", + "modeViewText": "View", + "modeViewTitle": "Switch to tree view", + "modePreviewText": "Preview", + "modePreviewTitle": "Switch to preview mode", + "examples": "Examples", + "default": "Default", + "containsInvalidProperties": "Contains invalid properties", + "containsInvalidItems": "Contains invalid items" } } diff --git a/GUI/src/i18n/et/common.json b/GUI/src/i18n/et/common.json index 0c93c9278..bf2450d35 100644 --- a/GUI/src/i18n/et/common.json +++ b/GUI/src/i18n/et/common.json @@ -471,5 +471,98 @@ "waiting-for-user-input": "Ootan kasutaja sisendit", "title": "Chat Integration", "input-placeholder": "Kirjutage oma sõnum..." + }, + "jsonEditor": { + "array": "Massiiv", + "auto": "Automaatne", + "appendText": "Lisa lõppu", + "appendTitle": "Lisa uus väli tüübiga 'auto' selle välja järele (Ctrl+Shift+Ins)", + "appendSubmenuTitle": "Vali lisatava välja tüüp", + "appendTitleAuto": "Lisa uus väli tüübiga 'auto' (Ctrl+Shift+Ins)", + "ascending": "Kasvav", + "ascendingTitle": "Sorteeri selle ${type} alamvälju kasvavas järjekorras", + "actionsMenu": "Klõpsa tegevuste menüü avamiseks (Ctrl+M)", + "cannotParseFieldError": "Ei saa välja JSON-iks teisendada", + "cannotParseValueError": "Ei saa väärtust JSON-iks teisendada", + "collapseAll": "Ahenda kõik väljad", + "compactTitle": "Tihenda JSON andmed, eemalda kõik tühikud (Ctrl+Shift+I)", + "descending": "Kahanev", + "descendingTitle": "Sorteeri selle ${type} alamvälju kahanevas järjekorras", + "drag": "Lohista välja liigutamiseks (Alt+Shift+Nooled)", + "duplicateKey": "dubleeritud võti", + "duplicateText": "Dubleeri", + "duplicateTitle": "Dubleeri valitud väljad (Ctrl+D)", + "duplicateField": "Dubleeri see väli (Ctrl+D)", + "duplicateFieldError": "Dubleeritud välja nimi", + "empty": "tühi", + "expandAll": "Laienda kõik väljad", + "expandTitle": "Klõpsa välja laiendamiseks/ahendamiseks (Ctrl+E).\nCtrl+Klõps laiendab/ahendab koos kõigi alamväljadega.", + "formatTitle": "Vorminda JSON andmed, korraliku taanduse ja reavahetustega (Ctrl+I)", + "insert": "Lisa", + "insertTitle": "Lisa uus väli tüübiga 'auto' enne seda välja (Ctrl+Ins)", + "insertSub": "Vali lisatava välja tüüp", + "object": "Objekt", + "ok": "Ok", + "redo": "Tee uuesti (Ctrl+Shift+Z)", + "removeText": "Eemalda", + "removeTitle": "Eemalda valitud väljad (Ctrl+Del)", + "removeField": "Eemalda see väli (Ctrl+Del)", + "repairTitle": "Paranda JSON: paranda jutumärgid ja paomärgid, eemalda kommentaarid ja JSONP märgendid, teisenda JavaScript objektid JSON-iks.", + "searchTitle": "Otsi välju ja väärtusi", + "searchNextResultTitle": "Järgmine tulemus (Enter)", + "searchPreviousResultTitle": "Eelmine tulemus (Shift + Enter)", + "selectNode": "Vali sõlm...", + "showAll": "näita kõik", + "showMore": "näita rohkem", + "showMoreStatus": "kuvatakse ${visibleChilds} ${totalChilds}-st elemendist.", + "sort": "Sorteeri", + "sortTitle": "Sorteeri selle ${type} alamvälju", + "sortTitleShort": "Sorteeri sisu", + "sortFieldLabel": "Väli:", + "sortDirectionLabel": "Suund:", + "sortFieldTitle": "Vali pesastatud väli, mille järgi massiivi või objekti sorteerida", + "sortAscending": "Kasvav", + "sortAscendingTitle": "Sorteeri valitud väli kasvavas järjekorras", + "sortDescending": "Kahanev", + "sortDescendingTitle": "Sorteeri valitud väli kahanevas järjekorras", + "string": "Sõne", + "transform": "Teisenda", + "transformTitle": "Filtreeri, sorteeri või teisenda selle ${type} alamvälju", + "transformTitleShort": "Filtreeri, sorteeri või teisenda sisu", + "extract": "Eralda", + "extractTitle": "Eralda see ${type}", + "transformQueryTitle": "Sisesta JMESPath päring", + "transformWizardLabel": "Nõustaja", + "transformWizardFilter": "Filter", + "transformWizardSortBy": "Sorteeri", + "transformWizardSelectFields": "Vali väljad", + "transformQueryLabel": "Päring", + "transformPreviewLabel": "Eelvaade", + "type": "Tüüp", + "typeTitle": "Muuda selle välja tüüpi", + "openUrl": "Ctrl+Klõps või Ctrl+Enter URL-i uues aknas avamiseks", + "undo": "Võta viimane tegevus tagasi (Ctrl+Z)", + "validationCannotMove": "Välja ei saa liigutada iseenda alamväljaks", + "autoType": "Välja tüüp \"auto\". Välja tüüp määratakse automaatselt väärtuse põhjal ja võib olla sõne, number, tõeväärtus või null.", + "objectType": "Välja tüüp \"objekt\". Objekt sisaldab järjestamata võti/väärtus paaride kogumit.", + "arrayType": "Välja tüüp \"massiiv\". Massiiv sisaldab järjestatud väärtuste kogumit.", + "stringType": "Välja tüüp \"sõne\". Välja tüüpi ei määrata väärtuse põhjal, vaid tagastatakse alati sõnena.", + "modeEditorTitle": "Vaheta redaktori režiimi", + "modeCodeText": "Kood", + "modeCodeTitle": "Lülitu koodi esiletõstule", + "modeFormText": "Vorm", + "modeFormTitle": "Lülitu vormi redaktorile", + "modeTextText": "Tekst", + "modeTextTitle": "Lülitu lihtteksti redaktorile", + "modeTreeText": "Puu", + "modeTreeTitle": "Lülitu puu redaktorile", + "modeViewText": "Vaade", + "modeViewTitle": "Lülitu puu vaatele", + "modePreviewText": "Eelvaade", + "modePreviewTitle": "Lülitu eelvaate režiimile", + "examples": "Näited", + "default": "Vaikimisi", + "containsInvalidProperties": "Sisaldab vigaseid omadusi", + "containsInvalidItems": "Sisaldab vigaseid elemente" } } From 890d12bbcc4d57dcf6215d3d7fb2e794b6fdb595 Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Fri, 15 Aug 2025 12:31:11 +0300 Subject: [PATCH 38/90] fix(665): Clea --- .../components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx index 08e9deccd..c17c55e59 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx @@ -93,8 +93,6 @@ const ObjectTree: React.FC = () => { useEffect(() => { if (editorRef.current && !jsonEditorRef.current) { const editor = new JSONEditor(editorRef.current, { - // todo only tree - modes: ["tree", "code"], language: i18n.language, languages: { [i18n.language]: jsonEditor, From fa70860a6b0ab55bba794b0a53fa71bb80931593 Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Fri, 15 Aug 2025 12:36:49 +0300 Subject: [PATCH 39/90] fix(665): Clean up --- .../AssignBuilder/ObjectTree.tsx | 49 +++++++++---------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx index c17c55e59..15d3c94b5 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx @@ -71,24 +71,25 @@ const ObjectTree: React.FC = () => { const jsonEditorRef = useRef(null); const [hoveredElement, setHoveredElement] = useState(null); - // Get all JSONEditor translations as an object const jsonEditor = t("jsonEditor", { returnObjects: true }); - const [data, setData] = useState({ - name: "John Doe", - age: 30, - email: "john@example.com", - address: { - street: "123 Main St", - city: "Anytown", - zip: "12345", + const [data, setData] = useState | unknown[]>([ + { + name: "John Doe", + age: 30, + email: "john@example.com", + address: { + street: "123 Main St", + city: "Anytown", + zip: "12345", + }, + preferences: { + theme: "dark", + notifications: true, + }, + hobbies: ["reading", "gaming", "coding"], + scores: [85, 92, 78], }, - preferences: { - theme: "dark", - notifications: true, - }, - hobbies: ["reading", "gaming", "coding"], - scores: [85, 92, 78], - }); + ]); useEffect(() => { if (editorRef.current && !jsonEditorRef.current) { @@ -132,10 +133,8 @@ const ObjectTree: React.FC = () => { // Get the element under the cursor const element = document.elementFromPoint(e.clientX, e.clientY); if (element) { - // Find the closest JSON editor node - improved selectors for all data types - const jsonNode = element.closest( - ".jsoneditor-value, .jsoneditor-field, .jsoneditor-string, .jsoneditor-number, .jsoneditor-boolean, .jsoneditor-null, .jsoneditor-object, .jsoneditor-array", - ); + // Find the closest JSON editor node + const jsonNode = element.closest(".jsoneditor-value"); // Remove highlight from previously hovered element if (hoveredElement && hoveredElement !== jsonNode) { @@ -171,15 +170,13 @@ const ObjectTree: React.FC = () => { const dragData = getDragData(e); if (dragData && jsonEditorRef.current) { // Extract just the value from the drag data - const valueToReplace = dragData.value || dragData.data || dragData; + const valueToReplace = dragData.value; // Get the element under the cursor const element = document.elementFromPoint(e.clientX, e.clientY); if (element) { // Find the closest JSON editor node - const jsonNode = element.closest( - ".jsoneditor-value, .jsoneditor-field, .jsoneditor-string, .jsoneditor-number, .jsoneditor-boolean, .jsoneditor-null, .jsoneditor-object, .jsoneditor-array", - ); + const jsonNode = element.closest(".jsoneditor-value"); if (jsonNode) { // Get the current JSON data @@ -190,9 +187,9 @@ const ObjectTree: React.FC = () => { if (path) { // Update the value at the specific path - const newData = updateValueAtPath(currentData as Record, path, valueToReplace); + const newData = updateValueAtPath(currentData, path, valueToReplace); jsonEditorRef.current.set(newData); - setData(newData as typeof data); + setData(newData); } } } From d4a1e99aacef13ad6586b07593f82da020a6b98f Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Fri, 15 Aug 2025 12:41:58 +0300 Subject: [PATCH 40/90] fix(665): Clean up --- .../AssignBuilder/ObjectTree.tsx | 22 ++++++------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx index 15d3c94b5..ef928c6f4 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx @@ -11,7 +11,7 @@ const findNodePath = (node: Element, data: Record): string | nu // Try to find by text content (for values) const textContent = node.textContent?.trim(); if (textContent) { - const path = searchInCollection(data, textContent, "", Array.isArray(data)); + const path = searchInCollection(data, textContent); return path; } @@ -30,15 +30,12 @@ const isValueMatch = (objValue: unknown, value: string): boolean => { }; // Helper function to search for value in a collection -const searchInCollection = ( - collection: Record | unknown[], - value: string, - currentPath: string, - isArray: boolean, -): string | null => { +const searchInCollection = (collection: object, value: string, currentPath = ""): string | null => { + const isArray = Array.isArray(collection); + const entries = isArray - ? (collection as unknown[]).map((value, index) => ({ key: index, value })) - : Object.entries(collection as Record).map(([key, value]) => ({ key, value })); + ? collection.map((value, index) => ({ key: index, value })) + : Object.entries(collection).map(([key, value]) => ({ key, value })); for (const { key, value: objValue } of entries) { const newPath = currentPath @@ -52,12 +49,7 @@ const searchInCollection = ( if (isValueMatch(objValue, value)) return newPath; if (isObject(objValue)) { - const result = searchInCollection( - objValue as Record | unknown[], - value, - newPath, - Array.isArray(objValue), - ); + const result = searchInCollection(objValue, value, newPath); if (result) return result; } } From 05cd3e64bb418d2feaa6c574dfdbbcb238a9905c Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Fri, 15 Aug 2025 13:00:58 +0300 Subject: [PATCH 41/90] fix(665): Clean up --- .../AssignBuilder/ObjectTree.tsx | 82 ++++++++++++++----- .../FlowElementsPopup/PreviousVariables.tsx | 24 ++++-- GUI/src/utils/object-util.ts | 13 ++- 3 files changed, 88 insertions(+), 31 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx index ef928c6f4..5ce4f45e5 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx @@ -8,13 +8,42 @@ import styles from "./ObjectTree.module.scss"; // Helper function to find the path to a node in the JSON structure const findNodePath = (node: Element, data: Record): string | null => { + console.log("findNodePath called with node:", node.outerHTML); + // Try to find by text content (for values) const textContent = node.textContent?.trim(); if (textContent) { + console.log("Searching for text content:", textContent); const path = searchInCollection(data, textContent); + console.log("Found path for text content:", path); return path; } + // Try to find by field name if it's a field node + const fieldNameElement = node.querySelector(".jsoneditor-field-name"); + if (fieldNameElement) { + const fieldName = fieldNameElement.textContent?.trim(); + console.log("Found field name:", fieldName); + if (fieldName) { + // For field names, we need to find the parent object and then the field + const parentNode = node.closest(".jsoneditor-field"); + if (parentNode) { + const parentText = parentNode.textContent?.trim(); + console.log("Parent text:", parentText); + if (parentText) { + const parentPath = searchInCollection(data, parentText); + console.log("Parent path:", parentPath); + if (parentPath) { + const fullPath = `${parentPath}.${fieldName}`; + console.log("Full path:", fullPath); + return fullPath; + } + } + } + } + } + + console.log("No path found"); return null; }; @@ -32,6 +61,7 @@ const isValueMatch = (objValue: unknown, value: string): boolean => { // Helper function to search for value in a collection const searchInCollection = (collection: object, value: string, currentPath = ""): string | null => { const isArray = Array.isArray(collection); + console.log("searchInCollection:", { collection, value, currentPath, isArray }); const entries = isArray ? collection.map((value, index) => ({ key: index, value })) @@ -46,6 +76,8 @@ const searchInCollection = (collection: object, value: string, currentPath = "") ? `[${key}]` : String(key); + console.log("Checking path:", newPath, "value:", objValue, "matches:", isValueMatch(objValue, value)); + if (isValueMatch(objValue, value)) return newPath; if (isObject(objValue)) { @@ -64,28 +96,28 @@ const ObjectTree: React.FC = () => { const [hoveredElement, setHoveredElement] = useState(null); const jsonEditor = t("jsonEditor", { returnObjects: true }); - const [data, setData] = useState | unknown[]>([ - { - name: "John Doe", - age: 30, - email: "john@example.com", - address: { - street: "123 Main St", - city: "Anytown", - zip: "12345", - }, - preferences: { - theme: "dark", - notifications: true, - }, - hobbies: ["reading", "gaming", "coding"], - scores: [85, 92, 78], + const [data, setData] = useState | unknown[]>({ + name: "John Doe", + age: 30, + email: "john@example.com", + address: { + street: "123 Main St", + city: "Anytown", + zip: "12345", + }, + preferences: { + theme: "dark", + notifications: true, }, - ]); + hobbies: ["reading", "gaming", "coding"], + scores: [85, 92, { test: "test value" }, 78], + }); useEffect(() => { if (editorRef.current && !jsonEditorRef.current) { const editor = new JSONEditor(editorRef.current, { + // todo remove? + modes: ["tree", "code"], language: i18n.language, languages: { [i18n.language]: jsonEditor, @@ -125,8 +157,10 @@ const ObjectTree: React.FC = () => { // Get the element under the cursor const element = document.elementFromPoint(e.clientX, e.clientY); if (element) { - // Find the closest JSON editor node - const jsonNode = element.closest(".jsoneditor-value"); + // Find the closest JSON editor node - improved selectors for all data types + const jsonNode = element.closest( + ".jsoneditor-value, .jsoneditor-field, .jsoneditor-string, .jsoneditor-number, .jsoneditor-boolean, .jsoneditor-null, .jsoneditor-object, .jsoneditor-array", + ); // Remove highlight from previously hovered element if (hoveredElement && hoveredElement !== jsonNode) { @@ -167,8 +201,10 @@ const ObjectTree: React.FC = () => { // Get the element under the cursor const element = document.elementFromPoint(e.clientX, e.clientY); if (element) { - // Find the closest JSON editor node - const jsonNode = element.closest(".jsoneditor-value"); + // Find the closest JSON editor node - improved selectors for all data types + const jsonNode = element.closest( + ".jsoneditor-value, .jsoneditor-field, .jsoneditor-string, .jsoneditor-number, .jsoneditor-boolean, .jsoneditor-null, .jsoneditor-object, .jsoneditor-array", + ); if (jsonNode) { // Get the current JSON data @@ -178,10 +214,14 @@ const ObjectTree: React.FC = () => { const path = findNodePath(jsonNode, currentData); if (path) { + console.log("Found path:", path, "for value:", valueToReplace); // Update the value at the specific path const newData = updateValueAtPath(currentData, path, valueToReplace); jsonEditorRef.current.set(newData); setData(newData); + } else { + console.log("Could not find path for node:", jsonNode.textContent); + console.log("Node structure:", jsonNode.outerHTML); } } } diff --git a/GUI/src/components/FlowElementsPopup/PreviousVariables.tsx b/GUI/src/components/FlowElementsPopup/PreviousVariables.tsx index d84494be0..f8ef810f9 100644 --- a/GUI/src/components/FlowElementsPopup/PreviousVariables.tsx +++ b/GUI/src/components/FlowElementsPopup/PreviousVariables.tsx @@ -15,6 +15,7 @@ import { getHelperTooltips } from "utils/constants"; import { datesVariables, environmentVariables, helperVariables } from "resources/variables-constants"; import { Node, Edge } from "@xyflow/react"; import { NodeDataProps } from "types/service-flow"; +import { name } from "react-quill"; type PreviousVariablesProps = { readonly nodeId: string; @@ -22,7 +23,7 @@ type PreviousVariablesProps = { // Unique key for predefined elements, used below to identify it // All other assign element keys are UUIDs -const predefinedInputKeys = ['-1', '-2']; +const predefinedInputKeys = ["-1", "-2"]; const PreviousVariables: FC = ({ nodeId }) => { const { t } = useTranslation(); @@ -41,7 +42,7 @@ const PreviousVariables: FC = ({ nodeId }) => { const helperVariablesWithTooltips = helperVariables.map((variable, index) => { return { ...variable, - tooltip: getHelperTooltips()[index] + tooltip: getHelperTooltips()[index], }; }); @@ -50,7 +51,7 @@ const PreviousVariables: FC = ({ nodeId }) => { const currentNode = nodes[currentNodeIndex]; let startIndex = nodes.findLastIndex( - (node, i) => i < currentNodeIndex && node.data.stepType === StepType.MultiChoiceQuestion + (node, i) => i < currentNodeIndex && node.data.stepType === StepType.MultiChoiceQuestion, ); let previousNodes = nodes.slice(startIndex === -1 ? 0 : startIndex, currentNodeIndex); @@ -66,7 +67,8 @@ const PreviousVariables: FC = ({ nodeId }) => { setEndpoints(endpointsVariables); // Get Assign variables - const assignNodes: Node[] = previousNodes.filter((node) => node.data.stepType === StepType.Assign) as Node[] ?? []; + const assignNodes: Node[] = + (previousNodes.filter((node) => node.data.stepType === StepType.Assign) as Node[]) ?? []; const assignElements = assignNodes.map((node) => node.data.assignElements).flat(); const predefinedInputElements: Assign[] = [ { @@ -201,7 +203,7 @@ const PreviousVariables: FC = ({ nodeId }) => { : { data: chip.data, path: chip.value, - } + }, ); }} > @@ -236,6 +238,7 @@ const VariableSection = ({ border, }: any) => { const { t } = useTranslation(); + return ( { const typeColor = getTypeColor(variable?.value); + const rawName = + title === t("serviceFlow.previousVariables.environmentVariables.title") || + title === t("serviceFlow.previousVariables.assignElements") + ? variable.key + : t(variable.key); + const name = rawName.length > 0 ? rawName : t("serviceFlow.previousVariables.noName"); + return isObject(variable.data) && !predefinedInputKeys.includes(variable.id) ? ( @@ -290,7 +300,7 @@ const VariableSection = ({ style={{ cursor: variable.key ? "grab" : "default" }} borderColor={typeColor.color} > - {variable.key.length > 0 ? t(variable.key) : t("serviceFlow.previousVariables.noName")} + {name} ); diff --git a/GUI/src/utils/object-util.ts b/GUI/src/utils/object-util.ts index 449a0aca7..9d96905c8 100644 --- a/GUI/src/utils/object-util.ts +++ b/GUI/src/utils/object-util.ts @@ -69,7 +69,7 @@ export const isArray = (x: unknown) => { }; export const getTypeColor = ( - value: unknown + value: unknown, ): { type: "null/undefined" | "string" | "number" | "date" | "array" | "object" | "unknown"; color: string } => { switch (true) { case value === null || value === undefined: @@ -134,7 +134,7 @@ const parsePath = (path: string): (string | number)[] => { export const updateValueAtPath = ( obj: Record | unknown[], path: string, - newValue: unknown + newValue: unknown, ): Record | unknown[] => { const pathParts = parsePath(path); const newObj = Array.isArray(obj) ? [...obj] : { ...obj }; @@ -143,8 +143,15 @@ export const updateValueAtPath = ( // Navigate to the parent of the target for (let i = 0; i < pathParts.length - 1; i++) { const part = pathParts[i]; + const nextPart = pathParts[i + 1]; + if (current[part] === undefined) { - current[part] = {}; + // Check if the next part is a number (array index) or string (object key) + if (typeof nextPart === "number") { + current[part] = []; + } else { + current[part] = {}; + } } current = current[part]; } From 7e6b64ac5b43a08a21220bdce60125a0b2912b36 Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Fri, 15 Aug 2025 13:06:43 +0300 Subject: [PATCH 42/90] fix(665): fix drag drop on array elements --- .../AssignBuilder/ObjectTree.tsx | 2 ++ .../FlowElementsPopup/PreviousVariables.tsx | 1 - GUI/src/utils/object-util.ts | 19 +++++++++++++++++-- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx index 5ce4f45e5..b3545b9a3 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx @@ -215,8 +215,10 @@ const ObjectTree: React.FC = () => { if (path) { console.log("Found path:", path, "for value:", valueToReplace); + console.log("About to call updateValueAtPath with currentData:", currentData); // Update the value at the specific path const newData = updateValueAtPath(currentData, path, valueToReplace); + console.log("updateValueAtPath returned:", newData); jsonEditorRef.current.set(newData); setData(newData); } else { diff --git a/GUI/src/components/FlowElementsPopup/PreviousVariables.tsx b/GUI/src/components/FlowElementsPopup/PreviousVariables.tsx index f8ef810f9..b0d553ae2 100644 --- a/GUI/src/components/FlowElementsPopup/PreviousVariables.tsx +++ b/GUI/src/components/FlowElementsPopup/PreviousVariables.tsx @@ -15,7 +15,6 @@ import { getHelperTooltips } from "utils/constants"; import { datesVariables, environmentVariables, helperVariables } from "resources/variables-constants"; import { Node, Edge } from "@xyflow/react"; import { NodeDataProps } from "types/service-flow"; -import { name } from "react-quill"; type PreviousVariablesProps = { readonly nodeId: string; diff --git a/GUI/src/utils/object-util.ts b/GUI/src/utils/object-util.ts index 9d96905c8..d54041b4b 100644 --- a/GUI/src/utils/object-util.ts +++ b/GUI/src/utils/object-util.ts @@ -95,6 +95,7 @@ export const getKeyPathString = (keyPath: KeyPath) => { // Helper function to parse object path into parts const parsePath = (path: string): (string | number)[] => { + console.log("parsePath called with:", path); const pathParts: (string | number)[] = []; let currentPath = path; @@ -119,14 +120,21 @@ const parsePath = (path: string): (string | number)[] => { // Check for dot notation const dotIndex = currentPath.indexOf("."); if (dotIndex === -1) { - pathParts.push(currentPath); + // No more dots, add the remaining part if it's not empty + if (currentPath.length > 0) { + pathParts.push(currentPath); + } break; } else { - pathParts.push(currentPath.substring(0, dotIndex)); + const part = currentPath.substring(0, dotIndex); + if (part.length > 0) { + pathParts.push(part); + } currentPath = currentPath.substring(dotIndex + 1); } } + console.log("parsePath result:", pathParts); return pathParts; }; @@ -136,7 +144,9 @@ export const updateValueAtPath = ( path: string, newValue: unknown, ): Record | unknown[] => { + console.log("updateValueAtPath called with:", { obj, path, newValue }); const pathParts = parsePath(path); + console.log("Path parts:", pathParts); const newObj = Array.isArray(obj) ? [...obj] : { ...obj }; let current: any = newObj; @@ -144,13 +154,16 @@ export const updateValueAtPath = ( for (let i = 0; i < pathParts.length - 1; i++) { const part = pathParts[i]; const nextPart = pathParts[i + 1]; + console.log(`Navigating to part ${i}:`, part, "next part:", nextPart, "current:", current[part]); if (current[part] === undefined) { // Check if the next part is a number (array index) or string (object key) if (typeof nextPart === "number") { current[part] = []; + console.log("Created array at", part); } else { current[part] = {}; + console.log("Created object at", part); } } current = current[part]; @@ -158,7 +171,9 @@ export const updateValueAtPath = ( // Update the value at the target path const lastPart = pathParts[pathParts.length - 1]; + console.log("Setting value at", lastPart, "to", newValue); current[lastPart] = newValue; + console.log("Final result:", newObj); return newObj; }; From b5742581cfc2503a63416ce293c969f3bdd96f72 Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Fri, 15 Aug 2025 13:09:36 +0300 Subject: [PATCH 43/90] fix(665): remove console logs --- .../AssignBuilder/ObjectTree.tsx | 21 +------------------ GUI/src/utils/object-util.ts | 9 -------- 2 files changed, 1 insertion(+), 29 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx index b3545b9a3..aae41c43d 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx @@ -8,14 +8,10 @@ import styles from "./ObjectTree.module.scss"; // Helper function to find the path to a node in the JSON structure const findNodePath = (node: Element, data: Record): string | null => { - console.log("findNodePath called with node:", node.outerHTML); - // Try to find by text content (for values) const textContent = node.textContent?.trim(); if (textContent) { - console.log("Searching for text content:", textContent); const path = searchInCollection(data, textContent); - console.log("Found path for text content:", path); return path; } @@ -23,27 +19,21 @@ const findNodePath = (node: Element, data: Record): string | nu const fieldNameElement = node.querySelector(".jsoneditor-field-name"); if (fieldNameElement) { const fieldName = fieldNameElement.textContent?.trim(); - console.log("Found field name:", fieldName); if (fieldName) { // For field names, we need to find the parent object and then the field const parentNode = node.closest(".jsoneditor-field"); if (parentNode) { const parentText = parentNode.textContent?.trim(); - console.log("Parent text:", parentText); if (parentText) { const parentPath = searchInCollection(data, parentText); - console.log("Parent path:", parentPath); if (parentPath) { - const fullPath = `${parentPath}.${fieldName}`; - console.log("Full path:", fullPath); - return fullPath; + return `${parentPath}.${fieldName}`; } } } } } - console.log("No path found"); return null; }; @@ -61,7 +51,6 @@ const isValueMatch = (objValue: unknown, value: string): boolean => { // Helper function to search for value in a collection const searchInCollection = (collection: object, value: string, currentPath = ""): string | null => { const isArray = Array.isArray(collection); - console.log("searchInCollection:", { collection, value, currentPath, isArray }); const entries = isArray ? collection.map((value, index) => ({ key: index, value })) @@ -76,8 +65,6 @@ const searchInCollection = (collection: object, value: string, currentPath = "") ? `[${key}]` : String(key); - console.log("Checking path:", newPath, "value:", objValue, "matches:", isValueMatch(objValue, value)); - if (isValueMatch(objValue, value)) return newPath; if (isObject(objValue)) { @@ -214,16 +201,10 @@ const ObjectTree: React.FC = () => { const path = findNodePath(jsonNode, currentData); if (path) { - console.log("Found path:", path, "for value:", valueToReplace); - console.log("About to call updateValueAtPath with currentData:", currentData); // Update the value at the specific path const newData = updateValueAtPath(currentData, path, valueToReplace); - console.log("updateValueAtPath returned:", newData); jsonEditorRef.current.set(newData); setData(newData); - } else { - console.log("Could not find path for node:", jsonNode.textContent); - console.log("Node structure:", jsonNode.outerHTML); } } } diff --git a/GUI/src/utils/object-util.ts b/GUI/src/utils/object-util.ts index d54041b4b..d92f49aa3 100644 --- a/GUI/src/utils/object-util.ts +++ b/GUI/src/utils/object-util.ts @@ -95,7 +95,6 @@ export const getKeyPathString = (keyPath: KeyPath) => { // Helper function to parse object path into parts const parsePath = (path: string): (string | number)[] => { - console.log("parsePath called with:", path); const pathParts: (string | number)[] = []; let currentPath = path; @@ -134,7 +133,6 @@ const parsePath = (path: string): (string | number)[] => { } } - console.log("parsePath result:", pathParts); return pathParts; }; @@ -144,9 +142,7 @@ export const updateValueAtPath = ( path: string, newValue: unknown, ): Record | unknown[] => { - console.log("updateValueAtPath called with:", { obj, path, newValue }); const pathParts = parsePath(path); - console.log("Path parts:", pathParts); const newObj = Array.isArray(obj) ? [...obj] : { ...obj }; let current: any = newObj; @@ -154,16 +150,13 @@ export const updateValueAtPath = ( for (let i = 0; i < pathParts.length - 1; i++) { const part = pathParts[i]; const nextPart = pathParts[i + 1]; - console.log(`Navigating to part ${i}:`, part, "next part:", nextPart, "current:", current[part]); if (current[part] === undefined) { // Check if the next part is a number (array index) or string (object key) if (typeof nextPart === "number") { current[part] = []; - console.log("Created array at", part); } else { current[part] = {}; - console.log("Created object at", part); } } current = current[part]; @@ -171,9 +164,7 @@ export const updateValueAtPath = ( // Update the value at the target path const lastPart = pathParts[pathParts.length - 1]; - console.log("Setting value at", lastPart, "to", newValue); current[lastPart] = newValue; - console.log("Final result:", newObj); return newObj; }; From f6fbb216383f9ae929ba72aaa44e8ed28a594265 Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Fri, 15 Aug 2025 13:10:09 +0300 Subject: [PATCH 44/90] fix(665): Clean up --- .../AssignBuilder/ObjectTree.tsx | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx index aae41c43d..9302bc1df 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx @@ -15,25 +15,6 @@ const findNodePath = (node: Element, data: Record): string | nu return path; } - // Try to find by field name if it's a field node - const fieldNameElement = node.querySelector(".jsoneditor-field-name"); - if (fieldNameElement) { - const fieldName = fieldNameElement.textContent?.trim(); - if (fieldName) { - // For field names, we need to find the parent object and then the field - const parentNode = node.closest(".jsoneditor-field"); - if (parentNode) { - const parentText = parentNode.textContent?.trim(); - if (parentText) { - const parentPath = searchInCollection(data, parentText); - if (parentPath) { - return `${parentPath}.${fieldName}`; - } - } - } - } - } - return null; }; From 31b1f4bc6a303b004e16b0707dea624452ab8cb8 Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Fri, 15 Aug 2025 13:11:07 +0300 Subject: [PATCH 45/90] fix(665): Clean up --- .../FlowElementsPopup/AssignBuilder/ObjectTree.tsx | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx index 9302bc1df..4e0f1f5b4 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectTree.tsx @@ -125,10 +125,7 @@ const ObjectTree: React.FC = () => { // Get the element under the cursor const element = document.elementFromPoint(e.clientX, e.clientY); if (element) { - // Find the closest JSON editor node - improved selectors for all data types - const jsonNode = element.closest( - ".jsoneditor-value, .jsoneditor-field, .jsoneditor-string, .jsoneditor-number, .jsoneditor-boolean, .jsoneditor-null, .jsoneditor-object, .jsoneditor-array", - ); + const jsonNode = element.closest(".jsoneditor-value"); // Remove highlight from previously hovered element if (hoveredElement && hoveredElement !== jsonNode) { @@ -169,10 +166,7 @@ const ObjectTree: React.FC = () => { // Get the element under the cursor const element = document.elementFromPoint(e.clientX, e.clientY); if (element) { - // Find the closest JSON editor node - improved selectors for all data types - const jsonNode = element.closest( - ".jsoneditor-value, .jsoneditor-field, .jsoneditor-string, .jsoneditor-number, .jsoneditor-boolean, .jsoneditor-null, .jsoneditor-object, .jsoneditor-array", - ); + const jsonNode = element.closest(".jsoneditor-value"); if (jsonNode) { // Get the current JSON data From 0ef138f1f02a8895cce877b9f3e17e835b705c96 Mon Sep 17 00:00:00 2001 From: 1AhmedYasser <26207361+1AhmedYasser@users.noreply.github.com> Date: Sat, 16 Aug 2025 14:05:22 +0300 Subject: [PATCH 46/90] chore(685): Added show error flag to saving flow --- GUI/src/components/NewServiceHeader/index.tsx | 2 +- GUI/src/services/service-builder.ts | 26 +++++++++---------- GUI/src/store/new-services.store.ts | 6 ++--- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/GUI/src/components/NewServiceHeader/index.tsx b/GUI/src/components/NewServiceHeader/index.tsx index 6dff33f79..7cca5bb0e 100644 --- a/GUI/src/components/NewServiceHeader/index.tsx +++ b/GUI/src/components/NewServiceHeader/index.tsx @@ -50,7 +50,7 @@ const NewServiceHeader: FC = ({ activeStep, backOnClick, appearance={isSaving ? "loading" : "primary"} onClick={async () => { setIsSaving(true); - await useServiceStore.getState().onServiceSave(ServiceState.Draft); + await useServiceStore.getState().onServiceSave(ServiceState.Draft, false); setIsSaving(false); saveOnClick(); }} diff --git a/GUI/src/services/service-builder.ts b/GUI/src/services/service-builder.ts index 5780fa6d3..b2d9b0d93 100644 --- a/GUI/src/services/service-builder.ts +++ b/GUI/src/services/service-builder.ts @@ -64,6 +64,7 @@ interface SaveFlowConfig { serviceId: string; isNewService: boolean; status: "draft" | "ready"; + showError?: boolean; } const hasInvalidRules = (elements: any[]): boolean => { @@ -129,9 +130,10 @@ export const saveFlow = async ({ serviceId, isNewService, status = "ready", + showError = true, }: SaveFlowConfig) => { try { - let yamlContent = getYamlContent(nodes, edges, name, description); + let yamlContent = getYamlContent(nodes, edges, name, description, showError); const mcqNodes = nodes.filter( (node) => node.data?.stepType === StepType.MultiChoiceQuestion @@ -142,7 +144,7 @@ export const saveFlow = async ({ 0, nodes.findIndex((node) => node.data?.stepType === StepType.MultiChoiceQuestion) + 1 ); - yamlContent = getYamlContent(nodesUpToFirstMcq, edges, name, description); + yamlContent = getYamlContent(nodesUpToFirstMcq, edges, name, description, showError); } await saveService( @@ -170,7 +172,7 @@ export const saveFlow = async ({ ); await saveService( - getYamlContent(branchNodes, branchEdges, serviceName, description), + getYamlContent(branchNodes, branchEdges, serviceName, description, showError), { name: serviceName, serviceId, description, slot, isCommon, nodes, edges, isNewService } as SaveFlowConfig, false, status @@ -179,10 +181,6 @@ export const saveFlow = async ({ } } catch (e: any) { onError(e); - useToastStore.getState().error({ - title: i18next.t("toast.cannot-save-flow"), - message: e?.message, - }); } }; @@ -223,7 +221,7 @@ async function saveService( .catch(onError); } -function getYamlContent(nodes: Node[], edges: Edge[], name: string, description: string): any { +function getYamlContent(nodes: Node[], edges: Edge[], name: string, description: string, showError = true): any { const allRelations: any[] = []; nodes.forEach((node) => { @@ -259,7 +257,7 @@ function getYamlContent(nodes: Node[], edges: Edge[], name: string, description: break; } - if (error) { + if (error && showError) { throw new Error(error); } @@ -367,10 +365,9 @@ function getYamlContent(nodes: Node[], edges: Edge[], name: string, description: finishedFlow.set(parentStepName, template); }); } catch (e: any) { - useToastStore.getState().error({ - title: i18next.t("toast.cannot-save-flow"), - message: e?.message, - }); + if (showError) { + throw new Error(i18next.t("toast.cannot-save-flow") ?? e?.message ?? "Error"); + } } finishedFlow.set("format_messages", { @@ -719,7 +716,7 @@ const getTemplateDataFromNode = (node: Node): { templateName: string; body?: any } }; -export const saveFlowClick = async (status: "draft" | "ready" = "ready") => { +export const saveFlowClick = async (status: "draft" | "ready" = "ready", showError: boolean = true) => { const name = removeTrailingUnderscores(useServiceStore.getState().serviceNameDashed()); const serviceId = useServiceStore.getState().serviceId; const description = useServiceStore.getState().description; @@ -754,6 +751,7 @@ export const saveFlowClick = async (status: "draft" | "ready" = "ready") => { serviceId, isNewService, status, + showError, }); }; diff --git a/GUI/src/store/new-services.store.ts b/GUI/src/store/new-services.store.ts index e1e7af5ee..7d80b18f3 100644 --- a/GUI/src/store/new-services.store.ts +++ b/GUI/src/store/new-services.store.ts @@ -99,7 +99,7 @@ interface ServiceStoreState { resetState: () => void; resetAssign: () => void; resetRules: () => void; - onServiceSave: (status: "draft" | "ready") => Promise; + onServiceSave: (status: "draft" | "ready", showError?: boolean) => Promise; onContinueClick: () => Promise; selectedNode: Node | null; setSelectedNode: (node: Node | null | undefined) => void; @@ -627,8 +627,8 @@ const useServiceStore = create((set, get, store) => ({ }, reactFlowInstance: null, setReactFlowInstance: (reactFlowInstance) => set({ reactFlowInstance }), - onServiceSave: async (status: "draft" | "ready" = "ready") => { - await saveFlowClick(status); + onServiceSave: async (status: "draft" | "ready" = "ready", showError = true) => { + await saveFlowClick(status, showError); }, onContinueClick: async () => { const vaildServiceInfo = get().vaildServiceInfo(); From 1544c269b62497bb77559c69b46dafbbcbc4e660 Mon Sep 17 00:00:00 2001 From: 1AhmedYasser <26207361+1AhmedYasser@users.noreply.github.com> Date: Sat, 16 Aug 2025 14:25:20 +0300 Subject: [PATCH 47/90] chore(685): Enhanced Continue Error Handling --- GUI/src/services/service-builder.ts | 1 + GUI/src/store/new-services.store.ts | 10 +++++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/GUI/src/services/service-builder.ts b/GUI/src/services/service-builder.ts index b2d9b0d93..491cbe276 100644 --- a/GUI/src/services/service-builder.ts +++ b/GUI/src/services/service-builder.ts @@ -744,6 +744,7 @@ export const saveFlowClick = async (status: "draft" | "ready" = "ready", showErr title: i18next.t("newService.toast.failed"), message: e.response?.status === 409 ? t("newService.toast.serviceNameAlreadyExists") : e?.message, }); + throw new Error(e.response?.status === 409 ? t("newService.toast.serviceNameAlreadyExists").toString() : e?.message); }, description, slot, diff --git a/GUI/src/store/new-services.store.ts b/GUI/src/store/new-services.store.ts index 7d80b18f3..2b2b574bc 100644 --- a/GUI/src/store/new-services.store.ts +++ b/GUI/src/store/new-services.store.ts @@ -593,7 +593,7 @@ const useServiceStore = create((set, get, store) => ({ updateEndpointData: (data: RequestVariablesTabsRowsData, endpoint?: EndpointData) => { if (!endpoint) return; - const defEndpoint = endpoint.definitions[0]; + const defEndpoint = endpoint.definitions[0]; if (!defEndpoint) return; @@ -638,12 +638,16 @@ const useServiceStore = create((set, get, store) => ({ title: i18next.t("newService.toast.missingFields"), message: i18next.t("newService.toast.serviceMissingFields"), }); - return; + return Promise.reject(new Error(i18next.t("newService.toast.missingFields") ?? "Error")); } const { isNewService, onServiceSave } = get(); - await onServiceSave(ServiceState.Ready); + try { + await onServiceSave(ServiceState.Ready); + } catch (e: any) { + return Promise.reject(new Error(i18next.t("toast.cannot-save-flow") ?? e?.message ?? "Error")); + } if (isNewService) { set({ isNewService: false }); From 05d9c4d24c14b7de9b5dc6716832e771b14eba3e Mon Sep 17 00:00:00 2001 From: 1AhmedYasser <26207361+1AhmedYasser@users.noreply.github.com> Date: Sat, 16 Aug 2025 15:51:51 +0300 Subject: [PATCH 48/90] fix(686): Added Enhancment checks to ensure saving and continue execution --- DSL/Ruuter/services/POST/services/edit.yml | 24 +++++++++++++++++++ GUI/src/components/NewServiceHeader/index.tsx | 12 +++++++++- GUI/src/components/ServicesTable/index.tsx | 3 ++- GUI/src/i18n/en/common.json | 3 ++- GUI/src/i18n/et/common.json | 3 ++- GUI/src/pages/ServiceFlowPage.tsx | 3 --- GUI/src/store/services.store.ts | 10 -------- 7 files changed, 41 insertions(+), 17 deletions(-) diff --git a/DSL/Ruuter/services/POST/services/edit.yml b/DSL/Ruuter/services/POST/services/edit.yml index a44f670ee..0b2815bda 100644 --- a/DSL/Ruuter/services/POST/services/edit.yml +++ b/DSL/Ruuter/services/POST/services/edit.yml @@ -36,6 +36,10 @@ declaration: - field: id type: string description: "Parameter 'id'" + headers: + - field: cookie + type: string + description: "Cookie field" extract_request_data: assign: @@ -169,6 +173,7 @@ assign_values: old_name: ${old_service_result.response.body[0].name} old_structure: ${old_service_result.response.body[0].structure} old_state: ${old_service_result.response.body[0].state} + service_type: ${old_service_result.response.body[0].type} check_new_structure: switch: @@ -209,6 +214,25 @@ service_edit: state: ${state ?? 'draft'} result: editedService +check for_state: + switch: + - condition: ${state === 'draft'} + next: change_state_to_draft + next: return_ok + +change_state_to_draft: + call: http.post + args: + url: "[#SERVICE_RUUTER]/services/status" + headers: + cookie: ${incoming.headers.cookie} + body: + id: ${id} + state: "draft" + type: ${service_type ?? 'POST'} + result: changeStateResult + next: return_ok + return_ok: reloadDsl: true status: 200 diff --git a/GUI/src/components/NewServiceHeader/index.tsx b/GUI/src/components/NewServiceHeader/index.tsx index 6dff33f79..e973bfa50 100644 --- a/GUI/src/components/NewServiceHeader/index.tsx +++ b/GUI/src/components/NewServiceHeader/index.tsx @@ -10,6 +10,7 @@ import { ServiceState } from "types"; import { deleteService } from "resources/api-constants"; import api from "../../services/api-dev"; import { removeTrailingUnderscores } from "utils/string-util"; +import useToastStore from "store/toasts.store"; type NewServiceHeaderProps = { activeStep: number; @@ -57,7 +58,16 @@ const NewServiceHeader: FC = ({ activeStep, backOnClick, > {t("global.save")} - diff --git a/GUI/src/components/ServicesTable/index.tsx b/GUI/src/components/ServicesTable/index.tsx index 7078f9b5c..8b92b3f4a 100644 --- a/GUI/src/components/ServicesTable/index.tsx +++ b/GUI/src/components/ServicesTable/index.tsx @@ -212,7 +212,8 @@ const ServicesTable: FC = ({ isCommon = false }) => { {t("overview.cancel")} {readyPopupText != t("overview.popup.connectionPending").toString() && - readyPopupText != t("overview.popup.setActive").toString() && ( + readyPopupText != t("overview.popup.setActive").toString() && + readyPopupText != t("overview.popup.intentNotConnected").toString() && ( )} {getActiveAndConnectionButton()} diff --git a/GUI/src/i18n/en/common.json b/GUI/src/i18n/en/common.json index 652226fd1..8774de915 100644 --- a/GUI/src/i18n/en/common.json +++ b/GUI/src/i18n/en/common.json @@ -163,7 +163,8 @@ }, "requestedConnection": "Connection Requested", "cancelledConnection": "Connection Request Cancelled", - "connectedToIntentSuccessfully": "Connected to intent successfully" + "connectedToIntentSuccessfully": "Connected to intent successfully", + "cannotContinueUntilServiceIsSaved": "Cannot proceed until the service is saved" } }, "popup": { diff --git a/GUI/src/i18n/et/common.json b/GUI/src/i18n/et/common.json index 0c93c9278..3f27ee13f 100644 --- a/GUI/src/i18n/et/common.json +++ b/GUI/src/i18n/et/common.json @@ -163,7 +163,8 @@ }, "requestedConnection": "Ühendamist taotletakse", "cancelledConnection": "Ühendamistaotlus tühistati", - "connectedToIntentSuccessfully": "Kavatsusega ühendamine õnnestus" + "connectedToIntentSuccessfully": "Kavatsusega ühendamine õnnestus", + "cannotContinueUntilServiceIsSaved": "Ei saa jätkata enne, kui teenus on salvestatud" } }, "popup": { diff --git a/GUI/src/pages/ServiceFlowPage.tsx b/GUI/src/pages/ServiceFlowPage.tsx index 8fb0da89c..cabfce351 100644 --- a/GUI/src/pages/ServiceFlowPage.tsx +++ b/GUI/src/pages/ServiceFlowPage.tsx @@ -105,11 +105,8 @@ const ServiceFlowPage: FC = () => { const serviceResponse = await useServiceStore.getState().loadService(serviceId); if (serviceResponse) { useServiceListStore.getState().setSelectedService(serviceResponse?.data); - await useServiceListStore.getState().changeServiceStateToDraft(serviceResponse?.data); navigate(ROUTES.replaceWithId(ROUTES.EDITSERVICE_ROUTE, serviceId)); } - } else { - await useServiceListStore.getState().changeServiceStateToDraft(); } }} /> diff --git a/GUI/src/store/services.store.ts b/GUI/src/store/services.store.ts index 5d362524d..a7eca6a23 100644 --- a/GUI/src/store/services.store.ts +++ b/GUI/src/store/services.store.ts @@ -26,7 +26,6 @@ interface ServiceStoreState { deleteService: (id: string) => Promise; selectedService: Service | undefined; setSelectedService: (service: Service) => void; - changeServiceStateToDraft: (service?: Service) => Promise; changeServiceState: ( onEnd: () => void, successMessage: string, @@ -158,15 +157,6 @@ const useServiceListStore = create((set, get, store) => ({ selectedService: service, }); }, - changeServiceStateToDraft: async (service?: Service) => { - const selectedService = service ?? get().selectedService; - if (!selectedService) return; - await api.post(changeServiceStatus(), { - id: selectedService.serviceId, - state: ServiceState.Draft, - type: selectedService.type, - }); - }, changeServiceState: async (onEnd, successMessage, errorMessage, activate, draft, pagination, sorting) => { const selectedService = get().selectedService; if (!selectedService) return; From 397abba53f3e85d1b2faf64d361f79cc84cd2917 Mon Sep 17 00:00:00 2001 From: 1AhmedYasser <26207361+1AhmedYasser@users.noreply.github.com> Date: Sat, 16 Aug 2025 16:01:34 +0300 Subject: [PATCH 49/90] chore(686): Enhanced Service Deletion Button --- GUI/src/components/ServicesTable/index.tsx | 19 ++++++++++++++++--- GUI/src/store/services.store.ts | 4 +--- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/GUI/src/components/ServicesTable/index.tsx b/GUI/src/components/ServicesTable/index.tsx index 8b92b3f4a..9ae847060 100644 --- a/GUI/src/components/ServicesTable/index.tsx +++ b/GUI/src/components/ServicesTable/index.tsx @@ -43,6 +43,8 @@ const ServicesTable: FC = ({ isCommon = false }) => { useServiceListStore.getState().loadCommonServicesList(paginationState, sortingState); }; + const [isDeletingService, setIsDeletingService] = useState(false); + useEffect(() => { if (isCommon) { loadCommonServices(pagination, sorting); @@ -110,15 +112,26 @@ const ServicesTable: FC = ({ isCommon = false }) => { }; const deleteSelectedService = () => { + setIsDeletingService(true); useServiceListStore .getState() .deleteSelectedService( - () => setIsDeletePopupVisible(false), + async () => { + setIsDeletePopupVisible(false); + await useServiceListStore.getState().loadServicesList(pagination, sorting); + await useServiceListStore.getState().loadCommonServicesList(pagination, sorting); + }, t("overview.service.toast.deleted"), t("overview.service.toast.failed.delete"), pagination, sorting - ); + ) + .then(() => { + setIsDeletingService(false); + }) + .catch(() => { + setIsDeletingService(false); + }); }; const requestServiceIntentConnection = (intent: string) => { @@ -180,7 +193,7 @@ const ServicesTable: FC = ({ isCommon = false }) => { - diff --git a/GUI/src/store/services.store.ts b/GUI/src/store/services.store.ts index a7eca6a23..e7c10057c 100644 --- a/GUI/src/store/services.store.ts +++ b/GUI/src/store/services.store.ts @@ -218,11 +218,9 @@ const useServiceListStore = create((set, get, store) => ({ type: selectedService?.type, }); useToastStore.getState().success({ title: successMessage }); - await useServiceListStore.getState().loadServicesList(pagination, sorting); - await useServiceListStore.getState().loadCommonServicesList(pagination, sorting); } catch (error) { - console.error(error); useToastStore.getState().error({ title: errorMessage }); + throw error; } set({ selectedService: undefined, From 13a6e71bfede6c50486dcf0b43fbc683d3298aab Mon Sep 17 00:00:00 2001 From: 1AhmedYasser <26207361+1AhmedYasser@users.noreply.github.com> Date: Sat, 16 Aug 2025 16:07:59 +0300 Subject: [PATCH 50/90] chore(686): Enhanced Continue Button --- GUI/src/components/NewServiceHeader/index.tsx | 17 +++++++++++++++-- GUI/src/pages/ServiceFlowPage.tsx | 12 ++---------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/GUI/src/components/NewServiceHeader/index.tsx b/GUI/src/components/NewServiceHeader/index.tsx index e973bfa50..1abc14384 100644 --- a/GUI/src/components/NewServiceHeader/index.tsx +++ b/GUI/src/components/NewServiceHeader/index.tsx @@ -26,6 +26,7 @@ const NewServiceHeader: FC = ({ activeStep, backOnClick, const navigate = useNavigate(); const [isSaving, setIsSaving] = useState(false); const [isDeleting, setIsDeleting] = useState(false); + const [isContinuing, setIsContinuing] = useState(false); const [isDeleteServiceModalVisible, setIsDeleteServiceModalVisible] = useState(false); return ( @@ -59,12 +60,24 @@ const NewServiceHeader: FC = ({ activeStep, backOnClick, {t("global.save")} - @@ -237,7 +223,7 @@ const ServicesTable: FC = ({ isCommon = false }) => { {isIntentConnectionPopupVisible && ( setIsIntentConnectionPopupVisible(false)} - onConnect={(intent: Intent) => requestServiceIntentConnection(intent.intent)} + onConnect={() => setIsIntentConnectionPopupVisible(false)} /> )} void; - onConnect: (intent: Intent) => void; + onConnect: () => void; canCancel?: boolean; canSkip?: boolean; onSkip?: () => void; @@ -27,6 +27,7 @@ const ConnectServiceToIntentModel: FC = ({ onM const [intents, setIntents] = useState(undefined); const [selectedIntent, setSelectedIntent] = useState(); const [showConfirmationModal, setShowConfirmationModal] = useState(false); + const [isConnecting, setIsConnecting] = useState(false); const loadAvailableIntents = (pagination: PaginationState, sorting: SortingState, search: string) => { useServiceStore @@ -130,8 +131,29 @@ const ConnectServiceToIntentModel: FC = ({ onM {t("global.no")} - )} + + {onRemove && ( + + )} + - + + {isObjectTreeOpen && } + ); }; diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/index.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/index.tsx index 75d87680a..da6d4c07c 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/index.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/index.tsx @@ -5,7 +5,7 @@ import AssignElement from "./assignElement"; import { useAssignBuilder } from "./useAssignBuilder"; import "../styles.scss"; import { Assign } from "../../../types/assign"; -import ObjectTree from "./ObjectTree"; +import ObjectEditor from "./ObjectEditor"; interface AssignBuilderProps { onChange: (group: Assign[]) => void; @@ -31,8 +31,6 @@ const AssignBuilder: React.FC = ({ onChange, seedGroup }) => {elements?.map((element) => ( ))} - - ); }; From 0ddba79cd47344d5618fd15813d5117b808a87b8 Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Mon, 18 Aug 2025 06:50:39 +0300 Subject: [PATCH 54/90] fix(665): WIP button --- .../AssignBuilder/ObjectEditor.tsx | 29 ++++++++++--------- .../AssignBuilder/assignElement.tsx | 8 ++--- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx index ae3d65004..df402d4ae 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx @@ -65,20 +65,21 @@ const ObjectEditor: React.FC = () => { const jsonEditor = t("jsonEditor", { returnObjects: true }); const [data, setData] = useState | unknown[]>({ - name: "John Doe", - age: 30, - email: "john@example.com", - address: { - street: "123 Main St", - city: "Anytown", - zip: "12345", - }, - preferences: { - theme: "dark", - notifications: true, - }, - hobbies: ["reading", "gaming", "coding"], - scores: [85, 92, { test: "test value" }, 78], + // todo remove + // name: "John Doe", + // age: 30, + // email: "john@example.com", + // address: { + // street: "123 Main St", + // city: "Anytown", + // zip: "12345", + // }, + // preferences: { + // theme: "dark", + // notifications: true, + // }, + // hobbies: ["reading", "gaming", "coding"], + // scores: [85, 92, { test: "test value" }, 78], }); useEffect(() => { diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx index a014d804a..3b9ba3f13 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx @@ -120,6 +120,10 @@ const AssignElement: React.FC = ({ )} + + {!isEditingManually ? (
@@ -128,10 +132,6 @@ const AssignElement: React.FC = ({ ) : null} - - {onRemove && ( @@ -140,7 +143,7 @@ const AssignElement: React.FC = ({ - {isObjectTreeOpen && } + {isObjectEditorOpen && } ); }; From a61475014626f9d2a4b0ddc0b35b7d4390e5cbf5 Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Mon, 18 Aug 2025 07:11:10 +0300 Subject: [PATCH 56/90] fix(665): WIP state --- .../AssignBuilder/ObjectEditor.tsx | 18 +++++++++++++++++- .../AssignBuilder/assignElement.tsx | 2 +- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx index df402d4ae..c5f445f3a 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx @@ -5,6 +5,8 @@ import "jsoneditor/dist/jsoneditor.css"; import { getDragData } from "utils/component-util"; import { isObject, updateValueAtPath } from "utils/object-util"; import styles from "./ObjectEditor.module.scss"; +import { Assign } from "types"; +import { stringToTemplate } from "utils/string-util"; // Helper function to find the path to a node in the JSON structure const findNodePath = (node: Element, data: Record): string | null => { @@ -57,7 +59,12 @@ const searchInCollection = (collection: object, value: string, currentPath = "") return null; }; -const ObjectEditor: React.FC = () => { +interface ObjectEditorProps { + assignElement: Assign; + onChange: (element: Assign) => void; +} + +const ObjectEditor: React.FC = ({ assignElement, onChange }) => { const { t, i18n } = useTranslation(); const editorRef = useRef(null); const jsonEditorRef = useRef(null); @@ -91,6 +98,9 @@ const ObjectEditor: React.FC = () => { languages: { [i18n.language]: jsonEditor, }, + onChange: () => { + onChange({ ...assignElement, value: stringToTemplate(JSON.stringify(jsonEditorRef.current?.get())) }); + }, }); editor.set(data); @@ -176,11 +186,17 @@ const ObjectEditor: React.FC = () => { // Try to find the path to the dropped node const path = findNodePath(jsonNode, currentData); + console.log("IGOR path", path); + if (path) { // Update the value at the specific path const newData = updateValueAtPath(currentData, path, valueToReplace); jsonEditorRef.current.set(newData); setData(newData); + + console.log("IGOR newData", newData); + + onChange({ ...assignElement, value: stringToTemplate(JSON.stringify(newData)) }); } } } diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx index ed646aced..744e92101 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx @@ -143,7 +143,7 @@ const AssignElement: React.FC = ({ - {isObjectEditorOpen && } + {isObjectEditorOpen && } ); }; From 8382768caf7187df47968d942fce0ce38ec9ba38 Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Mon, 18 Aug 2025 07:13:23 +0300 Subject: [PATCH 57/90] fix(665): WIP state --- .../FlowElementsPopup/AssignBuilder/ObjectEditor.tsx | 9 ++++----- .../FlowElementsPopup/AssignBuilder/assignElement.tsx | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx index c5f445f3a..dea0c88cb 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx @@ -60,11 +60,10 @@ const searchInCollection = (collection: object, value: string, currentPath = "") }; interface ObjectEditorProps { - assignElement: Assign; - onChange: (element: Assign) => void; + onChange: (value: string) => void; } -const ObjectEditor: React.FC = ({ assignElement, onChange }) => { +const ObjectEditor: React.FC = ({ onChange }) => { const { t, i18n } = useTranslation(); const editorRef = useRef(null); const jsonEditorRef = useRef(null); @@ -99,7 +98,7 @@ const ObjectEditor: React.FC = ({ assignElement, onChange }) [i18n.language]: jsonEditor, }, onChange: () => { - onChange({ ...assignElement, value: stringToTemplate(JSON.stringify(jsonEditorRef.current?.get())) }); + onChange(stringToTemplate(JSON.stringify(jsonEditorRef.current?.get()))); }, }); @@ -196,7 +195,7 @@ const ObjectEditor: React.FC = ({ assignElement, onChange }) console.log("IGOR newData", newData); - onChange({ ...assignElement, value: stringToTemplate(JSON.stringify(newData)) }); + onChange(stringToTemplate(JSON.stringify(newData))); } } } diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx index 744e92101..7b6cf3c5f 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx @@ -143,7 +143,7 @@ const AssignElement: React.FC = ({ - {isObjectEditorOpen && } + {isObjectEditorOpen && onChange({ ...element, value })} />} ); }; From 55ae7f1fbc845009d245a68dea218ec81503e7b5 Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Mon, 18 Aug 2025 07:14:03 +0300 Subject: [PATCH 58/90] fix(665): WIP state --- .../FlowElementsPopup/AssignBuilder/ObjectEditor.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx index dea0c88cb..09a8dbe84 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx @@ -185,16 +185,12 @@ const ObjectEditor: React.FC = ({ onChange }) => { // Try to find the path to the dropped node const path = findNodePath(jsonNode, currentData); - console.log("IGOR path", path); - if (path) { // Update the value at the specific path const newData = updateValueAtPath(currentData, path, valueToReplace); jsonEditorRef.current.set(newData); setData(newData); - console.log("IGOR newData", newData); - onChange(stringToTemplate(JSON.stringify(newData))); } } From 77d8a78a16f06b5b56216b5f4d2211b47f6dcb62 Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Mon, 18 Aug 2025 07:17:37 +0300 Subject: [PATCH 59/90] fix(665): WIP state --- .../AssignBuilder/ObjectEditor.tsx | 23 +++---------------- .../AssignBuilder/assignElement.tsx | 7 +++++- 2 files changed, 9 insertions(+), 21 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx index 09a8dbe84..82f597a3d 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx @@ -61,38 +61,21 @@ const searchInCollection = (collection: object, value: string, currentPath = "") interface ObjectEditorProps { onChange: (value: string) => void; + data: Record | unknown[]; } -const ObjectEditor: React.FC = ({ onChange }) => { +const ObjectEditor: React.FC = ({ onChange, data: inputData }) => { const { t, i18n } = useTranslation(); const editorRef = useRef(null); const jsonEditorRef = useRef(null); const [hoveredElement, setHoveredElement] = useState(null); const jsonEditor = t("jsonEditor", { returnObjects: true }); - const [data, setData] = useState | unknown[]>({ - // todo remove - // name: "John Doe", - // age: 30, - // email: "john@example.com", - // address: { - // street: "123 Main St", - // city: "Anytown", - // zip: "12345", - // }, - // preferences: { - // theme: "dark", - // notifications: true, - // }, - // hobbies: ["reading", "gaming", "coding"], - // scores: [85, 92, { test: "test value" }, 78], - }); + const [data, setData] = useState | unknown[]>(inputData); useEffect(() => { if (editorRef.current && !jsonEditorRef.current) { const editor = new JSONEditor(editorRef.current, { - // todo remove? - modes: ["tree", "code"], language: i18n.language, languages: { [i18n.language]: jsonEditor, diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx index 7b6cf3c5f..95619dfff 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx @@ -143,7 +143,12 @@ const AssignElement: React.FC = ({ - {isObjectEditorOpen && onChange({ ...element, value })} />} + {isObjectEditorOpen && ( + onChange({ ...element, value })} + data={JSON.parse(templateToString(element.value))} + /> + )} ); }; From 5120aa318c228d508e7efc11738d30eb64bf8392 Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Mon, 18 Aug 2025 07:20:30 +0300 Subject: [PATCH 60/90] fix(665): WIP state --- .../FlowElementsPopup/AssignBuilder/ObjectEditor.tsx | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx index 82f597a3d..75831b527 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx @@ -66,13 +66,11 @@ interface ObjectEditorProps { const ObjectEditor: React.FC = ({ onChange, data: inputData }) => { const { t, i18n } = useTranslation(); + const jsonEditor = t("jsonEditor", { returnObjects: true }); const editorRef = useRef(null); const jsonEditorRef = useRef(null); const [hoveredElement, setHoveredElement] = useState(null); - const jsonEditor = t("jsonEditor", { returnObjects: true }); - const [data, setData] = useState | unknown[]>(inputData); - useEffect(() => { if (editorRef.current && !jsonEditorRef.current) { const editor = new JSONEditor(editorRef.current, { @@ -85,7 +83,7 @@ const ObjectEditor: React.FC = ({ onChange, data: inputData } }, }); - editor.set(data); + editor.set(inputData); jsonEditorRef.current = editor; } @@ -97,11 +95,12 @@ const ObjectEditor: React.FC = ({ onChange, data: inputData } }; }, []); + // todo check if necessary useEffect(() => { if (jsonEditorRef.current) { - jsonEditorRef.current.set(data); + jsonEditorRef.current.set(inputData); } - }, [data]); + }, [inputData]); // Cleanup effect to remove highlight when component unmounts useEffect(() => { @@ -172,7 +171,6 @@ const ObjectEditor: React.FC = ({ onChange, data: inputData } // Update the value at the specific path const newData = updateValueAtPath(currentData, path, valueToReplace); jsonEditorRef.current.set(newData); - setData(newData); onChange(stringToTemplate(JSON.stringify(newData))); } From 418f9f3bd51d78e37338c0e1ad2d27924dac6e86 Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Mon, 18 Aug 2025 07:21:31 +0300 Subject: [PATCH 61/90] fix(665): WIP state --- .../FlowElementsPopup/AssignBuilder/assignElement.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx index 95619dfff..02efd4ef9 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx @@ -146,7 +146,7 @@ const AssignElement: React.FC = ({ {isObjectEditorOpen && ( onChange({ ...element, value })} - data={JSON.parse(templateToString(element.value))} + data={element.value ? JSON.parse(templateToString(element.value)) : {}} /> )} From 35b276aafe71a23bf69fbb982a9955f8d9e72408 Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Mon, 18 Aug 2025 07:22:00 +0300 Subject: [PATCH 62/90] fix(665): WIP state --- .../FlowElementsPopup/AssignBuilder/ObjectEditor.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx index 75831b527..f38180ac5 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx @@ -64,7 +64,7 @@ interface ObjectEditorProps { data: Record | unknown[]; } -const ObjectEditor: React.FC = ({ onChange, data: inputData }) => { +const ObjectEditor: React.FC = ({ onChange, data }) => { const { t, i18n } = useTranslation(); const jsonEditor = t("jsonEditor", { returnObjects: true }); const editorRef = useRef(null); @@ -83,7 +83,7 @@ const ObjectEditor: React.FC = ({ onChange, data: inputData } }, }); - editor.set(inputData); + editor.set(data); jsonEditorRef.current = editor; } @@ -98,9 +98,9 @@ const ObjectEditor: React.FC = ({ onChange, data: inputData } // todo check if necessary useEffect(() => { if (jsonEditorRef.current) { - jsonEditorRef.current.set(inputData); + jsonEditorRef.current.set(data); } - }, [inputData]); + }, [data]); // Cleanup effect to remove highlight when component unmounts useEffect(() => { From 6366faa56a94f3c73069e5bd4fae71be5e665b1b Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Mon, 18 Aug 2025 07:22:13 +0300 Subject: [PATCH 63/90] fix(665): WIP state --- .../FlowElementsPopup/AssignBuilder/assignElement.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx index 02efd4ef9..bc99e607a 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx @@ -145,8 +145,8 @@ const AssignElement: React.FC = ({ {isObjectEditorOpen && ( onChange({ ...element, value })} data={element.value ? JSON.parse(templateToString(element.value)) : {}} + onChange={(value) => onChange({ ...element, value })} /> )} From 7572013c69f7bc4b33b3fff361bf3f425b228466 Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Mon, 18 Aug 2025 07:28:45 +0300 Subject: [PATCH 64/90] fix(665): WIP state --- .../components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx index f38180ac5..d54292be9 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx @@ -5,7 +5,6 @@ import "jsoneditor/dist/jsoneditor.css"; import { getDragData } from "utils/component-util"; import { isObject, updateValueAtPath } from "utils/object-util"; import styles from "./ObjectEditor.module.scss"; -import { Assign } from "types"; import { stringToTemplate } from "utils/string-util"; // Helper function to find the path to a node in the JSON structure From 06892fcea035bc2da86c20d78e65da8aaee310ef Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Mon, 18 Aug 2025 07:40:00 +0300 Subject: [PATCH 65/90] fix(665): WIP style --- .../AssignBuilder/AssignElement.module.scss | 12 ++++++++++++ .../AssignBuilder/ObjectEditor.module.scss | 5 ----- .../FlowElementsPopup/AssignBuilder/ObjectEditor.tsx | 1 + .../FlowElementsPopup/AssignBuilder/index.tsx | 7 +++---- .../FlowElementsPopup/DynamicChoicesContent.tsx | 2 +- GUI/src/components/FlowElementsPopup/styles.scss | 1 - 6 files changed, 17 insertions(+), 11 deletions(-) create mode 100644 GUI/src/components/FlowElementsPopup/AssignBuilder/AssignElement.module.scss diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/AssignElement.module.scss b/GUI/src/components/FlowElementsPopup/AssignBuilder/AssignElement.module.scss new file mode 100644 index 000000000..f4561f5b4 --- /dev/null +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/AssignElement.module.scss @@ -0,0 +1,12 @@ +@import "src/styles/tools/spacing"; +@import "src/styles/tools/color"; +@import "src/styles/settings/variables/other"; +@import "src/styles/settings/variables/typography"; + +.dragHoverHighlight { + background-color: #e3f2fd !important; + border: 1px solid #2196f3 !important; + border-radius: 4px !important; + box-shadow: 0 0 8px rgba(33, 150, 243, 0.3) !important; + transition: all 0.2s ease-in-out !important; +} diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.module.scss b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.module.scss index f4561f5b4..c4603f357 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.module.scss +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.module.scss @@ -1,8 +1,3 @@ -@import "src/styles/tools/spacing"; -@import "src/styles/tools/color"; -@import "src/styles/settings/variables/other"; -@import "src/styles/settings/variables/typography"; - .dragHoverHighlight { background-color: #e3f2fd !important; border: 1px solid #2196f3 !important; diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx index d54292be9..9952f7dea 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx @@ -167,6 +167,7 @@ const ObjectEditor: React.FC = ({ onChange, data }) => { const path = findNodePath(jsonNode, currentData); if (path) { + // todo i think this broke after removing component state // Update the value at the specific path const newData = updateValueAtPath(currentData, path, valueToReplace); jsonEditorRef.current.set(newData); diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/index.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/index.tsx index da6d4c07c..418871e62 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/index.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/index.tsx @@ -1,11 +1,10 @@ import React from "react"; import { useTranslation } from "react-i18next"; import { Track } from "components"; -import AssignElement from "./assignElement"; +import AssignElement from "./AssignElement"; import { useAssignBuilder } from "./useAssignBuilder"; import "../styles.scss"; import { Assign } from "../../../types/assign"; -import ObjectEditor from "./ObjectEditor"; interface AssignBuilderProps { onChange: (group: Assign[]) => void; @@ -20,8 +19,8 @@ const AssignBuilder: React.FC = ({ onChange, seedGroup }) => }); return ( - - + + - {!isEditingManually ? ( - -
- } /> -
-
- ) : null} - {onRemove && ( From 18486911ba5fd97270b7be4c2e4dd5129245c8bb Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Mon, 18 Aug 2025 08:25:06 +0300 Subject: [PATCH 74/90] fix(665): WIP toggle --- .../FlowElementsPopup/AssignBuilder/assignElement.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx index bae6c4d36..43ade7b35 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx @@ -140,8 +140,12 @@ const AssignElement: React.FC = ({ From fb583d41522be5f84e6fe0ec0b9fbee703995993 Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Mon, 18 Aug 2025 08:34:10 +0300 Subject: [PATCH 76/90] fix(665): WIP parsing --- .../AssignBuilder/assignElement.tsx | 60 +++++++++++-------- GUI/src/i18n/en/common.json | 3 +- GUI/src/i18n/et/common.json | 3 +- 3 files changed, 38 insertions(+), 28 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx index f4747b52a..0d5128cef 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx @@ -3,7 +3,7 @@ import { DragInput, FormInput, Icon, Tooltip, Track } from "components"; import { MdDataObject, MdDeleteOutline, MdEdit, MdMoveDown } from "react-icons/md"; import { Assign } from "../../../types/assign"; import "../styles.scss"; -import { stringToTemplate, templateToString } from "utils/string-util"; +import { isTemplate, stringToTemplate, templateToString } from "utils/string-util"; import { isArray, isObject } from "utils/object-util"; import { t } from "i18next"; import { getDragData } from "utils/component-util"; @@ -137,33 +137,41 @@ const AssignElement: React.FC = ({ )} - + }} + > + } /> + + {onRemove && ( +
{onRemove && ( From cf1d6c1d4e25a02ab0bcb03e522567606485c125 Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Mon, 18 Aug 2025 08:52:27 +0300 Subject: [PATCH 80/90] fix(665): parsing --- .../AssignBuilder/assignElement.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx index fbc4c119b..c987480e2 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx @@ -11,6 +11,13 @@ import ObjectEditor from "./ObjectEditor"; import styles from "./AssignElement.module.scss"; import useToastStore from "store/toasts.store"; +const showInvalidObjectError = () => { + useToastStore.getState().error({ + title: t("serviceFlow.apiElements.cannotOpenEditor"), + message: t("serviceFlow.apiElements.invalidObjectError"), + }); +}; + interface AssignElementProps { element: Assign; onRemove?: (id: string) => void; @@ -85,10 +92,7 @@ const AssignElement: React.FC = ({ } if (!isTemplate(element.value)) { - useToastStore.getState().error({ - title: t("serviceFlow.apiElements.cannotOpenEditor"), - message: t("serviceFlow.apiElements.invalidObjectError"), - }); + showInvalidObjectError(); return; } @@ -96,11 +100,7 @@ const AssignElement: React.FC = ({ JSON.parse(templateToString(element.value)); setIsObjectEditorOpen(!isObjectEditorOpen); } catch (error) { - // Handle JSON parsing errors gracefully - useToastStore.getState().error({ - title: t("serviceFlow.apiElements.cannotOpenEditor"), - message: t("serviceFlow.apiElements.invalidObjectError"), - }); + showInvalidObjectError(); } } }; From 0b9ac1e515fe078919f367add579dad9b8a247fb Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Mon, 18 Aug 2025 08:55:56 +0300 Subject: [PATCH 81/90] fix(665): clean --- GUI/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/GUI/package.json b/GUI/package.json index 381694463..b04db787f 100644 --- a/GUI/package.json +++ b/GUI/package.json @@ -33,7 +33,6 @@ "howler": "^2.2.4", "i18next": "^22.4.9", "i18next-browser-languagedetector": "^7.0.1", - "json-edit-react": "^1.28.2", "jsoneditor": "^10.3.0", "moment": "^2.29.4", "msw": "^0.49.2", From 1ac0a65843d10a99f9e55003582d1a0be3500bab Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Mon, 18 Aug 2025 08:56:51 +0300 Subject: [PATCH 82/90] fix(665): clean --- docker-compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 66c379b31..f1e85977d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -45,8 +45,8 @@ services: - database environment: - sqlms.datasources.[0].name=byk - - sqlms.datasources.[0].jdbcUrl=jdbc:postgresql://database:5432/services_db # For Local Use - # - sqlms.datasources.[0].jdbcUrl=jdbc:postgresql://171.22.247.13:5435/services_db + # - sqlms.datasources.[0].jdbcUrl=jdbc:postgresql://database:5432/services_db # For Local Use + - sqlms.datasources.[0].jdbcUrl=jdbc:postgresql://171.22.247.13:5435/services_db - sqlms.datasources.[0].username=byk - sqlms.datasources.[0].password=01234 - logging.level.org.springframework.boot=INFO From e679f6386c7bd51b37f9c4061e02c77be8a02809 Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Mon, 18 Aug 2025 08:58:37 +0300 Subject: [PATCH 83/90] fix(665): clean --- .../FlowElementsPopup/AssignBuilder/assignElement.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx index c987480e2..58f581629 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx @@ -170,8 +170,8 @@ const AssignElement: React.FC = ({ )} - -
+ +
} />
From 1f0fbcd743f862233f796b52d0fcd327d5a0adb6 Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Mon, 18 Aug 2025 09:03:45 +0300 Subject: [PATCH 84/90] fix(665): clean --- .../FlowElementsPopup/AssignBuilder/assignElement.tsx | 1 + GUI/src/utils/object-util.ts | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx index 58f581629..5e548be75 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx @@ -100,6 +100,7 @@ const AssignElement: React.FC = ({ JSON.parse(templateToString(element.value)); setIsObjectEditorOpen(!isObjectEditorOpen); } catch (error) { + console.log("Error parsing input", error); showInvalidObjectError(); } } diff --git a/GUI/src/utils/object-util.ts b/GUI/src/utils/object-util.ts index d92f49aa3..fe7c34008 100644 --- a/GUI/src/utils/object-util.ts +++ b/GUI/src/utils/object-util.ts @@ -100,7 +100,7 @@ const parsePath = (path: string): (string | number)[] => { while (currentPath.length > 0) { // First, check for array index at the beginning - const arrayMatch = currentPath.match(/^\[(\d+)\]/); + const arrayMatch = /^\[(\d+)\]/.exec(currentPath); if (arrayMatch) { pathParts.push(parseInt(arrayMatch[1])); currentPath = currentPath.substring(arrayMatch[0].length); @@ -108,7 +108,7 @@ const parsePath = (path: string): (string | number)[] => { } // Then check for property name followed by array index - const propertyArrayMatch = currentPath.match(/^([^.\[\]]+)\[(\d+)\]/); + const propertyArrayMatch = /^([^.\[\]]+)\[(\d+)\]/.exec(currentPath); if (propertyArrayMatch) { pathParts.push(propertyArrayMatch[1]); // property name pathParts.push(parseInt(propertyArrayMatch[2])); // array index From 90a442bca551cc3b5e3332392d05bb90129e786b Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Mon, 18 Aug 2025 09:07:05 +0300 Subject: [PATCH 85/90] fix(665): clean --- .../AssignBuilder/ObjectEditor.tsx | 43 +++++++++++++++---- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx index 875ab2773..667b83a73 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx @@ -21,11 +21,18 @@ const findNodePath = (node: Element, data: Record): string | nu // Helper function to check if values match (handling different types) const isValueMatch = (objValue: unknown, value: string): boolean => { + // Handle boolean conversion + let booleanValue: boolean | null = null; + if (value.toLowerCase() === "true") { + booleanValue = true; + } else if (value.toLowerCase() === "false") { + booleanValue = false; + } + return ( objValue === value || (typeof objValue === "number" && !isNaN(Number(value)) && objValue === Number(value)) || - (typeof objValue === "boolean" && - objValue === (value.toLowerCase() === "true" ? true : value.toLowerCase() === "false" ? false : null)) || + (typeof objValue === "boolean" && objValue === booleanValue) || (objValue === null && value.toLowerCase() === "null") ); }; @@ -39,13 +46,21 @@ const searchInCollection = (collection: object, value: string, currentPath = "") : Object.entries(collection).map(([key, value]) => ({ key, value })); for (const { key, value: objValue } of entries) { - const newPath = currentPath - ? isArray - ? `${currentPath}[${key}]` - : `${currentPath}.${key}` - : isArray - ? `[${key}]` - : String(key); + let newPath: string; + + if (currentPath) { + if (isArray) { + newPath = `${currentPath}[${key}]`; + } else { + newPath = `${currentPath}.${key}`; + } + } else { + if (isArray) { + newPath = `[${key}]`; + } else { + newPath = String(key); + } + } if (isValueMatch(objValue, value)) return newPath; @@ -182,6 +197,16 @@ const ObjectEditor: React.FC = ({ onChange, data }) => { onDragLeave={handleDragLeave} onDrop={handleDrop} className={styles.editor} + role="application" + tabIndex={0} + onKeyDown={(e) => { + // Handle keyboard interactions for accessibility + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + // Focus the editor for keyboard navigation + editorRef.current?.focus(); + } + }} /> ); }; From 1f4bf52e5148df229fa059d191aac487e0310442 Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Mon, 18 Aug 2025 09:17:02 +0300 Subject: [PATCH 86/90] fix(665): clean --- .../FlowElementsPopup/AssignBuilder/ObjectEditor.tsx | 11 +++++------ GUI/src/i18n/en/common.json | 5 +++-- GUI/src/i18n/et/common.json | 5 +++-- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx index 667b83a73..81bedf9c7 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx @@ -54,12 +54,10 @@ const searchInCollection = (collection: object, value: string, currentPath = "") } else { newPath = `${currentPath}.${key}`; } + } else if (isArray) { + newPath = `[${key}]`; } else { - if (isArray) { - newPath = `[${key}]`; - } else { - newPath = String(key); - } + newPath = String(key); } if (isValueMatch(objValue, value)) return newPath; @@ -80,7 +78,7 @@ interface ObjectEditorProps { const ObjectEditor: React.FC = ({ onChange, data }) => { const { t, i18n } = useTranslation(); - const jsonEditor = t("jsonEditor", { returnObjects: true }); + const jsonEditor = t("objectEditor", { returnObjects: true }); const editorRef = useRef(null); const jsonEditorRef = useRef(null); const [hoveredElement, setHoveredElement] = useState(null); @@ -198,6 +196,7 @@ const ObjectEditor: React.FC = ({ onChange, data }) => { onDrop={handleDrop} className={styles.editor} role="application" + aria-label={t("objectEditor.editor")!} tabIndex={0} onKeyDown={(e) => { // Handle keyboard interactions for accessibility diff --git a/GUI/src/i18n/en/common.json b/GUI/src/i18n/en/common.json index 32d47fa7f..0ab5ca218 100644 --- a/GUI/src/i18n/en/common.json +++ b/GUI/src/i18n/en/common.json @@ -475,7 +475,7 @@ "title": "Chat Integration", "input-placeholder": "Enter your message..." }, - "jsonEditor": { + "objectEditor": { "array": "Array", "auto": "Auto", "appendText": "Append", @@ -566,6 +566,7 @@ "examples": "Examples", "default": "Default", "containsInvalidProperties": "Contains invalid properties", - "containsInvalidItems": "Contains invalid items" + "containsInvalidItems": "Contains invalid items", + "editor": "Object Editor" } } diff --git a/GUI/src/i18n/et/common.json b/GUI/src/i18n/et/common.json index 4f4859168..932ee467b 100644 --- a/GUI/src/i18n/et/common.json +++ b/GUI/src/i18n/et/common.json @@ -476,7 +476,7 @@ "title": "Chat Integration", "input-placeholder": "Kirjutage oma sõnum..." }, - "jsonEditor": { + "objectEditor": { "array": "Massiiv", "auto": "Automaatne", "appendText": "Lisa lõppu", @@ -567,6 +567,7 @@ "examples": "Näited", "default": "Vaikimisi", "containsInvalidProperties": "Sisaldab vigaseid omadusi", - "containsInvalidItems": "Sisaldab vigaseid elemente" + "containsInvalidItems": "Sisaldab vigaseid elemente", + "editor": "Objektiredaktor" } } From f35b2ab5d1878c39513db148b0ef499363b2572d Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Mon, 18 Aug 2025 09:22:48 +0300 Subject: [PATCH 87/90] fix(665): fix soinar --- .../AssignBuilder/ObjectEditor.tsx | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx index 81bedf9c7..76a338225 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx @@ -37,6 +37,21 @@ const isValueMatch = (objValue: unknown, value: string): boolean => { ); }; +// Helper function to build path for array elements +const buildArrayPath = (currentPath: string, key: number): string => { + return currentPath ? `${currentPath}[${key}]` : `[${key}]`; +}; + +// Helper function to build path for object elements +const buildObjectPath = (currentPath: string, key: string): string => { + return currentPath ? `${currentPath}.${key}` : String(key); +}; + +// Helper function to build the new path based on collection type +const buildNewPath = (isArray: boolean, currentPath: string, key: string | number): string => { + return isArray ? buildArrayPath(currentPath, key as number) : buildObjectPath(currentPath, key as string); +}; + // Helper function to search for value in a collection const searchInCollection = (collection: object, value: string, currentPath = ""): string | null => { const isArray = Array.isArray(collection); @@ -46,19 +61,7 @@ const searchInCollection = (collection: object, value: string, currentPath = "") : Object.entries(collection).map(([key, value]) => ({ key, value })); for (const { key, value: objValue } of entries) { - let newPath: string; - - if (currentPath) { - if (isArray) { - newPath = `${currentPath}[${key}]`; - } else { - newPath = `${currentPath}.${key}`; - } - } else if (isArray) { - newPath = `[${key}]`; - } else { - newPath = String(key); - } + const newPath = buildNewPath(isArray, currentPath, key); if (isValueMatch(objValue, value)) return newPath; From 3be0fd70ab9ccf379180fca4523cea259b3f6470 Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Mon, 18 Aug 2025 09:27:15 +0300 Subject: [PATCH 88/90] fix(665): fix sonar --- .../AssignBuilder/ObjectEditor.tsx | 47 ++++++++++--------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx index 76a338225..1a9c62bc7 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/ObjectEditor.tsx @@ -37,31 +37,27 @@ const isValueMatch = (objValue: unknown, value: string): boolean => { ); }; -// Helper function to build path for array elements -const buildArrayPath = (currentPath: string, key: number): string => { - return currentPath ? `${currentPath}[${key}]` : `[${key}]`; -}; - -// Helper function to build path for object elements -const buildObjectPath = (currentPath: string, key: string): string => { - return currentPath ? `${currentPath}.${key}` : String(key); -}; +// Helper function to search for value in an array +const searchInArray = (array: unknown[], value: string, currentPath = ""): string | null => { + for (let index = 0; index < array.length; index++) { + const objValue = array[index]; + const newPath = currentPath ? `${currentPath}[${index}]` : `[${index}]`; -// Helper function to build the new path based on collection type -const buildNewPath = (isArray: boolean, currentPath: string, key: string | number): string => { - return isArray ? buildArrayPath(currentPath, key as number) : buildObjectPath(currentPath, key as string); -}; + if (isValueMatch(objValue, value)) return newPath; -// Helper function to search for value in a collection -const searchInCollection = (collection: object, value: string, currentPath = ""): string | null => { - const isArray = Array.isArray(collection); + if (isObject(objValue)) { + const result = searchInCollection(objValue, value, newPath); + if (result) return result; + } + } - const entries = isArray - ? collection.map((value, index) => ({ key: index, value })) - : Object.entries(collection).map(([key, value]) => ({ key, value })); + return null; +}; - for (const { key, value: objValue } of entries) { - const newPath = buildNewPath(isArray, currentPath, key); +// Helper function to search for value in an object +const searchInObject = (obj: Record, value: string, currentPath = ""): string | null => { + for (const [key, objValue] of Object.entries(obj)) { + const newPath = currentPath ? `${currentPath}.${key}` : String(key); if (isValueMatch(objValue, value)) return newPath; @@ -74,6 +70,15 @@ const searchInCollection = (collection: object, value: string, currentPath = "") return null; }; +// Helper function to search for value in a collection +const searchInCollection = (collection: object, value: string, currentPath = ""): string | null => { + if (Array.isArray(collection)) { + return searchInArray(collection, value, currentPath); + } + + return searchInObject(collection as Record, value, currentPath); +}; + interface ObjectEditorProps { onChange: (value: string) => void; data: Record | unknown[]; From 73b8eb7ec975d91751d9ebc32e8011abb15d95ff Mon Sep 17 00:00:00 2001 From: 1AhmedYasser <26207361+1AhmedYasser@users.noreply.github.com> Date: Mon, 18 Aug 2025 10:47:01 +0300 Subject: [PATCH 89/90] chore(686): Enhanced Activate Button --- GUI/src/components/ServicesTable/index.tsx | 45 +++++++++++++++------- GUI/src/store/services.store.ts | 1 + 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/GUI/src/components/ServicesTable/index.tsx b/GUI/src/components/ServicesTable/index.tsx index 6259d0562..b47406842 100644 --- a/GUI/src/components/ServicesTable/index.tsx +++ b/GUI/src/components/ServicesTable/index.tsx @@ -33,6 +33,7 @@ const ServicesTable: FC = ({ isCommon = false }) => { pageSize: 10, }); const [sorting, setSorting] = useState([{ id: "name", desc: false }]); + const [isActivating, setIsActivating] = useState(false); const loadServices = (paginationState: PaginationState, sortingState: SortingState) => { useServiceListStore.getState().loadServicesList(paginationState, sortingState); @@ -96,18 +97,26 @@ const ServicesTable: FC = ({ isCommon = false }) => { ); const changeServiceState = (activate: boolean = false, draft: boolean = false) => { - useServiceListStore.getState().changeServiceState( - () => { - setIsReadyPopupVisible(false); - setIsStatePopupVisible(false); - }, - t("overview.service.toast.updated"), - t("overview.service.toast.failed.state"), - activate, - draft, - pagination, - sorting - ); + useServiceListStore + .getState() + .changeServiceState( + () => { + setIsReadyPopupVisible(false); + setIsStatePopupVisible(false); + }, + t("overview.service.toast.updated"), + t("overview.service.toast.failed.state"), + activate, + draft, + pagination, + sorting + ) + .then(() => { + setIsActivating(false); + }) + .catch(() => { + setIsActivating(false); + }); }; const deleteSelectedService = () => { @@ -154,7 +163,17 @@ const ServicesTable: FC = ({ isCommon = false }) => { const getActiveAndConnectionButton = () => { if (readyPopupText === t("overview.popup.setActive")) { - return ; + return ( + + ); } if (readyPopupText === t("overview.popup.connectionPending")) { return ; diff --git a/GUI/src/store/services.store.ts b/GUI/src/store/services.store.ts index e7c10057c..f32463ffe 100644 --- a/GUI/src/store/services.store.ts +++ b/GUI/src/store/services.store.ts @@ -184,6 +184,7 @@ const useServiceListStore = create((set, get, store) => ({ } catch (error) { console.error(error); useToastStore.getState().error({ title: errorMessage }); + throw error; } set({ selectedService: undefined, From 6eb453e1fbd5280ef8d4be07ad1a471956c66b53 Mon Sep 17 00:00:00 2001 From: Ahmed yasser <26207361+1AhmedYasser@users.noreply.github.com> Date: Tue, 19 Aug 2025 14:12:07 +0300 Subject: [PATCH 90/90] Hot-fix: Form Select Font Size (#690) * fix(0): Fixed Form Select Font Size * chore(0): Removed unused import * fix(0): Fixed Payload keys * fix(0): Assign Element Styles --- .../Endpoints/Custom/index.tsx | 2 +- GUI/src/components/ApiEndpointCard/index.tsx | 1 + .../AssignBuilder/AssignElement.module.scss | 1 - .../AssignBuilder/assignElement.tsx | 22 +++++++++---------- .../FormElements/FormSelect/index.tsx | 3 +-- GUI/src/services/service-builder.ts | 2 +- 6 files changed, 15 insertions(+), 16 deletions(-) diff --git a/GUI/src/components/ApiEndpointCard/Endpoints/Custom/index.tsx b/GUI/src/components/ApiEndpointCard/Endpoints/Custom/index.tsx index f45087c76..8cfd5a507 100644 --- a/GUI/src/components/ApiEndpointCard/Endpoints/Custom/index.tsx +++ b/GUI/src/components/ApiEndpointCard/Endpoints/Custom/index.tsx @@ -72,7 +72,7 @@ const EndpointCustom: React.FC = ({ = ({ placeholder={t("newService.endpoint.type").toString()} options={options} disabled={selectedTab === EndpointEnv.Test} + style={{ fontSize: "15px" }} onSelectionChange={(selection) => { setOption(selection); endpoint.type = selection?.value as EndpointType; diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/AssignElement.module.scss b/GUI/src/components/FlowElementsPopup/AssignBuilder/AssignElement.module.scss index cb7d022b8..0ebd88a29 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/AssignElement.module.scss +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/AssignElement.module.scss @@ -2,5 +2,4 @@ .assignElement { padding: 8px 16px 8px 16px; - border-top: 1px solid get-color(black-coral-6); } diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx index 5e548be75..6b7d397e4 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx @@ -107,8 +107,8 @@ const AssignElement: React.FC = ({ }; return ( -
- + <> + = ({ {slots.length && isObject(slots[0].data) && !isArray(slots[0].data) ? ( { setIsSecondSlotOpen(!isSecondSlotOpen); @@ -171,11 +169,13 @@ const AssignElement: React.FC = ({ )} - -
- } /> -
-
+ {!isEditingManually && ( + +
+ } /> +
+
+ )} {onRemove && (
+ ); }; diff --git a/GUI/src/components/FormElements/FormSelect/index.tsx b/GUI/src/components/FormElements/FormSelect/index.tsx index 15b3ccb62..e809402de 100644 --- a/GUI/src/components/FormElements/FormSelect/index.tsx +++ b/GUI/src/components/FormElements/FormSelect/index.tsx @@ -5,7 +5,6 @@ import { MdArrowDropDown } from 'react-icons/md'; import { Icon } from '../../../components'; import './FormSelect.scss'; -import { t } from 'i18next'; type FormSelectProps = SelectHTMLAttributes & { label: string; @@ -77,7 +76,7 @@ const FormSelect: FC = ({
{selectedItem?.label ?? placeholder} - } /> + } />
    {isOpen && ( diff --git a/GUI/src/services/service-builder.ts b/GUI/src/services/service-builder.ts index 491cbe276..2cea2b51d 100644 --- a/GUI/src/services/service-builder.ts +++ b/GUI/src/services/service-builder.ts @@ -595,7 +595,7 @@ function handleDynamicChoices( service_name: parentNode.data.dynamicChoices?.serviceName ?? "", key: parentNode.data.dynamicChoices?.key ?? "", payload_prefix: "#service, /POST/", - payload_keys: parentNode.data.dynamicChoices?.payloadKeys.split(",") ?? [], + payload_keys: parentNode.data.dynamicChoices?.payloadKeys.split(",").filter((item) => item.trim()) ?? [], }, }, result: "dynamic_choices_res",