Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrate Monaco Editor in ZEN #42

Closed
wants to merge 23 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
f14414d
Adding Monaco Editor
sakshibobade21 Sep 7, 2023
2f9707f
Additional props for monacoEdito and jsonforms and configservice
sakshibobade21 Sep 9, 2023
78c4e42
Yaml and Schema validation
sakshibobade21 Sep 12, 2023
3500aff
Adding errors
sakshibobade21 Sep 12, 2023
cfbda18
Adding the editor to the installation page
sakshibobade21 Sep 13, 2023
8406c3b
Adding form validations
sakshibobade21 Sep 14, 2023
0d4773c
Syncing the form and zowe in one way
sakshibobade21 Sep 22, 2023
bf64a6d
Reflecting the change in forms when editor content is updated
sakshibobade21 Sep 26, 2023
1e59d43
Restoring stepper
sakshibobade21 Sep 27, 2023
c24d44b
Restoring stepper again
sakshibobade21 Sep 27, 2023
91baa87
Adding the editor to the required steps
sakshibobade21 Sep 27, 2023
8d33f03
Adding the button and removing a link
sakshibobade21 Sep 27, 2023
e24248b
Renaming certain things
sakshibobade21 Sep 28, 2023
ed18b69
Removing the code req to test
sakshibobade21 Sep 29, 2023
774b934
Resolving the conflicts
sakshibobade21 Sep 29, 2023
22114ad
Resolving conflicts
sakshibobade21 Sep 29, 2023
0c8df2f
Updating the lock file
sakshibobade21 Sep 29, 2023
fcc43ca
Config service
sakshibobade21 Oct 2, 2023
6f27906
Delete src/services/configService.ts
sakshibobade21 Oct 2, 2023
defb93c
Restoring data
sakshibobade21 Oct 3, 2023
965507c
Merge branch 'add-editor' of https://github.com/zowe/zen into add-editor
sakshibobade21 Oct 3, 2023
9a79b73
Resolving the conflicts
sakshibobade21 Oct 4, 2023
e97ebc6
Updated lock file
sakshibobade21 Oct 4, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4,344 changes: 2,382 additions & 1,962 deletions package-lock.json

Large diffs are not rendered by default.

