|
|
@@ -1,7 +1,8 @@
|
|
|
module PureTabs.Background where
|
|
|
|
|
|
import Browser.Runtime as Runtime
|
|
|
-import Browser.Tabs (Tab(..), TabId, WindowId, query, removeOne, activateTab, moveTab, createTab)
|
|
|
+import Browser.Tabs (Tab, TabId, WindowId)
|
|
|
+import Browser.Tabs as BT
|
|
|
import Browser.Tabs.OnActivated as OnActivated
|
|
|
import Browser.Tabs.OnAttached as OnAttached
|
|
|
import Browser.Tabs.OnCreated as OnCreated
|
|
|
@@ -13,20 +14,18 @@ import Browser.Utils (Listener, mkListenerOne, mkListenerTwo, mkListenerUnit)
|
|
|
import Browser.Windows (Window)
|
|
|
import Browser.Windows.OnCreated as WinOnCreated
|
|
|
import Browser.Windows.OnRemoved as WinOnRemoved
|
|
|
-import Control.Alt (map, (<#>), (<|>))
|
|
|
+import Control.Alt ((<#>))
|
|
|
import Control.Alternative (pure, (*>))
|
|
|
import Control.Bind ((=<<), (>>=))
|
|
|
-import Control.Category (identity, (>>>))
|
|
|
+import Control.Category ((>>>))
|
|
|
import Data.Array as A
|
|
|
import Data.CommutativeRing ((+))
|
|
|
-import Data.Eq ((/=), (==))
|
|
|
-import Data.Function (const, flip, (#))
|
|
|
-import Data.Lens (_Just, over, preview, set, view)
|
|
|
+import Data.Function (flip, (#))
|
|
|
+import Data.Lens (_Just, set, view)
|
|
|
import Data.Lens.At (at)
|
|
|
-import Data.Lens.Iso.Newtype (_Newtype)
|
|
|
import Data.List (List, foldMap)
|
|
|
import Data.Map as M
|
|
|
-import Data.Maybe (Maybe(..), fromMaybe, maybe, maybe')
|
|
|
+import Data.Maybe (Maybe(..), maybe)
|
|
|
import Data.Monoid ((<>))
|
|
|
import Data.Show (show)
|
|
|
import Data.Unit (unit)
|
|
|
@@ -34,34 +33,17 @@ import Effect (Effect)
|
|
|
import Effect.Aff (Aff, launchAff_)
|
|
|
import Effect.Class (liftEffect)
|
|
|
import Effect.Console (log)
|
|
|
-import Effect.Exception (throw)
|
|
|
-import Effect.Exception.Unsafe (unsafeThrow)
|
|
|
import Effect.Ref as Ref
|
|
|
import Prelude (Unit, bind, ($), discard, (<<<))
|
|
|
-import PureTabs.Model
|
|
|
- ( BackgroundEvent(..)
|
|
|
- , ExtWindow
|
|
|
- , GlobalState
|
|
|
- , SidebarEvent(..)
|
|
|
- , _active
|
|
|
- , _index
|
|
|
- , _port
|
|
|
- , _portFromWindow
|
|
|
- , _portFromWindowId
|
|
|
- , _positions
|
|
|
- , _tabFromTabIdAndWindow
|
|
|
- , _tabFromWindow
|
|
|
- , _tabs
|
|
|
- , _windowIdToWindow
|
|
|
- , _windows
|
|
|
- , _windowIdToTabIdToTab
|
|
|
- , emptyWindow
|
|
|
- , initialTabListToGlobalState
|
|
|
- )
|
|
|
+import PureTabs.Model.Events (BackgroundEvent(..), SidebarEvent(..))
|
|
|
+import PureTabs.Model.GlobalState as GS
|
|
|
|
|
|
type Ports
|
|
|
= Ref.Ref (List Runtime.Port)
|
|
|
|
|
|
+type StateRef = Ref.Ref GS.GlobalState
|
|
|
+
|
|
|
+
|
|
|
main :: Effect Unit
|
|
|
main = do
|
|
|
log "starting background"
|
|
|
@@ -69,10 +51,10 @@ main = do
|
|
|
where
|
|
|
runMain :: Aff Unit
|
|
|
runMain = do
|
|
|
- allTabs <- query
|
|
|
- liftEffect $ initializeBackground =<< (Ref.new $ initialTabListToGlobalState allTabs)
|
|
|
+ allTabs <- BT.browserQuery
|
|
|
+ liftEffect $ initializeBackground =<< (Ref.new $ GS.initialTabListToGlobalState allTabs)
|
|
|
|
|
|
-initializeBackground :: Ref.Ref GlobalState -> Effect Unit
|
|
|
+initializeBackground :: Ref.Ref GS.GlobalState -> Effect Unit
|
|
|
initializeBackground ref = do
|
|
|
(mkListenerOne $ onWindowCreated ref) >>= WinOnCreated.addListener
|
|
|
(mkListenerOne $ onWindowRemoved ref) >>= WinOnRemoved.addListener
|
|
|
@@ -85,177 +67,61 @@ initializeBackground ref = do
|
|
|
(mkListenerTwo $ onTabMoved ref) >>= OnMoved.addListener
|
|
|
(mkListenerOne $ onConnect ref) >>= Runtime.onConnectAddListener
|
|
|
|
|
|
-onWindowCreated :: (Ref.Ref GlobalState) -> Window -> Effect Unit
|
|
|
-onWindowCreated ref { id: winId } =
|
|
|
- (log $ "bg: created window " <> (show winId))
|
|
|
- *> (ref # Ref.modify_ (over (_windows <<< at winId) (_ <|> (Just emptyWindow))))
|
|
|
+onWindowCreated :: StateRef -> Window -> Effect Unit
|
|
|
+onWindowCreated ref { id: winId } = do
|
|
|
+ log $ "bg: created window " <> (show winId)
|
|
|
+ ref # Ref.modify_ (GS.addEmptyWindow winId)
|
|
|
|
|
|
-onWindowRemoved :: (Ref.Ref GlobalState) -> WindowId -> Effect Unit
|
|
|
-onWindowRemoved ref winId =
|
|
|
- (log $ "bg: deleted window " <> (show winId))
|
|
|
- *> (ref # Ref.modify_ \s -> s { windows = M.delete winId s.windows })
|
|
|
+onWindowRemoved :: StateRef -> WindowId -> Effect Unit
|
|
|
+onWindowRemoved ref winId = do
|
|
|
+ log $ "bg: deleted window " <> (show winId)
|
|
|
+ ref # Ref.modify_ (GS.deleteWindow winId)
|
|
|
|
|
|
-onTabCreated :: (Ref.Ref GlobalState) -> Tab -> Effect Unit
|
|
|
-onTabCreated stateRef (Tab tab) = do
|
|
|
- log $ "bg: created tab " <> show tab.id
|
|
|
- state <-
|
|
|
- Ref.modify (insertTab (Tab tab)) stateRef
|
|
|
- case (preview (_portFromWindow (Tab tab)) state) of
|
|
|
- Nothing -> pure unit
|
|
|
- Just port -> Runtime.postMessageJson port $ BgTabCreated (Tab tab)
|
|
|
- where
|
|
|
- -- | insert a tab, creating the window and updating the position
|
|
|
- insertTab :: Tab -> GlobalState -> GlobalState
|
|
|
- insertTab (Tab t) s =
|
|
|
- let
|
|
|
- windows = case M.lookup t.windowId s.windows of
|
|
|
- Nothing -> M.insert t.windowId emptyWindow s.windows
|
|
|
- Just _ -> s.windows
|
|
|
- in
|
|
|
- s { windows = M.update updateWindow t.windowId windows }
|
|
|
- where
|
|
|
- updateWindow :: ExtWindow -> Maybe ExtWindow
|
|
|
- updateWindow win =
|
|
|
- -- this will delete the window if there is an issue with the position..
|
|
|
- -- not the best solution but we can't really recover from it anyway.
|
|
|
- (A.insertAt t.index t.id win.positions)
|
|
|
- <#> \newPos ->
|
|
|
- win
|
|
|
- { positions = newPos
|
|
|
- , tabs = M.insert t.id (Tab t) win.tabs
|
|
|
- }
|
|
|
+onTabCreated :: StateRef -> Tab -> Effect Unit
|
|
|
+onTabCreated stateRef tab = do
|
|
|
+ log $ "bg: created tab " <> (BT.showTabId tab)
|
|
|
+ state <- Ref.modify (GS.createTab tab) stateRef
|
|
|
+ GS.sendToTabPort tab state $ BgTabCreated tab
|
|
|
|
|
|
-onTabUpdated :: (Ref.Ref GlobalState) -> TabId -> OnUpdated.ChangeInfo -> Tab -> Effect Unit
|
|
|
-onTabUpdated stateRef tid cinfo tab' = do
|
|
|
+onTabUpdated :: StateRef -> TabId -> OnUpdated.ChangeInfo -> Tab -> Effect Unit
|
|
|
+onTabUpdated stateRef tid cinfo tab = do
|
|
|
log $ "bg: updated tab " <> show tid
|
|
|
- state <- Ref.modify (updateTab tab') stateRef
|
|
|
- case (preview (_portFromWindow tab') state) of
|
|
|
- Nothing -> pure unit
|
|
|
- Just port -> Runtime.postMessageJson port $ BgTabUpdated tid cinfo tab'
|
|
|
- where
|
|
|
- updateTab :: Tab -> GlobalState -> GlobalState
|
|
|
- updateTab (Tab t) =
|
|
|
- -- update by replacing the tab only if it already exists
|
|
|
- (over (_tabFromWindow (Tab t)) (map $ const (Tab t)))
|
|
|
- -- or update the currently detached tab
|
|
|
-
|
|
|
- >>> ( \s -> case s.detached of
|
|
|
- Just (Tab t')
|
|
|
- | t.id == t'.id -> s { detached = Just (Tab t') }
|
|
|
- _ -> s
|
|
|
- )
|
|
|
+ state <- Ref.modify (GS.updateTab tab) stateRef
|
|
|
+ GS.sendToTabPort tab state $ BgTabUpdated tid cinfo tab
|
|
|
|
|
|
-onTabMoved :: (Ref.Ref GlobalState) -> TabId -> OnMoved.MoveInfo -> Effect Unit
|
|
|
+onTabMoved :: StateRef -> TabId -> OnMoved.MoveInfo -> Effect Unit
|
|
|
onTabMoved ref tid minfo = do
|
|
|
log $ "bg: moved tab " <> show tid
|
|
|
- s <- Ref.modify (updateState minfo) ref
|
|
|
- case (preview (_portFromWindowId minfo.windowId) s) of
|
|
|
- Nothing -> pure unit
|
|
|
- Just port -> Runtime.postMessageJson port $ BgTabMoved tid minfo.fromIndex minfo.toIndex
|
|
|
- where
|
|
|
- updateState :: OnMoved.MoveInfo -> GlobalState -> GlobalState
|
|
|
- updateState minfo' state =
|
|
|
- let
|
|
|
- newState = updatePositions minfo' state
|
|
|
-
|
|
|
- newPositions :: Array TabId
|
|
|
- newPositions = view ((_windowIdToWindow minfo'.windowId) <<< _positions) newState
|
|
|
- in
|
|
|
- over ((_windowIdToWindow minfo'.windowId) <<< _tabs) (updateTabsIndex newPositions) newState
|
|
|
-
|
|
|
- updatePositions :: OnMoved.MoveInfo -> GlobalState -> GlobalState
|
|
|
- updatePositions minfo' = over ((_windowIdToWindow minfo'.windowId) <<< _positions) $ unsafeUpdatePositions minfo'
|
|
|
-
|
|
|
- -- | given a move info, update the positions tabs
|
|
|
- unsafeUpdatePositions :: OnMoved.MoveInfo -> Array TabId -> Array TabId
|
|
|
- unsafeUpdatePositions minfo' =
|
|
|
- (moveElement minfo'.fromIndex minfo'.toIndex)
|
|
|
- -- the indexes should exist, we need to revisit the code if it doesn't
|
|
|
-
|
|
|
- >>> (maybe' (\_ -> unsafeThrow "invalid indexes") identity)
|
|
|
-
|
|
|
- moveElement :: forall a. Int -> Int -> Array a -> Maybe (Array a)
|
|
|
- moveElement from to arr = do
|
|
|
- tab <- arr A.!! from
|
|
|
- A.deleteAt from arr >>= A.insertAt to tab
|
|
|
+ state <- Ref.modify (GS.moveTab minfo.fromIndex minfo.toIndex minfo.windowId) ref
|
|
|
+ GS.sendToWindowPort minfo.windowId state $ BgTabMoved tid minfo.fromIndex minfo.toIndex
|
|
|
|
|
|
- -- | update the index of the tab given the positions
|
|
|
- updateTabsIndex :: Array TabId -> M.Map TabId Tab -> M.Map TabId Tab
|
|
|
- updateTabsIndex positions tabs =
|
|
|
- let
|
|
|
- modifyFuncs :: Array (M.Map TabId Tab -> M.Map TabId Tab)
|
|
|
- modifyFuncs = A.mapWithIndex (\idx tid' -> set (at tid' <<< _Just <<< _Newtype <<< _index) idx) positions
|
|
|
- in
|
|
|
- A.foldl (#) tabs modifyFuncs
|
|
|
-
|
|
|
-onTabActived :: (Ref.Ref GlobalState) -> OnActivated.ActiveInfo -> Effect Unit
|
|
|
+onTabActived :: StateRef -> OnActivated.ActiveInfo -> Effect Unit
|
|
|
onTabActived stateRef (OnActivated.ActiveInfo aInfo) = do
|
|
|
log $ "bg: activated tab " <> show aInfo.tabId
|
|
|
- state <- Ref.modify (updateGlobalState aInfo.previousTabId aInfo.tabId) stateRef
|
|
|
- case (preview (_portFromWindowId aInfo.windowId) state) of
|
|
|
- Nothing -> pure unit
|
|
|
- Just port -> Runtime.postMessageJson port $ BgTabActivated aInfo.previousTabId aInfo.tabId
|
|
|
- where
|
|
|
- updateGlobalState :: (Maybe TabId) -> TabId -> GlobalState -> GlobalState
|
|
|
- updateGlobalState prev new state =
|
|
|
- let
|
|
|
- -- TODO: we have the windowId, we can directly get the tab from that
|
|
|
- -- without using _tabFromTabIdAndWindow that goes through all the windows.
|
|
|
- prevTab = prev >>= _tabFromTabIdAndWindow state
|
|
|
-
|
|
|
- prevTabF :: GlobalState -> GlobalState
|
|
|
- prevTabF = maybe identity (\t -> set (_activeTab t) false) prevTab
|
|
|
-
|
|
|
- newTab = _tabFromTabIdAndWindow state new
|
|
|
-
|
|
|
- newTabF :: GlobalState -> GlobalState
|
|
|
- newTabF = maybe identity (\t -> set (_activeTab t) true) newTab
|
|
|
+ state <- Ref.modify (GS.activateTab aInfo.windowId aInfo.previousTabId aInfo.tabId) stateRef
|
|
|
+ GS.sendToWindowPort aInfo.windowId state $ BgTabActivated aInfo.previousTabId aInfo.tabId
|
|
|
|
|
|
- _activeTab t = (_tabFromWindow t) <<< _Just <<< _Newtype <<< _active
|
|
|
- in
|
|
|
- (prevTabF >>> newTabF) state
|
|
|
+onTabDeleted :: StateRef -> TabId -> OnRemoved.RemoveInfo -> Effect Unit
|
|
|
+onTabDeleted stateRef tabId info = do
|
|
|
+ log $ "bg: deleted tab " <> show tabId
|
|
|
+ state <- Ref.modify (GS.deleteTab info.windowId tabId) stateRef
|
|
|
+ GS.sendToWindowPort info.windowId state $ BgTabDeleted tabId
|
|
|
|
|
|
-stateDeleteTab :: WindowId -> TabId -> GlobalState -> GlobalState
|
|
|
-stateDeleteTab wid tid =
|
|
|
- ( (set (_windowIdToTabIdToTab wid tid) Nothing)
|
|
|
- >>> over (_windowIdToWindow wid <<< _positions) (A.filter ((/=) tid))
|
|
|
- )
|
|
|
-
|
|
|
-deleteTab :: (Ref.Ref GlobalState) -> WindowId -> TabId -> Effect Unit
|
|
|
-deleteTab stateRef wid tid = do
|
|
|
- log $ "bg: deleted tab " <> show tid
|
|
|
- state <- Ref.modify (stateDeleteTab wid tid) stateRef
|
|
|
- let
|
|
|
- port = preview (_portFromWindowId wid) state
|
|
|
- maybe (pure unit) (\p -> Runtime.postMessageJson p (BgTabDeleted tid)) port
|
|
|
-
|
|
|
-onTabDeleted :: (Ref.Ref GlobalState) -> TabId -> OnRemoved.RemoveInfo -> Effect Unit
|
|
|
-onTabDeleted stateRef tabId info = deleteTab stateRef info.windowId tabId
|
|
|
-
|
|
|
-onTabDetached :: (Ref.Ref GlobalState) -> TabId -> OnDetached.DetachInfo -> Effect Unit
|
|
|
+onTabDetached :: StateRef -> TabId -> OnDetached.DetachInfo -> Effect Unit
|
|
|
onTabDetached stateRef tabId info = do
|
|
|
log $ "bg: detached tab " <> show tabId
|
|
|
- oldState <- Ref.read stateRef
|
|
|
- case preview (_windowIdToTabIdToTab info.oldWindowId tabId) oldState of
|
|
|
- Just (Just tab) -> do
|
|
|
- deleteTab stateRef info.oldWindowId tabId
|
|
|
- Ref.modify_ (_ { detached = Just tab }) stateRef
|
|
|
- _ -> throw $ "tab " <> (show tabId) <> " not found, shouldn't happen"
|
|
|
+ state <- Ref.modify (GS.detachTab info.oldWindowId tabId) stateRef
|
|
|
+ GS.sendToWindowPort info.oldWindowId state $ BgTabDetached tabId
|
|
|
|
|
|
-onTabAttached :: (Ref.Ref GlobalState) -> TabId -> OnAttached.AttachInfo -> Effect Unit
|
|
|
+onTabAttached :: StateRef -> TabId -> OnAttached.AttachInfo -> Effect Unit
|
|
|
onTabAttached stateRef tid info = do
|
|
|
log $ "bg: attached tab " <> show tid
|
|
|
- state <- Ref.read stateRef
|
|
|
- case state.detached of
|
|
|
- Just (Tab tab) ->
|
|
|
- let
|
|
|
- newTab = Tab (tab { windowId = info.newWindowId, index = info.newPosition })
|
|
|
- in
|
|
|
- onTabCreated stateRef newTab
|
|
|
- *> Ref.modify_ (_ { detached = Nothing }) stateRef
|
|
|
- _ -> throw $ "tab " <> (show tid) <> " doesn't exist in the state, this shouldn't happen"
|
|
|
+ state <- Ref.modify (GS.attachTab info.newWindowId tid info.newPosition) stateRef
|
|
|
+ case GS.tabFromWinIdAndTabId info.newWindowId tid state of
|
|
|
+ Just newTab -> GS.sendToWindowPort info.newWindowId state $ BgTabAttached newTab
|
|
|
+ Nothing -> pure unit
|
|
|
|
|
|
-onConnect :: (Ref.Ref GlobalState) -> Runtime.Port -> Effect Unit
|
|
|
+onConnect :: StateRef -> Runtime.Port -> Effect Unit
|
|
|
onConnect stateRef port = do
|
|
|
-- create a temporary listener ref that will only be held until the sidebar has sent its current window
|
|
|
listenerRef <- Ref.new Nothing
|
|
|
@@ -276,12 +142,12 @@ onConnect stateRef port = do
|
|
|
onNewWindowId ::
|
|
|
forall a.
|
|
|
Runtime.Port ->
|
|
|
- (Ref.Ref GlobalState) ->
|
|
|
+ StateRef ->
|
|
|
(Ref.Ref (Maybe (Listener a))) ->
|
|
|
WindowId -> Effect Unit
|
|
|
onNewWindowId port stateRef listenerRef winId = do
|
|
|
-- initial state of the current window
|
|
|
- initWindowState port stateRef winId
|
|
|
+ Ref.modify_ (GS.initializeWindowState winId port) stateRef
|
|
|
-- remove the previous onMessage listener
|
|
|
ogListener <- Ref.read listenerRef
|
|
|
foldMap (\l -> Runtime.onMessageRemoveListener port l) ogListener
|
|
|
@@ -303,28 +169,21 @@ onNewWindowId port stateRef listenerRef winId = do
|
|
|
onDisconnectListener <- mkListenerUnit $ onDisconnect stateRef winId sidebarListener
|
|
|
Runtime.portOnDisconnect port onDisconnectListener
|
|
|
|
|
|
-initWindowState :: Runtime.Port -> (Ref.Ref GlobalState) -> WindowId -> Effect Unit
|
|
|
-initWindowState port ref winId =
|
|
|
- (flip Ref.modify_) ref
|
|
|
- $ over (_windows <<< (at winId))
|
|
|
- (\win -> Just $ set _port (Just port) (fromMaybe emptyWindow win))
|
|
|
-
|
|
|
-- TODO don't pass the full ref, but only a set of function to manipulate/access
|
|
|
-- the data required
|
|
|
-manageSidebar :: (Ref.Ref GlobalState) -> WindowId -> Runtime.Port -> SidebarEvent -> Effect Unit
|
|
|
+manageSidebar :: StateRef -> WindowId -> Runtime.Port -> SidebarEvent -> Effect Unit
|
|
|
manageSidebar ref winId port = case _ of
|
|
|
- SbDeleteTab tabId -> launchAff_ $ removeOne tabId
|
|
|
- SbActivateTab tabId -> launchAff_ $ activateTab tabId
|
|
|
- SbMoveTab tabId newIndex -> moveTab tabId { index: newIndex }
|
|
|
+ SbDeleteTab tabId -> launchAff_ $ BT.browserRemoveOne tabId
|
|
|
+ SbActivateTab tabId -> launchAff_ $ BT.browserActivateTab tabId
|
|
|
+ SbMoveTab tabId newIndex -> BT.browserMoveTab tabId { index: newIndex }
|
|
|
SbCreateTab tid' -> case tid' of
|
|
|
- Nothing -> createTab { windowId: winId }
|
|
|
+ Nothing -> BT.browserCreateTab { windowId: winId }
|
|
|
Just tid ->
|
|
|
- Ref.read ref <#> view (_positions >>> _windowIdToWindow winId)
|
|
|
+ Ref.read ref <#> view (GS._positions >>> GS._windowIdToWindow winId)
|
|
|
>>= \positions -> case A.elemIndex tid positions of
|
|
|
- Nothing -> createTab { windowId: winId }
|
|
|
- Just idx -> createTab { windowId: winId, index: idx + 1 }
|
|
|
+ Nothing -> BT.browserCreateTab { windowId: winId }
|
|
|
+ Just idx -> BT.browserCreateTab { windowId: winId, index: idx + 1 }
|
|
|
_ -> pure unit
|
|
|
|
|
|
-onDisconnect :: forall a. (Ref.Ref GlobalState) -> WindowId -> Listener a -> Effect Unit
|
|
|
-onDisconnect stateRef winId listener = Ref.modify_ (set (_windows <<< (at winId) <<< _Just <<< _port) Nothing) stateRef
|
|
|
+onDisconnect :: forall a. StateRef -> WindowId -> Listener a -> Effect Unit
|
|
|
+onDisconnect stateRef winId listener = Ref.modify_ (set (GS._windows <<< (at winId) <<< _Just <<< GS._port) Nothing) stateRef
|