-
-
Save i-am-the-slime/eb0f9168e17277b4ff37de25dfe6816e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Facebook.AdInfo.List.Component where | |
import Yoga.Prelude.View hiding (top) | |
import Data.Function.Uncurried (mkFn2) | |
import Data.Newtype (un) | |
import Effect.Aff (Aff, Error, error, forkAff, killFiber, launchAff) | |
import Effect.Aff.Extra (safelyLaunchAff_) | |
import Facebook.AdInfo.Component.Types (DetailVisibility(..)) | |
import Facebook.AdInfo.Component.Types as AdInfo | |
import Foreign.Object as Object | |
import LazyComponents (mkAdInfoLazy, renderWhenLoaded) | |
import Murmuras.Insights.API.AdInfo.Queries (AdSummary) | |
import Murmuras.Insights.API.AdInfo.Types (AdId(..), GQLAdInfo) | |
import Network.RemoteData (RemoteData) | |
import Network.RemoteData as RD | |
import React.Basic.DOM as R | |
import React.Basic.Hooks as React | |
import React.Virtuoso (virtuosoWithData) | |
type Props | |
= { adSummaries ∷ Array AdSummary | |
, keyword ∷ String | |
, loadDetails ∷ AdId → Aff (Either Error GQLAdInfo) | |
, spinner ∷ JSX | |
} | |
modifyRef ∷ ∀ a. (a → a) → Ref a → Effect Unit | |
modifyRef fn ref = do | |
React.readRef ref >>= React.writeRef ref <<< fn | |
mkFacebookAdResultsList ∷ React.Component Props | |
mkFacebookAdResultsList = do | |
React.component "FacebookAdResultsList" \props → React.do | |
let { adSummaries, keyword, loadDetails } = props | |
lazyAdInfoComponent /\ setAdInfo ← React.useState' (RD.NotAsked ∷ _ _ (AdInfo.Props → JSX)) | |
expandedViews /\ updateExpandedViews ← React.useState Object.empty | |
adDetails /\ updateAdDetails ← React.useState Object.empty | |
runningFibersRef ← React.useRef Object.empty | |
lastFiberIdRef ← React.useRef 0 | |
let collapseAllViews = do updateExpandedViews (const Object.empty) | |
let | |
killInFlightFibers = do | |
runningFibers ← React.readRef runningFibersRef | |
safelyLaunchAff_ do | |
for_ (Object.values runningFibers) (killFiber (error "Component unmounted, fiber not needed anymore")) | |
-- Cancel any pending fibers on component unmount | |
React.useEffectOnce (pure killInFlightFibers) | |
-- A keyword change is like a reset for the list view | |
React.useEffect keyword do | |
-- We don't need ad details for the old keyword | |
killInFlightFibers | |
React.writeRef runningFibersRef Object.empty | |
-- Free the memory holding the info about which old ad ids are expanded | |
collapseAllViews | |
mempty | |
let | |
lookupExpandedView adId = | |
expandedViews # Object.lookup (un AdId adId) # fromMaybe DetailCollapsed | |
hideAdDetails adId = updateExpandedViews $ Object.insert (un AdId adId) DetailCollapsed | |
showAdDetails adId = updateExpandedViews $ Object.insert (un AdId adId) DetailVisible | |
lookupAdDetails ∷ AdId → (RemoteData Error GQLAdInfo) | |
lookupAdDetails adId = adDetails # Object.lookup (un AdId adId) # fromMaybe RD.NotAsked | |
loadAdDetails ∷ AdId → Effect Unit | |
loadAdDetails adId = do | |
if (adDetails # Object.member (un AdId adId)) then | |
mempty | |
else do | |
updateAdDetails $ Object.insert (un AdId adId) RD.Loading | |
-- [TODO] Cancel affs? | |
fiberId ← React.readRef lastFiberIdRef <#> (_ + 1) | |
React.writeRef lastFiberIdRef fiberId | |
fiber ← | |
launchAff do | |
forkAff do | |
result ← loadDetails adId <#> RD.fromEither | |
updateAdDetails (Object.insert (un AdId adId) result) # liftEffect | |
runningFibersRef # modifyRef (Object.delete (show fiberId)) # liftEffect | |
runningFibersRef # modifyRef (Object.insert (show fiberId) fiber) | |
-- Lazy load the component | |
React.useEffectOnce do | |
when (RD.isNotAsked lazyAdInfoComponent) do | |
safelyLaunchAff_ do | |
setAdInfo RD.Loading # liftEffect | |
adInfo ← mkAdInfoLazy | |
setAdInfo (RD.Success adInfo) # liftEffect | |
mempty | |
let | |
renderItem ∷ (AdInfo.Props → JSX) → AdSummary → JSX | |
renderItem adInfoComponent ad@{ id } = | |
adInfoComponent | |
{ adInfo: ad | |
, rdAdDetails: lookupAdDetails id | |
, showAdDetails: showAdDetails id *> loadAdDetails id | |
, hideAdDetails: hideAdDetails id | |
, detailVisibility: lookupExpandedView id | |
, keyword | |
} | |
adInfoListView adInfoComponent = | |
fragment | |
[ React.element virtuosoWithData | |
{ useWindowScroll: true | |
, data: adSummaries | |
, itemContent: | |
mkFn2 \index item → do | |
let key = keyword <> "-" <> show index | |
R.div' | |
</ { key } | |
/> | |
[ renderItem adInfoComponent item | |
] | |
} | |
, props.spinner | |
] | |
pure | |
$ lazyAdInfoComponent | |
# renderWhenLoaded adInfoListView |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment