Split the search view in order to workaround the scroll visibility issue
This commit is contained in:
+22
-22
@@ -3,29 +3,29 @@
|
|||||||
<!--FORMS message blocks-->
|
<!--FORMS message blocks-->
|
||||||
|
|
||||||
<message id="Lms.Explore.Search.template">
|
<message id="Lms.Explore.Search.template">
|
||||||
<ul class="nav nav-pills mb-3" id="myTab" role="tablist">
|
<div class="mb-3">
|
||||||
<li class="nav-item" role="presentation">
|
<ul class="nav nav-pills mb-3">
|
||||||
<button class="nav-link active" id="releases-tab" data-bs-toggle="tab" data-bs-target="#releases" type="button" role="tab" aria-controls="releases" aria-selected="true">${tr:Lms.Explore.releases}</button>
|
<li class="nav-item">
|
||||||
</li>
|
${releases class="nav-link"}
|
||||||
<li class="nav-item" role="presentation">
|
</li>
|
||||||
<button class="nav-link" id="artists-tab" data-bs-toggle="tab" data-bs-target="#artists" type="button" role="tab" aria-controls="artists" aria-selected="false">${tr:Lms.Explore.artists}</button>
|
<li class="nav-item">
|
||||||
</li>
|
${artists class="nav-link"}
|
||||||
<li class="nav-item" role="presentation">
|
</li>
|
||||||
<button class="nav-link" id="tracks-tab" data-bs-toggle="tab" data-bs-target="#tracks" type="button" role="tab" aria-controls="tracks" aria-selected="false">${tr:Lms.Explore.tracks}</button>
|
<li class="nav-item">
|
||||||
</li>
|
${tracks class="nav-link"}
|
||||||
</ul>
|
</li>
|
||||||
<div class="tab-content" id="myTabContent">
|
</ul>
|
||||||
<div class="tab-pane show active" id="releases" role="tabpanel" aria-labelledby="releases-tab">${releases}</div>
|
|
||||||
<div class="tab-pane" id="artists" role="tabpanel" aria-labelledby="artists-tab">
|
|
||||||
<div class="row mb-3">
|
|
||||||
<div class="col-lg-3">
|
|
||||||
${link-type class="form-select"}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
${artists}
|
|
||||||
</div>
|
|
||||||
<div class="tab-pane" id="tracks" role="tabpanel" aria-labelledby="tracks-tab">${tracks}</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
${results}
|
||||||
|
</message>
|
||||||
|
|
||||||
|
<message id="Lms.Explore.Search.template.artists">
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-lg-3">
|
||||||
|
${link-type class="form-select"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
${artists}
|
||||||
</message>
|
</message>
|
||||||
|
|
||||||
</messages>
|
</messages>
|
||||||
|
|||||||
+137
-115
@@ -19,6 +19,8 @@
|
|||||||
|
|
||||||
#include "SearchView.hpp"
|
#include "SearchView.hpp"
|
||||||
|
|
||||||
|
#include <Wt/WPushButton.h>
|
||||||
|
|
||||||
#include "services/database/Artist.hpp"
|
#include "services/database/Artist.hpp"
|
||||||
#include "services/database/Release.hpp"
|
#include "services/database/Release.hpp"
|
||||||
#include "services/database/Session.hpp"
|
#include "services/database/Session.hpp"
|
||||||
@@ -35,142 +37,162 @@ using namespace Database;
|
|||||||
|
|
||||||
namespace UserInterface
|
namespace UserInterface
|
||||||
{
|
{
|
||||||
SearchView::SearchView(Filters& filters, PlayQueueController& playQueueController)
|
SearchView::SearchView(Filters& filters, PlayQueueController& playQueueController)
|
||||||
: Wt::WTemplate {Wt::WString::tr("Lms.Explore.Search.template")}
|
: Wt::WTemplate{ Wt::WString::tr("Lms.Explore.Search.template") }
|
||||||
, _playQueueController {playQueueController}
|
, _playQueueController{ playQueueController }
|
||||||
, _filters {filters}
|
, _filters{ filters }
|
||||||
, _artistCollector {filters, ArtistCollector::Mode::Search, getMaxCount(Mode::Artist)}
|
, _artistCollector{ filters, ArtistCollector::Mode::Search, getMaxCount(Mode::Artist) }
|
||||||
, _releaseCollector {filters, ReleaseCollector::Mode::Search, getMaxCount(Mode::Release)}
|
, _releaseCollector{ filters, ReleaseCollector::Mode::Search, getMaxCount(Mode::Release) }
|
||||||
, _trackCollector {filters, TrackCollector::Mode::Search, getMaxCount(Mode::Track)}
|
, _trackCollector{ filters, TrackCollector::Mode::Search, getMaxCount(Mode::Track) }
|
||||||
{
|
{
|
||||||
addFunction("tr", &Wt::WTemplate::Functions::tr);
|
addFunction("tr", &Wt::WTemplate::Functions::tr);
|
||||||
|
|
||||||
_artists = bindNew<InfiniteScrollingContainer>("artists", Wt::WString::tr("Lms.Explore.Artists.template.container"));
|
_stack = bindNew<Wt::WStackedWidget>("results");
|
||||||
_artists->onRequestElements.connect([this] { addSomeArtists(); });
|
|
||||||
|
|
||||||
_artistLinkType = bindNew<Wt::WComboBox>("link-type");
|
// releases
|
||||||
_artistLinkType->setModel(ArtistListHelpers::createArtistLinkTypesModel());
|
_releases = _stack->addNew<InfiniteScrollingContainer>(Wt::WString::tr("Lms.Explore.Releases.template.container"));
|
||||||
_artistLinkType->changed().connect([this]
|
_releases->onRequestElements.connect([this] { addSomeReleases(); });
|
||||||
{
|
|
||||||
const std::optional<TrackArtistLinkType> linkType {static_cast<ArtistLinkTypesModel*>(_artistLinkType->model().get())->getValue(_artistLinkType->currentIndex())};
|
|
||||||
refreshView(linkType);
|
|
||||||
});
|
|
||||||
|
|
||||||
_releases = bindNew<InfiniteScrollingContainer>("releases", Wt::WString::tr("Lms.Explore.Releases.template.container"));
|
// artists
|
||||||
_releases->onRequestElements.connect([this] { addSomeReleases(); });
|
{
|
||||||
|
Wt::WTemplate* artistResults{ _stack->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Search.template.artists")) };
|
||||||
|
|
||||||
_tracks = bindNew<InfiniteScrollingContainer>("tracks");
|
_artistLinkType = artistResults->bindNew<Wt::WComboBox>("link-type");
|
||||||
_tracks->onRequestElements.connect([this] { addSomeTracks(); });
|
_artistLinkType->setModel(ArtistListHelpers::createArtistLinkTypesModel());
|
||||||
|
_artistLinkType->changed().connect([this]
|
||||||
|
{
|
||||||
|
const std::optional<TrackArtistLinkType> linkType{ static_cast<ArtistLinkTypesModel*>(_artistLinkType->model().get())->getValue(_artistLinkType->currentIndex()) };
|
||||||
|
refreshView(linkType);
|
||||||
|
});
|
||||||
|
|
||||||
filters.updated().connect([=]
|
_artists = artistResults->bindNew<InfiniteScrollingContainer>("artists", Wt::WString::tr("Lms.Explore.Artists.template.container"));
|
||||||
{
|
_artists->onRequestElements.connect([this] { addSomeArtists(); });
|
||||||
refreshView();
|
}
|
||||||
});
|
|
||||||
|
|
||||||
LmsApp->getScannerEvents().scanComplete.connect(this, [this](const Scanner::ScanStats& stats)
|
// Tracks
|
||||||
{
|
_tracks = _stack->addNew<InfiniteScrollingContainer>();
|
||||||
if (stats.nbChanges())
|
_tracks->onRequestElements.connect([this] { addSomeTracks(); });
|
||||||
_artistLinkType->setModel(ArtistListHelpers::createArtistLinkTypesModel());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
std::size_t
|
// Menu
|
||||||
SearchView::getBatchSize(Mode mode) const
|
auto bindMenuItem{ [this](std::size_t index, const std::string& var, const Wt::WString& title)
|
||||||
{
|
{
|
||||||
auto it {_batchSizes.find(mode)};
|
Wt::WPushButton* menuItem {bindNew<Wt::WPushButton>(var, title)};
|
||||||
assert(it != _batchSizes.cend());
|
menuItem->clicked().connect([=]
|
||||||
return it->second;
|
{
|
||||||
}
|
_stack->setCurrentIndex(index);
|
||||||
|
_currentActiveItem->removeStyleClass("active");
|
||||||
|
menuItem->addStyleClass("active");
|
||||||
|
_currentActiveItem = menuItem;
|
||||||
|
});
|
||||||
|
|
||||||
std::size_t
|
if (index == 0)
|
||||||
SearchView::getMaxCount(Mode mode) const
|
{
|
||||||
{
|
_currentActiveItem = menuItem;
|
||||||
auto it {_maxCounts.find(mode)};
|
_currentActiveItem->addStyleClass("active");
|
||||||
assert(it != _maxCounts.cend());
|
}
|
||||||
return it->second;
|
} };
|
||||||
}
|
|
||||||
|
|
||||||
void
|
bindMenuItem(0, "releases", Wt::WString::tr("Lms.Explore.releases"));
|
||||||
SearchView::refreshView(std::optional<TrackArtistLinkType> linkType)
|
bindMenuItem(1, "artists", Wt::WString::tr("Lms.Explore.artists"));
|
||||||
{
|
bindMenuItem(2, "tracks", Wt::WString::tr("Lms.Explore.tracks"));
|
||||||
_artistCollector.setArtistLinkType(linkType);
|
|
||||||
refreshView();
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
filters.updated().connect([=]
|
||||||
SearchView::refreshView(const Wt::WString& searchText)
|
{
|
||||||
{
|
refreshView();
|
||||||
_releaseCollector.setSearch(searchText.toUTF8());
|
});
|
||||||
_artistCollector.setSearch(searchText.toUTF8());
|
|
||||||
_trackCollector.setSearch(searchText.toUTF8());
|
|
||||||
refreshView();
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
LmsApp->getScannerEvents().scanComplete.connect(this, [this](const Scanner::ScanStats& stats)
|
||||||
SearchView::refreshView()
|
{
|
||||||
{
|
if (stats.nbChanges())
|
||||||
_artists->reset();
|
_artistLinkType->setModel(ArtistListHelpers::createArtistLinkTypesModel());
|
||||||
_releases->reset();
|
});
|
||||||
_tracks->reset();
|
}
|
||||||
|
|
||||||
addSomeArtists();
|
std::size_t SearchView::getBatchSize(Mode mode) const
|
||||||
addSomeReleases();
|
{
|
||||||
addSomeTracks();
|
auto it{ _batchSizes.find(mode) };
|
||||||
}
|
assert(it != _batchSizes.cend());
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
std::size_t SearchView::getMaxCount(Mode mode) const
|
||||||
SearchView::addSomeArtists()
|
{
|
||||||
{
|
auto it{ _maxCounts.find(mode) };
|
||||||
using namespace Database;
|
assert(it != _maxCounts.cend());
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
const Range range {_artists->getCount(), getBatchSize(Mode::Artist)};
|
void SearchView::refreshView(std::optional<TrackArtistLinkType> linkType)
|
||||||
const RangeResults<ArtistId> artistIds {_artistCollector.get(range)};
|
{
|
||||||
{
|
_artistCollector.setArtistLinkType(linkType);
|
||||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
refreshView();
|
||||||
|
}
|
||||||
|
|
||||||
for (const ArtistId artistId : artistIds.results)
|
void SearchView::refreshView(const Wt::WString& searchText)
|
||||||
{
|
{
|
||||||
const Artist::pointer artist {Artist::find(LmsApp->getDbSession(), artistId)};
|
_releaseCollector.setSearch(searchText.toUTF8());
|
||||||
_artists->add(ArtistListHelpers::createEntry(artist));
|
_artistCollector.setSearch(searchText.toUTF8());
|
||||||
}
|
_trackCollector.setSearch(searchText.toUTF8());
|
||||||
}
|
refreshView();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void SearchView::refreshView()
|
||||||
SearchView::addSomeReleases()
|
{
|
||||||
{
|
_artists->reset();
|
||||||
using namespace Database;
|
_releases->reset();
|
||||||
|
_tracks->reset();
|
||||||
|
}
|
||||||
|
|
||||||
const Range range {_releases->getCount(), getBatchSize(Mode::Release)};
|
void SearchView::addSomeArtists()
|
||||||
const RangeResults<ReleaseId> releaseIds {_releaseCollector.get(range)};
|
{
|
||||||
{
|
using namespace Database;
|
||||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
|
||||||
|
|
||||||
for (const ReleaseId releaseId : releaseIds.results)
|
const Range range{ _artists->getCount(), getBatchSize(Mode::Artist) };
|
||||||
{
|
const RangeResults<ArtistId> artistIds{ _artistCollector.get(range) };
|
||||||
const Release::pointer release {Release::find(LmsApp->getDbSession(), releaseId)};
|
{
|
||||||
_releases->add(ReleaseListHelpers::createEntry(release));
|
auto transaction{ LmsApp->getDbSession().createSharedTransaction() };
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
for (const ArtistId artistId : artistIds.results)
|
||||||
SearchView::addSomeTracks()
|
{
|
||||||
{
|
const Artist::pointer artist{ Artist::find(LmsApp->getDbSession(), artistId) };
|
||||||
using namespace Database;
|
_artists->add(ArtistListHelpers::createEntry(artist));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const Range range {_tracks->getCount(), getBatchSize(Mode::Track)};
|
void SearchView::addSomeReleases()
|
||||||
const RangeResults<TrackId> trackIds {_trackCollector.get(range)};
|
{
|
||||||
|
using namespace Database;
|
||||||
|
|
||||||
{
|
const Range range{ _releases->getCount(), getBatchSize(Mode::Release) };
|
||||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
const RangeResults<ReleaseId> releaseIds{ _releaseCollector.get(range) };
|
||||||
|
{
|
||||||
|
auto transaction{ LmsApp->getDbSession().createSharedTransaction() };
|
||||||
|
|
||||||
for (const TrackId trackId : trackIds.results)
|
for (const ReleaseId releaseId : releaseIds.results)
|
||||||
{
|
{
|
||||||
const Track::pointer track {Track::find(LmsApp->getDbSession(), trackId)};
|
const Release::pointer release{ Release::find(LmsApp->getDbSession(), releaseId) };
|
||||||
_tracks->add(TrackListHelpers::createEntry(track, _playQueueController, _filters));
|
_releases->add(ReleaseListHelpers::createEntry(release));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SearchView::addSomeTracks()
|
||||||
|
{
|
||||||
|
using namespace Database;
|
||||||
|
|
||||||
|
const Range range{ _tracks->getCount(), getBatchSize(Mode::Track) };
|
||||||
|
const RangeResults<TrackId> trackIds{ _trackCollector.get(range) };
|
||||||
|
|
||||||
|
{
|
||||||
|
auto transaction{ LmsApp->getDbSession().createSharedTransaction() };
|
||||||
|
|
||||||
|
for (const TrackId trackId : trackIds.results)
|
||||||
|
{
|
||||||
|
const Track::pointer track{ Track::find(LmsApp->getDbSession(), trackId) };
|
||||||
|
_tracks->add(TrackListHelpers::createEntry(track, _playQueueController, _filters));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} // namespace UserInterface
|
} // namespace UserInterface
|
||||||
|
|
||||||
|
|||||||
@@ -33,62 +33,61 @@
|
|||||||
|
|
||||||
namespace UserInterface
|
namespace UserInterface
|
||||||
{
|
{
|
||||||
class InfiniteScrollingContainer;
|
class InfiniteScrollingContainer;
|
||||||
class Filters;
|
class Filters;
|
||||||
class PlayQueueController;
|
class PlayQueueController;
|
||||||
|
|
||||||
class SearchView : public Wt::WTemplate
|
class SearchView : public Wt::WTemplate
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
SearchView(Filters& filters, PlayQueueController& playQueueController);
|
SearchView(Filters& filters, PlayQueueController& playQueueController);
|
||||||
|
|
||||||
void refreshView(const Wt::WString& searchText);
|
void refreshView(const Wt::WString& searchText);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// same order as in the menu
|
// same order as in the menu
|
||||||
enum class Mode
|
enum class Mode
|
||||||
{
|
{
|
||||||
Artist,
|
Artist,
|
||||||
Release,
|
Release,
|
||||||
Track,
|
Track,
|
||||||
};
|
};
|
||||||
|
|
||||||
static constexpr Mode _defaultMode {Mode::Release};
|
static constexpr Mode _defaultMode{ Mode::Release };
|
||||||
static inline std::unordered_map<Mode, std::size_t> _batchSizes
|
static inline std::unordered_map<Mode, std::size_t> _batchSizes
|
||||||
{
|
{
|
||||||
{Mode::Artist, 6},
|
{Mode::Artist, 6},
|
||||||
{Mode::Release, 6},
|
{Mode::Release, 6},
|
||||||
{Mode::Track, 6},
|
{Mode::Track, 6},
|
||||||
};
|
};
|
||||||
static inline std::unordered_map<Mode, std::size_t> _maxCounts
|
static inline std::unordered_map<Mode, std::size_t> _maxCounts
|
||||||
{
|
{
|
||||||
{Mode::Artist, 1000},
|
{Mode::Artist, 1000},
|
||||||
{Mode::Release, 504},
|
{Mode::Release, 504},
|
||||||
{Mode::Track, 500},
|
{Mode::Track, 500},
|
||||||
};
|
};
|
||||||
std::size_t getBatchSize(Mode mode) const;
|
std::size_t getBatchSize(Mode mode) const;
|
||||||
std::size_t getMaxCount(Mode mode) const;
|
std::size_t getMaxCount(Mode mode) const;
|
||||||
|
|
||||||
void refreshView();
|
void refreshView();
|
||||||
void refreshView(std::optional<Database::TrackArtistLinkType> linkType);
|
void refreshView(std::optional<Database::TrackArtistLinkType> linkType);
|
||||||
void addSomeArtists();
|
void addSomeArtists();
|
||||||
void addSomeReleases();
|
void addSomeReleases();
|
||||||
void addSomeTracks();
|
void addSomeTracks();
|
||||||
|
|
||||||
PlayQueueController& _playQueueController;
|
PlayQueueController& _playQueueController;
|
||||||
Filters& _filters;
|
Filters& _filters;
|
||||||
ArtistCollector _artistCollector;
|
ArtistCollector _artistCollector;
|
||||||
ReleaseCollector _releaseCollector;
|
ReleaseCollector _releaseCollector;
|
||||||
TrackCollector _trackCollector;
|
TrackCollector _trackCollector;
|
||||||
|
Wt::WStackedWidget* _stack{};
|
||||||
|
Wt::WWidget* _currentActiveItem {};
|
||||||
|
InfiniteScrollingContainer* _artists{};
|
||||||
|
InfiniteScrollingContainer* _releases{};
|
||||||
|
InfiniteScrollingContainer* _tracks{};
|
||||||
|
|
||||||
InfiniteScrollingContainer* _artists {};
|
Wt::WComboBox* _artistLinkType{};
|
||||||
InfiniteScrollingContainer* _releases {};
|
};
|
||||||
InfiniteScrollingContainer* _tracks {};
|
|
||||||
|
|
||||||
Wt::WComboBox* _artistLinkType {};
|
|
||||||
|
|
||||||
std::vector<InfiniteScrollingContainer*> _results;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace UserInterface
|
} // namespace UserInterface
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user