144 lines
2.7 KiB
Elm
144 lines
2.7 KiB
Elm
module Zipper exposing (Zipper, current, currentPage, fromList, init, isAtEnd, isAtStart, length, next, prev, samePageAs, toEnd, toStart)
|
|
|
|
{-| The Zipper allows to dynamically paginate between items.
|
|
-}
|
|
|
|
|
|
type Zipper a
|
|
= Zipper
|
|
{ prev : List a
|
|
, current : a
|
|
, next : List a
|
|
}
|
|
|
|
|
|
{-| Gets the current item in the zipper.
|
|
-}
|
|
current : Zipper a -> a
|
|
current (Zipper data) =
|
|
data.current
|
|
|
|
|
|
{-| If counting from 1, determines the number corresponding to the currently selected item.
|
|
-}
|
|
currentPage : Zipper a -> Int
|
|
currentPage (Zipper data) =
|
|
List.length data.prev + 1
|
|
|
|
|
|
{-| Builds a zipper from a list of items.
|
|
-}
|
|
fromList : a -> List a -> Zipper a
|
|
fromList head tail =
|
|
Zipper { prev = [], current = head, next = tail }
|
|
|
|
|
|
{-| Create a new zipper from nothing.
|
|
-}
|
|
init : a -> Zipper a
|
|
init x =
|
|
Zipper { prev = [], current = x, next = [] }
|
|
|
|
|
|
{-| Determines whether the zipper is at the end.
|
|
-}
|
|
isAtEnd : Zipper a -> Bool
|
|
isAtEnd (Zipper data) =
|
|
List.isEmpty data.next
|
|
|
|
|
|
{-| Determines whether the zipper is at the start.
|
|
-}
|
|
isAtStart : Zipper a -> Bool
|
|
isAtStart (Zipper data) =
|
|
List.isEmpty data.prev
|
|
|
|
|
|
{-| Determine the total number of items in the zipper.
|
|
-}
|
|
length : Zipper a -> Int
|
|
length (Zipper data) =
|
|
List.length data.prev + List.length data.next + 1
|
|
|
|
|
|
{-| Paginates one further in the zipper.
|
|
-}
|
|
next : Zipper a -> Zipper a
|
|
next (Zipper data) =
|
|
case data.next of
|
|
[] ->
|
|
Zipper data
|
|
|
|
head :: tail ->
|
|
Zipper
|
|
{ prev = data.current :: data.prev
|
|
, current = head
|
|
, next = tail
|
|
}
|
|
|
|
|
|
{-| Paginates one back in the zipper.
|
|
-}
|
|
prev : Zipper a -> Zipper a
|
|
prev (Zipper data) =
|
|
case data.prev of
|
|
[] ->
|
|
Zipper data
|
|
|
|
head :: tail ->
|
|
Zipper
|
|
{ prev = tail
|
|
, current = head
|
|
, next = data.current :: data.next
|
|
}
|
|
|
|
|
|
{-| Synchronize a zipper to be at the same page as another, if possible.
|
|
-}
|
|
samePageAs : Zipper a -> Zipper a -> Zipper a
|
|
samePageAs goal z =
|
|
let
|
|
cp =
|
|
currentPage z
|
|
|
|
tp =
|
|
currentPage goal
|
|
in
|
|
if cp == tp then
|
|
z
|
|
|
|
else if cp < tp then
|
|
if isAtEnd z then
|
|
z
|
|
|
|
else
|
|
samePageAs goal (next z)
|
|
|
|
else if isAtStart z then
|
|
z
|
|
|
|
else
|
|
samePageAs goal (prev z)
|
|
|
|
|
|
{-| Navigate all the way to the end of the zipper.
|
|
-}
|
|
toEnd : Zipper a -> Zipper a
|
|
toEnd z =
|
|
if isAtEnd z then
|
|
z
|
|
|
|
else
|
|
toEnd (next z)
|
|
|
|
|
|
{-| Navigate all the way to the start of the zipper.
|
|
-}
|
|
toStart : Zipper a -> Zipper a
|
|
toStart z =
|
|
if isAtStart z then
|
|
z
|
|
|
|
else
|
|
toStart (prev z)
|