svitak/app/Types.hs

147 lines
4.3 KiB
Haskell

{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-- | Shared types for the whole reader. The parser ('EpubParser') produces a
-- 'BookStructure' cheaply and renders each 'Chapter' on demand; the UI
-- consumes both through the 'BookInfo' interface.
module Types
( -- * Identifiers
ChapterIndex (..),
InternalPath (..),
-- * Book content
Chapter (..),
ChapterRef (..),
TocEntry (..),
BookStructure (..),
-- * The book interface the UI talks to
BookInfo (..),
-- * UI state
AppState (..),
UserAction (..),
-- * EPUB environment
EpubEnv (..),
EpubAction,
ReadMode(..),
Html(..),
PlainText(..),
)
where
import Codec.Archive.Zip (Archive)
import qualified Codec.Epub.Data.Manifest as DMan
import qualified Codec.Epub.Data.Metadata as DMeta
import Control.Monad.Except (ExceptT)
import Control.Monad.Reader (ReaderT)
import qualified Data.Map.Strict as M
import qualified Data.Text as T
-- | Position of a chapter within the linear reading order (the spine).
newtype ChapterIndex = ChapterIndex Int
deriving (Show, Eq, Ord, Num)
-- | A fully-resolved path to a file inside the EPUB zip.
newtype InternalPath = InternalPath FilePath
deriving (Show, Eq, Ord)
-- | A lightweight pointer to a chapter: enough to navigate and label it in the
-- sidebar without having rendered its (potentially large) content yet.
data ChapterRef = ChapterRef
{ refPath :: InternalPath,
refTitle :: T.Text,
refIndex :: ChapterIndex
}
deriving (Show)
-- | A chapter rendered to self-contained HTML, ready for WebKit.
data Chapter = Chapter
{ chapterPath :: InternalPath,
chapterTitle :: T.Text,
chapterHtml :: Html,
chapterIndex :: ChapterIndex
}
deriving (Show)
newtype Html = Html T.Text deriving (Show, Eq)
newtype PlainText = PlainText T.Text deriving (Show, Eq)
-- | A node in the (possibly nested) table of contents. @tocTarget@ is the
-- resolved chapter path; @tocFragment@ is the optional in-page anchor.
data TocEntry = TocEntry
{ tocLabel :: T.Text,
tocTarget :: InternalPath,
tocFragment :: T.Text,
tocChildren :: [TocEntry]
}
deriving (Show)
-- | The cheap-to-compute skeleton of a book: reading order + navigation tree.
data BookStructure = BookStructure
{ structRefs :: [ChapterRef],
structToc :: [TocEntry]
}
-- | The UI only ever sees a book through this interface, so it stays
-- format-agnostic. The single instance ('EpubEnv') lives in 'EpubParser'.
class BookInfo a where
bookTitle :: a -> T.Text
bookAuthors :: a -> [T.Text]
bookFilePath :: a -> FilePath
-- | Resolve the spine and TOC. Fast: does not render chapter bodies.
loadStructure :: a -> IO (Either String BookStructure)
-- | Render a single chapter's body to self-contained HTML, on demand.
renderChapter :: a -> ChapterRef -> IO (Either String Chapter)
-- | Extract a single chapter's plain text (tags stripped), for search.
chapterText :: a -> ChapterRef -> IO (Either String PlainText)
-- | Mutable application state, held in a 'Control.Concurrent.STM.TVar'.
-- Rendered chapters accumulate in 'stCache' as the reader visits them.
data AppState = AppState
{ stRefs :: [ChapterRef],
stToc :: [TocEntry],
stCache :: M.Map ChapterIndex Chapter,
stIndex :: ChapterIndex,
stZoom :: Double,
stTitle :: T.Text,
stBookPath :: FilePath,
stGen :: Int,
stUse :: M.Map ChapterIndex Int,
stTick :: Int,
stSearchIndex :: M.Map ChapterIndex PlainText,
stMode :: ReadMode
}
-- Enable toggling between paginated (2 pages on the screen) or scrolling (single, flowing page)
data ReadMode = Paginated | Scrolling
deriving (Eq)
-- | A user request, produced by the keyboard / sidebar and handled centrally.
-- 'GoToChapter' carries an optional anchor to scroll to within the chapter.
data UserAction
= NextChapter
| PrevChapter
| ZoomIn
| ZoomOut
| GoToChapter ChapterIndex T.Text
| NextPage
| PrevPage
| ToggleMode
| SearchFocus
-- | Parsed EPUB, carried through the parser as a reader environment.
data EpubEnv = EpubEnv
{ archive :: Archive,
opfXml :: String,
baseDir :: FilePath,
bookPath :: FilePath,
eMetadata :: DMeta.Metadata,
eManifest :: DMan.Manifest,
eVersion :: T.Text
}
-- | The parser monad: read-only access to the 'EpubEnv', with string errors.
type EpubAction a = ReaderT EpubEnv (ExceptT String IO) a