1import { r as reactExports, j as jsxRuntimeExports } from "./vendor-react-Bay9Y7Lb.js"; 2import { a as api } from "./index-D0Bmh-UX.js"; 3import { b6 as LoaderCircle, cn as FileMusic, bg as Music, dM as Calendar, b5 as Trash2 } from "./vendor-dVNaLJx_.js"; 4const ProjectList = ({ onSelectProject, layout = "grid" }) => { 5 const [projects, setProjects] = reactExports.useState([]); 6 const [loading, setLoading] = reactExports.useState(true); 7 const [error, setError] = reactExports.useState(null); 8 const [deletingId, setDeletingId] = reactExports.useState(null); 9 const fetchProjects = async () => { 10 setLoading(true); 11 setError(null); 12 try { 13 const data = await api.getProjects(); 14 setProjects(data); 15 } catch (err) { 16 console.error("Error fetching projects:", err); 17 setError(err.message || "Failed to load projects"); 18 } finally { 19 setLoading(false); 20 } 21 }; 22 reactExports.useEffect(() => { 23 fetchProjects(); 24 }, []); 25 const handleDelete = async (e, id) => { 26 e.stopPropagation(); 27 if (!confirm("Are you sure you want to delete this project? This action cannot be undone.")) { 28 return; 29 } 30 setDeletingId(id); 31 try { 32 await api.deleteProject(id); 33 setProjects((prev) => prev.filter((p) => p.id !== id)); 34 } catch (err) { 35 console.error("Error deleting project:", err); 36 alert(err.message || "Failed to delete project"); 37 } finally { 38 setDeletingId(null); 39 } 40 }; 41 if (loading) { 42 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center py-12 text-zinc-500 dark:text-zinc-400", children: [
43 /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "w-8 h-8 animate-spin text-indigo-500 mb-2" }), 44 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm", children: "Loading your projects..." }) 45 ] }); 46 } 47 if (error) { 48 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center py-12 text-red-500", children: [ 49 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "font-semibold mb-2", children: "Error loading projects" }), 50 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs opacity-80 mb-4", children: error }), 51 /* @__PURE__ */ jsxRuntimeExports.jsx( 52 "button", 53 { 54 onClick: fetchProjects, 55 className: "px-4 py-2 bg-zinc-800 hover:bg-zinc-700 text-white rounded text-sm transition-colors border border-zinc-700", 56 children: "Retry" 57 } 58 ) 59 ] }); 60 } 61 if (projects.length === 0) { 62 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center py-16 text-zinc-500 dark:text-zinc-400 border border-dashed border-zinc-300 dark:border-zinc-800 rounded-xl bg-zinc-50/50 dark:bg-zinc-950/20", children: [
63 /* @__PURE__ */ jsxRuntimeExports.jsx(FileMusic, { className: "w-12 h-12 text-zinc-400 mb-3" }), 64 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "font-medium text-zinc-800 dark:text-zinc-200", children: "No saved projects yet" }), 65 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs mt-1 text-center max-w-sm px-4", children: 'Open the MIDI editor, create or import a file, and click "Save" to keep your work online.' }) 66 ] }); 67 } 68 const formatDate = (dateStr) => { 69 try { 70 const date = new Date(dateStr); 71 return date.toLocaleDateString(void 0, { 72 year: "numeric", 73 month: "short", 74 day: "numeric", 75 hour: "2-digit", 76 minute: "2-digit" 77 }); 78 } catch (e) { 79 return dateStr; 80 } 81 }; 82 if (layout === "list") { 83 return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-2 max-h-[400px] overflow-y-auto pr-1", children: projects.map((project) => /* @__PURE__ */ jsxRuntimeExports.jsxs( 84 "div", 85 { 86 onClick: () => onSelectProject(project), 87 className: "group flex items-center justify-between p-3 rounded-lg border border-zinc-200 dark:border-zinc-800 hover:border-indigo-500/50 dark:hover:border-indigo-500/50 bg-white dark:bg-zinc-900/55 hover:bg-indigo-50/30 dark:hover:bg-indigo-950/10 cursor-pointer transition-all duration-200", 88 children: [
89 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3 min-w-0", children: [ 90 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-2 bg-indigo-50 dark:bg-indigo-950/50 text-indigo-600 dark:text-indigo-400 rounded-lg group-hover:scale-105 transition-transform", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Music, { size: 18 }) }), 91 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0", children: [ 92 /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "font-semibold text-zinc-800 dark:text-zinc-200 truncate pr-2 group-hover:text-indigo-600 dark:group-hover:text-indigo-400 transition-colors", children: project.title }), 93 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1 text-[10px] text-zinc-500 dark:text-zinc-400 mt-0.5", children: [ 94 /* @__PURE__ */ jsxRuntimeExports.jsx(Calendar, { size: 10 }), 95 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { children: [ 96 "Updated ", 97 formatDate(project.updatedAt) 98 ] }) 99 ] }) 100 ] }) 101 ] }),
102 /* @__PURE__ */ jsxRuntimeExports.jsx( 103 "button", 104 { 105 onClick: (e) => handleDelete(e, project.id), 106 disabled: deletingId === project.id, 107 className: "p-2 text-zinc-400 hover:text-red-500 dark:text-zinc-500 dark:hover:text-red-400 hover:bg-red-50 dark:hover:bg-red-950/20 rounded-md transition-all self-center disabled:opacity-50", 108 title: "Delete project", 109 children: deletingId === project.id ? /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { size: 16, className: "animate-spin text-zinc-400" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Trash2, { size: 16 }) 110 } 111 ) 112 ] 113 }, 114 project.id 115 )) }); 116 } 117 return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4", children: projects.map((project) => /* @__PURE__ */ jsxRuntimeExports.jsxs( 118 "div", 119 { 120 onClick: () => onSelectProject(project), 121 className: "group relative flex flex-col justify-between p-4 rounded-xl border border-zinc-200 dark:border-zinc-800 hover:border-indigo-500/50 dark:hover:border-indigo-500/50 bg-white dark:bg-zinc-900/55 hover:bg-indigo-50/10 dark:hover:bg-indigo-950/5 cursor-pointer transition-all duration-200 shadow-sm hover:shadow-md", 122 children: [
123 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start justify-between gap-4 mb-4", children: [ 124 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start gap-3 min-w-0", children: [ 125 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "p-3 bg-indigo-50 dark:bg-indigo-950/50 text-indigo-600 dark:text-indigo-400 rounded-xl group-hover:scale-105 transition-transform shrink-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Music, { size: 20 }) }), 126 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0", children: [ 127 /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "font-bold text-zinc-800 dark:text-zinc-200 group-hover:text-indigo-600 dark:group-hover:text-indigo-400 transition-colors line-clamp-2 leading-snug", children: project.title }), 128 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1 text-[11px] text-zinc-500 dark:text-zinc-400 mt-1", children: [
129 /* @__PURE__ */ jsxRuntimeExports.jsx(Calendar, { size: 11, className: "shrink-0" }), 130 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "truncate", children: [ 131 "Updated ", 132 formatDate(project.updatedAt) 133 ] }) 134 ] }) 135 ] }) 136 ] }), 137 /* @__PURE__ */ jsxRuntimeExports.jsx( 138 "button", 139 { 140 onClick: (e) => handleDelete(e, project.id), 141 disabled: deletingId === project.id, 142 className: "p-2 text-zinc-400 hover:text-red-500 dark:text-zinc-500 dark:hover:text-red-400 hover:bg-red-50 dark:hover:bg-red-950/20 rounded-lg transition-all shrink-0 disabled:opacity-50", 143 title: "Delete project", 144 children: deletingId === project.id ? /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { size: 16, className: "animate-spin text-zinc-400" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Trash2, { size: 16 }) 145 } 146 ) 147 ] }),
148 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border-t border-zinc-150 dark:border-zinc-800/80 pt-3 flex items-center justify-between text-xs text-zinc-500 dark:text-zinc-400", children: [ 149 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] px-2 py-0.5 bg-zinc-100 dark:bg-zinc-800 rounded-full font-mono", children: project.id.substring(0, 8) }), 150 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-indigo-600 dark:text-indigo-400 font-semibold group-hover:translate-x-0.5 transition-transform flex items-center gap-1", children: "Load Project â" }) 151 ] }) 152 ] 153 }, 154 project.id 155 )) }); 156}; 157export { 158 ProjectList as P 159};
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.