add style to the search result
This commit is contained in:
parent
f19216fe5a
commit
40bd247ee9
|
@ -54,6 +54,7 @@ def get_packages(evaluation):
|
|||
check=True,
|
||||
)
|
||||
packages = json.loads(result.stdout).items()
|
||||
packages = list(packages)[:10]
|
||||
|
||||
def gen():
|
||||
for attr_name, data in packages:
|
||||
|
@ -87,7 +88,14 @@ def get_packages(evaluation):
|
|||
)
|
||||
for maintainer in data["meta"].get("maintainers", [])
|
||||
]
|
||||
platforms = [
|
||||
type(platform) == str
|
||||
and platform
|
||||
or None
|
||||
for platform in data["meta"].get("platforms", [])
|
||||
]
|
||||
yield dict(
|
||||
id=attr_name,
|
||||
attr_name=attr_name,
|
||||
name=data["pname"],
|
||||
version=data["version"],
|
||||
|
@ -95,6 +103,7 @@ def get_packages(evaluation):
|
|||
longDescription=data["meta"].get("longDescription", ""),
|
||||
license=licenses,
|
||||
maintainers=maintainers,
|
||||
platforms=[i for i in platforms if i],
|
||||
position=position,
|
||||
homepage=data["meta"].get("homepage"),
|
||||
)
|
||||
|
@ -114,6 +123,7 @@ def get_options(evaluation):
|
|||
if os.path.exists(options_file):
|
||||
with open(options_file) as f:
|
||||
options = json.load(f).items()
|
||||
options = list(options)[:10]
|
||||
|
||||
def gen():
|
||||
for name, option in options:
|
||||
|
@ -123,6 +133,7 @@ def get_options(evaluation):
|
|||
example.get("_type") == "literalExample":
|
||||
example = str(example["text"])
|
||||
yield dict(
|
||||
id=name,
|
||||
option_name=name,
|
||||
description=option.get("description"),
|
||||
type=option.get("type"),
|
||||
|
@ -133,6 +144,7 @@ def get_options(evaluation):
|
|||
|
||||
return len(options), gen
|
||||
|
||||
|
||||
def recreate_index(es, channel):
|
||||
if es.indices.exists(f"{channel}-packages"):
|
||||
es.indices.delete(index=f"{channel}-packages")
|
||||
|
@ -162,6 +174,7 @@ def recreate_index(es, channel):
|
|||
github=dict(type="text"),
|
||||
),
|
||||
),
|
||||
platforms=dict(type="keyword"),
|
||||
position=dict(type="text"),
|
||||
homepage=dict(type="text"),
|
||||
),
|
||||
|
|
288
src/Main.elm
288
src/Main.elm
|
@ -6,19 +6,33 @@ import Browser.Navigation as Nav exposing (Key)
|
|||
import Html
|
||||
exposing
|
||||
( Html
|
||||
, a
|
||||
, button
|
||||
, div
|
||||
, footer
|
||||
, form
|
||||
, h1
|
||||
, header
|
||||
, img
|
||||
, input
|
||||
, li
|
||||
, p
|
||||
, pre
|
||||
, table
|
||||
, tbody
|
||||
, td
|
||||
, text
|
||||
, th
|
||||
, thead
|
||||
, tr
|
||||
, ul
|
||||
)
|
||||
import Html.Attributes
|
||||
exposing
|
||||
( class
|
||||
, colspan
|
||||
, href
|
||||
, src
|
||||
, type_
|
||||
, value
|
||||
)
|
||||
|
@ -26,6 +40,7 @@ import Html.Events
|
|||
exposing
|
||||
( onClick
|
||||
, onInput
|
||||
, onSubmit
|
||||
)
|
||||
import Http
|
||||
import Json.Decode as D
|
||||
|
@ -33,6 +48,7 @@ import Json.Decode.Pipeline as DP
|
|||
import Json.Encode as E
|
||||
import RemoteData as R
|
||||
import Url exposing (Url)
|
||||
import Url.Builder as UrlBuilder
|
||||
import Url.Parser as UrlParser
|
||||
exposing
|
||||
( (<?>)
|
||||
|
@ -64,8 +80,9 @@ type alias Model =
|
|||
|
||||
|
||||
type alias SearchModel =
|
||||
{ query : String
|
||||
{ query : Maybe String
|
||||
, result : R.WebData SearchResult
|
||||
, showDetailsFor : Maybe String
|
||||
}
|
||||
|
||||
|
||||
|
@ -117,6 +134,7 @@ type alias SearchResultPackage =
|
|||
, longDescription : Maybe String
|
||||
, licenses : List SearchResultPackageLicense
|
||||
, maintainers : List SearchResultPackageMaintainer
|
||||
, platforms : List String
|
||||
, position : Maybe String
|
||||
, homepage : Maybe String
|
||||
}
|
||||
|
@ -133,7 +151,7 @@ type alias SearchResultOption =
|
|||
|
||||
|
||||
type alias SearchResultPackageLicense =
|
||||
{ fullName : String
|
||||
{ fullName : Maybe String
|
||||
, url : Maybe String
|
||||
}
|
||||
|
||||
|
@ -147,21 +165,77 @@ type alias SearchResultPackageMaintainer =
|
|||
|
||||
emptySearch : Page
|
||||
emptySearch =
|
||||
SearchPage { query = "", result = R.NotAsked }
|
||||
SearchPage
|
||||
{ query = Nothing
|
||||
, result = R.NotAsked
|
||||
, showDetailsFor = Nothing
|
||||
}
|
||||
|
||||
|
||||
init : Flags -> Url -> Key -> ( Model, Cmd Msg )
|
||||
init flags url key =
|
||||
( { key = key
|
||||
let
|
||||
model =
|
||||
{ key = key
|
||||
, elasticsearchUrl = flags.elasticsearchUrl
|
||||
, elasticsearchUsername = flags.elasticsearchUsername
|
||||
, elasticsearchPassword = flags.elasticsearchPassword
|
||||
, page = UrlParser.parse urlParser url |> Maybe.withDefault emptySearch
|
||||
}
|
||||
, Cmd.none
|
||||
in
|
||||
( model
|
||||
, initPageCmd model model
|
||||
)
|
||||
|
||||
|
||||
initPageCmd : Model -> Model -> Cmd Msg
|
||||
initPageCmd oldModel model =
|
||||
let
|
||||
makeRequest query =
|
||||
Http.riskyRequest
|
||||
{ method = "POST"
|
||||
, headers =
|
||||
[ Http.header "Authorization" ("Basic " ++ Base64.encode (model.elasticsearchUsername ++ ":" ++ model.elasticsearchPassword))
|
||||
]
|
||||
, url = model.elasticsearchUrl ++ "/nixos-unstable-packages/_search"
|
||||
, body =
|
||||
Http.jsonBody <|
|
||||
E.object
|
||||
[ ( "query"
|
||||
, E.object
|
||||
[ ( "match"
|
||||
, E.object
|
||||
[ ( "attr_name"
|
||||
, E.object
|
||||
[ ( "query", E.string query )
|
||||
|
||||
-- I'm not sure we need fuziness
|
||||
--, ( "fuzziness", E.int 1 )
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
]
|
||||
, expect = Http.expectJson (R.fromResult >> SearchQueryResponse) decodeResult
|
||||
, timeout = Nothing
|
||||
, tracker = Nothing
|
||||
}
|
||||
in
|
||||
case oldModel.page of
|
||||
SearchPage oldSearchModel ->
|
||||
case model.page of
|
||||
SearchPage searchModel ->
|
||||
if (oldSearchModel.query == searchModel.query) && R.isSuccess oldSearchModel.result then
|
||||
Cmd.none
|
||||
|
||||
else
|
||||
searchModel.query
|
||||
|> Maybe.map makeRequest
|
||||
|> Maybe.withDefault Cmd.none
|
||||
|
||||
|
||||
|
||||
-- ---------------------------
|
||||
-- URL Parsing and Routing
|
||||
|
@ -182,13 +256,14 @@ urlParser : Parser (Page -> msg) msg
|
|||
urlParser =
|
||||
UrlParser.oneOf
|
||||
[ UrlParser.map
|
||||
(\q ->
|
||||
(\query showDetailsFor ->
|
||||
SearchPage
|
||||
{ query = q |> Maybe.withDefault ""
|
||||
{ query = query
|
||||
, result = R.NotAsked
|
||||
, showDetailsFor = showDetailsFor
|
||||
}
|
||||
)
|
||||
(UrlParser.s "search" <?> UrlParserQuery.string "query")
|
||||
(UrlParser.s "search" <?> UrlParserQuery.string "query" <?> UrlParserQuery.string "showDetailsFor")
|
||||
]
|
||||
|
||||
|
||||
|
@ -204,6 +279,7 @@ type Msg
|
|||
| SearchPageInput String
|
||||
| SearchQuerySubmit
|
||||
| SearchQueryResponse (R.WebData SearchResult)
|
||||
| SearchShowPackageDetails String
|
||||
|
||||
|
||||
decodeResult : D.Decoder SearchResult
|
||||
|
@ -255,6 +331,7 @@ decodeResultPackage =
|
|||
|> DP.required "longDescription" (D.nullable D.string)
|
||||
|> DP.required "license" (D.list decodeResultPackageLicense)
|
||||
|> DP.required "maintainers" (D.list decodeResultPackageMaintainer)
|
||||
|> DP.required "platforms" (D.list D.string)
|
||||
|> DP.required "position" (D.nullable D.string)
|
||||
|> DP.required "homepage" (D.nullable D.string)
|
||||
|
||||
|
@ -262,7 +339,7 @@ decodeResultPackage =
|
|||
decodeResultPackageLicense : D.Decoder SearchResultPackageLicense
|
||||
decodeResultPackageLicense =
|
||||
D.map2 SearchResultPackageLicense
|
||||
(D.field "fullName" D.string)
|
||||
(D.field "fullName" (D.nullable D.string))
|
||||
(D.field "url" (D.nullable D.string))
|
||||
|
||||
|
||||
|
@ -285,44 +362,6 @@ decodeResultOption =
|
|||
(D.field "source" D.string)
|
||||
|
||||
|
||||
initPage : Model -> Cmd Msg
|
||||
initPage model =
|
||||
case model.page of
|
||||
SearchPage searchModel ->
|
||||
if searchModel.query == "" then
|
||||
Cmd.none
|
||||
|
||||
else
|
||||
Http.riskyRequest
|
||||
{ method = "POST"
|
||||
, headers =
|
||||
[ Http.header "Authorization" ("Basic " ++ Base64.encode (model.elasticsearchUsername ++ ":" ++ model.elasticsearchPassword))
|
||||
]
|
||||
, url = model.elasticsearchUrl ++ "/nixos-unstable-packages/_search"
|
||||
, body =
|
||||
Http.jsonBody <|
|
||||
E.object
|
||||
[ ( "query"
|
||||
, E.object
|
||||
[ ( "match"
|
||||
, E.object
|
||||
[ ( "name"
|
||||
, E.object
|
||||
[ ( "query", E.string searchModel.query )
|
||||
, ( "fuzziness", E.int 1 )
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
]
|
||||
, expect = Http.expectJson (R.fromResult >> SearchQueryResponse) decodeResult
|
||||
, timeout = Nothing
|
||||
, tracker = Nothing
|
||||
}
|
||||
|
||||
|
||||
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||
update message model =
|
||||
case message of
|
||||
|
@ -340,18 +379,19 @@ update message model =
|
|||
SearchPage
|
||||
{ searchModel
|
||||
| result =
|
||||
if searchModel.query == "" then
|
||||
R.NotAsked
|
||||
|
||||
else
|
||||
case searchModel.query of
|
||||
Just query ->
|
||||
R.Loading
|
||||
|
||||
Nothing ->
|
||||
R.NotAsked
|
||||
}
|
||||
|
||||
newNewModel =
|
||||
{ newModel | page = newPage }
|
||||
in
|
||||
( newNewModel
|
||||
, initPage newNewModel
|
||||
, initPageCmd newModel newNewModel
|
||||
)
|
||||
|
||||
SearchPageInput query ->
|
||||
|
@ -359,7 +399,7 @@ update message model =
|
|||
| page =
|
||||
case model.page of
|
||||
SearchPage searchModel ->
|
||||
SearchPage { searchModel | query = query }
|
||||
SearchPage { searchModel | query = Just query }
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
@ -368,7 +408,7 @@ update message model =
|
|||
case model.page of
|
||||
SearchPage searchModel ->
|
||||
( model
|
||||
, Nav.pushUrl model.key <| "/search?query=" ++ searchModel.query
|
||||
, Nav.pushUrl model.key <| createSearchUrl searchModel
|
||||
)
|
||||
|
||||
SearchQueryResponse result ->
|
||||
|
@ -382,6 +422,47 @@ update message model =
|
|||
, Cmd.none
|
||||
)
|
||||
|
||||
SearchShowPackageDetails showDetailsFor ->
|
||||
case model.page of
|
||||
SearchPage searchModel ->
|
||||
let
|
||||
newSearchModel =
|
||||
{ searchModel
|
||||
| showDetailsFor =
|
||||
if searchModel.showDetailsFor == Just showDetailsFor then
|
||||
Nothing
|
||||
|
||||
else
|
||||
Just showDetailsFor
|
||||
}
|
||||
in
|
||||
( model
|
||||
, Nav.pushUrl model.key <| createSearchUrl newSearchModel
|
||||
)
|
||||
|
||||
|
||||
createSearchUrl : SearchModel -> String
|
||||
createSearchUrl model =
|
||||
[]
|
||||
|> List.append
|
||||
(model.query
|
||||
|> Maybe.map
|
||||
(\query ->
|
||||
[ UrlBuilder.string "query" query ]
|
||||
)
|
||||
|> Maybe.withDefault []
|
||||
)
|
||||
|> List.append
|
||||
(model.showDetailsFor
|
||||
|> Maybe.map
|
||||
(\x ->
|
||||
[ UrlBuilder.string "showDetailsFor" x
|
||||
]
|
||||
)
|
||||
|> Maybe.withDefault []
|
||||
)
|
||||
|> UrlBuilder.absolute [ "search" ]
|
||||
|
||||
|
||||
|
||||
-- ---------------------------
|
||||
|
@ -391,27 +472,54 @@ update message model =
|
|||
|
||||
view : Model -> Html Msg
|
||||
view model =
|
||||
div [ class "container" ]
|
||||
div []
|
||||
[ header []
|
||||
[ h1 [] [ text "NixOS Search" ]
|
||||
[ div [ class "navbar navbar-static-top" ]
|
||||
[ div [ class "navbar-inner" ]
|
||||
[ div [ class "container" ]
|
||||
[ a [ class "brand", href "https://search.nixos.org" ]
|
||||
[ img [ src "https://nixos.org/logo/nix-wiki.png", class "logo" ] []
|
||||
]
|
||||
, case model.page of
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
, div [ class "container main" ]
|
||||
[ case model.page of
|
||||
SearchPage searchModel ->
|
||||
searchPage searchModel
|
||||
, footer [] []
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
searchPage : SearchModel -> Html Msg
|
||||
searchPage model =
|
||||
div []
|
||||
[ div []
|
||||
div [ class "search-page" ]
|
||||
[ h1 [ class "page-header" ] [ text "Search for packages and options" ]
|
||||
, div [ class "search-input" ]
|
||||
[ form [ onSubmit SearchQuerySubmit ]
|
||||
[ div [ class "input-append" ]
|
||||
[ input
|
||||
[ type_ "text"
|
||||
, onInput SearchPageInput
|
||||
, value model.query
|
||||
, value <| Maybe.withDefault "" model.query
|
||||
]
|
||||
[]
|
||||
, button [ onClick SearchQuerySubmit ] [ text "Search" ]
|
||||
, div [ class "btn-group" ]
|
||||
[ button [ class "btn" ] [ text "Search" ]
|
||||
|
||||
--TODO: and option to select the right channel+version+evaluation
|
||||
--, button [ class "btn" ] [ text "Loading ..." ]
|
||||
--, div [ class "popover bottom" ]
|
||||
-- [ div [ class "arrow" ] []
|
||||
-- , div [ class "popover-title" ] [ text "Select options" ]
|
||||
-- , div [ class "popover-content" ]
|
||||
-- [ p [] [ text "Sed posuere consectetur est at lobortis. Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum." ] ]
|
||||
-- ]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
, case model.result of
|
||||
R.NotAsked ->
|
||||
|
@ -421,26 +529,60 @@ searchPage model =
|
|||
div [] [ text "Loading" ]
|
||||
|
||||
R.Success result ->
|
||||
ul [] (searchPageResult result.hits)
|
||||
searchPageResult model.showDetailsFor result.hits
|
||||
|
||||
R.Failure error ->
|
||||
div [] [ text "Error!", pre [] [ text (Debug.toString error) ] ]
|
||||
]
|
||||
|
||||
|
||||
searchPageResult : SearchResultHits -> List (Html Msg)
|
||||
searchPageResult result =
|
||||
List.map searchPageResultItem result.hits
|
||||
searchPageResult : Maybe String -> SearchResultHits -> Html Msg
|
||||
searchPageResult showDetailsFor result =
|
||||
div [ class "search-result" ]
|
||||
[ table [ class "table table-hover" ]
|
||||
[ thead []
|
||||
[ tr []
|
||||
[ th [] [ text "Attribute name" ]
|
||||
, th [] [ text "Name" ]
|
||||
, th [] [ text "Version" ]
|
||||
, th [] [ text "Description" ]
|
||||
]
|
||||
]
|
||||
, tbody [] <| List.concatMap (searchPageResultItem showDetailsFor) result.hits
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
searchPageResultItem : SearchResultItem -> Html Msg
|
||||
searchPageResultItem item =
|
||||
-- case item.source of
|
||||
-- Package package ->
|
||||
-- li [] [ text package.attr_name ]
|
||||
-- Option option ->
|
||||
-- li [] [ text option.option_name ]
|
||||
li [] [ text <| Debug.toString item ]
|
||||
searchPageResultItem : Maybe String -> SearchResultItem -> List (Html Msg)
|
||||
searchPageResultItem showDetailsFor item =
|
||||
case item.source of
|
||||
Package package ->
|
||||
let
|
||||
packageDetails =
|
||||
if Just item.id == showDetailsFor then
|
||||
[ td [ colspan 4 ] [ text "works!" ] ]
|
||||
|
||||
else
|
||||
[]
|
||||
in
|
||||
[ tr [ onClick <| SearchShowPackageDetails item.id ]
|
||||
[ td [] [ text package.attr_name ]
|
||||
, td [] [ text package.name ]
|
||||
, td [] [ text package.version ]
|
||||
, td [] [ text <| Maybe.withDefault "" package.description ]
|
||||
]
|
||||
]
|
||||
++ packageDetails
|
||||
|
||||
Option option ->
|
||||
[ tr
|
||||
[]
|
||||
[-- td [] [ text option.option_name ]
|
||||
--, td [] [ text option.name ]
|
||||
--, td [] [ text option.version ]
|
||||
--, td [] [ text option.description ]
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,11 +1,28 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Elm hotloading dev environment</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
</body>
|
||||
|
||||
<script type="text/javascript" src="https://nixos.org/js/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="https://nixos.org/js/jquery-ui.min.js"></script>
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
||||
<script type="text/javascript" src="https://nixos.org/bootstrap/js/bootstrap.min.js"></script>
|
||||
<link rel="stylesheet" href="https://nixos.org/bootstrap/css/bootstrap.min.css" />
|
||||
|
||||
<link rel="stylesheet" href="https://nixos.org/bootstrap/css/bootstrap-responsive.min.css" />
|
||||
|
||||
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" />
|
||||
|
||||
<link rel="shortcut icon" type="image/png" href="https://nixos.org/favicon.png" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
|
@ -1,30 +1,27 @@
|
|||
@import '~purecss/build/pure.css';
|
||||
|
||||
body {
|
||||
background-color: #ddd;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
img {
|
||||
width: 60px;
|
||||
margin-right: 30px;
|
||||
header .navbar a.brand {
|
||||
line-height: 1.5em;
|
||||
img.logo {
|
||||
height: 1.5em;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
background: url('images/logo.png');
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
background-size: cover;
|
||||
.search-page {
|
||||
.search-input {
|
||||
text-align: center;
|
||||
.input-append input {
|
||||
font-size: 24px;
|
||||
height: 40px;
|
||||
}
|
||||
.input-append button {
|
||||
font-size: 24px;
|
||||
height: 50px;
|
||||
}
|
||||
}
|
||||
.search-result {
|
||||
tbody tr {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue