Skip to content

Commit

Permalink
Refactor and Added Handles to resize
Browse files Browse the repository at this point in the history
  • Loading branch information
tobiashochguertel committed Jul 7, 2023
1 parent 810ef12 commit b5e967e
Show file tree
Hide file tree
Showing 25 changed files with 400 additions and 355 deletions.
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@

</div>

<script type="module" src="/src/webapp/main.tsx"></script>
<script type="module" src="/src/main.tsx"></script>
</body>

</html>
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"react-feather": "^2.0.10",
"react-monaco-editor": "^0.52.0",
"react-router-dom": "^6.10.0",
"react-split": "^2.0.14",
"sort-by": "^1.2.0",
"swagger-autogen": "^2.23.1"
},
Expand Down
153 changes: 153 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import { Container, Row } from 'react-bootstrap';
import React, { useEffect, useState } from 'react';
import { Tree, useToasts } from '@geist-ui/core'
import TemplateOutput, { Content } from './TemplateOutput';
import MyConfiguration from './components/MyConfiguration/MyConfiguration';
import LOCAL_STORAGE_KEY from './components/MyConfiguration/LOCAL_STORAGE_KEY';
import Editor from './components/Editor/Editor';
import replaceSlash from './components/utils/replaceSlash';
import Split from 'react-split'
import './assets/split.css'

function isEmpty(array: Array<unknown> | Map<unknown, unknown>) {
if (array instanceof Map) return array.size === 0;
return array.length === 0;
}

function App() {
const { setToast } = useToasts()

const [output, setOutput] = useState<string>("");
const [code, setCode] = useState<Content>({ filename: '', content: '', type: 'file' } as Content);
const [selectedFile, setSelectedFile] = useState("");
const [filesTree, setFilesTree] = useState([]);
const [isFileChange, setIsFileChange] = useState(false);

const [chart, setChart] = useState<Map<string, Content>>(new Map<string, Content>());
const [chartLoaded, setChartLoaded] = useState(false);

const [myConfig, setMyConfig] = useState(JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY) || ""));

useEffect(() => {
if (isEmpty(chart)) {
fetch(`/api/chart`).then(response => response.json()).then((data) => {
// https://www.cloudhadoop.com/2018/09/typescript-how-to-convert-map-tofrom.html
const map = new Map<string, Content>();
for (const value in data) {
map.set(value, data[value]);
}
setChart(map);
setChartLoaded(true);
});
}
if (isEmpty(filesTree)) {
fetch(`/api/tree`).then(response => response.json()).then((data) => {
setFilesTree(data);
});
}
if (isFileChange === true) {
const code: Content = {
filename: selectedFile,
content: chart.get(selectedFile)?.content || "",
type: "file"
}
setCode(code);
TemplateOutput(chart, code, myConfig)
.then((output) => setOutput(output));
setIsFileChange(false);
}
}, [chart, filesTree, isFileChange, myConfig, selectedFile]);

// eslint-disable-next-line @typescript-eslint/no-unused-vars
function onChange(newValue: unknown, _e: unknown) {
const code: Content = {
filename: selectedFile,
content: newValue as string,
type: "file"
}
TemplateOutput(chart, code, myConfig)
.then((output) => setOutput(output));

const requestOptions = {
method: 'PUT',
headers: { 'Content-Type': 'text/plain' },
body: newValue as string
};

if (isFileChange === false) {
fetch(`/api/file/${replaceSlash(selectedFile)}`, requestOptions)
.then(response => response.text())
.then(() => {
const currentValue: Content = chart.get(selectedFile) || { filename: selectedFile, content: "", type: "file" } as Content;
currentValue.content = newValue as string;
chart.set(selectedFile, currentValue)
})
}
}

const handler = (path: string) => {
setToast({ text: path })
if (selectedFile !== path) {
setIsFileChange(true);
setSelectedFile(path);
}
}

const myConfigurationChangeHandler = (newValue: string) => {
setMyConfig(newValue);
TemplateOutput(chart, code, newValue)
.then((output) => setOutput(output));
}

if (chart.size === 0) return (<>'No Files available... or backend not available?'</>)
if (chartLoaded === false) return (<>'Files Content not yet loaded...'</>)

return (
<>
<header className="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0 shadow">
<a className="navbar-brand col-md-3 col-lg-2 me-0 px-3 fs-6" href="#">Helm Chart Editor</a>
</header>

<Container fluid className="">
<Row>
<Split className="split" sizes={[25, 75]}>
<nav id="sidebarMenu" className="col-md-3 col-lg-2 d-md-block bg-body-tertiary sidebar collapse">
<div className="position-sticky pt-3 sidebar-sticky">
<ul className="nav flex-column">
<li className="nav-item">
<MyConfiguration
name={"Set Configuration Values"}
chart={chart}
onChange={myConfigurationChangeHandler}
/>
</li>
</ul>

{/* List Files / Directories */}
<h6 className="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-body-secondary text-uppercase">
<span>Files</span>
</h6>
<Tree
onClick={handler}
value={filesTree}
initialExpand={false}
/>
</div>
</nav>
<main className="col-md-9 ms-sm-auto col-lg-10 px-md-4">
{selectedFile && <Editor
input={code.content}
output={output}
onChange={onChange}
selectedFile={selectedFile}
/>}
</main>
</Split>
</Row>
</Container>

</>
)
}

