diff options
author | Tor Andersson <tor@ccxvii.net> | 2022-10-05 16:18:59 +0200 |
---|---|---|
committer | Tor Andersson <tor@ccxvii.net> | 2022-10-05 17:36:50 +0200 |
commit | d599d78732c0e7b312e6e366c27577a45edf856e (patch) | |
tree | 4f4f7a84a568304a24b8249e113e1f2236113d34 | |
parent | a7a26d847be77d0c9007b3e9766a83cfa3b34876 (diff) | |
download | server-d599d78732c0e7b312e6e366c27577a45edf856e.tar.gz |
Track read status of in-game chat messages.
-rw-r--r-- | public/style.css | 2 | ||||
-rw-r--r-- | schema.sql | 8 | ||||
-rw-r--r-- | server.js | 74 | ||||
-rw-r--r-- | views/head.pug | 1 |
4 files changed, 69 insertions, 16 deletions
diff --git a/public/style.css b/public/style.css index 65c92df..68346bb 100644 --- a/public/style.css +++ b/public/style.css @@ -193,3 +193,5 @@ article hr + p { font-style: italic; } .game_item.your_turn .game_head { background-color: gold } .game_item.your_turn .game_main { background-color: lightyellow } .game_item.your_turn a:hover { color: #860 } + +.game_item.unread .game_head div::after { content: " \1f4dd "; } @@ -244,6 +244,12 @@ create table if not exists game_chat ( message text ); +create table if not exists unread_chats ( + user_id integer, + game_id integer, + primary key (user_id, game_id) +) without rowid; + drop view if exists game_chat_view; create view game_chat_view as select @@ -379,6 +385,7 @@ begin delete from game_chat where game_id = old.game_id; delete from game_replay where game_id = old.game_id; delete from last_notified where game_id = old.game_id; + delete from unread_chats where game_id = old.game_id; delete from players where game_id = old.game_id; end; @@ -390,6 +397,7 @@ begin delete from user_last_seen where user_id = old.user_id; delete from last_notified where user_id = old.user_id; delete from read_threads where user_id = old.user_id; + delete from unread_chats where user_id = old.user_id; delete from contacts where me = old.user_id or you = old.user_id; delete from messages where from_id = old.user_id or to_id = old.user_id; delete from posts where author_id = old.user_id; @@ -75,6 +75,24 @@ function SQL(s) { return db.prepare(s) } +function set_has(set, item) { + if (!set) + return false + let a = 0 + let b = set.length - 1 + while (a <= b) { + let m = (a + b) >> 1 + let x = set[m] + if (item < x) + b = m - 1 + else if (item > x) + a = m + 1 + else + return true + } + return false +} + /* * Notification mail setup. */ @@ -655,7 +673,7 @@ app.get('/user/:who_name', function (req, res) { who.ctime = human_date(who.ctime) who.atime = human_date(who.atime) let games = QUERY_LIST_ACTIVE_GAMES_OF_USER.all({ user_id: who.user_id }) - annotate_games(games, 0) + annotate_games(games, 0, null) let relation = 0 if (req.user) relation = SQL_SELECT_RELATION.get(req.user.user_id, who.user_id) | 0 @@ -1084,6 +1102,10 @@ const SQL_DELETE_GAME = SQL("DELETE FROM games WHERE game_id=? AND owner_id=?") const SQL_SELECT_USER_CHAT = SQL("SELECT game_id,unixepoch(time),name,message FROM game_chat_view WHERE game_id IN ( SELECT DISTINCT game_id FROM players WHERE user_id=? ) ORDER BY chat_id DESC").raw() const SQL_SELECT_USER_CHAT_N = SQL("SELECT game_id,unixepoch(time),name,message FROM game_chat_view WHERE game_id IN ( SELECT DISTINCT game_id FROM players WHERE user_id=? ) ORDER BY chat_id DESC LIMIT ?").raw() +const SQL_SELECT_UNREAD_CHAT_GAMES = SQL("select game_id from unread_chats where user_id = ?").pluck() +const SQL_INSERT_UNREAD_CHAT = SQL("insert or ignore into unread_chats (user_id,game_id) values (?,?)") +const SQL_DELETE_UNREAD_CHAT = SQL("delete from unread_chats where user_id = ? and game_id = ?") + const SQL_SELECT_GAME_CHAT = SQL("SELECT chat_id,unixepoch(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,unixepoch(time),NULL,message").raw() @@ -1106,6 +1128,7 @@ const SQL_SELECT_GAME_RANDOM = SQL("SELECT is_random FROM games WHERE game_id=?" const SQL_SELECT_GAME_HAS_TITLE_AND_STATUS = SQL("SELECT 1 FROM games WHERE game_id=? AND title_id=? AND status=?") +const SQL_SELECT_PLAYERS_ID = SQL("SELECT DISTINCT user_id FROM players WHERE game_id=?").pluck() const SQL_SELECT_PLAYERS = SQL("SELECT * FROM players NATURAL JOIN user_view WHERE game_id=?") const SQL_SELECT_PLAYERS_JOIN = SQL("SELECT role, user_id, name FROM players NATURAL JOIN users WHERE game_id=?") const SQL_SELECT_PLAYER_ROLE = SQL("SELECT role FROM players WHERE game_id=? AND user_id=?").pluck() @@ -1201,7 +1224,7 @@ function format_options(options) { return Object.entries(options||{}).map(([k,v]) => (v === true || v === 1) ? to_english(k) : `${to_english(k)}=${to_english(v)}`).join(", ") } -function annotate_game(game, user_id) { +function annotate_game(game, user_id, unread) { let players = SQL_SELECT_PLAYERS_JOIN.all(game.game_id) let options = JSON.parse(game.options) let roles = get_game_roles(game.title_id, game.scenario, options) @@ -1216,6 +1239,7 @@ function annotate_game(game, user_id) { players.sort((a, b) => a.index - b.index) game.is_ready = is_game_ready(game.title_id, game.scenario, options, players) + game.is_unread = set_has(unread, game.game_id) let your_count = 0 let your_role = null @@ -1264,9 +1288,9 @@ function annotate_game(game, user_id) { game.mtime = human_date(game.mtime) } -function annotate_games(games, user_id) { +function annotate_games(games, user_id, unread) { for (let i = 0; i < games.length; ++i) - annotate_game(games[i], user_id) + annotate_game(games[i], user_id, unread) } app.get('/profile', must_be_logged_in, function (req, res) { @@ -1294,22 +1318,24 @@ app.get('/games/next', must_be_logged_in, function (req, res) { app.get('/games/active', must_be_logged_in, function (req, res) { let games = QUERY_LIST_ACTIVE_GAMES_OF_USER.all({ user_id: req.user.user_id }) - annotate_games(games, req.user.user_id) + let unread = SQL_SELECT_UNREAD_CHAT_GAMES.all(req.user.user_id) + annotate_games(games, req.user.user_id, unread) games.sort(sort_your_turn) - res.render('games_active.pug', { user: req.user, who: req.user, games: games }) + res.render('games_active.pug', { user: req.user, who: req.user, games }) }) app.get('/games/finished', must_be_logged_in, function (req, res) { let games = QUERY_LIST_FINISHED_GAMES_OF_USER.all({user_id: req.user.user_id}) - annotate_games(games, req.user.user_id) - res.render('games_finished.pug', { user: req.user, who: req.user, games: games }) + let unread = SQL_SELECT_UNREAD_CHAT_GAMES.all(req.user.user_id) + annotate_games(games, req.user.user_id, unread) + res.render('games_finished.pug', { user: req.user, who: req.user, games }) }) app.get('/games/finished/:who_name', function (req, res) { let who = SQL_SELECT_USER_BY_NAME.get(req.params.who_name) if (who) { let games = QUERY_LIST_FINISHED_GAMES_OF_USER.all({ user_id: who.user_id }) - annotate_games(games, 0) + annotate_games(games, 0, null) res.render('games_finished.pug', { user: req.user, who: who, games: games }) } else { return res.status(404).send("Invalid user name.") @@ -1318,11 +1344,13 @@ app.get('/games/finished/:who_name', function (req, res) { app.get('/games/public', function (req, res) { let games = QUERY_LIST_PUBLIC_GAMES.all() - if (req.user) - annotate_games(games, req.user.user_id) - else - annotate_games(games, 0) - res.render('games_public.pug', { user: req.user, games: games }) + if (req.user) { + let unread = SQL_SELECT_UNREAD_CHAT_GAMES.all(req.user.user_id) + annotate_games(games, req.user.user_id, unread) + } else { + annotate_games(games, 0, null) + } + res.render('games_public.pug', { user: req.user, games }) }) app.get('/info/:title_id', function (req, res) { @@ -1333,10 +1361,13 @@ function get_title_page(req, res, title_id) { let title = TITLES[title_id] if (!title) return res.status(404).send("Invalid title.") + let unread = null + if (req.user) + unread = SQL_SELECT_UNREAD_CHAT_GAMES.all(req.user.user_id) let active_games = QUERY_LIST_GAMES_OF_TITLE.all(title_id, 0, 1, 1000) let finished_games = QUERY_LIST_GAMES_OF_TITLE.all(title_id, 2, 2, 50) - annotate_games(active_games, req.user ? req.user.user_id : 0) - annotate_games(finished_games, req.user ? req.user.user_id : 0) + annotate_games(active_games, req.user ? req.user.user_id : 0, unread) + annotate_games(finished_games, req.user ? req.user.user_id : 0, unread) res.render('info.pug', { user: req.user, title: title, @@ -2058,6 +2089,7 @@ function on_getchat(socket, seen) { SLOG(socket, "GETCHAT", seen, chat.length) for (let i = 0; i < chat.length; ++i) send_message(socket, 'chat', chat[i]) + SQL_DELETE_UNREAD_CHAT.run(socket.user.user_id, socket.game_id) } catch (err) { console.log(err) return send_message(socket, 'error', err.toString()) @@ -2073,6 +2105,16 @@ function on_chat(socket, message) { for (let other of game_clients[socket.game_id]) if (other.role !== "Observer") send_message(other, 'chat', chat) + + let users = SQL_SELECT_PLAYERS_ID.all(socket.game_id) + for (let user_id of users) { + let found = false + for (let other of game_clients[socket.game_id]) + if (other.user.user_id === user_id) + found = true + if (!found) + SQL_INSERT_UNREAD_CHAT.run(user_id, socket.game_id) + } } catch (err) { console.log(err) return send_message(socket, 'error', err.toString()) diff --git a/views/head.pug b/views/head.pug index 4df5759..6ea9285 100644 --- a/views/head.pug +++ b/views/head.pug @@ -44,6 +44,7 @@ mixin gamelist(list,hide_title=0) - let className = "game_item" if (item.your_turn) className += " your_turn" + if (item.is_unread) className += " unread" if (item.status === 0 && !item.is_ready) className += " open" else if (item.status === 0 && item.is_ready) className += " ready" else if (item.status === 1 && !item.is_ready) className += " replacement" |