diff options
-rw-r--r-- | connect-better-sqlite3.js | 2 | ||||
-rw-r--r-- | public/common/client.js | 38 | ||||
-rw-r--r-- | public/join.js | 11 | ||||
-rw-r--r-- | server.js | 86 | ||||
-rw-r--r-- | views/games.ejs | 44 | ||||
-rw-r--r-- | views/info.ejs | 74 | ||||
-rw-r--r-- | views/join.ejs | 16 | ||||
-rw-r--r-- | views/profile.ejs | 55 |
8 files changed, 193 insertions, 133 deletions
diff --git a/connect-better-sqlite3.js b/connect-better-sqlite3.js index 008114b..6574346 100644 --- a/connect-better-sqlite3.js +++ b/connect-better-sqlite3.js @@ -31,7 +31,7 @@ module.exports = function (session) { let db = new SQLite(db_path, options.mode); db.pragma("journal_mode = WAL"); db.pragma("synchronous = OFF"); - db.exec("CREATE TABLE IF NOT EXISTS "+table+" (sid PRIMARY KEY, expires INTEGER, sess TEXT)"); + db.exec("CREATE TABLE IF NOT EXISTS "+table+" (sid PRIMARY KEY, expires INTEGER, sess TEXT) WITHOUT ROWID"); db.exec("DELETE FROM "+table+" WHERE "+now()+" > expires"); db.exec("VACUUM"); db.exec("PRAGMA wal_checkpoint(TRUNCATE)"); diff --git a/public/common/client.js b/public/common/client.js index 1e31f79..ccc9718 100644 --- a/public/common/client.js +++ b/public/common/client.js @@ -2,6 +2,16 @@ /* global io, on_update */ +/* URL: /$title_id/play:$game_id:$role */ +if (!/\/[\w-]+\/play:\d+(:[\w-]+)?/.test(window.location.pathname)) { + document.querySelector("#prompt").textContent = "Invalid game ID."; + throw Error("Invalid game ID."); +} + +const param_title_id = window.location.pathname.split("/")[1]; +const param_game_id = window.location.pathname.split("/")[2].split(":")[1] | 0; +const param_role = window.location.pathname.split("/")[2].split(":")[2] || "Observer"; + let game = null; let game_over = false; let player = null; @@ -105,8 +115,8 @@ function stop_blinker() { window.addEventListener("focus", stop_blinker); -function load_chat(game_id) { - chat_key = "chat/" + game_id; +function load_chat() { + chat_key = "chat/" + param_game_id; chat_text = document.querySelector(".chat_text"); chat_last_day = null; chat_log = 0; @@ -158,11 +168,6 @@ function update_chat(chat_id, utc_date, user, message) { } function init_client(roles) { - let params = new URLSearchParams(window.location.search); - let title = window.location.pathname.split("/")[1]; - let game_id = params.get("game"); - let role = params.get("role"); - game = null; player = null; @@ -186,13 +191,13 @@ function init_client(roles) { ".role.seven .role_user", ]; - load_chat(game_id); + load_chat(); - console.log("JOINING", title + "/" + game_id + "/" + role); + console.log("JOINING", param_title_id + "/" + param_game_id + "/" + param_role); socket = io({ transports: ['websocket'], - query: { title: title, game: game_id, role: role }, + query: { title: param_title_id, game: param_game_id, role: param_role }, }); socket.on('connect', () => { @@ -242,7 +247,7 @@ function init_client(roles) { socket.on('save', (msg) => { console.log("SAVE"); - window.localStorage[title + '/save'] = msg; + window.localStorage[param_title_id + '/save'] = msg; }); socket.on('error', (msg) => { @@ -446,8 +451,7 @@ function send_save() { } function send_restore() { - let title = window.location.pathname.split("/")[1]; - socket.emit('restore', window.localStorage[title + '/save']); + socket.emit('restore', window.localStorage[param_title_id + '/save']); } function send_restart(scenario) { @@ -474,13 +478,9 @@ function on_game_over() { } function send_rematch() { - let params = new URLSearchParams(window.location.search); - let game_id = params.get("game"); - let role_id = params.get("role"); - window.location = '/rematch/' + game_id + '/' + role_id; + window.location = '/rematch/' + param_game_id + '/' + param_role; } function send_exit() { - let title = window.location.pathname.split("/")[1]; - window.location = '/info/' + title; + window.location = '/info/' + param_title_id; } diff --git a/public/join.js b/public/join.js index ce3d0d0..898113b 100644 --- a/public/join.js +++ b/public/join.js @@ -60,7 +60,7 @@ function start_event_source() { evtsrc.addEventListener("game", function (evt) { console.log("GAME:", evt.data); game = JSON.parse(evt.data); - if (game.status > 1) { + if (game.status > 0) { clearInterval(timer); evtsrc.close(); } @@ -85,9 +85,6 @@ function is_solo() { } function update() { - window.game_status.textContent = ["Open","Active","Finished","Abandoned"][game.status]; - window.game_result.textContent = game.result || "\u2014"; - for (let i = 0; i < roles.length; ++i) { let role = roles[i]; let role_id = "role_" + role.replace(/ /g, "_"); @@ -103,7 +100,7 @@ function update() { else element.className = ""; if (player.user_id === user_id) - element.innerHTML = `<a href="/play/${game.game_id}/${role}">Play</a>`; + element.innerHTML = `<a href="/${game.title_id}/play:${game.game_id}:${role}">Play</a>`; else element.innerHTML = player.name; } else { @@ -129,7 +126,7 @@ function update() { else message.innerHTML = "Waiting for players to join..."; } else { - message.innerHTML = `<a class="play" href="/play/${game.game_id}/Observer">Observe</a>`; + message.innerHTML = `<a href="/${game.title_id}/play:${game.game_id}">Observe</a>`; } if (game.owner_id === user_id) { @@ -150,7 +147,7 @@ function update() { window.onload = function () { update(); - if (game.status < 2) { + if (game.status === 0) { start_event_source(); timer = setInterval(start_event_source, 15000); } @@ -907,7 +907,9 @@ const SQL_SELECT_PLAYERS_JOIN = SQL("SELECT role, user_id, name FROM players NAT 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_DELETE_PLAYER_ROLE = SQL("DELETE FROM players WHERE game_id=? AND role=?"); -const SQL_UPDATE_PLAYER_ROLE = db.prepare("UPDATE players SET role=? 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_AUTHORIZE_GAME_ROLE = SQL("SELECT 1 FROM players NATURAL JOIN games WHERE title_id=? AND game_id=? AND role=? AND user_id=?").pluck(); const SQL_SELECT_OPEN_GAMES = db.prepare("SELECT * FROM games WHERE status=0"); const SQL_COUNT_OPEN_GAMES = SQL("SELECT COUNT(*) FROM games WHERE owner_id=? AND status=0").pluck(); @@ -927,12 +929,14 @@ const SQL_INSERT_REMATCH = SQL(` const QUERY_LIST_GAMES = SQL(` SELECT * FROM game_view WHERE private=0 AND status=? + AND EXISTS ( SELECT 1 FROM players WHERE players.game_id = game_view.game_id AND user_id = game_view.owner_id ) ORDER BY mtime DESC `); const QUERY_LIST_GAMES_OF_TITLE = SQL(` SELECT * FROM game_view WHERE private=0 AND title_id=? AND status=? + AND EXISTS ( SELECT 1 FROM players WHERE players.game_id = game_view.game_id AND user_id = game_view.owner_id ) ORDER BY mtime DESC LIMIT ? `); @@ -967,24 +971,44 @@ function is_solo(players) { return players.every(p => p.user_id === players[0].user_id) } -function annotate_games(games, user_id) { - for (let i = 0; i < games.length; ++i) { - let game = games[i]; - let players = SQL_SELECT_PLAYERS_JOIN.all(game.game_id); - game.player_names = players.map(p => { - let name = p.name.replace(/ /g, '\xa0'); - return p.user_id > 0 ? `<a href="/user/${p.name}">${name}</a>` : name; - }).join(", "); - game.is_active = is_active(game, players, user_id); - game.is_shared = is_shared(game, players, user_id); - game.is_yours = false; - if (user_id > 0) - for (let i = 0; i < players.length; ++i) - if (players[i].user_id === user_id) - game.is_yours = 1; - game.ctime = human_date(game.ctime); - game.mtime = human_date(game.mtime); +function format_options(options) { + function to_english(k) { + if (k === true) return 'yes'; + if (k === false) return 'no'; + return k.replace(/_/g, " ").replace(/^\w/, c => c.toUpperCase()); } + if (!options || options === '{}') + return "None"; + options = JSON.parse(options); + return Object.entries(options||{}).map(([k,v]) => v === true ? to_english(k) : `${to_english(k)}=${to_english(v)}`).join(", "); +} + +function annotate_game(game, user_id) { + let players = SQL_SELECT_PLAYERS_JOIN.all(game.game_id); + game.player_names = players.map(p => { + let name = p.name.replace(/ /g, '\xa0'); + return p.user_id > 0 ? `<a href="/user/${p.name}">${name}</a>` : name; + }).join(", "); + game.options = format_options(game.options); + game.is_active = is_active(game, players, user_id); + game.is_shared = is_shared(game, players, user_id); + game.is_yours = false; + game.your_role = null; + if (user_id > 0) { + for (let i = 0; i < players.length; ++i) { + if (players[i].user_id === user_id) { + game.is_yours = 1; + game.your_role = players[i].role; + } + } + } + game.ctime = human_date(game.ctime); + game.mtime = human_date(game.mtime); +} + +function annotate_games(games, user_id) { + for (let i = 0; i < games.length; ++i) + annotate_game(games[i], user_id); } app.get('/games', may_be_logged_in, function (req, res) { @@ -1189,6 +1213,7 @@ app.get('/join/:game_id', must_be_logged_in, function (req, res) { let game = SQL_SELECT_GAME_VIEW.get(game_id); if (!game) return res.status(404).send("Invalid game ID."); + annotate_game(game, req.user.user_id); let roles = ROLES[game.title_id]; let players = SQL_SELECT_PLAYERS_JOIN.all(game_id); let ready = (game.status === 0) && RULES[game.title_id].ready(game.scenario, game.options, players); @@ -1310,7 +1335,7 @@ app.get('/play/:game_id/:role', may_be_logged_in, function (req, res) { let title = SQL_SELECT_GAME_TITLE.get(game_id); if (!title) return res.redirect('/join/'+game_id); - res.redirect('/'+title+'/play.html?game='+game_id+'&role='+role); + res.redirect('/'+title+'/play:'+game_id+':'+role); }); app.get('/play/:game_id', may_be_logged_in, function (req, res) { @@ -1322,8 +1347,27 @@ app.get('/play/:game_id', may_be_logged_in, function (req, res) { return res.redirect('/join/'+game_id); let role = SQL_SELECT_PLAYER_ROLE.get(game_id, user_id); if (!role) - return res.redirect('/'+title+'/play.html?game='+game_id+'&role=Observer'); - return res.redirect('/'+title+'/play.html?game='+game_id+'&role='+role); + role = "Observer"; + res.redirect('/'+title+'/play:'+game_id+':'+role); +}); + +app.get('/:title_id/play\::game_id\::role', must_be_logged_in, function (req, res) { + let user_id = req.user ? req.user.user_id : 0; + let title_id = req.params.title_id + let game_id = req.params.game_id; + let role = req.params.role; + if (!SQL_AUTHORIZE_GAME_ROLE.get(title_id, game_id, role, user_id)) + return res.send("You are not assigned that role."); + return res.sendFile(__dirname + '/public/' + title_id + '/play.html'); +}); + +app.get('/:title_id/play\::game_id', may_be_logged_in, function (req, res) { + let title_id = req.params.title_id + let game_id = req.params.game_id; + let a_title = SQL_SELECT_GAME_TITLE.get(game_id); + if (a_title !== title_id) + return res.send("Invalid game ID."); + return res.sendFile(__dirname + '/public/' + title_id + '/play.html'); }); /* diff --git a/views/games.ejs b/views/games.ejs index 3b3f485..86a2502 100644 --- a/views/games.ejs +++ b/views/games.ejs @@ -2,41 +2,49 @@ <h2>Open</h2> <table class="game"> -<tr><th>ID<th>Title<th>Scenario<th>Players<th>Description<th>Created<th> -<% if (open_games.length > 0) { %> -<% open_games.forEach((row) => { %> +<tr><th>ID<th>Title<th>Scenario<th>Options<th>Players<th>Description<th>Created<th> +<%_ if (open_games.length > 0) { _%> +<%_ open_games.forEach((row) => { _%> <tr> <td class="id"><%= row.game_id %> <td class="title"><a href="/info/<%= row.title_id %>"><%= row.title_name %></a> <td class="scenario"><%= row.scenario %> -<td class="players"><%- row.player_names || row.owner_name %> +<td class="options"><%- row.options %> +<td class="players"><%- row.player_names %> <td class="description"><%= row.description %> <td class="time"><%= row.ctime %> <td class="command"><a href="/join/<%= row.game_id %>">Join</a> -<% }); } else { %> -<tr><td colspan="7">No open games. -<% } %> +<%_ }); } else { _%> +<tr><td colspan="8">No open games. +<%_ } _%> </table> <h2>Active</h2> <table class="game"> -<tr><th>ID<th>Title<th>Scenario<th>Players<th>Description<th>Changed<th>Active<th> -<% if (active_games.length > 0) { %> -<% active_games.forEach((row) => { %> +<tr><th>ID<th>Title<th>Scenario<th>Options<th>Players<th>Description<th>Changed<th>Active<th> +<%_ if (active_games.length > 0) { _%> +<%_ active_games.forEach((row) => { _%> <tr> <td class="id"><%= row.game_id %> <td class="title"><a href="/info/<%= row.title_id %>"><%= row.title_name %></a> <td class="scenario"><%= row.scenario %> +<td class="options"><%- row.options %> <td class="players"><%- row.player_names %> <td class="description"><%= row.description %> <td class="time"><%= row.mtime %> <td class="role <%= row.is_active ? "is_active" : "" %>"><%= row.active %> -<% if (row.is_yours) { %> -<td class="command"><a href="/join/<%= row.game_id %>">Enter</a> -<% } else { %> -<td class="command"><a href="/play/<%- row.game_id %>/Observer">View</a> -<% } %> -<% }); } else { %> -<tr><td colspan="8">No active games. -<% } %> +<%_ + if (row.is_yours) { + if (row.is_shared) { + %><td class="command"><a href="/join/<%= row.game_id %>">View</a><% + } else { + %><td class="command"><a href="/<%- row.title_id %>/play:<%- row.game_id %>:<%- row.your_role %>">View</a><% + } + } else { + %><td class="command"><a href="/<%- row.title_id %>/play:<%- row.game_id %>">View</a><% + } +_%> +<%_ }); } else { _%> +<tr><td colspan="9">No active games. +<%_ } _%> </table> diff --git a/views/info.ejs b/views/info.ejs index 7c98e21..4a515ff 100644 --- a/views/info.ejs +++ b/views/info.ejs @@ -1,4 +1,4 @@ -<%- include('header', { title: title.title_name, refresh: (user ? 300 : 0) }) %> +<%- include('header', { title: title.title_name, refresh: (user ? 300 : 0) }) _%> <img class="logo" src="/<%= title.title_id %>/cover.jpg"> <%- include('../public/' + title.title_id + '/about.html') %> <p> @@ -7,62 +7,78 @@ Read more about the game on <h2>Open Games</h2> <table class="game"> -<tr><th>ID<th>Scenario<th>Players<th>Description<th>Created<th> -<% if (open_games.length > 0) { %> -<% open_games.forEach((row) => { %> +<tr><th>ID<th>Scenario<th>Options<th>Players<th>Description<th>Created<th> +<%_ if (open_games.length > 0) { _%> +<%_ open_games.forEach((row) => { _%> <tr> <td class="id"><%= row.game_id %> -<td><%= row.scenario %> -<td class="players"><%- row.player_names || `<a href="/user/${row.owner_name}">${row.owner_name}</a>` %> +<td class="scenario"><%= row.scenario %> +<td class="options"><%- row.options %> +<td class="players"><%- row.player_names %> <td class="description"><%= row.description %> <td class="time"><%= row.ctime %> <td class="command"><a href="/join/<%= row.game_id %>">Join</a> -<% }); } else { %> -<tr><td colspan="6">No open games. -<% } %> +<%_ }); } else { _%> +<tr><td colspan="7">No open games. +<%_ } _%> </table> <p> <a href="/create/<%= title.title_id %>">Create a new game</a>. -<% if (active_games.length > 0) { %> +<%_ if (active_games.length > 0) { _%> <h2>Active Games</h2> <table class="game"> -<tr><th>ID<th>Scenario<th>Players<th>Description<th>Changed<th>Turn<th> -<% active_games.forEach((row) => { %> +<tr><th>ID<th>Scenario<th>Options<th>Players<th>Description<th>Changed<th>Turn<th> +<%_ active_games.forEach((row) => { _%> <tr> <td class="id"><%= row.game_id %> <td class="scenario"><%= row.scenario %> +<td class="options"><%- row.options %> <td class="players"><%- row.player_names %> <td class="description"><%= row.description %> <td class="time"><%= row.mtime %> <td class="<%= row.is_active ? "role is_active" : "role" %>"><%= row.active %> -<% if (row.is_yours) { %> -<td class="command"><a href="/join/<%- row.game_id %>">Enter</a> -<% } else { %> -<td class="command"><a href="/play/<%- row.game_id %>/Observer">View</a> -<% } %> -<% }); %> +<%_ + if (row.is_yours) { + if (row.is_shared) { + %><td class="command"><a href="/join/<%= row.game_id %>">Play</a><% + } else { + %><td class="command"><a href="/<%- row.title_id %>/play:<%- row.game_id %>:<%- row.your_role %>">Play</a><% + } + } else { + %><td class="command"><a href="/<%- row.title_id %>/play:<%- row.game_id %>">View</a><% + + } +_%> +<%_ }); _%> </table> -<% } %> +<%_ } _%> -<% if (finished_games.length > 0) { %> +<%_ if (finished_games.length > 0) { _%> <h2>Finished Games</h2> <table class="game"> -<tr><th>ID<th>Scenario<th>Players<th>Description<th>Finished<th>Result<th> -<% finished_games.forEach((row) => { %> +<tr><th>ID<th>Scenario<th>Options<th>Players<th>Description<th>Finished<th>Result<th> +<%_ finished_games.forEach((row) => { _%> <tr> <td class="id"><%= row.game_id %> <td class="scenario"><%= row.scenario %> +<td class="options"><%- row.options %> <td class="players"><%- row.player_names %> <td class="description"><%= row.description %> <td class="time"><%= row.mtime %> <td class="result"><%= row.result %> -<% if (row.is_yours) { %> -<td class="command"><a href="/join/<%= row.game_id %>">Enter</a> -<% } else { %> -<td class="command"><a href="/play/<%- row.game_id %>/Observer">View</a> -<% } %> -<% }); %> +<%_ + if (row.is_yours) { + if (row.is_shared) { + %><td class="command"><a href="/join/<%= row.game_id %>">View</a><% + } else { + %><td class="command"><a href="/<%- row.title_id %>/play:<%- row.game_id %>:<%- row.your_role %>">View</a><% + } + } else { + %><td class="command"><a href="/<%- row.title_id %>/play:<%- row.game_id %>">View</a><% + } +_%> +<%_ }); _%> </table> -<% } %> +<%_ } _%> diff --git a/views/join.ejs b/views/join.ejs index fa33d5c..6558745 100644 --- a/views/join.ejs +++ b/views/join.ejs @@ -1,14 +1,4 @@ <%- include('header', { title: game.title_name }) -%> -<% -function to_english(k) { - if (k === true) return 'yes'; - if (k === false) return 'no'; - return k.replace(/_/g, " ").replace(/^\w/, c => c.toUpperCase()); -} -function format_options(options) { - return Object.entries(options||{}).map(([k,v]) => v === true ? to_english(k) : `${to_english(k)}=${to_english(v)}`).join(", "); -} --%> <style> th, td { min-width: 10em; font-size: 16px; } a.red { text-decoration: none; color: brown; font-size: 15px; } @@ -35,13 +25,9 @@ Private: <%= game.private ? "yes" : "no" %> <br> Scenario: <%= game.scenario %> <br> -Options: <%- format_options(JSON.parse(game.options)) %> +Options: <%= game.options %> <br> Description: <%= game.description || "No description." %> -<br> -Status: <span id="game_status"></span> -<br> -Result: <span id="game_result"></span> <br clear=left> diff --git a/views/profile.ejs b/views/profile.ejs index 4d97486..a001e21 100644 --- a/views/profile.ejs +++ b/views/profile.ejs @@ -1,4 +1,4 @@ -<%- include('header', { title: "Rally the Troops!", refresh: (active_games.length > 0 ? 300 : 0) }) %> +<%- include('header', { title: "Rally the Troops!", refresh: (active_games.length > 0 ? 300 : 0) }) _%> <img class="logo avatar" src="<%= avatar %>" width="80" height="80"> <p> Welcome, <%= user.name %>! @@ -26,70 +26,79 @@ or <a href="/change_about">profile text</a>. <br>» <a href="/logout">Logout</a> -<% if (open_games.length > 0) { %> +<%_ if (open_games.length > 0) { _%> <h2>Open Games</h2> <table class="game"> -<tr><th>ID<th>Game<th>Scenario<th>Players<th>Description<th>Created<th> -<% open_games.forEach((row) => { %> +<tr><th>ID<th>Game<th>Scenario<th>Options<th>Players<th>Description<th>Created<th> +<%_ open_games.forEach((row) => { _%> <tr> <td class="id"><%= row.game_id %> <td class="name"><a href="/info/<%= row.title_id %>"><%= row.title_name %></a> <td class="scenario"><%= row.scenario %> +<td class="options"><%- row.options %> <td class="players"><%- row.player_names %> <td class="description"><%= row.description %> <td class="time"><%= row.ctime %> <td class="command"><a href="/join/<%= row.game_id %>">Join</a> -<% }); %> +<%_ }); _%> </table> -<% } %> +<%_ } _%> -<% if (active_games.length > 0) { %> +<%_ if (active_games.length > 0) { _%> <h2>Active Games</h2> <table class="game"> -<tr><th>ID<th>Game<th>Scenario<th>Players<th>Description<th>Changed<th>Turn<th> -<% active_games.forEach((row) => { %> +<tr><th>ID<th>Game<th>Scenario<th>Options<th>Players<th>Description<th>Changed<th>Turn<th> +<%_ active_games.forEach((row) => { _%> <tr> <td class="id"><%= row.game_id %> <td class="title"><a href="/info/<%= row.title_id %>"><%= row.title_name %></a> <td class="scenario"><%= row.scenario %> +<td class="options"><%- row.options %> <td class="players"><%- row.player_names %> <td class="description"><%= row.description %> <td class="time"><%= row.mtime %> -<% +<%_ if (row.is_active) { %><td class="role is_active"><%= row.active %><% } else { %><td class="role"><%= row.active %><% } if (row.is_shared) { - %><td class="command"><a href="/join/<%= row.game_id %>">Enter</a><% + %><td class="command"><a href="/join/<%= row.game_id %>">Play</a><% } else { - %><td class="command"><a href="/play/<%= row.game_id %>">Play</a><% + %><td class="command"><a href="/<%- row.title_id %>/play:<%- row.game_id %>:<%- row.your_role %>">Play</a><% } -%> -<% }); %> +_%> +<%_ }); _%> </table> -<% } %> +<%_ } _%> -<% if (finished_games.length > 0) { %> +<%_ if (finished_games.length > 0) { _%> <h2>Finished Games</h2> <table class="game"> -<tr><th>ID<th>Game<th>Scenario<th>Players<th>Description<th>Finished<th>Result<th> -<% finished_games.forEach((row) => { %> +<tr><th>ID<th>Game<th>Scenario<th>Options<th>Players<th>Description<th>Finished<th>Result<th> +<%_ finished_games.forEach((row) => { _%> <tr> <td class="id"><%= row.game_id %> <td class="title"><a href="/info/<%= row.title_id %>"><%= row.title_name %></a> <td class="scenario"><%= row.scenario %> +<td class="options"><%- row.options %> <td class="players"><%- row.player_names %> <td class="description"><%= row.description %> <td class="time"><%= row.mtime %> <td class="result"><%= row.result %> -<td class="command"><a href="/join/<%= row.game_id %>">View</a> -<% }); %> +<%_ + if (row.is_shared) { + %><td class="command"><a href="/join/<%= row.game_id %>">View</a><% + } else { + %><td class="command"><a href="/<%- row.title_id %>/play:<%- row.game_id %>:<%- row.your_role %>">View</a><% + } +_%> +<%_ }); _%> </table> -<% } %> +<%_ } _%> -<% if (open_games.length === 0 && active_games.length === 0 && finished_games.length === 0) { %> +<%_ if (open_games.length === 0 && active_games.length === 0 && finished_games.length === 0) { _%> <p> You don't have any current or finished games. -<% } %> +<%_ } _%> |