add: template editor
This commit is contained in:
65
src/components/MarkdownTemplate/EnumsEditor.js
Normal file
65
src/components/MarkdownTemplate/EnumsEditor.js
Normal file
@@ -0,0 +1,65 @@
|
||||
import React, { useState } from "react";
|
||||
|
||||
const EnumsEditor = ({ enums, onChange }) => {
|
||||
const [_enums, setEnums] = useState(enums || []);
|
||||
|
||||
return (
|
||||
<div className="box">
|
||||
<ul>
|
||||
{_enums.map((item, index) => (
|
||||
<li key={index} className="field has-addons" style={{ marginBottom: "0.5rem" }}>
|
||||
<div className="control is-expanded">
|
||||
<input
|
||||
className="input is-small"
|
||||
type="text"
|
||||
value={item}
|
||||
onChange={(e) => {
|
||||
const updated = [..._enums];
|
||||
updated[index] = e.target.value;
|
||||
setEnums(updated);
|
||||
onChange(updated);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="control">
|
||||
<button
|
||||
className="button is-small is-danger"
|
||||
type="button"
|
||||
onClick={() => {
|
||||
const updated = [..._enums];
|
||||
updated.splice(index, 1);
|
||||
setEnums(updated);
|
||||
onChange(updated);
|
||||
}}
|
||||
>
|
||||
<span className="icon is-small">
|
||||
<i className="fas fa-times" />
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="field">
|
||||
<div className="control">
|
||||
<button
|
||||
className="button is-small is-primary"
|
||||
type="button"
|
||||
onClick={() => {
|
||||
const updated = [..._enums, ""];
|
||||
setEnums(updated);
|
||||
onChange(updated);
|
||||
}}
|
||||
>
|
||||
<span className="icon is-small">
|
||||
<i className="fas fa-plus" />
|
||||
</span>
|
||||
<span>Add Enum</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EnumsEditor;
|
||||
16
src/components/MarkdownTemplate/LayoutEditor.js
Normal file
16
src/components/MarkdownTemplate/LayoutEditor.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import React, {useState} from 'react';
|
||||
const LayoutEditor = ({layout, onChange}) => {
|
||||
const [_layout, setLayout] = useState(layout || "");
|
||||
return (
|
||||
<textarea
|
||||
className="textarea"
|
||||
value={_layout}
|
||||
onChange={(e) => {
|
||||
setLayout(e.target.value);
|
||||
onChange(layout);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default LayoutEditor;
|
||||
90
src/components/MarkdownTemplate/MarkdownTemplateEditor.js
Normal file
90
src/components/MarkdownTemplate/MarkdownTemplateEditor.js
Normal file
@@ -0,0 +1,90 @@
|
||||
import React, { useContext, useState } from "react";
|
||||
import { AuthContext } from "../../AuthProvider";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { useMarkdownTemplate, useSaveMarkdownTemplate } from "../../utils/queries/template-queries";
|
||||
import LayoutEditor from "./LayoutEditor";
|
||||
import ParametersManager from "./ParametersManager";
|
||||
import "bulma/css/bulma.min.css";
|
||||
|
||||
const MarkdownTemplateEditor = () => {
|
||||
const { roles } = useContext(AuthContext);
|
||||
if (!roles.includes("admin") || roles.includes("creator"))
|
||||
return <div className="notification is-danger">Permission Denied</div>;
|
||||
|
||||
const navigate = useNavigate();
|
||||
const { id } = useParams();
|
||||
const { data: template, isFetching: templateIsFetching } = useMarkdownTemplate(id);
|
||||
const saveMarkdownTemplate = useSaveMarkdownTemplate();
|
||||
|
||||
const [title, setTitle] = useState(template?.id || "");
|
||||
const [parameters, setParameters] = useState(template?.parameters || []);
|
||||
const [layout, setLayout] = useState(template?.layout || "");
|
||||
|
||||
if (templateIsFetching) {
|
||||
return <p>Loading...</p>;
|
||||
}
|
||||
|
||||
const handleSave = () => {
|
||||
saveMarkdownTemplate.mutate(
|
||||
{ id, data: { title, parameters, layout } },
|
||||
{
|
||||
onSuccess: () => {
|
||||
navigate("/");
|
||||
},
|
||||
onError: () => {
|
||||
alert("Error saving template.");
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="section">
|
||||
<div className="container">
|
||||
<h2 className="title is-4">Markdown Template Editor</h2>
|
||||
<div className="field">
|
||||
<label className="label">Title:</label>
|
||||
<div className="control">
|
||||
<input
|
||||
className="input"
|
||||
type="text"
|
||||
placeholder="Enter template title"
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="columns is-variable is-8">
|
||||
<div className="column">
|
||||
<h3 className="title is-5">Layout</h3>
|
||||
<div className="box">
|
||||
<LayoutEditor
|
||||
layout={layout}
|
||||
parameters={parameters}
|
||||
onChange={(newLayout) => setLayout(newLayout)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="column">
|
||||
<h3 className="title is-5">Parameters</h3>
|
||||
<div className="box">
|
||||
<ParametersManager
|
||||
parameters={parameters}
|
||||
onChange={(newParameters) => setParameters(newParameters)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="field is-grouped">
|
||||
<div className="control">
|
||||
<button className="button is-primary" onClick={handleSave}>
|
||||
Save Template
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default MarkdownTemplateEditor;
|
||||
84
src/components/MarkdownTemplate/ParametersManager.js
Normal file
84
src/components/MarkdownTemplate/ParametersManager.js
Normal file
@@ -0,0 +1,84 @@
|
||||
import React, { useState } from "react";
|
||||
import TypeEditor from "./TypeEditor";
|
||||
|
||||
const ParametersManager = ({ parameters, onChange }) => {
|
||||
const [_parameters, setParameters] = useState(parameters || []);
|
||||
|
||||
const handleAdd = () => {
|
||||
const updated = [
|
||||
..._parameters,
|
||||
{
|
||||
name: "",
|
||||
type: { base_type: "string" }
|
||||
}
|
||||
];
|
||||
setParameters(updated);
|
||||
onChange(updated);
|
||||
};
|
||||
|
||||
const handleNameChange = (index, newName) => {
|
||||
const updated = [..._parameters];
|
||||
updated[index].name = newName;
|
||||
setParameters(updated);
|
||||
onChange(updated);
|
||||
};
|
||||
|
||||
const handleDelete = (index) => {
|
||||
const updated = [..._parameters];
|
||||
updated.splice(index, 1);
|
||||
setParameters(updated);
|
||||
onChange(updated);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="box">
|
||||
<div className="field">
|
||||
<div className="control">
|
||||
<button className="button is-primary" onClick={handleAdd}>
|
||||
Add Parameter
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{_parameters.map((param, index) => (
|
||||
<div key={index} className="box" style={{ marginBottom: "1rem" }}>
|
||||
<div className="field is-grouped is-grouped-multiline">
|
||||
<div className="control is-expanded">
|
||||
<label className="label">Name:</label>
|
||||
<input
|
||||
type="text"
|
||||
className="input"
|
||||
value={param.name}
|
||||
onChange={(e) => handleNameChange(index, e.target.value)}
|
||||
placeholder="Parameter name"
|
||||
/>
|
||||
</div>
|
||||
<div className="control">
|
||||
<button
|
||||
className="button is-danger"
|
||||
onClick={() => handleDelete(index)}
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="field">
|
||||
<label className="label">Type:</label>
|
||||
<div className="control">
|
||||
<TypeEditor
|
||||
type={param.type}
|
||||
onChange={(newType) => {
|
||||
const updated = [..._parameters];
|
||||
updated[index].type = newType;
|
||||
setParameters(updated);
|
||||
onChange(updated);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ParametersManager;
|
||||
30
src/components/MarkdownTemplate/TemplateSelector.js
Normal file
30
src/components/MarkdownTemplate/TemplateSelector.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import React, {useState} from "react";
|
||||
import {useMarkdownTemplates} from "../../utils/queries/template-queries";
|
||||
|
||||
const TemplateSelector = ({template, onChange}) => {
|
||||
const [_template, setTemplate] = useState(template || {
|
||||
title: "",
|
||||
parameters: [],
|
||||
layout: ""
|
||||
});
|
||||
const {data:templates, isFetching: templatesAreFetching} = useMarkdownTemplates();
|
||||
if(templatesAreFetching) {
|
||||
return <p>Loading...</p>
|
||||
}
|
||||
return (
|
||||
<select
|
||||
value={_template.title}
|
||||
onChange={(e) => {
|
||||
setTemplate(e.target.value);
|
||||
onChange(e.target.value);
|
||||
}}>
|
||||
{
|
||||
templates.map((tmpl, index) => (
|
||||
<option key={index} value={tmpl} >{tmpl.title}</option>
|
||||
))
|
||||
}
|
||||
</select>
|
||||
);
|
||||
};
|
||||
|
||||
export default TemplateSelector;
|
||||
94
src/components/MarkdownTemplate/TypeEditor.js
Normal file
94
src/components/MarkdownTemplate/TypeEditor.js
Normal file
@@ -0,0 +1,94 @@
|
||||
import React from 'react';
|
||||
import EnumsEditor from './EnumsEditor';
|
||||
import TemplateSelector from './TemplateSelector';
|
||||
|
||||
const TypeEditor = ({ type, onChange }) => {
|
||||
const [_type, setType] = React.useState(type || {});
|
||||
const renderExtraFields = () => {
|
||||
switch (_type.base_type) {
|
||||
case 'enum':
|
||||
return <EnumsEditor
|
||||
enums={_type.definition.enums}
|
||||
onChange={(newEnums) => {
|
||||
const updated = {
|
||||
..._type,
|
||||
definition: {
|
||||
..._type.definition,
|
||||
enums: newEnums
|
||||
}
|
||||
};
|
||||
setType(updated);
|
||||
onChange(updated);
|
||||
}}
|
||||
/>;
|
||||
case 'list':
|
||||
return (
|
||||
<div>
|
||||
<TypeEditor
|
||||
extendType={_type.extend_type}
|
||||
onChange={(extendType) => {
|
||||
const updated = {..._type, extend_type: extendType};
|
||||
setType(updated);
|
||||
onChange(updated);
|
||||
}}
|
||||
/>
|
||||
<textarea
|
||||
className="textarea"
|
||||
value={_type.definition.iter_layout}
|
||||
onChange={(e) => {
|
||||
const updated = {
|
||||
..._type,
|
||||
definition: {
|
||||
..._type.definition,
|
||||
iter_layout: e.target.value
|
||||
}
|
||||
};
|
||||
setType(updated);
|
||||
onChange(updated);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
case 'template':
|
||||
return <TemplateSelector
|
||||
template={_type.definition.template}
|
||||
onChange={(newTemplate) => {
|
||||
const updated = {
|
||||
..._type,
|
||||
definition: {
|
||||
..._type.definition,
|
||||
template: newTemplate
|
||||
}
|
||||
};
|
||||
setType(updated);
|
||||
onChange(updated);
|
||||
}}
|
||||
/>;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<label>type:</label>
|
||||
<select value={_type.base_type} onChange={(e) => {
|
||||
const updated = {
|
||||
base_type: e.target.value,
|
||||
definition: {}
|
||||
};
|
||||
setType(updated);
|
||||
onChange(updated);
|
||||
}}>
|
||||
<option value="string">string</option>
|
||||
<option value="markdown">markdown</option>
|
||||
<option value="enum">enum</option>
|
||||
<option value="list">list</option>
|
||||
<option value="template">template</option>
|
||||
</select>
|
||||
{renderExtraFields()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TypeEditor;
|
||||
Reference in New Issue
Block a user