mirror of
https://github.com/roc-lang/roc.git
synced 2024-09-19 23:07:33 +03:00
Update scripts to WIP site for REPL
workaround for copy static files add REPL to WIP home page WIP site download REPL nightly scripts build WIP Netlify and dev
This commit is contained in:
parent
77d2136d00
commit
eb3e97fd95
10
www/build.sh
10
www/build.sh
@ -17,6 +17,7 @@ cd $SCRIPT_RELATIVE_DIR
|
||||
|
||||
rm -rf build/
|
||||
cp -r public/ build/
|
||||
mkdir build/wip # for WIP site
|
||||
|
||||
# download fonts just-in-time so we don't have to bloat the repo with them.
|
||||
DESIGN_ASSETS_COMMIT="4d949642ebc56ca455cf270b288382788bce5873"
|
||||
@ -36,13 +37,14 @@ curl -fLJO https://github.com/roc-lang/roc/archive/www.tar.gz
|
||||
REPL_TARFILE="roc_repl_wasm.tar.gz"
|
||||
curl -fLJO https://github.com/roc-lang/roc/releases/download/nightly/$REPL_TARFILE
|
||||
tar -xzf $REPL_TARFILE -C repl
|
||||
tar -xzf $REPL_TARFILE -C wip # note we also need this for WIP repl
|
||||
rm $REPL_TARFILE
|
||||
ls -lh repl
|
||||
ls -lh wip
|
||||
|
||||
popd
|
||||
|
||||
pushd ..
|
||||
echo 'Generating builtin docs...'
|
||||
cargo --version
|
||||
|
||||
# We set ROC_DOCS_ROOT_DIR=builtins so that links will be generated relative to
|
||||
@ -92,10 +94,8 @@ $roc run www/generate_tutorial/src/tutorial.roc -- www/generate_tutorial/src/inp
|
||||
mv www/build/tutorial/tutorial.html www/build/tutorial/index.html
|
||||
|
||||
# for new wip site
|
||||
mkdir www/build/wip
|
||||
$roc run www/wip_new_website/main.roc -- www/wip_new_website/content/ www/build/wip
|
||||
cp -r www/wip_new_website/static/site.css www/build/wip
|
||||
cp -r www/build/fonts www/build/wip/fonts
|
||||
$roc run www/wip_new_website/main.roc -- www/wip_new_website/content/ www/build/wip/
|
||||
cp -r www/wip_new_website/static/* www/build/wip/
|
||||
|
||||
# cleanup
|
||||
rm -rf roc_nightly roc_releases.json
|
||||
|
15
www/wip_new_website/build-dev-local.sh
Normal file
15
www/wip_new_website/build-dev-local.sh
Normal file
@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Use this script to for testing the WIP site locally without downloading assets every time.
|
||||
|
||||
# NOTE run `bash www/build.sh` to cache local copy of fonts, and repl assets etc
|
||||
|
||||
rm -rf dist/
|
||||
mkdir dist
|
||||
mkdir dist/wip
|
||||
cp -r ../build/wip/* dist/wip/
|
||||
roc run main.roc -- content/ dist/wip/
|
||||
cp -r static/* dist/wip/
|
||||
cp -r ../build/fonts/ dist/fonts/
|
||||
|
||||
simple-http-server -p 8080 --nocache --index -- dist/
|
@ -1,54 +0,0 @@
|
||||
#!/usr/bin/env roc
|
||||
app "website-builder"
|
||||
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.5.0/Cufzl36_SnJ4QbOoEmiJ5dIpUxBvdB3NEySvuH82Wio.tar.br" }
|
||||
imports [
|
||||
pf.Task.{ Task },
|
||||
pf.Cmd,
|
||||
]
|
||||
provides [main] to pf
|
||||
|
||||
main =
|
||||
# TODO take dist folder name and main.roc path as args once https://github.com/roc-lang/basic-cli/issues/82 is fixed
|
||||
# TODO add function to remove boilerplate
|
||||
# Remove dist folder
|
||||
{} <-
|
||||
Cmd.new "rm"
|
||||
|> Cmd.args ["-rf", "dist/"]
|
||||
|> Cmd.status
|
||||
|> Task.onErr \_ -> crash "Failed to remove dist folder"
|
||||
|> Task.await
|
||||
|
||||
# Build site
|
||||
{} <-
|
||||
Cmd.new "roc"
|
||||
|> Cmd.args ["run", "main.roc", "--", "content/", "dist/wip/"]
|
||||
|> Cmd.status
|
||||
|> Task.onErr \_ -> crash "Failed to build site"
|
||||
|> Task.await
|
||||
|
||||
# Copy static files
|
||||
{} <-
|
||||
Cmd.new "cp"
|
||||
|> Cmd.args ["-r", "static/site.css", "dist/wip/"]
|
||||
|> Cmd.status
|
||||
|> Task.onErr \_ -> crash "Failed to copy static files"
|
||||
|> Task.await
|
||||
|
||||
# Copy font files - assume that www/build.sh has been run previously and the
|
||||
# fonts are available locally in ../build/fonts
|
||||
{} <-
|
||||
Cmd.new "cp"
|
||||
|> Cmd.args ["-r", "../build/fonts/", "dist/fonts/"]
|
||||
|> Cmd.status
|
||||
|> Task.onErr \_ -> crash "Failed to copy static files"
|
||||
|> Task.await
|
||||
|
||||
# Start file server
|
||||
{} <-
|
||||
Cmd.new "simple-http-server"
|
||||
|> Cmd.args ["-p", "8080", "--nocache", "--index", "--", "dist/"]
|
||||
|> Cmd.status
|
||||
|> Task.onErr \_ -> crash "Failed to run file server; consider intalling with `cargo install simple-http-server`"
|
||||
|> Task.await
|
||||
|
||||
Task.ok {}
|
@ -18,7 +18,6 @@ A work-in-progress programming language that aims to be fast, friendly, and func
|
||||
</div>
|
||||
<div class="home-goals-column">
|
||||
<h3 class="home-goals-title">Friendly</h3>
|
||||
<h2 class="home-goals-title">Friendly</h2>
|
||||
<p class="home-goals-description">Roc aims to be a user-friendly language with a friendly community of users. This involves the set of tools Roc includes, and also the spirit of the community of Roc programmers. <a class="home-goals-learn-more" href="/design_goals.html#friendly">What does <i>friendly</i> mean here?</a></p>
|
||||
</div>
|
||||
<div class="home-goals-column">
|
||||
@ -28,19 +27,21 @@ A work-in-progress programming language that aims to be fast, friendly, and func
|
||||
|
||||
## Try Roc
|
||||
|
||||
<!-- TODO WebREPL to go here -->
|
||||
<link rel="stylesheet" href="/wip/repl.css" />
|
||||
<div id="repl">
|
||||
<code class="history">
|
||||
<div id="help-text"></div>
|
||||
<div id="history-text"><div id="loading-message">Loading REPL WebAssembly module…please wait!</div></div>
|
||||
</code>
|
||||
<section id="source-input-wrapper">
|
||||
<textarea rows="5" autofocus id="source-input" placeholder="You can enter Roc code here once the REPL loads!"
|
||||
disabled></textarea>
|
||||
</section>
|
||||
</div>
|
||||
<script type="module" src="/wip/repl.js"></script>
|
||||
</div>
|
||||
|
||||
The code below shows a Roc application which prints `Hello World!` to the terminal. It does this using the [roc-lang/basic-cli](https://github.com/roc-lang/basic-cli) platform.
|
||||
|
||||
```roc
|
||||
app "hello-world"
|
||||
packages { pf: "https://github.com/roc-lang/basic-cli/releases/download/0.5.0/Cufzl36_SnJ4QbOoEmiJ5dIpUxBvdB3NEySvuH82Wio.tar.br" }
|
||||
imports [pf.Stdout]
|
||||
provides [main] to pf
|
||||
|
||||
main =
|
||||
Stdout.line "Hello, World!"
|
||||
```
|
||||
## Examples
|
||||
|
||||
We have developed a number of smaller code [examples](https://github.com/roc-lang/examples) which demonstrate how to use Roc. These cover a range of topics from basic syntax to more advanced features such as random number generation and using the popular `Task` feature.
|
||||
|
||||
|
173
www/wip_new_website/static/repl.css
Normal file
173
www/wip_new_website/static/repl.css
Normal file
@ -0,0 +1,173 @@
|
||||
#repl {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* h1 {
|
||||
font-family: "Merriweather";
|
||||
font-size: 48px;
|
||||
line-height: 48px;
|
||||
padding: 48px 0;
|
||||
margin: 0;
|
||||
color: var(--header-link-color);
|
||||
} */
|
||||
|
||||
#source-input-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#source-input-wrapper::before {
|
||||
content: "» ";
|
||||
position: absolute;
|
||||
left: 15px;
|
||||
top: 18px;
|
||||
line-height: 16px;
|
||||
height: 16px;
|
||||
z-index: 2;
|
||||
font-family: var(--font-mono);
|
||||
color: var(--cyan);
|
||||
/* Let clicks pass through to the textarea */
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#source-input {
|
||||
width: 100%;
|
||||
font-family: var(--font-mono);
|
||||
color: var(--code-color);
|
||||
background-color: var(--code-bg);
|
||||
display: inline-block;
|
||||
height: 76px;
|
||||
padding: 16px;
|
||||
padding-left: 36px;
|
||||
border: 1px solid transparent;
|
||||
margin: 0;
|
||||
margin-bottom: 2em;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#source-input:focus {
|
||||
border: 1px solid var(--cyan);
|
||||
box-sizing: border-box;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* li {
|
||||
margin: 8px;
|
||||
} */
|
||||
|
||||
.history {
|
||||
padding: 1em;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
#help-text,
|
||||
#history-text {
|
||||
white-space: pre-wrap;
|
||||
|
||||
}
|
||||
|
||||
#history-text {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
#loading-message {
|
||||
text-align: center;
|
||||
/* approximately match height after loading and printing help message */
|
||||
height: 96px;
|
||||
}
|
||||
|
||||
.history-item {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.history-item .input {
|
||||
margin: 0;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.history-item .output {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.panic {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.input-line-prefix {
|
||||
color: var(--cyan);
|
||||
}
|
||||
|
||||
.color-red {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.color-green {
|
||||
color: var(--green);
|
||||
}
|
||||
|
||||
.color-yellow {
|
||||
color: var(--orange);
|
||||
}
|
||||
|
||||
.color-blue {
|
||||
color: var(--cyan);
|
||||
}
|
||||
|
||||
.color-magenta {
|
||||
color: var(--magenta);
|
||||
}
|
||||
|
||||
.color-cyan {
|
||||
color: var(--cyan);
|
||||
}
|
||||
|
||||
.color-white {
|
||||
/* Really this isn't white so much as "default text color." For the repl, this should be black
|
||||
in a light color scheme, and only white in dark mode. The name could be better! */
|
||||
color: black;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.color-white {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.underline {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
|
||||
/* Mobile-friendly screen width */
|
||||
@media only screen and (max-width: 767px) {
|
||||
h1 {
|
||||
font-size: 24px !important;
|
||||
margin: 0;
|
||||
padding: 16px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#repl {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
min-height: calc(100vh - var(--top-bar-height));
|
||||
}
|
||||
|
||||
code.history {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
#source-input {
|
||||
margin: 0
|
||||
}
|
||||
|
||||
#loading-message {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
238
www/wip_new_website/static/repl.js
Normal file
238
www/wip_new_website/static/repl.js
Normal file
@ -0,0 +1,238 @@
|
||||
// The only way we can provide values to wasm_bindgen's generated code is to set globals
|
||||
window.js_create_app = js_create_app;
|
||||
window.js_run_app = js_run_app;
|
||||
window.js_get_result_and_memory = js_get_result_and_memory;
|
||||
|
||||
// The only place we use console.error is in wasm_bindgen, where it gets a single string argument.
|
||||
console.error = function displayErrorInHistoryPanel(string) {
|
||||
const html = `<div class="panic">${string}</div>`;
|
||||
updateHistoryEntry(repl.inputHistoryIndex, false, html);
|
||||
};
|
||||
|
||||
import * as roc_repl_wasm from "/wip/roc_repl_wasm.js";
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// REPL state
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
const repl = {
|
||||
elemHistory: document.getElementById("history-text"),
|
||||
elemSourceInput: document.getElementById("source-input"),
|
||||
|
||||
inputQueue: [],
|
||||
inputHistory: [],
|
||||
inputHistoryIndex: 0,
|
||||
inputStash: "", // stash the user input while we're toggling through history with up/down arrows
|
||||
|
||||
textDecoder: new TextDecoder(),
|
||||
textEncoder: new TextEncoder(),
|
||||
|
||||
compiler: null,
|
||||
app: null,
|
||||
|
||||
// Temporary storage for the address of the result of running the user's code.
|
||||
// Used while control flow returns to Rust to allocate space to copy the app's memory buffer.
|
||||
result_addr: 0,
|
||||
};
|
||||
|
||||
// Initialise
|
||||
repl.elemSourceInput.addEventListener("input", onInput);
|
||||
repl.elemSourceInput.addEventListener("keydown", onInputKeydown);
|
||||
repl.elemSourceInput.addEventListener("keyup", onInputKeyup);
|
||||
roc_repl_wasm.default("/wip/roc_repl_wasm_bg.wasm").then(async (instance) => {
|
||||
repl.elemHistory.querySelector("#loading-message").remove();
|
||||
repl.elemSourceInput.disabled = false;
|
||||
repl.elemSourceInput.placeholder = "Type some Roc code and press Enter.";
|
||||
repl.elemSourceInput.focus();
|
||||
repl.compiler = instance;
|
||||
|
||||
// Get help text from the compiler, and display it at top of the history panel
|
||||
try {
|
||||
const helpText = await roc_repl_wasm.entrypoint_from_js(":help");
|
||||
const helpElem = document.getElementById("help-text");
|
||||
helpElem.innerHTML = helpText.trim();
|
||||
} catch (e) {
|
||||
// Print error for Roc devs. Don't use console.error, we overrode that above to display on the page!
|
||||
console.warn(e);
|
||||
}
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Handle inputs
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
function onInput(event) {
|
||||
// Have the textarea grow with the input
|
||||
event.target.style.height = event.target.scrollHeight + 2 + "px"; // +2 for the border
|
||||
}
|
||||
|
||||
function onInputKeydown(event) {
|
||||
const ENTER = 13;
|
||||
|
||||
const { keyCode } = event;
|
||||
|
||||
if (keyCode === ENTER) {
|
||||
if (!event.shiftKey && !event.ctrlKey && !event.altKey) {
|
||||
// Don't advance the caret to the next line
|
||||
event.preventDefault();
|
||||
|
||||
const inputText = repl.elemSourceInput.value.trim();
|
||||
|
||||
repl.elemSourceInput.value = "";
|
||||
repl.elemSourceInput.style.height = "";
|
||||
|
||||
repl.inputQueue.push(inputText);
|
||||
if (repl.inputQueue.length === 1) {
|
||||
processInputQueue();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onInputKeyup(event) {
|
||||
const UP = 38;
|
||||
const DOWN = 40;
|
||||
|
||||
const { keyCode } = event;
|
||||
|
||||
const el = repl.elemSourceInput;
|
||||
|
||||
switch (keyCode) {
|
||||
case UP:
|
||||
if (repl.inputHistory.length === 0) {
|
||||
return;
|
||||
}
|
||||
if (repl.inputHistoryIndex == repl.inputHistory.length - 1) {
|
||||
repl.inputStash = el.value;
|
||||
}
|
||||
setInput(repl.inputHistory[repl.inputHistoryIndex]);
|
||||
|
||||
if (repl.inputHistoryIndex > 0) {
|
||||
repl.inputHistoryIndex--;
|
||||
}
|
||||
break;
|
||||
|
||||
case DOWN:
|
||||
if (repl.inputHistory.length === 0) {
|
||||
return;
|
||||
}
|
||||
if (repl.inputHistoryIndex === repl.inputHistory.length - 1) {
|
||||
setInput(repl.inputStash);
|
||||
} else {
|
||||
repl.inputHistoryIndex++;
|
||||
setInput(repl.inputHistory[repl.inputHistoryIndex]);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function setInput(value) {
|
||||
const el = repl.elemSourceInput;
|
||||
el.value = value;
|
||||
el.selectionStart = value.length;
|
||||
el.selectionEnd = value.length;
|
||||
}
|
||||
|
||||
// Use a queue just in case we somehow get inputs very fast
|
||||
// We want the REPL to only process one at a time, since we're using some global state.
|
||||
// In normal usage we shouldn't see this edge case anyway. Maybe with copy/paste?
|
||||
async function processInputQueue() {
|
||||
while (repl.inputQueue.length) {
|
||||
const inputText = repl.inputQueue[0];
|
||||
repl.inputHistoryIndex = createHistoryEntry(inputText);
|
||||
repl.inputStash = "";
|
||||
|
||||
let outputText = "";
|
||||
let ok = true;
|
||||
if (inputText) {
|
||||
try {
|
||||
outputText = await roc_repl_wasm.entrypoint_from_js(inputText);
|
||||
} catch (e) {
|
||||
outputText = `${e}`;
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
|
||||
updateHistoryEntry(repl.inputHistoryIndex, ok, outputText);
|
||||
repl.inputQueue.shift();
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Callbacks to JS from Rust
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// Load Wasm code into the browser's virtual machine, so we can run it later.
|
||||
// This operation is async, so we call it before entering any code shared
|
||||
// with the command-line REPL, which is sync.
|
||||
async function js_create_app(wasm_module_bytes) {
|
||||
const { instance } = await WebAssembly.instantiate(wasm_module_bytes);
|
||||
// Keep the instance alive so we can run it later from shared REPL code
|
||||
repl.app = instance;
|
||||
}
|
||||
|
||||
// Call the `main` function of the user app, via the `wrapper` function.
|
||||
function js_run_app() {
|
||||
const { wrapper, memory } = repl.app.exports;
|
||||
|
||||
// Run the user code, and remember the result address
|
||||
// We'll pass it to Rust in the next callback
|
||||
repl.result_addr = wrapper();
|
||||
|
||||
// Tell Rust how much space to reserve for its copy of the app's memory buffer.
|
||||
// We couldn't know that size until we actually ran the app.
|
||||
return memory.buffer.byteLength;
|
||||
}
|
||||
|
||||
// After Rust has allocated space for the app's memory buffer,
|
||||
// we copy it, and return the result address too
|
||||
function js_get_result_and_memory(buffer_alloc_addr) {
|
||||
const appMemory = new Uint8Array(repl.app.exports.memory.buffer);
|
||||
const compilerMemory = new Uint8Array(repl.compiler.memory.buffer);
|
||||
compilerMemory.set(appMemory, buffer_alloc_addr);
|
||||
return repl.result_addr;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Rendering
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
function createHistoryEntry(inputText) {
|
||||
const historyIndex = repl.inputHistory.length;
|
||||
repl.inputHistory.push(inputText);
|
||||
|
||||
const firstLinePrefix = '<span class="input-line-prefix">» </span>';
|
||||
const otherLinePrefix = '<br><span class="input-line-prefix">… </span>';
|
||||
const inputLines = inputText.split("\n");
|
||||
if (inputLines[inputLines.length - 1] === "") {
|
||||
inputLines.pop();
|
||||
}
|
||||
const inputWithPrefixes = firstLinePrefix + inputLines.join(otherLinePrefix);
|
||||
|
||||
const inputElem = document.createElement("div");
|
||||
inputElem.innerHTML = inputWithPrefixes;
|
||||
inputElem.classList.add("input");
|
||||
|
||||
const historyItem = document.createElement("div");
|
||||
historyItem.appendChild(inputElem);
|
||||
historyItem.classList.add("history-item");
|
||||
|
||||
repl.elemHistory.appendChild(historyItem);
|
||||
|
||||
return historyIndex;
|
||||
}
|
||||
|
||||
function updateHistoryEntry(index, ok, outputText) {
|
||||
const outputElem = document.createElement("div");
|
||||
outputElem.innerHTML = outputText;
|
||||
outputElem.classList.add("output", ok ? "output-ok" : "output-error");
|
||||
|
||||
const historyItem = repl.elemHistory.children[index];
|
||||
historyItem.appendChild(outputElem);
|
||||
|
||||
// Scroll the page to the bottom so you can see the most recent output.
|
||||
// window.scrollTo(0, document.body.scrollHeight);
|
||||
}
|
Loading…
Reference in New Issue
Block a user