Files
configucci/src/App.tsx

158 lines
4.9 KiB
TypeScript

import { useState } from 'react';
import { File } from 'lucide-react';
import { Env } from './models/Env';
import { Environment } from './componets/env/Environment';
import { Content } from './componets/content/Content';
import { FileChooser } from './componets/FileChooser';
import { Config } from './models/Config';
class AppState {
private constructor(
public config: Config = new Config(),
) {}
static readonly Instance = new AppState();
public loadConfig(cfg: Config) {
this.config = cfg;
}
public async saveEnv(env: Env): Promise<number> {
// Simulate async save with 1 second delay
return await new Promise<number>((resolve) => {
setTimeout(() => {
console.log('Saved env:', env.name);
resolve(0);
}, 1000);
});
}
}
function App() {
const [config, setConfig] = useState(() => AppState.Instance.config);
const [envs, setEnvs] = useState(() => AppState.Instance.config.envs);
const [selectedEnv, setSelectedEnv] = useState(0);
// Ensure selectedEnv is always valid
const validSelectedEnv = Math.min(selectedEnv, Math.max(0, envs.length - 1));
const currentEnv = envs[validSelectedEnv];
async function handleEnvChanged(env: Env) {
// Optimistic update - update React state immediately
setEnvs((prevEnvs) => {
const newEnvs = [...prevEnvs];
const idx = newEnvs.findIndex((x) => x.id === env.id);
if (idx > -1) {
newEnvs[idx] = env;
}
return newEnvs;
});
// Also update config.envs and template to keep them in sync
setConfig((prevConfig) => {
const newConfig = new Config();
newConfig.envs = prevConfig.envs.map((e) => (e.id === env.id ? env : e));
newConfig.template = prevConfig.template;
newConfig.updateTemplateFromEnv(env);
return newConfig;
});
// Fire off async save in background (no need to wait)
AppState.Instance.saveEnv(env);
}
function handleEnvSelected(idx: number) {
setSelectedEnv(idx);
}
function handleEnvAdded(env: Env): number {
const newIdx = envs.length;
setEnvs((prevEnvs) => [...prevEnvs, env]);
setConfig((prevConfig) => {
const newConfig = new Config();
newConfig.envs = [...prevConfig.envs, env];
newConfig.template = prevConfig.template;
return newConfig;
});
return newIdx;
}
function handleEnvRemoved(envId: number) {
setEnvs((prevEnvs) => prevEnvs.filter((e) => e.id !== envId));
setConfig((prevConfig) => {
const newConfig = new Config();
newConfig.envs = prevConfig.envs.filter((e) => e.id !== envId);
newConfig.template = prevConfig.template;
return newConfig;
});
}
function handleTemplateSaved(newContent: string) {
setConfig((prevConfig) => {
const newConfig = new Config();
newConfig.envs = prevConfig.envs;
newConfig.setTemplate(newContent);
return newConfig;
});
}
return (
<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]">
{/* Header */}
<div className="mb-6">
<FileChooser
onSelected={(x) => {
AppState.Instance.loadConfig(x);
setEnvs(x.envs);
setConfig(x);
}}
config={config}
/>
</div>
{envs.length > 0 ? (
<div className="grid grid-cols-1 lg:grid-cols-12 gap-6">
{/* Environment Panel - Fixed 4/12 on all large screens */}
<section className="lg:col-span-4 xl:col-span-4 2xl:col-span-3">
<div className="sticky top-6">
<Environment
envs={envs}
onChanged={async (e) => await handleEnvChanged(e)}
onSelected={handleEnvSelected}
onAdd={handleEnvAdded}
onRemove={handleEnvRemoved}
/>
</div>
</section>
{/* Content Panel - Maximum width (8/12 → 9/12 on 2xl) */}
<section className="lg:col-span-8 xl:col-span-8 2xl:col-span-9">
<Content
env={currentEnv}
config={config}
onTemplateSaved={handleTemplateSaved}
/>
</section>
</div>
) : (
/* Empty State */
<div className="flex flex-col items-center justify-center py-20">
<div className="w-24 h-24 bg-gradient-to-br from-blue-400 to-blue-600 rounded-2xl flex items-center justify-center mb-6 shadow-lg">
<File className="w-12 h-12 text-white opacity-80" />
</div>
<h2 className="text-2xl font-bold text-slate-700 mb-2">
No Configuration Loaded
</h2>
<p className="text-slate-500 text-center max-w-md">
Create a new configuration or upload an existing XML file to get started
</p>
</div>
)}
</main>
</div>
);
}
export default App;