recipes.coopcloud.tech/src/Pages/Top.elm

418 lines
12 KiB
Elm
Raw Normal View History

2021-04-17 21:42:28 +00:00
module Pages.Top exposing (Model, Msg, Params, page)
import Enum exposing (Enum)
2021-08-18 23:23:54 +00:00
import Html exposing (Html, a, button, div, h2, h5, i, img, li, p, span, text, ul, form, label, select, option, input)
import Html.Attributes exposing (alt, class, href, src, style, id, for, value)
import Html.Events exposing (onClick, onInput)
2021-04-17 21:42:28 +00:00
import Http
import Json.Decode as Decode
import Json.Decode.Extra as Decode exposing (andMap)
2021-05-04 16:27:42 +00:00
import Maybe exposing (withDefault)
2021-04-17 21:42:28 +00:00
import Spa.Document exposing (Document)
2021-05-04 16:27:42 +00:00
import Spa.Generated.Route as Route
2021-04-17 21:42:28 +00:00
import Spa.Page as Page exposing (Page)
import Spa.Url as Url exposing (Url)
import String.Extra exposing (ellipsis)
2021-05-04 16:27:42 +00:00
import Util exposing (Direction(..), andThen, by)
2021-04-17 21:42:28 +00:00
page : Page Params Model Msg
page =
Page.element
{ init = init
, update = update
, view = view
, subscriptions = subscriptions
}
2021-05-04 16:27:42 +00:00
2021-04-17 21:42:28 +00:00
-- INIT
type alias Params =
()
2021-05-04 16:27:42 +00:00
2021-04-17 21:42:28 +00:00
type alias App =
{ name : String
, category : String
, repository : Maybe String
, versions : Maybe (List String)
2021-04-18 10:47:50 +00:00
, icon : Maybe String
2021-04-25 13:37:32 +00:00
, status : Int
, slug : String
, default_branch : String
, website : Maybe String
, description : Maybe String
2021-04-17 21:42:28 +00:00
}
type alias Model =
{ filter_score : Maybe Int
, filter_category : Maybe Category
2021-08-18 23:23:54 +00:00
, filter_text : Maybe String
, status : Status
, apps : (List App)
, results : (List App)
}
type Status
2021-04-17 21:42:28 +00:00
= Failure
| Loading
| Success
type Category
= Apps
| Utilities
| Development
| Graveyard
| All
categories : Enum Category
categories =
Enum.create
[ ( "Apps", Apps )
, ( "Utilities", Utilities )
, ( "Development", Development )
, ("All", All)
]
2021-04-17 21:42:28 +00:00
init : Url Params -> ( Model, Cmd Msg )
init { params } =
( default_model, loadApps )
2021-04-17 21:42:28 +00:00
2021-04-18 10:47:50 +00:00
default_image : String
2021-05-04 16:27:42 +00:00
default_image =
"/logo.png"
2021-04-18 10:47:50 +00:00
default_model : Model
default_model =
2021-08-18 23:23:54 +00:00
{ filter_score = Nothing, filter_category = Nothing, filter_text = Nothing,
status = Loading, apps = [], results = [] }
2021-04-18 10:47:50 +00:00
2021-04-17 21:42:28 +00:00
-- UPDATE
type Msg
= MorePlease
| FilterScore String
| FilterCategory String
2021-08-18 23:23:54 +00:00
| FilterText String
2021-04-17 21:42:28 +00:00
| GotApps (Result Http.Error (List App))
filterAppsScore : List App -> Maybe Int -> List App
filterAppsScore apps score =
case score of
Just s ->
List.filter (\app ->
app.status <= s
) apps
Nothing ->
apps
filterAppsCategory : List App -> Maybe Category -> List App
filterAppsCategory apps category =
case category of
Just c ->
if c == All then
apps
else
List.filter (\app ->
app.category == categories.toString c
) apps
Nothing ->
apps
2021-08-18 23:23:54 +00:00
filterAppsText : List App -> Maybe String -> List App
filterAppsText apps text =
case text of
Just "" ->
apps
Just t ->
List.filter (\app ->
String.contains (String.toLower t) (
String.toLower app.name ++ String.toLower (
Maybe.withDefault "" app.description
)
)
) apps
Nothing ->
apps
2021-04-17 21:42:28 +00:00
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
MorePlease ->
( default_model, loadApps )
FilterScore filter ->
let
filter_score = String.toInt filter
in
( { model
| filter_score = filter_score
, results = filterAppsScore (
2021-08-18 23:23:54 +00:00
filterAppsCategory (
filterAppsText model.apps model.filter_text
) model.filter_category
) (String.toInt filter)
}, Cmd.none )
FilterCategory filter ->
let
category = categories.fromString filter
in
( { model
| filter_category = category
, results = filterAppsCategory (
2021-08-18 23:23:54 +00:00
filterAppsScore (
filterAppsText model.apps model.filter_text
)model.filter_score
) category
}, Cmd.none )
2021-04-17 21:42:28 +00:00
2021-08-18 23:23:54 +00:00
FilterText filter ->
( { model
| filter_text = Just filter
, results = filterAppsText (
filterAppsScore (
filterAppsCategory model.apps model.filter_category
) model.filter_score
) (Just filter)
}, Cmd.none )
2021-04-17 21:42:28 +00:00
GotApps result ->
case result of
Ok apps ->
( { default_model
| status = Success
, apps = apps
, results = apps }, Cmd.none )
2021-04-17 21:42:28 +00:00
Err _ ->
( { default_model
| status = Failure}, Cmd.none )
2021-04-17 21:42:28 +00:00
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.none
-- VIEW
view : Model -> Document Msg
view model =
{ title = "abra recipes"
2021-04-17 21:42:28 +00:00
, body = [ body model ]
}
body : Model -> Html Msg
body model =
2021-04-18 10:47:50 +00:00
div [ class "pt-3" ]
2021-04-17 21:42:28 +00:00
[ viewApps model
]
2021-05-04 16:27:42 +00:00
2021-04-19 23:31:11 +00:00
viewStatusBadge : App -> Html Msg
viewStatusBadge app =
2021-04-18 10:47:50 +00:00
let
status_class =
case app.status of
2021-04-25 13:37:32 +00:00
1 ->
2021-04-18 10:47:50 +00:00
"badge-success"
2021-05-04 16:27:42 +00:00
2021-04-25 13:37:32 +00:00
2 ->
2021-04-18 10:47:50 +00:00
"badge-info"
2021-05-04 16:27:42 +00:00
2021-04-25 13:37:32 +00:00
3 ->
2021-04-18 10:47:50 +00:00
"badge-warning"
2021-05-04 16:27:42 +00:00
2021-04-25 13:37:32 +00:00
4 ->
2021-04-18 10:47:50 +00:00
"badge-danger"
2021-05-04 16:27:42 +00:00
2021-04-18 10:47:50 +00:00
_ ->
"badge-dark"
in
2021-05-04 16:27:42 +00:00
span [ class ("card-link badge " ++ status_class) ]
[ text ("Score: " ++ String.fromInt app.status) ]
2021-04-18 10:47:50 +00:00
2021-04-19 23:31:11 +00:00
viewApp : App -> Html Msg
viewApp app =
2021-04-18 10:47:50 +00:00
let
icon_url =
case app.icon of
Just "" ->
default_image
2021-05-04 16:27:42 +00:00
2021-04-18 10:47:50 +00:00
Just i ->
i
2021-05-04 16:27:42 +00:00
2021-04-18 10:47:50 +00:00
Nothing ->
default_image
2021-05-04 16:27:42 +00:00
2021-04-25 13:25:25 +00:00
repository_link =
2021-04-18 10:47:50 +00:00
case app.repository of
2021-04-19 23:31:11 +00:00
Just link ->
2021-04-25 13:25:25 +00:00
a [ class "card-link", href link ]
2021-05-04 16:27:42 +00:00
[ i [ class "fab fa-git-alt" ] []
, text "code"
2021-04-19 23:31:11 +00:00
]
2021-05-04 16:27:42 +00:00
2021-04-18 10:47:50 +00:00
Nothing ->
text ""
2021-05-04 16:27:42 +00:00
2021-04-25 13:25:25 +00:00
website_link =
case app.website of
Just link ->
case link of
"" ->
text ""
2021-05-04 16:27:42 +00:00
_ ->
2021-04-25 13:25:25 +00:00
a [ class "card-link", href link ]
[ i [ class "fas fa-home" ] []
2021-05-04 16:27:42 +00:00
, text "homepage"
]
Nothing ->
text ""
2021-05-04 16:27:42 +00:00
app_href =
Route.toString <| Route.App_String { app = app.slug }
in
div [ class "col-md-4 mb-4 col-sm-12" ]
[ div [ class "smaller-card" ]
2021-05-04 16:27:42 +00:00
[ img [ class "card-img-top", src icon_url, alt ("icon for " ++ app.name) ] []
, div [ class "card-body" ]
[ h5 [ class "title-container" ]
[ a [ class "title", href app_href ] [ text app.name ] ]
, p [ class "card-description" ] [ text (ellipsis 100 (withDefault "" app.description)) ]
2021-05-04 16:27:42 +00:00
]
, div [ class "footer" ]
[ span [ class "badge app-category" ] [ text app.category ]]
2021-04-17 21:42:28 +00:00
]
2021-05-04 16:27:42 +00:00
]
2021-04-17 21:42:28 +00:00
2021-04-24 13:36:56 +00:00
viewCategories : (String, Category) -> Html Msg
viewCategories category =
div [ class "category-tile", onClick (FilterCategory (Tuple.first category)) ] [text (Tuple.first category)]
2021-04-17 21:42:28 +00:00
viewApps : Model -> Html Msg
viewApps model =
case model.status of
2021-04-17 21:42:28 +00:00
Failure ->
2021-04-24 14:12:56 +00:00
div []
[ div [ class "alert alert-danger" ]
[ p [] [ text "Unable to load app data" ]
, button [ class "btn btn-danger", onClick MorePlease ] [ text "Try Again!" ]
]
2021-04-17 21:42:28 +00:00
]
Loading ->
2021-04-25 17:10:41 +00:00
div [ class "d-flex align-items-center", style "height" "89vh" ]
[ div [ class "spinner-border m-auto text-light" ]
2021-05-04 16:27:42 +00:00
[ span [ class "sr-only" ] [ text "Loading..." ]
2021-04-25 17:10:41 +00:00
]
]
2021-05-04 16:27:42 +00:00
Success ->
div [ class "row justify-content-center" ]
[ div [ class "col-md-3" ] [
h2 [ class "app-headings" ] [text "Categories"]
, div [] (List.map viewCategories categories.list)
]
, div [ class "col-md-6" ]
[ div [ class "row" ]
[ div [ class "col-sm-12" ] [
div [] [
form [ class "search-bar-container" ] [
label [ for "text" ] [ text "Search" ]
, input [ class "search-bar-input", id "text", onInput FilterText ] []
, label [ for "level" ] [ text " and show only items that have at least " ]
, select [ class "search-dropdown", id "level", onInput FilterScore ] [
option [ ] [ text "any" ]
, option [ value "1" ] [ text "1 (production)" ]
, option [ value "2" ] [ text "2 (beta)" ]
, option [ value "3" ] [ text "3 (alpha)" ]
, option [ value "4" ] [ text "4 (pre-alpha)" ]
]
, text "builds "
]
]
]
]
, h2 [ class "app-headings" ] [ text "Apps" ]
, div [ class "row" ]
(List.map viewApp
(model.results
|> List.sortWith
(by .status ASC
|> andThen (String.toLower << .name) ASC
)
)
2021-05-04 16:27:42 +00:00
)
]
, div [class "col-md-3"] []
2021-04-17 21:42:28 +00:00
]
-- HTTP
loadApps : Cmd Msg
loadApps =
Http.get
{ url = "https://apps.coopcloud.tech/"
2021-04-17 21:42:28 +00:00
, expect = Http.expectJson GotApps appListDecoder
}
2021-05-04 16:27:42 +00:00
2021-04-18 10:47:50 +00:00
featuresDecoder =
2021-05-04 16:27:42 +00:00
Decode.oneOf
2021-04-25 13:37:32 +00:00
[ Decode.at [ "status" ] Decode.int
, Decode.succeed 5
2021-04-18 10:47:50 +00:00
]
2021-04-17 21:42:28 +00:00
appDecoder : Decode.Decoder App
appDecoder =
Decode.succeed App
|> andMap (Decode.field "name" Decode.string)
|> andMap (Decode.field "category" Decode.string)
|> andMap (Decode.maybe (Decode.field "repository" Decode.string))
|> andMap (Decode.succeed Nothing)
|> andMap (Decode.maybe (Decode.field "icon" Decode.string))
|> andMap (Decode.at [ "features" ] featuresDecoder)
|> andMap (Decode.succeed "")
|> andMap (Decode.field "default_branch" Decode.string)
|> andMap (Decode.maybe (Decode.field "website" Decode.string))
|> andMap (Decode.maybe (Decode.field "description" Decode.string))
2021-04-17 21:42:28 +00:00
2021-04-25 13:25:25 +00:00
2021-04-17 21:42:28 +00:00
appListDecoder : Decode.Decoder (List App)
appListDecoder =
2021-04-25 13:25:25 +00:00
Decode.keyValuePairs appDecoder
|> Decode.map buildApp
2021-05-04 16:27:42 +00:00
buildApp : List ( String, App ) -> List App
2021-04-25 13:25:25 +00:00
buildApp apps =
2021-05-04 16:27:42 +00:00
List.map (\( slug, app ) -> { app | slug = slug }) apps