From 132b851de7a4e12800e1a22820e46d69e969a986 Mon Sep 17 00:00:00 2001 From: flifloo Date: Tue, 9 Mar 2021 00:12:17 +0100 Subject: [PATCH] Add & edit slot to deck with live update --- app.js | 9 +- public/javascripts/index.js | 37 ++++--- public/javascripts/settings/decks.js | 153 +++++++++++++++++++++++++++ routes/settings.js | 8 ++ sockets/getOptions.js | 15 +++ sockets/getSlot.js | 18 ++++ sockets/getType.js | 8 ++ sockets/index.js | 3 + sockets/setSlot.js | 23 ++++ types/Base.js | 22 +++- types/Deck.js | 26 ++++- types/ExecCommand.js | 20 +++- views/settings/decks.pug | 28 +++++ views/settings/settings.pug | 10 ++ 14 files changed, 353 insertions(+), 27 deletions(-) create mode 100644 public/javascripts/settings/decks.js create mode 100644 routes/settings.js create mode 100644 sockets/getOptions.js create mode 100644 sockets/getSlot.js create mode 100644 sockets/getType.js create mode 100644 sockets/setSlot.js create mode 100644 views/settings/decks.pug create mode 100644 views/settings/settings.pug diff --git a/app.js b/app.js index da9af3e..fc7b055 100644 --- a/app.js +++ b/app.js @@ -4,9 +4,7 @@ const path = require("path"); const cookieParser = require("cookie-parser"); const logger = require("morgan"); const sassMiddleware = require("node-sass-middleware"); -const jsMiddleware = require("./middleware/jsMiddleware") - -const indexRouter = require("./routes/index"); +const jsMiddleware = require("./middleware/jsMiddleware"); const app = express(); @@ -25,10 +23,11 @@ app.use(sassMiddleware({ indentedSyntax: true, // true = .sass and false = .scss sourceMap: true })); -app.use(jsMiddleware("/javascripts/materialize.js", "materialize-css/dist/js/materialize")) +app.use(jsMiddleware("/javascripts/materialize.js", "materialize-css/dist/js/materialize")); app.use(express.static(path.join(__dirname, "public"))); -app.use("/", indexRouter); +app.use("/", require("./routes/index")); +app.use("/settings", require("./routes/settings")); // catch 404 and forward to error handler app.use(function(req, res, next) { diff --git a/public/javascripts/index.js b/public/javascripts/index.js index eaf46ed..5e00106 100644 --- a/public/javascripts/index.js +++ b/public/javascripts/index.js @@ -8,6 +8,7 @@ socket.on("connected", () => { socket.on("getDeck", d => { let data = d.data, name = d.name; + console.log(name); deck.innerHTML = ""; for (let x = 0; x < data.x; x++) { @@ -22,23 +23,31 @@ socket.on("getDeck", d => { } for (const [x, cols] of Object.entries(data.rows)) - for (const [y, col] of Object.entries(cols)) { - let e = document.getElementById(`r${x}c${y}`); - if (e) { - if (col.image) - e.insertAdjacentHTML("beforeend", `${col.text}`); - else if (col.text) - e.insertAdjacentHTML("beforeend", `

${col.text}

`); - - e.addEventListener("click", ev => { - ev.stopPropagation(); - socket.emit("trigger", [name, x, y]); - }) - } - } + for (const [y, col] of Object.entries(cols)) + setSlot(name, col, x, y); }); socket.on("trigger", data => { if (data.error) alert(data.error) }); + +socket.on("setSlot", data => { + if (data && !data.error) + setSlot(data.name, data.data, ...data.position) +}); + +function setSlot(name, data, x, y) { + let e = document.getElementById(`r${x}c${y}`); + if (e) { + if (data.image) + e.insertAdjacentHTML("beforeend", `${data.text}`); + else if (data.text) + e.insertAdjacentHTML("beforeend", `

${data.text}

`); + + e.addEventListener("click", ev => { + ev.stopPropagation(); + socket.emit("trigger", [name, x, y]); + }) + } +} diff --git a/public/javascripts/settings/decks.js b/public/javascripts/settings/decks.js new file mode 100644 index 0000000..ad7e9d8 --- /dev/null +++ b/public/javascripts/settings/decks.js @@ -0,0 +1,153 @@ +const socket = io(); +const deck = document.getElementById("deck"); +const modal = document.getElementById("modal"); +const type = document.getElementById("type"); +const title = document.getElementById("title"); +const customs = document.getElementById("customs"); +const form = modal.querySelector("form"); +let modalInstance, types, slot; + +socket.on("connected", () => { + console.log("Connected !"); + socket.emit("getDeck"); + socket.emit("getType"); +}); + +socket.on("getDeck", d => { + let data = d.data, name = d.name; + deck.innerHTML = ""; + + for (let x = 0; x < data.x; x++) { + const r = document.createElement("div"); + r.id = "r" + x; + for (let y = 0; y < data.y; y++) { + const c = document.createElement("div"); + c.id = r.id+"c"+y; + c.addEventListener("click", ev => { + ev.stopPropagation(); + socket.emit("getSlot", {name: name, slot: [x, y]}); + }); + r.insertAdjacentElement("beforeend", c); + } + deck.insertAdjacentElement("beforeend", r); + } + + for (const [x, cols] of Object.entries(data.rows)) + for (const [y, col] of Object.entries(cols)) { + let e = document.getElementById(`r${x}c${y}`); + if (e) { + if (col.image) + e.insertAdjacentHTML("beforeend", `${col.text}`); + else if (col.text) + e.insertAdjacentHTML("beforeend", `

${col.text}

`); + } + } +}); + +socket.on("getSlot", data => { + slot = data; + customs.innerHTML = ""; + modal.querySelectorAll("#type>option[selected]").forEach(e => e.selected = false); + + if (data.data) { + title.value = data.data.text; + type.querySelector(`option[value=${data.data.type}]`).selected = true; + customFields(data.data.options); + } else + type.value = title.value = ""; + + M.updateTextFields(); + M.FormSelect.init(type); + modalInstance.open(); +}); + +socket.on("getType", data => { + types = data; + type.innerHTML = ""; + for (let t of data) + type.insertAdjacentHTML("beforeend", ``); +}); + +socket.on("setSlot", data => { + if (data.error) + alert(data.error); + else { + modalInstance.close(); + let e = document.getElementById(`r${data.position[0]}c${data.position[1]}`); + if (e) { + if (data.data.image) + e.insertAdjacentHTML("beforeend", `${data.data.text}`); + else if (data.data.text) + e.insertAdjacentHTML("beforeend", `

${data.data.text}

`); + } else + e.innerHTML = ""; + } +}); + +document.getElementById("save").addEventListener("click", ev => { + ev.stopPropagation(); + + let data = {}; + for (const e of new FormData(form)) + data[e[0]] = e[1]; + + if (!slot.data) + slot.data = {}; + + for (const k of ["text", "type"]) { //image + slot.data[k] = data[k]; + delete data[k]; + } + + slot.data.options = data; + + socket.emit("setSlot", {name: slot.name, data: slot.data, position: slot.position}); +}); + +type.addEventListener("change", ev => { + ev.stopPropagation(); + customFields(); +}); + +document.addEventListener("DOMContentLoaded", () => { + M.AutoInit(); + modalInstance = M.Modal.getInstance(modal); +}); + +function customFields(values) { + customs.innerHTML = ""; + let t = types.find(v => v.type === type.value); + for (const [name, field] of Object.entries(t.fields)) { + let e; + switch (field.type) { + case "text": + e = document.createElement("input"); + e.type = "text"; + break; + case "select": + e = document.createElement("select"); + for (let option of field.options) + e.insertAdjacentHTML("beforeend", ``); + break; + } + e.name = name; + e.id = name; + e.required = true; + e.classList.add("validate"); + let d = document.createElement("div"); + d.classList.add("input-field"); + d.insertAdjacentElement("beforeend", e); + d.insertAdjacentHTML("beforeend", ``); + customs.insertAdjacentElement("beforeend", d); + if (values && name in values) { + e.value = values[name]; + if (field.type === "select") + e.querySelector(`option[value=${values[name]}]`).selected = true; + } + if (field.type === "select") { + M.FormSelect.init(e); + e.style.display = "none"; + } + } + M.updateTextFields(); +} diff --git a/routes/settings.js b/routes/settings.js new file mode 100644 index 0000000..6b43665 --- /dev/null +++ b/routes/settings.js @@ -0,0 +1,8 @@ +const express = require("express"); +const router = express.Router(); + +router.get("/", function(req, res) { + res.render("settings/decks", { title: "Settings"}); +}); + +module.exports = router; diff --git a/sockets/getOptions.js b/sockets/getOptions.js new file mode 100644 index 0000000..1856a55 --- /dev/null +++ b/sockets/getOptions.js @@ -0,0 +1,15 @@ +const getSlot = require("../types").getSlot; + + +module.exports = socket => { + return data => { + if (data) { + let s = getSlot(name, ...slot); + if (s) + socket.emit("getOptions", {name: name, data: s.toJSON()}); + else + socket.emit("getOptions", {name: name, data: null}); + } else + socket.emit("getOptions", {error: "invalidData"}); + } +}; diff --git a/sockets/getSlot.js b/sockets/getSlot.js new file mode 100644 index 0000000..aab27a0 --- /dev/null +++ b/sockets/getSlot.js @@ -0,0 +1,18 @@ +const db = require("../db.json"); +const getSlot = require("../types").getSlot; + + +module.exports = socket => { + return data => { + let name = data.name, slot = data.slot; + if (db.decks[name]) { + let s = getSlot(name, ...slot); + if (s) + socket.emit("getSlot", {name: name, data: s.toJSON(), position: slot}); + else + socket.emit("getSlot", {name: name, data: null, position: slot}); + } + else + socket.emit("getSlot", {error: "deckNotFound"}); + } +}; diff --git a/sockets/getType.js b/sockets/getType.js new file mode 100644 index 0000000..eefc3e8 --- /dev/null +++ b/sockets/getType.js @@ -0,0 +1,8 @@ +const types = require("../types").types; + + +module.exports = socket => { + return () => { + socket.emit("getType", Object.values(types).map(t => t.staticToJSON())); + } +}; diff --git a/sockets/index.js b/sockets/index.js index 496007f..35d28d5 100644 --- a/sockets/index.js +++ b/sockets/index.js @@ -1,5 +1,8 @@ module.exports = socket => { socket.on("getDeck", require("./getDeck")(socket)); + socket.on("getSlot", require("./getSlot")(socket)); + socket.on("getType", require("./getType")(socket)); + socket.on("setSlot", require("./setSlot")(socket)); socket.on("trigger", require("./trigger")(socket)); console.log("New connection !"); socket.emit("connected"); diff --git a/sockets/setSlot.js b/sockets/setSlot.js new file mode 100644 index 0000000..c3e644c --- /dev/null +++ b/sockets/setSlot.js @@ -0,0 +1,23 @@ +const { getSlot, types } = require("../types"); + + +module.exports = socket => { + return data => { + let s = getSlot(data.name, ...data.position); + try { + if (!s) + s = new types[data.data.type](data.data.text, data.data.image, data.data.options); + else { + s.text = data.data.text; + s.image = data.data.image; + s.options = data.data.options + } + s.save(data.name, data.position); + socket.emit("setSlot", data); + socket.broadcast.emit("setSlot", data); + } catch (err) { + console.error(err); + socket.emit("setSlot", {error: err.code}); + } + } +}; diff --git a/types/Base.js b/types/Base.js index 10d8e3f..7a2e5da 100644 --- a/types/Base.js +++ b/types/Base.js @@ -1,10 +1,11 @@ +const db = require("../db.json"); +const fs = require("fs"); + /** * @abstract */ class Base { - text; - image; - options; + static name = "Base"; constructor(text, image = null, options = null) { this.text = text; @@ -16,6 +17,21 @@ class Base { return; }; + save(name, position) { + if (!(position[0] in db.decks[name].rows)) + db.decks[name].rows[position[0]] = {}; + db.decks[name].rows[position[0]][position[1]] = this.toJSON(); + fs.writeFileSync("./db.json", JSON.stringify(db)); + } + + static staticToJSON(name, type, fields) { + return { + "name": name, + "type": type, + "fields": fields + } + } + toJSON(type) { return { "text": this.text, diff --git a/types/Deck.js b/types/Deck.js index b0289a3..3d8b9a8 100644 --- a/types/Deck.js +++ b/types/Deck.js @@ -3,23 +3,43 @@ const db = require("../db.json"); class Deck extends Base { + static name = "Deck"; + static type = "deck"; + constructor(text, image = null, options = null) { super(text, image, options); } + static fields() { + return { + deck: { + "type": "select", + "options": Object.keys(db.decks), + "name": "Deck" + } + }; + } + + /** + * @override + */ + static staticToJSON() { + return super.staticToJSON(Deck.name, Deck.type, Deck.fields()); + } + /** * @override */ toJSON() { - return super.toJSON("deck") + return super.toJSON(Deck.type) } /** * @override */ trigger(socket) { - if (typeof this.options === "string") { - socket.emit("getDeck", {name: this.options, data: db.decks[this.options]}); + if (this.options && typeof this.options.deck === "string") { + socket.emit("getDeck", {name: this.options.deck, data: db.decks[this.options.deck]}); } else socket.emit("trigger", {error: "invalidOptions"}); } diff --git a/types/ExecCommand.js b/types/ExecCommand.js index d63b4ca..68a0afe 100644 --- a/types/ExecCommand.js +++ b/types/ExecCommand.js @@ -2,22 +2,38 @@ const Base = require("./Base"); const { exec } = require('child_process'); class ExecCommand extends Base { + static name = "Exec command"; + static type = "execCommand"; + static fields = { + cmd: { + type: "text", + name: "Command" + } + }; + constructor(text, image = null, options = null) { super(text, image, options); } + /** + * @override + */ + static staticToJSON() { + return super.staticToJSON(ExecCommand.name, ExecCommand.type, ExecCommand.fields); + } + /** * @override */ toJSON() { - return super.toJSON("execCommand") + return super.toJSON(ExecCommand.type) } /** * @override */ trigger() { - exec(this.options, (err) => { + exec(this.options.cmd, (err) => { if (err) console.error(err); }); diff --git a/views/settings/decks.pug b/views/settings/decks.pug new file mode 100644 index 0000000..5c4e007 --- /dev/null +++ b/views/settings/decks.pug @@ -0,0 +1,28 @@ +extends ./settings + +block settings + .input-field + select#deck-select + option(value="default" selected) Default + label Deck + + .container#deck + + #modal.modal + .modal-content + .row + form.col.s12 + .row + .input-field.col.s12 + input#title(type="text" name="text" required).validate + label(for="title") Text + .row + .input-field.col.s12 + select#type(name="type" required).validate + label(for="type") Type + #customs + .modal-footer + a.modal-close.waves-effect.waves-red.btn-flat(href="#!") Cancel + a#save.modal-close.waves-effect.waves-green.btn-flat Save + + script(src="/javascripts/settings/decks.js") diff --git a/views/settings/settings.pug b/views/settings/settings.pug new file mode 100644 index 0000000..2f9102d --- /dev/null +++ b/views/settings/settings.pug @@ -0,0 +1,10 @@ +extends ../layout + +block content + nav + .nav-wrapper + a.brand-logo.right(href="/") OpenDeck + ul#nav-mobile.left.hide-on-med-and-down + li + a(href="sass.html") Decks + block settings