Handle artist display names
This commit is contained in:
@@ -14,6 +14,7 @@ The following extra fields are implemented:
|
|||||||
* `musicBrainzId`
|
* `musicBrainzId`
|
||||||
* `genres`
|
* `genres`
|
||||||
* `artists`
|
* `artists`
|
||||||
|
* `displayArtist`
|
||||||
* `releaseTypes`
|
* `releaseTypes`
|
||||||
* `moods`
|
* `moods`
|
||||||
* `originalReleaseDate`
|
* `originalReleaseDate`
|
||||||
@@ -23,7 +24,9 @@ The following extra fields are implemented:
|
|||||||
* `musicBrainzId`: note this is actually the recording MBID when this response refers to a song
|
* `musicBrainzId`: note this is actually the recording MBID when this response refers to a song
|
||||||
* `genres`
|
* `genres`
|
||||||
* `artists`
|
* `artists`
|
||||||
|
* `displayArtist`
|
||||||
* `albumArtists`
|
* `albumArtists`
|
||||||
|
* `displayAlbumArtist`
|
||||||
* `contributors`
|
* `contributors`
|
||||||
* `moods`
|
* `moods`
|
||||||
* `replayGain`
|
* `replayGain`
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
<div class="col">
|
<div class="col">
|
||||||
<h2>${name}</h2>
|
<h2>${name}</h2>
|
||||||
${<if-has-release-artists>}${artists}${</if-has-release-artists>}
|
${<if-has-release-artists>}${artists}${</if-has-release-artists>}
|
||||||
${<if-has-various-release-artists>}<div class="text-secondary">${tr:Lms.Explore.various-artists}</div>${</if-has-various-release-artists>}
|
|
||||||
<div class="small text-muted">${<if-has-year>}${year} · ${</if-has-year>}${duration}</div>
|
<div class="small text-muted">${<if-has-year>}${year} · ${</if-has-year>}${duration}</div>
|
||||||
${clusters class="mb-2"}
|
${clusters class="mb-2"}
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
|
|||||||
@@ -40,12 +40,11 @@
|
|||||||
<message id="Lms.Explore.Releases.template.entry-grid">
|
<message id="Lms.Explore.Releases.template.entry-grid">
|
||||||
<div class="col h-100 Lms-bg-dark-hover rounded">
|
<div class="col h-100 Lms-bg-dark-hover rounded">
|
||||||
<div class="p-2">
|
<div class="p-2">
|
||||||
<div class="Lms-responsive-square Lms-cover-container">
|
<div class="Lms-responsive-square Lms-cover-container mb-1">
|
||||||
${cover class="shadow-sm"}
|
${cover class="shadow-sm"}
|
||||||
</div>
|
</div>
|
||||||
${release-name class="d-block text-truncate text-nowrap text-decoration-none link-success"}
|
${release-name class="d-block text-truncate text-nowrap text-decoration-none link-success"}
|
||||||
${<if-has-artist>}${artist-name class="d-block text-truncate text-nowrap text-decoration-none link-secondary"}${</if-has-artist>}
|
${<if-has-artist>}${artist-name class="d-block text-truncate text-nowrap"}${</if-has-artist>}
|
||||||
${<if-has-various-artists>}<div class="text-truncate text-nowrap text-secondary">${tr:Lms.Explore.various-artists}</div>${</if-has-various-artists>}
|
|
||||||
${<if-has-year>}<div class="small text-muted">${year}</div>${</if-has-year>}
|
${<if-has-year>}<div class="small text-muted">${year}</div>${</if-has-year>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -210,6 +210,7 @@ getRelease(const TagMap& tags)
|
|||||||
|
|
||||||
release.emplace();
|
release.emplace();
|
||||||
release->name = std::move(*releaseName);
|
release->name = std::move(*releaseName);
|
||||||
|
release->artistDisplayName = getPropertyValueAs<std::string_view>(tags, "ALBUMARTIST").value_or("");
|
||||||
release->mbid = getPropertyValueFirstMatchAs<UUID>(tags, {"MUSICBRAINZ_ALBUMID", "MUSICBRAINZ ALBUM ID", "MUSICBRAINZ/ALBUM ID"});
|
release->mbid = getPropertyValueFirstMatchAs<UUID>(tags, {"MUSICBRAINZ_ALBUMID", "MUSICBRAINZ ALBUM ID", "MUSICBRAINZ/ALBUM ID"});
|
||||||
release->artists = getArtists(tags, {"ALBUMARTISTS", "ALBUMARTIST"}, {"ALBUMARTISTSSORT", "ALBUMARTISTSORT"}, {"MUSICBRAINZ_ALBUMARTISTID", "MUSICBRAINZ ALBUM ARTIST ID", "MUSICBRAINZ/ALBUM ARTIST ID"});
|
release->artists = getArtists(tags, {"ALBUMARTISTS", "ALBUMARTIST"}, {"ALBUMARTISTSSORT", "ALBUMARTISTSORT"}, {"MUSICBRAINZ_ALBUMARTISTID", "MUSICBRAINZ ALBUM ARTIST ID", "MUSICBRAINZ/ALBUM ARTIST ID"});
|
||||||
release->mediumCount = getPropertyValueAs<std::size_t>(tags, "DISCTOTAL");
|
release->mediumCount = getPropertyValueAs<std::size_t>(tags, "DISCTOTAL");
|
||||||
@@ -353,6 +354,8 @@ TagLibParser::processTag(Track& track, const std::string& tag, const std::vector
|
|||||||
track.copyrightURL = value;
|
track.copyrightURL = value;
|
||||||
else if (tag == "REPLAYGAIN_TRACK_GAIN")
|
else if (tag == "REPLAYGAIN_TRACK_GAIN")
|
||||||
track.replayGain = StringUtils::readAs<float>(value);
|
track.replayGain = StringUtils::readAs<float>(value);
|
||||||
|
else if (tag == "ARTIST")
|
||||||
|
track.artistDisplayName = value;
|
||||||
else if (_clusterTypeNames.find(tag) != _clusterTypeNames.end())
|
else if (_clusterTypeNames.find(tag) != _clusterTypeNames.end())
|
||||||
{
|
{
|
||||||
std::set<std::string> clusterNames;
|
std::set<std::string> clusterNames;
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ namespace MetaData
|
|||||||
|
|
||||||
std::optional<UUID> mbid;
|
std::optional<UUID> mbid;
|
||||||
std::string name;
|
std::string name;
|
||||||
|
std::string artistDisplayName;
|
||||||
std::vector<Artist> artists;
|
std::vector<Artist> artists;
|
||||||
std::optional<std::size_t> mediumCount;
|
std::optional<std::size_t> mediumCount;
|
||||||
std::optional<PrimaryType> primaryType;
|
std::optional<PrimaryType> primaryType;
|
||||||
@@ -117,6 +118,7 @@ namespace MetaData
|
|||||||
std::string copyright;
|
std::string copyright;
|
||||||
std::string copyrightURL;
|
std::string copyrightURL;
|
||||||
std::optional<float> replayGain;
|
std::optional<float> replayGain;
|
||||||
|
std::string artistDisplayName;
|
||||||
std::vector<Artist> artists;
|
std::vector<Artist> artists;
|
||||||
std::vector<Artist> conductorArtists;
|
std::vector<Artist> conductorArtists;
|
||||||
std::vector<Artist> composerArtists;
|
std::vector<Artist> composerArtists;
|
||||||
|
|||||||
@@ -665,6 +665,18 @@ CREATE TABLE IF NOT EXISTS "track_backup" (
|
|||||||
ScanSettings::get(session).modify()->incScanVersion();
|
ScanSettings::get(session).modify()->incScanVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
void
|
||||||
|
migrateFromV40(Session& session)
|
||||||
|
{
|
||||||
|
// add artist_display_name in Release and Track
|
||||||
|
session.getDboSession().execute("ALTER TABLE release ADD artist_display_name TEXT");
|
||||||
|
session.getDboSession().execute("ALTER TABLE track ADD artist_display_name TEXT");
|
||||||
|
|
||||||
|
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
|
||||||
|
ScanSettings::get(session).modify()->incScanVersion();
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
doDbMigration(Session& session)
|
doDbMigration(Session& session)
|
||||||
{
|
{
|
||||||
@@ -711,6 +723,7 @@ CREATE TABLE IF NOT EXISTS "track_backup" (
|
|||||||
{37, migrateFromV37},
|
{37, migrateFromV37},
|
||||||
{38, migrateFromV38},
|
{38, migrateFromV38},
|
||||||
{39, migrateFromV39},
|
{39, migrateFromV39},
|
||||||
|
{40, migrateFromV40},
|
||||||
};
|
};
|
||||||
|
|
||||||
while (1)
|
while (1)
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ namespace Database
|
|||||||
class Session;
|
class Session;
|
||||||
|
|
||||||
using Version = std::size_t;
|
using Version = std::size_t;
|
||||||
static constexpr Version LMS_DATABASE_VERSION {40};
|
static constexpr Version LMS_DATABASE_VERSION {41};
|
||||||
class VersionInfo
|
class VersionInfo
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -113,6 +113,7 @@ namespace Database
|
|||||||
Wt::WDateTime getLastWritten() const;
|
Wt::WDateTime getLastWritten() const;
|
||||||
std::optional<ReleaseTypePrimary> getPrimaryType() const { return _primaryType; }
|
std::optional<ReleaseTypePrimary> getPrimaryType() const { return _primaryType; }
|
||||||
EnumSet<ReleaseTypeSecondary> getSecondaryTypes() const { return _secondaryTypes; }
|
EnumSet<ReleaseTypeSecondary> getSecondaryTypes() const { return _secondaryTypes; }
|
||||||
|
std::string_view getArtistDisplayName() const { return _artistDisplayName; }
|
||||||
std::size_t getTracksCount() const;
|
std::size_t getTracksCount() const;
|
||||||
|
|
||||||
// Setters
|
// Setters
|
||||||
@@ -121,6 +122,7 @@ namespace Database
|
|||||||
void setTotalDisc(std::optional<int> totalDisc) { _totalDisc = totalDisc; }
|
void setTotalDisc(std::optional<int> totalDisc) { _totalDisc = totalDisc; }
|
||||||
void setPrimaryType(std::optional<ReleaseTypePrimary> type) { _primaryType = type; }
|
void setPrimaryType(std::optional<ReleaseTypePrimary> type) { _primaryType = type; }
|
||||||
void setSecondaryTypes(EnumSet<ReleaseTypeSecondary> types) { _secondaryTypes = types; }
|
void setSecondaryTypes(EnumSet<ReleaseTypeSecondary> types) { _secondaryTypes = types; }
|
||||||
|
void setArtistDisplayName(std::string_view name) { _artistDisplayName = name; }
|
||||||
|
|
||||||
// Get the artists of this release
|
// Get the artists of this release
|
||||||
std::vector<ObjectPtr<Artist>> getArtists(TrackArtistLinkType type = TrackArtistLinkType::Artist) const;
|
std::vector<ObjectPtr<Artist>> getArtists(TrackArtistLinkType type = TrackArtistLinkType::Artist) const;
|
||||||
@@ -137,7 +139,7 @@ namespace Database
|
|||||||
Wt::Dbo::field(a, _totalDisc, "total_disc");
|
Wt::Dbo::field(a, _totalDisc, "total_disc");
|
||||||
Wt::Dbo::field(a, _primaryType, "primary_type");
|
Wt::Dbo::field(a, _primaryType, "primary_type");
|
||||||
Wt::Dbo::field(a, _secondaryTypes, "secondary_types");
|
Wt::Dbo::field(a, _secondaryTypes, "secondary_types");
|
||||||
|
Wt::Dbo::field(a, _artistDisplayName, "artist_display_name");
|
||||||
Wt::Dbo::hasMany(a, _tracks, Wt::Dbo::ManyToOne, "release");
|
Wt::Dbo::hasMany(a, _tracks, Wt::Dbo::ManyToOne, "release");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,6 +157,7 @@ namespace Database
|
|||||||
std::optional<int> _totalDisc{};
|
std::optional<int> _totalDisc{};
|
||||||
std::optional<ReleaseTypePrimary> _primaryType;
|
std::optional<ReleaseTypePrimary> _primaryType;
|
||||||
EnumSet<ReleaseTypeSecondary> _secondaryTypes;
|
EnumSet<ReleaseTypeSecondary> _secondaryTypes;
|
||||||
|
std::string _artistDisplayName;
|
||||||
|
|
||||||
Wt::Dbo::collection<Wt::Dbo::ptr<Track>> _tracks; // Tracks in the release
|
Wt::Dbo::collection<Wt::Dbo::ptr<Track>> _tracks; // Tracks in the release
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -138,6 +138,7 @@ class Track final : public Object<Track, TrackId>
|
|||||||
void setCopyrightURL(const std::string& copyrightURL) { _copyrightURL = std::string(copyrightURL, 0, _maxCopyrightURLLength); }
|
void setCopyrightURL(const std::string& copyrightURL) { _copyrightURL = std::string(copyrightURL, 0, _maxCopyrightURLLength); }
|
||||||
void setTrackReplayGain(std::optional<float> replayGain) { _trackReplayGain = replayGain; }
|
void setTrackReplayGain(std::optional<float> replayGain) { _trackReplayGain = replayGain; }
|
||||||
void setReleaseReplayGain(std::optional<float> replayGain) { _releaseReplayGain = replayGain; } // may be by disc!
|
void setReleaseReplayGain(std::optional<float> replayGain) { _releaseReplayGain = replayGain; } // may be by disc!
|
||||||
|
void setArtistDisplayName(std::string_view name) { _artistDisplayName = name; }
|
||||||
void clearArtistLinks();
|
void clearArtistLinks();
|
||||||
void addArtistLink(const ObjectPtr<TrackArtistLink>& artistLink);
|
void addArtistLink(const ObjectPtr<TrackArtistLink>& artistLink);
|
||||||
void setRelease(ObjectPtr<Release> release) { _release = getDboPtr(release); }
|
void setRelease(ObjectPtr<Release> release) { _release = getDboPtr(release); }
|
||||||
@@ -163,6 +164,7 @@ class Track final : public Object<Track, TrackId>
|
|||||||
std::optional<std::string> getCopyrightURL() const;
|
std::optional<std::string> getCopyrightURL() const;
|
||||||
std::optional<float> getTrackReplayGain() const { return _trackReplayGain; }
|
std::optional<float> getTrackReplayGain() const { return _trackReplayGain; }
|
||||||
std::optional<float> getReleaseReplayGain() const { return _releaseReplayGain; }
|
std::optional<float> getReleaseReplayGain() const { return _releaseReplayGain; }
|
||||||
|
std::string_view getArtistDisplayName() const { return _artistDisplayName; }
|
||||||
// no artistLinkTypes means get all
|
// no artistLinkTypes means get all
|
||||||
std::vector<ObjectPtr<Artist>> getArtists(EnumSet<TrackArtistLinkType> artistLinkTypes) const; // no type means all
|
std::vector<ObjectPtr<Artist>> getArtists(EnumSet<TrackArtistLinkType> artistLinkTypes) const; // no type means all
|
||||||
std::vector<ArtistId> getArtistIds(EnumSet<TrackArtistLinkType> artistLinkTypes) const; // no type means all
|
std::vector<ArtistId> getArtistIds(EnumSet<TrackArtistLinkType> artistLinkTypes) const; // no type means all
|
||||||
@@ -195,6 +197,7 @@ class Track final : public Object<Track, TrackId>
|
|||||||
Wt::Dbo::field(a, _copyrightURL, "copyright_url");
|
Wt::Dbo::field(a, _copyrightURL, "copyright_url");
|
||||||
Wt::Dbo::field(a, _trackReplayGain, "track_replay_gain");
|
Wt::Dbo::field(a, _trackReplayGain, "track_replay_gain");
|
||||||
Wt::Dbo::field(a, _releaseReplayGain, "release_replay_gain"); // here in Track since Release does not have concept of "disc" (yet?)
|
Wt::Dbo::field(a, _releaseReplayGain, "release_replay_gain"); // here in Track since Release does not have concept of "disc" (yet?)
|
||||||
|
Wt::Dbo::field(a, _artistDisplayName, "artist_display_name");
|
||||||
Wt::Dbo::belongsTo(a, _release, "release", Wt::Dbo::OnDeleteCascade);
|
Wt::Dbo::belongsTo(a, _release, "release", Wt::Dbo::OnDeleteCascade);
|
||||||
Wt::Dbo::hasMany(a, _trackArtistLinks, Wt::Dbo::ManyToOne, "track");
|
Wt::Dbo::hasMany(a, _trackArtistLinks, Wt::Dbo::ManyToOne, "track");
|
||||||
Wt::Dbo::hasMany(a, _clusters, Wt::Dbo::ManyToMany, "track_cluster", "", Wt::Dbo::OnDeleteCascade);
|
Wt::Dbo::hasMany(a, _clusters, Wt::Dbo::ManyToMany, "track_cluster", "", Wt::Dbo::OnDeleteCascade);
|
||||||
@@ -228,6 +231,7 @@ class Track final : public Object<Track, TrackId>
|
|||||||
std::string _copyrightURL;
|
std::string _copyrightURL;
|
||||||
std::optional<float> _trackReplayGain;
|
std::optional<float> _trackReplayGain;
|
||||||
std::optional<float> _releaseReplayGain;
|
std::optional<float> _releaseReplayGain;
|
||||||
|
std::string _artistDisplayName;
|
||||||
|
|
||||||
Wt::Dbo::ptr<Release> _release;
|
Wt::Dbo::ptr<Release> _release;
|
||||||
Wt::Dbo::collection<Wt::Dbo::ptr<TrackArtistLink>> _trackArtistLinks;
|
Wt::Dbo::collection<Wt::Dbo::ptr<TrackArtistLink>> _trackArtistLinks;
|
||||||
|
|||||||
@@ -189,9 +189,11 @@ namespace
|
|||||||
if (release->getPrimaryType() != primaryType)
|
if (release->getPrimaryType() != primaryType)
|
||||||
release.modify()->setPrimaryType(primaryType);
|
release.modify()->setPrimaryType(primaryType);
|
||||||
}
|
}
|
||||||
const EnumSet<ReleaseTypeSecondary> secondaryTypes {convertReleaseTypesSecondary(releaseInfo.secondaryTypes)};
|
const EnumSet<ReleaseTypeSecondary> secondaryTypes{ convertReleaseTypesSecondary(releaseInfo.secondaryTypes) };
|
||||||
if (release->getSecondaryTypes() != secondaryTypes)
|
if (release->getSecondaryTypes() != secondaryTypes)
|
||||||
release.modify()->setSecondaryTypes(secondaryTypes);
|
release.modify()->setSecondaryTypes(secondaryTypes);
|
||||||
|
if (release->getArtistDisplayName() != releaseInfo.artistDisplayName)
|
||||||
|
release.modify()->setArtistDisplayName(releaseInfo.artistDisplayName);
|
||||||
}
|
}
|
||||||
|
|
||||||
Release::pointer
|
Release::pointer
|
||||||
@@ -525,5 +527,6 @@ namespace Scanner
|
|||||||
track.modify()->setCopyright(trackInfo->copyright);
|
track.modify()->setCopyright(trackInfo->copyright);
|
||||||
track.modify()->setCopyrightURL(trackInfo->copyrightURL);
|
track.modify()->setCopyrightURL(trackInfo->copyrightURL);
|
||||||
track.modify()->setTrackReplayGain(trackInfo->replayGain);
|
track.modify()->setTrackReplayGain(trackInfo->replayGain);
|
||||||
|
track.modify()->setArtistDisplayName(trackInfo->artistDisplayName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -186,6 +186,8 @@ namespace API::Subsonic
|
|||||||
for (const Artist::pointer& artist : release->getReleaseArtists())
|
for (const Artist::pointer& artist : release->getReleaseArtists())
|
||||||
albumNode.addArrayChild("artists", createArtistNode(artist));
|
albumNode.addArrayChild("artists", createArtistNode(artist));
|
||||||
|
|
||||||
|
albumNode.setAttribute("displayArtist", release->getArtistDisplayName());
|
||||||
|
|
||||||
{
|
{
|
||||||
const Wt::WDate originalReleaseDate{ release->getOriginalReleaseDate() };
|
const Wt::WDate originalReleaseDate{ release->getOriginalReleaseDate() };
|
||||||
albumNode.setAttribute("originalReleaseDate", originalReleaseDate.isValid() ? StringUtils::toISO8601String(originalReleaseDate) : "");
|
albumNode.setAttribute("originalReleaseDate", originalReleaseDate.isValid() ? StringUtils::toISO8601String(originalReleaseDate) : "");
|
||||||
|
|||||||
@@ -134,11 +134,12 @@ namespace API::Subsonic
|
|||||||
trackResponse.setAttribute("artistId", idToString(artists.front()->getId()));
|
trackResponse.setAttribute("artistId", idToString(artists.front()->getId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (track->getRelease())
|
Release::pointer release{ track->getRelease() };
|
||||||
|
if (release)
|
||||||
{
|
{
|
||||||
trackResponse.setAttribute("album", track->getRelease()->getName());
|
trackResponse.setAttribute("album", release->getName());
|
||||||
trackResponse.setAttribute("albumId", idToString(track->getRelease()->getId()));
|
trackResponse.setAttribute("albumId", idToString(release->getId()));
|
||||||
trackResponse.setAttribute("parent", idToString(track->getRelease()->getId()));
|
trackResponse.setAttribute("parent", idToString(release->getId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
trackResponse.setAttribute("duration", std::chrono::duration_cast<std::chrono::seconds>(track->getDuration()).count());
|
trackResponse.setAttribute("duration", std::chrono::duration_cast<std::chrono::seconds>(track->getDuration()).count());
|
||||||
@@ -195,7 +196,11 @@ namespace API::Subsonic
|
|||||||
} };
|
} };
|
||||||
|
|
||||||
addArtistLinks("artists", TrackArtistLinkType::Artist);
|
addArtistLinks("artists", TrackArtistLinkType::Artist);
|
||||||
|
trackResponse.setAttribute("displayArtist", track->getArtistDisplayName());
|
||||||
|
|
||||||
addArtistLinks("albumartists", TrackArtistLinkType::ReleaseArtist);
|
addArtistLinks("albumartists", TrackArtistLinkType::ReleaseArtist);
|
||||||
|
if (release)
|
||||||
|
trackResponse.setAttribute("displayAlbumArtist", release->getArtistDisplayName());
|
||||||
|
|
||||||
auto addClusters{ [&](std::string_view field, std::string_view clusterTypeName)
|
auto addClusters{ [&](std::string_view field, std::string_view clusterTypeName)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -535,8 +535,8 @@ PlayQueue::addEntry(const Database::TrackListEntry::pointer& tracklistEntry)
|
|||||||
if (!artists.empty())
|
if (!artists.empty())
|
||||||
{
|
{
|
||||||
entry->setCondition("if-has-artists", true);
|
entry->setCondition("if-has-artists", true);
|
||||||
entry->bindWidget("artists", Utils::createArtistContainer(artists));
|
entry->bindWidget("artists", Utils::createArtistAnchorList(artists));
|
||||||
entry->bindWidget("artists-md", Utils::createArtistContainer(artists));
|
entry->bindWidget("artists-md", Utils::createArtistAnchorList(artists));
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto release {track->getRelease()};
|
const auto release {track->getRelease()};
|
||||||
|
|||||||
+229
-164
@@ -38,204 +38,269 @@
|
|||||||
namespace UserInterface::Utils
|
namespace UserInterface::Utils
|
||||||
{
|
{
|
||||||
|
|
||||||
std::string
|
std::string durationToString(std::chrono::milliseconds msDuration)
|
||||||
durationToString(std::chrono::milliseconds msDuration)
|
{
|
||||||
{
|
const std::chrono::seconds duration{ std::chrono::duration_cast<std::chrono::seconds>(msDuration) };
|
||||||
const std::chrono::seconds duration {std::chrono::duration_cast<std::chrono::seconds>(msDuration)};
|
|
||||||
|
|
||||||
std::ostringstream oss;
|
std::ostringstream oss;
|
||||||
|
|
||||||
if (duration.count() >= 3600)
|
if (duration.count() >= 3600)
|
||||||
{
|
{
|
||||||
oss << (duration.count() / 3600)
|
oss << (duration.count() / 3600)
|
||||||
<< ":"
|
<< ":"
|
||||||
<< std::setfill('0') << std::setw(2) << (duration.count() % 3600) / 60
|
<< std::setfill('0') << std::setw(2) << (duration.count() % 3600) / 60
|
||||||
<< ":"
|
<< ":"
|
||||||
<< std::setfill('0') << std::setw(2) << duration.count() % 60;
|
<< std::setfill('0') << std::setw(2) << duration.count() % 60;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
oss << (duration.count() / 60)
|
oss << (duration.count() / 60)
|
||||||
<< ":"
|
<< ":"
|
||||||
<< std::setfill('0') << std::setw(2) << duration.count() % 60;
|
<< std::setfill('0') << std::setw(2) << duration.count() % 60;
|
||||||
}
|
}
|
||||||
|
|
||||||
return oss.str();
|
return oss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::unique_ptr<Wt::WImage>
|
std::unique_ptr<Wt::WImage> createCover(Database::ReleaseId releaseId, CoverResource::Size size)
|
||||||
createCover(Database::ReleaseId releaseId, CoverResource::Size size)
|
{
|
||||||
{
|
auto cover{ std::make_unique<Wt::WImage>() };
|
||||||
auto cover {std::make_unique<Wt::WImage>()};
|
cover->setImageLink(LmsApp->getCoverResource()->getReleaseUrl(releaseId, size));
|
||||||
cover->setImageLink(LmsApp->getCoverResource()->getReleaseUrl(releaseId, size));
|
cover->setStyleClass("Lms-cover img-fluid"); // HACK
|
||||||
cover->setStyleClass("Lms-cover img-fluid"); // HACK
|
cover->setAttributeValue("onload", LmsApp->javaScriptClass() + ".onLoadCover(this)"); // HACK
|
||||||
cover->setAttributeValue("onload", LmsApp->javaScriptClass() + ".onLoadCover(this)"); // HACK
|
return cover;
|
||||||
return cover;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<Wt::WImage>
|
std::unique_ptr<Wt::WImage> createCover(Database::TrackId trackId, CoverResource::Size size)
|
||||||
createCover(Database::TrackId trackId, CoverResource::Size size)
|
{
|
||||||
{
|
auto cover{ std::make_unique<Wt::WImage>() };
|
||||||
auto cover {std::make_unique<Wt::WImage>()};
|
cover->setImageLink(LmsApp->getCoverResource()->getTrackUrl(trackId, size));
|
||||||
cover->setImageLink(LmsApp->getCoverResource()->getTrackUrl(trackId, size));
|
cover->setStyleClass("Lms-cover img-fluid"); // HACK
|
||||||
cover->setStyleClass("Lms-cover img-fluid"); // HACK
|
cover->setAttributeValue("onload", LmsApp->javaScriptClass() + ".onLoadCover(this)"); // HACK
|
||||||
cover->setAttributeValue("onload", LmsApp->javaScriptClass() + ".onLoadCover(this)"); // HACK
|
return cover;
|
||||||
return cover;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<Wt::WInteractWidget>
|
std::unique_ptr<Wt::WInteractWidget> createCluster(Database::ClusterId clusterId, bool canDelete)
|
||||||
createCluster(Database::ClusterId clusterId, bool canDelete)
|
{
|
||||||
{
|
auto transaction{ LmsApp->getDbSession().createSharedTransaction() };
|
||||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
|
||||||
|
|
||||||
const Database::Cluster::pointer cluster {Database::Cluster::find(LmsApp->getDbSession(), clusterId)};
|
const Database::Cluster::pointer cluster{ Database::Cluster::find(LmsApp->getDbSession(), clusterId) };
|
||||||
if (!cluster)
|
if (!cluster)
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
auto getStyleClass {[](const Database::Cluster::pointer& cluster) -> const char*
|
auto getStyleClass{ [](const Database::Cluster::pointer& cluster) -> const char*
|
||||||
{
|
{
|
||||||
switch (cluster->getType()->getId().getValue() % 8)
|
switch (cluster->getType()->getId().getValue() % 8)
|
||||||
{
|
{
|
||||||
case 0: return "bg-primary";
|
case 0: return "bg-primary";
|
||||||
case 1: return "bg-secondary";
|
case 1: return "bg-secondary";
|
||||||
case 2: return "bg-success";
|
case 2: return "bg-success";
|
||||||
case 3: return "bg-danger";
|
case 3: return "bg-danger";
|
||||||
case 4: return "bg-warning text-dark";
|
case 4: return "bg-warning text-dark";
|
||||||
case 5: return "bg-info text-dark";
|
case 5: return "bg-info text-dark";
|
||||||
case 6: return "bg-light text-dark";
|
case 6: return "bg-light text-dark";
|
||||||
case 7: return "bg-dark";
|
case 7: return "bg-dark";
|
||||||
}
|
}
|
||||||
|
|
||||||
return "bg-primary";
|
return "bg-primary";
|
||||||
}};
|
} };
|
||||||
|
|
||||||
const std::string styleClass {getStyleClass(cluster)};
|
const std::string styleClass{ getStyleClass(cluster) };
|
||||||
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)};
|
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->setStyleClass("Lms-badge-cluster badge me-1 " + styleClass); // HACK
|
res->setStyleClass("Lms-badge-cluster badge me-1 " + styleClass); // HACK
|
||||||
res->setToolTip(cluster->getType()->getName(), Wt::TextFormat::Plain);
|
res->setToolTip(cluster->getType()->getName(), Wt::TextFormat::Plain);
|
||||||
res->setInline(true);
|
res->setInline(true);
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Wt::WContainerWidget>
|
std::unique_ptr<Wt::WContainerWidget> createArtistAnchorList(const std::vector<Database::ArtistId>& artistIds, std::string_view cssAnchorClass)
|
||||||
createArtistContainer(const std::vector<Database::ArtistId>& artistIds)
|
{
|
||||||
{
|
using namespace Database;
|
||||||
using namespace Database;
|
|
||||||
|
|
||||||
std::unique_ptr<Wt::WContainerWidget> artistContainer {std::make_unique<Wt::WContainerWidget>()};
|
std::unique_ptr<Wt::WContainerWidget> artistContainer{ std::make_unique<Wt::WContainerWidget>() };
|
||||||
|
|
||||||
bool firstArtist {true};
|
bool firstArtist{ true };
|
||||||
|
|
||||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
auto transaction{ LmsApp->getDbSession().createSharedTransaction() };
|
||||||
for (const ArtistId artistId : artistIds)
|
for (const ArtistId artistId : artistIds)
|
||||||
{
|
{
|
||||||
const Artist::pointer artist {Artist::find(LmsApp->getDbSession(), artistId)};
|
const Artist::pointer artist{ Artist::find(LmsApp->getDbSession(), artistId) };
|
||||||
if (!artist)
|
if (!artist)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (!firstArtist)
|
if (!firstArtist)
|
||||||
artistContainer->addNew<Wt::WText>(" · ");
|
artistContainer->addNew<Wt::WText>(" · ");
|
||||||
|
|
||||||
auto anchor {createArtistAnchor(artist)};
|
auto anchor{ createArtistAnchor(artist) };
|
||||||
anchor->addStyleClass("link-success text-decoration-none"); // hack
|
anchor->addStyleClass("text-decoration-none"); // hack
|
||||||
artistContainer->addWidget(std::move(anchor));
|
anchor->addStyleClass(std::string{ cssAnchorClass });
|
||||||
firstArtist = false;
|
artistContainer->addWidget(std::move(anchor));
|
||||||
}
|
firstArtist = false;
|
||||||
|
}
|
||||||
|
|
||||||
return artistContainer;
|
return artistContainer;
|
||||||
}
|
}
|
||||||
|
|
||||||
Wt::WLink
|
std::unique_ptr<Wt::WContainerWidget> createArtistDisplayNameWithAnchors(std::string_view displayName, const std::vector<Database::ArtistId>& artistIds, std::string_view cssAnchorClass)
|
||||||
createArtistLink(Database::Artist::pointer artist)
|
{
|
||||||
{
|
using namespace Database;
|
||||||
if (const auto mbid {artist->getMBID()})
|
|
||||||
return Wt::WLink {Wt::LinkType::InternalPath, "/artist/mbid/" + std::string {mbid->getAsString()}};
|
|
||||||
else
|
|
||||||
return Wt::WLink {Wt::LinkType::InternalPath, "/artist/" + artist->getId().toString()};
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<Wt::WAnchor>
|
std::size_t matchCount{};
|
||||||
createArtistAnchor(Database::Artist::pointer artist, bool setText)
|
std::string_view::size_type currentOffset{};
|
||||||
{
|
|
||||||
auto res = std::make_unique<Wt::WAnchor>(createArtistLink(artist));
|
|
||||||
|
|
||||||
if (setText)
|
auto result{ std::make_unique<Wt::WContainerWidget>() };
|
||||||
{
|
auto transaction{ LmsApp->getDbSession().createSharedTransaction() };
|
||||||
res->setTextFormat(Wt::TextFormat::Plain);
|
|
||||||
res->setText(Wt::WString::fromUTF8(artist->getName()));
|
|
||||||
res->setToolTip(Wt::WString::fromUTF8(artist->getName()), Wt::TextFormat::Plain);
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
// consider order is guaranteed + we will likely succeed
|
||||||
}
|
for (const ArtistId artistId : artistIds)
|
||||||
|
{
|
||||||
|
const Artist::pointer artist{ Artist::find(LmsApp->getDbSession(), artistId) };
|
||||||
|
if (!artist)
|
||||||
|
break;
|
||||||
|
|
||||||
Wt::WLink
|
const auto pos{ displayName.find(artist->getName(), currentOffset) };
|
||||||
createReleaseLink(Database::Release::pointer release)
|
if (pos == std::string_view::npos)
|
||||||
{
|
break;
|
||||||
if (const auto mbid {release->getMBID()})
|
|
||||||
return Wt::WLink {Wt::LinkType::InternalPath, "/release/mbid/" + std::string {mbid->getAsString()}};
|
|
||||||
else
|
|
||||||
return Wt::WLink {Wt::LinkType::InternalPath, "/release/" + release->getId().toString()};
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<Wt::WAnchor>
|
assert(pos >= currentOffset);
|
||||||
createReleaseAnchor(Database::Release::pointer release, bool setText)
|
if (pos != currentOffset)
|
||||||
{
|
result->addNew<Wt::WText>(std::string{ displayName.substr(currentOffset, pos - currentOffset) }, Wt::TextFormat::Plain);
|
||||||
auto res = std::make_unique<Wt::WAnchor>(createReleaseLink(release));
|
|
||||||
|
|
||||||
if (setText)
|
auto anchor{ createArtistAnchor(artist) };
|
||||||
{
|
anchor->addStyleClass("text-decoration-none"); // hack
|
||||||
res->setTextFormat(Wt::TextFormat::Plain);
|
anchor->addStyleClass(std::string{ cssAnchorClass }); // hack
|
||||||
res->setText(Wt::WString::fromUTF8(release->getName()));
|
result->addWidget(std::move(anchor));
|
||||||
res->setToolTip(Wt::WString::fromUTF8(release->getName()), Wt::TextFormat::Plain);
|
currentOffset = pos + artist->getName().size();
|
||||||
}
|
matchCount += 1;
|
||||||
|
}
|
||||||
|
|
||||||
return res;
|
if (matchCount != artistIds.size())
|
||||||
}
|
return createArtistAnchorList(artistIds, cssAnchorClass);
|
||||||
|
|
||||||
std::unique_ptr<Wt::WAnchor>
|
return result;
|
||||||
createTrackListAnchor(Database::TrackList::pointer trackList, bool setText)
|
}
|
||||||
{
|
|
||||||
Wt::WLink link {Wt::LinkType::InternalPath, "/tracklist/" + trackList->getId().toString()};
|
|
||||||
auto res {std::make_unique<Wt::WAnchor>(link)};
|
|
||||||
|
|
||||||
if (setText)
|
std::unique_ptr<Wt::WContainerWidget> createArtistsAnchorsForRelease(Database::ObjectPtr<Database::Release> release, Database::ArtistId omitIfMatchThisArtist, std::string_view cssAnchorClass)
|
||||||
{
|
{
|
||||||
const Wt::WString name {Wt::WString::fromUTF8(std::string {trackList->getName()})};
|
using namespace Database;
|
||||||
res->setTextFormat(Wt::TextFormat::Plain);
|
|
||||||
res->setText(name);
|
|
||||||
res->setToolTip(name, Wt::TextFormat::Plain);
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
Artist::FindParameters params;
|
||||||
}
|
params.setRelease(release->getId());
|
||||||
|
params.setLinkType(TrackArtistLinkType::ReleaseArtist);
|
||||||
|
|
||||||
std::unique_ptr<Wt::WContainerWidget>
|
if (const auto releaseArtists{ Artist::find(LmsApp->getDbSession(), params) }; !releaseArtists.results.empty())
|
||||||
createClustersForTrack(Database::Track::pointer track, Filters& filters)
|
{
|
||||||
{
|
if (releaseArtists.results.size() == 1 && releaseArtists.results.front() == omitIfMatchThisArtist)
|
||||||
using namespace Database;
|
return {};
|
||||||
|
|
||||||
std::unique_ptr<Wt::WContainerWidget> clusterContainer {std::make_unique<Wt::WContainerWidget>()};
|
return Utils::createArtistDisplayNameWithAnchors(release->getArtistDisplayName(), releaseArtists.results, cssAnchorClass);
|
||||||
|
}
|
||||||
|
|
||||||
const auto clusterTypes {ScanSettings::get(LmsApp->getDbSession())->getClusterTypes()};
|
params.setLinkType(TrackArtistLinkType::Artist);
|
||||||
const auto clusterGroups {track->getClusterGroups(clusterTypes, 3)};
|
const auto artists{ Artist::find(LmsApp->getDbSession(), params) };
|
||||||
|
if (artists.results.size() == 1)
|
||||||
|
{
|
||||||
|
if (artists.results.front() == omitIfMatchThisArtist)
|
||||||
|
return {};
|
||||||
|
|
||||||
for (const auto& clusters : clusterGroups)
|
return Utils::createArtistAnchorList({ artists.results.front() }, cssAnchorClass);
|
||||||
{
|
}
|
||||||
for (const Cluster::pointer& cluster : clusters)
|
|
||||||
{
|
|
||||||
const ClusterId clusterId {cluster->getId()};
|
|
||||||
Wt::WInteractWidget* entry {clusterContainer->addWidget(Utils::createCluster(clusterId))};
|
|
||||||
entry->clicked().connect([&filters, clusterId]
|
|
||||||
{
|
|
||||||
filters.add(clusterId);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return clusterContainer;
|
if (artists.results.size() > 1)
|
||||||
}
|
{
|
||||||
|
auto res{ std::make_unique<Wt::WContainerWidget>() };
|
||||||
|
res->addNew<Wt::WText>(Wt::WString::tr("Lms.Explore.various-artists"));
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
Wt::WLink createArtistLink(Database::Artist::pointer artist)
|
||||||
|
{
|
||||||
|
if (const auto mbid{ artist->getMBID() })
|
||||||
|
return Wt::WLink{ Wt::LinkType::InternalPath, "/artist/mbid/" + std::string {mbid->getAsString()} };
|
||||||
|
else
|
||||||
|
return Wt::WLink{ Wt::LinkType::InternalPath, "/artist/" + artist->getId().toString() };
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Wt::WAnchor> createArtistAnchor(Database::Artist::pointer artist, bool setText)
|
||||||
|
{
|
||||||
|
auto res = std::make_unique<Wt::WAnchor>(createArtistLink(artist));
|
||||||
|
|
||||||
|
if (setText)
|
||||||
|
{
|
||||||
|
res->setTextFormat(Wt::TextFormat::Plain);
|
||||||
|
res->setText(Wt::WString::fromUTF8(artist->getName()));
|
||||||
|
res->setToolTip(Wt::WString::fromUTF8(artist->getName()), Wt::TextFormat::Plain);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
Wt::WLink createReleaseLink(Database::Release::pointer release)
|
||||||
|
{
|
||||||
|
if (const auto mbid{ release->getMBID() })
|
||||||
|
return Wt::WLink{ Wt::LinkType::InternalPath, "/release/mbid/" + std::string {mbid->getAsString()} };
|
||||||
|
else
|
||||||
|
return Wt::WLink{ Wt::LinkType::InternalPath, "/release/" + release->getId().toString() };
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Wt::WAnchor> createReleaseAnchor(Database::Release::pointer release, bool setText)
|
||||||
|
{
|
||||||
|
auto res = std::make_unique<Wt::WAnchor>(createReleaseLink(release));
|
||||||
|
|
||||||
|
if (setText)
|
||||||
|
{
|
||||||
|
res->setTextFormat(Wt::TextFormat::Plain);
|
||||||
|
res->setText(Wt::WString::fromUTF8(release->getName()));
|
||||||
|
res->setToolTip(Wt::WString::fromUTF8(release->getName()), Wt::TextFormat::Plain);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Wt::WAnchor> createTrackListAnchor(Database::TrackList::pointer trackList, bool setText)
|
||||||
|
{
|
||||||
|
Wt::WLink link{ Wt::LinkType::InternalPath, "/tracklist/" + trackList->getId().toString() };
|
||||||
|
auto res{ std::make_unique<Wt::WAnchor>(link) };
|
||||||
|
|
||||||
|
if (setText)
|
||||||
|
{
|
||||||
|
const Wt::WString name{ Wt::WString::fromUTF8(std::string {trackList->getName()}) };
|
||||||
|
res->setTextFormat(Wt::TextFormat::Plain);
|
||||||
|
res->setText(name);
|
||||||
|
res->setToolTip(name, Wt::TextFormat::Plain);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Wt::WContainerWidget> createClustersForTrack(Database::Track::pointer track, Filters& filters)
|
||||||
|
{
|
||||||
|
using namespace Database;
|
||||||
|
|
||||||
|
std::unique_ptr<Wt::WContainerWidget> clusterContainer{ std::make_unique<Wt::WContainerWidget>() };
|
||||||
|
|
||||||
|
const auto clusterTypes{ ScanSettings::get(LmsApp->getDbSession())->getClusterTypes() };
|
||||||
|
const auto clusterGroups{ track->getClusterGroups(clusterTypes, 3) };
|
||||||
|
|
||||||
|
for (const auto& clusters : clusterGroups)
|
||||||
|
{
|
||||||
|
for (const Cluster::pointer& cluster : clusters)
|
||||||
|
{
|
||||||
|
const ClusterId clusterId{ cluster->getId() };
|
||||||
|
Wt::WInteractWidget* entry{ clusterContainer->addWidget(Utils::createCluster(clusterId)) };
|
||||||
|
entry->clicked().connect([&filters, clusterId]
|
||||||
|
{
|
||||||
|
filters.add(clusterId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return clusterContainer;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
#include <Wt/WContainerWidget.h>
|
#include <Wt/WContainerWidget.h>
|
||||||
#include <Wt/WImage.h>
|
#include <Wt/WImage.h>
|
||||||
@@ -57,7 +58,9 @@ namespace UserInterface::Utils
|
|||||||
|
|
||||||
std::unique_ptr<Wt::WInteractWidget> createCluster(Database::ClusterId clusterId, bool canDelete = false);
|
std::unique_ptr<Wt::WInteractWidget> createCluster(Database::ClusterId clusterId, bool canDelete = false);
|
||||||
|
|
||||||
std::unique_ptr<Wt::WContainerWidget> createArtistContainer(const std::vector<Database::ArtistId>& artists);
|
std::unique_ptr<Wt::WContainerWidget> createArtistAnchorList(const std::vector<Database::ArtistId>& artistIds, std::string_view cssAnchorClass = "link-success");
|
||||||
|
std::unique_ptr<Wt::WContainerWidget> createArtistDisplayNameWithAnchors(std::string_view displayName, const std::vector<Database::ArtistId>& artistIds, std::string_view cssAnchorClass = "link-success");
|
||||||
|
std::unique_ptr<Wt::WContainerWidget> createArtistsAnchorsForRelease(Database::ObjectPtr<Database::Release> release, Database::ArtistId omitIfMatchThisArtist = {}, std::string_view cssAnchorClass = "link-success");
|
||||||
|
|
||||||
Wt::WLink createArtistLink(Database::ObjectPtr<Database::Artist> artist);
|
Wt::WLink createArtistLink(Database::ObjectPtr<Database::Artist> artist);
|
||||||
std::unique_ptr<Wt::WAnchor> createArtistAnchor(Database::ObjectPtr<Database::Artist> artist, bool setText = true);
|
std::unique_ptr<Wt::WAnchor> createArtistAnchor(Database::ObjectPtr<Database::Artist> artist, bool setText = true);
|
||||||
|
|||||||
@@ -32,122 +32,110 @@ using namespace Database;
|
|||||||
|
|
||||||
namespace UserInterface::ReleaseListHelpers
|
namespace UserInterface::ReleaseListHelpers
|
||||||
{
|
{
|
||||||
static
|
namespace
|
||||||
std::unique_ptr<Wt::WTemplate>
|
{
|
||||||
createEntryInternal(const Release::pointer& release, const std::string& templateKey, const Artist::pointer& artist, const bool showYear)
|
std::unique_ptr<Wt::WTemplate> createEntryInternal(const Release::pointer& release, const std::string& templateKey, const Artist::pointer& artist, const bool showYear)
|
||||||
{
|
{
|
||||||
auto entry {std::make_unique<Wt::WTemplate>(Wt::WString::tr(templateKey))};
|
auto entry{ std::make_unique<Wt::WTemplate>(Wt::WString::tr(templateKey)) };
|
||||||
|
|
||||||
entry->bindWidget("release-name", Utils::createReleaseAnchor(release));
|
entry->bindWidget("release-name", Utils::createReleaseAnchor(release));
|
||||||
entry->addFunction("tr", &Wt::WTemplate::Functions::tr);
|
entry->addFunction("tr", &Wt::WTemplate::Functions::tr);
|
||||||
|
|
||||||
{
|
{
|
||||||
Wt::WAnchor* anchor {entry->bindWidget("cover", Utils::createReleaseAnchor(release, false))};
|
Wt::WAnchor* anchor{ entry->bindWidget("cover", Utils::createReleaseAnchor(release, false)) };
|
||||||
auto cover {Utils::createCover(release->getId(), CoverResource::Size::Large)};
|
auto cover{ Utils::createCover(release->getId(), CoverResource::Size::Large) };
|
||||||
cover->addStyleClass("Lms-cover-release Lms-cover-anchor");
|
cover->addStyleClass("Lms-cover-release Lms-cover-anchor");
|
||||||
anchor->setImage(std::move(cover));
|
anchor->setImage(std::move(cover));
|
||||||
}
|
}
|
||||||
|
|
||||||
auto artists {release->getReleaseArtists()};
|
auto artistAnchors{ Utils::createArtistsAnchorsForRelease(release, artist ? artist->getId() : ArtistId{}, "link-secondary") };
|
||||||
if (artists.empty())
|
if (artistAnchors)
|
||||||
artists = release->getArtists();
|
{
|
||||||
|
entry->setCondition("if-has-artist", true);
|
||||||
|
entry->bindWidget("artist-name", std::move(artistAnchors));
|
||||||
|
}
|
||||||
|
|
||||||
const bool isSameArtist {(std::find(std::cbegin(artists), std::cend(artists), artist) != artists.end())};
|
if (showYear)
|
||||||
|
{
|
||||||
|
Wt::WString year{ ReleaseHelpers::buildReleaseYearString(release->getReleaseDate(), release->getOriginalReleaseDate()) };
|
||||||
|
if (!year.empty())
|
||||||
|
{
|
||||||
|
entry->setCondition("if-has-year", true);
|
||||||
|
entry->bindString("year", year, Wt::TextFormat::Plain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (artists.size() > 1)
|
return entry;
|
||||||
{
|
}
|
||||||
entry->setCondition("if-has-various-artists", true);
|
}
|
||||||
}
|
|
||||||
else if (artists.size() == 1 && !isSameArtist)
|
|
||||||
{
|
|
||||||
entry->setCondition("if-has-artist", true);
|
|
||||||
entry->bindWidget("artist-name", Utils::createArtistAnchor(artists.front()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showYear)
|
std::unique_ptr<Wt::WTemplate> createEntry(const Release::pointer& release, const Artist::pointer& artist, bool showYear)
|
||||||
{
|
{
|
||||||
Wt::WString year {ReleaseHelpers::buildReleaseYearString(release->getReleaseDate(), release->getOriginalReleaseDate())};
|
return createEntryInternal(release, "Lms.Explore.Releases.template.entry-grid", artist, showYear);
|
||||||
if (!year.empty())
|
}
|
||||||
{
|
|
||||||
entry->setCondition("if-has-year", true);
|
|
||||||
entry->bindString("year", year, Wt::TextFormat::Plain);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return entry;
|
std::unique_ptr<Wt::WTemplate> createEntry(const Release::pointer& release)
|
||||||
}
|
{
|
||||||
|
return createEntry(release, Artist::pointer{}, false /*year*/);
|
||||||
|
}
|
||||||
|
|
||||||
std::unique_ptr<Wt::WTemplate>
|
std::unique_ptr<Wt::WTemplate> createEntryForArtist(const Database::Release::pointer& release, const Database::Artist::pointer& artist)
|
||||||
createEntry(const Release::pointer& release, const Artist::pointer& artist, bool showYear)
|
{
|
||||||
{
|
return createEntry(release, artist, true);
|
||||||
return createEntryInternal(release, "Lms.Explore.Releases.template.entry-grid", artist, showYear);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<Wt::WTemplate>
|
|
||||||
createEntry(const Release::pointer& release)
|
|
||||||
{
|
|
||||||
return createEntry(release, Artist::pointer {}, false /*year*/);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<Wt::WTemplate>
|
|
||||||
createEntryForArtist(const Database::Release::pointer& release, const Database::Artist::pointer& artist)
|
|
||||||
{
|
|
||||||
return createEntry(release, artist, true);
|
|
||||||
}
|
|
||||||
} // namespace UserInterface
|
} // namespace UserInterface
|
||||||
|
|
||||||
namespace UserInterface::ReleaseHelpers
|
namespace UserInterface::ReleaseHelpers
|
||||||
{
|
{
|
||||||
Wt::WString
|
Wt::WString buildReleaseTypeString(ReleaseTypePrimary primaryType, EnumSet<ReleaseTypeSecondary> secondaryTypes)
|
||||||
buildReleaseTypeString(ReleaseTypePrimary primaryType, EnumSet<ReleaseTypeSecondary> secondaryTypes)
|
{
|
||||||
{
|
Wt::WString res;
|
||||||
Wt::WString res;
|
|
||||||
|
|
||||||
switch (primaryType)
|
switch (primaryType)
|
||||||
{
|
{
|
||||||
case ReleaseTypePrimary::Album: res = Wt::WString::tr("Lms.Explore.Release.type-primary-album"); break;
|
case ReleaseTypePrimary::Album: res = Wt::WString::tr("Lms.Explore.Release.type-primary-album"); break;
|
||||||
case ReleaseTypePrimary::Broadcast: res = Wt::WString::tr("Lms.Explore.Release.type-primary-broadcast"); break;
|
case ReleaseTypePrimary::Broadcast: res = Wt::WString::tr("Lms.Explore.Release.type-primary-broadcast"); break;
|
||||||
case ReleaseTypePrimary::EP: res = Wt::WString::tr("Lms.Explore.Release.type-primary-ep"); break;
|
case ReleaseTypePrimary::EP: res = Wt::WString::tr("Lms.Explore.Release.type-primary-ep"); break;
|
||||||
case ReleaseTypePrimary::Single: res = Wt::WString::tr("Lms.Explore.Release.type-primary-single"); break;
|
case ReleaseTypePrimary::Single: res = Wt::WString::tr("Lms.Explore.Release.type-primary-single"); break;
|
||||||
case ReleaseTypePrimary::Other: res = Wt::WString::tr("Lms.Explore.Release.type-primary-other"); break;
|
case ReleaseTypePrimary::Other: res = Wt::WString::tr("Lms.Explore.Release.type-primary-other"); break;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (ReleaseTypeSecondary secondaryType : secondaryTypes)
|
for (ReleaseTypeSecondary secondaryType : secondaryTypes)
|
||||||
{
|
{
|
||||||
res += Wt::WString {" · "};
|
res += Wt::WString{ " · " };
|
||||||
|
|
||||||
switch (secondaryType)
|
switch (secondaryType)
|
||||||
{
|
{
|
||||||
case ReleaseTypeSecondary::Compilation: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-compilation"); break;
|
case ReleaseTypeSecondary::Compilation: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-compilation"); break;
|
||||||
case ReleaseTypeSecondary::Spokenword: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-spokenword"); break;
|
case ReleaseTypeSecondary::Spokenword: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-spokenword"); break;
|
||||||
case ReleaseTypeSecondary::Soundtrack: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-soundtrack"); break;
|
case ReleaseTypeSecondary::Soundtrack: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-soundtrack"); break;
|
||||||
case ReleaseTypeSecondary::Interview: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-interview"); break;
|
case ReleaseTypeSecondary::Interview: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-interview"); break;
|
||||||
case ReleaseTypeSecondary::Audiobook: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-audiobook"); break;
|
case ReleaseTypeSecondary::Audiobook: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-audiobook"); break;
|
||||||
case ReleaseTypeSecondary::AudioDrama: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-audiodrama"); break;
|
case ReleaseTypeSecondary::AudioDrama: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-audiodrama"); break;
|
||||||
case ReleaseTypeSecondary::Live: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-live"); break;
|
case ReleaseTypeSecondary::Live: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-live"); break;
|
||||||
case ReleaseTypeSecondary::Remix: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-remix"); break;
|
case ReleaseTypeSecondary::Remix: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-remix"); break;
|
||||||
case ReleaseTypeSecondary::DJMix: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-djmix"); break;
|
case ReleaseTypeSecondary::DJMix: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-djmix"); break;
|
||||||
case ReleaseTypeSecondary::Mixtape_Street: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-mixtape-street"); break;
|
case ReleaseTypeSecondary::Mixtape_Street: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-mixtape-street"); break;
|
||||||
case ReleaseTypeSecondary::Demo: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-demo"); break;
|
case ReleaseTypeSecondary::Demo: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-demo"); break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
Wt::WString buildReleaseYearString(const Wt::WDate& releaseDate, const Wt::WDate& originalReleaseDate)
|
Wt::WString buildReleaseYearString(const Wt::WDate& releaseDate, const Wt::WDate& originalReleaseDate)
|
||||||
{
|
{
|
||||||
Wt::WString res;
|
Wt::WString res;
|
||||||
|
|
||||||
// Year can be here, but originalYear can't be here without year (enforced by scanner)
|
// Year can be here, but originalYear can't be here without year (enforced by scanner)
|
||||||
if (!releaseDate.isValid())
|
if (!releaseDate.isValid())
|
||||||
return res;
|
return res;
|
||||||
|
|
||||||
if (originalReleaseDate.isValid() && originalReleaseDate != releaseDate)
|
if (originalReleaseDate.isValid() && originalReleaseDate != releaseDate)
|
||||||
res = std::to_string(originalReleaseDate.year()) + " (" + std::to_string(releaseDate.year()) + ")";
|
res = std::to_string(originalReleaseDate.year()) + " (" + std::to_string(releaseDate.year()) + ")";
|
||||||
else
|
else
|
||||||
res = std::to_string(releaseDate.year());
|
res = std::to_string(releaseDate.year());
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
} // namespace UserInterface::ReleaseHelpers}
|
} // namespace UserInterface::ReleaseHelpers}
|
||||||
|
|||||||
@@ -31,18 +31,18 @@
|
|||||||
|
|
||||||
namespace Database
|
namespace Database
|
||||||
{
|
{
|
||||||
class Artist;
|
class Artist;
|
||||||
class Release;
|
class Release;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace UserInterface::ReleaseListHelpers
|
namespace UserInterface::ReleaseListHelpers
|
||||||
{
|
{
|
||||||
std::unique_ptr<Wt::WTemplate> createEntry(const Database::ObjectPtr<Database::Release>& release);
|
std::unique_ptr<Wt::WTemplate> createEntry(const Database::ObjectPtr<Database::Release>& release);
|
||||||
std::unique_ptr<Wt::WTemplate> createEntryForArtist(const Database::ObjectPtr<Database::Release>& release, const Database::ObjectPtr<Database::Artist>& artist);
|
std::unique_ptr<Wt::WTemplate> createEntryForArtist(const Database::ObjectPtr<Database::Release>& release, const Database::ObjectPtr<Database::Artist>& artist);
|
||||||
} // namespace UserInterface
|
} // namespace UserInterface
|
||||||
|
|
||||||
namespace UserInterface::ReleaseHelpers
|
namespace UserInterface::ReleaseHelpers
|
||||||
{
|
{
|
||||||
Wt::WString buildReleaseTypeString(Database::ReleaseTypePrimary primaryType, EnumSet<Database::ReleaseTypeSecondary> secondaryTypes);
|
Wt::WString buildReleaseTypeString(Database::ReleaseTypePrimary primaryType, EnumSet<Database::ReleaseTypeSecondary> secondaryTypes);
|
||||||
Wt::WString buildReleaseYearString(const Wt::WDate& releaseDate, const Wt::WDate& originalReleaseDate);
|
Wt::WString buildReleaseYearString(const Wt::WDate& releaseDate, const Wt::WDate& originalReleaseDate);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ showReleaseInfoModal(Database::ReleaseId releaseId)
|
|||||||
|
|
||||||
for (const auto& [role, artistIds] : artistMap)
|
for (const auto& [role, artistIds] : artistMap)
|
||||||
{
|
{
|
||||||
std::unique_ptr<Wt::WContainerWidget> artistContainer {Utils::createArtistContainer(std::vector (std::cbegin(artistIds), std::cend(artistIds)))};
|
std::unique_ptr<Wt::WContainerWidget> artistContainer {Utils::createArtistAnchorList(std::vector (std::cbegin(artistIds), std::cend(artistIds)))};
|
||||||
auto artistsEntry {std::make_unique<Template>(Wt::WString::tr("Lms.Explore.template.info.artists"))};
|
auto artistsEntry {std::make_unique<Template>(Wt::WString::tr("Lms.Explore.template.info.artists"))};
|
||||||
artistsEntry->bindString("type", role);
|
artistsEntry->bindString("type", role);
|
||||||
artistsEntry->bindWidget("artist-container", std::move(artistContainer));
|
artistsEntry->bindWidget("artist-container", std::move(artistContainer));
|
||||||
@@ -400,8 +400,8 @@ Release::refreshView()
|
|||||||
if (variousArtists && !artists.empty())
|
if (variousArtists && !artists.empty())
|
||||||
{
|
{
|
||||||
entry->setCondition("if-has-artists", true);
|
entry->setCondition("if-has-artists", true);
|
||||||
entry->bindWidget("artists", Utils::createArtistContainer(artists));
|
entry->bindWidget("artists", Utils::createArtistDisplayNameWithAnchors(track->getArtistDisplayName(), artists));
|
||||||
entry->bindWidget("artists-md", Utils::createArtistContainer(artists));
|
entry->bindWidget("artists-md", Utils::createArtistDisplayNameWithAnchors(track->getArtistDisplayName(),artists));
|
||||||
}
|
}
|
||||||
|
|
||||||
auto trackNumber {track->getTrackNumber()};
|
auto trackNumber {track->getTrackNumber()};
|
||||||
@@ -480,29 +480,11 @@ Release::refreshView()
|
|||||||
void
|
void
|
||||||
Release::refreshReleaseArtists(const Database::Release::pointer& release)
|
Release::refreshReleaseArtists(const Database::Release::pointer& release)
|
||||||
{
|
{
|
||||||
std::vector<ObjectPtr<Artist>> artists;
|
auto container{ Utils::createArtistsAnchorsForRelease(release) };
|
||||||
|
if (container)
|
||||||
artists = release->getReleaseArtists();
|
|
||||||
if (artists.empty())
|
|
||||||
{
|
|
||||||
artists = release->getArtists(TrackArtistLinkType::Artist);
|
|
||||||
if (artists.size() > 1)
|
|
||||||
{
|
|
||||||
setCondition("if-has-various-release-artists", true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!artists.empty())
|
|
||||||
{
|
{
|
||||||
setCondition("if-has-release-artists", true);
|
setCondition("if-has-release-artists", true);
|
||||||
|
bindWidget("artists", std::move(container));
|
||||||
Wt::WContainerWidget* artistsContainer {bindNew<Wt::WContainerWidget>("artists")};
|
|
||||||
for (const auto& artist : artists)
|
|
||||||
{
|
|
||||||
Wt::WTemplate* artistTemplate {artistsContainer->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Release.template.entry-release-artist"))};
|
|
||||||
artistTemplate->bindWidget("artist", Utils::createArtistAnchor(artist));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ namespace UserInterface::TrackListHelpers
|
|||||||
|
|
||||||
for (const auto& [role, artistIds] : artistMap)
|
for (const auto& [role, artistIds] : artistMap)
|
||||||
{
|
{
|
||||||
std::unique_ptr<Wt::WContainerWidget> artistContainer {Utils::createArtistContainer(std::vector (std::cbegin(artistIds), std::cend(artistIds)))};
|
std::unique_ptr<Wt::WContainerWidget> artistContainer {Utils::createArtistAnchorList(std::vector (std::cbegin(artistIds), std::cend(artistIds)))};
|
||||||
auto artistsEntry {std::make_unique<Template>(Wt::WString::tr("Lms.Explore.template.info.artists"))};
|
auto artistsEntry {std::make_unique<Template>(Wt::WString::tr("Lms.Explore.template.info.artists"))};
|
||||||
artistsEntry->bindString("type", role);
|
artistsEntry->bindString("type", role);
|
||||||
artistsEntry->bindWidget("artist-container", std::move(artistContainer));
|
artistsEntry->bindWidget("artist-container", std::move(artistContainer));
|
||||||
@@ -171,8 +171,8 @@ namespace UserInterface::TrackListHelpers
|
|||||||
if (!artists.empty())
|
if (!artists.empty())
|
||||||
{
|
{
|
||||||
entry->setCondition("if-has-artists", true);
|
entry->setCondition("if-has-artists", true);
|
||||||
entry->bindWidget("artists", Utils::createArtistContainer(artists));
|
entry->bindWidget("artists", Utils::createArtistDisplayNameWithAnchors(track->getArtistDisplayName(), artists));
|
||||||
entry->bindWidget("artists-md", Utils::createArtistContainer(artists));
|
entry->bindWidget("artists-md", Utils::createArtistDisplayNameWithAnchors(track->getArtistDisplayName(), artists));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (track->getRelease())
|
if (track->getRelease())
|
||||||
|
|||||||
@@ -96,6 +96,9 @@ operator<<(std::ostream& os, const MetaData::Release& release)
|
|||||||
if (release.mediumCount)
|
if (release.mediumCount)
|
||||||
std::cout << "\tMediumCount: " << *release.mediumCount << std::endl;
|
std::cout << "\tMediumCount: " << *release.mediumCount << std::endl;
|
||||||
|
|
||||||
|
if (!release.artistDisplayName.empty())
|
||||||
|
std::cout << "\tDisplay artist: " << release.artistDisplayName << std::endl;
|
||||||
|
|
||||||
for (const MetaData::Artist& artist : release.artists)
|
for (const MetaData::Artist& artist : release.artists)
|
||||||
std::cout << "\tRelease artist: " << artist << std::endl;
|
std::cout << "\tRelease artist: " << artist << std::endl;
|
||||||
|
|
||||||
@@ -154,6 +157,9 @@ void parse(MetaData::IParser& parser, const std::filesystem::path& file)
|
|||||||
|
|
||||||
std::cout << "Parsed metadata:" << std::endl;
|
std::cout << "Parsed metadata:" << std::endl;
|
||||||
|
|
||||||
|
if (!track->artistDisplayName.empty())
|
||||||
|
std::cout << "Display artist: " << track->artistDisplayName << std::endl;
|
||||||
|
|
||||||
for (const Artist& artist : track->artists)
|
for (const Artist& artist : track->artists)
|
||||||
std::cout << "Artist: " << artist << std::endl;
|
std::cout << "Artist: " << artist << std::endl;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user