Merge branch 'develop' for release v3.15.0
This commit is contained in:
+26
-23
@@ -3,42 +3,45 @@
|
|||||||
<!--FORMS message blocks-->
|
<!--FORMS message blocks-->
|
||||||
|
|
||||||
<message id="Lms.Explore.Artist.template">
|
<message id="Lms.Explore.Artist.template">
|
||||||
|
<div class="Lms-header">
|
||||||
|
<h2><div class="Lms-artistname text-primary">${name}</div></h2>
|
||||||
|
${clusters}
|
||||||
|
${play-btn}${add-btn}
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
${releases class="Lms-explore-artist-entries-container"}
|
||||||
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-8">
|
|
||||||
<div class="Lms-header">
|
|
||||||
<h2><div class="Lms-artistname text-primary">${name}</div></h2>
|
|
||||||
${clusters}
|
|
||||||
${play-btn}${add-btn}
|
|
||||||
</div>
|
|
||||||
${releases class="Lms-explore-artist-entries-container"}
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<div class="well well-sm">
|
<div class="well well-sm">
|
||||||
<h3>${tr:Lms.Explore.Artist.similar-artists}</h3>
|
<h3>${tr:Lms.Explore.Artist.similar-artists}</h3>
|
||||||
${similar-artists}
|
${similar-artists}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
${<if-has-mbid>}
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="well well-sm">
|
||||||
|
<h3>${tr:Lms.Explore.links}</h3>
|
||||||
|
${mbid-link class="text-muted"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
${</if-has-mbid>}
|
||||||
</div>
|
</div>
|
||||||
</message>
|
</message>
|
||||||
|
|
||||||
<message id="Lms.Explore.Artist.template.entry">
|
<message id="Lms.Explore.Artist.template.entry">
|
||||||
<div class="row Lms-explore-artist-entry Lms-entry-hoverable">
|
<div class="Lms-explore-entry-list">
|
||||||
<div class="col-xs-12">
|
<div class="row Lms-vertical-align">
|
||||||
<div class="media">
|
<div class="col-xs-4 col-sm-3 col-md-2">
|
||||||
<div class="media-left media-middle">
|
<div class="Lms-responsive-square Lms-cover-container">
|
||||||
<div class="media-object Lms-cover-container-medium">
|
${cover}
|
||||||
${cover}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="media-body media-middle">
|
|
||||||
<h4 class="media-heading">
|
|
||||||
${name class="Lms-releasename text-primary"}
|
|
||||||
</h4>
|
|
||||||
${<if-has-artist>}<h4><small>${artist class="Lms-artistname text-muted"}</small></h4>${</if-has-artist>}
|
|
||||||
<h4><small><div class="text-muted">${<if-has-year>}${year}${</if-has-year>}${<if-has-orig-year>} (${orig-year})${</if-has-orig-year>}</div></small></h4>
|
|
||||||
${play-btn}${add-btn}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-xs-8 col-sm-9 col-md-10 Lms-explore-list-entry-text-container">
|
||||||
|
<h5>${name class="Lms-releasename text-primary"}</h5>
|
||||||
|
${<if-has-artist>}<h6>${artist class="Lms-artistname text-muted"}</h6>${</if-has-artist>}
|
||||||
|
<h6><small><div class="text-muted">${<if-has-year>}${year}${</if-has-year>}${<if-has-orig-year>} (${orig-year})${</if-has-orig-year>}</div></small></h6>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</message>
|
</message>
|
||||||
|
|||||||
+6
-12
@@ -13,26 +13,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
${artists}
|
${artists}
|
||||||
<div class="row">
|
<div class="Lms-horizontal-center">
|
||||||
<div class="col-xs-12 Lms-horizontal-center">
|
${show-more class="btn-primary Lms-show-more"}
|
||||||
${show-more class="btn-primary Lms-show-more"}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</message>
|
</message>
|
||||||
|
|
||||||
<message id="Lms.Explore.Artists.template.entry">
|
<message id="Lms.Explore.Artists.template.entry">
|
||||||
<div class="row Lms-explore-artists-entry Lms-entry-hoverable Lms-vertical-align">
|
<div class="Lms-explore-artists-entry Lms-entry-hoverable Lms-vertical-align">
|
||||||
<div class="col-xs-12">
|
<h4>${name class="Lms-artistname text-primary"}</h4>
|
||||||
<h4>${name class="Lms-artistname text-primary"}</h4>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</message>
|
</message>
|
||||||
|
|
||||||
<message id="Lms.Explore.Artists.template.entry-small">
|
<message id="Lms.Explore.Artists.template.entry-small">
|
||||||
<div class="row Lms-explore-artists-entry-small Lms-entry-hoverable Lms-vertical-align">
|
<div class="Lms-explore-artists-entry-small Lms-entry-hoverable Lms-vertical-align">
|
||||||
<div class="col-xs-12">
|
${name class="Lms-artistname text-primary"}
|
||||||
${name class="Lms-artistname text-primary"}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</message>
|
</message>
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="hidden-xs col-sm-2 Lms-player-duration text-muted">
|
<div class="hidden-xs col-sm-2 Lms-player-duration text-muted">
|
||||||
<a id="lms-transcoding-active" class="Lms-player-btn Lms-control-btn" data-toggle="tooltip" title="${tr:Lms.Player.transcoding-active}" style="display:none"><i class="fa fa-fw fa-info-circle" aria-hidden="true"></i></a>
|
<a id="lms-transcoding-active" class="Lms-player-btn Lms-control-btn" data-toggle="tooltip" title="${tr:Lms.Player.transcoding-active}" style="visibility:hidden"><i class="fa fa-fw fa-info-circle" aria-hidden="true"></i></a>
|
||||||
<span id="lms-mp-curtime">--</span> / <span id="lms-mp-duration">--</span>
|
<span id="lms-mp-curtime">--</span> / <span id="lms-mp-duration">--</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
<message id="Lms.password-old">Old password</message>
|
<message id="Lms.password-old">Old password</message>
|
||||||
<message id="Lms.password-too-weak">Password too weak</message>
|
<message id="Lms.password-too-weak">Password too weak</message>
|
||||||
<message id="Lms.passwords-dont-match">Passwords don't match</message>
|
<message id="Lms.passwords-dont-match">Passwords don't match</message>
|
||||||
<message id="Lms.quit-confirm">Are you sure?</message>
|
|
||||||
<message id="Lms.quit-other-session">Another session has been open. Reopen this one?</message>
|
<message id="Lms.quit-other-session">Another session has been open. Reopen this one?</message>
|
||||||
<message id="Lms.save">Save</message>
|
<message id="Lms.save">Save</message>
|
||||||
<message id="Lms.user">User</message>
|
<message id="Lms.user">User</message>
|
||||||
@@ -106,7 +105,10 @@
|
|||||||
<message id="Lms.Explore.artists">Artists</message>
|
<message id="Lms.Explore.artists">Artists</message>
|
||||||
<message id="Lms.Explore.filter-added">Filter added</message>
|
<message id="Lms.Explore.filter-added">Filter added</message>
|
||||||
<message id="Lms.Explore.filters">Filters</message>
|
<message id="Lms.Explore.filters">Filters</message>
|
||||||
|
<message id="Lms.Explore.links">Links</message>
|
||||||
<message id="Lms.Explore.most-played">Most played</message>
|
<message id="Lms.Explore.most-played">Most played</message>
|
||||||
|
<message id="Lms.Explore.musicbrainz-artist">MusicBrainz Artist</message>
|
||||||
|
<message id="Lms.Explore.musicbrainz-release">MusicBrainz Release</message>
|
||||||
<message id="Lms.Explore.random">Random</message>
|
<message id="Lms.Explore.random">Random</message>
|
||||||
<message id="Lms.Explore.recently-added">Recently added</message>
|
<message id="Lms.Explore.recently-added">Recently added</message>
|
||||||
<message id="Lms.Explore.recently-played">Recently played</message>
|
<message id="Lms.Explore.recently-played">Recently played</message>
|
||||||
@@ -194,4 +196,3 @@
|
|||||||
<message id="Lms.Settings.settings-saved">New settings saved!</message>
|
<message id="Lms.Settings.settings-saved">New settings saved!</message>
|
||||||
|
|
||||||
</messages>
|
</messages>
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
<message id="Lms.password-old">Ancien mot de passe</message>
|
<message id="Lms.password-old">Ancien mot de passe</message>
|
||||||
<message id="Lms.password-too-weak">Mot de passe trop faible</message>
|
<message id="Lms.password-too-weak">Mot de passe trop faible</message>
|
||||||
<message id="Lms.passwords-dont-match">Les mots de passe ne correspondent pas</message>
|
<message id="Lms.passwords-dont-match">Les mots de passe ne correspondent pas</message>
|
||||||
<message id="Lms.quit-confirm">Êtes-vous sûr ?</message>
|
|
||||||
<message id="Lms.quit-other-session">Une autre session a été ouverte. Reouvrir celle-ci ?</message>
|
<message id="Lms.quit-other-session">Une autre session a été ouverte. Reouvrir celle-ci ?</message>
|
||||||
<message id="Lms.save">Sauvegarder</message>
|
<message id="Lms.save">Sauvegarder</message>
|
||||||
<message id="Lms.user">Utilisateur</message>
|
<message id="Lms.user">Utilisateur</message>
|
||||||
@@ -106,7 +105,10 @@
|
|||||||
<message id="Lms.Explore.artists">Artistes</message>
|
<message id="Lms.Explore.artists">Artistes</message>
|
||||||
<message id="Lms.Explore.filter-added">Filtre ajouté</message>
|
<message id="Lms.Explore.filter-added">Filtre ajouté</message>
|
||||||
<message id="Lms.Explore.filters">Filtres</message>
|
<message id="Lms.Explore.filters">Filtres</message>
|
||||||
<message id="Lms.Explore.mostly-played">Plus joués</message>
|
<message id="Lms.Explore.links">Liens</message>
|
||||||
|
<message id="Lms.Explore.most-played">Plus joués</message>
|
||||||
|
<message id="Lms.Explore.musicbrainz-artist">MusicBrainz Artist</message>
|
||||||
|
<message id="Lms.Explore.musicbrainz-release">MusicBrainz Release</message>
|
||||||
<message id="Lms.Explore.random">Aléatoire</message>
|
<message id="Lms.Explore.random">Aléatoire</message>
|
||||||
<message id="Lms.Explore.recently-added">Ajoutés récemment</message>
|
<message id="Lms.Explore.recently-added">Ajoutés récemment</message>
|
||||||
<message id="Lms.Explore.recently-played">Joués récemment</message>
|
<message id="Lms.Explore.recently-played">Joués récemment</message>
|
||||||
@@ -191,7 +193,6 @@
|
|||||||
<message id="Lms.Settings.transcode-mode.always">Toujours</message>
|
<message id="Lms.Settings.transcode-mode.always">Toujours</message>
|
||||||
<message id="Lms.Settings.transcode-mode.if-format-not-supported">Seulement lorsque le format n'est pas pris en charge</message>
|
<message id="Lms.Settings.transcode-mode.if-format-not-supported">Seulement lorsque le format n'est pas pris en charge</message>
|
||||||
<message id="Lms.Settings.transcode-mode.never">Jamais</message>
|
<message id="Lms.Settings.transcode-mode.never">Jamais</message>
|
||||||
<message id="Lms.Settings.settings">Paramètres</message>
|
|
||||||
<message id="Lms.Settings.settings-saved">Paramètres sauvegardés !</message>
|
<message id="Lms.Settings.settings-saved">Paramètres sauvegardés !</message>
|
||||||
|
|
||||||
<!--Wt-->
|
<!--Wt-->
|
||||||
|
|||||||
+15
-21
@@ -9,17 +9,15 @@
|
|||||||
|
|
||||||
<message id="Lms.PlayQueue.template">
|
<message id="Lms.PlayQueue.template">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-8">
|
<div class="col-lg-8">
|
||||||
<div class="Lms-header">
|
<div class="Lms-header">
|
||||||
<h2>${tr:Lms.PlayQueue.playqueue}</h2>
|
<h2>${tr:Lms.PlayQueue.playqueue}</h2>
|
||||||
${clear-btn}${shuffle-btn}${repeat-btn}${radio-btn}
|
${clear-btn}${shuffle-btn}${repeat-btn}${radio-btn}
|
||||||
<h4><small>${nb-tracks}</small></h4>
|
<h4><small>${nb-tracks}</small></h4>
|
||||||
</div>
|
</div>
|
||||||
${entries}
|
${entries}
|
||||||
<div class="row">
|
<div class="Lms-horizontal-center">
|
||||||
<div class="col-xs-12 Lms-horizontal-center">
|
${show-more class="btn-primary Lms-show-more"}
|
||||||
${show-more class="btn-primary Lms-show-more"}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -29,24 +27,20 @@
|
|||||||
<message id="Lms.PlayQueue.template.delete-btn"><i class="fa fa-fw fa-times-circle Lms-playqueue-btn Lms-control-btn"></i></message>
|
<message id="Lms.PlayQueue.template.delete-btn"><i class="fa fa-fw fa-times-circle Lms-playqueue-btn Lms-control-btn"></i></message>
|
||||||
|
|
||||||
<message id="Lms.PlayQueue.template.entry">
|
<message id="Lms.PlayQueue.template.entry">
|
||||||
<div class="row Lms-playqueue-entry Lms-entry-hoverable Lms-vertical-align ${is-selected}">
|
<div class="row Lms-vertical-align Lms-explore-entry-list Lms-playqueue-entry ${is-selected}">
|
||||||
<div class="col-xs-9 Lms-playqueue-entry-name">
|
<div class="col-xs-3 col-sm-2 col-md-2">
|
||||||
<div class="media">
|
<div class="Lms-responsive-square Lms-cover-container">
|
||||||
<div class="media-left media-middle hidden-xs">
|
${cover}
|
||||||
<div class="media-object Lms-cover-container-small">
|
|
||||||
${cover}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="media-body media-middle">
|
|
||||||
<h4><div class="Lms-trackname text-primary">${name}</div></h4>
|
|
||||||
${<if-has-artists-or-release>}
|
|
||||||
${<if-has-release>}<small>${release class="Lms-releasename text-primary"}</small>${</if-has-release>}
|
|
||||||
${<if-has-artists>}${artists}${</if-has-artists>}
|
|
||||||
${</if-has-artists-or-release>}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-3">
|
<div class="col-xs-7 col-sm-8 col-md-8 Lms-explore-entry-list-text-container">
|
||||||
|
<div class="Lms-trackname text-primary"><h5>${name}</h5></div>
|
||||||
|
${<if-has-artists-or-release>}
|
||||||
|
${<if-has-release>}<small>${release class="Lms-releasename text-primary"}</small>${</if-has-release>}
|
||||||
|
${<if-has-artists>}${artists}${</if-has-artists>}
|
||||||
|
${</if-has-artists-or-release>}
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-2 col-sm-2 col-md-2">
|
||||||
<div class="Lms-playqueue-entry-controls Lms-vertical-align">
|
<div class="Lms-playqueue-entry-controls Lms-vertical-align">
|
||||||
${play-btn}${del-btn}
|
${play-btn}${del-btn}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+26
-11
@@ -7,11 +7,11 @@
|
|||||||
</message>
|
</message>
|
||||||
|
|
||||||
<message id="Lms.Explore.Release.template">
|
<message id="Lms.Explore.Release.template">
|
||||||
<div class="row">
|
<div class="row Lms-explore-release">
|
||||||
<div class="col-md-3">
|
<div class="col-md-4">
|
||||||
${cover class="Lms-explore-release-cover"}
|
${cover class="Lms-explore-release-cover"}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-5">
|
<div class="col-md-8">
|
||||||
<div class="Lms-header">
|
<div class="Lms-header">
|
||||||
<h3><div class="Lms-releasename text-primary">${name}</div></h3>
|
<h3><div class="Lms-releasename text-primary">${name}</div></h3>
|
||||||
${<if-has-artist>}<h4>${artist class="Lms-artistname text-muted"}</h4>${</if-has-artist>}
|
${<if-has-artist>}<h4>${artist class="Lms-artistname text-muted"}</h4>${</if-has-artist>}
|
||||||
@@ -19,19 +19,34 @@
|
|||||||
${clusters}
|
${clusters}
|
||||||
${play-btn}${add-btn}
|
${play-btn}${add-btn}
|
||||||
</div>
|
</div>
|
||||||
${container class="Lms-explore-release-entries-container"}
|
${container}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4">
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-12 col-md-8">
|
||||||
<div class="well well-sm">
|
<div class="well well-sm">
|
||||||
${<if-has-copyright-or-copyright-url>}
|
|
||||||
<h3>${tr:Lms.Explore.Release.copyright}</h3>
|
|
||||||
${<if-has-copyright>}<small>${copyright}</small>${</if-has-copyright>}
|
|
||||||
${<if-has-copyright-url>}<small>${copyright-url}</small>${</if-has-copyright-url>}
|
|
||||||
${</if-has-copyright-or-copyright-url>}
|
|
||||||
<h3>${tr:Lms.Explore.Release.similar-releases}</h3>
|
<h3>${tr:Lms.Explore.Release.similar-releases}</h3>
|
||||||
${similar-releases}
|
<div class="row">
|
||||||
|
${similar-releases}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
${<if-has-copyright>}
|
||||||
|
<div class="col-xs-12 col-md-4">
|
||||||
|
<div class="well well-sm">
|
||||||
|
<h3>${tr:Lms.Explore.Release.copyright}</h3>
|
||||||
|
${copyright class="text-muted"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
${</if-has-copyright>}
|
||||||
|
${<if-has-mbid>}
|
||||||
|
<div class="col-xs-12 col-md-4">
|
||||||
|
<div class="well well-sm">
|
||||||
|
<h3>${tr:Lms.Explore.links}</h3>
|
||||||
|
${mbid-link class="text-muted"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
${</if-has-mbid>}
|
||||||
</div>
|
</div>
|
||||||
</message>
|
</message>
|
||||||
|
|
||||||
|
|||||||
+13
-41
@@ -8,54 +8,26 @@
|
|||||||
${mode class="nav nav-pills Lms-explore-mode-container"}
|
${mode class="nav nav-pills Lms-explore-mode-container"}
|
||||||
${play-btn}${add-btn}
|
${play-btn}${add-btn}
|
||||||
</div>
|
</div>
|
||||||
${releases}
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-12 Lms-horizontal-center">
|
${releases}
|
||||||
${show-more class="btn-primary Lms-show-more"}
|
</div>
|
||||||
</div>
|
<div class="Lms-horizontal-center">
|
||||||
|
${show-more class="btn-primary Lms-show-more"}
|
||||||
</div>
|
</div>
|
||||||
</message>
|
</message>
|
||||||
|
|
||||||
<message id="Lms.Explore.Releases.template.entry">
|
<message id="Lms.Explore.Releases.template.entry-grid">
|
||||||
<div class="row Lms-explore-releases-entry Lms-entry-hoverable">
|
<div class="col-xs-6 col-sm-3 col-md-2 col-lg-2">
|
||||||
<div class="col-xs-12">
|
<div class="Lms-explore-entry-grid">
|
||||||
<div class="media">
|
<div class="Lms-responsive-square Lms-cover-container">
|
||||||
<div class="media-left media-middle">
|
${cover}
|
||||||
<div class="media-object Lms-cover-container-medium">
|
</div>
|
||||||
${cover}
|
<div class="Lms-explore-entry-grid-text-container">
|
||||||
</div>
|
<h5>${release-name class="Lms-releasename text-primary"}</h5>
|
||||||
</div>
|
<div class="Lms-explore-entry-separator"><h6>${<if-has-year>}<small><span class="text-muted">${year}</span></small>${</if-has-year>}${<if-has-artist>}${artist-name class="Lms-artistname text-muted"}${</if-has-artist>}</h6></div>
|
||||||
<div class="media-body media-middle">
|
|
||||||
<h4 class="media-heading">
|
|
||||||
${release-name class="Lms-releasename text-primary"}
|
|
||||||
</h4>
|
|
||||||
${<if-has-artist>}<h4><small>${artist-name class="Lms-artistname text-muted"}</small></h4>${</if-has-artist>}
|
|
||||||
${play-btn}${add-btn}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</message>
|
</message>
|
||||||
|
|
||||||
<message id="Lms.Explore.Releases.template.entry-small">
|
|
||||||
<div class="row Lms-explore-releaselink Lms-entry-hoverable">
|
|
||||||
<div class="col-xs-12">
|
|
||||||
<div class="media">
|
|
||||||
<div class="media-left media-middle">
|
|
||||||
<div class="media-object Lms-cover-container-smaller">
|
|
||||||
${cover}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="media-body media-middle">
|
|
||||||
<div class="media-heading">
|
|
||||||
${release-name class="Lms-releasename text-primary"}
|
|
||||||
</div>
|
|
||||||
${<if-has-artist>}<small>${artist-name class="Lms-artistname text-muted"}</small>${</if-has-artist>}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</message>
|
|
||||||
|
|
||||||
|
|
||||||
</messages>
|
</messages>
|
||||||
|
|||||||
+24
-29
@@ -6,38 +6,33 @@
|
|||||||
<div class="Lms-header">
|
<div class="Lms-header">
|
||||||
<h2>${tr:Lms.Explore.Search.search}</h2>
|
<h2>${tr:Lms.Explore.Search.search}</h2>
|
||||||
</div>
|
</div>
|
||||||
${<if-artists>}
|
<div class="row">
|
||||||
<h3>${tr:Lms.Explore.artists}</h3>
|
${<if-artists>}
|
||||||
${artists}
|
<div class="col-lg-8">
|
||||||
${</if-artists>}
|
<div class="well well-sm">
|
||||||
${<if-releases>}
|
<h3>${tr:Lms.Explore.artists}</h3>
|
||||||
<h3>${tr:Lms.Explore.releases}</h3>
|
${artists}
|
||||||
${releases}
|
</div>
|
||||||
${</if-releases>}
|
</div>
|
||||||
${<if-tracks>}
|
${</if-artists>}
|
||||||
<h3>${tr:Lms.Explore.tracks}</h3>
|
${<if-releases>}
|
||||||
${tracks}
|
<div class="col-lg-8">
|
||||||
${</if-tracks>}
|
<div class="well well-sm">
|
||||||
</message>
|
<h3>${tr:Lms.Explore.releases}</h3>
|
||||||
|
<div class="row">
|
||||||
<message id="Lms.Explore.Search.template.track-entry">
|
${releases}
|
||||||
<div class="row Lms-explore-tracks-entry Lms-entry-hoverable Lms-vertical-align">
|
|
||||||
<div class="col-xs-12 Lms-explore-tracks-entry-name">
|
|
||||||
<div class="media">
|
|
||||||
<div class="media-left media-middle hidden-xs">
|
|
||||||
<div class="media-object Lms-cover-container-small">
|
|
||||||
${cover}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="media-body media-middle">
|
|
||||||
<h4><div class="Lms-trackname text-primary">${name}</div></h4>
|
|
||||||
${<if-has-artists-or-release>}
|
|
||||||
${<if-has-release>}<small>${release class="Lms-releasename text-primary"}</small>${</if-has-release>}
|
|
||||||
${<if-has-artists>}${artists}${</if-has-artists>}
|
|
||||||
${</if-has-artists-or-release>}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
${</if-releases>}
|
||||||
|
${<if-tracks>}
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<div class="well well-sm">
|
||||||
|
<h3>${tr:Lms.Explore.tracks}</h3>
|
||||||
|
${tracks}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
${</if-tracks>}
|
||||||
</div>
|
</div>
|
||||||
</message>
|
</message>
|
||||||
|
|
||||||
|
|||||||
+26
-32
@@ -4,50 +4,44 @@
|
|||||||
<!--FORMS message blocks-->
|
<!--FORMS message blocks-->
|
||||||
|
|
||||||
<message id="Lms.Explore.Tracks.template">
|
<message id="Lms.Explore.Tracks.template">
|
||||||
<div class="Lms-header">
|
|
||||||
<h2>${tr:Lms.Explore.tracks}</h2>
|
|
||||||
${mode class="nav nav-pills Lms-explore-mode-container"}
|
|
||||||
${play-btn}${add-btn}
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-8">
|
<div class="col-lg-8">
|
||||||
|
<div class="Lms-header">
|
||||||
|
<h2>${tr:Lms.Explore.tracks}</h2>
|
||||||
|
${mode class="nav nav-pills Lms-explore-mode-container"}
|
||||||
|
${play-btn}${add-btn}
|
||||||
|
</div>
|
||||||
${tracks}
|
${tracks}
|
||||||
</div>
|
<div class="Lms-horizontal-center">
|
||||||
</div>
|
${show-more class="btn-primary Lms-show-more"}
|
||||||
<div class="row">
|
</div>
|
||||||
<div class="col-lg-8 col-xs-12 Lms-horizontal-center">
|
|
||||||
${show-more class="btn-primary Lms-show-more"}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</message>
|
</message>
|
||||||
|
|
||||||
<message id="Lms.Explore.Tracks.template.entry">
|
<message id="Lms.Explore.Tracks.template.entry">
|
||||||
<div class="row Lms-explore-tracks-entry Lms-entry-hoverable Lms-vertical-align ${is-playing}">
|
<div class="row Lms-vertical-align Lms-explore-entry-list Lms-explore-tracks-entry ${is-playing}">
|
||||||
<div class="col-xs-9 Lms-explore-tracks-entry-name">
|
<div class="col-xs-3 col-sm-2 col-md-2">
|
||||||
<div class="media">
|
<div class="Lms-responsive-square Lms-cover-container">
|
||||||
<div class="media-left media-middle hidden-xs">
|
${cover}
|
||||||
<div class="media-object Lms-cover-container-small">
|
|
||||||
${cover}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="media-body media-middle">
|
</div>
|
||||||
<h4><div class="Lms-trackname text-primary">${name}</div></h4>
|
<div class="col-xs-7 col-sm-8 col-md-8 Lms-explore-entry-list-text-container">
|
||||||
${<if-has-artists-or-release>}
|
<div class="Lms-trackname text-primary"><h5>${name}</h5></div>
|
||||||
${<if-has-release>}<small>${release class="Lms-releasename text-primary"}</small>${</if-has-release>}
|
${<if-has-artists-or-release>}
|
||||||
${<if-has-artists>}${artists}${</if-has-artists>}
|
${<if-has-release>}<small>${release class="Lms-releasename text-primary"}</small>${</if-has-release>}
|
||||||
${</if-has-artists-or-release>}
|
${<if-has-artists>}${artists}${</if-has-artists>}
|
||||||
|
${</if-has-artists-or-release>}
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-2 col-sm-2 col-md-2">
|
||||||
|
<div class="Lms-explore-tracks-entry-controls">
|
||||||
|
${play-btn}${add-btn}
|
||||||
|
</div>
|
||||||
|
<div class="Lms-explore-tracks-entry-duration text-muted">
|
||||||
|
${duration}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-3">
|
|
||||||
<div class="Lms-explore-tracks-entry-controls">
|
|
||||||
${play-btn}${add-btn}
|
|
||||||
</div>
|
|
||||||
<div class="Lms-explore-tracks-entry-duration text-muted">
|
|
||||||
${duration}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</message>
|
</message>
|
||||||
|
|
||||||
<message id="Lms.Explore.Tracks.template.entry-artist">
|
<message id="Lms.Explore.Tracks.template.entry-artist">
|
||||||
|
|||||||
+60
-25
@@ -75,27 +75,18 @@ a.Lms-artistname:hover, a.Lms-artistname:focus {
|
|||||||
margin: auto;
|
margin: auto;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.4);
|
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.4);
|
||||||
|
opacity: 0;
|
||||||
|
-moz-transition: opacity 1s;
|
||||||
|
-webkit-transition: opacity 1s;
|
||||||
|
-o-transition: opacity 1s;
|
||||||
|
transition: opacity 1s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-cover:hover {
|
.Lms-cover:hover {
|
||||||
filter: brightness(130%);
|
filter: brightness(130%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-cover-container-smaller {
|
.Lms-cover-container {
|
||||||
width: 64px;
|
|
||||||
height: 64px;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Lms-cover-container-small {
|
|
||||||
width: 96px;
|
|
||||||
height: 96px;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Lms-cover-container-medium {
|
|
||||||
width: 128px;
|
|
||||||
height: 128px;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,6 +95,10 @@ a.Lms-artistname:hover, a.Lms-artistname:focus {
|
|||||||
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.4);
|
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.Lms-cover-loaded {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.Lms-entry-playing {
|
.Lms-entry-playing {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
@@ -124,17 +119,52 @@ a.Lms-artistname:hover, a.Lms-artistname:focus {
|
|||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-explore-releases-entry {
|
.Lms-explore-entry-list {
|
||||||
padding-top: 4px;
|
padding-top: 4px;
|
||||||
padding-bottom: 4px;
|
padding-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-explore-release-cover {
|
.Lms-explore-entry-list-text-container {
|
||||||
max-width: 100%;
|
margin-left: -15px;
|
||||||
|
text-align: left;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-explore-release-entries-container {
|
.Lms-explore-entry-list-text-container h5 {
|
||||||
margin-bottom: 15px;
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Lms-explore-entry-grid {
|
||||||
|
margin-bottom: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Lms-explore-entry-grid-text-container {
|
||||||
|
height: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Lms-explore-entry-grid-text-container h5, h6 {
|
||||||
|
margin-top: 6px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
text-align: left;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Lms-explore-entry-separator :nth-child(2) {
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Lms-explore-release {
|
||||||
|
margin-bottom: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Lms-explore-release-cover {
|
||||||
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-explore-release-entry-tracknumber {
|
.Lms-explore-release-entry-tracknumber {
|
||||||
@@ -207,11 +237,6 @@ a.Lms-artistname:hover, a.Lms-artistname:focus {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-explore-tracks-entry-name {
|
|
||||||
margin-top: 4px;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Lms-explore-tracks-entry-controls {
|
.Lms-explore-tracks-entry-controls {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
display: none;
|
display: none;
|
||||||
@@ -443,6 +468,16 @@ a.Lms-releasename:hover, a.Lms-releasename:focus {
|
|||||||
color: var(--hover-color);
|
color: var(--hover-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.Lms-responsive-square {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Lms-responsive-square:after {
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
padding-bottom: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.Lms-show-more {
|
.Lms-show-more {
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
|
|||||||
@@ -203,10 +203,10 @@ LMS.mediaplayer = function () {
|
|||||||
|
|
||||||
_elems.audio.addEventListener("canplay", function() {
|
_elems.audio.addEventListener("canplay", function() {
|
||||||
if (_getAudioMode() == Mode.Transcode) {
|
if (_getAudioMode() == Mode.Transcode) {
|
||||||
_elems.transcodingActive.style.display = "inline-block";
|
_elems.transcodingActive.style.visibility = "visible";
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
_elems.transcodingActive.style.display = "none";
|
_elems.transcodingActive.style.visibility = "hidden";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -362,7 +362,7 @@ Artist::getReleases(const std::set<IdType>& clusterIds) const
|
|||||||
if (!clusterIds.empty())
|
if (!clusterIds.empty())
|
||||||
oss << " GROUP BY t.id HAVING COUNT(DISTINCT c.id) = " << clusterIds.size();
|
oss << " GROUP BY t.id HAVING COUNT(DISTINCT c.id) = " << clusterIds.size();
|
||||||
|
|
||||||
oss << " ORDER BY t.year,r.name";
|
oss << " ORDER BY t.year DESC, r.name COLLATE NOCASE";
|
||||||
|
|
||||||
Wt::Dbo::Query<Release::pointer> query = session()->query<Release::pointer>( oss.str() );
|
Wt::Dbo::Query<Release::pointer> query = session()->query<Release::pointer>( oss.str() );
|
||||||
|
|
||||||
@@ -395,7 +395,7 @@ Artist::getTracks(std::optional<TrackArtistLink::Type> linkType) const
|
|||||||
|
|
||||||
auto query {session()->query<Wt::Dbo::ptr<Track>>("SELECT DISTINCT t FROM track t INNER JOIN artist a ON a.id = t_a_l.artist_id INNER JOIN track_artist_link t_a_l ON t_a_l.track_id = t.id")
|
auto query {session()->query<Wt::Dbo::ptr<Track>>("SELECT DISTINCT t FROM track t INNER JOIN artist a ON a.id = t_a_l.artist_id INNER JOIN track_artist_link t_a_l ON t_a_l.track_id = t.id")
|
||||||
.where("a.id = ?").bind(self()->id())
|
.where("a.id = ?").bind(self()->id())
|
||||||
.orderBy("t.year,t.release_id,t.disc_number,t.track_number")};
|
.orderBy("t.year DESC,t.release_id,t.disc_number,t.track_number")};
|
||||||
|
|
||||||
if (linkType)
|
if (linkType)
|
||||||
query.where("t_a_l.type = ?").bind(*linkType);
|
query.where("t_a_l.type = ?").bind(*linkType);
|
||||||
@@ -453,7 +453,12 @@ Artist::getSimilarArtists(std::optional<std::size_t> offset, std::optional<std::
|
|||||||
" INNER JOIN track t ON t.id = t_a_l.track_id"
|
" INNER JOIN track t ON t.id = t_a_l.track_id"
|
||||||
" INNER JOIN track_cluster t_c ON t_c.track_id = t.id"
|
" INNER JOIN track_cluster t_c ON t_c.track_id = t.id"
|
||||||
" WHERE "
|
" WHERE "
|
||||||
" t_c.cluster_id IN (SELECT c.id from cluster c INNER JOIN track t ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id INNER JOIN artist a ON a.id = t_a_l.artist_id INNER JOIN track_artist_link t_a_l ON t_a_l.track_id = t.id WHERE a.id = ?)"
|
" t_c.cluster_id IN (SELECT c.id from cluster c"
|
||||||
|
" INNER JOIN track t ON c.id = t_c.cluster_id"
|
||||||
|
" INNER JOIN track_cluster t_c ON t_c.track_id = t.id"
|
||||||
|
" INNER JOIN artist a ON a.id = t_a_l.artist_id"
|
||||||
|
" INNER JOIN track_artist_link t_a_l ON t_a_l.track_id = t.id"
|
||||||
|
" WHERE a.id = ?)"
|
||||||
" AND a.id <> ?"
|
" AND a.id <> ?"
|
||||||
)
|
)
|
||||||
.bind(self()->id())
|
.bind(self()->id())
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ add_executable(lms
|
|||||||
ui/LmsApplicationGroup.cpp
|
ui/LmsApplicationGroup.cpp
|
||||||
ui/LmsTheme.cpp
|
ui/LmsTheme.cpp
|
||||||
ui/MediaPlayer.cpp
|
ui/MediaPlayer.cpp
|
||||||
ui/PlayQueueView.cpp
|
ui/PlayQueue.cpp
|
||||||
ui/SettingsView.cpp
|
ui/SettingsView.cpp
|
||||||
ui/TrackStringUtils.cpp
|
ui/TrackStringUtils.cpp
|
||||||
ui/admin/DatabaseSettingsView.cpp
|
ui/admin/DatabaseSettingsView.cpp
|
||||||
@@ -24,6 +24,7 @@ add_executable(lms
|
|||||||
ui/explore/ReleasesView.cpp
|
ui/explore/ReleasesView.cpp
|
||||||
ui/explore/ReleaseView.cpp
|
ui/explore/ReleaseView.cpp
|
||||||
ui/explore/SearchView.cpp
|
ui/explore/SearchView.cpp
|
||||||
|
ui/explore/TrackListHelpers.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
|
||||||
|
|||||||
@@ -50,7 +50,7 @@
|
|||||||
#include "LmsApplicationException.hpp"
|
#include "LmsApplicationException.hpp"
|
||||||
#include "LmsTheme.hpp"
|
#include "LmsTheme.hpp"
|
||||||
#include "MediaPlayer.hpp"
|
#include "MediaPlayer.hpp"
|
||||||
#include "PlayQueueView.hpp"
|
#include "PlayQueue.hpp"
|
||||||
#include "SettingsView.hpp"
|
#include "SettingsView.hpp"
|
||||||
|
|
||||||
|
|
||||||
@@ -251,6 +251,7 @@ LmsApplication::createArtistAnchor(Database::Artist::pointer artist, bool addTex
|
|||||||
{
|
{
|
||||||
res->setTextFormat(Wt::TextFormat::Plain);
|
res->setTextFormat(Wt::TextFormat::Plain);
|
||||||
res->setText(Wt::WString::fromUTF8(artist->getName()));
|
res->setText(Wt::WString::fromUTF8(artist->getName()));
|
||||||
|
res->setToolTip(Wt::WString::fromUTF8(artist->getName()), Wt::TextFormat::Plain);
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
@@ -269,8 +270,10 @@ LmsApplication::createReleaseAnchor(Database::Release::pointer release, bool add
|
|||||||
|
|
||||||
if (addText)
|
if (addText)
|
||||||
{
|
{
|
||||||
|
res->setWordWrap(false);
|
||||||
res->setTextFormat(Wt::TextFormat::Plain);
|
res->setTextFormat(Wt::TextFormat::Plain);
|
||||||
res->setText(Wt::WString::fromUTF8(release->getName()));
|
res->setText(Wt::WString::fromUTF8(release->getName()));
|
||||||
|
res->setToolTip(Wt::WString::fromUTF8(release->getName()), Wt::TextFormat::Plain);
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
@@ -314,7 +317,6 @@ LmsApplication::handleException(LmsApplicationException& e)
|
|||||||
Wt::WPushButton* btn {t->bindNew<Wt::WPushButton>("btn-go-home", Wt::WString::tr("Lms.Error.go-home"))};
|
Wt::WPushButton* btn {t->bindNew<Wt::WPushButton>("btn-go-home", Wt::WString::tr("Lms.Error.go-home"))};
|
||||||
btn->clicked().connect([this]()
|
btn->clicked().connect([this]()
|
||||||
{
|
{
|
||||||
setConfirmCloseMessage("");
|
|
||||||
redirect(".");
|
redirect(".");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -322,7 +324,6 @@ LmsApplication::handleException(LmsApplicationException& e)
|
|||||||
void
|
void
|
||||||
LmsApplication::goHomeAndQuit()
|
LmsApplication::goHomeAndQuit()
|
||||||
{
|
{
|
||||||
setConfirmCloseMessage("");
|
|
||||||
WApplication::quit("");
|
WApplication::quit("");
|
||||||
redirect(".");
|
redirect(".");
|
||||||
}
|
}
|
||||||
@@ -394,7 +395,6 @@ LmsApplication::handleUserLoggedOut()
|
|||||||
getUser().modify()->clearAuthTokens();
|
getUser().modify()->clearAuthTokens();
|
||||||
}
|
}
|
||||||
|
|
||||||
setConfirmCloseMessage("");
|
|
||||||
goHomeAndQuit();
|
goHomeAndQuit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -426,7 +426,8 @@ LmsApplication::createHome()
|
|||||||
_audioTranscodeResource = std::make_shared<AudioTranscodeResource>();
|
_audioTranscodeResource = std::make_shared<AudioTranscodeResource>();
|
||||||
_audioFileResource = std::make_shared<AudioFileResource>();
|
_audioFileResource = std::make_shared<AudioFileResource>();
|
||||||
|
|
||||||
setConfirmCloseMessage(Wt::WString::tr("Lms.quit-confirm"));
|
declareJavaScriptFunction("onLoadCover", "function(id) { id.className += \" Lms-cover-loaded\"}");
|
||||||
|
doJavaScript("$('body').tooltip({ selector: '[data-toggle=\"tooltip\"]'})");
|
||||||
|
|
||||||
Wt::WTemplate* main {root()->addWidget(std::make_unique<Wt::WTemplate>(Wt::WString::tr("Lms.template")))};
|
Wt::WTemplate* main {root()->addWidget(std::make_unique<Wt::WTemplate>(Wt::WString::tr("Lms.template")))};
|
||||||
|
|
||||||
@@ -467,7 +468,7 @@ LmsApplication::createHome()
|
|||||||
mainStack->setAttributeValue("style", "overflow-x:visible;overflow-y:visible;");
|
mainStack->setAttributeValue("style", "overflow-x:visible;overflow-y:visible;");
|
||||||
|
|
||||||
Explore* explore = mainStack->addNew<Explore>(filters);
|
Explore* explore = mainStack->addNew<Explore>(filters);
|
||||||
PlayQueue* playqueue = mainStack->addNew<PlayQueue>();
|
_playQueue = mainStack->addNew<PlayQueue>();
|
||||||
auto* search {mainStack->addNew<SearchView>(filters)};
|
auto* search {mainStack->addNew<SearchView>(filters)};
|
||||||
mainStack->addNew<SettingsView>();
|
mainStack->addNew<SettingsView>();
|
||||||
|
|
||||||
@@ -490,37 +491,31 @@ LmsApplication::createHome()
|
|||||||
mainStack->addNew<UserView>();
|
mainStack->addNew<UserView>();
|
||||||
}
|
}
|
||||||
|
|
||||||
explore->tracksAdd.connect([=] (const std::vector<Database::IdType>& trackIds)
|
explore->tracksAction.connect([this] (PlayQueueAction action, const std::vector<Database::IdType>& trackIds)
|
||||||
{
|
{
|
||||||
playqueue->addTracks(trackIds);
|
_playQueue->processTracks(action, trackIds);
|
||||||
});
|
});
|
||||||
|
|
||||||
explore->tracksPlay.connect([=] (const std::vector<Database::IdType>& trackIds)
|
|
||||||
{
|
|
||||||
playqueue->playTracks(trackIds);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
// Events from MediaPlayer
|
// Events from MediaPlayer
|
||||||
_mediaPlayer->playNext.connect([=]
|
_mediaPlayer->playNext.connect([this]
|
||||||
{
|
{
|
||||||
playqueue->playNext();
|
_playQueue->playNext();
|
||||||
});
|
});
|
||||||
_mediaPlayer->playPrevious.connect([=]
|
_mediaPlayer->playPrevious.connect([this]
|
||||||
{
|
{
|
||||||
playqueue->playPrevious();
|
_playQueue->playPrevious();
|
||||||
});
|
});
|
||||||
_mediaPlayer->playbackEnded.connect([=]
|
_mediaPlayer->playbackEnded.connect([this]
|
||||||
{
|
{
|
||||||
playqueue->playNext();
|
_playQueue->playNext();
|
||||||
});
|
});
|
||||||
|
|
||||||
playqueue->trackSelected.connect([=] (Database::IdType trackId, bool play, float replayGain)
|
_playQueue->trackSelected.connect([this] (Database::IdType trackId, bool play, float replayGain)
|
||||||
{
|
{
|
||||||
_mediaPlayer->loadTrack(trackId, play, replayGain);
|
_mediaPlayer->loadTrack(trackId, play, replayGain);
|
||||||
});
|
});
|
||||||
|
|
||||||
playqueue->trackUnselected.connect([=] ()
|
_playQueue->trackUnselected.connect([this] ()
|
||||||
{
|
{
|
||||||
_mediaPlayer->stop();
|
_mediaPlayer->stop();
|
||||||
});
|
});
|
||||||
@@ -579,7 +574,6 @@ LmsApplication::createHome()
|
|||||||
// Only one active session by user
|
// Only one active session by user
|
||||||
if (!LmsApp->isUserDemo())
|
if (!LmsApp->isUserDemo())
|
||||||
{
|
{
|
||||||
setConfirmCloseMessage("");
|
|
||||||
quit(Wt::WString::tr("Lms.quit-other-session"));
|
quit(Wt::WString::tr("Lms.quit-other-session"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ class Auth;
|
|||||||
class ImageResource;
|
class ImageResource;
|
||||||
class LmsApplicationException;
|
class LmsApplicationException;
|
||||||
class MediaPlayer;
|
class MediaPlayer;
|
||||||
|
class PlayQueue;
|
||||||
|
|
||||||
// Events that can be listen from anywhere in the application
|
// Events that can be listen from anywhere in the application
|
||||||
struct Events
|
struct Events
|
||||||
@@ -100,7 +101,8 @@ class LmsApplication : public Wt::WApplication
|
|||||||
static std::unique_ptr<Wt::WAnchor> createReleaseAnchor(Wt::Dbo::ptr<Database::Release> release, bool addText = true);
|
static std::unique_ptr<Wt::WAnchor> createReleaseAnchor(Wt::Dbo::ptr<Database::Release> release, bool addText = true);
|
||||||
static std::unique_ptr<Wt::WText> createCluster(Wt::Dbo::ptr<Database::Cluster> cluster, bool canDelete = false);
|
static std::unique_ptr<Wt::WText> createCluster(Wt::Dbo::ptr<Database::Cluster> cluster, bool canDelete = false);
|
||||||
|
|
||||||
MediaPlayer* getMediaPlayer() const { return _mediaPlayer; }
|
MediaPlayer& getMediaPlayer() const { return *_mediaPlayer; }
|
||||||
|
PlayQueue& getPlayQueue() const { return *_playQueue; }
|
||||||
|
|
||||||
// Signal emitted just before the session ends (user may already be logged out)
|
// Signal emitted just before the session ends (user may already be logged out)
|
||||||
Wt::Signal<>& preQuit() { return _preQuit; }
|
Wt::Signal<>& preQuit() { return _preQuit; }
|
||||||
@@ -124,13 +126,14 @@ class LmsApplication : public Wt::WApplication
|
|||||||
Wt::Signal<> _preQuit;
|
Wt::Signal<> _preQuit;
|
||||||
Database::Session _dbSession;
|
Database::Session _dbSession;
|
||||||
LmsApplicationGroupContainer& _appGroups;
|
LmsApplicationGroupContainer& _appGroups;
|
||||||
Events _events;
|
Events _events;
|
||||||
std::optional<Database::IdType> _userId;
|
std::optional<Database::IdType> _userId;
|
||||||
std::optional<bool> _userAuthStrong;
|
std::optional<bool> _userAuthStrong;
|
||||||
std::shared_ptr<AudioTranscodeResource> _audioTranscodeResource;
|
std::shared_ptr<AudioTranscodeResource> _audioTranscodeResource;
|
||||||
std::shared_ptr<AudioFileResource> _audioFileResource;
|
std::shared_ptr<AudioFileResource> _audioFileResource;
|
||||||
std::shared_ptr<ImageResource> _imageResource;
|
std::shared_ptr<ImageResource> _imageResource;
|
||||||
MediaPlayer* _mediaPlayer {};
|
MediaPlayer* _mediaPlayer {};
|
||||||
|
PlayQueue* _playQueue {};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -255,9 +255,8 @@ MediaPlayer::loadTrack(Database::IdType trackId, bool play, float replayGain)
|
|||||||
<< " artist: \"" << (!artists.empty() ? StringUtils::jsEscape(artists.front()->getName()) : "") << "\","
|
<< " artist: \"" << (!artists.empty() ? StringUtils::jsEscape(artists.front()->getName()) : "") << "\","
|
||||||
<< " release: \"" << (track->getRelease() ? StringUtils::jsEscape(track->getRelease()->getName()) : "") << "\","
|
<< " release: \"" << (track->getRelease() ? StringUtils::jsEscape(track->getRelease()->getName()) : "") << "\","
|
||||||
<< " artwork: ["
|
<< " artwork: ["
|
||||||
<< " { src: \"" << LmsApp->getImageResource()->getTrackUrl(trackId, 96) << "\", sizes: \"96x96\", type: \"" << imgResourceMimeType << "\" },"
|
<< " { src: \"" << LmsApp->getImageResource()->getTrackUrl(trackId, ImageResource::Size::Small) << "\", sizes: \"128x128\", type: \"" << imgResourceMimeType << "\" },"
|
||||||
<< " { src: \"" << LmsApp->getImageResource()->getTrackUrl(trackId, 256) << "\", sizes: \"256x256\", type: \"" << imgResourceMimeType << "\" },"
|
<< " { src: \"" << LmsApp->getImageResource()->getTrackUrl(trackId, ImageResource::Size::Large) << "\", sizes: \"512x512\", type: \"" << imgResourceMimeType << "\" },"
|
||||||
<< " { src: \"" << LmsApp->getImageResource()->getTrackUrl(trackId, 512) << "\", sizes: \"512x512\", type: \"" << imgResourceMimeType << "\" },"
|
|
||||||
<< " ]"
|
<< " ]"
|
||||||
<< "};";
|
<< "};";
|
||||||
oss << "LMS.mediaplayer.loadTrack(params, " << (play ? "true" : "false") << ")"; // true to autoplay
|
oss << "LMS.mediaplayer.loadTrack(params, " << (play ? "true" : "false") << ")"; // true to autoplay
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "PlayQueueView.hpp"
|
#include "PlayQueue.hpp"
|
||||||
|
|
||||||
#include <Wt/WText.h>
|
#include <Wt/WText.h>
|
||||||
#include <Wt/WText.h>
|
#include <Wt/WText.h>
|
||||||
@@ -50,8 +50,15 @@ PlayQueue::PlayQueue()
|
|||||||
_radioMode = LmsApp->getUser()->isRadioSet();
|
_radioMode = LmsApp->getUser()->isRadioSet();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto setToolTip = [](Wt::WWidget& widget, Wt::WString title)
|
||||||
|
{
|
||||||
|
widget.setAttributeValue("data-toggle", "tooltip");
|
||||||
|
widget.setAttributeValue("data-placement", "bottom");
|
||||||
|
widget.setAttributeValue("title", std::move(title));
|
||||||
|
};
|
||||||
|
|
||||||
Wt::WText* clearBtn = bindNew<Wt::WText>("clear-btn", Wt::WString::tr("Lms.PlayQueue.template.clear-btn"), Wt::TextFormat::XHTML);
|
Wt::WText* clearBtn = bindNew<Wt::WText>("clear-btn", Wt::WString::tr("Lms.PlayQueue.template.clear-btn"), Wt::TextFormat::XHTML);
|
||||||
clearBtn->setToolTip(Wt::WString::tr("Lms.PlayQueue.clear"));
|
setToolTip(*clearBtn, Wt::WString::tr("Lms.PlayQueue.clear"));
|
||||||
clearBtn->clicked().connect([=]
|
clearBtn->clicked().connect([=]
|
||||||
{
|
{
|
||||||
clearTracks();
|
clearTracks();
|
||||||
@@ -68,7 +75,7 @@ PlayQueue::PlayQueue()
|
|||||||
});
|
});
|
||||||
|
|
||||||
Wt::WText* shuffleBtn = bindNew<Wt::WText>("shuffle-btn", Wt::WString::tr("Lms.PlayQueue.template.shuffle-btn"), Wt::TextFormat::XHTML);
|
Wt::WText* shuffleBtn = bindNew<Wt::WText>("shuffle-btn", Wt::WString::tr("Lms.PlayQueue.template.shuffle-btn"), Wt::TextFormat::XHTML);
|
||||||
shuffleBtn->setToolTip(Wt::WString::tr("Lms.PlayQueue.shuffle"));
|
setToolTip(*shuffleBtn, Wt::WString::tr("Lms.PlayQueue.shuffle"));
|
||||||
shuffleBtn->clicked().connect([=]
|
shuffleBtn->clicked().connect([=]
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
@@ -87,7 +94,7 @@ PlayQueue::PlayQueue()
|
|||||||
});
|
});
|
||||||
|
|
||||||
_repeatBtn = bindNew<Wt::WText>("repeat-btn", Wt::WString::tr("Lms.PlayQueue.template.repeat-btn"), Wt::TextFormat::XHTML);
|
_repeatBtn = bindNew<Wt::WText>("repeat-btn", Wt::WString::tr("Lms.PlayQueue.template.repeat-btn"), Wt::TextFormat::XHTML);
|
||||||
_repeatBtn->setToolTip(Wt::WString::tr("Lms.PlayQueue.repeat"));
|
setToolTip(*_repeatBtn, Wt::WString::tr("Lms.PlayQueue.repeat"));
|
||||||
_repeatBtn->clicked().connect([=]
|
_repeatBtn->clicked().connect([=]
|
||||||
{
|
{
|
||||||
_repeatAll = !_repeatAll;
|
_repeatAll = !_repeatAll;
|
||||||
@@ -101,7 +108,7 @@ PlayQueue::PlayQueue()
|
|||||||
updateRepeatBtn();
|
updateRepeatBtn();
|
||||||
|
|
||||||
_radioBtn = bindNew<Wt::WText>("radio-btn", Wt::WString::tr("Lms.PlayQueue.template.radio-btn"));
|
_radioBtn = bindNew<Wt::WText>("radio-btn", Wt::WString::tr("Lms.PlayQueue.template.radio-btn"));
|
||||||
_radioBtn->setToolTip(Wt::WString::tr("Lms.PlayQueue.radio-mode"));
|
setToolTip(*_radioBtn, Wt::WString::tr("Lms.PlayQueue.radio-mode"));
|
||||||
_radioBtn->clicked().connect([=]
|
_radioBtn->clicked().connect([=]
|
||||||
{
|
{
|
||||||
_radioMode = !_radioMode;
|
_radioMode = !_radioMode;
|
||||||
@@ -136,7 +143,7 @@ PlayQueue::PlayQueue()
|
|||||||
|
|
||||||
if (!LmsApp->getUser()->isDemo())
|
if (!LmsApp->getUser()->isDemo())
|
||||||
{
|
{
|
||||||
LmsApp->getMediaPlayer()->settingsLoaded.connect([=]
|
LmsApp->getMediaPlayer().settingsLoaded.connect([=]
|
||||||
{
|
{
|
||||||
if (_mediaPlayerSettingsLoaded)
|
if (_mediaPlayerSettingsLoaded)
|
||||||
return;
|
return;
|
||||||
@@ -327,20 +334,27 @@ PlayQueue::enqueueTrack(Database::IdType trackId)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
PlayQueue::addTracks(const std::vector<Database::IdType>& trackIds)
|
PlayQueue::processTracks(PlayQueueAction action, const std::vector<Database::IdType>& trackIds)
|
||||||
{
|
{
|
||||||
enqueueTracks(trackIds);
|
switch (action)
|
||||||
LmsApp->notifyMsg(MsgType::Info, Wt::WString::trn("Lms.PlayQueue.nb-tracks-added", trackIds.size()).arg(trackIds.size()), std::chrono::milliseconds(2000));
|
{
|
||||||
}
|
case PlayQueueAction::AddLast:
|
||||||
|
enqueueTracks(trackIds);
|
||||||
|
LmsApp->notifyMsg(MsgType::Info, Wt::WString::trn("Lms.PlayQueue.nb-tracks-added", trackIds.size()).arg(trackIds.size()), std::chrono::milliseconds(2000));
|
||||||
|
break;
|
||||||
|
|
||||||
void
|
case PlayQueueAction::AddNext:
|
||||||
PlayQueue::playTracks(const std::vector<Database::IdType>& trackIds)
|
break;
|
||||||
{
|
|
||||||
clearTracks();
|
|
||||||
enqueueTracks(trackIds);
|
|
||||||
loadTrack(0, true);
|
|
||||||
|
|
||||||
LmsApp->notifyMsg(MsgType::Info, Wt::WString::trn("Lms.PlayQueue.nb-tracks-playing", trackIds.size()).arg(trackIds.size()), std::chrono::milliseconds(2000));
|
case PlayQueueAction::Play:
|
||||||
|
clearTracks();
|
||||||
|
enqueueTracks(trackIds);
|
||||||
|
loadTrack(0, true);
|
||||||
|
|
||||||
|
LmsApp->notifyMsg(MsgType::Info, Wt::WString::trn("Lms.PlayQueue.nb-tracks-playing", trackIds.size()).arg(trackIds.size()), std::chrono::milliseconds(2000));
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -384,16 +398,18 @@ PlayQueue::addSome()
|
|||||||
{
|
{
|
||||||
Wt::WAnchor* anchor = entry->bindWidget("cover", LmsApplication::createReleaseAnchor(release, false));
|
Wt::WAnchor* anchor = entry->bindWidget("cover", LmsApplication::createReleaseAnchor(release, false));
|
||||||
auto cover = std::make_unique<Wt::WImage>();
|
auto cover = std::make_unique<Wt::WImage>();
|
||||||
cover->setImageLink(LmsApp->getImageResource()->getReleaseUrl(release.id(), 96));
|
cover->setImageLink(LmsApp->getImageResource()->getReleaseUrl(release.id(), ImageResource::Size::Large));
|
||||||
cover->setStyleClass("Lms-cover-small");
|
cover->setStyleClass("Lms-cover");
|
||||||
|
cover->setAttributeValue("onload", LmsApp->javaScriptClass() + ".onLoadCover(this)");
|
||||||
anchor->setImage(std::move(cover));
|
anchor->setImage(std::move(cover));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
auto cover = entry->bindNew<Wt::WImage>("cover");
|
auto cover = entry->bindNew<Wt::WImage>("cover");
|
||||||
cover->setImageLink(LmsApp->getImageResource()->getTrackUrl(track.id(), 96));
|
cover->setImageLink(LmsApp->getImageResource()->getTrackUrl(track.id(), ImageResource::Size::Large));
|
||||||
cover->setStyleClass("Lms-cover-small");
|
cover->setStyleClass("Lms-cover");
|
||||||
|
cover->setAttributeValue("onload", LmsApp->javaScriptClass() + ".onLoadCover(this)");
|
||||||
}
|
}
|
||||||
|
|
||||||
entry->bindString("duration", trackDurationToString(track->getDuration()), Wt::TextFormat::Plain);
|
entry->bindString("duration", trackDurationToString(track->getDuration()), Wt::TextFormat::Plain);
|
||||||
@@ -444,7 +460,7 @@ PlayQueue::enqueueRadioTrack()
|
|||||||
std::optional<float>
|
std::optional<float>
|
||||||
PlayQueue::getReplayGain(std::size_t pos, const Database::Track::pointer& track) const
|
PlayQueue::getReplayGain(std::size_t pos, const Database::Track::pointer& track) const
|
||||||
{
|
{
|
||||||
const auto& settings {LmsApp->getMediaPlayer()->getSettings()};
|
const auto& settings {LmsApp->getMediaPlayer().getSettings()};
|
||||||
if (!settings)
|
if (!settings)
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
|
||||||
@@ -27,6 +27,7 @@
|
|||||||
#include <Wt/WText.h>
|
#include <Wt/WText.h>
|
||||||
|
|
||||||
#include "database/Types.hpp"
|
#include "database/Types.hpp"
|
||||||
|
#include "PlayQueueAction.hpp"
|
||||||
|
|
||||||
namespace Similarity {
|
namespace Similarity {
|
||||||
class Finder;
|
class Finder;
|
||||||
@@ -44,8 +45,7 @@ class PlayQueue : public Wt::WTemplate
|
|||||||
public:
|
public:
|
||||||
PlayQueue();
|
PlayQueue();
|
||||||
|
|
||||||
void addTracks(const std::vector<Database::IdType>& trackIds);
|
void processTracks(PlayQueueAction action, const std::vector<Database::IdType>& trackIds);
|
||||||
void playTracks(const std::vector<Database::IdType>& trackIds);
|
|
||||||
|
|
||||||
// play the next track in the queue
|
// play the next track in the queue
|
||||||
void playNext();
|
void playNext();
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 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
|
||||||
|
|
||||||
|
namespace UserInterface
|
||||||
|
{
|
||||||
|
|
||||||
|
enum class PlayQueueAction
|
||||||
|
{
|
||||||
|
Play,
|
||||||
|
AddNext,
|
||||||
|
AddLast,
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -156,7 +156,7 @@ class SettingsModel : public Wt::WFormModel
|
|||||||
settings.replayGain.preAmpGain = Wt::asNumber(value(ReplayGainPreAmpGainField));
|
settings.replayGain.preAmpGain = Wt::asNumber(value(ReplayGainPreAmpGainField));
|
||||||
settings.replayGain.preAmpGainIfNoInfo = Wt::asNumber(value(ReplayGainPreAmpGainIfNoInfoField));
|
settings.replayGain.preAmpGainIfNoInfo = Wt::asNumber(value(ReplayGainPreAmpGainIfNoInfoField));
|
||||||
|
|
||||||
LmsApp->getMediaPlayer()->setSettings(settings);
|
LmsApp->getMediaPlayer().setSettings(settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -191,7 +191,7 @@ class SettingsModel : public Wt::WFormModel
|
|||||||
setValue(DarkModeField, user->getUITheme() == User::UITheme::Dark);
|
setValue(DarkModeField, user->getUITheme() == User::UITheme::Dark);
|
||||||
|
|
||||||
{
|
{
|
||||||
const auto settings {*LmsApp->getMediaPlayer()->getSettings()};
|
const auto settings {*LmsApp->getMediaPlayer().getSettings()};
|
||||||
|
|
||||||
auto transcodeModeRow {_transcodeModeModel->getRowFromValue(settings.transcode.mode)};
|
auto transcodeModeRow {_transcodeModeModel->getRowFromValue(settings.transcode.mode)};
|
||||||
if (transcodeModeRow)
|
if (transcodeModeRow)
|
||||||
@@ -350,7 +350,7 @@ SettingsView::SettingsView()
|
|||||||
refreshView();
|
refreshView();
|
||||||
});
|
});
|
||||||
|
|
||||||
LmsApp->getMediaPlayer()->settingsLoaded.connect([=]()
|
LmsApp->getMediaPlayer().settingsLoaded.connect([=]()
|
||||||
{
|
{
|
||||||
refreshView();
|
refreshView();
|
||||||
});
|
});
|
||||||
@@ -367,7 +367,7 @@ SettingsView::refreshView()
|
|||||||
clear();
|
clear();
|
||||||
|
|
||||||
// Hack to wait for the audio player know the settings applied
|
// Hack to wait for the audio player know the settings applied
|
||||||
if (!LmsApp->getMediaPlayer()->getSettings())
|
if (!LmsApp->getMediaPlayer().getSettings())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto t {addNew<Wt::WTemplateFormView>(Wt::WString::tr("Lms.Settings.template"))};
|
auto t {addNew<Wt::WTemplateFormView>(Wt::WString::tr("Lms.Settings.template"))};
|
||||||
@@ -426,7 +426,7 @@ SettingsView::refreshView()
|
|||||||
t->updateModel(model.get());
|
t->updateModel(model.get());
|
||||||
t->updateView(model.get());
|
t->updateView(model.get());
|
||||||
});
|
});
|
||||||
if (LmsApp->getMediaPlayer()->getSettings()->transcode.mode == MediaPlayer::Settings::Transcode::Mode::Never)
|
if (LmsApp->getMediaPlayer().getSettings()->transcode.mode == MediaPlayer::Settings::Transcode::Mode::Never)
|
||||||
{
|
{
|
||||||
model->setReadOnly(SettingsModel::TranscodeFormatField, true);
|
model->setReadOnly(SettingsModel::TranscodeFormatField, true);
|
||||||
model->setReadOnly(SettingsModel::TranscodeBitrateField, true);
|
model->setReadOnly(SettingsModel::TranscodeBitrateField, true);
|
||||||
@@ -456,7 +456,7 @@ SettingsView::refreshView()
|
|||||||
t->updateModel(model.get());
|
t->updateModel(model.get());
|
||||||
t->updateView(model.get());
|
t->updateView(model.get());
|
||||||
});
|
});
|
||||||
if (LmsApp->getMediaPlayer()->getSettings()->replayGain.mode == MediaPlayer::Settings::ReplayGain::Mode::None)
|
if (LmsApp->getMediaPlayer().getSettings()->replayGain.mode == MediaPlayer::Settings::ReplayGain::Mode::None)
|
||||||
{
|
{
|
||||||
model->setReadOnly(SettingsModel::SettingsModel::ReplayGainPreAmpGainField, true);
|
model->setReadOnly(SettingsModel::SettingsModel::ReplayGainPreAmpGainField, true);
|
||||||
model->setReadOnly(SettingsModel::SettingsModel::ReplayGainPreAmpGainIfNoInfoField, true);
|
model->setReadOnly(SettingsModel::SettingsModel::ReplayGainPreAmpGainIfNoInfoField, true);
|
||||||
|
|||||||
@@ -32,11 +32,11 @@
|
|||||||
#include "utils/String.hpp"
|
#include "utils/String.hpp"
|
||||||
|
|
||||||
#include "resource/ImageResource.hpp"
|
#include "resource/ImageResource.hpp"
|
||||||
|
|
||||||
#include "ArtistListHelpers.hpp"
|
#include "ArtistListHelpers.hpp"
|
||||||
|
#include "Filters.hpp"
|
||||||
#include "LmsApplication.hpp"
|
#include "LmsApplication.hpp"
|
||||||
#include "LmsApplicationException.hpp"
|
#include "LmsApplicationException.hpp"
|
||||||
#include "Filters.hpp"
|
#include "ReleaseListHelpers.hpp"
|
||||||
|
|
||||||
using namespace Database;
|
using namespace Database;
|
||||||
|
|
||||||
@@ -77,10 +77,11 @@ Artist::refreshView()
|
|||||||
|
|
||||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||||
|
|
||||||
const Database::Artist::pointer artist = Database::Artist::getById(LmsApp->getDbSession(), *artistId);
|
const Database::Artist::pointer artist {Database::Artist::getById(LmsApp->getDbSession(), *artistId)};
|
||||||
if (!artist)
|
if (!artist)
|
||||||
throw ArtistNotFoundException {*artistId};
|
throw ArtistNotFoundException {*artistId};
|
||||||
|
|
||||||
|
refreshLinks(artist);
|
||||||
refreshSimilarArtists(similarArtistIds);
|
refreshSimilarArtists(similarArtistIds);
|
||||||
|
|
||||||
Wt::WContainerWidget* clusterContainers = bindNew<Wt::WContainerWidget>("clusters");
|
Wt::WContainerWidget* clusterContainers = bindNew<Wt::WContainerWidget>("clusters");
|
||||||
@@ -109,7 +110,7 @@ Artist::refreshView()
|
|||||||
|
|
||||||
playBtn->clicked().connect([=]
|
playBtn->clicked().connect([=]
|
||||||
{
|
{
|
||||||
artistsPlay.emit({*artistId});
|
artistsAction.emit(PlayQueueAction::Play, {*artistId});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,7 +119,7 @@ Artist::refreshView()
|
|||||||
|
|
||||||
addBtn->clicked().connect([=]
|
addBtn->clicked().connect([=]
|
||||||
{
|
{
|
||||||
artistsAdd.emit({*artistId});
|
artistsAction.emit(PlayQueueAction::AddLast, {*artistId});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,79 +128,14 @@ Artist::refreshView()
|
|||||||
auto releases = artist->getReleases(_filters->getClusterIds());
|
auto releases = artist->getReleases(_filters->getClusterIds());
|
||||||
for (const auto& release : releases)
|
for (const auto& release : releases)
|
||||||
{
|
{
|
||||||
releasesContainer->addWidget(createRelease(artist, release));
|
releasesContainer->addWidget(ReleaseListHelpers::createEntryForArtist(release, artist));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Wt::WTemplate>
|
|
||||||
Artist::createRelease(const Database::Artist::pointer& artist, const Release::pointer& release)
|
|
||||||
{
|
|
||||||
auto releaseId = release.id();
|
|
||||||
|
|
||||||
auto entry = std::make_unique<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Artist.template.entry"));
|
|
||||||
entry->addFunction("tr", Wt::WTemplate::Functions::tr);
|
|
||||||
|
|
||||||
{
|
|
||||||
Wt::WAnchor* anchor = entry->bindWidget("cover", LmsApplication::createReleaseAnchor(release, false));
|
|
||||||
|
|
||||||
auto cover = std::make_unique<Wt::WImage>();
|
|
||||||
cover->setImageLink(LmsApp->getImageResource()->getReleaseUrl(release.id(), 128));
|
|
||||||
cover->setStyleClass("Lms-cover");
|
|
||||||
anchor->setImage(std::move(cover));
|
|
||||||
}
|
|
||||||
|
|
||||||
entry->bindWidget("name", LmsApplication::createReleaseAnchor(release));
|
|
||||||
|
|
||||||
auto artists {release->getReleaseArtists()};
|
|
||||||
if (artists.empty())
|
|
||||||
artists = release->getArtists();
|
|
||||||
|
|
||||||
bool isSameArtist {(std::find(std::cbegin(artists), std::cend(artists), artist) != artists.end())};
|
|
||||||
|
|
||||||
if (artists.size() > 1)
|
|
||||||
{
|
|
||||||
entry->setCondition("if-has-artist", true);
|
|
||||||
entry->bindNew<Wt::WText>("artist", Wt::WString::tr("Lms.Explore.various-artists"));
|
|
||||||
}
|
|
||||||
else if (artists.size() == 1 && !isSameArtist)
|
|
||||||
{
|
|
||||||
entry->setCondition("if-has-artist", true);
|
|
||||||
entry->bindWidget("artist", LmsApplication::createArtistAnchor(artists.front()));
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<int> year {release->getReleaseYear()};
|
|
||||||
if (year)
|
|
||||||
{
|
|
||||||
entry->setCondition("if-has-year", true);
|
|
||||||
entry->bindInt("year", *year);
|
|
||||||
|
|
||||||
std::optional<int> originalYear {release->getReleaseYear(true)};
|
|
||||||
if (originalYear && *originalYear != *year)
|
|
||||||
{
|
|
||||||
entry->setCondition("if-has-orig-year", true);
|
|
||||||
entry->bindInt("orig-year", *originalYear);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Wt::WText* playBtn = entry->bindNew<Wt::WText>("play-btn", Wt::WString::tr("Lms.Explore.template.play-btn"), Wt::TextFormat::XHTML);
|
|
||||||
playBtn->clicked().connect([=]
|
|
||||||
{
|
|
||||||
releasesPlay.emit({releaseId});
|
|
||||||
});
|
|
||||||
|
|
||||||
Wt::WText* addBtn = entry->bindNew<Wt::WText>("add-btn", Wt::WString::tr("Lms.Explore.template.add-btn"), Wt::TextFormat::XHTML);
|
|
||||||
addBtn->clicked().connect([=]
|
|
||||||
{
|
|
||||||
releasesAdd.emit({releaseId});
|
|
||||||
});
|
|
||||||
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
Artist::refreshSimilarArtists(const std::vector<Database::IdType>& similarArtistsId)
|
Artist::refreshSimilarArtists(const std::vector<Database::IdType>& similarArtistsId)
|
||||||
{
|
{
|
||||||
auto* similarArtistsContainer {bindNew<Wt::WContainerWidget>("similar-artists")};
|
Wt::WContainerWidget* similarArtistsContainer {bindNew<Wt::WContainerWidget>("similar-artists")};
|
||||||
|
|
||||||
for (Database::IdType artistId : similarArtistsId)
|
for (Database::IdType artistId : similarArtistsId)
|
||||||
{
|
{
|
||||||
@@ -211,5 +147,20 @@ Artist::refreshSimilarArtists(const std::vector<Database::IdType>& similarArtist
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Artist::refreshLinks(const Database::Artist::pointer& artist)
|
||||||
|
{
|
||||||
|
const auto mbid {artist->getMBID()};
|
||||||
|
if (mbid)
|
||||||
|
{
|
||||||
|
setCondition("if-has-mbid", true);
|
||||||
|
|
||||||
|
Wt::WLink link {"https://musicbrainz.org/artist/" + std::string {mbid->getAsString()}};
|
||||||
|
link.setTarget(Wt::LinkTarget::NewWindow);
|
||||||
|
|
||||||
|
bindNew<Wt::WAnchor>("mbid-link", link, Wt::WString::tr("Lms.Explore.musicbrainz-artist"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace UserInterface
|
} // namespace UserInterface
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
#include <Wt/WTemplate.h>
|
#include <Wt/WTemplate.h>
|
||||||
|
|
||||||
#include "database/Types.hpp"
|
#include "database/Types.hpp"
|
||||||
|
#include "PlayQueueAction.hpp"
|
||||||
|
|
||||||
namespace Database
|
namespace Database
|
||||||
{
|
{
|
||||||
@@ -41,15 +42,12 @@ class Artist : public Wt::WTemplate
|
|||||||
public:
|
public:
|
||||||
Artist(Filters* filters);
|
Artist(Filters* filters);
|
||||||
|
|
||||||
Wt::Signal<const std::vector<Database::IdType>&> artistsAdd;
|
Wt::Signal<PlayQueueAction, const std::vector<Database::IdType>&> artistsAction;
|
||||||
Wt::Signal<const std::vector<Database::IdType>&> artistsPlay;
|
|
||||||
|
|
||||||
Wt::Signal<const std::vector<Database::IdType>&> releasesAdd;
|
|
||||||
Wt::Signal<const std::vector<Database::IdType>&> releasesPlay;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void refreshView();
|
void refreshView();
|
||||||
void refreshSimilarArtists(const std::vector<Database::IdType>& similarArtistsId);
|
void refreshSimilarArtists(const std::vector<Database::IdType>& similarArtistsId);
|
||||||
|
void refreshLinks(const Wt::Dbo::ptr<Database::Artist>& artist);
|
||||||
|
|
||||||
std::unique_ptr<Wt::WTemplate> createRelease(const Wt::Dbo::ptr<Database::Artist>& artist, const Wt::Dbo::ptr<Database::Release>& release);
|
std::unique_ptr<Wt::WTemplate> createRelease(const Wt::Dbo::ptr<Database::Artist>& artist, const Wt::Dbo::ptr<Database::Release>& release);
|
||||||
|
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ Artists::getArtists(std::optional<Range> range, bool& moreResults)
|
|||||||
if (modeLimit)
|
if (modeLimit)
|
||||||
{
|
{
|
||||||
if (range)
|
if (range)
|
||||||
range->limit = std::min(*modeLimit - range->offset, range->offset + range->limit);
|
range->limit = std::min(*modeLimit - range->offset, range->limit);
|
||||||
else
|
else
|
||||||
range = Range {0, *modeLimit};
|
range = Range {0, *modeLimit};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ class Artists : public Wt::WTemplate
|
|||||||
{Mode::RecentlyPlayed, batchSize * 4},
|
{Mode::RecentlyPlayed, batchSize * 4},
|
||||||
{Mode::RecentlyAdded, batchSize * 2},
|
{Mode::RecentlyAdded, batchSize * 2},
|
||||||
{Mode::MostPlayed, batchSize * 2},
|
{Mode::MostPlayed, batchSize * 2},
|
||||||
{Mode::All, std::nullopt},
|
{Mode::All, batchSize * 50},
|
||||||
};
|
};
|
||||||
|
|
||||||
Mode _mode {defaultMode};
|
Mode _mode {defaultMode};
|
||||||
|
|||||||
@@ -88,27 +88,20 @@ Explore::Explore(Filters* filters)
|
|||||||
contentsStack->addWidget(std::move(artists));
|
contentsStack->addWidget(std::move(artists));
|
||||||
|
|
||||||
auto artist = std::make_unique<Artist>(_filters);
|
auto artist = std::make_unique<Artist>(_filters);
|
||||||
artist->artistsAdd.connect(this, &Explore::handleArtistsAdd);
|
artist->artistsAction.connect(this, &Explore::handleArtistsAction);
|
||||||
artist->artistsPlay.connect(this, &Explore::handleArtistsPlay);
|
|
||||||
artist->releasesAdd.connect(this, &Explore::handleReleasesAdd);
|
|
||||||
artist->releasesPlay.connect(this, &Explore::handleReleasesPlay);
|
|
||||||
contentsStack->addWidget(std::move(artist));
|
contentsStack->addWidget(std::move(artist));
|
||||||
|
|
||||||
auto releases = std::make_unique<Releases>(_filters);
|
auto releases = std::make_unique<Releases>(_filters);
|
||||||
releases->releasesAdd.connect(this, &Explore::handleReleasesAdd);
|
releases->releasesAction.connect(this, &Explore::handleReleasesAction);
|
||||||
releases->releasesPlay.connect(this, &Explore::handleReleasesPlay);
|
|
||||||
contentsStack->addWidget(std::move(releases));
|
contentsStack->addWidget(std::move(releases));
|
||||||
|
|
||||||
auto release = std::make_unique<Release>(_filters);
|
auto release = std::make_unique<Release>(_filters);
|
||||||
release->releasesAdd.connect(this, &Explore::handleReleasesAdd);
|
release->releasesAction.connect(this, &Explore::handleReleasesAction);
|
||||||
release->releasesPlay.connect(this, &Explore::handleReleasesPlay);
|
release->tracksAction.connect(this, &Explore::handleTracksAction);
|
||||||
release->tracksAdd.connect(this, &Explore::handleTracksAdd);
|
|
||||||
release->tracksPlay.connect(this, &Explore::handleTracksPlay);
|
|
||||||
contentsStack->addWidget(std::move(release));
|
contentsStack->addWidget(std::move(release));
|
||||||
|
|
||||||
auto tracks = std::make_unique<Tracks>(_filters);
|
auto tracks = std::make_unique<Tracks>(_filters);
|
||||||
tracks->tracksAdd.connect(this, &Explore::handleTracksAdd);
|
tracks->tracksAction.connect(this, &Explore::handleTracksAction);
|
||||||
tracks->tracksPlay.connect(this, &Explore::handleTracksPlay);
|
|
||||||
contentsStack->addWidget(std::move(tracks));
|
contentsStack->addWidget(std::move(tracks));
|
||||||
|
|
||||||
wApp->internalPathChanged().connect([=]
|
wApp->internalPathChanged().connect([=]
|
||||||
@@ -167,40 +160,23 @@ getReleasesTracks(Database::Session& session, const std::vector<Database::IdType
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Explore::handleArtistsAdd(const std::vector<Database::IdType>& artistsId)
|
Explore::handleArtistsAction(PlayQueueAction action, const std::vector<Database::IdType>& artistsId)
|
||||||
{
|
{
|
||||||
tracksAdd.emit(getArtistsTracks(LmsApp->getDbSession(), artistsId, _filters->getClusterIds()));
|
tracksAction.emit(action, getArtistsTracks(LmsApp->getDbSession(), artistsId, _filters->getClusterIds()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Explore::handleArtistsPlay(const std::vector<Database::IdType>& artistsId)
|
Explore::handleReleasesAction(PlayQueueAction action, const std::vector<Database::IdType>& releasesId)
|
||||||
{
|
{
|
||||||
tracksPlay.emit(getArtistsTracks(LmsApp->getDbSession(), artistsId, _filters->getClusterIds()));
|
tracksAction.emit(action, getReleasesTracks(LmsApp->getDbSession(), releasesId, _filters->getClusterIds()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Explore::handleReleasesAdd(const std::vector<Database::IdType>& releasesId)
|
Explore::handleTracksAction(PlayQueueAction action, const std::vector<Database::IdType>& tracksId)
|
||||||
{
|
{
|
||||||
tracksAdd.emit(getReleasesTracks(LmsApp->getDbSession(), releasesId, _filters->getClusterIds()));
|
tracksAction.emit(action, tracksId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
|
||||||
Explore::handleReleasesPlay(const std::vector<Database::IdType>& releasesId)
|
|
||||||
{
|
|
||||||
tracksPlay.emit(getReleasesTracks(LmsApp->getDbSession(), releasesId, _filters->getClusterIds()));
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Explore::handleTracksAdd(const std::vector<Database::IdType>& tracksId)
|
|
||||||
{
|
|
||||||
tracksAdd.emit(tracksId);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Explore::handleTracksPlay(const std::vector<Database::IdType>& tracksId)
|
|
||||||
{
|
|
||||||
tracksPlay.emit(tracksId);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace UserInterface
|
} // namespace UserInterface
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
#include <Wt/WTemplate.h>
|
#include <Wt/WTemplate.h>
|
||||||
|
|
||||||
#include "database/Types.hpp"
|
#include "database/Types.hpp"
|
||||||
|
#include "PlayQueueAction.hpp"
|
||||||
|
|
||||||
namespace UserInterface {
|
namespace UserInterface {
|
||||||
|
|
||||||
@@ -32,17 +33,13 @@ class Explore : public Wt::WTemplate
|
|||||||
public:
|
public:
|
||||||
Explore(Filters* filters);
|
Explore(Filters* filters);
|
||||||
|
|
||||||
Wt::Signal<std::vector<Database::IdType>> tracksAdd;
|
Wt::Signal<PlayQueueAction, const std::vector<Database::IdType>&> tracksAction;
|
||||||
Wt::Signal<std::vector<Database::IdType>> tracksPlay;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
void handleArtistsAdd(const std::vector<Database::IdType>& artistsId);
|
void handleArtistsAction(PlayQueueAction action, const std::vector<Database::IdType>& artistsId);
|
||||||
void handleArtistsPlay(const std::vector<Database::IdType>& artistsId);
|
void handleReleasesAction(PlayQueueAction action, const std::vector<Database::IdType>& releasesId);
|
||||||
void handleReleasesAdd(const std::vector<Database::IdType>& releasesId);
|
void handleTracksAction(PlayQueueAction action, const std::vector<Database::IdType>& tracksId);
|
||||||
void handleReleasesPlay(const std::vector<Database::IdType>& releasesId);
|
|
||||||
void handleTracksAdd(const std::vector<Database::IdType>& tracksId);
|
|
||||||
void handleTracksPlay(const std::vector<Database::IdType>& tracksId);
|
|
||||||
|
|
||||||
Filters* _filters {};
|
Filters* _filters {};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
#include <Wt/WImage.h>
|
#include <Wt/WImage.h>
|
||||||
#include <Wt/WText.h>
|
#include <Wt/WText.h>
|
||||||
|
|
||||||
|
#include "database/Artist.hpp"
|
||||||
#include "database/Release.hpp"
|
#include "database/Release.hpp"
|
||||||
#include "resource/ImageResource.hpp"
|
#include "resource/ImageResource.hpp"
|
||||||
|
|
||||||
@@ -33,36 +34,76 @@ using namespace Database;
|
|||||||
namespace UserInterface::ReleaseListHelpers
|
namespace UserInterface::ReleaseListHelpers
|
||||||
{
|
{
|
||||||
|
|
||||||
|
static
|
||||||
std::unique_ptr<Wt::WTemplate>
|
std::unique_ptr<Wt::WTemplate>
|
||||||
createEntrySmall(const Database::Release::pointer& release)
|
createEntryInternal(const Release::pointer& release, const std::string& templateKey, const Artist::pointer& artist, const bool showYear)
|
||||||
{
|
{
|
||||||
auto entry {std::make_unique<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Releases.template.entry-small"))};
|
auto entry {std::make_unique<Wt::WTemplate>(Wt::WString::tr(templateKey))};
|
||||||
|
|
||||||
entry->bindWidget("release-name", LmsApplication::createReleaseAnchor(release));
|
entry->bindWidget("release-name", LmsApplication::createReleaseAnchor(release));
|
||||||
|
|
||||||
Wt::WAnchor* anchor = entry->bindWidget("cover", LmsApplication::createReleaseAnchor(release, false));
|
Wt::WAnchor* anchor = entry->bindWidget("cover", LmsApplication::createReleaseAnchor(release, false));
|
||||||
auto cover = std::make_unique<Wt::WImage>();
|
auto cover = std::make_unique<Wt::WImage>();
|
||||||
cover->setImageLink(LmsApp->getImageResource()->getReleaseUrl(release.id(), 64));
|
cover->setImageLink(LmsApp->getImageResource()->getReleaseUrl(release.id(), ImageResource::Size::Large));
|
||||||
cover->setStyleClass("Lms-cover");
|
cover->setStyleClass("Lms-cover");
|
||||||
|
cover->setAttributeValue("onload", LmsApp->javaScriptClass() + ".onLoadCover(this)");
|
||||||
anchor->setImage(std::move(cover));
|
anchor->setImage(std::move(cover));
|
||||||
|
|
||||||
auto artists = release->getReleaseArtists();
|
auto artists = release->getReleaseArtists();
|
||||||
if (artists.empty())
|
if (artists.empty())
|
||||||
artists = release->getArtists();
|
artists = release->getArtists();
|
||||||
|
|
||||||
|
bool isSameArtist {(std::find(std::cbegin(artists), std::cend(artists), artist) != artists.end())};
|
||||||
|
|
||||||
if (artists.size() > 1)
|
if (artists.size() > 1)
|
||||||
{
|
{
|
||||||
entry->setCondition("if-has-artist", true);
|
entry->setCondition("if-has-artist", true);
|
||||||
entry->bindNew<Wt::WText>("artist-name", Wt::WString::tr("Lms.Explore.various-artists"));
|
entry->bindNew<Wt::WText>("artist-name", Wt::WString::tr("Lms.Explore.various-artists"));
|
||||||
}
|
}
|
||||||
else if (artists.size() == 1)
|
else if (artists.size() == 1 && !isSameArtist)
|
||||||
{
|
{
|
||||||
entry->setCondition("if-has-artist", true);
|
entry->setCondition("if-has-artist", true);
|
||||||
entry->bindWidget("artist-name", LmsApplication::createArtistAnchor(artists.front()));
|
entry->bindWidget("artist-name", LmsApplication::createArtistAnchor(artists.front()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showYear)
|
||||||
|
{
|
||||||
|
if (std::optional<int> year {release->getReleaseYear()})
|
||||||
|
{
|
||||||
|
entry->setCondition("if-has-year", true);
|
||||||
|
|
||||||
|
std::string strYear {std::to_string(*year)};
|
||||||
|
|
||||||
|
std::optional<int> originalYear {release->getReleaseYear(true)};
|
||||||
|
if (originalYear && *originalYear != *year)
|
||||||
|
{
|
||||||
|
strYear += " (" + std::to_string(*originalYear) + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
entry->bindString("year", strYear);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Wt::WTemplate>
|
||||||
|
createEntry(const Release::pointer& release, const Artist::pointer& artist, bool showYear)
|
||||||
|
{
|
||||||
|
return createEntryInternal(release, "Lms.Explore.Releases.template.entry-grid", artist, showYear);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Wt::WTemplate>
|
||||||
|
createEntry(const Release::pointer& release)
|
||||||
|
{
|
||||||
|
return createEntry(release, Artist::pointer {}, false /*year*/);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Wt::WTemplate>
|
||||||
|
createEntryForArtist(const Wt::Dbo::ptr<Database::Release>& release, const Wt::Dbo::ptr<Database::Artist>& artist)
|
||||||
|
{
|
||||||
|
return createEntry(release, artist, true);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace UserInterface
|
} // namespace UserInterface
|
||||||
|
|
||||||
|
|||||||
@@ -25,13 +25,13 @@
|
|||||||
|
|
||||||
namespace Database
|
namespace Database
|
||||||
{
|
{
|
||||||
|
class Artist;
|
||||||
class Release;
|
class Release;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace UserInterface::ReleaseListHelpers
|
namespace UserInterface::ReleaseListHelpers
|
||||||
{
|
{
|
||||||
|
std::unique_ptr<Wt::WTemplate> createEntry(const Wt::Dbo::ptr<Database::Release>& release);
|
||||||
std::unique_ptr<Wt::WTemplate> createEntrySmall(const Wt::Dbo::ptr<Database::Release>& release);
|
std::unique_ptr<Wt::WTemplate> createEntryForArtist(const Wt::Dbo::ptr<Database::Release>& release, const Wt::Dbo::ptr<Database::Artist>& artist);
|
||||||
|
|
||||||
} // namespace UserInterface
|
} // namespace UserInterface
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
#include <Wt/WAnchor.h>
|
#include <Wt/WAnchor.h>
|
||||||
#include <Wt/WApplication.h>
|
#include <Wt/WApplication.h>
|
||||||
#include <Wt/WImage.h>
|
#include <Wt/WImage.h>
|
||||||
|
#include <Wt/WPopupMenu.h>
|
||||||
#include <Wt/WTemplate.h>
|
#include <Wt/WTemplate.h>
|
||||||
#include <Wt/WText.h>
|
#include <Wt/WText.h>
|
||||||
|
|
||||||
@@ -75,7 +76,7 @@ Release::refreshView()
|
|||||||
if (!releaseId)
|
if (!releaseId)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const std::vector<Database::IdType> similarReleasesIds {ServiceProvider<Recommendation::IEngine>::get()->getSimilarReleases(LmsApp->getDbSession(), *releaseId, 5)};
|
const std::vector<Database::IdType> similarReleasesIds {ServiceProvider<Recommendation::IEngine>::get()->getSimilarReleases(LmsApp->getDbSession(), *releaseId, 6)};
|
||||||
|
|
||||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||||
|
|
||||||
@@ -84,6 +85,7 @@ Release::refreshView()
|
|||||||
throw ReleaseNotFoundException {*releaseId};
|
throw ReleaseNotFoundException {*releaseId};
|
||||||
|
|
||||||
refreshCopyright(release);
|
refreshCopyright(release);
|
||||||
|
refreshLinks(release);
|
||||||
refreshSimilarReleases(similarReleasesIds);
|
refreshSimilarReleases(similarReleasesIds);
|
||||||
|
|
||||||
bindString("name", Wt::WString::fromUTF8(release->getName()), Wt::TextFormat::Plain);
|
bindString("name", Wt::WString::fromUTF8(release->getName()), Wt::TextFormat::Plain);
|
||||||
@@ -122,8 +124,9 @@ Release::refreshView()
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
Wt::WImage* cover {bindNew<Wt::WImage>("cover", Wt::WLink(LmsApp->getImageResource()->getReleaseUrl(release.id(), 512)))};
|
Wt::WImage* cover {bindNew<Wt::WImage>("cover", Wt::WLink(LmsApp->getImageResource()->getReleaseUrl(release.id(), ImageResource::Size::Large)))};
|
||||||
cover->setStyleClass("Lms-cover-large");
|
cover->setStyleClass("Lms-cover-large");
|
||||||
|
cover->setAttributeValue("onload", LmsApp->javaScriptClass() + ".onLoadCover(this)");
|
||||||
}
|
}
|
||||||
|
|
||||||
Wt::WContainerWidget* clusterContainers {bindNew<Wt::WContainerWidget>("clusters")};
|
Wt::WContainerWidget* clusterContainers {bindNew<Wt::WContainerWidget>("clusters")};
|
||||||
@@ -149,7 +152,7 @@ Release::refreshView()
|
|||||||
Wt::WText* playBtn {bindNew<Wt::WText>("play-btn", Wt::WString::tr("Lms.Explore.template.play-btn"), Wt::TextFormat::XHTML)};
|
Wt::WText* playBtn {bindNew<Wt::WText>("play-btn", Wt::WString::tr("Lms.Explore.template.play-btn"), Wt::TextFormat::XHTML)};
|
||||||
playBtn->clicked().connect([=]
|
playBtn->clicked().connect([=]
|
||||||
{
|
{
|
||||||
releasesPlay.emit({*releaseId});
|
releasesAction.emit(PlayQueueAction::Play, {*releaseId});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,7 +160,7 @@ Release::refreshView()
|
|||||||
Wt::WText* addBtn {bindNew<Wt::WText>("add-btn", Wt::WString::tr("Lms.Explore.template.add-btn"), Wt::TextFormat::XHTML)};
|
Wt::WText* addBtn {bindNew<Wt::WText>("add-btn", Wt::WString::tr("Lms.Explore.template.add-btn"), Wt::TextFormat::XHTML)};
|
||||||
addBtn->clicked().connect([=]
|
addBtn->clicked().connect([=]
|
||||||
{
|
{
|
||||||
releasesAdd.emit({*releaseId});
|
releasesAction.emit(PlayQueueAction::AddLast, {*releaseId});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,27 +234,29 @@ Release::refreshView()
|
|||||||
Wt::WText* playBtn {entry->bindNew<Wt::WText>("play-btn", Wt::WString::tr("Lms.Explore.template.play-btn"), Wt::TextFormat::XHTML)};
|
Wt::WText* playBtn {entry->bindNew<Wt::WText>("play-btn", Wt::WString::tr("Lms.Explore.template.play-btn"), Wt::TextFormat::XHTML)};
|
||||||
playBtn->clicked().connect([=]()
|
playBtn->clicked().connect([=]()
|
||||||
{
|
{
|
||||||
tracksPlay.emit({trackId});
|
tracksAction.emit(PlayQueueAction::Play, {trackId});
|
||||||
});
|
});
|
||||||
|
|
||||||
Wt::WText* addBtn {entry->bindNew<Wt::WText>("add-btn", Wt::WString::tr("Lms.Explore.template.add-btn"), Wt::TextFormat::XHTML)};
|
Wt::WText* addBtn {entry->bindNew<Wt::WText>("add-btn", Wt::WString::tr("Lms.Explore.template.add-btn"), Wt::TextFormat::XHTML)};
|
||||||
addBtn->clicked().connect([=]()
|
addBtn->clicked().connect([=]()
|
||||||
{
|
{
|
||||||
tracksAdd.emit({trackId});
|
tracksAction.emit(PlayQueueAction::AddLast, {trackId});
|
||||||
});
|
});
|
||||||
|
|
||||||
entry->bindString("duration", trackDurationToString(track->getDuration()), Wt::TextFormat::Plain);
|
entry->bindString("duration", trackDurationToString(track->getDuration()), Wt::TextFormat::Plain);
|
||||||
|
|
||||||
LmsApp->getMediaPlayer()->trackLoaded.connect(entry, [=] (Database::IdType loadedTrackId)
|
LmsApp->getMediaPlayer().trackLoaded.connect(entry, [=] (Database::IdType loadedTrackId)
|
||||||
{
|
{
|
||||||
entry->bindString("is-playing", loadedTrackId == trackId ? "Lms-entry-playing" : "");
|
entry->bindString("is-playing", loadedTrackId == trackId ? "Lms-entry-playing" : "");
|
||||||
});
|
});
|
||||||
|
|
||||||
if (auto trackIdLoaded {LmsApp->getMediaPlayer()->getTrackLoaded()})
|
if (auto trackIdLoaded {LmsApp->getMediaPlayer().getTrackLoaded()})
|
||||||
{
|
{
|
||||||
if (*trackIdLoaded == trackId)
|
if (*trackIdLoaded == trackId)
|
||||||
entry->bindString("is-playing", "Lms-entry-playing");
|
entry->bindString("is-playing", "Lms-entry-playing");
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
entry->bindString("is-playing", "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,24 +266,40 @@ Release::refreshCopyright(const Database::Release::pointer& release)
|
|||||||
std::optional<std::string> copyright {release->getCopyright()};
|
std::optional<std::string> copyright {release->getCopyright()};
|
||||||
std::optional<std::string> copyrightURL {release->getCopyrightURL()};
|
std::optional<std::string> copyrightURL {release->getCopyrightURL()};
|
||||||
|
|
||||||
setCondition("if-has-copyright-or-copyright-url", copyright || copyrightURL);
|
if (!copyright && !copyrightURL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
setCondition("if-has-copyright", true);
|
||||||
|
|
||||||
|
std::string copyrightText {copyright ? *copyright : ""};
|
||||||
|
if (copyrightText.empty() && copyrightURL)
|
||||||
|
copyrightText = *copyrightURL;
|
||||||
|
|
||||||
if (copyrightURL)
|
if (copyrightURL)
|
||||||
{
|
{
|
||||||
setCondition("if-has-copyright-url", true);
|
|
||||||
|
|
||||||
Wt::WLink link {*copyrightURL};
|
Wt::WLink link {*copyrightURL};
|
||||||
link.setTarget(Wt::LinkTarget::NewWindow);
|
link.setTarget(Wt::LinkTarget::NewWindow);
|
||||||
|
|
||||||
Wt::WAnchor* anchor {bindNew<Wt::WAnchor>("copyright-url", link)};
|
Wt::WAnchor* anchor {bindNew<Wt::WAnchor>("copyright", link)};
|
||||||
anchor->setTextFormat(Wt::TextFormat::XHTML);
|
anchor->setTextFormat(Wt::TextFormat::Plain);
|
||||||
anchor->setText(Wt::WString::tr("Lms.Explore.Release.template.link-btn"));
|
anchor->setText(Wt::WString::fromUTF8(copyrightText));
|
||||||
}
|
}
|
||||||
|
else
|
||||||
if (copyright)
|
|
||||||
{
|
|
||||||
setCondition("if-has-copyright", true);
|
|
||||||
bindString("copyright", Wt::WString::fromUTF8(*copyright), Wt::TextFormat::Plain);
|
bindString("copyright", Wt::WString::fromUTF8(*copyright), Wt::TextFormat::Plain);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Release::refreshLinks(const Database::Release::pointer& release)
|
||||||
|
{
|
||||||
|
const auto mbid {release->getMBID()};
|
||||||
|
if (mbid)
|
||||||
|
{
|
||||||
|
setCondition("if-has-mbid", true);
|
||||||
|
|
||||||
|
Wt::WLink link {"https://musicbrainz.org/release/" + std::string {mbid->getAsString()}};
|
||||||
|
link.setTarget(Wt::LinkTarget::NewWindow);
|
||||||
|
|
||||||
|
bindNew<Wt::WAnchor>("mbid-link", link, Wt::WString::tr("Lms.Explore.musicbrainz-release"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,7 +314,7 @@ Release::refreshSimilarReleases(const std::vector<Database::IdType>& similarRele
|
|||||||
if (!similarRelease)
|
if (!similarRelease)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
similarReleasesContainer->addWidget(ReleaseListHelpers::createEntrySmall(similarRelease));
|
similarReleasesContainer->addWidget(ReleaseListHelpers::createEntry(similarRelease));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
#include <Wt/WTemplate.h>
|
#include <Wt/WTemplate.h>
|
||||||
|
|
||||||
#include "database/Types.hpp"
|
#include "database/Types.hpp"
|
||||||
|
#include "PlayQueueAction.hpp"
|
||||||
|
|
||||||
namespace Database
|
namespace Database
|
||||||
{
|
{
|
||||||
@@ -38,15 +39,16 @@ namespace UserInterface
|
|||||||
public:
|
public:
|
||||||
Release(Filters* filters);
|
Release(Filters* filters);
|
||||||
|
|
||||||
Wt::Signal<const std::vector<Database::IdType>&> releasesAdd;
|
Wt::Signal<PlayQueueAction, const std::vector<Database::IdType>&> releasesAction;
|
||||||
Wt::Signal<const std::vector<Database::IdType>&> releasesPlay;
|
Wt::Signal<PlayQueueAction, const std::vector<Database::IdType>&> tracksAction;
|
||||||
|
|
||||||
Wt::Signal<const std::vector<Database::IdType>&> tracksAdd;
|
Wt::Signal<Wt::WWidget&, Database::IdType> createReleaseActionMenu;
|
||||||
Wt::Signal<const std::vector<Database::IdType>&> tracksPlay;
|
Wt::Signal<Wt::WWidget&, Database::IdType> createTrackActionMenu;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void refreshView();
|
void refreshView();
|
||||||
void refreshCopyright(const Wt::Dbo::ptr<Database::Release>& release);
|
void refreshCopyright(const Wt::Dbo::ptr<Database::Release>& release);
|
||||||
|
void refreshLinks(const Wt::Dbo::ptr<Database::Release>& release);
|
||||||
void refreshSimilarReleases(const std::vector<Database::IdType>& similarReleasesId);
|
void refreshSimilarReleases(const std::vector<Database::IdType>& similarReleasesId);
|
||||||
|
|
||||||
Filters* _filters {};
|
Filters* _filters {};
|
||||||
|
|||||||
@@ -29,12 +29,11 @@
|
|||||||
#include "database/Release.hpp"
|
#include "database/Release.hpp"
|
||||||
#include "database/User.hpp"
|
#include "database/User.hpp"
|
||||||
#include "database/TrackList.hpp"
|
#include "database/TrackList.hpp"
|
||||||
|
|
||||||
#include "utils/Logger.hpp"
|
#include "utils/Logger.hpp"
|
||||||
#include "utils/String.hpp"
|
#include "utils/String.hpp"
|
||||||
|
|
||||||
#include "resource/ImageResource.hpp"
|
#include "resource/ImageResource.hpp"
|
||||||
|
#include "ReleaseListHelpers.hpp"
|
||||||
#include "Filters.hpp"
|
#include "Filters.hpp"
|
||||||
#include "LmsApplication.hpp"
|
#include "LmsApplication.hpp"
|
||||||
|
|
||||||
@@ -70,12 +69,12 @@ _filters {filters}
|
|||||||
Wt::WText* playBtn {bindNew<Wt::WText>("play-btn", Wt::WString::tr("Lms.Explore.template.play-btn"), Wt::TextFormat::XHTML)};
|
Wt::WText* playBtn {bindNew<Wt::WText>("play-btn", Wt::WString::tr("Lms.Explore.template.play-btn"), Wt::TextFormat::XHTML)};
|
||||||
playBtn->clicked().connect([this]
|
playBtn->clicked().connect([this]
|
||||||
{
|
{
|
||||||
releasesPlay.emit(getAllReleases());
|
releasesAction.emit(PlayQueueAction::Play, getAllReleases());
|
||||||
});
|
});
|
||||||
Wt::WText* addBtn {bindNew<Wt::WText>("add-btn", Wt::WString::tr("Lms.Explore.template.add-btn"), Wt::TextFormat::XHTML)};
|
Wt::WText* addBtn {bindNew<Wt::WText>("add-btn", Wt::WString::tr("Lms.Explore.template.add-btn"), Wt::TextFormat::XHTML)};
|
||||||
addBtn->clicked().connect([this]
|
addBtn->clicked().connect([this]
|
||||||
{
|
{
|
||||||
releasesAdd.emit(getAllReleases());
|
releasesAction.emit(PlayQueueAction::AddLast, getAllReleases());
|
||||||
});
|
});
|
||||||
|
|
||||||
_container = bindNew<Wt::WContainerWidget>("releases");
|
_container = bindNew<Wt::WContainerWidget>("releases");
|
||||||
@@ -106,62 +105,17 @@ Releases::refreshView(Mode mode)
|
|||||||
refreshView();
|
refreshView();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Wt::WTemplate>
|
|
||||||
Releases::createEntry(const Release::pointer& release)
|
|
||||||
{
|
|
||||||
const IdType releaseId {release.id()};
|
|
||||||
|
|
||||||
auto entry = std::make_unique<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Releases.template.entry"));
|
|
||||||
entry->addFunction("tr", Wt::WTemplate::Functions::tr);
|
|
||||||
|
|
||||||
Wt::WAnchor* anchor = entry->bindWidget("cover", LmsApplication::createReleaseAnchor(release, false));
|
|
||||||
auto cover = std::make_unique<Wt::WImage>();
|
|
||||||
cover->setImageLink(LmsApp->getImageResource()->getReleaseUrl(release.id(), 128));
|
|
||||||
cover->setStyleClass("Lms-cover");
|
|
||||||
anchor->setImage(std::move(cover));
|
|
||||||
|
|
||||||
entry->bindWidget("release-name", LmsApplication::createReleaseAnchor(release));
|
|
||||||
|
|
||||||
auto artists = release->getReleaseArtists();
|
|
||||||
if (artists.empty())
|
|
||||||
artists = release->getArtists();
|
|
||||||
|
|
||||||
if (artists.size() > 1)
|
|
||||||
{
|
|
||||||
entry->setCondition("if-has-artist", true);
|
|
||||||
entry->bindNew<Wt::WText>("artist-name", Wt::WString::tr("Lms.Explore.various-artists"));
|
|
||||||
}
|
|
||||||
else if (artists.size() == 1)
|
|
||||||
{
|
|
||||||
entry->setCondition("if-has-artist", true);
|
|
||||||
entry->bindWidget("artist-name", LmsApplication::createArtistAnchor(artists.front()));
|
|
||||||
}
|
|
||||||
|
|
||||||
Wt::WText* playBtn = entry->bindNew<Wt::WText>("play-btn", Wt::WString::tr("Lms.Explore.template.play-btn"), Wt::TextFormat::XHTML);
|
|
||||||
playBtn->clicked().connect([=]
|
|
||||||
{
|
|
||||||
releasesPlay.emit({releaseId});
|
|
||||||
});
|
|
||||||
|
|
||||||
Wt::WText* addBtn = entry->bindNew<Wt::WText>("add-btn", Wt::WString::tr("Lms.Explore.template.add-btn"), Wt::TextFormat::XHTML);
|
|
||||||
addBtn->clicked().connect([=]
|
|
||||||
{
|
|
||||||
releasesAdd.emit({releaseId});
|
|
||||||
});
|
|
||||||
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
Releases::addSome()
|
Releases::addSome()
|
||||||
{
|
{
|
||||||
bool moreResults {};
|
bool moreResults {};
|
||||||
|
|
||||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||||
|
const auto releases {getReleases(Range {static_cast<std::size_t>(_container->count()), batchSize}, moreResults)};
|
||||||
|
|
||||||
for (const Release::pointer& release : getReleases(Range {static_cast<std::size_t>(_container->count()), batchSize}, moreResults))
|
for (const Release::pointer& release : releases)
|
||||||
{
|
{
|
||||||
_container->addWidget(createEntry(release));
|
_container->addWidget(ReleaseListHelpers::createEntry(release));
|
||||||
}
|
}
|
||||||
|
|
||||||
_showMore->setHidden(!moreResults);
|
_showMore->setHidden(!moreResults);
|
||||||
@@ -201,7 +155,7 @@ Releases::getReleases(std::optional<Range> range, bool& moreResults)
|
|||||||
if (modeLimit)
|
if (modeLimit)
|
||||||
{
|
{
|
||||||
if (range)
|
if (range)
|
||||||
range->limit = std::min(*modeLimit - range->offset, range->offset + range->limit);
|
range->limit = std::min(*modeLimit - range->offset, range->limit);
|
||||||
else
|
else
|
||||||
range = Range {0, *modeLimit};
|
range = Range {0, *modeLimit};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
#include <Wt/WTemplate.h>
|
#include <Wt/WTemplate.h>
|
||||||
|
|
||||||
#include "database/Types.hpp"
|
#include "database/Types.hpp"
|
||||||
|
#include "PlayQueueAction.hpp"
|
||||||
|
|
||||||
namespace Database
|
namespace Database
|
||||||
{
|
{
|
||||||
@@ -41,8 +42,7 @@ class Releases : public Wt::WTemplate
|
|||||||
public:
|
public:
|
||||||
Releases(Filters* filters);
|
Releases(Filters* filters);
|
||||||
|
|
||||||
Wt::Signal<const std::vector<Database::IdType>&> releasesAdd;
|
Wt::Signal<PlayQueueAction, const std::vector<Database::IdType>&> releasesAction;
|
||||||
Wt::Signal<const std::vector<Database::IdType>&> releasesPlay;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
@@ -61,17 +61,16 @@ class Releases : public Wt::WTemplate
|
|||||||
std::vector<Wt::Dbo::ptr<Database::Release>> getReleases(std::optional<Database::Range> range, bool& moreResults);
|
std::vector<Wt::Dbo::ptr<Database::Release>> getReleases(std::optional<Database::Range> range, bool& moreResults);
|
||||||
std::vector<Wt::Dbo::ptr<Database::Release>> getRandomReleases(std::optional<Database::Range> range, bool& moreResults);
|
std::vector<Wt::Dbo::ptr<Database::Release>> getRandomReleases(std::optional<Database::Range> range, bool& moreResults);
|
||||||
std::vector<Database::IdType> getAllReleases();
|
std::vector<Database::IdType> getAllReleases();
|
||||||
std::unique_ptr<Wt::WTemplate> createEntry(const Wt::Dbo::ptr<Database::Release>& release);
|
|
||||||
|
|
||||||
static constexpr Mode defaultMode {Mode::Random};
|
static constexpr Mode defaultMode {Mode::Random};
|
||||||
static constexpr std::size_t batchSize {20};
|
static constexpr std::size_t batchSize {18};
|
||||||
static inline std::unordered_map<Mode, std::optional<std::size_t>> maxItemsPerMode
|
static inline std::unordered_map<Mode, std::optional<std::size_t>> maxItemsPerMode
|
||||||
{
|
{
|
||||||
{Mode::Random, batchSize * 6},
|
{Mode::Random, batchSize * 6},
|
||||||
{Mode::RecentlyPlayed, batchSize * 3},
|
{Mode::RecentlyPlayed, batchSize * 3},
|
||||||
{Mode::RecentlyAdded, batchSize * 2},
|
{Mode::RecentlyAdded, batchSize * 2},
|
||||||
{Mode::MostPlayed, batchSize * 2},
|
{Mode::MostPlayed, batchSize * 2},
|
||||||
{Mode::All, std::nullopt},
|
{Mode::All, batchSize * 30},
|
||||||
};
|
};
|
||||||
|
|
||||||
Mode _mode {defaultMode};
|
Mode _mode {defaultMode};
|
||||||
|
|||||||
@@ -29,63 +29,15 @@
|
|||||||
#include "resource/ImageResource.hpp"
|
#include "resource/ImageResource.hpp"
|
||||||
#include "ArtistListHelpers.hpp"
|
#include "ArtistListHelpers.hpp"
|
||||||
#include "Filters.hpp"
|
#include "Filters.hpp"
|
||||||
#include "ReleaseListHelpers.hpp"
|
|
||||||
#include "LmsApplication.hpp"
|
#include "LmsApplication.hpp"
|
||||||
|
#include "ReleaseListHelpers.hpp"
|
||||||
|
#include "TrackListHelpers.hpp"
|
||||||
|
|
||||||
static constexpr std::size_t maxEntries {5};
|
static constexpr std::size_t maxEntries {6};
|
||||||
|
|
||||||
namespace UserInterface
|
namespace UserInterface
|
||||||
{
|
{
|
||||||
|
|
||||||
static
|
|
||||||
std::unique_ptr<Wt::WTemplate>
|
|
||||||
createTrack(const Database::Track::pointer& track)
|
|
||||||
{
|
|
||||||
auto entry {std::make_unique<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Search.template.track-entry"))};
|
|
||||||
|
|
||||||
entry->bindString("name", Wt::WString::fromUTF8(track->getName()), Wt::TextFormat::Plain);
|
|
||||||
|
|
||||||
auto artists {track->getArtists()};
|
|
||||||
auto release {track->getRelease()};
|
|
||||||
const auto trackId {track.id()};
|
|
||||||
|
|
||||||
if (!artists.empty() || release)
|
|
||||||
entry->setCondition("if-has-artists-or-release", true);
|
|
||||||
|
|
||||||
if (!artists.empty())
|
|
||||||
{
|
|
||||||
entry->setCondition("if-has-artists", true);
|
|
||||||
|
|
||||||
Wt::WContainerWidget* artistContainer {entry->bindNew<Wt::WContainerWidget>("artists")};
|
|
||||||
for (const auto& artist : artists)
|
|
||||||
{
|
|
||||||
Wt::WTemplate* a {artistContainer->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Tracks.template.entry-artist"))};
|
|
||||||
a->bindWidget("artist", LmsApplication::createArtistAnchor(artist));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (track->getRelease())
|
|
||||||
{
|
|
||||||
entry->setCondition("if-has-release", true);
|
|
||||||
entry->bindWidget("release", LmsApplication::createReleaseAnchor(track->getRelease()));
|
|
||||||
{
|
|
||||||
Wt::WAnchor* anchor = entry->bindWidget("cover", LmsApplication::createReleaseAnchor(release, false));
|
|
||||||
auto cover = std::make_unique<Wt::WImage>();
|
|
||||||
cover->setImageLink(LmsApp->getImageResource()->getReleaseUrl(release.id(), 96));
|
|
||||||
cover->setStyleClass("Lms-cover");
|
|
||||||
anchor->setImage(std::move(cover));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
auto cover = entry->bindNew<Wt::WImage>("cover");
|
|
||||||
cover->setImageLink(LmsApp->getImageResource()->getTrackUrl(trackId, 96));
|
|
||||||
cover->setStyleClass("Lms-cover");
|
|
||||||
}
|
|
||||||
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
SearchView::SearchView(Filters* filters)
|
SearchView::SearchView(Filters* filters)
|
||||||
: Wt::WTemplate {Wt::WString::tr("Lms.Explore.Search.template")}
|
: Wt::WTemplate {Wt::WString::tr("Lms.Explore.Search.template")}
|
||||||
, _filters {filters}
|
, _filters {filters}
|
||||||
@@ -155,7 +107,7 @@ namespace UserInterface
|
|||||||
auto* container {bindNew<Wt::WContainerWidget>("releases")};
|
auto* container {bindNew<Wt::WContainerWidget>("releases")};
|
||||||
|
|
||||||
for (const Database::Release::pointer& release : releases)
|
for (const Database::Release::pointer& release : releases)
|
||||||
container->addWidget(ReleaseListHelpers::createEntrySmall(release));
|
container->addWidget(ReleaseListHelpers::createEntry(release));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,7 +127,7 @@ namespace UserInterface
|
|||||||
auto* container {bindNew<Wt::WContainerWidget>("tracks")};
|
auto* container {bindNew<Wt::WContainerWidget>("tracks")};
|
||||||
|
|
||||||
for (const Database::Track::pointer& track : tracks)
|
for (const Database::Track::pointer& track : tracks)
|
||||||
container->addWidget(createTrack(track));
|
container->addWidget(TrackListHelpers::createEntry(track));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,119 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 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 "TrackListHelpers.hpp"
|
||||||
|
|
||||||
|
#include <Wt/WAnchor.h>
|
||||||
|
#include <Wt/WContainerWidget.h>
|
||||||
|
#include <Wt/WImage.h>
|
||||||
|
#include <Wt/WText.h>
|
||||||
|
|
||||||
|
#include "database/Artist.hpp"
|
||||||
|
#include "database/Release.hpp"
|
||||||
|
#include "database/Track.hpp"
|
||||||
|
#include "resource/ImageResource.hpp"
|
||||||
|
#include "LmsApplication.hpp"
|
||||||
|
#include "MediaPlayer.hpp"
|
||||||
|
#include "PlayQueue.hpp"
|
||||||
|
#include "TrackStringUtils.hpp"
|
||||||
|
|
||||||
|
using namespace Database;
|
||||||
|
|
||||||
|
namespace UserInterface::TrackListHelpers
|
||||||
|
{
|
||||||
|
std::unique_ptr<Wt::WTemplate>
|
||||||
|
createEntry(const Wt::Dbo::ptr<Database::Track>& track)
|
||||||
|
{
|
||||||
|
auto entry {std::make_unique<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Tracks.template.entry"))};
|
||||||
|
auto* entryPtr {entry.get()};
|
||||||
|
|
||||||
|
Wt::WText* name {entry->bindNew<Wt::WText>("name", Wt::WString::fromUTF8(track->getName()), Wt::TextFormat::Plain)};
|
||||||
|
name->setToolTip(Wt::WString::fromUTF8(track->getName()));
|
||||||
|
|
||||||
|
const auto artists {track->getArtists()};
|
||||||
|
const Release::pointer release {track->getRelease()};
|
||||||
|
const IdType trackId {track.id()};
|
||||||
|
|
||||||
|
if (!artists.empty() || release)
|
||||||
|
entry->setCondition("if-has-artists-or-release", true);
|
||||||
|
|
||||||
|
if (!artists.empty())
|
||||||
|
{
|
||||||
|
entry->setCondition("if-has-artists", true);
|
||||||
|
|
||||||
|
Wt::WContainerWidget* artistContainer {entry->bindNew<Wt::WContainerWidget>("artists")};
|
||||||
|
for (const Artist::pointer& artist : artists)
|
||||||
|
{
|
||||||
|
Wt::WTemplate* a {artistContainer->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Tracks.template.entry-artist"))};
|
||||||
|
a->bindWidget("artist", LmsApplication::createArtistAnchor(artist));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (track->getRelease())
|
||||||
|
{
|
||||||
|
entry->setCondition("if-has-release", true);
|
||||||
|
entry->bindWidget("release", LmsApplication::createReleaseAnchor(track->getRelease()));
|
||||||
|
{
|
||||||
|
Wt::WAnchor* anchor = entry->bindWidget("cover", LmsApplication::createReleaseAnchor(release, false));
|
||||||
|
auto cover = std::make_unique<Wt::WImage>();
|
||||||
|
cover->setImageLink(LmsApp->getImageResource()->getReleaseUrl(release.id(), ImageResource::Size::Large));
|
||||||
|
cover->setStyleClass("Lms-cover");
|
||||||
|
cover->setAttributeValue("onload", LmsApp->javaScriptClass() + ".onLoadCover(this)");
|
||||||
|
anchor->setImage(std::move(cover));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto cover = entry->bindNew<Wt::WImage>("cover");
|
||||||
|
cover->setImageLink(LmsApp->getImageResource()->getTrackUrl(trackId, ImageResource::Size::Large));
|
||||||
|
cover->setStyleClass("Lms-cover");
|
||||||
|
cover->setAttributeValue("onload", LmsApp->javaScriptClass() + ".onLoadCover(this)");
|
||||||
|
}
|
||||||
|
|
||||||
|
entry->bindString("duration", trackDurationToString(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);
|
||||||
|
playBtn->clicked().connect([=]
|
||||||
|
{
|
||||||
|
LmsApp->getPlayQueue().processTracks(PlayQueueAction::Play, {trackId});
|
||||||
|
});
|
||||||
|
|
||||||
|
Wt::WText* addBtn = entry->bindNew<Wt::WText>("add-btn", Wt::WString::tr("Lms.Explore.template.add-btn"), Wt::TextFormat::XHTML);
|
||||||
|
addBtn->clicked().connect([=]
|
||||||
|
{
|
||||||
|
LmsApp->getPlayQueue().processTracks(PlayQueueAction::AddLast, {trackId});
|
||||||
|
});
|
||||||
|
|
||||||
|
LmsApp->getMediaPlayer().trackLoaded.connect(entryPtr, [=] (Database::IdType loadedTrackId)
|
||||||
|
{
|
||||||
|
entryPtr->bindString("is-playing", loadedTrackId == trackId ? "Lms-entry-playing" : "");
|
||||||
|
});
|
||||||
|
|
||||||
|
if (auto trackIdLoaded {LmsApp->getMediaPlayer().getTrackLoaded()})
|
||||||
|
{
|
||||||
|
entry->bindString("is-playing", *trackIdLoaded == trackId ? "Lms-entry-playing" : "");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
entry->bindString("is-playing", "");
|
||||||
|
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace UserInterface
|
||||||
|
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 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 <memory>
|
||||||
|
|
||||||
|
#include <Wt/WTemplate.h>
|
||||||
|
|
||||||
|
namespace Database
|
||||||
|
{
|
||||||
|
class Track;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace UserInterface::TrackListHelpers
|
||||||
|
{
|
||||||
|
std::unique_ptr<Wt::WTemplate> createEntry(const Wt::Dbo::ptr<Database::Track>& track);
|
||||||
|
} // namespace UserInterface
|
||||||
|
|
||||||
@@ -37,6 +37,7 @@
|
|||||||
#include "Filters.hpp"
|
#include "Filters.hpp"
|
||||||
#include "LmsApplication.hpp"
|
#include "LmsApplication.hpp"
|
||||||
#include "MediaPlayer.hpp"
|
#include "MediaPlayer.hpp"
|
||||||
|
#include "TrackListHelpers.hpp"
|
||||||
#include "TrackStringUtils.hpp"
|
#include "TrackStringUtils.hpp"
|
||||||
|
|
||||||
using namespace Database;
|
using namespace Database;
|
||||||
@@ -71,13 +72,13 @@ _filters {filters}
|
|||||||
Wt::WText* playBtn = bindNew<Wt::WText>("play-btn", Wt::WString::tr("Lms.Explore.template.play-btn"), Wt::TextFormat::XHTML);
|
Wt::WText* playBtn = bindNew<Wt::WText>("play-btn", Wt::WString::tr("Lms.Explore.template.play-btn"), Wt::TextFormat::XHTML);
|
||||||
playBtn->clicked().connect([=]
|
playBtn->clicked().connect([=]
|
||||||
{
|
{
|
||||||
tracksPlay.emit(getAllTracks());
|
tracksAction.emit(PlayQueueAction::Play, getAllTracks());
|
||||||
});
|
});
|
||||||
|
|
||||||
Wt::WText* addBtn = bindNew<Wt::WText>("add-btn", Wt::WString::tr("Lms.Explore.template.add-btn"), Wt::TextFormat::XHTML);
|
Wt::WText* addBtn = bindNew<Wt::WText>("add-btn", Wt::WString::tr("Lms.Explore.template.add-btn"), Wt::TextFormat::XHTML);
|
||||||
addBtn->clicked().connect([=]
|
addBtn->clicked().connect([=]
|
||||||
{
|
{
|
||||||
tracksAdd.emit(getAllTracks());
|
tracksAction.emit(PlayQueueAction::AddLast, getAllTracks());
|
||||||
});
|
});
|
||||||
|
|
||||||
_tracksContainer = bindNew<Wt::WContainerWidget>("tracks");
|
_tracksContainer = bindNew<Wt::WContainerWidget>("tracks");
|
||||||
@@ -145,7 +146,7 @@ Tracks::getTracks(std::optional<Range> range, bool& moreResults)
|
|||||||
if (modeLimit)
|
if (modeLimit)
|
||||||
{
|
{
|
||||||
if (range)
|
if (range)
|
||||||
range->limit = std::min(*modeLimit - range->offset, range->offset + range->limit);
|
range->limit = std::min(*modeLimit - range->offset, range->limit);
|
||||||
else
|
else
|
||||||
range = Range {0, *modeLimit};
|
range = Range {0, *modeLimit};
|
||||||
}
|
}
|
||||||
@@ -194,80 +195,6 @@ Tracks::getAllTracks()
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Wt::WTemplate>
|
|
||||||
Tracks::createEntry(const Track::pointer& track)
|
|
||||||
{
|
|
||||||
auto entry {std::make_unique<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Tracks.template.entry"))};
|
|
||||||
auto* entryPtr {entry.get()};
|
|
||||||
|
|
||||||
entry->bindString("name", Wt::WString::fromUTF8(track->getName()), Wt::TextFormat::Plain);
|
|
||||||
|
|
||||||
auto artists {track->getArtists()};
|
|
||||||
auto release {track->getRelease()};
|
|
||||||
const auto trackId {track.id()};
|
|
||||||
|
|
||||||
if (!artists.empty() || release)
|
|
||||||
entry->setCondition("if-has-artists-or-release", true);
|
|
||||||
|
|
||||||
if (!artists.empty())
|
|
||||||
{
|
|
||||||
entry->setCondition("if-has-artists", true);
|
|
||||||
|
|
||||||
Wt::WContainerWidget* artistContainer {entry->bindNew<Wt::WContainerWidget>("artists")};
|
|
||||||
for (const auto& artist : artists)
|
|
||||||
{
|
|
||||||
Wt::WTemplate* a {artistContainer->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Tracks.template.entry-artist"))};
|
|
||||||
a->bindWidget("artist", LmsApplication::createArtistAnchor(artist));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (track->getRelease())
|
|
||||||
{
|
|
||||||
entry->setCondition("if-has-release", true);
|
|
||||||
entry->bindWidget("release", LmsApplication::createReleaseAnchor(track->getRelease()));
|
|
||||||
{
|
|
||||||
Wt::WAnchor* anchor = entry->bindWidget("cover", LmsApplication::createReleaseAnchor(release, false));
|
|
||||||
auto cover = std::make_unique<Wt::WImage>();
|
|
||||||
cover->setImageLink(LmsApp->getImageResource()->getReleaseUrl(release.id(), 96));
|
|
||||||
cover->setStyleClass("Lms-cover");
|
|
||||||
anchor->setImage(std::move(cover));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
auto cover = entry->bindNew<Wt::WImage>("cover");
|
|
||||||
cover->setImageLink(LmsApp->getImageResource()->getTrackUrl(trackId, 96));
|
|
||||||
cover->setStyleClass("Lms-cover");
|
|
||||||
}
|
|
||||||
|
|
||||||
entry->bindString("duration", trackDurationToString(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);
|
|
||||||
playBtn->clicked().connect([=]
|
|
||||||
{
|
|
||||||
tracksPlay.emit({trackId});
|
|
||||||
});
|
|
||||||
|
|
||||||
Wt::WText* addBtn = entry->bindNew<Wt::WText>("add-btn", Wt::WString::tr("Lms.Explore.template.add-btn"), Wt::TextFormat::XHTML);
|
|
||||||
addBtn->clicked().connect([=]
|
|
||||||
{
|
|
||||||
tracksAdd.emit({trackId});
|
|
||||||
});
|
|
||||||
|
|
||||||
LmsApp->getMediaPlayer()->trackLoaded.connect(entryPtr, [=] (Database::IdType loadedTrackId)
|
|
||||||
{
|
|
||||||
entryPtr->bindString("is-playing", loadedTrackId == trackId ? "Lms-entry-playing" : "");
|
|
||||||
});
|
|
||||||
|
|
||||||
if (auto trackIdLoaded {LmsApp->getMediaPlayer()->getTrackLoaded()})
|
|
||||||
{
|
|
||||||
if (*trackIdLoaded == trackId)
|
|
||||||
entry->bindString("is-playing", "Lms-entry-playing");
|
|
||||||
}
|
|
||||||
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
Tracks::addSome()
|
Tracks::addSome()
|
||||||
{
|
{
|
||||||
@@ -276,7 +203,7 @@ Tracks::addSome()
|
|||||||
bool moreResults;
|
bool moreResults;
|
||||||
for (const Track::pointer& track : getTracks(Range {static_cast<std::size_t>(_tracksContainer->count()), batchSize}, moreResults))
|
for (const Track::pointer& track : getTracks(Range {static_cast<std::size_t>(_tracksContainer->count()), batchSize}, moreResults))
|
||||||
{
|
{
|
||||||
_tracksContainer->addWidget(createEntry(track));
|
_tracksContainer->addWidget(TrackListHelpers::createEntry(track));
|
||||||
}
|
}
|
||||||
|
|
||||||
_showMore->setHidden(!moreResults);
|
_showMore->setHidden(!moreResults);
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
#include <Wt/WTemplate.h>
|
#include <Wt/WTemplate.h>
|
||||||
|
|
||||||
#include "database/Types.hpp"
|
#include "database/Types.hpp"
|
||||||
|
#include "PlayQueueAction.hpp"
|
||||||
|
|
||||||
namespace Database
|
namespace Database
|
||||||
{
|
{
|
||||||
@@ -41,8 +42,7 @@ class Tracks : public Wt::WTemplate
|
|||||||
public:
|
public:
|
||||||
Tracks(Filters* filters);
|
Tracks(Filters* filters);
|
||||||
|
|
||||||
Wt::Signal<const std::vector<Database::IdType>&> tracksAdd;
|
Wt::Signal<PlayQueueAction, const std::vector<Database::IdType>&> tracksAction;
|
||||||
Wt::Signal<const std::vector<Database::IdType>&> tracksPlay;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
@@ -59,7 +59,6 @@ class Tracks : public Wt::WTemplate
|
|||||||
void refreshView(Mode mode);
|
void refreshView(Mode mode);
|
||||||
void addSome();
|
void addSome();
|
||||||
|
|
||||||
std::unique_ptr<Wt::WTemplate> createEntry(const Wt::Dbo::ptr<Database::Track>& track);
|
|
||||||
std::vector<Wt::Dbo::ptr<Database::Track>> getRandomTracks(std::optional<Database::Range> range, bool& moreResults);
|
std::vector<Wt::Dbo::ptr<Database::Track>> getRandomTracks(std::optional<Database::Range> range, bool& moreResults);
|
||||||
std::vector<Wt::Dbo::ptr<Database::Track>> getTracks(std::optional<Database::Range> range, bool& moreResults);
|
std::vector<Wt::Dbo::ptr<Database::Track>> getTracks(std::optional<Database::Range> range, bool& moreResults);
|
||||||
std::vector<Database::IdType> getAllTracks();
|
std::vector<Database::IdType> getAllTracks();
|
||||||
@@ -72,7 +71,7 @@ class Tracks : public Wt::WTemplate
|
|||||||
{Mode::RecentlyPlayed, batchSize * 10},
|
{Mode::RecentlyPlayed, batchSize * 10},
|
||||||
{Mode::RecentlyAdded, batchSize * 10},
|
{Mode::RecentlyAdded, batchSize * 10},
|
||||||
{Mode::MostPlayed, batchSize * 10},
|
{Mode::MostPlayed, batchSize * 10},
|
||||||
{Mode::All, std::nullopt},
|
{Mode::All, batchSize * 50},
|
||||||
};
|
};
|
||||||
|
|
||||||
Mode _mode {defaultMode};
|
Mode _mode {defaultMode};
|
||||||
|
|||||||
@@ -41,15 +41,15 @@ ImageResource::~ImageResource()
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::string
|
std::string
|
||||||
ImageResource::getReleaseUrl(Database::IdType releaseId, std::size_t size) const
|
ImageResource::getReleaseUrl(Database::IdType releaseId, Size size) const
|
||||||
{
|
{
|
||||||
return url() + "&releaseid=" + std::to_string(releaseId) + "&size=" + std::to_string(size);
|
return url() + "&releaseid=" + std::to_string(releaseId) + "&size=" + std::to_string(static_cast<std::size_t>(size));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string
|
std::string
|
||||||
ImageResource::getTrackUrl(Database::IdType trackId, std::size_t size) const
|
ImageResource::getTrackUrl(Database::IdType trackId, Size size) const
|
||||||
{
|
{
|
||||||
return url() + "&trackid=" + std::to_string(trackId) + "&size=" + std::to_string(size);
|
return url() + "&trackid=" + std::to_string(trackId) + "&size=" + std::to_string(static_cast<std::size_t>(size));
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|||||||
@@ -36,8 +36,14 @@ class ImageResource : public Wt::WResource
|
|||||||
|
|
||||||
~ImageResource();
|
~ImageResource();
|
||||||
|
|
||||||
std::string getReleaseUrl(Database::IdType releaseId, size_t size) const;
|
enum class Size : std::size_t
|
||||||
std::string getTrackUrl(Database::IdType trackId, size_t size) const;
|
{
|
||||||
|
Small = 128,
|
||||||
|
Large = 512,
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string getReleaseUrl(Database::IdType releaseId, Size size) const;
|
||||||
|
std::string getTrackUrl(Database::IdType trackId, Size size) const;
|
||||||
|
|
||||||
static std::string getMimeType();
|
static std::string getMimeType();
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
|
|
||||||
static
|
static
|
||||||
void
|
void
|
||||||
dumpRecommendation(Database::Session session, Recommendation::IEngine& engine)
|
dumpTracksRecommendation(Database::Session session, Recommendation::IEngine& engine)
|
||||||
{
|
{
|
||||||
const std::vector<Database::IdType> trackIds {[&]()
|
const std::vector<Database::IdType> trackIds {[&]()
|
||||||
{
|
{
|
||||||
@@ -69,7 +69,12 @@ dumpRecommendation(Database::Session session, Recommendation::IEngine& engine)
|
|||||||
for (Database::IdType similarTrackId : engine.getSimilarTracks(session, {trackId}, 3))
|
for (Database::IdType similarTrackId : engine.getSimilarTracks(session, {trackId}, 3))
|
||||||
std::cout << "\t- Similar track '" << trackToString(similarTrackId) << std::endl;
|
std::cout << "\t- Similar track '" << trackToString(similarTrackId) << std::endl;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
void
|
||||||
|
dumpReleasesRecommendation(Database::Session session, Recommendation::IEngine& engine)
|
||||||
|
{
|
||||||
const std::vector<Database::IdType> releaseIds = std::invoke([&]()
|
const std::vector<Database::IdType> releaseIds = std::invoke([&]()
|
||||||
{
|
{
|
||||||
auto transaction {session.createSharedTransaction()};
|
auto transaction {session.createSharedTransaction()};
|
||||||
@@ -91,7 +96,12 @@ dumpRecommendation(Database::Session session, Recommendation::IEngine& engine)
|
|||||||
for (Database::IdType similarReleaseId : engine.getSimilarReleases(session, releaseId, 3))
|
for (Database::IdType similarReleaseId : engine.getSimilarReleases(session, releaseId, 3))
|
||||||
std::cout << "\t- Similar release '" << releaseToString(similarReleaseId) << "'" << std::endl;
|
std::cout << "\t- Similar release '" << releaseToString(similarReleaseId) << "'" << std::endl;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
void
|
||||||
|
dumpArtistsRecommendation(Database::Session session, Recommendation::IEngine& engine)
|
||||||
|
{
|
||||||
const std::vector<Database::IdType> artistIds = std::invoke([&]()
|
const std::vector<Database::IdType> artistIds = std::invoke([&]()
|
||||||
{
|
{
|
||||||
auto transaction {session.createSharedTransaction()};
|
auto transaction {session.createSharedTransaction()};
|
||||||
@@ -113,10 +123,6 @@ dumpRecommendation(Database::Session session, Recommendation::IEngine& engine)
|
|||||||
for (Database::IdType similarArtistId : engine.getSimilarArtists(session, artistId, 3))
|
for (Database::IdType similarArtistId : engine.getSimilarArtists(session, artistId, 3))
|
||||||
std::cout << "\t- Similar artist '" << artistToString(similarArtistId) << "'" << std::endl;
|
std::cout << "\t- Similar artist '" << artistToString(similarArtistId) << "'" << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -153,7 +159,9 @@ int main(int argc, char *argv[])
|
|||||||
sem.wait();
|
sem.wait();
|
||||||
std::cout << "Recommendation engine loaded!" << std::endl;
|
std::cout << "Recommendation engine loaded!" << std::endl;
|
||||||
|
|
||||||
dumpRecommendation(db, *engine);
|
dumpTracksRecommendation(db, *engine);
|
||||||
|
dumpReleasesRecommendation(db, *engine);
|
||||||
|
dumpArtistsRecommendation(db, *engine);
|
||||||
|
|
||||||
engine->stop();
|
engine->stop();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user