mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
96 lines
3.9 KiB
React
96 lines
3.9 KiB
React
// components/generic/CMS/BlockList.jsx
|
|
|
|
import { BlockWrapper } from "./BlockWrapper";
|
|
import { TextBlock } from "./Blocks/Admin/TextBlock";
|
|
import { ImageBlock } from "./Blocks/Admin/ImageBlock";
|
|
import { TextImageBlock } from "./Blocks/Admin/TextImageBlock";
|
|
import { VideoBlock } from "./Blocks/Admin/VideoBlock";
|
|
import { TextVideoBlock } from "./Blocks/Admin/TextVideoBlock";
|
|
import { AudioBlock } from "./Blocks/Admin/AudioBlock";
|
|
import { CodeBlock } from "./Blocks/Admin/CodeBlock";
|
|
import { MarkdownBlock } from "./Blocks/Admin/MarkdownBlock";
|
|
|
|
// ─── Block renderer ───────────────────────────────────────────────────────────
|
|
//
|
|
// blockId is forwarded to TextBlock (and any future rich-text blocks) so the
|
|
// WYSIWYG editor knows exactly when to seed its innerHTML from saved data.
|
|
|
|
function BlockContent({ block, onUpdate }) {
|
|
const { id, type, content } = block;
|
|
|
|
switch (type) {
|
|
case "text":
|
|
return (
|
|
<TextBlock
|
|
blockId={id}
|
|
content={content}
|
|
onUpdate={onUpdate}
|
|
/>
|
|
);
|
|
case "image":
|
|
return <ImageBlock content={content} onUpdate={onUpdate} />;
|
|
case "text-image":
|
|
return <TextImageBlock blockId={id} content={content} onUpdate={onUpdate} />;
|
|
case "video":
|
|
return <VideoBlock content={content} onUpdate={onUpdate} />;
|
|
case "text-video":
|
|
return <TextVideoBlock blockId={id} content={content} onUpdate={onUpdate} />;
|
|
case "audio":
|
|
return <AudioBlock content={content} onUpdate={onUpdate} />;
|
|
case "code":
|
|
return <CodeBlock content={content} onUpdate={onUpdate} />;
|
|
case "markdown":
|
|
return <MarkdownBlock content={content} onUpdate={onUpdate} />;
|
|
default:
|
|
return <p className="text-sm text-muted-foreground">Unknown block type.</p>;
|
|
}
|
|
}
|
|
|
|
// ─── Default content per type ─────────────────────────────────────────────────
|
|
|
|
export const DEFAULT_CONTENT = {
|
|
"text": { body: "" },
|
|
"image": { asset_id: null, url: "", alt: "" },
|
|
"text-image": { body: "", asset_id: null, url: "", alt: "", image_position: "right" },
|
|
"video": { asset_id: null, url: "", thumbnail_url: "", duration_seconds: 0 },
|
|
"text-video": { body: "", asset_id: null, url: "", thumbnail_url: "", video_position: "right", duration_seconds: 0 },
|
|
"audio": { asset_id: null, url: "", title: "", artist: "", tag: "", thumbnail: "", duration_seconds: 0 },
|
|
"code": { language: "javascript", code: "" },
|
|
"markdown": { body: "" },
|
|
};
|
|
|
|
// ─── List ─────────────────────────────────────────────────────────────────────
|
|
|
|
export function BlockList({ blocks, onUpdate, onMove, onDelete }) {
|
|
if (!blocks.length) {
|
|
return (
|
|
<div className="flex flex-col items-center justify-center rounded-lg border-2 border-dashed border-muted-foreground/20 py-16 gap-2">
|
|
<p className="text-sm text-muted-foreground">No blocks yet.</p>
|
|
<p className="text-xs text-muted-foreground">
|
|
Use the button below to add your first block.
|
|
</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="flex flex-col gap-3">
|
|
{blocks.map((block, index) => (
|
|
<BlockWrapper
|
|
key={block.id}
|
|
type={block.type}
|
|
index={index}
|
|
total={blocks.length}
|
|
onMoveUp={() => onMove(block.id, "up")}
|
|
onMoveDown={() => onMove(block.id, "down")}
|
|
onDelete={() => onDelete(block.id)}
|
|
>
|
|
<BlockContent
|
|
block={block}
|
|
onUpdate={(updated) => onUpdate(block.id, updated)}
|
|
/>
|
|
</BlockWrapper>
|
|
))}
|
|
</div>
|
|
);
|
|
} |