Skip to content

Instantly share code, notes, and snippets.

@aratama

aratama/followbox.purs

Last active Sep 23, 2016
Embed
What would you like to do?
module Main where
import Prelude (Unit(), unit, pure, ($), (<$>), (<#>), map, bind, show, void, const, (++), (==))
import Data.Maybe (Maybe(..), fromMaybe)
import Data.Either (either)
import Data.Array (updateAt, take, drop, findIndex, head, tail, replicate)
import Data.Traversable (for)
import Data.Functor ((<$))
import Control.Monad.Aff (Aff(), runAff)
import Control.Monad.Eff (Eff())
import Control.Monad.Eff.Exception (throwException)
import Halogen (HalogenEffects(), ComponentDSL(), Natural(), ComponentHTML(), Component(), action, runUI, component, set, get, liftEff', liftH, HTML())
import Halogen.Util (appendToBody, onLoad)
import Halogen.HTML.Core (className)
import Halogen.HTML.Indexed (text, div, span, button, img, a)
import Halogen.HTML.Events.Indexed (onClick, input_)
import Halogen.HTML.Properties.Indexed (src, href, class_)
import Network.HTTP.Affjax (AJAX(), AffjaxResponse())
import Network.HTTP.Affjax (get) as Affjax
import Data.Argonaut.Core (Json(), toArray, toObject)
import Data.Argonaut.Combinators ((.?))
import Control.Monad.Eff.Random (RANDOM(), randomInt)
import DOM.File.Types (Blob())
type State = { display :: Array (Maybe User), users :: Array User }
type User = { login :: String, avatar_url :: String, html_url :: String }
data Query a = Refresh a | Close String a
type Effects = HalogenEffects (ajax :: AJAX, random :: RANDOM)
numberOfUsers :: Int
numberOfUsers = 3
parse :: Json -> Maybe (Array User)
parse json = do
xs <- toArray json
for xs \json -> do
m <- toObject json
either (const Nothing) Just do
login <- m .? "login"
avatar_url <- m .? "avatar_url"
html_url <- m .? "html_url"
pure { login, avatar_url, html_url }
ui :: Component State Query (Aff Effects)
ui = component render eval
where
render :: State -> ComponentHTML Query
render s = div [class_ (className "outer")] [
div [class_ (className "title")] [
span [] [text "Who to Follow - "],
button [class_ (className "refresh"), onClick (input_ Refresh)] [text "Refresh"]
],
div [class_ (className "users")] $ map renderUser s.display
]
renderUser :: forall p. Maybe User -> HTML p Query
renderUser user = div [class_ (className "user")] case user of
Nothing -> []
Just user -> [
img [src user.avatar_url, class_ (className "avator")],
a [class_ (className "name"), href user.html_url] [text user.login],
button [class_ (className "close"), onClick $ input_ (Close user.login)] [text "X"]
]
eval :: Natural Query (ComponentDSL State Query (Aff Effects))
eval (Close login next) = next <$ do
state <- get
fromMaybe (pure unit) do
index <- findIndex (\u -> (u <#> _.login) == Just login) state.display
users <- tail state.users
user <- head state.users
blank <- updateAt index Nothing state.display
display <- updateAt index (Just user) state.display
pure do
set { display: blank, users }
_ :: AffjaxResponse Blob <- liftH $ Affjax.get user.avatar_url
set { display, users }
eval (Refresh next) = next <$ do
set { display: replicate numberOfUsers Nothing, users: [] } -- まず一覧をクリアする
randomOffset <- liftEff' $ randomInt 0 500 -- それが終わったら0-500の間の乱数を1個生成する
res <- liftH $ Affjax.get ("https://api.github.com/users?since=" ++ show randomOffset) -- 次にgetでAPIを叩く
let parsed = fromMaybe [] (parse res.response) -- レスポンスが返ってきら、そのJSONをパース
let display = Just <$> take numberOfUsers parsed -- 上から3人を取り出す
let users = drop numberOfUsers parsed -- 残りの人を取り出す
set { display, users } -- 状態を設定する
main :: Eff Effects Unit
main = runAff throwException pure $ void do
app <- runUI ui { display: [], users : [] } -- アプリケーションを作成する
onLoad $ appendToBody app.node -- 作成が終わったら、次に要素をページに追加する
app.driver $ action Refresh -- それが終わったら、`Refresh`アクションを投げてユーザ一覧を更新する
<script src="index.js"></script>
<style>
.outer {
margin: 10px;
border: solid 1px black;
padding: 10px;
border-radius: 4px;
width: 250px;
}
.title {
font-size: large;
color: grey;
font-weight: bold;
margin: 8px;
}
.refreash {
font-size: small;
border: none;
background-color: transparent;
color: rgba(0, 0, 0, 0.6);
}
.users {
display: flex;
flex-direction: column;
}
.user {
height: 50px;
display: flex;
}
img.avator {
width: 40px;
height: 40px;
border-radius: 20px;
flex-grow: 0;
border: none;
}
.name {
flex-grow: 1.0;
padding: 10px;
}
.close {
font-size: small;
border: none;
background-color: transparent;
color: rgba(0, 0, 0, 0.6);
flex-grow: 0;
}
</style>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.