diff --git a/examples/helloWorld.roc b/examples/helloWorld.roc index fe6bddd76e..d826ba7e96 100644 --- a/examples/helloWorld.roc +++ b/examples/helloWorld.roc @@ -3,5 +3,105 @@ app "helloWorld" imports [pf.Stdout] provides [main] to pf +Section : [Desc (List Token) Str, Indent, Outdent, Newline] +Token : [Kw Str, Ident Str, Str Str, Num Str] + main = - Stdout.line "Hello, World!" + output = + # subject = "Awesome Programmer" + # + # helloWorld = + # Str.withCapacity 45 + # |> Str.concat greeting + # |> Str.concat ", " + # |> Str.concat subject + # |> Str.concat "!" + sectionsToStr [ + Desc [Ident "subject", Kw "=", Str "Awesome Programmer"] "

This assigns the name subject to the string \"Awesome programmer\".

In Roc, assignments are always constant, which means writing subject = again in the same scope would give an error.

Learn more about naming things

", + Newline, + Newline, + Desc [Ident "helloWorld", Kw "="] "

This assigns the name helloWorld to the value returned by this chain of function calls.

In Roc, assignments are always constant, which means writing helloWorld = again in the same scope would give an error.

Learn more about naming things

", + Indent, + Desc [Ident "Str.withCapacity", Num "45"] "

This calls the Str.withCapacity function passing 45 as its argument.

This creates a new string with capacity for 45 bytes without needing to allocate more space.

", + Newline, + Desc [Kw "|>", Ident "Str.concat", Ident "greeting"] "

This calls the Str.concat function passing greeting as its second argument, and the output of the pipeline up to this point as its first argument.

