aboutsummaryrefslogtreecommitdiff
path: root/src_frontend/Components/Editor
diff options
context:
space:
mode:
Diffstat (limited to 'src_frontend/Components/Editor')
-rw-r--r--src_frontend/Components/Editor/Controls.svelte83
-rw-r--r--src_frontend/Components/Editor/Editor.svelte296
-rw-r--r--src_frontend/Components/Editor/Output.svelte56
-rw-r--r--src_frontend/Components/Editor/Pane.svelte41
-rw-r--r--src_frontend/Components/Editor/TopBar.svelte62
5 files changed, 538 insertions, 0 deletions
diff --git a/src_frontend/Components/Editor/Controls.svelte b/src_frontend/Components/Editor/Controls.svelte
new file mode 100644
index 0000000..302aa7a
--- /dev/null
+++ b/src_frontend/Components/Editor/Controls.svelte
@@ -0,0 +1,83 @@
+<script>
+ import { onMount } from "svelte";
+ import PrettyVar from "../../ComponentLib/PrettyVar.svelte";
+ import { openSocket } from "../../stores/socketStore";
+
+ let brightnessValue = 0;
+ let power_on = false;
+ let variables = {};
+
+ function setBrightness() {
+ if (!power_on) { openSocket.emit("power:set", true); }
+ openSocket.emit("brightness:set", brightnessValue);
+ }
+ function setPower() { openSocket.emit("power:set", power_on); }
+ function setVar(ev) {
+ openSocket.emit("var:set", ev.target.id, ev.target.value);
+ }
+
+ openSocket.on("power", (power) => power_on = power);
+ openSocket.on("brightness", (value) => brightnessValue = value);
+ openSocket.on("vars", (vars) => variables = vars);
+ openSocket.on("var", (name, value) => {
+ name = name.replace("variable/", "");
+ if (value.value == null) {
+ delete variables[name];
+ } else {
+ variables[name] = value;
+ }
+ variables = variables;
+ });
+
+ onMount(() => {
+ openSocket.emit("power:get");
+ openSocket.emit("brightness:get");
+ openSocket.emit("vars:get")
+ });
+</script>
+
+<style>
+ label {
+ width: 100%;
+ font-size: 12px;
+ color: var(--grey-500);
+ }
+ .var-group {
+ display: flex;
+ }
+ input[type=range] {
+ width: 100%;
+ }
+ input[type=text] {
+ margin-top: 5px;
+ display: block;
+ width: 100%;
+ background-color: #737373;
+ padding: 5px;
+ color: white;
+ border: none;
+ box-sizing: border-box;
+ border-radius: 5px;
+ }
+</style>
+
+<div>
+ <div class="var-group">
+ <label for="power">Power</label>
+ <input type="checkbox" id="power" bind:checked={power_on} on:change={setPower} />
+ </div>
+ <div>
+ <label for="brightness">Brightness</label>
+ <div class="var-group">
+ <input id="brightness" type="range" min=0 max=255 bind:value={brightnessValue} on:change={setBrightness} />
+ {brightnessValue}
+ </div>
+ </div>
+
+ {#each Object.entries(variables) as [name, value]}
+ <div>
+ <label for="{name}"><PrettyVar varText={name} /></label>
+ <input type="text" id="{name}" bind:value={value.value} on:blur={setVar} />
+ </div>
+ {/each}
+</div> \ No newline at end of file
diff --git a/src_frontend/Components/Editor/Editor.svelte b/src_frontend/Components/Editor/Editor.svelte
new file mode 100644
index 0000000..b63ee9b
--- /dev/null
+++ b/src_frontend/Components/Editor/Editor.svelte
@@ -0,0 +1,296 @@
+<script context="module">
+ let debuggerInitialised = false;
+</script>
+<script>
+ import { onDestroy } from "svelte";
+ import { pop } from "svelte-spa-router";
+ import { EditorState, EditorView, basicSetup } from "@codemirror/basic-setup"
+ import { python } from "@codemirror/lang-python"
+ import { HighlightStyle, tags as t } from "@codemirror/highlight"
+ import { notif } from "../../stores/notifs";
+ import TopBar from "./TopBar.svelte";
+ import Pane from "./Pane.svelte";
+ import ControlComponents from "../MainControls/ControlComponents.svelte";
+ import Controls from "./Controls.svelte";
+ import Output from "./Output.svelte";
+
+ import { authorizedSocket, authorizedSocketNeeded } from "../../stores/socketStore";
+ authorizedSocketNeeded.set(true);
+
+ export let modeId;
+
+ let codeEditorView;
+ let codeEditorEl;
+ let codeEditorHasChanges = false;
+ let procIsRunning = false;
+
+ function initDebugger() {
+ if (debuggerInitialised) { return; }
+ debuggerInitialised = true;
+ authorizedSocket.emit("editor:open", `user/${modeId}`, (res) => {
+ if (!res.success) { notif({title: res.reason, type: "danger"}); return; }
+ });
+ }
+
+ authorizedSocket.on("editor:code", (modeId, code) => {
+ const chalky = "#e5c07b",
+ coral = "#e06c75",
+ cyan = "#56b6c2",
+ invalid = "#ffffff",
+ ivory = "#abb2bf",
+ stone = "#7d8799",
+ malibu = "#61afef",
+ sage = "#98c379",
+ whiskey = "#d19a66",
+ violet = "#c678dd",
+ darkBackground = "#21252b",
+ highlightBackground = "#2c313a",
+ background = "#282c34",
+ selection = "#3E4451",
+ cursor = "#528bff"
+
+ codeEditorView = new EditorView({
+ state: EditorState.create({
+ extensions: [
+ basicSetup,
+ python(),
+ EditorView.updateListener.of(update => {
+ if (update.docChanged) {
+ codeEditorHasChanges = true;
+ }
+ }),
+ EditorView.theme({
+ "&": {
+ color: ivory,
+ },
+
+ ".cm-content": {
+ caretColor: cursor
+ },
+
+ "&.cm-focused .cm-cursor": {borderLeftColor: cursor},
+ "&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection": {backgroundColor: selection},
+
+ ".cm-panels": {backgroundColor: darkBackground, color: ivory},
+ ".cm-panels.cm-panels-top": {borderBottom: "2px solid black"},
+ ".cm-panels.cm-panels-bottom": {borderTop: "2px solid black"},
+
+ ".cm-searchMatch": {
+ backgroundColor: "#72a1ff59",
+ outline: "1px solid #457dff"
+ },
+ ".cm-searchMatch.cm-searchMatch-selected": {
+ backgroundColor: "#6199ff2f"
+ },
+
+ ".cm-activeLine": {backgroundColor: highlightBackground},
+ ".cm-selectionMatch": {backgroundColor: "#aafe661a"},
+
+ ".cm-matchingBracket, .cm-nonmatchingBracket": {
+ backgroundColor: "#bad0f847",
+ outline: "1px solid #515a6b"
+ },
+
+ ".cm-gutters": {
+ backgroundColor: "transparent",
+ color: stone,
+ border: "none"
+ },
+
+ ".cm-activeLineGutter": {
+ backgroundColor: highlightBackground
+ },
+
+ ".cm-foldPlaceholder": {
+ backgroundColor: "transparent",
+ border: "none",
+ color: "#ddd"
+ },
+
+ ".cm-tooltip": {
+ border: "1px solid #181a1f",
+ backgroundColor: darkBackground
+ },
+ ".cm-tooltip-autocomplete": {
+ "& > ul > li[aria-selected]": {
+ backgroundColor: highlightBackground,
+ color: ivory
+ }
+ }
+ }, {dark:true}),
+ HighlightStyle.define([
+ {tag: t.keyword,
+ color: violet},
+ {tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName],
+ color: coral},
+ {tag: [t.function(t.variableName), t.labelName],
+ color: malibu},
+ {tag: [t.color, t.constant(t.name), t.standard(t.name)],
+ color: whiskey},
+ {tag: [t.definition(t.name), t.separator],
+ color: ivory},
+ {tag: [t.typeName, t.className, t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace],
+ color: chalky},
+ {tag: [t.operator, t.operatorKeyword, t.url, t.escape, t.regexp, t.link, t.special(t.string)],
+ color: cyan},
+ {tag: [t.meta, t.comment],
+ color: stone},
+ {tag: t.strong,
+ fontWeight: "bold"},
+ {tag: t.emphasis,
+ fontStyle: "italic"},
+ {tag: t.strikethrough,
+ textDecoration: "line-through"},
+ {tag: t.link,
+ color: stone,
+ textDecoration: "underline"},
+ {tag: t.heading,
+ fontWeight: "bold",
+ color: coral},
+ {tag: [t.atom, t.bool, t.special(t.variableName)],
+ color: whiskey },
+ {tag: [t.processingInstruction, t.string, t.inserted],
+ color: sage},
+ {tag: t.invalid,
+ color: invalid},
+ ]),
+ ],
+ doc: code
+ }),
+ parent: codeEditorEl
+ })
+ });
+ authorizedSocket.on("editor:proc:start", () => procIsRunning = true);
+ authorizedSocket.on("editor:proc:exit", (code) => {
+ procIsRunning = false;
+ });
+
+ function startProc() {
+ saveCode(() => {
+ authorizedSocket.emit("editor:startmode", (res) => {
+ if (!res.success) { notif({title: res.reason, type: "danger"}); }
+ });
+ });
+ }
+
+ function stopProc() {
+ authorizedSocket.emit("editor:stopmode", (res) => {
+ if (!res.success) { notif({title: res.reason, type: "danger"}); }
+ });
+ }
+
+ function restartProc () {
+ saveCode((res) => {
+ if (!res.success) { notif({title: res.reason, type: "danger"}); }
+ authorizedSocket.emit("editor:restartmode", (res) => {
+ if (!res.success) { notif({title: res.reason, type: "danger"}); }
+ });
+ });
+ }
+
+ function saveCode(fn) {
+ if (codeEditorView == null) { return; }
+ authorizedSocket.emit("editor:save", `user/${modeId}`, codeEditorView.state.doc.toString(), res => {
+ if (!res.success) { notif({title: res.reason, type: "danger"}); }
+ if (fn != null) { fn(res) }
+ });
+ codeEditorHasChanges = false;
+ }
+
+ function closeDebugger() {
+ saveCode((res) => {
+ if (!res.success) { notif({title: res.reason, type: "danger"}); }
+ authorizedSocket.emit("editor:close", res => {
+ if (!res.success) { notif({title: res.reason, type: "danger"}); }
+ debuggerInitialised = false;
+ });
+ });
+ }
+
+ onDestroy(() => {
+ closeDebugger();
+ })
+
+ document.addEventListener("keydown", function(e) {
+ if ((window.navigator.platform.match("Mac") ? e.metaKey : e.ctrlKey) && e.keyCode == 83) {
+ e.preventDefault();
+ saveCode();
+ }
+ }, false);
+
+ setInterval(() => {
+ if (codeEditorHasChanges) {
+ saveCode();
+ }
+ }, 5000);
+</script>
+
+<style>
+ main {
+ display: grid;
+ box-sizing: border-box;
+ padding: 15px;
+ column-gap: 15px;
+ row-gap: 15px;
+ grid-template-columns: 300px 1fr;
+ grid-template-rows: 50% 1fr 33%;
+ grid-template-areas:
+ "simulation editor"
+ "controls editor"
+ "controls output";
+ width: 100%;
+ height: calc(100% - 35px);
+ background-color: #333333;
+ color: white;
+ }
+ .simulation { grid-area: simulation; }
+ .controls { grid-area: controls; }
+ .editor { grid-area: editor; }
+ .output { grid-area: output; }
+
+ .editor {
+ overflow: auto;
+ }
+
+ @media (max-width: 800px) {
+ main {
+ grid-template-columns: auto;
+ grid-template-areas:
+ "editor"
+ "editor"
+ "output";
+ }
+ .controls, .simulation {
+ display: none;
+ }
+ }
+</style>
+
+<TopBar modeId={modeId}
+ hasChange={codeEditorHasChanges}
+ on:closedebugger={pop}
+ on:start={startProc}
+ on:stop={stopProc}
+ on:restart={restartProc}
+ bind:procIsRunning={procIsRunning} />
+<main use:initDebugger>
+ <div class="simulation">
+ <Pane header="simulation">
+ </Pane>
+ </div>
+
+ <div class="controls">
+ <Pane header="Controls">
+ <!-- <ControlComponents /> -->
+ <Controls />
+ </Pane>
+ </div>
+
+ <div class="editor" bind:this={codeEditorEl}></div>
+
+ <div class="output">
+ <Pane header="output" padding={false} scrollable={false}>
+ <Output />
+ </Pane>
+ </div>
+</main>
diff --git a/src_frontend/Components/Editor/Output.svelte b/src_frontend/Components/Editor/Output.svelte
new file mode 100644
index 0000000..9e4c953
--- /dev/null
+++ b/src_frontend/Components/Editor/Output.svelte
@@ -0,0 +1,56 @@
+<script>
+ import { authorizedSocket, authorizedSocketNeeded } from "../../stores/socketStore";
+ authorizedSocketNeeded.set(true);
+
+ let scrollBox;
+ let htmlCode = "";
+
+ function addData(data, classname) {
+ // let styles = "white-space:pre-wrap;margin:0;";
+ // let styles = "overflow-x:auto;";
+ let styles = "";
+ switch (classname) {
+ case "exit":
+ styles += "color: green";
+ break;
+ case"stderr":
+ styles += "color: red";
+ break;
+ }
+ htmlCode += `<span style="${styles}">${data}</span>`;
+ if (scrollBox != null) {
+ scrollBox.scrollTop = scrollBox.scrollHeight + 100;
+ }
+ }
+
+ authorizedSocket.on("editor:proc:start", () => htmlCode = "");
+ authorizedSocket.on("editor:proc:exit", (code) => addData(`\nMode exited with ${code}\n\n`, "exit"));
+ authorizedSocket.on("editor:proc:stdout", (stdout) => addData(stdout, "stdout"));
+ authorizedSocket.on("editor:proc:stderr", (stderr) => addData(stderr, "stderr"));
+</script>
+
+<style>
+ div {
+ height: 100%;
+ width: 100%;
+ }
+ pre {
+ height: 100%;
+ width: calc(100vw - 30px);
+ overflow: auto;
+ padding: 15px;
+ margin: 0;
+ box-sizing: border-box;
+ }
+ @media (min-width: 800px) {
+ pre {
+ width: calc(100vw - 360px);
+ }
+ }
+</style>
+
+<div>
+ <pre bind:this={scrollBox}>
+ {@html htmlCode}
+ </pre>
+</div>
diff --git a/src_frontend/Components/Editor/Pane.svelte b/src_frontend/Components/Editor/Pane.svelte
new file mode 100644
index 0000000..143c569
--- /dev/null
+++ b/src_frontend/Components/Editor/Pane.svelte
@@ -0,0 +1,41 @@
+<script>
+ export let header;
+ export let padding = true;
+ export let scrollable = true;
+</script>
+
+<style>
+ .box {
+ background-color: #444242;
+ border-radius: 5px;
+ height: 100%;
+ }
+ .header {
+ width: 100%;
+ padding: 10px;
+ box-sizing: border-box;
+ border-bottom: 1px solid #333333;
+ }
+ .header h1 {
+ margin: 0;
+ font-size: 12px;
+ font-weight: bold;
+ text-transform: uppercase;
+ }
+ .content {
+ height: calc(100% - 35px);
+ width: 100%;
+ box-sizing: border-box;
+ }
+ .padding { padding: 15px; }
+ .scrollable { overflow: auto; }
+</style>
+
+<div class="box">
+ <div class="header">
+ <h1>{header}</h1>
+ </div>
+ <div class="content" class:padding={padding} class:scrollable={scrollable}>
+ <slot></slot>
+ </div>
+</div> \ No newline at end of file
diff --git a/src_frontend/Components/Editor/TopBar.svelte b/src_frontend/Components/Editor/TopBar.svelte
new file mode 100644
index 0000000..c74adf0
--- /dev/null
+++ b/src_frontend/Components/Editor/TopBar.svelte
@@ -0,0 +1,62 @@
+<script>
+ import { createEventDispatcher } from 'svelte';
+ import { pop } from "svelte-spa-router";
+ import PrettyVar from "../../ComponentLib/PrettyVar.svelte";
+
+ const dispatch = createEventDispatcher();
+
+ export let modeId;
+ export let hasChange = false;
+ export let procIsRunning = false;
+</script>
+
+<style>
+ .topbar {
+ display: flex;
+ background-color: #444242;
+ height: 35px;
+ box-sizing: border-box;
+ padding: 10px;
+ font-size: 12px;
+ color: white;
+ }
+ .topbar .title { margin: auto; }
+ .savestatus {
+ font-size: 10px;
+ color: var(--grey-400);
+ }
+ button {
+ background: #444242;
+ border: none;
+ color: white;
+ }
+ button i {
+ margin-right: 5px;
+ }
+ button:hover {
+ filter: brightness(0.95);
+ }
+ button:active {
+ filter: brightness(0.90);
+ }
+</style>
+
+<div class="topbar">
+ <div><button on:click={() => dispatch("closedebugger")}><i class="fas fa-chevron-left"></i></button></div>
+ <div class="title">
+ <span class="filename"><PrettyVar varText={modeId} /></span>
+ <span class="savestatus">
+ {#if hasChange}
+ (not saved)
+ {/if}
+ </span>
+ </div>
+ <div>
+ {#if procIsRunning}
+ <button on:click={() => dispatch("restart")}><i class="fas fa-sync-alt"></i>Restart</button>
+ <button on:click={() => dispatch("stop")}><i class="fas fa-stop"></i>Stop</button>
+ {:else}
+ <button on:click={() => dispatch("start")}><i class="fas fa-play"></i>Start</button>
+ {/if}
+ </div>
+</div> \ No newline at end of file