separate IDE mode
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Wed, 20 Apr 2022 04:26:29 +0000 (04:26 +0000)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Wed, 20 Apr 2022 04:26:29 +0000 (04:26 +0000)
static/css/index.css
static/src/folders.js [new file with mode: 0644]
static/src/index.js
static/src/service-worker.js
templates/index.html

index 20e8e39402faf6c050edc68a5b4a0b27fbad4e60..d50e1d032b53240adaaca5cac556ea995ffdfb42 100644 (file)
@@ -83,6 +83,12 @@ select:hover {
     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;
@@ -294,30 +300,44 @@ select:hover {
     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;
 }
 
diff --git a/static/src/folders.js b/static/src/folders.js
new file mode 100644 (file)
index 0000000..5dc1e03
--- /dev/null
@@ -0,0 +1,281 @@
+
+// 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
index a5f4e77903c9daa9bc9a5314ffc82506dc93e5c4..dfdeaa83fc49a00df04b38794525704869fc30fe 100644 (file)
@@ -5,6 +5,7 @@ let ui_theme = "dark";
 
 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');
@@ -124,12 +125,12 @@ async function kill_code() {
 }
 
 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>");
     }
 }
 
@@ -183,6 +184,21 @@ function change_ui_theme(value) {
     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;
@@ -285,18 +301,18 @@ async function request_permalink() {
 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", {
@@ -313,7 +329,7 @@ window.onload = () => {
 
     populate_examples();
     load_settings();
-    // build_folder_view();
+    change_ui_mode("ide");
 
     make_resizer("main-horizontal-divider", "--folder-width", "", (e) => {
         save_split_sizes();
@@ -336,127 +352,4 @@ window.onload = () => {
 
     $("#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
index c9564c68c38920f2fe35bd8ccd094c83aabe524f..b1780a45bcb1675305ab998d38dcc3a9da674346 100644 (file)
@@ -10,6 +10,7 @@ const precacheResources = [
   '/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',
index 7b49e1fd397a28806ceef7f7e21d122bc812d10a..6b18f7f1e292bb7797d45a67582de330e20f63ee 100644 (file)
@@ -16,6 +16,7 @@
         <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">
@@ -71,6 +85,7 @@
             </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">