PageSourceSearch

https://www.hamienet.com/assets/ProjectList-DNE9O-rf.js

js hamienet.com collected 2026-09-24 19:00:09 UTC 9,642 bytes, 159 lines download raw bytes

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.