From ce705eaa4fb5b2754a275cda2f0f95de7846e359 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophile=20Choutri?= Date: Tue, 31 Oct 2023 17:42:55 +0100 Subject: [PATCH] [FLORA-388] Reverse dependencies search bar (#476) * [FLORA-388] Reverse dependencies search bar --------- Co-authored-by: Dylan Thinnes --- .github/mergify.yml | 2 +- CHANGELOG.md | 1 + assets/css/2-components/5-primary-search.css | 61 ++++++++++ .../css/2-components/6-secondary-search.css | 60 ++++++++++ assets/css/3-screens/4-front-page.css | 59 ---------- assets/css/styles.css | 15 ++- flora.cabal | 4 +- src/core/Flora/Model/Package/Query.hs | 110 +++++++++++++++--- src/core/Flora/Search.hs | 9 +- src/web/FloraWeb/Components/Icons.hs | 57 +++++++++ src/web/FloraWeb/Components/MainSearchBar.hs | 23 ++++ src/web/FloraWeb/Components/PaginationNav.hs | 4 +- src/web/FloraWeb/Components/SlimSearchBar.hs | 30 +++++ src/web/FloraWeb/Components/Utils.hs | 2 +- src/web/FloraWeb/Links.hs | 10 +- src/web/FloraWeb/Pages/Routes/Packages.hs | 2 + src/web/FloraWeb/Pages/Server/Packages.hs | 40 +++++-- src/web/FloraWeb/Pages/Templates/Packages.hs | 76 +++++------- .../FloraWeb/Pages/Templates/Pages/Home.hs | 33 +----- .../Pages/Templates/Pages/Packages.hs | 4 +- test/Flora/PackageSpec.hs | 2 +- 21 files changed, 419 insertions(+), 185 deletions(-) create mode 100644 assets/css/2-components/5-primary-search.css create mode 100644 assets/css/2-components/6-secondary-search.css create mode 100644 src/web/FloraWeb/Components/Icons.hs create mode 100644 src/web/FloraWeb/Components/MainSearchBar.hs create mode 100644 src/web/FloraWeb/Components/SlimSearchBar.hs diff --git a/.github/mergify.yml b/.github/mergify.yml index fe7cbd69..6e8b56c9 100644 --- a/.github/mergify.yml +++ b/.github/mergify.yml @@ -11,7 +11,7 @@ pull_request_rules: conditions: - label=merge me - 'check-success=Frontend_tests' - - 'check-success=Backend_tests' + - 'check-success~=.*Backend_tests.*' # - '#approved-reviews-by>=1' # merge+squash strategy - actions: diff --git a/CHANGELOG.md b/CHANGELOG.md index 557898b7..71a50e9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * Add a page on namespaces in the documentation ([#451](/~https://github.com/flora-pm/flora-server/pull/451)) * Add initial support for hosting package tarballs ([#452](/~https://github.com/flora-pm/flora-server/pull/452)) * Show depended on components in dependencies page ([#464](/~https://github.com/flora-pm/flora-server/pull/464)) +* Add search bar for reverse dependencies ([#476](/~https://github.com/flora-pm/flora-server/pull/476)) ## 1.0.13 -- 2023-09-17 * Exclude deprecated releases from latest versions and search ([#373](/~https://github.com/flora-pm/flora-server/pull/373)) diff --git a/assets/css/2-components/5-primary-search.css b/assets/css/2-components/5-primary-search.css new file mode 100644 index 00000000..46ad7145 --- /dev/null +++ b/assets/css/2-components/5-primary-search.css @@ -0,0 +1,61 @@ +/* stylelint-disable selector-class-pattern */ +/* stylelint-disable declaration-block-no-redundant-longhand-properties */ + +.main-search { + background-color: var(--search-bar-background-color); + border-radius: 0.75rem; + border-width: 2px; + display: flex; + font-size: 1.5rem; + justify-content: center; + line-height: 2rem; + max-width: 28rem; + outline-offset: -2px; + overflow: hidden; + padding: 0.5rem; + + .search-bar { + background-color: var(--search-bar-background-color); + color: var(--search-bar-color); + display: block; + margin-left: 0.5rem; + font-size: 1.5rem; + line-height: 2rem; + padding: 0.5rem; + flex-grow: 1; + min-width: 0; + } + + .search-bar:hover { + background-color: var(--search-bar-background-hover-color); + } + + .search-bar:focus { + background-color: var(--search-bar-background-focus-color); + outline: 2px solid transparent; + outline-offset: 2px; + } +} + +.main-search:focus-within { + border-color: var(--search-bar-focus-border-color); + transition-property: box-shadow; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 400ms; + + /* offset-x | offset-y | blur-radius | spread-radius | color */ + box-shadow: 5px 5px 5px 2px var(--search-bar-focus-border-color); +} + +.main-search button { + margin-bottom: 1.25rem; + margin-right: 1rem; + margin-top: 1.25rem; + + svg { + width: 1.5rem; + height: 1.5rem; + margin-top: auto; + margin-bottom: auto; + } +} diff --git a/assets/css/2-components/6-secondary-search.css b/assets/css/2-components/6-secondary-search.css new file mode 100644 index 00000000..6bef98a1 --- /dev/null +++ b/assets/css/2-components/6-secondary-search.css @@ -0,0 +1,60 @@ +/* stylelint-disable selector-class-pattern */ +/* stylelint-disable declaration-block-no-redundant-longhand-properties */ + +.secondary-search { + background-color: var(--search-bar-background-color); + border-radius: 0.75rem; + border-width: 2px; + display: flex; + font-size: 1rem; + justify-content: center; + line-height: 2rem; + max-width: 20rem; + outline-offset: -2px; + overflow: hidden; + padding: 0.3rem; + + .search-bar { + background-color: var(--search-bar-background-color); + color: var(--search-bar-color); + display: block; + font-size: 1.5rem; + line-height: 2rem; + padding: 0.5rem; + flex-grow: 1; + min-width: 0; + } + + .search-bar:hover { + background-color: var(--search-bar-background-hover-color); + } + + .search-bar:focus { + background-color: var(--search-bar-background-focus-color); + outline: transparent solid 2px; + outline-offset: 2px; + } +} + +.secondary-search:focus-within { + border-color: var(--search-bar-focus-border-color); + transition-property: box-shadow; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 300ms; + + /* offset-x | offset-y | blur-radius | spread-radius | color */ + box-shadow: 4px 4px 4px 1px var(--search-bar-focus-border-color); +} + +.secondary-search button { + margin-bottom: 1.25rem; + margin-right: 1rem; + margin-top: 1.25rem; + + svg { + width: 1em; + height: 1em; + margin-top: auto; + margin-bottom: auto; + } +} diff --git a/assets/css/3-screens/4-front-page.css b/assets/css/3-screens/4-front-page.css index 9515e534..1cc00908 100644 --- a/assets/css/3-screens/4-front-page.css +++ b/assets/css/3-screens/4-front-page.css @@ -13,65 +13,6 @@ text-align: center; } -.main-search { - background-color: var(--search-bar-background-color); - border-radius: 0.75rem; - border-width: 2px; - display: flex; - font-size: 1.5rem; - justify-content: center; - line-height: 2rem; - max-width: 28rem; - outline-offset: -2px; - overflow: hidden; - padding: 0.5rem; - - .search-bar { - background-color: var(--search-bar-background-color); - color: var(--search-bar-color); - display: block; - margin-left: 0.5rem; - font-size: 1.5rem; - line-height: 2rem; - padding: 0.5rem; - flex-grow: 1; - min-width: 0; - } - - .search-bar:hover { - background-color: var(--search-bar-background-hover-color); - } - - .search-bar:focus { - background-color: var(--search-bar-background-focus-color); - outline: 2px solid transparent; - outline-offset: 2px; - } -} - -.main-search:focus-within { - border-color: var(--search-bar-focus-border-color); - transition-property: box-shadow; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); - transition-duration: 400ms; - - /* offset-x | offset-y | blur-radius | spread-radius | color */ - box-shadow: 5px 5px 5px 2px var(--search-bar-focus-border-color); -} - -.main-search button { - margin-bottom: 1.25rem; - margin-right: 1rem; - margin-top: 1.25rem; - - svg { - width: 1.5rem; - height: 1.5rem; - margin-top: auto; - margin-bottom: auto; - } -} - section#main-page-buttons { border-top: 1px solid var(--text-color); border-color: var(--navbar-background-color); diff --git a/assets/css/styles.css b/assets/css/styles.css index 3932e3b5..5c7e17d3 100644 --- a/assets/css/styles.css +++ b/assets/css/styles.css @@ -8,6 +8,8 @@ @import "2-components/2-package-component.css"; @import "2-components/3-breadcrumb.css"; @import "2-components/4-license.css"; +@import "2-components/5-primary-search.css"; +@import "2-components/6-secondary-search.css"; @import "3-screens/1-package/1-package.css"; @import "3-screens/1-package/2-release-changelog.css"; @@ -75,16 +77,19 @@ } } +ul.package-list > * { + margin-top: 1rem; + margin-bottom: 1rem; + padding-left: 1rem; + padding-top: 0.25rem; + padding-bottom: 0.25rem; +} + .package-list-item { list-style: none; a { display: block; - margin-top: 1rem; - margin-bottom: 1rem; - padding-left: 1rem; - padding-top: 0.25rem; - padding-bottom: 0.25rem; font-size: 1.25rem; line-height: 1.75rem; } diff --git a/flora.cabal b/flora.cabal index 2729aa2f..4843e1a6 100644 --- a/flora.cabal +++ b/flora.cabal @@ -39,7 +39,6 @@ common common-extensions OverloadedStrings PackageImports PolyKinds - QuasiQuotes RecordWildCards StrictData TypeFamilies @@ -223,10 +222,13 @@ library flora-web FloraWeb.Components.CategoryCard FloraWeb.Components.Footer FloraWeb.Components.Header + FloraWeb.Components.Icons + FloraWeb.Components.MainSearchBar FloraWeb.Components.Navbar FloraWeb.Components.PackageListHeader FloraWeb.Components.PackageListItem FloraWeb.Components.PaginationNav + FloraWeb.Components.SlimSearchBar FloraWeb.Components.Utils FloraWeb.Components.VersionListHeader FloraWeb.Embedded diff --git a/src/core/Flora/Model/Package/Query.hs b/src/core/Flora/Model/Package/Query.hs index dd048929..a889a2d4 100644 --- a/src/core/Flora/Model/Package/Query.hs +++ b/src/core/Flora/Model/Package/Query.hs @@ -81,7 +81,21 @@ getAllPackageDependents => Namespace -> PackageName -> Eff es (Vector Package) -getAllPackageDependents namespace packageName = dbtToEff $ query Select packageDependentsQuery (namespace, packageName) +getAllPackageDependents namespace packageName = + dbtToEff $ query Select packageDependentsQuery (namespace, packageName) + +getPackageDependentsByName + :: DB :> es + => Namespace + -> PackageName + -> Text + -> Eff es (Vector Package) +getPackageDependentsByName namespace packageName searchString = + dbtToEff $ + query + Select + searchPackageDependentsQuery + (namespace, packageName, searchString) -- | This function gets the first 6 dependents of a package getPackageDependents :: DB :> es => Namespace -> PackageName -> Eff es (Vector Package) @@ -89,14 +103,26 @@ getPackageDependents namespace packageName = dbtToEff $ query Select q (namespac where q = packageDependentsQuery <> " LIMIT 6" -getNumberOfPackageDependents :: DB :> es => Namespace -> PackageName -> Eff es Word -getNumberOfPackageDependents namespace packageName = - dbtToEff $ do - (result :: Maybe (Only Int)) <- - queryOne Select numberOfPackageDependentsQuery (namespace, packageName) - case result of - Just (Only n) -> pure $ fromIntegral n - Nothing -> pure 0 +getNumberOfPackageDependents + :: DB :> es + => Namespace + -> PackageName + -> Maybe Text + -> Eff es Word +getNumberOfPackageDependents namespace packageName mbSearchString = do + case mbSearchString of + Nothing -> + dbtToEff $ do + (result :: Maybe (Only Int)) <- queryOne Select numberOfPackageDependentsQuery (namespace, packageName) + case result of + Just (Only n) -> pure $ fromIntegral n + Nothing -> pure 0 + Just searchString -> + dbtToEff $ do + (result :: Maybe (Only Int)) <- queryOne Select searchNumberOfPackageDependentsQuery (namespace, packageName, searchString) + case result of + Just (Only n) -> pure $ fromIntegral n + Nothing -> pure 0 numberOfPackageDependentsQuery :: Query numberOfPackageDependentsQuery = @@ -109,6 +135,19 @@ numberOfPackageDependentsQuery = AND dep."name" = ? |] +searchNumberOfPackageDependentsQuery :: Query +searchNumberOfPackageDependentsQuery = + [sql| + SELECT DISTINCT count(p."package_id") + FROM "packages" AS p + INNER JOIN "dependents" AS dep + ON p."package_id" = dep."dependent_id" + WHERE dep."namespace" = ? + AND dep."name" = ? + AND ? <% p."name" + |] + +-- | Fetch the dependents of a package. packageDependentsQuery :: Query packageDependentsQuery = [sql| @@ -127,16 +166,27 @@ packageDependentsQuery = AND dep."name" = ? |] +searchPackageDependentsQuery :: Query +searchPackageDependentsQuery = + packageDependentsQuery <> " AND ? <% p.name" + getAllPackageDependentsWithLatestVersion :: DB :> es => Namespace -> PackageName -> (Word, Word) + -> Maybe Text -> Eff es (Vector DependencyInfo) -getAllPackageDependentsWithLatestVersion namespace packageName (offset, limit) = - dbtToEff $ query Select q (namespace, packageName, offset, limit) - where - q = packageDependentsWithLatestVersionQuery <> " OFFSET ? LIMIT ?" +getAllPackageDependentsWithLatestVersion namespace packageName (offset, limit) mSearchString = + case mSearchString of + Nothing -> + dbtToEff $ query Select q (namespace, packageName, offset, limit) + where + q = packageDependentsWithLatestVersionQuery <> " OFFSET ? LIMIT ?" + Just searchString -> + dbtToEff $ query Select q (namespace, packageName, searchString, offset, limit) + where + q = searchPackageDependentsWithLatestVersionQuery <> " OFFSET ? LIMIT ?" getPackageDependentsWithLatestVersion :: (DB :> es, Log :> es, Time :> es) @@ -158,13 +208,13 @@ getPackageDependentsWithLatestVersion namespace packageName = do packageDependentsWithLatestVersionQuery :: Query packageDependentsWithLatestVersionQuery = [sql| - SELECT DISTINCT p."namespace" - , p."name" + SELECT DISTINCT p.namespace + , p.name , '' , array[]::text[] - , max(r."version") - , r.synopsis as "synopsis" - , r.license as "license" + , max(r.version) + , r.synopsis + , r.license FROM "packages" AS p INNER JOIN "dependents" AS dep ON p."package_id" = dep."dependent_id" @@ -172,7 +222,29 @@ packageDependentsWithLatestVersionQuery = ON r."package_id" = p."package_id" WHERE dep."namespace" = ? AND dep."name" = ? - GROUP BY (p.namespace, p.name, synopsis, license) + GROUP BY (p.namespace, p.name, r.synopsis, r.license) + ORDER BY p.namespace DESC + |] + +searchPackageDependentsWithLatestVersionQuery :: Query +searchPackageDependentsWithLatestVersionQuery = + [sql| + SELECT DISTINCT p."namespace" + , p."name" + , '' + , array[]::text[] + , max(r.version) + , r.synopsis + , r.license + FROM "packages" AS p + INNER JOIN "dependents" AS dep + ON p."package_id" = dep."dependent_id" + INNER JOIN "releases" AS r + ON r."package_id" = p."package_id" + WHERE dep.namespace = ? + AND dep.name = ? + AND ? <% p."name" + GROUP BY (p.namespace, p.name, r.synopsis, r.license) ORDER BY p.namespace DESC |] diff --git a/src/core/Flora/Search.hs b/src/core/Flora/Search.hs index c59ec650..cf0feaa1 100644 --- a/src/core/Flora/Search.hs +++ b/src/core/Flora/Search.hs @@ -21,14 +21,19 @@ data SearchAction = ListAllPackages | ListAllPackagesInNamespace Namespace | SearchPackages Text - | DependentsOf Namespace PackageName + | DependentsOf Namespace PackageName (Maybe Text) deriving (Eq, Ord, Show) instance Display SearchAction where displayBuilder ListAllPackages = "Packages" displayBuilder (ListAllPackagesInNamespace namespace) = "Packages in " <> displayBuilder namespace displayBuilder (SearchPackages title) = "\"" <> Builder.fromText title <> "\"" - displayBuilder (DependentsOf namespace packageName) = "Dependents of " <> displayBuilder namespace <> "/" <> displayBuilder packageName + displayBuilder (DependentsOf namespace packageName mbSearchString) = + "Dependents of " + <> displayBuilder namespace + <> "/" + <> displayBuilder packageName + <> foldMap (\searchString -> " \"" <> Builder.fromText searchString <> "\"") mbSearchString searchPackageByName :: (DB :> es, Log :> es, Time :> es) diff --git a/src/web/FloraWeb/Components/Icons.hs b/src/web/FloraWeb/Components/Icons.hs new file mode 100644 index 00000000..0b4a4847 --- /dev/null +++ b/src/web/FloraWeb/Components/Icons.hs @@ -0,0 +1,57 @@ +{-# LANGUAGE QuasiQuotes #-} + +module FloraWeb.Components.Icons + ( usageInstructionTooltip + , chevronRightOutline + , pen + , lookingGlass + ) where + +import Data.Text (Text) +import Lucid +import Lucid.Svg + ( d_ + , fill_ + , path_ + , stroke_ + , stroke_linecap_ + , stroke_linejoin_ + , stroke_width_ + , viewBox_ + ) +import PyF + +import FloraWeb.Pages.Templates.Types (FloraHTML) + +usageInstructionTooltip :: FloraHTML +usageInstructionTooltip = + toHtmlRaw @Text + [str| + + + +|] + +chevronRightOutline :: FloraHTML +chevronRightOutline = + toHtmlRaw @Text + [str| + + + +|] + +pen :: FloraHTML +pen = + toHtmlRaw @Text + [str| + + + +|] + +lookingGlass :: FloraHTML +lookingGlass = + button_ [type_ "submit"] $ + svg_ [xmlns_ "http://www.w3.org/2000/svg", style_ "color: gray", fill_ "none", viewBox_ "0 0 24 24", stroke_ "currentColor"] $ + path_ [stroke_linecap_ "round", stroke_linejoin_ "round", stroke_width_ "2", d_ "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"] diff --git a/src/web/FloraWeb/Components/MainSearchBar.hs b/src/web/FloraWeb/Components/MainSearchBar.hs new file mode 100644 index 00000000..f31a63ac --- /dev/null +++ b/src/web/FloraWeb/Components/MainSearchBar.hs @@ -0,0 +1,23 @@ +module FloraWeb.Components.MainSearchBar where + +import FloraWeb.Components.Icons +import FloraWeb.Pages.Templates.Types (FloraHTML) +import Lucid + +mainSearchBar :: FloraHTML +mainSearchBar = + form_ [action_ "/search", method_ "GET"] $ do + div_ [class_ "main-search"] $ do + label_ [for_ "search"] "" + input_ + [ class_ + "search-bar" + , type_ "search" + , id_ "search" + , name_ "q" + , placeholder_ "Find a package" + , value_ "" + , tabindex_ "1" + , autofocus_ + ] + lookingGlass diff --git a/src/web/FloraWeb/Components/PaginationNav.hs b/src/web/FloraWeb/Components/PaginationNav.hs index dcf1f4ad..4e432746 100644 --- a/src/web/FloraWeb/Components/PaginationNav.hs +++ b/src/web/FloraWeb/Components/PaginationNav.hs @@ -49,8 +49,8 @@ mkURL (ListAllPackagesInNamespace namespace) pageNumber = "/" <> toUrlPiece (Links.namespaceLink namespace pageNumber) mkURL (SearchPackages searchTerm) pageNumber = "/" <> toUrlPiece (Links.packageSearchLink searchTerm pageNumber) -mkURL (DependentsOf namespace packageName) pageNumber = - "/" <> toUrlPiece (Links.packageDependents namespace packageName pageNumber) +mkURL (DependentsOf namespace packageName mbSearchString) pageNumber = + "/" <> toUrlPiece (Links.packageDependents namespace packageName pageNumber mbSearchString) paginate :: Word diff --git a/src/web/FloraWeb/Components/SlimSearchBar.hs b/src/web/FloraWeb/Components/SlimSearchBar.hs new file mode 100644 index 00000000..0d4685de --- /dev/null +++ b/src/web/FloraWeb/Components/SlimSearchBar.hs @@ -0,0 +1,30 @@ +module FloraWeb.Components.SlimSearchBar (slimSearchBar, SearchBarOptions (..)) where + +import Data.Text (Text) +import FloraWeb.Components.Icons +import FloraWeb.Pages.Templates.Types (FloraHTML) +import Lucid + +data SearchBarOptions = SearchBarOptions + { actionUrl :: Text + , placeholder :: Text + , value :: Text + } + +slimSearchBar :: SearchBarOptions -> FloraHTML +slimSearchBar SearchBarOptions{actionUrl, placeholder, value} = + form_ [action_ actionUrl, method_ "GET"] $! do + div_ [class_ "secondary-search"] $ do + label_ [for_ "search"] "" + input_ + [ class_ + "search-bar" + , type_ "search" + , id_ "search" + , name_ "q" + , placeholder_ placeholder + , value_ value + , tabindex_ "1" + , autofocus_ + ] + lookingGlass diff --git a/src/web/FloraWeb/Components/Utils.hs b/src/web/FloraWeb/Components/Utils.hs index 21e31556..777694d7 100644 --- a/src/web/FloraWeb/Components/Utils.hs +++ b/src/web/FloraWeb/Components/Utils.hs @@ -2,7 +2,7 @@ module FloraWeb.Components.Utils where import Data.Text (Text) import FloraWeb.Pages.Templates.Types (FloraHTML) -import Lucid (Attribute, a_, class_, href_, role_, toHtml) +import Lucid import Lucid.Base (makeAttribute) text :: Text -> FloraHTML diff --git a/src/web/FloraWeb/Links.hs b/src/web/FloraWeb/Links.hs index 7529b679..b2b7f767 100644 --- a/src/web/FloraWeb/Links.hs +++ b/src/web/FloraWeb/Links.hs @@ -82,14 +82,20 @@ packageDependencies namespace packageName version = /: packageName /: version -packageDependents :: Namespace -> PackageName -> Positive Word -> Link -packageDependents namespace packageName pageNumber = +packageDependents + :: Namespace + -> PackageName + -> Positive Word + -> Maybe Text + -> Link +packageDependents namespace packageName pageNumber search = links // Web.packages // Web.showDependents /: namespace /: packageName /: Just pageNumber + /: search packageVersions :: Namespace -> PackageName -> Link packageVersions namespace packageName = diff --git a/src/web/FloraWeb/Pages/Routes/Packages.hs b/src/web/FloraWeb/Pages/Routes/Packages.hs index bc57cdf7..73e54008 100644 --- a/src/web/FloraWeb/Pages/Routes/Packages.hs +++ b/src/web/FloraWeb/Pages/Routes/Packages.hs @@ -38,6 +38,7 @@ data Routes' mode = Routes' :> Capture "package" PackageName :> "dependents" :> QueryParam "page" (Positive Word) + :> QueryParam "q" Text :> Get '[HTML] (Html ()) , showVersionDependents :: mode @@ -46,6 +47,7 @@ data Routes' mode = Routes' :> Capture "version" Version :> "dependents" :> QueryParam "page" (Positive Word) + :> QueryParam "q" Text :> Get '[HTML] (Html ()) , showDependencies :: mode diff --git a/src/web/FloraWeb/Pages/Server/Packages.hs b/src/web/FloraWeb/Pages/Server/Packages.hs index 98adcf73..3316b6d6 100644 --- a/src/web/FloraWeb/Pages/Server/Packages.hs +++ b/src/web/FloraWeb/Pages/Server/Packages.hs @@ -101,7 +101,7 @@ showPackageVersion namespace packageName mversion = do dependents <- Query.getPackageDependents namespace packageName releaseDependencies <- Query.getRequirements release.releaseId categories <- Query.getPackageCategories package.packageId - numberOfDependents <- Query.getNumberOfPackageDependents namespace packageName + numberOfDependents <- Query.getNumberOfPackageDependents namespace packageName Nothing numberOfDependencies <- Query.getNumberOfPackageRequirements release.releaseId let templateEnv = @@ -141,17 +141,30 @@ showPackageVersion namespace packageName mversion = do numberOfDependencies categories -showDependentsHandler :: Namespace -> PackageName -> Maybe (Positive Word) -> FloraPage (Html ()) -showDependentsHandler namespace packageName mPage = do +showDependentsHandler + :: Namespace + -> PackageName + -> Maybe (Positive Word) + -> Maybe Text + -> FloraPage (Html ()) +showDependentsHandler namespace packageName mPage mSearch = do package <- guardThatPackageExists namespace packageName (\_ _ -> web404) releases <- Query.getAllReleases package.packageId let latestRelease = maximumBy (compare `on` (.version)) releases - showVersionDependentsHandler namespace packageName latestRelease.version mPage + showVersionDependentsHandler namespace packageName latestRelease.version mPage mSearch -showVersionDependentsHandler :: Namespace -> PackageName -> Version -> Maybe (Positive Word) -> FloraPage (Html ()) -showVersionDependentsHandler namespace packageName version Nothing = - showVersionDependentsHandler namespace packageName version (Just $ PositiveUnsafe 1) -showVersionDependentsHandler namespace packageName version (Just pageNumber) = do +showVersionDependentsHandler + :: Namespace + -> PackageName + -> Version + -> Maybe (Positive Word) + -> Maybe Text + -> FloraPage (Html ()) +showVersionDependentsHandler namespace packageName version Nothing mSearch = + showVersionDependentsHandler namespace packageName version (Just $ PositiveUnsafe 1) mSearch +showVersionDependentsHandler namespace packageName version pageNumber (Just "") = + showVersionDependentsHandler namespace packageName version pageNumber Nothing +showVersionDependentsHandler namespace packageName version (Just pageNumber) mSearch = do session <- getSession templateEnv' <- fromSession session defaultTemplateEnv package <- guardThatPackageExists namespace packageName (\_ _ -> web404) @@ -161,8 +174,14 @@ showVersionDependentsHandler namespace packageName version (Just pageNumber) = d { title = display namespace <> "/" <> display packageName , description = "Dependents of " <> display namespace <> display packageName } - results <- Query.getAllPackageDependentsWithLatestVersion namespace packageName (fromPage pageNumber) - totalDependents <- Query.getNumberOfPackageDependents namespace packageName + results <- + Query.getAllPackageDependentsWithLatestVersion + namespace + packageName + (fromPage pageNumber) + mSearch + + totalDependents <- Query.getNumberOfPackageDependents namespace packageName mSearch render templateEnv $ Package.showDependents namespace @@ -171,6 +190,7 @@ showVersionDependentsHandler namespace packageName version (Just pageNumber) = d totalDependents results pageNumber + mSearch showDependenciesHandler :: Namespace -> PackageName -> FloraPage (Html ()) showDependenciesHandler namespace packageName = do diff --git a/src/web/FloraWeb/Pages/Templates/Packages.hs b/src/web/FloraWeb/Pages/Templates/Packages.hs index 7858005d..3c2a1638 100644 --- a/src/web/FloraWeb/Pages/Templates/Packages.hs +++ b/src/web/FloraWeb/Pages/Templates/Packages.hs @@ -1,11 +1,11 @@ -{-# LANGUAGE QuasiQuotes #-} - module FloraWeb.Pages.Templates.Packages where import Control.Monad (when) import Control.Monad.Reader (ask) +import Data.Foldable (fold) import Data.List qualified as List import Data.Map.Strict qualified as Map +import Data.Maybe (fromJust, fromMaybe, isJust) import Data.Positive import Data.Text (Text) import Data.Text qualified as Text @@ -16,29 +16,28 @@ import Data.Vector (Vector) import Data.Vector qualified as Vector import Data.Vector.Algorithms.Intro qualified as MVector import Distribution.Orphans () +import Distribution.Pretty (pretty) import Distribution.SPDX.License qualified as SPDX import Distribution.Types.Flag (PackageFlag (..)) import Distribution.Types.Flag qualified as Flag import Distribution.Types.Version (Version, mkVersion, versionNumbers) import Lucid import Lucid.Base -import PyF import Servant (ToHttpApiData (..)) import Text.PrettyPrint (Doc, hcat, render) import Text.PrettyPrint qualified as PP -import Data.Foldable (fold) -import Data.Maybe (fromJust, isJust) -import Distribution.Pretty (pretty) import Flora.Environment (FeatureEnv (..)) import Flora.Model.Category.Types import Flora.Model.Package import Flora.Model.Release.Types import Flora.Model.Requirement import Flora.Search (SearchAction (..)) +import FloraWeb.Components.Icons import FloraWeb.Components.PackageListItem (licenseIcon, packageListItem, requirementListItem) import FloraWeb.Components.PaginationNav (paginationNav) -import FloraWeb.Components.Utils (text) +import FloraWeb.Components.SlimSearchBar +import FloraWeb.Components.Utils import FloraWeb.Links qualified as Links import FloraWeb.Pages.Templates (FloraHTML, TemplateEnv (..)) import FloraWeb.Pages.Templates.Haddock (renderHaddock) @@ -101,19 +100,28 @@ showDependents -> Word -> Vector DependencyInfo -> Positive Word + -> Maybe Text -> FloraHTML -showDependents namespace packageName release count packagesInfo currentPage = +showDependents namespace packageName release count packagesInfo currentPage mSearch = div_ [class_ "container"] $ do presentationHeaderForSubpage namespace packageName release Dependents count - div_ [class_ ""] $ do - ul_ [class_ "package-list"] $ - Vector.forM_ - packagesInfo - ( \dep -> - packageListItem (dep.namespace, dep.name, dep.latestSynopsis, dep.latestVersion, dep.latestLicense) - ) - when (count > 30) $ - paginationNav count currentPage (DependentsOf namespace packageName) + let placeholder = fromMaybe "Search dependents" mSearch + let value = fromMaybe "" mSearch + ul_ [class_ "package-list"] $ do + slimSearchBar (SearchBarOptions{actionUrl = "", placeholder, value}) + Vector.forM_ + packagesInfo + ( \dep -> + packageListItem + ( dep.namespace + , dep.name + , dep.latestSynopsis + , dep.latestVersion + , dep.latestLicense + ) + ) + when (count > 30) $ + paginationNav count currentPage (DependentsOf namespace packageName Nothing) showDependencies :: Namespace -> PackageName -> Release -> ComponentDependencies -> FloraHTML showDependencies namespace packageName release componentsInfo = do @@ -270,8 +278,7 @@ displayVersions namespace packageName versions numberOfReleases = span_ [] $ do toHtml $ Time.formatTime defaultTimeLocale "%a, %_d %b %Y" ts case release.revisedAt of - Nothing -> do - span_ [] "" + Nothing -> span_ [] "" Just revisionDate -> do span_ [ dataText_ @@ -299,7 +306,7 @@ displayDependencies (namespace, packageName, version) numberOfDependencies depen showAll :: Target -> Maybe Version -> Namespace -> PackageName -> FloraHTML showAll target mVersion namespace packageName = do let resource = case target of - Dependents -> Links.packageDependents namespace packageName (PositiveUnsafe 1) + Dependents -> Links.packageDependents namespace packageName (PositiveUnsafe 1) Nothing Dependencies -> Links.packageDependencies namespace packageName (fromJust mVersion) Versions -> Links.packageVersions namespace packageName a_ [class_ "dependency", href_ ("/" <> toUrlPiece resource)] "Show all…" @@ -454,35 +461,6 @@ defaultMarker :: Bool -> FloraHTML defaultMarker True = em_ "(on by default)" defaultMarker False = em_ "(off by default)" ---- - -usageInstructionTooltip :: FloraHTML -usageInstructionTooltip = - toHtmlRaw @Text - [str| - - - -|] - -chevronRightOutline :: FloraHTML -chevronRightOutline = - toHtmlRaw @Text - [str| - - - -|] - -pen :: FloraHTML -pen = - toHtmlRaw @Text - [str| - - - -|] - -- | @datalist@ element dataText_ :: Text -> Attribute dataText_ = makeAttribute "data-text" diff --git a/src/web/FloraWeb/Pages/Templates/Pages/Home.hs b/src/web/FloraWeb/Pages/Templates/Pages/Home.hs index d8390549..358f58f4 100644 --- a/src/web/FloraWeb/Pages/Templates/Pages/Home.hs +++ b/src/web/FloraWeb/Pages/Templates/Pages/Home.hs @@ -6,26 +6,17 @@ import CMarkGFM import Control.Monad.Reader import Data.Text (Text) import Lucid -import Lucid.Svg - ( d_ - , fill_ - , path_ - , stroke_ - , stroke_linecap_ - , stroke_linejoin_ - , stroke_width_ - , viewBox_ - ) import PyF import Flora.Environment +import FloraWeb.Components.MainSearchBar (mainSearchBar) import FloraWeb.Pages.Templates.Types show :: FloraHTML show = do banner div_ [class_ "container-small"] $ do - searchBar + mainSearchBar buttons banner :: FloraHTML @@ -34,26 +25,6 @@ banner = do h1_ [class_ "main-title"] $ span_ [class_ "main-title"] "Search Haskell packages on Flora" -searchBar :: FloraHTML -searchBar = - form_ [action_ "/search", method_ "GET"] $ do - div_ [class_ "main-search"] $ do - label_ [for_ "search"] "" - input_ - [ class_ - "search-bar" - , type_ "search" - , id_ "search" - , name_ "q" - , placeholder_ "Find a package" - , value_ "" - , tabindex_ "1" - , autofocus_ - ] - button_ [type_ "submit"] $ - svg_ [xmlns_ "http://www.w3.org/2000/svg", style_ "color: gray", fill_ "none", viewBox_ "0 0 24 24", stroke_ "currentColor"] $ - path_ [stroke_linecap_ "round", stroke_linejoin_ "round", stroke_width_ "2", d_ "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"] - buttons :: FloraHTML buttons = section_ [id_ "main-page-buttons"] $ do diff --git a/src/web/FloraWeb/Pages/Templates/Pages/Packages.hs b/src/web/FloraWeb/Pages/Templates/Pages/Packages.hs index 2225bf55..7757a16a 100644 --- a/src/web/FloraWeb/Pages/Templates/Pages/Packages.hs +++ b/src/web/FloraWeb/Pages/Templates/Pages/Packages.hs @@ -13,9 +13,9 @@ import Lucid.Orphans () import Flora.Model.Category.Types (Category (..)) import Flora.Model.Package.Types import Flora.Model.Release.Types (Release (..)) +import FloraWeb.Components.Icons import FloraWeb.Pages.Templates.Packages - ( chevronRightOutline - , displayCategories + ( displayCategories , displayDependencies , displayDependents , displayInstructions diff --git a/test/Flora/PackageSpec.hs b/test/Flora/PackageSpec.hs index 649d5254..d5912cfc 100644 --- a/test/Flora/PackageSpec.hs +++ b/test/Flora/PackageSpec.hs @@ -135,7 +135,7 @@ testCorrectNumberInHaskellNamespace = do testBytestringDependents :: TestEff () testBytestringDependents = do - results <- Query.getAllPackageDependentsWithLatestVersion (Namespace "haskell") (PackageName "bytestring") (0, 30) + results <- Query.getAllPackageDependentsWithLatestVersion (Namespace "haskell") (PackageName "bytestring") (0, 30) Nothing assertEqual 24 (Vector.length results)