~alcinnz/amphiarao

ref: 097acc6e5d470b3f3651a65475808274932c7e48 amphiarao/src/UI/Templates.hs -rw-r--r-- 8.3 KiB
097acc6e — Adrian Cochrane Display CSS styles in web UI. 2 years ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
{-# LANGUAGE OverloadedStrings, ScopedTypeVariables #-}
module UI.Templates(page, inspector, inspector', sessionForm,
    xmlNode, xmlNode', elSelector, elPage, hlCSS, hlCSSs, identTok, symbolTok) where

import Happstack.Lite
import Text.Blaze.Html5 as H
import Text.Blaze.Html5.Attributes as A
import Text.Blaze.Html
import Data.Text as Txt

import Internal
import Internal.Load (isClickableEl)
import Internal.Forms (isTypableEl, readInput')
import Control.Monad.IO.Class (liftIO)
import Control.Monad (unless, when, forM)
import Control.Concurrent.MVar
import Data.UUID as ID

import Text.XML (Element(..), Node(..), Instruction(..), Name(..))
import Text.XML.Cursor as XC
import qualified Data.Map as M
import Data.Maybe (fromMaybe)

import Data.CSS.Syntax.Tokens

import Messages
import Happstack.Server.I18N

page :: (Response -> ServerPart Response) -> [Text] -> AttributeValue -> ([Text] -> Html) -> ServerPart Response
page return' title class_ body' = do
    langs <- bestLanguage <$> acceptLanguage
    return' $ toResponse $ html $ do
        H.head $ do
            link ! rel "stylesheet" ! href "/assets/pantheon.css"
            H.title $ text $ intercalate " — " title
        body ! A.class_ class_ $ body' langs

inspector :: (Response -> ServerPart Response) -> Text -> Session' -> ([Text] -> Html) -> ServerPart Response
inspector = inspector' ""
inspector' :: String -> (Response -> ServerPart Response) -> Text -> Session' -> ([Text] -> Html) -> ServerPart Response
inspector' q return' title session' body' = do
    let timeout = H.stringValue $ show $ pageLoad $ timeouts session'
    page return' [title, pack $ show $ currentURL $ session', "Amphiarao"] "fill" $ \langs -> do
        header $ do
            unless (Prelude.null $ backStack session') $ postButton "/nav/back" (l' langs Back') "🡸"
            unless (Prelude.null $ nextStack session') $ postButton "/nav/next" (l' langs Next') "🡺"
            postButton "/nav/reload" (l' langs Reload') "↻"
            hr
            H.form ! action' ["/", uuid', "/search"] ! alt (l' langs Search') $ do
                input ! type_ "search" ! name "q" ! value (stringValue q) ! placeholder (l' langs Search')
        body' langs
        footer $ do
            H.form ! action' ["/close/", uuid'] ! A.method "POST" ! alt (l' langs CloseSession') $ do
                button ! type_ "submit" $ l langs CloseSession
            hr
            H.form ! action' ["/", uuid', "/timeout"] ! A.method "POST" ! alt (l' langs LoadTimeout') $ p $ do
                H.label $ do
                    l langs LoadTimeout
                    input ! type_ "number" ! name "pageLoad" ! value timeout
                text "ms"

  where
    uuid' = ID.toString $ uuid_ session'
    action' = A.action . H.stringValue . Prelude.concat
    postButton target title' label = H.form ! action' ["/", uuid', target] ! alt title' ! A.method "POST" $ do
        button ! type_ "submit" ! A.title title' $ label

sessionForm langs = H.form ! A.method "POST" ! action "/" ! alt (l' langs CreateSession') $ do
    input ! type_ "url" ! name "target" ! placeholder "URL to debug"
    button ! type_ "submit" $ l langs CreateSession

--- XML formatting

xmlNode (NodeElement (Element name attrs childs)) = do
    symbolTok False "<"
    ident'Tok name
    forM (M.toList attrs) $ \(name, value) -> do
        text " "
        ident'Tok name
        symbolTok True "="
        stringTok $ show value -- quote it!
    when (Prelude.null childs) $ symbolTok True "/"
    symbolTok False ">"
  where
    ident'Tok (Name tag _ ns) = do
        case ns of
            Just ns' -> do
                qualifyTok ns'
                symbolTok False ":"
            Nothing -> return ()
        identTok tag
xmlNode (NodeInstruction (Instruction name value)) = do
    symbolTok False "<"
    symbolTok True "?"
    identTok name
    text " "
    stringTok $ unpack value
    symbolTok True "?"
    symbolTok False ">"
xmlNode (NodeContent text) = stringTok $ show text
xmlNode (NodeComment text) = do
    symbolTok False "<!--"
    commentTok text
    symbolTok False "-->"

xmlNode' (NodeElement el) = elSelector el
xmlNode' node = xmlNode node

elSelector (Element (Name name _ ns) attrs _) = do
    -- Yes, token classification isn't a great fit!
    case ns of
        Just ns' -> do
            keywordTok ns'
            symbolTok False "|"
        Nothing -> return ()
    keywordTok name
    qualifiers "id" "#" identTok
    qualifiers "class" "." qualifyTok
    return ()
  where
    qualifiers attr symb tok
        | Just val <- attr `M.lookup` attrs = forM (Txt.words val) $ \val' -> do
            symbolTok True symb
            tok val'
        | otherwise = return []

elPage uuid cursor links elValue langs = blockquote $ do
    nav $ do
        forM (Prelude.reverse $ XC.ancestor cursor) $ \el -> do
            link el $ xmlNode' $ XC.node el -- Should all be elements...
            symbolTok False " > "
        H.form ! class_ "disclosure" ! target "_top" ! alt (l' langs SearchChildren') !
                action (stringValue ('/':ID.toString uuid ++ "/search")) ! A.method "GET" $ do
            input ! type_ "hidden" ! name "el" !
                value (stringValue $ fromMaybe "" $ ID.toString <$> Prelude.lookup (XC.node cursor) links)
            input ! type_ "search" ! name "q" ! placeholder (l' langs SearchChildren')
        when (isClickableEl cursor) $ H.form ! class_ "disclosure" ! target "_top" ! alt (l' langs Click') !
                action (stringValue ('/':ID.toString uuid ++ "/click")) ! A.method "POST" $ do
            input ! type_ "hidden" ! name "el" !
                value (stringValue $ fromMaybe "" $ ID.toString <$> Prelude.lookup (XC.node cursor) links)
            button $ l langs Click
    link cursor $ p $ xmlNode $ XC.node cursor
    ol $ void $ forM (XC.child cursor) $ \el -> li $ link el $ xmlNode $ XC.node el
    when (isTypableEl cursor) $ H.form ! target "_top" ! alt (l' langs EnterValue') !
            action (stringValue ('/':ID.toString uuid ++ "/type")) ! A.method "POST" $ do
        input ! type_ "hidden" ! name "el" !
            value (stringValue $ fromMaybe "" $ ID.toString <$> Prelude.lookup (XC.node cursor) links)
        input ! type_ "text" ! name "text" ! placeholder (l' langs EnterValue') ! value (textValue elValue)
        button $ l langs SetValue
        button ! name "reset" $ l langs ResetValue
  where
    link el | Just uuid' <- Prelude.lookup (XC.node el) links =
            a ! target "_top" ! href (stringValue $ href' uuid')
        | otherwise = H.span -- Shouldn't happen...
    href' el = '/':ID.toString uuid ++ "/el/" ++ (ID.toString el)

void act = act >> return ()

symbolTok False = token "symbol silent"
symbolTok True = token "symbol"
qualifyTok = token "qualify"
keywordTok = token "keyword"
identTok = token "ident"
stringTok = token "string" . pack
stringTok' = token "string"
commentTok = token "comment"
errorTok = token "error"
numTok = token "num"

token type_ txt = H.span ! class_ (stringValue ("tok-" ++ type_)) $ text txt

--- CSS formatting
hlCSSs toks = do
    forM toks $ \tok -> do
        hlCSS tok
        text " " -- Because Haskell Stylist stripes Whitespace out from properties.
    return ()
hlCSS Whitespace = " " -- Filtered out by caller.
hlCSS CDO = commentTok "<!--"
hlCSS CDC = commentTok "-->"
hlCSS Comma = symbolTok True ","
hlCSS Colon = symbolTok True ":"
hlCSS Semicolon = symbolTok True ";"
hlCSS LeftParen = "("
hlCSS RightParen = ")"
hlCSS LeftSquareBracket = "["
hlCSS RightSquareBracket = "]"
hlCSS LeftCurlyBracket = "{"
hlCSS RightCurlyBracket = "}"
hlCSS SuffixMatch = symbolTok True "$="
hlCSS SubstringMatch = symbolTok True "*="
hlCSS PrefixMatch = symbolTok True "^="
hlCSS DashMatch = symbolTok True "|="
hlCSS IncludeMatch = symbolTok True "~="
hlCSS Column = symbolTok True "||"
hlCSS tok@(String _) = stringTok' $ serialize [tok]
hlCSS BadString = errorTok "\"\n"
hlCSS (Number x _) = numTok x
hlCSS (Percentage x _) = numTok (x `Txt.append` "%")
hlCSS (Dimension x _ u) = numTok (x `Txt.append` u)
hlCSS (Url x) = do
    identTok "url("
    stringTok' x
    text ")"
hlCSS BadUrl = errorTok "url(()"
hlCSS (Ident x) = keywordTok x
hlCSS (AtKeyword x) = keywordTok ('@' `Txt.cons` x)
hlCSS (Function x) = identTok (x `Txt.append` "(")
hlCSS (Hash HId x) = keywordTok ('#' `Txt.cons` x)
hlCSS (Hash HUnrestricted x) = stringTok' ('#' `Txt.cons` x)
hlCSS (Delim x) = symbolTok True $ Txt.pack [x]