Skip to content

Commit 0a1a905

Browse files
Add options for JSON indentation (#219)
* 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 <pranavghorpade61@gmail.com> * fix(settings): correct JSX structure and formatting Signed-off-by: Pranav-0440 <pranavghorpade61@gmail.com> * feat(settings): add configurable JSON indentation (2 or 4 spaces) Signed-off-by: Pranav-0440 <pranavghorpade61@gmail.com> * refactor(settings): move jsonIndentation to context and keep other settings local Signed-off-by: Pranav-0440 <pranavghorpade61@gmail.com> * revert: restore original error handling logic to keep PR scope focused Signed-off-by: Pranav-0440 <pranavghorpade61@gmail.com> * refactor(settings): replace select with Dropdown and align jsonIndentation with context API Signed-off-by: Pranav-0440 <pranavghorpade61@gmail.com> * fix(settings): resolve TypeScript errors and separate jsonIndentation from SettingsData Signed-off-by: Pranav-0440 <pranavghorpade61@gmail.com> * style: fix prettier formatting Signed-off-by: Pranav-0440 <pranavghorpade61@gmail.com> * fix drop use of context to prop drilling Signed-off-by: Ricardo Silva <rephyrus0877@protonmail.com> * fix typescript configurations Signed-off-by: Ricardo Silva <rephyrus0877@protonmail.com> * test update and add tests for Settings Signed-off-by: Ricardo Silva <rephyrus0877@protonmail.com> * test fix, add settings integration tests Signed-off-by: Ricardo Silva <rephyrus0877@protonmail.com> --------- Signed-off-by: Pranav-0440 <pranavghorpade61@gmail.com> Signed-off-by: Ricardo Silva <rephyrus0877@protonmail.com> Co-authored-by: Pranav-0440 <pranavghorpade61@gmail.com>
1 parent d794624 commit 0a1a905

12 files changed

Lines changed: 551 additions & 251 deletions

env.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
declare const APP_VERSION: string;
2+
declare module "*.css";

src/App.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ const App = () => {
5151
const context = useContext(ediTDorContext);
5252

5353
const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null);
54+
const [jsonIndentation, setJsonIndentation] = useState<2 | 4>(2);
5455
const [customBreakpointsState, setCustomBreakpointsState] = useState(0);
5556
const tdViewerRef = useRef<HTMLDivElement>(null);
5657
const [pendingTd, setPendingTd] = useState<string>("");
@@ -229,7 +230,10 @@ const App = () => {
229230

230231
return (
231232
<main className="flex max-h-screen w-screen flex-col">
232-
<AppHeader></AppHeader>
233+
<AppHeader
234+
jsonIndentation={jsonIndentation}
235+
onJsonIndentationChange={setJsonIndentation}
236+
></AppHeader>
233237

234238
<div className="">
235239
<Container className="height-adjust flex flex-col md:flex-row">
@@ -249,7 +253,10 @@ const App = () => {
249253
/>
250254

251255
<Section className="w-full md:w-5/12">
252-
<JsonEditor editorRef={editorRef} />
256+
<JsonEditor
257+
editorRef={editorRef}
258+
jsonIndentation={jsonIndentation}
259+
/>
253260
</Section>
254261
</Container>
255262
</div>

src/components/App/AppHeader.tsx

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,15 @@ const INVALID_TYPE_MESSAGE =
4949
const VALIDATION_FAILED_MESSAGE =
5050
"The Thing Model did not pass the JSON schema validation Please make sure the Thing Model is valid according to the JSON schema before contributing it to the catalog.";
5151

52-
const AppHeader: React.FC = () => {
52+
interface AppHeaderProps {
53+
jsonIndentation: 2 | 4;
54+
onJsonIndentationChange: (value: 2 | 4) => void;
55+
}
56+
57+
const AppHeader: React.FC<AppHeaderProps> = ({
58+
jsonIndentation,
59+
onJsonIndentationChange,
60+
}) => {
5361
const context = useContext(ediTDorContext);
5462
const td: ThingDescription = context.parsedTD;
5563
/** States*/
@@ -109,9 +117,8 @@ const AppHeader: React.FC = () => {
109117
context.updateOfflineTD(res.td);
110118
context.updateIsModified(false);
111119

112-
context.setFileHandle(res.fileHandle || res.fileNname);
113-
context.updateLinkedTd(undefined);
114-
context.addLinkedTd(linkedTd);
120+
context.setFileHandle(res.fileHandle || res.fileName);
121+
context.updateLinkedTd(linkedTd);
115122
} catch (error) {
116123
const msg = "Opening a new TD was canceled or an error occured.";
117124
console.error(msg, error);
@@ -341,7 +348,11 @@ const AppHeader: React.FC = () => {
341348
<ConvertTmDialog ref={convertTmDialog} />
342349
<ShareDialog ref={shareDialog} />
343350
<CreateTdDialog ref={createTdDialog} />
344-
<SettingsDialog ref={settingsDialog} />
351+
<SettingsDialog
352+
ref={settingsDialog}
353+
jsonIndentation={jsonIndentation}
354+
onJsonIndentationChange={onJsonIndentationChange}
355+
/>
345356
<ContributeToCatalog ref={contributeToCatalog} />
346357
<ErrorDialog
347358
isOpen={errorDisplay.state}

src/components/App/Settings.tsx

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import React, { useState, useEffect, useCallback } from "react";
1414
import InfoIconWrapper from "../base/InfoIconWrapper";
1515
import TextField from "../base/TextField";
1616
import { isValidUrl } from "../../utils/strings";
17+
import Dropdown from "../base/Dropdown";
1718

1819
export interface SettingsData {
1920
northboundUrl: string;
@@ -32,6 +33,8 @@ interface SettingsProps {
3233
onChange?: (data: SettingsData, isValid: boolean) => void;
3334
hideTitle?: boolean;
3435
className?: string;
36+
jsonIndentation?: 2 | 4;
37+
onJsonIndentationChange?: (value: 2 | 4) => void;
3538
}
3639

3740
const Settings: React.FC<SettingsProps> = ({
@@ -43,6 +46,8 @@ const Settings: React.FC<SettingsProps> = ({
4346
onChange,
4447
hideTitle = false,
4548
className = "",
49+
jsonIndentation,
50+
onJsonIndentationChange,
4651
}) => {
4752
const [data, setData] = useState<SettingsData>(initialData);
4853
const [errors, setErrors] = useState<SettingsErrors>({
@@ -51,10 +56,6 @@ const Settings: React.FC<SettingsProps> = ({
5156
pathToValue: "",
5257
});
5358

54-
useEffect(() => {
55-
setData(initialData);
56-
}, [initialData]);
57-
5859
useEffect(() => {
5960
if (onChange) {
6061
const isValid =
@@ -127,8 +128,35 @@ const Settings: React.FC<SettingsProps> = ({
127128
[]
128129
);
129130

131+
const handleJsonIndentationChange = useCallback(
132+
(e: React.ChangeEvent<HTMLSelectElement>) => {
133+
if (!onJsonIndentationChange) {
134+
return;
135+
}
136+
137+
const parsed = Number(e.target.value);
138+
const value: 2 | 4 = parsed === 4 ? 4 : 2;
139+
onJsonIndentationChange(value);
140+
},
141+
[onJsonIndentationChange]
142+
);
143+
130144
return (
131145
<div className={className}>
146+
<div className="my-4 rounded-md bg-black bg-opacity-80 p-2">
147+
{!hideTitle && <h1 className="font-bold">JSON Editor</h1>}
148+
<div className="px-4">
149+
<Dropdown
150+
id="json-indentation-select"
151+
label="Space indentation"
152+
value={String(jsonIndentation)}
153+
onChange={handleJsonIndentationChange}
154+
options={["2", "4"]}
155+
className="w-full"
156+
/>
157+
</div>
158+
</div>
159+
132160
<div className="rounded-md bg-black bg-opacity-80 p-2">
133161
{!hideTitle && (
134162
<h1 className="font-bold">Third Party Service Configuration</h1>

src/components/Dialogs/SettingsDialog.tsx

Lines changed: 71 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -21,68 +21,85 @@ export interface SettingsDialogRef {
2121
close: () => void;
2222
}
2323

24-
const SettingsDialog = forwardRef<SettingsDialogRef>((_, ref) => {
25-
const [display, setDisplay] = useState<boolean>(false);
26-
const [settingsData, setSettingsData] = useState<SettingsData>({
27-
northboundUrl: "",
28-
southboundUrl: "",
29-
pathToValue: "/",
30-
});
31-
const [isValid, setIsValid] = useState(true);
24+
interface SettingsDialogProps {
25+
jsonIndentation: 2 | 4;
26+
onJsonIndentationChange: (value: 2 | 4) => void;
27+
}
3228

33-
useImperativeHandle(ref, () => {
34-
return {
35-
openModal: () => open(),
36-
close: () => close(),
37-
};
38-
});
29+
const SettingsDialog = forwardRef<SettingsDialogRef, SettingsDialogProps>(
30+
({ jsonIndentation, onJsonIndentationChange }, ref) => {
31+
const [display, setDisplay] = useState<boolean>(false);
32+
const [settingsData, setSettingsData] = useState<SettingsData>({
33+
northboundUrl: "",
34+
southboundUrl: "",
35+
pathToValue: "/",
36+
});
37+
const [draftJsonIndentation, setDraftJsonIndentation] = useState<2 | 4>(
38+
jsonIndentation
39+
);
40+
const [isValid, setIsValid] = useState(true);
3941

40-
const open = () => {
41-
setDisplay(true);
42-
setSettingsData({
43-
northboundUrl: getLocalStorage("northbound") || "",
44-
southboundUrl: getLocalStorage("southbound") || "",
45-
pathToValue: getLocalStorage("valuePath") || "/",
42+
useImperativeHandle(ref, () => {
43+
return {
44+
openModal: () => open(),
45+
close: () => close(),
46+
};
4647
});
47-
};
4848

49-
const close = async () => {
50-
setDisplay(false);
51-
};
49+
const open = () => {
50+
setDisplay(true);
51+
setSettingsData({
52+
northboundUrl: getLocalStorage("northbound") || "",
53+
southboundUrl: getLocalStorage("southbound") || "",
54+
pathToValue: getLocalStorage("valuePath") || "/",
55+
});
56+
setDraftJsonIndentation(jsonIndentation);
57+
};
5258

53-
const handleSubmit = () => {
54-
if (isValid) {
55-
setLocalStorage(settingsData.northboundUrl, "northbound");
56-
setLocalStorage(settingsData.southboundUrl, "southbound");
57-
setLocalStorage(settingsData.pathToValue, "valuePath");
58-
close();
59-
}
60-
};
59+
const close = async () => {
60+
setDisplay(false);
61+
};
6162

62-
const handleSettingsChange = (data: SettingsData, valid: boolean) => {
63-
setSettingsData(data);
64-
setIsValid(valid);
65-
};
63+
const handleSubmit = () => {
64+
if (isValid) {
65+
setLocalStorage(settingsData.northboundUrl, "northbound");
66+
setLocalStorage(settingsData.southboundUrl, "southbound");
67+
setLocalStorage(settingsData.pathToValue, "valuePath");
68+
onJsonIndentationChange(draftJsonIndentation);
69+
close();
70+
}
71+
};
6672

67-
if (display) {
68-
return ReactDOM.createPortal(
69-
<DialogTemplate
70-
hasSubmit={true}
71-
onHandleEventLeftButton={close}
72-
leftButton={"Cancel"}
73-
rightButton={"Save Changes"}
74-
onHandleEventRightButton={handleSubmit}
75-
title={"Settings"}
76-
description={"Change the ediTDors configuration to your needs"}
77-
>
78-
<Settings initialData={settingsData} onChange={handleSettingsChange} />
79-
</DialogTemplate>,
80-
document.getElementById("modal-root") as HTMLElement
81-
);
82-
}
73+
const handleSettingsChange = (data: SettingsData, valid: boolean) => {
74+
setSettingsData(data);
75+
setIsValid(valid);
76+
};
8377

84-
return null;
85-
});
78+
if (display) {
79+
return ReactDOM.createPortal(
80+
<DialogTemplate
81+
hasSubmit={true}
82+
onHandleEventLeftButton={close}
83+
leftButton={"Cancel"}
84+
rightButton={"Save Changes"}
85+
onHandleEventRightButton={handleSubmit}
86+
title={"Settings"}
87+
description={"Change the ediTDors configuration to your needs"}
88+
>
89+
<Settings
90+
initialData={settingsData}
91+
onChange={handleSettingsChange}
92+
jsonIndentation={draftJsonIndentation}
93+
onJsonIndentationChange={setDraftJsonIndentation}
94+
/>
95+
</DialogTemplate>,
96+
document.getElementById("modal-root") as HTMLElement
97+
);
98+
}
99+
100+
return null;
101+
}
102+
);
86103

87104
SettingsDialog.displayName = "SettingsDialog";
88105
export default SettingsDialog;

0 commit comments

Comments
 (0)