feat: JSON comments support + full-height layout
This commit is contained in:
@@ -98,7 +98,7 @@ function App() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100">
|
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100">
|
||||||
<main className="container mx-auto px-4 py-6 max-w-[1920px]">
|
<main className="container mx-auto px-4 py-6 max-w-[1920px] min-h-[calc(100vh-48px)]">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<FileChooser
|
<FileChooser
|
||||||
@@ -112,10 +112,10 @@ function App() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{envs.length > 0 ? (
|
{envs.length > 0 ? (
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-12 gap-6">
|
<div className="grid grid-cols-1 lg:grid-cols-12 gap-6 min-h-[calc(100vh-200px)]">
|
||||||
{/* Environment Panel - 5/12 width */}
|
{/* Environment Panel - 5/12 width */}
|
||||||
<section className="lg:col-span-5 xl:col-span-5 2xl:col-span-5">
|
<section className="lg:col-span-5 xl:col-span-5 2xl:col-span-5">
|
||||||
<div className="sticky top-6">
|
<div className="sticky top-6 h-full">
|
||||||
<Environment
|
<Environment
|
||||||
envs={envs}
|
envs={envs}
|
||||||
onChanged={async (e) => await handleEnvChanged(e)}
|
onChanged={async (e) => await handleEnvChanged(e)}
|
||||||
@@ -127,7 +127,7 @@ function App() {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Content Panel - 7/12 width */}
|
{/* Content Panel - 7/12 width */}
|
||||||
<section className="lg:col-span-7 xl:col-span-7 2xl:col-span-7">
|
<section className="lg:col-span-7 xl:col-span-7 2xl:col-span-7 h-full">
|
||||||
<Content
|
<Content
|
||||||
env={currentEnv}
|
env={currentEnv}
|
||||||
config={config}
|
config={config}
|
||||||
|
|||||||
@@ -34,33 +34,40 @@ export function ConfigTemplateEditor({ config, onSaved }: ConfigTemplateEditorPr
|
|||||||
setMode('view');
|
setMode('view');
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSave() {
|
function validateJson(value: string): boolean {
|
||||||
// Validate JSON before saving (with placeholder support)
|
|
||||||
try {
|
try {
|
||||||
const sanitizedValue = draftContent.replace(/@[^@]+@/g, '1');
|
if (!value.trim()) {
|
||||||
|
setJsonError(null);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strip comments (// single-line and /* */ multi-line) and placeholders
|
||||||
|
const sanitizedValue = value
|
||||||
|
.replace(/\/\*[\s\S]*?\*\//g, '') // Remove /* */ comments
|
||||||
|
.replace(/\/\/.*$/gm, '') // Remove // comments
|
||||||
|
.replace(/@[^@]+@/g, '1'); // Replace placeholders
|
||||||
|
|
||||||
JSON.parse(sanitizedValue);
|
JSON.parse(sanitizedValue);
|
||||||
setJsonError(null);
|
setJsonError(null);
|
||||||
onSaved(draftContent);
|
return true;
|
||||||
setMode('view');
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setJsonError((e as Error).message);
|
setJsonError((e as Error).message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSave() {
|
||||||
|
// Validate JSON before saving (with comment and placeholder support)
|
||||||
|
if (validateJson(draftContent)) {
|
||||||
|
onSaved(draftContent);
|
||||||
|
setMode('view');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDraftChange(value: string) {
|
function handleDraftChange(value: string) {
|
||||||
setDraftContent(value);
|
setDraftContent(value);
|
||||||
// Validate JSON on every change
|
// Validate JSON on every change
|
||||||
try {
|
validateJson(value);
|
||||||
if (value.trim()) {
|
|
||||||
const sanitizedValue = value.replace(/@[^@]+@/g, '1');
|
|
||||||
JSON.parse(sanitizedValue);
|
|
||||||
setJsonError(null);
|
|
||||||
} else {
|
|
||||||
setJsonError(null);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
setJsonError((e as Error).message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleKeyDown(e: React.KeyboardEvent<HTMLTextAreaElement>) {
|
function handleKeyDown(e: React.KeyboardEvent<HTMLTextAreaElement>) {
|
||||||
|
|||||||
@@ -31,10 +31,12 @@ export function Content({ config, env, onTemplateSaved }: ContentProps) {
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-white rounded-xl shadow-lg border border-slate-200 overflow-hidden">
|
<div className="bg-white rounded-xl shadow-lg border border-slate-200 overflow-hidden h-full flex flex-col">
|
||||||
<Tabs tabs={tabs} activeTab={activeTab} onChange={setActiveTab} />
|
<div className="flex-shrink-0">
|
||||||
|
<Tabs tabs={tabs} activeTab={activeTab} onChange={setActiveTab} />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="p-4">
|
<div className="flex-1 overflow-y-auto p-4 min-h-0">
|
||||||
<TabPanel isActive={activeTab === 'env'}>
|
<TabPanel isActive={activeTab === 'env'}>
|
||||||
<ContentParams env={env} />
|
<ContentParams env={env} />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|||||||
16
src/componets/env/Environment.tsx
vendored
16
src/componets/env/Environment.tsx
vendored
@@ -94,10 +94,10 @@ export function Environment({ envs, onChanged, onSelected, onAdd, onRemove }: En
|
|||||||
));
|
));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card variant="bordered" padding="none" className="h-full">
|
<Card variant="bordered" padding="none" className="h-full overflow-hidden flex flex-col">
|
||||||
<CardBody className="space-y-1">
|
<CardBody className="space-y-2 flex flex-col h-full overflow-hidden">
|
||||||
{/* Environment Selector */}
|
{/* Environment Selector */}
|
||||||
<div className="flex gap-2">
|
<div className="flex-shrink-0 flex gap-2">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<Select
|
<Select
|
||||||
label="Environment"
|
label="Environment"
|
||||||
@@ -112,7 +112,7 @@ export function Environment({ envs, onChanged, onSelected, onAdd, onRemove }: En
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col justify-center gap-2 pt-6">
|
<div className="flex flex-col justify-center gap-2 pt-6 flex-shrink-0">
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Button
|
<Button
|
||||||
variant="success"
|
variant="success"
|
||||||
@@ -134,13 +134,13 @@ export function Environment({ envs, onChanged, onSelected, onAdd, onRemove }: En
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Parameters Section */}
|
{/* Parameters Section - Scrollable */}
|
||||||
<div>
|
<div className="flex-1 overflow-hidden flex flex-col min-h-0">
|
||||||
<h3 className="text-sm font-semibold text-slate-700 mb-1 uppercase tracking-wide">
|
<h3 className="text-sm font-semibold text-slate-700 mb-1 uppercase tracking-wide flex-shrink-0">
|
||||||
Parameters
|
Parameters
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div className="space-y-0">
|
<div className="flex-1 overflow-y-auto space-y-0 pr-2 -mr-2">
|
||||||
{paramCtrls}
|
{paramCtrls}
|
||||||
|
|
||||||
<EnvironmentParam
|
<EnvironmentParam
|
||||||
|
|||||||
Reference in New Issue
Block a user