gostart/src/Main.elm

773 lines
20 KiB
Elm
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

module Main exposing (..)
import Browser
import Browser.Navigation exposing (load)
import Html exposing (..)
import Html.Attributes
exposing
( checked
, class
, classList
, href
, name
, placeholder
, src
, type_
, value
)
import Html.Events exposing (..)
import Http
import Ignores
import Json.Encode as Encode
import Links
import Table exposing (defaultCustomizations)
import Watches
main : Program () Model Msg
main =
Browser.element
{ init = init
, update = update
, subscriptions = subscriptions
, view = view
}
type Msg
= AddedLink (Result Http.Error ())
| AddedWatch (Result Http.Error ())
| DeletedLink (Result Http.Error ())
| ClickedLink (Result Http.Error String)
| DeleteLink Int
| IncrementLink Int
| DeletedWatch (Result Http.Error ())
| DeleteWatch Int
| DeletedIgnore (Result Http.Error ())
| DeleteIgnore Int
| GotLinks (Result Http.Error Links.Links)
| GotNewLink Links.NewLink
| GotNewWatch Watches.NewWatch
| GotWatches (Result Http.Error Watches.Watches)
| GotIgnores (Result Http.Error Ignores.Ignores)
| HideWatchedItem Int String
| HidItem (Result Http.Error ())
| Reload
| ReloadLinks
| ReloadWatches
| ReloadIgnores
| SubmitLink
| SubmitWatch
| FetchIcons
| LoadIcons (Result Http.Error ())
| SetLinkTableState Table.State
| SetWatchTableState Table.State
| SetIgnoreTableState Table.State
type Status
= Loading
| LoadedWatches Watches.Watches
| LoadedIgnores Ignores.Ignores
| LoadedLinks Links.Links
| Errored String
type alias Model =
{ watches : Watches.Watches
, links : Links.Links
, ignores : Ignores.Ignores
, errors : List String
, status : Status
, newlink : Links.NewLink
, newwatch : Watches.NewWatch
, linkTableState : Table.State
, watchTableState : Table.State
, ignoreTableState : Table.State
}
initialModel : Model
initialModel =
{ watches = []
, links = []
, ignores = []
, errors = []
, status = Loading
, newlink =
{ name = ""
, url = ""
, shared = False
, logo_url = ""
}
, newwatch =
{ name = ""
, repo = ""
}
, linkTableState = Table.initialSort "Created"
, watchTableState = Table.initialSort "Created"
, ignoreTableState = Table.initialSort "Created"
}
init : () -> ( Model, Cmd Msg )
init _ =
( initialModel, Cmd.batch [ getLinks, getWatches, getIgnores ] )
hideWatched : Int -> String -> Cmd Msg
hideWatched id repo =
let
body =
Encode.object
[ ( "number", Encode.int id )
, ( "repo", Encode.string repo )
]
|> Http.jsonBody
in
Http.post
{ url = "/prignores"
, body = body
, expect = Http.expectWhatever HidItem
}
addLink : Model -> Cmd Msg
addLink model =
let
body =
Encode.object
[ ( "name", Encode.string model.newlink.name )
, ( "url", Encode.string model.newlink.url )
, ( "logo_url", Encode.string model.newlink.logo_url )
, ( "shared", Encode.bool model.newlink.shared )
]
|> Http.jsonBody
in
Http.post
{ url = "/links"
, body = body
, expect = Http.expectWhatever AddedLink
}
addWatch : Model -> Cmd Msg
addWatch model =
let
body =
Encode.object
[ ( "name", Encode.string model.newwatch.name )
, ( "repo", Encode.string model.newwatch.repo )
]
|> Http.jsonBody
in
Http.post
{ url = "/watches"
, body = body
, expect = Http.expectWhatever AddedWatch
}
fetchIcons : Cmd Msg
fetchIcons =
Http.get
{ url = "/update-icons"
, expect = Http.expectWhatever LoadIcons
}
deleteLink : Int -> Cmd Msg
deleteLink linkId =
Http.request
{ url = "/links/" ++ String.fromInt linkId
, method = "DELETE"
, timeout = Nothing
, tracker = Nothing
, headers = []
, body = Http.emptyBody
, expect = Http.expectWhatever DeletedLink
}
incrementLink : Int -> Cmd Msg
incrementLink linkId =
Http.request
{ url = "/links/" ++ String.fromInt linkId
, method = "GET"
, timeout = Nothing
, tracker = Nothing
, headers = []
, body = Http.emptyBody
, expect = Http.expectString ClickedLink
}
deleteIgnore : Int -> Cmd Msg
deleteIgnore ignoreId =
Http.request
{ url = "/prignores/" ++ String.fromInt ignoreId
, method = "DELETE"
, timeout = Nothing
, tracker = Nothing
, headers = []
, body = Http.emptyBody
, expect = Http.expectWhatever DeletedIgnore
}
deleteWatch : Int -> Cmd Msg
deleteWatch watchId =
Http.request
{ url = "/watches/" ++ String.fromInt watchId
, method = "DELETE"
, timeout = Nothing
, tracker = Nothing
, headers = []
, body = Http.emptyBody
, expect = Http.expectWhatever DeletedWatch
}
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
LoadIcons (Ok _) ->
( model, getLinks )
LoadIcons (Err _) ->
( { model | status = Errored "Server error reloading icons!" }, Cmd.none )
FetchIcons ->
( model, fetchIcons )
DeleteLink linkId ->
( model, deleteLink linkId )
IncrementLink linkId ->
( model, incrementLink linkId )
DeleteWatch watchId ->
( model, deleteWatch watchId )
DeleteIgnore ignoreId ->
( model, deleteIgnore ignoreId )
GotNewWatch newwatch ->
( { model | newwatch = newwatch }, Cmd.none )
GotNewLink newlink ->
( { model | newlink = newlink }, Cmd.none )
AddedWatch (Err _) ->
( { model | status = Errored "Server error adding a watch!" }, Cmd.none )
AddedWatch (Ok _) ->
( { model | newwatch = initialModel.newwatch }, getWatches )
AddedLink (Err _) ->
( { model | status = Errored "Server error adding a link!" }, Cmd.none )
AddedLink (Ok _) ->
( { model | newlink = initialModel.newlink }, getLinks )
ClickedLink (Ok newUrl) ->
( model, load newUrl )
ClickedLink (Err _) ->
( { model | status = Errored "Server error incrementing link!" }, Cmd.none )
DeletedLink (Ok _) ->
( model, getLinks )
DeletedLink (Err _) ->
( { model | status = Errored "Server error deleting link!" }, Cmd.none )
DeletedWatch (Ok _) ->
( model, getWatches )
DeletedWatch (Err _) ->
( { model | status = Errored "Server error deleting watch!" }, Cmd.none )
DeletedIgnore (Ok _) ->
( model, getIgnores )
DeletedIgnore (Err _) ->
( { model | status = Errored "Server error deleting ignore!" }, Cmd.none )
HidItem (Err _) ->
( { model | status = Errored "Server error when hiding a watch item!" }, Cmd.none )
HidItem (Ok _) ->
( model, getWatches )
HideWatchedItem itemId repo ->
( model, hideWatched itemId repo )
SubmitWatch ->
( model, addWatch model )
SubmitLink ->
( model, addLink model )
Reload ->
( model, Cmd.batch [ getWatches, getLinks ] )
ReloadWatches ->
( model, getWatches )
ReloadLinks ->
( model, getLinks )
ReloadIgnores ->
( model, getIgnores )
GotWatches (Err _) ->
( { model | status = Errored "Server error when fetching watches!" }, Cmd.none )
GotLinks (Err _) ->
( { model | status = Errored "Server error when fetching links!" }, Cmd.none )
GotIgnores (Err _) ->
( { model | status = Errored "Server error when fetching ignores!" }, Cmd.none )
GotWatches (Ok watches) ->
case watches of
_ :: _ ->
( { model
| watches = watches
, status = LoadedWatches watches
}
, Cmd.none
)
[] ->
( { model | status = Errored "No Watches found" }, Cmd.none )
GotLinks (Ok links) ->
case links of
_ :: _ ->
( { model
| links = links
, status =
LoadedLinks links
}
, Cmd.none
)
[] ->
( { model | status = Errored "No Links found" }, Cmd.none )
GotIgnores (Ok ignores) ->
case ignores of
_ :: _ ->
( { model
| ignores = ignores
, status = LoadedIgnores ignores
}
, Cmd.none
)
[] ->
( { model | status = Errored "No Watches found" }, Cmd.none )
SetLinkTableState newState ->
( { model | linkTableState = newState }, Cmd.none )
SetWatchTableState newState ->
( { model | watchTableState = newState }, Cmd.none )
SetIgnoreTableState newState ->
( { model | ignoreTableState = newState }, Cmd.none )
subscriptions : Model -> Sub Msg
subscriptions _ =
Sub.none
mainEle : List (Attribute msg) -> List (Html msg) -> Html msg
mainEle attributes children =
node "main" attributes children
view : Model -> Html Msg
view model =
div []
[ mainEle
[]
[ div [ class "grid" ]
[ div [ class "col" ]
[ viewWatches model
]
, div [ class "col" ]
[ viewLinks model
]
]
, div [ class "grid" ]
[ case model.status of
Errored e ->
div
[ classList
[ ( "error", True )
]
]
[ text e ]
_ ->
text ""
]
]
, footer []
[ details []
[ summary []
[ b [] [ text "Maintenence" ]
]
, div []
[ button [ onClick FetchIcons ] [ text "Update Icons" ]
]
, div []
[ h3 [] [ text "Links" ]
, Table.view linkTableConfig model.linkTableState model.links
]
, div []
[ h3 [] [ text "Watched Items" ]
, Table.view watchTableConfig model.watchTableState model.watches
]
, div []
[ h3
[]
[ text "Watched Items Ignores" ]
, Table.view ignoreTableConfig model.ignoreTableState model.ignores
]
]
]
]
shareTxt : Links.Link -> Table.HtmlDetails Msg
shareTxt link =
if link.shared then
Table.HtmlDetails []
[ text "Yes" ]
else
Table.HtmlDetails []
[ text "No" ]
shareColumn : Table.Column Links.Link Msg
shareColumn =
Table.veryCustomColumn
{ name = "Shared"
, viewData = \data -> shareTxt data
, sorter = Table.unsortable
}
linkTimeColumn : Table.Column Links.Link Msg
linkTimeColumn =
Table.customColumn
{ name = "Created"
, viewData = .createdAt
, sorter = Table.decreasingOrIncreasingBy .createdAt
}
deleteLinkColumn : Table.Column Links.Link Msg
deleteLinkColumn =
Table.veryCustomColumn
{ name = "Action"
, viewData = linkDeleteView
, sorter = Table.unsortable
}
linkTableConfig : Table.Config Links.Link Msg
linkTableConfig =
Table.customConfig
{ toId = .name
, toMsg = SetLinkTableState
, columns =
[ Table.stringColumn "Name" .name
, Table.stringColumn "URL" .url
, shareColumn
, Table.stringColumn "Logo URL" .logoURL
, linkTimeColumn
, deleteLinkColumn
]
, customizations = defaultCustomizations
}
linkDeleteView : Links.Link -> Table.HtmlDetails Msg
linkDeleteView { id } =
Table.HtmlDetails []
[ button
[ onClick (DeleteLink id) ]
[ text "Delete" ]
]
watchTableConfig : Table.Config Watches.Watch Msg
watchTableConfig =
Table.config
{ toId = .name
, toMsg = SetWatchTableState
, columns =
[ Table.stringColumn "Name" .name
, Table.stringColumn "Repo" .repo
, deleteWatchColumn
]
}
deleteWatchColumn : Table.Column Watches.Watch Msg
deleteWatchColumn =
Table.veryCustomColumn
{ name = "Action"
, viewData = watchDeleteView
, sorter = Table.unsortable
}
watchDeleteView : Watches.Watch -> Table.HtmlDetails Msg
watchDeleteView { id } =
Table.HtmlDetails []
[ button
[ onClick (DeleteWatch id) ]
[ text "Delete" ]
]
ignoreTimeColumn : Table.Column Ignores.Ignore Msg
ignoreTimeColumn =
Table.customColumn
{ name = "Created"
, viewData = .createdAt
, sorter = Table.decreasingOrIncreasingBy .createdAt
}
deleteIgnoreColumn : Table.Column Ignores.Ignore Msg
deleteIgnoreColumn =
Table.veryCustomColumn
{ name = "Action"
, viewData = ignoreDeleteView
, sorter = Table.unsortable
}
ignoreDeleteView : Ignores.Ignore -> Table.HtmlDetails Msg
ignoreDeleteView { id } =
Table.HtmlDetails []
[ button
[ onClick (DeleteIgnore id) ]
[ text "Delete" ]
]
ignoreTableConfig : Table.Config Ignores.Ignore Msg
ignoreTableConfig =
Table.customConfig
{ toId = .createdAt
, toMsg = SetIgnoreTableState
, columns =
[ Table.intColumn "ID" .id
, Table.stringColumn "Repo" .repo
, Table.intColumn "Number" .number
, ignoreTimeColumn
, deleteIgnoreColumn
]
, customizations = defaultCustomizations
}
getIgnores : Cmd Msg
getIgnores =
Http.get
{ url = "/prignores"
, expect = Http.expectJson GotIgnores Ignores.ignoreListDecoder
}
getLinks : Cmd Msg
getLinks =
Http.get
{ url = "/links"
, expect = Http.expectJson GotLinks Links.linkListDecoder
}
getWatches : Cmd Msg
getWatches =
Http.get
{ url = "/watches"
, expect = Http.expectJson GotWatches Watches.watchListDecoder
}
watchForm : Model -> Watches.NewWatch -> Html Msg
watchForm model newwatch =
div []
[ createForm "Watches"
SubmitWatch
(div
[]
[ labeledInput "Item: " "some string..." "name" model.newwatch.name (\v -> GotNewWatch { newwatch | name = v })
, labeledInput "Repository: " "NixOS/nixpkgs" "repo" model.newwatch.repo (\v -> GotNewWatch { newwatch | repo = v })
]
)
]
linkForm : Model -> Links.NewLink -> Html Msg
linkForm model newlink =
div []
[ createForm "Links"
SubmitLink
(div [ class "form-content" ]
[ div
[]
[ labeledInput "Name: " "Potato" "name" model.newlink.name (\v -> GotNewLink { newlink | name = v })
, labeledInput "URL: " "https://...." "url" model.newlink.url (\v -> GotNewLink { newlink | url = v })
, labeledInput "Icon: " "https://...." "logo_url" model.newlink.logo_url (\v -> GotNewLink { newlink | logo_url = v })
, label []
[ text "Shared: "
, input
[ type_ "checkbox"
, name "linkshared"
, onCheck (\v -> GotNewLink { newlink | shared = v })
, checked <| newlink.shared
]
[]
]
]
]
)
]
labeledInput : String -> String -> String -> String -> (String -> msg) -> Html msg
labeledInput labelStr placeStr inputName inputValue inputHandler =
label []
[ text labelStr
, input
[ type_ "text"
, name inputName
, onInput inputHandler
, placeholder placeStr
, value inputValue
]
[]
]
createForm : String -> Msg -> Html Msg -> Html Msg
createForm title action content =
details []
[ summary [] [ b [] [ text title ] ]
, Html.form
[ onSubmit action
, class "form-container"
]
[ div [ class "form-content" ]
[ content
, button
[]
[ text "Submit" ]
]
]
]
viewBar : Html Msg -> Html Msg -> Html Msg
viewBar left right =
header [ class "bar" ]
[ div [ class "bar-left" ] [ left ]
, div [ class "bar-right" ] [ right ]
]
viewLinks : Model -> Html Msg
viewLinks model =
div []
[ viewBar (linkForm model model.newlink) (a [ onClick ReloadLinks ] [ text " " ])
, case model.links of
_ :: _ ->
div
[ class "icon-grid" ]
(List.map viewLink model.links)
[] ->
text "No links found!"
]
viewLink : Links.Link -> Html Msg
viewLink link =
div []
[ div [ class "icon" ]
[ span [ onClick (DeleteLink link.id) ] [ text "×" ]
, a
[ onClick (IncrementLink link.id)
-- , href link.url
]
[ div
[]
[ header []
[ img [ src ("/icons/" ++ String.fromInt link.id) ] []
]
, text link.name
]
]
]
]
viewWatches : Model -> Html Msg
viewWatches model =
div []
[ viewBar (watchForm model model.newwatch) (a [ onClick ReloadWatches ] [ text " " ])
, case model.watches of
_ :: _ ->
ul [] (List.map viewWatch model.watches)
[] ->
text "No watches found!"
]
viewWatch : Watches.Watch -> Html Msg
viewWatch watch =
case watch.results of
[] ->
text ""
_ ->
div []
[ ul []
[ li
[]
[ b []
[ text watch.repo
, text " -> "
, text watch.name
, span [ onClick (DeleteWatch watch.id) ] [ text " ×" ]
]
, ul [] (List.map viewResult watch.results)
]
]
]
viewResult : Watches.Node -> Html Msg
viewResult node =
li []
[ a [ href node.url ] [ text (String.fromInt node.number) ]
, text " :: "
, span [ onClick (HideWatchedItem node.number node.repository.nameWithOwner) ] [ text "" ]
, text " :: "
, text node.title
]