Files
Shifted/stories/LayoutEditor.stories.tsx
2026-02-10 01:14:19 +00:00

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>&nbsp;{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 />,
};