{-# LANGUAGE CPP #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} -- | Local storage IO operations -- Get and set local storage values from some 'LocalStorageKey' module Shpadoinkle.Html.LocalStorage where import Control.Monad (void) import Control.Monad.Trans.Maybe (MaybeT (MaybeT, runMaybeT)) import Data.Maybe (fromMaybe) import Data.String (IsString) import Data.Text (Text) import GHC.Generics (Generic) import GHCJS.DOM (currentWindow) import GHCJS.DOM.Storage (getItem, setItem) import GHCJS.DOM.Types (MonadJSM, liftJSM) import GHCJS.DOM.Window (getLocalStorage) import Text.Read (readMaybe) import UnliftIO (MonadIO (liftIO), MonadUnliftIO, TVar, newTVarIO) import UnliftIO.Concurrent (forkIO) import Shpadoinkle (shouldUpdate) -- | The key for a specific state kept in local storage newtype LocalStorageKey a = LocalStorageKey { unLocalStorageKey :: Text } deriving (Semigroup, Monoid, IsString, Eq, Ord, Show, Read, Generic) setStorage :: MonadJSM m => Show a => LocalStorageKey a -> a -> m () setStorage (LocalStorageKey k) m = do w <- currentWindow case w of Just w' -> do s <- getLocalStorage w' setItem s k $ show m return () Nothing -> return () getStorage :: MonadJSM m => Read a => LocalStorageKey a -> m (Maybe a) getStorage (LocalStorageKey k) = runMaybeT $ do w <- MaybeT currentWindow s <- MaybeT $ Just <$> getLocalStorage w MaybeT $ (>>= readMaybe) <$> getItem s k -- When we should update we save saveOnChange :: MonadJSM m => Show a => Eq a => LocalStorageKey a -> TVar a -> m () saveOnChange k = liftJSM . shouldUpdate (const $ setStorage k) () manageLocalStorage :: MonadUnliftIO m #ifndef ghcjs_HOST_OS => MonadJSM m #endif => Show a => Read a => Eq a => LocalStorageKey a -> a -> m (TVar a) manageLocalStorage k initial = do model <- liftIO . newTVarIO . fromMaybe initial =<< getStorage k void . forkIO $ saveOnChange k model return model