fix : i think its ready to production
CI / Verify (push) Successful in 57s
CI / Build immutable images and deploy (push) Successful in 1m36s

This commit is contained in:
2026-08-30 12:13:05 +00:00 Unverified
parent 89c27e230f
commit 811678fd52
4 changed files with 592 additions and 122 deletions
+138 -33
View File
@@ -97,18 +97,33 @@ export function EditorWorkspace({
}) {
const router = useRouter();
const initialDraft = draftFromSnapshot(snapshot);
const [history, setHistory] = useState<HistoryState>({ items: [initialDraft], index: 0 });
const [history, setHistory] = useState<HistoryState>({
items: [initialDraft],
index: 0,
});
const draft = history.items[history.index];
const [selectedId, setSelectedId] = useState<string | null>(draft.blocks[0]?.id ?? null);
const [selectedId, setSelectedId] = useState<string | null>(
draft.blocks[0]?.id ?? null,
);
const [commandOpen, setCommandOpen] = useState(false);
const [viewport, setViewport] = useState<Viewport>("desktop");
const [autosaveStatus, setAutosaveStatus] = useState<AutosaveStatus>({ state: "idle" });
const [autosaveStatus, setAutosaveStatus] = useState<AutosaveStatus>({
state: "idle",
});
const [dataSources, setDataSources] = useState(snapshot.dataSources);
const [sourceSaving, setSourceSaving] = useState(false);
const coordinatorRef = useRef<AutosaveCoordinator<EditorDraft, EditorSaveInput, PageSnapshot> | null>(null);
const coordinatorRef = useRef<AutosaveCoordinator<
EditorDraft,
EditorSaveInput,
PageSnapshot
> | null>(null);
useEffect(() => {
const coordinator = new AutosaveCoordinator<EditorDraft, EditorSaveInput, PageSnapshot>({
const coordinator = new AutosaveCoordinator<
EditorDraft,
EditorSaveInput,
PageSnapshot
>({
initialVersion: snapshot.page.version,
createPayload: (next, expectedVersion) => ({ ...next, expectedVersion }),
save: saveEditorPage,
@@ -137,7 +152,10 @@ export function EditorWorkspace({
function commit(next: EditorDraft) {
setHistory((current) => ({
items: [...current.items.slice(0, current.index + 1), structuredClone(next)].slice(-80),
items: [
...current.items.slice(0, current.index + 1),
structuredClone(next),
].slice(-80),
index: Math.min(current.index + 1, 79),
}));
coordinatorRef.current?.schedule(next);
@@ -164,7 +182,10 @@ export function EditorWorkspace({
setSourceSaving(false);
if (result.status === "saved") {
if (result.snapshot) {
setDataSources((current) => ({ ...current, [source.id]: result.snapshot! }));
setDataSources((current) => ({
...current,
[source.id]: result.snapshot!,
}));
}
toast.success("บันทึกแหล่งข้อมูลแล้ว");
} else if (result.status === "conflict") {
@@ -203,7 +224,9 @@ export function EditorWorkspace({
selectedId={selectedId}
dataSources={dataSources}
onDraftChange={commit}
onDataSourceChange={(source) => setDataSources((current) => ({ ...current, [source.id]: source }))}
onDataSourceChange={(source) =>
setDataSources((current) => ({ ...current, [source.id]: source }))
}
onSaveDataSource={saveSource}
sourceSaving={sourceSaving}
onMediaReady={addUploadedMedia}
@@ -222,13 +245,35 @@ export function EditorWorkspace({
<SidebarTrigger />
<Breadcrumb className="min-w-0 flex-1">
<BreadcrumbList className="flex-nowrap">
<BreadcrumbItem className="truncate">{snapshot.character.nameTh}</BreadcrumbItem>
<BreadcrumbItem className="truncate">
{snapshot.character.nameTh}
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem className="min-w-0"><BreadcrumbPage className="truncate">{draft.title}</BreadcrumbPage></BreadcrumbItem>
<BreadcrumbItem className="min-w-0">
<BreadcrumbPage className="truncate">
{draft.title}
</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
<Button variant="ghost" size="icon-sm" onClick={() => travel(-1)} disabled={history.index === 0} aria-label="ย้อนกลับ"><Undo2Icon /></Button>
<Button variant="ghost" size="icon-sm" onClick={() => travel(1)} disabled={history.index === history.items.length - 1} aria-label="ทำซ้ำ"><Redo2Icon /></Button>
<Button
variant="ghost"
size="icon-sm"
onClick={() => travel(-1)}
disabled={history.index === 0}
aria-label="ย้อนกลับ"
>
<Undo2Icon />
</Button>
<Button
variant="ghost"
size="icon-sm"
onClick={() => travel(1)}
disabled={history.index === history.items.length - 1}
aria-label="ทำซ้ำ"
>
<Redo2Icon />
</Button>
<SaveStatus status={autosaveStatus} />
<label className="hidden min-h-8 items-center gap-2 text-sm sm:flex">
<Switch
@@ -249,21 +294,56 @@ export function EditorWorkspace({
spacing={0}
className="hidden lg:flex"
>
<ToggleGroupItem value="mobile" aria-label="ตัวอย่างมือถือ"><SmartphoneIcon /></ToggleGroupItem>
<ToggleGroupItem value="tablet" aria-label="ตัวอย่างแท็บเล็ต"><TabletIcon /></ToggleGroupItem>
<ToggleGroupItem value="desktop" aria-label="ตัวอย่างเดสก์ท็อป"><MonitorIcon /></ToggleGroupItem>
<ToggleGroupItem value="mobile" aria-label="ตัวอย่างมือถือ">
<SmartphoneIcon />
</ToggleGroupItem>
<ToggleGroupItem value="tablet" aria-label="ตัวอย่างแท็บเล็ต">
<TabletIcon />
</ToggleGroupItem>
<ToggleGroupItem value="desktop" aria-label="ตัวอย่างเดสก์ท็อป">
<MonitorIcon />
</ToggleGroupItem>
</ToggleGroup>
<Button variant="outline" size="sm" onClick={() => setCommandOpen(true)}>
<Button
variant="outline"
size="sm"
onClick={() => setCommandOpen(true)}
>
<CommandIcon />
<span className="hidden xl:inline">คำสั่ง</span>
</Button>
<Button variant="ghost" size="icon-sm" render={<Link href={`/${snapshot.character.slug}/${draft.slug}`} target="_blank" />} aria-label="เปิดหน้าสาธารณะ"><EyeIcon /></Button>
<Button
variant="ghost"
size="icon-sm"
render={
<Link
href={`/${snapshot.character.slug}/${draft.slug}`}
target="_blank"
/>
}
aria-label="เปิดหน้าสาธารณะ"
>
<EyeIcon />
</Button>
<Drawer swipeDirection="right">
<DrawerTrigger render={<Button variant="outline" size="icon-sm" className="lg:hidden" aria-label="เปิด Inspector" />}><PanelRightIcon /></DrawerTrigger>
<DrawerTrigger
render={
<Button
variant="outline"
size="icon-sm"
className="lg:hidden"
aria-label="เปิด Inspector"
/>
}
>
<PanelRightIcon />
</DrawerTrigger>
<DrawerContent className="[--drawer-content-width:min(92vw,28rem)]">
<DrawerHeader className="sr-only">
<DrawerTitle>Inspector และ Live Preview</DrawerTitle>
<DrawerDescription>แก้ไขคุณสมบัติบล็อกและดูตัวอย่าง</DrawerDescription>
<DrawerDescription>
แก้ไขคุณสมบัติบล็อกและดูตัวอย่าง
</DrawerDescription>
</DrawerHeader>
{inspector}
</DrawerContent>
@@ -274,12 +354,14 @@ export function EditorWorkspace({
<ResizablePanelGroup orientation="horizontal" className="h-full">
<ResizablePanel defaultSize="70%" minSize="45%">
<div className="h-full overflow-y-auto bg-muted/25">
<div className={cn(
"mx-auto min-h-full transition-[max-width]",
viewport === "mobile" && "max-w-[390px] border-x",
viewport === "tablet" && "max-w-[768px] border-x",
viewport === "desktop" && "max-w-none",
)}>
<div
className={cn(
"mx-auto min-h-full transition-[max-width]",
viewport === "mobile" && "max-w-[390px] border-x",
viewport === "tablet" && "max-w-[768px] border-x",
viewport === "desktop" && "max-w-none",
)}
>
<BlockCanvas
draft={draft}
selectedId={selectedId}
@@ -291,7 +373,12 @@ export function EditorWorkspace({
</div>
</ResizablePanel>
<ResizableHandle withHandle className="hidden lg:flex" />
<ResizablePanel defaultSize="30%" minSize="22%" maxSize="48%" className="hidden lg:block">
<ResizablePanel
defaultSize="30%"
minSize="22%"
maxSize="48%"
className="hidden lg:block"
>
{inspector}
</ResizablePanel>
</ResizablePanelGroup>
@@ -311,12 +398,16 @@ export function EditorWorkspace({
<AlertDialogHeader>
<AlertDialogTitle>พบการแก้ไขที่ใหม่กว่า</AlertDialogTitle>
<AlertDialogDescription>
เวอร์ชันบนเซิร์ฟเวอร์เปลี่ยนไประหว่างที่คุณกำลังทำงาน ฉบับในเครื่องยังไม่ถูกเขียนทับ คุณสามารถกลับไปตรวจเทียบหรือโหลดฉบับล่าสุด
เวอร์ชันบนเซิร์ฟเวอร์เปลี่ยนไประหว่างที่คุณกำลังทำงาน
ฉบับในเครื่องยังไม่ถูกเขียนทับ
คุณสามารถกลับไปตรวจเทียบหรือโหลดฉบับล่าสุด
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>กลับไปตรวจเทียบ</AlertDialogCancel>
<AlertDialogAction onClick={() => router.refresh()}>โหลดเวอร์ชันล่าสุด</AlertDialogAction>
<AlertDialogAction onClick={() => router.refresh()}>
โหลดเวอร์ชันล่าสุด
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
@@ -325,10 +416,24 @@ export function EditorWorkspace({
}
function SaveStatus({ status }: { status: AutosaveStatus }) {
if (status.state === "saving") return <Badge variant="secondary"><Spinner />กำลังบันทึก</Badge>;
if (status.state === "dirty") return <Badge variant="outline">รอบันทึก</Badge>;
if (status.state === "saved") return <Badge variant="outline" className="text-success">บันทึกแล้ว · v{status.version}</Badge>;
if (status.state === "conflict") return <Badge variant="destructive">ข้อมูลชนกัน</Badge>;
if (status.state === "error") return <Badge variant="destructive">บันทึกไม่สำเร็จ</Badge>;
if (status.state === "saving")
return (
<Badge variant="secondary">
<Spinner />
กำลังบันทึก
</Badge>
);
if (status.state === "dirty")
return <Badge variant="outline">รอบันทึก</Badge>;
if (status.state === "saved")
return (
<Badge variant="outline" className="text-success">
บันทึกแล้ว · v{status.version}
</Badge>
);
if (status.state === "conflict")
return <Badge variant="destructive">ข้อมูลชนกัน</Badge>;
if (status.state === "error")
return <Badge variant="destructive">บันทึกไม่สำเร็จ</Badge>;
return <Badge variant="outline">พร้อมแก้ไข</Badge>;
}