Add streamer UI

Closes #11
master
Dessa Simpson 2020-11-01 13:07:16 -07:00
parent b479cd2948
commit 68faec1ae8
7 changed files with 207 additions and 16 deletions

View File

@ -2,6 +2,6 @@ CREATE TABLE votes (
requestUrl varchar, requestUrl varchar,
userId int, userId int,
PRIMARY KEY (requestUrl,userId), PRIMARY KEY (requestUrl,userId),
FOREIGN KEY (requestUrl) REFERENCES requests(url), FOREIGN KEY (requestUrl) REFERENCES requests(url) ON DELETE CASCADE,
FOREIGN KEY (userId) REFERENCES users(userId) FOREIGN KEY (userId) REFERENCES users(userId) ON DELETE CASCADE
); );

View File

@ -3,5 +3,5 @@ CREATE TABLE scores (
score int NOT NULL DEFAULT 0, score int NOT NULL DEFAULT 0,
scoreModifier int NOT NULL DEFAULT 0, scoreModifier int NOT NULL DEFAULT 0,
PRIMARY KEY (url), PRIMARY KEY (url),
FOREIGN KEY (url) REFERENCES requests(url) FOREIGN KEY (url) REFERENCES requests(url) ON DELETE CASCADE
); );

View File

@ -10,26 +10,34 @@ function getRequests(count,allRequests) {
reqUrl += `?count=${count}`; reqUrl += `?count=${count}`;
fetch(reqUrl) fetch(reqUrl)
.then(response => response.json()) .then(response => response.json())
.then(requests => buildTable(requests,allRequests)); .then(requests => {
window.requests = requests;
console.log(requests);
buildTable();
});
} }
function buildTable(requests,allRequests) { function buildTable() {
var requestsDivHTML = '<table><tr><th class="request-url">URL</th><th class="request-requester">Requester</th><th class="request-score">Score</th>'; var requestsDivHTML = '<table><tr><th class="request-url">URL</th><th class="request-requester">Requester</th><th class="request-score">Score</th>';
if (allRequests) requestsDivHTML += `<th class="request-state">State</td>`; requestsDivHTML += '<th class="request-state">State</td>';
if (window.loggedIn) requestsDivHTML += `<th class="request-vote">Vote</td>`; if (window.loggedIn) requestsDivHTML += '<th class="request-vote">Vote</td>';
if (window.isStreamer) requestsDivHTML += '<th class="request-update">Update</th>'
requestsDivHTML += "</tr>"; requestsDivHTML += "</tr>";
for (request of requests) { for (request of requests) {
console.log(request); console.log(request);
requestsDivHTML += `<tr><td class="request-url"><a href="${request.url}" target="_blank">${request.url}</a></td>\ requestsDivHTML += `<tr><td class="request-url"><a href="${request.url}" target="_blank">${request.url}</a></td>\
<td class="request-requester">${request.imageurl ? `<img src="${request.imageurl}" class="table-userpic"/>` : ''}${request.requester}</td>\ <td class="request-requester">${request.imageurl ? `<img src="${request.imageurl}" class="table-userpic"/>` : ''}${request.requester}</td>\
<td class="request-score">${request.score}</td>`; <td class="request-score">${request.score}</td>`;
if (allRequests) requestsDivHTML += `<td class="request-state">${request.state}</td>`; requestsDivHTML += `<td class="request-state">${request.state}</td>`;
if (window.loggedIn) { if (window.loggedIn) {
if (request.voted) { if (request.voted) {
requestsDivHTML += `<td class="request-vote"><button onclick="deleteVote('${request.url}')">Unvote</button></td>`; requestsDivHTML += `<td class="request-vote"><button onclick="deleteVote('${request.url}')">Unvote</button></td>`;
} else { } else {
requestsDivHTML += `<td class="request-vote"><button onclick="addVote('${request.url}')">Vote</button></td>`; requestsDivHTML += `<td class="request-vote"><button onclick="addVote('${request.url}')">Vote</button></td>`;
} }
if (window.isStreamer) {
requestsDivHTML += '<td class="request-update"><button onclick="openUpdateRequestModal(this.parentElement.parentElement)">Update</button></td>'
}
} }
requestsDivHTML += "</tr>"; requestsDivHTML += "</tr>";
} }
@ -38,9 +46,8 @@ function buildTable(requests,allRequests) {
} }
function updateTable() { function updateTable() {
var count = document.getElementById("count").value; allRequests = document.getElementById("allRequests").checked;
var allRequests = document.getElementById("allRequests").checked; getRequests(document.getElementById("count").value,allRequests);
getRequests(count,allRequests);
} }
function addRequestErr(msg) { function addRequestErr(msg) {
@ -53,8 +60,21 @@ function addRequestErrReset() {
document.getElementById('addRequestError').innerText = ""; document.getElementById('addRequestError').innerText = "";
} }
function updateRequestErr(msg) {
document.getElementById('updateRequestError').style.display = "inline-block";
document.getElementById('updateRequestError').innerText = msg;
}
function updateRequestErrReset() {
document.getElementById('updateRequestError').style.display = "none";
document.getElementById('updateRequestError').innerText = "";
}
function showMessage(msg) { function showMessage(msg) {
document.getElementById("messageModalText").innerText = msg; document.getElementById("messageModalText").innerText = msg;
document.getElementById("addRequestModal").style.display = "none";
document.getElementById("updateRequestModal").style.display = "none";
document.getElementById("deleteRequestModal").style.display = "none";
document.getElementById("modalBackground").style.display = "flex"; document.getElementById("modalBackground").style.display = "flex";
document.getElementById("messageModal").style.display = "block"; document.getElementById("messageModal").style.display = "block";
} }
@ -66,6 +86,8 @@ function closeMessageModal() {
function openAddRequestModal() { function openAddRequestModal() {
document.getElementById("modalBackground").style.display = "flex"; document.getElementById("modalBackground").style.display = "flex";
document.getElementById("updateRequestModal").style.display = "none";
document.getElementById("messageModal").style.display = "none";
document.getElementById("addRequestModal").style.display = "block"; document.getElementById("addRequestModal").style.display = "block";
} }
@ -74,6 +96,49 @@ function closeAddRequestModal() {
document.getElementById("addRequestModal").style.display = "none"; document.getElementById("addRequestModal").style.display = "none";
} }
function openUpdateRequestModal(tr) {
var url = tr.getElementsByClassName('request-url')[0].innerText;
var score = tr.getElementsByClassName('request-score')[0].innerText;
var state = tr.getElementsByClassName('request-state')[0].innerText;
document.getElementById("updateRequestUrl").href = url;
document.getElementById("updateRequestUrl").innerText = url;
document.getElementById("updateRequestModalCurrentScore").innerText = score;
document.querySelector(`#updateRequestStateSelect [value="${state}"]`).selected = true;
document.getElementById("scoreModifierInput").value = 0;
document.getElementById("messageModal").style.display = "none";
document.getElementById("addRequestModal").style.display = "none";
document.getElementById("modalBackground").style.display = "flex";
document.getElementById("updateRequestModal").style.display = "block";
}
function closeUpdateRequestModal() {
document.getElementById("modalBackground").style.display = "none";
document.getElementById("updateRequestModal").style.display = "none";
}
function openDeleteRequestModal(url) {
document.getElementById("updateRequestUrl").href = url;
document.getElementById("updateRequestUrl").innerText = url;
document.getElementById("messageModal").style.display = "none";
document.getElementById("addRequestModal").style.display = "none";
document.getElementById("updateRequestModal").style.display = "none";
document.getElementById("modalBackground").style.display = "flex";
document.getElementById("deleteRequestModal").style.display = "block";
}
function closeDeleteRequestModal() {
document.getElementById("deleteRequestModal").style.display = "none";
document.getElementById("updateRequestModal").style.display = "block";
}
function closeAllModals() {
document.getElementById("messageModal").style.display = "none";
document.getElementById("addRequestModal").style.display = "none";
document.getElementById("updateRequestModal").style.display = "none";
document.getElementById("deleteRequestModal").style.display = "none";
document.getElementById("modalBackground").style.display = "none";
}
const validUrlRegexes = [ const validUrlRegexes = [
/^https:\/\/www\.youtube\.com\/watch\?v=[a-zA-Z0-9_-]{11}$/ /^https:\/\/www\.youtube\.com\/watch\?v=[a-zA-Z0-9_-]{11}$/
]; ];
@ -132,6 +197,50 @@ function deleteVote(url) {
}); });
} }
function updateRequestState(url,state) {
updateRequestErrReset();
fetch("/api/updateRequestState", { method: 'POST', body: new URLSearchParams({
url: url,
state: state
})})
.then(response => {
if (!response.ok) {
response.text().then(updateRequestErr);
return;
}
updateTable();
response.text().then(showMessage);
});
}
function updateRequestScoreModifier(url,scoreDiff) {
updateRequestErrReset();
fetch("/api/updateRequestScoreModifier", { method: 'POST', body: new URLSearchParams({
url: url,
scoreDiff: scoreDiff
})})
.then(response => {
if (!response.ok) {
response.text().then(updateRequestErr);
return;
}
updateTable();
response.text().then(showMessage);
});
}
function deleteRequest(url) {
fetch("/api/deleteRequest", { method: 'POST', body: new URLSearchParams({
url: url
})})
.then(response => {
updateTable();
response.text().then(showMessage);
});
}
updateTable(); updateTable();
document.addEventListener("keydown", function onEvent(event) { document.addEventListener("keydown", function onEvent(event) {
@ -140,4 +249,12 @@ document.addEventListener("keydown", function onEvent(event) {
closeAddRequestModal(); closeAddRequestModal();
} }
}); });
document.getElementById("modalBackground").addEventListener("click", (e) => { if (e.target === e.currentTarget) closeAddRequestModal();}); document.getElementById("modalBackground").addEventListener("click", (e) => { if (e.target === e.currentTarget) closeAllModals();});
var updateRequestStateSelect = document.getElementById("updateRequestStateSelect");
for(state of validStates) {
var opt = document.createElement("option");
opt.text = state;
opt.value = state;
updateRequestStateSelect.add(opt)
}

