A music player that connects to your cloud/distributed storage.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add source contextmenu

+335 -91
+126 -37
src/Applications/UI.elm
··· 25 25 import Html.Styled.Lazy as Lazy 26 26 import Json.Decode 27 27 import Json.Encode 28 + import List.Extra as List 28 29 import Maybe.Extra as Maybe 29 30 import Notifications 30 31 import Process ··· 32 33 import Return3 33 34 import Sources 34 35 import Sources.Encoding 36 + import Sources.Services.Dropbox 37 + import Sources.Services.Google 35 38 import Tachyons.Classes as T 36 39 import Task 37 40 import Time ··· 55 58 import UI.Settings as Settings 56 59 import UI.Settings.Page 57 60 import UI.Sources as Sources 61 + import UI.Sources.ContextMenu as Sources 62 + import UI.Sources.Form 58 63 import UI.Sources.Page 59 64 import UI.Svg.Elements 60 65 import UI.Tracks as Tracks ··· 89 94 let 90 95 maybePage = 91 96 Page.fromUrl url 97 + 98 + page = 99 + Maybe.withDefault Page.Index maybePage 92 100 in 93 - ( ----------------------------------------- 94 - -- Initial model 95 - ----------------------------------------- 96 - { contextMenu = Nothing 97 - , currentTime = Time.millisToPosix flags.initialTime 98 - , isLoading = True 99 - , navKey = key 100 - , notifications = [] 101 - , page = Maybe.withDefault Page.Index maybePage 102 - , url = url 103 - , viewport = flags.viewport 101 + { contextMenu = Nothing 102 + , currentTime = Time.millisToPosix flags.initialTime 103 + , isLoading = True 104 + , navKey = key 105 + , notifications = [] 106 + , page = page 107 + , url = url 108 + , viewport = flags.viewport 104 109 105 - -- Audio 106 - -------- 107 - , audioDuration = 0 108 - , audioHasStalled = False 109 - , audioIsLoading = False 110 - , audioIsPlaying = False 110 + -- Audio 111 + -------- 112 + , audioDuration = 0 113 + , audioHasStalled = False 114 + , audioIsLoading = False 115 + , audioIsPlaying = False 111 116 112 - -- Children 113 - ----------- 114 - , authentication = Authentication.initialModel url 115 - , backdrop = Backdrop.initialModel 116 - , equalizer = Equalizer.initialModel 117 - , queue = Queue.initialModel 118 - , sources = Sources.initialModel (Maybe.andThen Page.sources maybePage) 119 - , tracks = Tracks.initialModel 120 - } 121 - ----------------------------------------- 122 - -- Initial command 123 - ----------------------------------------- 124 - , case maybePage of 125 - Just _ -> 126 - Cmd.none 117 + -- Children 118 + ----------- 119 + , authentication = Authentication.initialModel url 120 + , backdrop = Backdrop.initialModel 121 + , equalizer = Equalizer.initialModel 122 + , queue = Queue.initialModel 123 + , sources = Sources.initialModel 124 + , tracks = Tracks.initialModel 125 + } 126 + |> update 127 + (PageChanged page) 128 + |> addCommand 129 + (case maybePage of 130 + Just _ -> 131 + Cmd.none 127 132 128 - Nothing -> 129 - Nav.replaceUrl key "/" 130 - ) 133 + Nothing -> 134 + Nav.replaceUrl key "/" 135 + ) 131 136 132 137 133 138 ··· 222 227 SignOut -> 223 228 { model 224 229 | authentication = Authentication.initialModel model.url 225 - , sources = Sources.initialModel (Page.sources Page.Index) 230 + , sources = Sources.initialModel 226 231 , tracks = Tracks.initialModel 227 232 } 228 233 |> update (BackdropMsg Backdrop.Default) ··· 364 369 UI.Notifications.show notification model 365 370 366 371 ----------------------------------------- 372 + -- Page Transitions 373 + ----------------------------------------- 374 + PageChanged (Page.Sources (UI.Sources.Page.NewThroughRedirect service args)) -> 375 + let 376 + ( sources, form, defaultContext ) = 377 + ( model.sources 378 + , model.sources.form 379 + , UI.Sources.Form.defaultContext 380 + ) 381 + in 382 + { defaultContext 383 + | data = 384 + case service of 385 + Sources.Dropbox -> 386 + Sources.Services.Dropbox.authorizationSourceData args 387 + 388 + Sources.Google -> 389 + Sources.Services.Google.authorizationSourceData args 390 + 391 + _ -> 392 + defaultContext.data 393 + , service = 394 + service 395 + } 396 + |> (\c -> { form | context = c, step = UI.Sources.Form.How }) 397 + |> (\f -> { sources | form = f }) 398 + |> (\s -> { model | sources = s }) 399 + |> return 400 + 401 + PageChanged (Page.Sources (UI.Sources.Page.Edit sourceId)) -> 402 + let 403 + isLoading = 404 + model.isLoading 405 + 406 + maybeSource = 407 + List.find (.id >> (==) sourceId) model.sources.collection 408 + in 409 + case ( isLoading, maybeSource ) of 410 + ( False, Just source ) -> 411 + let 412 + ( sources, form ) = 413 + ( model.sources 414 + , model.sources.form 415 + ) 416 + 417 + newForm = 418 + { form | context = source } 419 + 420 + newSources = 421 + { sources | form = newForm } 422 + in 423 + return { model | sources = newSources } 424 + 425 + ( False, Nothing ) -> 426 + return model 427 + 428 + ( True, _ ) -> 429 + -- Redirect away from edit-source page 430 + UI.Sources.Page.Index 431 + |> Page.Sources 432 + |> ChangeUrlUsingPage 433 + |> updateWithModel model 434 + 435 + PageChanged _ -> 436 + return model 437 + 438 + ----------------------------------------- 367 439 -- URL 368 440 ----------------------------------------- 369 441 ChangeUrlUsingPage page -> ··· 385 457 UrlChanged url -> 386 458 case Page.fromUrl url of 387 459 Just page -> 388 - return { model | page = page, url = url } 460 + { model | page = page, url = url } 461 + |> return 462 + |> andThen (update <| PageChanged page) 389 463 390 464 Nothing -> 391 465 returnWithModel model (Nav.replaceUrl model.navKey "/") ··· 431 505 ----------------------------------------- 432 506 ShowMoreAuthenticationOptions coordinates -> 433 507 return { model | contextMenu = Just (Authentication.moreOptionsMenu coordinates) } 508 + 509 + ShowSourceContextMenu coordinates source -> 510 + return { model | contextMenu = Just (Sources.sourceMenu source coordinates) } 434 511 435 512 ShowTracksContextMenu coordinates tracks -> 436 513 return { model | contextMenu = Just (Tracks.trackMenu tracks coordinates) } ··· 541 618 |> Tracks.RemoveBySourceId 542 619 |> TracksMsg 543 620 |> updateWithModel model 621 + 622 + ReplaceSourceInCollection source -> 623 + let 624 + sources = 625 + model.sources 626 + in 627 + model.sources.collection 628 + |> List.map (\s -> ifThenElse (s.id == source.id) source s) 629 + |> (\c -> { sources | collection = c }) 630 + |> (\s -> { model | sources = s }) 631 + |> return 632 + |> andThen (translateReply SaveSources) 544 633 545 634 ----------------------------------------- 546 635 -- User Data
+4
src/Applications/UI/Core.elm
··· 128 128 | RemoveNotification { id : Int } 129 129 | ShowNotification (Notification Msg) 130 130 ----------------------------------------- 131 + -- Page Transitions 132 + ----------------------------------------- 133 + | PageChanged Page 134 + ----------------------------------------- 131 135 -- URL 132 136 ----------------------------------------- 133 137 | ChangeUrlUsingPage Page
+13 -7
src/Applications/UI/List.elm
··· 4 4 import Color exposing (Color) 5 5 import Color.Ext as Color 6 6 import Css exposing (px, solid) 7 + import Html.Events.Extra.Mouse as Mouse exposing (onClick) 7 8 import Html.Styled as Html exposing (Html, fromUnstyled) 8 - import Html.Styled.Attributes exposing (css, style) 9 - import Html.Styled.Events exposing (onClick) 9 + import Html.Styled.Attributes as Attributes exposing (css, style, title) 10 10 import Tachyons.Classes as T 11 11 import UI.Kit 12 12 import VirtualDom ··· 18 18 19 19 type alias Action msg = 20 20 { icon : Color -> Int -> VirtualDom.Node msg 21 - , msg : msg 21 + , msg : Mouse.Event -> msg 22 + , title : String 22 23 } 23 24 24 25 ··· 54 55 , chunk 55 56 [ T.flex, T.items_center ] 56 57 (List.map 57 - (\{ icon, msg } -> 58 + (\action -> 58 59 brick 59 - [ onClick msg, style "line-height" "0" ] 60 - [ T.pointer ] 61 - [ fromUnstyled (icon UI.Kit.colors.text 16) ] 60 + [ Attributes.fromUnstyled (onClick action.msg) 61 + , style "line-height" "0" 62 + , title action.title 63 + ] 64 + [ T.ml2 65 + , T.pointer 66 + ] 67 + [ fromUnstyled (action.icon UI.Kit.colors.text 16) ] 62 68 ) 63 69 actions 64 70 )
+4 -3
src/Applications/UI/Notifications.elm
··· 7 7 import Css.Global 8 8 import Css.Transitions exposing (transition) 9 9 import Html.Styled as Html exposing (Html) 10 - import Html.Styled.Attributes exposing (css) 10 + import Html.Styled.Attributes exposing (css, rel) 11 11 import Html.Styled.Events exposing (onDoubleClick) 12 + import Html.Styled.Lazy 12 13 import Notifications exposing (..) 13 14 import Process 14 15 import Tachyons.Classes as T ··· 79 80 , T.z_9999 80 81 ] 81 82 (List.map 82 - notificationView 83 + (Html.Styled.Lazy.lazy notificationView) 83 84 (List.reverse notifications) 84 85 ) 85 86 ··· 132 133 T.o_0 133 134 134 135 else 135 - "" 136 + T.o_100 136 137 ] 137 138 [ contents notification 138 139 , if options.sticky then
+6 -2
src/Applications/UI/Page.elm
··· 65 65 ----------------------------------------- 66 66 -- Sources 67 67 ----------------------------------------- 68 + Sources (Sources.Edit sourceId) -> 69 + "/sources/edit/" ++ sourceId 70 + 68 71 Sources Sources.Index -> 69 72 "/sources" 70 73 ··· 128 131 ----------------------------------------- 129 132 -- Queue 130 133 ----------------------------------------- 131 - , map (Queue Queue.History) (s "queue" </> s "history") 132 134 , map (Queue Queue.Index) (s "queue") 135 + , map (Queue Queue.History) (s "queue" </> s "history") 133 136 134 137 ----------------------------------------- 135 138 -- Settings 136 139 ----------------------------------------- 140 + , map (Settings Settings.Index) (s "settings") 137 141 , map (Settings Settings.ImportExport) (s "settings" </> s "import-export") 138 - , map (Settings Settings.Index) (s "settings") 139 142 140 143 ----------------------------------------- 141 144 -- Sources 142 145 ----------------------------------------- 143 146 , map (Sources Sources.Index) (s "sources") 147 + , map (Sources << Sources.Edit) (s "sources" </> s "edit" </> string) 144 148 , map (Sources Sources.New) (s "sources" </> s "new") 145 149 146 150 -- Oauth
+2
src/Applications/UI/Reply.elm
··· 21 21 -- Context Menu 22 22 ----------------------------------------- 23 23 | ShowMoreAuthenticationOptions Coordinates 24 + | ShowSourceContextMenu Coordinates Source 24 25 | ShowTracksContextMenu Coordinates (List IdentifiedTrack) 25 26 ----------------------------------------- 26 27 -- Notifications ··· 44 45 | ExternalSourceAuthorization (String -> String) 45 46 | ProcessSources 46 47 | RemoveTracksWithSourceId String 48 + | ReplaceSourceInCollection Source 47 49 ----------------------------------------- 48 50 -- User Data 49 51 -----------------------------------------
+60 -7
src/Applications/UI/Sources.elm
··· 1 1 module UI.Sources exposing (Model, Msg(..), initialModel, update, view) 2 2 3 3 import Chunky exposing (..) 4 + import Conditional exposing (ifThenElse) 5 + import Coordinates exposing (Coordinates) 4 6 import Dict.Ext as Dict 7 + import Html.Events.Extra.Mouse as Mouse 5 8 import Html.Styled as Html exposing (Html, text) 6 9 import Json.Decode as Json 7 10 import Material.Icons.Action as Icons ··· 34 37 } 35 38 36 39 37 - initialModel : Maybe Sources.Page -> Model 38 - initialModel maybePage = 40 + initialModel : Model 41 + initialModel = 39 42 { collection = [] 40 43 , currentTime = Time.millisToPosix 0 41 - , form = Form.initialModel maybePage 44 + , form = Form.initialModel 42 45 , isProcessing = False 43 46 , processingNotificationId = Nothing 44 47 } ··· 60 63 -- Collection 61 64 ----------------------------------------- 62 65 | AddToCollection Source 63 - | RemoveFromCollection String 66 + | RemoveFromCollection { sourceId : String } 64 67 | UpdateSourceData Json.Value 68 + ----------------------------------------- 69 + -- Individual 70 + ----------------------------------------- 71 + | SourceContextMenu Source Mouse.Event 72 + | ToggleActivation { sourceId : String } 65 73 66 74 67 75 update : Msg -> Model -> Return Model Msg Reply ··· 107 115 , UI.Reply.ProcessSources 108 116 ] 109 117 110 - RemoveFromCollection sourceId -> 118 + RemoveFromCollection { sourceId } -> 111 119 model.collection 112 120 |> List.filter (.id >> (/=) sourceId) 113 121 |> (\c -> { model | collection = c }) ··· 137 145 |> return 138 146 |> addReply UI.Reply.SaveSources 139 147 148 + ----------------------------------------- 149 + -- Individual 150 + ----------------------------------------- 151 + SourceContextMenu source mouseEvent -> 152 + let 153 + coordinates = 154 + Coordinates.fromTuple mouseEvent.clientPos 155 + in 156 + returnRepliesWithModel 157 + model 158 + [ ShowSourceContextMenu coordinates source ] 159 + 160 + ToggleActivation { sourceId } -> 161 + model.collection 162 + |> List.map 163 + (\source -> 164 + ifThenElse 165 + (source.id == sourceId) 166 + { source | enabled = not source.enabled } 167 + source 168 + ) 169 + |> (\collection -> { model | collection = collection }) 170 + |> return 171 + |> addReply SaveSources 172 + 140 173 141 174 142 175 -- 🗺 ··· 148 181 (case page of 149 182 Index -> 150 183 index model 184 + 185 + Edit sourceId -> 186 + List.map (Html.map FormMsg) (Form.edit model.form) 151 187 152 188 New -> 153 189 List.map (Html.map FormMsg) (Form.new model.form) ··· 215 251 216 252 sourceActions : Source -> List (UI.List.Action Msg) 217 253 sourceActions source = 218 - [ { icon = Icons.close 219 - , msg = RemoveFromCollection source.id 254 + [ { icon = 255 + if source.enabled then 256 + Icons.check 257 + 258 + else 259 + Icons.block 260 + , msg = always (ToggleActivation { sourceId = source.id }) 261 + , title = 262 + if source.enabled then 263 + "Enabled (click to disable)" 264 + 265 + else 266 + "Disabled (click to enable)" 267 + } 268 + 269 + -- 270 + , { icon = Icons.settings 271 + , msg = SourceContextMenu source 272 + , title = "Menu" 220 273 } 221 274 ]
+32
src/Applications/UI/Sources/ContextMenu.elm
··· 1 + module UI.Sources.ContextMenu exposing (sourceMenu) 2 + 3 + import ContextMenu exposing (..) 4 + import Coordinates exposing (Coordinates) 5 + import Material.Icons.Action as Icons 6 + import Material.Icons.Image as Icons 7 + import Sources exposing (Source) 8 + import UI.Core exposing (Msg(..)) 9 + import UI.Page 10 + import UI.Sources as Sources 11 + import UI.Sources.Page 12 + 13 + 14 + 15 + -- 🔱 16 + 17 + 18 + sourceMenu : Source -> Coordinates -> ContextMenu Msg 19 + sourceMenu source = 20 + ContextMenu 21 + [ ( Icons.edit 22 + , "Edit" 23 + , source.id 24 + |> UI.Sources.Page.Edit 25 + |> UI.Page.Sources 26 + |> ChangeUrlUsingPage 27 + ) 28 + , ( Icons.delete 29 + , "Remove" 30 + , SourcesMsg (Sources.RemoveFromCollection { sourceId = source.id }) 31 + ) 32 + ]
+84 -33
src/Applications/UI/Sources/Form.elm
··· 1 - module UI.Sources.Form exposing (FormStep(..), Model, Msg(..), initialModel, new, takeStepBackwards, takeStepForwards, update) 1 + module UI.Sources.Form exposing (FormStep(..), Model, Msg(..), defaultContext, edit, initialModel, new, takeStepBackwards, takeStepForwards, update) 2 2 3 3 import Browser.Navigation as Nav 4 4 import Chunky exposing (..) ··· 19 19 import Tachyons.Classes as T 20 20 import UI.Kit exposing (ButtonType(..), select) 21 21 import UI.Navigation exposing (..) 22 - import UI.Page 23 - import UI.Reply exposing (Reply) 22 + import UI.Page as Page 23 + import UI.Reply as Reply exposing (Reply) 24 24 import UI.Sources.Page as Sources 25 25 26 26 ··· 40 40 | By 41 41 42 42 43 - initialModel : Maybe Sources.Page -> Model 44 - initialModel maybePage = 45 - { step = 46 - case maybePage of 47 - Just (Sources.NewThroughRedirect _ _) -> 48 - How 49 - 50 - _ -> 51 - Where 52 - , context = 53 - case maybePage of 54 - Just (Sources.NewThroughRedirect Dropbox args) -> 55 - { defaultContext 56 - | data = Sources.Services.Dropbox.authorizationSourceData args 57 - , service = Dropbox 58 - } 59 - 60 - Just (Sources.NewThroughRedirect Google args) -> 61 - { defaultContext 62 - | data = Sources.Services.Google.authorizationSourceData args 63 - , service = Google 64 - } 65 - 66 - _ -> 67 - defaultContext 43 + initialModel : Model 44 + initialModel = 45 + { step = Where 46 + , context = defaultContext 68 47 } 69 48 70 49 ··· 90 69 type Msg 91 70 = AddSource 92 71 | Bypass 72 + | EditSource 73 + | ReturnToIndex 93 74 | SelectService String 94 75 | SetData String String 95 76 | TakeStep ··· 102 83 AddSource -> 103 84 returnRepliesWithModel 104 85 { model | step = Where, context = defaultContext } 105 - [ UI.Reply.GoToPage (UI.Page.Sources Sources.Index) 106 - , UI.Reply.AddSourceToCollection model.context 86 + [ Reply.GoToPage (Page.Sources Sources.Index) 87 + , Reply.AddSourceToCollection model.context 107 88 ] 108 89 109 90 Bypass -> 110 91 return model 111 92 93 + EditSource -> 94 + returnRepliesWithModel 95 + { model | step = Where, context = defaultContext } 96 + [ Reply.ReplaceSourceInCollection model.context 97 + , Reply.ProcessSources 98 + , Reply.GoToPage (Page.Sources Sources.Index) 99 + ] 100 + 101 + ReturnToIndex -> 102 + returnRepliesWithModel 103 + model 104 + [ Reply.GoToPage (Page.Sources Sources.Index) ] 105 + 112 106 SelectService serviceKey -> 113 107 case Services.keyToType serviceKey of 114 108 Just service -> ··· 144 138 ( Where, Dropbox ) -> 145 139 model.context.data 146 140 |> Sources.Services.Dropbox.authorizationUrl 147 - |> UI.Reply.ExternalSourceAuthorization 141 + |> Reply.ExternalSourceAuthorization 148 142 |> returnReplyWithModel model 149 143 150 144 ( Where, Google ) -> 151 145 model.context.data 152 146 |> Sources.Services.Google.authorizationUrl 153 - |> UI.Reply.ExternalSourceAuthorization 147 + |> Reply.ExternalSourceAuthorization 154 148 |> returnReplyWithModel model 155 149 156 150 _ -> ··· 205 199 UI.Navigation.local 206 200 [ ( Icon Icons.arrow_back 207 201 , Label "Back to list" Hidden 208 - , GoToPage (UI.Page.Sources Sources.Index) 202 + , GoToPage (Page.Sources Sources.Index) 209 203 ) 210 204 ] 211 205 ··· 340 334 Normal 341 335 AddSource 342 336 (text "Add source") 337 + ] 338 + ] 339 + 340 + 341 + 342 + -- EDIT 343 + 344 + 345 + edit : Model -> List (Html Msg) 346 + edit { context } = 347 + [ ----------------------------------------- 348 + -- Navigation 349 + ----------------------------------------- 350 + UI.Navigation.local 351 + [ ( Icon Icons.arrow_back 352 + , Label "Go Back" Shown 353 + , PerformMsg ReturnToIndex 354 + ) 355 + ] 356 + 357 + ----------------------------------------- 358 + -- Content 359 + ----------------------------------------- 360 + , (\h -> form [ chunk [ T.tl, T.w_100 ] [ UI.Kit.canister h ] ]) 361 + [ UI.Kit.h3 "Edit source" 362 + 363 + -- Fields 364 + --------- 365 + , let 366 + properties = 367 + Services.properties context.service 368 + 369 + dividingPoint = 370 + toFloat (List.length properties) / 2 371 + 372 + ( listA, listB ) = 373 + List.splitAt (ceiling dividingPoint) properties 374 + in 375 + chunk 376 + [ T.flex, T.pt3 ] 377 + [ chunk 378 + [ T.flex_grow_1, T.pr3 ] 379 + (List.map (renderProperty context) listA) 380 + , chunk 381 + [ T.flex_grow_1, T.pl3 ] 382 + (List.map (renderProperty context) listB) 383 + ] 384 + 385 + -- Button 386 + --------- 387 + , chunk 388 + [ T.mt3, T.tc ] 389 + [ UI.Kit.button 390 + Normal 391 + EditSource 392 + (text "Save") 393 + ] 343 394 ] 344 395 ] 345 396
+1
src/Applications/UI/Sources/Page.elm
··· 9 9 10 10 type Page 11 11 = Index 12 + | Edit String 12 13 | New 13 14 | NewThroughRedirect Service { codeOrToken : Maybe String, state : Maybe String }
+2 -1
src/Applications/UI/Tracks.elm
··· 106 106 return model 107 107 108 108 SetEnabledSourceIds sourceIds -> 109 - return { model | enabledSourceIds = sourceIds } 109 + reviseCollection identify 110 + { model | enabledSourceIds = sourceIds } 110 111 111 112 SetNowPlaying maybeIdentifiedTrack -> 112 113 let
+1 -1
src/Library/Notifications.elm
··· 108 108 Html.pre 109 109 [ style "font-size" "11px" ] 110 110 [ T.bg_black_50, T.br2, T.mb0, T.mt3, T.pa2 ] 111 - [ Html.code [ class T.v_mid ] [ Html.text code ] ] 111 + [ slab Html.code [] [ T.ws_normal, T.v_mid ] [ Html.text code ] ] 112 112 ] 113 113 ) 114 114