somehow in a working starte with bugs and missing features
This commit is contained in:
parent
f0318458d8
commit
387d70eaa3
|
@ -54,7 +54,7 @@ def get_packages(evaluation):
|
|||
check=True,
|
||||
)
|
||||
packages = json.loads(result.stdout).items()
|
||||
packages = list(packages)[:10]
|
||||
packages = list(packages)
|
||||
|
||||
def gen():
|
||||
for attr_name, data in packages:
|
||||
|
@ -123,7 +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]
|
||||
options = list(options)
|
||||
|
||||
def gen():
|
||||
for name, option in options:
|
||||
|
|
315
src/ElasticSearch.elm
Normal file
315
src/ElasticSearch.elm
Normal file
|
@ -0,0 +1,315 @@
|
|||
module ElasticSearch exposing
|
||||
( Model
|
||||
, Msg(..)
|
||||
, Options
|
||||
, Result
|
||||
, ResultItem
|
||||
, decodeResult
|
||||
, init
|
||||
, makeRequest
|
||||
, showLoadingOnQuery
|
||||
, update
|
||||
, view
|
||||
)
|
||||
|
||||
import Base64
|
||||
import Browser.Navigation
|
||||
import Html
|
||||
exposing
|
||||
( Html
|
||||
, button
|
||||
, div
|
||||
, form
|
||||
, h1
|
||||
, input
|
||||
, text
|
||||
)
|
||||
import Html.Attributes
|
||||
exposing
|
||||
( class
|
||||
, type_
|
||||
, value
|
||||
)
|
||||
import Html.Events
|
||||
exposing
|
||||
( onInput
|
||||
, onSubmit
|
||||
)
|
||||
import Http
|
||||
import Json.Decode
|
||||
import Json.Encode
|
||||
import RemoteData
|
||||
import Url.Builder
|
||||
|
||||
|
||||
type alias Model a =
|
||||
{ query : Maybe String
|
||||
, result : RemoteData.WebData (Result a)
|
||||
, showDetailsFor : Maybe String
|
||||
}
|
||||
|
||||
|
||||
type alias Result a =
|
||||
{ hits : ResultHits a
|
||||
}
|
||||
|
||||
|
||||
type alias ResultHits a =
|
||||
{ total : ResultHitsTotal
|
||||
, max_score : Maybe Float
|
||||
, hits : List (ResultItem a)
|
||||
}
|
||||
|
||||
|
||||
type alias ResultHitsTotal =
|
||||
{ value : Int
|
||||
, relation : String -- TODO: this should probably be Enum
|
||||
}
|
||||
|
||||
|
||||
type alias ResultItem a =
|
||||
{ index : String
|
||||
, id : String
|
||||
, score : Float
|
||||
, source : a
|
||||
}
|
||||
|
||||
|
||||
init :
|
||||
Maybe String
|
||||
-> Maybe String
|
||||
-> ( Model a, Cmd msg )
|
||||
init query showDetailsFor =
|
||||
( { query = query
|
||||
, result = RemoteData.NotAsked
|
||||
, showDetailsFor = showDetailsFor
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
|
||||
|
||||
-- ---------------------------
|
||||
-- UPDATE
|
||||
-- ---------------------------
|
||||
|
||||
|
||||
type Msg a
|
||||
= QueryInput String
|
||||
| QuerySubmit
|
||||
| QueryResponse (RemoteData.WebData (Result a))
|
||||
| ShowDetails String
|
||||
|
||||
|
||||
update :
|
||||
String
|
||||
-> Browser.Navigation.Key
|
||||
-> Msg a
|
||||
-> Model a
|
||||
-> ( Model a, Cmd (Msg a) )
|
||||
update path navKey msg model =
|
||||
case msg of
|
||||
QueryInput query ->
|
||||
( { model | query = Just query }
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
QuerySubmit ->
|
||||
( model
|
||||
, createUrl path model.query model.showDetailsFor
|
||||
|> Browser.Navigation.pushUrl navKey
|
||||
)
|
||||
|
||||
QueryResponse result ->
|
||||
( { model | result = result }
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
ShowDetails selected ->
|
||||
( { model | showDetailsFor = Just selected }
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
|
||||
showLoadingOnQuery : Model a -> Model a
|
||||
showLoadingOnQuery model =
|
||||
-- TODO: use this
|
||||
{ model
|
||||
| result =
|
||||
case model.query of
|
||||
Just query ->
|
||||
RemoteData.Loading
|
||||
|
||||
Nothing ->
|
||||
RemoteData.NotAsked
|
||||
}
|
||||
|
||||
|
||||
createUrl : String -> Maybe String -> Maybe String -> String
|
||||
createUrl path query showDetailsFor =
|
||||
[]
|
||||
|> List.append
|
||||
(query
|
||||
|> Maybe.map
|
||||
(\x ->
|
||||
[ Url.Builder.string "query" x ]
|
||||
)
|
||||
|> Maybe.withDefault []
|
||||
)
|
||||
|> List.append
|
||||
(showDetailsFor
|
||||
|> Maybe.map
|
||||
(\x ->
|
||||
[ Url.Builder.string "showDetailsFor" x
|
||||
]
|
||||
)
|
||||
|> Maybe.withDefault []
|
||||
)
|
||||
|> Url.Builder.absolute [ path ]
|
||||
|
||||
|
||||
|
||||
-- VIEW
|
||||
|
||||
|
||||
view :
|
||||
{ title : String }
|
||||
-> Model a
|
||||
-> (Maybe String -> Result a -> Html b)
|
||||
-> (Msg a -> b)
|
||||
-> Html b
|
||||
view options model viewSuccess outMsg =
|
||||
div [ class "search-page" ]
|
||||
[ h1 [ class "page-header" ] [ text options.title ]
|
||||
, div [ class "search-input" ]
|
||||
[ form [ onSubmit (outMsg QuerySubmit) ]
|
||||
[ div [ class "input-append" ]
|
||||
[ input
|
||||
[ type_ "text"
|
||||
, onInput (\x -> outMsg (QueryInput x))
|
||||
, value <| Maybe.withDefault "" model.query
|
||||
]
|
||||
[]
|
||||
, 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
|
||||
RemoteData.NotAsked ->
|
||||
div [] [ text "NotAsked" ]
|
||||
|
||||
RemoteData.Loading ->
|
||||
div [] [ text "Loading" ]
|
||||
|
||||
RemoteData.Success result ->
|
||||
viewSuccess model.showDetailsFor result
|
||||
|
||||
RemoteData.Failure error ->
|
||||
div []
|
||||
[ text "Error!"
|
||||
|
||||
--, pre [] [ text (Debug.toString error) ]
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
|
||||
-- API
|
||||
|
||||
|
||||
type alias Options =
|
||||
{ url : String
|
||||
, username : String
|
||||
, password : String
|
||||
}
|
||||
|
||||
|
||||
makeRequestBody : String -> String -> Http.Body
|
||||
makeRequestBody field query =
|
||||
let
|
||||
stringIn name value =
|
||||
[ ( name, Json.Encode.string value ) ]
|
||||
|
||||
objectIn name object =
|
||||
[ ( name, Json.Encode.object object ) ]
|
||||
in
|
||||
-- I'm not sure we need fuziness
|
||||
--, ( "fuzziness", Json.Encode.int 1 )
|
||||
query
|
||||
|> stringIn "query"
|
||||
|> objectIn field
|
||||
|> objectIn "match"
|
||||
|> objectIn "query"
|
||||
|> Json.Encode.object
|
||||
|> Http.jsonBody
|
||||
|
||||
|
||||
makeRequest :
|
||||
String
|
||||
-> String
|
||||
-> Json.Decode.Decoder a
|
||||
-> Options
|
||||
-> String
|
||||
-> Cmd (Msg a)
|
||||
makeRequest field index decodeResultItemSource options query =
|
||||
Http.riskyRequest
|
||||
{ method = "POST"
|
||||
, headers =
|
||||
[ Http.header "Authorization" ("Basic " ++ Base64.encode (options.username ++ ":" ++ options.password))
|
||||
]
|
||||
, url = options.url ++ "/" ++ index ++ "/_search"
|
||||
, body = makeRequestBody field query
|
||||
, expect =
|
||||
Http.expectJson
|
||||
(RemoteData.fromResult >> QueryResponse)
|
||||
(decodeResult decodeResultItemSource)
|
||||
, timeout = Nothing
|
||||
, tracker = Nothing
|
||||
}
|
||||
|
||||
|
||||
|
||||
-- JSON
|
||||
|
||||
|
||||
decodeResult :
|
||||
Json.Decode.Decoder a
|
||||
-> Json.Decode.Decoder (Result a)
|
||||
decodeResult decodeResultItemSource =
|
||||
Json.Decode.map Result
|
||||
(Json.Decode.field "hits" (decodeResultHits decodeResultItemSource))
|
||||
|
||||
|
||||
decodeResultHits : Json.Decode.Decoder a -> Json.Decode.Decoder (ResultHits a)
|
||||
decodeResultHits decodeResultItemSource =
|
||||
Json.Decode.map3 ResultHits
|
||||
(Json.Decode.field "total" decodeResultHitsTotal)
|
||||
(Json.Decode.field "max_score" (Json.Decode.nullable Json.Decode.float))
|
||||
(Json.Decode.field "hits" (Json.Decode.list (decodeResultItem decodeResultItemSource)))
|
||||
|
||||
|
||||
decodeResultHitsTotal : Json.Decode.Decoder ResultHitsTotal
|
||||
decodeResultHitsTotal =
|
||||
Json.Decode.map2 ResultHitsTotal
|
||||
(Json.Decode.field "value" Json.Decode.int)
|
||||
(Json.Decode.field "relation" Json.Decode.string)
|
||||
|
||||
|
||||
decodeResultItem : Json.Decode.Decoder a -> Json.Decode.Decoder (ResultItem a)
|
||||
decodeResultItem decodeResultItemSource =
|
||||
Json.Decode.map4 ResultItem
|
||||
(Json.Decode.field "_index" Json.Decode.string)
|
||||
(Json.Decode.field "_id" Json.Decode.string)
|
||||
(Json.Decode.field "_score" Json.Decode.float)
|
||||
(Json.Decode.field "_source" decodeResultItemSource)
|
685
src/Main.elm
685
src/Main.elm
|
@ -1,66 +1,39 @@
|
|||
module Main exposing (main)
|
||||
|
||||
import Base64
|
||||
import Browser exposing (UrlRequest(..))
|
||||
import Browser.Navigation as Nav exposing (Key)
|
||||
--exposing (UrlRequest(..))
|
||||
|
||||
import Browser
|
||||
import Browser.Navigation
|
||||
import ElasticSearch
|
||||
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
|
||||
, classList
|
||||
, href
|
||||
, src
|
||||
, type_
|
||||
, value
|
||||
)
|
||||
import Html.Events
|
||||
exposing
|
||||
( onClick
|
||||
, onInput
|
||||
, onSubmit
|
||||
)
|
||||
import Http
|
||||
import Json.Decode as D
|
||||
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
|
||||
( (<?>)
|
||||
, Parser
|
||||
)
|
||||
import Url.Parser.Query as UrlParserQuery
|
||||
import Page.Home
|
||||
import Page.Options
|
||||
import Page.Packages
|
||||
import RemoteData
|
||||
import Route
|
||||
import Url
|
||||
|
||||
|
||||
|
||||
-- ---------------------------
|
||||
-- MODEL
|
||||
-- ---------------------------
|
||||
|
||||
|
||||
type alias Flags =
|
||||
|
@ -71,403 +44,174 @@ type alias Flags =
|
|||
|
||||
|
||||
type alias Model =
|
||||
{ key : Key
|
||||
, elasticsearchUrl : String
|
||||
, elasticsearchUsername : String
|
||||
, elasticsearchPassword : String
|
||||
{ navKey : Browser.Navigation.Key
|
||||
, url : Url.Url
|
||||
, elasticsearch : ElasticSearch.Options
|
||||
, page : Page
|
||||
}
|
||||
|
||||
|
||||
type alias SearchModel =
|
||||
{ query : Maybe String
|
||||
, result : R.WebData SearchResult
|
||||
, showDetailsFor : Maybe String
|
||||
}
|
||||
|
||||
|
||||
type Page
|
||||
= SearchPage SearchModel
|
||||
= NotFound
|
||||
| Home Page.Home.Model
|
||||
| Packages Page.Packages.Model
|
||||
| Options Page.Options.Model
|
||||
|
||||
|
||||
|
||||
--| PackagePage SearchModel
|
||||
--| MaintainerPage SearchModel
|
||||
|
||||
|
||||
type alias SearchResult =
|
||||
{ hits : SearchResultHits
|
||||
}
|
||||
|
||||
|
||||
type alias SearchResultHits =
|
||||
{ total : SearchResultHitsTotal
|
||||
, max_score : Maybe Float
|
||||
, hits : List SearchResultItem
|
||||
}
|
||||
|
||||
|
||||
type alias SearchResultHitsTotal =
|
||||
{ value : Int
|
||||
, relation : String -- TODO: this should probably be Enum
|
||||
}
|
||||
|
||||
|
||||
type alias SearchResultItem =
|
||||
{ index : String
|
||||
, id : String
|
||||
, score : Float
|
||||
, source : SearchResultItemSource
|
||||
}
|
||||
|
||||
|
||||
type SearchResultItemSource
|
||||
= Package SearchResultPackage
|
||||
| Option SearchResultOption
|
||||
|
||||
|
||||
type alias SearchResultPackage =
|
||||
{ attr_name : String
|
||||
, name : String
|
||||
, version : String
|
||||
, description : Maybe String
|
||||
, longDescription : Maybe String
|
||||
, licenses : List SearchResultPackageLicense
|
||||
, maintainers : List SearchResultPackageMaintainer
|
||||
, platforms : List String
|
||||
, position : Maybe String
|
||||
, homepage : Maybe String
|
||||
}
|
||||
|
||||
|
||||
type alias SearchResultOption =
|
||||
{ option_name : String
|
||||
, description : String
|
||||
, type_ : String
|
||||
, default : String
|
||||
, example : String
|
||||
, source : String
|
||||
}
|
||||
|
||||
|
||||
type alias SearchResultPackageLicense =
|
||||
{ fullName : Maybe String
|
||||
, url : Maybe String
|
||||
}
|
||||
|
||||
|
||||
type alias SearchResultPackageMaintainer =
|
||||
{ name : String
|
||||
, email : String
|
||||
, github : String
|
||||
}
|
||||
|
||||
|
||||
emptySearch : Page
|
||||
emptySearch =
|
||||
SearchPage
|
||||
{ query = Nothing
|
||||
, result = R.NotAsked
|
||||
, showDetailsFor = Nothing
|
||||
}
|
||||
|
||||
|
||||
init : Flags -> Url -> Key -> ( Model, Cmd Msg )
|
||||
init flags url key =
|
||||
init :
|
||||
Flags
|
||||
-> Url.Url
|
||||
-> Browser.Navigation.Key
|
||||
-> ( Model, Cmd Msg )
|
||||
init flags url navKey =
|
||||
let
|
||||
model =
|
||||
{ key = key
|
||||
, elasticsearchUrl = flags.elasticsearchUrl
|
||||
, elasticsearchUsername = flags.elasticsearchUsername
|
||||
, elasticsearchPassword = flags.elasticsearchPassword
|
||||
, page = UrlParser.parse urlParser url |> Maybe.withDefault emptySearch
|
||||
{ navKey = navKey
|
||||
, url = url
|
||||
, elasticsearch =
|
||||
ElasticSearch.Options
|
||||
flags.elasticsearchUrl
|
||||
flags.elasticsearchUsername
|
||||
flags.elasticsearchPassword
|
||||
, page = NotFound
|
||||
}
|
||||
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
|
||||
changeRouteTo model url
|
||||
|
||||
|
||||
|
||||
-- ---------------------------
|
||||
-- URL Parsing and Routing
|
||||
-- ---------------------------
|
||||
|
||||
|
||||
handleUrlRequest : Key -> UrlRequest -> Cmd msg
|
||||
handleUrlRequest key urlRequest =
|
||||
case urlRequest of
|
||||
Internal url ->
|
||||
Nav.pushUrl key (Url.toString url)
|
||||
|
||||
External url ->
|
||||
Nav.load url
|
||||
|
||||
|
||||
urlParser : Parser (Page -> msg) msg
|
||||
urlParser =
|
||||
UrlParser.oneOf
|
||||
[ UrlParser.map
|
||||
(\query showDetailsFor ->
|
||||
SearchPage
|
||||
{ query = query
|
||||
, result = R.NotAsked
|
||||
, showDetailsFor = showDetailsFor
|
||||
}
|
||||
)
|
||||
(UrlParser.s "search" <?> UrlParserQuery.string "query" <?> UrlParserQuery.string "showDetailsFor")
|
||||
]
|
||||
|
||||
|
||||
|
||||
-- ---------------------------
|
||||
-- UPDATE
|
||||
-- ---------------------------
|
||||
|
||||
|
||||
type Msg
|
||||
= OnUrlRequest UrlRequest
|
||||
| OnUrlChange Url
|
||||
| SearchPageInput String
|
||||
| SearchQuerySubmit
|
||||
| SearchQueryResponse (R.WebData SearchResult)
|
||||
| SearchShowPackageDetails String
|
||||
= ChangedUrl Url.Url
|
||||
| ClickedLink Browser.UrlRequest
|
||||
| HomeMsg Page.Home.Msg
|
||||
| PackagesMsg Page.Packages.Msg
|
||||
| OptionsMsg Page.Options.Msg
|
||||
|
||||
|
||||
decodeResult : D.Decoder SearchResult
|
||||
decodeResult =
|
||||
D.map SearchResult
|
||||
(D.field "hits" decodeResultHits)
|
||||
updateWith :
|
||||
(subModel -> Page)
|
||||
-> (subMsg -> Msg)
|
||||
-> Model
|
||||
-> ( subModel, Cmd subMsg )
|
||||
-> ( Model, Cmd Msg )
|
||||
updateWith toPage toMsg model ( subModel, subCmd ) =
|
||||
( { model | page = toPage subModel }
|
||||
, Cmd.map toMsg subCmd
|
||||
)
|
||||
|
||||
|
||||
decodeResultHits : D.Decoder SearchResultHits
|
||||
decodeResultHits =
|
||||
D.map3 SearchResultHits
|
||||
(D.field "total" decodeResultHitsTotal)
|
||||
(D.field "max_score" (D.nullable D.float))
|
||||
(D.field "hits" (D.list decodeResultItem))
|
||||
submitQuery :
|
||||
Model
|
||||
-> ( Model, Cmd Msg )
|
||||
-> ( Model, Cmd Msg )
|
||||
submitQuery old ( new, cmd ) =
|
||||
let
|
||||
triggerSearch oldModel newModel msg makeRequest =
|
||||
if (oldModel.query == newModel.query) && RemoteData.isSuccess oldModel.result then
|
||||
( new, cmd )
|
||||
|
||||
else
|
||||
( new
|
||||
, Cmd.batch
|
||||
[ cmd
|
||||
, Page.Packages.makeRequest
|
||||
new.elasticsearch
|
||||
(Maybe.withDefault "" newModel.query)
|
||||
|> Cmd.map PackagesMsg
|
||||
]
|
||||
)
|
||||
in
|
||||
case ( old.page, new.page ) of
|
||||
( Packages oldModel, Packages newModel ) ->
|
||||
triggerSearch oldModel newModel PackagesMsg Page.Packages.makeRequest
|
||||
|
||||
( Options oldModel, Options newModel ) ->
|
||||
triggerSearch oldModel newModel OptionsMsg Page.Options.makeRequest
|
||||
|
||||
( _, _ ) ->
|
||||
( new, cmd )
|
||||
|
||||
|
||||
decodeResultHitsTotal : D.Decoder SearchResultHitsTotal
|
||||
decodeResultHitsTotal =
|
||||
D.map2 SearchResultHitsTotal
|
||||
(D.field "value" D.int)
|
||||
(D.field "relation" D.string)
|
||||
changeRouteTo : Model -> Url.Url -> ( Model, Cmd Msg )
|
||||
changeRouteTo model url =
|
||||
let
|
||||
newModel =
|
||||
{ model | url = url }
|
||||
|
||||
|
||||
decodeResultItem : D.Decoder SearchResultItem
|
||||
decodeResultItem =
|
||||
D.map4 SearchResultItem
|
||||
(D.field "_index" D.string)
|
||||
(D.field "_id" D.string)
|
||||
(D.field "_score" D.float)
|
||||
(D.field "_source" decodeResultItemSource)
|
||||
|
||||
|
||||
decodeResultItemSource : D.Decoder SearchResultItemSource
|
||||
decodeResultItemSource =
|
||||
D.oneOf
|
||||
[ D.map Package decodeResultPackage
|
||||
|
||||
--, D.map Option decodeResultOption
|
||||
]
|
||||
|
||||
|
||||
decodeResultPackage : D.Decoder SearchResultPackage
|
||||
decodeResultPackage =
|
||||
D.succeed SearchResultPackage
|
||||
|> DP.required "attr_name" D.string
|
||||
|> DP.required "name" D.string
|
||||
|> DP.required "version" D.string
|
||||
|> DP.required "description" (D.nullable D.string)
|
||||
|> 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)
|
||||
|
||||
|
||||
decodeResultPackageLicense : D.Decoder SearchResultPackageLicense
|
||||
decodeResultPackageLicense =
|
||||
D.map2 SearchResultPackageLicense
|
||||
(D.field "fullName" (D.nullable D.string))
|
||||
(D.field "url" (D.nullable D.string))
|
||||
|
||||
|
||||
decodeResultPackageMaintainer : D.Decoder SearchResultPackageMaintainer
|
||||
decodeResultPackageMaintainer =
|
||||
D.map3 SearchResultPackageMaintainer
|
||||
(D.field "name" D.string)
|
||||
(D.field "email" D.string)
|
||||
(D.field "github" D.string)
|
||||
|
||||
|
||||
decodeResultOption : D.Decoder SearchResultOption
|
||||
decodeResultOption =
|
||||
D.map6 SearchResultOption
|
||||
(D.field "option_name" D.string)
|
||||
(D.field "description" D.string)
|
||||
(D.field "type" D.string)
|
||||
(D.field "default" D.string)
|
||||
(D.field "example" D.string)
|
||||
(D.field "source" D.string)
|
||||
|
||||
|
||||
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||
update message model =
|
||||
case message of
|
||||
OnUrlRequest urlRequest ->
|
||||
( model, handleUrlRequest model.key urlRequest )
|
||||
|
||||
OnUrlChange url ->
|
||||
let
|
||||
newModel =
|
||||
{ model | page = UrlParser.parse urlParser url |> Maybe.withDefault model.page }
|
||||
|
||||
newPage =
|
||||
case newModel.page of
|
||||
SearchPage searchModel ->
|
||||
SearchPage
|
||||
{ searchModel
|
||||
| result =
|
||||
case searchModel.query of
|
||||
Just query ->
|
||||
R.Loading
|
||||
|
||||
Nothing ->
|
||||
R.NotAsked
|
||||
}
|
||||
|
||||
newNewModel =
|
||||
{ newModel | page = newPage }
|
||||
in
|
||||
( newNewModel
|
||||
, initPageCmd newModel newNewModel
|
||||
)
|
||||
|
||||
SearchPageInput query ->
|
||||
( { model
|
||||
| page =
|
||||
case model.page of
|
||||
SearchPage searchModel ->
|
||||
SearchPage { searchModel | query = Just query }
|
||||
maybeRoute =
|
||||
Route.fromUrl url
|
||||
in
|
||||
case maybeRoute of
|
||||
Nothing ->
|
||||
( { newModel
|
||||
| page = NotFound
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
SearchQuerySubmit ->
|
||||
case model.page of
|
||||
SearchPage searchModel ->
|
||||
( model
|
||||
, Nav.pushUrl model.key <| createSearchUrl searchModel
|
||||
)
|
||||
|
||||
SearchQueryResponse result ->
|
||||
case model.page of
|
||||
SearchPage searchModel ->
|
||||
let
|
||||
newPage =
|
||||
SearchPage { searchModel | result = result }
|
||||
in
|
||||
( { model | page = newPage }
|
||||
, 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 []
|
||||
Just Route.NotFound ->
|
||||
( { newModel
|
||||
| page = NotFound
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|> List.append
|
||||
(model.showDetailsFor
|
||||
|> Maybe.map
|
||||
(\x ->
|
||||
[ UrlBuilder.string "showDetailsFor" x
|
||||
]
|
||||
|
||||
Just Route.Home ->
|
||||
-- Always redirect to /packages until we have something to show
|
||||
-- on the home page
|
||||
( newModel, Browser.Navigation.pushUrl newModel.navKey "/packages" )
|
||||
|
||||
Just (Route.Packages query showDetailsFor) ->
|
||||
Page.Packages.init query showDetailsFor
|
||||
|> updateWith Packages PackagesMsg newModel
|
||||
|> submitQuery newModel
|
||||
|
||||
Just (Route.Options query showDetailsFor) ->
|
||||
Page.Options.init query showDetailsFor
|
||||
|> updateWith Options OptionsMsg newModel
|
||||
|> submitQuery newModel
|
||||
|
||||
|
||||
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||
update msg model =
|
||||
case ( msg, model.page ) of
|
||||
( ClickedLink urlRequest, _ ) ->
|
||||
case urlRequest of
|
||||
Browser.Internal url ->
|
||||
( model
|
||||
, Browser.Navigation.pushUrl model.navKey <| Url.toString url
|
||||
)
|
||||
|> Maybe.withDefault []
|
||||
)
|
||||
|> UrlBuilder.absolute [ "search" ]
|
||||
|
||||
Browser.External href ->
|
||||
( model
|
||||
, Browser.Navigation.load href
|
||||
)
|
||||
|
||||
( ChangedUrl url, _ ) ->
|
||||
changeRouteTo model url
|
||||
|
||||
( HomeMsg subMsg, Home subModel ) ->
|
||||
Page.Home.update subMsg subModel
|
||||
|> updateWith Home HomeMsg model
|
||||
|
||||
( PackagesMsg subMsg, Packages subModel ) ->
|
||||
Page.Packages.update model.navKey subMsg subModel
|
||||
|> updateWith Packages PackagesMsg model
|
||||
|
||||
( OptionsMsg subMsg, Options subModel ) ->
|
||||
Page.Options.update model.navKey subMsg subModel
|
||||
|> updateWith Options OptionsMsg model
|
||||
|
||||
( _, _ ) ->
|
||||
-- Disregard messages that arrived for the wrong page.
|
||||
( model, Cmd.none )
|
||||
|
||||
|
||||
|
||||
-- ---------------------------
|
||||
-- VIEW
|
||||
-- ---------------------------
|
||||
|
||||
|
||||
view : Model -> Html Msg
|
||||
|
@ -480,134 +224,79 @@ view model =
|
|||
[ a [ class "brand", href "https://search.nixos.org" ]
|
||||
[ img [ src "https://nixos.org/logo/nix-wiki.png", class "logo" ] []
|
||||
]
|
||||
, viewNavigation model.url
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
, div [ class "container main" ]
|
||||
[ case model.page of
|
||||
SearchPage searchModel ->
|
||||
searchPage searchModel
|
||||
[ viewPage model
|
||||
, footer [] []
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
searchPage : SearchModel -> Html Msg
|
||||
searchPage model =
|
||||
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 <| Maybe.withDefault "" model.query
|
||||
]
|
||||
[]
|
||||
, 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." ] ]
|
||||
-- ]
|
||||
]
|
||||
]
|
||||
]
|
||||
viewNavigation : Url.Url -> Html Msg
|
||||
viewNavigation url =
|
||||
ul [ class "nav" ]
|
||||
(List.map
|
||||
(viewNavigationItem url)
|
||||
[ ( "/packages", "Packages" )
|
||||
, ( "/options", "Options" )
|
||||
]
|
||||
, case model.result of
|
||||
R.NotAsked ->
|
||||
div [] [ text "NotAsked" ]
|
||||
|
||||
R.Loading ->
|
||||
div [] [ text "Loading" ]
|
||||
|
||||
R.Success result ->
|
||||
searchPageResult model.showDetailsFor result.hits
|
||||
|
||||
R.Failure error ->
|
||||
div []
|
||||
[ text "Error!"
|
||||
|
||||
--, pre [] [ text (Debug.toString error) ]
|
||||
]
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
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
|
||||
]
|
||||
]
|
||||
viewNavigationItem :
|
||||
Url.Url
|
||||
-> ( String, String )
|
||||
-> Html Msg
|
||||
viewNavigationItem url ( path, title ) =
|
||||
li
|
||||
[ classList [ ( "active", path == url.path ) ] ]
|
||||
[ a [ href path ] [ text title ] ]
|
||||
|
||||
|
||||
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 ]
|
||||
[]
|
||||
]
|
||||
viewPage : Model -> Html Msg
|
||||
viewPage model =
|
||||
case model.page of
|
||||
NotFound ->
|
||||
div [] [ text "Not Found" ]
|
||||
|
||||
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
|
||||
Home _ ->
|
||||
div [] [ text "Welcome" ]
|
||||
|
||||
Option option ->
|
||||
[ tr
|
||||
[]
|
||||
[-- td [] [ text option.option_name ]
|
||||
--, td [] [ text option.name ]
|
||||
--, td [] [ text option.version ]
|
||||
--, td [] [ text option.description ]
|
||||
]
|
||||
]
|
||||
Packages packagesModel ->
|
||||
Html.map (\m -> PackagesMsg m) <| Page.Packages.view packagesModel
|
||||
|
||||
Options optionsModel ->
|
||||
Html.map (\m -> OptionsMsg m) <| Page.Options.view optionsModel
|
||||
|
||||
|
||||
|
||||
-- SUBSCRIPTIONS
|
||||
|
||||
|
||||
subscriptions : Model -> Sub Msg
|
||||
subscriptions _ =
|
||||
Sub.none
|
||||
|
||||
|
||||
|
||||
-- ---------------------------
|
||||
-- MAIN
|
||||
-- ---------------------------
|
||||
|
||||
|
||||
main : Program Flags Model Msg
|
||||
main =
|
||||
Browser.application
|
||||
{ init = init
|
||||
, onUrlRequest = ClickedLink
|
||||
, onUrlChange = ChangedUrl
|
||||
, subscriptions = subscriptions
|
||||
, update = update
|
||||
, view =
|
||||
\m ->
|
||||
{ title = "NixOS Search"
|
||||
, body = [ view m ]
|
||||
}
|
||||
, subscriptions = \_ -> Sub.none
|
||||
, onUrlRequest = OnUrlRequest
|
||||
, onUrlChange = OnUrlChange
|
||||
}
|
||||
|
|
28
src/Page/Home.elm
Normal file
28
src/Page/Home.elm
Normal file
|
@ -0,0 +1,28 @@
|
|||
module Page.Home exposing (Model, Msg, init, update, view)
|
||||
|
||||
import Html exposing (Html, text, div )
|
||||
|
||||
-- MODEL
|
||||
|
||||
type alias Model = ()
|
||||
|
||||
|
||||
init : (Model, Cmd Msg)
|
||||
init =
|
||||
((), Cmd.none)
|
||||
|
||||
|
||||
-- UPDATE
|
||||
|
||||
type Msg = NoOp
|
||||
|
||||
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||
update msg model =
|
||||
(model, Cmd.none)
|
||||
|
||||
-- VIEW
|
||||
|
||||
view : Model -> Html Msg
|
||||
view model =
|
||||
div [] [text "Home"]
|
||||
|
169
src/Page/Options.elm
Normal file
169
src/Page/Options.elm
Normal file
|
@ -0,0 +1,169 @@
|
|||
module Page.Options exposing
|
||||
( Model
|
||||
, Msg
|
||||
, decodeResultItemSource
|
||||
, init
|
||||
, makeRequest
|
||||
, update
|
||||
, view
|
||||
)
|
||||
|
||||
import Browser.Navigation
|
||||
import ElasticSearch
|
||||
import Html
|
||||
exposing
|
||||
( Html
|
||||
, div
|
||||
, table
|
||||
, tbody
|
||||
, td
|
||||
, text
|
||||
, th
|
||||
, thead
|
||||
, tr
|
||||
)
|
||||
import Html.Attributes
|
||||
exposing
|
||||
( class
|
||||
, colspan
|
||||
)
|
||||
import Html.Events
|
||||
exposing
|
||||
( onClick
|
||||
)
|
||||
import Json.Decode
|
||||
|
||||
|
||||
|
||||
-- MODEL
|
||||
|
||||
|
||||
type alias Model =
|
||||
ElasticSearch.Model ResultItemSource
|
||||
|
||||
|
||||
type alias ResultItemSource =
|
||||
{ option_name : String
|
||||
, description : String
|
||||
, type_ : String
|
||||
, default : String
|
||||
, example : String
|
||||
, source : String
|
||||
}
|
||||
|
||||
|
||||
init :
|
||||
Maybe String
|
||||
-> Maybe String
|
||||
-> ( Model, Cmd Msg )
|
||||
init =
|
||||
ElasticSearch.init
|
||||
|
||||
|
||||
|
||||
-- UPDATE
|
||||
|
||||
|
||||
type Msg
|
||||
= SearchMsg (ElasticSearch.Msg ResultItemSource)
|
||||
|
||||
|
||||
update : Browser.Navigation.Key -> Msg -> Model -> ( Model, Cmd Msg )
|
||||
update navKey msg model =
|
||||
case msg of
|
||||
SearchMsg subMsg ->
|
||||
let
|
||||
( newModel, newCmd ) =
|
||||
ElasticSearch.update "options" navKey subMsg model
|
||||
in
|
||||
( newModel, Cmd.map SearchMsg newCmd )
|
||||
|
||||
|
||||
|
||||
-- VIEW
|
||||
|
||||
|
||||
view : Model -> Html Msg
|
||||
view model =
|
||||
ElasticSearch.view
|
||||
{ title = "Search NixOS options" }
|
||||
model
|
||||
viewSuccess
|
||||
SearchMsg
|
||||
|
||||
|
||||
viewSuccess :
|
||||
Maybe String
|
||||
-> ElasticSearch.Result ResultItemSource
|
||||
-> Html Msg
|
||||
viewSuccess showDetailsFor result =
|
||||
div [ class "search-result" ]
|
||||
[ table [ class "table table-hover" ]
|
||||
[ thead []
|
||||
[ tr []
|
||||
[ th [] [ text "Option name" ]
|
||||
]
|
||||
]
|
||||
, tbody
|
||||
[]
|
||||
(List.concatMap
|
||||
(viewResultItem showDetailsFor)
|
||||
result.hits.hits
|
||||
)
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
viewResultItem :
|
||||
Maybe String
|
||||
-> ElasticSearch.ResultItem ResultItemSource
|
||||
-> List (Html Msg)
|
||||
viewResultItem showDetailsFor item =
|
||||
let
|
||||
packageDetails =
|
||||
if Just item.id == showDetailsFor then
|
||||
[ td [ colspan 1 ]
|
||||
[ text "This are details!" ]
|
||||
]
|
||||
|
||||
else
|
||||
[]
|
||||
in
|
||||
tr [ onClick (SearchMsg (ElasticSearch.ShowDetails item.id)) ]
|
||||
[ td [] [ text item.source.option_name ]
|
||||
]
|
||||
:: packageDetails
|
||||
|
||||
|
||||
|
||||
-- API
|
||||
|
||||
|
||||
makeRequest :
|
||||
ElasticSearch.Options
|
||||
-> String
|
||||
-> Cmd Msg
|
||||
makeRequest options query =
|
||||
ElasticSearch.makeRequest
|
||||
"option_name"
|
||||
-- TODO: add support for different channels
|
||||
"nixos-unstable-options"
|
||||
decodeResultItemSource
|
||||
options
|
||||
query
|
||||
|> Cmd.map SearchMsg
|
||||
|
||||
|
||||
|
||||
-- JSON
|
||||
|
||||
|
||||
decodeResultItemSource : Json.Decode.Decoder ResultItemSource
|
||||
decodeResultItemSource =
|
||||
Json.Decode.map6 ResultItemSource
|
||||
(Json.Decode.field "option_name" Json.Decode.string)
|
||||
(Json.Decode.field "description" Json.Decode.string)
|
||||
(Json.Decode.field "type" Json.Decode.string)
|
||||
(Json.Decode.field "default" Json.Decode.string)
|
||||
(Json.Decode.field "example" Json.Decode.string)
|
||||
(Json.Decode.field "source" Json.Decode.string)
|
211
src/Page/Packages.elm
Normal file
211
src/Page/Packages.elm
Normal file
|
@ -0,0 +1,211 @@
|
|||
module Page.Packages exposing
|
||||
( Model
|
||||
, Msg
|
||||
, decodeResultItemSource
|
||||
, init
|
||||
, makeRequest
|
||||
, update
|
||||
, view
|
||||
)
|
||||
|
||||
import Browser.Navigation
|
||||
import ElasticSearch
|
||||
import Html
|
||||
exposing
|
||||
( Html
|
||||
, div
|
||||
, table
|
||||
, tbody
|
||||
, td
|
||||
, text
|
||||
, th
|
||||
, thead
|
||||
, tr
|
||||
)
|
||||
import Html.Attributes
|
||||
exposing
|
||||
( class
|
||||
, colspan
|
||||
)
|
||||
import Html.Events
|
||||
exposing
|
||||
( onClick
|
||||
)
|
||||
import Json.Decode
|
||||
import Json.Decode.Pipeline
|
||||
|
||||
|
||||
|
||||
-- MODEL
|
||||
|
||||
|
||||
type alias Model =
|
||||
ElasticSearch.Model ResultItemSource
|
||||
|
||||
|
||||
type alias ResultItemSource =
|
||||
{ attr_name : String
|
||||
, name : String
|
||||
, version : String
|
||||
, description : Maybe String
|
||||
, longDescription : Maybe String
|
||||
, licenses : List ResultPackageLicense
|
||||
, maintainers : List ResultPackageMaintainer
|
||||
, platforms : List String
|
||||
, position : Maybe String
|
||||
, homepage : Maybe String
|
||||
}
|
||||
|
||||
|
||||
type alias ResultPackageLicense =
|
||||
{ fullName : Maybe String
|
||||
, url : Maybe String
|
||||
}
|
||||
|
||||
|
||||
type alias ResultPackageMaintainer =
|
||||
{ name : String
|
||||
, email : String
|
||||
, github : String
|
||||
}
|
||||
|
||||
|
||||
init :
|
||||
Maybe String
|
||||
-> Maybe String
|
||||
-> ( Model, Cmd Msg )
|
||||
init =
|
||||
ElasticSearch.init
|
||||
|
||||
|
||||
|
||||
-- UPDATE
|
||||
|
||||
|
||||
type Msg
|
||||
= SearchMsg (ElasticSearch.Msg ResultItemSource)
|
||||
|
||||
|
||||
update : Browser.Navigation.Key -> Msg -> Model -> ( Model, Cmd Msg )
|
||||
update navKey msg model =
|
||||
case msg of
|
||||
SearchMsg subMsg ->
|
||||
let
|
||||
( newModel, newCmd ) =
|
||||
ElasticSearch.update "packages" navKey subMsg model
|
||||
in
|
||||
( newModel, Cmd.map SearchMsg newCmd )
|
||||
|
||||
|
||||
|
||||
-- VIEW
|
||||
|
||||
|
||||
view : Model -> Html Msg
|
||||
view model =
|
||||
ElasticSearch.view
|
||||
{ title = "Search NixOS packages" }
|
||||
model
|
||||
viewSuccess
|
||||
SearchMsg
|
||||
|
||||
|
||||
viewSuccess :
|
||||
Maybe String
|
||||
-> ElasticSearch.Result ResultItemSource
|
||||
-> Html Msg
|
||||
viewSuccess 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
|
||||
(viewResultItem showDetailsFor)
|
||||
result.hits.hits
|
||||
)
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
viewResultItem :
|
||||
Maybe String
|
||||
-> ElasticSearch.ResultItem ResultItemSource
|
||||
-> List (Html Msg)
|
||||
viewResultItem showDetailsFor item =
|
||||
let
|
||||
packageDetails =
|
||||
if Just item.id == showDetailsFor then
|
||||
[ td [ colspan 4 ]
|
||||
[ text "This are details!" ]
|
||||
]
|
||||
|
||||
else
|
||||
[]
|
||||
in
|
||||
tr [ onClick (SearchMsg (ElasticSearch.ShowDetails item.id)) ]
|
||||
[ td [] [ text item.source.attr_name ]
|
||||
, td [] [ text item.source.name ]
|
||||
, td [] [ text item.source.version ]
|
||||
, td [] [ text <| Maybe.withDefault "" item.source.description ]
|
||||
]
|
||||
:: packageDetails
|
||||
|
||||
|
||||
|
||||
-- API
|
||||
|
||||
|
||||
makeRequest :
|
||||
ElasticSearch.Options
|
||||
-> String
|
||||
-> Cmd Msg
|
||||
makeRequest options query =
|
||||
ElasticSearch.makeRequest
|
||||
"attr_name"
|
||||
"nixos-unstable-packages"
|
||||
decodeResultItemSource
|
||||
options
|
||||
query
|
||||
|> Cmd.map SearchMsg
|
||||
|
||||
|
||||
|
||||
-- JSON
|
||||
|
||||
|
||||
decodeResultItemSource : Json.Decode.Decoder ResultItemSource
|
||||
decodeResultItemSource =
|
||||
Json.Decode.succeed ResultItemSource
|
||||
|> Json.Decode.Pipeline.required "attr_name" Json.Decode.string
|
||||
|> Json.Decode.Pipeline.required "name" Json.Decode.string
|
||||
|> Json.Decode.Pipeline.required "version" Json.Decode.string
|
||||
|> Json.Decode.Pipeline.required "description" (Json.Decode.nullable Json.Decode.string)
|
||||
|> Json.Decode.Pipeline.required "longDescription" (Json.Decode.nullable Json.Decode.string)
|
||||
|> Json.Decode.Pipeline.required "license" (Json.Decode.list decodeResultPackageLicense)
|
||||
|> Json.Decode.Pipeline.required "maintainers" (Json.Decode.list decodeResultPackageMaintainer)
|
||||
|> Json.Decode.Pipeline.required "platforms" (Json.Decode.list Json.Decode.string)
|
||||
|> Json.Decode.Pipeline.required "position" (Json.Decode.nullable Json.Decode.string)
|
||||
|> Json.Decode.Pipeline.required "homepage" (Json.Decode.nullable Json.Decode.string)
|
||||
|
||||
|
||||
decodeResultPackageLicense : Json.Decode.Decoder ResultPackageLicense
|
||||
decodeResultPackageLicense =
|
||||
Json.Decode.map2 ResultPackageLicense
|
||||
(Json.Decode.field "fullName" (Json.Decode.nullable Json.Decode.string))
|
||||
(Json.Decode.field "url" (Json.Decode.nullable Json.Decode.string))
|
||||
|
||||
|
||||
decodeResultPackageMaintainer : Json.Decode.Decoder ResultPackageMaintainer
|
||||
decodeResultPackageMaintainer =
|
||||
Json.Decode.map3 ResultPackageMaintainer
|
||||
(Json.Decode.field "name" Json.Decode.string)
|
||||
(Json.Decode.field "email" Json.Decode.string)
|
||||
(Json.Decode.field "github" Json.Decode.string)
|
95
src/Route.elm
Normal file
95
src/Route.elm
Normal file
|
@ -0,0 +1,95 @@
|
|||
module Route exposing (Route(..), fromUrl, href, replaceUrl)
|
||||
|
||||
import Browser.Navigation
|
||||
import Html
|
||||
import Html.Attributes
|
||||
import Url
|
||||
import Url.Parser exposing ((<?>))
|
||||
import Url.Parser.Query
|
||||
|
||||
|
||||
|
||||
-- ROUTING
|
||||
|
||||
|
||||
type Route
|
||||
= NotFound
|
||||
| Home
|
||||
| Packages (Maybe String) (Maybe String)
|
||||
| Options (Maybe String) (Maybe String)
|
||||
|
||||
|
||||
parser : Url.Parser.Parser (Route -> msg) msg
|
||||
parser =
|
||||
Url.Parser.oneOf
|
||||
[ Url.Parser.map
|
||||
Home
|
||||
Url.Parser.top
|
||||
, Url.Parser.map
|
||||
NotFound
|
||||
(Url.Parser.s "not-found")
|
||||
, Url.Parser.map
|
||||
Packages
|
||||
(Url.Parser.s "packages"
|
||||
<?> Url.Parser.Query.string "query"
|
||||
<?> Url.Parser.Query.string "showDetailsFor"
|
||||
)
|
||||
, Url.Parser.map
|
||||
Options
|
||||
(Url.Parser.s "options"
|
||||
<?> Url.Parser.Query.string "query"
|
||||
<?> Url.Parser.Query.string "showDetailsFor"
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
|
||||
-- PUBLIC HELPERS
|
||||
|
||||
|
||||
href : Route -> Html.Attribute msg
|
||||
href targetRoute =
|
||||
Html.Attributes.href (routeToString targetRoute)
|
||||
|
||||
|
||||
replaceUrl : Browser.Navigation.Key -> Route -> Cmd msg
|
||||
replaceUrl navKey route =
|
||||
Browser.Navigation.replaceUrl navKey (routeToString route)
|
||||
|
||||
|
||||
fromUrl : Url.Url -> Maybe Route
|
||||
fromUrl url =
|
||||
-- The RealWorld spec treats the fragment like a path.
|
||||
-- This makes it *literally* the path, so we can proceed
|
||||
-- with parsing as if it had been a normal path all along.
|
||||
--{ url | path = Maybe.withDefault "" url.fragment, fragment = Nothing }
|
||||
Url.Parser.parse parser url
|
||||
|
||||
|
||||
|
||||
-- INTERNAL
|
||||
|
||||
|
||||
routeToString : Route -> String
|
||||
routeToString page =
|
||||
let
|
||||
( path, query ) =
|
||||
routeToPieces page
|
||||
in
|
||||
"/" ++ String.join "/" path ++ "?" ++ String.join "&" (List.filterMap Basics.identity query)
|
||||
|
||||
|
||||
routeToPieces : Route -> ( List String, List (Maybe String) )
|
||||
routeToPieces page =
|
||||
case page of
|
||||
Home ->
|
||||
( [], [] )
|
||||
|
||||
NotFound ->
|
||||
( [ "not-found" ], [] )
|
||||
|
||||
Packages query showDetailsFor ->
|
||||
( [ "packages" ], [ query, showDetailsFor ] )
|
||||
|
||||
Options query showDetailsFor ->
|
||||
( [ "options" ], [ query, showDetailsFor ] )
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
require("./styles.scss");
|
||||
require("./index.scss");
|
||||
|
||||
const {Elm} = require('./Main');
|
||||
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
|
||||
header .navbar a.brand {
|
||||
line-height: 1.5em;
|
||||
header .navbar {
|
||||
a.brand {
|
||||
line-height: 1.5em;
|
||||
}
|
||||
img.logo {
|
||||
height: 1.5em;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
ul.nav > li > a {
|
||||
line-height: 2.5em;
|
||||
}
|
||||
}
|
||||
|
||||
.search-page {
|
Loading…
Reference in a new issue