summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--public/common/play.js2
-rw-r--r--public/join.js72
-rw-r--r--public/style.css5
-rw-r--r--schema.sql1
-rw-r--r--server.js104
-rw-r--r--views/join.pug15
6 files changed, 143 insertions, 56 deletions
diff --git a/public/common/play.js b/public/common/play.js
index 054343c..964889b 100644
--- a/public/common/play.js
+++ b/public/common/play.js
@@ -343,7 +343,7 @@ function remove_resign_menu() {
}
function goto_rematch() {
- window.location = "/rematch/" + params.game_id + "/" + params.role
+ window.location = "/rematch/" + params.game_id
}
function goto_replay() {
diff --git a/public/join.js b/public/join.js
index 5bb8b9c..72da646 100644
--- a/public/join.js
+++ b/public/join.js
@@ -3,6 +3,7 @@
let start_status = game.status
let evtsrc = null
let timer = 0
+let invite_role = null
function confirm_delete() {
let warning = `Are you sure you want to DELETE this game?`
@@ -39,6 +40,25 @@ function kick(role) {
post(`/part/${game.game_id}/${encodeURIComponent(role)}`)
}
+function accept(role) {
+ post(`/accept/${game.game_id}/${encodeURIComponent(role)}`)
+}
+
+function send_invite() {
+ let invite_user = document.getElementById("invite_user").value
+ post(`/invite/${game.game_id}/${encodeURIComponent(invite_role)}/${encodeURIComponent(invite_user)}`)
+ document.getElementById("invite").close()
+}
+
+function show_invite(role) {
+ invite_role = role
+ document.getElementById("invite").showModal()
+}
+
+function hide_invite() {
+ document.getElementById("invite").close()
+}
+
let blink_title = document.title
let blink_timer = 0
@@ -115,6 +135,14 @@ function is_solo() {
return players.every(p => p.user_id === players[0].user_id)
}
+function play_link(player) {
+ return `<a href="/${game.title_id}/play.html?game=${game.game_id}&role=${encodeURIComponent(player.role)}">${player.name}</a>`
+}
+
+function action_link(player, action, color, text) {
+ return `<a class="${color}" href="javascript:${action}('${player.role}')">${text}</a>`
+}
+
function update() {
for (let i = 0; i < roles.length; ++i) {
let role = roles[i]
@@ -125,29 +153,47 @@ function update() {
let player = players.find(p => p.role === role)
let element = document.getElementById(role_id)
if (player) {
+ element.classList.remove("is_invite")
switch (game.status) {
case 2:
if (player.user_id === user_id)
- element.innerHTML = `<a href="/${game.title_id}/play.html?game=${game.game_id}&role=${encodeURIComponent(role)}">${player.name}</a>`
+ element.innerHTML = play_link(player)
else
element.innerHTML = player.name
break
case 1:
element.classList.toggle("is_active", is_active(player, role))
if (player.user_id === user_id)
- element.innerHTML = `<a href="/${game.title_id}/play.html?game=${game.game_id}&role=${encodeURIComponent(role)}">${player.name}</a><a class="red" href="javascript:part('${role}')">\u274c</a>`
+ element.innerHTML = play_link(player) + action_link(player, "part", "red", "\u274c")
else if (game.owner_id === user_id)
- element.innerHTML = `${player.name}<a class="red" href="javascript:kick('${role}')">\u274c</a>`
+ element.innerHTML = player.name + action_link(player, "kick", "red", "\u274c")
else
element.innerHTML = player.name
break
case 0:
- if (player.user_id === user_id)
- element.innerHTML = `${player.name}<a class="red" href="javascript:part('${role}')">\u274c</a>`
- else if (game.owner_id === user_id)
- element.innerHTML = `${player.name}<a class="red" href="javascript:kick('${role}')">\u274c</a>`
- else
- element.innerHTML = player.name
+ if (player.is_invite) {
+ element.classList.add("is_invite")
+ if (player.user_id === user_id)
+ element.innerHTML = player.name + " ?" +
+ action_link(player, "part", "red", "\u274c") +
+ action_link(player, "accept", "green", "\u2714")
+ else if (player.user_id === user_id)
+ element.innerHTML = player.name + " ?" + action_link(player, "part", "red", "\u274c")
+ else if (game.owner_id === user_id)
+ element.innerHTML = player.name + " ?" + action_link(player, "kick", "red", "\u274c")
+ else
+ element.innerHTML = player.name + " ?"
+ } else {
+ element.classList.remove("is_invite")
+ if (player.user_id === user_id && player.is_invite)
+ element.innerHTML = player.name + action_link(player, "part", "red", "\u274c")
+ else if (player.user_id === user_id)
+ element.innerHTML = player.name + action_link(player, "part", "red", "\u274c")
+ else if (game.owner_id === user_id)
+ element.innerHTML = player.name + action_link(player, "kick", "red", "\u274c")
+ else
+ element.innerHTML = player.name
+ }
break
}
element.classList.toggle("friend", is_friend(player))
@@ -157,14 +203,20 @@ function update() {
else
element.title = ""
} else {
+ element.classList.remove("is_invite")
switch (game.status) {
case 2:
element.innerHTML = `<i>Empty</i>`
break
case 1:
- case 0:
element.innerHTML = `<a class="join" href="javascript:join('${role}')">Join</a>`
break
+ case 0:
+ if (game.owner_id === user_id)
+ element.innerHTML = `<a class="join" href="javascript:join('${role}')">Join</a><a class="green" href="javascript:show_invite('${role}')">\u{2795}</a>`
+ else
+ element.innerHTML = `<a class="join" href="javascript:join('${role}')">Join</a>`
+ break
}
element.classList.remove("friend")
element.classList.remove("enemy")
diff --git a/public/style.css b/public/style.css
index c4b0b9e..e530ef3 100644
--- a/public/style.css
+++ b/public/style.css
@@ -1,9 +1,9 @@
html, input, textarea {
- font-family: "Source Serif", "Georgia", "Dingbats", "Noto Emoji", serif;
+ font-family: "Source Serif", "Georgia", "Noto Emoji", "Dingbats", serif;
font-size: 16px;
}
button, select {
- font-family: "Source Sans", "Verdana", "Dingbats", "Noto Emoji", sans-serif;
+ font-family: "Source Sans", "Verdana", "Noto Emoji", "Dingbats", sans-serif;
font-size: 16px;
}
@@ -156,6 +156,7 @@ article hr + p { font-style: italic; }
.game_item a:not(:hover) { text-decoration: none; color: black; }
.game_item a.command { text-decoration: underline; font-weight: bold }
.game_info .is_active { text-decoration: underline }
+.game_info .is_invite { opacity: 0.5 }
.game_info div {
text-indent: -20px;
padding-left: 20px;
diff --git a/schema.sql b/schema.sql
index e2ca8a4..a3c0e2b 100644
--- a/schema.sql
+++ b/schema.sql
@@ -322,6 +322,7 @@ create table if not exists players (
game_id integer,
role text,
user_id integer,
+ is_invite integer,
primary key (game_id, role)
) without rowid;
diff --git a/server.js b/server.js
index 6ca6692..9942cd7 100644
--- a/server.js
+++ b/server.js
@@ -267,6 +267,7 @@ const SQL_SELECT_LOGIN_BY_NAME = SQL("SELECT * FROM user_login_view WHERE name=?
const SQL_SELECT_USER_PROFILE = SQL("SELECT * FROM user_profile_view WHERE name=?")
const SQL_SELECT_USER_DYNAMIC = SQL("select * from user_dynamic_view where user_id=?")
const SQL_SELECT_USER_NAME = SQL("SELECT name FROM users WHERE user_id=?").pluck()
+const SQL_SELECT_USER_ID = SQL("SELECT user_id FROM users WHERE name=?").pluck()
const SQL_OFFLINE_USER = SQL("SELECT * FROM user_view NATURAL JOIN user_last_seen WHERE user_id=? AND julianday() > atime + ?")
@@ -1054,6 +1055,9 @@ function get_game_roles(title_id, scenario, options) {
}
function is_game_ready(title_id, scenario, options, players) {
+ for (let p of players)
+ if (p.is_invite)
+ return false
return get_game_roles(title_id, scenario, options).length === players.length
}
@@ -1093,11 +1097,12 @@ const SQL_SELECT_GAME_HAS_TITLE_AND_STATUS = SQL("SELECT 1 FROM games WHERE game
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_PLAYERS_JOIN = SQL("SELECT role, user_id, name, is_invite FROM players NATURAL JOIN users WHERE game_id=?")
+const SQL_UPDATE_PLAYER_ACCEPT = SQL("UPDATE players SET is_invite=0 WHERE game_id=? AND role=? AND user_id=?")
+const SQL_UPDATE_PLAYER_ROLE = SQL("UPDATE players SET role=? WHERE game_id=? AND role=? AND user_id=?")
const SQL_SELECT_PLAYER_ROLE = SQL("SELECT role FROM players WHERE game_id=? AND user_id=?").pluck()
-const SQL_INSERT_PLAYER_ROLE = SQL("INSERT OR IGNORE INTO players (game_id,role,user_id) VALUES (?,?,?)")
+const SQL_INSERT_PLAYER_ROLE = SQL("INSERT OR IGNORE INTO players (game_id,role,user_id,is_invite) VALUES (?,?,?,?)")
const SQL_DELETE_PLAYER_ROLE = SQL("DELETE FROM players WHERE game_id=? AND role=?")
-const SQL_UPDATE_PLAYER_ROLE = SQL("UPDATE players SET role=? WHERE game_id=? AND role=? AND user_id=?")
const SQL_AUTHORIZE_GAME_ROLE = SQL("SELECT 1 FROM players NATURAL JOIN games WHERE title_id=? AND game_id=? AND role=? AND user_id=?").pluck()
@@ -1109,13 +1114,15 @@ const SQL_INSERT_REMATCH = SQL(`
INSERT INTO games
(owner_id, title_id, scenario, options, is_private, is_random, description)
SELECT
- $user_id, title_id, scenario, options, is_private, is_random, $magic
+ $user_id, title_id, scenario, options, is_private, 0, $magic
FROM games
WHERE game_id = $game_id AND NOT EXISTS (
SELECT * FROM games WHERE description=$magic
)
`)
+const SQL_INSERT_REMATCH_PLAYERS = SQL("insert into players (game_id, user_id, role, is_invite) select ?, user_id, role, user_id!=? from players where game_id=?")
+
const QUERY_LIST_PUBLIC_GAMES = SQL(`
SELECT * FROM game_view
WHERE is_private=0 AND status = ?
@@ -1224,10 +1231,14 @@ function annotate_game(game, user_id, unread) {
your_count++
if ((p_is_active || p_is_owner) && game.is_ready)
game.your_turn = true
+ if (p.is_invite)
+ game.your_turn = true
}
let link
- if (p_is_active || p_is_owner)
+ if (p.is_invite)
+ link = `<span class="is_invite"><a href="/user/${p.name}">${p.name}?</a></span>`
+ else if (p_is_active || p_is_owner)
link = `<span class="is_active"><a href="/user/${p.name}">${p.name}</a></span>`
else
link = `<a href="/user/${p.name}">${p.name}</a>`
@@ -1403,44 +1414,19 @@ app.get('/delete/:game_id', must_be_logged_in, function (req, res) {
res.redirect('/'+title_id)
})
-function join_rematch(req, res, game_id, role) {
- try {
- let is_random = SQL_SELECT_GAME_RANDOM.get(game_id)
- if (is_random) {
- let role = SQL_SELECT_PLAYER_ROLE.get(game_id, req.user.user_id)
- if (!role) {
- for (let i = 1; i <= 6; ++i) {
- let info = SQL_INSERT_PLAYER_ROLE.run(game_id, 'Random ' + i, req.user.user_id)
- if (info.changes === 1) {
- update_join_clients_players(game_id)
- break
- }
- }
- }
- } else {
- let info = SQL_INSERT_PLAYER_ROLE.run(game_id, role, req.user.user_id)
- if (info.changes === 1)
- update_join_clients_players(game_id)
- }
- } catch (err) {
- console.log(err)
- }
- return res.redirect('/join/'+game_id)
-}
-
-app.get('/rematch/:old_game_id/:role', must_be_logged_in, function (req, res) {
+app.get('/rematch/:old_game_id', must_be_logged_in, function (req, res) {
let old_game_id = req.params.old_game_id | 0
- let role = req.params.role
let magic = "\u{1F503} " + old_game_id
let new_game_id = 0
+ console.log("FOO", old_game_id, magic)
let info = SQL_INSERT_REMATCH.run({user_id: req.user.user_id, game_id: old_game_id, magic: magic})
- if (info.changes === 1)
+ if (info.changes === 1) {
new_game_id = info.lastInsertRowid
- else
+ SQL_INSERT_REMATCH_PLAYERS.run(new_game_id, req.user.user_id, old_game_id)
+ } else {
new_game_id = SQL_SELECT_REMATCH.get(magic)
- if (new_game_id)
- return join_rematch(req, res, new_game_id, role)
- return res.status(404).send("Can't create or find rematch game!")
+ }
+ return res.redirect('/join/'+new_game_id)
})
var join_clients = {}
@@ -1499,9 +1485,12 @@ app.get('/join/:game_id', must_be_logged_in, function (req, res) {
let players = SQL_SELECT_PLAYERS_JOIN.all(game_id)
let whitelist = SQL_SELECT_CONTACT_WHITELIST.all(req.user.user_id)
let blacklist = SQL_SELECT_CONTACT_BLACKLIST.all(req.user.user_id)
+ let friends = null
+ if (game.owner_id === req.user.user_id)
+ friends = SQL_SELECT_CONTACT_FRIEND_NAMES.all(req.user.user_id)
let ready = (game.status === 0) && is_game_ready(game.title_id, game.scenario, game.options, players)
res.render('join.pug', {
- user: req.user, game, roles, players, ready, whitelist, blacklist
+ user: req.user, game, roles, players, ready, whitelist, blacklist, friends
})
})
@@ -1539,9 +1528,7 @@ app.get('/join-events/:game_id', must_be_logged_in, function (req, res) {
res.write("data: " + JSON.stringify(players) + "\n\n")
})
-app.post('/join/:game_id/:role', must_be_logged_in, function (req, res) {
- let game_id = req.params.game_id | 0
- let role = req.params.role
+function do_join(res, game_id, role, user_id, is_invite) {
let game = SQL_SELECT_GAME.get(game_id)
let roles = get_game_roles(game.title_id, game.scenario, game.options)
if (game.is_random && game.status === 0) {
@@ -1552,12 +1539,43 @@ app.post('/join/:game_id/:role', must_be_logged_in, function (req, res) {
if (!roles.includes(role))
return res.status(404).send("Invalid role.")
}
- let info = SQL_INSERT_PLAYER_ROLE.run(game_id, role, req.user.user_id)
+ let info = SQL_INSERT_PLAYER_ROLE.run(game_id, role, user_id, is_invite)
+ if (info.changes === 1) {
+ update_join_clients_players(game_id)
+ res.send("SUCCESS")
+ } else {
+ if (is_invite)
+ res.send("Could not invite.")
+ else
+ res.send("Could not join game.")
+ }
+}
+
+app.post('/join/:game_id/:role', must_be_logged_in, function (req, res) {
+ let game_id = req.params.game_id | 0
+ let role = req.params.role
+ do_join(res, game_id, role, req.user.user_id, 0)
+})
+
+app.post('/invite/:game_id/:role/:user', must_be_logged_in, function (req, res) {
+ let game_id = req.params.game_id | 0
+ let role = req.params.role
+ let user_id = SQL_SELECT_USER_ID.get(req.params.user)
+ if (user_id)
+ do_join(res, game_id, role, user_id, 1)
+ else
+ res.send("User not found.")
+})
+
+app.post('/accept/:game_id/:role', must_be_logged_in, function (req, res) {
+ let game_id = req.params.game_id | 0
+ let role = req.params.role
+ let info = SQL_UPDATE_PLAYER_ACCEPT.run(game_id, role, req.user.user_id)
if (info.changes === 1) {
update_join_clients_players(game_id)
res.send("SUCCESS")
} else {
- res.send("Could not join game.")
+ res.send("Could not accept invite.")
}
})
diff --git a/views/join.pug b/views/join.pug
index 90f0db7..abbaf2c 100644
--- a/views/join.pug
+++ b/views/join.pug
@@ -11,11 +11,13 @@ html
table { min-width: 0; }
th,td { border: 1px solid black; }
td a.red { text-decoration: none; color: brown; font-size: 15px; float: right; }
+ td a.green { text-decoration: none; color: green; font-size: 15px; float: right; }
td a { text-decoration: underline; color: blue; }
th { white-space: nowrap; background-color: gainsboro; }
td { width: 180px; background-color: white; }
#message { background-color: whitesmoke; }
.hide { display: none; }
+ td.is_invite { color: gray }
td.enemy { background-color: #f66 }
td.enemy::before { content: "\1f6ab "; color: #000; font-size: 15px; }
script.
@@ -25,6 +27,7 @@ html
let user_id = !{ user.user_id }
let whitelist = !{ JSON.stringify(whitelist) }
let blacklist = !{ JSON.stringify(blacklist) }
+ let friends = !{ JSON.stringify(friends) }
let ready = !{ ready }
script(src="/join.js")
body
@@ -50,6 +53,18 @@ html
br(clear="left")
+ dialog(id="invite")
+ | Invite a friend:
+ br
+ input(id="invite_user" type="text" list="friends" onchange="send_invite()")
+ datalist(id="friends")
+ if friends
+ each who in friends
+ option= who
+ br
+ button(onclick="send_invite()") Invite
+ button(onclick="hide_invite()") Cancel
+
p
table
tbody