From b24747973f8358913472b8948b0520b55458c33e Mon Sep 17 00:00:00 2001 From: klntsky Date: Mon, 6 Jul 2020 21:26:34 +0300 Subject: [PATCH 1/7] Group modules by package in the sidebar: WIP --- spago.dhall | 1 + src/Docs/Search/App.purs | 74 ++++++++++-- src/Docs/Search/App/SearchResults.purs | 2 +- src/Docs/Search/App/Sidebar.purs | 153 +++++++++++++++++++++++++ src/Docs/Search/Config.purs | 10 +- src/Docs/Search/Declarations.purs | 10 +- src/Docs/Search/Engine.purs | 7 +- src/Docs/Search/IndexBuilder.purs | 16 ++- src/Docs/Search/Interactive.purs | 4 +- src/Docs/Search/ModuleIndex.js | 24 ++++ src/Docs/Search/ModuleIndex.purs | 50 ++++++++ src/Docs/Search/PackageIndex.js | 2 +- src/Docs/Search/Types.purs | 5 + 13 files changed, 328 insertions(+), 30 deletions(-) create mode 100644 src/Docs/Search/App/Sidebar.purs create mode 100644 src/Docs/Search/ModuleIndex.js create mode 100644 src/Docs/Search/ModuleIndex.purs create mode 100644 src/Docs/Search/Types.purs diff --git a/spago.dhall b/spago.dhall index 28ed1b6..9a8b195 100644 --- a/spago.dhall +++ b/spago.dhall @@ -29,6 +29,7 @@ , "node-readline" , "optparse" , "profunctor" + , "profunctor-lenses" , "search-trie" , "string-parsers" , "strings" diff --git a/src/Docs/Search/App.purs b/src/Docs/Search/App.purs index 0359584..4367969 100644 --- a/src/Docs/Search/App.purs +++ b/src/Docs/Search/App.purs @@ -1,13 +1,14 @@ -- | This is the main module of the client-side Halogen app. module Docs.Search.App where -import Prelude - import Docs.Search.App.SearchField as SearchField import Docs.Search.App.SearchResults as SearchResults +import Docs.Search.App.Sidebar as Sidebar import Docs.Search.Extra (whenJust) +import Docs.Search.ModuleIndex as ModuleIndex import Docs.Search.PackageIndex as PackageIndex +import Prelude import Control.Coroutine as Coroutine import Data.Maybe (Maybe(..)) import Data.Newtype (wrap) @@ -17,6 +18,7 @@ import Halogen as H import Halogen.Aff as HA import Halogen.VDom.Driver (runUI) import MarkdownIt as MD +import Web.DOM.ChildNode as ChildNode import Web.DOM.Document as Document import Web.DOM.Element as Element import Web.DOM.Node as Node @@ -24,10 +26,11 @@ import Web.DOM.ParentNode as ParentNode import Web.DOM.Text as Text import Web.Event.EventTarget (addEventListener, eventListener) import Web.HTML as HTML +import Web.HTML.Event.HashChangeEvent.EventTypes (hashchange) import Web.HTML.HTMLDocument as HTMLDocument import Web.HTML.HTMLElement (fromElement) import Web.HTML.Window as Window -import Web.HTML.Event.HashChangeEvent.EventTypes (hashchange) + main :: Effect Unit main = do @@ -40,11 +43,21 @@ main = do -- Initialize a `markdown-it` instance (we need it to render the docs as markdown) markdownIt <- MD.newMarkdownIt MD.Default mempty - whenJust mbContainers \ { searchField, searchResults, pageContents } -> do + whenJust mbContainers \ { searchField + , searchResults + , pageContents + , sidebarContainer + , realSidebar } -> do + + -- Hide real sidebar completely - we are going to recreate it as Halogen component. + ChildNode.remove $ Element.toChildNode realSidebar + HA.runHalogenAff do packageIndex <- PackageIndex.loadPackageIndex + moduleIndex <- ModuleIndex.loadModuleIndex let initialSearchEngineState = { packageIndex: packageIndex + , moduleIndex: moduleIndex , index: mempty , typeIndex: mempty } @@ -53,6 +66,7 @@ main = do SearchResults.mkComponent initialSearchEngineState pageContents markdownIt sfio <- runUI SearchField.component unit searchField + sbio <- runUI (Sidebar.mkComponent moduleIndex) unit sidebarContainer srio <- runUI resultsComponent unit searchResults sfio.subscribe $ @@ -102,24 +116,52 @@ insertStyle doc = do /* Add a margin between badge icons and package/module names. */ margin-right: 0.25em; } + .li-package { + font-weight: bold; + cursor: pointer; + color: #c4953a; + } + ul .li-collapsed-package::before { + content: "▸"; + } + ul .li-expanded-package::before { + content: "▾"; + } + /* Make spaces narrower in the sidebar */ + .li-expanded-package > ul { + margin-top: auto; + margin-bottom: auto; + } + #group-modules__label, #group-modules__input { + display: inline-block; + vertical-align: middle; + color: #666666; + font-size: 0.8rem; + font-weight: 300; + letter-spacing: 1px; + margin-bottom: -0.8em; + } """ mbHead <- ParentNode.querySelector (wrap "head") (Document.toParentNode doc) whenJust mbHead \head -> do - contents <- Document.createTextNode styleContents doc style <- Document.createElement "style" doc void $ Node.appendChild (Text.toNode contents) (Element.toNode style) void $ Node.appendChild (Element.toNode style) (Element.toNode head) + -- | Query the DOM for specific elements that should always be present. getContainers :: Document.Document -> Effect (Maybe { searchField :: HTML.HTMLElement , searchResults :: HTML.HTMLElement - , pageContents :: Element.Element }) + , pageContents :: Element.Element + , sidebarContainer :: HTML.HTMLElement + , realSidebar :: Element.Element + }) getContainers doc = do let docPN = Document.toParentNode doc mbBanner <- @@ -128,13 +170,21 @@ getContainers doc = do ParentNode.querySelector (wrap ".everything-except-footer") docPN mbContainer <- ParentNode.querySelector (wrap ".everything-except-footer > .container") docPN + mbMainContainer <- + ParentNode.querySelector (wrap ".everything-except-footer > main") docPN + mbRealSidebar <- + ParentNode.querySelector (wrap ".col--aside") docPN case unit of - _ | Just banner <- mbBanner - , Just everything <- mbEverything - , Just pageContents <- mbContainer -> do + _ | Just banner <- mbBanner + , Just everything <- mbEverything + , Just pageContents <- mbContainer + , Just mainContainer <- mbMainContainer + , Just realSidebar <- mbRealSidebar -> do search <- Document.createElement "div" doc void $ Node.appendChild (Element.toNode search) (Element.toNode banner) - pure $ fromElement search >>= \searchField -> - fromElement everything >>= \searchResults -> - pure { searchField, searchResults, pageContents } + pure do + searchField <- fromElement search + searchResults <- fromElement everything + sidebarContainer <- fromElement mainContainer + pure { searchField, searchResults, pageContents, realSidebar, sidebarContainer } | otherwise -> pure Nothing diff --git a/src/Docs/Search/App/SearchResults.purs b/src/Docs/Search/App/SearchResults.purs index a79f494..11c6d45 100644 --- a/src/Docs/Search/App/SearchResults.purs +++ b/src/Docs/Search/App/SearchResults.purs @@ -168,7 +168,7 @@ hidePageContents hidePageContents = do state <- H.get H.liftEffect do - Element.setAttribute "style" "display: none" state.contents + Element.setAttribute "style" "display: none;" state.contents render diff --git a/src/Docs/Search/App/Sidebar.purs b/src/Docs/Search/App/Sidebar.purs new file mode 100644 index 0000000..ec5f4d4 --- /dev/null +++ b/src/Docs/Search/App/Sidebar.purs @@ -0,0 +1,153 @@ +module Docs.Search.App.Sidebar where + +import Docs.Search.ModuleIndex (ModuleIndex) +import Docs.Search.Types (ModuleName) + +import Prelude +import Data.Array as Array +import Data.Lens (Setter', _2, (%~), (.~)) +import Data.Lens.Record (prop) +import Data.List (List, foldr) +import Data.Map as Map +import Data.Maybe (Maybe(..)) +import Data.Newtype (wrap) +import Data.Profunctor.Strong (first, (***)) +import Data.Search.Trie (Trie) +import Data.Search.Trie as Trie +import Data.Set (Set) +import Data.Set as Set +import Data.String.CodeUnits (singleton, toCharArray) as String +import Data.Symbol (SProxy(..)) +import Data.Tuple.Nested (type (/\), (/\)) +import Effect.Aff (Aff) +import Halogen as H +import Halogen.HTML as HH +import Halogen.HTML.Events as HE +import Halogen.HTML.Properties as HP + + +data Action = ToggleCollapse String | ToggleGrouping Boolean + + +data Mode = GroupByPackage | DontGroup + +derive instance modeEq :: Eq Mode + + +data PackageEntryState = Expanded | Collapsed + +derive instance packageEntryStateEq :: Eq PackageEntryState + +type State = { expansions :: Trie Char (Set ModuleName /\ PackageEntryState) + , mode :: Mode + , moduleNames :: Array ModuleName + } + + +mkComponent + :: forall o i q + . ModuleIndex + -> H.Component HH.HTML q i o Aff +mkComponent moduleIndex = + H.mkComponent + { initialState: const { expansions + , mode: GroupByPackage + , moduleNames + } + , render + , eval: H.mkEval $ H.defaultEval { handleAction = handleAction } + } + where + moduleNames = Array.sort $ Array.fromFoldable $ foldr Set.union mempty moduleIndex + + -- Convert `ModuleIndex` to the appropriate format. + expansions :: Trie Char (Set ModuleName /\ PackageEntryState) + expansions = + moduleIndex # + Map.toUnfoldable <#> + (String.toCharArray >>> Array.toUnfoldable) *** (_ /\ Collapsed) # + Trie.fromList + + +handleAction + :: forall o + . Action + -> H.HalogenM State Action () o Aff Unit +handleAction (ToggleCollapse packageName) = do + H.modify_ ( + _expansions %~ trieKey packageName %~ _2 %~ + case _ of + Expanded -> Collapsed + Collapsed -> Expanded + ) +handleAction (ToggleGrouping flag) = + H.modify_ (_mode .~ if flag then GroupByPackage else DontGroup) + + +render + :: forall m + . State + -> H.ComponentHTML Action () m +render { expansions, mode, moduleNames } = + + HH.div [ HP.classes [ wrap "col", wrap "col--aside" ] ] + + [ HH.h3_ [ HH.text "Modules" ] + , HH.input [ HP.id_ "group-modules__input" + , HP.type_ HP.InputCheckbox + , HP.checked (mode == GroupByPackage) + , HE.onChecked $ Just <<< ToggleGrouping + ] + + , HH.text " " + , HH.label [ HP.for "group-modules__input" + , HP.id_ "group-modules__label" + ] + [ HH.text " GROUP BY PACKAGE" ] + + , if mode == GroupByPackage + then HH.ul_ $ renderPackageEntry <$> packageList + else HH.ul_ $ renderModuleName <$> moduleNames + ] + where + + renderPackageEntry (packageName /\ modules /\ status) = + + HH.li [ HE.onClick $ const $ Just $ ToggleCollapse packageName + , HP.classes [ wrap $ if status == Expanded + then "li-expanded-package" + else "li-collapsed-package" + , wrap "li-package" ] + ] + + if status == Expanded + then [ HH.text packageName + , HH.ul_ $ Set.toUnfoldable modules <#> renderModuleName ] + else [ HH.text packageName ] + + renderModuleName moduleName = + HH.li_ + [ HH.a [ HP.href (moduleName <> ".html") ] + [ HH.text moduleName ] + ] + + packageList :: Array (String /\ Set ModuleName /\ PackageEntryState) + packageList = + first (Array.foldMap String.singleton) <$> ( + Trie.toUnfoldable expansions :: Array (Array Char /\ Set ModuleName /\ PackageEntryState) + ) + + +-- Some optics: + +_expansions :: forall a b rest. (a -> b) -> { expansions :: a | rest } -> { expansions :: b | rest } +_expansions = prop (SProxy :: SProxy "expansions") + +_mode :: forall a b rest. (a -> b) -> { mode :: a | rest } -> { mode :: b | rest } +_mode = prop (SProxy :: SProxy "mode") + +trieKey :: forall a. String -> Setter' (Trie Char a) a +trieKey key f = Trie.update f path + where + path :: List Char + path = Array.toUnfoldable $ String.toCharArray key diff --git a/src/Docs/Search/Config.purs b/src/Docs/Search/Config.purs index bf5c4cb..745f8c9 100644 --- a/src/Docs/Search/Config.purs +++ b/src/Docs/Search/Config.purs @@ -7,6 +7,8 @@ config :: { declIndexDirectory :: String , mkIndexPartLoadPath :: Int -> String , mkIndexPartPath :: Int -> String + , moduleIndexPath :: String + , moduleIndexLoadPath :: String , packageInfoPath :: String , packageInfoLoadPath :: String , mkShapeScriptPath :: String -> String @@ -43,9 +45,11 @@ config = \(partId :: Int) -> "html/index/declarations/" <> show partId <> ".js" , mkIndexPartLoadPath: \(partId :: Int) -> "./index/declarations/" <> show partId <> ".js" - , packageInfoPath: "generated-docs/html/index/packages.json" - -- ^ Path to package index. - , packageInfoLoadPath: "./index/packages.json" + , moduleIndexPath: "generated-docs/html/index/modules.js" + , moduleIndexLoadPath: "./index/modules.js" + -- ^ Used to load mode index to the browser scope. + , packageInfoPath: "generated-docs/html/index/packages.js" + , packageInfoLoadPath: "./index/packages.js" -- ^ Used to load package index to the browser scope. , resultsCount: 25 -- ^ How many results to show by default? diff --git a/src/Docs/Search/Declarations.purs b/src/Docs/Search/Declarations.purs index 403bfbb..de17249 100644 --- a/src/Docs/Search/Declarations.purs +++ b/src/Docs/Search/Declarations.purs @@ -4,15 +4,16 @@ import Docs.Search.DocsJson (ChildDeclType(..), ChildDeclaration(..), DeclType(. import Docs.Search.PackageIndex (Scores) import Docs.Search.SearchResult (ResultInfo(..), SearchResult(..)) import Docs.Search.TypeDecoder (Constraint(..), QualifiedName(..), Type(..), Kind, joinForAlls) +import Docs.Search.Types (ModuleName, PackageName) import Prelude - import Control.Alt ((<|>)) import Data.Array ((!!)) import Data.Array as Array import Data.Foldable (foldr) import Data.List (List, (:)) import Data.List as List +import Data.Map as Map import Data.Maybe (Maybe(..), fromMaybe) import Data.Newtype (class Newtype, unwrap, wrap) import Data.Search.Trie (Trie, alter) @@ -20,11 +21,6 @@ import Data.String.CodeUnits (stripPrefix, stripSuffix, toCharArray) import Data.String.Common (split) as String import Data.String.Common (toLower) import Data.String.Pattern (Pattern(..)) -import Data.Map as Map - - -type ModuleName = String -type PackageName = String newtype Declarations @@ -191,7 +187,7 @@ getLevelAndName DeclExternKind name = { name, declLevel: KindLevel } -- | Extract package name from `sourceSpan.name`, which contains path to -- | the source file. If `ModuleName` string starts with `Prim.`, it's a -- | built-in (guaranteed by the compiler). -extractPackageName :: ModuleName -> Maybe SourceSpan -> String +extractPackageName :: ModuleName -> Maybe SourceSpan -> PackageName extractPackageName moduleName _ | String.split (Pattern ".") moduleName !! 0 == Just "Prim" = "" extractPackageName _ Nothing = "" diff --git a/src/Docs/Search/Engine.purs b/src/Docs/Search/Engine.purs index a4300cb..5e0b376 100644 --- a/src/Docs/Search/Engine.purs +++ b/src/Docs/Search/Engine.purs @@ -1,6 +1,7 @@ module Docs.Search.Engine where import Docs.Search.PackageIndex (PackageIndex, PackageResult) +import Docs.Search.ModuleIndex (ModuleIndex) import Docs.Search.SearchResult (SearchResult, typeOfResult) import Docs.Search.TypeQuery (TypeQuery(..), parseTypeQuery, penalty) @@ -36,6 +37,7 @@ type EngineState index typeIndex = { index :: index , typeIndex :: typeIndex , packageIndex :: PackageIndex + , moduleIndex :: ModuleIndex } @@ -44,9 +46,10 @@ mkEngineState . index -> typeIndex -> PackageIndex + -> ModuleIndex -> EngineState index typeIndex -mkEngineState index typeIndex packageIndex = - { index, typeIndex, packageIndex } +mkEngineState index typeIndex packageIndex moduleIndex = + { index, typeIndex, packageIndex, moduleIndex } data Result diff --git a/src/Docs/Search/IndexBuilder.purs b/src/Docs/Search/IndexBuilder.purs index 41c107c..0200d02 100644 --- a/src/Docs/Search/IndexBuilder.purs +++ b/src/Docs/Search/IndexBuilder.purs @@ -1,16 +1,16 @@ module Docs.Search.IndexBuilder where +import Docs.Search.BrowserEngine (getPartId) import Docs.Search.Config (config) import Docs.Search.Declarations (Declarations(..), mkDeclarations) import Docs.Search.DocsJson (DocsJson) import Docs.Search.Extra ((>#>)) -import Docs.Search.BrowserEngine (getPartId) +import Docs.Search.ModuleIndex (ModuleIndex, mkModuleIndex) import Docs.Search.PackageIndex (PackageInfo, mkPackageInfo, mkScores) import Docs.Search.SearchResult (SearchResult) import Docs.Search.TypeIndex (TypeIndex, mkTypeIndex) import Prelude - import Data.Argonaut.Core (stringify) import Data.Argonaut.Decode (decodeJson) import Data.Argonaut.Encode (encodeJson) @@ -78,6 +78,7 @@ run' cfg = do index = mkDeclarations scores docsJsons typeIndex = mkTypeIndex scores docsJsons packageInfo = mkPackageInfo packageMetas + moduleIndex = mkModuleIndex index createDirectories cfg @@ -85,6 +86,7 @@ run' cfg = do ignore <$> parallel (writeIndex cfg index) <*> parallel (writeTypeIndex cfg typeIndex) <*> parallel (writePackageInfo packageInfo) + <*> parallel (writeModuleIndex moduleIndex) <*> parallel (if cfg.noPatch then pure unit else patchDocs cfg) @@ -105,7 +107,7 @@ run' cfg = do show countOfPackages <> " packages to the search index." - where ignore _ _ _ _ _ = unit + where ignore _ _ _ _ _ _ _ = unit -- | Exit early if something is missing. @@ -220,6 +222,14 @@ writePackageInfo packageInfo = do where header = "window.DocsSearchPackageIndex = " + +writeModuleIndex :: ModuleIndex -> Aff Unit +writeModuleIndex moduleIndex = do + writeTextFile UTF8 config.moduleIndexPath $ + header <> stringify (encodeJson moduleIndex) + where + header = "window.DocsSearchModuleIndex = " + -- | Get a mapping from index parts to index contents. getIndex :: Declarations -> Map Int (Array (Tuple String (Array SearchResult))) getIndex (Declarations trie) = diff --git a/src/Docs/Search/Interactive.purs b/src/Docs/Search/Interactive.purs index a4ad5da..e3fd65e 100644 --- a/src/Docs/Search/Interactive.purs +++ b/src/Docs/Search/Interactive.purs @@ -2,6 +2,7 @@ module Docs.Search.Interactive where import Docs.Search.Declarations (Declarations, mkDeclarations) +import Docs.Search.ModuleIndex (mkModuleIndex) import Docs.Search.DocsJson (DataDeclType(..)) import Docs.Search.Engine (mkEngineState, Result(..)) import Docs.Search.Engine as Engine @@ -51,7 +52,8 @@ run cfg = launchAff_ $ do index = mkDeclarations scores docsJsons typeIndex = docsJsons >>= resultsWithTypes scores packageIndex = mkPackageIndex $ mkPackageInfo packageMetas - engineState = mkEngineState (unwrap index) typeIndex packageIndex + moduleIndex = mkModuleIndex index + engineState = mkEngineState (unwrap index) typeIndex packageIndex moduleIndex let countOfDefinitions = Trie.size $ unwrap index countOfTypeDefinitions = Array.length typeIndex diff --git a/src/Docs/Search/ModuleIndex.js b/src/Docs/Search/ModuleIndex.js new file mode 100644 index 0000000..fd74abc --- /dev/null +++ b/src/Docs/Search/ModuleIndex.js @@ -0,0 +1,24 @@ +/* global exports */ + +exports.load = function (url) { + return function () { + return new Promise(function (resolve, reject) { + if (typeof window.DocsSearchModuleIndex === 'undefined') { + var script = document.createElement('script'); + script.type = 'text/javascript'; + script.src = url; + script.addEventListener('load', function () { + if (typeof window.DocsSearchModuleIndex === 'undefined') { + reject(new Error("Couldn't load module index")); + } else { + resolve(window.DocsSearchModuleIndex); + } + }); + script.addEventListener('error', reject); + document.body.appendChild(script); + } else { + resolve(window.DocsSearchModuleIndex); + } + }); + }; +}; diff --git a/src/Docs/Search/ModuleIndex.purs b/src/Docs/Search/ModuleIndex.purs new file mode 100644 index 0000000..bf61eea --- /dev/null +++ b/src/Docs/Search/ModuleIndex.purs @@ -0,0 +1,50 @@ +module Docs.Search.ModuleIndex where + +import Docs.Search.Config (config) +import Docs.Search.Declarations (Declarations(..)) +import Docs.Search.SearchResult (SearchResult(..)) +import Docs.Search.Types (ModuleName, PackageName) + +import Prelude +import Control.Promise (Promise, toAffE) +import Data.Argonaut.Core (Json) +import Data.Argonaut.Decode (decodeJson) +import Data.Either (hush) +import Data.Foldable (foldr) +import Data.List (List) +import Data.Map (Map) +import Data.Map as Map +import Data.Maybe (fromMaybe) +import Data.Search.Trie as Trie +import Data.Set (Set) +import Data.Set as Set +import Effect (Effect) +import Effect.Aff (Aff) + + +type ModuleIndex = Map PackageName (Set ModuleName) + + +-- | Constructs a mapping from packages to modules +mkModuleIndex :: Declarations -> ModuleIndex +mkModuleIndex (Declarations trie) = + foldr (Map.unionWith Set.union) mempty $ extract <$> Trie.values trie + where + extract + :: List SearchResult + -> Map PackageName (Set ModuleName) + extract = foldr (Map.unionWith Set.union) mempty <<< map mkEntry + where + mkEntry (SearchResult { packageName, moduleName }) = + Map.singleton packageName (Set.singleton moduleName) + + +loadModuleIndex :: Aff ModuleIndex +loadModuleIndex = do + json <- toAffE $ load config.moduleIndexLoadPath + pure $ fromMaybe mempty $ hush $ decodeJson json + + +foreign import load + :: String + -> Effect (Promise Json) diff --git a/src/Docs/Search/PackageIndex.js b/src/Docs/Search/PackageIndex.js index 29bf0ed..a68a8a6 100644 --- a/src/Docs/Search/PackageIndex.js +++ b/src/Docs/Search/PackageIndex.js @@ -9,7 +9,7 @@ exports.load = function (url) { script.src = url; script.addEventListener('load', function () { if (typeof window.DocsSearchPackageIndex === 'undefined') { - reject(new Error("Couldn't load index for type shape " + shape)); + reject(new Error("Couldn't load package index.")); } else { resolve(window.DocsSearchPackageIndex); } diff --git a/src/Docs/Search/Types.purs b/src/Docs/Search/Types.purs new file mode 100644 index 0000000..1f65654 --- /dev/null +++ b/src/Docs/Search/Types.purs @@ -0,0 +1,5 @@ +module Docs.Search.Types where + +type ModuleName = String + +type PackageName = String From 47506e8d7b911b473834246246b750c81d374753 Mon Sep 17 00:00:00 2001 From: klntsky Date: Mon, 6 Jul 2020 22:01:13 +0300 Subject: [PATCH 2/7] Do not allow the module listing to collapse before the page unloads. --- src/Docs/Search/App/Sidebar.purs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/Docs/Search/App/Sidebar.purs b/src/Docs/Search/App/Sidebar.purs index ec5f4d4..f89e51c 100644 --- a/src/Docs/Search/App/Sidebar.purs +++ b/src/Docs/Search/App/Sidebar.purs @@ -24,9 +24,12 @@ import Halogen as H import Halogen.HTML as HH import Halogen.HTML.Events as HE import Halogen.HTML.Properties as HP +import Web.Event.Event (stopPropagation) +import Web.UIEvent.MouseEvent (MouseEvent) +import Web.UIEvent.MouseEvent as ME -data Action = ToggleCollapse String | ToggleGrouping Boolean +data Action = ToggleCollapse String | ToggleGrouping Boolean | ModuleClicked MouseEvent data Mode = GroupByPackage | DontGroup @@ -70,9 +73,12 @@ mkComponent moduleIndex = handleAction - :: forall o - . Action - -> H.HalogenM State Action () o Aff Unit + :: forall o + . Action + -> H.HalogenM State Action () o Aff Unit +handleAction (ModuleClicked event) = do + -- Do not allow the module listing to collapse before the page unloads. + H.liftEffect $ stopPropagation $ ME.toEvent event handleAction (ToggleCollapse packageName) = do H.modify_ ( _expansions %~ trieKey packageName %~ _2 %~ @@ -127,7 +133,9 @@ render { expansions, mode, moduleNames } = renderModuleName moduleName = HH.li_ - [ HH.a [ HP.href (moduleName <> ".html") ] + [ HH.a [ HP.href (moduleName <> ".html") + , HE.onClick $ Just <<< ModuleClicked + ] [ HH.text moduleName ] ] From fc47df69fea5de8a43977293e077258e96eb7659 Mon Sep 17 00:00:00 2001 From: klntsky Date: Mon, 6 Jul 2020 22:27:52 +0300 Subject: [PATCH 3/7] Revert "Do not allow the module listing to collapse before the page unloads." This reverts commit 47506e8d7b911b473834246246b750c81d374753. --- src/Docs/Search/App/Sidebar.purs | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/Docs/Search/App/Sidebar.purs b/src/Docs/Search/App/Sidebar.purs index f89e51c..ec5f4d4 100644 --- a/src/Docs/Search/App/Sidebar.purs +++ b/src/Docs/Search/App/Sidebar.purs @@ -24,12 +24,9 @@ import Halogen as H import Halogen.HTML as HH import Halogen.HTML.Events as HE import Halogen.HTML.Properties as HP -import Web.Event.Event (stopPropagation) -import Web.UIEvent.MouseEvent (MouseEvent) -import Web.UIEvent.MouseEvent as ME -data Action = ToggleCollapse String | ToggleGrouping Boolean | ModuleClicked MouseEvent +data Action = ToggleCollapse String | ToggleGrouping Boolean data Mode = GroupByPackage | DontGroup @@ -73,12 +70,9 @@ mkComponent moduleIndex = handleAction - :: forall o - . Action - -> H.HalogenM State Action () o Aff Unit -handleAction (ModuleClicked event) = do - -- Do not allow the module listing to collapse before the page unloads. - H.liftEffect $ stopPropagation $ ME.toEvent event + :: forall o + . Action + -> H.HalogenM State Action () o Aff Unit handleAction (ToggleCollapse packageName) = do H.modify_ ( _expansions %~ trieKey packageName %~ _2 %~ @@ -133,9 +127,7 @@ render { expansions, mode, moduleNames } = renderModuleName moduleName = HH.li_ - [ HH.a [ HP.href (moduleName <> ".html") - , HE.onClick $ Just <<< ModuleClicked - ] + [ HH.a [ HP.href (moduleName <> ".html") ] [ HH.text moduleName ] ] From f6286def4bccc4cf58158f897d47d5d33274b99c Mon Sep 17 00:00:00 2001 From: klntsky Date: Mon, 6 Jul 2020 22:30:30 +0300 Subject: [PATCH 4/7] Use
instead of implementing package list expansion using halogen --- src/Docs/Search/App.purs | 11 +++---- src/Docs/Search/App/Sidebar.purs | 50 ++++++++++---------------------- 2 files changed, 19 insertions(+), 42 deletions(-) diff --git a/src/Docs/Search/App.purs b/src/Docs/Search/App.purs index 4367969..ade7b7e 100644 --- a/src/Docs/Search/App.purs +++ b/src/Docs/Search/App.purs @@ -121,14 +121,8 @@ insertStyle doc = do cursor: pointer; color: #c4953a; } - ul .li-collapsed-package::before { - content: "▸"; - } - ul .li-expanded-package::before { - content: "▾"; - } /* Make spaces narrower in the sidebar */ - .li-expanded-package > ul { + .li-package > details > ul { margin-top: auto; margin-bottom: auto; } @@ -141,6 +135,9 @@ insertStyle doc = do letter-spacing: 1px; margin-bottom: -0.8em; } + summary:focus { + outline: none; + } """ mbHead <- diff --git a/src/Docs/Search/App/Sidebar.purs b/src/Docs/Search/App/Sidebar.purs index ec5f4d4..14a9efd 100644 --- a/src/Docs/Search/App/Sidebar.purs +++ b/src/Docs/Search/App/Sidebar.purs @@ -5,13 +5,13 @@ import Docs.Search.Types (ModuleName) import Prelude import Data.Array as Array -import Data.Lens (Setter', _2, (%~), (.~)) +import Data.Lens (Setter', (.~)) import Data.Lens.Record (prop) import Data.List (List, foldr) import Data.Map as Map import Data.Maybe (Maybe(..)) import Data.Newtype (wrap) -import Data.Profunctor.Strong (first, (***)) +import Data.Profunctor.Strong (first) import Data.Search.Trie (Trie) import Data.Search.Trie as Trie import Data.Set (Set) @@ -26,7 +26,7 @@ import Halogen.HTML.Events as HE import Halogen.HTML.Properties as HP -data Action = ToggleCollapse String | ToggleGrouping Boolean +data Action = ToggleGrouping Boolean data Mode = GroupByPackage | DontGroup @@ -34,11 +34,7 @@ data Mode = GroupByPackage | DontGroup derive instance modeEq :: Eq Mode -data PackageEntryState = Expanded | Collapsed - -derive instance packageEntryStateEq :: Eq PackageEntryState - -type State = { expansions :: Trie Char (Set ModuleName /\ PackageEntryState) +type State = { expansions :: Trie Char (Set ModuleName) , mode :: Mode , moduleNames :: Array ModuleName } @@ -61,11 +57,11 @@ mkComponent moduleIndex = moduleNames = Array.sort $ Array.fromFoldable $ foldr Set.union mempty moduleIndex -- Convert `ModuleIndex` to the appropriate format. - expansions :: Trie Char (Set ModuleName /\ PackageEntryState) + expansions :: Trie Char (Set ModuleName) expansions = moduleIndex # Map.toUnfoldable <#> - (String.toCharArray >>> Array.toUnfoldable) *** (_ /\ Collapsed) # + first (String.toCharArray >>> Array.toUnfoldable) # Trie.fromList @@ -73,13 +69,6 @@ handleAction :: forall o . Action -> H.HalogenM State Action () o Aff Unit -handleAction (ToggleCollapse packageName) = do - H.modify_ ( - _expansions %~ trieKey packageName %~ _2 %~ - case _ of - Expanded -> Collapsed - Collapsed -> Expanded - ) handleAction (ToggleGrouping flag) = H.modify_ (_mode .~ if flag then GroupByPackage else DontGroup) @@ -111,19 +100,13 @@ render { expansions, mode, moduleNames } = ] where - renderPackageEntry (packageName /\ modules /\ status) = - - HH.li [ HE.onClick $ const $ Just $ ToggleCollapse packageName - , HP.classes [ wrap $ if status == Expanded - then "li-expanded-package" - else "li-collapsed-package" - , wrap "li-package" ] - ] - - if status == Expanded - then [ HH.text packageName - , HH.ul_ $ Set.toUnfoldable modules <#> renderModuleName ] - else [ HH.text packageName ] + renderPackageEntry (packageName /\ modules) = + HH.li [ HP.classes [ wrap "li-package" ] ] + [ HH.details_ + [ HH.summary_ [ HH.text packageName ] + , HH.ul_ $ Set.toUnfoldable modules <#> renderModuleName + ] + ] renderModuleName moduleName = HH.li_ @@ -131,18 +114,15 @@ render { expansions, mode, moduleNames } = [ HH.text moduleName ] ] - packageList :: Array (String /\ Set ModuleName /\ PackageEntryState) + packageList :: Array (String /\ Set ModuleName) packageList = first (Array.foldMap String.singleton) <$> ( - Trie.toUnfoldable expansions :: Array (Array Char /\ Set ModuleName /\ PackageEntryState) + Trie.toUnfoldable expansions :: Array (Array Char /\ Set ModuleName) ) -- Some optics: -_expansions :: forall a b rest. (a -> b) -> { expansions :: a | rest } -> { expansions :: b | rest } -_expansions = prop (SProxy :: SProxy "expansions") - _mode :: forall a b rest. (a -> b) -> { mode :: a | rest } -> { mode :: b | rest } _mode = prop (SProxy :: SProxy "mode") From e9103663f72d5fd37d64272e0d63cef3acc85f2c Mon Sep 17 00:00:00 2001 From: klntsky Date: Tue, 7 Jul 2020 02:02:27 +0300 Subject: [PATCH 5/7] Remove dead code --- src/Docs/Search/App/Sidebar.purs | 42 +++++++++----------------------- 1 file changed, 11 insertions(+), 31 deletions(-) diff --git a/src/Docs/Search/App/Sidebar.purs b/src/Docs/Search/App/Sidebar.purs index 14a9efd..4c08d26 100644 --- a/src/Docs/Search/App/Sidebar.purs +++ b/src/Docs/Search/App/Sidebar.purs @@ -1,22 +1,19 @@ module Docs.Search.App.Sidebar where import Docs.Search.ModuleIndex (ModuleIndex) -import Docs.Search.Types (ModuleName) +import Docs.Search.Types (ModuleName, PackageName) import Prelude import Data.Array as Array -import Data.Lens (Setter', (.~)) +import Data.Lens ((.~)) import Data.Lens.Record (prop) -import Data.List (List, foldr) +import Data.List (foldr) +import Data.Map (Map) import Data.Map as Map import Data.Maybe (Maybe(..)) import Data.Newtype (wrap) -import Data.Profunctor.Strong (first) -import Data.Search.Trie (Trie) -import Data.Search.Trie as Trie import Data.Set (Set) import Data.Set as Set -import Data.String.CodeUnits (singleton, toCharArray) as String import Data.Symbol (SProxy(..)) import Data.Tuple.Nested (type (/\), (/\)) import Effect.Aff (Aff) @@ -34,7 +31,7 @@ data Mode = GroupByPackage | DontGroup derive instance modeEq :: Eq Mode -type State = { expansions :: Trie Char (Set ModuleName) +type State = { moduleIndex :: Map PackageName (Set ModuleName) , mode :: Mode , moduleNames :: Array ModuleName } @@ -46,7 +43,7 @@ mkComponent -> H.Component HH.HTML q i o Aff mkComponent moduleIndex = H.mkComponent - { initialState: const { expansions + { initialState: const { moduleIndex , mode: GroupByPackage , moduleNames } @@ -56,14 +53,6 @@ mkComponent moduleIndex = where moduleNames = Array.sort $ Array.fromFoldable $ foldr Set.union mempty moduleIndex - -- Convert `ModuleIndex` to the appropriate format. - expansions :: Trie Char (Set ModuleName) - expansions = - moduleIndex # - Map.toUnfoldable <#> - first (String.toCharArray >>> Array.toUnfoldable) # - Trie.fromList - handleAction :: forall o @@ -77,7 +66,7 @@ render :: forall m . State -> H.ComponentHTML Action () m -render { expansions, mode, moduleNames } = +render { moduleIndex, mode, moduleNames } = HH.div [ HP.classes [ wrap "col", wrap "col--aside" ] ] @@ -94,9 +83,9 @@ render { expansions, mode, moduleNames } = ] [ HH.text " GROUP BY PACKAGE" ] - , if mode == GroupByPackage - then HH.ul_ $ renderPackageEntry <$> packageList - else HH.ul_ $ renderModuleName <$> moduleNames + , HH.ul_ $ if mode == GroupByPackage + then renderPackageEntry <$> packageList + else renderModuleName <$> moduleNames ] where @@ -115,19 +104,10 @@ render { expansions, mode, moduleNames } = ] packageList :: Array (String /\ Set ModuleName) - packageList = - first (Array.foldMap String.singleton) <$> ( - Trie.toUnfoldable expansions :: Array (Array Char /\ Set ModuleName) - ) + packageList = Map.toUnfoldable moduleIndex -- Some optics: _mode :: forall a b rest. (a -> b) -> { mode :: a | rest } -> { mode :: b | rest } _mode = prop (SProxy :: SProxy "mode") - -trieKey :: forall a. String -> Setter' (Trie Char a) a -trieKey key f = Trie.update f path - where - path :: List Char - path = Array.toUnfoldable $ String.toCharArray key From 64bee541bc7c21800d28f215cb3fc091fe6654bf Mon Sep 17 00:00:00 2001 From: klntsky Date: Wed, 8 Jul 2020 14:10:11 +0300 Subject: [PATCH 6/7] Save sidebar checkbox state to localStorage. --- spago.dhall | 1 + src/Docs/Search/App.purs | 15 +++++- src/Docs/Search/App/Sidebar.purs | 80 +++++++++++++++++++++++++------- src/Docs/Search/Config.purs | 3 ++ 4 files changed, 80 insertions(+), 19 deletions(-) diff --git a/spago.dhall b/spago.dhall index 9a8b195..2504af6 100644 --- a/spago.dhall +++ b/spago.dhall @@ -36,6 +36,7 @@ , "test-unit" , "web-dom" , "web-html" + , "web-storage" ] , packages = ./packages.dhall diff --git a/src/Docs/Search/App.purs b/src/Docs/Search/App.purs index ade7b7e..ce014e9 100644 --- a/src/Docs/Search/App.purs +++ b/src/Docs/Search/App.purs @@ -30,6 +30,7 @@ import Web.HTML.Event.HashChangeEvent.EventTypes (hashchange) import Web.HTML.HTMLDocument as HTMLDocument import Web.HTML.HTMLElement (fromElement) import Web.HTML.Window as Window +import Web.HTML.Event.EventTypes (focus) main :: Effect Unit @@ -66,7 +67,9 @@ main = do SearchResults.mkComponent initialSearchEngineState pageContents markdownIt sfio <- runUI SearchField.component unit searchField - sbio <- runUI (Sidebar.mkComponent moduleIndex) unit sidebarContainer + sbio <- do + component <- Sidebar.mkComponent moduleIndex + runUI component unit sidebarContainer srio <- runUI resultsComponent unit searchResults sfio.subscribe $ @@ -86,6 +89,16 @@ main = do addEventListener hashchange listener true (Window.toEventTarget win) + -- Subscribe to window focus events + H.liftEffect do + + listener <- + eventListener \event -> + launchAff_ do + sbio.query $ H.tell Sidebar.UpdateModuleGrouping + + addEventListener focus listener true (Window.toEventTarget win) + insertStyle :: Document.Document -> Effect Unit insertStyle doc = do diff --git a/src/Docs/Search/App/Sidebar.purs b/src/Docs/Search/App/Sidebar.purs index 4c08d26..02af801 100644 --- a/src/Docs/Search/App/Sidebar.purs +++ b/src/Docs/Search/App/Sidebar.purs @@ -1,5 +1,6 @@ module Docs.Search.App.Sidebar where +import Docs.Search.Config (config) import Docs.Search.ModuleIndex (ModuleIndex) import Docs.Search.Types (ModuleName, PackageName) @@ -10,21 +11,26 @@ import Data.Lens.Record (prop) import Data.List (foldr) import Data.Map (Map) import Data.Map as Map -import Data.Maybe (Maybe(..)) +import Data.Maybe (Maybe(..), isJust) import Data.Newtype (wrap) import Data.Set (Set) import Data.Set as Set import Data.Symbol (SProxy(..)) import Data.Tuple.Nested (type (/\), (/\)) +import Effect (Effect) import Effect.Aff (Aff) import Halogen as H import Halogen.HTML as HH import Halogen.HTML.Events as HE import Halogen.HTML.Properties as HP +import Web.HTML as HTML +import Web.HTML.Window as Window +import Web.Storage.Storage as Storage -data Action = ToggleGrouping Boolean +data Action = ToggleGrouping Mode +data Query a = UpdateModuleGrouping a data Mode = GroupByPackage | DontGroup @@ -38,28 +44,52 @@ type State = { moduleIndex :: Map PackageName (Set ModuleName) mkComponent - :: forall o i q + :: forall i . ModuleIndex - -> H.Component HH.HTML q i o Aff -mkComponent moduleIndex = - H.mkComponent - { initialState: const { moduleIndex - , mode: GroupByPackage - , moduleNames - } - , render - , eval: H.mkEval $ H.defaultEval { handleAction = handleAction } - } - where - moduleNames = Array.sort $ Array.fromFoldable $ foldr Set.union mempty moduleIndex + -> Aff (H.Component HH.HTML Query i Action Aff) +mkComponent moduleIndex = do + mode <- H.liftEffect loadModeFromLocalStorage + pure $ + H.mkComponent + { initialState: const { moduleIndex + , mode + , moduleNames + } + , render + , eval: H.mkEval $ H.defaultEval { handleAction = handleAction + , handleQuery = handleQuery + } + } + where + moduleNames = Array.sort $ Array.fromFoldable $ foldr Set.union mempty moduleIndex handleAction :: forall o . Action -> H.HalogenM State Action () o Aff Unit -handleAction (ToggleGrouping flag) = - H.modify_ (_mode .~ if flag then GroupByPackage else DontGroup) +handleAction (ToggleGrouping mode) = do + H.modify_ (_mode .~ mode) + + H.liftEffect do + window <- HTML.window + localStorage <- Window.localStorage window + + if mode == GroupByPackage + then Storage.setItem config.groupModulesItem "true" localStorage + else Storage.removeItem config.groupModulesItem localStorage + + +handleQuery + :: forall a i + . Query a + -> H.HalogenM State i () Action Aff (Maybe a) +handleQuery (UpdateModuleGrouping next) = do + oldMode <- H.get <#> _.mode + newMode <- H.liftEffect loadModeFromLocalStorage + when (oldMode /= newMode) do + H.modify_ (_mode .~ newMode) + pure Nothing render @@ -74,7 +104,7 @@ render { moduleIndex, mode, moduleNames } = , HH.input [ HP.id_ "group-modules__input" , HP.type_ HP.InputCheckbox , HP.checked (mode == GroupByPackage) - , HE.onChecked $ Just <<< ToggleGrouping + , HE.onChecked $ Just <<< ToggleGrouping <<< isCheckedToMode ] , HH.text " " @@ -107,6 +137,20 @@ render { moduleIndex, mode, moduleNames } = packageList = Map.toUnfoldable moduleIndex +-- | Decide whether to group modules by package in the sidebar, using localStorage. +loadModeFromLocalStorage :: Effect Mode +loadModeFromLocalStorage = do + window <- HTML.window + localStorage <- Window.localStorage window + mbGroupModules <- Storage.getItem config.groupModulesItem localStorage + pure $ if isJust mbGroupModules then GroupByPackage else DontGroup + + +-- | Convert checkbox status to sidebar mode +isCheckedToMode :: Boolean -> Mode +isCheckedToMode = if _ then GroupByPackage else DontGroup + + -- Some optics: _mode :: forall a b rest. (a -> b) -> { mode :: a | rest } -> { mode :: b | rest } diff --git a/src/Docs/Search/Config.purs b/src/Docs/Search/Config.purs index 745f8c9..86b9fab 100644 --- a/src/Docs/Search/Config.purs +++ b/src/Docs/Search/Config.purs @@ -9,6 +9,7 @@ config :: , mkIndexPartPath :: Int -> String , moduleIndexPath :: String , moduleIndexLoadPath :: String + , groupModulesItem :: String , packageInfoPath :: String , packageInfoLoadPath :: String , mkShapeScriptPath :: String -> String @@ -48,6 +49,8 @@ config = , moduleIndexPath: "generated-docs/html/index/modules.js" , moduleIndexLoadPath: "./index/modules.js" -- ^ Used to load mode index to the browser scope. + , groupModulesItem: "PureScriptDocsSearchGroupModules" + -- ^ localStorage key to save sidebar checkbox value to. , packageInfoPath: "generated-docs/html/index/packages.js" , packageInfoLoadPath: "./index/packages.js" -- ^ Used to load package index to the browser scope. From b2cefe5f1ac51661eaf80180e3adf8cca84722fc Mon Sep 17 00:00:00 2001 From: klntsky Date: Wed, 8 Jul 2020 14:21:16 +0300 Subject: [PATCH 7/7] Rename: "mode" -> "groupingMode" --- src/Docs/Search/App/Sidebar.purs | 46 ++++++++++++++++---------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/Docs/Search/App/Sidebar.purs b/src/Docs/Search/App/Sidebar.purs index 02af801..c69b510 100644 --- a/src/Docs/Search/App/Sidebar.purs +++ b/src/Docs/Search/App/Sidebar.purs @@ -28,17 +28,17 @@ import Web.HTML.Window as Window import Web.Storage.Storage as Storage -data Action = ToggleGrouping Mode +data Action = ToggleGrouping GroupingMode data Query a = UpdateModuleGrouping a -data Mode = GroupByPackage | DontGroup +data GroupingMode = GroupByPackage | DontGroup -derive instance modeEq :: Eq Mode +derive instance groupingModeEq :: Eq GroupingMode type State = { moduleIndex :: Map PackageName (Set ModuleName) - , mode :: Mode + , groupingMode :: GroupingMode , moduleNames :: Array ModuleName } @@ -48,11 +48,11 @@ mkComponent . ModuleIndex -> Aff (H.Component HH.HTML Query i Action Aff) mkComponent moduleIndex = do - mode <- H.liftEffect loadModeFromLocalStorage + groupingMode <- H.liftEffect loadGroupingModeFromLocalStorage pure $ H.mkComponent { initialState: const { moduleIndex - , mode + , groupingMode , moduleNames } , render @@ -68,14 +68,14 @@ handleAction :: forall o . Action -> H.HalogenM State Action () o Aff Unit -handleAction (ToggleGrouping mode) = do - H.modify_ (_mode .~ mode) +handleAction (ToggleGrouping groupingMode) = do + H.modify_ (_groupingMode .~ groupingMode) H.liftEffect do window <- HTML.window localStorage <- Window.localStorage window - if mode == GroupByPackage + if groupingMode == GroupByPackage then Storage.setItem config.groupModulesItem "true" localStorage else Storage.removeItem config.groupModulesItem localStorage @@ -85,10 +85,10 @@ handleQuery . Query a -> H.HalogenM State i () Action Aff (Maybe a) handleQuery (UpdateModuleGrouping next) = do - oldMode <- H.get <#> _.mode - newMode <- H.liftEffect loadModeFromLocalStorage - when (oldMode /= newMode) do - H.modify_ (_mode .~ newMode) + oldGroupingMode <- H.get <#> _.groupingMode + newGroupingMode <- H.liftEffect loadGroupingModeFromLocalStorage + when (oldGroupingMode /= newGroupingMode) do + H.modify_ (_groupingMode .~ newGroupingMode) pure Nothing @@ -96,15 +96,15 @@ render :: forall m . State -> H.ComponentHTML Action () m -render { moduleIndex, mode, moduleNames } = +render { moduleIndex, groupingMode, moduleNames } = HH.div [ HP.classes [ wrap "col", wrap "col--aside" ] ] [ HH.h3_ [ HH.text "Modules" ] , HH.input [ HP.id_ "group-modules__input" , HP.type_ HP.InputCheckbox - , HP.checked (mode == GroupByPackage) - , HE.onChecked $ Just <<< ToggleGrouping <<< isCheckedToMode + , HP.checked (groupingMode == GroupByPackage) + , HE.onChecked $ Just <<< ToggleGrouping <<< isCheckedToGroupingMode ] , HH.text " " @@ -113,7 +113,7 @@ render { moduleIndex, mode, moduleNames } = ] [ HH.text " GROUP BY PACKAGE" ] - , HH.ul_ $ if mode == GroupByPackage + , HH.ul_ $ if groupingMode == GroupByPackage then renderPackageEntry <$> packageList else renderModuleName <$> moduleNames ] @@ -138,8 +138,8 @@ render { moduleIndex, mode, moduleNames } = -- | Decide whether to group modules by package in the sidebar, using localStorage. -loadModeFromLocalStorage :: Effect Mode -loadModeFromLocalStorage = do +loadGroupingModeFromLocalStorage :: Effect GroupingMode +loadGroupingModeFromLocalStorage = do window <- HTML.window localStorage <- Window.localStorage window mbGroupModules <- Storage.getItem config.groupModulesItem localStorage @@ -147,11 +147,11 @@ loadModeFromLocalStorage = do -- | Convert checkbox status to sidebar mode -isCheckedToMode :: Boolean -> Mode -isCheckedToMode = if _ then GroupByPackage else DontGroup +isCheckedToGroupingMode :: Boolean -> GroupingMode +isCheckedToGroupingMode = if _ then GroupByPackage else DontGroup -- Some optics: -_mode :: forall a b rest. (a -> b) -> { mode :: a | rest } -> { mode :: b | rest } -_mode = prop (SProxy :: SProxy "mode") +_groupingMode :: forall a b rest. (a -> b) -> { groupingMode :: a | rest } -> { groupingMode :: b | rest } +_groupingMode = prop (SProxy :: SProxy "groupingMode")