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 ""
+
+ 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;
+}