1module Pipeline.PinMenu.PinMenu exposing
2    ( TableRow
3    , View
4    , pinMenu
5    , update
6    , viewPinMenu
7    )
8
9import Colors
10import Concourse
11import Dict
12import EffectTransformer exposing (ET)
13import HoverState
14import Html exposing (Html)
15import Html.Attributes exposing (id, style)
16import Html.Events exposing (onClick, onMouseEnter, onMouseLeave)
17import Json.Decode
18import Json.Encode
19import Message.Message exposing (DomID(..), Message(..))
20import Pipeline.PinMenu.Styles as Styles
21import Pipeline.PinMenu.Views as Views
22import Routes
23import SideBar.Styles as SS
24import Views.Styles
25
26
27type alias Model b =
28    { b
29        | fetchedResources : Maybe Json.Encode.Value
30        , pipelineLocator : Concourse.PipelineIdentifier
31        , pinMenuExpanded : Bool
32    }
33
34
35type alias View =
36    { hoverable : Bool
37    , clickable : Bool
38    , background : Views.Background
39    , opacity : SS.Opacity
40    , badge : Maybe Badge
41    , dropdown : Maybe Dropdown
42    }
43
44
45type alias Dropdown =
46    { position : Views.Position
47    , items : List DropdownItem
48    }
49
50
51type alias DropdownItem =
52    { title : Text
53    , table : List TableRow
54    , paddingPx : Int
55    , background : String
56    , onClick : Message
57    , hoverable : Bool
58    }
59
60
61type alias Badge =
62    { color : String
63    , diameterPx : Int
64    , position : Views.Position
65    , text : String
66    }
67
68
69type alias Text =
70    { content : String
71    , fontWeight : String
72    , color : String
73    }
74
75
76type alias TableRow =
77    { left : String
78    , right : String
79    , color : String
80    }
81
82
83update : Message -> ET (Model b)
84update message ( model, effects ) =
85    case message of
86        Click PinIcon ->
87            ( { model | pinMenuExpanded = not model.pinMenuExpanded }
88            , effects
89            )
90
91        _ ->
92            ( model, effects )
93
94
95pinMenu :
96    { a | hovered : HoverState.HoverState }
97    -> Model b
98    -> View
99pinMenu { hovered } model =
100    let
101        pinnedResources =
102            getPinnedResources model.fetchedResources
103
104        pipeline =
105            model.pipelineLocator
106
107        pinCount =
108            List.length pinnedResources
109
110        hasPinnedResources =
111            pinCount > 0
112
113        isHovered =
114            hovered == HoverState.Hovered PinIcon
115    in
116    { hoverable = hasPinnedResources
117    , clickable = hasPinnedResources
118    , opacity =
119        if isHovered || model.pinMenuExpanded then
120            SS.Bright
121
122        else if hasPinnedResources then
123            SS.GreyedOut
124
125        else
126            SS.Dim
127    , background =
128        if model.pinMenuExpanded then
129            Views.Light
130
131        else
132            Views.Dark
133    , badge =
134        if hasPinnedResources then
135            Just
136                { color = Colors.pinned
137                , diameterPx = 15
138                , position = Views.TopRight (Views.Px 10) (Views.Px 10)
139                , text = String.fromInt pinCount
140                }
141
142        else
143            Nothing
144    , dropdown =
145        if model.pinMenuExpanded then
146            Just
147                { position =
148                    Views.TopRight (Views.Percent 100) (Views.Percent 0)
149                , items =
150                    List.map
151                        (\( resourceName, pinnedVersion ) ->
152                            { title =
153                                { content = resourceName
154                                , fontWeight = Views.Styles.fontWeightDefault
155                                , color = Colors.text
156                                }
157                            , table =
158                                pinnedVersion
159                                    |> Dict.toList
160                                    |> List.map
161                                        (\( k, v ) ->
162                                            { left = k
163                                            , right = v
164                                            , color = Colors.text
165                                            }
166                                        )
167                            , paddingPx = 10
168                            , background =
169                                if
170                                    hovered
171                                        == HoverState.Hovered
172                                            (PinMenuDropDown resourceName)
173                                then
174                                    Colors.sideBarActive
175
176                                else
177                                    Colors.sideBar
178                            , hoverable = True
179                            , onClick =
180                                GoToRoute <|
181                                    Routes.Resource
182                                        { id =
183                                            { teamName = pipeline.teamName
184                                            , pipelineName = pipeline.pipelineName
185                                            , resourceName = resourceName
186                                            }
187                                        , page = Nothing
188                                        }
189                            }
190                        )
191                        pinnedResources
192                }
193
194        else
195            Nothing
196    }
197
198
199viewPinMenu :
200    { a | hovered : HoverState.HoverState }
201    -> Model b
202    -> Html Message
203viewPinMenu session m =
204    pinMenu session m
205        |> viewView
206
207
208getPinnedResources : Maybe Json.Encode.Value -> List ( String, Concourse.Version )
209getPinnedResources fetchedResources =
210    case fetchedResources of
211        Nothing ->
212            []
213
214        Just res ->
215            Json.Decode.decodeValue (Json.Decode.list Concourse.decodeResource) res
216                |> Result.withDefault []
217                |> List.filterMap (\r -> Maybe.map (\v -> ( r.name, v )) r.pinnedVersion)
218
219
220viewView : View -> Html Message
221viewView view =
222    Html.div
223        (([ ( id "pin-icon", True )
224          , ( onMouseEnter <| Hover <| Just PinIcon, view.hoverable )
225          , ( onMouseLeave <| Hover Nothing, view.hoverable )
226          , ( onClick <| Click PinIcon, view.clickable )
227          ]
228            |> List.filter Tuple.second
229            |> List.map Tuple.first
230         )
231            ++ Styles.pinIconBackground view
232        )
233        (Html.div
234            (Styles.pinIcon view)
235            []
236            :: ([ Maybe.map viewBadge view.badge
237                , Maybe.map viewDropdown view.dropdown
238                ]
239                    |> List.filterMap identity
240               )
241        )
242
243
244viewBadge : Badge -> Html Message
245viewBadge badge =
246    Html.div
247        (id "pin-badge" :: Styles.pinBadge badge)
248        [ Html.div [] [ Html.text badge.text ] ]
249
250
251viewDropdown : Dropdown -> Html Message
252viewDropdown dropdown =
253    Html.ul
254        (Styles.pinIconDropdown dropdown)
255        (List.map viewDropdownItem dropdown.items)
256
257
258viewDropdownItem : DropdownItem -> Html Message
259viewDropdownItem item =
260    Html.li
261        (onClick item.onClick
262            :: (if item.hoverable then
263                    [ onMouseEnter <|
264                        Hover <|
265                            Just <|
266                                PinMenuDropDown item.title.content
267                    , onMouseLeave <| Hover Nothing
268                    ]
269
270                else
271                    []
272               )
273            ++ Styles.pinIconDropdownItem item
274        )
275        [ viewTitle item.title
276        , Html.table [] (List.map viewTableRow item.table)
277        ]
278
279
280viewTitle : Text -> Html Message
281viewTitle title =
282    Html.div
283        (Styles.title title)
284        [ Html.text title.content ]
285
286
287viewTableRow : TableRow -> Html Message
288viewTableRow { left, right, color } =
289    Html.tr
290        [ style "color" color ]
291        [ Html.td [] [ Html.text left ]
292        , Html.td [] [ Html.text right ]
293        ]
294