summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--public/common/play.css43
-rw-r--r--public/common/play.js107
-rw-r--r--schema.sql9
-rw-r--r--server.js39
4 files changed, 180 insertions, 18 deletions
diff --git a/public/common/play.css b/public/common/play.css
index 314bf2a..02a667d 100644
--- a/public/common/play.css
+++ b/public/common/play.css
@@ -4,12 +4,12 @@ html {
image-rendering: -webkit-optimize-contrast; /* try to fix chromium's terrible image rescaling */
}
-html, button, input, select {
+html, button, input, select, textarea {
font-family: "Source Sans", "Circled Numbers", "Dingbats", "Noto Emoji", "Verdana", sans-serif;
font-size: 16px;
}
-#chat_text, #chat_input {
+#chat_text, #chat_input, #notepad_input {
font-family: "Source Serif", "Circled Numbers", "Dingbats", "Noto Emoji", "Georgia", serif;
}
@@ -301,10 +301,18 @@ header .replay button {
}
#chat_window {
- position: fixed;
left: 24px;
top: 68px;
width: 640px;
+}
+
+#notepad_window {
+ left: 60px;
+ top: 200px;
+}
+
+#chat_window, #notepad_window {
+ position: fixed;
z-index: 500;
border: 1px solid black;
background-color: white;
@@ -312,11 +320,11 @@ header .replay button {
visibility: hidden;
}
-#chat_window.show {
+#chat_window.show, #notepad_window.show {
visibility: visible;
}
-#chat_header {
+#chat_header, #notepad_header {
user-select: none;
cursor: move;
background-color: gainsboro;
@@ -324,7 +332,7 @@ header .replay button {
padding: 4px 8px;
}
-#chat_x {
+#chat_x, #notepad_x {
user-select: none;
cursor: pointer;
float: right;
@@ -332,12 +340,13 @@ header .replay button {
margin: 4px 4px;
}
-#chat_x:hover {
+#chat_x:hover, #notepad_x:hover {
background-color: black;
color: white;
}
-#chat_text {
+#chat_text, #notepad_input {
+ margin: 0;
font-size: 16px;
line-height: 24px;
height: 216px;
@@ -365,6 +374,20 @@ header .replay button {
font-size: 16px;
}
+#notepad_input {
+ outline: none;
+ border: none;
+ resize: none;
+}
+
+#notepad_footer {
+ display: flex;
+ justify-content: end;
+ padding: 8px;
+ background-color: gainsboro;
+ border-top: 1px solid black;
+}
+
/* MOBILE PHONE LAYOUT */
@media (max-width: 640px) {
@@ -399,7 +422,7 @@ header .replay button {
white-space: normal;
}
- #chat_window {
+ #chat_window, #notepad_window {
position: static;
grid-column: 1;
grid-row: 2;
@@ -408,7 +431,7 @@ header .replay button {
box-shadow: none;
border-top: none;
}
- #chat_window.show {
+ #chat_window.show, #notepad_window.show {
display: block;
}
diff --git a/public/common/play.js b/public/common/play.js
index 613d3a3..ba59d6f 100644
--- a/public/common/play.js
+++ b/public/common/play.js
@@ -177,8 +177,9 @@ function init_chat() {
}
}
if (e.key === "Enter") {
- let input = document.getElementById("chat_input")
- if (document.activeElement !== input) {
+ let chat_input = document.getElementById("chat_input")
+ let notepad_input = document.getElementById("notepad_input")
+ if (document.activeElement !== chat_input && document.activeElement !== notepad_input) {
e.preventDefault()
show_chat()
}
@@ -257,6 +258,83 @@ function toggle_chat() {
show_chat()
}
+/* NOTEPAD */
+
+let notepad = null
+
+function init_notepad() {
+ if (notepad !== null)
+ return
+
+ add_main_menu_item("Notepad", toggle_notepad)
+
+ let notepad_window = document.createElement("div")
+ notepad_window.id = "notepad_window"
+ notepad_window.innerHTML = `
+ <div id="notepad_x" onclick="toggle_notepad()">\u274c</div>
+ <div id="notepad_header">Notepad: ${player}</div>
+ <textarea id="notepad_input" cols="55" rows="20" maxlength="16000" oninput="dirty_notepad()"></textarea>
+ <div id="notepad_footer"><button id="notepad_save" onclick="save_notepad()" disabled>Save</button></div>
+ `
+ document.querySelector("body").appendChild(notepad_window)
+
+ notepad = {
+ is_visible: false,
+ is_dirty: false,
+ }
+
+ drag_element_with_mouse("#notepad_window", "#notepad_header")
+}
+
+function dirty_notepad() {
+ if (!notepad.is_dirty) {
+ notepad.is_dirty = true
+ document.getElementById("notepad_save").disabled = false
+ }
+}
+
+function save_notepad() {
+ if (notepad.is_dirty) {
+ let text = document.getElementById("notepad_input").value
+ send_message("putnote", text)
+ notepad.is_dirty = false
+ document.getElementById("notepad_save").disabled = true
+ }
+}
+
+function load_notepad() {
+ send_message("getnote")
+}
+
+function update_notepad(text) {
+ document.getElementById("notepad_input").value = text
+}
+
+function show_notepad() {
+ if (!notepad.is_visible) {
+ load_notepad()
+ document.getElementById("notepad_window").classList.add("show")
+ document.getElementById("notepad_input").focus()
+ notepad.is_visible = true
+ }
+}
+
+function hide_notepad() {
+ if (notepad.is_visible) {
+ save_notepad()
+ document.getElementById("notepad_window").classList.remove("show")
+ document.getElementById("notepad_input").blur()
+ notepad.is_visible = false
+ }
+}
+
+function toggle_notepad() {
+ if (notepad.is_visible)
+ hide_notepad()
+ else
+ show_notepad()
+}
+
/* REMATCH BUTTON */
function remove_resign_menu() {
@@ -361,13 +439,19 @@ function connect_play() {
update_chat(arg[0], arg[1], arg[2], arg[3])
break
+ case 'note':
+ update_notepad(arg)
+ break
+
case 'players':
player = arg[0]
document.querySelector("body").classList.add(player.replace(/ /g, "_"))
- if (player !== "Observer")
+ if (player !== "Observer") {
init_chat()
- else
+ init_notepad()
+ } else {
remove_resign_menu()
+ }
init_player_names(arg[1])
break
@@ -908,16 +992,23 @@ window.addEventListener("load", function () {
connect_play()
})
-function init_home_menu(link, text) {
+function init_main_menu() {
let popup = document.querySelector(".menu_popup")
let sep = document.createElement("div")
sep.className = "menu_separator"
+ sep.id = "main_menu_separator"
popup.insertBefore(sep, popup.firstChild)
+}
+
+function add_main_menu_item(text, onclick) {
+ let popup = document.querySelector(".menu_popup")
+ let sep = document.getElementById("main_menu_separator")
let item = document.createElement("div")
item.className = "menu_item"
- item.onclick = () => window.open(link, "_self")
+ item.onclick = onclick
item.textContent = text
- popup.insertBefore(item, popup.firstChild)
+ popup.insertBefore(item, sep)
}
-init_home_menu("/games/active", "Go Home")
+init_main_menu()
+add_main_menu_item("Go Home", () => window.open("/games/active", "_self"))
diff --git a/schema.sql b/schema.sql
index 39c0f35..28f32e3 100644
--- a/schema.sql
+++ b/schema.sql
@@ -286,6 +286,15 @@ create table if not exists game_replay (
create index if not exists game_replay_idx on game_replay(game_id);
+create table if not exists game_notes (
+ game_id integer
+ references games
+ on delete cascade,
+ role text,
+ note text,
+ primary key (game_id, role)
+) without rowid;
+
create table if not exists players (
game_id integer
references games
diff --git a/server.js b/server.js
index 9747f7c..3ec457d 100644
--- a/server.js
+++ b/server.js
@@ -988,6 +988,10 @@ const SQL_SELECT_USER_CHAT_N = SQL("SELECT game_id,time,name,message FROM game_c
const SQL_SELECT_GAME_CHAT = SQL("SELECT chat_id,time,name,message FROM game_chat_view WHERE game_id=? AND chat_id>?").raw()
const SQL_INSERT_GAME_CHAT = SQL("INSERT INTO game_chat (game_id,user_id,message) VALUES (?,?,?) RETURNING chat_id,time,'',message").raw()
+const SQL_SELECT_GAME_NOTE = SQL("SELECT note FROM game_notes WHERE game_id=? AND role=?").pluck()
+const SQL_UPDATE_GAME_NOTE = SQL("INSERT OR REPLACE INTO game_notes (game_id,role,note) VALUES (?,?,?)")
+const SQL_DELETE_GAME_NOTE = SQL("DELETE FROM game_notes WHERE game_id=? AND role=?")
+
const SQL_SELECT_GAME_STATE = SQL("SELECT state FROM game_state WHERE game_id=?").pluck()
const SQL_UPDATE_GAME_STATE = SQL("INSERT OR REPLACE INTO game_state (game_id,state,active,mtime) VALUES (?,?,?,datetime('now'))")
const SQL_UPDATE_GAME_RESULT = SQL("UPDATE games SET status=?, result=? WHERE game_id=?")
@@ -1898,6 +1902,35 @@ function on_resign(socket) {
}
}
+function on_getnote(socket) {
+ try {
+ let note = SQL_SELECT_GAME_NOTE.get(socket.game_id, socket.role)
+ if (note) {
+ SLOG(socket, "GETNOTE", note.length)
+ send_message(socket, 'note', note)
+ } else {
+ SLOG(socket, "GETNOTE null")
+ send_message(socket, 'note', "")
+ }
+ } catch (err) {
+ console.log(err)
+ return send_message(socket, 'error', err.toString())
+ }
+}
+
+function on_putnote(socket, note) {
+ try {
+ SLOG(socket, "PUTNOTE", note.length)
+ if (note.length > 0)
+ SQL_UPDATE_GAME_NOTE.run(socket.game_id, socket.role, note)
+ else
+ SQL_DELETE_GAME_NOTE.run(socket.game_id, socket.role)
+ } catch (err) {
+ console.log(err)
+ return send_message(socket, 'error', err.toString())
+ }
+}
+
function on_getchat(socket, seen) {
try {
let chat = SQL_SELECT_GAME_CHAT.all(socket.game_id, seen)
@@ -2015,6 +2048,12 @@ function handle_player_message(socket, cmd, arg) {
case "resign":
on_resign(socket)
break
+ case "getnote":
+ on_getnote(socket)
+ break
+ case "putnote":
+ on_putnote(socket, arg)
+ break
case "getchat":
on_getchat(socket, arg)
break