From 54795b81ac2063e7988c5a5b61eef0190e196ddf Mon Sep 17 00:00:00 2001 From: Joscha Date: Wed, 18 Mar 2020 18:08:04 +0000 Subject: [PATCH] [web] Connect to server The individual components are more-or-less working, but the code that glues them together is still pretty ugly. I should probably revisit and clean up the individual components too. Also, the cursor code is missing a few features, but everything is usable for the first time :D --- forest-web/node.css | 12 ++-- forest-web/node.js | 144 ++++++++++++++++++++++++++++++++++++-------- 2 files changed, 124 insertions(+), 32 deletions(-) diff --git a/forest-web/node.css b/forest-web/node.css index e02c175..d19ee56 100644 --- a/forest-web/node.css +++ b/forest-web/node.css @@ -29,21 +29,21 @@ } /* Fancy tree lines */ -.node, .node::before { +.node-children > *, .node-children > *::before { border-color: var(--bright-black); border-width: 2px; } -.node-children > .node { +.node-children > * { position: relative; /* .node is containing block for its .node::before */ margin-left: calc(0.5ch - 1px); padding-left: calc(1.5ch - 1px); border-left-style: solid; } -.node-children > .node:last-child { +.node-children > *:last-child { padding-left: calc(1.5ch + 1px); border-left-style: none; } -.node-children > .node::before { +.node-children > *::before { content: ""; position: absolute; left: 0; @@ -52,9 +52,9 @@ height: calc(0.6em - 1px); border-bottom-style: solid; } -.node-children > .node:last-child::before { +.node-children > *:last-child::before { border-left-style: solid; - transition: all 0.4s; + transition: border-bottom-left-radius 0.4s; } /* Curvy lines */ diff --git a/forest-web/node.js b/forest-web/node.js index 33e722c..a374cd7 100644 --- a/forest-web/node.js +++ b/forest-web/node.js @@ -349,6 +349,7 @@ class Editor { this.nodeTree = nodeTree; this.textarea = newElement("textarea"); + this.element = newElement("div", "node-editor", this.textarea); this.textarea.addEventListener("input", event => this._updateTextAreaHeight()); this.path = undefined; @@ -370,21 +371,22 @@ class Editor { node.elements.main.classList.remove("has-editor"); } - this.textarea.parentNode.removeChild(this.textarea); + this.element.parentNode.removeChild(this.element); } _attachTo(node, asChild) { if (asChild) { - node.elements.children.appendChild(this.textarea); + node.elements.children.appendChild(this.element); + node.setFolded(false); } else { node.elements.main.classList.add("has-editor"); - node.elements.main.insertBefore(this.textarea, node.elements.children); + node.elements.main.insertBefore(this.element, node.elements.children); } this._updateTextAreaHeight(); } restore() { - if (this.textarea.parentNode !== null) return; // Already attached + if (this.element.parentNode !== null) return; // Already attached let node = this._getAttachedNode(); if (node === undefined) return; // Nowhere to attach this._attachTo(node, this.asChild); @@ -417,6 +419,64 @@ class Editor { } } +class Connection { + constructor(nodeTree, cursor, editor, url) { + this.nodeTree = nodeTree; + this.cursor = cursor; + this.editor = editor; + + this.url = url; + this.ws = new WebSocket(this.url); + this.ws.addEventListener("message", msg => this.onMessage(msg)); + this.ws.addEventListener("open", _ => this.sendHello()); + } + + onMessage(msg) { + let content = JSON.parse(msg.data); + if (content.type === "hello") { + this.onHello(content); + } else if (content.type === "update") { + this.onUpdate(content); + } + } + + onHello(content) { + this.nodeTree.updateAt(new Path(), new Node(content.node)); + this.cursor.restore(); + this.editor.restore(); + } + + onUpdate(content) { + this.nodeTree.updateAt(new Path(...content.path), new Node(content.node)); + this.cursor.restore(); + this.editor.restore(); + } + + _send(thing) { + this.ws.send(JSON.stringify(thing)); + } + + sendHello() { + this._send({type: "hello", extensions: []}); + } + + sendEdit(path, text) { + this._send({type: "edit", path: path.elements, text: text}); + } + + sendDelete(path) { + this._send({type: "delete", path: path.elements}); + } + + sendReply(path, text) { + this._send({type: "reply", path: path.elements, text: text}); + } + + sendAct(path) { + this._send({type: "act", path: path.elements}); + } +} + /* * The main application */ @@ -426,29 +486,48 @@ const loadingNode = new Node({text: "Connecting...", children: {}, order: []}); const nodeTree = new NodeTree(rootNodeContainer, loadingNode); const cursor = new Cursor(nodeTree); const editor = new Editor(nodeTree); +const conn = new Connection(nodeTree, cursor, editor, "ws://127.0.0.1:8080/"); -// TODO Replace this testing node with the real websocket code -const testNode = new Node({text: "Forest", children: [ - {text: "Test", children: [ - {text: "Bla", children: [], order: []}, - ], order: [0]}, - {text: "Sandbox", edit: true, delete: true, reply: true, act: true, children: [], order: []}, - {text: "About", children: [ - {text: "This project is an experiment in tree-based interaction.", children: [], order: []}, - {text: "Motivation", children: [], order: []}, - {text: "Inspirations", children: [], order: []}, - ], order: [0, 1, 2]} -], order: [0, 1, 2]}); -nodeTree.updateAt(new Path(), testNode); +function beginEdit() { + let node = cursor.getSelectedNode(); + editor.content = node.text; + editor.attachTo(cursor.path, false); +} + +function beginDirectReply() { + editor.content = ""; + editor.attachTo(cursor.path, true); +} + +function beginIndirectReply() { + let path = cursor.path.parent; + if (path === undefined) return; + editor.content = ""; + editor.attachTo(path, true); +} + +function cancelEdit() { + editor.detach(); +} + +function completeEdit() { + let path = editor.path; + let text = editor.textarea.value; + if (editor.asChild) { + conn.sendReply(path, text); + } else { + conn.sendEdit(path, text); + } + editor.detach(); +} document.addEventListener("keydown", event => { - console.log(event); if (event.code === "Escape") { - editor.detach(); - } else if (document.activeElement === editor.textarea) { - if (event.code === "Enter" && !event.shiftKey) { - editor.detach(); - } + cancelEdit(); + event.preventDefault(); + } else if (event.code === "Enter" && !event.shiftKey) { + completeEdit(); + event.preventDefault(); } else if (document.activeElement.tagName === "TEXTAREA") { return; // Do nothing special } else if (event.code === "Tab") { @@ -461,9 +540,22 @@ document.addEventListener("keydown", event => { cursor.moveDown(); event.preventDefault(); } else if (event.code === "KeyE") { - let node = cursor.getSelectedNode(); - editor.content = node.text; - editor.attachTo(cursor.path, false); + beginEdit(); + event.preventDefault(); + } else if (event.code === "KeyR") { + if (event.shiftKey) { + console.log("indirect"); + beginIndirectReply(); + } else { + console.log("direct"); + beginDirectReply(); + } + event.preventDefault(); + } else if (event.code === "KeyD") { + conn.sendDelete(cursor.path); + event.preventDefault(); + } else if (event.code === "KeyA") { + conn.sendAct(cursor.path); event.preventDefault(); } });