1import{a as Be,u as T,b as Z,j as e}from"./vendor-tanstack-xYs0qipK.js";import{a as i}from"./vendor-utils-BW31qAuo.js";import{u as He,aF as Ke,a as ee,k as o,C as u,j as x,h as j,i as g,n as f,T as Qe,e as Re,f as I,I as q,g as B,B as Ce,D as ze,b as Ge,c as Je,d as Ye,J as Ve,L as se,M as _e}from"./index-C4jTWo1c.js";import{T as Oe,a as We,b as A,c as h,d as Xe,e as n}from"./table-77WcBReA.js";import{g as Se,P as ke,C as Ze,U as Te,eC as Ae,M as es,p as ss,n as Le,O as as,a1 as ts,bN as rs,eD as ls,ej as is,L as ns}from"./vendor-lucide-CKlzdszV.js";import"./vendor-radix-0HuR87kU.js";import"./vendor-jspdf-Coa_bvud.js";import"./vendor-zod-DGqi7D6U.js";import"./vendor-ui-libs-BOTq-ovd.js";import"./vendor-recharts-BZMKJDOF.js";import"./vendor-date-fns-BjUe4mL7.js";import"./vendor-framer-motion-BzSWfuIU.js";import"./vendor-dnd-kit-COGLKFcE.js";function Ns({onOpenProviders:H,onOpenOtaRadio:K}={}){var we;const[r,ae]=i.useState(null),[L,te]=i.useState(null),[D,re]=i.useState(null),[le,c]=i.useState("artists"),[M,De]=i.useState(""),[Q,ie]=i.useState(""),[R,ne]=i.useState(""),[Me,m]=i.useState(!1),[d,z]=i.useState(null),[G,P]=i.useState(""),[J,oe]=i.useState(""),[ce,de]=i.useState(""),{toast:p}=He(),me=Be(),{playTrack:Pe,replaceQueue:Fe}=Ke(),{data:t,error:Y,isLoading:Ee,refetch:$e}=T({queryKey:["/api/music/sources"],refetchInterval:s=>{var a;return(a=s.state.data)!=null&&a.some(l=>l.status==="syncing")?3e3:3e4}}),V=()=>{z(null),P(""),oe(""),de("")},_=Z({mutationFn:async()=>ee("POST","/api/music/sources",{name:G,kind:d,mountPath:J,shareUrl:d==="nas"&&ce||void 0,scanNow:!0}),onSuccess:async s=>{const a=s==null?void 0:s.source;await me.invalidateQueries({queryKey:["/api/music/sources"]}),a!=null&&a.id&&ae(a.id),m(!1),V(),p({title:"Media source added",description:s!=null&&s.scanStarted?"DomoCore is indexing playable media now.":"The source is configured and ready to scan."})},onError:s=>{p({title:"Could not add media source",description:s.message,variant:"destructive"})}}),O=Z({mutationFn:async s=>ee("POST",`/api/music/sources/${s}/rescan`,{}),onSuccess:async()=>{await me.invalidateQueries({queryKey:["/api/music/sources"]}),p({title:"Library scan started",description:"New and changed media will appear as it is indexed."})},onError:s=>{p({title:"Could not scan this source",description:s.message,variant:"destructive"})}}),{data:W,error:F,isLoading:ue}=T({queryKey:["/api/music/library/artists",r],enabled:!!r,queryFn:async()=>{const s=await fetch(`/api/music/library/artists?sourceId=${r}`);if(!s.ok)throw new Error("Failed to fetch artists");return s.json()}}),{data:X,error:E,isLoading:xe}=T({queryKey:["/api/music/library/albums",r,L],enabled:!!r,queryFn:async()=>{const s=new URLSearchParams;r&&s.append("sourceId",r),L&&s.append("artistId",L.toString());const a=await fetch(`/api/music/library/albums?${s}`);if(!a.ok)throw new Error("Failed to fetch albums");return a.json()}}),{data:b,error:$,isLoading:he}=T({queryKey:["/api/music/library/tracks",r,D],enabled:!!r,queryFn:async()=>{const s=new URLSearchParams;r&&s.append("sourceId",r),D&&s.append("albumId",D.toString());const a=await fetch(`/api/music/library/tracks?${s}`);if(!a.ok)throw new Error("Failed to fetch tracks");return a.json()}}),{data:y,error:U,isLoading:pe}=T({queryKey:["/api/music/playlists"],refetchInterval:15e3}),je=s=>({id:`library-${s.id}`,title:s.title,artist:s.artistName||"Unknown artist",album:s.albumTitle||void 0,imageUrl:s.coverArtUri||void 0,duration:(s.duration||0)*1e3,progress:0,source:"local",streamUrl:s.streamUrl}),ge=Z({mutationFn:async s=>{const a=await ee(`/api/music/playlists/${s}/tracks`);if(!Array.isArray(a))throw new Error("Playlist returned an invalid track list");return a},onSuccess:async s=>{if(s.length===0){p({title:"Playlist is empty",description:"Add tracks before starting playback."});return}await Fe(s.map(je))},onError:s=>{p({title:"Playlist playback failed",description:s.message,variant:"destructive"})}}),Ue=s=>{Pe(je(s))},fe=s=>{if(!s)return"--:--";const a=Math.floor(s/60),l=s%60;return`${a}:${l.toString().padStart(2,"0")}`},be=(W==null?void 0:W.filter(s=>s.name.toLowerCase().includes(M.toLowerCase())))||[],ye=(X==null?void 0:X.filter(s=>s.title.toLowerCase().includes(M.toLowerCase())))||[],Ie=[...new Set((b||[]).flatMap(s=>{var a;return((a=s.metadata)==null?void 0:a.genres)||[]}
1).filter(Boolean))].sort((s,a)=>s.localeCompare(a)),qe=[...new Set((b||[]).flatMap(s=>{var a;return((a=s.metadata)==null?void 0:a.moods)||[]}).filter(Boolean))].sort((s,a)=>s.localeCompare(a)),Ne=M.trim().toLowerCase(),ve=(b==null?void 0:b.filter(s=>{var l,N,v,w,C,S;const a=[s.title,s.artistName,s.albumTitle,...((l=s.metadata)==null?void 0:l.genres)||[],...((N=s.metadata)==null?void 0:N.moods)||[]].filter(Boolean).join(" ").toLowerCase();return(!Ne||a.includes(Ne))&&(!Q||((w=(v=s.metadata)==null?void 0:v.genres)==null?void 0:w.includes(Q)))&&(!R||((S=(C=s.metadata)==null?void 0:C.moods)==null?void 0:S.includes(R)))}))||[];return e.jsxs("div",{className:"container mx-auto min-w-0 space-y-6 p-4 sm:p-6","data-testid":"music-library-page",children:[e.jsxs("div",{className:"flex min-w-0 flex-col gap-4 sm:flex-row sm:items-center sm:justify-between",children:[e.jsxs("div",{className:"min-w-0",children:[e.jsx("h1",{className:"text-3xl font-bold",children:"Music Library"}),e.jsx("p",{className:"text-muted-foreground",children:"Browse and play your music collection"})]}),e.jsxs("div",{className:"flex w-full flex-col gap-2 sm:w-auto sm:flex-row",children:[t&&t.length>0&&e.jsxs("select",{className:"w-full max-w-full rounded-md border bg-background px-4 py-2 sm:w-auto",value:r||"",onChange:s=>{ae(s.target.value||null),te(null),re(null),ie(""),ne(""),c("artists")},"data-testid":"select-music-source","aria-label":"Music source",children:[e.jsx("option",{value:"",children:"Select a music source"}),t.map(s=>e.jsxs("option",{value:s.id,children:[s.name," (",s.status==="syncing"?"indexing":s.provider,")"]},s.id))]}),r&&e.jsxs(o,{variant:"outline",onClick:()=>O.mutate(r),disabled:O.isPending||((we=t==null?void 0:t.find(s=>s.id===r))==null?void 0:we.status)==="syncing","data-testid":"button-rescan-source",children:[e.jsx(Se,{className:`mr-2 h-4 w-4 ${O.isPending?"animate-spin":""}`}),"Scan"]}),e.jsxs(o,{onClick:()=>m(!0),"data-testid":"button-add-music-source",children:[e.jsx(ke,{className:"mr-2 h-4 w-4"}),"Add source"]})]})]}),Ee?e.jsx(u,{"data-testid":"music-sources-loading",children:e.jsx(x,{className:"py-8 text-center text-muted-foreground",children:"Loading music sourcesâ¦"})}):Y?e.jsxs(u,{className:"border-destructive/50","data-testid":"music-sources-error",children:[e.jsxs(j,{children:[e.jsxs(g,{className:"flex items-center gap-2 text-destructive",children:[e.jsx(Ze,{className:"h-5 w-5"}),"Music sources unavailable"]}),e.jsx(f,{children:Y instanceof Error?Y.message:"The music library could not be loaded."})]}),e.jsx(x,{children:e.jsxs(o,{variant:"outline",onClick:()=>$e(),children:[e.jsx(Se,{className:"mr-2 h-4 w-4"}),"Try again"]})})]}):r?e.jsxs(Qe,{value:le,onValueChange:c,className:"space-y-4",children:[e.jsxs(Re,{className:"flex w-full overflow-x-auto md:grid md:grid-cols-4",children:[e.jsxs(I,{value:"artists","data-testid":"tab-artists",className:"flex-shrink-0",onClick:()=>c("artists"),children:[e.jsx(Te,{className:"h-4 w-4 mr-2"}),e.jsx("span",{className:"hidden sm:inline",children:"Artists"})]}),e.jsxs(I,{value:"albums","data-testid":"tab-albums",className:"flex-shrink-0",onClick:()=>c("albums"),children:[e.jsx(Ae,{className:"h-4 w-4 mr-2"}),e.jsx("span",{className:"hidden sm:inline",children:"Albums"})]}),e.jsxs(I,{value:"tracks","data-testid":"tab-tracks",className:"flex-shrink-0",onClick:()=>c("tracks"),children:[e.jsx(es,{className:"h-4 w-4 mr-2"}),e.jsx("span",{className:"hidden sm:inline",children:"Tracks"})]}),e.jsxs(I,{value:"playlists","data-testid":"tab-playlists",className:"flex-shrink-0",onClick:()=>c("playlists"),children:[e.jsx(ss,{className:"h-4 w-4 mr-2"}),e.jsx("span",{className:"hidden sm:inline",children:"Playlists"})]})]}),e.jsxs("div",{className:"flex flex-col gap-2 md:flex-row md:items-center",children:[e.jsx(q,{placeholder:"Search titles, artists, genres, and moodsâ¦",value:M,onChange:s=>De(s.target.value),className:"max-w-md","data-testid":"input-search"}),le==="tracks"&&e.jsxs(e.Fragment,{children:[e.jsxs("select",{className:"rounded-md border bg-background px-3 py-2 text-sm",value:Q,onChange:s=>ie(s.target.value),"aria-label":"Filter by genre","data-testid":"select-track-genre",children:[e.jsx("option",{value:"",children:"All genres"}),Ie.map(s=>e.jsx("option",{value:s,children:s},s))]}),e.jsxs("select",{className:"rounded-md border bg-background px-3 py-2 text-sm",value:R,onChange:s=>ne(s.target.value),"aria-label":"Filter by mood","data-testid":"select-track-mood",children:[e.jsx("option",{value:"",children:"All moods"}),qe.map(s=>e.jsx("option",{value:s,children:s},s))]})]})]}),e.jsx(B,{value:"artists",className:"space-y-4",children:e.jsxs(u,{children:[e.jsxs(j,{children:[e.jsx(g,{children:"Artists"}),e.jsx(f,{children:"Browse artists in your library"})]}),e.jsx(x,{children:e.jsxs("div",{className:"grid gap-2",children:[ue?e.jsx("p",{className:"py-8 text-center text-muted-foreground",children:"Loading artistsâ¦"}):F?e.jsx("p",{className:"py-8 text-center text-destructive",role:"alert",children:F instanceof Error?F.message:"Could not load artists."}):be.map(s=>{var a,l;return e.jsxs("button",{className:"flex items-center justify-between p-4 border rounded-lg hover:bg-accent transition-colors text-left",onClick:()=>{te(s.id),c("albums")},"data-testid":`artist-${s.id}`,children:[e.jsxs("div",{children:[e.jsx("p",{className:"font-medium",children:s.name}),e.jsxs("p",{className:"text-sm text-muted-foreground",children:[((a=s.metadata)==null?void 0:a.albumCount)||0," albums ⢠",((l=s.metadata)==null?void 0:l.trackCount)||0," tracks"]})]}),e.jsx(Te,{className:"h-5 w-5 text-muted-foreground"})]},s.id)}),!ue&&!F&&be.length===0&&e.jsx("p",{className:"text-center text-muted-foreground py-8",children:"No artists found"})]})})]})}),e.jsx(B,{value:"albums",className:"space-y-4",children:e.jsxs(u,{children:[e.jsxs(j,{children:[e.jsx(g,{children:"Albums"}),e.jsx(f,{children:L?"Albums by selected artist":"All albums in your library"})]}),e.jsx(x,{children:e.jsxs("div",{className:"grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4",children:[xe?e.jsx("p",{className:"col-span-full py-8 text-center text-muted-foreground",children:"Loading albumsâ¦"}):E?e.jsx("p",{className:"col-span-full py-8 text-center text-destructive",role:"alert",children:E instanceof Error?E.message:"Could not load albums."}):ye.map(s=>{var a;return e.jsxs("button",{className:"flex flex-col items-start p-4 border rounded-lg hover:bg-accent transition-colors text-left",onClick:()=>{re(s.id),c("tracks")},"data-testid":`album-${s.id}`,children:[e.jsx("div",{className:"w-full aspect-square bg-muted rounded-md mb-2 flex items-center justify-center",children:e.jsx(Ae,{className:"h-12 w-12 text-muted-foreground"})}),e.jsx("p",{className:"font-medium line-clamp-1",children:s.title}),e.jsx("p",{className:"text-sm text-muted-foreground",children:s.releaseYear||"Unknown year"}),e.jsxs("p",{className:"text-xs text-muted-foreground",children:[((a=s.metadata)==null?void 0:a.totalTracks)||0," tracks"]})]},s.id)}),!xe&&!E&&ye.length===0&&e.jsx("p",{className:"col-span-full text-center text-muted-foreground py-8",children:"No albums found"})]})})]})}),e.jsx(B,{value:"tracks",className:"space-y-4",children:e.jsxs(u,{children:[e.jsxs(j,{children:[e.jsx(g,{children:"Tracks"}),e.jsx(f,{children:D?"Tracks from selected album":"All tracks in your library"})]}),e.jsx(x,{children:e.jsx("div",{className:"max-w-full overflow-x-auto",children:e.jsxs(Oe,{children:[e.jsx(We,{children:e.jsxs(A,{children:[e.jsx(h,{className:"w-12",children:"#"}),e.jsx(h,{children:"Title"}),e.jsx(h,{children:"Artist"}),e.jsx(h,{children:"Genre & mood"}),e.jsx(h,{children:"Duration"}),e.jsx(h,{children:"Format"}),e.jsx(h,{className:"w-24",children:"Actions"})]})}),e.jsxs(Xe,{children:[he?e.jsx(A,{children:e.jsx(n,{colSpan:7,className:"text-center text-muted-foreground",children:"Loading tracksâ¦"})}):$?e.jsx(A,{children:e.jsx(n,{colSpan:7,className:"text-center text-destructive",role:"alert",children:$ instanceof Error?$.message:"Could not load tracks."})}):ve.map((s,a)=>{var l,N,v,w,C,S;return e.jsxs(A,{"data-testid":`track-${s.id}`,children:[e.jsx(n,{children:s.trackNumber||a+1}),e.jsx(n,{className:"font-medium",children:s.title}),e.jsx(n,{children:s.artistName||"Unknown artist"}),e.jsx(n,{children:e.jsxs("div",{className:"flex max-w-64 flex-wrap gap-1",children:[(((l=s.metadata)==null?void 0:l.genres)||[]).slice(0,2).map(k=>e.jsx(Ce,{variant:"secondary",children:k},`genre-${k}`)),(((N=s.metadata)==null?void 0:N.moods)||[]).slice(0,2).map(k=>e.jsx(Ce,{variant:"outline",children:k},`mood-${k}`)),!((w=(v=s.metadata)==null?void 0:v.genres)!=null&&w.length)&&!((S=(C=s.metadata)==null?void 0:C.moods)!=null&&S.length)&&e.jsx("span",{className:"text-xs text-muted-foreground",children:"Unclassified"})]})}),e.jsx(n,{children:fe(s.duration)}),e.jsx(n,{className:"uppercase",children:s.format||"unknown"}),e.jsx(n,{children:e.jsx(o,{size:"sm",variant:"ghost",onClick:()=>Ue(s),"data-testid":`button-play-${s.id}`,"aria-label":`Play ${s.title}`,children:e.jsx(Le,{className:"h-4 w-4"})})})]},s.id)}),!he&&!$&&ve.length===0&&e.jsx(A,{children:e.jsx(n,{colSpan:7,className:"text-center text-muted-foreground",children:"No tracks foun
1d"})})]})]})})})]})}),e.jsx(B,{value:"playlists",className:"space-y-4",children:e.jsxs(u,{children:[e.jsxs(j,{children:[e.jsx(g,{children:"Playlists"}),e.jsx(f,{children:"Your music playlists"})]}),e.jsx(x,{children:e.jsxs("div",{className:"grid gap-2",children:[pe?e.jsx("p",{className:"py-8 text-center text-muted-foreground",children:"Loading playlistsâ¦"}):U?e.jsx("p",{className:"py-8 text-center text-destructive",role:"alert",children:U instanceof Error?U.message:"Could not load playlists."}):y==null?void 0:y.map(s=>{var a,l;return e.jsxs("div",{className:"flex items-center justify-between p-4 border rounded-lg","data-testid":`playlist-${s.id}`,children:[e.jsxs("div",{children:[e.jsx("p",{className:"font-medium",children:s.name}),e.jsxs("p",{className:"text-sm text-muted-foreground",children:[((a=s.metadata)==null?void 0:a.trackCount)||0," tracks ⢠",fe((l=s.metadata)==null?void 0:l.totalDuration)]})]}),e.jsx(o,{variant:"ghost",size:"sm",onClick:()=>ge.mutate(s.id),disabled:ge.isPending,"aria-label":`Play playlist ${s.name}`,children:e.jsx(Le,{className:"h-4 w-4"})})]},s.id)}),!pe&&!U&&(!y||y.length===0)&&e.jsx("p",{className:"text-center text-muted-foreground py-8",children:"No playlists yet"})]})})]})})]}):e.jsxs(u,{children:[e.jsxs(j,{children:[e.jsx(g,{children:t!=null&&t.length?"Welcome to Music Library":"No indexed music sources"}),e.jsx(f,{children:t!=null&&t.length?"Select a music source from the dropdown above to browse your music collection.":"Connect a local, Plex, Jellyfin, or network music source and index it to browse tracks here."})]}),e.jsxs(x,{children:[e.jsx("p",{className:"mb-4 text-sm text-muted-foreground",children:t&&t.length>0?`You have ${t.length} music source${t.length>1?"s":""} configured.`:"Streaming radio and Jamendo remain available from Free Radio."}),!(t!=null&&t.length)&&e.jsxs(o,{onClick:()=>m(!0),children:[e.jsx(ke,{className:"mr-2 h-4 w-4"}),"Choose a media source"]})]})]}),e.jsx(ze,{open:Me,onOpenChange:s=>{m(s),s||V()},children:e.jsxs(Ge,{className:"max-w-2xl",children:[e.jsxs(Je,{children:[e.jsx(Ye,{children:"Add a media source"}),e.jsx(Ve,{children:"Choose where DomoCore should find music and other media with a playable audio track."})]}),d?e.jsxs("div",{className:"space-y-4",children:[e.jsx(o,{variant:"ghost",size:"sm",onClick:V,children:"â Choose a different source"}),e.jsxs("div",{className:"space-y-2",children:[e.jsx(se,{htmlFor:"media-source-name",children:"Display name"}),e.jsx(q,{id:"media-source-name",value:G,onChange:s=>P(s.target.value),placeholder:d==="nas"?"Home NAS":"Music drive"})]}),e.jsxs("div",{className:"space-y-2",children:[e.jsx(se,{htmlFor:"media-source-path",children:d==="nas"?"Mounted folder on DomoCore":"Folder path on DomoCore"}),e.jsxs("div",{className:"relative",children:[e.jsx(is,{className:"absolute left-3 top-3 h-4 w-4 text-muted-foreground"}),e.jsx(q,{id:"media-source-path",className:"pl-9",value:J,onChange:s=>oe(s.target.value),placeholder:d==="nas"?"/mnt/media/music":"/Volumes/Music"})]}),e.jsxs("p",{className:"text-xs text-muted-foreground",children:["Enter the absolute path as seen by the DomoCore. Allowed roots are controlled by",e.jsx("span",{className:"font-mono",children:" DOMOLABS_MEDIA_ROOTS"}),"."]})]}),d==="nas"&&e.jsxs("div",{className:"space-y-2",children:[e.jsx(se,{htmlFor:"media-share-url",children:"Share address (optional)"}),e.jsx(q,{id:"media-share-url",value:ce,onChange:s=>de(s.target.value),placeholder:"smb://nas.local/Music"}),e.jsx("p",{className:"text-xs text-muted-foreground",children:"This identifies the share. Mount it on DomoCore first; credentials are not stored here."})]})]}):e.jsxs("div",{className:"grid gap-3 sm:grid-cols-2",children:[e.jsxs("button",{type:"button",className:"rounded-lg border p-4 text-left transition-colors hover:bg-accent",onClick:()=>{m(!1),H==null||H()},"data-testid":"source-option-plex",children:[e.jsx(as,{className:"mb-3 h-6 w-6 text-amber-500"}),e.jsx("span",{className:"block font-medium",children:"Plex Media Server"}),e.jsx("span",{className:"mt-1 block text-sm text-muted-foreground",children:"Connect and choose a Plex music library."})]}),e.jsxs("button",{type:"button",className:"rounded-lg border p-4 text-left transition-colors hover:bg-accent",onClick:()=>{z("drive"),P("Local media")},"data-testid":"source-option-drive",children:[e.jsx(ts,{className:"mb-3 h-6 w-6 text-cyan-500"}),e.jsx("span",{className:"block font-medium",children:"Drive or folder"}),e.jsx("span",{className:"mt-1 block text-sm text-muted-foreground",children:"Index a USB drive, external disk, or folder attached to DomoCore."})]}),e.jsxs("button",{type:"button",className:"rounded-lg border p-4 text-left transition-colors hover:bg-accent",onClick:()=>{z("nas"),P("Home NAS")},"data-testid":"source-option-nas",children:[e.jsx(rs,{className:"mb-3 h-6 w-6 text-emerald-500"}),e.jsx("span",{className:"block font-medium",children:"NAS or network share"}),e.jsx("span",{className:"mt-1 block text-sm text-muted-foreground",children:"Use an SMB or NFS share mounted on the DomoCore."})]}),e.jsxs("button",{type:"button",className:"rounded-lg border p-4 text-left transition-colors hover:bg-accent",onClick:()=>{m(!1),K==null||K()},"data-testid":"source-option-hdhomerun",children:[e.jsx(ls,{className:"mb-3 h-6 w-6 text-purple-500"}),e.jsx("span",{className:"block font-medium",children:"HDHomeRun / OTA"}),e.jsx("span",{className:"mt-1 block text-sm text-muted-foreground",children:"Play audio from clear OTA services reported by a LAN tuner."})]})]}),e.jsxs(_e,{children:[e.jsx(o,{variant:"outline",onClick:()=>m(!1),children:"Cancel"}),d&&e.jsxs(o,{onClick:()=>_.mutate(),disabled:!G.trim()||!J.trim()||_.isPending,children:[_.isPending&&e.jsx(ns,{className:"mr-2 h-4 w-4 animate-spin"}),"Add and index"]})]})]})})]})}export{Ns as default}; 2//# sourceMappingURL=MusicLibraryPage-zgGWCPd4.js.map
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.