From 7fb747a2b2854b1ff3b8ae1825a35bbf1eb7267d Mon Sep 17 00:00:00 2001 From: Pranav-0440 Date: Sat, 14 Feb 2026 00:19:17 +0530 Subject: [PATCH 1/8] feat(settings): add JSON editor indentation option (2 or 4 spaces) - Added new 'JSON Editor' section in Settings - Introduced jsonIndentation (2 | 4) in SettingsData - Connected indentation setting to Monaco editor tabSize - Auto-format JSON using selected indentation - Prevent infinite formatting loop - Apply changes immediately after saving settings Closes #185 Signed-off-by: Pranav-0440 --- src/components/App/Settings.tsx | 32 ++++++++++++++++++++++++++++ src/components/Editor/JsonEditor.tsx | 26 +++++++++++++++------- 2 files changed, 50 insertions(+), 8 deletions(-) diff --git a/src/components/App/Settings.tsx b/src/components/App/Settings.tsx index 875708e9..553a4285 100644 --- a/src/components/App/Settings.tsx +++ b/src/components/App/Settings.tsx @@ -19,6 +19,7 @@ export interface SettingsData { northboundUrl: string; southboundUrl: string; pathToValue: string; + jsonIndentation: 2 | 4; } export interface SettingsErrors { @@ -39,6 +40,7 @@ const Settings: React.FC = ({ northboundUrl: "", southboundUrl: "", pathToValue: "/", + jsonIndentation: 2, }, onChange, hideTitle = false, @@ -127,6 +129,36 @@ const Settings: React.FC = ({ [] ); + const handleJsonIndentationChange = useCallback( + (e: React.ChangeEvent) => { + const parsed = Number(e.target.value); + const value: 2 | 4 = parsed === 4 ? 4 : 2; + setData((prev) => ({ ...prev, jsonIndentation: value })); + }, + [] + ); + +
+

JSON Editor

