Made some more progress on theming
This commit is contained in:
@@ -21,8 +21,6 @@
|
|||||||
${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">
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
+7
-2
@@ -15,17 +15,22 @@
|
|||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
<h3><div class="Lms-releasename text-primary">${name}</div></h3>
|
<h3><div class="Lms-releasename text-primary">${name}</div></h3>
|
||||||
${<if-has-artist>}<h4>${artist class="Lms-artistname text-muted"}</h4>${</if-has-artist>}
|
${<if-has-artist>}<h4>${artist class="Lms-artistname text-muted"}</h4>${</if-has-artist>}
|
||||||
<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-entry-hoverable Lms-vertical-align ${is-playing}">
|
<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 text-muted">
|
<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">
|
||||||
<div class="Lms-trackname text-primary">${name}</div>
|
<div class="Lms-trackname text-primary">${name}</div>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
${contents class="Lms-contents"}
|
${contents class="Lms-contents"}
|
||||||
</div>
|
</div>
|
||||||
${player class="Lms-player well well-sm"}
|
${player class="Lms-player"}
|
||||||
</message>
|
</message>
|
||||||
|
|
||||||
</messages>
|
</messages>
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
:root {
|
:root {
|
||||||
|
--body-color: #ffffff;
|
||||||
--hover-color: #00bc8c;
|
--hover-color: #00bc8c;
|
||||||
--body-color: #cacaca;
|
|
||||||
--playing-background-color: #375a7f4f;
|
--playing-background-color: #375a7f4f;
|
||||||
--slider-background-color: #5a5a5a;
|
--slider-background-color: #5a5a5a;
|
||||||
|
--player-background-color: #303030;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-artistname.text-muted {
|
.Lms-artistname.text-muted {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
--hover-color: #18bc9c;
|
--hover-color: #18bc9c;
|
||||||
--playing-background-color: #2c3e5029;
|
--playing-background-color: #2c3e5029;
|
||||||
--slider-background-color: #2c3e5029;
|
--slider-background-color: #2c3e5029;
|
||||||
|
--player-background-color: #ecf0f1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-artistname.text-muted {
|
.Lms-artistname.text-muted {
|
||||||
|
|||||||
+13
-60
@@ -1,3 +1,4 @@
|
|||||||
|
/* 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;
|
||||||
@@ -51,66 +52,17 @@ a.Lms-artistname:hover, a.Lms-artistname:focus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.Lms-cluster {
|
.Lms-cluster {
|
||||||
display: inline;
|
display: inline-block;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
line-height: 32px;
|
line-height: 1.3;
|
||||||
margin: 4px;
|
margin-right: 4px;
|
||||||
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-cluster:hover {
|
.Lms-cluster:hover {
|
||||||
filter: brightness(130%);
|
filter: brightness(130%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-cluster-type-0 {
|
|
||||||
background-color: darkslategrey;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Lms-cluster-type-1 {
|
|
||||||
background-color: darkblue;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Lms-cluster-type-2 {
|
|
||||||
background-color: darkgrey;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Lms-cluster-type-3 {
|
|
||||||
background-color: darkorchid;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Lms-cluster-type-4 {
|
|
||||||
background-color: darkgoldenrod;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Lms-cluster-type-5 {
|
|
||||||
background-color: darkkhaki;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Lms-cluster-type-6 {
|
|
||||||
background-color: darkolivegreen;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.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-control-btn:hover {
|
.Lms-control-btn:hover {
|
||||||
color: var(--hover-color);
|
color: var(--hover-color);
|
||||||
}
|
}
|
||||||
@@ -121,24 +73,24 @@ a.Lms-artistname:hover, a.Lms-artistname:focus {
|
|||||||
|
|
||||||
.Lms-cover-smaller {
|
.Lms-cover-smaller {
|
||||||
width: 64px; /* some cover may not be square */
|
width: 64px; /* some cover may not be square */
|
||||||
border-radius: 10px;
|
border-radius: 3px;
|
||||||
box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.4);
|
box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-cover-small {
|
.Lms-cover-small {
|
||||||
width: 96px; /* some cover may not be square */
|
width: 96px; /* some cover may not be square */
|
||||||
border-radius: 12px;
|
border-radius: 3px;
|
||||||
box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.4);
|
box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-cover-medium {
|
.Lms-cover-medium {
|
||||||
width: 128px;
|
width: 128px;
|
||||||
border-radius: 16px;
|
border-radius: 3px;
|
||||||
box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.4);
|
box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-cover-large {
|
.Lms-cover-large {
|
||||||
border-radius: 16px;
|
border-radius: 3px;
|
||||||
box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.4);
|
box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,7 +114,7 @@ a.Lms-artistname:hover, a.Lms-artistname:focus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.Lms-explore-release-entry-tracknumber {
|
.Lms-explore-release-entry-tracknumber {
|
||||||
text-align: center;
|
text-align: right;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -333,6 +285,7 @@ a.Lms-artistname:hover, a.Lms-artistname:focus {
|
|||||||
height: 70px;
|
height: 70px;
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
|
background-color: var(--player-background-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-player-btn {
|
.Lms-player-btn {
|
||||||
@@ -397,7 +350,7 @@ a.Lms-artistname:hover, a.Lms-artistname:focus {
|
|||||||
top: 0px;
|
top: 0px;
|
||||||
left: 0px;
|
left: 0px;
|
||||||
height: 8px;
|
height: 8px;
|
||||||
border-radius: 4px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Lms-player-seek::-moz-range-thumb {
|
.Lms-player-seek::-moz-range-thumb {
|
||||||
@@ -448,7 +401,7 @@ a.Lms-artistname:hover, a.Lms-artistname:focus {
|
|||||||
height: 8px;
|
height: 8px;
|
||||||
outline: none;
|
outline: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: 4px;
|
border-radius: 3px;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-width: thin;
|
border-width: thin;
|
||||||
background-color: var(--slider-background-color);
|
background-color: var(--slider-background-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 16
|
#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)
|
||||||
@@ -173,15 +178,41 @@ CREATE TABLE IF NOT EXISTS "track_bookmark" (
|
|||||||
{
|
{
|
||||||
_session.execute("ALTER TABLE user ADD ui_theme INTEGER NOT NULL DEFAULT(" + std::to_string(static_cast<int>(User::defaultUITheme)) + ")");
|
_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;
|
||||||
|
|||||||
@@ -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,9 +20,7 @@
|
|||||||
#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/WLinkedCssStyleSheet.h>
|
|
||||||
#include <Wt/WMenu.h>
|
#include <Wt/WMenu.h>
|
||||||
#include <Wt/WNavigationBar.h>
|
#include <Wt/WNavigationBar.h>
|
||||||
#include <Wt/WPopupMenu.h>
|
#include <Wt/WPopupMenu.h>
|
||||||
@@ -50,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"
|
||||||
@@ -58,40 +57,6 @@
|
|||||||
|
|
||||||
namespace UserInterface {
|
namespace UserInterface {
|
||||||
|
|
||||||
class LmsBooststrapTheme : public Wt::WBootstrapTheme
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
LmsBooststrapTheme(Database::User::UITheme theme) : _theme {theme} {}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::vector<Wt::WLinkedCssStyleSheet> styleSheets() const override
|
|
||||||
{
|
|
||||||
switch (_theme)
|
|
||||||
{
|
|
||||||
case Database::User::UITheme::Dark:
|
|
||||||
return
|
|
||||||
{
|
|
||||||
Wt::WLinkedCssStyleSheet {"css/bootstrap-darkly.min.css"},
|
|
||||||
Wt::WLinkedCssStyleSheet {"resources/themes/bootstrap/3/wt.css"},
|
|
||||||
Wt::WLinkedCssStyleSheet {"css/lms.css"},
|
|
||||||
Wt::WLinkedCssStyleSheet {"css/lms-darkly.css"},
|
|
||||||
};
|
|
||||||
|
|
||||||
case Database::User::UITheme::Light:
|
|
||||||
return
|
|
||||||
{
|
|
||||||
Wt::WLinkedCssStyleSheet {"css/bootstrap-flatly.min.css"},
|
|
||||||
Wt::WLinkedCssStyleSheet {"resources/themes/bootstrap/3/wt.css"},
|
|
||||||
Wt::WLinkedCssStyleSheet {"css/lms.css"},
|
|
||||||
Wt::WLinkedCssStyleSheet {"css/lms-flatly.css"},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
Database::User::UITheme _theme;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::unique_ptr<Wt::WApplication>
|
std::unique_ptr<Wt::WApplication>
|
||||||
LmsApplication::create(const Wt::WEnvironment& env, Database::Db& db, LmsApplicationGroupContainer& appGroups)
|
LmsApplication::create(const Wt::WEnvironment& env, Database::Db& db, LmsApplicationGroupContainer& appGroups)
|
||||||
{
|
{
|
||||||
@@ -202,6 +167,7 @@ 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;
|
||||||
}
|
}
|
||||||
@@ -217,10 +183,8 @@ LmsApplication::LmsApplication(const Wt::WEnvironment& env,
|
|||||||
if (user)
|
if (user)
|
||||||
theme = user->getUITheme();
|
theme = user->getUITheme();
|
||||||
}
|
}
|
||||||
auto LmsTheme {std::make_unique<LmsBooststrapTheme>(theme)};
|
|
||||||
LmsTheme->setVersion(Wt::BootstrapVersion::v3);
|
setTheme(std::make_unique<LmsTheme>(theme));
|
||||||
LmsTheme->setResponsive(true);
|
|
||||||
setTheme(std::move(LmsTheme));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userId)
|
if (userId)
|
||||||
@@ -245,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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -308,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);
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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 {
|
||||||
|
|
||||||
@@ -88,7 +89,14 @@ class SettingsModel : public Wt::WFormModel
|
|||||||
|
|
||||||
User::pointer user {LmsApp->getUser()};
|
User::pointer user {LmsApp->getUser()};
|
||||||
|
|
||||||
user.modify()->setUITheme(Wt::asNumber(value(DarkModeField)) ? User::UITheme::Dark : User::UITheme::Light);
|
{
|
||||||
|
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)));
|
||||||
|
|
||||||
auto transcodeBitrateRow {_transcodeBitrateModel->getRowFromString(valueText(TranscodeBitrateField))};
|
auto transcodeBitrateRow {_transcodeBitrateModel->getRowFromString(valueText(TranscodeBitrateField))};
|
||||||
|
|||||||
@@ -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()};
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
@@ -154,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()};
|
||||||
@@ -188,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([=]
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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