Skip to content

Commit 8d2fc97

Browse files
TejInacoegekorkan
andauthored
Add version validation when the user converts a Tm to Td (#162)
* fix access to key title when covertion to Td Signed-off-by: Ricardo M G da Silva <[email protected]> * feat add validation logic and input field for instance Signed-off-by: Ricardo M G da Silva <[email protected]> * fix conversationTMtoTD unit tests Signed-off-by: Ricardo M G da Silva <[email protected]> * feat add version feature unit tests Signed-off-by: Ricardo M G da Silva <[email protected]> * up the version (#161) * fix cursor jump to the eof (#164) Signed-off-by: Ricardo M G da Silva <[email protected]> * fix change label and a field for helper text int he base component Signed-off-by: Ricardo M G da Silva <[email protected]> --------- Signed-off-by: Ricardo M G da Silva <[email protected]> Co-authored-by: Ege Korkan <[email protected]>
1 parent d40d88b commit 8d2fc97

File tree

10 files changed

+6879
-8037
lines changed

10 files changed

+6879
-8037
lines changed

src/components/Dialogs/ConvertTmDialog.tsx

Lines changed: 50 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -23,29 +23,38 @@ import DialogTemplate from "./DialogTemplate";
2323
import {
2424
processConversionTMtoTD,
2525
extractPlaceholders,
26+
isVersionValid,
2627
} from "../../services/operations";
2728
import TmInputForm from "../App/TmInputForm";
29+
import DialogTextField from "./base/DialogTextField";
30+
import type { IEdiTDorContext } from "../../types/context";
2831

2932
export interface ConvertTmDialogRef {
3033
openModal: () => void;
3134
close: () => void;
3235
}
3336

3437
const ConvertTmDialog = forwardRef<ConvertTmDialogRef>((props, ref) => {
35-
const context = useContext(ediTDorContext);
36-
const td = context.offlineTD;
37-
const [htmlInputs, setHtmlInputs] = React.useState<JSX.Element[]>([]);
38-
const [display, setDisplay] = React.useState<boolean>(() => {
38+
const context: IEdiTDorContext = useContext(ediTDorContext);
39+
const td: string = context.offlineTD;
40+
const [htmlInputs, setHtmlInputs] = useState<JSX.Element[]>([]);
41+
const [display, setDisplay] = useState<boolean>(() => {
3942
return false;
4043
});
41-
4244
const [affordanceElements, setAffordanceElements] = useState<JSX.Element[]>(
4345
[]
4446
);
4547
const [placeholderValues, setPlaceholderValues] = useState<
4648
Record<string, string>
4749
>({});
4850

51+
const [validVersion, setValidVersion] = useState<boolean>(false);
52+
const [versionInput, setVersionInput] = useState<string>("");
53+
54+
useEffect(() => {
55+
setValidVersion(isVersionValid(context.parsedTD));
56+
}, [context.parsedTD]);
57+
4958
useEffect(() => {
5059
setHtmlInputs(createHtmlInputs(context.offlineTD));
5160
setAffordanceElements(createAffordanceElements(context.offlineTD));
@@ -54,10 +63,13 @@ const ConvertTmDialog = forwardRef<ConvertTmDialogRef>((props, ref) => {
5463
useEffect(() => {
5564
if (td) {
5665
const placeholders = extractPlaceholders(td);
57-
const initialValues = placeholders.reduce((acc, key) => {
58-
acc[key] = "";
59-
return acc;
60-
}, {});
66+
const initialValues = placeholders.reduce<Record<string, string>>(
67+
(acc, key) => {
68+
acc[key] = "";
69+
return acc;
70+
},
71+
{}
72+
);
6173
setPlaceholderValues(initialValues);
6274
}
6375
}, [td]);
@@ -81,7 +93,8 @@ const ConvertTmDialog = forwardRef<ConvertTmDialogRef>((props, ref) => {
8193
placeholderValues,
8294
selections.properties,
8395
selections.actions,
84-
selections.events
96+
selections.events,
97+
versionInput
8598
);
8699
const resultJson = JSON.stringify(newTD, null, 2);
87100
localStorage.setItem("td", resultJson);
@@ -91,6 +104,14 @@ const ConvertTmDialog = forwardRef<ConvertTmDialogRef>((props, ref) => {
91104
);
92105
};
93106

107+
const handleVersionInputChange = (
108+
e: React.ChangeEvent<HTMLInputElement>
109+
): void => {
110+
const value = e.target.value;
111+
const trimmedValue = value.trim();
112+
setVersionInput(trimmedValue);
113+
};
114+
94115
if (!display) return null;
95116

96117
if (display) {
@@ -108,6 +129,17 @@ const ConvertTmDialog = forwardRef<ConvertTmDialogRef>((props, ref) => {
108129
onValueChange={handleFieldChange}
109130
/>
110131

132+
{!validVersion && (
133+
<DialogTextField
134+
label="TD instance version"
135+
id="instance"
136+
autoFocus={true}
137+
onChange={handleVersionInputChange}
138+
placeholder="ex: 1.0.0"
139+
value={versionInput}
140+
helperText="The Thing Model contains a version without instance key and corresponding value. If you leave this field empty it will automatic generate a instance value."
141+
></DialogTextField>
142+
)}
111143
<h2 className="pb-2 pt-4 text-gray-400">
112144
Select/unselect the interaction affordances you would like to see in
113145
the new TD.
@@ -134,9 +166,9 @@ function getSelectedAffordances(elements: JSX.Element[]) {
134166
if (element.props.className.includes("form-checkbox")) {
135167
const checkbox = document.getElementById(
136168
element.props.children[0].props.id
137-
) as HTMLInputElement;
169+
) as HTMLInputElement | null;
138170
if (checkbox?.checked) {
139-
const [type, name] = element.key.toString().split("/");
171+
const [type, name] = element.key?.toString().split("/") ?? [];
140172

141173
if (type === "properties") result.properties.push(name);
142174
else if (type === "actions") result.actions.push(name);
@@ -191,17 +223,6 @@ const createHtmlInputs = (td: string): JSX.Element[] => {
191223
const { properties, actions, events, requiredFields } =
192224
extractAffordances(parsed);
193225

194-
if (parsed["tm:required"]) {
195-
for (const field of parsed["tm:required"]) {
196-
if (field.startsWith("#properties/"))
197-
requiredFields["properties"].push(field.split("/")[1]);
198-
else if (field.startsWith("#actions/"))
199-
requiredFields["actions"].push(field.split("/")[1]);
200-
else if (field.startsWith("#events/"))
201-
requiredFields["events"].push(field.split("/")[1]);
202-
}
203-
}
204-
205226
htmlProperties = createAffordanceHtml(
206227
"properties",
207228
properties,
@@ -213,12 +234,12 @@ const createHtmlInputs = (td: string): JSX.Element[] => {
213234

214235
return [...htmlProperties, ...htmlActions, ...htmlEvents];
215236
} catch (e) {
216-
console.log(e);
237+
console.error("Error creating HTML inputs:", e);
217238
return [];
218239
}
219240
};
220241

221-
export function createAffordanceHtml(
242+
function createAffordanceHtml(
222243
affName: "properties" | "actions" | "events",
223244
affContainer: string[],
224245
requiredFields: { [k: string]: string[] }
@@ -246,7 +267,7 @@ export function createAffordanceHtml(
246267
});
247268
}
248269

249-
export function extractAffordances(parsed: any) {
270+
function extractAffordances(parsed: any) {
250271
const properties = Object.keys(parsed["properties"] || {});
251272
const actions = Object.keys(parsed["actions"] || {});
252273
const events = Object.keys(parsed["events"] || {});
@@ -255,10 +276,13 @@ export function extractAffordances(parsed: any) {
255276
if (parsed["tm:required"]) {
256277
for (const field of parsed["tm:required"]) {
257278
if (field.startsWith("#properties/"))
279+
// @ts-ignore
258280
requiredFields["properties"].push(field.split("/")[1]);
259281
else if (field.startsWith("#actions/"))
282+
// @ts-ignore
260283
requiredFields["actions"].push(field.split("/")[1]);
261284
else if (field.startsWith("#events/"))
285+
// @ts-ignore
262286
requiredFields["events"].push(field.split("/")[1]);
263287
}
264288
}

src/components/Dialogs/base/DialogTextField.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ interface IDialogTextFieldProps {
2121
autoFocus?: boolean;
2222
className?: string;
2323
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
24+
helperText?: string | ReactNode;
2425
}
2526

2627
const DialogTextField: React.FC<IDialogTextFieldProps> = (props) => {
@@ -32,6 +33,9 @@ const DialogTextField: React.FC<IDialogTextFieldProps> = (props) => {
3233
>
3334
{props.label}
3435
</label>
36+
{props.helperText && (
37+
<div className="pb-1 pl-10 text-sm text-white">{props.helperText}</div>
38+
)}
3539
<input
3640
name={props.id}
3741
id={props.id}

src/context/ediTDorContext.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
* SPDX-License-Identifier: EPL-2.0 OR W3C-20150513
1212
********************************************************************************/
1313
import React from "react";
14+
import type { IEdiTDorContext } from "../types/context";
1415
// Default
1516
const defaultContext: IEdiTDorContext = {
1617
offlineTD: "",

src/context/editorReducers.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ const updateOfflineTDReducer = (
110110
let linkedTd = state.linkedTd;
111111
if (!linkedTd) {
112112
// If the user writes a Thing description without the wizard, we save it in linkedTd
113-
const href = parsedTD["title"] || "ediTDor Thing";
113+
const href = parsedTD?.title ?? "ediTDor Thing";
114114
linkedTd = { [href]: parsedTD };
115115
} else if (linkedTd && typeof state.fileHandle !== "object") {
116116
if (document.getElementById("linkedTd")) {
@@ -119,7 +119,7 @@ const updateOfflineTDReducer = (
119119
) as HTMLInputElement | null;
120120
const href = linkedTdElement?.value || "";
121121
if (href === "") {
122-
linkedTd[parsedTD["title"] || "ediTDor Thing"] = parsedTD;
122+
linkedTd[parsedTD?.title || "ediTDor Thing"] = parsedTD;
123123
} else {
124124
linkedTd[href] = parsedTD;
125125
}

0 commit comments

Comments
 (0)