From 75825230abcc5f303a29f1f74b29c36e6140a064 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Sun, 13 Sep 2015 22:46:48 +0200 Subject: Implement package details page --- brep/buildfile | 2 +- brep/options.cli | 1 + brep/package | 7 ++ brep/package-version-search.cxx | 162 +++++++++++++++++++++++++++++++++++++++- brep/page | 52 +++++++++++++ brep/page.cxx | 103 +++++++++++++++++++++++++ 6 files changed, 324 insertions(+), 3 deletions(-) create mode 100644 brep/page create mode 100644 brep/page.cxx (limited to 'brep') diff --git a/brep/buildfile b/brep/buildfile index 9b64ed4..7e73d2b 100644 --- a/brep/buildfile +++ b/brep/buildfile @@ -22,7 +22,7 @@ libso{brep}: cxx.export.poptions = -I$out_root -I$src_root import libs += libstudxml%lib{studxml} brep = cxx{diagnostics module services package-search package-version-search \ - shared-database} cli.cxx{options} + shared-database page} cli.cxx{options} web = ../web/apache/cxx{request service} ../web/cxx{mime-url-encoding} libso{brep-apache}: $brep $web libso{brep} $libs diff --git a/brep/options.cli b/brep/options.cli index 8062f59..a042f45 100644 --- a/brep/options.cli +++ b/brep/options.cli @@ -30,6 +30,7 @@ namespace brep class package_version_search: module, db { std::uint16_t results-on-page = 10; + std::uint16_t pages-in-pager = 10; }; } diff --git a/brep/package b/brep/package index 0132b73..0ef4407 100644 --- a/brep/package +++ b/brep/package @@ -482,6 +482,13 @@ namespace brep #pragma db member(id) virtual(package_version::_id_type) \ get() set(_id (std::move (?))) }; + + #pragma db view object(package_version) + struct package_version_count + { + #pragma db column("count(1)") + std::size_t count; + }; } // Nested container emulation support for ODB. diff --git a/brep/package-version-search.cxx b/brep/package-version-search.cxx index a989434..d95b392 100644 --- a/brep/package-version-search.cxx +++ b/brep/package-version-search.cxx @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -60,14 +61,171 @@ namespace brep throw invalid_request (400, e.what ()); } - const char* title ("Package"); + const char* ident ("\n "); + const string title ("Package " + name); serializer s (rs.content (), title); s << HTML << HEAD << TITLE << title << ~TITLE + << CSS_STYLE << ident + << pager_style () << ident + << "a {text-decoration: none;}" << ident + << "a:hover {text-decoration: underline;}" << ident + << ".name {font-size: xx-large; font-weight: bold;}" << ident + << ".summary {font-size: x-large; margin: 0.2em 0 0;}" << ident + << ".url {font-size: small;}" << ident + << ".email {font-size: small;}" << ident + << ".description {margin: 0.5em 0 0;}" << ident + << ".tags {margin: 0.5em 0 0;}" << ident + << ".tag {padding: 0 0.3em 0 0;}" << ident + << ".versions {font-size: x-large; margin: 0.5em 0 0;}" << ident + << ".package_version {margin: 0.5em 0 0;}" << ident + << ".version {font-size: x-large;}" << ident + << ~CSS_STYLE << ~HEAD - << BODY << name << ~BODY + << BODY; + + transaction t (db_->begin ()); + + shared_ptr p; + + try + { + p = db_->load (name); + } + catch (const object_not_persistent& ) + { + throw invalid_request (404, "Package '" + name + "' not found"); + } + + s << DIV(CLASS="name") + << name + << ~DIV + << DIV(CLASS="summary") + << p->summary + << ~DIV; + + s << DIV(CLASS="url") + << A << HREF << p->url << ~HREF << p->url << ~A + << ~DIV + << DIV(CLASS="email") + << A << HREF << "mailto:" << p->email << ~HREF << p->email << ~A + << ~DIV; + + if (p->description) + s << DIV(CLASS="description") + << *p->description + << ~DIV; + + if (!p->tags.empty ()) + { + s << DIV(CLASS="tags"); + + for (const auto& t: p->tags) + s << SPAN(CLASS="tag") << t << ~SPAN << " "; + + s << ~DIV; + } + + // @@ Query will also include search criteria if specified. + // + size_t pvc ( + db_->query_value ( + query::id.data.package == name).count); + + s << DIV(CLASS="versions") + << "Versions (" << pvc << ")" + << ~DIV; + + if (p->package_url) + s << DIV(CLASS="url") + << A << HREF << *p->package_url << ~HREF << *p->package_url << ~A + << ~DIV; + + if (p->package_email) + s << DIV(CLASS="email") + << A + << HREF << "mailto:" << *p->package_email << ~HREF + << *p->package_email + << ~A + << ~DIV; + + size_t rop (options_->results_on_page ()); + + // @@ Use appropriate view when clarify which package version info to be + // displayed and search index structure get implemented. Query will also + // include search criteria if specified. + // + using query = query; + auto r ( + db_->query ((query::id.data.package == name) + + "ORDER BY" + query::id.data.epoch + "DESC," + + query::id.data.canonical_upstream + "DESC," + + query::id.revision + "DESC " + + "OFFSET" + to_string (pr.page () * rop) + + "LIMIT" + to_string (rop))); + + for (const auto& v: r) + { + static const strings priority_names ( + {"low", "medium", "high", "security"}); + + assert (v.priority < priority_names.size ()); + + const string& vs (v.version.string ()); + + s << DIV(CLASS="package_version") + << DIV(CLASS="version") + << A + << HREF + << "/go/" << mime_url_encode (name) << "/" << vs << "/" + << ~HREF + << vs + << ~A + << ~DIV + << DIV(CLASS="priority") + << "Priority: " << priority_names[v.priority] + << ~DIV + << DIV(CLASS="licenses") + << "Licenses: "; + + for (const auto& la: v.license_alternatives) + { + if (&la != &v.license_alternatives[0]) + s << " or "; + + for (const auto& l: la) + { + if (&l != &la[0]) + s << ", "; + + s << l; + } + } + + s << ~DIV + << ~DIV; + } + + t.commit (); + + auto u ( + [&name, &pr](size_t p) + { + string url ("/go/" + name + "/"); + if (p > 0) + url += "?p=" + to_string (p); + + if (!pr.query ().empty ()) + url += + string (p > 0 ? "&" : "?") + "q=" + mime_url_encode (pr.query ()); + + return url; + }); + + s << pager (pr.page (), pvc, rop, options_->pages_in_pager (), u) + << ~BODY << ~HTML; } } diff --git a/brep/page b/brep/page new file mode 100644 index 0000000..81b6da8 --- /dev/null +++ b/brep/page @@ -0,0 +1,52 @@ +// file : brep/page -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#ifndef BREP_PAGE +#define BREP_PAGE + +#include +#include // size_t +#include + +#include + +namespace brep +{ + // Page common building blocks. + // + + // Generates paging element block. + // + class pager + { + public: + using get_url_type = std::function; + + pager (std::size_t current_page, + std::size_t item_count, + std::size_t item_per_page, + std::size_t page_number_count, + get_url_type get_url); + + void + operator() (xml::serializer& s) const; + + private: + std::size_t current_page_; + std::size_t item_count_; + std::size_t item_per_page_; + std::size_t page_number_count_; + get_url_type get_url_; + }; + + // Default pager element block style. + // + struct pager_style + { + void + operator() (xml::serializer& s) const; + }; +} + +#endif // BREP_PAGE diff --git a/brep/page.cxx b/brep/page.cxx new file mode 100644 index 0000000..70c6d8c --- /dev/null +++ b/brep/page.cxx @@ -0,0 +1,103 @@ +// file : brep/page.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2015 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include // move() +#include // min() + +#include + +#include + +using namespace std; +using namespace xml; +using namespace web::xhtml; + +namespace brep +{ + // pager + // + pager:: + pager (size_t current_page, + size_t item_count, + size_t item_per_page, + size_t page_number_count, + get_url_type get_url) + : current_page_ (current_page), + item_count_ (item_count), + item_per_page_ (item_per_page), + page_number_count_ (page_number_count), + get_url_ (move (get_url)) + { + } + + void pager:: + operator() (serializer& s) const + { + if (item_count_ == 0 || item_per_page_ == 0) + return; + + size_t pc (item_count_ / item_per_page_); // Page count. + + if (item_count_ % item_per_page_) + ++pc; + + if (pc > 1) + { + // Can consider customizing class names if use-case appear. + // + s << DIV(CLASS="pager"); + + if (current_page_ > 0) + s << A(CLASS="pg-prev") + << HREF << get_url_ (current_page_ - 1) << ~HREF + << "<<" + << ~A + << " "; + + if (page_number_count_) + { + size_t offset (page_number_count_ / 2); + size_t fp (current_page_ > offset ? current_page_ - offset : 0); + size_t tp (min (fp + page_number_count_, pc)); + + for (size_t p (fp); p < tp; ++p) + { + if (p == current_page_) + s << SPAN(CLASS="pg-cpage") + << p + 1 + << ~SPAN; + else + s << A(CLASS="pg-page") + << HREF << get_url_ (p) << ~HREF + << p + 1 + << ~A; + + s << " "; + } + } + + if (current_page_ < pc - 1) + s << A(CLASS="pg-next") + << HREF << get_url_ (current_page_ + 1) << ~HREF + << ">>" + << ~A; + + s << ~DIV; + } + } + + // pager_style + // + void pager_style:: + operator() (xml::serializer& s) const + { + const char* ident ("\n "); + s << ".pager {margin: 0.5em 0 0;}" << ident + << ".pg-prev {padding: 0 0.3em 0 0;}" << ident + << ".pg-page {padding: 0 0.3em 0 0;}" << ident + << ".pg-cpage {padding: 0 0.3em 0 0; font-weight: bold;}"; + } +} -- cgit v1.1