"use client"; import Image from "@/components/ui/resilient-image"; import { closestCenter, DndContext, KeyboardSensor, PointerSensor, useSensor, useSensors, type DragEndEvent } from "@dnd-kit/core"; import { SortableContext, arrayMove, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable"; import { CSS } from "@dnd-kit/utilities"; import { GripVerticalIcon, PlusIcon, Trash2Icon } from "lucide-react"; import { useState } from "react"; import { saveGuideOverview } from "@/app/admin/actions"; import { AutosaveStatus } from "@/components/admin/autosave-status"; import { MediaUploader } from "@/components/admin/media-uploader"; import { MaterialPoster, type PosterBlendMode } from "@/components/public/material-poster"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from "@/components/ui/card"; import { Field, FieldDescription, FieldError, FieldGroup, FieldLabel, } from "@/components/ui/field"; import { Input } from "@/components/ui/input"; import { Combobox, ComboboxContent, ComboboxEmpty, ComboboxInput, ComboboxItem, ComboboxList, } from "@/components/ui/combobox"; import { Slider } from "@/components/ui/slider"; import { Textarea } from "@/components/ui/textarea"; import { useAutosave } from "@/hooks/use-autosave"; import { adminDraftKey } from "@/lib/editor/local-draft"; import { parseYouTubeUrl } from "@/lib/guides/youtube"; import { detectThemeColorsFromPixels } from "@/lib/overview-image"; import type { PublicGuide } from "@/lib/guides/queries"; import { cn } from "@/lib/utils"; type BleedingLayer = { id: string; mediaId: string; imageUrl: string | null; amount: number; blendMode: PosterBlendMode; }; type VideoLink = { id: string; url: string; }; const blendModes: PosterBlendMode[] = ["normal", "multiply", "screen", "overlay", "darken", "lighten", "color-dodge", "color-burn", "hard-light", "soft-light", "difference", "exclusion", "hue", "saturation", "color", "luminosity"]; const blendModeItems = blendModes.map((value) => ({ value, label: value.split("-").map((part) => part[0].toUpperCase() + part.slice(1)).join(" "), })); let bleedingLayerIdCounter = 0; function createBleedingLayerId(): string { const browserCrypto = globalThis.crypto; if (typeof browserCrypto?.randomUUID === "function") return `bleeding-${browserCrypto.randomUUID()}`; bleedingLayerIdCounter += 1; return `bleeding-${Date.now()}-${bleedingLayerIdCounter}`; } function createVideoLinkId(): string { const browserCrypto = globalThis.crypto; if (typeof browserCrypto?.randomUUID === "function") return `video-${browserCrypto.randomUUID()}`; bleedingLayerIdCounter += 1; return `video-${Date.now()}-${bleedingLayerIdCounter}`; } function SortableVideoLink({ link, index, guideName, onChange, onRemove }: { link: VideoLink; index: number; guideName: string; onChange: (url: string) => void; onRemove: () => void }) { const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: link.id }); const video = parseYouTubeUrl(link.url.trim()); const invalid = Boolean(link.url.trim() && !video); return (
YouTube video {index + 1}
onChange(event.target.value)} /> {invalid ? กรุณาใส่ลิงก์วิดีโอ YouTube ที่ถูกต้อง : null} {video ? (