From a96d162f649704df5d9b567158366015cbe3172c Mon Sep 17 00:00:00 2001 From: Jan Tuomi Date: Sat, 5 Feb 2022 16:48:16 +0200 Subject: Get basic stuff working --- src/App.module.css | 12 ++++++++- src/App.tsx | 38 +++++++++++++++------------- src/Editor.module.css | 7 ++++++ src/Editor.tsx | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/Tutorial.tsx | 37 ++++++++++++++++++++++++++++ src/api.ts | 43 ++++++++++++++++++++++++++------ src/index.css | 20 +++++++++++++-- 7 files changed, 198 insertions(+), 27 deletions(-) create mode 100644 src/Editor.module.css create mode 100644 src/Editor.tsx create mode 100644 src/Tutorial.tsx (limited to 'src') diff --git a/src/App.module.css b/src/App.module.css index 0bf5f42..3b75e8c 100644 --- a/src/App.module.css +++ b/src/App.module.css @@ -32,4 +32,14 @@ .error { color: var(--red) -} \ No newline at end of file +} + +.examples { + margin-bottom: 20px; +} + +.editor { + margin-bottom: 40px; +} + +.tutorial {} diff --git a/src/App.tsx b/src/App.tsx index 5645dcc..905149e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,24 +1,26 @@ -import { useSnippets } from "./api" +import { useExamples } from "./api" import styles from "./App.module.css" +import Editor from "./Editor" +import Tutorial from "./Tutorial" import icon from "./favicon.png" const App = () => { - const snippets = useSnippets() + const examples = useExamples() - const renderSnippets = () => { - if (snippets.type === "failed") { - return
- Unexpected error loading snippets. See console for details. -
- } - - if (snippets.type === "loading") { - return
Loading snippets...
+ const renderExamples = () => { + switch (examples.type) { + case "not_yet_requested": + case "loading": + return
Loading examples...
+ case "failed": + return
+ An unexpected error occurred while loading examples. See console for details. +
+ case "success": + return examples.data.map((example) => +
{example.title}
, + ) } - - return snippets.data.map((snippet) => -
{snippet.title}
, - ) } return ( @@ -32,9 +34,11 @@ const App = () => { A Mere Stack Language -
- {renderSnippets()} +
+ {renderExamples()}
+
+
) } diff --git a/src/Editor.module.css b/src/Editor.module.css new file mode 100644 index 0000000..2116903 --- /dev/null +++ b/src/Editor.module.css @@ -0,0 +1,7 @@ +.runButton { + margin-top: 10px; +} + +.error { + color: var(--red) +} diff --git a/src/Editor.tsx b/src/Editor.tsx new file mode 100644 index 0000000..d179b83 --- /dev/null +++ b/src/Editor.tsx @@ -0,0 +1,68 @@ +import AceEditor from "react-ace" +import "ace-builds/src-noconflict/mode-java" +import "ace-builds/src-noconflict/theme-github" +import { useState } from "react" +import styles from "./Editor.module.css" +import { APIResult, submitSource } from "./api" + +const Editor = () => { + const [sourceText, setSourceText] = useState("") + const [result, setResult] = useState>({ type: "not_yet_requested" }) + const onChange = setSourceText + + const executeCode = async (text: string) => { + const result = await submitSource(text) + setResult(result) + } + + const renderResults = () => { + switch (result.type) { + case "not_yet_requested": + case "loading": + return null + case "failed": + return ( +
+ Failed to run code on the server. See console for details. +
+ ) + case "success": + return ( + undefined} + value={result.data} + name="results" + editorProps={{ $blockScrolling: true }} + width="100%" + fontSize={16} + readOnly + /> + ) + } + } + + return ( + <> + + +
+ {renderResults()} +
+ + ) +} + +export default Editor diff --git a/src/Tutorial.tsx b/src/Tutorial.tsx new file mode 100644 index 0000000..917142b --- /dev/null +++ b/src/Tutorial.tsx @@ -0,0 +1,37 @@ +import { useState } from "react" + +const Tutorial = () => { + const [collapsed, setCollapsed] = useState(true) + + if (collapsed) { + return + } + + return ( + <> + + +

+ SLAM is a procedural, stack-based programming language + inspired by Forth and Lisp. +

+ +

+ In the core of every SLAM program, there is an implied, global stack of + strongly and dynamically typed values. A SLAM program consists of a sequence + of words that when evaluated, can consume and produce values from/to + the stack. +

+ +

+ Phrases, roughly equal to Lisp's quoted S-expressions, are a sequence + of words that are not immediately evaluated when encountered, but instead put on the stack for later + manipulation. Phrases can be used to implement strings or homogenous list structures. Phrases + are also the core of conditional expressions, where branches of code are only evaluated + when a condition is met. +

+ + ) +} + +export default Tutorial \ No newline at end of file diff --git a/src/api.ts b/src/api.ts index 7fd9e64..725f0d8 100644 --- a/src/api.ts +++ b/src/api.ts @@ -10,27 +10,31 @@ const fetcher = (path: string) => fetch(`${API_URL}${path}`) throw err }) -interface Snippet { +interface Example { id: string title: string content: string } -interface APISuccess { +export interface APISuccess { readonly type: "success" data: D } -interface APILoading { +export interface APILoading { readonly type: "loading" } -interface APIFailed { +export interface APIFailed { readonly type: "failed" error: E } -type APIResult = APISuccess | APILoading | APIFailed +export interface APINotYetRequested { + readonly type: "not_yet_requested" +} + +export type APIResult = APISuccess | APILoading | APIFailed | APINotYetRequested const mapSWRResult = ({ data, error }: SWRResponse): APIResult => { if (error !== undefined) { @@ -45,6 +49,31 @@ const mapSWRResult = ({ data, error }: SWRResponse): APIResult return { type: "success", data } } -export const useSnippets = () => mapSWRResult( - useSWR("/snippets", fetcher), +const postRequest = async (path: string, body: any, opts?: any): Promise> => { + const url = `${API_URL}${path}` + const body_ = typeof body === "string" ? body : JSON.stringify(body) + try { + const resp = await fetch(url, { method: "POST", body: body_, headers: { + "Content-Type": "application/json", + }, ...opts }) + + if (resp.status >= 400) { + throw new Error(`${url} responded with ${resp.status}: ${resp.text}`) + } + const data = await resp.json() + return { type: "success", data } + } catch (err: any) { + console.error(`An error occurred when POSTing to API path ${path}`) + console.error(err) + return { type: "failed", error: err } + } +} + +export const useExamples = () => mapSWRResult( + useSWR("/examples", fetcher), +) + +export const submitSource = async (text: string) => postRequest( + "/submit", + text, ) diff --git a/src/index.css b/src/index.css index 910f8af..6f181ab 100644 --- a/src/index.css +++ b/src/index.css @@ -23,8 +23,24 @@ body { padding: 0 10px; max-width: 900px; text-align: left; + font-size: var(--font-s); } -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; +button { + border-radius: 0; + border: none; + outline: none; + padding: 5px; + margin: 0 -5px; + background: var(--dark-grey); + color: var(--white); + font-size: var(--font-s); + cursor: pointer; + display: flex; + flex-flow: row nowrap; + align-items: center; +} + +button:hover { + text-decoration: underline; } \ No newline at end of file -- cgit v1.3