This commit is contained in:
sokol
2026-02-18 11:38:25 +03:00
commit 83a5bb87c1
40 changed files with 5803 additions and 0 deletions

View File

@@ -0,0 +1,37 @@
import { Env } from "../models/Env";
import { ConfigReader } from "../models/ConfigReader";
import { Config } from "../models/Config";
export function FileChooser(props: { onSelected: (x: Config) => void }) {
async function handleFile(x: React.ChangeEvent<HTMLInputElement>) {
let file = x.target.files![0];
console.log(file.name, file.type, file.size, "supported:", ConfigReader.isSupportedFormat(file));
let reader = new ConfigReader();
let cfg = await reader.parseFromFile(file);
if (cfg !== null) {
props.onSelected(cfg);
}
}
function handleNew(){
let cfg = new Config();
cfg.addEnvs([new Env(0,"DEFAULT", [])]);
props.onSelected(cfg);
}
return (
<>
<div className="col-2">
<button className="btn btn-primary" onClick={handleNew} >Create new</button>
</div>
<div className="col-1">or</div>
<div className="col">
<input className="form-control" type="file" id="formFile" onChange={handleFile} />
</div >
</>
);
}

View File

@@ -0,0 +1,4 @@
.highlihgt-scrolled {
overflow-x: auto;
max-width: 90%;
}

View File

@@ -0,0 +1,162 @@
import { useState } from "react";
import { Env } from "../../models/Env";
import Highlight from 'react-highlight'
import 'highlight.js/styles/far.css'
import { Builder } from "../../builders";
import { Config } from "../../models/Config";
export function Content(props: { config: Config, env: Env }) {
const [selectTab, setTab] = useState(ContentType.Env);
return (
<>
<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.Raw ? (<ContentRaw config={props.config} env={props.env} />) : ""}
{selectTab == ContentType.Test ? (<ContentTest config={props.config} env={props.env} />) : ""}
</div>
</>
);
}
enum ContentType {
Env = 0,
Json = 1,
Raw = 2,
Test = 3
}
function ContentTabs(props: { onSelected: (id: ContentType) => void }) {
const [selectTab, setSelect] = useState(ContentType.Env);
function clickHandler(type: ContentType) {
setSelect(type);
props.onSelected(type);
}
function isActive(type: ContentType): string {
return type == selectTab ? " active" : " ";
}
return (
<ul className="nav nav-pills nav-fill">
<li className="nav-item">
<a className={"nav-link" + isActive(ContentType.Env)} aria-current="page" href="#" onClick={() => clickHandler(ContentType.Env)}>Env</a>
</li>
<li className="nav-item">
<a className={"nav-link" + isActive(ContentType.Json)} href="#" onClick={() => clickHandler(ContentType.Json)} >Content Template</a>
</li>
<li className="nav-item">
<a className={"nav-link" + isActive(ContentType.Raw)} href="#" onClick={() => clickHandler(ContentType.Raw)}>Raw template</a>
</li>
<li className="nav-item">
<a className={"nav-link" + isActive(ContentType.Test)} href="#" onClick={() => clickHandler(ContentType.Test)}>Test-filled template</a>
</li>
</ul>
)
}
function ContentRaw(props: { config: Config, env: Env }) {
const envsXml = Builder.getEnvs(props.config.envs);
const templateContent = props.config.template.content;
const xml = `<engine>
${envsXml}
<template>
${templateContent}
</template>
</engine>`;
return (
<>
<Highlight className="language-xml">
{xml}
</Highlight>
</>
)
}
function ContentTest(props: { config: Config, env: Env }) {
const [selectedEnvId, setSelectedEnvId] = useState(props.env.id);
const selectedEnv = props.config.envs.find(e => e.id === selectedEnvId) ?? props.env;
const filledTemplate = fillTemplate(props.config, selectedEnv);
return (
<>
<div className="mb-2">
<label className="form-label">Select Environment:</label>
<select
className="form-select w-auto d-inline-block"
value={selectedEnvId}
onChange={(e) => setSelectedEnvId(Number(e.target.value))}
>
{props.config.envs.map(env => (
<option key={env.id} value={env.id}>{env.name}</option>
))}
</select>
</div>
<Highlight className="language-json">
{filledTemplate}
</Highlight>
</>
)
}
function fillTemplate(config: Config, env: Env): string {
const defaultEnv = config.envs.find(e => e.name === "DEFAULT");
const paramMap = new Map<string, string>();
// First, load DEFAULT values as fallback
if (defaultEnv) {
for (const param of defaultEnv.params) {
if (param.name && param.value !== undefined) {
paramMap.set(param.name, param.value);
}
}
}
// Then, override with selected environment values (precedence)
for (const param of env.params) {
if (param.name && param.value !== undefined) {
paramMap.set(param.name, param.value);
}
}
let filledTemplate = config.template.content;
const placeholderRegex = /@(\w+)@/g;
filledTemplate = filledTemplate.replace(placeholderRegex, (match, paramName) => {
if (paramName === Config.ENV_NAME_PARAM) {
return env.name ?? "--NO-VALUE--";
}
return paramMap.get(paramName) ?? "--NO-VALUE--";
});
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);
return (
<Highlight className="language-xml">
{bldr.build()}
</Highlight>
)
}

