diff options
Diffstat (limited to 'Hero/App.hs')
-rw-r--r-- | Hero/App.hs | 1012 |
1 files changed, 558 insertions, 454 deletions
diff --git a/Hero/App.hs b/Hero/App.hs index 7f55052..6afcbd2 100644 --- a/Hero/App.hs +++ b/Hero/App.hs @@ -1,46 +1,48 @@ -{-# LANGUAGE CPP #-} -{-# LANGUAGE DataKinds #-} -{-# LANGUAGE DeriveGeneric #-} -{-# LANGUAGE NoImplicitPrelude #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE QuasiQuotes #-} -{-# LANGUAGE RecordWildCards #-} -{-# LANGUAGE TypeFamilies #-} -{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeOperators #-} +{-# LANGUAGE NoImplicitPrelude #-} {-# OPTIONS_GHC -fno-warn-missing-signatures #-} + module Hero.App where -import Alpha +import Alpha import qualified Clay -import qualified Hero.Assets as Assets -import Hero.Look as Look -import Hero.Look.Typography -import Network.RemoteData -import Data.Aeson ( ToJSON(..) - , FromJSON(..) - , genericToJSON - , genericParseJSON - , defaultOptions - ) +import Data.Aeson + ( FromJSON (..), + ToJSON (..), + defaultOptions, + genericParseJSON, + genericToJSON, + ) import qualified Data.List as List import qualified Data.List.Split as List -import Data.Proxy ( Proxy(..) ) -import Data.String -import Data.String.Quote -import Data.Text ( Text, replace, toLower ) -import GHC.Generics ( Generic ) +import Data.Proxy (Proxy (..)) +import Data.String +import Data.String.Quote +import Data.Text (Text, replace, toLower) +import GHC.Generics (Generic) import qualified GHC.Show as Legacy -import Miso +import qualified Hero.Assets as Assets +import Hero.Look as Look +import Hero.Look.Typography +import Miso import qualified Miso (for_) -import Miso.String -import Protolude hiding (replace) -import Servant.API ( Capture - , URI(..) - , safeLink - , (:<|>)(..) - , (:>) - ) -import Servant.Links ( linkURI ) +import Miso.String +import Network.RemoteData +import Protolude hiding (replace) +import Servant.API + ( (:<|>) (..), + (:>), + Capture, + URI (..), + safeLink, + ) +import Servant.Links (linkURI) crossorigin_ :: MisoString -> Attribute action crossorigin_ = textProp "crossorigin" @@ -53,30 +55,36 @@ audioId = "audioSource" -- overide 'a_' links, for example. onPreventClick :: Action -> Attribute Action onPreventClick action = - onWithOptions Miso.defaultOptions { preventDefault = True } - "click" emptyDecoder (\() -> action) + onWithOptions + Miso.defaultOptions {preventDefault = True} + "click" + emptyDecoder + (\() -> action) -- TODO: make ComicId a hashid https://hackage.haskell.org/package/hashids-1.0.2.4/docs/Web-Hashids.html type ComicId = String -- | Class for turning different string types to snakeCase. class CanSnakeCase str where - snake :: str -> str + snake :: str -> str instance CanSnakeCase Text where - snake = Data.Text.replace " " "-" . Data.Text.toLower + snake = Data.Text.replace " " "-" . Data.Text.toLower -- | Used for looking up images on S3, mostly comicSlug :: Comic -> Text -comicSlug Comic{..} = snake comicName <> "-" <> comicIssue - -data Comic = Comic - { comicId :: ComicId - , comicPages :: Integer - , comicName :: Text - , comicIssue :: Text -- ^ Ideally this would be a dynamic number-like type - , comicDescription :: Text - } deriving (Show, Eq, Generic) +comicSlug Comic {..} = snake comicName <> "-" <> comicIssue + +data Comic + = Comic + { comicId :: ComicId, + comicPages :: Integer, + comicName :: Text, + -- | Ideally this would be a dynamic number-like type + comicIssue :: Text, + comicDescription :: Text + } + deriving (Show, Eq, Generic) instance ToJSON Comic where toJSON = genericToJSON Data.Aeson.defaultOptions @@ -86,73 +94,93 @@ instance FromJSON Comic where -- | Class for rendering media objects in different ways. class IsMediaObject o where - -- | Render a thumbnail for use in a shelf, or otherwise. - thumbnail :: o -> View Action - -- | Render a featured banner. - feature :: o -> Library -> View Action - -- | Media info view - info :: o -> Library -> View Action + -- | Render a thumbnail for use in a shelf, or otherwise. + thumbnail :: o -> View Action + + -- | Render a featured banner. + feature :: o -> Library -> View Action + + -- | Media info view + info :: o -> Library -> View Action instance IsMediaObject Comic where - thumbnail c@Comic{..} = li_ [] - [ a_ - [ class_ "comic grow clickable" - , id_ $ "comic-" <> ms comicId - , onClick $ SetMediaInfo $ Just c + thumbnail c@Comic {..} = + li_ + [] + [ a_ + [ class_ "comic grow clickable", + id_ $ "comic-" <> ms comicId, + onClick $ SetMediaInfo $ Just c ] - [ img_ [ src_ $ ms $ Assets.demo <> comicSlug c <> ".png" ] - , span_ [] [ text $ "Issue #" <> ms comicIssue ] - , span_ [] [ text $ ms comicName ] + [ img_ [src_ $ ms $ Assets.demo <> comicSlug c <> ".png"], + span_ [] [text $ "Issue #" <> ms comicIssue], + span_ [] [text $ ms comicName] ] - ] - feature comic lib = div_ [ id_ "featured-comic" ] - [ img_ [ id_ "featured-banner", src_ $ ms $ Assets.demo <> "feature-banner.png" ] - , div_ [ id_ "featured-content" ] - [ div_ [ class_ "hero-original", css wide ] - [ span_ [ css thicc ] [ text "Herø" ] - , span_ [ css euro ] [ text " Original" ] - ] - , div_ [ class_ "comic-logo" ] - [ img_ [ src_ $ ms $ Assets.demo <> comicSlug comic <> "-logo.png" ] ] - , div_ [ class_ "comic-action-menu" ] $ el <$> [ Watch comic, Read comic, Save comic lib ] - , p_ [ class_ "description" ] - [ text . ms $ comicDescription comic - ] + ] + feature comic lib = + div_ + [id_ "featured-comic"] + [ img_ [id_ "featured-banner", src_ $ ms $ Assets.demo <> "feature-banner.png"], + div_ + [id_ "featured-content"] + [ div_ + [class_ "hero-original", css wide] + [ span_ [css thicc] [text "Herø"], + span_ [css euro] [text " Original"] + ], + div_ + [class_ "comic-logo"] + [img_ [src_ $ ms $ Assets.demo <> comicSlug comic <> "-logo.png"]], + div_ [class_ "comic-action-menu"] $ el <$> [Watch comic, Read comic, Save comic lib], + p_ + [class_ "description"] + [ text . ms $ comicDescription comic + ] ] - ] - info c@Comic {..} lib = div_ [ class_ "media-info", css euro ] - [ div_ [ class_ "media-info-meta" ] - [ column [ img_ [ src_ $ ms $ Assets.demo <> "dmc-widethumb.png" ] ] - , column - [ span_ [ style_ title ] [ text $ ms comicName ] - , span_ [ style_ subtitle ] [ text $ "Issue #" <> ms comicIssue ] - , span_ [] [ text "Released: " ] - , span_ [] [ text $ "Pages: " <> ms (show comicPages :: String) ] - ] - ] - , div_ [ class_ "media-info-summary" ] - [ p_ [ style_ $ uppercase <> bold <> Look.expanded <> "font-size" =: ".8rem" ] - [ text "Summary" ] - , p_ [] [ text $ ms comicDescription ] - ] - , div_ [ class_ "media-info-actions" ] $ el <$> [ Save c lib, Read c, Watch c ] + ] + info c@Comic {..} lib = + div_ + [class_ "media-info", css euro] + [ div_ + [class_ "media-info-meta"] + [ column [img_ [src_ $ ms $ Assets.demo <> "dmc-widethumb.png"]], + column + [ span_ [style_ title] [text $ ms comicName], + span_ [style_ subtitle] [text $ "Issue #" <> ms comicIssue], + span_ [] [text "Released: "], + span_ [] [text $ "Pages: " <> ms (show comicPages :: String)] + ] + ], + div_ + [class_ "media-info-summary"] + [ p_ + [style_ $ uppercase <> bold <> Look.expanded <> "font-size" =: ".8rem"] + [text "Summary"], + p_ [] [text $ ms comicDescription] + ], + div_ [class_ "media-info-actions"] $ el <$> [Save c lib, Read c, Watch c] -- , row [ text "credits" ] - ] - where - title = "color" =: "red" <> "font-size" =: "1.6rem" <> uppercase - <> "line-height" =: "100%" <> Look.condensed <> bold - subtitle = "color" =: "#fff" <> "font-size" =: "1.2rem" <> bold <> Look.condensed - + ] + where + title = + "color" =: "red" <> "font-size" =: "1.6rem" <> uppercase + <> "line-height" + =: "100%" + <> Look.condensed + <> bold + subtitle = "color" =: "#fff" <> "font-size" =: "1.2rem" <> bold <> Look.condensed type ZoomModel = Int -- | All the buttons. data Button - = Watch Comic | Read Comic | Save Comic Library - | SaveIcon Comic Library - | ZoomIcon ZoomModel Comic Page - | PlayPause MisoString AudioState - | Arrow Action + = Watch Comic + | Read Comic + | Save Comic Library + | SaveIcon Comic Library + | ZoomIcon ZoomModel Comic Page + | PlayPause MisoString AudioState + | Arrow Action -- | Class for defining general, widely used elements in the heroverse. class Elemental v where el :: v -> View Action @@ -160,84 +188,100 @@ class Elemental v where el :: v -> View Action -- TODO: what if I just did this on all actions? -- then I could e.g. `el $ ToggleAudio audioId audioState` instance Elemental Button where - el (PlayPause id model) = button_ - [ class_ "button is-large icon" - , onClick $ ToggleAudio id - ] - [ i_ [ class_ $ "fa " <> icon ][]] - where - icon = case model of - Paused -> "fa-play-circle" - Playing -> "fa-pause-circle" - el (Arrow act) = button_ - [class_ "button is-large turn-page", onClick act] - [ img_ [src_ $ ms $ Assets.demo <> image <> ".png"]] - where image = case act of - PrevPage -> "prev-page" - NextPage -> "next-page" - _ -> "prev-page" - el (Save c lib) = - if c `elem` lib then -- in library - a_ [ class_ $ "wrs-button saved", onClick $ ToggleInLibrary c ] - [ img_ [ src_ $ ms $ Assets.icon <> "save.svg" ] - , span_ [] [ text "saved" ] - ] - else -- not in library - a_ [ class_ $ "wrs-button", onClick $ ToggleInLibrary c ] - [ img_ [ src_ $ ms $ Assets.icon <> "save.svg" ] - , span_ [] [ text "save" ] - ] - el (SaveIcon c lib) = - if c `elem` lib then -- in library - button_ - [ class_ "button is-large has-background-black" - , onClick $ ToggleInLibrary c - ] - [ img_ [ src_ $ ms $ Assets.demo <> "library-add.png" ] ] - else -- not in library - button_ - [ class_ "button is-large has-background-black-bis" - , onClick $ ToggleInLibrary c - ] - [ img_ [ src_ $ ms $ Assets.demo <> "library-add.png" ] ] + el (PlayPause id model) = + button_ + [ class_ "button is-large icon", + onClick $ ToggleAudio id + ] + [i_ [class_ $ "fa " <> icon] []] + where + icon = case model of + Paused -> "fa-play-circle" + Playing -> "fa-pause-circle" + el (Arrow act) = + button_ + [class_ "button is-large turn-page", onClick act] + [img_ [src_ $ ms $ Assets.demo <> image <> ".png"]] + where + image = case act of + PrevPage -> "prev-page" + NextPage -> "next-page" + _ -> "prev-page" + el (Save c lib) = + if c `elem` lib -- in library + then + a_ + [class_ $ "wrs-button saved", onClick $ ToggleInLibrary c] + [ img_ [src_ $ ms $ Assets.icon <> "save.svg"], + span_ [] [text "saved"] + ] + else-- not in library - el (ZoomIcon zmodel comic page) = button_ - [ id_ "zoom-button", class_ "button is-large" - , onClick $ ToggleZoom comic page + a_ + [class_ $ "wrs-button", onClick $ ToggleInLibrary c] + [ img_ [src_ $ ms $ Assets.icon <> "save.svg"], + span_ [] [text "save"] ] - [ img_ [ src_ $ ms $ Assets.demo <> "zoom.png" ] - , input_ - [ type_ "range", min_ "0", max_ "100", disabled_ True - , value_ $ ms (show zmodel :: String) - , class_ "ctrl", id_ "zoom" - ] - , label_ - [ class_ "ctrl", Miso.for_ "zoom" ] - [ text $ ms $ (show zmodel :: String) ++ "%" ] + el (SaveIcon c lib) = + if c `elem` lib -- in library + then + button_ + [ class_ "button is-large has-background-black", + onClick $ ToggleInLibrary c ] + [img_ [src_ $ ms $ Assets.demo <> "library-add.png"]] + else-- not in library - el (Read c) = a_ [ class_ $ "wrs-button", onClick $ SelectExperience c ] - [ img_ [ src_ $ ms $ Assets.icon <> "read.svg" ] - , span_ [] [ text "read" ] - ] - - el (Watch c) = a_ [ class_ $ "wrs-button", onClick $ StartWatching c ] - [ img_ [ src_ $ ms $ Assets.icon <> "watch.svg" ] - , span_ [] [ text "watch" ] - ] + button_ + [ class_ "button is-large has-background-black-bis", + onClick $ ToggleInLibrary c + ] + [img_ [src_ $ ms $ Assets.demo <> "library-add.png"]] + el (ZoomIcon zmodel comic page) = + button_ + [ id_ "zoom-button", + class_ "button is-large", + onClick $ ToggleZoom comic page + ] + [ img_ [src_ $ ms $ Assets.demo <> "zoom.png"], + input_ + [ type_ "range", + min_ "0", + max_ "100", + disabled_ True, + value_ $ ms (show zmodel :: String), + class_ "ctrl", + id_ "zoom" + ], + label_ + [class_ "ctrl", Miso.for_ "zoom"] + [text $ ms $ (show zmodel :: String) ++ "%"] + ] + el (Read c) = + a_ + [class_ $ "wrs-button", onClick $ SelectExperience c] + [ img_ [src_ $ ms $ Assets.icon <> "read.svg"], + span_ [] [text "read"] + ] + el (Watch c) = + a_ + [class_ $ "wrs-button", onClick $ StartWatching c] + [ img_ [src_ $ ms $ Assets.icon <> "watch.svg"], + span_ [] [text "watch"] + ] data AudioState = Playing | Paused - deriving (Show, Eq) + deriving (Show, Eq) type Library = [Comic] data ComicReaderState - = NotReading - | Cover ComicId - | ChooseExperience ComicId Page - | Reading ComicReaderView ComicId Page - | Watching ComicId - deriving (Show, Eq) + = NotReading + | Cover ComicId + | ChooseExperience ComicId Page + | Reading ComicReaderView ComicId Page + | Watching ComicId + deriving (Show, Eq) findComic :: ComicId -> [Comic] -> Maybe Comic findComic id ls = List.find (\c -> comicId c == id) ls @@ -246,36 +290,39 @@ findComic id ls = List.find (\c -> comicId c == id) ls -- -- Try to prefix component-specific state with the component initials: 'd' for -- discover, 'cp' for comic player. -data Model = Model - { uri :: URI - , appComics :: RemoteData MisoString [Comic] - , userLibrary :: Library - , dMediaInfo :: Maybe Comic - , cpState :: ComicReaderState - , cpAudioState :: AudioState - , zoomModel :: ZoomModel - } deriving (Show, Eq) +data Model + = Model + { uri :: URI, + appComics :: RemoteData MisoString [Comic], + userLibrary :: Library, + dMediaInfo :: Maybe Comic, + cpState :: ComicReaderState, + cpAudioState :: AudioState, + zoomModel :: ZoomModel + } + deriving (Show, Eq) initModel :: URI -> Model initModel uri_ = - Model { uri = uri_ - , appComics = NotAsked - , dMediaInfo = Nothing - , userLibrary = Protolude.empty - , cpState = detectPlayerState uri_ - , cpAudioState = Paused - , zoomModel = 100 - } + Model + { uri = uri_, + appComics = NotAsked, + dMediaInfo = Nothing, + userLibrary = Protolude.empty, + cpState = detectPlayerState uri_, + cpAudioState = Paused, + zoomModel = 100 + } -- | Hacky way to initialize the 'ComicReaderState' from the URI. detectPlayerState :: URI -> ComicReaderState detectPlayerState u = case List.splitOn "/" $ uriPath u of - ["", "comic", id, pg, "experience"] -> ChooseExperience id $ toPage pg - ["", "comic", id, _, "video"] -> Watching id - ["", "comic", id, pg, "full"] -> Reading Full id $ toPage pg - ["", "comic", id, pg] -> Reading Spread id $ toPage pg - ["", "comic", id] -> Cover id - _ -> NotReading + ["", "comic", id, pg, "experience"] -> ChooseExperience id $ toPage pg + ["", "comic", id, _, "video"] -> Watching id + ["", "comic", id, pg, "full"] -> Reading Full id $ toPage pg + ["", "comic", id, pg] -> Reading Spread id $ toPage pg + ["", "comic", id] -> Cover id + _ -> NotReading where toPage pg = fromMaybe 1 (readMaybe pg :: Maybe Page) @@ -283,8 +330,8 @@ type Page = Int data Action = NoOp - -- comic player stuff - | SelectExperience Comic + | -- comic player stuff + SelectExperience Comic | StartReading Comic | StartWatching Comic | NextPage @@ -294,11 +341,11 @@ data Action | FetchComics | SetComics (RemoteData MisoString [Comic]) | ToggleFullscreen - -- discover stuff - | SetMediaInfo (Maybe Comic) + | -- discover stuff + SetMediaInfo (Maybe Comic) | ToggleInLibrary Comic - -- app stuff - | ScrollIntoView MisoString + | -- app stuff + ScrollIntoView MisoString | HandleURI URI | ChangeURI URI | DumpModel @@ -307,50 +354,62 @@ data Action type Discover = "discover" :> View Action type Home = - View Action + View Action type ComicCover = - "comic" + "comic" :> Capture "comicId" ComicId :> View Action type ComicReaderSpread = - "comic" + "comic" :> Capture "id" ComicId :> Capture "page" Page :> View Action type ComicReaderFull = - "comic" + "comic" :> Capture "id" ComicId :> Capture "page" Page :> "full" :> View Action type ComicVideo = - "comic" + "comic" :> Capture "id" ComicId :> Capture "page" Page :> "video" :> View Action type ChooseExperience = - "comic" + "comic" :> Capture "id" ComicId :> Capture "page" Page :> "experience" :> View Action type Login = - "login" :> View Action - -type ClientRoutes = Home - :<|> ComicCover :<|> ComicReaderSpread :<|> ComicReaderFull :<|> ComicVideo - :<|> Login :<|> Discover :<|> ChooseExperience - -handlers = home - :<|> comicCover :<|> comicPlayer :<|> comicPlayer :<|> comicPlayer - :<|> login :<|> discover :<|> comicPlayer + "login" :> View Action + +type ClientRoutes = + Home + :<|> ComicCover + :<|> ComicReaderSpread + :<|> ComicReaderFull + :<|> ComicVideo + :<|> Login + :<|> Discover + :<|> ChooseExperience + +handlers = + home + :<|> comicCover + :<|> comicPlayer + :<|> comicPlayer + :<|> comicPlayer + :<|> login + :<|> discover + :<|> comicPlayer routes :: Proxy ClientRoutes routes = Proxy @@ -383,21 +442,23 @@ home :: Model -> View Action home = login discover :: Model -> View Action -discover model@(Model { userLibrary = lib}) = template "discover" - [ topbar - , main_ [id_ "app-body"] $ case appComics model of - NotAsked -> [loading] - Loading -> [loading] - Failure _ -> [nocomics] - Success [] -> [nocomics] - Success (comic:rest) -> - [ feature comic lib - , shelf "Recent Releases" (comic:rest) - , maybeView (flip info lib) $ dMediaInfo model - ] - , appmenu - , discoverFooter - ] +discover model@(Model {userLibrary = lib}) = + template + "discover" + [ topbar, + main_ [id_ "app-body"] $ case appComics model of + NotAsked -> [loading] + Loading -> [loading] + Failure _ -> [nocomics] + Success [] -> [nocomics] + Success (comic : rest) -> + [ feature comic lib, + shelf "Recent Releases" (comic : rest), + maybeView (flip info lib) $ dMediaInfo model + ], + appmenu, + discoverFooter + ] -- | If 'View' had a 'Monoid' instance, then '(text "")' could just be 'mempty' maybeView :: (a -> View action) -> Maybe a -> View action @@ -405,155 +466,173 @@ maybeView f obj = maybe (text "") f obj mediaInfo :: Maybe Comic -> Library -> View Action mediaInfo Nothing _ = text "" -mediaInfo (Just comic) lib = div_ [ class_ "media-info" ] [ info comic lib ] +mediaInfo (Just comic) lib = div_ [class_ "media-info"] [info comic lib] appmenu :: View Action -appmenu = aside_ [ id_ "appmenu" ] $ btn </ links +appmenu = aside_ [id_ "appmenu"] $ btn </ links where - links = [ (discoverLink, "discover.svg", "discover") - , (homeLink, "save.svg", "library") - , (homeLink, "watch.svg", "videos") - , (comicLink "1", "read.svg", "comics") - , (homeLink, "listen.svg", "music") - ] - btn (lnk,img,label) = a_ - [ class_ "button" - , onPreventClick $ ChangeURI $ lnk + links = + [ (discoverLink, "discover.svg", "discover"), + (homeLink, "save.svg", "library"), + (homeLink, "watch.svg", "videos"), + (comicLink "1", "read.svg", "comics"), + (homeLink, "listen.svg", "music") + ] + btn (lnk, img, label) = + a_ + [ class_ "button", + onPreventClick $ ChangeURI $ lnk ] - [ img_ [src_ $ ms $ Assets.icon <> img] - , span_ [] [ text label ] + [ img_ [src_ $ ms $ Assets.icon <> img], + span_ [] [text label] ] -- TODO: make this a loading gif of some sort... maybe the hero icon filling from white to red loading :: View Action -loading = div_ [ class_ "loading" ] [ text "Loading..." ] +loading = div_ [class_ "loading"] [text "Loading..."] nocomics :: View Action -nocomics = div_ [ class_ "loading" ] [ text "error: no comics found" ] +nocomics = div_ [class_ "loading"] [text "error: no comics found"] shelf :: IsMediaObject o => MisoString -> [o] -> View Action -shelf title comics = div_ [ class_ "shelf" ] - [ div_ [ class_ "shelf-head" ] [ text title ] - , ul_ [ class_ "shelf-body" ] $ thumbnail </ comics +shelf title comics = + div_ + [class_ "shelf"] + [ div_ [class_ "shelf-head"] [text title], + ul_ [class_ "shelf-body"] $ thumbnail </ comics ] discoverFooter :: View Action -discoverFooter = footer_ - [ id_ "app-foot" - , class_ "is-black" - ] - [ div_ - [id_ "app-foot-social", css euro] - [ div_ [class_ "row is-marginless"] - [ smallImg "facebook.png" $ Just "https://www.facebook.com/musicmeetscomics" - , smallImg "twitter.png" $ Just "https://twitter.com/musicmeetscomic" - , smallImg "instagram.png" $ Just "https://www.instagram.com/musicmeetscomics/" - , smallImg "spotify.png" $ Just "https://open.spotify.com/user/i4ntfg6ganjgxdsylinigcjlq?si=ymWsSkwsT9iaLw2LeAJNNg" - , smallImg "youtube.png" $ Just "https://www.youtube.com/channel/UCnNPLiuJ1ueo1KTPgHDE7lA/" - ] - , div_ [class_ "row"] [ text "Team | Contact Us | Privacy Policy" ] - ] - , div_ - [ id_ "app-foot-quote", css euro ] - [ p_ [] [text "With great power comes great responsiblity."] - , p_ [] [text "-Stan Lee"] +discoverFooter = + footer_ + [ id_ "app-foot", + class_ "is-black" ] - , div_ - [ css euro, id_ "app-foot-logo", onClick DumpModel ] - [ a_ [ class_ "social-icon", href_ "#" ] [ img_ [ src_ $ ms $ Assets.icon <> "hero-logo.svg" ]] - , span_ [] [ text "© Hero Records, Inc. All Rights Reserved" ] + [ div_ + [id_ "app-foot-social", css euro] + [ div_ + [class_ "row is-marginless"] + [ smallImg "facebook.png" $ Just "https://www.facebook.com/musicmeetscomics", + smallImg "twitter.png" $ Just "https://twitter.com/musicmeetscomic", + smallImg "instagram.png" $ Just "https://www.instagram.com/musicmeetscomics/", + smallImg "spotify.png" $ Just "https://open.spotify.com/user/i4ntfg6ganjgxdsylinigcjlq?si=ymWsSkwsT9iaLw2LeAJNNg", + smallImg "youtube.png" $ Just "https://www.youtube.com/channel/UCnNPLiuJ1ueo1KTPgHDE7lA/" + ], + div_ [class_ "row"] [text "Team | Contact Us | Privacy Policy"] + ], + div_ + [id_ "app-foot-quote", css euro] + [ p_ [] [text "With great power comes great responsiblity."], + p_ [] [text "-Stan Lee"] + ], + div_ + [css euro, id_ "app-foot-logo", onClick DumpModel] + [ a_ [class_ "social-icon", href_ "#"] [img_ [src_ $ ms $ Assets.icon <> "hero-logo.svg"]], + span_ [] [text "© Hero Records, Inc. All Rights Reserved"] + ] ] - ] where - attrs Nothing = [ class_ "social-icon" ] - attrs (Just lnk) = [ class_ "social-icon", href_ lnk, target_ "_blank" ] - smallImg x lnk = a_ (attrs lnk) - [ img_ [src_ $ ms $ Assets.cdnEdge <> "/old-assets/images/icons/" <> x ]] + attrs Nothing = [class_ "social-icon"] + attrs (Just lnk) = [class_ "social-icon", href_ lnk, target_ "_blank"] + smallImg x lnk = + a_ + (attrs lnk) + [img_ [src_ $ ms $ Assets.cdnEdge <> "/old-assets/images/icons/" <> x]] comicCover :: ComicId -> Model -> View Action comicCover comicId_ model = comicPlayer comicId_ 1 model data ComicReaderView = Spread | Full - deriving (Show, Eq) + deriving (Show, Eq) comicPlayer :: ComicId -> Page -> Model -> View Action comicPlayer _ _ model = case appComics model of - NotAsked -> loading - Loading -> loading - Failure _ -> nocomics - Success comics -> case cpState model of - NotReading -> template "comic-player" [ text "error: not reading" ] - Cover id -> viewOr404 comics comicSpread id 1 model - ChooseExperience id pg -> - viewOr404 comics chooseExperiencePage id pg model - Reading Spread id pg -> viewOr404 comics comicSpread id pg model - Reading Full id pg -> viewOr404 comics zoomScreen id pg model - Watching id -> viewOr404 comics comicVideo id 0 model - -viewOr404 :: [Comic] - -> (Comic -> Page -> Model -> View Action) - -> ComicId -> Page -> Model -> View Action + NotAsked -> loading + Loading -> loading + Failure _ -> nocomics + Success comics -> case cpState model of + NotReading -> template "comic-player" [text "error: not reading"] + Cover id -> viewOr404 comics comicSpread id 1 model + ChooseExperience id pg -> + viewOr404 comics chooseExperiencePage id pg model + Reading Spread id pg -> viewOr404 comics comicSpread id pg model + Reading Full id pg -> viewOr404 comics zoomScreen id pg model + Watching id -> viewOr404 comics comicVideo id 0 model + +viewOr404 :: + [Comic] -> + (Comic -> Page -> Model -> View Action) -> + ComicId -> + Page -> + Model -> + View Action viewOr404 comics f id pg model = - case findComic id comics of - Just c -> f c pg model - Nothing -> the404 model + case findComic id comics of + Just c -> f c pg model + Nothing -> the404 model template :: MisoString -> [View Action] -> View Action template id rest = div_ [id_ id, class_ "app is-black"] rest closeButton :: View Action -closeButton = a_ [ id_ "close-button", onClick $ ChangeURI discoverLink ] - [ text "x" ] +closeButton = + a_ + [id_ "close-button", onClick $ ChangeURI discoverLink] + [text "x"] zoomScreen :: Comic -> Page -> Model -> View Action -zoomScreen comic page model = template "comic-player" - [ topbar - , main_ - [id_ "app-body"] - [ img_ - [ src_ comicImg - , class_ "comic-page-full" - ] +zoomScreen comic page model = + template + "comic-player" + [ topbar, + main_ + [id_ "app-body"] + [ img_ + [ src_ comicImg, + class_ "comic-page-full" + ] + ], + comicControls comic page model ] - , comicControls comic page model - ] - where - comicImg = + where + comicImg = ms Assets.demo - <> ms (comicSlug comic) - <> "-" - <> padLeft page - <> ".png" + <> ms (comicSlug comic) + <> "-" + <> padLeft page + <> ".png" comicSpread :: Comic -> Page -> Model -> View Action -comicSpread comic page model = template "comic-player" - [ topbar - , main_ - [id_ "app-body"] - [ div_ - [class_ "comic-player"] - [ img_ [ src_ comicImgLeft, class_ "comic-page" ] - , img_ [ src_ comicImgRight, class_ "comic-page" ] - ] - , closeButton +comicSpread comic page model = + template + "comic-player" + [ topbar, + main_ + [id_ "app-body"] + [ div_ + [class_ "comic-player"] + [ img_ [src_ comicImgLeft, class_ "comic-page"], + img_ [src_ comicImgRight, class_ "comic-page"] + ], + closeButton + ], + appmenu, + comicControls comic page model ] - , appmenu - , comicControls comic page model - ] - where - comicImgLeft, comicImgRight :: MisoString - comicImgLeft = + where + comicImgLeft, comicImgRight :: MisoString + comicImgLeft = ms Assets.demo - <> ms (comicSlug comic) - <> "-" - <> padLeft page - <> ".png" - comicImgRight = + <> ms (comicSlug comic) + <> "-" + <> padLeft page + <> ".png" + comicImgRight = ms Assets.demo - <> ms (comicSlug comic) - <> "-" - <> (padLeft $ 1 + page) - <> ".png" + <> ms (comicSlug comic) + <> "-" + <> (padLeft $ 1 + page) + <> ".png" frameborder_ :: MisoString -> Attribute action frameborder_ = textProp "frameborder" @@ -562,125 +641,147 @@ allowfullscreen_ :: Bool -> Attribute action allowfullscreen_ = boolProp "allowfullscreen" comicVideo :: Comic -> Page -> Model -> View Action -comicVideo _ _ _ = template "comic-player" - [ topbar - , main_ - [ id_ "app-body" ] - [ div_ [class_ "comic-video"] - [ iframe_ - [ src_ "//player.vimeo.com/video/325757560" - , frameborder_ "0" - , allowfullscreen_ True - ] - [] +comicVideo _ _ _ = + template + "comic-player" + [ topbar, + main_ + [id_ "app-body"] + [ div_ + [class_ "comic-video"] + [ iframe_ + [ src_ "//player.vimeo.com/video/325757560", + frameborder_ "0", + allowfullscreen_ True + ] + [] + ] ] - ] ] padLeft :: Int -> MisoString -padLeft n | n < 10 = ms $ ("0" <> Legacy.show n) - | otherwise = ms $ Legacy.show n +padLeft n + | n < 10 = ms $ ("0" <> Legacy.show n) + | otherwise = ms $ Legacy.show n comicControls :: Comic -> Page -> Model -> View Action -comicControls comic page model = footer_ - [ id_ "app-foot", class_ "comic-controls" ] - [ div_ - [ class_ "comic-nav-audio" - , css $ flexCenter ] - [ audio_ - [id_ audioId, loop_ True, crossorigin_ "anonymous"] - [source_ [src_ $ ms $ Assets.demo <> "stars-instrumental.mp3"]] - , el $ PlayPause audioId $ cpAudioState model - , span_ - [ css $ euro <> thicc <> smol <> wide ] - [ text "Experiencing: Original" ] - ] - , div_ - [ class_ "comic-controls-pages", css euro ] - [ el $ Arrow $ PrevPage - , span_ [] [ text $ leftPage <> "-" <> rightPage <> " of " <> totalpages ] - , el $ Arrow $ NextPage +comicControls comic page model = + footer_ + [id_ "app-foot", class_ "comic-controls"] + [ div_ + [ class_ "comic-nav-audio", + css $ flexCenter + ] + [ audio_ + [id_ audioId, loop_ True, crossorigin_ "anonymous"] + [source_ [src_ $ ms $ Assets.demo <> "stars-instrumental.mp3"]], + el $ PlayPause audioId $ cpAudioState model, + span_ + [css $ euro <> thicc <> smol <> wide] + [text "Experiencing: Original"] + ], + div_ + [class_ "comic-controls-pages", css euro] + [ el $ Arrow $ PrevPage, + span_ [] [text $ leftPage <> "-" <> rightPage <> " of " <> totalpages], + el $ Arrow $ NextPage + ], + div_ + [class_ "comic-controls-share"] + [ el $ SaveIcon comic $ userLibrary model, + el $ ZoomIcon (zoomModel model) comic page, + button_ + [class_ "button icon is-large", onClick ToggleFullscreen] + [i_ [class_ "fa fa-expand"] []] + ] ] - , div_ [class_ "comic-controls-share"] - [ el $ SaveIcon comic $ userLibrary model - , el $ ZoomIcon (zoomModel model) comic page - , button_ - [class_ "button icon is-large", onClick ToggleFullscreen] - [i_ [ class_ "fa fa-expand" ] []] - ] - ] where - leftPage = ms . Legacy.show $ page - rightPage = ms . Legacy.show $ 1 + page + leftPage = ms . Legacy.show $ page + rightPage = ms . Legacy.show $ 1 + page totalpages = ms . Legacy.show $ comicPages comic login :: Model -> View Action -login _ = template "login" - [ div_ [ id_ "login-inner" ] - [ img_ [ class_ fadeIn - , src_ $ ms $ Assets.cdnEdge <> "/old-assets/images/icons/hero-large.png" - ] - , hr_ [class_ fadeIn] - , form_ [class_ fadeIn] - [ ctrl [class_ "input", type_ "email", placeholder_ "Email"] - , ctrl [class_ "input", type_ "password", placeholder_ "Password"] - , div_ [class_ "action", css euro] - [ div_ [class_ "checkbox remember-me"] - [ input_ [type_ "checkbox"] - , label_ [Miso.for_ "checkbox"] [text "Remember Me"] - ] - , div_ [class_ "button is-black", onClick $ ChangeURI discoverLink] - [ text "Login" ] - ] - ] - , hr_ [class_ fadeIn] - , p_ [ class_ $ "help " <> fadeIn ] - [ a_ [href_ "#"][text "Forgot your username or password?"] - , a_ [href_ "#"][text "Don't have an account? Sign Up"] - ] - , img_ [ id_ "hero-logo" - , class_ "blur-out" - , src_ $ ms $ Assets.cdnEdge <> "/old-assets/images/icons/success-her-image.png" - ] - ] +login _ = + template + "login" + [ div_ + [id_ "login-inner"] + [ img_ + [ class_ fadeIn, + src_ $ ms $ Assets.cdnEdge <> "/old-assets/images/icons/hero-large.png" + ], + hr_ [class_ fadeIn], + form_ + [class_ fadeIn] + [ ctrl [class_ "input", type_ "email", placeholder_ "Email"], + ctrl [class_ "input", type_ "password", placeholder_ "Password"], + div_ + [class_ "action", css euro] + [ div_ + [class_ "checkbox remember-me"] + [ input_ [type_ "checkbox"], + label_ [Miso.for_ "checkbox"] [text "Remember Me"] + ], + div_ + [class_ "button is-black", onClick $ ChangeURI discoverLink] + [text "Login"] + ] + ], + hr_ [class_ fadeIn], + p_ + [class_ $ "help " <> fadeIn] + [ a_ [href_ "#"] [text "Forgot your username or password?"], + a_ [href_ "#"] [text "Don't have an account? Sign Up"] + ], + img_ + [ id_ "hero-logo", + class_ "blur-out", + src_ $ ms $ Assets.cdnEdge <> "/old-assets/images/icons/success-her-image.png" + ] + ] ] where fadeIn = "animated fadeIn delay-2s" - ctrl x = div_ [class_ "control"] [ input_ x ] + ctrl x = div_ [class_ "control"] [input_ x] chooseExperiencePage :: Comic -> Page -> Model -> View Action -chooseExperiencePage comic page model = template "choose-experience" - [ topbar - , main_ [ id_ "app-body" ] - [ h2_ [] [ text "Choose Your Musical Experience" ] - , p_ [] [ text experienceBlurb ] - , ul_ [] $ li comic </ experiences - ] - , appmenu - , comicControls comic page model +chooseExperiencePage comic page model = + template + "choose-experience" + [ topbar, + main_ + [id_ "app-body"] + [ h2_ [] [text "Choose Your Musical Experience"], + p_ [] [text experienceBlurb], + ul_ [] $ li comic </ experiences + ], + appmenu, + comicControls comic page model ] where - li c (name, artist, track) = li_ - [ onClick $ StartReading c ] - [ div_ [] - [ img_ [ src_ $ ms $ Assets.demo <> name <> ".png" ] - , span_ [] [ text $ ms name ] - ] - , span_ [ css $ thicc ] [ text $ ms artist ] - , span_ [] [ text $ ms track ] + li c (name, artist, track) = + li_ + [onClick $ StartReading c] + [ div_ + [] + [ img_ [src_ $ ms $ Assets.demo <> name <> ".png"], + span_ [] [text $ ms name] + ], + span_ [css $ thicc] [text $ ms artist], + span_ [] [text $ ms track] ] experiences :: [(Text, Text, Text)] experiences = - [ ("comedic", "RxGF", "Soft Reveal") - , ("dark", "Logan Henderson", "Speak of the Devil") - , ("original", "Mehcad Brooks", "Stars") - , ("energetic", "Skela", "What's wrong with me") - , ("dramatic", "Josh Jacobson", "Sideline") - ] - + [ ("comedic", "RxGF", "Soft Reveal"), + ("dark", "Logan Henderson", "Speak of the Devil"), + ("original", "Mehcad Brooks", "Stars"), + ("energetic", "Skela", "What's wrong with me"), + ("dramatic", "Josh Jacobson", "Sideline") + ] experienceBlurb :: MisoString -experienceBlurb = [s| +experienceBlurb = + [s| As you enter the world of Hero, you will find that music and visual art have a symbiotic relationship that can only be experienced, not described. Here, choose the tonality of the experience you wish to adventure on, whether it's a comedic, @@ -689,33 +790,36 @@ with the original curated music for this piece of visual art. |] topbar :: View Action -topbar = header_ - [id_ "app-head", class_ "is-black", css euro] - [ a_ - [class_ "button is-medium is-black", onClick $ ChangeURI homeLink] - [img_ [src_ $ ms $ Assets.icon <> "hero-logo.svg"]] - , div_ - [id_ "app-head-right"] - [ button_ [class_ "button icon is-medium is-black"] - [i_ [class_ "fas fa-search" ] []] - , button_ [ class_ "button is-medium is-black is-size-7" - , css $ euro <> wide <> thicc - ] - [text "News"] - , span_ [ class_ "icon is-large" ] - [ i_ [ class_ "fas fa-user" ] [] +topbar = + header_ + [id_ "app-head", class_ "is-black", css euro] + [ a_ + [class_ "button is-medium is-black", onClick $ ChangeURI homeLink] + [img_ [src_ $ ms $ Assets.icon <> "hero-logo.svg"]], + div_ + [id_ "app-head-right"] + [ button_ + [class_ "button icon is-medium is-black"] + [i_ [class_ "fas fa-search"] []], + button_ + [ class_ "button is-medium is-black is-size-7", + css $ euro <> wide <> thicc + ] + [text "News"], + span_ + [class_ "icon is-large"] + [ i_ [class_ "fas fa-user"] [] + ] ] ] - ] row :: [View Action] -> View Action -row = div_ [ css $ Clay.display Clay.flex <> Clay.flexDirection Clay.row ] +row = div_ [css $ Clay.display Clay.flex <> Clay.flexDirection Clay.row] column :: [View Action] -> View Action -column = div_ [ css $ Clay.display Clay.flex <> Clay.flexDirection Clay.column ] +column = div_ [css $ Clay.display Clay.flex <> Clay.flexDirection Clay.column] -- | Links - comicLink :: ComicId -> URI comicLink comicId_ = linkURI $ safeLink routes comicProxy $ comicId_ @@ -745,4 +849,4 @@ the404 _ = template "404" [p_ [] [text "Not found"]] chooseExperienceLink :: ComicId -> Page -> URI chooseExperienceLink id page = - linkURI $ safeLink routes chooseExperienceProxy id page + linkURI $ safeLink routes chooseExperienceProxy id page |