-- {-# LANGUAGE DataKinds #-} -- {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE FlexibleContexts #-} -- {-# LANGUAGE FlexibleInstances #-} -- {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedLists #-} {-# LANGUAGE OverloadedStrings #-} -- {-# LANGUAGE RankNTypes #-} -- {-# LANGUAGE RecordWildCards #-} -- {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} module Glazier.React.Widgets.Collection ( HKD , UKey , zeroUKey , smallerUKey , largerUKey , betweenUKey -- * Collection , Collection , HKCollection , collectionWindow , deleteCollectionItem , insertCollectionItem ) where import Control.Lens import Data.Foldable import qualified Data.Map.Strict as M import qualified GHC.Generics as G import Glazier.React import qualified JavaScript.Extras as JE -- "Higher-Kinded Data" http://reasonablypolymorphic.com/blog/higher-kinded-data/ -- Erases Identity type family HKD f a where HKD Identity a = a HKD f a = f a -- | A key where you can always create -- another key ordered between two different keys, -- or another key above or below this key. -- Memonic: U for uncountable https://en.wikipedia.org/wiki/Uncountable_set newtype UKey = UKey { unUKey :: [Int] } deriving (G.Generic, Show) -- | For comparison purposes, an empty list is equivalent to [0,0,..] instance Ord UKey where compare (UKey []) (UKey []) = EQ compare (UKey xs) (UKey []) = compare (UKey xs) (UKey [0]) compare (UKey []) (UKey ys) = compare (UKey [0]) (UKey ys) compare (UKey (x : xs)) (UKey (y : ys)) = case compare x y of EQ -> compare (UKey xs) (UKey ys) o -> o -- | For comparison purposes, an empty list is equivalent to [0,0,..] instance Eq UKey where (UKey []) == (UKey []) = True (UKey xs) == (UKey []) = (UKey xs) == (UKey [0]) (UKey []) == (UKey ys) = (UKey [0]) == (UKey ys) (UKey (x : xs)) == (UKey (y : ys)) = if x == y then (UKey xs) == (UKey ys) else False zeroUKey :: UKey zeroUKey = UKey [] ukeyStep :: Int ukeyStep = 32 -- | Create a key smaller than the input key. smallerUKey :: UKey -> UKey smallerUKey (UKey []) = UKey [-ukeyStep] smallerUKey (UKey (a : as)) = UKey $ case compare a (JE.minSafeInteger + ukeyStep) of LT -> if JE.minSafeInteger == a then JE.minSafeInteger : unUKey (smallerUKey (UKey as)) else [JE.minSafeInteger] _ -> [a - ukeyStep] -- | Create a key larger than the input key. largerUKey :: UKey -> UKey largerUKey (UKey []) = UKey [ukeyStep] largerUKey (UKey (a : as)) = UKey $ case compare a (JE.maxSafeInteger - ukeyStep) of GT -> if JE.maxSafeInteger == a then JE.maxSafeInteger : unUKey (largerUKey (UKey as)) else [JE.maxSafeInteger] _ -> [a + ukeyStep] -- | Make a key that will fit in between the two provided keys, -- with no guarantees on how close it is to the mid point. -- Except when the inputs are equal, then it will return the same key. betweenUKey :: UKey -> UKey -> UKey betweenUKey (UKey []) (UKey []) = zeroUKey betweenUKey (UKey xs) (UKey []) = betweenUKey (UKey xs) (UKey [0]) betweenUKey (UKey []) (UKey ys) = betweenUKey (UKey [0]) (UKey ys) betweenUKey (UKey (x : xs)) (UKey (y : ys)) = UKey $ case compare x y of LT -> if x + 1 == y then x : unUKey (betweenUKey (UKey xs) (UKey $ repeat JE.maxSafeInteger)) else [betweenUncInt x y] GT -> if y + 1 == x then y : unUKey (betweenUKey (UKey ys) (UKey $ repeat JE.maxSafeInteger)) else [betweenUncInt x y] EQ -> x : unUKey (betweenUKey (UKey xs) (UKey ys)) betweenUncInt :: Int -> Int -> Int betweenUncInt x y = let (xq, xr) = quotRem x 2 (yq, yr) = quotRem y 2 z = case (xr + yr) of 2 -> 1 _ -> 0 in xq + yq + z ----------------------------------------------------------------- -- | Collection of higher kinded data type Collection t s f = t (HKD f s) -- | Collection of higher kinded "higher kinded data" type HKCollection t s f = t (HKD f (s f)) -- | Collection doesn't have an initializing gadget since -- the 'Subject's in the model are all initialized via 'addSubject'. collectionWindow :: (Functor t, Foldable t) => ReactId -> Window (t (Subject s)) () collectionWindow ri = do ss <- view _model let displayItem s = Als $ (displaySubject s) bh "ul" [("key", JE.toJSR $ ri)] (getAls (fold $ displayItem <$> ss)) deleteCollectionItem :: (MonadReactor p allS cmd m, Ord k) => k -> ModelState (M.Map k (Subject s)) (m ()) deleteCollectionItem k = do old <- use (id.at k) (at k) .= Nothing pure $ maybe (pure ()) bookSubjectCleanup old insertCollectionItem :: (MonadReactor p allS cmd m, Ord k) => k -> Subject s -> ModelState (M.Map k (Subject s)) (m ()) insertCollectionItem k sbj = do old <- use (at k) (at k) .= Just sbj pure $ maybe (pure ()) bookSubjectCleanup old