feat: add manual template editor with view/edit modes

This commit is contained in:
sokol
2026-02-18 17:09:32 +03:00
parent acce21c0e6
commit 15f890abda
3 changed files with 131 additions and 15 deletions

View File

@@ -0,0 +1,118 @@
import { useState } from "react";
import Highlight from 'react-highlight';
import 'highlight.js/styles/far.css';
import { Config } from "../../models/Config";
interface ConfigTemplateProps {
config: Config;
onSaved: (newContent: string) => void;
}
export function ConfigTemplate(props: ConfigTemplateProps) {
const [mode, setMode] = useState<'view' | 'edit'>('view');
const [draftContent, setDraftContent] = useState(props.config.template.content);
const [originalContent, setOriginalContent] = useState(props.config.template.content);
const [jsonError, setJsonError] = useState<string | null>(null);
// Sync draft when config changes (in view mode)
if (mode === 'view') {
setDraftContent(props.config.template.content);
}
function handleEdit() {
setOriginalContent(props.config.template.content);
setDraftContent(props.config.template.content);
setJsonError(null);
setMode('edit');
}
function handleRevert() {
setDraftContent(originalContent);
setJsonError(null);
setMode('view');
}
function handleSave() {
// Validate JSON before saving
try {
JSON.parse(draftContent);
setJsonError(null);
props.onSaved(draftContent);
setMode('view');
} catch (e) {
setJsonError((e as Error).message);
}
}
function handleDraftChange(value: string) {
setDraftContent(value);
// Validate JSON on every change
try {
if (value.trim()) {
JSON.parse(value);
setJsonError(null);
} else {
setJsonError(null);
}
} catch (e) {
setJsonError((e as Error).message);
}
}
const isValidJson = jsonError === null;
return (
<div className="config-template-editor">
{mode === 'view' ? (
<>
<div className="mb-2">
<button className="btn btn-primary btn-sm" onClick={handleEdit}>
Edit
</button>
</div>
<Highlight className="language-json">
{props.config.template.content || "{}"}
</Highlight>
</>
) : (
<>
<div className="mb-2 d-flex gap-2 align-items-center">
<button
className="btn btn-success btn-sm"
onClick={handleSave}
disabled={!isValidJson}
>
Save
</button>
<button
className="btn btn-secondary btn-sm"
onClick={handleRevert}
>
× Revert
</button>
<span className={`ms-2 ${isValidJson ? 'text-success' : 'text-danger'}`}>
{isValidJson ? 'Valid JSON' : 'Invalid JSON'}
</span>
</div>
{jsonError && (
<div className="alert alert-danger py-1 px-2 mb-2" style={{ fontSize: '0.875rem' }}>
{jsonError}
</div>
)}
<textarea
className={`form-control font-monospace ${isValidJson ? 'border-success' : 'border-danger'}`}
value={draftContent}
onChange={(e) => handleDraftChange(e.target.value)}
rows={20}
style={{
fontFamily: 'monospace',
whiteSpace: 'pre',
overflowX: 'auto'
}}
spellCheck={false}
/>
</>
)}
</div>
);
}

View File

@@ -4,9 +4,10 @@ import Highlight from 'react-highlight'
import 'highlight.js/styles/far.css'
import { Builder } from "../../builders";
import { Config } from "../../models/Config";
import { ConfigTemplate } from "./ConfigTemplate";
export function Content(props: { config: Config, env: Env }) {
export function Content(props: { config: Config, env: Env, onTemplateSaved: (newContent: string) => void }) {
const [selectTab, setTab] = useState(ContentType.Env);
return (
@@ -14,7 +15,7 @@ export function Content(props: { config: Config, env: Env }) {
<ContentTabs onSelected={(id) => setTab(id)} />
<div className="">
{selectTab == ContentType.Env ? (<ContentParams env={props.env} />) : ""}
{selectTab == ContentType.Json ? (<ContentTemplate env={props.env} config={props.config} />) : ""}
{selectTab == ContentType.Json ? (<ConfigTemplate config={props.config} onSaved={props.onTemplateSaved} />) : ""}
{selectTab == ContentType.Raw ? (<ContentRaw config={props.config} env={props.env} />) : ""}
{selectTab == ContentType.Test ? (<ContentTest config={props.config} env={props.env} />) : ""}
</div>
@@ -139,18 +140,6 @@ function fillTemplate(config: Config, env: Env): string {
return filledTemplate;
}
function ContentTemplate(props: { config: Config, env: Env }) {
let text = props.config.getTemplateAsJson() ?? "{//no content. at all. tottaly empty!!!\n}";
return (
<>
<Highlight className="language-json" >
{text ?? ""}
</Highlight>
</>
)
}
function ContentParams(props: { env: Env }) {
const bldr = Builder.getEnv(props.env);