View File

@ -130,7 +130,7 @@ div#nav-userpic {
box-shadow: 0px 5px 20px black; box-shadow: 0px 5px 20px black;
} }
.modal h1 { .modal h1, .modal h2, .modal h3 {
margin: 0; margin: 0;
} }
@ -166,3 +166,16 @@ div#nav-userpic {
margin-top: -3px; margin-top: -3px;
margin-bottom: -7px; margin-bottom: -7px;
} }
#scoreModifierInput {
width: 4em;
text-align: right;
}
#scoreModifierHelp {
font-size: 75%;
}
#deleteRequestLink {
color: #f00;
}

View File

@ -274,6 +274,7 @@ app.get("/callback", async (request, response) => {
app.get("/", async (request, response) => { app.get("/", async (request, response) => {
if (request.session) await validateApiToken(request.session); if (request.session) await validateApiToken(request.session);
var streamerInfo = await db.query(queries.getStreamerInfo).then((result: pg.QueryResult) => result.rows[0]); var streamerInfo = await db.query(queries.getStreamerInfo).then((result: pg.QueryResult) => result.rows[0]);
var validStates = JSON.stringify((await db.query(queries.getValidStates).then((result: pg.QueryResult) => result.rows)).map((row: any) => row.state));
if (typeof streamerInfo == 'undefined') response.redirect(307, `https://id.twitch.tv/oauth2/authorize?client_id=${config.twitchClientId}&redirect_uri=${config.urlPrefix}/callback&response_type=code&scope=channel:read:subscriptions moderation:read`); if (typeof streamerInfo == 'undefined') response.redirect(307, `https://id.twitch.tv/oauth2/authorize?client_id=${config.twitchClientId}&redirect_uri=${config.urlPrefix}/callback&response_type=code&scope=channel:read:subscriptions moderation:read`);
if (!request.session || !request.session.user) { if (!request.session || !request.session.user) {
response.render('main.eta', { response.render('main.eta', {
@ -288,6 +289,8 @@ app.get("/", async (request, response) => {
loggedIn: true, loggedIn: true,
userName: request.session.user.display_name, userName: request.session.user.display_name,
userProfilePicture: request.session.user.profile_image_url, userProfilePicture: request.session.user.profile_image_url,
validStates: validStates,
isStreamer: streamerInfo['userid'] == request.session.user.id,
streamerName: streamerInfo['displayname'], streamerName: streamerInfo['displayname'],
streamerProfilePicture: streamerInfo['imageurl'] streamerProfilePicture: streamerInfo['imageurl']
}); });

View File

@ -23,7 +23,7 @@ export const getStreamerIdToken = {
export const getStreamerInfo = { export const getStreamerInfo = {
name: "getStreamerInfo", name: "getStreamerInfo",
text: "SELECT displayname,imageurl FROM streamer_user_vw" text: "SELECT userid,displayname,imageurl FROM streamer_user_vw"
} }
export const updateStreamer = { export const updateStreamer = {
@ -91,3 +91,8 @@ export const checkVoteExists = {
name: "checkVoteExists", name: "checkVoteExists",
text: "SELECT userid FROM votes WHERE requesturl = $1 AND userid = $2" text: "SELECT userid FROM votes WHERE requesturl = $1 AND userid = $2"
} }
export const getValidStates = {
name: "getValidStates",
text: "SELECT * FROM states"
}

View File

@ -3,7 +3,11 @@
<head> <head>
<link rel=stylesheet href=style.css /> <link rel=stylesheet href=style.css />
<title><%= it.streamerName %>'s Learn Request Queue</title> <title><%= it.streamerName %>'s Learn Request Queue</title>
<script>window.loggedIn = <%= it.loggedIn %></script> <script>
window.loggedIn = <%= it.loggedIn %>;
window.validStates = <%~ it.validStates %>;
window.isStreamer = <%= it.isStreamer %>;
</script>
<script src="main.js" defer></script> <script src="main.js" defer></script>
</head> </head>
<body> <body>
@ -47,6 +51,55 @@
</span> </span>
Currently, only Youtube links are accepted. Currently, only Youtube links are accepted.
</div> </div>
<div class="modal" id="updateRequestModal">
<div class="modalClose"><a href="#" onclick="closeAddRequestModal()">&times;</a></div>
<h2>Update Request</h2>
<div class="error" id="updateRequestError"></div>
<br>
<a id="updateRequestUrl"></a>
<br>
Current Score: <span id="updateRequestModalCurrentScore"></span>
<hr>
<div>
State:
<select id="updateRequestStateSelect" onchange="updateRequestState(
document.getElementById('updateRequestUrl').innerText,
this.value)">Submit</button>
)"></select>
</div>
<br>
<div>
Modify Score:
<input type="number" id="scoreModifierInput"></input>
<button onclick="updateRequestScoreModifier(
document.getElementById('updateRequestUrl').innerText,
document.getElementById('scoreModifierInput').value)">Submit</button>
<br>
<span id="scoreModifierHelp">
Enter a number to add to (or negative to subtract from) the score of
a request. Use this for things like donations and channel points
redemptions.
</span>
</div>
<br>
<div>
<a id="deleteRequestLink" href="#" onclick="openDeleteRequestModal(
document.getElementById('updateRequestUrl').innerText
)">Delete Request</a>
</div>
</div>
<div class="modal" id="deleteRequestModal">
<div class="modalClose"><a href="#" onclick="closeDeleteRequestModal()">&times;</a></div>
<h2>Delete Request</h2>
<span style="color: #f00">WARNING:</span>
Deleting a request will remove the request and all votes for it from the
database. This action is irreversible. It will NOT prevent the request
from being made again - use the Rejected state for that. Are you sure
you want to delete this request?
<br><br>
<button onclick="closeDeleteRequestModal()">No</button>
<button onclick="deleteRequest(document.getElementById('updateRequestUrl').innerText)">Yes</button>
</div>
</div> </div>
</body> </body>
</html> </html>