diff options
author | Tor Andersson <tor@ccxvii.net> | 2022-09-28 18:00:03 +0200 |
---|---|---|
committer | Tor Andersson <tor@ccxvii.net> | 2022-10-05 17:36:50 +0200 |
commit | f968d091d6c3f313054b88a048c63280dd73fd31 (patch) | |
tree | 71303a9bc09de62d58b0d4d539bfe330ac8f8b06 | |
parent | 868599b8e2df9e9a01aa93479371d24566f49126 (diff) | |
download | server-f968d091d6c3f313054b88a048c63280dd73fd31.tar.gz |
Add notepad.
-rw-r--r-- | public/common/play.css | 43 | ||||
-rw-r--r-- | public/common/play.js | 107 | ||||
-rw-r--r-- | schema.sql | 9 | ||||
-rw-r--r-- | server.js | 39 |
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")) @@ -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 @@ -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 |