Show remaining time to answer
This commit is contained in:
@@ -140,8 +140,9 @@ func (app *application) nextQuestion(w http.ResponseWriter, r *http.Request, par
|
|||||||
|
|
||||||
if player, err := app.model.GetPlayerWithSessionAndGame(playerUid, r.Context()); err == nil {
|
if player, err := app.model.GetPlayerWithSessionAndGame(playerUid, r.Context()); err == nil {
|
||||||
var sessionId = player.Edges.Session.ID
|
var sessionId = player.Edges.Session.ID
|
||||||
if err := app.model.NextQuestion(sessionId, time.Now(), r.Context()); err == nil {
|
var now = time.Now()
|
||||||
if su, err := app.model.GetQuestionStateUpdate(sessionId, r.Context()); err == nil {
|
if err := app.model.NextQuestion(sessionId, now, r.Context()); err == nil {
|
||||||
|
if su, err := app.model.GetQuestionStateUpdate(sessionId, now, r.Context()); err == nil {
|
||||||
app.rtClients.SendToAll(sessionId, su)
|
app.rtClients.SendToAll(sessionId, su)
|
||||||
w.WriteHeader(http.StatusNoContent)
|
w.WriteHeader(http.StatusNoContent)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ func (app *application) processWebSocket(w http.ResponseWriter, r *http.Request,
|
|||||||
var ch = make(chan rtcomm.StateUpdate, suBufferSize)
|
var ch = make(chan rtcomm.StateUpdate, suBufferSize)
|
||||||
app.rtClients.AddClient(player.Edges.Session.ID, ch)
|
app.rtClients.AddClient(player.Edges.Session.ID, ch)
|
||||||
defer app.rtClients.RemoveClient(player.Edges.Session.ID, ch)
|
defer app.rtClients.RemoveClient(player.Edges.Session.ID, ch)
|
||||||
if su, err := app.model.GetFullStateUpdate(player.Edges.Session.ID, r.Context()); err == nil {
|
if su, err := app.model.GetFullStateUpdate(player.Edges.Session.ID, time.Now(), r.Context()); err == nil {
|
||||||
select {
|
select {
|
||||||
case ch <- su:
|
case ch <- su:
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ func (m *Model) GetPlayersStateUpdate(sessionId uuid.UUID, c context.Context) (r
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Model) GetQuestionStateUpdate(sessionId uuid.UUID, c context.Context) (rtcomm.StateUpdate, error) {
|
func (m *Model) GetQuestionStateUpdate(sessionId uuid.UUID, now time.Time, c context.Context) (rtcomm.StateUpdate, error) {
|
||||||
tx, err := m.c.BeginTx(c, &sql.TxOptions{
|
tx, err := m.c.BeginTx(c, &sql.TxOptions{
|
||||||
Isolation: sql.LevelRepeatableRead,
|
Isolation: sql.LevelRepeatableRead,
|
||||||
ReadOnly: true,
|
ReadOnly: true,
|
||||||
@@ -181,6 +181,11 @@ func (m *Model) GetQuestionStateUpdate(sessionId uuid.UUID, c context.Context) (
|
|||||||
var q = aq.Edges.Question
|
var q = aq.Edges.Question
|
||||||
var qu rtcomm.QuestionUpdate
|
var qu rtcomm.QuestionUpdate
|
||||||
qu.Title = q.Title
|
qu.Title = q.Title
|
||||||
|
if !aq.Ended.After(now) {
|
||||||
|
qu.RemainingTime = 0
|
||||||
|
} else {
|
||||||
|
qu.RemainingTime = uint64(aq.Ended.Sub(now).Round(time.Millisecond).Milliseconds())
|
||||||
|
}
|
||||||
qu.Answers = make([]rtcomm.Answer, 0, len(q.Edges.Choices))
|
qu.Answers = make([]rtcomm.Answer, 0, len(q.Edges.Choices))
|
||||||
for i := 0; i < len(q.Edges.Choices); i++ {
|
for i := 0; i < len(q.Edges.Choices); i++ {
|
||||||
qu.Answers = append(qu.Answers, rtcomm.Answer{
|
qu.Answers = append(qu.Answers, rtcomm.Answer{
|
||||||
@@ -198,12 +203,12 @@ func (m *Model) GetQuestionStateUpdate(sessionId uuid.UUID, c context.Context) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
//TODO reuse transaction
|
//TODO reuse transaction
|
||||||
func (m *Model) GetFullStateUpdate(sessionId uuid.UUID, c context.Context) (rtcomm.StateUpdate, error) {
|
func (m *Model) GetFullStateUpdate(sessionId uuid.UUID, now time.Time, c context.Context) (rtcomm.StateUpdate, error) {
|
||||||
su, err := m.GetPlayersStateUpdate(sessionId, c)
|
su, err := m.GetPlayersStateUpdate(sessionId, c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return rtcomm.StateUpdate{}, err
|
return rtcomm.StateUpdate{}, err
|
||||||
}
|
}
|
||||||
if su2, err := m.GetQuestionStateUpdate(sessionId, c); err == nil {
|
if su2, err := m.GetQuestionStateUpdate(sessionId, now, c); err == nil {
|
||||||
su.Question = su2.Question
|
su.Question = su2.Question
|
||||||
} else {
|
} else {
|
||||||
return rtcomm.StateUpdate{}, err
|
return rtcomm.StateUpdate{}, err
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ type Player struct {
|
|||||||
|
|
||||||
type QuestionUpdate struct {
|
type QuestionUpdate struct {
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
|
RemainingTime uint64 `json:"remainingTime"`
|
||||||
Answers []Answer `json:"answers"`
|
Answers []Answer `json:"answers"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<template id="question-template">
|
<template id="question-template">
|
||||||
<h1 class="question"></h1>
|
<h1 class="question"></h1>
|
||||||
|
<div id="timer"></div>
|
||||||
<div class="answers"></div>
|
<div class="answers"></div>
|
||||||
</template>
|
</template>
|
||||||
<section id="question"></section>
|
<section id="question"></section>
|
||||||
@@ -85,6 +86,7 @@
|
|||||||
questionClone.querySelector('.question').innerText = data.question.title;
|
questionClone.querySelector('.question').innerText = data.question.title;
|
||||||
const answers = questionClone.querySelector('.answers');
|
const answers = questionClone.querySelector('.answers');
|
||||||
const organiser = document.body.classList.contains('organiser');
|
const organiser = document.body.classList.contains('organiser');
|
||||||
|
const timer = questionClone.querySelector("#timer");
|
||||||
for (const answer of data.question.answers) {
|
for (const answer of data.question.answers) {
|
||||||
const answerClone = answerTemplate.content.cloneNode(true);
|
const answerClone = answerTemplate.content.cloneNode(true);
|
||||||
const button = answerClone.querySelector('.answer');
|
const button = answerClone.querySelector('.answer');
|
||||||
@@ -108,6 +110,19 @@
|
|||||||
}
|
}
|
||||||
answers.appendChild(answerClone);
|
answers.appendChild(answerClone);
|
||||||
}
|
}
|
||||||
|
if (data.question.remainingTime > 0) {
|
||||||
|
timer.style.width = "100%";
|
||||||
|
const initialRemainingTime = data.question.remainingTime;
|
||||||
|
const initialTime = Date.now();
|
||||||
|
let handler = () => {
|
||||||
|
const remainingTime = Math.max(initialRemainingTime - (Date.now() - initialTime), 0);
|
||||||
|
timer.style.width = String(remainingTime / initialRemainingTime * 100) + "%";
|
||||||
|
if (remainingTime > 0) {
|
||||||
|
window.setTimeout(handler, 100);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.setTimeout(handler, 100);
|
||||||
|
}
|
||||||
questionSection.appendChild(questionClone);
|
questionSection.appendChild(questionClone);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,7 @@
|
|||||||
#question > h1 {
|
#question > h1 {
|
||||||
font-size: 3rem;
|
font-size: 3rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
margin-bottom: .2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
#question .answers {
|
#question .answers {
|
||||||
@@ -53,6 +54,14 @@
|
|||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#timer {
|
||||||
|
height: 2px;
|
||||||
|
background-color: blue;
|
||||||
|
margin-bottom: 5rem;
|
||||||
|
transition: width .2s;
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.answer.selected {
|
.answer.selected {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
text-shadow: 2px 1px 5px #0003;
|
text-shadow: 2px 1px 5px #0003;
|
||||||
|
|||||||
Reference in New Issue
Block a user