cursor: pointer;
}
+.menu-bar a[title="Settings"] {
+ float: right;
+ color: var(--foreground-color);
+ padding-top: 4px;
+}
+
#folder-view {
width: calc(var(--folder-width) - var(--divider-size));
float: left;
position: relative;
cursor: pointer;
transition: all 200ms;
- /* border-left: 2px solid var(--active-color); */
padding-left: 4px;
}
-.folder-item:hover {
+.folder-item.file.active {
background-color: var(--light-background-color);
}
+.folder-item:not(.active):hover {
+ background-color: var(--active-color);
+}
+
+.folder-item > .folder-item-button {
+ opacity: 0;
+ float: right;
+ transition: all 200ms;
+}
+
+.folder-item:hover > .folder-item-button {
+ opacity: 1;
+}
+
.folder {
position: relative;
left: 32px;
- overflow-x: hidden;
+ width: calc(100% - 32px);
overflow-y: auto;
+ overflow-x: hidden;
}
-.folder.root {
+.folder-root {
left: 0px !important;
overflow-y: auto;
overflow-x: hidden;
height: 100%;
- max-height: calc(100vh - var(--menu-bar-height) - var(--tab-line-height)); /* subtract the padding */
+ max-height: calc(100vh - var(--menu-bar-height) * 2 - var(--tab-line-height) - 12px); /* subtract the padding */
}
-.folder.root > .folder-item {
+.folder-root > .folder-item {
border-left: none !important;
}
--- /dev/null
+
+// Experimental Folder System
+/*
+ {
+ "name": "Examples",
+ "type": "dir",
+ "state": "open",
+ "elems": [
+ {
+ "name": "example 1",
+ "type": "file",
+ },
+ {
+ "name": "example 2",
+ "type": "file",
+ },
+ {
+ "name": "example 3",
+ "type": "file",
+ },
+ {
+ "name": "Built ins",
+ "type": "dir",
+ "state": "open",
+ "elems": [
+ {
+ "name": "example 1",
+ "type": "file",
+ },
+ {
+ "name": "example 2",
+ "type": "file",
+ },
+ {
+ "name": "example 3",
+ "type": "file",
+ },
+ ],
+ },
+ ],
+ },
+ {
+ "name": "Built ins",
+ "type": "dir",
+ "state": "open",
+ "elems": [
+ {
+ "name": "example 1",
+ "type": "file",
+ },
+ {
+ "name": "example 2",
+ "type": "file",
+ },
+ {
+ "name": "example 3",
+ "type": "file",
+ },
+ ],
+ },
+]
+*/
+
+class FolderSystem {
+ constructor() {
+ this.folders = [];
+ }
+
+ create_directory(path) {
+ let parts = path.split("/");
+ let name = parts.at(-1);
+
+ let root = this.folders;
+ for (let part of parts.slice(0, parts.length - 1)) {
+ let matches = root.filter((x) => x.name == part);
+ if (matches.length == 0) {
+ root.push({
+ name: part,
+ type: "dir",
+ state: "closed",
+ elems: [],
+ });
+ root = root[root.length - 1].elems;
+
+ } else {
+ root = matches[0].elems;
+ }
+ }
+
+ for (let elem of root) {
+ if (elem.name == name) return elem;
+ }
+
+ root.push({
+ name,
+ type: "dir",
+ state: "closed",
+ elems: [],
+ })
+
+ return root[root.length - 1];
+ }
+
+ lookup(path) {
+ let parts = path.split("/");
+
+ let root = this.folders;
+ for (let part of parts) {
+ if (!Array.isArray(root)) return null;
+
+ let elems = root.filter((x) => x.name == part);
+ if (elems.length != 1) return null;
+
+ root = elems[0];
+ if (root.type == "dir") {
+ root = root.elems;
+ }
+ }
+
+ return root;
+ }
+
+ build_folder_view(onclick, selector=".folder-root") {
+ let $root = $(selector);
+
+ let build = (t, name) => {
+ let output = "";
+ switch (t.type) {
+ case "dir": {
+ output += `<div class="folder-item directory ${t.state}">
+ <i class="fa ${t.state == "open" ? "fa-folder-open" : "fa-folder"}"></i>
+ ${t.name}
+ <i class="folder-item-button fa fa-trash"></i>
+ <i class="folder-item-button fa fa-pencil-alt"></i>
+ </div>`;
+
+ t.elems.sort((a, b) => {
+ let av = a.type == "dir" ? 0 : 1;
+ let bv = b.type == "dir" ? 0 : 1;
+
+ if (av == bv) {
+ return a.name < b.name ? -1 : 1;
+ } else {
+ return av - bv;
+ }
+ })
+
+ output += `<div class="folder ${t.state == "closed" ? "hidden" : ""}">`;
+ for (let i of t.elems) {
+ output += build(i, name + "/" + i.name);
+ }
+ output += `</div>`;
+
+ break;
+ }
+
+ case "file": {
+ output += `<div class="folder-item file" data-filename="${name}">
+ <i class="fa fa-file"></i>
+ ${t.name}
+ <i class="folder-item-button fa fa-trash"></i>
+ <i class="folder-item-button fa fa-pencil-alt"></i>
+ </div>`;
+ break;
+ }
+ }
+
+ return output;
+ }
+
+ let root_html = "";
+ for (let elem of this.folders) {
+ root_html += build(elem, elem.name);
+ }
+
+ $root.html(root_html);
+ $root.find(".folder-item").click(onclick);
+ }
+
+ save() {
+ localStorage["filesystem"] = JSON.stringify(this.folders);
+ }
+
+ restore() {
+ if ("filesystem" in localStorage) {
+ this.folders = JSON.parse(localStorage["filesystem"]);
+ return true;
+ }
+
+ return false;
+ }
+}
+
+
+async function enable_ide_mode() {
+ $("#simple-menubar").addClass("hidden");
+ $("#ide-menubar").removeClass("hidden");
+ $("#folder-view").removeClass("hidden");
+ $("#main-horizontal-divider").removeClass("hidden");
+ $(":root").css("--folder-width", localStorage.getItem("folder-width"));
+
+ folders = new FolderSystem();
+ folders.restore();
+
+ if (folders.lookup("Examples") == null) {
+ await populate_examples_folder();
+ }
+
+ await populate_simple_saved_folder();
+
+ folders.save();
+ folders.build_folder_view(folder_item_click);
+}
+
+function disable_ide_mode() {
+ $("#simple-menubar").removeClass("hidden");
+ $("#ide-menubar").addClass("hidden");
+ $("#folder-view").addClass("hidden");
+ $("#main-horizontal-divider").addClass("hidden");
+ $(":root").css("--folder-width", "0px");
+}
+
+async function populate_examples_folder() {
+ let examples = await fetch(ROOT_ENDPOINT + "/list_examples").then(x => x.json())
+ let example_dir = folders.create_directory("Examples");
+ for (let ex of examples) {
+ let example_code = await fetch(`${ROOT_ENDPOINT}/example?example=${ex}`).then(x => x.text());
+ example_dir.elems.push({
+ name: ex,
+ type: "file",
+ contents: example_code
+ })
+ }
+}
+
+async function populate_simple_saved_folder() {
+ let folder_name = "Simple Mode Saves";
+
+ let saved_dir = folders.create_directory(folder_name);
+ for (let item in localStorage) {
+ if (/saved_/.test(item)) {
+ let item_name = item.substring(6);
+
+ let existing_item = folders.lookup(folder_name + "/" + item_name);
+ if (existing_item) {
+ existing_item.contents = localStorage[item];
+
+ } else {
+ saved_dir.elems.push({
+ name: item_name,
+ type: "file",
+ contents: localStorage[item],
+ });
+ }
+ }
+ }
+}
+
+function folder_item_click(e) {
+ let $target = $(e.target);
+
+ if ($target.hasClass("file")) {
+ let editor = ace.edit("code-editor");
+ let filename = $target.attr("data-filename");
+ let file = folders.lookup(filename);
+ editor.setValue(file.contents);
+ editor.clearSelection();
+ }
+
+ if ($target.hasClass("directory")) {
+ if ($target.hasClass("open")) {
+ $target.removeClass("open").addClass("closed");
+ $target.children("i").removeClass("fa-folder-open").addClass("fa-folder");
+ $target.next().addClass("hidden");
+ } else {
+ $target.removeClass("closed").addClass("open");
+ $target.children("i").removeClass("fa-folder").addClass("fa-folder-open");
+ $target.next().removeClass("hidden");
+ }
+ }
+}
\ No newline at end of file
let input_shared_buffer = new SharedArrayBuffer(1024 * Uint8Array.BYTES_PER_ELEMENT);
let canvas_shared_buffer = new SharedArrayBuffer(7 * Int32Array.BYTES_PER_ELEMENT);
+let folders = null;
async function clear_output() {
let elem = document.getElementById('code-result');
}
function update_running_msg() {
- let elem = document.getElementById('run-button');
+ let elem = $('.run-button');
if (wasm_worker == null) {
- elem.innerHTML = "<i class='fas fa-play'></i>";
+ elem.html("<i class='fas fa-play'></i>");
} else {
- elem.innerHTML = "<i class='fas fa-circle-notch fa-spin'></i>";
+ elem.html("<i class='fas fa-circle-notch fa-spin'></i>");
}
}
persist_settings();
}
+function change_ui_mode(value) {
+ let elem = document.getElementById('ui-mode');
+ if (value == null) {
+ value = elem.value;
+ } else {
+ document.querySelector(`#ui-mode option[value="${value}"]`).selected = true;
+ }
+
+ if (value == "ide") {
+ enable_ide_mode();
+ } else {
+ disable_ide_mode();
+ }
+}
+
function persist_settings() {
localStorage["editor_theme"] = editor_theme;
localStorage["editor_keybind_mode"] = editor_keybind_mode;
function load_split_sizes() {
let $root = $(":root");
- $root.css("--folder-width", localStorage.getItem("folder-width"));
$root.css("--top-half-height", localStorage.getItem("top-half-height"));
$root.css("--left-half-width", localStorage.getItem("left-half-width"));
+ $root.css("--folder-width", "0px");
}
function save_split_sizes() {
let $root = $(":root");
- localStorage.setItem("folder-width", $root.css("--folder-width"));
localStorage.setItem("top-half-height", $root.css("--top-half-height"));
localStorage.setItem("left-half-width", $root.css("--left-half-width"));
}
+
window.onload = () => {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register("/playground/static/src/service-worker.js", {
populate_examples();
load_settings();
- // build_folder_view();
+ change_ui_mode("ide");
make_resizer("main-horizontal-divider", "--folder-width", "", (e) => {
save_split_sizes();
$("#save-filename").on('keyup', (ev) => { if (ev.keyCode === 13) save_to_local_storage() });
$("#load-filename").on('keyup', (ev) => { if (ev.keyCode === 13) load_from_local_storage() });
-};
-
-// Experimental Folder System
-/*
-let folders = [
- {
- "name": "Examples",
- "type": "dir",
- "state": "open",
- "elems": [
- {
- "name": "example 1",
- "type": "file",
- },
- {
- "name": "example 2",
- "type": "file",
- },
- {
- "name": "example 3",
- "type": "file",
- },
- {
- "name": "Built ins",
- "type": "dir",
- "state": "open",
- "elems": [
- {
- "name": "example 1",
- "type": "file",
- },
- {
- "name": "example 2",
- "type": "file",
- },
- {
- "name": "example 3",
- "type": "file",
- },
- ],
- },
- ],
- },
- {
- "name": "Built ins",
- "type": "dir",
- "state": "open",
- "elems": [
- {
- "name": "example 1",
- "type": "file",
- },
- {
- "name": "example 2",
- "type": "file",
- },
- {
- "name": "example 3",
- "type": "file",
- },
- ],
- },
-]
-
-function build_folder_view() {
- let $root = $(".folder.root");
-
- let build = (t) => {
- let output = "";
- switch (t.type) {
- case "dir": {
- output += `<div class="folder-item directory ${t.state}">
- <i class="fa ${t.state == "open" ? "fa-folder-open" : "fa-folder"}"></i>
- ${t.name}</div>`;
-
- if (t.state == "open") {
- output += `<div class="folder">`;
- for (let i of t.elems) {
- output += build(i);
- }
- output += `</div>`;
- }
-
- break;
- }
-
- case "file": {
- output += `<div class="folder-item file">${t.name}</div>`;
- break;
- }
- }
-
- return output;
- }
-
- let root_html = "";
- for (let elem of folders) {
- root_html += build(elem);
- }
-
- $root.html(root_html);
- $(".folder-item").click(folder_item_click);
-}
-
-function folder_item_click(e) {
- let $target = $(e.target);
- console.log($target);
-
- if ($target.hasClass("file")) {
- }
-
- if ($target.hasClass("directory")) {
- if ($target.hasClass("open")) {
- $target.removeClass("open").addClass("closed");
- $target.children("i").removeClass("fa-folder-open").addClass("fa-folder");
- $target.next().addClass("hidden");
- } else {
- $target.removeClass("closed").addClass("open");
- $target.children("i").removeClass("fa-folder").addClass("fa-folder-open");
- $target.next().removeClass("hidden");
- }
- }
-}
-*/
\ No newline at end of file
+};
\ No newline at end of file
'/playground/static/src/resizer.js',
'/playground/static/src/storage.js',
'/playground/static/src/canvas.js',
+ '/playground/static/src/folders.js',
'/playground/static/src/index.js',
'/playground/static/vendor/ace/ace.js',
'/playground/static/vendor/jquery/jquery.min.js',
<script defer src="{{ config['ENDPOINT'] + url_for('static', filename='src/resizer.js') }}"></script>
<script defer src="{{ config['ENDPOINT'] + url_for('static', filename='src/storage.js') }}"></script>
<script defer src="{{ config['ENDPOINT'] + url_for('static', filename='src/canvas.js') }}"></script>
+ <script defer src="{{ config['ENDPOINT'] + url_for('static', filename='src/folders.js') }}"></script>
<script defer src="{{ config['ENDPOINT'] + url_for('static', filename='vendor/ace/ace.js') }}"></script>
<script defer src="{{ config['ENDPOINT'] + url_for('static', filename='vendor/jquery/jquery.min.js') }}"></script>
<script defer src="{{ config['ENDPOINT'] + url_for('static', filename='vendor/jquery/jquery.modal.min.js') }}"></script>
</head>
<body id="body">
- <div class="menu-bar">
- <button title="Run" id="run-button" onclick="submit_code()"><i class="fas fa-play"></i></button>
+ <div id="simple-menubar" class="menu-bar">
+ <button title="Run" class="run-button" onclick="submit_code()"><i class="fas fa-play"></i></button>
<button title="Stop" onclick="kill_code()"><i class="fas fa-stop"></i></button>
<button title="Save" onclick="prompt_save()"><i class="fas fa-save"></i></button>
<button title="Load" onclick="prompt_load()"><i class="fas fa-folder"></i></button>
<button title="Upload" onclick="prompt_upload()"><i class="fas fa-upload"></i></button>
<button title="Download" onclick="prompt_download()"><i class="fas fa-download"></i></button>
- <input id="fileupload" style="display:none" type="file" onchange="file_uploaded(event)"/>
- <a title="Settings" href="#settings-modal" rel="modal:open" style="color: var(--foreground-color)"><i class="fas fa fa-gears"></i></a>
+ <a title="Settings" href="#settings-modal" rel="modal:open"><i class="fas fa fa-gears"></i></a>
<label style="margin-left: 64px" for="examples">Load example:</label>
<select name="examples" id="examples-selector" onchange="load_example()">
</select>
</div>
- <!--
+ <div id="ide-menubar" class="menu-bar hidden">
+ <div style="text-align: center; float: left; width: 100%">
+ <button title="Run" class="run-button" onclick="submit_code()"><i class="fas fa-play"></i></button>
+ <button title="Stop" onclick="kill_code()"><i class="fas fa-stop"></i></button>
+ <a title="Settings" href="#settings-modal" rel="modal:open"><i class="fas fa fa-gears"></i></a>
+ </div>
+ </div>
+
<div id="folder-view" class="hidden">
<div class="tab-line"><span>Storage</span></div>
+ <div class="menu-bar" style="border-bottom: 2px solid var(--light-background-color); margin-bottom: 12px">
+ <div style="text-align: center">
+ <button title="New File" onclick="prompt_create_file()"><i class="fas fa-plus"></i></button>
+ <button title="Save" onclick="prompt_save()"><i class="fas fa-save"></i></button>
+ <button title="Upload" onclick="prompt_upload()"><i class="fas fa-upload"></i></button>
+ <button title="Download" onclick="prompt_download()"><i class="fas fa-download"></i></button>
+ </div>
+ </div>
- <div class="folder root">
+ <div class="folder-root">
</div>
</div>
<div id="main-horizontal-divider" class="divider hidden"></div>
- -->
<div id="code-container">
<div class="left-half">
</div>
</div>
+ <input id="fileupload" style="display:none" type="file" onchange="file_uploaded(event)"/>
<div id="settings-modal" class="modal">
<h2>Settings</h2>
<option value="sublime">Sublime Text</option>
<option value="vscode">VS Code</option>
</select>
+
+ <br />
+
+ <label for="ui-mode">UI Mode:</label>
+ <select aria-label="UI Mode" name="ui-mode" id="ui-mode" onchange="change_ui_mode()">
+ <option value="simple">Simple</option>
+ <option value="ide">IDE</option>
+ </select>
</div>
<div id="save-modal" class="modal">