Bar.purs 20 KB

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