export default App
40 changes: 40 additions & 0 deletions src/TemplateOutput.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import * as jsYaml from 'js-yaml';

export type Content = {
filename: string
content?: string
type: string
}

export default async function helmChartTemplateOutput(
chart: Map<string, Content>,
template: Content,
myValuesOverride?: string,
) {

let valuesYaml: string = chart.get('values.yaml')?.content || "";
if (myValuesOverride !== undefined) {
const myConfigJson: object = jsYaml.load(myValuesOverride) as object;
const chartConfigJson: object = jsYaml.load(chart.get('values.yaml')?.content || "") as object;
const mergedConfig: object = { ...chartConfigJson, ...myConfigJson };
valuesYaml = jsYaml.dump(mergedConfig)
}

const result = await fetch(`/template`, {
method: 'POST',
cache: "no-cache",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
selection: template,
myValues: valuesYaml,
chart: Object.fromEntries(chart)
}),
}
).then(response => response.text()).then((data) => {
return data;
});

return result;
}
4 changes: 2 additions & 2 deletions src/webapp/assets/dashboard.css → src/assets/dashboard.css
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ body {
*/

.sidebar {
position: fixed;
/* position: fixed; */
top: 0;
/* rtl:raw:
right: 0;
*/
bottom: 0;
/* rtl:remove */
left: 0;
z-index: 100;
/* z-index: 100; */
/* Behind the navbar */
padding: 48px 0 0;
/* Height of navbar */
Expand Down
File renamed without changes.
15 changes: 15 additions & 0 deletions src/assets/split.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
.split {
display: flex;
flex-direction: row;
}

.gutter {
background-color: #eee;
background-repeat: no-repeat;
background-position: 50%;
}

.gutter.gutter-horizontal {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg==');
cursor: col-resize;
}
File renamed without changes
59 changes: 59 additions & 0 deletions src/components/Editor/Editor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import React from 'react';
import { Editor as MonacoEditor } from '@monaco-editor/react';
import Split from 'react-split'

type Props = {
input: string | undefined,
output: string | undefined,
onChange?: (newValue: unknown, e: unknown) => void,
selectedFile?: string,
}

function Editor(
{
input,
output,
onChange = (newValue: unknown, e: unknown) => {
console.debug("newValue", newValue);
console.debug("e", e);
},
selectedFile = "",
}: Props) {

const language_editor = "yaml";
const language_render = "yaml";

function handleEditorChange(value: unknown, event: unknown) {
onChange(value, event);
}

return (
<Split className="split">
<div>
<h6>Selected File: {selectedFile}</h6>
<MonacoEditor
key={input}
height="90vh"
defaultLanguage={language_editor}
defaultValue={input}
onChange={handleEditorChange}
/>
</div>
<div>
<h6>Render Output</h6>
<MonacoEditor
height="90vh"
defaultLanguage={language_render}
defaultValue={output}
value={output}
options={{
readOnly: true,
wordWrap: "on",
}}
/>
</div>
</Split>
)
}

export default Editor;
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import React from "react";
import { useRouteError } from "react-router-dom";

export default function ErrorPage() {
Expand Down
3 changes: 3 additions & 0 deletions src/components/MyConfiguration/LOCAL_STORAGE_KEY.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const LOCAL_STORAGE_KEY = 'my-configuration'

export default LOCAL_STORAGE_KEY
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
import React, { useEffect, useState } from 'react';

import Offcanvas from 'react-bootstrap/Offcanvas';
import { Editor as MonacoEditor } from '@monaco-editor/react';

import './SetConfigurationValuesMenu.css';
import { useEffect, useState } from 'react';
import { Button, ButtonGroup, Col, Container, Row } from 'react-bootstrap';
import { Content } from './TemplateOutput';
import { Content } from '../../TemplateOutput';
import LOCAL_STORAGE_KEY from './LOCAL_STORAGE_KEY';
import useResize from '../UseResizeHook/UseResize';


type OffCanvasProps = {
name: string | undefined,
chart: Map<string, Content>,
onChange?: (newValue: string) => void,
}

export const LOCAL_STORAGE_KEY = 'my-configuration'

export default function MyConfiguration({ name, chart, onChange }: OffCanvasProps) {
const [show, setShow] = useState(false);
const handleClose = () => setShow(false);
const toggleShow = () => setShow((s) => !s);
const { width, enableResize } = useResize({ minWidth: 500 });

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
Expand Down Expand Up @@ -49,7 +53,7 @@ export default function MyConfiguration({ name, chart, onChange }: OffCanvasProp
<a href='#' onClick={toggleShow} className="nav-link">
{name}
</a>
<Offcanvas show={show} onHide={handleClose} scroll={true} backdrop={false}>
<Offcanvas show={show} onHide={handleClose} scroll={true} backdrop={false} style={{ width: width }}>
<Offcanvas.Header closeButton>
<Offcanvas.Title>My Configuration:</Offcanvas.Title>
</Offcanvas.Header>
Expand Down Expand Up @@ -84,6 +88,11 @@ export default function MyConfiguration({ name, chart, onChange }: OffCanvasProp
}}
/>
</Offcanvas.Body>

<div
className='resize-handle'
onMouseDown={enableResize}
/>
</Offcanvas >
</>
);
Expand Down
Loading

0 comments on commit b5e967e

Please sign in to comment.