- ${collapse-button}
-
- ${contents}
+
+ ${navbar-top class="main-nav"}
+ ${contents class="container"}
+
+
+
+ ${tr:msg-home-welcome}
+
+
+
+ ${filters}
+ ${contents}
+
+
+
+
+ ${add-filter}${filters}
diff --git a/approot/tracks.xml b/approot/tracks.xml
new file mode 100644
index 00000000..047796e8
--- /dev/null
+++ b/approot/tracks.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+ ${tracks class="list-group"}
+
+
+
+
+
+
+
diff --git a/docroot/css/lms.css b/docroot/css/lms.css
index 814622af..9c47a5dc 100644
--- a/docroot/css/lms.css
+++ b/docroot/css/lms.css
@@ -1,14 +1,5 @@
.main-nav {
margin-bottom: 0px;
- box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.4);
-}
-
-.Wt-hrh2 {
- background-color: white;
-}
-
-.Wt-vrh2 {
- background-color: white;
}
.playqueue {
@@ -198,26 +189,3 @@
box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.5)
}
-.release_truncated-name {
- white-space: nowrap;
- overflow: hidden;
- overflow-x: hidden;
- overflow-y: hidden;
- text-overflow: ellipsis;
-}
-
-.release_img-responsive {
- display: block;
- max-width: 100%;
- height: auto;
-}
-
-.release-search-cover {
- width: 512px;
- min-width: 512px;
- height: 512px;
- min-height: 512px;
- display: flex;
- justify-content: space-between;
- align-items: center;
-}
diff --git a/src/Makefile.am b/src/Makefile.am
index 6de2fd19..02bd8c3b 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -5,7 +5,7 @@ lms_SOURCES = \
$(srcdir)/av/AvInfo.cpp \
$(srcdir)/av/AvTranscoder.cpp \
$(srcdir)/cover/CoverArtGrabber.cpp \
- $(srcdir)/database/Artist.cpp \
+ $(srcdir)/database/DbArtist.cpp \
$(srcdir)/database/DatabaseHandler.cpp \
$(srcdir)/database/MediaDirectory.cpp \
$(srcdir)/database/Playlist.cpp \
@@ -24,41 +24,22 @@ lms_SOURCES = \
$(srcdir)/image/Image.cpp \
$(srcdir)/metadata/AvFormat.cpp \
$(srcdir)/metadata/TagLibParser.cpp \
+ $(srcdir)/ui/ArtistView.cpp \
+ $(srcdir)/ui/ArtistsView.cpp \
+ $(srcdir)/ui/Explore.cpp \
+ $(srcdir)/ui/Filters.cpp \
+ $(srcdir)/ui/HomeView.cpp \
$(srcdir)/ui/LmsApplication.cpp \
+ $(srcdir)/ui/ReleasesView.cpp \
+ $(srcdir)/ui/ReleaseView.cpp \
+ $(srcdir)/ui/TracksView.cpp \
$(srcdir)/ui/auth/LmsAuth.cpp \
- $(srcdir)/ui/audio/AudioPlayer.cpp \
- $(srcdir)/ui/audio/desktop/DesktopAudio.cpp \
- $(srcdir)/ui/audio/desktop/FilterChain.cpp \
- $(srcdir)/ui/audio/desktop/KeywordSearchFilter.cpp \
- $(srcdir)/ui/audio/desktop/PlayQueue.cpp \
- $(srcdir)/ui/audio/desktop/TableFilter.cpp \
- $(srcdir)/ui/audio/desktop/TrackView.cpp \
- $(srcdir)/ui/audio/mobile/ArtistSearch.cpp \
- $(srcdir)/ui/audio/mobile/ArtistSearchView.cpp \
- $(srcdir)/ui/audio/mobile/ArtistView.cpp \
- $(srcdir)/ui/audio/mobile/MobileAudio.cpp \
- $(srcdir)/ui/audio/mobile/MobilePlayQueue.cpp \
- $(srcdir)/ui/audio/mobile/PreviewSearchView.cpp \
- $(srcdir)/ui/audio/mobile/ReleaseSearch.cpp \
- $(srcdir)/ui/audio/mobile/ReleaseSearchView.cpp \
- $(srcdir)/ui/audio/mobile/ReleaseView.cpp \
- $(srcdir)/ui/audio/mobile/TrackSearch.cpp \
- $(srcdir)/ui/audio/mobile/TrackSearchView.cpp \
$(srcdir)/ui/common/DirectoryValidator.cpp \
$(srcdir)/ui/common/InputRange.cpp \
$(srcdir)/ui/common/LineEdit.cpp \
$(srcdir)/ui/resource/AvConvTranscodeStreamResource.cpp \
$(srcdir)/ui/resource/ImageResource.cpp \
$(srcdir)/ui/resource/TranscodeResource.cpp \
- $(srcdir)/ui/settings/Settings.cpp \
- $(srcdir)/ui/settings/SettingsAccountFormView.cpp \
- $(srcdir)/ui/settings/SettingsAudioFormView.cpp \
- $(srcdir)/ui/settings/SettingsDatabaseFormView.cpp \
- $(srcdir)/ui/settings/SettingsFirstConnectionFormView.cpp \
- $(srcdir)/ui/settings/SettingsMediaDirectories.cpp \
- $(srcdir)/ui/settings/SettingsMediaDirectoryFormView.cpp \
- $(srcdir)/ui/settings/SettingsUserFormView.cpp \
- $(srcdir)/ui/settings/SettingsUsers.cpp \
$(srcdir)/utils/Config.cpp \
$(srcdir)/utils/Logger.cpp \
$(srcdir)/utils/Path.cpp \
diff --git a/src/cover/CoverArtGrabber.cpp b/src/cover/CoverArtGrabber.cpp
index a43f3626..a73a502c 100644
--- a/src/cover/CoverArtGrabber.cpp
+++ b/src/cover/CoverArtGrabber.cpp
@@ -178,11 +178,7 @@ Grabber::getFromRelease(Wt::Dbo::Session& session, Database::Release::id_type re
if (!release || release->isNone())
return std::vector
();
- // Get the first track of the release
- std::vector tracks = Track::getByFilter(session,
- SearchFilter::ById(SearchFilter::Field::Release, releaseId),
- -1, 1 /* limit result size */);
-
+ std::vector tracks = release->getTracks();
if (tracks.empty())
return std::vector();
diff --git a/src/database/DatabaseHandler.cpp b/src/database/DatabaseHandler.cpp
index c159d378..1aedb3b8 100644
--- a/src/database/DatabaseHandler.cpp
+++ b/src/database/DatabaseHandler.cpp
@@ -187,7 +187,7 @@ Handler::createConnectionPool(boost::filesystem::path p)
connection->executeSql("pragma journal_mode=WAL");
- // connection->setProperty("show-queries", "true");
+ connection->setProperty("show-queries", "true");
return new Wt::Dbo::FixedSqlConnectionPool(connection, 1);
}
diff --git a/src/database/DatabaseHandler.hpp b/src/database/DatabaseHandler.hpp
index 38978325..6b07984e 100644
--- a/src/database/DatabaseHandler.hpp
+++ b/src/database/DatabaseHandler.hpp
@@ -33,6 +33,7 @@
namespace Database {
+typedef Wt::Dbo::dbo_default_traits::IdType id_type;
typedef Wt::Auth::Dbo::UserDatabase UserDatabase;
// Session living class handling the database and the login
diff --git a/src/database/DbArtist.cpp b/src/database/DbArtist.cpp
new file mode 100644
index 00000000..7dc341f6
--- /dev/null
+++ b/src/database/DbArtist.cpp
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2015 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 .
+ */
+
+#include "Types.hpp"
+#include "SqlQuery.hpp"
+
+#include "utils/Logger.hpp"
+
+namespace Database
+{
+
+Artist::Artist(const std::string& name, const std::string& MBID)
+: _name(std::string(name, 0 , _maxNameLength)),
+_MBID(MBID)
+{
+
+}
+
+std::vector
+Artist::getByName(Wt::Dbo::Session& session, const std::string& name)
+{
+ Wt::Dbo::collection res = session.find().where("name = ?").bind( std::string(name, 0, _maxNameLength) );
+ return std::vector(res.begin(), res.end());
+}
+
+Artist::pointer
+Artist::getByMBID(Wt::Dbo::Session& session, const std::string& mbid)
+{
+ return session.find().where("mbid = ?").bind(mbid);
+}
+
+Artist::pointer
+Artist::getById(Wt::Dbo::Session& session, Artist::id_type id)
+{
+ return session.find().where("id = ?").bind(id);
+}
+
+Artist::pointer
+Artist::create(Wt::Dbo::Session& session, const std::string& name, const std::string& MBID)
+{
+ return session.add(new Artist(name, MBID));
+}
+
+Artist::pointer
+Artist::getNone(Wt::Dbo::Session& session)
+{
+ std::vector res = getByName(session, "");
+ if (res.empty())
+ return create(session, "");
+
+ return res.front();
+}
+
+bool
+Artist::isNone() const
+{
+ return _name == "";
+}
+
+std::vector
+Artist::getAll(Wt::Dbo::Session& session, int offset, int size)
+{
+ Wt::Dbo::collection res = session.find().orderBy("LOWER(name)").offset(offset).limit(size);
+ return std::vector(res.begin(), res.end());
+}
+
+std::vector
+Artist::getAllOrphans(Wt::Dbo::Session& session)
+{
+ Wt::Dbo::collection res = session.query< Wt::Dbo::ptr >("SELECT DISTINCT a FROM artist a LEFT OUTER JOIN Track t ON a.id = t.artist_id WHERE t.id IS NULL");
+
+ return std::vector(res.begin(), res.end());
+}
+
+static
+Wt::Dbo::Query
+getQuery(Wt::Dbo::Session& session,
+ const std::vector& clusterIds,
+ const std::vector& keywords)
+{
+ WhereClause where;
+
+ std::ostringstream oss;
+ oss << "SELECT DISTINCT a FROM artist a";
+
+ for (auto keyword : keywords)
+ where.And(WhereClause("a.name LIKE ?")).bind("%%" + keyword + "%%");
+
+ if (!clusterIds.empty())
+ {
+ oss << " INNER JOIN track t ON t.artist_id = a.id INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id";
+
+ WhereClause clusterClause;
+
+ for (auto id : clusterIds)
+ clusterClause.Or(WhereClause("c.id = ?")).bind(std::to_string(id));
+
+ where.And(clusterClause);
+ }
+ oss << " " << where.get();
+
+ if (!clusterIds.empty())
+ oss << " GROUP BY t.id HAVING COUNT(*) = " << clusterIds.size();
+
+ oss << " ORDER BY a.name";
+
+ Wt::Dbo::Query query = session.query( oss.str() );
+
+ for (const std::string& bindArg : where.getBindArgs())
+ {
+ query.bind(bindArg);
+ }
+
+ return query;
+}
+
+std::vector
+Artist::getByFilter(Wt::Dbo::Session& session,
+ const std::vector& clusters,
+ const std::vector keywords,
+ int offset, int size, bool& moreResults)
+{
+ Wt::Dbo::collection collection = getQuery(session, clusters, keywords).limit(size).offset(offset);
+
+ auto res = std::vector(collection.begin(), collection.end());
+
+ if (size != -1 && res.size() == static_cast(size) + 1)
+ {
+ moreResults = true;
+ res.pop_back();
+ }
+ else
+ moreResults = false;
+
+ return res;
+}
+
+std::vector >
+Artist::getReleases(const std::vector& clusterIds) const
+{
+ assert(self());
+ assert(self()->id() != Wt::Dbo::dbo_traits::invalidId() );
+ assert(session());
+
+ WhereClause where;
+
+ std::ostringstream oss;
+ oss << "SELECT DISTINCT r FROM release r INNER JOIN artist a ON t.artist_id = a.id INNER JOIN track t ON t.release_id = r.id";
+
+ if (!clusterIds.empty())
+ {
+ oss << " INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id";
+
+ WhereClause clusterClause;
+
+ for (auto id : clusterIds)
+ clusterClause.Or(WhereClause("c.id = ?")).bind(std::to_string(id));
+
+ where.And(clusterClause);
+ }
+
+ where.And(WhereClause("a.id = ?")).bind(std::to_string(id()));
+
+ oss << " " << where.get();
+
+ if (!clusterIds.empty())
+ oss << " GROUP BY t.id HAVING COUNT(*) = " << clusterIds.size();
+
+ // TODO order
+ oss << " ORDER BY t.date,r.name";
+
+ Wt::Dbo::Query query = session()->query( oss.str() );
+
+ for (const std::string& bindArg : where.getBindArgs())
+ {
+ query.bind(bindArg);
+ }
+
+ Wt::Dbo::collection< Wt::Dbo::ptr > res = query;
+
+ return std::vector< Wt::Dbo::ptr > (res.begin(), res.end());
+}
+
+
+
+} // namespace Database
diff --git a/src/database/DbArtist.hpp b/src/database/DbArtist.hpp
new file mode 100644
index 00000000..7b45bcb6
--- /dev/null
+++ b/src/database/DbArtist.hpp
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2015 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 .
+ */
+
+#ifndef _DB_ARTIST_HPP_
+#define _DB_ARTIST_HPP_
+
+#include
+#include
+
+#include
+#include
+
+#include "SearchFilter.hpp"
+
+namespace Database
+{
+
+class Track;
+class Cluster;
+class Release;
+
+class Artist : public Wt::Dbo::Dbo
+{
+ public:
+
+ typedef Wt::Dbo::ptr pointer;
+ typedef Wt::Dbo::dbo_traits::IdType id_type;
+
+ Artist() {}
+ Artist(const std::string& name, const std::string& MBID = "");
+
+ // Accessors
+ static pointer getByMBID(Wt::Dbo::Session& session, const std::string& MBID);
+ static pointer getById(Wt::Dbo::Session& session, id_type id);
+ static pointer getNone(Wt::Dbo::Session& session); // Special entry
+ static std::vector getByName(Wt::Dbo::Session& session, const std::string& name);
+ static std::vector getByFilter(Wt::Dbo::Session& session,
+ const std::vector& clusters, // at least one track that belongs to these clusters
+ const std::vector keywords, // name must match all of these keywords
+ int offset,
+ int size,
+ bool& moreExpected);
+
+ static std::vector getAll(Wt::Dbo::Session& session, int offset = -1, int size = -1);
+ static std::vector getAllOrphans(Wt::Dbo::Session& session); // No track related
+
+ // Accessors
+ std::string getName(void) const { return _name; }
+ std::string getMBID(void) const { return _MBID; }
+
+ // Get the releases that have at least one track for this artist + belongs to optonal cluster filters
+ std::vector> getReleases(const std::vector& clusterIds = std::vector()) const;
+
+ void setMBID(std::string mbid) { _MBID = mbid; }
+
+ // Create
+ static pointer create(Wt::Dbo::Session& session, const std::string& name, const std::string& MBID = "");
+
+ bool isNone(void) const;
+
+ template
+ void persist(Action& a)
+ {
+ Wt::Dbo::field(a, _name, "name");
+ Wt::Dbo::field(a, _MBID, "mbid");
+
+ Wt::Dbo::hasMany(a, _tracks, Wt::Dbo::ManyToOne, "artist");
+ }
+
+ private:
+
+ static const std::size_t _maxNameLength = 128;
+
+ std::string _name;
+ std::string _MBID; // Musicbrainz Identifier
+
+ Wt::Dbo::collection> _tracks; // Tracks of this artist
+};
+
+} // namespace Database
+
+#endif
diff --git a/src/database/Release.cpp b/src/database/Release.cpp
index d82cbcef..ca9e1fb5 100644
--- a/src/database/Release.cpp
+++ b/src/database/Release.cpp
@@ -82,68 +82,61 @@ Release::getAll(Wt::Dbo::Session& session, int offset, int size)
std::vector
Release::getAllOrphans(Wt::Dbo::Session& session)
{
- Wt::Dbo::collection res = session.query< Wt::Dbo::ptr >("select r from release r LEFT OUTER JOIN Track t ON r.id = t.release_id WHERE t.id IS NULL");
+ Wt::Dbo::collection res = session.query>("select r from release r LEFT OUTER JOIN Track t ON r.id = t.release_id WHERE t.id IS NULL");
return std::vector(res.begin(), res.end());
}
+static
Wt::Dbo::Query
-Release::getQuery(Wt::Dbo::Session& session, SearchFilter filter)
+getQuery(Wt::Dbo::Session& session,
+ const std::vector& clusterIds,
+ const std::vector keywords)
{
- SqlQuery sqlQuery = generatePartialQuery(filter);
+ WhereClause where;
- Wt::Dbo::Query query
- = session.query("SELECT r FROM release r INNER JOIN artist a ON a.id = t.artist_id INNER JOIN track t ON t.release_id = r.id INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id " + sqlQuery.where().get()).groupBy("r.id").orderBy("r.name");
+ std::ostringstream oss;
+ oss << "SELECT DISTINCT r FROM release r";
- for (const std::string& bindArg : sqlQuery.where().getBindArgs())
- query.bind(bindArg);
+ for (auto keyword : keywords)
+ where.And(WhereClause("r.name LIKE ?")).bind("%%" + keyword + "%%");
- return query;
-}
-
-Wt::Dbo::Query
-Release::getUIQuery(Wt::Dbo::Session& session, SearchFilter filter)
-{
- SqlQuery sqlQuery = generatePartialQuery(filter);
-
- // TODO DATE of RELEASE
- Wt::Dbo::Query query
- = session.query("SELECT r.id, r.name, t.date, COUNT(DISTINCT t.id) FROM release r INNER JOIN track t ON t.release_id = r.id INNER JOIN artist a ON a.id = t.artist_id INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id " + sqlQuery.where().get()).groupBy("r.id").orderBy("r.name");
-
- for (const std::string& bindArg : sqlQuery.where().getBindArgs())
- query.bind(bindArg);
-
- return query;
-}
-
-void
-Release::updateUIQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel& model, SearchFilter filter, const std::vector& columnNames)
-{
- Wt::Dbo::Query query = getUIQuery(session, filter);
-
- model.setQuery(query, columnNames.empty() ? true : false);
-
- // TODO do something better
- if (columnNames.size() == 3)
+ if (!clusterIds.empty())
{
- model.addColumn( "r.name", columnNames[0]);
- model.addColumn( "t.date", columnNames[1]);
- model.addColumn( "COUNT(DISTINCT t.id)", columnNames[2]);
+ oss << " INNER JOIN track t ON t.release_id = r.id INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id";
+
+ WhereClause clusterClause;
+
+ for (auto id : clusterIds)
+ clusterClause.And(WhereClause("c.id = ?")).bind(std::to_string(id));
+
+ where.And(clusterClause);
}
+
+ oss << " " << where.get();
+
+ if (!clusterIds.empty())
+ oss << " GROUP BY t.id HAVING COUNT(*) = " << clusterIds.size();
+
+ oss << " ORDER BY r.name";
+
+ Wt::Dbo::Query query = session.query( oss.str() );
+
+ for (const std::string& bindArg : where.getBindArgs())
+ query.bind(bindArg);
+
+ return query;
}
std::vector
-Release::getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size)
+Release::getByFilter(Wt::Dbo::Session& session,
+ const std::vector& clusterIds,
+ const std::vector keywords,
+ int offset, int size, bool& moreResults)
{
- Wt::Dbo::collection res = getQuery(session, filter).limit(size).offset(offset);
+ Wt::Dbo::collection collection = getQuery(session, clusterIds, keywords).limit(size).offset(offset);
- return std::vector(res.begin(), res.end());
-}
-
-std::vector
-Release::getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size, bool& moreResults)
-{
- auto res = getByFilter(session, filter, offset, size + 1);
+ auto res = std::vector(collection.begin(), collection.end());
if (size != -1 && res.size() == static_cast(size) + 1)
{
@@ -156,28 +149,63 @@ Release::getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset,
return res;
}
-int
+boost::optional
Release::getReleaseYear(bool original) const
{
assert(session());
- // TODO something better
- auto tracks = Track::getByFilter(*session(), SearchFilter::ById(SearchFilter::Field::Release, this->id()), -1, 1);
+ Wt::Dbo::collection times = session()->query(
+ std::string("SELECT ") + (original ? "t.original_date" : "t.date") + " FROM track t INNER JOIN release r ON r.id = t.release_id")
+ .where("r.id = ?")
+ .groupBy("t.date")
+ .bind(this->id());
- if (tracks.empty())
- return 0;
+ /* various dates, no date */
+ if (times.empty() || times.size() > 1)
+ return boost::none;
- boost::gregorian::date date;
-
- if (original)
- date = tracks.front()->getOriginalDate().date();
- else
- date = tracks.front()->getDate().date();
+ boost::gregorian::date date = times.front().date();
if (date.is_special())
- return 0;
+ return boost::none;
- return date.year();
+ return boost::make_optional(date.year());
}
+std::vector>
+Release::getArtists() const
+{
+ assert(self());
+ assert(self()->id() != Wt::Dbo::dbo_traits::invalidId() );
+ assert(session());
+
+ Wt::Dbo::collection> res = session()->query>(
+ "SELECT DISTINCT a FROM artist a INNER JOIN release r ON t.artist_id = a.id INNER JOIN track t ON t.release_id = r.id")
+ .where("r.id = ?")
+ .bind(id());
+
+ return std::vector>(res.begin(), res.end());
+}
+
+bool
+Release::hasVariousArtists() const
+{
+ // TODO optimize
+ return getArtists().size() > 1;
+}
+
+std::vector>
+Release::getTracks() const
+{
+ assert(self());
+ assert(session());
+
+ Wt::Dbo::collection> res = session()->query>(
+ "SELECT t FROM track t INNER JOIN release r ON t.release_id = r.id")
+ .where("r.id = ?")
+ .orderBy("t.disc_number,t.track_number")
+ .bind(id());
+
+ return std::vector>(res.begin(), res.end());
+}
} // namespace Database
diff --git a/src/database/Release.hpp b/src/database/Release.hpp
index f92b6c0c..fc6fedcb 100644
--- a/src/database/Release.hpp
+++ b/src/database/Release.hpp
@@ -17,11 +17,11 @@
* along with LMS. If not, see .
*/
-#ifndef _DB_RELEASE_HPP_
-#define _DB_RELEASE_HPP_
+#pragma once
+
+#include
#include
-#include
#include "SearchFilter.hpp"
@@ -30,6 +30,7 @@ namespace Database
class Track;
class Release;
+class Artist;
class Release : public Wt::Dbo::Dbo
{
@@ -46,30 +47,34 @@ class Release : public Wt::Dbo::Dbo
static std::vector getByName(Wt::Dbo::Session& session, const std::string& name);
static pointer getById(Wt::Dbo::Session& session, id_type id);
static pointer getNone(Wt::Dbo::Session& session); // Special entry
- static std::vector getAllOrphans(Wt::Dbo::Session& session);
+ static std::vector getAllOrphans(Wt::Dbo::Session& session); // no track related
static std::vector getAll(Wt::Dbo::Session& session, int offset, int size);
- static std::vector getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset = -1, int size = -1);
- static std::vector getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size, bool& moreExpected);
+
+ static std::vector getByFilter(Wt::Dbo::Session& session,
+ const std::vector& clusters, // at least one track that belongs to these clusters
+ const std::vector keywords, // name must match all of these keywords
+ int offset,
+ int size,
+ bool& moreExpected);
+
+ std::vector> getTracks() const;
// Create
static pointer create(Wt::Dbo::Session& session, const std::string& name, const std::string& MBID = "");
// Utility functions
- int getReleaseYear(bool originalDate = false) const; // 0 if unknown or various
+ boost::optional getReleaseYear(bool originalDate = false) const; // 0 if unknown or various
- // MVC models for the user interface
- // ID, Release name, year, track counts
- typedef boost::tuple UIQueryResult;
- static Wt::Dbo::Query getUIQuery(Wt::Dbo::Session& session, SearchFilter filter);
- static void updateUIQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel< UIQueryResult >& model, SearchFilter filter, const std::vector& columnNames = std::vector());
-
-
- // Accessosrs
+ // Accessors
std::string getName() const { return _name; }
std::string getMBID() const { return _MBID; }
bool isNone(void) const;
boost::posix_time::time_duration getDuration(void) const;
+ // Get the artists of this release
+ std::vector > getArtists() const;
+ bool hasVariousArtists() const;
+
void setMBID(std::string mbid) { _MBID = mbid; }
template
@@ -82,8 +87,6 @@ class Release : public Wt::Dbo::Dbo
}
private:
- static Wt::Dbo::Query getQuery(Wt::Dbo::Session& session, SearchFilter filter);
-
static const std::size_t _maxNameLength = 128;
std::string _name;
@@ -94,5 +97,4 @@ class Release : public Wt::Dbo::Dbo
} // namespace Database
-#endif
diff --git a/src/database/SearchFilter.cpp b/src/database/SearchFilter.cpp
index 194296e4..5fb23e94 100644
--- a/src/database/SearchFilter.cpp
+++ b/src/database/SearchFilter.cpp
@@ -24,98 +24,15 @@
namespace Database
{
-static std::ostream& operator<<(std::ostream& ost, const std::vector< Wt::Dbo::dbo_default_traits::IdType>& ids)
+void
+SearchFilter::operator+=(const SearchFilter& filter)
{
- const char *sep = "";
-
- for (auto id : ids)
- {
- ost << sep << std::to_string(id);
- sep = ",";
- }
-
- return ost;
}
-SqlQuery generatePartialQuery(SearchFilter& filter)
+SqlQuery
+SearchFilter::generatePartialQuery()
{
SqlQuery sqlQuery;
-
- // Process name like parameters
- for (auto nameLikeMatches : filter.nameLikeMatch)
- {
- WhereClause likeWhereClause;
-
- for (auto nameLikeMatch : nameLikeMatches)
- {
- switch (nameLikeMatch.first)
- {
- case SearchFilter::Field::Artist:
- for (const std::string& name : nameLikeMatch.second)
- likeWhereClause.Or( WhereClause("a.name LIKE ?") ).bind("%%" + name + "%%");
- break;
-
- case SearchFilter::Field::Release:
- for (const std::string& name : nameLikeMatch.second)
- likeWhereClause.Or( WhereClause("r.name LIKE ?") ).bind("%%" + name + "%%");
- break;
-
- case SearchFilter::Field::Cluster:
- for (const std::string& name : nameLikeMatch.second)
- likeWhereClause.Or( WhereClause("c.name LIKE ?") ).bind("%%" + name + "%%");
- break;
-
- case SearchFilter::Field::Track:
- for (const std::string& name : nameLikeMatch.second)
- likeWhereClause.Or( WhereClause("t.name LIKE ?") ).bind("%%" + name + "%%");
- break;
- }
- }
- sqlQuery.where().And( likeWhereClause );
- }
-
- // Process id exact match parameters
- // Id list may be long, we do not bind the parameters
- // Safe since we already known these are just ints
- for (auto idMatch : filter.idMatch)
- {
- WhereClause idWhereClause;
-
- switch (idMatch.first)
- {
- case SearchFilter::Field::Artist:
- {
- std::ostringstream oss;
- oss << "a.id IN (" << idMatch.second << ")";
- idWhereClause.Or(oss.str());
- }
- break;
- case SearchFilter::Field::Release:
- {
- std::ostringstream oss;
- oss << "r.id IN (" << idMatch.second << ")";
- idWhereClause.Or(oss.str());
- }
- break;
- case SearchFilter::Field::Cluster:
- {
- std::ostringstream oss;
- oss << "c.id IN (" << idMatch.second << ")";
- idWhereClause.Or(oss.str());
- }
- break;
- case SearchFilter::Field::Track:
- {
- std::ostringstream oss;
- oss << "t.id IN (" << idMatch.second << ")";
- idWhereClause.Or(oss.str());
- }
- break;
- }
-
- sqlQuery.where().And( idWhereClause );
- }
-
return sqlQuery;
}
diff --git a/src/database/SearchFilter.hpp b/src/database/SearchFilter.hpp
index 2156ecfe..01bbcb06 100644
--- a/src/database/SearchFilter.hpp
+++ b/src/database/SearchFilter.hpp
@@ -31,71 +31,37 @@
namespace Database
{
+typedef Wt::Dbo::dbo_default_traits::IdType id_type;
+
class SearchFilter
{
public:
- enum class Field {
- Artist, // artist
- Release, // release
- Track, // track
- Cluster, // cluster
- };
-
- typedef std::vector > > NameLikeMatchType;
- typedef std::map > IdMatchType;
+ typedef int id_type;
SearchFilter() {}
- // Helpers
- static SearchFilter IdMatch( const IdMatchType& _idMatch )
- {
- return SearchFilter(_idMatch);
- }
+ static SearchFilter Artist(std::string name) {return SearchFilter();}
+ static SearchFilter Artist(id_type id) {return SearchFilter();}
- static SearchFilter NameLikeMatch( const NameLikeMatchType& _nameLikeMatch )
- {
- return SearchFilter(_nameLikeMatch);
- }
+ static SearchFilter Release(std::string name) {return SearchFilter();}
+ static SearchFilter Release(id_type id) {return SearchFilter();}
- // Single Field ID match
- static SearchFilter ById(Field field, Wt::Dbo::dbo_default_traits::IdType id)
- {
- return SearchFilter({{field, {id} }});
- }
+ static SearchFilter Track(std::string name) {return SearchFilter();}
- // Single Field Name match OR
- static SearchFilter ByNameOr(Field field, std::vector keywords)
- {
- return NameLikeMatch({{{field, keywords}}});
- }
+ static SearchFilter Cluster(id_type id) {return SearchFilter();}
- // Single Field Name match AND
- static SearchFilter ByNameAnd(Field field, std::vector keywords)
- {
- NameLikeMatchType nameLikeMatch;
+ // Combine search filters by add operation
+ // Caution: multiple filters on different artist/release/track
+ // values may lead to empty results
+ void operator+=(const SearchFilter& filter);
- for (auto keyword : keywords)
- nameLikeMatch.push_back({{{field, {keyword}}}});
-
- return NameLikeMatch(nameLikeMatch);
- }
-
- // The filter is a AND of the following conditions:
-
- // ((Field1.name LIKE STR1-1 OR Field1.name LIKE STR1-2 ...) OR (Field2.name LIKE STR2-1 OR Field2.name LIKE STR2-2 ...) ...
- NameLikeMatchType nameLikeMatch;
-
- // (Field1.id IN (ID1-1,ID1-2 ... ) AND (Field2.id IN (ID2-1,ID2-2 ... ) ...
- IdMatchType idMatch;
+ SqlQuery generatePartialQuery();
private:
- SearchFilter(const NameLikeMatchType& _nameLikeMatch) : nameLikeMatch(_nameLikeMatch) {}
- SearchFilter(const IdMatchType& _idMatch) : idMatch(_idMatch) {}
};
-SqlQuery generatePartialQuery(SearchFilter& filter);
} // namespace Database
diff --git a/src/database/Track.cpp b/src/database/Track.cpp
index cfb3f8ea..ac7faa18 100644
--- a/src/database/Track.cpp
+++ b/src/database/Track.cpp
@@ -108,29 +108,42 @@ Track::getClusters(void) const
return clusters;
}
+static
Wt::Dbo::Query< Track::pointer >
-Track::getQuery(Wt::Dbo::Session& session, SearchFilter filter)
+getQuery(Wt::Dbo::Session& session,
+ const std::vector& clusterIds,
+ const std::vector keywords)
{
- SqlQuery sqlQuery = generatePartialQuery(filter);
+ WhereClause where;
- Wt::Dbo::Query query
- = session.query( "SELECT t FROM track t INNER JOIN artist a ON t.artist_id = a.id INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id INNER JOIN release r ON r.id = t.release_id " + sqlQuery.where().get()).groupBy("t.id").orderBy("a.name,t.date,r.name,t.disc_number,t.track_number");
+ std::ostringstream oss;
+ oss << "SELECT t FROM track t";
- for (const std::string& bindArg : sqlQuery.where().getBindArgs())
- query.bind(bindArg);
+ for (auto keyword : keywords)
+ where.And(WhereClause("t.name LIKE ?")).bind("%%" + keyword + "%%");
- return query;
-}
+ if (!clusterIds.empty())
+ {
+ oss << " INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id";
-Wt::Dbo::Query< Track::UIQueryResult >
-Track::getUIQuery(Wt::Dbo::Session& session, SearchFilter filter)
-{
- SqlQuery sqlQuery = generatePartialQuery(filter);
+ WhereClause clusterClause;
- Wt::Dbo::Query query
- = session.query( "SELECT t.id, a.name, r.name, t.disc_number, t.track_number, t.name, t.duration, t.date, t.original_date, t.genre_list FROM track t INNER JOIN artist a ON t.artist_id = a.id INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id INNER JOIN release r ON r.id = t.release_id " + sqlQuery.where().get()).groupBy("t.id").orderBy("a.name,t.date,r.name,t.disc_number,t.track_number");
+ for (auto id : clusterIds)
+ clusterClause.And(WhereClause("c.id = ?")).bind(std::to_string(id));
- for (const std::string& bindArg : sqlQuery.where().getBindArgs())
+ where.And(clusterClause);
+ }
+
+ oss << " " << where.get();
+
+ if (!clusterIds.empty())
+ oss << " GROUP BY t.id HAVING COUNT(*) = " << clusterIds.size();
+
+ oss << " ORDER BY t.name";
+
+ Wt::Dbo::Query query = session.query( oss.str() );
+
+ for (const std::string& bindArg : where.getBindArgs())
query.bind(bindArg);
return query;
@@ -139,7 +152,7 @@ Track::getUIQuery(Wt::Dbo::Session& session, SearchFilter filter)
Track::StatsQueryResult
Track::getStats(Wt::Dbo::Session& session, SearchFilter filter)
{
- SqlQuery sqlQuery = generatePartialQuery(filter);
+ SqlQuery sqlQuery = filter.generatePartialQuery();
Wt::Dbo::Query query = session.query( "SELECT COUNT(\"id\"), SUM(\"dur\") FROM (SELECT t.id as \"id\", t.duration as \"dur\" FROM track t INNER JOIN artist a ON t.artist_id = a.id INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id INNER JOIN release r ON r.id = t.release_id " + sqlQuery.where().get() + " GROUP BY t.id)");
@@ -151,17 +164,14 @@ Track::getStats(Wt::Dbo::Session& session, SearchFilter filter)
std::vector
-Track::getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size)
+Track::getByFilter(Wt::Dbo::Session& session,
+ const std::vector& clusterIds,
+ const std::vector keywords,
+ int offset, int size, bool& moreResults)
{
- Wt::Dbo::collection res = getQuery(session, filter).limit(size).offset(offset);
+ Wt::Dbo::collection collection = getQuery(session, clusterIds, keywords).limit(size).offset(offset);
- return std::vector(res.begin(), res.end());
-}
-
-std::vector
-Track::getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size, bool& moreResults)
-{
- auto res = getByFilter(session, filter, offset, size + 1);
+ auto res = std::vector(collection.begin(), collection.end());
if (size != -1 && res.size() == static_cast(size) + 1)
{
@@ -174,26 +184,28 @@ Track::getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, i
return res;
}
-void
-Track::updateUIQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel< UIQueryResult >& model, SearchFilter filter, const std::vector& columnNames)
+boost::optional
+Track::getTrackNumber(void) const
{
- Wt::Dbo::Query< UIQueryResult > query = getUIQuery(session, filter);
- model.setQuery(query, columnNames.empty() ? true : false);
+ return (_trackNumber > 0) ? boost::make_optional(_trackNumber) : boost::none;
+}
- // TODO do something better
- if (columnNames.size() == 9)
- {
- model.addColumn( "a.name", columnNames[0] );
- model.addColumn( "r.name", columnNames[1] );
- model.addColumn( "t.disc_number", columnNames[2] );
- model.addColumn( "t.track_number", columnNames[3] );
- model.addColumn( "t.name", columnNames[4] );
- model.addColumn( "t.duration", columnNames[5] );
- model.addColumn( "t.date", columnNames[6] );
- model.addColumn( "t.original_date", columnNames[7] );
- model.addColumn( "t.genre_list", columnNames[8] );
- }
+boost::optional
+Track::getTotalTrackNumber(void) const
+{
+ return (_totalTrackNumber > 0) ? boost::make_optional(_totalTrackNumber) : boost::none;
+}
+boost::optional
+Track::getDiscNumber(void) const
+{
+ return (_discNumber > 0) ? boost::make_optional(_discNumber) : boost::none;
+}
+
+boost::optional
+Track::getTotalDiscNumber(void) const
+{
+ return (_totalDiscNumber > 0) ? boost::make_optional(_totalDiscNumber) : boost::none;
}
@@ -221,6 +233,13 @@ Cluster::get(Wt::Dbo::Session& session, std::string type, std::string name)
return session.find().where("type = ?").where("name = ?").bind( std::string(type, 0, _maxTypeLength)).bind( std::string(name, 0, _maxNameLength));
}
+std::vector
+Cluster::getByType(Wt::Dbo::Session& session, std::string type)
+{
+ Wt::Dbo::collection res = session.find().where("type = ?").bind( std::string(type, 0, _maxTypeLength)).orderBy("name");
+ return std::vector(res.begin(), res.end());
+}
+
Cluster::pointer
Cluster::getNone(Wt::Dbo::Session& session)
{
@@ -252,7 +271,7 @@ Cluster::remove(Wt::Dbo::Session& session, std::string type)
Wt::Dbo::Query
Cluster::getQuery(Wt::Dbo::Session& session, SearchFilter filter)
{
- SqlQuery sqlQuery = generatePartialQuery(filter);
+ SqlQuery sqlQuery = filter.generatePartialQuery();
Wt::Dbo::Query query
= session.query( "SELECT g FROM cluster c INNER JOIN track_cluster t_c ON t_c.cluster_id = c.id INNER JOIN artist a ON t.artist_id = a.id INNER JOIN release r ON r.id = t.release_id INNER JOIN track t ON t.id = t_c.track_id " + sqlQuery.where().get()).groupBy("c.name").orderBy("c.name");
@@ -263,34 +282,6 @@ Cluster::getQuery(Wt::Dbo::Session& session, SearchFilter filter)
return query;
}
-Wt::Dbo::Query
-Cluster::getUIQuery(Wt::Dbo::Session& session, SearchFilter filter)
-{
- SqlQuery sqlQuery = generatePartialQuery(filter);
-
- Wt::Dbo::Query query
- = session.query( "SELECT c.id, c.name, COUNT(DISTINCT t.id) FROM cluster c INNER JOIN track_cluster t_c ON t_c.cluster_id = c.id INNER JOIN artist a ON t.artist_id = a.id INNER JOIN release r ON r.id = t.release_id INNER JOIN track t ON t.id = t_c.track_id " + sqlQuery.where().get()).groupBy("c.name").orderBy("c.name");
-
- for (const std::string& bindArg : sqlQuery.where().getBindArgs())
- query.bind(bindArg);
-
- return query;
-}
-
-void
-Cluster::updateUIQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel& model, SearchFilter filter, const std::vector& columnNames)
-{
- Wt::Dbo::Query query = getUIQuery(session, filter);
- model.setQuery(query, columnNames.empty() ? true : false);
-
- // TODO do something better
- if (columnNames.size() == 2)
- {
- model.addColumn( "c.name", columnNames[0] );
- model.addColumn( "COUNT(DISTINCT t.id)", columnNames[1] );
- }
-}
-
std::vector
Cluster::getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size)
{
diff --git a/src/database/Track.hpp b/src/database/Track.hpp
index 3d523814..d1af053c 100644
--- a/src/database/Track.hpp
+++ b/src/database/Track.hpp
@@ -24,6 +24,7 @@
#include
#include
+#include
#include
#include
@@ -44,6 +45,12 @@ class Cluster
{
public:
+ enum class Type
+ {
+ Genre = 1,
+ Mood = 2,
+ };
+
typedef Wt::Dbo::ptr pointer;
typedef Wt::Dbo::dbo_traits::IdType id_type;
@@ -57,12 +64,6 @@ class Cluster
static Wt::Dbo::collection getAll(Wt::Dbo::Session& session);
static std::vector getByType(Wt::Dbo::Session& session, std::string type);
- // MVC models for the user interface
- // ClusterID, type, name, track count
- typedef boost::tuple UIQueryResult;
- static Wt::Dbo::Query getUIQuery(Wt::Dbo::Session& session, SearchFilter filter);
- static void updateUIQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel& model, SearchFilter filter, const std::vector& columnNames = std::vector());
-
// Create utility
static pointer create(Wt::Dbo::Session& session, std::string type, std::string name);
@@ -118,8 +119,13 @@ class Track
static pointer getByPath(Wt::Dbo::Session& session, const boost::filesystem::path& p);
static pointer getById(Wt::Dbo::Session& session, id_type id);
static pointer getByMBID(Wt::Dbo::Session& session, const std::string& MBID);
- static std::vector getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset = -1, int size = -1);
- static std::vector getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size, bool &moreResults);
+ static std::vector getByFilter(Wt::Dbo::Session& session,
+ const std::vector& clusters, // tracks that belong to these clusters
+ const std::vector keywords, // name must match all of these keywords
+ int offset,
+ int size,
+ bool& moreExpected);
+
static Wt::Dbo::collection< pointer > getAll(Wt::Dbo::Session& session);
static std::vector getAllIds(Wt::Dbo::Session& session); // nested transaction
static std::vector getAllPaths(Wt::Dbo::Session& session); // nested transaction
@@ -127,22 +133,6 @@ class Track
static std::vector getChecksumDuplicates(Wt::Dbo::Session& session);
// Utility fonctions
- // MVC models for the user interface
- // ID, Artist name, Release Name, DiscNumber, TrackNumber, Name, duration, date, original date, genre list
- typedef boost::tuple // genre list
- UIQueryResult;
- static Wt::Dbo::Query< UIQueryResult > getUIQuery(Wt::Dbo::Session& session, SearchFilter filter);
- static void updateUIQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel< UIQueryResult >& model, SearchFilter filter, const std::vector& columnNames = std::vector());
-
// Stats for a given search filter
typedef boost::tuple<
int, // Total tracks
@@ -174,10 +164,10 @@ class Track
void setArtist(Wt::Dbo::ptr artist) { _artist = artist; }
void setRelease(Wt::Dbo::ptr release) { _release = release; }
- int getTrackNumber(void) const { return _trackNumber; }
- int getTotalTrackNumber(void) const { return _totalTrackNumber; }
- int getDiscNumber(void) const { return _discNumber; }
- int getTotalDiscNumber(void) const { return _totalDiscNumber; }
+ boost::optional getTrackNumber(void) const;
+ boost::optional getTotalTrackNumber(void) const;
+ boost::optional getDiscNumber(void) const;
+ boost::optional getTotalDiscNumber(void) const;
std::string getName(void) const { return _name; }
boost::filesystem::path getPath(void) const { return _filePath; }
boost::posix_time::time_duration getDuration(void) const { return _duration; }
@@ -218,8 +208,6 @@ class Track
private:
- static Wt::Dbo::Query< pointer > getQuery(Wt::Dbo::Session& session, SearchFilter filter);
-
static const std::size_t _maxNameLength = 128;
int _trackNumber;
diff --git a/src/database/Types.hpp b/src/database/Types.hpp
index a04b978b..681e4c97 100644
--- a/src/database/Types.hpp
+++ b/src/database/Types.hpp
@@ -19,7 +19,7 @@
// header file aimed to ease database class declarations
-#include "Artist.hpp"
+#include "DbArtist.hpp"
#include "Track.hpp"
#include "Playlist.hpp"
#include "Release.hpp"
diff --git a/src/ui/ArtistView.cpp b/src/ui/ArtistView.cpp
new file mode 100644
index 00000000..5327ced6
--- /dev/null
+++ b/src/ui/ArtistView.cpp
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2018 Emeric Poupon
+ *
+ * This file is part of LMS.
+ *
+ * LMS is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * LMS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with LMS. If not, see .
+ */
+
+#include
+#include
+#include
+#include
+#include
+
+#include "database/Types.hpp"
+
+#include "utils/Logger.hpp"
+#include "utils/Utils.hpp"
+
+#include "LmsApplication.hpp"
+#include "ArtistView.hpp"
+
+namespace UserInterface {
+
+Artist::Artist(Filters* filters, Wt::WContainerWidget* parent)
+: Wt::WContainerWidget(parent),
+ _filters(filters)
+{
+ wApp->internalPathChanged().connect(std::bind([=]
+ {
+ refresh();
+ }));
+
+ refresh();
+}
+
+void
+Artist::refresh()
+{
+ if (!wApp->internalPathMatches("/artist/"))
+ return;
+
+ clear();
+
+ Database::Artist::id_type artistId;
+ if (!readAs(wApp->internalPathNextPart("/artist/"), artistId))
+ return;
+
+ Wt::Dbo::Transaction transaction(DboSession());
+ auto artist = Database::Artist::getById(DboSession(), artistId);
+
+ if (!artist)
+ {
+ LmsApp->goHome();
+ return;
+ }
+
+ auto t = new Wt::WTemplate(Wt::WString::tr("template-artist"), this);
+ t->addFunction("tr", &Wt::WTemplate::Functions::tr);
+
+ t->bindString("name", Wt::WString::fromUTF8(artist->getName()), Wt::PlainText);
+
+ auto releasesContainer = new Wt::WContainerWidget();
+ t->bindWidget("releases", releasesContainer);
+
+ auto releases = artist->getReleases(_filters->getClusterIds());
+ for (auto release : releases)
+ {
+ if (release->isNone())
+ continue;
+
+ Wt::WTemplate* entry = new Wt::WTemplate(Wt::WString::tr("template-artist-entry"), releasesContainer);
+ entry->addFunction("tr", Wt::WTemplate::Functions::tr);
+
+ {
+ Wt::WAnchor *coverAnchor = new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/release/" + std::to_string(release.id())));
+ Wt::WImage *cover = new Wt::WImage(coverAnchor);
+ cover->setImageLink(SessionImageResource()->getReleaseUrl(release.id(), 128));
+ // Some images may not be square
+ cover->setWidth(128);
+ entry->bindWidget("cover", coverAnchor);
+ }
+
+ {
+ Wt::WAnchor *releaseAnchor = new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/release/" + std::to_string(release.id())));
+ Wt::WText *releaseName = new Wt::WText(releaseAnchor);
+ releaseName->setText(Wt::WString::fromUTF8(release->getName(), Wt::PlainText));
+ entry->bindWidget("name", releaseAnchor);
+ }
+
+ if (release->hasVariousArtists())
+ entry->setCondition("if-has-various-artists", true);
+
+ boost::optional year = release->getReleaseYear();
+ if (year)
+ {
+ entry->setCondition("if-has-year", true);
+ entry->bindInt("year", *year);
+
+ boost::optional originalYear = release->getReleaseYear(true);
+ if (originalYear && *originalYear != *year)
+ {
+ entry->setCondition("if-has-orig-year", true);
+ entry->bindInt("orig-year", *originalYear);
+ }
+ }
+ }
+}
+
+} // namespace UserInterface
+
diff --git a/src/ui/audio/mobile/TrackSearchView.hpp b/src/ui/ArtistView.hpp
similarity index 77%
rename from src/ui/audio/mobile/TrackSearchView.hpp
rename to src/ui/ArtistView.hpp
index 602d9c56..eb7262d7 100644
--- a/src/ui/audio/mobile/TrackSearchView.hpp
+++ b/src/ui/ArtistView.hpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 Emeric Poupon
+ * Copyright (C) 2018 Emeric Poupon
*
* This file is part of LMS.
*
@@ -20,19 +20,21 @@
#pragma once
#include
-#include
+
+#include "Filters.hpp"
namespace UserInterface {
-namespace Mobile {
-class TrackSearchView : public Wt::WContainerWidget
+class Artist : public Wt::WContainerWidget
{
public:
+ Artist(Filters* filters, Wt::WContainerWidget* parent = 0);
- TrackSearchView(PlayQueueEvents& events, Wt::WContainerWidget* parent = 0);
+ private:
+ void refresh();
+ Filters* _filters;
};
-} // namespace Mobile
} // namespace UserInterface
diff --git a/src/ui/ArtistsView.cpp b/src/ui/ArtistsView.cpp
new file mode 100644
index 00000000..e684db2d
--- /dev/null
+++ b/src/ui/ArtistsView.cpp
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2018 Emeric Poupon
+ *
+ * This file is part of LMS.
+ *
+ * LMS is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * LMS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with LMS. If not, see .
+ */
+
+#include
+#include
+#include
+#include
+
+#include "database/Types.hpp"
+
+#include "utils/Logger.hpp"
+#include "utils/Utils.hpp"
+
+#include "LmsApplication.hpp"
+#include "ArtistsView.hpp"
+
+namespace UserInterface {
+
+using namespace Database;
+
+Artists::Artists(Filters* filters, Wt::WContainerWidget* parent)
+: Wt::WContainerWidget(parent),
+ _filters(filters)
+{
+ auto artists = new Wt::WTemplate(Wt::WString::tr("template-artists"), this);
+ artists->addFunction("tr", &Wt::WTemplate::Functions::tr);
+
+ auto search = new Wt::WLineEdit();
+ artists->bindWidget("search", search);
+ search->setPlaceholderText(Wt::WString::tr("msg-search-placeholder"));
+ search->textInput().connect(std::bind([this, search]
+ {
+ auto keywords = splitString(search->text().toUTF8(), " ");
+ refresh(keywords);
+ }));
+
+ _artistsContainer = new Wt::WContainerWidget();
+ artists->bindWidget("artists", _artistsContainer);
+
+ refresh();
+}
+
+
+void
+Artists::refresh(std::vector searchKeywords)
+{
+ _artistsContainer->clear();
+
+ auto clusterIds = _filters->getClusterIds();
+
+ Wt::Dbo::Transaction transaction(DboSession());
+
+ bool moreResults;
+ auto artists = Artist::getByFilter(DboSession(),
+ clusterIds,
+ searchKeywords,
+ 0, 40, moreResults);
+
+ for (auto artist : artists)
+ {
+ Wt::WTemplate* entry = new Wt::WTemplate(Wt::WString::tr("template-artists-entry"), _artistsContainer);
+
+ entry->bindInt("nb-release", artist->getReleases(clusterIds).size());
+
+ {
+ Wt::WAnchor *artistAnchor = new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/artist/" + std::to_string(artist.id())));
+ Wt::WText *artistText = new Wt::WText(artistAnchor);
+ artistText->setText(Wt::WString::fromUTF8(artist->getName(), Wt::PlainText));
+ entry->bindWidget("name", artistAnchor);
+ }
+ }
+
+}
+
+} // namespace UserInterface
+
diff --git a/src/ui/audio/mobile/MobileAudio.hpp b/src/ui/ArtistsView.hpp
similarity index 71%
rename from src/ui/audio/mobile/MobileAudio.hpp
rename to src/ui/ArtistsView.hpp
index c49b24ae..2cc29bfc 100644
--- a/src/ui/audio/mobile/MobileAudio.hpp
+++ b/src/ui/ArtistsView.hpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 Emeric Poupon
+ * Copyright (C) 2018 Emeric Poupon
*
* This file is part of LMS.
*
@@ -21,27 +21,21 @@
#include
-#include "audio/Audio.hpp"
-
-#include "MobilePlayQueueEvents.hpp"
-
+#include "Filters.hpp"
namespace UserInterface {
-namespace Mobile {
-class Audio : public UserInterface::Audio
+class Artists : public Wt::WContainerWidget
{
public:
- Audio(Wt::WContainerWidget *parent = 0);
-
- void search(std::string text);
+ Artists(Filters* filters, Wt::WContainerWidget* parent = 0);
private:
+ void refresh(std::vector searchKeywords = std::vector());
- PlayQueueEvents _playQueueEvents;
-
+ Filters* _filters;
+ Wt::WContainerWidget* _artistsContainer;
};
-} // namespace Mobile
} // namespace UserInterface
diff --git a/src/ui/Explore.cpp b/src/ui/Explore.cpp
new file mode 100644
index 00000000..46cfc5ca
--- /dev/null
+++ b/src/ui/Explore.cpp
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2018 Emeric Poupon
+ *
+ * This file is part of LMS.
+ *
+ * LMS is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * LMS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with LMS. If not, see .
+ */
+
+#include
+#include
+#include
+
+#include "utils/Logger.hpp"
+
+#include "LmsApplication.hpp"
+
+#include "ArtistsView.hpp"
+#include "ArtistView.hpp"
+#include "ReleasesView.hpp"
+#include "ReleaseView.hpp"
+#include "TracksView.hpp"
+
+#include "Explore.hpp"
+
+namespace UserInterface {
+
+
+enum IdxRoot
+{
+ IdxArtists = 0,
+ IdxArtist,
+ IdxReleases,
+ IdxRelease,
+ IdxTracks,
+};
+
+static void
+handlePathChange(Wt::WStackedWidget* stack)
+{
+ static const std::map indexes =
+ {
+ { "/artists", IdxArtists },
+ { "/artist", IdxArtist },
+ { "/releases", IdxReleases },
+ { "/release", IdxRelease },
+ { "/tracks", IdxTracks },
+ };
+
+ LMS_LOG(UI, DEBUG) << "Internal path changed to '" << wApp->internalPath() << "'";
+
+ for (auto index : indexes)
+ {
+ if (wApp->internalPathMatches(index.first))
+ {
+ stack->setCurrentIndex(index.second);
+ return;
+ }
+ }
+}
+
+Explore::Explore(Wt::WContainerWidget* parent)
+: Wt::WContainerWidget(parent)
+{
+ auto container = new Wt::WTemplate(Wt::WString::tr("template-explore"), this);
+
+ auto filters = new Filters();
+ container->bindWidget("filters", filters);
+
+ // Contents
+ Wt::WStackedWidget* stack = new Wt::WStackedWidget();
+ container->bindWidget("contents", stack);
+
+ stack->addWidget(new Artists(filters));
+ stack->addWidget(new Artist(filters));
+ stack->addWidget(new Releases(/*filters*/));
+ stack->addWidget(new Release(/*filters*/));
+ stack->addWidget(new Tracks(/*filters*/));
+
+ wApp->internalPathChanged().connect(std::bind([=]
+ {
+ handlePathChange(stack);
+ }));
+
+ handlePathChange(stack);
+}
+} // namespace UserInterface
+
diff --git a/src/ui/audio/mobile/ArtistSearchView.hpp b/src/ui/Explore.hpp
similarity index 80%
rename from src/ui/audio/mobile/ArtistSearchView.hpp
rename to src/ui/Explore.hpp
index 29971950..ac360a83 100644
--- a/src/ui/audio/mobile/ArtistSearchView.hpp
+++ b/src/ui/Explore.hpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 Emeric Poupon
+ * Copyright (C) 2018 Emeric Poupon
*
* This file is part of LMS.
*
@@ -22,16 +22,12 @@
#include
namespace UserInterface {
-namespace Mobile {
-class ArtistSearchView : public Wt::WContainerWidget
+class Explore : public Wt::WContainerWidget
{
public:
-
- ArtistSearchView(Wt::WContainerWidget* parent = 0);
-
+ Explore(Wt::WContainerWidget *parent = 0);
};
-} // namespace Mobile
} // namespace UserInterface
diff --git a/src/ui/audio/Audio.hpp b/src/ui/Filters.cpp
similarity index 57%
rename from src/ui/audio/Audio.hpp
rename to src/ui/Filters.cpp
index 422b0cca..8dc3f131 100644
--- a/src/ui/audio/Audio.hpp
+++ b/src/ui/Filters.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 Emeric Poupon
+ * Copyright (C) 2018 Emeric Poupon
*
* This file is part of LMS.
*
@@ -17,25 +17,29 @@
* along with LMS. If not, see .
*/
-#ifndef UI_AUDIO_HPP
-#define UI_AUDIO_HPP
+#include
+#include
-#include
-#include
+#include "Filters.hpp"
namespace UserInterface {
-class Audio : public Wt::WContainerWidget
+Filters::Filters(Wt::WContainerWidget *parent)
+: Wt::WContainerWidget(parent)
{
- public:
+ auto container = new Wt::WTemplate(Wt::WString::tr("template-filters"), this);
- Audio(Wt::WContainerWidget *parent) : Wt::WContainerWidget(parent) {}
- virtual ~Audio() {}
+ // Filters
+ Wt::WPushButton *addFilterBtn = new Wt::WPushButton(Wt::WText::tr("msg-filter-add"));
+ container->bindWidget("add-filter", addFilterBtn);
+
+ _filters = new Wt::WContainerWidget();
+ container->bindWidget("filters", _filters);
+
+// _filterIds = {108, 419};
+}
- virtual void search(std::string text) = 0;
-};
} // namespace UserInterface
-#endif
diff --git a/src/ui/audio/mobile/ArtistView.hpp b/src/ui/Filters.hpp
similarity index 68%
rename from src/ui/audio/mobile/ArtistView.hpp
rename to src/ui/Filters.hpp
index 089e6f53..98d2e4c0 100644
--- a/src/ui/audio/mobile/ArtistView.hpp
+++ b/src/ui/Filters.hpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 Emeric Poupon
+ * Copyright (C) 2018 Emeric Poupon
*
* This file is part of LMS.
*
@@ -20,24 +20,25 @@
#pragma once
#include
-#include
#include "database/Types.hpp"
-namespace UserInterface {
-namespace Mobile {
+#include "Filters.hpp"
-class ArtistView : public Wt::WContainerWidget
+namespace UserInterface {
+
+class Filters : public Wt::WContainerWidget
{
public:
+ Filters(Wt::WContainerWidget *parent = 0);
- static Wt::WLink getLink(Database::Artist::id_type id);
+ std::vector getClusterIds() const { return _filterIds; }
- ArtistView(Wt::WContainerWidget *parent = 0);
+ private:
+ Wt::WContainerWidget *_filters;
+ std::vector _filterIds;
};
-
-} //namespace Mobile
-} //namespace UserInterface
+} // namespace UserInterface
diff --git a/src/ui/audio/mobile/MobilePlayQueueEvents.hpp b/src/ui/HomeView.cpp
similarity index 71%
rename from src/ui/audio/mobile/MobilePlayQueueEvents.hpp
rename to src/ui/HomeView.cpp
index 61775642..730f981e 100644
--- a/src/ui/audio/mobile/MobilePlayQueueEvents.hpp
+++ b/src/ui/HomeView.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 Emeric Poupon
+ * Copyright (C) 2018 Emeric Poupon
*
* This file is part of LMS.
*
@@ -17,20 +17,22 @@
* along with LMS. If not, see .
*/
-#pragma once
+#include
-#include
-#include "database/Types.hpp"
+#include "utils/Logger.hpp"
+
+#include "LmsApplication.hpp"
+
+#include "HomeView.hpp"
namespace UserInterface {
-namespace Mobile {
-struct PlayQueueEvents
+
+Home::Home(Wt::WContainerWidget *parent)
+: Wt::WContainerWidget(parent)
{
- Wt::Signal trackPlay;
- Wt::Signal trackAdd;
-};
+ addWidget(new Wt::WTemplate(Wt::WString::tr("template-home")));
+}
-
-} // namespace Mobile
} // namespace UserInterface
+
diff --git a/src/ui/audio/mobile/ReleaseSearchView.hpp b/src/ui/HomeView.hpp
similarity index 80%
rename from src/ui/audio/mobile/ReleaseSearchView.hpp
rename to src/ui/HomeView.hpp
index 8427fe68..33f65d5e 100644
--- a/src/ui/audio/mobile/ReleaseSearchView.hpp
+++ b/src/ui/HomeView.hpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 Emeric Poupon
+ * Copyright (C) 2018 Emeric Poupon
*
* This file is part of LMS.
*
@@ -22,16 +22,18 @@
#include
namespace UserInterface {
-namespace Mobile {
-class ReleaseSearchView : public Wt::WContainerWidget
+class Home : public Wt::WContainerWidget
{
public:
+ Home(Wt::WContainerWidget *parent = 0);
- ReleaseSearchView(Wt::WContainerWidget* parent = 0);
+ private:
+
+ void displayFilter(void);
+ void displayContents(void);
};
-} // namespace Mobile
} // namespace UserInterface
diff --git a/src/ui/LmsApplication.cpp b/src/ui/LmsApplication.cpp
index d68c2f02..063e97be 100644
--- a/src/ui/LmsApplication.cpp
+++ b/src/ui/LmsApplication.cpp
@@ -19,28 +19,19 @@
#include
#include
-#include
#include
#include
#include
-#include
-#include
-#include
+#include
#include "config/config.h"
#include "utils/Logger.hpp"
#include "utils/Utils.hpp"
-#include "settings/Settings.hpp"
-#include "settings/SettingsFirstConnectionFormView.hpp"
-
#include "auth/LmsAuth.hpp"
-#include "audio/desktop/DesktopAudio.hpp"
-#include "audio/mobile/MobileAudio.hpp"
-#if HAVE_VIDEO
-#include "video/VideoWidget.hpp"
-#endif
-#include "common/LineEdit.hpp"
+#include "Explore.hpp"
+#include "HomeView.hpp"
+
#include "LmsApplication.hpp"
@@ -48,19 +39,6 @@ namespace skeletons {
extern const char *AuthStrings_xml1;
}
-namespace {
-
-bool agentIsMobile()
-{
- const Wt::WEnvironment& env = Wt::WApplication::instance()->environment();
- return (env.agentIsIEMobile()
- || env.agentIsMobileWebKit()
- || env.userAgent().find("Mobile") != std::string::npos // Workaround for firefox
- || env.userAgent().find("Tablet") != std::string::npos // Workaround for firefox
- );
-}
-}
-
namespace UserInterface {
Wt::WApplication*
@@ -100,7 +78,13 @@ LmsApplication::LmsApplication(const Wt::WEnvironment& env, Wt::Dbo::SqlConnecti
useStyleSheet("resources/font-awesome/css/font-awesome.min.css");
// Add a resource bundle
+ messageResourceBundle().use(appRoot() + "artist");
+ messageResourceBundle().use(appRoot() + "artists");
+ messageResourceBundle().use(appRoot() + "messages");
messageResourceBundle().use(appRoot() + "templates");
+ messageResourceBundle().use(appRoot() + "release");
+ messageResourceBundle().use(appRoot() + "releases");
+ messageResourceBundle().use(appRoot() + "tracks");
setTitle("LMS");
@@ -114,9 +98,9 @@ LmsApplication::LmsApplication(const Wt::WEnvironment& env, Wt::Dbo::SqlConnecti
LMS_LOG(UI, DEBUG) << "Creating root widget. First connection = " << std::boolalpha << firstConnection;
// If here is no account in the database, launch the first connection wizard
- if (firstConnection)
- createFirstConnectionUI();
- else
+// if (firstConnection)
+// createFirstConnectionUI();
+// else
createLmsUI();
}
@@ -149,13 +133,19 @@ TranscodeResource* SessionTranscodeResource()
return LmsApplication::instance()->getTranscodeResource();
}
+void
+LmsApplication::goHome()
+{
+ setInternalPath("/home", true);
+}
+
void
LmsApplication::createFirstConnectionUI()
{
// Hack, use the auth widget builtin strings
builtinLocalizedStrings().useBuiltin(skeletons::AuthStrings_xml1);
- root()->addWidget( new Settings::FirstConnectionFormView());
+// root()->addWidget( new Settings::FirstConnectionFormView());
}
void
@@ -164,6 +154,8 @@ LmsApplication::createLmsUI()
_imageResource = new ImageResource(_db, root());
_transcodeResource = new TranscodeResource(_db, root());
+ handleAuthEvent();
+ /*
DbHandler().getLogin().changed().connect(this, &LmsApplication::handleAuthEvent);
LmsAuth *authWidget = new LmsAuth();
@@ -174,11 +166,53 @@ LmsApplication::createLmsUI()
authWidget->processEnvironment();
root()->addWidget(authWidget);
+ */
+}
+
+
+enum IdxRoot
+{
+ IdxHome = 0,
+ IdxExplore,
+ IdxPlaylist,
+ IdxSettings,
+};
+
+
+
+static void
+handlePathChange(Wt::WStackedWidget* stack)
+{
+ static const std::map indexes =
+ {
+ { "/home", IdxHome },
+ { "/artists", IdxExplore },
+ { "/artist", IdxExplore },
+ { "/releases", IdxExplore },
+ { "/release", IdxExplore },
+ { "/tracks", IdxExplore },
+ { "/playlist", IdxPlaylist },
+ { "/settings", IdxSettings },
+ };
+
+ LMS_LOG(UI, DEBUG) << "Internal path changed to '" << wApp->internalPath() << "'";
+
+ for (auto index : indexes)
+ {
+ if (wApp->internalPathMatches(index.first))
+ {
+ stack->setCurrentIndex(index.second);
+ return;
+ }
+ }
+
+ wApp->setInternalPath("/home", true);
}
void
LmsApplication::handleAuthEvent(void)
{
+ /*
if (!DbHandler().getLogin().loggedIn())
{
LMS_LOG(UI, INFO) << "User logged out, session = " << Wt::WApplication::instance()->sessionId();
@@ -190,76 +224,61 @@ LmsApplication::handleAuthEvent(void)
}
LMS_LOG(UI, INFO) << "User '" << CurrentAuthUser().identity(Wt::Auth::Identity::LoginName) << "' logged in from '" << Wt::WApplication::instance()->environment().clientAddress() << "', user agent = " << Wt::WApplication::instance()->environment().agent() << ", session = " << Wt::WApplication::instance()->sessionId();
+*/
+ setConfirmCloseMessage(Wt::WString::tr("msg-quit-confirm"));
- this->root()->setOverflow(Wt::WContainerWidget::OverflowHidden);
- setConfirmCloseMessage("Closing LMS. Are you sure?");
+ auto main = new Wt::WTemplate(Wt::WString::tr("template-main"), root());
- // Create a Vertical layout: top is the nav bar, bottom is the contents
- Wt::WVBoxLayout *layout = new Wt::WVBoxLayout(this->root());
+ // Navbar
+ auto navbar = new Wt::WNavigationBar();
+ navbar->setTitle("LMS", Wt::WLink(Wt::WLink::InternalPath, "/home"));
+ navbar->setResponsive(true);
+// navbar->setStyleClass("main-nav");
- Wt::WNavigationBar *navigation = new Wt::WNavigationBar();
- navigation->setTitle("LMS", "https://github.com/epoupon/lms");
- navigation->setResponsive(true);
- navigation->addStyleClass("main-nav");
+ main->bindWidget("navbar-top", navbar);
- Wt::WStackedWidget *contentsStack = new Wt::WStackedWidget();
-
- contentsStack->setOverflow(Wt::WContainerWidget::OverflowAuto);
- contentsStack->addStyleClass("contents");
-
- Wt::WMenu *menu = new Wt::WMenu(contentsStack);
- navigation->addMenu(menu, Wt::AlignRight);
-
- LineEdit *searchEdit = new LineEdit(500);
- navigation->bindWidget("search", searchEdit);
- searchEdit->setEmptyText("Search...");
- searchEdit->addStyleClass("navbar-form navbar-nav");
- searchEdit->setWidth(150);
- // TODO add a span with a search icon
-
- menu->setInternalPathEnabled();
- menu->setInternalBasePath("/");
-
- Audio *audio;
- if (agentIsMobile())
- audio = new Mobile::Audio();
- else
- audio = new Desktop::Audio();
-
- menu->addItem("Audio", audio)->setPathComponent("audio");;
-#if defined HAVE_VIDEO
- menu->addItem("Video", new VideoWidget())->setPathComponent("video");
-#endif
- menu->addItem("Settings", new Settings::Settings())->setPathComponent("settings");
-
- Wt::WPopupMenu *popup = new Wt::WPopupMenu();
- popup->addItem("Logout");
-
- Wt::WMenuItem *item = new Wt::WMenuItem( CurrentAuthUser().identity(Wt::Auth::Identity::LoginName) );
- item->setMenu(popup);
- menu->addItem(item);
-
- popup->itemSelected().connect(std::bind([=] (Wt::WMenuItem* item)
+ auto menu = new Wt::WMenu();
{
- if (item && item->text() == "Logout")
- DbHandler().getLogin().logout();
- }, std::placeholders::_1));
-
- searchEdit->timedChanged().connect(std::bind([=] ()
+ auto menuItem = menu->insertItem(0, Wt::WString::tr("msg-artists"));
+ menuItem->setLink(Wt::WLink(Wt::WLink::InternalPath, "/artists"));
+ menuItem->setSelectable(false);
+ }
{
- // TODO: check which view is activated and search into it
- audio->search(searchEdit->text().toUTF8());
+ auto menuItem = menu->insertItem(1, Wt::WString::tr("msg-releases"));
+ menuItem->setLink(Wt::WLink(Wt::WLink::InternalPath, "/releases"));
+ menuItem->setSelectable(false);
+ }
+ {
+ auto menuItem = menu->insertItem(2, Wt::WString::tr("msg-tracks"));
+ menuItem->setLink(Wt::WLink(Wt::WLink::InternalPath, "/tracks"));
+ menuItem->setSelectable(false);
+ }
+ {
+ auto menuItem = menu->insertItem(3, Wt::WString::tr("msg-playlist"));
+ menuItem->setLink(Wt::WLink(Wt::WLink::InternalPath, "/playlist"));
+ menuItem->setSelectable(false);
+ }
+ navbar->addMenu(menu);
+
+ // Contents
+ Wt::WStackedWidget* mainStack = new Wt::WStackedWidget();
+ main->bindWidget("contents", mainStack);
+
+ mainStack->addWidget(new Home());
+ mainStack->addWidget(new Explore());
+ mainStack->addWidget(new Wt::WText("PLAYLIST"));
+ mainStack->addWidget(new Wt::WText("SETTINGS"));
+
+ // MediaPlayer
+// auto player = new AudioPlayer(AudioPlayer::ControlPlayqueue);
+// main->bindWidget("player", player);
+
+ internalPathChanged().connect(std::bind([=]
+ {
+ handlePathChange(mainStack);
}));
- layout->addWidget(navigation);
- layout->addWidget(contentsStack, 1);
- layout->setContentsMargins(0, 0, 0, 0);
-
- if (agentIsMobile())
- setInternalPath("/audio/search/preview", true);
- else
- setInternalPath("/audio");
-
+ handlePathChange(mainStack);
}
} // namespace UserInterface
diff --git a/src/ui/LmsApplication.hpp b/src/ui/LmsApplication.hpp
index ee0cfe52..de460e4e 100644
--- a/src/ui/LmsApplication.hpp
+++ b/src/ui/LmsApplication.hpp
@@ -44,6 +44,9 @@ class LmsApplication : public Wt::WApplication
TranscodeResource* getTranscodeResource() { return _transcodeResource; }
Database::Handler& getDbHandler() { return _db;}
+ // Utils
+ void goHome();
+
private:
void handleAuthEvent(void);
@@ -67,6 +70,7 @@ Database::User::pointer CurrentUser();
ImageResource *SessionImageResource();
TranscodeResource *SessionTranscodeResource();
+
} // namespace UserInterface
#endif
diff --git a/src/ui/ReleaseView.cpp b/src/ui/ReleaseView.cpp
new file mode 100644
index 00000000..098b12a5
--- /dev/null
+++ b/src/ui/ReleaseView.cpp
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2018 Emeric Poupon
+ *
+ * This file is part of LMS.
+ *
+ * LMS is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * LMS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with LMS. If not, see .
+ */
+
+#include
+#include
+#include