1import { r as reactExports, j as jsxRuntimeExports, R as React } from "./vendor-react-Bay9Y7Lb.js"; 2import { V as VideoCard } from "./main-CznOH3wZ.js"; 3import { u as useShouldShowAd, A as AdSlot } from "./AdSlot-CPW4Sb2o.js"; 4import { a as api } from "./index-D0Bmh-UX.js"; 5import { u as useTranslation } from "./vendor-i18n-R-bMoYp_.js"; 6import { bL as Clock, cv as LayoutGrid, cw as List } from "./vendor-dVNaLJx_.js"; 7import "./vendor-react-three-MDLA5jog.js"; 8import "./utils-BYCVoSTN.js"; 9import "./synth-BE1TSTD4.js"; 10import "./wasm-synth-adapter-BrqgBX1y.js"; 11import "./vendor-spessasynth-DH8n6o_V.js"; 12import "./vendor-audio-DH3w0BzW.js"; 13import "./Waveform-MxtdejK7.js"; 14import "./vendor-framer-C8eEgkdL.js"; 15import "./labTools-BATZiaPB.js"; 16import "./UserAvatar-BQN4jlY-.js"; 17const NewlyUploadedPage = ({ 18 onSelectTrack, 19 onToggleWatchLater, 20 watchLaterSet, 21 onAddToQueue, 22 onAddToPlaylist 23}) => { 24 const { t } = useTranslation(); 25 const shouldShowAd = useShouldShowAd("exploreList"); 26 const [tracks, setTracks] = reactExports.useState([]); 27 const [loading, setLoading] = reactExports.useState(true); 28 const [loadingMore, setLoadingMore] = reactExports.useState(false); 29 const [hasMore, setHasMore] = reactExports.useState(true); 30 const [viewMode, setViewMode] = reactExports.useState(() => { 31 return localStorage.getItem("hamie_view_mode") || "list"; 32 }); 33 const observerRef = reactExports.useRef(null); 34 const sentinelRef = reactExports.useRef(null); 35 reactExports.useEffect(() => { 36 localStorage.setItem("hamie_view_mode", viewMode); 37 }, [viewMode]); 38 reactExports.useEffect(() => { 39 const fetchInitial = async () => { 40 setLoading(true);
41 try { 42 const data = await api.getTracks("", "all", "recent", 30, 0); 43 setTracks(data); 44 if (data.length < 30 || data.length >= 300) { 45 setHasMore(false); 46 } 47 } catch (e) { 48 console.error("Failed to load initial newly uploaded tracks", e); 49 } finally { 50 setLoading(false); 51 } 52 }; 53 fetchInitial(); 54 }, []); 55 const loadMore = async () => { 56 if (loading || loadingMore || !hasMore || tracks.length >= 300) return; 57 setLoadingMore(true); 58 try { 59 const skip = tracks.length; 60 const limit = Math.min(30, 300 - skip); 61 const data = await api.getTracks("", "all", "recent", limit, skip); 62 if (data.length === 0) { 63 setHasMore(false); 64 } else { 65 setTracks((prev) => { 66 const nextTracks = [...prev, ...data]; 67 if (nextTracks.length >= 300) { 68 setHasMore(false); 69 } 70 return nextTracks; 71 }); 72 } 73 } catch (e) { 74 console.error("Failed to load more newly uploaded tracks", e); 75 } finally { 76 setLoadingMore(false); 77 } 78 }; 79 reactExports.useEffect(() => { 80 if (loading) return; 81 if (observerRef.current) { 82 observerRef.current.disconnect(); 83 } 84 observerRef.current = new IntersectionObserver( 85 (entries) => { 86 if (entries[0].isIntersecting && hasMore && !loadingMore && tracks.length < 300) { 87 loadMore(); 88 } 89 }, 90 { threshold: 0.1, rootMargin: "100px" } 91 ); 92 if (sentinelRef.current) { 93 observerRef.current.observe(sentinelRef.current); 94 } 95 return () => { 96 if (observerRef.current) { 97 observerRef.current.disconnect(); 98 } 99 }; 100 }, [loading, loadingMore, hasMore, tracks.length]); 101 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-6 max-w-6xl mx-auto animate-in fade-in duration-300", children: [ 102 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between gap-4 mb-8 border-b border-zinc-200 dark:border-zinc-800 pb-4", children: [
103 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-4", children: [ 104 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-12 h-12 sm:w-16 sm:h-16 bg-indigo-500/10 dark:bg-indigo-500/20 text-indigo-600 dark:text-indigo-400 rounded-full flex items-center justify-center", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Clock, { size: 28 }) }), 105 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 106 /* @__PURE__ */ jsxRuntimeExports.jsx("h1", { className: "text-2xl sm:text-3xl font-bold text-zinc-900 dark:text-white", children: t("home.newlyUploaded", "Newly Uploaded Playlist") }), 107 /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-xs text-zinc-500 dark:text-zinc-400 mt-1", children: [ 108 t("home.newlyUploadedDescription", "Latest MIDI preservation files added to the archive"), 109 " (Limit 300)" 110 ] }) 111 ] }) 112 ] }), 113 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center bg-zinc-100 dark:bg-zinc-800 p-1 rounded-xl shadow-inner", children: [
114 /* @__PURE__ */ jsxRuntimeExports.jsx( 115 "button", 116 { 117 onClick: () => setViewMode("grid"), 118 className: `p-1.5 rounded-lg transition-all ${viewMode === "grid" ? "bg-white dark:bg-zinc-700 text-indigo-600 shadow-sm" : "text-zinc-400 hover:text-zinc-600 dark:hover:text-zinc-300"}`, 119 title: "Grid View", 120 children: /* @__PURE__ */ jsxRuntimeExports.jsx(LayoutGrid, { size: 18 }) 121 } 122 ), 123 /* @__PURE__ */ jsxRuntimeExports.jsx( 124 "button", 125 { 126 onClick: () => setViewMode("list"), 127 className: `p-1.5 rounded-lg transition-all ${viewMode === "list" ? "bg-white dark:bg-zinc-700 text-indigo-600 shadow-sm" : "text-zinc-400 hover:text-zinc-600 dark:hover:text-zinc-300"}`, 128 title: "List View", 129 children: /* @__PURE__ */ jsxRuntimeExports.jsx(List, { size: 18 }) 130 } 131 ) 132 ] }) 133 ] }), 134 tracks.length > 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: viewMode === "grid" ? "grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6 gap-x-4 gap-y-8" : "flex flex-col gap-3", children: tracks.map((track, idx) => { 135 const showAd = (idx + 1) % 12 === 0; 136 return /* @__PURE__ */ jsxRuntimeExports.jsxs(React.Fragment, { children: [
137 /* @__PURE__ */ jsxRuntimeExports.jsx( 138 VideoCard, 139 { 140 track, 141 onSelect: (t2) => onSelectTrack(t2, void 0, tracks), 142 onToggleWatchLater, 143 isWatchLater: watchLaterSet.has(track.id), 144 variant: viewMode, 145 onAddToQueue, 146 onAddToPlaylist, 147 playlist: tracks 148 } 149 ), 150 showAd && shouldShowAd && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: viewMode === "grid" ? "col-span-1" : "w-full", children: /* @__PURE__ */ jsxRuntimeExports.jsx(AdSlot, { type: "exploreList", className: "!my-0 h-full min-h-[200px]" }) }) 151 ] }, track.id); 152 }) }) : !loading ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-center py-20", children: /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-zinc-400", children: t("list.empty", "This list is empty.") }) }) : null, 153 loading && tracks.length === 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: viewMode === "grid" ? "grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-6 gap-x-4 gap-y-8" : "flex flex-col gap-3", children: Array.from({ length: 12 }).map((_, idx) => /* @__PURE__ */ jsxRuntimeExports.jsx( 154 "div", 155 { 156 className: `animate-pulse bg-zinc-100 dark:bg-zinc-800 rounded-2xl ${viewMode === "grid" ? "h-48 w-full" : "h-16 w-full"}` 157 }, 158 `skeleton-${idx}` 159 )) }), 160 hasMore && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { ref: sentinelRef, className: "py-8 flex justify-center items-center", children: loadingMore && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "animate-spin rounded-full h-8 w-8 border-b-2 border-indigo-600" }) }) 161 ] }); 162}; 163export { 164 NewlyUploadedPage as default 165};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.