Add & edit slot to deck with live update
This commit is contained in:
parent
073189e04d
commit
132b851de7
14 changed files with 353 additions and 27 deletions
9
app.js
9
app.js
|
@ -4,9 +4,7 @@ const path = require("path");
|
||||||
const cookieParser = require("cookie-parser");
|
const cookieParser = require("cookie-parser");
|
||||||
const logger = require("morgan");
|
const logger = require("morgan");
|
||||||
const sassMiddleware = require("node-sass-middleware");
|
const sassMiddleware = require("node-sass-middleware");
|
||||||
const jsMiddleware = require("./middleware/jsMiddleware")
|
const jsMiddleware = require("./middleware/jsMiddleware");
|
||||||
|
|
||||||
const indexRouter = require("./routes/index");
|
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
|
@ -25,10 +23,11 @@ app.use(sassMiddleware({
|
||||||
indentedSyntax: true, // true = .sass and false = .scss
|
indentedSyntax: true, // true = .sass and false = .scss
|
||||||
sourceMap: true
|
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(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
|
// catch 404 and forward to error handler
|
||||||
app.use(function(req, res, next) {
|
app.use(function(req, res, next) {
|
||||||
|
|
|
@ -8,6 +8,7 @@ socket.on("connected", () => {
|
||||||
|
|
||||||
socket.on("getDeck", d => {
|
socket.on("getDeck", d => {
|
||||||
let data = d.data, name = d.name;
|
let data = d.data, name = d.name;
|
||||||
|
console.log(name);
|
||||||
deck.innerHTML = "";
|
deck.innerHTML = "";
|
||||||
|
|
||||||
for (let x = 0; x < data.x; x++) {
|
for (let x = 0; x < data.x; x++) {
|
||||||
|
@ -22,13 +23,27 @@ socket.on("getDeck", d => {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [x, cols] of Object.entries(data.rows))
|
for (const [x, cols] of Object.entries(data.rows))
|
||||||
for (const [y, col] of Object.entries(cols)) {
|
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}`);
|
let e = document.getElementById(`r${x}c${y}`);
|
||||||
if (e) {
|
if (e) {
|
||||||
if (col.image)
|
if (data.image)
|
||||||
e.insertAdjacentHTML("beforeend", `<img src="${col.image}" alt="${col.text}">`);
|
e.insertAdjacentHTML("beforeend", `<img src="${data.image}" alt="${data.text}">`);
|
||||||
else if (col.text)
|
else if (data.text)
|
||||||
e.insertAdjacentHTML("beforeend", `<p>${col.text}</p>`);
|
e.insertAdjacentHTML("beforeend", `<p>${data.text}</p>`);
|
||||||
|
|
||||||
e.addEventListener("click", ev => {
|
e.addEventListener("click", ev => {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
|
@ -36,9 +51,3 @@ socket.on("getDeck", d => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("trigger", data => {
|
|
||||||
if (data.error)
|
|
||||||
alert(data.error)
|
|
||||||
});
|
|
||||||
|
|
153
public/javascripts/settings/decks.js
Normal file
153
public/javascripts/settings/decks.js
Normal file
|
@ -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", `<img src="${col.image}" alt="${col.text}">`);
|
||||||
|
else if (col.text)
|
||||||
|
e.insertAdjacentHTML("beforeend", `<p>${col.text}</p>`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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", `<option value="${t.type}">${t.name}</option>`);
|
||||||
|
});
|
||||||
|
|
||||||
|
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", `<img src="${data.data.image}" alt="${data.data.text}">`);
|
||||||
|
else if (data.data.text)
|
||||||
|
e.insertAdjacentHTML("beforeend", `<p>${data.data.text}</p>`);
|
||||||
|
} 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", `<option value="${option}">${option}</option>`);
|
||||||
|
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", `<label for="${name}">${field.name}</label>`);
|
||||||
|
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();
|
||||||
|
}
|
8
routes/settings.js
Normal file
8
routes/settings.js
Normal file
|
@ -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;
|
15
sockets/getOptions.js
Normal file
15
sockets/getOptions.js
Normal file
|
@ -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"});
|
||||||
|
}
|
||||||
|
};
|
18
sockets/getSlot.js
Normal file
18
sockets/getSlot.js
Normal file
|
@ -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"});
|
||||||
|
}
|
||||||
|
};
|
8
sockets/getType.js
Normal file
8
sockets/getType.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
const types = require("../types").types;
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = socket => {
|
||||||
|
return () => {
|
||||||
|
socket.emit("getType", Object.values(types).map(t => t.staticToJSON()));
|
||||||
|
}
|
||||||
|
};
|
|
@ -1,5 +1,8 @@
|
||||||
module.exports = socket => {
|
module.exports = socket => {
|
||||||
socket.on("getDeck", require("./getDeck")(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));
|
socket.on("trigger", require("./trigger")(socket));
|
||||||
console.log("New connection !");
|
console.log("New connection !");
|
||||||
socket.emit("connected");
|
socket.emit("connected");
|
||||||
|
|
23
sockets/setSlot.js
Normal file
23
sockets/setSlot.js
Normal file
|
@ -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});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -1,10 +1,11 @@
|
||||||
|
const db = require("../db.json");
|
||||||
|
const fs = require("fs");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @abstract
|
* @abstract
|
||||||
*/
|
*/
|
||||||
class Base {
|
class Base {
|
||||||
text;
|
static name = "Base";
|
||||||
image;
|
|
||||||
options;
|
|
||||||
|
|
||||||
constructor(text, image = null, options = null) {
|
constructor(text, image = null, options = null) {
|
||||||
this.text = text;
|
this.text = text;
|
||||||
|
@ -16,6 +17,21 @@ class Base {
|
||||||
return;
|
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) {
|
toJSON(type) {
|
||||||
return {
|
return {
|
||||||
"text": this.text,
|
"text": this.text,
|
||||||
|
|
|
@ -3,23 +3,43 @@ const db = require("../db.json");
|
||||||
|
|
||||||
|
|
||||||
class Deck extends Base {
|
class Deck extends Base {
|
||||||
|
static name = "Deck";
|
||||||
|
static type = "deck";
|
||||||
|
|
||||||
constructor(text, image = null, options = null) {
|
constructor(text, image = null, options = null) {
|
||||||
super(text, image, options);
|
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
|
* @override
|
||||||
*/
|
*/
|
||||||
toJSON() {
|
toJSON() {
|
||||||
return super.toJSON("deck")
|
return super.toJSON(Deck.type)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @override
|
* @override
|
||||||
*/
|
*/
|
||||||
trigger(socket) {
|
trigger(socket) {
|
||||||
if (typeof this.options === "string") {
|
if (this.options && typeof this.options.deck === "string") {
|
||||||
socket.emit("getDeck", {name: this.options, data: db.decks[this.options]});
|
socket.emit("getDeck", {name: this.options.deck, data: db.decks[this.options.deck]});
|
||||||
} else
|
} else
|
||||||
socket.emit("trigger", {error: "invalidOptions"});
|
socket.emit("trigger", {error: "invalidOptions"});
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,22 +2,38 @@ const Base = require("./Base");
|
||||||
const { exec } = require('child_process');
|
const { exec } = require('child_process');
|
||||||
|
|
||||||
class ExecCommand extends Base {
|
class ExecCommand extends Base {
|
||||||
|
static name = "Exec command";
|
||||||
|
static type = "execCommand";
|
||||||
|
static fields = {
|
||||||
|
cmd: {
|
||||||
|
type: "text",
|
||||||
|
name: "Command"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
constructor(text, image = null, options = null) {
|
constructor(text, image = null, options = null) {
|
||||||
super(text, image, options);
|
super(text, image, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
static staticToJSON() {
|
||||||
|
return super.staticToJSON(ExecCommand.name, ExecCommand.type, ExecCommand.fields);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @override
|
* @override
|
||||||
*/
|
*/
|
||||||
toJSON() {
|
toJSON() {
|
||||||
return super.toJSON("execCommand")
|
return super.toJSON(ExecCommand.type)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @override
|
* @override
|
||||||
*/
|
*/
|
||||||
trigger() {
|
trigger() {
|
||||||
exec(this.options, (err) => {
|
exec(this.options.cmd, (err) => {
|
||||||
if (err)
|
if (err)
|
||||||
console.error(err);
|
console.error(err);
|
||||||
});
|
});
|
||||||
|
|
28
views/settings/decks.pug
Normal file
28
views/settings/decks.pug
Normal file
|
@ -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")
|
10
views/settings/settings.pug
Normal file
10
views/settings/settings.pug
Normal file
|
@ -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
|
Loading…
Reference in a new issue