"use client" import { useRef, useState } from "react" import Link from "next/link" import { Check, DownloadSimple, Pause, Play, Sticker as StickerIcon, } from "@phosphor-icons/react" import type { Group as KonvaGroup } from "konva/lib/Group" import type { Stage as KonvaStage } from "konva/lib/Stage" import { useReducedMotion } from "motion/react" import { BoothPanel } from "@/features/booth/BoothPanel" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { Switch } from "@/components/ui/switch" import { Drawer, DrawerContent, DrawerHeader, DrawerTitle, } from "@/components/ui/drawer" import { CLIP_FRAME_COUNT } from "@/features/capture/clipRecorder" import { useClipTicker } from "@/features/live/useClipTicker" import { track } from "@/lib/analytics" import { getLayout, type LayoutId } from "@/lib/layouts" import { cn } from "@/lib/utils" import { countFilled, hasLiveClips, useBoothStore } from "@/stores/booth" import { DecorList } from "./DecorList" import { DecorPanel } from "./DecorPanel" import StripCanvas from "./StripCanvas" import { getEffect, getFrame } from "./presets" /* DESIGN-SYSTEM §10.3: halaman Hias Strip. Rel kiri pilihan hias (lembar bawah di layar sempit), tengah kanvas strip, rel kanan kartu Strip Hidup lalu QR di strip, ringkasan, dan aksi utama. Berkas ini dan seluruh isi `features/editor` hanya dimuat lewat import() dinamis dari halaman, supaya Konva tidak ikut bundel `/` dan `/booth/capture`. */ export default function CustomizeEditor({ layoutId }: { layoutId: LayoutId }) { const shots = useBoothStore((state) => state.shots) const frameId = useBoothStore((state) => state.frameId) const effectId = useBoothStore((state) => state.effectId) const stickers = useBoothStore((state) => state.stickers) const qrEnabled = useBoothStore((state) => state.share.qrEnabled) const shareStrip = useBoothStore((state) => state.share.strip) const setQrEnabled = useBoothStore((state) => state.setQrEnabled) const liveEnabled = useBoothStore((state) => state.live.enabled) const setLiveEnabled = useBoothStore((state) => state.setLiveEnabled) /* M8: sesi teman tidak pernah mengunggah QR, dan salinan lokal pembuat (SE-10) tidak boleh menimpa versi temannya. Di dua keadaan itu kartu QR disembunyikan, dan versi hidup hanya jadi unduhan lokal. */ const relay = useBoothStore((state) => state.relay) const localOnly = useBoothStore((state) => state.localOnly) const hidesUpload = relay?.role === "guest" || localOnly const stageRef = useRef(null) const groupRef = useRef(null) const [drawerOpen, setDrawerOpen] = useState(false) /* PRD §4: "Strip Hidup menambah nilai" dihitung dari sesi yang benar-benar menyalakannya, jadi menyalakan-mematikan-menyalakan lagi tetap satu peristiwa per kunjungan halaman Hias. */ const liveTracked = useRef(false) const handleLiveToggle = (enabled: boolean) => { setLiveEnabled(enabled) if (enabled && !liveTracked.current) { liveTracked.current = true track("live_enabled") } } /* SH-02: pratinjau berputar selama sakelarnya nyala, kecuali pengguna meminta gerak dikurangi. Di situ klip ditampilkan diam dengan tombol Putar, dan `userPlay` menjadi izin eksplisitnya. */ const hasClips = hasLiveClips(shots) const prefersReducedMotion = useReducedMotion() const [userPlay, setUserPlay] = useState(false) const playing = liveEnabled && hasClips && (userPlay || prefersReducedMotion === false) const clipIndex = useClipTicker(playing, CLIP_FRAME_COUNT) const layout = getLayout(layoutId) return ( <> {`${countFilled(shots)} foto siap`} } left={ <>
} center={ <>
shot?.clip ?? null), index: playing ? clipIndex : -1, chrome: true, } : undefined } />

{`Ekspor ${layout.canvas.width} x ${layout.canvas.height} px (${layout.canvas.width / 300} x ${layout.canvas.height / 300} inci, 300 dpi)`}

} right={ <> {/* SH-02: kartu Strip Hidup, urutan pertama rel kanan mengikuti docs/ui-reference/customize.html. Sakelar selalu disertai teks keadaan (DESIGN-SYSTEM §9). */}

Strip Hidup

{liveEnabled ? "Nyala" : "Mati"}

{!hasClips ? "Klip hanya terekam saat memotret lewat kamera." : hidesUpload ? "Tiap foto diputar sebagai klip pendek di perangkat ini. Versi hidupnya tidak ikut diunggah." : "Tiap foto diputar sebagai klip pendek di halaman strip."}

{liveEnabled && hasClips && shareStrip === null && !hidesUpload ? (

Versi hidup ikut diunggah saat kamu membuat link.

) : null} {liveEnabled && hasClips && prefersReducedMotion === true ? ( ) : null}
{/* SH-04: kartu QR di strip. Teksnya dari docs/ui-reference/ customize.html; tidak ada aksi `tangerine` baru di sini supaya satu aksi utama per layar tetap "Selesai & Unduh". M8: teman tidak mengunggah QR, dan salinan lokal pembuat juga tidak, jadi kartunya disembunyikan di dua keadaan itu. */} {hidesUpload ? null : (

QR di strip

QR membuka halaman strip. Fotomu diunggah ke link pribadi dan terhapus otomatis setelah 7 hari.

{shareStrip !== null ? (

Link sudah dibuat di langkah Hasil.

) : qrEnabled ? (

Kamu akan dimintai izin unggah saat mengunduh.

) : null}
)}
Frame
{getFrame(frameId).label}
Efek
{getEffect(effectId).label}
Stiker
{stickers.length}
} /> Hiasan
) }