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

Parametric editor redesign. #76

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions plugins/cad/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"moment": "^2.29.1",
"monaco-editor": "^0.33.0",
"nanoid": "^5.0.7",
"pluralize": "^8.0.0",
"react-use": "^17.2.4"
},
"peerDependencies": {
Expand All @@ -62,6 +63,7 @@
"@types/js-yaml": "^4.0.0",
"@types/lodash": "^4.17.4",
"@types/node": "*",
"@types/pluralize": "^0.0.33",
"cross-fetch": "^3.1.5",
"msw": "^0.35.0"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/**
* Copyright 2024 The Nephio Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { Button, Dialog, DialogActions, DialogContent, DialogTitle, makeStyles } from '@material-ui/core';
import React, { useCallback, useEffect, useState } from 'react';
import { loadYaml } from '../../utils/yaml';
import { KubernetesResource } from '../../types/KubernetesResource';
import { getGroupVersionKind } from '../../utils/kubernetesResource';
import { FirstClassEditorSelector } from './components/FirstClassEditorSelector';
import { YamlViewer } from '../Controls';
import { ResourceEditorDialogProps } from './ResourceEditorDialog';

const useStyles = makeStyles({
container: {
width: '660px',
minHeight: 'min(420px, calc(80vh - 120px))',
maxHeight: 'calc(80vh - 120px)',
marginBottom: '10px',
overflowY: 'scroll',
},
});

export const LegacyResourceEditorDialog: React.FC<ResourceEditorDialogProps> = ({
open,
onClose,
yaml,
onSaveYaml,
packageResources,
}: ResourceEditorDialogProps) => {
const classes = useStyles();

const [showYamlView, setShowYamlView] = useState<boolean>(false);
const [latestYaml, setLatestYaml] = useState<string>('');
const [isNamedEditor, setIsNamedEditor] = useState<boolean>(true);

const resourceYaml = loadYaml(yaml) as KubernetesResource;
const groupVersionKind = resourceYaml && getGroupVersionKind(resourceYaml);

useEffect(() => {
if (open) {
// Reset the state of the dialog anytime it is opened
setLatestYaml(yaml);
setShowYamlView(false);
setIsNamedEditor(true);
}
}, [open, yaml]);

const toggleView = (): void => {
setShowYamlView(!showYamlView);
};

const handleNoNamedEditor = useCallback(() => setIsNamedEditor(false), []);

const handleUpdatedYaml = (updatedYaml: string): void => {
setLatestYaml(updatedYaml);
};

const onSave = (): void => {
onSaveYaml(latestYaml);
onClose();
};

const onDialogClose = (): void => {
onClose();
};

const latestYamlHeight = (latestYaml.split('\n').length + 1) * 18;
const title =
resourceYaml?.kind && resourceYaml?.metadata
? `${resourceYaml.kind} ${resourceYaml?.metadata.name}`
: 'New Resource';

return (
<Dialog open={open} onClose={onDialogClose} maxWidth="lg">
<DialogTitle>{title}</DialogTitle>
<DialogContent>
<>
<div className={classes.container} style={{ height: `${latestYamlHeight}px` }}>
{!showYamlView && isNamedEditor ? (
<FirstClassEditorSelector
groupVersionKind={groupVersionKind}
yaml={latestYaml}
packageResources={packageResources}
onNoNamedEditor={handleNoNamedEditor}
onUpdatedYaml={handleUpdatedYaml}
/>
) : (
<YamlViewer value={latestYaml} allowEdit onUpdatedValue={handleUpdatedYaml} />
)}
</div>
{isNamedEditor && (
<Button variant="text" color="primary" onClick={toggleView}>
Show {showYamlView ? 'Formatted View' : 'YAML View'}
</Button>
)}
</>
</DialogContent>
<DialogActions>
<Button color="primary" onClick={onDialogClose}>
Cancel
</Button>
<Button color="primary" onClick={onSave}>
Save
</Button>
</DialogActions>
</Dialog>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/**
* Copyright 2024 The Nephio Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { Button, Dialog, DialogActions, DialogContent, DialogTitle, makeStyles } from '@material-ui/core';
import { get } from 'lodash';
import React, { useState } from 'react';
import { ResourceEditorDialogProps } from './ResourceEditorDialog';
import { parseYaml } from './components/PxeParametricEditor/utils/yamlConversion';
import { YamlViewer } from '../Controls';
import { getGroupVersionKind } from '../../utils/kubernetesResource';
import { KubernetesResource } from '../../types/KubernetesResource';
import { PxeConfiguredEditorProps } from './components/PxeParametricEditor/createEditorFromConfiguration';
import { NephioTokenParametricEditor } from './components/ParametricFirstClassEditors/NephioTokenParametricEditor';
import { NephioNetworkParametricEditor } from './components/ParametricFirstClassEditors/NephioNetworkParametricEditor';
import { NephioWorkloadClusterParametricEditor } from './components/ParametricFirstClassEditors/NephioWorkloadClusterParametricEditor';
import { NephioCapacityParametricEditor } from './components/ParametricFirstClassEditors/NephioCapacityParametricEditor';

type EditorViewMode = 'gui' | 'yaml';

const EDITOR_COMPONENT_BY_GVK: Record<string, React.FC<PxeConfiguredEditorProps>> = {
'infra.nephio.org/v1alpha1/Token': NephioTokenParametricEditor,
'infra.nephio.org/v1alpha1/Network': NephioNetworkParametricEditor,
'infra.nephio.org/v1alpha1/WorkloadCluster': NephioWorkloadClusterParametricEditor,
'req.nephio.org/v1alpha1/Capacity': NephioCapacityParametricEditor,
};

export const ModernResourceEditorDialog = ({
yaml: initialYaml,
open,
onSaveYaml: handleSaveYaml,
onClose: handleClose,
}: ResourceEditorDialogProps) => {
// TODO The pattern of late component initialization is not great but in line with how editor dialogs currently work.
// It's ok but could be refactored in future.
const [previouslyOpen, setPreviouslyOpen] = useState(false);
const [gvk, setGVK] = useState<string>('');
const [title, setTitle] = useState<string>('');
const [yaml, setYaml] = useState<string>('');
const [viewMode, setViewMode] = useState<EditorViewMode>('gui');

if (open && !previouslyOpen) {
const resource = parseYaml(initialYaml).yamlObject;
setGVK(getGroupVersionKind(resource as KubernetesResource));
setTitle(dialogTitleFromResource(resource));

setYaml(initialYaml);
setViewMode('gui');
}
if (open !== previouslyOpen) {
setPreviouslyOpen(open);
}

const ConfiguredEditor = EDITOR_COMPONENT_BY_GVK[gvk];
const classes = useStyles();

const handleYamlChange = (newYaml: string) => setYaml(newYaml);
const handleViewModeToggle = () => setViewMode(viewMode === 'gui' ? 'yaml' : 'gui');
const handleCloseAndSave = () => {
handleSaveYaml(yaml);
handleClose();
};

return (
<Dialog open={open} onClose={handleClose} maxWidth="xl">
<DialogTitle>{title}</DialogTitle>
<DialogContent>
{ConfiguredEditor && (
<div className={classes.editorContainer}>
{viewMode === 'gui' ? (
<ConfiguredEditor yamlText={yaml} onResourceChange={handleYamlChange} />
) : (
<YamlViewer value={yaml} allowEdit onUpdatedValue={handleYamlChange} />
)}
</div>
)}
<Button variant="text" color="primary" onClick={handleViewModeToggle}>
Show {viewMode === 'gui' ? 'YAML View' : 'GUI View'}
</Button>
</DialogContent>
<DialogActions>
<Button color="primary" onClick={handleClose}>
Cancel
</Button>
<Button color="primary" onClick={handleCloseAndSave}>
Save
</Button>
</DialogActions>
</Dialog>
);
};

const dialogTitleFromResource = (resource: object) => {
const resourceKind = get(resource, 'kind', '');
const resourceName = get(resource, 'metadata.name', '');
return `${resourceKind} ${resourceName}`.trim() || 'Edit resource';
};

const useStyles = makeStyles({
editorContainer: {
width: '1200px',
height: 'calc(100vh - 480px)',
marginBottom: '16px',
paddingRight: '50px',
overflowY: 'auto',
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -14,112 +14,38 @@
* limitations under the License.
*/

import { Button, Dialog, DialogActions, DialogContent, DialogTitle, makeStyles } from '@material-ui/core';
import React, { useCallback, useEffect, useState } from 'react';
import React from 'react';
import { KubernetesResource } from '../../types/KubernetesResource';
import { getGroupVersionKind } from '../../utils/kubernetesResource';
import { PackageResource } from '../../utils/packageRevisionResources';
import { loadYaml } from '../../utils/yaml';
import { YamlViewer } from '../Controls';
import { FirstClassEditorSelector } from './components/FirstClassEditorSelector';
import { LegacyResourceEditorDialog } from './LegacyResourceEditorDialog';
import { ModernResourceEditorDialog } from './ModernResourceEditorDialog';

type OnSaveYamlFn = (yaml: string) => void;

type ResourceEditorProps = {
export type ResourceEditorDialogProps = {
open: boolean;
onClose: () => void;
yaml: string;
onSaveYaml: OnSaveYamlFn;
packageResources: PackageResource[];
};

const useStyles = makeStyles({
container: {
width: '660px',
minHeight: 'min(420px, calc(80vh - 120px))',
maxHeight: 'calc(80vh - 120px)',
marginBottom: '10px',
overflowY: 'scroll',
},
});
const MODERN_EDITOR_RESOURCES = [
'infra.nephio.org/v1alpha1/Token',
'infra.nephio.org/v1alpha1/Network',
'infra.nephio.org/v1alpha1/WorkloadCluster',
'req.nephio.org/v1alpha1/Capacity',
];

export const ResourceEditorDialog = ({ open, onClose, yaml, onSaveYaml, packageResources }: ResourceEditorProps) => {
const classes = useStyles();

const [showYamlView, setShowYamlView] = useState<boolean>(false);
const [latestYaml, setLatestYaml] = useState<string>('');
const [isNamedEditor, setIsNamedEditor] = useState<boolean>(true);

const resourceYaml = loadYaml(yaml) as KubernetesResource;
export const ResourceEditorDialog: React.FC<ResourceEditorDialogProps> = props => {
const resourceYaml = loadYaml(props.yaml) as KubernetesResource;
const groupVersionKind = resourceYaml && getGroupVersionKind(resourceYaml);

useEffect(() => {
if (open) {
// Reset the state of the dialog anytime it is opened
setLatestYaml(yaml);
setShowYamlView(false);
setIsNamedEditor(true);
}
}, [open, yaml]);

const toggleView = (): void => {
setShowYamlView(!showYamlView);
};

const handleNoNamedEditor = useCallback(() => setIsNamedEditor(false), []);

const handleUpdatedYaml = (updatedYaml: string): void => {
setLatestYaml(updatedYaml);
};

const onSave = (): void => {
onSaveYaml(latestYaml);
onClose();
};

const onDialogClose = (): void => {
onClose();
};

const latestYamlHeight = (latestYaml.split('\n').length + 1) * 18;
const title =
resourceYaml?.kind && resourceYaml?.metadata
? `${resourceYaml.kind} ${resourceYaml?.metadata.name}`
: 'New Resource';

return (
<Dialog open={open} onClose={onDialogClose} maxWidth="lg">
<DialogTitle>{title}</DialogTitle>
<DialogContent>
<>
<div className={classes.container} style={{ height: `${latestYamlHeight}px` }}>
{!showYamlView && isNamedEditor ? (
<FirstClassEditorSelector
groupVersionKind={groupVersionKind}
yaml={latestYaml}
packageResources={packageResources}
onNoNamedEditor={handleNoNamedEditor}
onUpdatedYaml={handleUpdatedYaml}
/>
) : (
<YamlViewer value={latestYaml} allowEdit onUpdatedValue={handleUpdatedYaml} />
)}
</div>
{isNamedEditor && (
<Button variant="text" color="primary" onClick={toggleView}>
Show {showYamlView ? 'Formatted View' : 'YAML View'}
</Button>
)}
</>
</DialogContent>
<DialogActions>
<Button color="primary" onClick={onDialogClose}>
Cancel
</Button>
<Button color="primary" onClick={onSave}>
Save
</Button>
</DialogActions>
</Dialog>
return MODERN_EDITOR_RESOURCES.includes(groupVersionKind) ? (
<ModernResourceEditorDialog {...props} />
) : (
<LegacyResourceEditorDialog {...props} />
);
};
Loading
Loading