diff --git a/direct.go b/direct.go index b833fe9..37d1809 100644 --- a/direct.go +++ b/direct.go @@ -14,10 +14,11 @@ import ( ) var ( - WSClients = map[int64][]WSClient{} - WSClientsMutex = sync.RWMutex{} - WSAdmin = []WSClient{} - WSAdminMutex = sync.RWMutex{} + OffsetQuestionTimer uint = 700 + WSClients = map[int64][]WSClient{} + WSClientsMutex = sync.RWMutex{} + WSAdmin = []WSClient{} + WSAdminMutex = sync.RWMutex{} ) func init() { @@ -152,6 +153,7 @@ type WSMessage struct { Stats map[string]interface{} `json:"stats,omitempty"` UserId *int64 `json:"user,omitempty"` Response string `json:"value,omitempty"` + Timer uint `json:"timer,omitempty"` } func (s *Survey) WSWriteAll(message WSMessage) { @@ -271,7 +273,20 @@ func SurveyWSAdmin(w http.ResponseWriter, r *http.Request, ps httprouter.Params, if survey, err := getSurvey(sid); err != nil { log.Println("Unable to retrieve survey:", err) } else { - survey.Direct = v.QuestionId + if v.Timer > 0 { + if *survey.Direct != 0 { + var z int64 = 0 + survey.Direct = &z + survey.Update() + } + go func() { + time.Sleep(time.Duration(OffsetQuestionTimer+v.Timer) * time.Millisecond) + survey.WSWriteAll(WSMessage{Action: "pause"}) + WSAdminWriteAll(WSMessage{Action: "pause", SurveyId: &survey.Id}) + }() + } else { + survey.Direct = v.QuestionId + } _, err = survey.Update() if err != nil { log.Println("Unable to update survey:", err) @@ -348,6 +363,20 @@ func SurveyWSAdmin(w http.ResponseWriter, r *http.Request, ps httprouter.Params, log.Println("Unable to update:", err) } } + } else if v.Action == "mark_answered" && v.Response == "all" { + if survey, err := getSurvey(sid); err != nil { + log.Println("Unable to retrieve survey:", err) + } else if asks, err := survey.GetAsks(v.Response == ""); err != nil { + log.Println("Unable to retrieve asks:", err) + } else { + for _, ask := range asks { + ask.Answered = true + err = ask.Update() + if err != nil { + log.Println("Unable to update:", err) + } + } + } } else { log.Println("Unknown admin action:", v.Action) } @@ -370,7 +399,6 @@ func (s *Survey) WSAdminWriteAll(message WSMessage) { defer WSAdminMutex.RUnlock() for _, ws := range WSAdmin { - log.Println("snd", message, ws.sid, s.Id) if ws.sid == s.Id { ws.c <- message } diff --git a/main.go b/main.go index 7f4efeb..ec1cae8 100644 --- a/main.go +++ b/main.go @@ -63,6 +63,7 @@ func main() { flag.StringVar(&DevProxy, "dev", DevProxy, "Proxify traffic to this host for static assets") flag.StringVar(&baseURL, "baseurl", baseURL, "URL prepended to each URL") flag.UintVar(¤tPromo, "current-promo", currentPromo, "Year of the current promotion") + flag.UintVar(&OffsetQuestionTimer, "offset-question-timer", OffsetQuestionTimer, "Duration to wait before sending pause msg in direct mode (in milliseconds)") flag.Var(&localAuthUsers, "local-auth-user", "Allow local authentication for this user (bypass OIDC).") flag.Parse() diff --git a/ui/src/routes/surveys/[sid]/admin.svelte b/ui/src/routes/surveys/[sid]/admin.svelte index 2ec87b1..b58aeef 100644 --- a/ui/src/routes/surveys/[sid]/admin.svelte +++ b/ui/src/routes/surveys/[sid]/admin.svelte @@ -13,6 +13,7 @@ import { user } from '../../../stores/user'; import SurveyAdmin from '../../../components/SurveyAdmin.svelte'; import SurveyBadge from '../../../components/SurveyBadge.svelte'; + import { getSurvey } from '../../../lib/surveys'; import { getQuestions } from '../../../lib/questions'; import { getUsers } from '../../../lib/users'; @@ -29,6 +30,10 @@ } }); + function updateSurvey() { + surveyP = getSurvey(survey.id); + } + function updateQuestions() { req_questions = getQuestions(survey.id); } @@ -38,6 +43,21 @@ let wsstats = null; let current_question = null; let responses = {}; + let timer = 20000; + let timer_end = null; + let timer_remain = 0; + let timer_cancel = null; + + function updTimer() { + const now = new Date().getTime(); + if (now > timer_end) { + timer_remain = 0; + clearInterval(timer_cancel); + timer_cancel = null; + } else { + timer_remain = Math.floor((timer_end - now) / 100)/10; + } + } let users = {}; function updateUsers() { @@ -80,6 +100,7 @@ ws_up = false; console.log('Socket is closed. Reconnect will be attempted in 1 second.', e.reason); ws = null; + updateSurvey(); setTimeout(function() { wsconnect(); }, 1500); @@ -96,6 +117,16 @@ console.log(data); if (data.action && data.action == "new_question") { current_question = data.question; + if (timer_cancel) { + clearInterval(timer_cancel); + timer_cancel = null; + } + if (data.timer) { + timer_end = new Date().getTime() + data.timer; + timer_cancel = setInterval(updTimer, 250); + } else { + timer_end = null; + } } else if (data.action && data.action == "stats") { wsstats = data.stats; } else if (data.action && data.action == "new_response") { @@ -106,6 +137,11 @@ asks = asks; } else { current_question = null; + timer_end = null; + if (timer_cancel) { + clearInterval(timer_cancel); + timer_cancel = null; + } } }); } @@ -118,9 +154,10 @@ {/if} @@ -132,6 +169,11 @@ Administration + {#if asks.length} + + + + {/if} {#if survey.direct !== null}
{:else} {#await req_questions} @@ -166,6 +209,27 @@ Question + {#if timer_end} +
+ + ms +
+ {:else} +
+ + ms +
+ {/if} + {/each} @@ -227,210 +299,218 @@
{/await} - {/if} -
- - -

- Questions +
+ + + +

+ Questions + {#if asks.length} + + {asks.length} question{#if asks.length > 1}s{/if} + + {/if} +

{#if asks.length} - - {asks.length} question{#if asks.length > 1}s{/if} - - {/if} - - {#if asks.length} - {#each asks as ask (ask.id)} -
-
-

- {ask.content} -

-
- -
- {/each} - {:else} -
- Pas de question pour l'instant. -
- {/if} - -
- -

- Réponses -

- {#if Object.keys(responses).length} - {#each Object.keys(responses) as q, qid (qid)} - {#await req_questions then questions} - {#each questions as question} - {#if question.id == q} -

- {question.title} -

- {#if question.kind == 'ucq'} - {#await question.getProposals()} -
-
- Chargement des propositions … -
- {:then proposals} -
- - - {#each proposals as proposal (proposal.id)} - - - - - - {/each} - -
- {proposal.label} - - {responsesbyid[q].filter((e) => e == proposal.id.toString()).length}/{responsesbyid[q].length} - - {Math.trunc(responsesbyid[q].filter((e) => e == proposal.id.toString()).length / responsesbyid[q].length * 1000)/10} % -
-
- {/await} - {:else if question.kind == 'mcq'} - {#await question.getProposals()} -
-
- Chargement des propositions … -
- {:then proposals} -
- - - {#each proposals as proposal (proposal.id)} - - - - - - {/each} - -
- {proposal.label} - - {responsesbyid[q].filter((e) => e.indexOf(proposal.id.toString()) >= 0).length}/{responsesbyid[q].length} - - {Math.trunc(responsesbyid[q].filter((e) => e.indexOf(proposal.id.toString()) >= 0).length / responsesbyid[q].length * 1000)/10} % -
-
- {/await} - {:else} -
- -
- {/if} - {/if} - {/each} - {/await} - {/each} - {/if} - -
- - -

- Connectés - {#if wsstats} - {wsstats.nb_clients} utilisateurs - {/if} -

- {#if wsstats} -
- {#each wsstats.users as login, lid (lid)} -
-
- {login} - + {#each asks as ask (ask.id)} +
+
+

+ {ask.content} +

+
+
{/each} -
+ {:else} +
+ Pas de question pour l'instant. +
+ {/if} + +
+ +

+ Réponses +

+ {#if Object.keys(responses).length} + {#each Object.keys(responses) as q, qid (qid)} + {#await req_questions then questions} + {#each questions as question} + {#if question.id == q} +

+ {question.title} +

+ {#if question.kind == 'ucq'} + {#await question.getProposals()} +
+
+ Chargement des propositions … +
+ {:then proposals} +
+ + + {#each proposals as proposal (proposal.id)} + + + + + + {/each} + +
+ {proposal.label} + + {responsesbyid[q].filter((e) => e == proposal.id.toString()).length}/{responsesbyid[q].length} + + {Math.trunc(responsesbyid[q].filter((e) => e == proposal.id.toString()).length / responsesbyid[q].length * 1000)/10} % +
+
+ {/await} + {:else if question.kind == 'mcq'} + {#await question.getProposals()} +
+
+ Chargement des propositions … +
+ {:then proposals} +
+ + + {#each proposals as proposal (proposal.id)} + + + + + + {/each} + +
+ {proposal.label} + + {responsesbyid[q].filter((e) => e.indexOf(proposal.id.toString()) >= 0).length}/{responsesbyid[q].length} + + {Math.trunc(responsesbyid[q].filter((e) => e.indexOf(proposal.id.toString()) >= 0).length / responsesbyid[q].length * 1000)/10} % +
+
+ {/await} + {:else} +
+ +
+ {/if} + {/if} + {/each} + {/await} + {/each} + {/if} + +
+ + +

+ Connectés + {#if wsstats} + {wsstats.nb_clients} utilisateurs + {/if} +

+ {#if wsstats} +
+ {#each wsstats.users as login, lid (lid)} +
+
+ {login} + +
+
+ {/each} +
+ {/if} {/if} {/await} diff --git a/ui/src/routes/surveys/[sid]/live.svelte b/ui/src/routes/surveys/[sid]/live.svelte index 6217a61..a4318da 100644 --- a/ui/src/routes/surveys/[sid]/live.svelte +++ b/ui/src/routes/surveys/[sid]/live.svelte @@ -28,6 +28,10 @@ let req_question; let nosend = false; + let timer_init = null; + let timer_end = null; + let timer = 0; + let timer_cancel = null; function afterQUpdate(q) { value = undefined; @@ -46,6 +50,19 @@ } } + function updTimer() { + const now = new Date().getTime(); + if (now > timer_end) { + timer = 100; + clearInterval(timer_cancel); + timer_cancel = null; + } else { + const dist1 = timer_end - timer_init; + const dist2 = timer_end - now; + timer = Math.ceil(100-dist2*100/dist1); + } + } + function wsconnect() { const ws = new WebSocket((window.location.protocol == 'https'?'wss://':'ws://') + window.location.host + `/api/surveys/${sid}/ws`); @@ -72,8 +89,25 @@ console.log(data); if (data.action && data.action == "new_question") { show_question = data.question; + if (timer_cancel) { + clearInterval(timer_cancel); + timer_cancel = null; + } + if (data.timer) { + timer_init = new Date().getTime();; + timer_end = timer_init + data.timer; + updTimer(); + timer_cancel = setInterval(updTimer, 150); + } else { + timer_init = null; + } } else { show_question = null; + if (timer_cancel) { + clearInterval(timer_cancel); + timer_cancel = null; + } + timer_init = null; } }); } @@ -153,12 +187,15 @@ = 100} bind:value={value} on:change={sendValue} > - + {#if timer_init} +
+
85 && timer < 100} class:bg-danger={timer >= 100} role="progressbar" style="width: {timer}%">
+
+ {/if}
{#if question.kind != 'mcq' && question.kind != 'ucq'}