+
+ + +
+
; + return (
diff --git a/src/components/Editor/JsonEditor.tsx b/src/components/Editor/JsonEditor.tsx index b207654a..2a926d98 100644 --- a/src/components/Editor/JsonEditor.tsx +++ b/src/components/Editor/JsonEditor.tsx @@ -27,11 +27,6 @@ import { IValidationMessage } from "../../types/context"; type SchemaMapMessage = Map>; // List of all Options can be found here: https://microsoft.github.io/monaco-editor/docs.html#interfaces/editor.IStandaloneEditorConstructionOptions.html -const editorOptions: editor.IStandaloneEditorConstructionOptions = { - selectOnLineNumbers: true, - automaticLayout: true, - lineDecorationsWidth: 20, -}; // delay function that executes the callback once it hasn't been called for // at least x ms. @@ -52,6 +47,7 @@ interface JsonSchemaEntry { const JsonEditor: React.FC = ({ editorRef }) => { const context = useContext(ediTDorContext); + const jsonIndentation = context.settings?.jsonIndentation ?? 2; const [schemas] = useState([]); const [proxy, setProxy] = useState(undefined); const editorInstance = useRef(null); @@ -83,6 +79,8 @@ const JsonEditor: React.FC = ({ editorRef }) => { ); useEffect(() => { + if (!proxy) return; + const updateMonacoSchemas = (schemaMap: SchemaMapMessage) => { proxy.splice(0, proxy.length); @@ -198,8 +196,14 @@ const JsonEditor: React.FC = ({ editorRef }) => { customMessage: "", }; try { - JSON.parse(editorText); - context.updateOfflineTD(editorText); + const parsed = JSON.parse(editorText); + const formatted = JSON.stringify(parsed, null, jsonIndentation); + + if (formatted !== editorText) { + context.updateOfflineTD(formatted); + } else { + context.updateOfflineTD(editorText); + } context.updateValidationMessage(validate); } catch (error) { @@ -269,7 +273,13 @@ const JsonEditor: React.FC = ({ editorRef }) => {
Date: Sat, 14 Feb 2026 00:47:29 +0530 Subject: [PATCH 2/8] fix(settings): correct JSX structure and formatting Signed-off-by: Pranav-0440 --- src/components/App/Settings.tsx | 42 ++++++++++++++-------------- src/components/Editor/JsonEditor.tsx | 3 ++ 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/src/components/App/Settings.tsx b/src/components/App/Settings.tsx index 553a4285..38281e56 100644 --- a/src/components/App/Settings.tsx +++ b/src/components/App/Settings.tsx @@ -138,29 +138,29 @@ const Settings: React.FC = ({ [] ); -
-

JSON Editor

-
- - -
-
; - return (
+
+ {!hideTitle &&

JSON Editor

} +
+ + +
+
+
{!hideTitle && (

Third Party Service Configuration

diff --git a/src/components/Editor/JsonEditor.tsx b/src/components/Editor/JsonEditor.tsx index 2a926d98..ef7c5277 100644 --- a/src/components/Editor/JsonEditor.tsx +++ b/src/components/Editor/JsonEditor.tsx @@ -201,8 +201,10 @@ const JsonEditor: React.FC = ({ editorRef }) => { if (formatted !== editorText) { context.updateOfflineTD(formatted); + setLocalTextState(formatted); } else { context.updateOfflineTD(editorText); + setLocalTextState(editorText); } context.updateValidationMessage(validate); @@ -279,6 +281,7 @@ const JsonEditor: React.FC = ({ editorRef }) => { lineDecorationsWidth: 20, tabSize: jsonIndentation, insertSpaces: true, + detectIndentation: false, }} theme={"vs-" + "dark"} language="json" From 7236487c85494dc220e752fc03ee7ef84aacc609 Mon Sep 17 00:00:00 2001 From: Pranav-0440 Date: Mon, 23 Feb 2026 20:15:12 +0530 Subject: [PATCH 3/8] feat(settings): add configurable JSON indentation (2 or 4 spaces) Signed-off-by: Pranav-0440 --- src/components/Editor/JsonEditor.tsx | 59 +++++++++++++++++----------- src/context/GlobalState.tsx | 16 ++++++++ src/context/editorReducers.ts | 13 ++++++ src/types/context.d.ts | 5 ++- 4 files changed, 69 insertions(+), 24 deletions(-) diff --git a/src/components/Editor/JsonEditor.tsx b/src/components/Editor/JsonEditor.tsx index ef7c5277..9b2093de 100644 --- a/src/components/Editor/JsonEditor.tsx +++ b/src/components/Editor/JsonEditor.tsx @@ -161,10 +161,8 @@ const JsonEditor: React.FC = ({ editorRef }) => { setProxy(proxy); }; - const onChange: OnChange = async (editorText: string | undefined) => { - if (!editorText) { - return; - } + const onChange: OnChange = (editorText: string | undefined) => { + if (!editorText) return; let validate: IValidationMessage = { report: { json: null, @@ -195,27 +193,24 @@ const JsonEditor: React.FC = ({ editorRef }) => { }, customMessage: "", }; + try { - const parsed = JSON.parse(editorText); - const formatted = JSON.stringify(parsed, null, jsonIndentation); + JSON.parse(editorText); - if (formatted !== editorText) { - context.updateOfflineTD(formatted); - setLocalTextState(formatted); - } else { + if (editorText !== context.offlineTD) { context.updateOfflineTD(editorText); - setLocalTextState(editorText); } + setLocalTextState(editorText); context.updateValidationMessage(validate); + + delay(messageWorkers, editorText, 500); } catch (error) { - let message: string = - "Invalid JSON: " + - (error instanceof Error ? error.message : String(error)); validate.report.json = "failed"; context.updateValidationMessage(validate); setLocalTextState(editorText); - delay(messageWorkers, editorText ?? "", 500); + + delay(messageWorkers, editorText, 500); } }; @@ -246,6 +241,19 @@ const JsonEditor: React.FC = ({ editorRef }) => { } }, [context.linkedTd, context.offlineTD]); + useEffect(() => { + try { + const parsed = JSON.parse(context.offlineTD); + const formatted = JSON.stringify(parsed, null, jsonIndentation); + + if (formatted !== context.offlineTD) { + context.updateOfflineTD(formatted); + } + } catch { + // ignore invalid JSON + } + }, [jsonIndentation, context.offlineTD]); + const changeLinkedTd = async () => { let href = (document.getElementById("linkedTd") as HTMLSelectElement).value; changeBetweenTd(context, href); @@ -259,6 +267,18 @@ const JsonEditor: React.FC = ({ editorRef }) => { }); }; + const editorOptions = useMemo( + () => ({ + selectOnLineNumbers: true, + automaticLayout: true, + lineDecorationsWidth: 20, + tabSize: jsonIndentation, + insertSpaces: true, + detectIndentation: false, + }), + [jsonIndentation] + ); + return ( <>
@@ -275,14 +295,7 @@ const JsonEditor: React.FC = ({ editorRef }) => {
= ({ children }) => { nameRepository: "", dynamicValues: {}, }, + settings: { + northboundUrl: "", + southboundUrl: "", + pathToValue: "/", + jsonIndentation: 2, + }, }); const updateOfflineTD = (offlineTD: string) => { @@ -177,6 +184,13 @@ const GlobalState: React.FC = ({ children }) => { }); }; + const updateSettings = (settings: SettingsData) => { + dispatch({ + type: UPDATE_SETTINGS, + settings, + }); + }; + return ( = ({ children }) => { validationMessage: editdorState.validationMessage, northboundConnection: editdorState.northboundConnection, contributeCatalog: editdorState.contributeCatalog, + settings: editdorState.settings, + updateSettings, updateOfflineTD, updateIsModified, setFileHandle, diff --git a/src/context/editorReducers.ts b/src/context/editorReducers.ts index 35e5bfb5..fb146df2 100644 --- a/src/context/editorReducers.ts +++ b/src/context/editorReducers.ts @@ -10,6 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 OR W3C-20150513 ********************************************************************************/ +import { SettingsData } from "../components/App/Settings"; import { ADD_FORM_TO_TD, ADD_LINKED_TD, @@ -23,6 +24,7 @@ import { UPDATE_VALIDATION_MESSAGE, UPDATE_NORTHBOUND_CONNECTION, UPDATE_CONTRIBUTE_CATALOG, + UPDATE_SETTINGS, } from "./GlobalState"; import type { ThingDescription, @@ -70,6 +72,8 @@ export const editdorReducer = ( return updateNorthboundConnection(action.northboundConnection, state); case UPDATE_CONTRIBUTE_CATALOG: return updateContributeCatalog(action.contributeCatalog, state); + case UPDATE_SETTINGS: + return updateSettingsReducer(action.settings, state); default: return state; } @@ -365,3 +369,12 @@ const updateContributeCatalog = ( ): EditorState => { return { ...state, contributeCatalog }; }; +const updateSettingsReducer = ( + settings: SettingsData, + state: EditorState +): EditorState => { + return { + ...state, + settings, + }; +}; diff --git a/src/types/context.d.ts b/src/types/context.d.ts index 48154da3..6fef2bfd 100644 --- a/src/types/context.d.ts +++ b/src/types/context.d.ts @@ -13,6 +13,7 @@ declare global { northboundConnection: INorthboundConnection; contributeCatalog: IContributeCatalog; + settings: SettingsData; // Callback functions updateOfflineTD: (td: string) => void; @@ -141,7 +142,8 @@ declare global { | { type: "UPDATE_CONTRIBUTE_CATALOG"; contributeCatalog: IContributeCatalog; - }; + } + | { type: "UPDATE_SETTINGS"; settings: SettingsData }; declare type Validation = "VALID" | "INVALID" | "VALIDATING" | null; declare type ActiveSection = @@ -157,6 +159,7 @@ declare global { northboundUrl: string; southboundUrl: string; pathToValue: string; + jsonIndentation: 2 | 4; } // Define the shape of the state From 10ebb531861d174be028e68916a3bced84b14e7f Mon Sep 17 00:00:00 2001 From: Pranav-0440 Date: Wed, 25 Feb 2026 18:42:44 +0530 Subject: [PATCH 4/8] refactor(settings): move jsonIndentation to context and keep other settings local Signed-off-by: Pranav-0440 --- src/components/App/Settings.tsx | 14 ++++++++++---- src/components/Editor/JsonEditor.tsx | 7 ------- src/context/GlobalState.tsx | 19 +++++++------------ src/context/editorReducers.ts | 13 ++++++------- src/types/context.d.ts | 5 +++-- 5 files changed, 26 insertions(+), 32 deletions(-) diff --git a/src/components/App/Settings.tsx b/src/components/App/Settings.tsx index 38281e56..79d95278 100644 --- a/src/components/App/Settings.tsx +++ b/src/components/App/Settings.tsx @@ -10,10 +10,11 @@ * * SPDX-License-Identifier: EPL-2.0 OR W3C-20150513 ********************************************************************************/ -import React, { useState, useEffect, useCallback } from "react"; +import React, { useState, useEffect, useCallback, useContext } from "react"; import InfoIconWrapper from "../base/InfoIconWrapper"; import TextField from "../base/TextField"; import { isValidUrl } from "../../utils/strings"; +import EdiTDorContext from "../../context/ediTDorContext"; export interface SettingsData { northboundUrl: string; @@ -46,6 +47,7 @@ const Settings: React.FC = ({ hideTitle = false, className = "", }) => { + const context = useContext(EdiTDorContext); const [data, setData] = useState(initialData); const [errors, setErrors] = useState({ northboundUrl: "", @@ -54,8 +56,11 @@ const Settings: React.FC = ({ }); useEffect(() => { - setData(initialData); - }, [initialData]); + setData((prev) => ({ + ...prev, + jsonIndentation: context.jsonIndentation, + })); + }, [context.jsonIndentation]); useEffect(() => { if (onChange) { @@ -134,8 +139,9 @@ const Settings: React.FC = ({ const parsed = Number(e.target.value); const value: 2 | 4 = parsed === 4 ? 4 : 2; setData((prev) => ({ ...prev, jsonIndentation: value })); + context.updateJsonIndentation(value); }, - [] + [context] ); return ( diff --git a/src/components/Editor/JsonEditor.tsx b/src/components/Editor/JsonEditor.tsx index 06b1fec7..9b2093de 100644 --- a/src/components/Editor/JsonEditor.tsx +++ b/src/components/Editor/JsonEditor.tsx @@ -27,13 +27,6 @@ import { IValidationMessage } from "../../types/context"; type SchemaMapMessage = Map>; // List of all Options can be found here: https://microsoft.github.io/monaco-editor/docs.html#interfaces/editor.IStandaloneEditorConstructionOptions.html -const editorOptions: editor.IStandaloneEditorConstructionOptions = { - selectOnLineNumbers: true, - automaticLayout: true, - lineDecorationsWidth: 20, - tabSize: 2, - insertSpaces: true, -}; // delay function that executes the callback once it hasn't been called for // at least x ms. diff --git a/src/context/GlobalState.tsx b/src/context/GlobalState.tsx index 4594a5bc..c76d8eec 100644 --- a/src/context/GlobalState.tsx +++ b/src/context/GlobalState.tsx @@ -28,7 +28,7 @@ export const UPDATE_VALIDATION_MESSAGE = "UPDATE_VALIDATION_MESSAGE"; export const UPDATE_NORTHBOUND_CONNECTION = "UPDATE_NORTHBOUND_CONNECTION"; export const UPDATE_CONTRIBUTE_CATALOG = "UPDATE_CONTRIBUTE_CATALOG"; export const UPDATE_BACKGROUND_TM = "UPDATE_BACKGROUND_TM"; -export const UPDATE_SETTINGS = "UPDATE_SETTINGS"; +export const UPDATE_JSON_INDENTATION = "UPDATE_JSON_INDENTATION"; interface IGlobalStateProps { children: ReactNode; @@ -94,12 +94,7 @@ const GlobalState: React.FC = ({ children }) => { nameRepository: "", dynamicValues: {}, }, - settings: { - northboundUrl: "", - southboundUrl: "", - pathToValue: "/", - jsonIndentation: 2, - }, + jsonIndentation: 2, }); const updateOfflineTD = (offlineTD: string) => { @@ -184,10 +179,10 @@ const GlobalState: React.FC = ({ children }) => { }); }; - const updateSettings = (settings: SettingsData) => { + const updateJsonIndentation = (value: 2 | 4) => { dispatch({ - type: UPDATE_SETTINGS, - settings, + type: UPDATE_JSON_INDENTATION, + value, }); }; @@ -204,8 +199,8 @@ const GlobalState: React.FC = ({ children }) => { validationMessage: editdorState.validationMessage, northboundConnection: editdorState.northboundConnection, contributeCatalog: editdorState.contributeCatalog, - settings: editdorState.settings, - updateSettings, + jsonIndentation: editdorState.jsonIndentation, + updateJsonIndentation, updateOfflineTD, updateIsModified, setFileHandle, diff --git a/src/context/editorReducers.ts b/src/context/editorReducers.ts index fb146df2..cda01f2e 100644 --- a/src/context/editorReducers.ts +++ b/src/context/editorReducers.ts @@ -10,7 +10,6 @@ * * SPDX-License-Identifier: EPL-2.0 OR W3C-20150513 ********************************************************************************/ -import { SettingsData } from "../components/App/Settings"; import { ADD_FORM_TO_TD, ADD_LINKED_TD, @@ -24,7 +23,7 @@ import { UPDATE_VALIDATION_MESSAGE, UPDATE_NORTHBOUND_CONNECTION, UPDATE_CONTRIBUTE_CATALOG, - UPDATE_SETTINGS, + UPDATE_JSON_INDENTATION, } from "./GlobalState"; import type { ThingDescription, @@ -72,8 +71,8 @@ export const editdorReducer = ( return updateNorthboundConnection(action.northboundConnection, state); case UPDATE_CONTRIBUTE_CATALOG: return updateContributeCatalog(action.contributeCatalog, state); - case UPDATE_SETTINGS: - return updateSettingsReducer(action.settings, state); + case UPDATE_JSON_INDENTATION: + return updateJsonIndentationReducer(action.value, state); default: return state; } @@ -369,12 +368,12 @@ const updateContributeCatalog = ( ): EditorState => { return { ...state, contributeCatalog }; }; -const updateSettingsReducer = ( - settings: SettingsData, +const updateJsonIndentationReducer = ( + jsonIndentation: 2 | 4, state: EditorState ): EditorState => { return { ...state, - settings, + jsonIndentation, }; }; diff --git a/src/types/context.d.ts b/src/types/context.d.ts index 6fef2bfd..9bf04717 100644 --- a/src/types/context.d.ts +++ b/src/types/context.d.ts @@ -13,7 +13,8 @@ declare global { northboundConnection: INorthboundConnection; contributeCatalog: IContributeCatalog; - settings: SettingsData; + jsonIndentation: 2 | 4; + updateJsonIndentation: (value: 2 | 4) => void; // Callback functions updateOfflineTD: (td: string) => void; @@ -143,7 +144,7 @@ declare global { type: "UPDATE_CONTRIBUTE_CATALOG"; contributeCatalog: IContributeCatalog; } - | { type: "UPDATE_SETTINGS"; settings: SettingsData }; + | { type: "UPDATE_JSON_INDENTATION"; value: 2 | 4 }; declare type Validation = "VALID" | "INVALID" | "VALIDATING" | null; declare type ActiveSection = From 5fc053a418ed63d89f7cbefbfa63dff105124b98 Mon Sep 17 00:00:00 2001 From: Pranav-0440 Date: Wed, 25 Feb 2026 22:00:11 +0530 Subject: [PATCH 5/8] revert: restore original error handling logic to keep PR scope focused Signed-off-by: Pranav-0440 --- src/components/Editor/JsonEditor.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/Editor/JsonEditor.tsx b/src/components/Editor/JsonEditor.tsx index 9b2093de..21f30e49 100644 --- a/src/components/Editor/JsonEditor.tsx +++ b/src/components/Editor/JsonEditor.tsx @@ -209,8 +209,7 @@ const JsonEditor: React.FC = ({ editorRef }) => { validate.report.json = "failed"; context.updateValidationMessage(validate); setLocalTextState(editorText); - - delay(messageWorkers, editorText, 500); + delay(messageWorkers, editorText ?? "", 500); } }; From a5896c8af751f0a47af461579118b878cf7e9152 Mon Sep 17 00:00:00 2001 From: Pranav-0440 Date: Fri, 27 Feb 2026 23:30:13 +0530 Subject: [PATCH 6/8] refactor(settings): replace select with Dropdown and align jsonIndentation with context API Signed-off-by: Pranav-0440 --- src/components/App/Settings.tsx | 20 +++++++------------- src/components/Editor/JsonEditor.tsx | 2 +- src/components/base/Dropdown.tsx | 4 ++++ 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/components/App/Settings.tsx b/src/components/App/Settings.tsx index 79d95278..a90c00cb 100644 --- a/src/components/App/Settings.tsx +++ b/src/components/App/Settings.tsx @@ -15,6 +15,7 @@ import InfoIconWrapper from "../base/InfoIconWrapper"; import TextField from "../base/TextField"; import { isValidUrl } from "../../utils/strings"; import EdiTDorContext from "../../context/ediTDorContext"; +import Dropdown from "../base/Dropdown"; export interface SettingsData { northboundUrl: string; @@ -149,21 +150,14 @@ const Settings: React.FC = ({
{!hideTitle &&

JSON Editor

}
- - + options={["2", "4"]} + className="w-full" + />
diff --git a/src/components/Editor/JsonEditor.tsx b/src/components/Editor/JsonEditor.tsx index 21f30e49..4ba90f28 100644 --- a/src/components/Editor/JsonEditor.tsx +++ b/src/components/Editor/JsonEditor.tsx @@ -47,7 +47,7 @@ interface JsonSchemaEntry { const JsonEditor: React.FC = ({ editorRef }) => { const context = useContext(ediTDorContext); - const jsonIndentation = context.settings?.jsonIndentation ?? 2; + const jsonIndentation = context.jsonIndentation ?? 2; const [schemas] = useState([]); const [proxy, setProxy] = useState(undefined); const editorInstance = useRef(null); diff --git a/src/components/base/Dropdown.tsx b/src/components/base/Dropdown.tsx index 74d2fe5f..34c04f35 100644 --- a/src/components/base/Dropdown.tsx +++ b/src/components/base/Dropdown.tsx @@ -17,6 +17,8 @@ interface IDropdownProps { id: string; label: string; options: string[]; + value?: string; + onChange?: (event: React.ChangeEvent) => void; className?: string; } @@ -33,6 +35,8 @@ const Dropdown: React.FC = (props) => {