Bar.purs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549
  1. module PureTabs.Sidebar.Bar where
  2. import Browser.Tabs (Tab(..), TabId)
  3. import Browser.Utils (eqBy, sortByKeyIndex, unsafeLog)
  4. import Control.Bind (bind, discard, map, void, (<#>), (=<<), (>>=))
  5. import Data.Array ((:))
  6. import Data.Array as A
  7. import Data.Array.NonEmpty (NonEmptyArray)
  8. import Data.Array.NonEmpty as NonEmptyArray
  9. import Data.Eq ((/=))
  10. import Data.Foldable (for_)
  11. import Data.Function (($))
  12. import Data.Map as M
  13. import Data.Maybe (Maybe(..), fromMaybe, fromMaybe', maybe)
  14. import Data.MediaType.Common (textPlain)
  15. import Data.Number (fromString)
  16. import Data.Set (Set, toUnfoldable) as S
  17. import Data.Set.NonEmpty (cons, max) as NES
  18. import Data.Symbol (SProxy(..))
  19. import Data.Traversable (sequence, traverse)
  20. import Data.Tuple (Tuple(..))
  21. import Data.Tuple as T
  22. import Data.Unit (Unit, unit)
  23. import Effect.Aff.Class (class MonadAff)
  24. import Effect.Class (class MonadEffect, liftEffect)
  25. import Effect.Console (log)
  26. import Halogen as H
  27. import Halogen.HTML as HH
  28. import Halogen.HTML.Events as HE
  29. import Halogen.HTML.Properties as HP
  30. import Prelude (flip, join, pure, show, (#), (&&), (+), (-), (<$>), (<<<), (<>), (==), (>), (>>>))
  31. import PureTabs.Model.Group (GroupId(..))
  32. import PureTabs.Model.GroupMapping (GroupData(..))
  33. import PureTabs.Model.SidebarEvent (SidebarEvent(..))
  34. import PureTabs.Model.TabWithGroup (TabWithGroup(..))
  35. import PureTabs.Sidebar.Component.GroupName as GroupName
  36. import PureTabs.Sidebar.Component.TopMenu as TopMenu
  37. import PureTabs.Sidebar.Tabs (Output(..))
  38. import PureTabs.Sidebar.Tabs as Tabs
  39. import PureTabs.Utils (ifU)
  40. import Sidebar.Utils (moveElem, whenC)
  41. import Web.HTML.Event.DataTransfer as DT
  42. import Web.HTML.Event.DragEvent as DE
  43. -- TODO: correctly use `pos` when adding or deleting a group (i.e. making sure
  44. -- the pos are contiguous from 0 to #groups - 1)
  45. type Group
  46. = { name :: String
  47. , pos :: Int
  48. }
  49. type State
  50. = { groups :: M.Map GroupId Group
  51. , tabsToGroup :: M.Map TabId GroupId
  52. , groupTabsPositions :: Array (Tuple TabId GroupId)
  53. , currentGroup :: GroupId
  54. , draggedCurrentGroup :: Maybe GroupId
  55. }
  56. data Action
  57. = UserSelectedGroup GroupId
  58. | UserRenameGroup GroupId String
  59. | UserCreatedGroup
  60. | UserChangedDeletion Boolean
  61. | UserDeletedGroup GroupId
  62. | HandleTabsOutput GroupId Tabs.Output
  63. | GroupNameDragOver DE.DragEvent GroupId
  64. | DragEnd DE.DragEvent
  65. data Query a
  66. = TabsQuery (Tabs.Query a)
  67. | InitialTabsWithGroup (Array GroupData) (Array TabWithGroup) a
  68. | InitializeGroups (Array GroupData) a
  69. | AssignTabToGroup TabId (Maybe GroupId) a
  70. | GroupDeleted GroupId (Maybe TabId) a
  71. initialGroup :: M.Map GroupId Group
  72. initialGroup = M.fromFoldable [ Tuple (GroupId 0) { name: "main", pos: 0 } ]
  73. initialState :: forall i. i -> State
  74. initialState _ =
  75. { groups: initialGroup
  76. , tabsToGroup: M.empty
  77. , groupTabsPositions : []
  78. , currentGroup: GroupId 0
  79. , draggedCurrentGroup: Nothing
  80. }
  81. type Slots
  82. = ( tabs :: Tabs.Slot GroupId
  83. , groupName :: GroupName.Slot GroupId
  84. , topMenu :: TopMenu.Slot Unit)
  85. _tabs :: SProxy "tabs"
  86. _tabs = (SProxy :: _ "tabs")
  87. _groupName :: SProxy "groupName"
  88. _groupName = (SProxy :: _ "groupName")
  89. _topMenu :: SProxy "topMenu"
  90. _topMenu = (SProxy :: _ "topMenu")
  91. component :: forall i m. MonadEffect m => MonadAff m => H.Component HH.HTML Query i SidebarEvent m
  92. component =
  93. H.mkComponent
  94. { initialState
  95. , render: render
  96. , eval:
  97. H.mkEval
  98. $ H.defaultEval
  99. { handleQuery = handleQuery
  100. , handleAction = handleAction
  101. }
  102. }
  103. where
  104. render :: State -> H.ComponentHTML Action Slots m
  105. render state =
  106. let
  107. currentGroupShown = fromMaybe state.currentGroup state.draggedCurrentGroup
  108. topMenu = HH.slot _topMenu unit TopMenu.component unit (
  109. Just <<< case _ of
  110. TopMenu.CreateGroup -> UserCreatedGroup
  111. TopMenu.ChangedDeletion value -> UserChangedDeletion value
  112. )
  113. -- TODO: order groups by `pos`
  114. barListGroup = HH.div [ HP.id_ "bar-list" ] [HH.ul [ HP.id_ "bar-list-group"] $
  115. (M.toUnfoldable state.groups) <#> \(Tuple gid g) -> renderGroup gid (gid == currentGroupShown) g
  116. ]
  117. tabsDivs = (S.toUnfoldable $ (M.keys state.groups)) <#>
  118. (\gid -> HH.div [
  119. HP.classes [(H.ClassName "bar-tabs"), whenC (gid == currentGroupShown) (H.ClassName "bar-tabs-active")]
  120. ] [renderGroupTabs gid])
  121. in
  122. HH.div [ HP.id_ "bar", HE.onDragEnd \evt -> Just $ DragEnd evt ] $ topMenu : barListGroup : tabsDivs
  123. renderGroupTabs :: GroupId -> H.ComponentHTML Action Slots m
  124. renderGroupTabs groupId = HH.slot _tabs groupId Tabs.component unit (Just <<< (HandleTabsOutput groupId))
  125. renderGroup :: GroupId -> Boolean -> Group -> H.ComponentHTML Action Slots m
  126. renderGroup groupId isActive group =
  127. HH.li [
  128. HP.classes [(H.ClassName "group-name"), whenC isActive (H.ClassName "active-group")]
  129. , HE.onClick (\_ -> Just (UserSelectedGroup groupId))
  130. , HE.onDragOver \evt -> Just $ GroupNameDragOver evt groupId
  131. ] [ HH.slot _groupName groupId GroupName.component group.name
  132. case _ of
  133. GroupName.NewName newName -> Just (UserRenameGroup groupId newName)
  134. GroupName.DeleteGroup -> Just (UserDeletedGroup groupId)
  135. ]
  136. handleAction :: forall m. MonadEffect m => Action -> H.HalogenM State Action Slots SidebarEvent m Unit
  137. handleAction =
  138. case _ of
  139. UserSelectedGroup gid -> do
  140. H.modify_ _ { currentGroup = gid }
  141. UserRenameGroup gid newName -> do
  142. H.modify_ \s -> s { groups = M.update (\g -> Just $ g { name = newName }) gid s.groups }
  143. H.raise $ SbRenamedGroup gid newName
  144. UserCreatedGroup -> do
  145. s <- H.get
  146. let Tuple gid newGroup = createGroup Nothing s
  147. H.modify_ $ insertGroup gid newGroup
  148. H.raise $ SbCreatedGroup gid newGroup.name
  149. UserChangedDeletion value -> void $ H.queryAll _groupName $ H.tell $ GroupName.DeletionEnabled value
  150. UserDeletedGroup gid -> do
  151. s <- H.get
  152. if M.size s.groups > 1 then
  153. H.raise $ SbDeletedGroup gid $ getTabIdsOfGroup gid s.tabsToGroup
  154. else
  155. void $ H.query _groupName gid $ H.tell $ GroupName.TriedToDeleteLastGroup
  156. GroupNameDragOver dragEvent gid -> do
  157. let
  158. dataTransfer = DE.dataTransfer dragEvent
  159. dragData <- H.liftEffect $ DT.getData textPlain dataTransfer
  160. case fromString dragData of
  161. Nothing -> H.liftEffect $ log $ "[sb] group drag over, got something else than a number: " <> dragData
  162. Just tid -> do
  163. H.modify_ _ { draggedCurrentGroup = Just gid }
  164. H.liftEffect $ log $ "[sb] dragging " <> (show tid) <> " over " <> (show gid)
  165. DragEnd evt -> do
  166. H.modify_ _ { draggedCurrentGroup = Nothing }
  167. H.liftEffect $ log $ "[sb] drag end from bar component"
  168. HandleTabsOutput gid output ->
  169. case output of
  170. OutputTabDragEnd tid' -> do
  171. s <- H.get
  172. case Tuple tid' s.draggedCurrentGroup of
  173. -- Only perform a move when we're dragging a tab onto a different group
  174. Tuple (Just tid) (Just draggedGroup) | s.currentGroup /= draggedGroup ->
  175. moveTabToGroup tid gid draggedGroup s
  176. _ -> pure unit
  177. H.modify_ _ { draggedCurrentGroup = Nothing }
  178. TabsSidebarAction (SbMoveTab tid groupIndex) -> sidebarMoveTab tid gid groupIndex
  179. TabsSidebarAction sbEvent -> H.raise sbEvent
  180. where
  181. moveTabToGroup
  182. :: MonadEffect m => TabId
  183. -> GroupId
  184. -> GroupId
  185. -> State
  186. -> H.HalogenM State Action Slots SidebarEvent m Unit
  187. moveTabToGroup _ fromGroup toGroup _ | fromGroup == toGroup = pure unit
  188. moveTabToGroup tid fromGroup toGroup state = do
  189. let
  190. -- XXX: The goal is to put it at the end, but if you:
  191. -- - create a new group
  192. -- - drag a tab from the first one to it
  193. -- - drag it back to the first group
  194. -- Then it will be at the beginning of the group, not the end.
  195. -- Right now we only put it at the end of the list.
  196. -- We don't support dragging at a specific place.
  197. newTabIndex =
  198. fromMaybe (A.length state.groupTabsPositions)
  199. $ lastWinTabIndexInGroup toGroup state.groupTabsPositions
  200. -- The new index of tab in the group will be at the end.
  201. newIndexInGroup = state.groupTabsPositions #
  202. A.length <<<
  203. A.filter (T.snd >>> (==) toGroup)
  204. s <- H.modify \s ->
  205. s { tabsToGroup = M.update (\_ -> Just toGroup) tid s.tabsToGroup
  206. , groupTabsPositions =
  207. s.groupTabsPositions
  208. <#> \(Tuple tid' gid') -> if tid' == tid then Tuple tid' toGroup else Tuple tid' gid'
  209. -- Reassign the current group directly here to avoid flickering
  210. , currentGroup = toGroup
  211. }
  212. deletedTab' <- H.query _tabs fromGroup $ H.request $ Tabs.TabDeleted tid
  213. case deletedTab' of
  214. Just (Just (Tab tab)) ->
  215. void $ H.query _tabs toGroup $ H.tell
  216. $ Tabs.TabCreated $ Tab (tab { index = newIndexInGroup })
  217. _ -> pure unit
  218. H.raise $ SbMoveTab tid newTabIndex
  219. H.raise $ SbActivateTab tid
  220. H.raise $ SbChangeTabGroup tid (Just toGroup)
  221. void $ handleTabsQuery $ Tabs.TabActivated (Just tid) tid Nothing
  222. -- | Raise a SbMoveTab event with the tab index corrected from the point of view of the
  223. -- | group to that of the Firefox window.
  224. sidebarMoveTab
  225. :: TabId
  226. -> GroupId
  227. -> Int
  228. -> H.HalogenM State Action Slots SidebarEvent m Unit
  229. sidebarMoveTab tid gid groupIndex = do
  230. s <- H.get
  231. let
  232. oldPosition = getPositionTab tid gid s.groupTabsPositions
  233. newIndex = do
  234. prevIdx <- oldPosition
  235. s.groupTabsPositions #
  236. A.mapWithIndex (Tuple)
  237. >>> A.filter (\(Tuple _ (Tuple _ gid')) -> gid' == gid)
  238. >>> (flip A.index) groupIndex
  239. >>> map T.fst
  240. -- Important: we ask Firefox to do the move, but we don't
  241. -- perform it ourselves. This means we don't update the state.
  242. -- We will get back a TabMoved event that will then be
  243. -- processed accordingly.
  244. newIndex # maybe (pure unit) \idx -> H.raise $ SbMoveTab tid idx
  245. handleQuery :: forall a m. MonadEffect m => Query a -> H.HalogenM State Action Slots SidebarEvent m (Maybe a)
  246. handleQuery = case _ of
  247. TabsQuery q -> handleTabsQuery q
  248. InitializeGroups groups a -> do
  249. liftEffect $ log $ "[sb] initializing groups"
  250. let newGroups = M.fromFoldable $
  251. A.mapWithIndex (\idx (GroupData g) -> Tuple g.groupId { name: g.name, pos: idx}) groups
  252. -- TODO: re-assign existing tabs to the new groups.
  253. H.modify_ \s ->
  254. if newGroups == s.groups then
  255. s
  256. else
  257. s { groups = newGroups }
  258. pure (Just a)
  259. -- Given Nothing, we assign the group ourselves (i.e. the tab had no group to start with)
  260. AssignTabToGroup tid Nothing a -> do
  261. { tabsToGroup } <- H.get
  262. let groupId = M.lookup tid tabsToGroup
  263. for_ groupId \gid -> H.raise $ SbChangeTabGroup tid (Just gid)
  264. pure (Just a)
  265. -- Given an existing group for the tab, we modify our state to reflect it. No need to update the
  266. -- background since the information already comes for there.
  267. AssignTabToGroup tid (Just gid) a -> do
  268. oldS <- H.get
  269. for_ (M.lookup tid oldS.tabsToGroup) \prevGid -> do
  270. liftEffect $ log $ "[sb] assigning " <> (show tid) <> " to " <> (show gid) <> " from " <> (show prevGid)
  271. s <- H.modify \s ->
  272. let newGroupTabsPositions =
  273. s.groupTabsPositions <#> \tup@(Tuple tid' gid') -> if tid == tid' then Tuple tid gid else tup
  274. in
  275. s { tabsToGroup = M.insert tid gid s.tabsToGroup, groupTabsPositions = newGroupTabsPositions }
  276. tab <- join <$> (H.query _tabs prevGid $ H.request $ Tabs.TabDeleted tid)
  277. let newTabIndex = getGroupPositionOfTab tid gid s.groupTabsPositions
  278. case Tuple tab newTabIndex of
  279. Tuple (Just (Tab tab')) (Just newTabIndex') ->
  280. void $ H.query _tabs gid $ H.tell $ Tabs.TabCreated (Tab $ tab' { index = newTabIndex'})
  281. _ -> liftEffect $ log $ "[sb] couldn't find the tab or the position of the tab"
  282. pure (Just a)
  283. InitialTabsWithGroup groups tabs a -> do
  284. -- Assign the tabs to their group and save the tabs positions
  285. s <- H.modify \s ->
  286. let
  287. newGroups =
  288. case groups of
  289. [] -> initialGroup
  290. newGroups' ->
  291. M.fromFoldable $
  292. A.mapWithIndex
  293. (\idx (GroupData g) -> Tuple g.groupId { name: g.name, pos: idx})
  294. newGroups'
  295. existingGroups = M.keys newGroups
  296. tabIdGroup = tabs <#> \(TabWithGroup (Tab t) gid) -> Tuple t.id gid
  297. in
  298. s { groups = newGroups, tabsToGroup = M.fromFoldable tabIdGroup, groupTabsPositions = tabIdGroup }
  299. -- Initialize each child tabs component with its tabs
  300. let
  301. tabsGroups = tabs <#> \(TabWithGroup tab@(Tab t) _) -> Tuple tab $ fromMaybe s.currentGroup (M.lookup t.id s.tabsToGroup)
  302. groupedTabs = A.groupBy (eqBy T.snd) (sortByKeyIndex T.snd tabsGroups)
  303. void $ traverse initializeGroup groupedTabs
  304. -- Activate the right tab and its group
  305. let activatedTab = tabsGroups # A.head <<< A.filter (\(Tuple (Tab t) _) -> t.active)
  306. activatedTab # maybe (pure unit) \(Tuple (Tab t) gid) -> do
  307. void $ tellChild gid $ Tabs.TabActivated Nothing t.id
  308. handleAction $ UserSelectedGroup gid
  309. pure (Just a)
  310. where
  311. initializeGroup :: forall act. NonEmptyArray (Tuple Tab GroupId) -> H.HalogenM State act Slots SidebarEvent m Unit
  312. initializeGroup groupedTabs =
  313. let
  314. gid = T.snd $ NonEmptyArray.head groupedTabs
  315. in
  316. void $ tellChild gid $ Tabs.InitialTabList $ A.fromFoldable $ T.fst <$> groupedTabs
  317. GroupDeleted gid currentTid a -> do
  318. H.modify_ \s ->
  319. let
  320. currentGroup = fromMaybe s.currentGroup $ currentTid >>= (flip M.lookup s.tabsToGroup)
  321. in
  322. s { groups = M.delete gid s.groups, currentGroup = currentGroup }
  323. pure $ Just a
  324. handleTabsQuery :: forall act a m. MonadEffect m => Tabs.Query a -> H.HalogenM State act Slots SidebarEvent m (Maybe a)
  325. handleTabsQuery = case _ of
  326. Tabs.InitialTabList tabs a -> pure $ Just a
  327. Tabs.TabCreated (Tab tab) a -> do
  328. s <- H.get
  329. let tabGroupId = fromMaybe s.currentGroup $ tab.openerTabId >>= (flip M.lookup) s.tabsToGroup
  330. newGroupTabsPositions =
  331. fromMaybe s.groupTabsPositions
  332. $ A.insertAt tab.index (Tuple tab.id tabGroupId) s.groupTabsPositions
  333. inGroupPosition = getPositionTabInGroup tab.index tabGroupId newGroupTabsPositions
  334. newTab = Tab $ tab { index = inGroupPosition }
  335. newS <- H.modify \state ->
  336. state
  337. { tabsToGroup = M.insert tab.id tabGroupId s.tabsToGroup
  338. , groupTabsPositions = newGroupTabsPositions
  339. , currentGroup = tabGroupId
  340. }
  341. void $ tellChild tabGroupId $ Tabs.TabCreated newTab
  342. pure $ Just a
  343. Tabs.TabDeleted tid reply -> do
  344. doOnTabGroup tid \gid -> do
  345. H.modify_ (\s -> s
  346. { tabsToGroup = M.delete tid s.tabsToGroup
  347. , groupTabsPositions = A.deleteBy
  348. -- This is ugly. There is no function to delete the
  349. -- first element of an array that matches a condition.
  350. (\(Tuple tid1 _) (Tuple tid2 _) -> tid1 == tid2)
  351. (Tuple tid s.currentGroup)
  352. s.groupTabsPositions
  353. })
  354. void $ H.query _tabs gid $ H.request $ Tabs.TabDeleted tid
  355. pure (Just (reply Nothing))
  356. Tabs.TabActivated prevTid' tid a -> do
  357. for_ prevTid' \prevTid ->
  358. doOnTabGroup prevTid \gid ->
  359. void $ tellChild gid $ Tabs.TabActivated prevTid' tid
  360. doOnTabGroup tid \gid -> do
  361. { tabsToGroup } <- H.modify (_ { currentGroup = gid})
  362. H.raise $ SbSelectedGroup $ getTabIdsOfGroup gid tabsToGroup
  363. void $ tellChild gid $ Tabs.TabActivated prevTid' tid
  364. pure (Just a)
  365. Tabs.TabMoved tid next a -> do
  366. doOnTabGroup tid \gid -> do
  367. { groupTabsPositions } <- H.get
  368. let
  369. newGroupTabsPositions = fromMaybe groupTabsPositions $ do
  370. prevPosition <- getPositionTab tid gid groupTabsPositions
  371. moveElem prevPosition next groupTabsPositions
  372. nextGroupPosition = getPositionTabInGroup next gid newGroupTabsPositions
  373. H.modify_ (_ { groupTabsPositions = newGroupTabsPositions })
  374. void $ tellChild gid $ Tabs.TabMoved tid nextGroupPosition
  375. pure (Just a)
  376. Tabs.TabInfoChanged tid cinfo a -> do
  377. doOnTabGroup tid \gid -> do
  378. void $ tellChild gid $ Tabs.TabInfoChanged tid cinfo
  379. pure (Just a)
  380. Tabs.TabDetached tid a -> do
  381. handleTabsQuery $ Tabs.TabDeleted tid \_ -> a
  382. Tabs.TabAttached tab a -> do
  383. handleTabsQuery $ Tabs.TabCreated tab a
  384. doOnTabGroup
  385. :: forall m act
  386. . TabId
  387. -> (GroupId -> H.HalogenM State act Slots SidebarEvent m Unit)
  388. -> H.HalogenM State act Slots SidebarEvent m Unit
  389. doOnTabGroup tabId f = do
  390. { tabsToGroup } <- H.get
  391. case M.lookup tabId tabsToGroup of
  392. Just groupId -> f groupId
  393. Nothing -> pure unit
  394. tellChild :: forall act m. GroupId -> (H.Tell Tabs.Query) -> H.HalogenM State act Slots SidebarEvent m (Maybe Unit)
  395. tellChild gid q = H.query _tabs gid $ H.tell q
  396. -- | Get the group position of the tab at the given index in the given group.
  397. -- | Return 0 if the tab doesn't exist (same as if the tab when in the first
  398. -- | position).
  399. getPositionTabInGroup
  400. :: Int
  401. -> GroupId
  402. -> Array (Tuple TabId GroupId)
  403. -> Int
  404. getPositionTabInGroup index gid =
  405. (A.take $ index + 1)
  406. >>> (A.filter \(Tuple _ gid') -> gid' == gid)
  407. >>> A.length
  408. >>> (flip (-) $ 1)
  409. -- | Get the window position of a tab.
  410. getPositionTab
  411. :: TabId
  412. -> GroupId
  413. -> Array (Tuple TabId GroupId)
  414. -> Maybe Int
  415. getPositionTab tid gid arr = A.findIndex (\(Tuple tid' gid') -> tid' == tid && gid' == gid) arr
  416. -- | Get the tab IDs of a group.
  417. getTabIdsOfGroup
  418. :: GroupId
  419. -> M.Map TabId GroupId
  420. -> Array TabId
  421. getTabIdsOfGroup gid =
  422. M.toUnfoldable
  423. >>> A.filter (\(Tuple tid gid') -> gid' == gid)
  424. >>> map T.fst
  425. getGroupPositionOfTab
  426. :: TabId
  427. -> GroupId
  428. -> Array (Tuple TabId GroupId)
  429. -> Maybe Int
  430. getGroupPositionOfTab tid gid =
  431. A.filter (T.snd >>> (==) gid)
  432. >>> A.findIndex (T.fst >>> (==) tid)
  433. -- | Obtain the window index of the last tab of a group.
  434. lastWinTabIndexInGroup
  435. :: GroupId
  436. -> Array (Tuple TabId GroupId)
  437. -> Maybe Int
  438. lastWinTabIndexInGroup gid =
  439. A.mapWithIndex (Tuple)
  440. >>> A.filter (T.snd >>> T.snd >>> (==) gid)
  441. >>> map T.fst
  442. >>> A.last
  443. >>> map ((+) 1)
  444. findNextGroupId :: S.Set GroupId -> GroupId
  445. findNextGroupId values =
  446. let GroupId(maxValue) = NES.max (NES.cons (GroupId 0) values)
  447. in GroupId(maxValue + 1)
  448. createGroup :: (Maybe GroupId) -> State -> Tuple GroupId Group
  449. createGroup mGid s =
  450. let
  451. gid = fromMaybe' (\_ -> findNextGroupId $ M.keys s.groups) mGid
  452. in
  453. Tuple gid { name: "new group", pos: M.size s.groups }
  454. insertGroup :: GroupId -> Group -> State -> State
  455. insertGroup gid group s = s { groups = M.insert gid group s.groups }