161 lines
4.1 KiB
TypeScript
161 lines
4.1 KiB
TypeScript
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
|
import type { Meta, StoryObj } from "@storybook/react";
|
|
|
|
const meta: Meta = {
|
|
title: "Layout/Editor",
|
|
parameters: {
|
|
layout: "fullscreen",
|
|
},
|
|
};
|
|
|
|
export default meta;
|
|
type Story = StoryObj;
|
|
|
|
const SERVER_URL = "http://localhost:4001/layout";
|
|
|
|
function LayoutEditor() {
|
|
const [value, setValue] = useState("");
|
|
const [status, setStatus] = useState("Idle");
|
|
const [error, setError] = useState("");
|
|
|
|
const isJsonValid = useMemo(() => {
|
|
try {
|
|
JSON.parse(value);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}, [value]);
|
|
|
|
const load = useCallback(async () => {
|
|
setStatus("Loading...");
|
|
setError("");
|
|
try {
|
|
const res = await fetch(SERVER_URL);
|
|
if (!res.ok) {
|
|
throw new Error(`Load failed (${res.status})`);
|
|
}
|
|
const json = await res.json();
|
|
setValue(JSON.stringify(json, null, 2));
|
|
setStatus("Loaded");
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : "Load failed");
|
|
setStatus("Error");
|
|
}
|
|
}, []);
|
|
|
|
const save = useCallback(async () => {
|
|
if (!isJsonValid) {
|
|
setError("JSON is invalid. Fix it before saving.");
|
|
setStatus("Error");
|
|
return;
|
|
}
|
|
setStatus("Saving...");
|
|
setError("");
|
|
try {
|
|
const res = await fetch(SERVER_URL, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: value,
|
|
});
|
|
if (!res.ok) {
|
|
const text = await res.text();
|
|
throw new Error(`Save failed (${res.status}): ${text}`);
|
|
}
|
|
setStatus("Saved");
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : "Save failed");
|
|
setStatus("Error");
|
|
}
|
|
}, [isJsonValid, value]);
|
|
|
|
useEffect(() => {
|
|
load();
|
|
}, [load]);
|
|
|
|
return (
|
|
<div
|
|
style={{
|
|
minHeight: "100vh",
|
|
background: "#f2efe9",
|
|
color: "#1f1f1f",
|
|
fontFamily: "Georgia, serif",
|
|
padding: "40px",
|
|
boxSizing: "border-box",
|
|
}}
|
|
>
|
|
<div style={{ maxWidth: 900, margin: "0 auto" }}>
|
|
<h1 style={{ fontSize: 36, margin: 0 }}>Layout Editor</h1>
|
|
<p style={{ marginTop: 8, fontSize: 16 }}>
|
|
Edit the JSON layout config and save it back to the repo.
|
|
</p>
|
|
<div style={{ display: "flex", gap: 12, margin: "16px 0" }}>
|
|
<button
|
|
type="button"
|
|
onClick={load}
|
|
style={{
|
|
padding: "10px 16px",
|
|
background: "#1f1f1f",
|
|
color: "#fff",
|
|
border: "none",
|
|
cursor: "pointer",
|
|
}}
|
|
>
|
|
Reload
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={save}
|
|
disabled={!isJsonValid}
|
|
style={{
|
|
padding: "10px 16px",
|
|
background: isJsonValid ? "#d95040" : "#b9b3ab",
|
|
color: "#fff",
|
|
border: "none",
|
|
cursor: isJsonValid ? "pointer" : "not-allowed",
|
|
}}
|
|
>
|
|
Save
|
|
</button>
|
|
<div style={{ display: "flex", alignItems: "center" }}>
|
|
<strong>Status:</strong> {status}
|
|
</div>
|
|
</div>
|
|
{error ? (
|
|
<div
|
|
style={{
|
|
background: "#fff0ee",
|
|
border: "1px solid #d95040",
|
|
color: "#a83a2d",
|
|
padding: "10px 12px",
|
|
marginBottom: 12,
|
|
}}
|
|
>
|
|
{error}
|
|
</div>
|
|
) : null}
|
|
<textarea
|
|
value={value}
|
|
onChange={(event) => setValue(event.target.value)}
|
|
spellCheck={false}
|
|
style={{
|
|
width: "100%",
|
|
minHeight: "65vh",
|
|
padding: 16,
|
|
border: "1px solid #c9c3bb",
|
|
background: "#fffdf9",
|
|
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
|
|
fontSize: 14,
|
|
lineHeight: 1.5,
|
|
boxSizing: "border-box",
|
|
}}
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export const Editor: Story = {
|
|
render: () => <LayoutEditor />,
|
|
};
|