9 changes: 8 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
"@electron-forge/maker-squirrel": "^6.0.5",
"@electron-forge/maker-zip": "^6.0.5",
"@electron-forge/plugin-webpack": "^6.0.5",
"@types/flat": "^5.0.2",
"@types/js-yaml": "^4.0.5",
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"@types/react-router-dom": "^5.3.3",
Expand All @@ -45,16 +47,21 @@
"dependencies": {
"@emotion/react": "^11.10.6",
"@emotion/styled": "^11.10.6",
"@fortawesome/free-solid-svg-icons": "^6.4.2",
"@fortawesome/react-fontawesome": "^0.2.0",
"@jsonforms/core": "^3.0.0",
"@jsonforms/material-renderers": "^3.0.0",
"@jsonforms/react": "^3.0.0",
"@mui/icons-material": "^5.11.11",
"@mui/material": "^5.11.11",
"@reduxjs/toolkit": "^1.9.3",
"@types/flat": "^5.0.2",
"ajv": "^8.12.0",
"ajv-formats": "^2.1.1",
"electron-squirrel-startup": "^1.0.0",
"electron-store": "^8.1.0",
"flat": "^5.0.2",
"js-yaml": "^4.1.0",
"monaco-editor": "^0.41.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-redux": "^8.0.5",
Expand Down
124 changes: 124 additions & 0 deletions src/renderer/components/common/EditorDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
* This program and the accompanying materials are made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Copyright Contributors to the Zowe Project.
*/

import { useState, useEffect } from "react";
import { useAppSelector, useAppDispatch } from '../../hooks';
import { Box, Button, Dialog, DialogActions, DialogContent, DialogTitle } from '@mui/material';
import { dump, load } from 'js-yaml';
import { selectYaml, selectSchema, setNextStepEnabled } from '../configuration-wizard/wizardSlice';
import { setConfiguration, getConfiguration, setZoweConfig, getZoweConfig } from '../../../services/ConfigService';
import Ajv from "ajv";
import MonacoEditorComponent from "../common/MonacoEditor";

const EditorDialog = ({isEditorVisible, toggleEditorVisibility, onChange} : any) => {

const dispatch = useAppDispatch();
const schema = useAppSelector(selectSchema);
const yaml = useAppSelector(selectYaml);
const [setupYaml, setSetupYaml] = useState(yaml);
const [editorVisible, setEditorVisible] = useState(false);
const [editorContent, setEditorContent] = useState('');
const [isSchemaValid, setIsSchemaValid] = useState(true);
const [schemaError, setSchemaError] = useState('');


const schemaCopy = { ...schema };
delete schemaCopy['$schema'];
delete schemaCopy['$id'];

const ajv = new Ajv();
ajv.addKeyword("$anchor");
const validate = ajv.compile(schemaCopy);

const initZoweConfig = getZoweConfig();

useEffect(() => {
setEditorVisible(isEditorVisible);
if(isEditorVisible) {
setEditorContent(dump(initZoweConfig));
}
}, [isEditorVisible])

useEffect(() => {
if(isEditorVisible && !isSchemaValid) {
dispatch(setNextStepEnabled(false));
}
if(!isEditorVisible) {
dispatch(setNextStepEnabled(true));
}
}, [isSchemaValid, isEditorVisible]);


const handleEditorContentChange = (newCode: any, isError: boolean) => {
if(isError) {
dispatch(setNextStepEnabled(false));
return;
}

let jsonData;

try {
// To parse the yaml and convert it to the javascript object
jsonData = load(newCode);
} catch (error) {
console.error('Error parsing YAML:', error);
}

// To validate the javascript object against the schema
const isValid = validate(jsonData);
setIsSchemaValid(isValid);

if(validate.errors) {
const errPath = validate.errors[0].schemaPath;
const errMsg = validate.errors[0].message;
setSchemaError(`Invalid Schema: ${errPath}. ${errMsg} `, );
} else if(isSchemaValid && jsonData) {
setZoweConfig(jsonData);
dispatch(setNextStepEnabled(true));
setSetupYaml(jsonData);
updateConfig(jsonData);
}

};

const updateConfig = (data: any) => {
if (data && Object.keys(data).length > 0) {
const setup = data.zowe.setup;
const properties = Object.keys(setup);
properties.map(prop => {
setConfiguration(prop, setup[prop]);
});
onChange(setup, true);
}
}

return (
<div>
<Dialog
open={editorVisible}
onClose={toggleEditorVisibility}
PaperProps={{
style: {
width: '100%',
},
}}>
<DialogTitle>zowe.yaml</DialogTitle>
<DialogContent sx={{paddingBottom: '0'}}>
<MonacoEditorComponent initialContent={editorContent} onContentChange={handleEditorContentChange} isSchemaValid={isSchemaValid} schemaError={schemaError} />
</DialogContent>
<DialogActions>
<Button onClick={toggleEditorVisibility}>Close</Button>
</DialogActions>
</Dialog>
</div>
);
};

export default EditorDialog;
3 changes: 1 addition & 2 deletions src/renderer/components/common/JsonForms.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@
import React, { useState } from 'react';
import { JsonForms } from '@jsonforms/react';
import { materialRenderers, materialCells } from '@jsonforms/material-renderers';
import { createTheme, ThemeProvider } from '@mui/material/styles';
import { Typography } from '@mui/material';
import { ThemeProvider } from '@mui/material/styles';
import jsonFormTheme from '../../jsonFormsTheme';

const makeUISchema = (schema: any, base: string, formData: any): any => {
Expand Down
68 changes: 68 additions & 0 deletions src/renderer/components/common/MonacoEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React, { useEffect, useRef, useState } from 'react';
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
import { load } from 'js-yaml';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
import { setConfiguration, getConfiguration, setZoweConfig, getZoweConfig } from '../../../services/ConfigService';

const MonacoEditorComponent = ({initialContent, onContentChange, isSchemaValid, schemaError} : any) => {

const editorRef = useRef(null);
const [isError, setIsError] = useState(false);
const [errorMsg, setErrorMsg] = useState('');

useEffect(() => {
editorRef.current = monaco.editor.create(document.getElementById('monaco-editor-container'), {
language: 'html',
theme: 'light',
value: initialContent,
});

editorRef.current.onDidChangeModelContent(() => {
const code = editorRef.current.getValue();

try {
// To parse the yaml and check if it is valid
const parsedYAML = load(code);
setError(false, '');
onContentChange(code, false);
} catch(error) {
let errorDesc = error.message ? error.message : "Invalid Yaml";
let errorMsg = error.reason ? error.reason : errorDesc;
setError(true, errorMsg);
onContentChange(code, true);
}

});

return () => {
editorRef.current.dispose();
};

}, []);

const setError = (isError: boolean, errorMessage: string) => {
setIsError(isError);
setErrorMsg(errorMessage);
}

return (
<div style={{ height: '400px' }}>
{isError && (
<div id="error-msg"
style={{ color: 'red', fontFamily: 'Arial, sans-serif', fontSize: 'small', paddingBottom: '1px' }}>
<FontAwesomeIcon icon={faExclamationTriangle} style={{ marginRight: '5px' }} />
{errorMsg}
</div>)}
{!isSchemaValid && (
<div id="error-msg"
style={{ color: 'red', fontFamily: 'Arial, sans-serif', fontSize: 'small' , paddingBottom: '5px' }}>
<FontAwesomeIcon icon={faExclamationTriangle} style={{ marginRight: '5px' }} />
{schemaError}
</div>)}
<div id="monaco-editor-container" style={{ width: '100%', height: '100%', margin: '0' }}></div>
</div>
);
};

export default MonacoEditorComponent;
62 changes: 48 additions & 14 deletions src/renderer/components/stages/Certificates.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,50 +9,84 @@
*/

import { useState, useEffect } from "react";
import ContainerCard from '../common/ContainerCard';
import { Box } from '@mui/material';
import { useAppSelector, useAppDispatch } from '../../hooks';
import { Box, Button } from '@mui/material';
import { selectYaml, selectSchema, setNextStepEnabled } from '../configuration-wizard/wizardSlice';
import { setConfiguration, getConfiguration } from '../../../services/ConfigService';
import ContainerCard from '../common/ContainerCard';
import JsonForm from '../common/JsonForms';
import { setConfiguration, getConfiguration } from '../../../services/configService';
import EditorDialog from "../common/EditorDialog";


const Certificates = () => {

const dispatch = useAppDispatch();
const schema = useAppSelector(selectSchema);
const yaml = useAppSelector(selectYaml);
const setupSchema = schema ? schema.properties.zowe.properties.setup.properties.certificate : "";
const setupSchema = schema ? schema.properties.zowe.properties.setup.properties.security : "";
const initialYaml = yaml ? yaml : "";
const [setupYaml, setSetupYaml] = useState(initialYaml.zowe.setup.certificate);
const [init, setInit] = useState(false);
const [editorVisible, setEditorVisible] = useState(false);

const section = 'certificate';
const initConfig: any = getConfiguration(section);

useEffect(() => {
dispatch(setNextStepEnabled(false));
if(Object.keys(initConfig) && Object.keys(initConfig).length != 0) {
setSetupYaml(initConfig);
}
setInit(true);
}, []);

const handleFormChange = (data: any) => {
const newData = init ? (initConfig? initConfig: data) : (data ? data : initConfig);

const toggleEditorVisibility = () => {
setEditorVisible(!editorVisible);
};

const handleFormChange = (data: any, isYamlUpdated?: boolean) => {
let newData = init ? (initConfig ? initConfig : data) : (data ? data : initConfig);
setInit(false);

if(newData) {
if (newData) {
newData = isYamlUpdated ? data.certificate : newData;

if(setupSchema.if) {
const ifProp = Object.keys(setupSchema.if.properties)[0];
const ifPropValue = setupSchema.if.properties[ifProp].const.toLowerCase();
const thenProp = setupSchema.then.required[0].toLowerCase();
const elseProp = setupSchema.else.required[0].toLowerCase();

if(newData && newData[ifProp]) {
const newDataPropValue = newData[ifProp].toLowerCase();
if( newDataPropValue == ifPropValue && newData[elseProp] ) {
delete newData[elseProp];
}
if(newDataPropValue != ifPropValue && newData[thenProp]) {
delete newData[thenProp];
}
}
}

setConfiguration(section, newData);
// Find some way to check if the form is valid or not?
dispatch(setNextStepEnabled(true));
setSetupYaml(newData);
}
};

return (
<ContainerCard title="Certificates" description="Configure certificates">
<Box sx={{ width: '60vw' }}>
<JsonForm schema={setupSchema} onChange={handleFormChange} formData={setupYaml} />
</Box>
</ContainerCard>
<div>
<div style={{ position: 'fixed', top: '140px', right: '30px'}}>
<Button style={{ color: 'white', backgroundColor: '#1976d2', fontSize: 'x-small'}} onClick={toggleEditorVisibility}>Open Editor</Button>
</div>
<ContainerCard title="Certificates" description="Configure certificates">
<EditorDialog isEditorVisible={editorVisible} toggleEditorVisibility={toggleEditorVisibility} onChange={handleFormChange}/>
<Box sx={{ width: '60vw' }}>
<JsonForm schema={setupSchema} onChange={handleFormChange} formData={setupYaml}/>
</Box>
</ContainerCard>
</div>
);
};

Expand Down
Loading
Loading