Added a playlist view. Can only play the playlists for now (no save/edit)
This commit is contained in:
@@ -14,9 +14,12 @@
|
|||||||
<div class="p-2 flex-fill">
|
<div class="p-2 flex-fill">
|
||||||
${name}${<if-tag>}<span class="badge bg-info ms-2">${tag}</span>${</if-tag>}
|
${name}${<if-tag>}<span class="badge bg-info ms-2">${tag}</span>${</if-tag>}
|
||||||
</div>
|
</div>
|
||||||
|
${<if-edit>}
|
||||||
<div class="p-2">
|
<div class="p-2">
|
||||||
${<if-edit>}${edit-btn class="btn btn-sm btn-secondary me-1"}${del-btn class="btn btn-sm btn-danger"}${</if-edit>}
|
${edit-btn class="btn btn-sm btn-outline-secondary border-0"}
|
||||||
|
${del-btn class="btn btn-sm btn-outline-danger border-0"}
|
||||||
</div>
|
</div>
|
||||||
|
${</if-edit>}
|
||||||
</div>
|
</div>
|
||||||
</message>
|
</message>
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -10,13 +10,13 @@
|
|||||||
${play-btn class="btn btn-primary"}
|
${play-btn class="btn btn-primary"}
|
||||||
<button type="button" class="btn btn-primary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false"/>
|
<button type="button" class="btn btn-primary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false"/>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<li>${play-shuffled class="btn dropdown-item"}</li>
|
<li>${play-shuffled class="dropdown-item"}</li>
|
||||||
<li>${play-last class="btn dropdown-item"}</li>
|
<li>${play-last class="dropdown-item"}</li>
|
||||||
<li>${star class="btn dropdown-item"}</li>
|
<li>${star class="dropdown-item"}</li>
|
||||||
${<if-has-mbid>}
|
${<if-has-mbid>}
|
||||||
<li><a href="${mbid-link}" target="_blank" class="dropdown-item">${tr:Lms.Explore.musicbrainz-artist}</a></li>
|
<li><a href="${mbid-link}" target="_blank" class="dropdown-item">${tr:Lms.Explore.musicbrainz-artist}</a></li>
|
||||||
${</if-has-mbid>}
|
${</if-has-mbid>}
|
||||||
<li>${download class="btn dropdown-item"}</li>
|
<li>${download class="dropdown-item"}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,9 +5,6 @@
|
|||||||
${contents}
|
${contents}
|
||||||
</message>
|
</message>
|
||||||
|
|
||||||
<message id="Lms.Explore.template.more-btn"><i class="fa fa-fw fa-ellipsis-v"></i></message>
|
|
||||||
<message id="Lms.Explore.template.play-btn"><i class="fa fa-fw fa-play"></i></message>
|
|
||||||
|
|
||||||
<message id="Lms.Explore.template.filters">
|
<message id="Lms.Explore.template.filters">
|
||||||
${add-filter class="btn btn-sm btn-outline-primary me-2"}${clusters class="d-inline-block align-items-center"}
|
${add-filter class="btn btn-sm btn-outline-primary me-2"}${clusters class="d-inline-block align-items-center"}
|
||||||
</message>
|
</message>
|
||||||
|
|||||||
+1
-1
@@ -32,7 +32,7 @@
|
|||||||
${tracks class="nav-link"}
|
${tracks class="nav-link"}
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
${playqueue class="nav-link"}
|
${tracklists class="nav-link"}
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
${filters class="nav-link d-flex align-items-center"}
|
${filters class="nav-link d-flex align-items-center"}
|
||||||
|
|||||||
+10
-5
@@ -14,7 +14,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<div class="d-inline-flex me-2">
|
<div class="py-2 pe-2 d-inline-flex">
|
||||||
<button id="lms-mp-previous" class="btn btn-sm btn-outline-primary border-0">
|
<button id="lms-mp-previous" class="btn btn-sm btn-outline-primary border-0">
|
||||||
<i class="fa fa-fw fa-step-backward"/>
|
<i class="fa fa-fw fa-step-backward"/>
|
||||||
</button>
|
</button>
|
||||||
@@ -29,16 +29,21 @@
|
|||||||
${title class="d-block text-truncate"}
|
${title class="d-block text-truncate"}
|
||||||
${release class="text-decoration-none text-truncate link-success"}
|
${release class="text-decoration-none text-truncate link-success"}
|
||||||
</div>
|
</div>
|
||||||
<div class="d-none d-md-flex align-items-center Lms-player-volume-container">
|
<div class="p-2 d-none d-sm-block text-muted text-nowrap">
|
||||||
|
<span id="lms-transcoding-active" title="${tr:Lms.Player.transcoding-active}" class="p-2" style="visibility:hidden"><i class="fa fa-fw fa-info-circle" aria-hidden="true"></i></span>
|
||||||
|
<span id="lms-mp-curtime">--</span> / <span id="lms-mp-duration">--</span>
|
||||||
|
</div>
|
||||||
|
<div class="p-2 d-none d-md-flex align-items-center Lms-player-volume-container">
|
||||||
<i id="lms-mp-volume" class="fa fa-fw fa-volume-up link-primary me-1"></i>
|
<i id="lms-mp-volume" class="fa fa-fw fa-volume-up link-primary me-1"></i>
|
||||||
<input id ="lms-mp-volume-slider" class="form-range" type="range" min="0" max="1" step="0.01" value="0.8"/>
|
<input id ="lms-mp-volume-slider" class="form-range" type="range" min="0" max="1" step="0.01" value="0.8"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-none d-sm-block text-muted text-nowrap">
|
<div class="py-2 ps-2">
|
||||||
<span id="lms-transcoding-active" title="${tr:Lms.Player.transcoding-active}" class="p-2" style="visibility:hidden"><i class="fa fa-fw fa-info-circle" aria-hidden="true"></i></span>
|
${playqueue-btn class="btn btn-outline-primary border-0"}
|
||||||
<span id="lms-mp-curtime">--</span> / <span id="lms-mp-duration">--</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</message>
|
</message>
|
||||||
|
|
||||||
|
<message id="Lms.MediaPlayer.template.playqueue-btn"><i class="fa fa-fw fa-th-list"/></message>
|
||||||
|
|
||||||
</messages>
|
</messages>
|
||||||
|
|||||||
@@ -6,9 +6,11 @@
|
|||||||
<message id="Lms.add">Add</message>
|
<message id="Lms.add">Add</message>
|
||||||
<message id="Lms.administration">Administration</message>
|
<message id="Lms.administration">Administration</message>
|
||||||
<message id="Lms.apply">Apply</message>
|
<message id="Lms.apply">Apply</message>
|
||||||
<message id="Lms.discard">Discard</message>
|
|
||||||
<message id="Lms.cancel">Cancel</message>
|
<message id="Lms.cancel">Cancel</message>
|
||||||
<message id="Lms.create">Create</message>
|
<message id="Lms.create">Create</message>
|
||||||
|
<message id="Lms.delete">Delete</message>
|
||||||
|
<message id="Lms.discard">Discard</message>
|
||||||
|
<message id="Lms.edit">Edit</message>
|
||||||
<message id="Lms.loading">Loading...</message>
|
<message id="Lms.loading">Loading...</message>
|
||||||
<message id="Lms.login">Login</message>
|
<message id="Lms.login">Login</message>
|
||||||
<message id="Lms.logout"><i class="fa fa-fw fa-sign-out" aria-hidden="true"></i> Logout</message>
|
<message id="Lms.logout"><i class="fa fa-fw fa-sign-out" aria-hidden="true"></i> Logout</message>
|
||||||
@@ -85,11 +87,9 @@
|
|||||||
<!--Users-->
|
<!--Users-->
|
||||||
<message id="Lms.Admin.Users.add">New user</message>
|
<message id="Lms.Admin.Users.add">New user</message>
|
||||||
<message id="Lms.Admin.Users.admin">Admin</message>
|
<message id="Lms.Admin.Users.admin">Admin</message>
|
||||||
<message id="Lms.Admin.Users.del">Delete</message>
|
|
||||||
<message id="Lms.Admin.Users.del-user">Delete user</message>
|
<message id="Lms.Admin.Users.del-user">Delete user</message>
|
||||||
<message id="Lms.Admin.Users.del-user-name">Delete user?</message>
|
<message id="Lms.Admin.Users.del-user-name">Delete user?</message>
|
||||||
<message id="Lms.Admin.Users.demo">Demo</message>
|
<message id="Lms.Admin.Users.demo">Demo</message>
|
||||||
<message id="Lms.Admin.Users.edit">Edit</message>
|
|
||||||
<message id="Lms.Admin.Users.users">Users</message>
|
<message id="Lms.Admin.Users.users">Users</message>
|
||||||
<message id="Lms.Admin.Users.menu-users"><i class="fa fa-fw fa-users" aria-hidden="true"></i> Users</message>
|
<message id="Lms.Admin.Users.menu-users"><i class="fa fa-fw fa-users" aria-hidden="true"></i> Users</message>
|
||||||
|
|
||||||
@@ -130,6 +130,7 @@
|
|||||||
<message id="Lms.Explore.releases">Albums</message>
|
<message id="Lms.Explore.releases">Albums</message>
|
||||||
<message id="Lms.Explore.star">Star</message>
|
<message id="Lms.Explore.star">Star</message>
|
||||||
<message id="Lms.Explore.starred">Starred</message>
|
<message id="Lms.Explore.starred">Starred</message>
|
||||||
|
<message id="Lms.Explore.tracklists">Playlists</message>
|
||||||
<message id="Lms.Explore.tracks">Tracks</message>
|
<message id="Lms.Explore.tracks">Tracks</message>
|
||||||
<message id="Lms.Explore.type">Type</message>
|
<message id="Lms.Explore.type">Type</message>
|
||||||
<message id="Lms.Explore.unstar">Unstar</message>
|
<message id="Lms.Explore.unstar">Unstar</message>
|
||||||
|
|||||||
@@ -6,9 +6,11 @@
|
|||||||
<message id="Lms.add">Ajouter</message>
|
<message id="Lms.add">Ajouter</message>
|
||||||
<message id="Lms.administration">Administration</message>
|
<message id="Lms.administration">Administration</message>
|
||||||
<message id="Lms.apply">Appliquer</message>
|
<message id="Lms.apply">Appliquer</message>
|
||||||
<message id="Lms.discard">Annuler</message>
|
|
||||||
<message id="Lms.cancel">Annuler</message>
|
<message id="Lms.cancel">Annuler</message>
|
||||||
<message id="Lms.create">Créer</message>
|
<message id="Lms.create">Créer</message>
|
||||||
|
<message id="Lms.delete">Supprimer</message>
|
||||||
|
<message id="Lms.discard">Annuler</message>
|
||||||
|
<message id="Lms.edit">Editer</message>
|
||||||
<message id="Lms.loading">Chargement...</message>
|
<message id="Lms.loading">Chargement...</message>
|
||||||
<message id="Lms.login">Login</message>
|
<message id="Lms.login">Login</message>
|
||||||
<message id="Lms.logout"><i class="fa fa-fw fa-sign-out" aria-hidden="true"></i> Quitter</message>
|
<message id="Lms.logout"><i class="fa fa-fw fa-sign-out" aria-hidden="true"></i> Quitter</message>
|
||||||
@@ -85,11 +87,9 @@
|
|||||||
<!--Users-->
|
<!--Users-->
|
||||||
<message id="Lms.Admin.Users.add">Ajouter</message>
|
<message id="Lms.Admin.Users.add">Ajouter</message>
|
||||||
<message id="Lms.Admin.Users.admin">Admin</message>
|
<message id="Lms.Admin.Users.admin">Admin</message>
|
||||||
<message id="Lms.Admin.Users.del">Supprimer</message>
|
|
||||||
<message id="Lms.Admin.Users.del-user">Supprimer l'utilisateur</message>
|
<message id="Lms.Admin.Users.del-user">Supprimer l'utilisateur</message>
|
||||||
<message id="Lms.Admin.Users.del-user-name">Supprimer l'utilisateur ?</message>
|
<message id="Lms.Admin.Users.del-user-name">Supprimer l'utilisateur ?</message>
|
||||||
<message id="Lms.Admin.Users.demo">Demo</message>
|
<message id="Lms.Admin.Users.demo">Demo</message>
|
||||||
<message id="Lms.Admin.Users.edit">Editer</message>
|
|
||||||
<message id="Lms.Admin.Users.users">Utilisateurs</message>
|
<message id="Lms.Admin.Users.users">Utilisateurs</message>
|
||||||
<message id="Lms.Admin.Users.menu-users"><i class="fa fa-fw fa-users" aria-hidden="true"></i> Utilisateurs</message>
|
<message id="Lms.Admin.Users.menu-users"><i class="fa fa-fw fa-users" aria-hidden="true"></i> Utilisateurs</message>
|
||||||
|
|
||||||
@@ -130,6 +130,7 @@
|
|||||||
<message id="Lms.Explore.releases">Albums</message>
|
<message id="Lms.Explore.releases">Albums</message>
|
||||||
<message id="Lms.Explore.star">Ajouter aux favoris</message>
|
<message id="Lms.Explore.star">Ajouter aux favoris</message>
|
||||||
<message id="Lms.Explore.starred">Favoris</message>
|
<message id="Lms.Explore.starred">Favoris</message>
|
||||||
|
<message id="Lms.Explore.tracklists">Playlists</message>
|
||||||
<message id="Lms.Explore.tracks">Pistes</message>
|
<message id="Lms.Explore.tracks">Pistes</message>
|
||||||
<message id="Lms.Explore.type">Type</message>
|
<message id="Lms.Explore.type">Type</message>
|
||||||
<message id="Lms.Explore.unstar">Retirer des favoris</message>
|
<message id="Lms.Explore.unstar">Retirer des favoris</message>
|
||||||
|
|||||||
@@ -6,9 +6,11 @@
|
|||||||
<message id="Lms.add">Aggiungi</message>
|
<message id="Lms.add">Aggiungi</message>
|
||||||
<message id="Lms.administration">Amministrazione</message>
|
<message id="Lms.administration">Amministrazione</message>
|
||||||
<message id="Lms.apply">Applica</message>
|
<message id="Lms.apply">Applica</message>
|
||||||
<message id="Lms.discard">Annulla</message>
|
|
||||||
<message id="Lms.cancel">Annulla</message>
|
<message id="Lms.cancel">Annulla</message>
|
||||||
<message id="Lms.create">Crea</message>
|
<message id="Lms.create">Crea</message>
|
||||||
|
<message id="Lms.del">Elimina</message>
|
||||||
|
<message id="Lms.discard">Annulla</message>
|
||||||
|
<message id="Lms.edit">Modifica</message>
|
||||||
<message id="Lms.loading">Caricamento...</message>
|
<message id="Lms.loading">Caricamento...</message>
|
||||||
<message id="Lms.login">Login</message>
|
<message id="Lms.login">Login</message>
|
||||||
<message id="Lms.logout"><i class="fa fa-fw fa-sign-out" aria-hidden="true"></i> Logout</message>
|
<message id="Lms.logout"><i class="fa fa-fw fa-sign-out" aria-hidden="true"></i> Logout</message>
|
||||||
@@ -84,11 +86,9 @@
|
|||||||
<!--Users-->
|
<!--Users-->
|
||||||
<message id="Lms.Admin.Users.add">Nuovo utente</message>
|
<message id="Lms.Admin.Users.add">Nuovo utente</message>
|
||||||
<message id="Lms.Admin.Users.admin">Amministratore</message>
|
<message id="Lms.Admin.Users.admin">Amministratore</message>
|
||||||
<message id="Lms.Admin.Users.del">Elimina</message>
|
|
||||||
<message id="Lms.Admin.Users.del-user">Elimina l'utente</message>
|
<message id="Lms.Admin.Users.del-user">Elimina l'utente</message>
|
||||||
<message id="Lms.Admin.Users.del-user-name">Vuoi eliminare l'utente?</message>
|
<message id="Lms.Admin.Users.del-user-name">Vuoi eliminare l'utente?</message>
|
||||||
<message id="Lms.Admin.Users.demo">Demo</message>
|
<message id="Lms.Admin.Users.demo">Demo</message>
|
||||||
<message id="Lms.Admin.Users.edit">Modifica</message>
|
|
||||||
<message id="Lms.Admin.Users.users">Utenti</message>
|
<message id="Lms.Admin.Users.users">Utenti</message>
|
||||||
<message id="Lms.Admin.Users.menu-users"><i class="fa fa-fw fa-users" aria-hidden="true"></i> Utenti</message>
|
<message id="Lms.Admin.Users.menu-users"><i class="fa fa-fw fa-users" aria-hidden="true"></i> Utenti</message>
|
||||||
|
|
||||||
@@ -129,6 +129,7 @@
|
|||||||
<message id="Lms.Explore.releases">Album</message>
|
<message id="Lms.Explore.releases">Album</message>
|
||||||
<message id="Lms.Explore.star">Aggiungi ai preferiti</message>
|
<message id="Lms.Explore.star">Aggiungi ai preferiti</message>
|
||||||
<message id="Lms.Explore.starred">Preferiti</message>
|
<message id="Lms.Explore.starred">Preferiti</message>
|
||||||
|
|
||||||
<message id="Lms.Explore.tracks">Tracce</message>
|
<message id="Lms.Explore.tracks">Tracce</message>
|
||||||
<message id="Lms.Explore.type">Tipo</message>
|
<message id="Lms.Explore.type">Tipo</message>
|
||||||
<message id="Lms.Explore.unstar">Rimuovi dai preferiti</message>
|
<message id="Lms.Explore.unstar">Rimuovi dai preferiti</message>
|
||||||
|
|||||||
@@ -6,9 +6,11 @@
|
|||||||
<message id="Lms.add">添加</message>
|
<message id="Lms.add">添加</message>
|
||||||
<message id="Lms.administration">管理</message>
|
<message id="Lms.administration">管理</message>
|
||||||
<message id="Lms.apply">应用</message>
|
<message id="Lms.apply">应用</message>
|
||||||
<message id="Lms.discard">撤销</message>
|
|
||||||
<message id="Lms.cancel">取消</message>
|
<message id="Lms.cancel">取消</message>
|
||||||
<message id="Lms.create">新建</message>
|
<message id="Lms.create">新建</message>
|
||||||
|
<message id="Lms.del">删除</message>
|
||||||
|
<message id="Lms.discard">撤销</message>
|
||||||
|
<message id="Lms.edit">编辑</message>
|
||||||
<message id="Lms.loading">加载中...</message>
|
<message id="Lms.loading">加载中...</message>
|
||||||
<message id="Lms.login">登陆</message>
|
<message id="Lms.login">登陆</message>
|
||||||
<message id="Lms.logout"><i class="fa fa-fw fa-sign-out" aria-hidden="true"></i> 注销</message>
|
<message id="Lms.logout"><i class="fa fa-fw fa-sign-out" aria-hidden="true"></i> 注销</message>
|
||||||
@@ -85,11 +87,9 @@
|
|||||||
<!--Users-->
|
<!--Users-->
|
||||||
<message id="Lms.Admin.Users.add">新建用户</message>
|
<message id="Lms.Admin.Users.add">新建用户</message>
|
||||||
<message id="Lms.Admin.Users.admin">管理员</message>
|
<message id="Lms.Admin.Users.admin">管理员</message>
|
||||||
<message id="Lms.Admin.Users.del">删除</message>
|
|
||||||
<message id="Lms.Admin.Users.del-user">删除用户</message>
|
<message id="Lms.Admin.Users.del-user">删除用户</message>
|
||||||
<message id="Lms.Admin.Users.del-user-name">确认要删除此用户?</message>
|
<message id="Lms.Admin.Users.del-user-name">确认要删除此用户?</message>
|
||||||
<message id="Lms.Admin.Users.demo">演示</message>
|
<message id="Lms.Admin.Users.demo">演示</message>
|
||||||
<message id="Lms.Admin.Users.edit">编辑</message>
|
|
||||||
<message id="Lms.Admin.Users.users">用户</message>
|
<message id="Lms.Admin.Users.users">用户</message>
|
||||||
<message id="Lms.Admin.Users.menu-users"><i class="fa fa-fw fa-users" aria-hidden="true"></i> 用户</message>
|
<message id="Lms.Admin.Users.menu-users"><i class="fa fa-fw fa-users" aria-hidden="true"></i> 用户</message>
|
||||||
|
|
||||||
@@ -130,6 +130,7 @@
|
|||||||
<message id="Lms.Explore.releases">专辑</message>
|
<message id="Lms.Explore.releases">专辑</message>
|
||||||
<message id="Lms.Explore.star">收藏</message>
|
<message id="Lms.Explore.star">收藏</message>
|
||||||
<message id="Lms.Explore.starred">已收藏</message>
|
<message id="Lms.Explore.starred">已收藏</message>
|
||||||
|
|
||||||
<message id="Lms.Explore.tracks">音轨</message>
|
<message id="Lms.Explore.tracks">音轨</message>
|
||||||
<message id="Lms.Explore.type">类型</message>
|
<message id="Lms.Explore.type">类型</message>
|
||||||
<message id="Lms.Explore.unstar">取消收藏</message>
|
<message id="Lms.Explore.unstar">取消收藏</message>
|
||||||
|
|||||||
+6
-1
@@ -9,9 +9,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</message>
|
</message>
|
||||||
|
|
||||||
<message id="Lms.infinite-scrolling-container">
|
<message id="Lms.infinite-scrolling-container.template">
|
||||||
${elements}
|
${elements}
|
||||||
${loading-indicator}
|
${loading-indicator}
|
||||||
</message>
|
</message>
|
||||||
|
|
||||||
|
<message id="Lms.template.delete-btn"><i class="fa fa-fw fa-times"></i></message>
|
||||||
|
<message id="Lms.template.edit-btn"><i class="fa fa-fw fa-edit"></i></message>
|
||||||
|
<message id="Lms.template.more-btn"><i class="fa fa-fw fa-ellipsis-v"></i></message>
|
||||||
|
<message id="Lms.template.play-btn"><i class="fa fa-fw fa-play"></i></message>
|
||||||
|
|
||||||
</messages>
|
</messages>
|
||||||
|
|||||||
@@ -26,10 +26,6 @@
|
|||||||
${entries}
|
${entries}
|
||||||
</message>
|
</message>
|
||||||
|
|
||||||
<message id="Lms.PlayQueue.template.play-btn"><i class="fa fa-fw fa-play"></i></message>
|
|
||||||
<message id="Lms.PlayQueue.template.delete-btn"><i class="fa fa-fw fa-times"></i></message>
|
|
||||||
<message id="Lms.PlayQueue.template.more-btn"><i class="fa fa-fw fa-ellipsis-v"></i></message>
|
|
||||||
|
|
||||||
<message id="Lms.PlayQueue.template.entry">
|
<message id="Lms.PlayQueue.template.entry">
|
||||||
<div class="d-flex align-items-center rounded Lms-bg-dark-hover mb-2">
|
<div class="d-flex align-items-center rounded Lms-bg-dark-hover mb-2">
|
||||||
<div class="p-1 Lms-cover-small">
|
<div class="p-1 Lms-cover-small">
|
||||||
@@ -44,14 +40,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="p-2 d-flex">
|
<div class="p-2 d-flex">
|
||||||
${play-btn class="btn btn-sm btn-outline-secondary border-0"}
|
${play-btn class="btn btn-sm btn-outline-secondary border-0"}
|
||||||
${del-btn class="btn btn-sm btn-outline-secondary border-0"}
|
|
||||||
<div class="dropdown d-none d-sm-block">
|
<div class="dropdown d-none d-sm-block">
|
||||||
${more-btn data-bs-toggle="dropdown" aria-expanded="false" class="btn btn-sm btn-outline-secondary border-0"}
|
${more-btn data-bs-toggle="dropdown" aria-expanded="false" class="btn btn-sm btn-outline-secondary border-0"}
|
||||||
<ul class="dropdown-menu" aria-labelledby="${id:more-btn}">
|
<ul class="dropdown-menu" aria-labelledby="${id:more-btn}">
|
||||||
<li>${star class="btn dropdown-item"}</li>
|
<li>${star class="dropdown-item"}</li>
|
||||||
<li>${download class="btn dropdown-item"}</li>
|
<li>${download class="dropdown-item"}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
${del-btn class="btn btn-sm btn-outline-secondary border-0"}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</message>
|
</message>
|
||||||
|
|||||||
+7
-7
@@ -21,13 +21,13 @@
|
|||||||
${play-btn class="btn btn-primary"}
|
${play-btn class="btn btn-primary"}
|
||||||
<button type="button" class="btn btn-primary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false"/>
|
<button type="button" class="btn btn-primary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false"/>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<li>${play-shuffled class="btn dropdown-item"}</li>
|
<li>${play-shuffled class="dropdown-item"}</li>
|
||||||
<li>${play-last class="btn dropdown-item"}</li>
|
<li>${play-last class="dropdown-item"}</li>
|
||||||
<li>${star class="btn dropdown-item"}</li>
|
<li>${star class="dropdown-item"}</li>
|
||||||
${<if-has-mbid>}
|
${<if-has-mbid>}
|
||||||
<li><a href="${mbid-link}" target="_blank" class="dropdown-item">${tr:Lms.Explore.musicbrainz-release}</a></li>
|
<li><a href="${mbid-link}" target="_blank" class="dropdown-item">${tr:Lms.Explore.musicbrainz-release}</a></li>
|
||||||
${</if-has-mbid>}
|
${</if-has-mbid>}
|
||||||
<li>${download class="btn dropdown-item"}</li>
|
<li>${download class="dropdown-item"}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -74,9 +74,9 @@
|
|||||||
<div class="dropdown d-inline-block">
|
<div class="dropdown d-inline-block">
|
||||||
${more-btn data-bs-toggle="dropdown" aria-expanded="false" class="btn btn-sm btn-outline-secondary border-0"}
|
${more-btn data-bs-toggle="dropdown" aria-expanded="false" class="btn btn-sm btn-outline-secondary border-0"}
|
||||||
<ul class="dropdown-menu" aria-labelledby="${id:more-btn}">
|
<ul class="dropdown-menu" aria-labelledby="${id:more-btn}">
|
||||||
<li>${play-last class="btn dropdown-item"}</li>
|
<li>${play-last class="dropdown-item"}</li>
|
||||||
<li>${star class="btn dropdown-item"}</li>
|
<li>${star class="dropdown-item"}</li>
|
||||||
<li>${download class="btn dropdown-item"}</li>
|
<li>${download class="dropdown-item"}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -28,8 +28,8 @@
|
|||||||
${play-btn class="btn btn-primary"}
|
${play-btn class="btn btn-primary"}
|
||||||
<button type="button" class="btn btn-primary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false"/>
|
<button type="button" class="btn btn-primary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false"/>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<li>${play-last class="btn dropdown-item"}</li>
|
<li>${play-last class="dropdown-item"}</li>
|
||||||
<li>${play-shuffled class="btn dropdown-item"}</li>
|
<li>${play-shuffled class="dropdown-item"}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<messages xmlns:if="Wt.WTemplate.conditions">
|
||||||
|
|
||||||
|
<message id="Lms.Explore.TrackLists.template">
|
||||||
|
${tracklists}
|
||||||
|
</message>
|
||||||
|
|
||||||
|
<message id="Lms.Explore.TrackLists.template.entry">
|
||||||
|
<div class="d-flex align-items-center rounded Lms-bg-dark-hover mb-2">
|
||||||
|
<div class="p-2 flex-fill text-truncate">
|
||||||
|
<div class="d-block text-truncate">${name}</div>
|
||||||
|
</div>
|
||||||
|
<div class="p-2 d-none d-sm-block text-muted">
|
||||||
|
${duration}
|
||||||
|
</div>
|
||||||
|
<div class="p-2 d-flex">
|
||||||
|
${play-btn class="btn btn-sm btn-outline-secondary border-0"}
|
||||||
|
<div class="dropdown d-inline-block">
|
||||||
|
${more-btn data-bs-toggle="dropdown" aria-expanded="false" class="btn btn-sm btn-outline-secondary border-0"}
|
||||||
|
<ul class="dropdown-menu" aria-labelledby="${id:more-btn}">
|
||||||
|
<li>${play-shuffled class="dropdown-item"}</li>
|
||||||
|
<li>${play-last class="dropdown-item"}</li>
|
||||||
|
<li>${download class="dropdown-item"}</li>
|
||||||
|
<li>${delete class="dropdown-item"}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</message>
|
||||||
|
|
||||||
|
<message id="Lms.Explore.TrackLists.template.container">
|
||||||
|
${elements}
|
||||||
|
${loading-indicator}
|
||||||
|
</message>
|
||||||
|
|
||||||
|
</messages>
|
||||||
+5
-5
@@ -29,8 +29,8 @@
|
|||||||
${play-btn class="btn btn-primary"}
|
${play-btn class="btn btn-primary"}
|
||||||
<button type="button" class="btn btn-primary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false"/>
|
<button type="button" class="btn btn-primary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false"/>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<li>${play-last class="btn dropdown-item"}</li>
|
<li>${play-last class="dropdown-item"}</li>
|
||||||
<li>${play-shuffled class="btn dropdown-item"}</li>
|
<li>${play-shuffled class="dropdown-item"}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -54,9 +54,9 @@
|
|||||||
<div class="dropdown d-inline-block">
|
<div class="dropdown d-inline-block">
|
||||||
${more-btn data-bs-toggle="dropdown" aria-expanded="false" class="btn btn-sm btn-outline-secondary border-0"}
|
${more-btn data-bs-toggle="dropdown" aria-expanded="false" class="btn btn-sm btn-outline-secondary border-0"}
|
||||||
<ul class="dropdown-menu" aria-labelledby="${id:more-btn}">
|
<ul class="dropdown-menu" aria-labelledby="${id:more-btn}">
|
||||||
<li>${play-last class="btn dropdown-item"}</li>
|
<li>${play-last class="dropdown-item"}</li>
|
||||||
<li>${star class="btn dropdown-item"}</li>
|
<li>${star class="dropdown-item"}</li>
|
||||||
<li>${download class="btn dropdown-item"}</li>
|
<li>${download class="dropdown-item"}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -110,6 +110,13 @@ createQuery(Session& session, const Track::FindParameters& params)
|
|||||||
else if (params.release.isValid())
|
else if (params.release.isValid())
|
||||||
query.where("t.release_id = ?").bind(params.release);
|
query.where("t.release_id = ?").bind(params.release);
|
||||||
|
|
||||||
|
if (params.trackList.isValid())
|
||||||
|
{
|
||||||
|
query.join("tracklist t_l ON t_l_e.tracklist_id = t_l.id");
|
||||||
|
query.join("tracklist_entry t_l_e ON t.id = t_l_e.track_id");
|
||||||
|
query.where("t_l.id = ?").bind(params.trackList);
|
||||||
|
}
|
||||||
|
|
||||||
switch (params.sortMethod)
|
switch (params.sortMethod)
|
||||||
{
|
{
|
||||||
case TrackSortMethod::None:
|
case TrackSortMethod::None:
|
||||||
@@ -133,6 +140,9 @@ createQuery(Session& session, const Track::FindParameters& params)
|
|||||||
case TrackSortMethod::Release:
|
case TrackSortMethod::Release:
|
||||||
query.orderBy("t.disc_number,t.track_number");
|
query.orderBy("t.disc_number,t.track_number");
|
||||||
break;
|
break;
|
||||||
|
case TrackSortMethod::TrackList:
|
||||||
|
assert(params.trackList.isValid());
|
||||||
|
query.orderBy("t_l.id");
|
||||||
}
|
}
|
||||||
|
|
||||||
return query;
|
return query;
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
|
|
||||||
namespace Database {
|
namespace Database {
|
||||||
|
|
||||||
TrackList::TrackList(std::string_view name, Type type, bool isPublic, ObjectPtr<User> user)
|
TrackList::TrackList(std::string_view name, TrackListType type, bool isPublic, ObjectPtr<User> user)
|
||||||
: _name {name},
|
: _name {name},
|
||||||
_type {type},
|
_type {type},
|
||||||
_isPublic {isPublic},
|
_isPublic {isPublic},
|
||||||
@@ -44,7 +44,7 @@ TrackList::TrackList(std::string_view name, Type type, bool isPublic, ObjectPtr<
|
|||||||
}
|
}
|
||||||
|
|
||||||
TrackList::pointer
|
TrackList::pointer
|
||||||
TrackList::create(Session& session, std::string_view name, Type type, bool isPublic, ObjectPtr<User> user)
|
TrackList::create(Session& session, std::string_view name, TrackListType type, bool isPublic, ObjectPtr<User> user)
|
||||||
{
|
{
|
||||||
session.checkUniqueLocked();
|
session.checkUniqueLocked();
|
||||||
assert(user);
|
assert(user);
|
||||||
@@ -65,7 +65,7 @@ TrackList::getCount(Session& session)
|
|||||||
|
|
||||||
|
|
||||||
TrackList::pointer
|
TrackList::pointer
|
||||||
TrackList::find(Session& session, std::string_view name, Type type, UserId userId)
|
TrackList::find(Session& session, std::string_view name, TrackListType type, UserId userId)
|
||||||
{
|
{
|
||||||
session.checkSharedLocked();
|
session.checkSharedLocked();
|
||||||
assert(userId.isValid());
|
assert(userId.isValid());
|
||||||
@@ -77,28 +77,44 @@ TrackList::find(Session& session, std::string_view name, Type type, UserId userI
|
|||||||
}
|
}
|
||||||
|
|
||||||
RangeResults<TrackListId>
|
RangeResults<TrackListId>
|
||||||
TrackList::find(Session& session, UserId userId, Range range)
|
TrackList::find(Session& session, const FindParameters& params)
|
||||||
{
|
{
|
||||||
session.checkSharedLocked();
|
session.checkSharedLocked();
|
||||||
|
|
||||||
auto query {session.getDboSession().query<TrackListId>("SELECT id FROM tracklist")
|
auto query {session.getDboSession().query<TrackListId>("SELECT DISTINCT t_l.id FROM tracklist t_l")};
|
||||||
.where("user_id = ?").bind(userId)
|
|
||||||
.orderBy("name COLLATE NOCASE")};
|
|
||||||
|
|
||||||
return execQuery(query, range);
|
if (params.user.isValid())
|
||||||
|
query.where("t_l.user_id = ?").bind(params.user);
|
||||||
|
|
||||||
|
if (params.type)
|
||||||
|
query.where("t_l.type = ?").bind(*params.type);
|
||||||
|
|
||||||
|
if (!params.clusters.empty())
|
||||||
|
{
|
||||||
|
query.join("tracklist_entry t_l_e ON t_l_e.tracklist_id = t_l.id");
|
||||||
|
query.join("track t ON t.id = t_l_e.track_id");
|
||||||
|
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss << "t.id IN (SELECT DISTINCT t.id FROM track t"
|
||||||
|
" INNER JOIN track_cluster t_c ON t_c.track_id = t.id"
|
||||||
|
" INNER JOIN cluster c ON c.id = t_c.cluster_id";
|
||||||
|
|
||||||
|
WhereClause clusterClause;
|
||||||
|
for (const ClusterId clusterId : params.clusters)
|
||||||
|
{
|
||||||
|
clusterClause.Or(WhereClause("c.id = ?"));
|
||||||
|
query.bind(clusterId);
|
||||||
}
|
}
|
||||||
|
|
||||||
RangeResults<TrackListId>
|
oss << " " << clusterClause.get();
|
||||||
TrackList::find(Session& session, UserId userId, Type type, Range range)
|
oss << " GROUP BY t.id HAVING COUNT(*) = " << params.clusters.size() << ")";
|
||||||
{
|
|
||||||
session.checkSharedLocked();
|
|
||||||
|
|
||||||
auto query {session.getDboSession().query<TrackListId>("SELECT id FROM tracklist")
|
query.where(oss.str());
|
||||||
.where("user_id = ?").bind(userId)
|
}
|
||||||
.where("type = ?").bind(type)
|
|
||||||
.orderBy("name COLLATE NOCASE")};
|
|
||||||
|
|
||||||
return execQuery(query, range);
|
query.orderBy("t_l.name COLLATE NOCASE");
|
||||||
|
|
||||||
|
return execQuery(query, params.range);
|
||||||
}
|
}
|
||||||
|
|
||||||
TrackList::pointer
|
TrackList::pointer
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
#include "services/database/Object.hpp"
|
#include "services/database/Object.hpp"
|
||||||
#include "services/database/ReleaseId.hpp"
|
#include "services/database/ReleaseId.hpp"
|
||||||
#include "services/database/TrackId.hpp"
|
#include "services/database/TrackId.hpp"
|
||||||
|
#include "services/database/TrackListId.hpp"
|
||||||
#include "services/database/Types.hpp"
|
#include "services/database/Types.hpp"
|
||||||
#include "services/database/UserId.hpp"
|
#include "services/database/UserId.hpp"
|
||||||
|
|
||||||
@@ -69,6 +70,7 @@ class Track : public Object<Track, TrackId>
|
|||||||
EnumSet<TrackArtistLinkType> trackArtistLinkTypes; // and for these link types
|
EnumSet<TrackArtistLinkType> trackArtistLinkTypes; // and for these link types
|
||||||
bool nonRelease {}; // only tracks that do not belong to a release
|
bool nonRelease {}; // only tracks that do not belong to a release
|
||||||
ReleaseId release; // matching this release
|
ReleaseId release; // matching this release
|
||||||
|
TrackListId trackList; // matching this trackList
|
||||||
|
|
||||||
FindParameters& setClusters(const std::vector<ClusterId>& _clusters) { clusters = _clusters; return *this; }
|
FindParameters& setClusters(const std::vector<ClusterId>& _clusters) { clusters = _clusters; return *this; }
|
||||||
FindParameters& setKeywords(const std::vector<std::string_view>& _keywords) { keywords = _keywords; return *this; }
|
FindParameters& setKeywords(const std::vector<std::string_view>& _keywords) { keywords = _keywords; return *this; }
|
||||||
@@ -79,6 +81,7 @@ class Track : public Object<Track, TrackId>
|
|||||||
FindParameters& setArtist(ArtistId _artist, EnumSet<TrackArtistLinkType> _trackArtistLinkTypes = {}) { artist = _artist; trackArtistLinkTypes = _trackArtistLinkTypes; return *this; }
|
FindParameters& setArtist(ArtistId _artist, EnumSet<TrackArtistLinkType> _trackArtistLinkTypes = {}) { artist = _artist; trackArtistLinkTypes = _trackArtistLinkTypes; return *this; }
|
||||||
FindParameters& setNonRelease(bool _nonRelease) { nonRelease = _nonRelease; return *this; }
|
FindParameters& setNonRelease(bool _nonRelease) { nonRelease = _nonRelease; return *this; }
|
||||||
FindParameters& setRelease(ReleaseId _release) { release = _release; return *this; }
|
FindParameters& setRelease(ReleaseId _release) { release = _release; return *this; }
|
||||||
|
FindParameters& setTrackList(TrackListId _trackList) { trackList = _trackList; return *this; }
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PathResult
|
struct PathResult
|
||||||
|
|||||||
@@ -47,14 +47,8 @@ class User;
|
|||||||
class TrackList : public Object<TrackList, TrackListId>
|
class TrackList : public Object<TrackList, TrackListId>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
enum class Type
|
|
||||||
{
|
|
||||||
Playlist, // user controlled playlists
|
|
||||||
Internal, // internal usage (current playqueue, history, ...)
|
|
||||||
};
|
|
||||||
|
|
||||||
TrackList() = default;
|
TrackList() = default;
|
||||||
TrackList(std::string_view name, Type type, bool isPublic, ObjectPtr<User> user);
|
TrackList(std::string_view name, TrackListType type, bool isPublic, ObjectPtr<User> user);
|
||||||
|
|
||||||
// Stats utility
|
// Stats utility
|
||||||
std::vector<ObjectPtr<Artist>> getTopArtists(const std::vector<ClusterId>& clusterIds, std::optional<TrackArtistLinkType> linkType, std::optional<Range> range, bool& moreResults) const;
|
std::vector<ObjectPtr<Artist>> getTopArtists(const std::vector<ClusterId>& clusterIds, std::optional<TrackArtistLinkType> linkType, std::optional<Range> range, bool& moreResults) const;
|
||||||
@@ -62,19 +56,30 @@ class TrackList : public Object<TrackList, TrackListId>
|
|||||||
std::vector<ObjectPtr<Track>> getTopTracks(const std::vector<ClusterId>& clusterIds, std::optional<Range> range, bool& moreResults) const;
|
std::vector<ObjectPtr<Track>> getTopTracks(const std::vector<ClusterId>& clusterIds, std::optional<Range> range, bool& moreResults) const;
|
||||||
|
|
||||||
// Search utility
|
// Search utility
|
||||||
|
struct FindParameters
|
||||||
|
{
|
||||||
|
std::vector<ClusterId> clusters; // if non empty, tracklists that have tracks that belong to these clusters
|
||||||
|
Range range;
|
||||||
|
std::optional<TrackListType> type;
|
||||||
|
UserId user; // only tracklists owned by this user
|
||||||
|
|
||||||
|
FindParameters& setClusters(const std::vector<ClusterId>& _clusters) { clusters = _clusters; return *this; }
|
||||||
|
FindParameters& setRange(Range _range) { range = _range; return *this; }
|
||||||
|
FindParameters& setType(TrackListType _type) { type = _type; return *this; }
|
||||||
|
FindParameters& setUser(UserId _user) { user = _user; return *this; }
|
||||||
|
};
|
||||||
static std::size_t getCount(Session& session);
|
static std::size_t getCount(Session& session);
|
||||||
static pointer find(Session& session, std::string_view name, Type type, UserId userId);
|
static pointer find(Session& session, std::string_view name, TrackListType type, UserId userId);
|
||||||
static pointer find(Session& session, TrackListId tracklistId);
|
static pointer find(Session& session, TrackListId tracklistId);
|
||||||
static RangeResults<TrackListId> find(Session& session, UserId userId, Range range);
|
static RangeResults<TrackListId> find(Session& session, const FindParameters& params);
|
||||||
static RangeResults<TrackListId> find(Session& session, UserId userId, Type type, Range range);
|
|
||||||
|
|
||||||
// Create utility
|
// Create utility
|
||||||
static pointer create(Session& session, std::string_view name, Type type, bool isPublic, ObjectPtr<User> user);
|
static pointer create(Session& session, std::string_view name, TrackListType type, bool isPublic, ObjectPtr<User> user);
|
||||||
|
|
||||||
// Accessors
|
// Accessors
|
||||||
std::string_view getName() const { return _name; }
|
std::string_view getName() const { return _name; }
|
||||||
bool isPublic() const { return _isPublic; }
|
bool isPublic() const { return _isPublic; }
|
||||||
Type getType() const { return _type; }
|
TrackListType getType() const { return _type; }
|
||||||
ObjectPtr<User> getUser() const { return _user; }
|
ObjectPtr<User> getUser() const { return _user; }
|
||||||
|
|
||||||
// Modifiers
|
// Modifiers
|
||||||
@@ -122,7 +127,7 @@ class TrackList : public Object<TrackList, TrackListId>
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
std::string _name;
|
std::string _name;
|
||||||
Type _type {Type::Playlist};
|
TrackListType _type {TrackListType::Playlist};
|
||||||
bool _isPublic {false};
|
bool _isPublic {false};
|
||||||
|
|
||||||
Wt::Dbo::ptr<User> _user;
|
Wt::Dbo::ptr<User> _user;
|
||||||
|
|||||||
@@ -113,6 +113,7 @@ namespace Database
|
|||||||
Name,
|
Name,
|
||||||
DateDescAndRelease,
|
DateDescAndRelease,
|
||||||
Release, // order by disc/track number
|
Release, // order by disc/track number
|
||||||
|
TrackList, // order by asc order in tracklist
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class TrackArtistLinkType
|
enum class TrackArtistLinkType
|
||||||
@@ -177,5 +178,11 @@ namespace Database
|
|||||||
ReleaseArtists = 1,
|
ReleaseArtists = 1,
|
||||||
TrackArtists = 2,
|
TrackArtists = 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class TrackListType
|
||||||
|
{
|
||||||
|
Playlist, // user controlled playlists
|
||||||
|
Internal, // internal usage (current playqueue, history, ...)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ TEST_F(DatabaseFixture, Cluster_multiTracksMultipleClustersTopRelease)
|
|||||||
ScopedRelease releaseC {session, "ReleaseC"};
|
ScopedRelease releaseC {session, "ReleaseC"};
|
||||||
|
|
||||||
ScopedUser user {session, "MyUser"};
|
ScopedUser user {session, "MyUser"};
|
||||||
ScopedTrackList trackList {session, "TrackList", TrackList::Type::Playlist, false, user.lockAndGet()};
|
ScopedTrackList trackList {session, "TrackList", TrackListType::Playlist, false, user.lockAndGet()};
|
||||||
|
|
||||||
{
|
{
|
||||||
auto transaction {session.createSharedTransaction()};
|
auto transaction {session.createSharedTransaction()};
|
||||||
@@ -618,7 +618,7 @@ TEST_F(DatabaseFixture, SingleTrackSingleReleaseSingleArtistMultiClusters)
|
|||||||
TEST_F(DatabaseFixture, SingleTrackListMultipleTrackSingleCluster)
|
TEST_F(DatabaseFixture, SingleTrackListMultipleTrackSingleCluster)
|
||||||
{
|
{
|
||||||
ScopedUser user {session, "MyUser"};
|
ScopedUser user {session, "MyUser"};
|
||||||
ScopedTrackList trackList {session, "MyTrackList", TrackList::Type::Playlist, false, user.lockAndGet()};
|
ScopedTrackList trackList {session, "MyTrackList", TrackListType::Playlist, false, user.lockAndGet()};
|
||||||
ScopedClusterType clusterType {session, "MyClusterType"};
|
ScopedClusterType clusterType {session, "MyClusterType"};
|
||||||
ScopedCluster cluster {session, clusterType.lockAndGet(), "MyCluster"};
|
ScopedCluster cluster {session, clusterType.lockAndGet(), "MyCluster"};
|
||||||
std::list<ScopedTrack> tracks;
|
std::list<ScopedTrack> tracks;
|
||||||
@@ -650,7 +650,7 @@ TEST_F(DatabaseFixture, SingleTrackListMultipleTrackSingleCluster)
|
|||||||
TEST_F(DatabaseFixture, SingleTrackListMultipleTrackMultiClusters)
|
TEST_F(DatabaseFixture, SingleTrackListMultipleTrackMultiClusters)
|
||||||
{
|
{
|
||||||
ScopedUser user {session, "MyUser"};
|
ScopedUser user {session, "MyUser"};
|
||||||
ScopedTrackList trackList {session, "MyTrackList", TrackList::Type::Playlist, false, user.lockAndGet()};
|
ScopedTrackList trackList {session, "MyTrackList", TrackListType::Playlist, false, user.lockAndGet()};
|
||||||
ScopedClusterType clusterType {session, "MyClusterType"};
|
ScopedClusterType clusterType {session, "MyClusterType"};
|
||||||
ScopedCluster cluster1 {session, clusterType.lockAndGet(), "MyCluster1"};
|
ScopedCluster cluster1 {session, clusterType.lockAndGet(), "MyCluster1"};
|
||||||
ScopedCluster cluster2 {session, clusterType.lockAndGet(), "MyCluster2"};
|
ScopedCluster cluster2 {session, clusterType.lockAndGet(), "MyCluster2"};
|
||||||
@@ -702,7 +702,7 @@ TEST_F(DatabaseFixture, SingleTrackListMultipleTrackMultiClusters)
|
|||||||
TEST_F(DatabaseFixture, SingleTrackListMultipleTrackMultiClustersRecentlyPlayed)
|
TEST_F(DatabaseFixture, SingleTrackListMultipleTrackMultiClustersRecentlyPlayed)
|
||||||
{
|
{
|
||||||
ScopedUser user {session, "MyUser"};
|
ScopedUser user {session, "MyUser"};
|
||||||
ScopedTrackList trackList {session, "MyTrackList", TrackList::Type::Playlist, false, user.lockAndGet()};
|
ScopedTrackList trackList {session, "MyTrackList", TrackListType::Playlist, false, user.lockAndGet()};
|
||||||
ScopedClusterType clusterType {session, "MyClusterType"};
|
ScopedClusterType clusterType {session, "MyClusterType"};
|
||||||
ScopedCluster cluster1 {session, clusterType.lockAndGet(), "MyCluster1"};
|
ScopedCluster cluster1 {session, clusterType.lockAndGet(), "MyCluster1"};
|
||||||
ScopedCluster cluster2 {session, clusterType.lockAndGet(), "MyCluster2"};
|
ScopedCluster cluster2 {session, clusterType.lockAndGet(), "MyCluster2"};
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ TEST_F(DatabaseFixture, SingleTrackList)
|
|||||||
EXPECT_EQ(TrackList::getCount(session), 0);
|
EXPECT_EQ(TrackList::getCount(session), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
ScopedTrackList trackList {session, "MytrackList", TrackList::Type::Playlist, false, user.lockAndGet()};
|
ScopedTrackList trackList {session, "MytrackList", TrackListType::Playlist, false, user.lockAndGet()};
|
||||||
|
|
||||||
{
|
{
|
||||||
auto transaction {session.createSharedTransaction()};
|
auto transaction {session.createSharedTransaction()};
|
||||||
@@ -39,10 +39,45 @@ TEST_F(DatabaseFixture, SingleTrackList)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(DatabaseFixture, SingleTrackListSingleTrack)
|
||||||
|
{
|
||||||
|
ScopedUser user {session, "MyUser"};
|
||||||
|
ScopedTrackList trackList1 {session, "MyTrackList1", TrackListType::Playlist, false, user.lockAndGet()};
|
||||||
|
ScopedTrackList trackList2 {session, "MyTrackList2", TrackListType::Playlist, false, user.lockAndGet()};
|
||||||
|
ScopedTrack track {session, "MyTrack"};
|
||||||
|
|
||||||
|
{
|
||||||
|
auto transaction {session.createSharedTransaction()};
|
||||||
|
|
||||||
|
auto tracks {Track::find(session, Track::FindParameters {}.setTrackList(trackList1.getId()))};
|
||||||
|
EXPECT_EQ(tracks.results.size(), 0);
|
||||||
|
|
||||||
|
tracks = Track::find(session, Track::FindParameters {}.setTrackList(trackList2.getId()));
|
||||||
|
EXPECT_EQ(tracks.results.size(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto transaction {session.createUniqueTransaction()};
|
||||||
|
|
||||||
|
TrackListEntry::create(session, track.get(), trackList1.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto transaction {session.createSharedTransaction()};
|
||||||
|
|
||||||
|
auto tracks {Track::find(session, Track::FindParameters {}.setTrackList(trackList1.getId()))};
|
||||||
|
ASSERT_EQ(tracks.results.size(), 1);
|
||||||
|
EXPECT_EQ(tracks.results.front(), track.getId());
|
||||||
|
|
||||||
|
tracks = Track::find(session, Track::FindParameters {}.setTrackList(trackList2.getId()));
|
||||||
|
EXPECT_EQ(tracks.results.size(), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(DatabaseFixture, SingleTrackListMultipleTrack)
|
TEST_F(DatabaseFixture, SingleTrackListMultipleTrack)
|
||||||
{
|
{
|
||||||
ScopedUser user {session, "MyUser"};
|
ScopedUser user {session, "MyUser"};
|
||||||
ScopedTrackList trackList {session, "MytrackList", TrackList::Type::Playlist, false, user.lockAndGet()};
|
ScopedTrackList trackList {session, "MytrackList", TrackListType::Playlist, false, user.lockAndGet()};
|
||||||
std::list<ScopedTrack> tracks;
|
std::list<ScopedTrack> tracks;
|
||||||
|
|
||||||
for (std::size_t i {}; i < 10; ++i)
|
for (std::size_t i {}; i < 10; ++i)
|
||||||
@@ -84,7 +119,7 @@ TEST_F(DatabaseFixture, SingleTrackListMultipleTrack)
|
|||||||
TEST_F(DatabaseFixture, SingleTrackListMultipleTrackDateTime)
|
TEST_F(DatabaseFixture, SingleTrackListMultipleTrackDateTime)
|
||||||
{
|
{
|
||||||
ScopedUser user {session, "MyUser"};
|
ScopedUser user {session, "MyUser"};
|
||||||
ScopedTrackList trackList {session, "MytrackList", TrackList::Type::Playlist, false, user.lockAndGet()};
|
ScopedTrackList trackList {session, "MytrackList", TrackListType::Playlist, false, user.lockAndGet()};
|
||||||
ScopedTrack track1 {session, "MyTrack1"};
|
ScopedTrack track1 {session, "MyTrack1"};
|
||||||
ScopedTrack track2 {session, "MyTrack2"};
|
ScopedTrack track2 {session, "MyTrack2"};
|
||||||
ScopedTrack track3 {session, "MyTrack3"};
|
ScopedTrack track3 {session, "MyTrack3"};
|
||||||
@@ -111,7 +146,7 @@ TEST_F(DatabaseFixture, SingleTrackListMultipleTrackDateTime)
|
|||||||
TEST_F(DatabaseFixture, SingleTrackListMultipleTrackRecentlyPlayed)
|
TEST_F(DatabaseFixture, SingleTrackListMultipleTrackRecentlyPlayed)
|
||||||
{
|
{
|
||||||
ScopedUser user {session, "MyUser"};
|
ScopedUser user {session, "MyUser"};
|
||||||
ScopedTrackList trackList {session, "MyTrackList", TrackList::Type::Playlist, false, user.lockAndGet()};
|
ScopedTrackList trackList {session, "MyTrackList", TrackListType::Playlist, false, user.lockAndGet()};
|
||||||
ScopedTrack track1 {session, "MyTrack1"};
|
ScopedTrack track1 {session, "MyTrack1"};
|
||||||
ScopedTrack track2 {session, "MyTrack1"};
|
ScopedTrack track2 {session, "MyTrack1"};
|
||||||
ScopedArtist artist1 {session, "MyArtist1"};
|
ScopedArtist artist1 {session, "MyArtist1"};
|
||||||
@@ -216,7 +251,7 @@ TEST_F(DatabaseFixture, SingleTrackListMultipleTrackRecentlyPlayed)
|
|||||||
TEST_F(DatabaseFixture, SingleTrackList_getArtists)
|
TEST_F(DatabaseFixture, SingleTrackList_getArtists)
|
||||||
{
|
{
|
||||||
ScopedUser user {session, "MyUser"};
|
ScopedUser user {session, "MyUser"};
|
||||||
ScopedTrackList trackList {session, "MytrackList", TrackList::Type::Playlist, false, user.lockAndGet()};
|
ScopedTrackList trackList {session, "MytrackList", TrackListType::Playlist, false, user.lockAndGet()};
|
||||||
ScopedTrack track1 {session, "MyTrack1"};
|
ScopedTrack track1 {session, "MyTrack1"};
|
||||||
ScopedTrack track2 {session, "MyTrack2"};
|
ScopedTrack track2 {session, "MyTrack2"};
|
||||||
ScopedRelease release {session, "MyRelease"};
|
ScopedRelease release {session, "MyRelease"};
|
||||||
@@ -297,7 +332,7 @@ TEST_F(DatabaseFixture, SingleTrackList_getArtists)
|
|||||||
TEST_F(DatabaseFixture, SingleTrackList_getReleases)
|
TEST_F(DatabaseFixture, SingleTrackList_getReleases)
|
||||||
{
|
{
|
||||||
ScopedUser user {session, "MyUser"};
|
ScopedUser user {session, "MyUser"};
|
||||||
ScopedTrackList trackList {session, "MytrackList", TrackList::Type::Playlist, false, user.lockAndGet()};
|
ScopedTrackList trackList {session, "MytrackList", TrackListType::Playlist, false, user.lockAndGet()};
|
||||||
ScopedTrack track {session, "MyTrack"};
|
ScopedTrack track {session, "MyTrack"};
|
||||||
ScopedRelease release {session, "MyRelease"};
|
ScopedRelease release {session, "MyRelease"};
|
||||||
|
|
||||||
@@ -332,7 +367,7 @@ TEST_F(DatabaseFixture, SingleTrackList_getReleases)
|
|||||||
TEST_F(DatabaseFixture, SingleTrackList_getTracks)
|
TEST_F(DatabaseFixture, SingleTrackList_getTracks)
|
||||||
{
|
{
|
||||||
ScopedUser user {session, "MyUser"};
|
ScopedUser user {session, "MyUser"};
|
||||||
ScopedTrackList trackList {session, "MytrackList", TrackList::Type::Playlist, false, user.lockAndGet()};
|
ScopedTrackList trackList {session, "MytrackList", TrackListType::Playlist, false, user.lockAndGet()};
|
||||||
ScopedTrack track1 {session, "MyTrack1"};
|
ScopedTrack track1 {session, "MyTrack1"};
|
||||||
ScopedTrack track2 {session, "MyTrack1"};
|
ScopedTrack track2 {session, "MyTrack1"};
|
||||||
|
|
||||||
@@ -363,3 +398,35 @@ TEST_F(DatabaseFixture, SingleTrackList_getTracks)
|
|||||||
ASSERT_EQ(tracks[1]->getId(), track2->getId());
|
ASSERT_EQ(tracks[1]->getId(), track2->getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(DatabaseFixture, SingleTrackListSingleTrackWithCluster)
|
||||||
|
{
|
||||||
|
ScopedUser user {session, "MyUser"};
|
||||||
|
ScopedTrackList trackList1 {session, "MyTrackList1", TrackListType::Playlist, false, user.lockAndGet()};
|
||||||
|
ScopedTrackList trackList2 {session, "MyTrackList2", TrackListType::Playlist, false, user.lockAndGet()};
|
||||||
|
ScopedClusterType clusterType {session, "MyClusterType"};
|
||||||
|
ScopedCluster cluster {session, clusterType.lockAndGet(), "MyCluster"};
|
||||||
|
ScopedTrack track {session, "MyTrack"};
|
||||||
|
|
||||||
|
{
|
||||||
|
auto transaction {session.createSharedTransaction()};
|
||||||
|
|
||||||
|
auto trackLists {TrackList::find(session, TrackList::FindParameters {}.setClusters({cluster.getId()}))};
|
||||||
|
EXPECT_EQ(trackLists.results.size(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto transaction {session.createUniqueTransaction()};
|
||||||
|
|
||||||
|
TrackListEntry::create(session, track.get(), trackList1.get());
|
||||||
|
cluster.get().modify()->addTrack(track.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto transaction {session.createSharedTransaction()};
|
||||||
|
|
||||||
|
auto trackLists {TrackList::find(session, TrackList::FindParameters {}.setClusters({cluster.getId()}))};
|
||||||
|
ASSERT_EQ(trackLists.results.size(), 1);
|
||||||
|
EXPECT_EQ(trackLists.results.front(), trackList1.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -553,7 +553,7 @@ handleCreatePlaylistRequest(RequestContext& context)
|
|||||||
tracklist = TrackList::find(context.dbSession, *id);
|
tracklist = TrackList::find(context.dbSession, *id);
|
||||||
if (!tracklist
|
if (!tracklist
|
||||||
|| tracklist->getUser() != user
|
|| tracklist->getUser() != user
|
||||||
|| tracklist->getType() != TrackList::Type::Playlist)
|
|| tracklist->getType() != TrackListType::Playlist)
|
||||||
{
|
{
|
||||||
throw RequestedDataNotFoundError {};
|
throw RequestedDataNotFoundError {};
|
||||||
}
|
}
|
||||||
@@ -563,7 +563,7 @@ handleCreatePlaylistRequest(RequestContext& context)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
tracklist = TrackList::create(context.dbSession, *name, TrackList::Type::Playlist, false, user);
|
tracklist = TrackList::create(context.dbSession, *name, TrackListType::Playlist, false, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const TrackId trackId : trackIds)
|
for (const TrackId trackId : trackIds)
|
||||||
@@ -644,7 +644,7 @@ handleDeletePlaylistRequest(RequestContext& context)
|
|||||||
TrackList::pointer tracklist {TrackList::find(context.dbSession, id)};
|
TrackList::pointer tracklist {TrackList::find(context.dbSession, id)};
|
||||||
if (!tracklist
|
if (!tracklist
|
||||||
|| tracklist->getUser() != user
|
|| tracklist->getUser() != user
|
||||||
|| tracklist->getType() != TrackList::Type::Playlist)
|
|| tracklist->getType() != TrackListType::Playlist)
|
||||||
{
|
{
|
||||||
throw RequestedDataNotFoundError {};
|
throw RequestedDataNotFoundError {};
|
||||||
}
|
}
|
||||||
@@ -1321,7 +1321,11 @@ handleGetPlaylistsRequest(RequestContext& context)
|
|||||||
Response response {Response::createOkResponse(context.serverProtocolVersion)};
|
Response response {Response::createOkResponse(context.serverProtocolVersion)};
|
||||||
Response::Node& playlistsNode {response.createNode("playlists")};
|
Response::Node& playlistsNode {response.createNode("playlists")};
|
||||||
|
|
||||||
auto tracklistIds {TrackList::find(context.dbSession, context.userId, TrackList::Type::Playlist, Range {})};
|
TrackList::FindParameters params;
|
||||||
|
params.setUser(context.userId);
|
||||||
|
params.setType(TrackListType::Playlist);
|
||||||
|
|
||||||
|
auto tracklistIds {TrackList::find(context.dbSession, params)};
|
||||||
for (const TrackListId trackListId : tracklistIds.results)
|
for (const TrackListId trackListId : tracklistIds.results)
|
||||||
{
|
{
|
||||||
const TrackList::pointer trackList {TrackList::find(context.dbSession, trackListId)};
|
const TrackList::pointer trackList {TrackList::find(context.dbSession, trackListId)};
|
||||||
@@ -1665,7 +1669,7 @@ handleUpdatePlaylistRequest(RequestContext& context)
|
|||||||
TrackList::pointer tracklist {TrackList::find(context.dbSession, id)};
|
TrackList::pointer tracklist {TrackList::find(context.dbSession, id)};
|
||||||
if (!tracklist
|
if (!tracklist
|
||||||
|| tracklist->getUser() != user
|
|| tracklist->getUser() != user
|
||||||
|| tracklist->getType() != TrackList::Type::Playlist)
|
|| tracklist->getType() != TrackListType::Playlist)
|
||||||
{
|
{
|
||||||
throw RequestedDataNotFoundError {};
|
throw RequestedDataNotFoundError {};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ bufferToString(const std::vector<unsigned char>& data)
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::string
|
std::string
|
||||||
replaceInString(const std::string& str, const std::string& from, const std::string& to)
|
replaceInString(std::string_view str, const std::string& from, const std::string& to)
|
||||||
{
|
{
|
||||||
std::string res {str};
|
std::string res {str};
|
||||||
size_t pos = 0;
|
size_t pos = 0;
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ readAs(std::string_view str);
|
|||||||
|
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
std::string
|
std::string
|
||||||
replaceInString(const std::string& str, const std::string& from, const std::string& to);
|
replaceInString(std::string_view str, const std::string& from, const std::string& to);
|
||||||
|
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
std::string
|
std::string
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ add_executable(lms
|
|||||||
ui/explore/SearchView.cpp
|
ui/explore/SearchView.cpp
|
||||||
ui/explore/TrackCollector.cpp
|
ui/explore/TrackCollector.cpp
|
||||||
ui/explore/TrackListHelpers.cpp
|
ui/explore/TrackListHelpers.cpp
|
||||||
|
ui/explore/TrackListsView.cpp
|
||||||
ui/explore/TracksView.cpp
|
ui/explore/TracksView.cpp
|
||||||
ui/resource/AudioFileResource.cpp
|
ui/resource/AudioFileResource.cpp
|
||||||
ui/resource/AudioTranscodeResource.cpp
|
ui/resource/AudioTranscodeResource.cpp
|
||||||
|
|||||||
@@ -191,6 +191,7 @@ LmsApplication::init()
|
|||||||
messageResourceBundle().use(appRoot() + "releases");
|
messageResourceBundle().use(appRoot() + "releases");
|
||||||
messageResourceBundle().use(appRoot() + "search");
|
messageResourceBundle().use(appRoot() + "search");
|
||||||
messageResourceBundle().use(appRoot() + "settings");
|
messageResourceBundle().use(appRoot() + "settings");
|
||||||
|
messageResourceBundle().use(appRoot() + "tracklists");
|
||||||
messageResourceBundle().use(appRoot() + "tracks");
|
messageResourceBundle().use(appRoot() + "tracks");
|
||||||
|
|
||||||
// Handle Media Scanner events and other session events
|
// Handle Media Scanner events and other session events
|
||||||
@@ -348,6 +349,7 @@ handlePathChange(Wt::WStackedWidget& stack, bool isAdmin)
|
|||||||
{ "/release", IdxExplore, false },
|
{ "/release", IdxExplore, false },
|
||||||
{ "/search", IdxExplore, false },
|
{ "/search", IdxExplore, false },
|
||||||
{ "/tracks", IdxExplore, false },
|
{ "/tracks", IdxExplore, false },
|
||||||
|
{ "/tracklists", IdxExplore, false },
|
||||||
{ "/playqueue", IdxPlayQueue, false },
|
{ "/playqueue", IdxPlayQueue, false },
|
||||||
{ "/settings", IdxSettings, false },
|
{ "/settings", IdxSettings, false },
|
||||||
{ "/admin/database", IdxAdminDatabase, true },
|
{ "/admin/database", IdxAdminDatabase, true },
|
||||||
@@ -432,9 +434,9 @@ LmsApplication::createHome()
|
|||||||
navbar->bindNew<Wt::WAnchor>("artists", Wt::WLink {Wt::LinkType::InternalPath, "/artists"}, Wt::WString::tr("Lms.Explore.artists"));
|
navbar->bindNew<Wt::WAnchor>("artists", Wt::WLink {Wt::LinkType::InternalPath, "/artists"}, Wt::WString::tr("Lms.Explore.artists"));
|
||||||
navbar->bindNew<Wt::WAnchor>("releases", Wt::WLink {Wt::LinkType::InternalPath, "/releases"}, Wt::WString::tr("Lms.Explore.releases"));
|
navbar->bindNew<Wt::WAnchor>("releases", Wt::WLink {Wt::LinkType::InternalPath, "/releases"}, Wt::WString::tr("Lms.Explore.releases"));
|
||||||
navbar->bindNew<Wt::WAnchor>("tracks", Wt::WLink {Wt::LinkType::InternalPath, "/tracks"}, Wt::WString::tr("Lms.Explore.tracks"));
|
navbar->bindNew<Wt::WAnchor>("tracks", Wt::WLink {Wt::LinkType::InternalPath, "/tracks"}, Wt::WString::tr("Lms.Explore.tracks"));
|
||||||
|
navbar->bindNew<Wt::WAnchor>("tracklists", Wt::WLink {Wt::LinkType::InternalPath, "/tracklists"}, Wt::WString::tr("Lms.Explore.tracklists"));
|
||||||
|
|
||||||
Filters* filters {navbar->bindNew<Filters>("filters")};
|
Filters* filters {navbar->bindNew<Filters>("filters")};
|
||||||
navbar->bindNew<Wt::WAnchor>("playqueue", Wt::WLink {Wt::LinkType::InternalPath, "/playqueue"}, Wt::WString::tr("Lms.PlayQueue.playqueue"));
|
|
||||||
navbar->bindString("username", getUserLoginName(), Wt::TextFormat::Plain);
|
navbar->bindString("username", getUserLoginName(), Wt::TextFormat::Plain);
|
||||||
navbar->bindNew<Wt::WAnchor>("settings", Wt::WLink {Wt::LinkType::InternalPath, "/settings"}, Wt::WString::tr("Lms.Settings.menu-settings"));
|
navbar->bindNew<Wt::WAnchor>("settings", Wt::WLink {Wt::LinkType::InternalPath, "/settings"}, Wt::WString::tr("Lms.Settings.menu-settings"));
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
#include <Wt/Json/Object.h>
|
#include <Wt/Json/Object.h>
|
||||||
#include <Wt/Json/Value.h>
|
#include <Wt/Json/Value.h>
|
||||||
#include <Wt/Json/Serializer.h>
|
#include <Wt/Json/Serializer.h>
|
||||||
|
#include <Wt/WPushButton.h>
|
||||||
|
|
||||||
#include "utils/Logger.hpp"
|
#include "utils/Logger.hpp"
|
||||||
|
|
||||||
@@ -209,6 +210,12 @@ MediaPlayer::MediaPlayer()
|
|||||||
_artist = bindNew<Wt::WAnchor>("artist");
|
_artist = bindNew<Wt::WAnchor>("artist");
|
||||||
_release = bindNew<Wt::WAnchor>("release");
|
_release = bindNew<Wt::WAnchor>("release");
|
||||||
|
|
||||||
|
{
|
||||||
|
Wt::WPushButton* playQueueBtn {bindNew<Wt::WPushButton>("playqueue-btn", Wt::WString::tr("Lms.MediaPlayer.template.playqueue-btn"), Wt::TextFormat::XHTML)};
|
||||||
|
playQueueBtn->setLink(Wt::WLink {Wt::LinkType::InternalPath, "/playqueue"});
|
||||||
|
playQueueBtn->setToolTip(tr("Lms.PlayQueue.playqueue"));
|
||||||
|
}
|
||||||
|
|
||||||
_settingsLoaded.connect([this](const std::string& settings)
|
_settingsLoaded.connect([this](const std::string& settings)
|
||||||
{
|
{
|
||||||
LMS_LOG(UI, DEBUG) << "Settings loaded! '" << settings << "'";
|
LMS_LOG(UI, DEBUG) << "Settings loaded! '" << settings << "'";
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ PlayQueue::PlayQueue()
|
|||||||
addFunction("id", &Wt::WTemplate::Functions::id);
|
addFunction("id", &Wt::WTemplate::Functions::id);
|
||||||
addFunction("tr", &Wt::WTemplate::Functions::tr);
|
addFunction("tr", &Wt::WTemplate::Functions::tr);
|
||||||
|
|
||||||
Wt::WText* clearBtn = bindNew<Wt::WText>("clear-btn", Wt::WString::tr("Lms.PlayQueue.template.clear-btn"), Wt::TextFormat::XHTML);
|
Wt::WPushButton* clearBtn {bindNew<Wt::WPushButton>("clear-btn", Wt::WString::tr("Lms.PlayQueue.template.clear-btn"), Wt::TextFormat::XHTML)};
|
||||||
clearBtn->clicked().connect([=]
|
clearBtn->clicked().connect([=]
|
||||||
{
|
{
|
||||||
clearTracks();
|
clearTracks();
|
||||||
@@ -62,7 +62,7 @@ PlayQueue::PlayQueue()
|
|||||||
updateCurrentTrack(true);
|
updateCurrentTrack(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
Wt::WText* shuffleBtn = bindNew<Wt::WText>("shuffle-btn", Wt::WString::tr("Lms.PlayQueue.template.shuffle-btn"), Wt::TextFormat::XHTML);
|
Wt::WPushButton* shuffleBtn {bindNew<Wt::WPushButton>("shuffle-btn", Wt::WString::tr("Lms.PlayQueue.template.shuffle-btn"), Wt::TextFormat::XHTML)};
|
||||||
shuffleBtn->clicked().connect([=]
|
shuffleBtn->clicked().connect([=]
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
@@ -148,14 +148,14 @@ PlayQueue::PlayQueue()
|
|||||||
});
|
});
|
||||||
|
|
||||||
static const std::string queuedListName {"__queued_tracks__"};
|
static const std::string queuedListName {"__queued_tracks__"};
|
||||||
trackList = Database::TrackList::find(LmsApp->getDbSession(), queuedListName, Database::TrackList::Type::Internal, LmsApp->getUserId());
|
trackList = Database::TrackList::find(LmsApp->getDbSession(), queuedListName, Database::TrackListType::Internal, LmsApp->getUserId());
|
||||||
if (!trackList)
|
if (!trackList)
|
||||||
trackList = Database::TrackList::create(LmsApp->getDbSession(), queuedListName, Database::TrackList::Type::Internal, false, LmsApp->getUser());
|
trackList = Database::TrackList::create(LmsApp->getDbSession(), queuedListName, Database::TrackListType::Internal, false, LmsApp->getUser());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
static const std::string currentPlayQueueName {"__current__playqueue__"};
|
static const std::string currentPlayQueueName {"__current__playqueue__"};
|
||||||
trackList = Database::TrackList::create(LmsApp->getDbSession(), currentPlayQueueName, Database::TrackList::Type::Internal, false, LmsApp->getUser());
|
trackList = Database::TrackList::create(LmsApp->getDbSession(), currentPlayQueueName, Database::TrackListType::Internal, false, LmsApp->getUser());
|
||||||
}
|
}
|
||||||
|
|
||||||
_tracklistId = trackList->getId();
|
_tracklistId = trackList->getId();
|
||||||
@@ -420,7 +420,7 @@ PlayQueue::addEntry(const Database::TrackListEntry::pointer& tracklistEntry)
|
|||||||
|
|
||||||
entry->bindString("duration", Utils::durationToString(track->getDuration()), Wt::TextFormat::Plain);
|
entry->bindString("duration", Utils::durationToString(track->getDuration()), Wt::TextFormat::Plain);
|
||||||
|
|
||||||
Wt::WText* playBtn {entry->bindNew<Wt::WText>("play-btn", Wt::WString::tr("Lms.PlayQueue.template.play-btn"), Wt::TextFormat::XHTML)};
|
Wt::WPushButton* playBtn {entry->bindNew<Wt::WPushButton>("play-btn", Wt::WString::tr("Lms.template.play-btn"), Wt::TextFormat::XHTML)};
|
||||||
playBtn->clicked().connect([=]
|
playBtn->clicked().connect([=]
|
||||||
{
|
{
|
||||||
const std::optional<std::size_t> pos {_entriesContainer->getIndexOf(*entry)};
|
const std::optional<std::size_t> pos {_entriesContainer->getIndexOf(*entry)};
|
||||||
@@ -428,7 +428,8 @@ PlayQueue::addEntry(const Database::TrackListEntry::pointer& tracklistEntry)
|
|||||||
loadTrack(*pos, true);
|
loadTrack(*pos, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
Wt::WText* delBtn {entry->bindNew<Wt::WText>("del-btn", Wt::WString::tr("Lms.PlayQueue.template.delete-btn"), Wt::TextFormat::XHTML)};
|
Wt::WPushButton* delBtn {entry->bindNew<Wt::WPushButton>("del-btn", Wt::WString::tr("Lms.template.delete-btn"), Wt::TextFormat::XHTML)};
|
||||||
|
delBtn->setToolTip(Wt::WString::tr("Lms.delete"));
|
||||||
delBtn->clicked().connect([=]
|
delBtn->clicked().connect([=]
|
||||||
{
|
{
|
||||||
// Remove the entry n both the widget tree and the playqueue
|
// Remove the entry n both the widget tree and the playqueue
|
||||||
@@ -451,7 +452,7 @@ PlayQueue::addEntry(const Database::TrackListEntry::pointer& tracklistEntry)
|
|||||||
updateInfo();
|
updateInfo();
|
||||||
});
|
});
|
||||||
|
|
||||||
entry->bindNew<Wt::WText>("more-btn", Wt::WString::tr("Lms.PlayQueue.template.more-btn"), Wt::TextFormat::XHTML);
|
entry->bindNew<Wt::WPushButton>("more-btn", Wt::WString::tr("Lms.template.more-btn"), Wt::TextFormat::XHTML);
|
||||||
|
|
||||||
auto isStarred {[=] { return Service<Scrobbling::IScrobblingService>::get()->isStarred(LmsApp->getUserId(), trackId); }};
|
auto isStarred {[=] { return Service<Scrobbling::IScrobblingService>::get()->isStarred(LmsApp->getUserId(), trackId); }};
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
#include "services/database/ArtistId.hpp"
|
#include "services/database/ArtistId.hpp"
|
||||||
#include "services/database/ReleaseId.hpp"
|
#include "services/database/ReleaseId.hpp"
|
||||||
#include "services/database/TrackId.hpp"
|
#include "services/database/TrackId.hpp"
|
||||||
|
#include "services/database/TrackListId.hpp"
|
||||||
|
|
||||||
namespace UserInterface
|
namespace UserInterface
|
||||||
{
|
{
|
||||||
@@ -38,5 +39,6 @@ namespace UserInterface
|
|||||||
using PlayQueueActionArtistSignal = Wt::Signal<PlayQueueAction, const std::vector<Database::ArtistId>&>;
|
using PlayQueueActionArtistSignal = Wt::Signal<PlayQueueAction, const std::vector<Database::ArtistId>&>;
|
||||||
using PlayQueueActionReleaseSignal = Wt::Signal<PlayQueueAction, const std::vector<Database::ReleaseId>&>;
|
using PlayQueueActionReleaseSignal = Wt::Signal<PlayQueueAction, const std::vector<Database::ReleaseId>&>;
|
||||||
using PlayQueueActionTrackSignal = Wt::Signal<PlayQueueAction, const std::vector<Database::TrackId>&>;
|
using PlayQueueActionTrackSignal = Wt::Signal<PlayQueueAction, const std::vector<Database::TrackId>&>;
|
||||||
|
using PlayQueueActionTrackListSignal = Wt::Signal<PlayQueueAction, Database::TrackListId>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -93,20 +93,22 @@ UsersView::refreshView()
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
entry->setCondition("if-edit", true);
|
entry->setCondition("if-edit", true);
|
||||||
Wt::WPushButton* editBtn = entry->bindNew<Wt::WPushButton>("edit-btn", Wt::WString::tr("Lms.Admin.Users.edit"));
|
Wt::WPushButton* editBtn = entry->bindNew<Wt::WPushButton>("edit-btn", Wt::WString::tr("Lms.template.edit-btn"), Wt::TextFormat::XHTML);
|
||||||
|
editBtn->setToolTip(Wt::WString::tr("Lms.edit"));
|
||||||
editBtn->clicked().connect([=]()
|
editBtn->clicked().connect([=]()
|
||||||
{
|
{
|
||||||
LmsApp->setInternalPath("/admin/user/" + userId.toString(), true);
|
LmsApp->setInternalPath("/admin/user/" + userId.toString(), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
Wt::WPushButton* delBtn = entry->bindNew<Wt::WPushButton>("del-btn", Wt::WString::tr("Lms.Admin.Users.del"));
|
Wt::WPushButton* delBtn = entry->bindNew<Wt::WPushButton>("del-btn", Wt::WString::tr("Lms.template.delete-btn"), Wt::TextFormat::XHTML);
|
||||||
|
delBtn->setToolTip(Wt::WString::tr("Lms.delete"));
|
||||||
delBtn->clicked().connect([=]
|
delBtn->clicked().connect([=]
|
||||||
{
|
{
|
||||||
auto modal {std::make_unique<Wt::WTemplate>(Wt::WString::tr("Lms.Admin.Users.template.delete-user"))};
|
auto modal {std::make_unique<Wt::WTemplate>(Wt::WString::tr("Lms.Admin.Users.template.delete-user"))};
|
||||||
modal->addFunction("tr", &Wt::WTemplate::Functions::tr);
|
modal->addFunction("tr", &Wt::WTemplate::Functions::tr);
|
||||||
Wt::WWidget* modalPtr {modal.get()};
|
Wt::WWidget* modalPtr {modal.get()};
|
||||||
|
|
||||||
auto* delBtn {modal->bindNew<Wt::WPushButton>("del-btn", Wt::WString::tr("Lms.Admin.Users.del"))};
|
auto* delBtn {modal->bindNew<Wt::WPushButton>("del-btn", Wt::WString::tr("Lms.delete"))};
|
||||||
delBtn->clicked().connect([=]
|
delBtn->clicked().connect([=]
|
||||||
{
|
{
|
||||||
auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
|
auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ namespace UserInterface
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
// "text" must contain loading-indicator and "elements"
|
// "text" must contain loading-indicator and "elements"
|
||||||
InfiniteScrollingContainer(const Wt::WString& text = Wt::WString::tr("Lms.infinite-scrolling-container"));
|
InfiniteScrollingContainer(const Wt::WString& text = Wt::WString::tr("Lms.infinite-scrolling-container.template"));
|
||||||
|
|
||||||
void clear();
|
void clear();
|
||||||
std::size_t getCount();
|
std::size_t getCount();
|
||||||
|
|||||||
@@ -37,6 +37,7 @@
|
|||||||
#include "ReleasesView.hpp"
|
#include "ReleasesView.hpp"
|
||||||
#include "ReleaseView.hpp"
|
#include "ReleaseView.hpp"
|
||||||
#include "SearchView.hpp"
|
#include "SearchView.hpp"
|
||||||
|
#include "TrackListsView.hpp"
|
||||||
#include "TracksView.hpp"
|
#include "TracksView.hpp"
|
||||||
|
|
||||||
namespace UserInterface {
|
namespace UserInterface {
|
||||||
@@ -50,6 +51,7 @@ handleContentsPathChange(Wt::WStackedWidget* stack)
|
|||||||
{
|
{
|
||||||
IdxArtists = 0,
|
IdxArtists = 0,
|
||||||
IdxArtist,
|
IdxArtist,
|
||||||
|
IdxTracklists,
|
||||||
IdxReleases,
|
IdxReleases,
|
||||||
IdxRelease,
|
IdxRelease,
|
||||||
IdxSearch,
|
IdxSearch,
|
||||||
@@ -60,6 +62,7 @@ handleContentsPathChange(Wt::WStackedWidget* stack)
|
|||||||
{
|
{
|
||||||
{ "/artists", IdxArtists },
|
{ "/artists", IdxArtists },
|
||||||
{ "/artist", IdxArtist },
|
{ "/artist", IdxArtist },
|
||||||
|
{ "/tracklists", IdxTracklists },
|
||||||
{ "/releases", IdxReleases },
|
{ "/releases", IdxReleases },
|
||||||
{ "/release", IdxRelease },
|
{ "/release", IdxRelease },
|
||||||
{ "/search", IdxSearch },
|
{ "/search", IdxSearch },
|
||||||
@@ -88,6 +91,7 @@ Explore::Explore(Filters* filters)
|
|||||||
Wt::WStackedWidget* contentsStack {bindNew<Wt::WStackedWidget>("contents")};
|
Wt::WStackedWidget* contentsStack {bindNew<Wt::WStackedWidget>("contents")};
|
||||||
contentsStack->setOverflow(Wt::Overflow::Visible); // wt makes it hidden by default
|
contentsStack->setOverflow(Wt::Overflow::Visible); // wt makes it hidden by default
|
||||||
|
|
||||||
|
// same order as enum Idx
|
||||||
auto artists = std::make_unique<Artists>(*_filters);
|
auto artists = std::make_unique<Artists>(*_filters);
|
||||||
contentsStack->addWidget(std::move(artists));
|
contentsStack->addWidget(std::move(artists));
|
||||||
|
|
||||||
@@ -96,6 +100,10 @@ Explore::Explore(Filters* filters)
|
|||||||
artist->tracksAction.connect(this, &Explore::handleTracksAction);
|
artist->tracksAction.connect(this, &Explore::handleTracksAction);
|
||||||
contentsStack->addWidget(std::move(artist));
|
contentsStack->addWidget(std::move(artist));
|
||||||
|
|
||||||
|
auto tracklists {std::make_unique<TrackLists>(*_filters)};
|
||||||
|
tracklists->trackListAction.connect(this, &Explore::handleTrackListAction);
|
||||||
|
contentsStack->addWidget(std::move(tracklists));
|
||||||
|
|
||||||
auto releases = std::make_unique<Releases>(*_filters);
|
auto releases = std::make_unique<Releases>(*_filters);
|
||||||
releases->releasesAction.connect(this, &Explore::handleReleasesAction);
|
releases->releasesAction.connect(this, &Explore::handleReleasesAction);
|
||||||
contentsStack->addWidget(std::move(releases));
|
contentsStack->addWidget(std::move(releases));
|
||||||
@@ -163,7 +171,6 @@ std::vector<Database::TrackId>
|
|||||||
getReleasesTracks(Database::Session& session, const std::vector<Database::ReleaseId>& releasesId, const std::vector<Database::ClusterId>& clusters, std::size_t maxTrackCount)
|
getReleasesTracks(Database::Session& session, const std::vector<Database::ReleaseId>& releasesId, const std::vector<Database::ClusterId>& clusters, std::size_t maxTrackCount)
|
||||||
{
|
{
|
||||||
using namespace Database;
|
using namespace Database;
|
||||||
|
|
||||||
assert(maxTrackCount);
|
assert(maxTrackCount);
|
||||||
|
|
||||||
std::vector<TrackId> res;
|
std::vector<TrackId> res;
|
||||||
@@ -190,6 +197,24 @@ getReleasesTracks(Database::Session& session, const std::vector<Database::Releas
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
std::vector<Database::TrackId>
|
||||||
|
getTrackListTracks(Database::Session& session, Database::TrackListId trackListId, const std::vector<Database::ClusterId>& clusters, std::size_t maxTrackCount)
|
||||||
|
{
|
||||||
|
using namespace Database;
|
||||||
|
assert(maxTrackCount);
|
||||||
|
|
||||||
|
auto transaction {session.createSharedTransaction()};
|
||||||
|
|
||||||
|
Database::Track::FindParameters params;
|
||||||
|
params.setTrackList(trackListId);
|
||||||
|
params.setClusters(clusters);
|
||||||
|
params.setRange({0, maxTrackCount});
|
||||||
|
params.setSortMethod(TrackSortMethod::TrackList);
|
||||||
|
|
||||||
|
return Database::Track::find(session, params).results;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Explore::handleArtistsAction(PlayQueueAction action, const std::vector<Database::ArtistId>& artistsId)
|
Explore::handleArtistsAction(PlayQueueAction action, const std::vector<Database::ArtistId>& artistsId)
|
||||||
{
|
{
|
||||||
@@ -202,9 +227,16 @@ Explore::handleReleasesAction(PlayQueueAction action, const std::vector<Database
|
|||||||
tracksAction.emit(action, getReleasesTracks(LmsApp->getDbSession(), releasesId, _filters->getClusterIds(), _maxTrackCount));
|
tracksAction.emit(action, getReleasesTracks(LmsApp->getDbSession(), releasesId, _filters->getClusterIds(), _maxTrackCount));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Explore::handleTrackListAction(PlayQueueAction action, Database::TrackListId trackListId)
|
||||||
|
{
|
||||||
|
tracksAction.emit(action, getTrackListTracks(LmsApp->getDbSession(), trackListId, _filters->getClusterIds(), _maxTrackCount));
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Explore::handleTracksAction(PlayQueueAction action, const std::vector<Database::TrackId>& tracksId)
|
Explore::handleTracksAction(PlayQueueAction action, const std::vector<Database::TrackId>& tracksId)
|
||||||
{
|
{
|
||||||
|
// consider things are already filtered here, and _maxTrackCount honored playqueue side...
|
||||||
tracksAction.emit(action, tracksId);
|
tracksAction.emit(action, tracksId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ namespace UserInterface
|
|||||||
private:
|
private:
|
||||||
void handleArtistsAction(PlayQueueAction action, const std::vector<Database::ArtistId>& artistsId);
|
void handleArtistsAction(PlayQueueAction action, const std::vector<Database::ArtistId>& artistsId);
|
||||||
void handleReleasesAction(PlayQueueAction action, const std::vector<Database::ReleaseId>& releasesId);
|
void handleReleasesAction(PlayQueueAction action, const std::vector<Database::ReleaseId>& releasesId);
|
||||||
|
void handleTrackListAction(PlayQueueAction action, Database::TrackListId trackiListId);
|
||||||
void handleTracksAction(PlayQueueAction action, const std::vector<Database::TrackId>& tracksId);
|
void handleTracksAction(PlayQueueAction action, const std::vector<Database::TrackId>& tracksId);
|
||||||
|
|
||||||
Filters* _filters {};
|
Filters* _filters {};
|
||||||
|
|||||||
@@ -279,14 +279,14 @@ Release::refreshView()
|
|||||||
entry->bindInt("track-number", *trackNumber);
|
entry->bindInt("track-number", *trackNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
Wt::WPushButton* playBtn {entry->bindNew<Wt::WPushButton>("play-btn", Wt::WString::tr("Lms.Explore.template.play-btn"), Wt::TextFormat::XHTML)};
|
Wt::WPushButton* playBtn {entry->bindNew<Wt::WPushButton>("play-btn", Wt::WString::tr("Lms.template.play-btn"), Wt::TextFormat::XHTML)};
|
||||||
playBtn->clicked().connect([=]
|
playBtn->clicked().connect([=]
|
||||||
{
|
{
|
||||||
tracksAction.emit(PlayQueueAction::Play, {trackId});
|
tracksAction.emit(PlayQueueAction::Play, {trackId});
|
||||||
});
|
});
|
||||||
|
|
||||||
{
|
{
|
||||||
entry->bindNew<Wt::WPushButton>("more-btn", Wt::WString::tr("Lms.Explore.template.more-btn"), Wt::TextFormat::XHTML);
|
entry->bindNew<Wt::WPushButton>("more-btn", Wt::WString::tr("Lms.template.more-btn"), Wt::TextFormat::XHTML);
|
||||||
entry->bindNew<Wt::WPushButton>("play-last", Wt::WString::tr("Lms.Explore.play-last"))
|
entry->bindNew<Wt::WPushButton>("play-last", Wt::WString::tr("Lms.Explore.play-last"))
|
||||||
->clicked().connect([=]
|
->clicked().connect([=]
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -19,8 +19,6 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Wt/WContainerWidget.h>
|
|
||||||
|
|
||||||
#include "services/database/Types.hpp"
|
#include "services/database/Types.hpp"
|
||||||
|
|
||||||
#include "common/Template.hpp"
|
#include "common/Template.hpp"
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ namespace UserInterface
|
|||||||
_releases = bindNew<InfiniteScrollingContainer>("releases", Wt::WString::tr("Lms.Explore.Releases.template.container"));
|
_releases = bindNew<InfiniteScrollingContainer>("releases", Wt::WString::tr("Lms.Explore.Releases.template.container"));
|
||||||
_releases->onRequestElements.connect([this] { addSomeReleases(); });
|
_releases->onRequestElements.connect([this] { addSomeReleases(); });
|
||||||
|
|
||||||
_tracks = bindNew<InfiniteScrollingContainer>("tracks", Wt::WString::tr("Lms.infinite-scrolling-container"));
|
_tracks = bindNew<InfiniteScrollingContainer>("tracks");
|
||||||
_tracks->onRequestElements.connect([this] { addSomeTracks(); });
|
_tracks->onRequestElements.connect([this] { addSomeTracks(); });
|
||||||
|
|
||||||
_filters->updated().connect([=]
|
_filters->updated().connect([=]
|
||||||
|
|||||||
@@ -23,7 +23,6 @@
|
|||||||
#include <Wt/WContainerWidget.h>
|
#include <Wt/WContainerWidget.h>
|
||||||
#include <Wt/WImage.h>
|
#include <Wt/WImage.h>
|
||||||
#include <Wt/WPushButton.h>
|
#include <Wt/WPushButton.h>
|
||||||
#include <Wt/WText.h>
|
|
||||||
|
|
||||||
#include "services/database/Artist.hpp"
|
#include "services/database/Artist.hpp"
|
||||||
#include "services/database/Release.hpp"
|
#include "services/database/Release.hpp"
|
||||||
@@ -74,13 +73,13 @@ namespace UserInterface::TrackListHelpers
|
|||||||
|
|
||||||
entry->bindString("duration", Utils::durationToString(track->getDuration()), Wt::TextFormat::Plain);
|
entry->bindString("duration", Utils::durationToString(track->getDuration()), Wt::TextFormat::Plain);
|
||||||
|
|
||||||
Wt::WText* playBtn {entry->bindNew<Wt::WText>("play-btn", Wt::WString::tr("Lms.Explore.template.play-btn"), Wt::TextFormat::XHTML)};
|
Wt::WPushButton* playBtn {entry->bindNew<Wt::WPushButton>("play-btn", Wt::WString::tr("Lms.template.play-btn"), Wt::TextFormat::XHTML)};
|
||||||
playBtn->clicked().connect([trackId, &tracksAction]
|
playBtn->clicked().connect([trackId, &tracksAction]
|
||||||
{
|
{
|
||||||
tracksAction.emit(PlayQueueAction::Play, {trackId});
|
tracksAction.emit(PlayQueueAction::Play, {trackId});
|
||||||
});
|
});
|
||||||
|
|
||||||
entry->bindNew<Wt::WPushButton>("more-btn", Wt::WString::tr("Lms.Explore.template.more-btn"), Wt::TextFormat::XHTML);
|
entry->bindNew<Wt::WPushButton>("more-btn", Wt::WString::tr("Lms.template.more-btn"), Wt::TextFormat::XHTML);
|
||||||
entry->bindNew<Wt::WPushButton>("play-last", Wt::WString::tr("Lms.Explore.play-last"))
|
entry->bindNew<Wt::WPushButton>("play-last", Wt::WString::tr("Lms.Explore.play-last"))
|
||||||
->clicked().connect([=, &tracksAction]
|
->clicked().connect([=, &tracksAction]
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,133 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Emeric Poupon
|
||||||
|
*
|
||||||
|
* This file is part of LMS.
|
||||||
|
*
|
||||||
|
* LMS is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* LMS is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "TrackListsView.hpp"
|
||||||
|
|
||||||
|
#include <Wt/WPushButton.h>
|
||||||
|
|
||||||
|
#include "services/database/Session.hpp"
|
||||||
|
#include "services/database/TrackList.hpp"
|
||||||
|
|
||||||
|
#include "common/InfiniteScrollingContainer.hpp"
|
||||||
|
#include "common/Template.hpp"
|
||||||
|
#include "resource/DownloadResource.hpp"
|
||||||
|
#include "ReleaseListHelpers.hpp"
|
||||||
|
#include "Filters.hpp"
|
||||||
|
#include "LmsApplication.hpp"
|
||||||
|
#include "Utils.hpp"
|
||||||
|
|
||||||
|
using namespace Database;
|
||||||
|
|
||||||
|
namespace UserInterface
|
||||||
|
{
|
||||||
|
|
||||||
|
TrackLists::TrackLists(Filters& filters)
|
||||||
|
: Template {Wt::WString::tr("Lms.Explore.TrackLists.template")}
|
||||||
|
, _filters {filters}
|
||||||
|
{
|
||||||
|
addFunction("tr", &Wt::WTemplate::Functions::tr);
|
||||||
|
addFunction("id", &Wt::WTemplate::Functions::id);
|
||||||
|
|
||||||
|
_container = bindNew<InfiniteScrollingContainer>("tracklists", Wt::WString::tr("Lms.Explore.TrackLists.template.container"));
|
||||||
|
_container->onRequestElements.connect([this]
|
||||||
|
{
|
||||||
|
addSome();
|
||||||
|
});
|
||||||
|
|
||||||
|
_filters.updated().connect([this]
|
||||||
|
{
|
||||||
|
refreshView();
|
||||||
|
});
|
||||||
|
|
||||||
|
refreshView();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TrackLists::refreshView()
|
||||||
|
{
|
||||||
|
_container->clear();
|
||||||
|
addSome();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TrackLists::addSome()
|
||||||
|
{
|
||||||
|
const Range range {static_cast<std::size_t>(_container->getCount()), _batchSize};
|
||||||
|
|
||||||
|
Session& session {LmsApp->getDbSession()};
|
||||||
|
auto transaction {session.createSharedTransaction()};
|
||||||
|
|
||||||
|
TrackList::FindParameters params;
|
||||||
|
params.setClusters(_filters.getClusterIds());
|
||||||
|
params.setUser(LmsApp->getUserId());
|
||||||
|
params.setType(TrackListType::Playlist);
|
||||||
|
params.setRange(range);
|
||||||
|
|
||||||
|
const auto trackListIds {TrackList::find(session, params)};
|
||||||
|
for (const TrackListId trackListId : trackListIds.results)
|
||||||
|
{
|
||||||
|
if (const TrackList::pointer trackList {TrackList::find(LmsApp->getDbSession(), trackListId)})
|
||||||
|
addTracklist(trackList);
|
||||||
|
}
|
||||||
|
|
||||||
|
_container->setHasMore(trackListIds.moreResults && _container->getCount() <= _maxCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
TrackLists::addTracklist(const ObjectPtr<TrackList>& trackList)
|
||||||
|
{
|
||||||
|
const TrackListId trackListId {trackList->getId()};
|
||||||
|
Template* entry {_container->addNew<Template>(Wt::WString::tr("Lms.Explore.TrackLists.template.entry"))};
|
||||||
|
entry->addFunction("id", &Wt::WTemplate::Functions::id);
|
||||||
|
|
||||||
|
entry->bindString("name", std::string {trackList->getName()}, Wt::TextFormat::Plain);
|
||||||
|
entry->bindString("duration", Utils::durationToString(trackList->getDuration()));
|
||||||
|
|
||||||
|
entry->bindNew<Wt::WPushButton>("play-btn", Wt::WString::tr("Lms.template.play-btn"), Wt::TextFormat::XHTML)
|
||||||
|
->clicked().connect([=]
|
||||||
|
{
|
||||||
|
trackListAction.emit(PlayQueueAction::Play, trackListId);
|
||||||
|
});
|
||||||
|
|
||||||
|
entry->bindNew<Wt::WPushButton>("more-btn", Wt::WString::tr("Lms.template.more-btn"), Wt::TextFormat::XHTML);
|
||||||
|
|
||||||
|
entry->bindNew<Wt::WPushButton>("play-shuffled", Wt::WString::tr("Lms.Explore.play-shuffled"))
|
||||||
|
->clicked().connect([=]
|
||||||
|
{
|
||||||
|
trackListAction.emit(PlayQueueAction::PlayShuffled, trackListId);
|
||||||
|
});
|
||||||
|
|
||||||
|
entry->bindNew<Wt::WPushButton>("play-last", Wt::WString::tr("Lms.Explore.play-last"))
|
||||||
|
->clicked().connect([=]
|
||||||
|
{
|
||||||
|
trackListAction.emit(PlayQueueAction::PlayLast, trackListId);
|
||||||
|
});
|
||||||
|
|
||||||
|
entry->bindNew<Wt::WPushButton>("download", Wt::WString::tr("Lms.Explore.download"))
|
||||||
|
->setLink(Wt::WLink {std::make_unique<DownloadTrackListResource>(trackListId)});
|
||||||
|
|
||||||
|
entry->bindNew<Wt::WPushButton>("delete", Wt::WString::tr("Lms.delete"))
|
||||||
|
->clicked().connect([=]
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace UserInterface
|
||||||
|
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 Emeric Poupon
|
||||||
|
*
|
||||||
|
* This file is part of LMS.
|
||||||
|
*
|
||||||
|
* LMS is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* LMS is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Wt/WTemplate.h>
|
||||||
|
|
||||||
|
#include "services/database/Object.hpp"
|
||||||
|
#include "services/database/Types.hpp"
|
||||||
|
|
||||||
|
#include "common/Template.hpp"
|
||||||
|
#include "PlayQueueAction.hpp"
|
||||||
|
|
||||||
|
namespace Database
|
||||||
|
{
|
||||||
|
class TrackList;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace UserInterface
|
||||||
|
{
|
||||||
|
class Filters;
|
||||||
|
class InfiniteScrollingContainer;
|
||||||
|
|
||||||
|
class TrackLists : public Template
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TrackLists(Filters& filters);
|
||||||
|
|
||||||
|
PlayQueueActionTrackListSignal trackListAction;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void refreshView();
|
||||||
|
void addSome();
|
||||||
|
void addTracklist(const Database::ObjectPtr<Database::TrackList>& trackList);
|
||||||
|
|
||||||
|
static constexpr std::size_t _batchSize {30};
|
||||||
|
static constexpr std::size_t _maxCount {500};
|
||||||
|
|
||||||
|
Filters& _filters;
|
||||||
|
InfiniteScrollingContainer* _container {};
|
||||||
|
};
|
||||||
|
} // namespace UserInterface
|
||||||
|
|
||||||
@@ -30,6 +30,7 @@
|
|||||||
#include "services/database/Release.hpp"
|
#include "services/database/Release.hpp"
|
||||||
#include "services/database/Session.hpp"
|
#include "services/database/Session.hpp"
|
||||||
#include "services/database/Track.hpp"
|
#include "services/database/Track.hpp"
|
||||||
|
#include "services/database/TrackList.hpp"
|
||||||
#include "utils/Exception.hpp"
|
#include "utils/Exception.hpp"
|
||||||
#include "utils/Logger.hpp"
|
#include "utils/Logger.hpp"
|
||||||
#include "utils/Zipper.hpp"
|
#include "utils/Zipper.hpp"
|
||||||
@@ -59,6 +60,7 @@ DownloadResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Resp
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
zipper = createZipper();
|
zipper = createZipper();
|
||||||
|
if (zipper)
|
||||||
response.setContentLength(zipper->getTotalZipFile());
|
response.setContentLength(zipper->getTotalZipFile());
|
||||||
response.setMimeType("application/zip");
|
response.setMimeType("application/zip");
|
||||||
}
|
}
|
||||||
@@ -141,6 +143,13 @@ getTrackPathName(Database::Track::pointer track)
|
|||||||
return fileName.str();
|
return fileName.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
std::string
|
||||||
|
getTrackListPathName(Database::TrackList::pointer trackList)
|
||||||
|
{
|
||||||
|
return StringUtils::replaceInString(trackList->getName(), "/", "_");
|
||||||
|
}
|
||||||
|
|
||||||
static
|
static
|
||||||
std::unique_ptr<Zip::Zipper>
|
std::unique_ptr<Zip::Zipper>
|
||||||
createZipper(const std::vector<Database::Track::pointer>& tracks)
|
createZipper(const std::vector<Database::Track::pointer>& tracks)
|
||||||
@@ -250,4 +259,31 @@ DownloadTrackResource::createZipper()
|
|||||||
return UserInterface::createZipper({track});
|
return UserInterface::createZipper({track});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DownloadTrackListResource::DownloadTrackListResource(Database::TrackListId trackListId)
|
||||||
|
: _trackListId {trackListId}
|
||||||
|
{
|
||||||
|
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||||
|
|
||||||
|
const Database::TrackList::pointer trackList {Database::TrackList::find(LmsApp->getDbSession(), trackListId)};
|
||||||
|
if (trackList)
|
||||||
|
suggestFileName(getTrackListPathName(trackList) + ".zip");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Zip::Zipper>
|
||||||
|
DownloadTrackListResource::createZipper()
|
||||||
|
{
|
||||||
|
using namespace Database;
|
||||||
|
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||||
|
|
||||||
|
Track::FindParameters params;
|
||||||
|
params.setTrackList(_trackListId);
|
||||||
|
const auto trackResults {Track::find(LmsApp->getDbSession(), params)};
|
||||||
|
|
||||||
|
std::vector<Track::pointer> tracks;
|
||||||
|
tracks.reserve(trackResults.results.size());
|
||||||
|
std::transform(std::cbegin(trackResults.results), std::cend(trackResults.results), std::back_inserter(tracks), [](TrackId trackId){ return Track::find(LmsApp->getDbSession(), trackId); });
|
||||||
|
|
||||||
|
return UserInterface::createZipper(tracks);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace UserInterface
|
} // namespace UserInterface
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
#include "services/database/ArtistId.hpp"
|
#include "services/database/ArtistId.hpp"
|
||||||
#include "services/database/ReleaseId.hpp"
|
#include "services/database/ReleaseId.hpp"
|
||||||
#include "services/database/TrackId.hpp"
|
#include "services/database/TrackId.hpp"
|
||||||
|
#include "services/database/TrackListId.hpp"
|
||||||
#include "utils/Zipper.hpp"
|
#include "utils/Zipper.hpp"
|
||||||
|
|
||||||
namespace UserInterface
|
namespace UserInterface
|
||||||
@@ -70,5 +71,15 @@ namespace UserInterface
|
|||||||
std::unique_ptr<Zip::Zipper> createZipper() override;
|
std::unique_ptr<Zip::Zipper> createZipper() override;
|
||||||
Database::TrackId _trackId;
|
Database::TrackId _trackId;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class DownloadTrackListResource : public DownloadResource
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DownloadTrackListResource(Database::TrackListId trackListId);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<Zip::Zipper> createZipper() override;
|
||||||
|
Database::TrackListId _trackListId;
|
||||||
|
};
|
||||||
} // namespace UserInterface
|
} // namespace UserInterface
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user