1import { r as reactExports, j as jsxRuntimeExports, R as React } from "./vendor-react-Bay9Y7Lb.js"; 2import { a as api } from "./index-w5lXls15.js"; 3import { b as getGeminiAI, c as getAiImageModel } from "./ai-B0Mlroaa.js"; 4import { bv as CircleCheckBig, b1 as CircleAlert, bK as PanelsTopLeft, cf as Plus, b6 as LoaderCircle, dM as Move, bA as Eye, bz as EyeOff, cx as List, b5 as Trash2, aX as ArrowLeft, bR as ExternalLink, b9 as Settings, aW as X, d9 as Image, bi as Sparkles, bn as Globe, cB as ChevronUp, cC as ChevronDown, f9 as Pen, bf as Search, bg as Music, b0 as FileText } from "./vendor-D0P4XgNj.js"; 5import "./vendor-i18n-BC7ZMXWQ.js"; 6import "./vendor-ai-gQv1oOjO.js"; 7function CollectionManager() { 8 var _a; 9 const [collections, setCollections] = reactExports.useState([]); 10 const [loading, setLoading] = reactExports.useState(true); 11 const [error, setError] = reactExports.useState(null); 12 const [notification, setNotification] = reactExports.useState(null); 13 const [expandedSections, setExpandedSections] = reactExports.useState(/* @__PURE__ */ new Set()); 14 const [selectedCollection, setSelectedCollection] = reactExports.useState(null); 15 const [editingCollection, setEditingCollection] = reactExports.useState(null); 16 const [editingSection, setEditingSection] = reactExports.useState(null); 17 const [editingSong, setEditingSong] = reactExports.useState(null); 18 const [rightPanelTab, setRightPanelTab] = reactExports.useState("works"); 19 const [expandedSongId, setExpandedSongId] = reactExports.useState(null); 20 const [songMidiInput, setSongMidiInput] = reactExports.useState(""); 21 const [draggedIdx, setDraggedIdx] = reactExports.useState(null); 22 const [draggedSectionIdx, setDraggedSectionIdx] = reactExports.useState(null); 23 const [draggedTrackIdx, setDraggedTrackIdx] = reactExports.useState(null); 24 const [draggedSongIdx, setDraggedSongIdx] = reactExports.useState(null); 25 const [tagSearch, setTagSearch] = reactExports.useState(""); 26 const [tagSuggestions, setTagSuggestions] = reactExports.useState([]); 27 const [allSourceTags, setAllSourceTags] = reactExports.useState([]); 28 const [showTagSuggestions, setShowTagSuggestions] = reactExports.useState(false); 29 const [trackSearch, setTrackSearch] = reactExports.useState(""); 30 const [trackResults, setTrackResults] = reactExports.useState([]); 31 const [searchingTracks, setSearchingTracks] = reactExports.useState(false); 32 const [generatingBannerId, setGeneratingBannerId] = reactExports.useState(null); 33 const [batchGenerating, setBatchGenerating] = reactExports.useState(false); 34 const [batchProgress, setBatchProgress] = reactExports.useState(""); 35 const [tracksCache, setTracksCache] = reactExports.useState({}); 36 const tagInputRef = reactExports.useRef(null); 37 const triggerNotification = (message, type = "success") => { 38 setNotification({ message, type }); 39 setTimeout(() => setNotification(null), 4e3); 40 }; 41 const fetchCollections = reactExports.useCallback(async () => { 42 setLoading(true); 43 setError(null); 44 try { 45 const data = await api.getAdminCollections(); 46 const sorted = [...data].sort((a, b) => a.sortOrder - b.sortOrder); 47 setCollections(sorted); 48 } catch (e) { 49 console.error(e); 50 setError(e.message || "Failed to fetch collections"); 51 } finally { 52 setLoading(false); 53 } 54 }, []); 55 const fetchSourceTags = reactExports.useCallback(async () => { 56 try { 57 const data = await api.getTags({ type: "SOURCE" }); 58 setAllSourceTags(data); 59 } catch (e) { 60 console.error("Failed to load source tags:", e); 61 } 62 }, []); 63 const updateUrlParams = (updates) => { 64 const params = new URLSearchParams(window.location.search); 65 for (const [key, val] of Object.entries(updates)) { 66 if (val === null) { 67 params.delete(key); 68 } else { 69 params.set(key, val); 70 } 71 } 72 const queryString = params.toString(); 73 const newUrl = queryString ? `${window.location.pathname}?${queryString}` : window.location.pathname; 74 window.history.pushState({}, "", newUrl); 75 }; 76 reactExports.useEffect(() => { 77 fetchCollections(); 78 fetchSourceTags(); 79 }, [fetchCollections, fetchSourceTags]); 80 reactExports.useEffect(() => { 81 if (collections.length > 0 && !selectedCollection) { 82 const params = new URLSearchParams(window.location.search); 83 const colParam = params.get("collection"); 84 if (colParam) { 85 const targetCol = collections.find((c) => c.slug === colParam || c.id === colParam); 86 if (targetCol) { 87 handleSelectCollection(targetCol); 88 } 89 } 90 } 91 }, [collections]); 92 reactExports.useEffect(() => { 93 var _a2; 94 if (selectedCollection) { 95 const params = new URLSearchParams(window.location.search); 96 const secParam = params.get("section"); 97 if (secParam && (!editingSection || editingSection.id !== secParam && ((_a2 = editingSection.composer) == null ? void 0 : _a2.toLowerCase().replace(/[^a-z0-9]+/g, "-")) !== secParam)) { 98 const targetSec = selectedCollection.sections.find( 99 (s) => s.id === secParam || s.composer && s.composer.toLowerCase().replace(/[^a-z0-9]+/g, "-") === secParam || s.title.toLowerCase().replace(/[^a-z0-9]+/g, "-") === secParam 100 );
101 if (targetSec) { 102 setEditingSection(targetSec); 103 } 104 } 105 } 106 }, [selectedCollection]); 107 reactExports.useEffect(() => { 108 const handlePopState = () => { 109 const params = new URLSearchParams(window.location.search); 110 const colParam = params.get("collection"); 111 const secParam = params.get("section"); 112 if (!colParam) { 113 setSelectedCollection(null); 114 setEditingSection(null); 115 } else if (collections.length > 0) { 116 const targetCol = collections.find((c) => c.slug === colParam || c.id === colParam); 117 if (targetCol) { 118 if ((selectedCollection == null ? void 0 : selectedCollection.id) !== targetCol.id) { 119 handleSelectCollection(targetCol); 120 } else if (!secParam) { 121 setEditingSection(null); 122 } 123 } 124 } 125 }; 126 window.addEventListener("popstate", handlePopState); 127 return () => window.removeEventListener("popstate", handlePopState); 128 }, [collections, selectedCollection]); 129 const handleSelectCollection = async (collection) => { 130 setLoading(true); 131 try { 132 const data = await api.getAdminCollection(collection.id); 133 setSelectedCollection(data); 134 setEditingCollection(null); 135 setEditingSection(null); 136 setExpandedSections(/* @__PURE__ */ new Set()); 137 const secParam = new URLSearchParams(window.location.search).get("section"); 138 updateUrlParams({ collection: collection.slug, section: secParam }); 139 const cacheUpdates = {}; 140 for (const section of data.sections) { 141 try { 142 const { tracks: sectionTracks } = await api.getSectionTracks(data.slug, section.id, "title", 100); 143 sectionTracks.forEach((t) => { 144 cacheUpdates[String(t.id)] = { title: t.title, artist: t.artist }; 145 }); 146 } catch (err) { 147 console.warn(`Failed to preload tracks for section ${section.id}:`, err); 148 } 149 } 150 setTracksCache((prev) => ({ ...prev, ...cacheUpdates })); 151 } catch (e) { 152 triggerNotification(e.message || "Failed to load collection details", "error"); 153 } finally { 154 setLoading(false); 155 } 156 }; 157 const handleSaveCollection = async () => { 158 if (!(editingCollection == null ? void 0 : editingCollection.title) || !(editingCollection == null ? void 0 : editingCollection.slug)) { 159 triggerNotification("Title and Slug are required", "error"); 160 return; 161 } 162 setLoading(true); 163 try { 164 if (editingCollection.id) { 165 const updated = await api.updateAdminCollection(editingCollection.id, editingCollection); 166 triggerNotification("Collection updated successfully"); 167 await fetchCollections(); 168 if (selectedCollection && selectedCollection.id === editingCollection.id) { 169 await handleSelectCollection(updated); 170 } 171 } else { 172 const created = await api.createAdminCollection({ 173 ...editingCollection, 174 sortOrder: collections.length, 175 theme: editingCollection.theme || { 176 gradientStart: "#4f46e5", 177 gradientEnd: "#7c3aed", 178 accentColor: "#3b82f6", 179 textColor: "#ffffff" 180 } 181 }); 182 triggerNotification("Collection created successfully"); 183 await fetchCollections(); 184 await handleSelectCollection(created); 185 } 186 setEditingCollection(null); 187 } catch (e) { 188 triggerNotification(e.message || "Failed to save collection", "error"); 189 } finally { 190 setLoading(false); 191 } 192 }; 193 const handleTogglePublished = async (col, e) => { 194 e.stopPropagation(); 195 try { 196 const nextStatus = !col.isPublished; 197 await api.updateAdminCollection(col.id, { isPublished: nextStatus }); 198 setCollections((prev) => prev.map((c) => c.id === col.id ? { ...c, isPublished: nextStatus } : c)); 199 if (selectedCollection && selectedCollection.id === col.id) { 200 setSelectedCollection((prev) => prev ? { ...prev, isPublished: nextStatus } : null); 201 } 202 triggerNotification(`Collection ${nextStatus ? "published" : "unpublished"} successfully`); 203 } catch (err) { 204 triggerNotification(err.message || "Failed to toggle status", "error"); 205 } 206 }; 207 const handleDeleteCollection = async (id, title, e) => { 208 e.stopPropagation(); 209 if (!confirm(`Are you sure you want to delete the collection "${title}"? All sections inside will be deleted.`)) { 210 return; 211 }
212 setLoading(true); 213 try { 214 await api.deleteAdminCollection(id); 215 triggerNotification("Collection deleted successfully"); 216 if ((selectedCollection == null ? void 0 : selectedCollection.id) === id) { 217 setSelectedCollection(null); 218 } 219 setEditingCollection(null); 220 setEditingSection(null); 221 await fetchCollections(); 222 } catch (err) { 223 triggerNotification(err.message || "Failed to delete collection", "error"); 224 } finally { 225 setLoading(false); 226 } 227 }; 228 const handleSaveSong = async () => { 229 if (!editingSong || !editingSong.title || !selectedCollection || !(editingSection == null ? void 0 : editingSection.id)) { 230 triggerNotification("Title is required", "error"); 231 return; 232 } 233 setLoading(true); 234 try { 235 if (editingSong.id) { 236 const updated = await api.updateSectionSong(selectedCollection.id, editingSection.id, editingSong.id, editingSong); 237 setEditingSection((prev) => { 238 var _a2; 239 if (!prev) return prev; 240 return { ...prev, songs: (_a2 = prev.songs) == null ? void 0 : _a2.map((s) => s.id === updated.id ? updated : s) }; 241 }); 242 triggerNotification("Work updated successfully"); 243 } else { 244 const created = await api.createSectionSong(selectedCollection.id, editingSection.id, editingSong); 245 setEditingSection((prev) => { 246 if (!prev) return prev; 247 return { ...prev, songs: [...prev.songs || [], created] }; 248 }); 249 triggerNotification("Work created successfully"); 250 } 251 setEditingSong(null); 252 } catch (e) { 253 triggerNotification(e.message || "Failed to save work", "error"); 254 } finally { 255 setLoading(false); 256 } 257 }; 258 const handleDeleteSong = async (songId, title, e) => { 259 e.stopPropagation(); 260 if (!selectedCollection || !(editingSection == null ? void 0 : editingSection.id)) return; 261 if (!confirm(`Are you sure you want to delete the curated work "${title}"?`)) return; 262 setLoading(true); 263 try { 264 await api.deleteSectionSong(selectedCollection.id, editingSection.id, songId); 265 setEditingSection((prev) => { 266 if (!prev) return prev; 267 return { ...prev, songs: (prev.songs || []).filter((s) => s.id !== songId) }; 268 }); 269 triggerNotification("Work deleted successfully"); 270 } catch (e2) { 271 triggerNotification(e2.message || "Failed to delete work", "error"); 272 } finally { 273 setLoading(false); 274 } 275 }; 276 const handleSongDragStart = (e, index) => { 277 setDraggedSongIdx(index); 278 e.dataTransfer.effectAllowed = "move"; 279 e.dataTransfer.setData("text/plain", index.toString()); 280 }; 281 const handleSongDragOver = (e) => { 282 e.preventDefault(); 283 e.dataTransfer.dropEffect = "move"; 284 }; 285 const handleSongDrop = async (e, dropIndex) => { 286 e.preventDefault(); 287 const actualDragIdx = draggedSongIdx !== null ? draggedSongIdx : parseInt(e.dataTransfer.getData("text/plain"), 10); 288 if (actualDragIdx === null || isNaN(actualDragIdx) || actualDragIdx === dropIndex || !(editingSection == null ? void 0 : editingSection.songs) || !selectedCollection || !editingSection.id) return; 289 const newSongs = [...editingSection.songs]; 290 const [removed] = newSongs.splice(actualDragIdx, 1); 291 newSongs.splice(dropIndex, 0, removed); 292 setEditingSection((prev) => prev ? { ...prev, songs: newSongs } : null); 293 try { 294 const songIds = newSongs.map((s) => s.id); 295 await api.reorderSectionSongs(selectedCollection.id, editingSection.id, songIds); 296 } catch (err) { 297 triggerNotification(err.message || "Failed to save work order", "error"); 298 const collectionData = await api.getAdminCollection(selectedCollection.id); 299 const refetchedSection = collectionData.sections.find((s) => s.id === editingSection.id); 300 if (refetchedSection) setEditingSection(refetchedSection); 301 } 302 setDraggedSongIdx(null); 303 }; 304 const handleSaveSection = async () => { 305 if (!selectedCollection) return; 306 if (!(editingSection == null ? void 0 : editingSection.title)) { 307 triggerNotification("Section title is required", "error"); 308 return; 309 }
310 setLoading(true); 311 try { 312 const sectionPayload = { 313 title: editingSection.title, 314 parentId: editingSection.parentId || null, 315 subtitle: editingSection.subtitle || "", 316 description: editingSection.description || "", 317 bannerImage: editingSection.bannerImage || "", 318 composer: editingSection.composer || "", 319 tagSlugs: editingSection.tagSlugs || [], 320 curatedTrackIds: editingSection.curatedTrackIds || [], 321 isPublished: editingSection.isPublished !== void 0 ? editingSection.isPublished : true 322 }; 323 if (editingSection.id) { 324 await api.updateAdminSection(selectedCollection.id, editingSection.id, sectionPayload); 325 triggerNotification("Section updated successfully"); 326 } else { 327 await api.createAdminSection(selectedCollection.id, sectionPayload); 328 triggerNotification("Section created successfully"); 329 } 330 await handleSelectCollection(selectedCollection); 331 setEditingSection(null); 332 } catch (err) { 333 triggerNotification(err.message || "Failed to save section", "error"); 334 } finally { 335 setLoading(false); 336 } 337 }; 338 const handleDeleteSection = async (sectionId, title) => { 339 if (!selectedCollection) return; 340 if (!confirm(`Are you sure you want to delete the section "${title}"?`)) return; 341 setLoading(true); 342 try { 343 await api.deleteAdminSection(selectedCollection.id, sectionId); 344 triggerNotification("Section deleted successfully"); 345 await handleSelectCollection(selectedCollection); 346 if ((editingSection == null ? void 0 : editingSection.id) === sectionId) { 347 setEditingSection(null); 348 } 349 } catch (err) { 350 triggerNotification(err.message || "Failed to delete section", "error"); 351 } finally { 352 setLoading(false); 353 } 354 }; 355 const handleCollDragStart = (e, index) => { 356 setDraggedIdx(index); 357 e.dataTransfer.effectAllowed = "move"; 358 e.dataTransfer.setData("text/plain", index.toString()); 359 }; 360 const handleCollDragOver = (e) => { 361 e.preventDefault(); 362 }; 363 const handleCollDrop = async (e, dropIndex) => { 364 e.preventDefault(); 365 const dragIndexStr = e.dataTransfer.getData("text/plain"); 366 const dragIndex = parseInt(dragIndexStr); 367 const actualDragIdx = !isNaN(dragIndex) ? dragIndex : draggedIdx; 368 if (actualDragIdx === null || actualDragIdx === dropIndex) return; 369 const newColls = [...collections]; 370 const [draggedItem] = newColls.splice(actualDragIdx, 1); 371 newColls.splice(dropIndex, 0, draggedItem); 372 setCollections(newColls); 373 setDraggedIdx(null); 374 try { 375 await Promise.all( 376 newColls.map((c, index) => api.updateAdminCollection(c.id, { sortOrder: index })) 377 ); 378 triggerNotification("Collection order updated"); 379 } catch (err) { 380 console.error("Failed to save collection order:", err); 381 triggerNotification("Failed to save collection order", "error"); 382 fetchCollections(); 383 } 384 }; 385 const handleSecDragStart = (e, index) => { 386 setDraggedSectionIdx(index); 387 e.dataTransfer.effectAllowed = "move"; 388 e.dataTransfer.setData("text/plain", index.toString()); 389 }; 390 const handleSecDragOver = (e) => { 391 e.preventDefault(); 392 }; 393 const handleSecDrop = async (e, dropIndex) => { 394 e.preventDefault(); 395 if (!selectedCollection) return; 396 const dragIndexStr = e.dataTransfer.getData("text/plain"); 397 const dragIndex = parseInt(dragIndexStr); 398 const actualDragIdx = !isNaN(dragIndex) ? dragIndex : draggedSectionIdx; 399 if (actualDragIdx === null || actualDragIdx === dropIndex) return; 400 const newSections = [...selectedCollection.sections]; 401 const [draggedItem] = newSections.splice(actualDragIdx, 1); 402 newSections.splice(dropIndex, 0, draggedItem); 403 setSelectedCollection({ 404 ...selectedCollection, 405 sections: newSections 406 }); 407 setDraggedSectionIdx(null); 408 try { 409 const sectionIds = newSections.map((s) => s.id); 410 await api.reorderAdminSections(selectedCollection.id, sectionIds); 411 triggerNotification("Section order updated"); 412 } catch (err) { 413 console.error("Failed to save section order:", err); 414 triggerNotification("Failed to save section order", "error"); 415 handleSelectCollection(selectedCollection); 416 } 417 }; 418 reactExports.useEffect(() => { 419 if (!tagSearch.trim()) { 420 setTagSuggestions([]); 421 return; 422 } 423 const query = tagSearch.toLowerCase(); 424 const matches = allSourceTags.filter( 425 (t) => (t.name.toLowerCase().includes(query) || t.slug.toLowerCase().includes(query)) && !((editingSection == null ? void 0 : editingSection.tagSlugs) || []).includes(t.slug) 426 ); 427 setTagSuggestions(matches.slice(0, 10)); 428 }, [tagSearch, allSourceTags, editingSection == null ? void 0 : editingSection.tagSlugs]); 429 const handleAddTag = (slug) => { 430 var _a2; 431 const currentSlugs = (editingSection == null ? void 0 : editingSection.tagSlugs) || []; 432 if (!currentSlugs.includes(slug)) { 433 setEditingSection((prev) => prev ? { 434 ...prev, 435 tagSlugs: [...currentSlugs, slug] 436 } : null); 437 } 438 setTagSearch(""); 439 setShowTagSuggestions(false); 440 (_a2 = tagInputRef.current) == null ? void 0 : _a2.focus(); 441 }; 442 const handleRemoveTag = (slugToRemove) => { 443 setEditingSection((prev) => prev ? { 444 ...prev, 445 tagSlugs: (prev.tagSlugs || []).filter((s) => s !== slugToRemove) 446 } : null); 447 }; 448 reactExports.useEffect(() => { 449 if (!trackSearch.trim() || trackSearch.length < 2) { 450 setTrackResults([]); 451 return; 452 } 453 const delayDebounce = setTimeout(async () => { 454 setSearchingTracks(true);
455 try { 456 const tracks = await api.getTracks(trackSearch, "all"); 457 setTrackResults(tracks.slice(0, 15)); 458 } catch (err) { 459 console.error(err); 460 } finally { 461 setSearchingTracks(false); 462 } 463 }, 300); 464 return () => clearTimeout(delayDebounce); 465 }, [trackSearch]); 466 const handlePinTrack = (track) => { 467 setTracksCache((prev) => ({ 468 ...prev, 469 [String(track.id)]: { title: track.title, artist: track.artist } 470 })); 471 const currentPinned = (editingSection == null ? void 0 : editingSection.curatedTrackIds) || []; 472 const trackIdStr = String(track.id); 473 if (!currentPinned.includes(trackIdStr)) { 474 setEditingSection((prev) => prev ? { 475 ...prev, 476 curatedTrackIds: [...currentPinned, trackIdStr] 477 } : null); 478 } 479 setTrackSearch(""); 480 setTrackResults([]); 481 }; 482 const handleUnpinTrack = (trackIdToRemove) => { 483 setEditingSection((prev) => prev ? { 484 ...prev, 485 curatedTrackIds: (prev.curatedTrackIds || []).filter((id) => id !== trackIdToRemove) 486 } : null); 487 }; 488 const handleTrackDragStart = (e, index) => { 489 setDraggedTrackIdx(index); 490 e.dataTransfer.effectAllowed = "move"; 491 e.dataTransfer.setData("text/plain", index.toString()); 492 }; 493 const handleTrackDragOver = (e) => { 494 e.preventDefault(); 495 }; 496 const handleTrackDrop = (e, dropIndex) => { 497 e.preventDefault(); 498 const dragIndexStr = e.dataTransfer.getData("text/plain"); 499 const dragIndex = parseInt(dragIndexStr); 500 const actualDragIdx = !isNaN(dragIndex) ? dragIndex : draggedTrackIdx; 501 if (actualDragIdx === null || actualDragIdx === dropIndex || !editingSection) return; 502 const newPinned = [...editingSection.curatedTrackIds || []]; 503 const [draggedItem] = newPinned.splice(actualDragIdx, 1); 504 newPinned.splice(dropIndex, 0, draggedItem); 505 setEditingSection({ 506 ...editingSection, 507 curatedTrackIds: newPinned 508 }); 509 setDraggedTrackIdx(null); 510 }; 511 const getThemeColor = (theme, key, fallback) => { 512 if (!theme) return fallback; 513 try { 514 return theme[key] || fallback; 515 } catch { 516 return fallback; 517 } 518 }; 519 const updateThemeKey = (key, val) => { 520 setEditingCollection((prev) => { 521 const currentTheme = (prev == null ? void 0 : prev.theme) || { 522 gradientStart: "#4f46e5", 523 gradientEnd: "#7c3aed", 524 accentColor: "#3b82f6", 525 textColor: "#ffffff" 526 }; 527 return { 528 ...prev, 529 theme: { 530 ...currentTheme, 531 [key]: val 532 } 533 }; 534 }); 535 }; 536 const buildBannerPrompt = (section, collection) => { 537 let prompt = `Create a cinematic, atmospheric banner image for a music website collection. `; 538 if (collection == null ? void 0 : collection.title) prompt += `The collection topic is '${collection.title}'. `; 539 if (section.title) prompt += `The specific section is about '${section.title}'. `; 540 if (section.subtitle) prompt += `Subtitle: '${section.subtitle}'. `; 541 if (section.composer) prompt += `Composer/Artist: '${section.composer}'. `; 542 if (section.description) prompt += `Context: ${section.description.substring(0, 300)}. `; 543 prompt += `Style: dramatic lighting, warm tones, high quality, artistic illustration suitable for a website header banner. Aspect ratio 16:9, no text, no words.`; 544 return prompt; 545 }; 546 const handleGenerateSectionBanner = async (section, isBatch = false) => { 547 var _a2, _b, _c; 548 if (!selectedCollection) return false; 549 const secId = section.id || "new"; 550 setGeneratingBannerId(String(secId)); 551 try { 552 const prompt = buildBannerPrompt(section, selectedCollection); 553 const ai = getGeminiAI(); 554 const model = getAiImageModel(); 555 const response = await ai.models.generateContent({ 556 model, 557 contents: { parts: [{ text: prompt }] } 558 }); 559 let dataUrl = ""; 560 for (const part of ((_c = (_b = (_a2 = response.candidates) == null ? void 0 : _a2[0]) == null ? void 0 : _b.content) == null ? void 0 : _c.parts) || []) { 561 if (part.inlineData) { 562 dataUrl = `data:${part.inlineData.mimeType};base64,${part.inlineData.data}`; 563 break; 564 } 565 } 566 if (!dataUrl) { 567 throw new Error("No image generated. The AI model might have returned text instead of an image."); 568 } 569 const safeName = (section.composer || section.title || "banner").toLowerCase().replace(/[^a-z0-9]+/g, "-"); 570 const res = await api.saveGeneratedAsset(dataUrl, `col-${selectedCollection.id}-${safeName}`); 571 if (editingSection && (!section.id || editingSection.id === section.id)) { 572 setEditingSection((prev) => prev ? { ...prev, bannerImage: res.url } : null); 573 } 574 if (section.id) { 575 await api.updateAdminSection(selectedCollection.id, section.id, { bannerImage: res.url }); 576 setSelectedCollection((prev) => { 577 if (!prev) return prev; 578 return { 579 ...prev, 580 sections: prev.sections.map((s) => s.id === section.id ? { ...s, bannerImage: res.url } : s) 581 }; 582 }); 583 } 584 if (!isBatch) { 585 triggerNotification("Banner generated successfully!"); 586 } 587 return true; 588 } catch (e) { 589 console.error(e); 590 if (!isBatch) { 591 triggerNotification(e.message || "Failed to generate banner", "error"); 592 } 593 return false;
594 } finally { 595 setGeneratingBannerId(null); 596 } 597 }; 598 const handleGenerateAllMissingBanners = async () => { 599 if (!selectedCollection) return; 600 const missingSections = selectedCollection.sections.filter((s) => !s.bannerImage); 601 if (missingSections.length === 0) { 602 triggerNotification("All sections already have banners!"); 603 return; 604 } 605 if (!confirm(`Generate banners for ${missingSections.length} sections? This may take a while.`)) { 606 return; 607 } 608 setBatchGenerating(true); 609 let successCount = 0; 610 for (let i = 0; i < missingSections.length; i++) { 611 setBatchProgress(`${i + 1}/${missingSections.length}`); 612 const success = await handleGenerateSectionBanner(missingSections[i], true); 613 if (success) successCount++; 614 } 615 setBatchGenerating(false); 616 setBatchProgress(""); 617 triggerNotification(`Batch generation complete! Created ${successCount} banners.`); 618 }; 619 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col h-full bg-zinc-50 dark:bg-zinc-950 text-zinc-800 dark:text-zinc-200", children: [
620 notification && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: `fixed top-4 right-4 z-50 px-4 py-3 rounded-lg shadow-lg flex items-center gap-2 border transition-all duration-300 ${notification.type === "success" ? "bg-emerald-50 border-emerald-200 text-emerald-800 dark:bg-emerald-950/80 dark:border-emerald-800 dark:text-emerald-300" : "bg-rose-50 border-rose-200 text-rose-800 dark:bg-rose-950/80 dark:border-rose-800 dark:text-rose-300"}`, children: [ 621 notification.type === "success" ? /* @__PURE__ */ jsxRuntimeExports.jsx(CircleCheckBig, { size: 18 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(CircleAlert, { size: 18 }), 622 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm font-medium", children: notification.message }) 623 ] }), 624 !selectedCollection ? ( 625 /* ---------------- COLLECTIONS DASHBOARD LIST ---------------- */ 626 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-6 flex-1 flex flex-col overflow-y-auto", children: [ 627 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center mb-6", children: [
628 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 629 /* @__PURE__ */ jsxRuntimeExports.jsxs("h1", { className: "text-2xl font-bold text-zinc-950 dark:text-white flex items-center gap-2", children: [ 630 /* @__PURE__ */ jsxRuntimeExports.jsx(PanelsTopLeft, { className: "text-indigo-500" }), 631 " MIDI Collections" 632 ] }), 633 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-zinc-500 text-sm mt-1", children: "Create and curate themed landing pages for MIDI files with custom tags, tracks, and beautiful visual templates." }) 634 ] }), 635 /* @__PURE__ */ jsxRuntimeExports.jsxs( 636 "button", 637 { 638 onClick: () => setEditingCollection({ 639 title: "", 640 slug: "", 641 subtitle: "", 642 description: "", 643 heroImage: "", 644 theme: { 645 gradientStart: "#111827", 646 gradientEnd: "#312e81", 647 accentColor: "#6366f1", 648 textColor: "#ffffff" 649 }, 650 isPublished: false, 651 metaTitle: "", 652 metaDescription: "" 653 }), 654 className: "bg-indigo-600 hover:bg-indigo-700 text-white font-semibold text-sm px-4 py-2.5 rounded-lg flex items-center gap-2 transition-all shadow-sm shadow-indigo-600/20", 655 children: [
656 /* @__PURE__ */ jsxRuntimeExports.jsx(Plus, { size: 16 }), 657 " Create Collection" 658 ] 659 } 660 ) 661 ] }), 662 editingCollection && !editingCollection.id && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-6 p-6 bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 rounded-xl shadow-sm", children: [ 663 /* @__PURE__ */ jsxRuntimeExports.jsxs("h2", { className: "text-lg font-bold mb-4 text-zinc-900 dark:text-white flex items-center gap-2", children: [ 664 /* @__PURE__ */ jsxRuntimeExports.jsx(Plus, { size: 18, className: "text-indigo-500" }), 665 " New Collection Details" 666 ] }), 667 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4 mb-4", children: [ 668 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 669 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-semibold text-zinc-500 uppercase tracking-wider mb-1", children: "Title *" }),
670 /* @__PURE__ */ jsxRuntimeExports.jsx( 671 "input", 672 { 673 type: "text", 674 className: "w-full bg-zinc-50 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-800 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 outline-none", 675 placeholder: "e.g. Broadway & Musicals", 676 value: editingCollection.title || "", 677 onChange: (e) => { 678 const val = e.target.value; 679 const autoSlug = val.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, ""); 680 setEditingCollection((prev) => ({ ...prev, title: val, slug: (prev == null ? void 0 : prev.slug) ? prev.slug : autoSlug })); 681 } 682 } 683 ) 684 ] }), 685 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 686 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-semibold text-zinc-500 uppercase tracking-wider mb-1", children: "Slug *" }),
687 /* @__PURE__ */ jsxRuntimeExports.jsx( 688 "input", 689 { 690 type: "text", 691 className: "w-full bg-zinc-50 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-800 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 outline-none", 692 placeholder: "e.g. broadway-musicals", 693 value: editingCollection.slug || "", 694 onChange: (e) => setEditingCollection((prev) => ({ ...prev, slug: e.target.value.toLowerCase() })) 695 } 696 ) 697 ] }) 698 ] }), 699 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-end gap-2 mt-6", children: [ 700 /* @__PURE__ */ jsxRuntimeExports.jsx( 701 "button", 702 { 703 onClick: () => setEditingCollection(null), 704 className: "px-4 py-2 text-sm border border-zinc-200 dark:border-zinc-800 rounded-lg hover:bg-zinc-100 dark:hover:bg-zinc-850 font-medium transition-colors", 705 children: "Cancel" 706 } 707 ),
708 /* @__PURE__ */ jsxRuntimeExports.jsx( 709 "button", 710 { 711 onClick: handleSaveCollection, 712 className: "px-4 py-2 text-sm bg-indigo-600 hover:bg-indigo-700 text-white font-medium rounded-lg transition-colors shadow-sm", 713 children: "Create Collection" 714 } 715 ) 716 ] }) 717 ] }), 718 loading && collections.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex flex-col justify-center items-center py-20 text-zinc-500", children: [ 719 /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "animate-spin text-indigo-500 mb-2", size: 32 }), 720 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Loading collections..." }) 721 ] }) : error ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex flex-col justify-center items-center py-20 text-rose-500", children: [ 722 /* @__PURE__ */ jsxRuntimeExports.jsx(CircleAlert, { className: "mb-2", size: 32 }),
723 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: error }) 724 ] }) : collections.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex flex-col justify-center items-center py-20 border-2 border-dashed border-zinc-200 dark:border-zinc-800 rounded-xl text-zinc-500 bg-zinc-50/50 dark:bg-zinc-900/10", children: [ 725 /* @__PURE__ */ jsxRuntimeExports.jsx(PanelsTopLeft, { className: "mb-3 text-zinc-400", size: 40 }), 726 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "font-medium text-sm", children: "No collections created yet" }), 727 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-zinc-400 mt-1", children: 'Click the "Create Collection" button above to get started.' }) 728 ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6", children: collections.map((col, index) => { 729 const gradStart = getThemeColor(col.theme, "gradientStart", "#4f46e5"); 730 const gradEnd = getThemeColor(col.theme, "gradientEnd", "#7c3aed"); 731 const txtColor = getThemeColor(col.theme, "textColor", "#ffffff"); 732 return /* @__PURE__ */ jsxRuntimeExports.jsxs( 733 "div", 734 { 735 draggable: true, 736 onDragStart: (e) => handleCollDragStart(e, index), 737 onDragOver: handleCollDragOver, 738 onDrop: (e) => handleCollDrop(e, index), 739 onClick: () => handleSelectCollection(col), 740 className: "group relative flex flex-col bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 rounded-xl overflow-hidden shadow-sm hover:shadow-md transition-all duration-300 cursor-pointer hover:border-zinc-400 dark:hover:border-zinc-700 select-none", 741 children: [
742 /* @__PURE__ */ jsxRuntimeExports.jsxs( 743 "div", 744 { 745 className: "h-24 p-4 flex flex-col justify-end relative transition-opacity duration-300", 746 style: { 747 backgroundImage: col.heroImage ? `url("${col.heroImage}"), linear-gradient(135deg, ${gradStart}, ${gradEnd})` : `linear-gradient(135deg, ${gradStart}, ${gradEnd})`, 748 backgroundSize: "cover", 749 backgroundPosition: "center" 750 }, 751 children: [ 752 col.heroImage && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute inset-0 bg-black/40 backdrop-blur-[2px]" }), 753 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute top-3 left-3 bg-black/40 backdrop-blur-md rounded-md p-1 opacity-0 group-hover:opacity-100 transition-opacity duration-200 cursor-move z-10", title: "Drag to reorder", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Move, { size: 14, className: "text-white" }) }),
754 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute top-3 right-3 flex items-center gap-1.5 z-10", children: /* @__PURE__ */ jsxRuntimeExports.jsx( 755 "button", 756 { 757 onClick: (e) => handleTogglePublished(col, e), 758 className: `rounded-full p-1.5 backdrop-blur-md transition-colors ${col.isPublished ? "bg-emerald-500/20 text-emerald-300 border border-emerald-500/30" : "bg-zinc-500/20 text-zinc-300 border border-zinc-500/30"}`, 759 title: col.isPublished ? "Published - Click to Unpublish" : "Unpublished - Click to Publish", 760 children: col.isPublished ? /* @__PURE__ */ jsxRuntimeExports.jsx(Eye, { size: 12 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(EyeOff, { size: 12 }) 761 } 762 ) }), 763 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] font-bold uppercase tracking-widest px-2 py-0.5 rounded bg-black/30 backdrop-blur-md w-fit z-10", style: { color: txtColor }, children: [ 764 "/", 765 col.slug 766 ] }) 767 ] 768 } 769 ),
770 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 flex-1 flex flex-col", children: [ 771 /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "font-bold text-base text-zinc-900 dark:text-white line-clamp-1", children: col.title }), 772 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-zinc-450 dark:text-zinc-500 mt-1 line-clamp-2 min-h-[32px]", children: col.subtitle || "No subtitle provided." }), 773 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center mt-4 pt-3 border-t border-zinc-150 dark:border-zinc-800/80", children: [ 774 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1 text-xs text-zinc-500 font-medium", children: [ 775 /* @__PURE__ */ jsxRuntimeExports.jsx(List, { size: 13 }), 776 " ", 777 col.sectionCount || 0, 778 " Sections" 779 ] }),
780 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1", children: [ 781 /* @__PURE__ */ jsxRuntimeExports.jsx( 782 "button", 783 { 784 onClick: (e) => { 785 e.stopPropagation(); 786 handleSelectCollection(col); 787 }, 788 className: "text-xs bg-zinc-100 hover:bg-zinc-200 dark:bg-zinc-800 dark:hover:bg-zinc-700 text-zinc-700 dark:text-zinc-300 font-semibold px-2.5 py-1.5 rounded-lg transition-colors flex items-center gap-1", 789 children: "Manage" 790 } 791 ), 792 /* @__PURE__ */ jsxRuntimeExports.jsx( 793 "button", 794 { 795 onClick: (e) => handleDeleteCollection(col.id, col.title, e), 796 className: "text-zinc-400 hover:text-rose-500 p-1.5 rounded-lg transition-colors", 797 title: "Delete Collection", 798 children: /* @__PURE__ */ jsxRuntimeExports.jsx(Trash2, { size: 14 }) 799 } 800 ) 801 ] }) 802 ] }) 803 ] }) 804 ] 805 }, 806 col.id 807 ); 808 }) }) 809 ] }) 810 ) : ( 811 /* ---------------- COLLECTION DETAILED WORKSPACE (METADATA + SECTIONS) ---------------- */
812 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex flex-col overflow-hidden", children: [ 813 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 bg-white dark:bg-zinc-900 border-b border-zinc-200 dark:border-zinc-800 flex justify-between items-center shrink-0", children: [ 814 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3", children: [ 815 /* @__PURE__ */ jsxRuntimeExports.jsx( 816 "button", 817 { 818 onClick: () => { 819 setSelectedCollection(null); 820 updateUrlParams({ collection: null, section: null }); 821 }, 822 className: "p-2 hover:bg-zinc-100 dark:hover:bg-zinc-800 rounded-lg text-zinc-550 transition-colors", 823 title: "Back to Collections Dashboard", 824 children: /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowLeft, { size: 18 }) 825 } 826 ), 827 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
828 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [ 829 /* @__PURE__ */ jsxRuntimeExports.jsx("h1", { className: "text-lg font-bold text-zinc-900 dark:text-white", children: selectedCollection.title }), 830 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-[10px] font-semibold px-2 py-0.5 rounded-full ${selectedCollection.isPublished ? "bg-emerald-100 text-emerald-800 dark:bg-emerald-950/50 dark:text-emerald-300" : "bg-zinc-200 text-zinc-800 dark:bg-zinc-800 dark:text-zinc-400"}`, children: selectedCollection.isPublished ? "Published" : "Draft" }) 831 ] }), 832 /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-xs text-zinc-500 mt-0.5", children: [ 833 "Slug: ", 834 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono", children: [ 835 "/", 836 selectedCollection.slug 837 ] }) 838 ] }) 839 ] }) 840 ] }),
841 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [ 842 /* @__PURE__ */ jsxRuntimeExports.jsxs( 843 "a", 844 { 845 href: `https://test.hamienet.hk:8443/collections/${selectedCollection.slug}`, 846 target: "_blank", 847 rel: "noreferrer", 848 className: "border border-zinc-200 dark:border-zinc-800 hover:bg-zinc-100 dark:hover:bg-zinc-800 text-zinc-700 dark:text-zinc-300 px-3.5 py-1.8 text-xs font-semibold rounded-lg flex items-center gap-1.5 transition-colors", 849 children: [ 850 /* @__PURE__ */ jsxRuntimeExports.jsx(Eye, { size: 13 }), 851 " View Live ", 852 /* @__PURE__ */ jsxRuntimeExports.jsx(ExternalLink, { size: 11 }) 853 ] 854 } 855 ), 856 /* @__PURE__ */ jsxRuntimeExports.jsxs( 857 "button", 858 { 859 onClick: () => setEditingCollection(selectedCollection), 860 className: "bg-indigo-600 hover:bg-indigo-700 text-white px-3.5 py-1.8 text-xs font-semibold rounded-lg flex items-center gap-1.5 transition-all shadow-sm shadow-indigo-600/10", 861 children: [
862 /* @__PURE__ */ jsxRuntimeExports.jsx(Settings, { size: 13 }), 863 " Edit Theme & Metadata" 864 ] 865 } 866 ) 867 ] }) 868 ] }), 869 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex overflow-hidden", children: [ 870 editingCollection && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "w-96 border-r border-zinc-200 dark:border-zinc-800 bg-white dark:bg-zinc-900 overflow-y-auto p-5 shrink-0 flex flex-col", children: [ 871 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center mb-4", children: [ 872 /* @__PURE__ */ jsxRuntimeExports.jsxs("h2", { className: "font-bold text-zinc-900 dark:text-white text-sm flex items-center gap-1.5", children: [ 873 /* @__PURE__ */ jsxRuntimeExports.jsx(Settings, { size: 16, className: "text-indigo-500" }), 874 " Collection Settings" 875 ] }), 876 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { onClick: () => setEditingCollection(null), className: "p-1 hover:bg-zinc-100 dark:hover:bg-zinc-800 rounded text-zinc-500", children: /* @__PURE__ */ jsxRuntimeExports.jsx(X, { size: 16 }) }) 877 ] }),
878 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4 flex-1", children: [ 879 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 880 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold text-zinc-450 uppercase mb-1", children: "Title" }), 881 /* @__PURE__ */ jsxRuntimeExports.jsx( 882 "input", 883 { 884 type: "text", 885 className: "w-full bg-zinc-50 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-800 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 outline-none", 886 value: editingCollection.title || "", 887 onChange: (e) => setEditingCollection((prev) => ({ ...prev, title: e.target.value })) 888 } 889 ) 890 ] }), 891 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 892 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold text-zinc-450 uppercase mb-1", children: "Slug" }),
893 /* @__PURE__ */ jsxRuntimeExports.jsx( 894 "input", 895 { 896 type: "text", 897 className: "w-full bg-zinc-50 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-800 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 outline-none", 898 value: editingCollection.slug || "", 899 onChange: (e) => setEditingCollection((prev) => ({ ...prev, slug: e.target.value.toLowerCase() })) 900 } 901 ) 902 ] }), 903 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 904 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold text-zinc-450 uppercase mb-1", children: "Subtitle" }), 905 /* @__PURE__ */ jsxRuntimeExports.jsx( 906 "input", 907 { 908 type: "text", 909 className: "w-full bg-zinc-50 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-800 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 outline-none", 910 value: editingCollection.subtitle || "", 911 onChange: (e) => setEditingCollection((prev) => ({ ...prev, subtitle: e.target.value })) 912 } 913 ) 914 ] }),
915 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 916 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold text-zinc-450 uppercase mb-1", children: "Description (Markdown Supported)" }), 917 /* @__PURE__ */ jsxRuntimeExports.jsx( 918 "textarea", 919 { 920 rows: 4, 921 className: "w-full bg-zinc-50 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-800 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 outline-none resize-y", 922 placeholder: "Write description...", 923 value: editingCollection.description || "", 924 onChange: (e) => setEditingCollection((prev) => ({ ...prev, description: e.target.value })) 925 } 926 ) 927 ] }), 928 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 929 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold text-zinc-450 uppercase mb-1", children: "Hero Image URL / Path" }),
930 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [ 931 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "bg-zinc-100 dark:bg-zinc-800 p-2 rounded-lg text-zinc-500 self-center", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Image, { size: 16 }) }), 932 /* @__PURE__ */ jsxRuntimeExports.jsx( 933 "input", 934 { 935 type: "text", 936 className: "w-full bg-zinc-50 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-800 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 outline-none", 937 placeholder: "/images/hero-broadway.jpg", 938 value: editingCollection.heroImage || "", 939 onChange: (e) => setEditingCollection((prev) => ({ ...prev, heroImage: e.target.value })) 940 } 941 ) 942 ] }), 943 editingCollection.heroImage && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-3 rounded-lg overflow-hidden border border-zinc-200 dark:border-zinc-800 bg-zinc-100 dark:bg-zinc-900 h-32 relative flex items-center justify-center", children: [
944 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs font-semibold text-zinc-400", children: "Image Preview" }), 945 /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: editingCollection.heroImage, alt: "Hero Preview", className: "absolute inset-0 w-full h-full object-cover", onError: (e) => e.currentTarget.style.display = "none", onLoad: (e) => e.currentTarget.style.display = "block" }) 946 ] }) 947 ] }), 948 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "pt-2 border-t border-zinc-100 dark:border-zinc-800/60 space-y-2", children: [ 949 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "block text-xs font-bold text-zinc-500 uppercase tracking-wide flex items-center gap-1", children: "Layout Settings" }), 950 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-2 text-xs font-semibold cursor-pointer select-none", children: [ 951 /* @__PURE__ */ jsxRuntimeExports.jsx( 952 "input", 953 { 954 type: "checkbox", 955 className: "rounded border-zinc-300 dark:border-zinc-700 text-indigo-600 focus:ring-indigo-500", 956 checked: !!((_a = editingCollection.theme) == null ? void 0 : _a.hideAbout), 957 onChange: (e) => updateThemeKey("hideAbout", e.target.checked) 958 } 959 ), 960 'Hide "About this Collection" Section' 961 ] }) 962 ] }),
963 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "pt-2 border-t border-zinc-100 dark:border-zinc-800/60", children: [ 964 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "block text-xs font-bold text-zinc-500 uppercase tracking-wide mb-2 flex items-center gap-1", children: [ 965 /* @__PURE__ */ jsxRuntimeExports.jsx(Sparkles, { size: 12 }), 966 " Theme Palette" 967 ] }), 968 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-3", children: [ 969 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 970 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] font-semibold text-zinc-400 mb-0.5", children: "Gradient Start" }), 971 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-1.5", children: [ 972 /* @__PURE__ */ jsxRuntimeExports.jsx( 973 "input", 974 { 975 type: "color", 976 className: "w-7 h-7 rounded border border-zinc-300 dark:border-zinc-800 cursor-pointer overflow-hidden bg-transparent", 977 value: getThemeColor(editingCollection.theme, "gradientStart", "#4f46e5"), 978 onChange: (e) => updateThemeKey("gradientStart", e.target.value) 979 } 980 ),
981 /* @__PURE__ */ jsxRuntimeExports.jsx( 982 "input", 983 { 984 type: "text", 985 className: "w-full bg-zinc-50 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-800 rounded px-2 py-1 text-xs outline-none", 986 value: getThemeColor(editingCollection.theme, "gradientStart", "#4f46e5"), 987 onChange: (e) => updateThemeKey("gradientStart", e.target.value) 988 } 989 ) 990 ] }) 991 ] }), 992 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 993 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] font-semibold text-zinc-400 mb-0.5", children: "Gradient End" }), 994 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-1.5", children: [ 995 /* @__PURE__ */ jsxRuntimeExports.jsx( 996 "input", 997 { 998 type: "color", 999 className: "w-7 h-7 rounded border border-zinc-300 dark:border-zinc-800 cursor-pointer overflow-hidden bg-transparent", 1000 value: getThemeColor(editingCollection.theme, "gradientEnd", "#7c3aed"), 1001 onChange: (e) => updateThemeKey("gradientEnd", e.target.value) 1002 } 1003 ),
1004 /* @__PURE__ */ jsxRuntimeExports.jsx( 1005 "input", 1006 { 1007 type: "text", 1008 className: "w-full bg-zinc-50 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-800 rounded px-2 py-1 text-xs outline-none", 1009 value: getThemeColor(editingCollection.theme, "gradientEnd", "#7c3aed"), 1010 onChange: (e) => updateThemeKey("gradientEnd", e.target.value) 1011 } 1012 ) 1013 ] }) 1014 ] }), 1015 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 1016 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] font-semibold text-zinc-400 mb-0.5", children: "Accent Color" }), 1017 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-1.5", children: [ 1018 /* @__PURE__ */ jsxRuntimeExports.jsx( 1019 "input", 1020 { 1021 type: "color", 1022 className: "w-7 h-7 rounded border border-zinc-300 dark:border-zinc-800 cursor-pointer overflow-hidden bg-transparent", 1023 value: getThemeColor(editingCollection.theme, "accentColor", "#3b82f6"), 1024 onChange: (e) => updateThemeKey("accentColor", e.target.value) 1025 } 1026 ),
1027 /* @__PURE__ */ jsxRuntimeExports.jsx( 1028 "input", 1029 { 1030 type: "text", 1031 className: "w-full bg-zinc-50 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-800 rounded px-2 py-1 text-xs outline-none", 1032 value: getThemeColor(editingCollection.theme, "accentColor", "#3b82f6"), 1033 onChange: (e) => updateThemeKey("accentColor", e.target.value) 1034 } 1035 ) 1036 ] }) 1037 ] }), 1038 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 1039 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] font-semibold text-zinc-400 mb-0.5", children: "Text Color" }), 1040 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-1.5", children: [ 1041 /* @__PURE__ */ jsxRuntimeExports.jsx( 1042 "input", 1043 { 1044 type: "color", 1045 className: "w-7 h-7 rounded border border-zinc-300 dark:border-zinc-800 cursor-pointer overflow-hidden bg-transparent", 1046 value: getThemeColor(editingCollection.theme, "textColor", "#ffffff"), 1047 onChange: (e) => updateThemeKey("textColor", e.target.value) 1048 } 1049 ),
1050 /* @__PURE__ */ jsxRuntimeExports.jsx( 1051 "input", 1052 { 1053 type: "text", 1054 className: "w-full bg-zinc-50 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-800 rounded px-2 py-1 text-xs outline-none", 1055 value: getThemeColor(editingCollection.theme, "textColor", "#ffffff"), 1056 onChange: (e) => updateThemeKey("textColor", e.target.value) 1057 } 1058 ) 1059 ] }) 1060 ] }) 1061 ] }) 1062 ] }), 1063 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "pt-2 border-t border-zinc-100 dark:border-zinc-800/60 space-y-3", children: [ 1064 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "block text-xs font-bold text-zinc-500 uppercase tracking-wide flex items-center gap-1", children: [ 1065 /* @__PURE__ */ jsxRuntimeExports.jsx(Globe, { size: 12 }), 1066 " SEO Meta Tags" 1067 ] }),
1068 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 1069 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] font-semibold text-zinc-450 mb-0.5", children: "Meta Title" }), 1070 /* @__PURE__ */ jsxRuntimeExports.jsx( 1071 "input", 1072 { 1073 type: "text", 1074 className: "w-full bg-zinc-50 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-800 rounded-lg px-2.5 py-1.5 text-xs outline-none focus:ring-1 focus:ring-indigo-500", 1075 placeholder: "Defaults to Title", 1076 value: editingCollection.metaTitle || "", 1077 onChange: (e) => setEditingCollection((prev) => ({ ...prev, metaTitle: e.target.value })) 1078 } 1079 ) 1080 ] }), 1081 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 1082 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] font-semibold text-zinc-450 mb-0.5", children: "Meta Description" }),
1083 /* @__PURE__ */ jsxRuntimeExports.jsx( 1084 "textarea", 1085 { 1086 rows: 2, 1087 className: "w-full bg-zinc-50 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-800 rounded-lg px-2.5 py-1.5 text-xs outline-none focus:ring-1 focus:ring-indigo-500 resize-none", 1088 placeholder: "Provide summaries...", 1089 value: editingCollection.metaDescription || "", 1090 onChange: (e) => setEditingCollection((prev) => ({ ...prev, metaDescription: e.target.value })) 1091 } 1092 ) 1093 ] }) 1094 ] }) 1095 ] }), 1096 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-6 pt-3 border-t border-zinc-200 dark:border-zinc-800 shrink-0 flex gap-2", children: [ 1097 /* @__PURE__ */ jsxRuntimeExports.jsx( 1098 "button", 1099 { 1100 onClick: () => setEditingCollection(null), 1101 className: "flex-1 py-2 text-xs border border-zinc-200 dark:border-zinc-850 hover:bg-zinc-100 dark:hover:bg-zinc-800 rounded-lg font-semibold transition-colors", 1102 children: "Discard" 1103 } 1104 ),
1105 /* @__PURE__ */ jsxRuntimeExports.jsx( 1106 "button", 1107 { 1108 onClick: handleSaveCollection, 1109 className: "flex-1 py-2 text-xs bg-indigo-600 hover:bg-indigo-700 text-white rounded-lg font-semibold transition-colors shadow-sm", 1110 children: "Save Changes" 1111 } 1112 ) 1113 ] }) 1114 ] }), 1115 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 flex flex-col overflow-hidden bg-zinc-50 dark:bg-zinc-950/20", children: !editingSection ? ( 1116 /* SECTION DRAG & DROP LIST */ 1117 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-6 flex-1 overflow-y-auto flex flex-col", children: [ 1118 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center mb-4", children: [ 1119 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 1120 /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "font-bold text-zinc-900 dark:text-white text-base", children: "Collection Sections" }),
1121 /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-zinc-500 mt-0.5", children: "Drag-and-drop rows using the handle icon to change visual sorting order." }) 1122 ] }), 1123 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [ 1124 selectedCollection.sections.some((s) => !s.bannerImage) && /* @__PURE__ */ jsxRuntimeExports.jsxs( 1125 "button", 1126 { 1127 onClick: handleGenerateAllMissingBanners, 1128 disabled: batchGenerating, 1129 className: "bg-indigo-600/10 hover:bg-indigo-600/20 text-indigo-600 dark:text-indigo-400 px-3 py-1.5 text-xs font-semibold rounded-lg flex items-center gap-1.5 transition-colors border border-indigo-600/20 disabled:opacity-50", 1130 children: [ 1131 batchGenerating ? /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { size: 14, className: "animate-spin" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Sparkles, { size: 14 }), 1132 batchGenerating ? `Generating ${batchProgress}...` : "Generate Missing Banners" 1133 ] 1134 } 1135 ),
1136 /* @__PURE__ */ jsxRuntimeExports.jsxs( 1137 "button", 1138 { 1139 onClick: () => setEditingSection({ 1140 title: "", 1141 subtitle: "", 1142 composer: "", 1143 description: "", 1144 bannerImage: "", 1145 tagSlugs: [], 1146 curatedTrackIds: [], 1147 isPublished: true 1148 }), 1149 className: "bg-indigo-600/10 hover:bg-indigo-600/20 text-indigo-600 dark:text-indigo-400 px-3 py-1.5 text-xs font-semibold rounded-lg flex items-center gap-1.5 transition-colors border border-indigo-600/20", 1150 children: [ 1151 /* @__PURE__ */ jsxRuntimeExports.jsx(Plus, { size: 14 }), 1152 " Add Section" 1153 ] 1154 } 1155 ) 1156 ] }) 1157 ] }), 1158 selectedCollection.sections.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex flex-col justify-center items-center py-20 border-2 border-dashed border-zinc-200 dark:border-zinc-800/80 rounded-xl text-zinc-450 bg-white dark:bg-zinc-900/40", children: [
1159 /* @__PURE__ */ jsxRuntimeExports.jsx(List, { className: "mb-2 text-zinc-350", size: 32 }), 1160 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm font-medium", children: "No sections added to this collection" }), 1161 /* @__PURE__ */ jsxRuntimeExports.jsx( 1162 "button", 1163 { 1164 onClick: () => setEditingSection({ title: "", tagSlugs: [], curatedTrackIds: [], isPublished: true }), 1165 className: "mt-3 text-xs bg-indigo-600 hover:bg-indigo-700 text-white font-semibold px-3 py-1.5 rounded-lg transition-colors", 1166 children: "Create First Section" 1167 } 1168 ) 1169 ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-3", children: selectedCollection.sections.map((sec, index) => { 1170 var _a2, _b; 1171 const hasChildren = selectedCollection.sections.some((s) => s.parentId === sec.id); 1172 let depth = 0; 1173 let current = selectedCollection.sections.find((s) => s.id === sec.parentId); 1174 while (current) { 1175 depth++; 1176 current = selectedCollection.sections.find((s) => s.id === (current == null ? void 0 : current.parentId)); 1177 } 1178 let isVisible = true; 1179 if (sec.parentId) { 1180 let curr = selectedCollection.sections.find((s) => s.id === sec.parentId); 1181 while (curr) { 1182 if (!expandedSections.has(curr.id)) { 1183 isVisible = false; 1184 break; 1185 } 1186 curr = selectedCollection.sections.find((s) => s.id === (curr == null ? void 0 : curr.parentId)); 1187 } 1188 } 1189 if (!isVisible) return null; 1190 return /* @__PURE__ */ jsxRuntimeExports.jsxs( 1191 "div", 1192 { 1193 draggable: true, 1194 onDragStart: (e) => handleSecDragStart(e, index), 1195 onDragOver: handleSecDragOver, 1196 onDrop: (e) => handleSecDrop(e, index), 1197 style: { marginLeft: `${depth * 24}px` }, 1198 className: "flex items-center justify-between p-4 bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 rounded-xl hover:border-zinc-300 dark:hover:border-zinc-700 transition-colors shadow-sm select-none", 1199 children: [
1200 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3", children: [ 1201 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "cursor-move text-zinc-400 hover:text-zinc-600 dark:hover:text-zinc-200", title: "Drag to reorder", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Move, { size: 16 }) }), 1202 hasChildren ? /* @__PURE__ */ jsxRuntimeExports.jsx( 1203 "button", 1204 { 1205 onClick: (e) => { 1206 e.stopPropagation(); 1207 setExpandedSections((prev) => { 1208 const next = new Set(prev); 1209 if (next.has(sec.id)) next.delete(sec.id); 1210 else next.add(sec.id); 1211 return next; 1212 }); 1213 }, 1214 className: "text-zinc-400 hover:text-zinc-700 dark:hover:text-zinc-200 transition-colors", 1215 children: expandedSections.has(sec.id) ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronUp, { size: 16 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { size: 16 }) 1216 } 1217 ) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-4" }), 1218 sec.bannerImage && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-16 h-10 rounded overflow-hidden flex-shrink-0 bg-zinc-100 dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700", children: /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: sec.bannerImage, className: "w-full h-full object-cover", onError: (e) => e.currentTarget.style.display = "none", alt: "banner" }) }),
1219 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 1220 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [ 1221 /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "font-bold text-zinc-900 dark:text-white text-sm", children: sec.title }), 1222 sec.composer && /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] bg-zinc-100 dark:bg-zinc-800 text-zinc-650 dark:text-zinc-400 px-2 py-0.5 rounded-md font-medium", children: [ 1223 "by ", 1224 sec.composer 1225 ] }), 1226 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: `text-[9px] px-1.5 py-0.2 rounded font-semibold ${sec.isPublished ? "bg-emerald-50 text-emerald-700 dark:bg-emerald-950/20 dark:text-emerald-400" : "bg-zinc-100 text-zinc-550 dark:bg-zinc-850 dark:text-zinc-500"}`, children: sec.isPublished ? "Published" : "Draft" }) 1227 ] }), 1228 sec.subtitle && /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-zinc-450 dark:text-zinc-500 mt-0.5", children: sec.subtitle }),
1229 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-4 mt-2 text-[10px] text-zinc-400", children: [ 1230 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "bg-zinc-50 dark:bg-zinc-950 px-2 py-0.5 border border-zinc-100 dark:border-zinc-850/60 rounded", children: [ 1231 "Tags: ", 1232 ((_a2 = sec.tagSlugs) == null ? void 0 : _a2.length) || 0 1233 ] }), 1234 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "bg-zinc-50 dark:bg-zinc-950 px-2 py-0.5 border border-zinc-100 dark:border-zinc-850/60 rounded", children: [ 1235 "Curated Tracks: ", 1236 ((_b = sec.curatedTrackIds) == null ? void 0 : _b.length) || 0 1237 ] }), 1238 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "bg-indigo-50/50 dark:bg-indigo-950/20 text-indigo-600 dark:text-indigo-400 px-2 py-0.5 rounded", children: [ 1239 "Total matched tracks: ", 1240 sec.trackCount || 0 1241 ] }) 1242 ] }) 1243 ] }) 1244 ] }),
1245 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-1.5 items-center", children: [ 1246 !sec.bannerImage && /* @__PURE__ */ jsxRuntimeExports.jsx( 1247 "button", 1248 { 1249 onClick: (e) => { 1250 e.stopPropagation(); 1251 handleGenerateSectionBanner(sec); 1252 }, 1253 disabled: generatingBannerId === String(sec.id), 1254 className: "p-1.5 bg-indigo-50 hover:bg-indigo-100 dark:bg-indigo-900/20 dark:hover:bg-indigo-900/40 text-indigo-600 dark:text-indigo-400 rounded-lg transition-colors border border-indigo-100 dark:border-indigo-800 disabled:opacity-50", 1255 title: "Generate Banner with AI", 1256 children: generatingBannerId === String(sec.id) ? /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { size: 13, className: "animate-spin" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Sparkles, { size: 13 }) 1257 } 1258 ),
1259 /* @__PURE__ */ jsxRuntimeExports.jsx( 1260 "button", 1261 { 1262 onClick: () => { 1263 setEditingSection(sec); 1264 const secSlug = sec.composer ? sec.composer.toLowerCase().replace(/[^a-z0-9]+/g, "-") : sec.id || "sec"; 1265 updateUrlParams({ section: secSlug }); 1266 }, 1267 className: "p-2 bg-zinc-50 hover:bg-zinc-100 dark:bg-zinc-850 dark:hover:bg-zinc-800 text-zinc-650 dark:text-zinc-300 rounded-lg transition-colors border border-zinc-150 dark:border-zinc-800/80", 1268 title: "Edit Section Details", 1269 children: /* @__PURE__ */ jsxRuntimeExports.jsx(Pen, { size: 13 }) 1270 } 1271 ), 1272 /* @__PURE__ */ jsxRuntimeExports.jsx( 1273 "button", 1274 { 1275 onClick: () => handleDeleteSection(sec.id, sec.title), 1276 className: "p-2 bg-zinc-50 hover:bg-rose-50 dark:bg-zinc-850 dark:hover:bg-rose-950/20 text-zinc-400 hover:text-rose-500 rounded-lg transition-colors border border-zinc-150 dark:border-zinc-800/80", 1277 title: "Delete Section", 1278 children: /* @__PURE__ */ jsxRuntimeExports.jsx(Trash2, { size: 13 }) 1279 } 1280 ) 1281 ] }) 1282 ] 1283 }, 1284 sec.id 1285 ); 1286 }) }) 1287 ] }) 1288 ) : ( 1289 /* SECTION DETAILED EDITOR FORM */
1290 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex flex-col overflow-hidden", children: [ 1291 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-4 bg-zinc-100 dark:bg-zinc-900 border-b border-zinc-200 dark:border-zinc-850 flex justify-between items-center shrink-0", children: [ 1292 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [ 1293 /* @__PURE__ */ jsxRuntimeExports.jsx( 1294 "button", 1295 { 1296 onClick: () => { 1297 setEditingSection(null); 1298 updateUrlParams({ section: null }); 1299 }, 1300 className: "p-1.5 hover:bg-zinc-200 dark:hover:bg-zinc-800 rounded-lg text-zinc-600 transition-colors", 1301 children: /* @__PURE__ */ jsxRuntimeExports.jsx(ArrowLeft, { size: 16 }) 1302 } 1303 ), 1304 /* @__PURE__ */ jsxRuntimeExports.jsx("h2", { className: "font-bold text-zinc-900 dark:text-white text-sm", children: editingSection.id ? `Edit Section: ${editingSection.title}` : "Create Collection Section" }) 1305 ] }),
1306 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [ 1307 /* @__PURE__ */ jsxRuntimeExports.jsx( 1308 "button", 1309 { 1310 onClick: () => { 1311 setEditingSection(null); 1312 updateUrlParams({ section: null }); 1313 }, 1314 className: "px-3.5 py-1.5 border border-zinc-200 dark:border-zinc-800 text-xs font-semibold rounded-lg hover:bg-zinc-200 dark:hover:bg-zinc-800 transition-colors", 1315 children: "Cancel" 1316 } 1317 ), 1318 /* @__PURE__ */ jsxRuntimeExports.jsx( 1319 "button", 1320 { 1321 onClick: handleSaveSection, 1322 className: "px-3.5 py-1.5 bg-indigo-600 hover:bg-indigo-700 text-white text-xs font-semibold rounded-lg transition-colors shadow-sm", 1323 children: "Save Section" 1324 } 1325 ) 1326 ] }) 1327 ] }),
1328 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 overflow-y-auto p-5", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-1 xl:grid-cols-2 gap-6 mb-6", children: [ 1329 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4 bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 rounded-xl p-5 shadow-sm h-fit", children: [ 1330 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center", children: [ 1331 /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "font-bold text-xs text-zinc-450 uppercase tracking-wider", children: "General Configuration" }), 1332 /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { className: "flex items-center gap-1.5 cursor-pointer text-xs font-medium", children: [ 1333 /* @__PURE__ */ jsxRuntimeExports.jsx( 1334 "input", 1335 { 1336 type: "checkbox", 1337 className: "rounded border-zinc-300 dark:border-zinc-700 focus:ring-indigo-500 text-indigo-600", 1338 checked: editingSection.isPublished !== void 0 ? editingSection.isPublished : true, 1339 onChange: (e) => setEditingSection((prev) => prev ? { ...prev, isPublished: e.target.checked } : null) 1340 } 1341 ), 1342 "Published" 1343 ] }) 1344 ] }),
1345 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 1346 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold text-zinc-450 uppercase mb-1", children: "Section Title *" }), 1347 /* @__PURE__ */ jsxRuntimeExports.jsx( 1348 "input", 1349 { 1350 type: "text", 1351 className: "w-full bg-zinc-50 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-800 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 outline-none", 1352 placeholder: "e.g. Act I Favorites", 1353 value: editingSection.title || "", 1354 onChange: (e) => setEditingSection((prev) => prev ? { ...prev, title: e.target.value } : null) 1355 } 1356 ) 1357 ] }), 1358 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 1359 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold text-zinc-450 uppercase mb-1", children: "Parent Section (Optional)" }),
1360 /* @__PURE__ */ jsxRuntimeExports.jsxs( 1361 "select", 1362 { 1363 className: "w-full bg-zinc-50 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-800 rounded-lg px-3 py-2 text-sm text-zinc-800 dark:text-zinc-200 focus:ring-2 focus:ring-indigo-500 outline-none", 1364 value: editingSection.parentId || "", 1365 onChange: (e) => setEditingSection((prev) => prev ? { ...prev, parentId: e.target.value || null } : null), 1366 children: [ 1367 /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: "", className: "text-zinc-800 dark:text-zinc-200 bg-white dark:bg-zinc-850", children: "None (Top Level)" }), 1368 selectedCollection.sections.filter((s) => s.id !== editingSection.id).map((s) => /* @__PURE__ */ jsxRuntimeExports.jsxs("option", { value: s.id, className: "text-zinc-800 dark:text-zinc-200 bg-white dark:bg-zinc-850", children: [ 1369 s.title, 1370 " ", 1371 s.composer ? `(${s.composer})` : "" 1372 ] }, s.id)) 1373 ] 1374 } 1375 ) 1376 ] }),
1377 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-4", children: [ 1378 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 1379 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold text-zinc-450 uppercase mb-1", children: "Composer / Artist" }), 1380 /* @__PURE__ */ jsxRuntimeExports.jsx( 1381 "input", 1382 { 1383 type: "text", 1384 className: "w-full bg-zinc-50 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-800 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 outline-none", 1385 placeholder: "e.g. Andrew Lloyd Webber", 1386 value: editingSection.composer || "", 1387 onChange: (e) => setEditingSection((prev) => prev ? { ...prev, composer: e.target.value } : null) 1388 } 1389 ) 1390 ] }),
1391 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 1392 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold text-zinc-450 uppercase mb-1", children: "Section Subtitle" }), 1393 /* @__PURE__ */ jsxRuntimeExports.jsx( 1394 "input", 1395 { 1396 type: "text", 1397 className: "w-full bg-zinc-50 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-800 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 outline-none", 1398 placeholder: "e.g. Popular Showstoppers", 1399 value: editingSection.subtitle || "", 1400 onChange: (e) => setEditingSection((prev) => prev ? { ...prev, subtitle: e.target.value } : null) 1401 } 1402 ) 1403 ] }) 1404 ] }), 1405 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
1406 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex justify-between items-center mb-1", children: [ 1407 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold text-zinc-450 uppercase", children: "Banner Image URL / Path" }), 1408 /* @__PURE__ */ jsxRuntimeExports.jsxs( 1409 "button", 1410 { 1411 onClick: () => handleGenerateSectionBanner(editingSection), 1412 disabled: generatingBannerId === String(editingSection.id || "new"), 1413 className: "flex items-center gap-1 text-[10px] font-bold text-indigo-600 dark:text-indigo-400 bg-indigo-50 dark:bg-indigo-900/20 px-2 py-1 rounded border border-indigo-100 dark:border-indigo-800 hover:bg-indigo-100 dark:hover:bg-indigo-900/40 transition-colors disabled:opacity-50", 1414 children: [ 1415 generatingBannerId === String(editingSection.id || "new") ? /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { size: 12, className: "animate-spin" }) : /* @__PURE__ */ jsxRuntimeExports.jsx(Sparkles, { size: 12 }), 1416 "Generate with AI" 1417 ] 1418 } 1419 ) 1420 ] }),
1421 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [ 1422 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "bg-zinc-150 dark:bg-zinc-800 p-2 rounded-lg text-zinc-550 self-center", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Image, { size: 16 }) }), 1423 /* @__PURE__ */ jsxRuntimeExports.jsx( 1424 "input", 1425 { 1426 type: "text", 1427 className: "w-full bg-zinc-50 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-800 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 outline-none", 1428 placeholder: "/images/broadway-banner.jpg", 1429 value: editingSection.bannerImage || "", 1430 onChange: (e) => setEditingSection((prev) => prev ? { ...prev, bannerImage: e.target.value } : null) 1431 } 1432 ) 1433 ] }), 1434 editingSection.bannerImage && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-3 rounded-lg overflow-hidden border border-zinc-200 dark:border-zinc-800 bg-zinc-100 dark:bg-zinc-900 h-24 relative flex items-center justify-center", children: [
1435 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs font-semibold text-zinc-400", children: "Image Preview" }), 1436 /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: editingSection.bannerImage, alt: "Banner Preview", className: "absolute inset-0 w-full h-full object-cover", onError: (e) => e.currentTarget.style.display = "none", onLoad: (e) => e.currentTarget.style.display = "block" }) 1437 ] }) 1438 ] }), 1439 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 1440 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold text-zinc-450 uppercase mb-1", children: "Description (Markdown Supported)" }), 1441 /* @__PURE__ */ jsxRuntimeExports.jsx( 1442 "textarea", 1443 { 1444 rows: 4, 1445 className: "w-full bg-zinc-50 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-800 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 outline-none resize-y", 1446 placeholder: "Provide section descriptions...", 1447 value: editingSection.description || "", 1448 onChange: (e) => setEditingSection((prev) => prev ? { ...prev, description: e.target.value } : null) 1449 } 1450 ) 1451 ] }),
1452 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "pt-2 border-t border-zinc-100 dark:border-zinc-850/60", children: [ 1453 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-xs font-bold text-zinc-450 uppercase mb-2", children: "Assigned Source Tags" }), 1454 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex flex-wrap gap-1.5 mb-2.5", children: (editingSection.tagSlugs || []).length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs text-zinc-450 italic", children: "No tags assigned. Dynamic track matching is disabled." }) : (editingSection.tagSlugs || []).map((slug) => /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1 bg-indigo-50 text-indigo-700 dark:bg-indigo-950/40 dark:text-indigo-400 px-2.5 py-1 rounded-md text-xs font-medium border border-indigo-100 dark:border-indigo-900/30", children: [ 1455 "#", 1456 slug, 1457 /* @__P
1457URE__ */ jsxRuntimeExports.jsx("button", { type: "button", onClick: () => handleRemoveTag(slug), className: "hover:text-rose-500 transition-colors p-0.5 rounded ml-0.5", children: /* @__PURE__ */ jsxRuntimeExports.jsx(X, { size: 10 }) }) 1458 ] }, slug)) }), 1459 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative", children: [ 1460 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center bg-zinc-50 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-800 rounded-lg px-3 py-1 text-sm focus-within:ring-2 focus-within:ring-indigo-500", children: [ 1461 /* @__PURE__ */ jsxRuntimeExports.jsx(Search, { size: 14, className: "text-zinc-400 mr-2" }), 1462 /* @__PURE__ */ jsxRuntimeExports.jsx( 1463 "input", 1464 { 1465 ref: tagInputRef, 1466 type: "text", 1467 className: "w-full bg-transparent border-none outline-none py-1 text-sm", 1468 placeholder: "Type to search and add source tag...", 1469 value: tagSearch, 1470 onChange: (e) => { 1471 setTagSearch(e.target.value); 1472 setShowTagSuggestions(true); 1473 }, 1474 onFocus: () => setShowTagSuggestions(true), 1475 onKeyDown: (e) => { 1476 if (e.key === "Enter" && tagSearch.trim()) { 1477 e.preventDefault(); 1478 const cleanSlug = tagSearch.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-"); 1479 if (cleanSlug) { 1480 handleAddTag(cleanSlug); 1481 } 1482 } 1483 } 1484 } 1485 ) 1486 ] }), 1487 showTagSuggestions && tagSuggestions.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
1488 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "fixed inset-0 z-10", onClick: () => setShowTagSuggestions(false) }), 1489 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute left-0 right-0 top-full mt-1 bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 rounded-lg shadow-lg z-20 max-h-48 overflow-y-auto", children: tagSuggestions.map((tag) => /* @__PURE__ */ jsxRuntimeExports.jsxs( 1490 "button", 1491 { 1492 type: "button", 1493 onClick: () => handleAddTag(tag.slug), 1494 className: "w-full text-left px-4 py-2 hover:bg-zinc-100 dark:hover:bg-zinc-800 text-xs font-semibold flex justify-between items-center", 1495 children: [ 1496 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-zinc-800 dark:text-zinc-150", children: [ 1497 "#", 1498 tag.slug 1499 ] }),
1500 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] text-zinc-400 font-normal", children: [ 1501 "Score: ", 1502 tag.score || 0 1503 ] }) 1504 ] 1505 }, 1506 tag.id 1507 )) }) 1508 ] }) 1509 ] }) 1510 ] }) 1511 ] }), 1512 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "space-y-4 bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 rounded-xl p-5 shadow-sm flex flex-col h-[650px]", children: [ 1513 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between border-b border-zinc-200 dark:border-zinc-850 pb-3 shrink-0", children: [ 1514 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [ 1515 /* @__PURE__ */ jsxRuntimeExports.jsxs( 1516 "button", 1517 { 1518 type: "button", 1519 onClick: () => setRightPanelTab("works"), 1520 className: `px-3 py-1.5 text-xs font-bold rounded-lg flex items-center gap-1.5 transition-colors ${rightPanelTab === "works" ? "bg-indigo-600 text-white shadow-sm" : "bg-zinc-100 dark:bg-zinc-800 text-zinc-600 dark:text-zinc-400 hover:bg-zinc-200 dark:hover:bg-zinc-700"}`, 1521 children: [
1522 /* @__PURE__ */ jsxRuntimeExports.jsx(List, { size: 14 }), 1523 " Canonical Works (", 1524 (editingSection.songs || []).length, 1525 ")" 1526 ] 1527 } 1528 ), 1529 /* @__PURE__ */ jsxRuntimeExports.jsxs( 1530 "button", 1531 { 1532 type: "button", 1533 onClick: () => setRightPanelTab("pinned"), 1534 className: `px-3 py-1.5 text-xs font-bold rounded-lg flex items-center gap-1.5 transition-colors ${rightPanelTab === "pinned" ? "bg-indigo-600 text-white shadow-sm" : "bg-zinc-100 dark:bg-zinc-800 text-zinc-600 dark:text-zinc-400 hover:bg-zinc-200 dark:hover:bg-zinc-700"}`, 1535 children: [ 1536 /* @__PURE__ */ jsxRuntimeExports.jsx(Sparkles, { size: 14, className: rightPanelTab === "pinned" ? "text-amber-300" : "text-amber-500" }), 1537 " Pinned Tracks (", 1538 (editingSection.curatedTrackIds || []).length, 1539 ")" 1540 ] 1541 } 1542 ) 1543 ] }), 1544 rightPanelTab === "works" && /* @__PURE__ */ jsxRuntimeExports.jsxs( 1545 "button", 1546 { 1547 type: "button", 1548 onClick: () => setEditingSong({ title: "", groupName: "", details: "", associatedMidiIds: [] }), 1549 className: "bg-indigo-600/10 hover:bg-indigo-600/20 text-indigo-600 dark:text-indigo-400 px-2.5 py-1 text-xs font-semibold rounded-lg flex items-center gap-1 transition-colors border border-indigo-600/20", 1550 children: [
1551 /* @__PURE__ */ jsxRuntimeExports.jsx(Plus, { size: 13 }), 1552 " Add Work" 1553 ] 1554 } 1555 ) 1556 ] }), 1557 rightPanelTab === "works" && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 flex flex-col overflow-hidden", children: editingSong ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "bg-zinc-50 dark:bg-zinc-950 border border-zinc-200 dark:border-zinc-800 rounded-lg p-4 shrink-0 overflow-y-auto", children: [ 1558 /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "text-xs font-bold mb-3", children: editingSong.id ? "Edit Work" : "Add New Work" }), 1559 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "grid grid-cols-2 gap-3 mb-3", children: [ 1560 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 1561 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] font-bold text-zinc-450 uppercase mb-1", children: "Group Name" }),
1562 /* @__PURE__ */ jsxRuntimeExports.jsx( 1563 "input", 1564 { 1565 type: "text", 1566 className: "w-full bg-white dark:bg-zinc-900 border border-zinc-300 dark:border-zinc-800 rounded px-3 py-1.5 text-xs focus:ring-1 focus:ring-indigo-500 outline-none", 1567 placeholder: "e.g. Piano, Concertos", 1568 value: editingSong.groupName || "", 1569 onChange: (e) => setEditingSong((prev) => prev ? { ...prev, groupName: e.target.value } : null) 1570 } 1571 ) 1572 ] }), 1573 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [ 1574 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] font-bold text-zinc-450 uppercase mb-1", children: "Details" }), 1575 /* @__PURE__ */ jsxRuntimeExports.jsx( 1576 "input", 1577 { 1578 type: "text", 1579 className: "w-full bg-white dark:bg-zinc-900 border border-zinc-300 dark:border-zinc-800 rounded px-3 py-1.5 text-xs focus:ring-1 focus:ring-indigo-500 outline-none", 1580 placeholder: "e.g. op 10", 1581 value: editingSong.details || "", 1582 onChange: (e) => setEditingSong((prev) => prev ? { ...prev, details: e.target.value } : null) 1583 } 1584 ) 1585 ] }) 1586 ] }),
1587 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-3", children: [ 1588 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] font-bold text-zinc-450 uppercase mb-1", children: "Title *" }), 1589 /* @__PURE__ */ jsxRuntimeExports.jsx( 1590 "input", 1591 { 1592 type: "text", 1593 className: "w-full bg-white dark:bg-zinc-900 border border-zinc-300 dark:border-zinc-800 rounded px-3 py-1.5 text-sm font-semibold focus:ring-1 focus:ring-indigo-500 outline-none", 1594 placeholder: "Work title...", 1595 value: editingSong.title || "", 1596 onChange: (e) => setEditingSong((prev) => prev ? { ...prev, title: e.target.value } : null) 1597 } 1598 ) 1599 ] }), 1600 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mb-4", children: [
1601 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] font-bold text-zinc-450 uppercase mb-1", children: "Attached MIDIs (MIDI Track IDs)" }), 1602 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex gap-1.5 mb-2 flex-wrap min-h-[28px] p-2 bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 rounded-md", children: (editingSong.associatedMidiIds || []).length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs text-zinc-400 italic", children: "No MIDIs attached yet. Add IDs below." }) : (editingSong.associatedMidiIds || []).map((midiId, i) => { 1603 const cached = tracksCache[String(midiId)]; 1604 return /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1 px-2 py-1 bg-indigo-100 dark:bg-indigo-900/50 text-indigo-800 dark:text-indigo-200 text-xs font-semibold rounded-md border border-indigo-200 dark:border-indigo-800", children: [
1605 /* @__PURE__ */ jsxRuntimeExports.jsx(Music, { size: 11 }), 1606 " #", 1607 midiId, 1608 " ", 1609 (cached == null ? void 0 : cached.title) ? `(${cached.title})` : "", 1610 /* @__PURE__ */ jsxRuntimeExports.jsx( 1611 "button", 1612 { 1613 type: "button", 1614 onClick: () => { 1615 const updated = (editingSong.associatedMidiIds || []).filter((_, idx) => idx !== i); 1616 setEditingSong((prev) => prev ? { ...prev, associatedMidiIds: updated } : null); 1617 }, 1618 className: "hover:text-rose-500 font-bold ml-1 text-sm leading-none", 1619 children: "Ã" 1620 } 1621 ) 1622 ] }, i); 1623 }) }),
1624 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2", children: [ 1625 /* @__PURE__ */ jsxRuntimeExports.jsx( 1626 "input", 1627 { 1628 type: "text", 1629 className: "flex-1 bg-white dark:bg-zinc-900 border border-zinc-300 dark:border-zinc-800 rounded px-3 py-1 text-xs outline-none focus:ring-1 focus:ring-indigo-500", 1630 placeholder: "Enter MIDI ID or comma-separated IDs (e.g. 1042, 1043)...", 1631 value: songMidiInput, 1632 onChange: (e) => setSongMidiInput(e.target.value), 1633 onKeyDown: (e) => { 1634 if (e.key === "Enter") { 1635 e.preventDefault(); 1636 if (songMidiInput.trim()) { 1637 const newIds = songMidiInput.split(",").map((s) => s.trim()).filter(Boolean); 1638 const current = (editingSong.associatedMidiIds || []).map(String); 1639 const combined = Array.from(/* @__PURE__ */ new Set([...current, ...newIds])); 1640 setEditingSong((prev) => prev ? { ...prev, associatedMidiIds: combined } : null); 1641 setSongMidiInput(""); 1642 } 1643 } 1644 } 1645 } 1646 ),
1647 /* @__PURE__ */ jsxRuntimeExports.jsx( 1648 "button", 1649 { 1650 type: "button", 1651 onClick: () => { 1652 if (songMidiInput.trim()) { 1653 const newIds = songMidiInput.split(",").map((s) => s.trim()).filter(Boolean); 1654 const current = (editingSong.associatedMidiIds || []).map(String); 1655 const combined = Array.from(/* @__PURE__ */ new Set([...current, ...newIds])); 1656 setEditingSong((prev) => prev ? { ...prev, associatedMidiIds: combined } : null); 1657 setSongMidiInput(""); 1658 } 1659 }, 1660 className: "px-3 py-1 bg-zinc-200 dark:bg-zinc-800 text-xs font-semibold rounded hover:bg-zinc-300 dark:hover:bg-zinc-700", 1661 children: "+ Add MIDI ID" 1662 } 1663 ) 1664 ] }) 1665 ] }),
1666 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex gap-2 justify-end pt-2 border-t border-zinc-200 dark:border-zinc-850", children: [ 1667 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", onClick: () => setEditingSong(null), className: "px-3 py-1.5 text-xs border border-zinc-300 dark:border-zinc-700 rounded-md hover:bg-zinc-100 dark:hover:bg-zinc-800 font-semibold", children: "Cancel" }), 1668 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", onClick: handleSaveSong, className: "px-3 py-1.5 text-xs bg-indigo-600 text-white rounded-md hover:bg-indigo-700 font-semibold", children: "Save Work" }) 1669 ] }) 1670 ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 border border-zinc-200 dark:border-zinc-800 rounded-lg overflow-y-auto bg-zinc-50/50 dark:bg-zinc-950/20", children: (editingSection.songs || []).length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center py-16 text-zinc-400", children: [
1671 /* @__PURE__ */ jsxRuntimeExports.jsx(FileText, { size: 28, className: "mb-1 opacity-60" }), 1672 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs", children: "No canonical works defined in this section." }) 1673 ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("table", { className: "w-full text-left text-xs", children: [ 1674 /* @__PURE__ */ jsxRuntimeExports.jsx("thead", { className: "bg-zinc-100 dark:bg-zinc-850 text-zinc-500 sticky top-0 z-10", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("tr", { children: [ 1675 /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "px-3 py-2 font-semibold w-8" }), 1676 /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "px-3 py-2 font-semibold w-24", children: "Group" }), 1677 /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "px-3 py-2 font-semibold", children: "Title" }), 1678 /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "px-3 py-2 font-semibold w-24", children: "Attached MIDIs" }),
1679 /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "px-3 py-2 font-semibold w-20", children: "Details" }), 1680 /* @__PURE__ */ jsxRuntimeExports.jsx("th", { className: "px-3 py-2 font-semibold w-16 text-right", children: "Actions" }) 1681 ] }) }), 1682 /* @__PURE__ */ jsxRuntimeExports.jsx("tbody", { className: "divide-y divide-zinc-200 dark:divide-zinc-800", children: (editingSection.songs || []).map((song, index) => { 1683 const attachedIds = (song.associatedMidiIds || []).map(String); 1684 const isExpanded = expandedSongId === song.id; 1685 return /* @__PURE__ */ jsxRuntimeExports.jsxs(React.Fragment, { children: [ 1686 /* @__PURE__ */ jsxRuntimeExports.jsxs( 1687 "tr", 1688 { 1689 draggable: true, 1690 onDragStart: (e) => handleSongDragStart(e, index), 1691 onDragOver: handleSongDragOver, 1692 onDrop: (e) => handleSongDrop(e, index), 1693 className: "bg-white dark:bg-zinc-900 hover:bg-zinc-50 dark:hover:bg-zinc-850/50 transition-colors", 1694 children: [
1695 /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-2 py-2 text-zinc-300 hover:text-zinc-500 cursor-move", title: "Drag to reorder", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Move, { size: 13 }) }), 1696 /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-3 py-2 text-zinc-500 dark:text-zinc-400 font-medium truncate max-w-[100px]", children: song.groupName || "-" }), 1697 /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-3 py-2 font-bold text-zinc-800 dark:text-zinc-200 truncate max-w-[180px]", children: song.title }), 1698 /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-3 py-2", children: /* @__PURE__ */ jsxRuntimeExports.jsxs( 1699 "button", 1700 { 1701 type: "button", 1702 onClick: () => setExpandedSongId(isExpanded ? null : song.id), 1703 className: `inline-flex items-center gap-1 px-2 py-0.5 rounded text-[11px] font-semibold transition-colors ${attachedIds.length > 0 ? "bg-indigo-50 dark:bg-indigo-950/60 text-indigo-600 dark:text-indigo-400 hover:bg-indigo-100" : "bg-zinc-100 dark:bg-zinc-800 text-zinc-400 hover:bg-zinc-200"}`, 1704 children: [
1705 /* @__PURE__ */ jsxRuntimeExports.jsx(Music, { size: 11 }), 1706 " ", 1707 attachedIds.length, 1708 " ", 1709 attachedIds.length === 1 ? "MIDI" : "MIDIs", 1710 isExpanded ? /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronUp, { size: 11 }) : /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { size: 11 }) 1711 ] 1712 } 1713 ) }), 1714 /* @__PURE__ */ jsxRuntimeExports.jsx("td", { className: "px-3 py-2 text-zinc-500 truncate max-w-[80px]", children: song.details || "-" }), 1715 /* @__PURE__ */ jsxRuntimeExports.jsxs("td", { className: "px-3 py-2 text-right space-x-1 shrink-0", children: [ 1716 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", onClick: () => setEditingSong(song), className: "p-1 text-zinc-400 hover:text-indigo-500 rounded bg-zinc-50 hover:bg-indigo-50 dark:bg-zinc-800 dark:hover:bg-indigo-900/30 transition-colors", title: "Edit Work", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Pen, { size: 12 }) }),
1717 /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", onClick: (e) => handleDeleteSong(song.id, song.title, e), className: "p-1 text-zinc-400 hover:text-rose-500 rounded bg-zinc-50 hover:bg-rose-50 dark:bg-zinc-800 dark:hover:bg-rose-900/30 transition-colors", title: "Delete Work", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Trash2, { size: 12 }) }) 1718 ] }) 1719 ] 1720 } 1721 ), 1722 isExpanded && /* @__PURE__ */ jsxRuntimeExports.jsx("tr", { className: "bg-indigo-50/20 dark:bg-indigo-950/20 border-b border-indigo-100 dark:border-indigo-900/30", children: /* @__PURE__ */ jsxRuntimeExports.jsx("td", { colSpan: 6, className: "px-4 py-2.5", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-xs", children: [ 1723 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between mb-1.5", children: [
1724 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-bold text-zinc-700 dark:text-zinc-300", children: [ 1725 'Attached MIDI Files for "', 1726 song.title, 1727 '":' 1728 ] }), 1729 /* @__PURE__ */ jsxRuntimeExports.jsx( 1730 "button", 1731 { 1732 type: "button", 1733 onClick: () => setEditingSong(song), 1734 className: "text-[11px] text-indigo-600 dark:text-indigo-400 hover:underline font-medium", 1735 children: "+ Manage / Attach MIDIs" 1736 } 1737 ) 1738 ] }), 1739 attachedIds.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-zinc-400 italic text-[11px]", children: "No MIDI files attached to this work. Click Edit to attach MIDI IDs." }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-1", children: attachedIds.map((mId) => { 1740 const cached = tracksCache[mId] || { title: `MIDI Track #${mId}` }; 1741 return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between p-1.5 bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 rounded", children: [
1742 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [ 1743 /* @__PURE__ */ jsxRuntimeExports.jsx(Music, { size: 12, className: "text-indigo-500" }), 1744 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-semibold text-zinc-800 dark:text-zinc-200", children: cached.title }), 1745 /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-[10px] text-zinc-400", children: [ 1746 "(ID: #", 1747 mId, 1748 ")" 1749 ] }) 1750 ] }), 1751 /* @__PURE__ */ jsxRuntimeExports.jsx( 1752 "button", 1753 { 1754 type: "button", 1755 onClick: async () => { 1756 if (!selectedCollection || !(editingSection == null ? void 0 : editingSection.id)) return; 1757 const newMidiIds = attachedIds.filter((id) => id !== mId); 1758 const updated = await api.updateSectionSong(selectedCollection.id, editingSection.id, song.id, { associatedMidiIds: newMidiIds }); 1759 setEditingSection((prev) => { 1760 var _a2; 1761 return prev ? { ...prev, songs: (_a2 = prev.songs) == null ? void 0 : _a2.map((s) => s.id === updated.id ? updated : s) } : null; 1762 }); 1763 triggerNotification("MIDI detached from work"); 1764 }, 1765 className: "text-[10px] text-rose-500 hover:text-rose-700 font-semibold px-2 py-0.5 rounded hover:bg-rose-50 dark:hover:bg-rose-950/40", 1766 children: "Detach" 1767 } 1768 ) 1769 ] }, mId); 1770 }) }) 1771 ] }) }) }) 1772 ] }, song.id); 1773 }) }) 1774 ] }) }) }), 1775 rightPanelTab === "pinned" && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex flex-col overflow-hidden", children: [
1776 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 overflow-y-auto border border-zinc-150 dark:border-zinc-800 rounded-lg p-3 bg-zinc-50/50 dark:bg-zinc-950/20 mb-3", children: [ 1777 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "block text-[10px] font-bold text-zinc-400 uppercase tracking-wider mb-2", children: "Pinned Tracks Order (Drag to Reorder)" }), 1778 (editingSection.curatedTrackIds || []).length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center py-16 text-zinc-400", children: [ 1779 /* @__PURE__ */ jsxRuntimeExports.jsx(FileText, { size: 28, className: "mb-1 opacity-60" }), 1780 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs", children: "No pinned tracks in this section." }) 1781 ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-2", children: (editingSection.curatedTrackIds || []).map((trackId, index) => { 1782 const cached = tracksCache[String(trackId)] || { title: `Track ID #${trackId}`, artist: "" }; 1783 return /* @__PURE__ */ jsxRuntimeExports.jsxs( 1784 "div", 1785 { 1786 draggable: true, 1787 onDragStart: (e) => handleTrackDragStart(e, index), 1788 onDragOver: handleTrackDragOver, 1789 onDrop: (e) => handleTrackDrop(e, index), 1790 className: "flex items-center justify-between p-2.5 bg-white dark:bg-zinc-900 border border-zinc-150 dark:border-zinc-800/80 rounded-lg hover:border-zinc-300 dark:hover:border-zinc-700 transition-colors shadow-sm select-none", 1791 children: [
1792 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2.5 overflow-hidden", children: [ 1793 /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "cursor-move text-zinc-300 hover:text-zinc-650", title: "Drag to reorder", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Move, { size: 14 }) }), 1794 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "overflow-hidden", children: [ 1795 /* @__PURE__ */ jsxRuntimeExports.jsx("h4", { className: "text-xs font-bold text-zinc-850 dark:text-zinc-200 truncate", children: cached.title }), 1796 /* @__PURE__ */ jsxRuntimeExports.jsxs("p", { className: "text-[10px] text-zinc-400 dark:text-zinc-500 truncate", children: [ 1797 cached.artist, 1798 " (ID: #", 1799 trackId, 1800 ")" 1801 ] }) 1802 ] }) 1803 ] }),
1804 /* @__PURE__ */ jsxRuntimeExports.jsx( 1805 "button", 1806 { 1807 type: "button", 1808 onClick: () => handleUnpinTrack(trackId), 1809 className: "p-1 hover:text-rose-500 text-zinc-400 rounded transition-colors", 1810 title: "Remove from curated list", 1811 children: /* @__PURE__ */ jsxRuntimeExports.jsx(Trash2, { size: 13 }) 1812 } 1813 ) 1814 ] 1815 }, 1816 trackId 1817 ); 1818 }) }) 1819 ] }), 1820 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "shrink-0 flex flex-col relative pt-2 border-t border-zinc-150 dark:border-zinc-800", children: [ 1821 /* @__PURE__ */ jsxRuntimeExports.jsx("label", { className: "block text-[10px] font-bold text-zinc-450 uppercase mb-1.5", children: "Add Track to Curator" }),
1822 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative flex items-center bg-zinc-50 dark:bg-zinc-950 border border-zinc-300 dark:border-zinc-800 rounded-lg px-3 py-1.5", children: [ 1823 /* @__PURE__ */ jsxRuntimeExports.jsx(Search, { size: 14, className: "text-zinc-400 mr-2" }), 1824 /* @__PURE__ */ jsxRuntimeExports.jsx( 1825 "input", 1826 { 1827 type: "text", 1828 className: "w-full bg-transparent border-none outline-none py-0.5 text-xs text-zinc-900 dark:text-white", 1829 placeholder: "Search MIDI title or artist name...", 1830 value: trackSearch, 1831 onChange: (e) => setTrackSearch(e.target.value) 1832 } 1833 ), 1834 searchingTracks && /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { size: 12, className: "animate-spin text-indigo-500" }) 1835 ] }), 1836 trackSearch.trim() && trackResults.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "absolute bottom-full left-0 right-0 mb-1 bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 rounded-lg shadow-xl z-20 max-h-56 overflow-y-auto p-1.5 space-y-1", children: trackResults.map((t) => { 1837 const isPinned = (editingSection.curatedTrackIds || []).includes(String(t.id)); 1838 return /* @__PURE__ */ jsxRuntimeExports.jsxs( 1839 "button", 1840 { 1841 type: "button", 1842 disabled: isPinned, 1843 onClick: () => handlePinTrack(t), 1844 className: `w-full text-left p-2 rounded-md text-xs flex justify-between items-center transition-colors ${isPinned ? "opacity-40 cursor-not-allowed bg-zinc-50 dark:bg-zinc-850" : "hover:bg-zinc-50 dark:hover:bg-zinc-850 text-zinc-800 dark:text-zinc-150"}`, 1845 children: [
1846 /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "overflow-hidden", children: [ 1847 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-bold block truncate", children: t.title }), 1848 /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-zinc-400 block truncate", children: t.artist }) 1849 ] }), 1850 isPinned ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] bg-zinc-200 dark:bg-zinc-750 text-zinc-500 dark:text-zinc-400 px-2 py-0.5 rounded font-semibold shrink-0", children: "Pinned" }) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] bg-indigo-50 dark:bg-indigo-900/30 text-indigo-600 dark:text-indigo-400 px-2 py-0.5 rounded font-semibold shrink-0 group-hover:bg-indigo-600 group-hover:text-white", children: "Pin +" }) 1851 ] 1852 }, 1853 t.id 1854 ); 1855 }) }) 1856 ] }) 1857 ] }) 1858 ] }) 1859 ] }) }) 1860 ] }) 1861 ) }) 1862 ] }) 1863 ] }) 1864 ) 1865 ] }); 1866} 1867export { 1868 CollectionManager as default 1869};
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.