Skip to content

Commit

Permalink
Add possibility to create config from template
Browse files Browse the repository at this point in the history
  • Loading branch information
breiler committed Dec 18, 2024
1 parent e547f1f commit fe9692e
Show file tree
Hide file tree
Showing 5 changed files with 247 additions and 23 deletions.
112 changes: 109 additions & 3 deletions src/components/createfilemodal/CreateFileModal.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import {
Form,
InputGroup,
Expand All @@ -8,21 +8,54 @@ import {
ModalHeader
} from "react-bootstrap";
import Button from "../button";
import ConfigService, {
ConfigBoard,
ConfigFile
} from "../../services/ConfigService";

type Props = {
show: boolean;
defaultFilename: string;
onCreate: (filename: string) => void;
createNew: boolean;
onCreate: (filename: string, content?: string) => void;
onCancel: () => void;
};

const CreateFileModal = ({
show,
defaultFilename,
createNew,
onCancel,
onCreate
}: Props) => {
const [filename, setFileName] = useState(defaultFilename);
const [boards, setBoards] = useState<ConfigBoard[]>([]);
const [createFromTemplate, setCreateFromTemplate] = useState(false);
const [selectedBoard, setSelectedBoard] = useState<
ConfigBoard | undefined
>();
const [selectedConfig, setSelectedConfig] = useState<
ConfigFile | undefined
>();
const [content, setContent] = useState<string>("");

useEffect(() => {
ConfigService.getBoards().then((boards) => setBoards(boards));
}, []);

useEffect(() => {
setContent("");
if (!selectedConfig || !selectedConfig.url) {
return;
}
fetch(selectedConfig.url).then((response) => {
response
.json()
.then((data) =>
setContent(Buffer.from(data.content, "base64").toString())
);
});
}, [selectedConfig]);

return (
<Modal show={show} size="sm" scrollable={true} centered={true}>
Expand All @@ -36,12 +69,85 @@ const CreateFileModal = ({
onChange={(event) => setFileName(event.target.value)}
/>
</InputGroup>

{createNew && (
<>
<InputGroup style={{ marginTop: "20px" }}>
<Form.Check
type="switch"
label="Create from template"
onChange={(event) =>
setCreateFromTemplate(event.target.checked)
}
/>
</InputGroup>

{createFromTemplate && (
<>
<Form.Label style={{ marginTop: "20px" }}>
Board
</Form.Label>
<Form.Select
onChange={(event) => {
const newBoard = boards.find(
(board) =>
board.name ===
event.target.value
);
setSelectedBoard(newBoard);

setSelectedConfig(
newBoard?.files?.length
? newBoard.files?.[0]
: undefined
);
}}
>
<option></option>
{boards.map((board, index) => (
<option
key={board.name + index}
value={board.name}
>
{board.name}
</option>
))}
</Form.Select>

<Form.Label style={{ marginTop: "20px" }}>
Config template
</Form.Label>
<Form.Select
onChange={(event) =>
setSelectedConfig(
selectedBoard.files.find(
(file) =>
file.name ===
event.target.value
)
)
}
value={selectedConfig?.name}
>
{selectedBoard?.files.map((file, index) => (
<option
key={file.name + index}
value={file.name}
>
{file.name}
</option>
))}
</Form.Select>
</>
)}
</>
)}
</ModalBody>
<ModalFooter>
<Button onClick={onCancel}>Cancel</Button>
<Button
buttonType="btn-success"
onClick={() => onCreate(filename)}
onClick={() => onCreate(filename, content)}
>
OK
</Button>
Expand Down
38 changes: 24 additions & 14 deletions src/components/editormodal/EditorModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,18 @@ import { ButtonType } from "../button";
type EditorModalProps = {
file?: ControllerFile;
fileData?: Buffer;
createNew: boolean;
onClose: () => void;
onSave: (file: ControllerFile, fileData: Buffer) => Promise<void>;
};

const EditorModal = ({ file, fileData, onClose, onSave }: EditorModalProps) => {
const EditorModal = ({
file,
fileData,
createNew,
onClose,
onSave
}: EditorModalProps) => {
const [value, setValue] = useState<string>(fileData?.toString() || "");
const [hasErrors, setHasErrors] = useState<boolean>(false);
const [isSaving, setIsSaving] = useState<boolean>(false);
Expand Down Expand Up @@ -57,6 +64,7 @@ const EditorModal = ({ file, fileData, onClose, onSave }: EditorModalProps) => {
)}
onCreate={(filename) => onSaveAs(filename)}
onCancel={() => setShowSaveAs(false)}
createNew={false}
/>
)}
<Modal show={!!file} size="xl" scrollable={true} centered={false}>
Expand Down Expand Up @@ -96,19 +104,21 @@ const EditorModal = ({ file, fileData, onClose, onSave }: EditorModalProps) => {
Close
</>
</Button>
<Button
disabled={isSaving}
buttonType={ButtonType.WARNING}
onClick={() => setShowSaveAs(true)}
>
<>
<FontAwesomeIcon
icon={faSave as IconDefinition}
style={{ marginRight: "8px" }}
/>{" "}
Save as...
</>
</Button>
{!createNew && (
<Button
disabled={isSaving}
buttonType={ButtonType.WARNING}
onClick={() => setShowSaveAs(true)}
>
<>
<FontAwesomeIcon
icon={faSave as IconDefinition}
style={{ marginRight: "8px" }}
/>{" "}
Save as...
</>
</Button>
)}
<Button
disabled={isSaving || hasErrors}
buttonType={"btn-success"}
Expand Down
17 changes: 11 additions & 6 deletions src/pages/filebrowser/FileBrowser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { generateNewFileName } from "../../utils/utils";
type EditFile = {
file: ControllerFile;
fileData: Buffer;
createNew: boolean;
};

const fileUpload = (file, onSave) => {
Expand Down Expand Up @@ -139,21 +140,23 @@ const FileBrowser = () => {
controllerService
.downloadFile("/littlefs/" + file.name)
.then((fileData) => {
setEditFile({ file, fileData });
setEditFile({ file, fileData, createNew: false });
})
.finally(() => {
setIsDownloading(false);
});
};

const onCreateConfig = async (filename: string) => {
const onCreateConfig = async (filename: string, content?: string) => {
if (!controllerService) {
return;
}

setEditFile({
file: { id: filename, name: filename, size: 0 },
fileData: Buffer.from("name: New configuration")
fileData: content
? Buffer.from(content)
: Buffer.from("name: New configuration"),
createNew: true
});
};

Expand Down Expand Up @@ -219,15 +222,17 @@ const FileBrowser = () => {
} /* we need to create a new modal in order to get the default file name */
defaultFilename={generateNewFileName(files, configFilename)}
onCancel={() => setShowNewConfigDialog(false)}
onCreate={(filename) => {
onCreate={(filename, content) => {
setShowNewConfigDialog(false);
onCreateConfig(filename);
onCreateConfig(filename, content);
}}
createNew={true}
/>
)}

{editFile && (
<EditorModal
createNew={editFile.createNew}
file={editFile?.file}
fileData={editFile?.fileData}
onSave={onSave}
Expand Down
62 changes: 62 additions & 0 deletions src/services/ConfigService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { GithubService, GithubTreeFile } from "./GitHubService";

export type ConfigBoard = {
name: string;
path: string;
files: ConfigFile[];
};

export type ConfigFile = {
name: string;
path: string;
size: number;
url: string;
};

const ConfigService = {
getBoards: () => {
return GithubService.getConfigTree().then((configTree) =>
configTree.tree
.filter(
(node) =>
node.path.startsWith("contributed/") &&
node.type === "tree"
)
.map((node) => ({
name: node.path
.replace("contributed/", "")
.replaceAll("_", " "),
path: node.path,
files: ConfigService.getConfigs(configTree.tree, node.path)
}))
.filter((board) => board.files.length)
);
},

getConfigs: (files: GithubTreeFile[], path: string) => {
return files
.filter((node) => node.type === "blob")
.filter((node) => {
const fileName = node.path.substring(
node.path.lastIndexOf("/") + 1
);
return node.path === path + "/" + fileName;
})
.filter((node) => node.path.endsWith(".yaml"))
.map((node) => {
const fileName = node.path
.substring(node.path.lastIndexOf("/") + 1)
.replace(".yaml", "")
.replaceAll("_", " ");
return {
name: fileName,
path: node.path,
size: node.size,
sha: node.sha,
url: node.url
};
});
}
};

export default ConfigService;
41 changes: 41 additions & 0 deletions src/services/GitHubService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,20 @@ export type GithubRelease = {
published_at: string;
};

export type GithubTree = {
sha: string;
url: string;
tree: GithubTreeFile[];
};

export type GithubTreeFile = {
path: string;
type: "blob" | "tree";
sha: string;
size: number;
url: string;
};

export type FirmwareImageSignature = {
algorithm: string;
value: string;
Expand Down Expand Up @@ -57,6 +71,8 @@ export type GithubReleaseManifest = {
installable: FirmwareChoice;
};

let configTree: GithubTree;

export const GithubService = {
/**
* Fetches all available releases from github
Expand Down Expand Up @@ -90,6 +106,31 @@ export const GithubService = {
});
},

getConfigTree: (): Promise<GithubTree> => {
// If the file has been retreived, return a cached variant
if (configTree) {
return Promise.resolve(configTree);
}

return fetch(
"https://api.github.com/repos/bdring/fluidnc-config-files/git/trees/main?recursive=true",
{
headers: {
Accept: "application/json"
}
}
).then((response) => {
if (response.status === 200 || response.status === 0) {
return response.json().then((data) => {
configTree = data;
return data;
});
} else {
return Promise.reject(new Error(response.statusText));
}
});
},

getReleaseManifest: (
release: GithubRelease
): Promise<GithubReleaseManifest> => {
Expand Down

0 comments on commit fe9692e

Please sign in to comment.