Merge branch 'theme' into develop. Fixes #45
This commit is contained in:
+6
-6
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
<message id="Lms.Explore.Artist.template">
|
<message id="Lms.Explore.Artist.template">
|
||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
<h2>${name}</h2>
|
<h2><div class="Lms-artistname text-primary">${name}</div></h2>
|
||||||
${clusters}
|
${clusters}
|
||||||
${play-btn}${add-btn}
|
${play-btn}${add-btn}
|
||||||
</div>
|
</div>
|
||||||
@@ -12,18 +12,18 @@
|
|||||||
</message>
|
</message>
|
||||||
|
|
||||||
<message id="Lms.Explore.Artist.template.entry">
|
<message id="Lms.Explore.Artist.template.entry">
|
||||||
<div class="row Lms-explore-artist-entry">
|
<div class="row Lms-explore-artist-entry Lms-entry-hoverable">
|
||||||
<div class="col-xs-12">
|
<div class="col-xs-12">
|
||||||
<div class="media">
|
<div class="media">
|
||||||
<div class="media-left media-middle">
|
<div class="media-left media-middle">
|
||||||
${cover class="media-object"}
|
${cover class="media-object Lms-cover"}
|
||||||
</div>
|
</div>
|
||||||
<div class="media-body media-middle">
|
<div class="media-body media-middle">
|
||||||
<h4 class="media-heading">
|
<h4 class="media-heading">
|
||||||
${name class="Lms-explore-releasename"}
|
${name class="Lms-releasename text-primary"}
|
||||||
</h4>
|
</h4>
|
||||||
${<if-has-artist>}<h4><small>${artist class="Lms-explore-artistname"}</small></h4>${</if-has-artist>}
|
${<if-has-artist>}<h4><small>${artist class="Lms-artistname text-muted"}</small></h4>${</if-has-artist>}
|
||||||
<h4><small>${<if-has-year>}${year}${</if-has-year>}${<if-has-orig-year>} (${orig-year})${</if-has-orig-year>}</small></h4>
|
<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}
|
${play-btn}${add-btn}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
<!--FORMS message blocks-->
|
<!--FORMS message blocks-->
|
||||||
|
|
||||||
<message id="Lms.Explore.ArtistLink.template">
|
<message id="Lms.Explore.ArtistLink.template">
|
||||||
<div class="row Lms-explore-artistlink Lms-vertical-align">
|
<div class="row Lms-explore-artistlink Lms-entry-hoverable Lms-vertical-align">
|
||||||
<div class="col-xs-12">
|
<div class="col-xs-12">
|
||||||
${name class="Lms-explore-artistname Lms-explore-artistlink-name"}
|
${name class="Lms-artistname text-primary"}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</message>
|
</message>
|
||||||
|
|||||||
+2
-2
@@ -28,9 +28,9 @@
|
|||||||
</message>
|
</message>
|
||||||
|
|
||||||
<message id="Lms.Explore.Artists.template.entry">
|
<message id="Lms.Explore.Artists.template.entry">
|
||||||
<div class="row Lms-explore-artists-entry Lms-vertical-align">
|
<div class="row Lms-explore-artists-entry Lms-entry-hoverable Lms-vertical-align">
|
||||||
<div class="col-xs-12">
|
<div class="col-xs-12">
|
||||||
<h4>${name class="Lms-explore-artistname Lms-explore-artists-entry-name"}</h4>
|
<h4>${name class="Lms-artistname text-primary"}</h4>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</message>
|
</message>
|
||||||
|
|||||||
+4
-13
@@ -7,29 +7,20 @@
|
|||||||
${contents class="Lms-explore-contents"}
|
${contents class="Lms-explore-contents"}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-4">
|
<div class="col-lg-4">
|
||||||
${filters class="Lms-explore-filters"}
|
${filters class="well well-sm"}
|
||||||
${info class="Lms-explore-info"}
|
${info class="well well-sm"}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</message>
|
</message>
|
||||||
|
|
||||||
<message id="Lms.Explore.template.add-btn">
|
<message id="Lms.Explore.template.add-btn"><i class="fa fa-plus-circle Lms-explore-btn Lms-control-btn"></i></message>
|
||||||
<i class="fa fa-plus-circle Lms-explore-btn"></i>
|
<message id="Lms.Explore.template.play-btn"><i class="fa fa-play-circle Lms-explore-btn Lms-control-btn"></i></message>
|
||||||
</message>
|
|
||||||
|
|
||||||
<message id="Lms.Explore.template.play-btn">
|
|
||||||
<i class="fa fa-play-circle Lms-explore-btn"></i>
|
|
||||||
</message>
|
|
||||||
|
|
||||||
<message id="Lms.Explore.template.filters">
|
<message id="Lms.Explore.template.filters">
|
||||||
<h3>${tr:Lms.Explore.filters} ${add-filter class="btn-primary"}</h3>
|
<h3>${tr:Lms.Explore.filters} ${add-filter class="btn-primary"}</h3>
|
||||||
${clusters}
|
${clusters}
|
||||||
</message>
|
</message>
|
||||||
|
|
||||||
<message id="Lms.Explore.template.cluster-entry">
|
|
||||||
<span class="badge {1}">${<if-can-delete>}<i class="fa fa-times-circle"></i> ${</if-can-delete>}${name}</span>
|
|
||||||
</message>
|
|
||||||
|
|
||||||
<message id="Lms.Explore.template.add-filter">
|
<message id="Lms.Explore.template.add-filter">
|
||||||
<div class="form-horizontal">
|
<div class="form-horizontal">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|||||||
+1
-1
@@ -37,7 +37,7 @@
|
|||||||
${tr:Lms.Auth.remember-me}
|
${tr:Lms.Auth.remember-me}
|
||||||
</label>
|
</label>
|
||||||
<div class="col-sm-5">
|
<div class="col-sm-5">
|
||||||
${remember-me}
|
<div class="checkbox"><label>${remember-me}</label></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|||||||
+8
-11
@@ -8,39 +8,36 @@
|
|||||||
<div class="Lms-player-seek-container">
|
<div class="Lms-player-seek-container">
|
||||||
<input id="lms-mp-seek" class="Lms-player-seek-common Lms-player-seek" type="range" min="0" max="100" step="1" value="0"/>
|
<input id="lms-mp-seek" class="Lms-player-seek-common Lms-player-seek" type="range" min="0" max="100" step="1" value="0"/>
|
||||||
<div class="progress Lms-player-seek-common Lms-player-progress">
|
<div class="progress Lms-player-seek-common Lms-player-progress">
|
||||||
<div id="lms-mp-progress" class="progress-bar progress-bar-info progress-bar-striped active" role="progressbar" aria-valuenow="20" aria-valuemin="0" aria-valuemax="100" style="width: 0%">
|
<div id="lms-mp-progress" class="progress-bar progress-bar-striped active" role="progressbar" aria-valuenow="20" aria-valuemin="0" aria-valuemax="100" style="width: 0%">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-4 col-sm-2 Lms-player-controls">
|
<div class="col-xs-5 col-sm-2 Lms-player-controls">
|
||||||
<i id="lms-mp-previous" class="fa fa-step-backward Lms-player-btn Lms-player-btn-controls"></i>
|
<i id="lms-mp-previous" class="fa fa-step-backward Lms-player-btn Lms-control-btn Lms-player-btn-controls"></i><i id="lms-mp-playpause" class="fa fa-play Lms-player-btn Lms-control-btn Lms-player-btn-controls"></i><i id="lms-mp-next" class="fa fa-step-forward Lms-player-btn Lms-control-btn Lms-player-btn-controls"></i>
|
||||||
<i id="lms-mp-playpause" class="fa fa-play Lms-player-btn Lms-player-btn-controls"></i>
|
|
||||||
<i id="lms-mp-next" class="fa fa-step-forward Lms-player-btn Lms-player-btn-controls"></i>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="hidden-xs col-sm-2">
|
<div class="hidden-xs col-sm-2">
|
||||||
<div class="Lms-player-volume">
|
<div class="Lms-player-volume">
|
||||||
<i id="lms-mp-volume" class="fa fa-fw fa-volume-up Lms-player-btn Lms-player-btn-volume"></i>
|
<i id="lms-mp-volume" class="fa fa-fw fa-volume-up Lms-player-btn Lms-control-btn Lms-player-btn-volume"></i>
|
||||||
<span class="hidden-xs Lms-player-volume-slider-container">
|
<span class="hidden-xs Lms-player-volume-slider-container">
|
||||||
<input id ="lms-mp-volume-slider" type="range" min="0" max="1" step="0.01" value="0.8" class="Lms-player-volume-slider"/>
|
<input id ="lms-mp-volume-slider" type="range" min="0" max="1" step="0.01" value="0.8" class="Lms-player-volume-slider"/>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-8 col-sm-6">
|
<div class="col-xs-7 col-sm-6">
|
||||||
<div class="Lms-player-text-center">
|
<div class="Lms-player-text-center">
|
||||||
${title class="Lms-player-text"}
|
${title class="Lms-player-text Lms-trackname text-primary"}
|
||||||
</div>
|
</div>
|
||||||
<div class="Lms-player-text-center">
|
<div class="Lms-player-text-center">
|
||||||
<small><span class="hidden-xs">${release class="Lms-player-text Lms-explore-releasename"}</span>${artist class="Lms-player-text Lms-explore-artistname Lms-player-artistname"}</small>
|
<span class="hidden-xs">${release class="Lms-player-text Lms-releasename text-primary"}</span><small>${artist class="Lms-player-text Lms-artistname text-muted"}</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="hidden-xs col-sm-2 Lms-player-duration">
|
<div class="hidden-xs col-sm-2 Lms-player-duration text-muted">
|
||||||
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</message>
|
</message>
|
||||||
|
|
||||||
</messages>
|
</messages>
|
||||||
|
|||||||
@@ -124,6 +124,7 @@
|
|||||||
<!--Explore:Release-->
|
<!--Explore:Release-->
|
||||||
<message id="Lms.Explore.ReleaseInfo.similar-releases">Similar albums</message>
|
<message id="Lms.Explore.ReleaseInfo.similar-releases">Similar albums</message>
|
||||||
<message id="Lms.Explore.Release.copyright">Copyright</message>
|
<message id="Lms.Explore.Release.copyright">Copyright</message>
|
||||||
|
<message id="Lms.Explore.Release.disc">Disc {1}</message>
|
||||||
|
|
||||||
<!--Explore:Releases-->
|
<!--Explore:Releases-->
|
||||||
<message id="Lms.Explore.ReleasesInfo.recently-added">Recently added</message>
|
<message id="Lms.Explore.ReleasesInfo.recently-added">Recently added</message>
|
||||||
@@ -155,8 +156,10 @@
|
|||||||
|
|
||||||
|
|
||||||
<!--Settings-->
|
<!--Settings-->
|
||||||
|
<message id="Lms.Settings.appearance">Appearance</message>
|
||||||
<message id="Lms.Settings.audio">Audio</message>
|
<message id="Lms.Settings.audio">Audio</message>
|
||||||
<message id="Lms.Settings.change-password">Change password</message>
|
<message id="Lms.Settings.change-password">Change password</message>
|
||||||
|
<message id="Lms.Settings.dark-mode">Dark mode</message>
|
||||||
<message id="Lms.Settings.demo-cannot-save">Cannot save using a demo account!</message>
|
<message id="Lms.Settings.demo-cannot-save">Cannot save using a demo account!</message>
|
||||||
<message id="Lms.Settings.password-bad">Bad password</message>
|
<message id="Lms.Settings.password-bad">Bad password</message>
|
||||||
<message id="Lms.Settings.password-must-fill-old-password">Old password must be filled in</message>
|
<message id="Lms.Settings.password-must-fill-old-password">Old password must be filled in</message>
|
||||||
|
|||||||
@@ -124,6 +124,7 @@
|
|||||||
<!--Explore:Release-->
|
<!--Explore:Release-->
|
||||||
<message id="Lms.Explore.ReleaseInfo.similar-releases">Albums similaires</message>
|
<message id="Lms.Explore.ReleaseInfo.similar-releases">Albums similaires</message>
|
||||||
<message id="Lms.Explore.Release.copyright">Copyright</message>
|
<message id="Lms.Explore.Release.copyright">Copyright</message>
|
||||||
|
<message id="Lms.Explore.Release.disc">Disque {1}</message>
|
||||||
|
|
||||||
<!--Explore:Releases-->
|
<!--Explore:Releases-->
|
||||||
<message id="Lms.Explore.ReleasesInfo.recently-added">Ajouts récents</message>
|
<message id="Lms.Explore.ReleasesInfo.recently-added">Ajouts récents</message>
|
||||||
@@ -155,8 +156,10 @@
|
|||||||
|
|
||||||
|
|
||||||
<!--Settings-->
|
<!--Settings-->
|
||||||
|
<message id="Lms.Settings.appearance">Apparence</message>
|
||||||
<message id="Lms.Settings.audio">Audio</message>
|
<message id="Lms.Settings.audio">Audio</message>
|
||||||
<message id="Lms.Settings.change-password">Changement de mot de passe</message>
|
<message id="Lms.Settings.change-password">Changement de mot de passe</message>
|
||||||
|
<message id="Lms.Settings.dark-mode">Mode sombre</message>
|
||||||
<message id="Lms.Settings.demo-cannot-save">Impossible de sauvegarder en utilisant un compte de démo !</message>
|
<message id="Lms.Settings.demo-cannot-save">Impossible de sauvegarder en utilisant un compte de démo !</message>
|
||||||
<message id="Lms.Settings.password-bad">Mauvais mot de passe</message>
|
<message id="Lms.Settings.password-bad">Mauvais mot de passe</message>
|
||||||
<message id="Lms.Settings.password-must-fill-old-password">L'ancien mot de passe doit être renseigné</message>
|
<message id="Lms.Settings.password-must-fill-old-password">L'ancien mot de passe doit être renseigné</message>
|
||||||
|
|||||||
+25
-13
@@ -3,31 +3,43 @@
|
|||||||
<!--FORMS message blocks-->
|
<!--FORMS message blocks-->
|
||||||
|
|
||||||
<message id="Lms.PlayHistory.template">
|
<message id="Lms.PlayHistory.template">
|
||||||
<div class="page-header">
|
|
||||||
<h2>${tr:Lms.PlayHistory.playhistory}</h2>
|
|
||||||
</div>
|
|
||||||
${entries}
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-12 Lms-horizontal-center">
|
<div class="col-lg-8">
|
||||||
${show-more class="btn-primary Lms-show-more"}
|
<div class="page-header">
|
||||||
|
<h2>${tr:Lms.PlayHistory.playhistory}</h2>
|
||||||
|
</div>
|
||||||
|
${entries}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-12 Lms-horizontal-center">
|
||||||
|
${show-more class="btn-primary Lms-show-more"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</message>
|
</message>
|
||||||
|
|
||||||
<message id="Lms.PlayHistory.template.entry">
|
<message id="Lms.PlayHistory.template.entry">
|
||||||
<div class="row Lms-playhistory-entry Lms-vertical-align">
|
<div class="row Lms-playhistory-entry Lms-entry-hoverable Lms-vertical-align">
|
||||||
<div class="col-xs-12 Lms-playhistory-entry-name">
|
<div class="col-xs-12 Lms-playhistory-entry-name">
|
||||||
<h4>${name}</h4>
|
<div class="media">
|
||||||
${<if-has-artists-or-release>}
|
<div class="media-left media-middle hidden-xs">
|
||||||
${<if-has-release>}<small>${release class="Lms-explore-releasename"}</small>${</if-has-release>}
|
${cover class="media-object Lms-cover"}
|
||||||
${<if-has-artists>}<small>${artists}</small>${</if-has-artists>}
|
</div>
|
||||||
${</if-has-artists-or-release>}
|
<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>}<small>${artists}</small>${</if-has-artists>}
|
||||||
|
${</if-has-artists-or-release>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</message>
|
</message>
|
||||||
|
|
||||||
<message id="Lms.PlayHistory.template.entry-artist">
|
<message id="Lms.PlayHistory.template.entry-artist">
|
||||||
${artist class="Lms-explore-artistname"}
|
${artist class="Lms-artistname text-muted"}
|
||||||
</message>
|
</message>
|
||||||
|
|
||||||
</messages>
|
</messages>
|
||||||
|
|||||||
+34
-39
@@ -2,58 +2,53 @@
|
|||||||
<messages xmlns:if="Wt.WTemplate.conditions">
|
<messages xmlns:if="Wt.WTemplate.conditions">
|
||||||
<!--FORMS message blocks-->
|
<!--FORMS message blocks-->
|
||||||
|
|
||||||
<message id="Lms.PlayQueue.template.clear-btn">
|
<message id="Lms.PlayQueue.template.clear-btn"><i class="fa fa-fw fa-times-circle Lms-playqueue-btn Lms-control-btn"></i></message>
|
||||||
<i class="fa fa-times-circle Lms-playqueue-btn"></i>
|
<message id="Lms.PlayQueue.template.shuffle-btn"><i class="fa fa-fw fa-random Lms-playqueue-btn Lms-control-btn"></i></message>
|
||||||
</message>
|
<message id="Lms.PlayQueue.template.repeat-btn"><i class="fa fa-fw fa-repeat Lms-playqueue-btn Lms-control-btn"></i></message>
|
||||||
|
<message id="Lms.PlayQueue.template.radio-btn"><i class="fa fa-fw fa-rss Lms-playqueue-btn Lms-control-btn"></i></message>
|
||||||
<message id="Lms.PlayQueue.template.shuffle-btn">
|
|
||||||
<i class="fa fa-random Lms-playqueue-btn"></i>
|
|
||||||
</message>
|
|
||||||
|
|
||||||
<message id="Lms.PlayQueue.template.repeat-btn">
|
|
||||||
<i class="fa fa-repeat Lms-playqueue-btn"></i>
|
|
||||||
</message>
|
|
||||||
|
|
||||||
<message id="Lms.PlayQueue.template.radio-btn">
|
|
||||||
<i class="fa fa-rss Lms-playqueue-btn"></i>
|
|
||||||
</message>
|
|
||||||
|
|
||||||
<message id="Lms.PlayQueue.template">
|
<message id="Lms.PlayQueue.template">
|
||||||
<div class="page-header">
|
|
||||||
<h2>${tr:Lms.PlayQueue.playqueue}</h2>
|
|
||||||
${clear-btn}${shuffle-btn}${repeat-btn}${radio-btn}
|
|
||||||
<h4><small>${nb-tracks}</small></h4>
|
|
||||||
</div>
|
|
||||||
${entries}
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-12 Lms-horizontal-center">
|
<div class="col-lg-8">
|
||||||
${show-more class="btn-primary Lms-show-more"}
|
<div class="page-header">
|
||||||
|
<h2>${tr:Lms.PlayQueue.playqueue}</h2>
|
||||||
|
${clear-btn}${shuffle-btn}${repeat-btn}${radio-btn}
|
||||||
|
<h4><small>${nb-tracks}</small></h4>
|
||||||
|
</div>
|
||||||
|
${entries}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-12 Lms-horizontal-center">
|
||||||
|
${show-more class="btn-primary Lms-show-more"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</message>
|
</message>
|
||||||
|
|
||||||
<message id="Lms.PlayQueue.template.play-btn">
|
<message id="Lms.PlayQueue.template.play-btn"><i class="fa fa-fw fa-play-circle Lms-playqueue-btn Lms-control-btn"></i></message>
|
||||||
<i class="fa fa-play-circle Lms-playqueue-btn"></i>
|
<message id="Lms.PlayQueue.template.delete-btn"><i class="fa fa-fw fa-times-circle Lms-playqueue-btn Lms-control-btn"></i></message>
|
||||||
</message>
|
|
||||||
|
|
||||||
<message id="Lms.PlayQueue.template.delete-btn">
|
|
||||||
<i class="fa fa-times-circle Lms-playqueue-btn"></i>
|
|
||||||
</message>
|
|
||||||
|
|
||||||
<message id="Lms.PlayQueue.template.entry">
|
<message id="Lms.PlayQueue.template.entry">
|
||||||
<div class="row Lms-playqueue-entry Lms-vertical-align">
|
<div class="row Lms-playqueue-entry Lms-entry-hoverable Lms-vertical-align ${is-selected}">
|
||||||
<div class="col-xs-9 Lms-playqueue-entry-name">
|
<div class="col-xs-9 Lms-playqueue-entry-name">
|
||||||
<h4>${name}</h4>
|
<div class="media">
|
||||||
${<if-has-artists-or-release>}
|
<div class="media-left media-middle hidden-xs">
|
||||||
${<if-has-release>}<small>${release class="Lms-explore-releasename"}</small>${</if-has-release>}
|
${cover class="media-object Lms-cover"}
|
||||||
${<if-has-artists>}<small>${artists}</small>${</if-has-artists>}
|
</div>
|
||||||
${</if-has-artists-or-release>}
|
<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 class="col-xs-3">
|
<div class="col-xs-3">
|
||||||
<div class="Lms-playqueue-entry-controls">
|
<div class="Lms-playqueue-entry-controls Lms-vertical-align">
|
||||||
${play-btn}${del-btn}
|
${play-btn}${del-btn}
|
||||||
</div>
|
</div>
|
||||||
<div class="Lms-playqueue-entry-duration">
|
<div class="Lms-playqueue-entry-duration text-muted">
|
||||||
${duration}
|
${duration}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -61,7 +56,7 @@
|
|||||||
</message>
|
</message>
|
||||||
|
|
||||||
<message id="Lms.PlayQueue.template.entry-artist">
|
<message id="Lms.PlayQueue.template.entry-artist">
|
||||||
${artist class="Lms-explore-artistname"}
|
<small>${artist class="Lms-artistname text-muted"}</small>
|
||||||
</message>
|
</message>
|
||||||
|
|
||||||
</messages>
|
</messages>
|
||||||
|
|||||||
+15
-10
@@ -13,29 +13,34 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
<h3>${name}</h3>
|
<h3><div class="Lms-releasename text-primary">${name}</div></h3>
|
||||||
${<if-has-artist>}<h4>${artist class="Lms-explore-artistname"}</h4>${</if-has-artist>}
|
${<if-has-artist>}<h4>${artist class="Lms-artistname text-muted"}</h4>${</if-has-artist>}
|
||||||
<h4><small>${<if-has-year>}${year}${</if-has-year>}${<if-has-orig-year>} (${orig-year})${</if-has-orig-year>}</small></h4>
|
<div class="text-muted"><small>${<if-has-year>}${year}${</if-has-year>}${<if-has-orig-year>} (${orig-year})${</if-has-orig-year>}</small></div>
|
||||||
${clusters}
|
${clusters}
|
||||||
${play-btn}${add-btn}
|
${play-btn}${add-btn}
|
||||||
</div>
|
</div>
|
||||||
|
${container}
|
||||||
|
</message>
|
||||||
|
|
||||||
|
<message id="Lms.Explore.Release.template.disc-entry">
|
||||||
|
<h4>${disc-title}</h4>
|
||||||
${tracks}
|
${tracks}
|
||||||
</message>
|
</message>
|
||||||
|
|
||||||
<message id="Lms.Explore.Release.template.entry">
|
<message id="Lms.Explore.Release.template.entry">
|
||||||
<div class="row Lms-explore-release-entry Lms-vertical-align">
|
<div class="row Lms-explore-release-entry Lms-entry-hoverable Lms-vertical-align ${is-playing}">
|
||||||
<div class="col-xs-1 Lms-explore-release-entry-tracknumber">
|
<div class="col-xs-1 Lms-explore-release-entry-tracknumber text-muted">
|
||||||
${<if-has-disc-number>}${disc-number}-${</if-has-disc-number>}${<if-has-track-number>}${track-number}${</if-has-track-number>}
|
${<if-has-track-number>}${track-number}${</if-has-track-number>}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-8 Lms-explore-release-entry-name">
|
<div class="col-xs-8 Lms-explore-release-entry-name">
|
||||||
${name}
|
<div class="Lms-trackname text-primary">${name}</div>
|
||||||
${<if-has-artists>}<small>${artists}</small>${</if-has-artists>}
|
${<if-has-artists>}${artists}${</if-has-artists>}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-3">
|
<div class="col-xs-3">
|
||||||
<div class="Lms-explore-release-entry-controls">
|
<div class="Lms-explore-release-entry-controls">
|
||||||
${play-btn}${add-btn}
|
${play-btn}${add-btn}
|
||||||
</div>
|
</div>
|
||||||
<div class="Lms-explore-release-entry-track-duration">
|
<div class="Lms-explore-release-entry-track-duration text-muted">
|
||||||
${duration}
|
${duration}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -43,7 +48,7 @@
|
|||||||
</message>
|
</message>
|
||||||
|
|
||||||
<message id="Lms.Explore.Release.template.entry-artist">
|
<message id="Lms.Explore.Release.template.entry-artist">
|
||||||
${artist class="Lms-explore-artistname"}
|
<small>${artist class="Lms-artistname text-muted"}</small>
|
||||||
</message>
|
</message>
|
||||||
|
|
||||||
</messages>
|
</messages>
|
||||||
|
|||||||
@@ -3,17 +3,17 @@
|
|||||||
<messages xmlns:if="Wt.WTemplate.conditions">
|
<messages xmlns:if="Wt.WTemplate.conditions">
|
||||||
|
|
||||||
<message id="Lms.Explore.ReleaseLink.template">
|
<message id="Lms.Explore.ReleaseLink.template">
|
||||||
<div class="row Lms-explore-releaselink">
|
<div class="row Lms-explore-releaselink Lms-entry-hoverable">
|
||||||
<div class="col-xs-12">
|
<div class="col-xs-12">
|
||||||
<div class="media">
|
<div class="media">
|
||||||
<div class="media-left media-middle">
|
<div class="media-left media-middle">
|
||||||
${cover class="media-object"}
|
${cover class="media-object Lms-cover"}
|
||||||
</div>
|
</div>
|
||||||
<div class="media-body media-middle">
|
<div class="media-body media-middle">
|
||||||
<div class="media-heading">
|
<div class="media-heading">
|
||||||
${release-name class="Lms-explore-releasename"}
|
${release-name class="Lms-releasename text-primary"}
|
||||||
</div>
|
</div>
|
||||||
${<if-has-artist>}<small>${artist-name class="Lms-explore-artistname Lms-explore-releaselink-artist"}</small>${</if-has-artist>}
|
${<if-has-artist>}<small>${artist-name class="Lms-artistname text-muted"}</small>${</if-has-artist>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -22,17 +22,17 @@
|
|||||||
</message>
|
</message>
|
||||||
|
|
||||||
<message id="Lms.Explore.Releases.template.entry">
|
<message id="Lms.Explore.Releases.template.entry">
|
||||||
<div class="row Lms-explore-releases-entry">
|
<div class="row Lms-explore-releases-entry Lms-entry-hoverable">
|
||||||
<div class="col-xs-12">
|
<div class="col-xs-12">
|
||||||
<div class="media">
|
<div class="media">
|
||||||
<div class="media-left media-middle">
|
<div class="media-left media-middle">
|
||||||
${cover class="media-object"}
|
${cover class="media-object Lms-cover"}
|
||||||
</div>
|
</div>
|
||||||
<div class="media-body media-middle">
|
<div class="media-body media-middle">
|
||||||
<h4 class="media-heading">
|
<h4 class="media-heading">
|
||||||
${release-name class="Lms-explore-releasename"}
|
${release-name class="Lms-releasename text-primary"}
|
||||||
</h4>
|
</h4>
|
||||||
${<if-has-artist>}<h4><small>${artist-name class="Lms-explore-artistname"}</small></h4>${</if-has-artist>}
|
${<if-has-artist>}<h4><small>${artist-name class="Lms-artistname text-muted"}</small></h4>${</if-has-artist>}
|
||||||
${play-btn}${add-btn}
|
${play-btn}${add-btn}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+17
-3
@@ -4,16 +4,30 @@
|
|||||||
<!--FORMS message blocks-->
|
<!--FORMS message blocks-->
|
||||||
|
|
||||||
<message id="Lms.Settings.template">
|
<message id="Lms.Settings.template">
|
||||||
|
<legend>${tr:Lms.Settings.appearance}</legend>
|
||||||
|
<div class="form-horizontal">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-sm-2 control-label" for="${id:dark-mode}">
|
||||||
|
${tr:Lms.Settings.dark-mode}
|
||||||
|
</label>
|
||||||
|
<div class="col-sm-5">
|
||||||
|
<div class="checkbox"><label>${dark-mode}</label></div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-5 help-block">
|
||||||
|
${dark-mode-info}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<legend>${tr:Lms.Settings.audio}</legend>
|
<legend>${tr:Lms.Settings.audio}</legend>
|
||||||
<div class="form-horizontal">
|
<div class="form-horizontal">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="control-label col-sm-2" for="${id:transcoding-enable}">
|
<label class="col-sm-2 control-label" for="${id:transcoding-enable}">
|
||||||
${tr:Lms.Settings.transcoding-enable}
|
${tr:Lms.Settings.transcoding-enable}
|
||||||
</label>
|
</label>
|
||||||
<div class="col-sm-5">
|
<div class="col-sm-5">
|
||||||
${transcoding-enable}
|
<div class="checkbox"><label>${transcoding-enable}</label></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="help-block col-sm-5">
|
<div class="col-sm-5 help-block">
|
||||||
${transcoding-enable-info}
|
${transcoding-enable-info}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<messages xmlns:if="Wt.WTemplate.conditions">
|
<messages xmlns:if="Wt.WTemplate.conditions">
|
||||||
|
|
||||||
<message id="Lms.template">
|
<message id="Lms.template">
|
||||||
${navbar-top class="main-nav"}
|
${navbar-top class="Lms-navbar"}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
${contents class="Lms-contents"}
|
${contents class="Lms-contents"}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+15
-8
@@ -23,19 +23,26 @@
|
|||||||
</message>
|
</message>
|
||||||
|
|
||||||
<message id="Lms.Explore.Tracks.template.entry">
|
<message id="Lms.Explore.Tracks.template.entry">
|
||||||
<div class="row Lms-explore-tracks-entry Lms-vertical-align">
|
<div class="row Lms-explore-tracks-entry Lms-entry-hoverable Lms-vertical-align ${is-playing}">
|
||||||
<div class="col-xs-9 Lms-explore-tracks-entry-name">
|
<div class="col-xs-9 Lms-explore-tracks-entry-name">
|
||||||
<h4>${name}</h4>
|
<div class="media">
|
||||||
${<if-has-artists-or-release>}
|
<div class="media-left media-middle hidden-xs">
|
||||||
${<if-has-release>}<small>${release class="Lms-explore-releasename"}</small>${</if-has-release>}
|
${cover class="media-object Lms-cover"}
|
||||||
${<if-has-artists>}<small>${artists}</small>${</if-has-artists>}
|
</div>
|
||||||
${</if-has-artists-or-release>}
|
<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 class="col-xs-3">
|
<div class="col-xs-3">
|
||||||
<div class="Lms-explore-tracks-entry-controls">
|
<div class="Lms-explore-tracks-entry-controls">
|
||||||
${play-btn}${add-btn}
|
${play-btn}${add-btn}
|
||||||
</div>
|
</div>
|
||||||
<div class="Lms-explore-tracks-entry-duration">
|
<div class="Lms-explore-tracks-entry-duration text-muted">
|
||||||
${duration}
|
${duration}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -43,7 +50,7 @@
|
|||||||
</message>
|
</message>
|
||||||
|
|
||||||
<message id="Lms.Explore.Tracks.template.entry-artist">
|
<message id="Lms.Explore.Tracks.template.entry-artist">
|
||||||
${artist class="Lms-explore-artistname"}
|
<small>${artist class="Lms-artistname text-muted"}</small>
|
||||||
</message>
|
</message>
|
||||||
|
|
||||||
</messages>
|
</messages>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
<message id="Lms.Explore.TracksInfo.template.entry">
|
<message id="Lms.Explore.TracksInfo.template.entry">
|
||||||
<div class="row Lms-explore-tracksinfo-entry Lms-vertical-align">
|
<div class="row Lms-explore-tracksinfo-entry Lms-vertical-align">
|
||||||
<div class="col-xs-12">
|
<div class="col-xs-12 Lms-trackname">
|
||||||
${name}
|
${name}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+11
File diff suppressed because one or more lines are too long
+11
File diff suppressed because one or more lines are too long
@@ -0,0 +1,41 @@
|
|||||||
|
:root {
|
||||||
|
--body-color: #ffffff;
|
||||||
|
--hover-color: #00bc8c;
|
||||||
|
--playing-background-color: #375a7f4f;
|
||||||
|
--slider-background-color: #5a5a5a;
|
||||||
|
--player-background-color: #303030;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Lms-artistname.text-muted {
|
||||||
|
font-style: italic;
|
||||||
|
color: #7e7e7e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Lms-artistname.text-primary, .Lms-artistname.text-primary:hover {
|
||||||
|
color: var(--body-color)
|
||||||
|
}
|
||||||
|
|
||||||
|
a.Lms-artistname.text-primary:hover {
|
||||||
|
color: var(--hover-color)
|
||||||
|
}
|
||||||
|
|
||||||
|
.Lms-control-btn {
|
||||||
|
color: var(--body-color)
|
||||||
|
}
|
||||||
|
|
||||||
|
.Lms-releasename.text-primary, .Lms-releasename.text-primary:hover {
|
||||||
|
color: var(--body-color)
|
||||||
|
}
|
||||||
|
|
||||||
|
a.Lms-releasename.text-primary:hover {
|
||||||
|
color: var(--hover-color)
|
||||||
|
}
|
||||||
|
|
||||||
|
.Lms-trackname.text-primary, .Lms-trackname.text-primary:hover {
|
||||||
|
color: var(--body-color)
|
||||||
|
}
|
||||||
|
|
||||||
|
.media, .media-body {
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
:root {
|
||||||
|
--body-color: #2c3e50;
|
||||||
|
--hover-color: #18bc9c;
|
||||||
|
--playing-background-color: #2c3e5029;
|
||||||
|
--slider-background-color: #2c3e5029;
|
||||||
|
--player-background-color: #ecf0f1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Lms-artistname.text-muted {
|
||||||
|
font-style: italic;
|
||||||
|
color: #7e8f9e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media, .media-body {
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
+111
-214
@@ -1,26 +1,9 @@
|
|||||||
|
/* Stylized for flaty/darkly themes */
|
||||||
a, a:link, a:active, a:visited {
|
a, a:link, a:active, a:visited {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
a:hover {
|
|
||||||
color: #337ab7;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Base style for artist names */
|
|
||||||
.Lms-explore-artistname
|
|
||||||
{
|
|
||||||
color: darkgrey;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Base style for release names */
|
|
||||||
.Lms-explore-releasename
|
|
||||||
{
|
|
||||||
color: black;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Lms-vertical-align {
|
.Lms-vertical-align {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -52,10 +35,6 @@ a:hover {
|
|||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-admin-users-entry:hover {
|
|
||||||
background-color: lightgrey;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Lms-admin-users-entry-role {
|
.Lms-admin-users-entry-role {
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
}
|
}
|
||||||
@@ -64,101 +43,78 @@ a:hover {
|
|||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.Lms-artistname {
|
||||||
|
color: var(--body-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
a.Lms-artistname:hover, a.Lms-artistname:focus {
|
||||||
|
color: var(--hover-color);
|
||||||
|
}
|
||||||
|
|
||||||
.Lms-cluster {
|
.Lms-cluster {
|
||||||
display: inline;
|
display: inline-block;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
line-height: 1.3;
|
||||||
|
margin-right: 4px;
|
||||||
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-cluster:hover {
|
.Lms-cluster:hover {
|
||||||
filter: brightness(85%);
|
filter: brightness(130%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-cluster-type-0 {
|
.Lms-control-btn:hover {
|
||||||
background-color: darkslategrey;
|
color: var(--hover-color);
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-cluster-type-1 {
|
.Lms-cover:hover {
|
||||||
background-color: darkblue;
|
filter: brightness(130%);
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-cluster-type-2 {
|
.Lms-cover-smaller {
|
||||||
background-color: darkgrey;
|
width: 64px; /* some cover may not be square */
|
||||||
margin-bottom: 4px;
|
border-radius: 3px;
|
||||||
|
box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-cluster-type-3 {
|
.Lms-cover-small {
|
||||||
background-color: darkorchid;
|
width: 96px; /* some cover may not be square */
|
||||||
margin-bottom: 4px;
|
border-radius: 3px;
|
||||||
|
box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-cluster-type-4 {
|
.Lms-cover-medium {
|
||||||
background-color: darkgoldenrod;
|
width: 128px;
|
||||||
margin-bottom: 4px;
|
border-radius: 3px;
|
||||||
|
box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-cluster-type-5 {
|
.Lms-cover-large {
|
||||||
background-color: darkkhaki;
|
border-radius: 3px;
|
||||||
margin-bottom: 4px;
|
box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-cluster-type-6 {
|
.Lms-entry-playing {
|
||||||
background-color: darkolivegreen;
|
font-weight: bold;
|
||||||
margin-bottom: 4px;
|
border-radius: 3px;
|
||||||
}
|
background-color: var(--playing-background-color);
|
||||||
|
|
||||||
.Lms-cluster-type-7 {
|
|
||||||
background-color: darkred;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Lms-cluster-type-8 {
|
|
||||||
background-color: darkviolet;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Lms-cluster-type-9 {
|
|
||||||
background-color: darkturquoise;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-explore-contents {
|
.Lms-explore-contents {
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-explore-filters {
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
padding: 8px;
|
|
||||||
border-radius: 8px;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Lms-explore-info {
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
padding: 8px;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Lms-explore-releases-entry {
|
.Lms-explore-releases-entry {
|
||||||
padding-top: 8px;
|
padding-top: 8px;
|
||||||
padding-bottom: 8px;
|
padding-bottom: 8px;
|
||||||
margin: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Lms-explore-releases-entry:hover {
|
|
||||||
background-color: lightgrey;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-explore-release-cover {
|
.Lms-explore-release-cover {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.4);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-explore-release-entry-tracknumber {
|
.Lms-explore-release-entry-tracknumber {
|
||||||
color: darkgrey;
|
text-align: right;
|
||||||
text-align: center;
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,15 +125,7 @@ a:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.Lms-explore-release-entry {
|
.Lms-explore-release-entry {
|
||||||
border-bottom-style: solid;
|
|
||||||
border-bottom-color: lightgrey;
|
|
||||||
border-bottom-width: thin;
|
|
||||||
min-height: 32px;
|
min-height: 32px;
|
||||||
margin: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Lms-explore-release-entry:hover {
|
|
||||||
background-color: lightgrey;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-explore-release-entry:hover .Lms-explore-release-entry-controls {
|
.Lms-explore-release-entry:hover .Lms-explore-release-entry-controls {
|
||||||
@@ -193,12 +141,7 @@ a:hover {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-explore-release-entry-playing {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Lms-explore-release-entry-track-duration {
|
.Lms-explore-release-entry-track-duration {
|
||||||
color: darkgrey;
|
|
||||||
text-align: right;
|
text-align: right;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
display: block;
|
display: block;
|
||||||
@@ -210,25 +153,11 @@ a:hover {
|
|||||||
padding-bottom: 4px;
|
padding-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-explore-releaselink:hover {
|
|
||||||
background-color: lightgrey;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Lms-explore-releaselink-artist {
|
|
||||||
color: dimgrey;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Lms-explore-artists-entry {
|
.Lms-explore-artists-entry {
|
||||||
min-height: 48px;
|
min-height: 48px;
|
||||||
margin: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Lms-explore-artists-entry:hover {
|
|
||||||
background-color: lightgrey;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-explore-artists-entry-name {
|
.Lms-explore-artists-entry-name {
|
||||||
color: black;
|
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,11 +168,6 @@ a:hover {
|
|||||||
.Lms-explore-artist-entry {
|
.Lms-explore-artist-entry {
|
||||||
padding-top: 8px;
|
padding-top: 8px;
|
||||||
padding-bottom: 8px;
|
padding-bottom: 8px;
|
||||||
margin: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Lms-explore-artist-entry:hover {
|
|
||||||
background-color: lightgrey;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-explore-artistlink {
|
.Lms-explore-artistlink {
|
||||||
@@ -251,25 +175,8 @@ a:hover {
|
|||||||
margin: 0px;
|
margin: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-explore-artistlink:hover {
|
|
||||||
background-color: lightgrey;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Lms-explore-artistlink-name {
|
|
||||||
color: black;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Lms-explore-tracks-entry {
|
.Lms-explore-tracks-entry {
|
||||||
min-height: 32px;
|
min-height: 32px;
|
||||||
border-bottom-style: solid;
|
|
||||||
border-bottom-color: lightgrey;
|
|
||||||
border-bottom-width: thin;
|
|
||||||
margin: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Lms-explore-tracks-entry:hover {
|
|
||||||
background-color: lightgrey;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-explore-tracks-entry:hover .Lms-explore-tracks-entry-controls {
|
.Lms-explore-tracks-entry:hover .Lms-explore-tracks-entry-controls {
|
||||||
@@ -291,7 +198,6 @@ a:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.Lms-explore-tracks-entry-duration {
|
.Lms-explore-tracks-entry-duration {
|
||||||
color: darkgrey;
|
|
||||||
text-align: right;
|
text-align: right;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
display: block;
|
display: block;
|
||||||
@@ -302,30 +208,19 @@ a:hover {
|
|||||||
margin: 0px;
|
margin: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-explore-tracksinfo-entry:hover {
|
|
||||||
background-color: lightgrey;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.Lms-explore-btn {
|
.Lms-explore-btn {
|
||||||
font-size: 22px !important;
|
font-size: 24px !important;
|
||||||
|
text-align: center;
|
||||||
|
width: 28px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-explore-btn:hover {
|
.Lms-navbar {
|
||||||
color: #337ab7;
|
border-radius: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-playqueue-entry {
|
.Lms-playqueue-entry {
|
||||||
border-bottom-style: solid;
|
|
||||||
border-bottom-color: lightgrey;
|
|
||||||
border-bottom-width: thin;
|
|
||||||
min-height: 32px;
|
min-height: 32px;
|
||||||
margin: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Lms-playqueue-entry:hover {
|
|
||||||
background-color: lightgrey;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-playqueue-entry:hover .Lms-playqueue-entry-controls {
|
.Lms-playqueue-entry:hover .Lms-playqueue-entry-controls {
|
||||||
@@ -339,15 +234,11 @@ a:hover {
|
|||||||
.Lms-playqueue-btn {
|
.Lms-playqueue-btn {
|
||||||
font-size: 22px !important;
|
font-size: 22px !important;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
margin: 2px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-playqueue-btn:hover {
|
.text-success .Lms-playqueue-btn {
|
||||||
color: #337ab7;
|
filter: brightness(130%);
|
||||||
}
|
filter: drop-shadow(0 0 0.65rem var(--hover-color));
|
||||||
|
|
||||||
.Lms-playqueue-btn-selected {
|
|
||||||
color: #337ab7;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-playqueue-entry-controls {
|
.Lms-playqueue-entry-controls {
|
||||||
@@ -356,7 +247,6 @@ a:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.Lms-playqueue-entry-duration {
|
.Lms-playqueue-entry-duration {
|
||||||
color: darkgrey;
|
|
||||||
text-align: right;
|
text-align: right;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
display: block;
|
display: block;
|
||||||
@@ -368,7 +258,9 @@ a:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.Lms-playqueue-selected {
|
.Lms-playqueue-selected {
|
||||||
font-weight: 800;
|
font-weight: bold;
|
||||||
|
border-radius: 3px;
|
||||||
|
background-color: var(--playing-background-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-playqueue-selected h4 {
|
.Lms-playqueue-selected h4 {
|
||||||
@@ -376,15 +268,8 @@ a:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.Lms-playhistory-entry {
|
.Lms-playhistory-entry {
|
||||||
border-bottom-style: solid;
|
|
||||||
border-bottom-color: lightgrey;
|
|
||||||
border-bottom-width: thin;
|
|
||||||
min-height: 32px;
|
min-height: 32px;
|
||||||
margin: 0px;
|
margin-bottom: 0px;
|
||||||
}
|
|
||||||
|
|
||||||
.Lms-playhistory-entry:hover {
|
|
||||||
background-color: lightgrey;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-playhistory-entry-name {
|
.Lms-playhistory-entry-name {
|
||||||
@@ -393,26 +278,20 @@ a:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.Lms-player {
|
.Lms-player {
|
||||||
|
z-index: 3;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 70px;
|
height: 70px;
|
||||||
background-color: #f5f5f5;
|
padding: 0px;
|
||||||
box-shadow: 0px -2px 8px #f5f5f5;
|
margin: 0px;
|
||||||
}
|
background-color: var(--player-background-color);
|
||||||
|
|
||||||
.Lms-player-artistname {
|
|
||||||
color: dimgrey;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-player-btn {
|
.Lms-player-btn {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-player-btn:hover {
|
|
||||||
color: #337ab7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Lms-player-btn-volume {
|
.Lms-player-btn-volume {
|
||||||
font-size: 18px !important;
|
font-size: 18px !important;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
@@ -421,16 +300,42 @@ a:hover {
|
|||||||
.Lms-player-btn-controls {
|
.Lms-player-btn-controls {
|
||||||
font-size: 40px !important;
|
font-size: 40px !important;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
width: 0.9em;
|
width: 1.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Lms-player-controls {
|
||||||
|
text-align: center;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-player-duration {
|
.Lms-player-duration {
|
||||||
line-height: 40px;
|
line-height: 40px;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
color: darkgrey;
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.Lms-player-progress {
|
||||||
|
background-color: var(--slider-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.Lms-player-seek {
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 1;
|
||||||
|
touch-action: manipulation;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
background: transparent;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: thin;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Lms-player-seek::-webkit-slider-thumb {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
height: 0px;
|
||||||
|
width: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
.Lms-player-seek-container {
|
.Lms-player-seek-container {
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
@@ -445,22 +350,7 @@ a:hover {
|
|||||||
top: 0px;
|
top: 0px;
|
||||||
left: 0px;
|
left: 0px;
|
||||||
height: 8px;
|
height: 8px;
|
||||||
}
|
border-radius: 3px;
|
||||||
|
|
||||||
.Lms-player-seek {
|
|
||||||
cursor: pointer;
|
|
||||||
z-index: 1;
|
|
||||||
touch-action: manipulation;
|
|
||||||
-webkit-appearance: none;
|
|
||||||
-moz-appearance: none;
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Lms-player-seek::-webkit-slider-thumb {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
-moz-appearance: none;
|
|
||||||
height: 0px;
|
|
||||||
width: 0px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-player-seek::-moz-range-thumb {
|
.Lms-player-seek::-moz-range-thumb {
|
||||||
@@ -470,15 +360,6 @@ a:hover {
|
|||||||
width: 0px;
|
width: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-player-progress {
|
|
||||||
background-color: darkgrey;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Lms-player-controls {
|
|
||||||
text-align: center;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Lms-player-text-center {
|
.Lms-player-text-center {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
@@ -497,10 +378,20 @@ a:hover {
|
|||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-player-volume-slider-container {
|
.Lms-player-volume-slider::-webkit-slider-thumb {
|
||||||
display: inline-block;
|
background-color: var(--body-color);
|
||||||
width: 85px;
|
}
|
||||||
margin: auto;
|
|
||||||
|
.Lms-player-volume-slider::-webkit-slider-thumb:hover {
|
||||||
|
background-color: var(--hover-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.Lms-player-volume-slider::-moz-range-thumb {
|
||||||
|
background-color: var(--body-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.Lms-player-volume-slider::-moz-range-thumb:hover {
|
||||||
|
background-color: var(--hover-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-player-volume-slider {
|
.Lms-player-volume-slider {
|
||||||
@@ -508,10 +399,12 @@ a:hover {
|
|||||||
appearance: none;
|
appearance: none;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 8px;
|
height: 8px;
|
||||||
background: lightgrey;
|
|
||||||
outline: none;
|
outline: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: 4px;
|
border-radius: 3px;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: thin;
|
||||||
|
background-color: var(--slider-background-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-player-volume-slider::-webkit-slider-thumb {
|
.Lms-player-volume-slider::-webkit-slider-thumb {
|
||||||
@@ -520,21 +413,25 @@ a:hover {
|
|||||||
width: 14px;
|
width: 14px;
|
||||||
height: 14px;
|
height: 14px;
|
||||||
border-radius: 7px;
|
border-radius: 7px;
|
||||||
background: darkgrey;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-player-volume-slider::-moz-range-thumb {
|
.Lms-player-volume-slider::-moz-range-thumb {
|
||||||
width: 14px;
|
width: 14px;
|
||||||
height: 14px;
|
height: 14px;
|
||||||
border-radius: 7px;
|
border-radius: 7px;
|
||||||
background: darkgrey;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-player-volume-slider::-webkit-slider-thumb:hover {
|
.Lms-player-volume-slider-container {
|
||||||
background: #337ab7;
|
display: inline-block;
|
||||||
|
width: 85px;
|
||||||
|
margin: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-player-volume-slider::-moz-range-thumb:hover {
|
.Lms-releasename {
|
||||||
background: #337ab7;
|
color: var(--body-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
a.Lms-releasename:hover, a.Lms-releasename:focus {
|
||||||
|
color: var(--hover-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -189,7 +189,7 @@ Grabber::getFromTrack(Database::Session& dbSession, Database::IdType trackId, st
|
|||||||
trackPath = track->getPath();
|
trackPath = track->getPath();
|
||||||
|
|
||||||
auto release {track->getRelease()};
|
auto release {track->getRelease()};
|
||||||
if (release && release->getTotalDiscNumber() > 1)
|
if (release && release->getTotalDisc() > 1)
|
||||||
isMultiDisc = true;
|
isMultiDisc = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,9 +33,9 @@ Db::Db(const std::filesystem::path& dbPath)
|
|||||||
LMS_LOG(DB, INFO) << "Creating connection pool on file " << dbPath.string();
|
LMS_LOG(DB, INFO) << "Creating connection pool on file " << dbPath.string();
|
||||||
|
|
||||||
std::unique_ptr<Wt::Dbo::backend::Sqlite3> connection {std::make_unique<Wt::Dbo::backend::Sqlite3>(dbPath.string())};
|
std::unique_ptr<Wt::Dbo::backend::Sqlite3> connection {std::make_unique<Wt::Dbo::backend::Sqlite3>(dbPath.string())};
|
||||||
|
// connection->setProperty("show-queries", "true");
|
||||||
connection->executeSql("pragma journal_mode=WAL");
|
connection->executeSql("pragma journal_mode=WAL");
|
||||||
connection->executeSql("pragma synchronous=normal");
|
connection->executeSql("pragma synchronous=normal");
|
||||||
// connection->setProperty("show-queries", "true");
|
|
||||||
|
|
||||||
auto connectionPool = std::make_unique<Wt::Dbo::FixedSqlConnectionPool>(std::move(connection), 10);
|
auto connectionPool = std::make_unique<Wt::Dbo::FixedSqlConnectionPool>(std::move(connection), 10);
|
||||||
connectionPool->setTimeout(std::chrono::seconds(10));
|
connectionPool->setTimeout(std::chrono::seconds(10));
|
||||||
@@ -43,6 +43,14 @@ Db::Db(const std::filesystem::path& dbPath)
|
|||||||
_connectionPool = std::move(connectionPool);
|
_connectionPool = std::move(connectionPool);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Db::executeSql(const std::string& sql)
|
||||||
|
{
|
||||||
|
auto connection {_connectionPool->getConnection()};
|
||||||
|
connection->executeSql(sql);
|
||||||
|
_connectionPool->returnConnection(std::move(connection));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Database
|
} // namespace Database
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -268,15 +268,29 @@ Release::getAllIdsWithClusters(Session& session, std::optional<std::size_t> limi
|
|||||||
|
|
||||||
|
|
||||||
std::optional<std::size_t>
|
std::optional<std::size_t>
|
||||||
Release::getTotalTrackNumber(void) const
|
Release::getTotalTrack(void) const
|
||||||
{
|
{
|
||||||
return (_totalTrackNumber > 0) ? std::make_optional<std::size_t>(_totalTrackNumber) : std::nullopt;
|
assert(session());
|
||||||
|
assert(IdIsValid(self()->id()));
|
||||||
|
|
||||||
|
int res = session()->query<int>("SELECT COALESCE(MAX(total_track),0) FROM track t INNER JOIN release r ON r.id = t.release_id")
|
||||||
|
.where("r.id = ?")
|
||||||
|
.bind(this->id());
|
||||||
|
|
||||||
|
return (res > 0) ? std::make_optional<std::size_t>(res) : std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<std::size_t>
|
std::optional<std::size_t>
|
||||||
Release::getTotalDiscNumber(void) const
|
Release::getTotalDisc(void) const
|
||||||
{
|
{
|
||||||
return (_totalDiscNumber > 0) ? std::make_optional<std::size_t>(_totalDiscNumber) : std::nullopt;
|
assert(session());
|
||||||
|
assert(IdIsValid(self()->id()));
|
||||||
|
|
||||||
|
int res = session()->query<int>("SELECT COALESCE(MAX(total_disc),0) FROM track t INNER JOIN release r ON r.id = t.release_id")
|
||||||
|
.where("r.id = ?")
|
||||||
|
.bind(this->id());
|
||||||
|
|
||||||
|
return (res > 0) ? std::make_optional<std::size_t>(res) : std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<int>
|
std::optional<int>
|
||||||
@@ -287,7 +301,7 @@ Release::getReleaseYear(bool original) const
|
|||||||
const std::string field {original ? "original_year" : "year"};
|
const std::string field {original ? "original_year" : "year"};
|
||||||
|
|
||||||
Wt::Dbo::collection<int> dates = session()->query<int>(
|
Wt::Dbo::collection<int> dates = session()->query<int>(
|
||||||
std::string{"SELECT "} + "t." + field + " FROM track t INNER JOIN release r ON r.id = t.release_id")
|
std::string {"SELECT "} + "t." + field + " FROM track t INNER JOIN release r ON r.id = t.release_id")
|
||||||
.where("r.id = ?")
|
.where("r.id = ?")
|
||||||
.groupBy(field)
|
.groupBy(field)
|
||||||
.bind(this->id());
|
.bind(this->id());
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
|
|
||||||
namespace Database {
|
namespace Database {
|
||||||
|
|
||||||
#define LMS_DATABASE_VERSION 15
|
#define LMS_DATABASE_VERSION 18
|
||||||
|
|
||||||
using Version = std::size_t;
|
using Version = std::size_t;
|
||||||
|
|
||||||
@@ -83,26 +83,31 @@ class VersionInfo
|
|||||||
void
|
void
|
||||||
Session::doDatabaseMigrationIfNeeded()
|
Session::doDatabaseMigrationIfNeeded()
|
||||||
{
|
{
|
||||||
auto uniqueTransaction {createUniqueTransaction()};
|
|
||||||
|
|
||||||
static const std::string outdatedMsg {"Outdated database, please rebuild it (delete the .db file and restart)"};
|
static const std::string outdatedMsg {"Outdated database, please rebuild it (delete the .db file and restart)"};
|
||||||
|
|
||||||
Version version;
|
Db::ScopedNoForeignKeys noPragmaKeys {_db};
|
||||||
try
|
|
||||||
{
|
|
||||||
version = VersionInfo::getOrCreate(*this)->getVersion();
|
|
||||||
LMS_LOG(DB, INFO) << "Database version = " << version << ", LMS binary version = " << LMS_DATABASE_VERSION;
|
|
||||||
if (version == LMS_DATABASE_VERSION)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
catch (std::exception& e)
|
|
||||||
{
|
|
||||||
LMS_LOG(DB, ERROR) << "Cannot get database version info: " << e.what();
|
|
||||||
throw LmsException {outdatedMsg};
|
|
||||||
}
|
|
||||||
|
|
||||||
while (version < LMS_DATABASE_VERSION)
|
while (1)
|
||||||
{
|
{
|
||||||
|
auto uniqueTransaction {createUniqueTransaction()};
|
||||||
|
|
||||||
|
Version version;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
version = VersionInfo::getOrCreate(*this)->getVersion();
|
||||||
|
LMS_LOG(DB, INFO) << "Database version = " << version << ", LMS binary version = " << LMS_DATABASE_VERSION;
|
||||||
|
if (version == LMS_DATABASE_VERSION)
|
||||||
|
{
|
||||||
|
LMS_LOG(DB, DEBUG) << "Lms database version " << LMS_DATABASE_VERSION << ": up to date!";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (std::exception& e)
|
||||||
|
{
|
||||||
|
LMS_LOG(DB, ERROR) << "Cannot get database version info: " << e.what();
|
||||||
|
throw LmsException {outdatedMsg};
|
||||||
|
}
|
||||||
|
|
||||||
LMS_LOG(DB, INFO) << "Migrating database from version " << version << "...";
|
LMS_LOG(DB, INFO) << "Migrating database from version " << version << "...";
|
||||||
|
|
||||||
if (version == 5)
|
if (version == 5)
|
||||||
@@ -169,15 +174,45 @@ CREATE TABLE IF NOT EXISTS "track_bookmark" (
|
|||||||
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
|
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
|
||||||
ScanSettings::get(*this).modify()->incScanVersion();
|
ScanSettings::get(*this).modify()->incScanVersion();
|
||||||
}
|
}
|
||||||
|
else if (version == 15)
|
||||||
|
{
|
||||||
|
_session.execute("ALTER TABLE user ADD ui_theme INTEGER NOT NULL DEFAULT(" + std::to_string(static_cast<int>(User::defaultUITheme)) + ")");
|
||||||
|
}
|
||||||
|
else if (version == 16)
|
||||||
|
{
|
||||||
|
_session.execute("ALTER TABLE track ADD total_disc INTEGER NOT NULL DEFAULT(0)");
|
||||||
|
_session.execute("ALTER TABLE track ADD total_track INTEGER NOT NULL DEFAULT(0)");
|
||||||
|
|
||||||
|
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
|
||||||
|
ScanSettings::get(*this).modify()->incScanVersion();
|
||||||
|
}
|
||||||
|
else if (version == 17)
|
||||||
|
{
|
||||||
|
// Drop colums total_disc/total_track from release
|
||||||
|
_session.execute(R"(
|
||||||
|
CREATE TABLE "release_backup" (
|
||||||
|
"id" integer primary key autoincrement,
|
||||||
|
"version" integer not null,
|
||||||
|
"name" text not null,
|
||||||
|
"mbid" text not null
|
||||||
|
))");
|
||||||
|
_session.execute("INSERT INTO release_backup SELECT id,version,name,mbid FROM release");
|
||||||
|
_session.execute("DROP TABLE release;");
|
||||||
|
_session.execute("ALTER TABLE release_backup RENAME TO release");
|
||||||
|
_session.execute("CREATE INDEX release_name_idx ON release(name)");
|
||||||
|
_session.execute("CREATE INDEX release_name_nocase_idx ON release(name COLLATE NOCASE)");
|
||||||
|
_session.execute("CREATE INDEX release_mbid_idx ON release(mbid)");
|
||||||
|
|
||||||
|
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
|
||||||
|
ScanSettings::get(*this).modify()->incScanVersion();
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
LMS_LOG(DB, ERROR) << "Database version " << version << " cannot be handled using migration";
|
LMS_LOG(DB, ERROR) << "Database version " << version << " cannot be handled using migration";
|
||||||
throw LmsException { LMS_DATABASE_VERSION > version ? outdatedMsg : "Server binary outdated, please upgrade it to handle this database"};
|
throw LmsException { LMS_DATABASE_VERSION > version ? outdatedMsg : "Server binary outdated, please upgrade it to handle this database"};
|
||||||
}
|
}
|
||||||
|
|
||||||
++version;
|
VersionInfo::get(*this).modify()->setVersion(++version);
|
||||||
|
|
||||||
VersionInfo::get(*this).modify()->setVersion(LMS_DATABASE_VERSION);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ Track::getAllIdsWithClusters(Session& session, std::optional<std::size_t> limit)
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::vector<Cluster::pointer>
|
std::vector<Cluster::pointer>
|
||||||
Track::getClusters(void) const
|
Track::getClusters() const
|
||||||
{
|
{
|
||||||
std::vector< Cluster::pointer > clusters;
|
std::vector< Cluster::pointer > clusters;
|
||||||
std::copy(_clusters.begin(), _clusters.end(), std::back_inserter(clusters));
|
std::copy(_clusters.begin(), _clusters.end(), std::back_inserter(clusters));
|
||||||
@@ -185,7 +185,7 @@ Track::getClusters(void) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::vector<IdType>
|
std::vector<IdType>
|
||||||
Track::getClusterIds(void) const
|
Track::getClusterIds() const
|
||||||
{
|
{
|
||||||
assert(self());
|
assert(self());
|
||||||
assert(IdIsValid(self()->id()));
|
assert(IdIsValid(self()->id()));
|
||||||
@@ -355,17 +355,29 @@ Track::setFeatures(const Wt::Dbo::ptr<TrackFeatures>& features)
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::optional<std::size_t>
|
std::optional<std::size_t>
|
||||||
Track::getTrackNumber(void) const
|
Track::getTrackNumber() const
|
||||||
{
|
{
|
||||||
return (_trackNumber > 0) ? std::make_optional<std::size_t>(_trackNumber) : std::nullopt;
|
return (_trackNumber > 0) ? std::make_optional<std::size_t>(_trackNumber) : std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<std::size_t>
|
std::optional<std::size_t>
|
||||||
Track::getDiscNumber(void) const
|
Track::getTotalTrack() const
|
||||||
|
{
|
||||||
|
return (_totalTrack > 0) ? std::make_optional<std::size_t>(_totalTrack) : std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::size_t>
|
||||||
|
Track::getDiscNumber() const
|
||||||
{
|
{
|
||||||
return (_discNumber > 0) ? std::make_optional<std::size_t>(_discNumber) : std::nullopt;
|
return (_discNumber > 0) ? std::make_optional<std::size_t>(_discNumber) : std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<std::size_t>
|
||||||
|
Track::getTotalDisc() const
|
||||||
|
{
|
||||||
|
return (_totalDisc > 0) ? std::make_optional<std::size_t>(_totalDisc) : std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
std::optional<int>
|
std::optional<int>
|
||||||
Track::getYear() const
|
Track::getYear() const
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -39,6 +39,25 @@ class Db
|
|||||||
std::shared_mutex& getMutex() { return _sharedMutex; }
|
std::shared_mutex& getMutex() { return _sharedMutex; }
|
||||||
Wt::Dbo::SqlConnectionPool& getConnectionPool() { return *_connectionPool; }
|
Wt::Dbo::SqlConnectionPool& getConnectionPool() { return *_connectionPool; }
|
||||||
|
|
||||||
|
class ScopedNoForeignKeys
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ScopedNoForeignKeys(Db& db) : _db {db}
|
||||||
|
{
|
||||||
|
_db.executeSql("PRAGMA foreign_keys=OFF");
|
||||||
|
}
|
||||||
|
~ScopedNoForeignKeys()
|
||||||
|
{
|
||||||
|
_db.executeSql("PRAGMA foreign_keys=ON");
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Db& _db;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
void executeSql(const std::string& sql);
|
||||||
|
|
||||||
std::shared_mutex _sharedMutex;
|
std::shared_mutex _sharedMutex;
|
||||||
std::unique_ptr<Wt::Dbo::SqlConnectionPool> _connectionPool;
|
std::unique_ptr<Wt::Dbo::SqlConnectionPool> _connectionPool;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -84,15 +84,11 @@ class Release : public Wt::Dbo::Dbo<Release>
|
|||||||
std::optional<std::string> getCopyright() const;
|
std::optional<std::string> getCopyright() const;
|
||||||
std::optional<std::string> getCopyrightURL() const;
|
std::optional<std::string> getCopyrightURL() const;
|
||||||
|
|
||||||
// Modifiers
|
|
||||||
void setTotalDiscNumber(std::size_t num) { _totalDiscNumber = static_cast<int>(num); }
|
|
||||||
void setTotalTrackNumber(std::size_t num) { _totalTrackNumber = static_cast<int>(num); }
|
|
||||||
|
|
||||||
// Accessors
|
// Accessors
|
||||||
const std::string& getName() const { return _name; }
|
const std::string& getName() const { return _name; }
|
||||||
std::optional<UUID> getMBID() const { return UUID::fromString(_MBID); }
|
std::optional<UUID> getMBID() const { return UUID::fromString(_MBID); }
|
||||||
std::optional<std::size_t> getTotalTrackNumber() const;
|
std::optional<std::size_t> getTotalTrack() const;
|
||||||
std::optional<std::size_t> getTotalDiscNumber() const;
|
std::optional<std::size_t> getTotalDisc() const;
|
||||||
std::chrono::milliseconds getDuration() const;
|
std::chrono::milliseconds getDuration() const;
|
||||||
|
|
||||||
// Get the artists of this release
|
// Get the artists of this release
|
||||||
@@ -109,8 +105,6 @@ class Release : public Wt::Dbo::Dbo<Release>
|
|||||||
{
|
{
|
||||||
Wt::Dbo::field(a, _name, "name");
|
Wt::Dbo::field(a, _name, "name");
|
||||||
Wt::Dbo::field(a, _MBID, "mbid");
|
Wt::Dbo::field(a, _MBID, "mbid");
|
||||||
Wt::Dbo::field(a, _totalDiscNumber, "total_disc_number");
|
|
||||||
Wt::Dbo::field(a, _totalTrackNumber, "total_track_number");
|
|
||||||
|
|
||||||
Wt::Dbo::hasMany(a, _tracks, Wt::Dbo::ManyToOne, "release");
|
Wt::Dbo::hasMany(a, _tracks, Wt::Dbo::ManyToOne, "release");
|
||||||
Wt::Dbo::hasMany(a, _starringUsers, Wt::Dbo::ManyToMany, "user_release_starred", "", Wt::Dbo::OnDeleteCascade);
|
Wt::Dbo::hasMany(a, _starringUsers, Wt::Dbo::ManyToMany, "user_release_starred", "", Wt::Dbo::OnDeleteCascade);
|
||||||
@@ -121,8 +115,6 @@ class Release : public Wt::Dbo::Dbo<Release>
|
|||||||
|
|
||||||
std::string _name;
|
std::string _name;
|
||||||
std::string _MBID;
|
std::string _MBID;
|
||||||
int _totalDiscNumber {};
|
|
||||||
int _totalTrackNumber {};
|
|
||||||
|
|
||||||
Wt::Dbo::collection<Wt::Dbo::ptr<Track>> _tracks; // Tracks in the release
|
Wt::Dbo::collection<Wt::Dbo::ptr<Track>> _tracks; // Tracks in the release
|
||||||
Wt::Dbo::collection<Wt::Dbo::ptr<User>> _starringUsers; // Users that starred this release
|
Wt::Dbo::collection<Wt::Dbo::ptr<User>> _starringUsers; // Users that starred this release
|
||||||
|
|||||||
@@ -88,6 +88,8 @@ class Track : public Wt::Dbo::Dbo<Track>
|
|||||||
void setScanVersion(std::size_t version) { _scanVersion = version; }
|
void setScanVersion(std::size_t version) { _scanVersion = version; }
|
||||||
void setTrackNumber(int num) { _trackNumber = num; }
|
void setTrackNumber(int num) { _trackNumber = num; }
|
||||||
void setDiscNumber(int num) { _discNumber = num; }
|
void setDiscNumber(int num) { _discNumber = num; }
|
||||||
|
void setTotalTrack(std::optional<int> totalTrack) { totalTrack ? _totalTrack = *totalTrack : 0; }
|
||||||
|
void setTotalDisc(std::optional<int> totalDisc) { totalDisc ? _totalDisc = *totalDisc : 0; }
|
||||||
void setName(const std::string& name) { _name = std::string(name, 0, _maxNameLength); }
|
void setName(const std::string& name) { _name = std::string(name, 0, _maxNameLength); }
|
||||||
void setDuration(std::chrono::milliseconds duration) { _duration = duration; }
|
void setDuration(std::chrono::milliseconds duration) { _duration = duration; }
|
||||||
void setLastWriteTime(Wt::WDateTime time) { _fileLastWrite = time; }
|
void setLastWriteTime(Wt::WDateTime time) { _fileLastWrite = time; }
|
||||||
@@ -106,7 +108,9 @@ class Track : public Wt::Dbo::Dbo<Track>
|
|||||||
|
|
||||||
std::size_t getScanVersion() const { return _scanVersion; }
|
std::size_t getScanVersion() const { return _scanVersion; }
|
||||||
std::optional<std::size_t> getTrackNumber() const;
|
std::optional<std::size_t> getTrackNumber() const;
|
||||||
|
std::optional<std::size_t> getTotalTrack() const;
|
||||||
std::optional<std::size_t> getDiscNumber() const;
|
std::optional<std::size_t> getDiscNumber() const;
|
||||||
|
std::optional<std::size_t> getTotalDisc() const;
|
||||||
std::string getName() const { return _name; }
|
std::string getName() const { return _name; }
|
||||||
std::filesystem::path getPath() const { return _filePath; }
|
std::filesystem::path getPath() const { return _filePath; }
|
||||||
std::chrono::milliseconds getDuration() const { return _duration; }
|
std::chrono::milliseconds getDuration() const { return _duration; }
|
||||||
@@ -135,6 +139,8 @@ class Track : public Wt::Dbo::Dbo<Track>
|
|||||||
Wt::Dbo::field(a, _scanVersion, "scan_version");
|
Wt::Dbo::field(a, _scanVersion, "scan_version");
|
||||||
Wt::Dbo::field(a, _trackNumber, "track_number");
|
Wt::Dbo::field(a, _trackNumber, "track_number");
|
||||||
Wt::Dbo::field(a, _discNumber, "disc_number");
|
Wt::Dbo::field(a, _discNumber, "disc_number");
|
||||||
|
Wt::Dbo::field(a, _totalTrack, "total_track");
|
||||||
|
Wt::Dbo::field(a, _totalDisc, "total_disc");
|
||||||
Wt::Dbo::field(a, _name, "name");
|
Wt::Dbo::field(a, _name, "name");
|
||||||
Wt::Dbo::field(a, _duration, "duration");
|
Wt::Dbo::field(a, _duration, "duration");
|
||||||
Wt::Dbo::field(a, _year, "year");
|
Wt::Dbo::field(a, _year, "year");
|
||||||
@@ -160,19 +166,21 @@ class Track : public Wt::Dbo::Dbo<Track>
|
|||||||
static const std::size_t _maxCopyrightLength = 128;
|
static const std::size_t _maxCopyrightLength = 128;
|
||||||
static const std::size_t _maxCopyrightURLLength = 128;
|
static const std::size_t _maxCopyrightURLLength = 128;
|
||||||
|
|
||||||
int _scanVersion = 0;
|
int _scanVersion {};
|
||||||
int _trackNumber = 0;
|
int _trackNumber {};
|
||||||
int _discNumber = 0;
|
int _discNumber {};
|
||||||
|
int _totalTrack {};
|
||||||
|
int _totalDisc {};
|
||||||
std::string _name;
|
std::string _name;
|
||||||
std::string _artistName;
|
std::string _artistName;
|
||||||
std::string _releaseName;
|
std::string _releaseName;
|
||||||
std::chrono::duration<int, std::milli> _duration;
|
std::chrono::duration<int, std::milli> _duration;
|
||||||
int _year = 0;
|
int _year {};
|
||||||
int _originalYear = 0;
|
int _originalYear {};
|
||||||
std::string _filePath;
|
std::string _filePath;
|
||||||
Wt::WDateTime _fileLastWrite;
|
Wt::WDateTime _fileLastWrite;
|
||||||
Wt::WDateTime _fileAdded;
|
Wt::WDateTime _fileAdded;
|
||||||
bool _hasCover = false;
|
bool _hasCover {};
|
||||||
std::string _MBID; // Musicbrainz Identifier
|
std::string _MBID; // Musicbrainz Identifier
|
||||||
std::string _copyright;
|
std::string _copyright;
|
||||||
std::string _copyrightURL;
|
std::string _copyrightURL;
|
||||||
|
|||||||
@@ -91,14 +91,13 @@ class User : public Wt::Dbo::Dbo<User>
|
|||||||
public:
|
public:
|
||||||
using pointer = Wt::Dbo::ptr<User>;
|
using pointer = Wt::Dbo::ptr<User>;
|
||||||
|
|
||||||
static const std::size_t MinNameLength = 3;
|
|
||||||
static const std::size_t MaxNameLength = 15;
|
|
||||||
|
|
||||||
|
// Do not change enum values!
|
||||||
enum class Type
|
enum class Type
|
||||||
{
|
{
|
||||||
REGULAR,
|
REGULAR = 0,
|
||||||
ADMIN,
|
ADMIN = 1,
|
||||||
DEMO
|
DEMO = 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PasswordHash
|
struct PasswordHash
|
||||||
@@ -107,8 +106,21 @@ class User : public Wt::Dbo::Dbo<User>
|
|||||||
std::string hash;
|
std::string hash;
|
||||||
};
|
};
|
||||||
|
|
||||||
// list of audio parameters
|
// Do not change enum values!
|
||||||
static const std::set<Bitrate> audioTranscodeAllowedBitrates;
|
enum class UITheme
|
||||||
|
{
|
||||||
|
Light = 0,
|
||||||
|
Dark = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const std::set<Bitrate> audioTranscodeAllowedBitrates;
|
||||||
|
static inline const std::size_t MinNameLength {3};
|
||||||
|
static inline const std::size_t MaxNameLength {15};
|
||||||
|
static inline const bool defaultAudioTranscodeEnable {true};
|
||||||
|
static inline const AudioFormat defaultAudioTranscodeFormat {AudioFormat::OGG_OPUS};
|
||||||
|
static inline const Bitrate defaultAudioTranscodeBitrate {128000};
|
||||||
|
static inline const UITheme defaultUITheme {UITheme::Dark};
|
||||||
|
|
||||||
|
|
||||||
User();
|
User();
|
||||||
User(const std::string& loginName, const PasswordHash& passwordHash);
|
User(const std::string& loginName, const PasswordHash& passwordHash);
|
||||||
@@ -138,6 +150,7 @@ class User : public Wt::Dbo::Dbo<User>
|
|||||||
void setCurPlayingTrackPos(std::size_t pos) { _curPlayingTrackPos = pos; }
|
void setCurPlayingTrackPos(std::size_t pos) { _curPlayingTrackPos = pos; }
|
||||||
void setRadio(bool val) { _radio = val; }
|
void setRadio(bool val) { _radio = val; }
|
||||||
void setRepeatAll(bool val) { _repeatAll = val; }
|
void setRepeatAll(bool val) { _repeatAll = val; }
|
||||||
|
void setUITheme(UITheme uiTheme) { _uiTheme = uiTheme; }
|
||||||
void clearAuthTokens();
|
void clearAuthTokens();
|
||||||
|
|
||||||
// read
|
// read
|
||||||
@@ -150,6 +163,7 @@ class User : public Wt::Dbo::Dbo<User>
|
|||||||
std::size_t getCurPlayingTrackPos() const { return _curPlayingTrackPos; }
|
std::size_t getCurPlayingTrackPos() const { return _curPlayingTrackPos; }
|
||||||
bool isRepeatAllSet() const { return _repeatAll; }
|
bool isRepeatAllSet() const { return _repeatAll; }
|
||||||
bool isRadioSet() const { return _radio; }
|
bool isRadioSet() const { return _radio; }
|
||||||
|
UITheme getUITheme() const { return _uiTheme; }
|
||||||
|
|
||||||
Wt::Dbo::ptr<TrackList> getPlayedTrackList(Session& session) const;
|
Wt::Dbo::ptr<TrackList> getPlayedTrackList(Session& session) const;
|
||||||
Wt::Dbo::ptr<TrackList> getQueuedTrackList(Session& session) const;
|
Wt::Dbo::ptr<TrackList> getQueuedTrackList(Session& session) const;
|
||||||
@@ -184,6 +198,7 @@ class User : public Wt::Dbo::Dbo<User>
|
|||||||
Wt::Dbo::field(a, _audioTranscodeEnable, "audio_transcode_enable");
|
Wt::Dbo::field(a, _audioTranscodeEnable, "audio_transcode_enable");
|
||||||
Wt::Dbo::field(a, _audioTranscodeBitrate, "audio_transcode_bitrate");
|
Wt::Dbo::field(a, _audioTranscodeBitrate, "audio_transcode_bitrate");
|
||||||
Wt::Dbo::field(a, _audioTranscodeFormat, "audio_transcode_format");
|
Wt::Dbo::field(a, _audioTranscodeFormat, "audio_transcode_format");
|
||||||
|
Wt::Dbo::field(a, _uiTheme, "ui_theme");
|
||||||
// User's dynamic data
|
// User's dynamic data
|
||||||
Wt::Dbo::field(a, _curPlayingTrackPos, "cur_playing_track_pos");
|
Wt::Dbo::field(a, _curPlayingTrackPos, "cur_playing_track_pos");
|
||||||
Wt::Dbo::field(a, _repeatAll, "repeat_all");
|
Wt::Dbo::field(a, _repeatAll, "repeat_all");
|
||||||
@@ -197,14 +212,11 @@ class User : public Wt::Dbo::Dbo<User>
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
static const bool defaultAudioTranscodeEnable {true};
|
|
||||||
static const AudioFormat defaultAudioTranscodeFormat {AudioFormat::OGG_OPUS};
|
|
||||||
static const Bitrate defaultAudioTranscodeBitrate {128000};
|
|
||||||
|
|
||||||
std::string _loginName;
|
std::string _loginName;
|
||||||
std::string _passwordSalt;
|
std::string _passwordSalt;
|
||||||
std::string _passwordHash;
|
std::string _passwordHash;
|
||||||
Wt::WDateTime _lastLogin;
|
Wt::WDateTime _lastLogin;
|
||||||
|
UITheme _uiTheme {defaultUITheme};
|
||||||
|
|
||||||
// Admin defined settings
|
// Admin defined settings
|
||||||
int _maxAudioTranscodeBitrate;
|
int _maxAudioTranscodeBitrate;
|
||||||
|
|||||||
@@ -718,13 +718,6 @@ MediaScanner::scanAudioFile(const std::filesystem::path& file, bool forceScan, S
|
|||||||
stats.updates++;
|
stats.updates++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Release related data
|
|
||||||
if (release)
|
|
||||||
{
|
|
||||||
release.modify()->setTotalTrackNumber(trackInfo->totalTrack ? *trackInfo->totalTrack : 0);
|
|
||||||
release.modify()->setTotalDiscNumber(trackInfo->totalDisc ? *trackInfo->totalDisc : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Track related data
|
// Track related data
|
||||||
assert(track);
|
assert(track);
|
||||||
|
|
||||||
@@ -744,6 +737,8 @@ MediaScanner::scanAudioFile(const std::filesystem::path& file, bool forceScan, S
|
|||||||
track.modify()->setAddedTime(Wt::WLocalDateTime::currentServerDateTime().toUTC());
|
track.modify()->setAddedTime(Wt::WLocalDateTime::currentServerDateTime().toUTC());
|
||||||
track.modify()->setTrackNumber(trackInfo->trackNumber ? *trackInfo->trackNumber : 0);
|
track.modify()->setTrackNumber(trackInfo->trackNumber ? *trackInfo->trackNumber : 0);
|
||||||
track.modify()->setDiscNumber(trackInfo->discNumber ? *trackInfo->discNumber : 0);
|
track.modify()->setDiscNumber(trackInfo->discNumber ? *trackInfo->discNumber : 0);
|
||||||
|
track.modify()->setTotalTrack(trackInfo->totalTrack);
|
||||||
|
track.modify()->setTotalDisc(trackInfo->totalDisc);
|
||||||
track.modify()->setYear(trackInfo->year ? *trackInfo->year : 0);
|
track.modify()->setYear(trackInfo->year ? *trackInfo->year : 0);
|
||||||
track.modify()->setOriginalYear(trackInfo->originalYear ? *trackInfo->originalYear : 0);
|
track.modify()->setOriginalYear(trackInfo->originalYear ? *trackInfo->originalYear : 0);
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ add_executable(lms
|
|||||||
ui/Auth.cpp
|
ui/Auth.cpp
|
||||||
ui/LmsApplication.cpp
|
ui/LmsApplication.cpp
|
||||||
ui/LmsApplicationGroup.cpp
|
ui/LmsApplicationGroup.cpp
|
||||||
|
ui/LmsTheme.cpp
|
||||||
ui/MediaPlayer.cpp
|
ui/MediaPlayer.cpp
|
||||||
ui/PlayQueueView.cpp
|
ui/PlayQueueView.cpp
|
||||||
ui/PlayHistoryView.cpp
|
ui/PlayHistoryView.cpp
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
#include "LmsApplication.hpp"
|
#include "LmsApplication.hpp"
|
||||||
|
|
||||||
#include <Wt/WAnchor.h>
|
#include <Wt/WAnchor.h>
|
||||||
#include <Wt/WBootstrapTheme.h>
|
|
||||||
#include <Wt/WEnvironment.h>
|
#include <Wt/WEnvironment.h>
|
||||||
#include <Wt/WMenu.h>
|
#include <Wt/WMenu.h>
|
||||||
#include <Wt/WNavigationBar.h>
|
#include <Wt/WNavigationBar.h>
|
||||||
@@ -49,6 +48,7 @@
|
|||||||
#include "resource/ImageResource.hpp"
|
#include "resource/ImageResource.hpp"
|
||||||
#include "Auth.hpp"
|
#include "Auth.hpp"
|
||||||
#include "LmsApplicationException.hpp"
|
#include "LmsApplicationException.hpp"
|
||||||
|
#include "LmsTheme.hpp"
|
||||||
#include "MediaPlayer.hpp"
|
#include "MediaPlayer.hpp"
|
||||||
#include "PlayHistoryView.hpp"
|
#include "PlayHistoryView.hpp"
|
||||||
#include "PlayQueueView.hpp"
|
#include "PlayQueueView.hpp"
|
||||||
@@ -63,7 +63,7 @@ LmsApplication::create(const Wt::WEnvironment& env, Database::Db& db, LmsApplica
|
|||||||
return std::make_unique<LmsApplication>(env, db, appGroups);
|
return std::make_unique<LmsApplication>(env, db, appGroups);
|
||||||
}
|
}
|
||||||
|
|
||||||
LmsApplication*
|
LmsApplication*
|
||||||
LmsApplication::instance()
|
LmsApplication::instance()
|
||||||
{
|
{
|
||||||
return reinterpret_cast<LmsApplication*>(Wt::WApplication::instance());
|
return reinterpret_cast<LmsApplication*>(Wt::WApplication::instance());
|
||||||
@@ -115,14 +115,8 @@ LmsApplication::LmsApplication(const Wt::WEnvironment& env,
|
|||||||
_dbSession {db},
|
_dbSession {db},
|
||||||
_appGroups {appGroups}
|
_appGroups {appGroups}
|
||||||
{
|
{
|
||||||
auto bootstrapTheme = std::make_unique<Wt::WBootstrapTheme>();
|
|
||||||
bootstrapTheme->setVersion(Wt::BootstrapVersion::v3);
|
|
||||||
bootstrapTheme->setResponsive(true);
|
|
||||||
setTheme(std::move(bootstrapTheme));
|
|
||||||
|
|
||||||
addMetaHeader(Wt::MetaHeaderType::Meta, "viewport", "width=device-width, user-scalable=no");
|
addMetaHeader(Wt::MetaHeaderType::Meta, "viewport", "width=device-width, user-scalable=no");
|
||||||
|
|
||||||
useStyleSheet("css/lms.css");
|
|
||||||
useStyleSheet("resources/font-awesome/css/font-awesome.min.css");
|
useStyleSheet("resources/font-awesome/css/font-awesome.min.css");
|
||||||
|
|
||||||
// Add a resource bundle
|
// Add a resource bundle
|
||||||
@@ -173,11 +167,26 @@ LmsApplication::LmsApplication(const Wt::WEnvironment& env,
|
|||||||
|
|
||||||
if (firstConnection)
|
if (firstConnection)
|
||||||
{
|
{
|
||||||
|
setTheme(std::make_unique<LmsTheme>(Database::User::defaultUITheme));
|
||||||
root()->addWidget(std::make_unique<InitWizardView>());
|
root()->addWidget(std::make_unique<InitWizardView>());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto userId {processAuthToken(env)};
|
const auto userId {processAuthToken(env)};
|
||||||
|
|
||||||
|
{
|
||||||
|
Database::User::UITheme theme {Database::User::defaultUITheme};
|
||||||
|
if (userId)
|
||||||
|
{
|
||||||
|
auto transaction {_dbSession.createSharedTransaction()};
|
||||||
|
const auto user {Database::User::getById(_dbSession, *userId)};
|
||||||
|
if (user)
|
||||||
|
theme = user->getUITheme();
|
||||||
|
}
|
||||||
|
|
||||||
|
setTheme(std::make_unique<LmsTheme>(theme));
|
||||||
|
}
|
||||||
|
|
||||||
if (userId)
|
if (userId)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -200,6 +209,16 @@ LmsApplication::LmsApplication(const Wt::WEnvironment& env,
|
|||||||
Auth* auth {root()->addNew<Auth>()};
|
Auth* auth {root()->addNew<Auth>()};
|
||||||
auth->userLoggedIn.connect(this, [this](Database::IdType userId)
|
auth->userLoggedIn.connect(this, [this](Database::IdType userId)
|
||||||
{
|
{
|
||||||
|
{
|
||||||
|
auto transaction {_dbSession.createSharedTransaction()};
|
||||||
|
const auto user {Database::User::getById(_dbSession, userId)};
|
||||||
|
if (user)
|
||||||
|
{
|
||||||
|
LmsTheme* lmsTheme {static_cast<LmsTheme*>(LmsApp->theme().get())};
|
||||||
|
lmsTheme->setTheme(user->getUITheme());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handleUserLoggedIn(userId, true);
|
handleUserLoggedIn(userId, true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -263,18 +282,27 @@ LmsApplication::createReleaseAnchor(Database::Release::pointer release, bool add
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Wt::WTemplate>
|
std::unique_ptr<Wt::WText>
|
||||||
LmsApplication::createCluster(Database::Cluster::pointer cluster, bool canDelete)
|
LmsApplication::createCluster(Database::Cluster::pointer cluster, bool canDelete)
|
||||||
{
|
{
|
||||||
std::string styleClass = "Lms-cluster-type-" + std::to_string(cluster->getType().id() % 10);
|
auto getStyleClass = [](const Database::Cluster::pointer cluster)
|
||||||
|
{
|
||||||
|
switch (cluster->getType().id() % 6)
|
||||||
|
{
|
||||||
|
case 0: return "label-primary";
|
||||||
|
case 1: return "label-default";
|
||||||
|
case 2: return "label-info";
|
||||||
|
case 3: return "label-warning";
|
||||||
|
case 4: return "label-success";
|
||||||
|
case 5: return "label-danger";
|
||||||
|
}
|
||||||
|
return "label-default";
|
||||||
|
};
|
||||||
|
|
||||||
auto res = std::make_unique<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.template.cluster-entry").arg(styleClass));
|
const std::string styleClass {getStyleClass(cluster)};
|
||||||
res->addFunction("tr", &Wt::WTemplate::Functions::tr);
|
auto res {std::make_unique<Wt::WText>(std::string {} + (canDelete ? "<i class=\"fa fa-times-circle\"></i> " : "") + Wt::WString::fromUTF8(cluster->getName()), Wt::TextFormat::UnsafeXHTML)};
|
||||||
|
|
||||||
res->bindString("name", Wt::WString::fromUTF8(cluster->getName()), Wt::TextFormat::Plain);
|
res->setStyleClass("Lms-cluster label " + styleClass);
|
||||||
res->setCondition("if-can-delete", canDelete);
|
|
||||||
|
|
||||||
res->setStyleClass("Lms-cluster");
|
|
||||||
res->setToolTip(cluster->getType()->getName(), Wt::TextFormat::Plain);
|
res->setToolTip(cluster->getType()->getName(), Wt::TextFormat::Plain);
|
||||||
res->setInline(true);
|
res->setInline(true);
|
||||||
|
|
||||||
@@ -473,6 +501,7 @@ LmsApplication::createHome()
|
|||||||
// Contents
|
// Contents
|
||||||
// Order is important in mainStack, see IdxRoot!
|
// Order is important in mainStack, see IdxRoot!
|
||||||
Wt::WStackedWidget* mainStack = main->bindNew<Wt::WStackedWidget>("contents");
|
Wt::WStackedWidget* mainStack = main->bindNew<Wt::WStackedWidget>("contents");
|
||||||
|
mainStack->setAttributeValue("style", "overflow-x:visible;overflow-y:visible;");
|
||||||
|
|
||||||
Explore* explore = mainStack->addNew<Explore>();
|
Explore* explore = mainStack->addNew<Explore>();
|
||||||
PlayQueue* playqueue = mainStack->addNew<PlayQueue>();
|
PlayQueue* playqueue = mainStack->addNew<PlayQueue>();
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ class LmsApplication : public Wt::WApplication
|
|||||||
static std::unique_ptr<Wt::WAnchor> createArtistAnchor(Wt::Dbo::ptr<Database::Artist> artist, bool addText = true);
|
static std::unique_ptr<Wt::WAnchor> createArtistAnchor(Wt::Dbo::ptr<Database::Artist> artist, bool addText = true);
|
||||||
static Wt::WLink createReleaseLink(Wt::Dbo::ptr<Database::Release> release);
|
static Wt::WLink createReleaseLink(Wt::Dbo::ptr<Database::Release> release);
|
||||||
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::WTemplate> 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);
|
||||||
|
|
||||||
// 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; }
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
* 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 "LmsTheme.hpp"
|
||||||
|
|
||||||
|
#include "utils/Logger.hpp"
|
||||||
|
#include "LmsApplication.hpp"
|
||||||
|
|
||||||
|
namespace UserInterface {
|
||||||
|
|
||||||
|
LmsTheme::LmsTheme(Database::User::UITheme theme)
|
||||||
|
: _theme {theme}
|
||||||
|
{
|
||||||
|
setVersion(Wt::BootstrapVersion::v3);
|
||||||
|
setResponsive(true);
|
||||||
|
applyMetaHeader();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
LmsTheme::applyMetaHeader() const
|
||||||
|
{
|
||||||
|
switch (_theme)
|
||||||
|
{
|
||||||
|
case Database::User::UITheme::Dark:
|
||||||
|
LmsApp->addMetaHeader("theme-color", "#303030");
|
||||||
|
break;
|
||||||
|
case Database::User::UITheme::Light:
|
||||||
|
LmsApp->removeMetaHeader(Wt::MetaHeaderType::Meta, "theme-color");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
LmsTheme::setTheme(Database::User::UITheme theme)
|
||||||
|
{
|
||||||
|
// Hack, use the application interface directly since changing theme is not allowed
|
||||||
|
|
||||||
|
const auto currentStyleSheets {getStyleSheets(_theme)};
|
||||||
|
for (auto it {std::crbegin(currentStyleSheets)}; it != std::crend(currentStyleSheets); ++it)
|
||||||
|
{
|
||||||
|
LMS_LOG(UI, DEBUG) << "Removing css file '" << it->url() << "'";
|
||||||
|
LmsApp->removeStyleSheet(*it);
|
||||||
|
}
|
||||||
|
|
||||||
|
_theme = theme;
|
||||||
|
for (const auto& styleSheet : getStyleSheets(theme))
|
||||||
|
{
|
||||||
|
LMS_LOG(UI, DEBUG) << "Adding css file '" << styleSheet.url() << "'";
|
||||||
|
LmsApp->useStyleSheet(styleSheet);
|
||||||
|
}
|
||||||
|
applyMetaHeader();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Wt::WLinkedCssStyleSheet>
|
||||||
|
LmsTheme::styleSheets() const
|
||||||
|
{
|
||||||
|
const std::vector<Wt::WLink> styleSheets {getStyleSheets(_theme)};
|
||||||
|
std::vector<Wt::WLinkedCssStyleSheet> res;
|
||||||
|
res.reserve(styleSheets.size());
|
||||||
|
|
||||||
|
std::transform(std::cbegin(styleSheets), std::cend(styleSheets), std::back_inserter(res), [](const Wt::WLink& styleSheet) { return Wt::WLinkedCssStyleSheet {styleSheet}; });
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Wt::WLink>
|
||||||
|
LmsTheme::getStyleSheets(Database::User::UITheme theme)
|
||||||
|
{
|
||||||
|
switch (theme)
|
||||||
|
{
|
||||||
|
case Database::User::UITheme::Dark:
|
||||||
|
return
|
||||||
|
{
|
||||||
|
{"css/bootstrap-darkly.min.css"},
|
||||||
|
{"resources/themes/bootstrap/3/wt.css"},
|
||||||
|
{"css/lms.css"},
|
||||||
|
{"css/lms-darkly.css"},
|
||||||
|
};
|
||||||
|
|
||||||
|
case Database::User::UITheme::Light:
|
||||||
|
return
|
||||||
|
{
|
||||||
|
{"css/bootstrap-flatly.min.css"},
|
||||||
|
{"resources/themes/bootstrap/3/wt.css"},
|
||||||
|
{"css/lms.css"},
|
||||||
|
{"css/lms-flatly.css"},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* 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 <vector>
|
||||||
|
|
||||||
|
#include <Wt/WBootstrapTheme.h>
|
||||||
|
#include <Wt/WLinkedCssStyleSheet.h>
|
||||||
|
|
||||||
|
#include "database/User.hpp"
|
||||||
|
|
||||||
|
namespace UserInterface {
|
||||||
|
|
||||||
|
class LmsTheme : public Wt::WBootstrapTheme
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LmsTheme(Database::User::UITheme theme);
|
||||||
|
|
||||||
|
void setTheme(Database::User::UITheme theme);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void applyMetaHeader() const;
|
||||||
|
std::vector<Wt::WLinkedCssStyleSheet> styleSheets() const override;
|
||||||
|
static std::vector<Wt::WLink> getStyleSheets(Database::User::UITheme theme);
|
||||||
|
|
||||||
|
Database::User::UITheme _theme;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -28,6 +28,7 @@
|
|||||||
#include "database/TrackList.hpp"
|
#include "database/TrackList.hpp"
|
||||||
#include "database/User.hpp"
|
#include "database/User.hpp"
|
||||||
|
|
||||||
|
#include "resource/ImageResource.hpp"
|
||||||
#include "LmsApplication.hpp"
|
#include "LmsApplication.hpp"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -61,6 +62,19 @@ std::unique_ptr<Wt::WTemplate> createEntry(Database::Track::pointer track)
|
|||||||
{
|
{
|
||||||
entry->setCondition("if-has-release", true);
|
entry->setCondition("if-has-release", true);
|
||||||
entry->bindWidget("release", LmsApplication::createReleaseAnchor(track->getRelease()));
|
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-small");
|
||||||
|
anchor->setImage(std::move(cover));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto cover = entry->bindNew<Wt::WImage>("cover");
|
||||||
|
cover->setImageLink(LmsApp->getImageResource()->getTrackUrl(track.id(), 96));
|
||||||
|
cover->setStyleClass("Lms-cover-small");
|
||||||
}
|
}
|
||||||
|
|
||||||
return entry;
|
return entry;
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
#include "utils/Service.hpp"
|
#include "utils/Service.hpp"
|
||||||
#include "utils/String.hpp"
|
#include "utils/String.hpp"
|
||||||
|
|
||||||
|
#include "resource/ImageResource.hpp"
|
||||||
#include "TrackStringUtils.hpp"
|
#include "TrackStringUtils.hpp"
|
||||||
#include "LmsApplication.hpp"
|
#include "LmsApplication.hpp"
|
||||||
|
|
||||||
@@ -163,13 +164,15 @@ PlayQueue::PlayQueue()
|
|||||||
void
|
void
|
||||||
PlayQueue::updateRepeatBtn()
|
PlayQueue::updateRepeatBtn()
|
||||||
{
|
{
|
||||||
_repeatBtn->toggleStyleClass("Lms-playqueue-btn-selected", _repeatAll);
|
_repeatBtn->toggleStyleClass("text-success", _repeatAll);
|
||||||
|
_repeatBtn->toggleStyleClass("text-muted", !_repeatAll);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
PlayQueue::updateRadioBtn()
|
PlayQueue::updateRadioBtn()
|
||||||
{
|
{
|
||||||
_radioBtn->toggleStyleClass("Lms-playqueue-btn-selected",_radioMode);
|
_radioBtn->toggleStyleClass("text-success",_radioMode);
|
||||||
|
_radioBtn->toggleStyleClass("text-muted", !_radioMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
Database::TrackList::pointer
|
Database::TrackList::pointer
|
||||||
@@ -282,14 +285,9 @@ PlayQueue::updateCurrentTrack(bool selected)
|
|||||||
if (!_trackPos || *_trackPos >= static_cast<std::size_t>(_entriesContainer->count()))
|
if (!_trackPos || *_trackPos >= static_cast<std::size_t>(_entriesContainer->count()))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto track = _entriesContainer->widget(*_trackPos);
|
Wt::WTemplate* entry {static_cast<Wt::WTemplate*>(_entriesContainer->widget(*_trackPos))};
|
||||||
if (track)
|
if (entry)
|
||||||
{
|
entry->bindString("is-selected", selected ? "Lms-playqueue-selected" : "");
|
||||||
if (selected)
|
|
||||||
track->addStyleClass("Lms-playqueue-selected");
|
|
||||||
else
|
|
||||||
track->removeStyleClass("Lms-playqueue-selected");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -346,15 +344,15 @@ PlayQueue::addSome()
|
|||||||
auto tracklistEntries = tracklist->getEntries(_entriesContainer->count(), 50);
|
auto tracklistEntries = tracklist->getEntries(_entriesContainer->count(), 50);
|
||||||
for (const Database::TrackListEntry::pointer& tracklistEntry : tracklistEntries)
|
for (const Database::TrackListEntry::pointer& tracklistEntry : tracklistEntries)
|
||||||
{
|
{
|
||||||
auto tracklistEntryId = tracklistEntry.id();
|
const auto tracklistEntryId {tracklistEntry.id()};
|
||||||
auto track = tracklistEntry->getTrack();
|
const auto track {tracklistEntry->getTrack()};
|
||||||
|
|
||||||
Wt::WTemplate* entry = _entriesContainer->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.PlayQueue.template.entry"));
|
Wt::WTemplate* entry = _entriesContainer->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.PlayQueue.template.entry"));
|
||||||
|
|
||||||
entry->bindString("name", Wt::WString::fromUTF8(track->getName()), Wt::TextFormat::Plain);
|
entry->bindString("name", Wt::WString::fromUTF8(track->getName()), Wt::TextFormat::Plain);
|
||||||
|
|
||||||
auto artists {track->getArtists()};
|
const auto artists {track->getArtists()};
|
||||||
auto release = track->getRelease();
|
const auto release {track->getRelease()};
|
||||||
|
|
||||||
if (!artists.empty() || release)
|
if (!artists.empty() || release)
|
||||||
entry->setCondition("if-has-artists-or-release", true);
|
entry->setCondition("if-has-artists-or-release", true);
|
||||||
@@ -373,7 +371,20 @@ PlayQueue::addSome()
|
|||||||
if (release)
|
if (release)
|
||||||
{
|
{
|
||||||
entry->setCondition("if-has-release", true);
|
entry->setCondition("if-has-release", true);
|
||||||
entry->bindWidget("release", LmsApplication::createReleaseAnchor(track->getRelease()));
|
entry->bindWidget("release", LmsApplication::createReleaseAnchor(release));
|
||||||
|
{
|
||||||
|
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-small");
|
||||||
|
anchor->setImage(std::move(cover));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto cover = entry->bindNew<Wt::WImage>("cover");
|
||||||
|
cover->setImageLink(LmsApp->getImageResource()->getTrackUrl(track.id(), 96));
|
||||||
|
cover->setStyleClass("Lms-cover-small");
|
||||||
}
|
}
|
||||||
|
|
||||||
entry->bindString("duration", trackDurationToString(track->getDuration()), Wt::TextFormat::Plain);
|
entry->bindString("duration", trackDurationToString(track->getDuration()), Wt::TextFormat::Plain);
|
||||||
|
|||||||
+27
-15
@@ -35,6 +35,7 @@
|
|||||||
#include "utils/Service.hpp"
|
#include "utils/Service.hpp"
|
||||||
|
|
||||||
#include "LmsApplication.hpp"
|
#include "LmsApplication.hpp"
|
||||||
|
#include "LmsTheme.hpp"
|
||||||
|
|
||||||
namespace UserInterface {
|
namespace UserInterface {
|
||||||
|
|
||||||
@@ -44,18 +45,20 @@ class SettingsModel : public Wt::WFormModel
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
// Associate each field with a unique string literal.
|
// Associate each field with a unique string literal.
|
||||||
static const Field TranscodeEnableField;
|
static inline const Field DarkModeField {"dark-mode"};
|
||||||
static const Field TranscodeFormatField;
|
static inline const Field TranscodeEnableField {"transcoding-enable"};
|
||||||
static const Field TranscodeBitrateField;
|
static inline const Field TranscodeFormatField {"transcoding-bitrate"};
|
||||||
static const Field PasswordOldField;
|
static inline const Field TranscodeBitrateField {"transcoding-format"};
|
||||||
static const Field PasswordField;
|
static inline const Field PasswordOldField {"password-old"};
|
||||||
static const Field PasswordConfirmField;
|
static inline const Field PasswordField {"password"};
|
||||||
|
static inline const Field PasswordConfirmField {"password-confirm"};
|
||||||
|
|
||||||
SettingsModel(bool withOldPassword)
|
SettingsModel(bool withOldPassword)
|
||||||
: _withOldPassword {withOldPassword}
|
: _withOldPassword {withOldPassword}
|
||||||
{
|
{
|
||||||
initializeModels();
|
initializeModels();
|
||||||
|
|
||||||
|
addField(DarkModeField);
|
||||||
addField(TranscodeEnableField);
|
addField(TranscodeEnableField);
|
||||||
addField(TranscodeBitrateField);
|
addField(TranscodeBitrateField);
|
||||||
addField(TranscodeFormatField);
|
addField(TranscodeFormatField);
|
||||||
@@ -77,14 +80,22 @@ class SettingsModel : public Wt::WFormModel
|
|||||||
|
|
||||||
void saveData()
|
void saveData()
|
||||||
{
|
{
|
||||||
Database::User::PasswordHash passwordHash;
|
User::PasswordHash passwordHash;
|
||||||
|
|
||||||
if (!valueText(PasswordField).empty())
|
if (!valueText(PasswordField).empty())
|
||||||
passwordHash = ServiceProvider<::Auth::IPasswordService>::get()->hashPassword(valueText(PasswordField).toUTF8());
|
passwordHash = ServiceProvider<::Auth::IPasswordService>::get()->hashPassword(valueText(PasswordField).toUTF8());
|
||||||
|
|
||||||
auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
|
auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
|
||||||
|
|
||||||
Database::User::pointer user {LmsApp->getUser()};
|
User::pointer user {LmsApp->getUser()};
|
||||||
|
|
||||||
|
{
|
||||||
|
const User::UITheme newTheme {Wt::asNumber(value(DarkModeField)) ? User::UITheme::Dark : User::UITheme::Light};
|
||||||
|
LmsTheme* lmsTheme {static_cast<LmsTheme*>(LmsApp->theme().get())};
|
||||||
|
lmsTheme->setTheme(newTheme);
|
||||||
|
|
||||||
|
user.modify()->setUITheme(newTheme);
|
||||||
|
}
|
||||||
|
|
||||||
user.modify()->setAudioTranscodeEnable(Wt::asNumber(value(TranscodeEnableField)));
|
user.modify()->setAudioTranscodeEnable(Wt::asNumber(value(TranscodeEnableField)));
|
||||||
|
|
||||||
@@ -107,6 +118,8 @@ class SettingsModel : public Wt::WFormModel
|
|||||||
{
|
{
|
||||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||||
|
|
||||||
|
setValue(DarkModeField, LmsApp->getUser()->getUITheme() == User::UITheme::Dark);
|
||||||
|
|
||||||
setValue(TranscodeEnableField, LmsApp->getUser()->getAudioTranscodeEnable());
|
setValue(TranscodeEnableField, LmsApp->getUser()->getAudioTranscodeEnable());
|
||||||
if (!LmsApp->getUser()->getAudioTranscodeEnable())
|
if (!LmsApp->getUser()->getAudioTranscodeEnable())
|
||||||
{
|
{
|
||||||
@@ -223,13 +236,6 @@ class SettingsModel : public Wt::WFormModel
|
|||||||
std::shared_ptr<ValueStringModel<AudioFormat>> _transcodeFormatModel;
|
std::shared_ptr<ValueStringModel<AudioFormat>> _transcodeFormatModel;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Wt::WFormModel::Field SettingsModel::TranscodeEnableField = "transcoding-enable";
|
|
||||||
const Wt::WFormModel::Field SettingsModel::TranscodeBitrateField = "transcoding-bitrate";
|
|
||||||
const Wt::WFormModel::Field SettingsModel::TranscodeFormatField = "transcoding-format";
|
|
||||||
const Wt::WFormModel::Field SettingsModel::PasswordOldField = "password-old";
|
|
||||||
const Wt::WFormModel::Field SettingsModel::PasswordField = "password";
|
|
||||||
const Wt::WFormModel::Field SettingsModel::PasswordConfirmField = "password-confirm";
|
|
||||||
|
|
||||||
SettingsView::SettingsView()
|
SettingsView::SettingsView()
|
||||||
{
|
{
|
||||||
wApp->internalPathChanged().connect(std::bind([=]
|
wApp->internalPathChanged().connect(std::bind([=]
|
||||||
@@ -252,6 +258,12 @@ SettingsView::refreshView()
|
|||||||
|
|
||||||
auto model {std::make_shared<SettingsModel>(!LmsApp->isUserAuthStrong())};
|
auto model {std::make_shared<SettingsModel>(!LmsApp->isUserAuthStrong())};
|
||||||
|
|
||||||
|
// Appearance
|
||||||
|
{
|
||||||
|
auto darkMode {std::make_unique<Wt::WCheckBox>()};
|
||||||
|
t->setFormWidget(SettingsModel::DarkModeField, std::move(darkMode));
|
||||||
|
}
|
||||||
|
|
||||||
// Old password
|
// Old password
|
||||||
if (!LmsApp->isUserAuthStrong())
|
if (!LmsApp->isUserAuthStrong())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -132,8 +132,7 @@ Artist::refresh()
|
|||||||
|
|
||||||
auto cover = std::make_unique<Wt::WImage>();
|
auto cover = std::make_unique<Wt::WImage>();
|
||||||
cover->setImageLink(LmsApp->getImageResource()->getReleaseUrl(release.id(), 128));
|
cover->setImageLink(LmsApp->getImageResource()->getReleaseUrl(release.id(), 128));
|
||||||
// Some images may not be square
|
cover->setStyleClass("Lms-cover-medium");
|
||||||
cover->setWidth(128);
|
|
||||||
anchor->setImage(std::move(cover));
|
anchor->setImage(std::move(cover));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -119,6 +119,7 @@ Explore::Explore()
|
|||||||
|
|
||||||
// Contents
|
// Contents
|
||||||
Wt::WStackedWidget* contentsStack = bindNew<Wt::WStackedWidget>("contents");
|
Wt::WStackedWidget* contentsStack = bindNew<Wt::WStackedWidget>("contents");
|
||||||
|
contentsStack->setAttributeValue("style", "overflow-x:visible;overflow-y:visible;");
|
||||||
|
|
||||||
auto artists = std::make_unique<Artists>(_filters);
|
auto artists = std::make_unique<Artists>(_filters);
|
||||||
artists->artistsAdd.connect(this, &Explore::handleArtistsAdd);
|
artists->artistsAdd.connect(this, &Explore::handleArtistsAdd);
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ void
|
|||||||
Filters::add(Database::IdType clusterId)
|
Filters::add(Database::IdType clusterId)
|
||||||
{
|
{
|
||||||
|
|
||||||
Wt::WTemplate* filter {};
|
Wt::WInteractWidget* filter {};
|
||||||
|
|
||||||
{
|
{
|
||||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ ReleaseLink::ReleaseLink(Database::Release::pointer release)
|
|||||||
|
|
||||||
Wt::WAnchor* anchor = bindWidget("cover", LmsApplication::createReleaseAnchor(release, false));
|
Wt::WAnchor* anchor = 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(), 48));
|
cover->setImageLink(LmsApp->getImageResource()->getReleaseUrl(release.id(), 64));
|
||||||
cover->setWidth(48);
|
cover->setStyleClass("Lms-cover-smaller");
|
||||||
anchor->setImage(std::move(cover));
|
anchor->setImage(std::move(cover));
|
||||||
|
|
||||||
auto artists = release->getReleaseArtists();
|
auto artists = release->getReleaseArtists();
|
||||||
|
|||||||
@@ -77,7 +77,6 @@ Release::refresh()
|
|||||||
throw ReleaseNotFoundException {*releaseId};
|
throw ReleaseNotFoundException {*releaseId};
|
||||||
|
|
||||||
Wt::WTemplate* t {addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Release.template"))};
|
Wt::WTemplate* t {addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Release.template"))};
|
||||||
t->addFunction("tr", &Wt::WTemplate::Functions::tr);
|
|
||||||
|
|
||||||
t->bindString("name", Wt::WString::fromUTF8(release->getName()), Wt::TextFormat::Plain);
|
t->bindString("name", Wt::WString::fromUTF8(release->getName()), Wt::TextFormat::Plain);
|
||||||
|
|
||||||
@@ -114,7 +113,10 @@ Release::refresh()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
t->bindNew<Wt::WImage>("cover", Wt::WLink(LmsApp->getImageResource()->getReleaseUrl(release.id(), 512)));
|
{
|
||||||
|
Wt::WImage* cover {t->bindNew<Wt::WImage>("cover", Wt::WLink(LmsApp->getImageResource()->getReleaseUrl(release.id(), 512)))};
|
||||||
|
cover->setStyleClass("Lms-cover-large");
|
||||||
|
}
|
||||||
|
|
||||||
Wt::WContainerWidget* clusterContainers {t->bindNew<Wt::WContainerWidget>("clusters")};
|
Wt::WContainerWidget* clusterContainers {t->bindNew<Wt::WContainerWidget>("clusters")};
|
||||||
{
|
{
|
||||||
@@ -151,18 +153,47 @@ Release::refresh()
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Wt::WContainerWidget* tracksContainer {t->bindNew<Wt::WContainerWidget>("tracks")};
|
Wt::WContainerWidget* rootContainer {t->bindNew<Wt::WContainerWidget>("container")};
|
||||||
|
|
||||||
auto clusterIds {_filters->getClusterIds()};
|
const bool variousArtists {release->hasVariousArtists()};
|
||||||
auto tracks {release->getTracks(clusterIds)};
|
const auto totalDisc {release->getTotalDisc()};
|
||||||
|
const bool isReleaseMultiDisc {totalDisc && *totalDisc > 1};
|
||||||
|
|
||||||
bool variousArtists {release->hasVariousArtists()};
|
// Expect to be call in asc order
|
||||||
|
std::map<std::size_t, Wt::WContainerWidget*> trackContainers;
|
||||||
|
auto getOrAddDiscContainer = [&](std::size_t discNumber) -> Wt::WContainerWidget*
|
||||||
|
{
|
||||||
|
{
|
||||||
|
auto it = trackContainers.find(discNumber);
|
||||||
|
if (it != std::cend(trackContainers))
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
Wt::WTemplate* disc {rootContainer->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Release.template.disc-entry"))};
|
||||||
|
disc->bindNew<Wt::WText>("disc-title", Wt::WString::tr("Lms.Explore.Release.disc").arg(discNumber));
|
||||||
|
disc->addFunction("tr", &Wt::WTemplate::Functions::tr);
|
||||||
|
|
||||||
|
Wt::WContainerWidget* tracksContainer {disc->bindNew<Wt::WContainerWidget>("tracks")};
|
||||||
|
trackContainers[discNumber] = tracksContainer;
|
||||||
|
|
||||||
|
return tracksContainer;
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto clusterIds {_filters->getClusterIds()};
|
||||||
|
const auto tracks {release->getTracks(clusterIds)};
|
||||||
|
|
||||||
for (const auto& track : tracks)
|
for (const auto& track : tracks)
|
||||||
{
|
{
|
||||||
auto trackId {track.id()};
|
auto trackId {track.id()};
|
||||||
|
|
||||||
Wt::WTemplate* entry {tracksContainer->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Release.template.entry"))};
|
const auto discNumber {track->getDiscNumber()};
|
||||||
|
|
||||||
|
Wt::WContainerWidget* container {rootContainer};
|
||||||
|
if (isReleaseMultiDisc && discNumber)
|
||||||
|
container = getOrAddDiscContainer(*discNumber);
|
||||||
|
|
||||||
|
Wt::WTemplate* entry {container->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Release.template.entry"))};
|
||||||
|
|
||||||
entry->bindString("name", Wt::WString::fromUTF8(track->getName()), Wt::TextFormat::Plain);
|
entry->bindString("name", Wt::WString::fromUTF8(track->getName()), Wt::TextFormat::Plain);
|
||||||
|
|
||||||
auto artists {track->getArtists()};
|
auto artists {track->getArtists()};
|
||||||
@@ -185,14 +216,6 @@ Release::refresh()
|
|||||||
entry->bindInt("track-number", *trackNumber);
|
entry->bindInt("track-number", *trackNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto discNumber {track->getDiscNumber()};
|
|
||||||
auto totalDiscNumber {release->getTotalDiscNumber()};
|
|
||||||
if (discNumber && totalDiscNumber && *totalDiscNumber > 1)
|
|
||||||
{
|
|
||||||
entry->setCondition("if-has-disc-number", true);
|
|
||||||
entry->bindInt("disc-number", *discNumber);
|
|
||||||
}
|
|
||||||
|
|
||||||
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(std::bind([=]
|
playBtn->clicked().connect(std::bind([=]
|
||||||
{
|
{
|
||||||
@@ -209,11 +232,13 @@ Release::refresh()
|
|||||||
|
|
||||||
LmsApp->getEvents().trackLoaded.connect(entry, [=] (Database::IdType loadedTrackId, bool /*play*/)
|
LmsApp->getEvents().trackLoaded.connect(entry, [=] (Database::IdType loadedTrackId, bool /*play*/)
|
||||||
{
|
{
|
||||||
entry->toggleStyleClass("Lms-explore-release-entry-playing", loadedTrackId == trackId);
|
entry->bindString("is-playing", loadedTrackId == trackId ? "Lms-entry-playing" : "");
|
||||||
});
|
});
|
||||||
|
|
||||||
if (LmsApp->getEvents().lastLoadedTrackId && *LmsApp->getEvents().lastLoadedTrackId == trackId)
|
if (LmsApp->getEvents().lastLoadedTrackId && *LmsApp->getEvents().lastLoadedTrackId == trackId)
|
||||||
entry->addStyleClass("Lms-explore-release-entry-playing");
|
{
|
||||||
|
entry->bindString("is-playing", "Lms-entry-playing");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ Releases::addSome()
|
|||||||
auto cover = std::make_unique<Wt::WImage>();
|
auto cover = std::make_unique<Wt::WImage>();
|
||||||
cover->setImageLink(LmsApp->getImageResource()->getReleaseUrl(release.id(), 128));
|
cover->setImageLink(LmsApp->getImageResource()->getReleaseUrl(release.id(), 128));
|
||||||
// Some images may not be square
|
// Some images may not be square
|
||||||
cover->setWidth(128);
|
cover->setStyleClass("Lms-cover-medium");
|
||||||
anchor->setImage(std::move(cover));
|
anchor->setImage(std::move(cover));
|
||||||
|
|
||||||
entry->bindWidget("release-name", LmsApplication::createReleaseAnchor(release));
|
entry->bindWidget("release-name", LmsApplication::createReleaseAnchor(release));
|
||||||
|
|||||||
@@ -144,6 +144,19 @@ Tracks::addSome()
|
|||||||
{
|
{
|
||||||
entry->setCondition("if-has-release", true);
|
entry->setCondition("if-has-release", true);
|
||||||
entry->bindWidget("release", LmsApplication::createReleaseAnchor(track->getRelease()));
|
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-small");
|
||||||
|
anchor->setImage(std::move(cover));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto cover = entry->bindNew<Wt::WImage>("cover");
|
||||||
|
cover->setImageLink(LmsApp->getImageResource()->getTrackUrl(trackId, 96));
|
||||||
|
cover->setStyleClass("Lms-cover-small");
|
||||||
}
|
}
|
||||||
|
|
||||||
entry->bindString("duration", trackDurationToString(track->getDuration()), Wt::TextFormat::Plain);
|
entry->bindString("duration", trackDurationToString(track->getDuration()), Wt::TextFormat::Plain);
|
||||||
@@ -159,6 +172,17 @@ Tracks::addSome()
|
|||||||
{
|
{
|
||||||
tracksAdd.emit({trackId});
|
tracksAdd.emit({trackId});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
LmsApp->getEvents().trackLoaded.connect(entry, [=] (Database::IdType loadedTrackId, bool /*play*/)
|
||||||
|
{
|
||||||
|
entry->bindString("is-playing", loadedTrackId == trackId ? "Lms-entry-playing" : "");
|
||||||
|
});
|
||||||
|
|
||||||
|
if (LmsApp->getEvents().lastLoadedTrackId && *LmsApp->getEvents().lastLoadedTrackId == trackId)
|
||||||
|
{
|
||||||
|
entry->bindString("is-playing", "Lms-entry-playing");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_showMore->setHidden(!moreResults);
|
_showMore->setHidden(!moreResults);
|
||||||
|
|||||||
@@ -487,6 +487,89 @@ testSingleTrackSingleRelease(Session& session)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
void
|
||||||
|
testMultiTracksSingleReleaseTotalDiscTrack(Session& session)
|
||||||
|
{
|
||||||
|
ScopedRelease release1 {session, "MyRelease"};
|
||||||
|
|
||||||
|
{
|
||||||
|
auto transaction {session.createSharedTransaction()};
|
||||||
|
|
||||||
|
CHECK(!release1->getTotalTrack());
|
||||||
|
CHECK(!release1->getTotalDisc());
|
||||||
|
}
|
||||||
|
|
||||||
|
ScopedTrack track1 {session, "MyTrack"};
|
||||||
|
{
|
||||||
|
auto transaction {session.createUniqueTransaction()};
|
||||||
|
|
||||||
|
track1.get().modify()->setRelease(release1.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto transaction {session.createSharedTransaction()};
|
||||||
|
|
||||||
|
CHECK(!release1->getTotalTrack());
|
||||||
|
CHECK(!release1->getTotalDisc());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto transaction {session.createUniqueTransaction()};
|
||||||
|
|
||||||
|
track1.get().modify()->setTotalTrack(36);
|
||||||
|
track1.get().modify()->setTotalDisc(6);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto transaction {session.createSharedTransaction()};
|
||||||
|
|
||||||
|
CHECK(release1->getTotalTrack() && *release1->getTotalTrack() == 36);
|
||||||
|
CHECK(release1->getTotalDisc() && *release1->getTotalDisc() == 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
ScopedTrack track2 {session, "MyTrack2"};
|
||||||
|
{
|
||||||
|
auto transaction {session.createUniqueTransaction()};
|
||||||
|
|
||||||
|
track2.get().modify()->setRelease(release1.get());
|
||||||
|
track2.get().modify()->setTotalTrack(37);
|
||||||
|
track2.get().modify()->setTotalDisc(67);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto transaction {session.createSharedTransaction()};
|
||||||
|
|
||||||
|
CHECK(release1->getTotalTrack() && *release1->getTotalTrack() == 37);
|
||||||
|
CHECK(release1->getTotalDisc() && *release1->getTotalDisc() == 67);
|
||||||
|
}
|
||||||
|
|
||||||
|
ScopedRelease release2 {session, "MyRelease2"};
|
||||||
|
{
|
||||||
|
auto transaction {session.createSharedTransaction()};
|
||||||
|
|
||||||
|
CHECK(!release2->getTotalTrack());
|
||||||
|
CHECK(!release2->getTotalDisc());
|
||||||
|
}
|
||||||
|
|
||||||
|
ScopedTrack track3 {session, "MyTrack3"};
|
||||||
|
{
|
||||||
|
auto transaction {session.createUniqueTransaction()};
|
||||||
|
|
||||||
|
track3.get().modify()->setRelease(release2.get());
|
||||||
|
track3.get().modify()->setTotalTrack(7);
|
||||||
|
track3.get().modify()->setTotalDisc(5);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto transaction {session.createSharedTransaction()};
|
||||||
|
|
||||||
|
CHECK(release1->getTotalTrack() && *release1->getTotalTrack() == 37);
|
||||||
|
CHECK(release1->getTotalDisc() && *release1->getTotalDisc() == 67);
|
||||||
|
CHECK(release2->getTotalTrack() && *release2->getTotalTrack() == 7);
|
||||||
|
CHECK(release2->getTotalDisc() && *release2->getTotalDisc() == 5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static
|
static
|
||||||
void
|
void
|
||||||
testSingleTrackSingleCluster(Session& session)
|
testSingleTrackSingleCluster(Session& session)
|
||||||
@@ -1461,6 +1544,7 @@ int main()
|
|||||||
RUN_TEST(testMultiArtistsSortMethod);
|
RUN_TEST(testMultiArtistsSortMethod);
|
||||||
|
|
||||||
RUN_TEST(testSingleTrackSingleRelease);
|
RUN_TEST(testSingleTrackSingleRelease);
|
||||||
|
RUN_TEST(testMultiTracksSingleReleaseTotalDiscTrack);
|
||||||
|
|
||||||
RUN_TEST(testSingleTrackSingleCluster);
|
RUN_TEST(testSingleTrackSingleCluster);
|
||||||
RUN_TEST(testMultipleTracksSingleCluster);
|
RUN_TEST(testMultipleTracksSingleCluster);
|
||||||
|
|||||||
Reference in New Issue
Block a user