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.

PWA and mobile stuff

+270 -70
+2
Makefile
··· 66 66 @curl https://unpkg.com/3box@1.8.5/dist/3box.min.js -o $(VENDOR_DIR)/3box.min.js 67 67 @curl https://unpkg.com/blockstack@19.2.1/dist/blockstack.js -o $(VENDOR_DIR)/blockstack.min.js 68 68 @curl https://unpkg.com/lunr@2.3.6/lunr.js -o $(VENDOR_DIR)/lunr.js 69 + @curl https://unpkg.com/inobounce@0.1.6/inobounce.min.js -o $(VENDOR_DIR)/inobounce.min.js 69 70 @curl https://unpkg.com/remotestoragejs@1.2.2/release/remotestorage.js -o $(VENDOR_DIR)/remotestorage.min.js 70 71 @curl https://unpkg.com/fast-text-encoding@1.0.0/text.min.js -o $(VENDOR_DIR)/text-encoding-polyfill.min.js 71 72 @curl https://unpkg.com/tachyons@4.11.1/css/tachyons.min.css -o $(VENDOR_DIR)/tachyons.min.css 73 + @curl https://unpkg.com/tocca@2.0.4/Tocca.min.js -o $(VENDOR_DIR)/tocca.min.js 72 74 73 75 @# Non-NPM dependencies 74 76 @curl https://raw.githubusercontent.com/icidasset/diffuse-musicmetadata/0ae8c854e18b6960b9f7e94b7eb47868416dc2ad/dist/musicmetadata.min.js -o $(VENDOR_DIR)/musicmetadata.min.js
+14 -3
src/Applications/UI.elm
··· 23 23 import File.Download 24 24 import File.Select 25 25 import Html.Events.Extra.Pointer as Pointer 26 + import Html.Events.Extra.Touch as Touch 26 27 import Html.Styled as Html exposing (Html, section, toUnstyled) 27 - import Html.Styled.Attributes as Attributes exposing (css, id) 28 + import Html.Styled.Attributes as Attributes exposing (css, id, style) 28 29 import Html.Styled.Events exposing (onClick) 29 30 import Html.Styled.Lazy as Lazy 30 31 import Json.Decode ··· 116 117 , isDragging = False 117 118 , isLoading = True 118 119 , isOnline = flags.isOnline 120 + , isTouchDevice = False 119 121 , navKey = key 120 122 , notifications = [] 121 123 , page = page ··· 246 248 ( { model | isOnline = bool } 247 249 , Cmd.none 248 250 ) 251 + 252 + IndicateTouchDevice -> 253 + return { model | isTouchDevice = True } 249 254 250 255 StoppedDragging -> 251 256 let ··· 1224 1229 , Attributes.fromUnstyled (Pointer.onUp <| always StoppedDragging) 1225 1230 ] 1226 1231 1232 + else if not model.isTouchDevice then 1233 + [ Attributes.fromUnstyled (Touch.onStart <| always IndicateTouchDevice) ] 1234 + 1227 1235 else 1228 1236 [] 1229 1237 ) ··· 1350 1358 1351 1359 content : List (Html msg) -> Html msg 1352 1360 content = 1353 - chunk 1361 + brick 1362 + [ style "min-height" "calc(var(--vh, 1vh) * 100)" ] 1354 1363 [ T.flex 1355 1364 , T.flex_column 1356 1365 , T.items_center ··· 1496 1505 1497 1506 vesselInnerStyles : List Css.Style 1498 1507 vesselInnerStyles = 1499 - [ Css.property "-webkit-mask-image" "-webkit-radial-gradient(white, black)" ] 1508 + [ Css.property "-webkit-mask-image" "-webkit-radial-gradient(white, black)" 1509 + , Css.property "-webkit-overflow-scrolling" "touch" 1510 + ]
+2
src/Applications/UI/Core.elm
··· 51 51 , isDragging : Bool 52 52 , isLoading : Bool 53 53 , isOnline : Bool 54 + , isTouchDevice : Bool 54 55 , navKey : Nav.Key 55 56 , notifications : List (Notification Msg) 56 57 , page : Page ··· 96 97 | HideAlfred 97 98 | HideContextMenu 98 99 | HideOverlay 100 + | IndicateTouchDevice 99 101 | KeyboardMsg Keyboard.Msg 100 102 | LoadEnclosedUserData Json.Value 101 103 | LoadHypaethralUserData Json.Value
+14 -2
src/Applications/UI/Equalizer.elm
··· 6 6 import Common 7 7 import Coordinates exposing (Coordinates) 8 8 import Css 9 + import Css.Ext as Css 9 10 import Equalizer exposing (..) 10 11 import Html.Events.Extra.Pointer as Pointer 11 12 import Html.Styled exposing (Html, text) ··· 232 233 ----------------------------------------- 233 234 -- Content 234 235 ----------------------------------------- 235 - , chunk 236 + , brick 237 + [ css eqStyles ] 236 238 [ T.relative ] 237 239 [ chunk 238 240 [ T.absolute, T.left_0, T.top_0 ] ··· 303 305 304 306 -- 305 307 , Html.Styled.Events.onDoubleClick (ResetKnob knobType) 306 - , Html.Styled.Attributes.fromUnstyled <| Pointer.onDown (ActivateKnob knobType) 308 + 309 + -- 310 + , knobType 311 + |> ActivateKnob 312 + |> Pointer.onDown 313 + |> Html.Styled.Attributes.fromUnstyled 307 314 ] 308 315 [ T.br_100, T.center, T.pointer, T.relative ] 309 316 [ Html.Styled.map never knob__ ] ··· 391 398 392 399 393 400 -- 🖼 401 + 402 + 403 + eqStyles : List Css.Style 404 + eqStyles = 405 + [ Css.disableUserSelection ] 394 406 395 407 396 408 columnStyles : List Css.Style
+10 -7
src/Applications/UI/Navigation.elm
··· 128 128 localWithTabindex tabindex_ items = 129 129 brick 130 130 [ css localStyles ] 131 - [ T.bb, T.flex ] 132 - (items 133 - |> List.reverse 134 - |> List.map (localItem tabindex_) 135 - |> List.reverse 136 - ) 131 + [ T.bb ] 132 + [ chunk 133 + [ T.flex ] 134 + (items 135 + |> List.reverse 136 + |> List.map (localItem tabindex_) 137 + |> List.reverse 138 + ) 139 + ] 137 140 138 141 139 142 localItem : Int -> ( Icon msg, Label, Action msg ) -> Html msg ··· 169 172 , T.bg_transparent 170 173 , T.bl_0 171 174 , T.fw6 172 - , T.inline_flex 175 + , T.flex 173 176 , T.items_center 174 177 , T.justify_center 175 178 , T.lh_solid
+3 -2
src/Applications/UI/Tracks.elm
··· 260 260 ----------------------------------------- 261 261 -- Menus 262 262 ----------------------------------------- 263 - ShowTrackMenu trackIndex mouseEvent -> 263 + ShowTrackMenu trackIndex coordinates -> 264 264 let 265 265 selection = 266 266 if List.isEmpty model.selectedTrackIndexes then ··· 281 281 |> Maybe.withDefault acc 282 282 ) 283 283 [] 284 - |> ShowTracksContextMenu (Coordinates.fromTuple mouseEvent.clientPos) 284 + |> ShowTracksContextMenu coordinates 285 285 |> returnReplyWithModel { model | selectedTrackIndexes = selection } 286 286 287 287 ShowViewMenu grouping mouseEvent -> ··· 544 544 List -> 545 545 UI.Tracks.Scene.List.view 546 546 { height = core.viewport.height 547 + , isTouchDevice = core.isTouchDevice 547 548 , isVisible = core.page == UI.Page.Index 548 549 } 549 550 core.tracks
+2 -1
src/Applications/UI/Tracks/Core.elm
··· 1 1 module UI.Tracks.Core exposing (Model, Msg(..), Scene(..)) 2 2 3 + import Coordinates exposing (Coordinates) 3 4 import Html.Events.Extra.Mouse as Mouse 4 5 import InfiniteList 5 6 import Json.Encode as Json ··· 69 70 ----------------------------------------- 70 71 -- Menus 71 72 ----------------------------------------- 72 - | ShowTrackMenu Int Mouse.Event 73 + | ShowTrackMenu Int Coordinates 73 74 | ShowViewMenu (Maybe Grouping) Mouse.Event 74 75 ----------------------------------------- 75 76 -- Playlists
+90 -28
src/Applications/UI/Tracks/Scene/List.elm
··· 7 7 import Color.Ext as Color 8 8 import Color.Manipulate as Color 9 9 import Conditional exposing (ifThenElse) 10 + import Coordinates 10 11 import Css 11 12 import Html exposing (Html, text) 12 13 import Html.Attributes exposing (class, style) 13 14 import Html.Events 14 - import Html.Events.Extra.Mouse as Mouse exposing (onWithOptions) 15 + import Html.Events.Extra.Mouse as Mouse 15 16 import Html.Styled 16 17 import Html.Styled.Attributes 17 18 import Html.Styled.Events 18 19 import Html.Styled.Lazy 19 20 import InfiniteList 20 - import Json.Decode 21 + import Json.Decode as Decode 21 22 import List.Ext as List 22 23 import Material.Icons exposing (Coloring(..)) 23 24 import Material.Icons.Av as Icons ··· 39 40 40 41 41 42 type alias Necessities = 42 - { height : Float, isVisible : Bool } 43 + { height : Float 44 + , isTouchDevice : Bool 45 + , isVisible : Bool 46 + } 43 47 44 48 45 49 view : Necessities -> Model -> Html.Styled.Html Msg ··· 65 69 [ Html.Styled.Attributes.fromUnstyled (InfiniteList.onScroll InfiniteListMsg) 66 70 , Html.Styled.Attributes.id containerId 67 71 , Html.Styled.Attributes.tabindex (ifThenElse necessities.isVisible 0 -1) 72 + , Html.Styled.Attributes.style "-webkit-overflow-scrolling" "touch" 68 73 ] 69 74 [ C.disable_selection 70 75 , T.flex_grow_1 ··· 91 96 { itemView = 92 97 case maybeDnD of 93 98 Just dnd -> 94 - playlistItemView favouritesOnly selectedTrackIndexes dnd 99 + playlistItemView 100 + necessities.isTouchDevice 101 + favouritesOnly 102 + selectedTrackIndexes 103 + dnd 95 104 96 105 _ -> 97 - defaultItemView favouritesOnly selectedTrackIndexes 106 + defaultItemView 107 + necessities.isTouchDevice 108 + favouritesOnly 109 + selectedTrackIndexes 98 110 99 111 -- 100 112 , itemHeight = InfiniteList.withVariableHeight dynamicRowHeight ··· 288 300 -- INFINITE LIST ITEM 289 301 290 302 291 - defaultItemView : Bool -> List Int -> Int -> Int -> IdentifiedTrack -> Html Msg 292 - defaultItemView favouritesOnly selectedTrackIndexes _ idx identifiedTrack = 303 + defaultItemView : Bool -> Bool -> List Int -> Int -> Int -> IdentifiedTrack -> Html Msg 304 + defaultItemView isTouchDevice favouritesOnly selectedTrackIndexes _ idx identifiedTrack = 293 305 let 294 306 ( identifiers, track ) = 295 307 identifiedTrack ··· 316 328 [ rowStyles idx isSelected identifiers 317 329 318 330 -- 319 - , [ contextMenuEvent identifiedTrack 320 - , mousePlayEvent identifiedTrack 321 - , mouseSelectEvent identifiedTrack 322 - ] 331 + , if isTouchDevice then 332 + [ touchContextMenuEvent identifiedTrack 333 + , touchPlayEvent identifiedTrack 334 + ] 335 + 336 + else 337 + [ mouseContextMenuEvent identifiedTrack 338 + , mousePlayEvent identifiedTrack 339 + , mouseSelectEvent identifiedTrack 340 + ] 323 341 324 342 -- 325 343 , [ T.flex ··· 341 359 ] 342 360 343 361 344 - playlistItemView : Bool -> List Int -> DnD.Model Int -> Int -> Int -> IdentifiedTrack -> Html Msg 345 - playlistItemView favouritesOnly selectedTrackIndexes dnd _ idx identifiedTrack = 362 + playlistItemView : Bool -> Bool -> List Int -> DnD.Model Int -> Int -> Int -> IdentifiedTrack -> Html Msg 363 + playlistItemView isTouchDevice favouritesOnly selectedTrackIndexes dnd _ idx identifiedTrack = 346 364 let 347 365 ( identifiers, track ) = 348 366 identifiedTrack ··· 363 381 [ rowStyles idx isSelected identifiers 364 382 365 383 -- 366 - , [ contextMenuEvent identifiedTrack 367 - , mousePlayEvent identifiedTrack 368 - , mouseSelectEvent identifiedTrack 369 - , DnD.listenToStart dragEnv listIdx 370 - ] 384 + , if isTouchDevice then 385 + [ touchContextMenuEvent identifiedTrack 386 + , touchPlayEvent identifiedTrack 387 + , DnD.listenToStart dragEnv listIdx 388 + ] 389 + 390 + else 391 + [ mouseContextMenuEvent identifiedTrack 392 + , mousePlayEvent identifiedTrack 393 + , mouseSelectEvent identifiedTrack 394 + , DnD.listenToStart dragEnv listIdx 395 + ] 371 396 372 397 -- 373 398 , [ T.flex ··· 400 425 ] 401 426 402 427 403 - contextMenuEvent : IdentifiedTrack -> Html.Attribute Msg 404 - contextMenuEvent ( i, _ ) = 405 - i.indexInList 406 - |> ShowTrackMenu 407 - |> onWithOptions 408 - "contextmenu" 409 - { stopPropagation = True 410 - , preventDefault = True 411 - } 428 + mouseContextMenuEvent : IdentifiedTrack -> Html.Attribute Msg 429 + mouseContextMenuEvent ( i, _ ) = 430 + Html.Events.custom 431 + "contextmenu" 432 + (Decode.map 433 + (\event -> 434 + { message = ShowTrackMenu i.indexInList (Coordinates.fromTuple event.clientPos) 435 + , stopPropagation = True 436 + , preventDefault = True 437 + } 438 + ) 439 + Mouse.eventDecoder 440 + ) 412 441 413 442 414 443 mousePlayEvent : IdentifiedTrack -> Html.Attribute Msg 415 444 mousePlayEvent ( i, t ) = 416 445 Html.Events.custom 417 446 "dblclick" 418 - (Json.Decode.succeed 447 + (Decode.succeed 419 448 { message = 420 449 if i.isMissing then 421 450 Bypass ··· 431 460 mouseSelectEvent : IdentifiedTrack -> Html.Attribute Msg 432 461 mouseSelectEvent ( i, _ ) = 433 462 Mouse.onClick (MarkAsSelected i.indexInList) 463 + 464 + 465 + touchContextMenuEvent : IdentifiedTrack -> Html.Attribute Msg 466 + touchContextMenuEvent ( i, _ ) = 467 + Html.Events.custom 468 + "longtap" 469 + (Decode.map2 470 + (\x y -> 471 + { message = ShowTrackMenu i.indexInList { x = x, y = y } 472 + , stopPropagation = True 473 + , preventDefault = True 474 + } 475 + ) 476 + (Decode.field "x" Decode.float) 477 + (Decode.field "y" Decode.float) 478 + ) 479 + 480 + 481 + touchPlayEvent : IdentifiedTrack -> Html.Attribute Msg 482 + touchPlayEvent ( i, t ) = 483 + Html.Events.custom 484 + "dbltap" 485 + (Decode.succeed 486 + { message = 487 + if i.isMissing then 488 + Bypass 489 + 490 + else 491 + Reply [ UI.Reply.PlayTrack ( i, t ) ] 492 + , stopPropagation = True 493 + , preventDefault = True 494 + } 495 + ) 434 496 435 497 436 498
+28
src/Javascript/index.js
··· 214 214 document.addEventListener("MediaNext", () => { 215 215 app.ports.requestNext.send(null) 216 216 }) 217 + 218 + 219 + 220 + // Touch Events 221 + // ------------ 222 + 223 + tocca({ 224 + dbltapThreshold: 600 225 + }) 226 + 227 + 228 + 229 + // Vertical Height 230 + // --------------- 231 + 232 + setVerticalHeightUnit() 233 + 234 + 235 + window.addEventListener("resize", () => { 236 + setTimeout(setVerticalHeightUnit, 0) 237 + setTimeout(setVerticalHeightUnit, 32) 238 + }) 239 + 240 + 241 + function setVerticalHeightUnit() { 242 + const vh = window.innerHeight * 0.01 243 + document.documentElement.style.setProperty("--vh", `${vh}px`) 244 + }
+73
src/Javascript/rubber-band.js
··· 1 + // 2 + // Rubber Band 3 + // \ (•◡•) / 4 + // 5 + // This prevents the iOS rubber-band scrolling on the body element. 6 + // It's based on the https://github.com/lazd/iNoBounce library, 7 + // with the difference that this'll scroll if the app is > 100vh. 8 + 9 + 10 + let touchStartY = 0, 11 + supportsPassiveOption = false 12 + 13 + 14 + try { 15 + window.addEventListener("test", null, Object.defineProperty( 16 + {}, "passive", { get: _ => { supportsPassiveOption = true } } 17 + )) 18 + } catch (e) { 19 + // 20 + } 21 + 22 + 23 + const documentTouchStart = event => { 24 + touchStartY = event.touches ? event.touches[0].screenY : event.screenY 25 + } 26 + 27 + 28 + const documentTouchMove = event => { 29 + let el = event.target 30 + 31 + while (el !== document.body && el !== document) { 32 + const style = window.getComputedStyle(el) 33 + if (!style) break 34 + 35 + // Ignore range input element 36 + if (el.nodeName === "INPUT" && el.getAttribute("type") === "range") return 37 + 38 + // Determine if the element should scroll 39 + const scrolling = style.getPropertyValue("-webkit-overflow-scrolling") 40 + const overflowY = style.getPropertyValue("overflow-y") 41 + const height = parseInt(style.getPropertyValue("height"), 10) 42 + const isScrollable = scrolling === "touch" && (overflowY === "auto" || overflowY === "scroll") 43 + const canScroll = el.scrollHeight > el.offsetHeight 44 + 45 + if (isScrollable && canScroll) { 46 + const currentY = event.touches ? event.touches[0].screenY : event.screenY 47 + const isAtTop = (touchStartY <= currentY && el.scrollTop === 0) 48 + const isAtBottom = (touchStartY >= currentY && el.scrollHeight - el.scrollTop === height) 49 + if (isAtTop || isAtBottom) event.preventDefault() 50 + return 51 + } 52 + 53 + el = el.parentNode 54 + } 55 + 56 + if (document.body.scrollHeight <= window.innerHeight) { 57 + event.preventDefault() 58 + } 59 + } 60 + 61 + 62 + window.addEventListener( 63 + "touchstart", 64 + documentTouchStart, 65 + supportsPassiveOption ? { passive : false } : false 66 + ) 67 + 68 + 69 + window.addEventListener( 70 + "touchmove", 71 + documentTouchMove, 72 + supportsPassiveOption ? { passive : false } : false 73 + )
+3 -3
src/Static/About/Layout.html
··· 13 13 <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" /> 14 14 <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" /> 15 15 <link rel="manifest" href="/site.webmanifest" /> 16 - <link rel="mask-icon" href="/safari-pinned-tab.svg" color="#995b6c" /> 17 - <meta name="msapplication-TileColor" content="#995b6c" /> 18 - <meta name="theme-color" content="#995b6c" /> 16 + <link rel="mask-icon" href="/safari-pinned-tab.svg" color="#1E191A" /> 17 + <meta name="msapplication-TileColor" content="#1E191A" /> 18 + <meta name="theme-color" content="#1E191A" /> 19 19 20 20 <!-- Styles --> 21 21 <link rel="stylesheet" href="/vendor/tachyons.min.css" />
-19
src/Static/Favicons/site.webmanifest
··· 1 - { 2 - "name": "Diffuse", 3 - "short_name": "Diffuse", 4 - "icons": [ 5 - { 6 - "src": "/android-chrome-192x192.png", 7 - "sizes": "192x192", 8 - "type": "image/png" 9 - }, 10 - { 11 - "src": "/android-chrome-512x512.png", 12 - "sizes": "512x512", 13 - "type": "image/png" 14 - } 15 - ], 16 - "theme_color": "#995b6c", 17 - "background_color": "#995b6c", 18 - "display": "standalone" 19 - }
+7 -4
src/Static/Html/Application.html
··· 4 4 5 5 <meta charset="utf-8" /> 6 6 <meta name="media-controllable" /> 7 + <meta name="apple-mobile-web-app-capable" content="yes" /> 7 8 8 9 <title>Diffuse</title> 9 10 10 11 <!-- Viewport --> 11 - <meta name="viewport" content="width=device-width, initial-scale=1" /> 12 + <meta name="viewport" content="width=device-width, initial-scale=0.9, maximum-scale=0.9, user-scalable=no" /> 12 13 13 14 <!-- Favicons & Mobile --> 14 15 <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" /> 15 16 <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" /> 16 17 <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" /> 17 18 <link rel="manifest" href="/site.webmanifest" /> 18 - <link rel="mask-icon" href="/safari-pinned-tab.svg" color="#995b6c" /> 19 - <meta name="msapplication-TileColor" content="#995b6c" /> 20 - <meta name="theme-color" content="#995b6c" /> 19 + <link rel="mask-icon" href="/safari-pinned-tab.svg" color="#1E191A" /> 20 + <meta name="msapplication-TileColor" content="#1E191A" /> 21 + <meta name="theme-color" content="#1E191A" /> 21 22 22 23 <!-- Stylesheets --> 23 24 <link rel="stylesheet" href="/vendor/tachyons.min.css"> ··· 87 88 <!-- Scripts --> 88 89 <script src="/vendor/pep.min.js"></script> 89 90 <script src="/vendor/subworkers-polyfill.min.js"></script> 91 + <script src="/vendor/tocca.min.js"></script> 90 92 91 93 <script src="/urls.js"></script> 92 94 <script src="/audio-engine.js"></script> 95 + <script src="/rubber-band.js"></script> 93 96 <script src="/application.js"></script> 94 97 95 98 <!-- Initialize Boot Procedure -->
+19
src/Static/Manifests/site.webmanifest
··· 1 + { 2 + "name": "Diffuse", 3 + "icons": [ 4 + { 5 + "src": "/images/icon.png", 6 + "sizes": "192x192", 7 + "type": "image/png" 8 + }, 9 + { 10 + "src": "/images/icon.png", 11 + "sizes": "512x512", 12 + "type": "image/png" 13 + } 14 + ], 15 + "theme_color": "#1E191A", 16 + "background_color": "#1E191A", 17 + "display": "standalone", 18 + "start_url": "/" 19 + }
src/Static/manifest.json src/Static/Manifests/manifest.json
+3 -1
system/Build/Main.hs
··· 70 70 , ( Html, list "Static/Html/**/*.html" ) 71 71 , ( Images, list "Static/Images/**/*.*" ) 72 72 , ( Js, list "Javascript/**/*.js" ) 73 - , ( Manifest, list "Static/manifest.json" ) 73 + , ( Manifest, list "Static/Manifests/*.*" ) 74 74 75 75 -- About Pages 76 76 , ( AboutPages, list "Static/About/**/*.md" ) ··· 138 138 ] 139 139 140 140 141 + 141 142 -- INSERT 142 143 143 144 ··· 182 183 [] 183 184 in 184 185 dict <> defs 186 + 185 187 186 188 187 189 -- COMMON