Skip to content

Commit d234eff

Browse files
committed
Add imdi viewers for consent and project documents
1 parent 73e122e commit d234eff

10 files changed

Lines changed: 561 additions & 251 deletions

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"main": "dist/src/mainProcess/main.js",
33
"name": "lameta",
44
"productName": "lameta",
5-
"version": "3.0.10-alpha",
5+
"version": "3.0.11-alpha",
66
"author": {
77
"name": "lameta",
88
"email": "sorryno@email.org"

src/components/ImdiView.tsx

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -116,11 +116,6 @@ export const ImdiView: React.FunctionComponent<{
116116
<br />
117117
</>
118118
)}
119-
{validationResult?.valid && (
120-
<Alert severity="success">
121-
<Trans>This XML conforms to the IMDI schema.</Trans> ({schemaName})
122-
</Alert>
123-
)}
124119
{validationResult && !validationResult.valid && (
125120
<Alert severity="error">
126121
<div
@@ -151,6 +146,28 @@ export const ImdiView: React.FunctionComponent<{
151146
content={imdi}
152147
language="xml"
153148
autoFocusSearch={true}
149+
headerContent={
150+
validationResult?.valid && (
151+
<Alert
152+
severity="success"
153+
css={css`
154+
padding: 0 8px;
155+
background-color: transparent;
156+
color: inherit;
157+
.MuiAlert-icon {
158+
padding: 0;
159+
margin-right: 6px;
160+
}
161+
.MuiAlert-message {
162+
padding: 0;
163+
}
164+
`}
165+
>
166+
<Trans>This XML conforms to the IMDI schema.</Trans> ({schemaName}
167+
)
168+
</Alert>
169+
)
170+
}
154171
/>
155172
</div>
156173
);

src/components/SearchableCodeViewer.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ export const SearchableCodeViewer: React.FC<SearchableCodeViewerProps> = ({
235235
// Custom style for syntax highlighter - lameta theme
236236
// lameta_orange (#e69664) for XML elements/tags
237237
// lameta_green (#cff09f) for attributes
238-
// lameta_blue (#becde4) for links
238+
// lameta_blue (#becde4) for attribute values and links
239239
// white for text content
240240
const syntaxStyle: { [key: string]: React.CSSProperties } = {
241241
hljs: {
@@ -248,7 +248,7 @@ export const SearchableCodeViewer: React.FC<SearchableCodeViewerProps> = ({
248248
"hljs-tag": { color: "#e69664" }, // lameta_orange
249249
"hljs-name": { color: "#e69664" }, // lameta_orange
250250
"hljs-attr": { color: "#cff09f" }, // lameta_green
251-
"hljs-string": { color: "white" },
251+
"hljs-string": { color: "#becde4" }, // lameta_blue - attribute values
252252
"hljs-number": { color: "#becde4" }, // lameta_blue
253253
"hljs-literal": { color: "#becde4" }, // lameta_blue
254254
"hljs-keyword": { color: "#e69664" }, // lameta_orange
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import * as React from "react";
2+
import { css } from "@emotion/react";
3+
import { Project } from "../../model/Project/Project";
4+
import ImdiGenerator, { IMDIMode } from "../../export/ImdiGenerator";
5+
import ImdiBundler from "../../export/ImdiBundler";
6+
import { mainProcessApi } from "../../mainProcess/MainProcessApiAccess";
7+
import { XMLValidationResult } from "xmllint-wasm";
8+
import Alert from "@mui/material/Alert";
9+
import { Trans } from "@lingui/macro";
10+
import { GetOtherConfigurationSettings } from "../../model/Project/OtherConfigurationSettings";
11+
import { SearchableCodeViewer } from "../SearchableCodeViewer";
12+
13+
interface IProps {
14+
project: Project;
15+
}
16+
17+
/**
18+
* Shows the IMDI that would be generated for the ConsentDocuments bundle.
19+
*
20+
* This mirrors what ImdiBundler.generateConsentBundleData does during export.
21+
* The consent bundle gathers all consent files from persons who contributed
22+
* to sessions in the project.
23+
*/
24+
export const ConsentImdiView: React.FunctionComponent<IProps> = (props) => {
25+
const [imdi, setImdi] = React.useState<string>("");
26+
const [validationResult, setValidationResult] =
27+
React.useState<XMLValidationResult | undefined>();
28+
const [schemaName, setSchemaName] = React.useState<string>("IMDI_3.0.xsd");
29+
30+
// Generate the IMDI for consent documents using the shared helper
31+
React.useEffect(() => {
32+
const { session, tempDir, hasConsentFiles } =
33+
ImdiBundler.createConsentBundleSession(props.project);
34+
35+
if (!hasConsentFiles) {
36+
setImdi("");
37+
ImdiBundler.cleanupConsentBundleTempDir(tempDir);
38+
return;
39+
}
40+
41+
// Generate the IMDI using the same logic as export
42+
const xml = ImdiGenerator.generateSession(
43+
IMDIMode.RAW_IMDI,
44+
session,
45+
props.project
46+
);
47+
setImdi(xml);
48+
49+
ImdiBundler.cleanupConsentBundleTempDir(tempDir);
50+
}, [props.project, props.project.sessions.items.length]);
51+
52+
// Validate the IMDI when it changes
53+
React.useEffect(() => {
54+
if (imdi) {
55+
const imdiSchema =
56+
GetOtherConfigurationSettings().imdiSchema || "IMDI_3.0.xsd";
57+
setSchemaName(imdiSchema);
58+
mainProcessApi.validateImdiAsync(imdi, imdiSchema).then((r) => {
59+
setValidationResult(r);
60+
});
61+
} else {
62+
setValidationResult(undefined);
63+
}
64+
}, [imdi]);
65+
66+
return (
67+
<div
68+
css={css`
69+
height: 500px;
70+
width: 100%;
71+
display: flex;
72+
flex-direction: column;
73+
flex-grow: 1;
74+
overflow: hidden;
75+
`}
76+
>
77+
{validationResult?.valid && (
78+
<Alert severity="success">
79+
<Trans>This XML conforms to the IMDI schema.</Trans> ({schemaName})
80+
</Alert>
81+
)}
82+
{validationResult && !validationResult.valid && (
83+
<Alert severity="error">
84+
<div
85+
css={css`
86+
font-weight: bold;
87+
margin-bottom: 8px;
88+
`}
89+
>
90+
Validation failed using schema: {schemaName}
91+
</div>
92+
{validationResult?.errors.map((e, i) => {
93+
return (
94+
<div
95+
key={i}
96+
css={css`
97+
margin: 0;
98+
font-weight: 500;
99+
white-space: pre-wrap;
100+
`}
101+
>
102+
{e.message}
103+
</div>
104+
);
105+
})}
106+
</Alert>
107+
)}
108+
<SearchableCodeViewer
109+
content={imdi}
110+
language="xml"
111+
autoFocusSearch={true}
112+
/>
113+
</div>
114+
);
115+
};
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import * as React from "react";
2+
import { css } from "@emotion/react";
3+
import { Folder } from "../../model/Folder/Folder";
4+
import { Project } from "../../model/Project/Project";
5+
import ImdiGenerator, { IMDIMode } from "../../export/ImdiGenerator";
6+
import { mainProcessApi } from "../../mainProcess/MainProcessApiAccess";
7+
import { XMLValidationResult } from "xmllint-wasm";
8+
import Alert from "@mui/material/Alert";
9+
import { Trans } from "@lingui/macro";
10+
import { GetOtherConfigurationSettings } from "../../model/Project/OtherConfigurationSettings";
11+
import { SearchableCodeViewer } from "../SearchableCodeViewer";
12+
13+
interface IProps {
14+
project: Project;
15+
folder: Folder;
16+
name: string;
17+
}
18+
19+
/**
20+
* Shows the IMDI that would be generated for a document folder
21+
* (DescriptionDocuments or OtherDocuments).
22+
*
23+
* This mirrors what ImdiBundler.generateDocumentFolderData does during export.
24+
*/
25+
export const DocumentFolderImdiView: React.FunctionComponent<IProps> = (
26+
props
27+
) => {
28+
const [imdi, setImdi] = React.useState<string>("");
29+
const [validationResult, setValidationResult] =
30+
React.useState<XMLValidationResult | undefined>();
31+
const [schemaName, setSchemaName] = React.useState<string>("IMDI_3.0.xsd");
32+
33+
// Generate the IMDI using the same logic as export
34+
React.useEffect(() => {
35+
if (props.folder.files.length === 0) {
36+
setImdi("");
37+
return;
38+
}
39+
40+
const generator = new ImdiGenerator(
41+
IMDIMode.RAW_IMDI,
42+
props.folder,
43+
props.project
44+
);
45+
const xml = generator.makePseudoSessionImdiForOtherFolder(
46+
props.name,
47+
props.folder
48+
);
49+
setImdi(xml);
50+
}, [props.project, props.folder, props.name, props.folder.files.length]);
51+
52+
// Validate the IMDI when it changes
53+
React.useEffect(() => {
54+
if (imdi) {
55+
const imdiSchema =
56+
GetOtherConfigurationSettings().imdiSchema || "IMDI_3.0.xsd";
57+
setSchemaName(imdiSchema);
58+
mainProcessApi.validateImdiAsync(imdi, imdiSchema).then((r) => {
59+
setValidationResult(r);
60+
});
61+
} else {
62+
setValidationResult(undefined);
63+
}
64+
}, [imdi]);
65+
66+
if (props.folder.files.length === 0) {
67+
return (
68+
<div
69+
css={css`
70+
padding: 20px;
71+
`}
72+
>
73+
<Alert severity="info">
74+
<Trans>
75+
This folder has no files. Add files to see the IMDI that will be
76+
generated.
77+
</Trans>
78+
</Alert>
79+
</div>
80+
);
81+
}
82+
83+
return (
84+
<div
85+
css={css`
86+
height: 500px;
87+
width: 100%;
88+
display: flex;
89+
flex-direction: column;
90+
flex-grow: 1;
91+
overflow: hidden;
92+
`}
93+
>
94+
{validationResult?.valid && (
95+
<Alert severity="success">
96+
<Trans>This XML conforms to the IMDI schema.</Trans> ({schemaName})
97+
</Alert>
98+
)}
99+
{validationResult && !validationResult.valid && (
100+
<Alert severity="error">
101+
<div
102+
css={css`
103+
font-weight: bold;
104+
margin-bottom: 8px;
105+
`}
106+
>
107+
Validation failed using schema: {schemaName}
108+
</div>
109+
{validationResult?.errors.map((e, i) => {
110+
return (
111+
<div
112+
key={i}
113+
css={css`
114+
margin: 0;
115+
font-weight: 500;
116+
white-space: pre-wrap;
117+
`}
118+
>
119+
{e.message}
120+
</div>
121+
);
122+
})}
123+
</Alert>
124+
)}
125+
<SearchableCodeViewer
126+
content={imdi}
127+
language="xml"
128+
autoFocusSearch={true}
129+
/>
130+
</div>
131+
);
132+
};

0 commit comments

Comments
 (0)