Skip to content

Instantly share code, notes, and snippets.

@aratama
Last active September 23, 2016 03:16
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aratama/fa9fd5eca5573fc2e6dd to your computer and use it in GitHub Desktop.
Save aratama/fa9fd5eca5573fc2e6dd to your computer and use it in GitHub Desktop.
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