", + ] + + Stdout.line output + +tokensToStr : List Token -> Str +tokensToStr = \tokens -> + List.walk tokens "" \buf, token -> + bufWithSpace = + if Str.isEmpty buf then + buf + else + Str.concat buf " " + + when token is + Kw str -> + Str.concat bufWithSpace "\(str)" + + Num str -> + Str.concat bufWithSpace "\(str)" + + Str str -> + Str.concat bufWithSpace "\"\(str)\"" + + Ident str -> + html = + List.walk (Str.split str ".") "" \accum, ident -> + identHtml = "\(ident)" + + if Str.isEmpty accum then + identHtml + else + "\(accum).\(identHtml)" + + Str.concat bufWithSpace html + +sectionsToStr : List Section -> Str +sectionsToStr = \sections -> + answer = List.walk sections { buf: "", count: 0, indent: 0 } \{ buf, count, indent }, section -> + bufWithSpace = + if Str.isEmpty buf then + buf + else if buf |> Str.endsWith "\n" then + Str.concat buf (Str.repeat " " indent) + else + Str.concat buf " " + + (afterSpaces, nextCount) = + when section is + Newline | Indent | Outdent -> + # Indent and outdent changes happen on the next iteration, + # so we only need a newline for them here. + (Str.concat buf "\n", count) + + Desc tokens str -> + html = radio count (tokensToStr tokens) str + + (Str.concat bufWithSpace html, count + 1) + + nextIndent = + when section is + Indent -> indent + 4 + Outdent -> indent - 4 + Newline | Desc _ _ -> indent + + { + buf: afterSpaces, + count: nextCount, + indent: nextIndent + } + + answer.buf + +radio : U16, Str, Str -> Str +radio = \index, labelHtml, descHtml -> + # The first radio button should always be checked, and none of the others should be. + checkedHtml = if index == 0 then " checked" else "" + + """ +
\(descHtml)
+ """ diff --git a/www/wip_new_website/InteractiveExample.roc b/www/wip_new_website/InteractiveExample.roc new file mode 100644 index 0000000000..65306a0299 --- /dev/null +++ b/www/wip_new_website/InteractiveExample.roc @@ -0,0 +1,151 @@ +interface InteractiveExample + exposes [view] + imports [pf.Html.{ pre, samp }, pf.Html.Attributes.{ class }] + +Section : [Desc (List Token) Str, Indent, Outdent, Newline] +Token : [Kw Str, Ident Str, Str Str, Num Str, Comment Str, ParensAround (List Token), Lambda (List Token)] + +view : Html.Node +view = + output = + # # Select anything here to see an explanation. + # main = + # cacheUserInfo (Path.fromStr "url.txt") + # |> Task.onErr handleErr + # + # cacheUserInfo = \filename -> + # url <- File.readUtf8 filename |> Task.await + # { username, email } <- Http.get url Json.codec |> Task.await + # + # File.writeUtf8 (Path.fromStr "\(username).txt") email + # + # handleUrl = \err -> + # when err is + # HttpErr url _ -> Stderr.line "Error fetching URL \(url)" + # FileReadErr path _ -> Stderr.line "Error reading \(Path.display path)" + # FileWriteErr path _ -> Stderr.line "Error writing \(Path.display path)" + sectionsToStr [ + Desc [Comment "Click anything here to see an explanation.Tap anything here to\n# see an explanation."] "

Comments in Roc begin with a # and go to the end of the line.

", + Newline, + Desc [Ident "main", Kw "="] "

This begins the definition of main, which is the code our program will run when it starts up.

In Roc, assignments are always constant, which means writing main = again in the same scope would give an error.

Learn more about naming things

", + Indent, + Desc [Ident "cacheUserInfo", Str "\"url.txt\""] "

This calls the cacheUserInfo function, passing the string \"url.txt\" as an argument.

In Roc, function arguments are separated with spaces and/or newlines. Parentheses are only used in nested function calls.

", + Newline, + Desc [Kw "|>", Ident "Task.onErr", Ident "handleErr"] "

TODO

", + Outdent, + Newline, + # Desc [Ident "cacheUserInfo", Kw "=", Lambda [Ident "filename"]] "

TODO

", + # Indent, + # Desc [Ident "url", Kw "<-", Ident "File.readUtf8", Ident "filename"] "

TODO backpassing

", + # Desc [Kw "|>", Ident "Task.await"] "

TODO Task.await

", + # Newline, + # Desc [Literal "{", Ident "username", Literal ",", Ident "email", Literal "}", Kw "<-"] "

TODO record destructuring and backpassing

", + ] + + pre [] [ + samp [class "interactive-example"] [ + Html.text output, + ], + ] + +tokensToStr : List Token -> Str +tokensToStr = \tokens -> + List.walk tokens "" tokenToStr + +tokenToStr : Str, Token -> Str +tokenToStr = \buf, token -> + bufWithSpace = + if Str.isEmpty buf then + buf + else + Str.concat buf " " + + when token is + ParensAround wrapped -> + # Don't put spaces after opening parens or before closing parens + bufWithSpace + |> Str.concat "(" + |> Str.concat (tokensToStr wrapped) + |> Str.concat ")" + + Lambda args -> + # Don't put spaces after opening parens or before closing parens + argsWithCommas = + args + |> List.map \t -> tokenToStr "" t + |> Str.joinWith ", " + + bufWithSpace + |> Str.concat "\\" + |> Str.concat argsWithCommas + |> Str.concat " ->" + + Kw str -> + Str.concat bufWithSpace "\(str)" + + Num str -> + Str.concat bufWithSpace "\(str)" + + Str str -> + Str.concat bufWithSpace "\(str)" + + Comment str -> + Str.concat bufWithSpace "# \(str)" + + Ident str -> + html = + List.walk (Str.split str ".") "" \accum, ident -> + identHtml = "\(ident)" + + if Str.isEmpty accum then + identHtml + else + "\(accum).\(identHtml)" + + Str.concat bufWithSpace html + +sectionsToStr : List Section -> Str +sectionsToStr = \sections -> + answer = List.walk sections { buf: "", count: 0, indent: 0 } \{ buf, count, indent }, section -> + bufWithSpace = + if Str.isEmpty buf then + buf + else if buf |> Str.endsWith "\n" then + Str.concat buf (Str.repeat " " indent) + else + Str.concat buf " " + + (afterSpaces, nextCount) = + when section is + Newline | Indent | Outdent -> + # Indent and outdent changes happen on the next iteration, + # so we only need a newline for them here. + (Str.concat buf "\n", count) + + Desc tokens str -> + html = radio count (tokensToStr tokens) str + + (Str.concat bufWithSpace html, count + 1) + + nextIndent = + when section is + Indent -> indent + 4 + Outdent -> indent - 4 + Newline | Desc _ _ -> indent + + { + buf: afterSpaces, + count: nextCount, + indent: nextIndent, + } + + answer.buf + +radio : U16, Str, Str -> Str +radio = \index, labelHtml, descHtml -> + # The first radio button should always be checked, and none of the others should be. + checkedHtml = if index == 0 then " checked" else "" + + """ +
\(descHtml)
+ """ diff --git a/www/wip_new_website/main.roc b/www/wip_new_website/main.roc index d77bdd6af7..41726e4c5c 100644 --- a/www/wip_new_website/main.roc +++ b/www/wip_new_website/main.roc @@ -3,6 +3,7 @@ app "roc-website" imports [ pf.Html.{ html, head, body, footer, div, main, text, nav, a, link, meta }, pf.Html.Attributes.{ content, name, id, href, rel, lang, class, title, charset, color }, + InteractiveExample, ] provides [transformFileContent] to pf @@ -19,7 +20,7 @@ pageData = getPage : Str -> { title : Str, description : Str } getPage = \current -> Dict.get pageData current - |> Result.withDefault { title: "", description: ""} + |> Result.withDefault { title: "", description: "" } getTitle : Str -> Str getTitle = \current -> @@ -35,6 +36,12 @@ transformFileContent = \page, htmlContent -> view : Str, Str -> Html.Node view = \page, htmlContent -> + mainBody = + if page == "index.html" then + [text htmlContent, InteractiveExample.view] + else + [text htmlContent] + html [lang "en"] [ head [] [ meta [charset "utf-8"] [], @@ -50,15 +57,13 @@ view = \page, htmlContent -> ], body [] [ viewNavbar page, - main [] [ - text htmlContent, - ], + main [] mainBody, footer [] [ div [id "footer"] [ text " powered by ", - a [href "https://www.netlify.com"] [ text "Netlify"], - ] - ] + a [href "https://www.netlify.com"] [text "Netlify"], + ], + ], ], # TODO - add site.js if needed # script [src "/site.js"] [], @@ -81,17 +86,22 @@ viewNavbar = \page -> ], ] +rocLogo : Html.Node rocLogo = - (Html.element "svg") [ + (Html.element "svg") + [ (Html.attribute "viewBox") "0 -6 51 58", (Html.attribute "xmlns") "http://www.w3.org/2000/svg", (Html.attribute "aria-labelledby") "logo-link", (Html.attribute "role") "img", - class "roc-logo" - ] [ + class "roc-logo", + ] + [ (Html.element "title") [id "logo-link"] [text "Return to Roc Home"], - (Html.element "polygon") [ + (Html.element "polygon") + [ (Html.attribute "role") "presentation", (Html.attribute "points") "0,0 23.8834,3.21052 37.2438,19.0101 45.9665,16.6324 50.5,22 45,22 44.0315,26.3689 26.4673,39.3424 27.4527,45.2132 17.655,53 23.6751,22.7086", - ] [], + ] + [], ] diff --git a/www/wip_new_website/static/site.css b/www/wip_new_website/static/site.css index 75ae1885e9..8fe7debbb5 100644 --- a/www/wip_new_website/static/site.css +++ b/www/wip_new_website/static/site.css @@ -63,6 +63,17 @@ footer { margin: 0 auto; } +/* Used for e.g. displaying the instruction "Click" on desktop and "Touch" on mobile. + * When we think we're on mobile (based on max-width), we can switch the instruction. +*/ +.desktop { + display: inline; +} + +.mobile { + display: none; +} + section p:last-child { margin-bottom: 0; } @@ -303,6 +314,14 @@ li { /* Mobile-friendly screen width */ @media only screen and (max-width: 1024px) { + /* Used for e.g. displaying the instruction "Click" on desktop and "Touch" on mobile. */ + .desktop { + display: none; + } + + .mobile { + display: inline; + } p, aside, @@ -759,3 +778,42 @@ code .dim { line-height: 1.5; margin-bottom: 2em; } + + +/* Interactive example on homepage */ + +.interactive-example { + position: relative; + display: block; + width: 100%; + height: 800px; + padding-right: 300px; + cursor: default; +} + +.interactive-example label:hover, +.interactive-radio:checked+label { + background-color: turquoise; + cursor: pointer; +} + +.interactive-desc { + display: none; + background-color: #ddd; + padding: 8px 16px; + margin-top: 9px; +} + +.interactive-radio { + display: none; +} + +.interactive-radio:checked+label+.interactive-desc { + display: block; + position: absolute; + top: 0; + right: 300px; + width: 300px; + white-space: normal; + font-family: -apple-system, BlinkMacSystemFont, Roboto, Helvetica, Arial; +}