View File

@@ -0,0 +1,3 @@
import { Content } from "./Content";
export default Content;

72
src/componets/env/Environment.tsx vendored Normal file
View File

@@ -0,0 +1,72 @@
import { useState } from "react";
import { AddEvent, AppEvent, DelEvent, Env, UpdateEvent } from "../../models/Env";
import { EnvParam } from "../../models/EnvParam";
import { EnvironmentParam } from "./EnvironmentParam";
export function Environment(props: { envs: Env[], onChanged: (env: Env) => void, onSelected: (envId: number) => void }) {
const [currEnv, setCurrEnv] = useState(props.envs[0]);
function handleParamChanged(e: AppEvent<EnvParam>) {
let isChanged = false;
let env = currEnv;
if (e instanceof DelEvent) {
env = currEnv.delParam(e.payload);
isChanged = true;
}
if (e instanceof AddEvent) {
env = currEnv.addParams(e.payload);
isChanged = true;
}
if (e instanceof UpdateEvent) {
env = currEnv.updateParams(e.payload);
isChanged = true;
}
if (isChanged) {
let idx = props.envs.findIndex(x => x.id === env.id);
if (idx > -1) {
props.envs[idx] = env;
props.onChanged(props.envs[idx]);
setCurrEnv(env);
}
}
}
const selectOptions = props.envs.map((x) => <option key={x.id} value={x.id} >{x.name}</option>);
const paramCtrls = currEnv.params.map(x =>
<EnvironmentParam key={`${currEnv.id}-${x.id}`}
param={new EnvParam(x.id, x.name, x.value)}
onChanged={handleParamChanged}
isNew={false} />);
return (
<>
<div className="row">
<select
id="environments"
name="environments"
aria-label="Environments"
className="form-select"
onChange={x => {
let id = Number.parseInt(x.target.value);
setCurrEnv(props.envs[id]);
props.onSelected(id);
}}>
{selectOptions}
</select>
</div>
<div className="row">Params</div>
{paramCtrls}
<EnvironmentParam key={`${currEnv.id}-new`}
param={new EnvParam(-1, "", "")}
onChanged={handleParamChanged}
isNew={true}
/>
</>
);
}

68
src/componets/env/EnvironmentParam.tsx vendored Normal file
View File

@@ -0,0 +1,68 @@
import { useState } from "react";
import { EnvParam } from "../../models/EnvParam";
import { AppEvent } from "../../models/Env";
export function EnvironmentParam(props: { param: EnvParam; onChanged: (e: AppEvent<EnvParam>) => void, isNew: boolean }) {
const [param, setParam] = useState(props.param);
const [isFocused, setIsFocused] = useState(false);
function doSet(x: string, act: (x: string) => void) {
act(x);
setParam(param.Changed(true));
}
function handleChange() {
if (!param.isChanged)
return;
let newParam = param.Changed(false);
if (!props.isNew) {
props.onChanged(AppEvent.update(newParam));
}
setParam(newParam);
}
function handleAdd() {
props.onChanged(AppEvent.add(param));
setParam(new EnvParam(0, "", ""));
}
function handleKeyUp(x: React.KeyboardEvent<HTMLInputElement>) {
if (x.key === "Enter") { handleChange(); }
}
return (
<div className={"row px-0" + (param.isChanged ? "border border-warning" : "")}
style={isFocused ? { backgroundColor: "lightskyblue", padding: "1px 0" } : { padding: "1px 0" }}>
<div className="col-4 mx-0 px-0">
<input type="text"
className="form-control"
style={{ backgroundColor: "rgba(170, 170, 247, 0.16)" }}
value={param.name}
onChange={x => doSet(x.target.value, (v) => param.name = v)}
onBlur={() => { handleChange(); setIsFocused(false); }}
onFocus={() => setIsFocused(true)}
onKeyUp={handleKeyUp}
placeholder="name"
aria-label="name" />
</div>
<div className="col mx-0 px-0">
<input type="text"
className="form-control"
value={param.value}
onChange={x => doSet(x.target.value, v => param.value = v)}
onBlur={() => { handleChange(); setIsFocused(false); }}
onFocus={() => setIsFocused(true)}
onKeyUp={handleKeyUp}
placeholder="value"
aria-label="value" />
</div>
<div className="col-1 mx-0 px-0" >
<button className="btn btn-success" hidden={!props.isNew} onClick={handleAdd}></button>
<button className="btn btn-warning" hidden={props.isNew} onClick={() => props.onChanged(AppEvent.del(param))} tabIndex={-1}></button>
</div>
</div>
);
}

3
src/componets/env/index.tsx vendored Normal file
View File

@@ -0,0 +1,3 @@
import { Environment } from "./Environment";
export default Environment;