1import { r as reactExports, j as jsxRuntimeExports } from "./vendor-react-Bay9Y7Lb.js"; 2import { a as api } from "./index-D0Bmh-UX.js"; 3import { d4 as ShieldCheck, b1 as CircleAlert, bv as CircleCheckBig, b2 as Upload, cG as Tag, aW as X } from "./vendor-dVNaLJx_.js"; 4import "./vendor-i18n-R-bMoYp_.js"; 5const SoundfontUploadPage = ({ isAdmin, onNavigate }) => { 6 const fileInputRef = reactExports.useRef(null); 7 const [file, setFile] = reactExports.useState(null); 8 const [name, setName] = reactExports.useState(""); 9 const [description, setDescription] = reactExports.useState(""); 10 const [tags, setTags] = reactExports.useState([]); 11 const [tagInput, setTagInput] = reactExports.useState(""); 12 const [eulaAccepted, setEulaAccepted] = reactExports.useState(false); 13 const [uploading, setUploading] = reactExports.useState(false); 14 const [error, setError] = reactExports.useState(null); 15 const handleFileSelect = (e) => { 16 if (e.target.files && e.target.files.length > 0) { 17 const selected = e.target.files[0]; 18 const ext = selected.name.toLowerCase(); 19 if (ext.endsWith(".sf2") || ext.endsWith(".sf3")) { 20 setFile(selected); 21 setError(null); 22 if (!name) { 23 setName(selected.name.replace(/\.h?sf[23]$/i, "")); 24 } 25 } else { 26 setFile(null); 27 setError("Only .sf2 and .sf3 files are supported."); 28 } 29 } 30 }; 31 const handleAddTag = () => { 32 const val = tagInput.trim().toLowerCase(); 33 if (val && !tags.includes(val) && tags.length < 5) { 34 setTags([...tags, val]); 35 setTagInput(""); 36 } 37 }; 38 const handleRemoveTag = (t) => { 39 setTags(tags.filter((tag) => tag !== t)); 40 }; 41 const handleSubmit = async (e) => { 42 var _a, _b; 43 e.preventDefault(); 44 if (!file) { 45 setError("Please select a soundfont file."); 46 return; 47 } 48 if (!eulaAccepted) { 49 setError("You must accept the EULA to upload a soundfont."); 50 return; 51 } 52 setUploading(true); 53 setError(null); 54 try { 55 const formData = new FormData(); 56 formData.append("soundfont", file); 57 formData.append("name", name); 58 formData.append("description", description); 59 formData.append("tags", JSON.stringify(tags)); 60 formData.append("eulaAccepted", "true"); 61 const res = await api.uploadSoundfont(formData); 62 if (res.success) { 63 onNavigate(`/soundfont/${res.soundfont.slug}`); 64 } else { 65 throw new Error("Upload failed"); 66 } 67 } catch (err) { 68 console.error("Upload error:", err); 69 setError(((_b = (_a = err.response) == null ? void 0 : _a.data) == null ? void 0 : _b.error) || err.message || "An error occurred during upload."); 70 setUploading(false); 71 } 72 }; 73 if (!isAdmin) { 74 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "max-w-4xl mx-auto py-24 px-6 text-center", children: [
75 /* @__PURE__ */ jsxRuntimeExports.jsx(ShieldCheck, { className: "w-24 h-24 mx-auto text-zinc-300 mb-6" }), 76 /* @__PURE__ */ jsxRuntimeExports.jsx("h1", { className: "text-3xl font-black mb-4", children: "Coming Soon" }) 77 ] }); 78 } 79 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "max-w-3xl mx-auto px-4 py-12", children: [ 80 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-8 text-center", children: [ 81 /* @__PURE__ */ jsxRuntimeExports.jsx("h1", { className: "text-4xl font-black text-zinc-900 dark:text-white mb-4", children: "Share a Soundfont" }), 82 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-lg text-zinc-600 dark:text-zinc-400 max-w-xl mx-auto", children: "Contribute to the community gallery. We'll extract, compress, and optimize it for web playback automatically." }) 83 ] }), 84 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "bg-white dark:bg-zinc-900 rounded-3xl shadow-xl overflow-hidden border border-zinc-200 dark:border-zinc-800", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("form", { onSubmit: handleSubmit, className: "p-8", children: [ 85 error && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-8 bg-red-50 dark:bg-red-900/30 border border-red-200 dark:border-red-800 rounded-2xl p-4 flex items-start gap-3 text-red-700 dark:text-red-400", children: [
86 /* @__PURE__ */ jsxRuntimeExports.jsx(CircleAlert, { className: "w-5 h-5 shrink-0 mt-0.5" }), 87 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm font-medium", children: error }) 88 ] }), 89 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-8", children: [ 90 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-sm font-bold text-zinc-700 dark:text-zinc-300 mb-2", children: "Soundfont File (.sf2, .sf3)" }), 91 /* @__PURE__ */ jsxRuntimeExports.jsxs( 92 "div", 93 { 94 className: `border-2 border-dashed rounded-2xl p-8 text-center transition-all ${file ? "border-indigo-500 bg-indigo-50 dark:bg-indigo-900/20" : "border-zinc-300 dark:border-zinc-700 hover:border-indigo-400 hover:bg-zinc-50 dark:hover:bg-zinc-800/50"}`, 95 children: [ 96 /* @__PURE__ */ jsxRuntimeExports.jsx( 97 "input", 98 { 99 type: "file", 100 ref: fileInputRef, 101 onChange: handleFileSelect, 102 accept: ".sf2,.sf3", 103 className: "hidden" 104 } 105 ), 106 file ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center", children: [
107 /* @__PURE__ */ jsxRuntimeExports.jsx(CircleCheckBig, { className: "w-12 h-12 text-indigo-500 mb-3" }), 108 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-bold text-zinc-900 dark:text-white mb-1", children: file.name }), 109 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-sm text-zinc-500 font-medium", children: [ 110 (file.size / (1024 * 1024)).toFixed(1), 111 " MB" 112 ] }), 113 /* @__PURE__ */ jsxRuntimeExports.jsx( 114 "button", 115 { 116 type: "button", 117 onClick: () => setFile(null), 118 className: "mt-4 text-xs font-bold text-red-500 hover:text-red-600 transition-colors", 119 children: "Remove file" 120 } 121 ) 122 ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center cursor-pointer", onClick: () => { 123 var _a; 124 return (_a = fileInputRef.current) == null ? void 0 : _a.click(); 125 }, children: [
126 /* @__PURE__ */ jsxRuntimeExports.jsx(Upload, { className: "w-12 h-12 text-zinc-400 mb-3" }), 127 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-bold text-zinc-900 dark:text-white mb-1", children: "Click to select a file" }), 128 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm text-zinc-500 font-medium", children: "Max 500MB. SF2 or SF3 formats only." }) 129 ] }) 130 ] 131 } 132 ) 133 ] }), 134 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-6", children: [ 135 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 136 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-sm font-bold text-zinc-700 dark:text-zinc-300 mb-2", children: "Display Name" }), 137 /* @__PURE__ */ jsxRuntimeExports.jsx( 138 "input", 139 { 140 type: "text", 141 required: true, 142 value: name, 143 onChange: (e) => setName(e.target.value), 144 className: "w-full bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl px-4 py-3 text-zinc-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition-all font-medium", 145 placeholder: "e.g., Yamaha Grand Piano HQ" 146 } 147 ) 148 ] }),
149 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 150 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-sm font-bold text-zinc-700 dark:text-zinc-300 mb-2", children: "Description" }), 151 /* @__PURE__ */ jsxRuntimeExports.jsx( 152 "textarea", 153 { 154 value: description, 155 onChange: (e) => setDescription(e.target.value), 156 rows: 4, 157 className: "w-full bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl px-4 py-3 text-zinc-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition-all font-medium resize-none", 158 placeholder: "Describe the origin, quality, and instruments included in this soundfont..." 159 } 160 ) 161 ] }), 162 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 163 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-sm font-bold text-zinc-700 dark:text-zinc-300 mb-2", children: "Tags (Max 5)" }),
164 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 mb-3", children: [ 165 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative flex-1", children: [ 166 /* @__PURE__ */ jsxRuntimeExports.jsx(Tag, { className: "absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-zinc-400" }), 167 /* @__PURE__ */ jsxRuntimeExports.jsx( 168 "input", 169 { 170 type: "text", 171 value: tagInput, 172 onChange: (e) => setTagInput(e.target.value), 173 onKeyDown: (e) => e.key === "Enter" && (e.preventDefault(), handleAddTag()), 174 disabled: tags.length >= 5, 175 className: "w-full bg-zinc-50 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl pl-10 pr-4 py-3 text-zinc-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition-all font-medium", 176 placeholder: tags.length >= 5 ? "Tag limit reached" : "Add a tag (e.g. piano, 8-bit, orchestral) and press Enter" 177 } 178 ) 179 ] }),
180 /* @__PURE__ */ jsxRuntimeExports.jsx( 181 "button", 182 { 183 type: "button", 184 onClick: handleAddTag, 185 disabled: tags.length >= 5 || !tagInput.trim(), 186 className: "px-6 py-3 bg-zinc-200 dark:bg-zinc-700 text-zinc-900 dark:text-white font-bold rounded-xl hover:bg-zinc-300 dark:hover:bg-zinc-600 transition-all disabled:opacity-50 disabled:cursor-not-allowed", 187 children: "Add" 188 } 189 ) 190 ] }), 191 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex flex-wrap gap-2", children: tags.map((tag) => /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1 bg-indigo-50 dark:bg-indigo-900/30 text-indigo-700 dark:text-indigo-400 px-3 py-1.5 rounded-lg text-sm font-bold", children: [ 192 "#", 193 tag, 194 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", onClick: () => handleRemoveTag(tag), className: "ml-1 text-indigo-400 hover:text-indigo-600 dark:hover:text-indigo-200 transition-colors", children: /* @__PURE__ */ jsxRuntimeExports.jsx(X, { className: "w-4 h-4" }) }) 195 ] }, tag)) }) 196 ] }),
197 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "pt-6 border-t border-zinc-100 dark:border-zinc-800", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-start gap-3 cursor-pointer group", children: [ 198 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "relative flex items-center justify-center mt-0.5", children: /* @__PURE__ */ jsxRuntimeExports.jsx( 199 "input", 200 { 201 type: "checkbox", 202 required: true, 203 checked: eulaAccepted, 204 onChange: (e) => setEulaAccepted(e.target.checked), 205 className: "w-5 h-5 rounded border-zinc-300 dark:border-zinc-600 text-indigo-600 focus:ring-indigo-500 dark:bg-zinc-800 transition-colors cursor-pointer" 206 } 207 ) }), 208 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-sm font-medium text-zinc-600 dark:text-zinc-400 leading-relaxed", children: [ 209 "I confirm that I have the right to share this soundfont, and it does not violate any copyright or licensing agreements. I agree to the ",
210 /* @__PURE__ */ jsxRuntimeExports.jsx("a", { href: "#", className: "text-indigo-600 dark:text-indigo-400 hover:underline", children: "Community Guidelines" }), 211 "." 212 ] }) 213 ] }) }) 214 ] }), 215 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-8 pt-8 border-t border-zinc-100 dark:border-zinc-800 flex justify-end", children: /* @__PURE__ */ jsxRuntimeExports.jsx( 216 "button", 217 { 218 type: "submit", 219 disabled: uploading || !file || !eulaAccepted, 220 className: `flex items-center justify-center gap-2 px-10 py-4 font-bold rounded-2xl transition-all ${uploading || !file || !eulaAccepted ? "bg-zinc-200 dark:bg-zinc-800 text-zinc-500 cursor-not-allowed" : "bg-indigo-600 hover:bg-indigo-700 text-white shadow-lg hover:shadow-indigo-500/25 hover:-translate-y-0.5 active:scale-95"}`, 221 children: uploading ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [ 222 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-5 h-5 border-2 border-white/30 border-t-white rounded-full animate-spin" }), 223 "Uploading..." 224 ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
225 /* @__PURE__ */ jsxRuntimeExports.jsx(Upload, { className: "w-5 h-5" }), 226 "Upload Soundfont" 227 ] }) 228 } 229 ) }) 230 ] }) }) 231 ] }); 232}; 233export { 234 SoundfontUploadPage as default 235};
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.