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