From bb19a2c26a99c480287cf925acd41fee753938fb Mon Sep 17 00:00:00 2001 From: Steven Vandevelde Date: Mon, 1 Jul 2019 12:07:47 +0200 Subject: [PATCH] Switch to hash-based routing to simplify hosting --- Makefile | 2 +- README.md | 22 +++----- src/Applications/UI.elm | 47 +++++++++++----- src/Applications/UI/Authentication.elm | 4 +- src/Applications/UI/Backdrop.elm | 4 +- src/Applications/UI/Kit.elm | 2 +- src/Applications/UI/Page.elm | 62 ++++++++++++++------- src/Applications/UI/Settings.elm | 2 +- src/Applications/UI/Sources/Form.elm | 4 +- src/Applications/UI/Tracks.elm | 2 +- src/Javascript/Workers/brain.js | 16 +++--- src/Javascript/Workers/search.js | 2 +- src/Javascript/Workers/service.js | 2 +- src/Javascript/index.js | 2 +- src/Javascript/indexed-db.js | 2 +- src/Library/Sources/Services/Dropbox.elm | 2 +- src/Library/Sources/Services/Google.elm | 4 +- src/Static/Css/Application.css | 2 +- src/Static/Hosting/_redirects | 2 +- src/Static/Html/301.html | 2 + src/Static/Html/Application.html | 70 +++++++++++++----------- system/Build/Main.hs | 4 +- 22 files changed, 150 insertions(+), 111 deletions(-) create mode 100644 src/Static/Html/301.html diff --git a/Makefile b/Makefile index 09bb21da..040b06ab 100644 --- a/Makefile +++ b/Makefile @@ -96,7 +96,7 @@ install: server: @echo "> Booting up web server on port 5000" - @devd --port 5000 --all --crossdomain --quiet --notfound=index.html $(BUILD_DIR) + @devd --port 5000 --all --crossdomain --quiet --notfound=301.html $(BUILD_DIR) test: diff --git a/README.md b/README.md index 8e9d78a0..ce613f5e 100644 --- a/README.md +++ b/README.md @@ -42,22 +42,14 @@ Music layer for music storage. ### Hosting on your own server -Diffuse is a static web application, which means it's just HTML, CSS and Javascript. No REST API, database, or anything backend-related involved. That said, the app does require a HTTP web server so it can have "clean urls" and use service workers (preferably HTTPS). It also requires one special rule, and that is, no matter which HTML page is requested, it should always render the root `200.html` or `index.html` file. `https://diffuse.sh` uses Netlify, which in turn uses the `_redirects` file for this. You can download a pre-build web-only version of Diffuse on the [releases](https://github.com/icidasset/diffuse/releases) page. +Diffuse is a static web application, which means it's just HTML, CSS and Javascript. No REST API, database, or anything backend-related involved. The app uses a hash, aka. fragment, based routing system, so you don't need any special server rules for routing. You can download a pre-build web-only version of Diffuse on the [releases](https://github.com/icidasset/diffuse/releases) page. Diffuse uses service workers, so you may need HTTPS for it to work smoothly in certain browsers. I should also note that some source services use OAuth, so you'll need to use your own application credentials (eg. google drive client id + secret). In short: - Diffuse is a static, serverless, web application -- Diffuse requires a HTTP server (prefer HTTPS for service worker) -- Always render the root `200.html` or `index.html` file +- Routing is done using hashes/fragments (eg. `diffuse.sh/#/sources`) - Download a web build on the [releases](https://github.com/icidasset/diffuse/releases) page - -```shell -# Example of a nginx configuration -# Disclaimer: I'm not confident this'll actually work, -# but it should be something along these lines. -location ~ .html$ { - try_files $uri /200.html; -} -``` +- Uses service workers (use HTTPS if possible) +- May need own OAuth application credentials for some source services @@ -67,15 +59,15 @@ location ~ .html$ { ### Building it yourself -For version numbers, -see `.tool-versions` and `stack.yaml`. +For version numbers, see `.tool-versions` and `stack.yaml`. +All of these, except the last one, can be install using [homebrew](https://brew.sh/). - [Elm](https://elm-lang.org/) programming language - [Haskell](https://docs.haskellstack.org/en/stable/README/) programming language - [Google Closure Compiler](https://github.com/google/closure-compiler#getting-started) minifying assets -- [Elm Proofread](https://github.com/icidasset/elm-proofread) documentation tests (optional) - [Devd](https://github.com/cortesi/devd) web server for development (optional) - [Watchexec](https://github.com/watchexec/watchexec) watching for file changes (optional) +- [Elm Proofread](https://github.com/icidasset/elm-proofread) documentation tests (optional) ```shell diff --git a/src/Applications/UI.elm b/src/Applications/UI.elm index 497fda7c..ed45d57e 100644 --- a/src/Applications/UI.elm +++ b/src/Applications/UI.elm @@ -154,12 +154,11 @@ init flags url key = |> update (PageChanged page) |> addCommand - (case maybePage of - Just _ -> - Cmd.none + (if Maybe.isNothing maybePage then + resetUrl key url page - Nothing -> - Nav.replaceUrl key "/" + else + Cmd.none ) @@ -383,7 +382,7 @@ update msg model = |> update (BackdropMsg Backdrop.Default) |> addCommand (Ports.toBrain <| Alien.trigger Alien.SignOut) |> addCommand (Ports.activeQueueItemChanged Nothing) - |> addCommand (Nav.pushUrl model.navKey "/") + |> addCommand (Nav.pushUrl model.navKey "") ----------------------------------------- -- Children @@ -650,9 +649,17 @@ update msg model = |> Nav.pushUrl model.navKey |> returnWithModel model - LinkClicked (Browser.Internal url) -> - if url.path == "/about" then - returnWithModel model (Nav.load "/about") + LinkClicked (Browser.Internal urlWithFragment) -> + let + url = + if urlWithFragment.fragment == Just "/" then + { urlWithFragment | fragment = Nothing } + + else + urlWithFragment + in + if url.path == "about" then + returnWithModel model (Nav.load "about") else returnWithModel model (Nav.pushUrl model.navKey <| Url.toString url) @@ -660,15 +667,22 @@ update msg model = LinkClicked (Browser.External href) -> returnWithModel model (Nav.load href) - UrlChanged url -> - case Page.fromUrl url of - Just page -> + UrlChanged ({ fragment, query } as urlWithQuery) -> + let + url = + { urlWithQuery | query = Nothing } + in + case ( query, Page.fromUrl url ) of + ( Nothing, Just page ) -> { model | page = page, url = url } |> return |> andThen (update <| PageChanged page) - Nothing -> - returnWithModel model (Nav.replaceUrl model.navKey "/") + ( Just _, Just page ) -> + returnWithModel model (resetUrl model.navKey url page) + + _ -> + returnWithModel model (resetUrl model.navKey url Page.Index) updateWithModel : Model -> Msg -> ( Model, Cmd Msg ) @@ -676,6 +690,11 @@ updateWithModel model msg = update msg model +resetUrl : Nav.Key -> Url -> Page.Page -> Cmd Msg +resetUrl key url page = + Nav.replaceUrl key (url.path ++ Page.toString page) + + -- 📣 ░░ CHILDREN & REPLIES diff --git a/src/Applications/UI/Authentication.elm b/src/Applications/UI/Authentication.elm index e44ec5fb..f55a5b7c 100644 --- a/src/Applications/UI/Authentication.elm +++ b/src/Applications/UI/Authentication.elm @@ -470,7 +470,7 @@ view model = [ T.pv3, T.relative ] [ img [ onClick Cancel - , src "/images/diffuse-light.svg" + , src "images/diffuse-light.svg" , width 190 -- @@ -582,7 +582,7 @@ view model = ] [ slab a - [ href "/about" ] + [ href "about" ] [ T.bb , T.no_underline , T.white_60 diff --git a/src/Applications/UI/Backdrop.elm b/src/Applications/UI/Backdrop.elm index 3c211cc4..83c88c83 100644 --- a/src/Applications/UI/Backdrop.elm +++ b/src/Applications/UI/Backdrop.elm @@ -181,7 +181,7 @@ chosen maybeChosen = Html.img [ css chosenStyles , on "load" loadingDecoder - , src ("/images/Background/" ++ c) + , src ("images/Background/" ++ c) ] [ T.fixed , T.overflow_hidden @@ -264,7 +264,7 @@ imageStyles fadeIn isPrevious loadedBackdrop = style "opacity" "0" -- - , style "background-image" ("url(/images/Background/" ++ loadedBackdrop ++ ")") + , style "background-image" ("url(images/Background/" ++ loadedBackdrop ++ ")") , style "background-size" "cover" , style "bottom" "-1px" , style "left" "-1px" diff --git a/src/Applications/UI/Kit.elm b/src/Applications/UI/Kit.elm index 36a1277d..556adeac 100644 --- a/src/Applications/UI/Kit.elm +++ b/src/Applications/UI/Kit.elm @@ -586,7 +586,7 @@ linkStyles = logoBackdropStyles : List Css.Style logoBackdropStyles = - [ Css.backgroundImage (url "/images/diffuse__icon-dark.svg") + [ Css.backgroundImage (url "images/diffuse__icon-dark.svg") , Css.backgroundPosition2 (pct -43.5) (px 98) , Css.backgroundRepeat Css.noRepeat , Css.backgroundSize Css.cover diff --git a/src/Applications/UI/Page.elm b/src/Applications/UI/Page.elm index 8027ea4a..bb18872a 100644 --- a/src/Applications/UI/Page.elm +++ b/src/Applications/UI/Page.elm @@ -1,5 +1,6 @@ module UI.Page exposing (Page(..), fromUrl, sameBase, sources, toString) +import Maybe.Extra as Maybe import Sources exposing (Service(..)) import UI.Playlists.Page as Playlists import UI.Queue.Page as Queue @@ -29,70 +30,93 @@ type Page fromUrl : Url -> Maybe Page fromUrl url = - -- For some oauth stuff, replace the query with the fragment - if Maybe.map (String.contains "token=") url.fragment == Just True then - parse route { url | query = url.fragment } + if Maybe.unwrap False (String.contains "path=") url.query == True then + -- Sometimes we have to use this kind of routing when doing redirections + let + maybePath = + url + |> Url.Parser.parse (query (Query.string "path")) + |> Maybe.join + + path = + Maybe.withDefault "" maybePath + in + if Maybe.unwrap False (String.contains "token=") url.fragment == True then + -- For some oauth stuff, replace the query with the fragment + parse route { url | path = path, query = url.fragment } + + else + parse route { url | path = path } else - parse route url + -- Otherwise do hash-based routing and replace the path with the fragment + parse route { url | path = Maybe.withDefault "" url.fragment } toString : Page -> String -toString page = +toString = + toString_ >> (++) "#/" + + +toString_ : Page -> String +toString_ page = case page of Equalizer -> - "/equalizer" + "equalizer" Index -> - "/" + "" ----------------------------------------- -- Playlists ----------------------------------------- Playlists Playlists.Index -> - "/playlists" + "playlists" Playlists Playlists.New -> - "/playlists/new" + "playlists/new" Playlists (Playlists.Edit playlistName) -> - "/playlists/edit/" ++ playlistName + "playlists/edit/" ++ playlistName ----------------------------------------- -- Queue ----------------------------------------- Queue Queue.History -> - "/queue/history" + "queue/history" Queue Queue.Index -> - "/queue" + "queue" ----------------------------------------- -- Settings ----------------------------------------- Settings Settings.ImportExport -> - "/settings/import-export" + "settings/import-export" Settings Settings.Index -> - "/settings" + "settings" ----------------------------------------- -- Sources ----------------------------------------- Sources (Sources.Edit sourceId) -> - "/sources/edit/" ++ sourceId + "sources/edit/" ++ sourceId Sources Sources.Index -> - "/sources" + "sources" Sources Sources.New -> - "/sources/new" + "sources/new" + + Sources (Sources.NewThroughRedirect Dropbox _) -> + "sources/new/dropbox" Sources (Sources.NewThroughRedirect Google _) -> - "/sources/new/google" + "sources/new/google" Sources (Sources.NewThroughRedirect _ _) -> - "/sources/new" + "sources/new" {-| Are the bases of these two pages the same? diff --git a/src/Applications/UI/Settings.elm b/src/Applications/UI/Settings.elm index b4f81207..ce17b0f8 100644 --- a/src/Applications/UI/Settings.elm +++ b/src/Applications/UI/Settings.elm @@ -253,6 +253,6 @@ backgroundThumbnailColorStyles = backgroundThumbnailInnerStyles : String -> List Css.Style backgroundThumbnailInnerStyles filename = - [ Css.backgroundImage (Css.url <| "/images/Background/Thumbnails/" ++ filename) + [ Css.backgroundImage (Css.url <| "images/Background/Thumbnails/" ++ filename) , Css.backgroundSize Css.cover ] diff --git a/src/Applications/UI/Sources/Form.elm b/src/Applications/UI/Sources/Form.elm index 441e9250..3684f331 100644 --- a/src/Applications/UI/Sources/Form.elm +++ b/src/Applications/UI/Sources/Form.elm @@ -338,7 +338,7 @@ newHow { context } = , text ", do. You can find the configuration for that server " , UI.Kit.link { label = "here" - , url = "/about#CORS__WebDAV" + , url = "about#CORS__WebDAV" } , text "." ] @@ -469,7 +469,7 @@ corsWarning id = , chunk [ T.f6, T.lh_title, T.mb4, T.mt1, T.o_50 ] [ text "You can find the instructions over " - , UI.Kit.link { label = "here", url = "/about#" ++ id } + , UI.Kit.link { label = "here", url = "about#" ++ id } ] ] diff --git a/src/Applications/UI/Tracks.elm b/src/Applications/UI/Tracks.elm index 29d9cd55..78b0f63b 100644 --- a/src/Applications/UI/Tracks.elm +++ b/src/Applications/UI/Tracks.elm @@ -712,7 +712,7 @@ noTracksView isProcessing amountOfSources amountOfTracks amountOfFavourites = [ inline [ T.dib, T.mb2 ] [ UI.Kit.buttonLink - "/sources/new" + "sources/new" UI.Kit.Normal (inline [] diff --git a/src/Javascript/Workers/brain.js b/src/Javascript/Workers/brain.js index e24a9403..fd365f28 100644 --- a/src/Javascript/Workers/brain.js +++ b/src/Javascript/Workers/brain.js @@ -4,14 +4,14 @@ // // This worker is responsible for everything non-UI. -importScripts("/vendor/musicmetadata.min.js") -importScripts("/vendor/subworkers-polyfill.min.js") - -importScripts("/brain.js") -importScripts("/encryption.js") -importScripts("/indexed-db.js") -importScripts("/processing.js") -importScripts("/urls.js") +importScripts("../vendor/musicmetadata.min.js") +importScripts("../vendor/subworkers-polyfill.min.js") + +importScripts("../brain.js") +importScripts("../encryption.js") +importScripts("../indexed-db.js") +importScripts("../processing.js") +importScripts("../urls.js") const app = Elm.Brain.init() diff --git a/src/Javascript/Workers/search.js b/src/Javascript/Workers/search.js index f7abbfe1..e1e77948 100644 --- a/src/Javascript/Workers/search.js +++ b/src/Javascript/Workers/search.js @@ -4,7 +4,7 @@ // // This worker is responsible for searching through a `Track` collection. -importScripts("/vendor/lunr.min.js") +importScripts("../vendor/lunr.min.js") let index diff --git a/src/Javascript/Workers/service.js b/src/Javascript/Workers/service.js index 09788ddf..90a09686 100644 --- a/src/Javascript/Workers/service.js +++ b/src/Javascript/Workers/service.js @@ -5,7 +5,7 @@ // This worker is responsible for caching the application // so it can be used offline. -importScripts("/version.js") +importScripts("version.js") const KEY = diff --git a/src/Javascript/index.js b/src/Javascript/index.js index 5b51d7c4..efc6afb0 100644 --- a/src/Javascript/index.js +++ b/src/Javascript/index.js @@ -26,7 +26,7 @@ addAudioContainer() // Brain // ===== -const brain = new Worker("/workers/brain.js") +const brain = new Worker("workers/brain.js") app.ports.toBrain.subscribe(thing => { brain.postMessage(thing) diff --git a/src/Javascript/indexed-db.js b/src/Javascript/indexed-db.js index 274baef1..85ac0e86 100644 --- a/src/Javascript/indexed-db.js +++ b/src/Javascript/indexed-db.js @@ -5,7 +5,7 @@ // The local database. // This is used instead of localStorage. -importScripts("/vendor/text-encoding-polyfill.min.js") +importScripts("../vendor/text-encoding-polyfill.min.js") const indexedDB = diff --git a/src/Library/Sources/Services/Dropbox.elm b/src/Library/Sources/Services/Dropbox.elm index 04d52306..bfd19cd6 100644 --- a/src/Library/Sources/Services/Dropbox.elm +++ b/src/Library/Sources/Services/Dropbox.elm @@ -85,7 +85,7 @@ authorizationUrl sourceData origin = in [ ( "response_type", "token" ) , ( "client_id", Dict.fetch "appKey" "unknown" sourceData ) - , ( "redirect_uri", origin ++ "/sources/new/dropbox" ) + , ( "redirect_uri", origin ++ "?path=sources/new/dropbox" ) , ( "state", state ) ] |> Common.queryString diff --git a/src/Library/Sources/Services/Google.elm b/src/Library/Sources/Services/Google.elm index dc2326eb..be91a544 100644 --- a/src/Library/Sources/Services/Google.elm +++ b/src/Library/Sources/Services/Google.elm @@ -101,7 +101,7 @@ authorizationUrl sourceData origin = [ ( "access_type", "offline" ) , ( "client_id", Dict.fetch "clientId" "unknown" sourceData ) , ( "prompt", "consent" ) - , ( "redirect_uri", origin ++ "/sources/new/google" ) + , ( "redirect_uri", origin ++ "?path=sources/new/google" ) , ( "response_type", "code" ) , ( "scope", "https://www.googleapis.com/auth/drive.readonly" ) , ( "state", state ) @@ -145,7 +145,7 @@ prepare origin srcData _ toMsg = , ( "client_secret", Dict.fetch "clientSecret" "" srcData ) , ( "code", Dict.fetch "authCode" "" srcData ) , ( "grant_type", "authorization_code" ) - , ( "redirect_uri", origin ++ "/sources/new/google" ) + , ( "redirect_uri", origin ++ "?path=sources/new/google" ) ] -- Refresh access token diff --git a/src/Static/Css/Application.css b/src/Static/Css/Application.css index 4a04643d..df075308 100644 --- a/src/Static/Css/Application.css +++ b/src/Static/Css/Application.css @@ -1,6 +1,6 @@ body { background-color: rgb(2, 7, 14); - background-image: url(/images/ep_naturalblack_pattern.jpg); + background-image: url(images/ep_naturalblack_pattern.jpg); } diff --git a/src/Static/Hosting/_redirects b/src/Static/Hosting/_redirects index b3dfed80..4353d4fc 100644 --- a/src/Static/Hosting/_redirects +++ b/src/Static/Hosting/_redirects @@ -1 +1 @@ -/* /index.html 200 +/* /index.html 301 diff --git a/src/Static/Html/301.html b/src/Static/Html/301.html new file mode 100644 index 00000000..8f1511c9 --- /dev/null +++ b/src/Static/Html/301.html @@ -0,0 +1,2 @@ + + diff --git a/src/Static/Html/Application.html b/src/Static/Html/Application.html index 922ba6ca..a436689f 100644 --- a/src/Static/Html/Application.html +++ b/src/Static/Html/Application.html @@ -9,44 +9,46 @@ + + Diffuse - - - - - + + + + + - + - - + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + @@ -89,21 +91,23 @@ - - - + + + + + - - - + + + - + diff --git a/system/Build/Main.hs b/system/Build/Main.hs index 01ae5d08..0d3bf40c 100644 --- a/system/Build/Main.hs +++ b/system/Build/Main.hs @@ -84,9 +84,7 @@ sequences = lsequence flow :: Dependencies -> (Sequence, Dictionary) -> Dictionary flow _ (Html, dict) = - dict - |> rename "Application.html" "200.html" - |> clone "200.html" "index.html" + rename "Application.html" "index.html" dict flow _ (Css, dict) = dict |> map lowerCasePath -- 2.51.2