Skip to content

Commit fbb57b2

Browse files
committed
Allow polls for restricted group of participants
1 parent 5caf5f6 commit fbb57b2

File tree

4 files changed

+198
-13
lines changed

4 files changed

+198
-13
lines changed

src/socket/interface.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,9 @@ export class SocketInterface {
149149
this.decodedtoken = event.data.decodedToken
150150
this.updateUserHash()
151151
break
152+
case 'setUserhash': // Note this is a network userhash, and not a device userhash
153+
this.networkUserhash = event.data.userhash
154+
break
152155
case 'idinform':
153156
if (event.data.id) this.id = event.data.id
154157
break
@@ -197,6 +200,10 @@ export class SocketInterface {
197200
return this.userhash
198201
}
199202

203+
getNetworkUserHash() {
204+
return this.networkUserhash
205+
}
206+
200207
onMessageError(event) {}
201208

202209
onError(event) {}

src/socket/worker.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,14 @@ class SocketWorker {
422422
this.scheduleReauthor() // request renewal
423423
})
424424

425+
this.socket.on('userhash', (userhash) => {
426+
this.userhash = userhash
427+
globalThis.postMessage({
428+
task: 'setUserhash',
429+
userhash
430+
})
431+
})
432+
425433
const toIden = async (iden, id) => {
426434
try {
427435
const toret = {

src/ui/failsboard.jsx

Lines changed: 160 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import { Button } from 'primereact/button'
2121
import { Chart } from 'primereact/chart'
2222
import { Dialog } from 'primereact/dialog'
23+
import { InputSwitch } from 'primereact/inputswitch'
2324
import { ListBox } from 'primereact/listbox'
2425
import { Steps } from 'primereact/steps'
2526
import { Toast } from 'primereact/toast'
@@ -38,6 +39,8 @@ import Pica from 'pica'
3839
import ImageBlobReduce from 'image-blob-reduce'
3940
import { notebookEditPseudoAppid } from './blackboard/jupyterhublet'
4041
import { maybeUseLatex, convertToLatex } from './misc/latex'
42+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
43+
import { faUser } from '@fortawesome/free-solid-svg-icons'
4144

4245
export class FailsBoard extends FailsBasis {
4346
constructor(props) {
@@ -57,6 +60,7 @@ export class FailsBoard extends FailsBasis {
5760
this.state.pictIndex = 0
5861
this.state.availscreens = []
5962
this.state.welcomeMessageSend = 0
63+
this.state.pollLimitedGroup = false
6064

6165
this.availscreensmenu = React.createRef()
6266

@@ -82,6 +86,7 @@ export class FailsBoard extends FailsBasis {
8286
this.blockChat = this.blockChat.bind(this)
8387
this.allowVideoquestion = this.allowVideoquestion.bind(this)
8488
this.onStartPoll = this.onStartPoll.bind(this)
89+
this.onSelParticipantsPoll = this.onSelParticipantsPoll.bind(this)
8590
this.onStartSelPoll = this.onStartSelPoll.bind(this)
8691
this.onFinishSelPoll = this.onFinishSelPoll.bind(this)
8792
this.ipynbTemplate = this.ipynbTemplate.bind(this)
@@ -201,14 +206,19 @@ export class FailsBoard extends FailsBasis {
201206
pollsel: undefined,
202207
pollshowres: false,
203208
pollvotes: {},
204-
pollballots: []
209+
pollballots: [],
210+
pollparticipantsServer: undefined
205211
})
206212
})
207213

208214
notepadsocket.on('finishPoll', (data) => {
209215
console.log('finishpoll incoming', data)
210216

211-
this.setState({ polltask: 2, pollsel: undefined })
217+
this.setState({
218+
polltask: 2,
219+
pollsel: undefined,
220+
pollparticipantsServer: data.participants
221+
})
212222
})
213223

214224
notepadsocket.on('castvote', (data) => {
@@ -530,13 +540,46 @@ export class FailsBoard extends FailsBasis {
530540
this.setState({ pollcoll: ret })
531541
}
532542

543+
onSelParticipantsPoll() {
544+
// if there are no participants set, we set some
545+
if (!this.state.pollparticipants) {
546+
let pollparticipants = []
547+
if (this.state.identobj?.idents) {
548+
pollparticipants = this.state.identobj?.idents
549+
.filter((el) => el.purpose === 'notes')
550+
.map(({ userhash }) => userhash)
551+
}
552+
this.setState({ polltask: 0.5, pollparticipants })
553+
return
554+
} else {
555+
// we should filter out unavailable userhashes
556+
let pollparticipants = []
557+
if (this.state.identobj?.idents) {
558+
const hashes = this.state.identobj?.idents
559+
.filter((el) => el.purpose === 'notes')
560+
.map(({ userhash }) => userhash)
561+
562+
pollparticipants = this.state.pollparticipants.filter((el) =>
563+
hashes.includes(el)
564+
)
565+
this.setState({ polltask: 0.5, pollparticipants })
566+
return
567+
}
568+
}
569+
this.setState({ polltask: 0.5 })
570+
}
571+
533572
onStartSelPoll() {
534573
if (!this.state.pollcoll) return
535574
const polfind = this.state.pollcoll.find(
536575
(el) => el.id === this.state.pollsel
537576
)
538577
this.socket.simpleEmit('startPoll', {
539-
poll: polfind
578+
poll: polfind,
579+
limited: this.state.pollLimitedGroup,
580+
participants: this.state.pollLimitedGroup
581+
? this.state.pollparticipants
582+
: undefined
540583
})
541584
}
542585

@@ -664,6 +707,25 @@ export class FailsBoard extends FailsBasis {
664707
const pollanswers = []
665708
let numballots = 0
666709

710+
let pollpotparticipants = []
711+
if (this.state.polltask === 0.5) {
712+
if (this.state.identobj?.idents) {
713+
pollpotparticipants = this.state.identobj?.idents
714+
.filter((el) => el.purpose === 'notes')
715+
.map(({ displayname, userhash, purpose }) => ({
716+
displayname,
717+
userhash,
718+
purpose
719+
}))
720+
pollpotparticipants = pollpotparticipants.filter(
721+
(el, index) =>
722+
pollpotparticipants.findIndex(
723+
(el2) => el2.userhash === el.userhash
724+
) === index
725+
)
726+
}
727+
}
728+
667729
if (this.state.polltask === 1 || this.state.polltask === 2) {
668730
const tpollres = this.calcPollresults()
669731
const tpolldata = tpollres.data
@@ -705,6 +767,14 @@ export class FailsBoard extends FailsBasis {
705767
{ label: 'Poll' },
706768
{ label: 'Results' }
707769
]
770+
let pollActiveIndex = this.state.polltask
771+
if (this.state.pollLimitedGroup) {
772+
pollitems.splice(1, 0, { label: 'Select' })
773+
if (pollActiveIndex > 0.5) {
774+
pollActiveIndex++
775+
}
776+
if (pollActiveIndex === 0.5) pollActiveIndex = 1
777+
}
708778
const blackbackground =
709779
typeof this.state.blackbackground === 'undefined'
710780
? true
@@ -886,12 +956,16 @@ export class FailsBoard extends FailsBasis {
886956
<Dialog
887957
header='Poll'
888958
visible={typeof this.state.polltask !== 'undefined'}
889-
closable={this.state.polltask === 2 || this.state.polltask === 0}
959+
closable={
960+
this.state.polltask === 2 ||
961+
this.state.polltask === 0 ||
962+
this.state.polltask === 0.5
963+
}
890964
onHide={() => {
891965
this.setState({ polltask: undefined, pollsel: undefined })
892966
}}
893967
>
894-
<Steps model={pollitems} activeIndex={this.state.polltask} />
968+
<Steps model={pollitems} activeIndex={pollActiveIndex} />
895969
{this.state.polltask === 0 && (
896970
<React.Fragment>
897971
<ListBox
@@ -903,12 +977,88 @@ export class FailsBoard extends FailsBasis {
903977
(el) => el.children && el.children.length > 1
904978
)
905979
}
980+
style={{
981+
maxHeight: '60vh',
982+
minWidth: '30vw',
983+
overflow: 'auto'
984+
}}
906985
optionLabel='name'
907986
optionValue='id'
908987
itemTemplate={this.pollTemplate}
909988
onChange={(e) => this.setState({ pollsel: e.value })}
910989
/>
911-
{this.state.pollsel && (
990+
<div className='p-d-flex p-ai-center'>
991+
<InputSwitch
992+
id='switchPollLG'
993+
checked={this.state.pollLimitedGroup}
994+
onChange={(e) => this.setState({ pollLimitedGroup: e.value })}
995+
/>
996+
<div className='p-ml-2'>
997+
<label
998+
htmlFor='limitedGroupSwitch'
999+
className='p-d-block p-text-bold'
1000+
>
1001+
Limited Group Voting
1002+
</label>
1003+
{/* p-text-secondary uses the theme's standard muted color */}
1004+
<small className='p-text-secondary'>
1005+
{this.state.pollLimitedGroup
1006+
? 'Only selected participants.'
1007+
: 'All participants.'}
1008+
</small>
1009+
</div>
1010+
</div>
1011+
{this.state.pollsel && !this.state.pollLimitedGroup && (
1012+
<Button
1013+
label='Start poll'
1014+
icon='pi pi-chart-bar'
1015+
className='p-m-2'
1016+
onClick={this.onStartSelPoll}
1017+
/>
1018+
)}
1019+
{this.state.pollsel && this.state.pollLimitedGroup && (
1020+
<Button
1021+
label='Select participants'
1022+
icon='pi pi-id-card'
1023+
className='p-m-2'
1024+
onClick={this.onSelParticipantsPoll}
1025+
/>
1026+
)}
1027+
</React.Fragment>
1028+
)}
1029+
{this.state.polltask === 0.5 && (
1030+
<React.Fragment>
1031+
<div className='p-d-flex p-ai-center'>
1032+
<div className='p-mr-2'>
1033+
<h3>Select voters</h3>
1034+
</div>
1035+
</div>
1036+
{pollpotparticipants.length === 0 && <b> No voters present!</b>}
1037+
{pollpotparticipants.length > 0 && (
1038+
<ListBox
1039+
optionLabel='displayname'
1040+
optionValue='userhash'
1041+
options={pollpotparticipants}
1042+
style={{
1043+
maxHeight: '60vh',
1044+
minHeight: '10vh',
1045+
minWidth: '30vw',
1046+
overflowY: 'auto'
1047+
}}
1048+
itemTemplate={(element) => (
1049+
<span>
1050+
<FontAwesomeIcon icon={faUser} /> {element.displayname}
1051+
</span>
1052+
)}
1053+
multiple
1054+
value={this.state.pollparticipants}
1055+
onChange={(ev) =>
1056+
this.setState({ pollparticipants: ev.value })
1057+
}
1058+
/>
1059+
)}
1060+
{(this.state.pollparticipants?.length > 0 ||
1061+
true) /* just for debugging */ && (
9121062
<Button
9131063
label='Start poll'
9141064
icon='pi pi-chart-bar'
@@ -987,7 +1137,10 @@ export class FailsBoard extends FailsBasis {
9871137
{
9881138
ballots: this.state.pollballots,
9891139
poll: this.state.curpoll,
990-
votes: this.state.pollvotes
1140+
votes: this.state.pollvotes,
1141+
eglibleVoters:
1142+
this.state.pollparticipantsServer ||
1143+
'unrestricted'
9911144
},
9921145
null,
9931146
2

src/ui/failsnotes.jsx

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -130,11 +130,19 @@ export class FailsNotes extends FailsBasis {
130130

131131
notessocket.on('startPoll', (data) => {
132132
console.log('startpoll incoming', data)
133+
let canVote = true
134+
if (typeof data.canVote !== 'undefined') {
135+
canVote = data.canVote
136+
}
137+
if (typeof data.participants !== 'undefined') {
138+
canVote &&= data.participants.includes(this.socket.getNetworkUserHash())
139+
}
133140

134141
this.setState({
135-
polltask: 1,
142+
polltask: canVote ? 1 : 2,
136143
curpoll: data,
137-
votesel: [],
144+
votesel: canVote ? [] : undefined,
145+
pollCanVote: canVote,
138146
pollvotes: {},
139147
polldata: undefined,
140148
pollballotid: undefined
@@ -325,7 +333,7 @@ export class FailsNotes extends FailsBasis {
325333
})
326334

327335
console.log('cast vote incl ballot id', ret)
328-
this.setState({ pollballotid: ret.ballot })
336+
this.setState({ pollballotid: ret.ballot, pollerror: ret.error })
329337
}
330338

331339
onNotesmodeEnterDialog({ persist, tryPersist, persistGranted }) {
@@ -848,8 +856,12 @@ export class FailsNotes extends FailsBasis {
848856
{this.state.reloading && <h1>Loading...</h1>}
849857
{!this.state.reloading && (
850858
<React.Fragment>
851-
<h1>The screencast is currently deactivated!</h1>
852-
<h2>Ask the docent for activation, when ready!</h2>
859+
<h1 key='screendeact1'>
860+
The screencast is currently deactivated!
861+
</h1>
862+
<h2 key='screendeact2'>
863+
Ask the docent for activation, when ready!
864+
</h2>
853865
</React.Fragment>
854866
)}
855867
</div>
@@ -913,6 +925,7 @@ export class FailsNotes extends FailsBasis {
913925
<React.Fragment>
914926
<Chart
915927
type='bar'
928+
key='chart'
916929
data={polldata}
917930
options={{
918931
indexAxis: 'x',
@@ -921,13 +934,17 @@ export class FailsNotes extends FailsBasis {
921934
}}
922935
/>
923936
{pollanswers}
924-
<h4> Voting is over!</h4>
937+
<h4 key='voteover'> Voting is over!</h4>
925938
</React.Fragment>
926939
)}
927940
{!polldata && <h4> Votes are still casted! </h4>}
941+
{!this.state.pollCanVote && <h4> Not eligible for voting. </h4>}
928942
{this.state.pollballotid && (
929943
<h4>My ballot id: {this.state.pollballotid}</h4>
930944
)}
945+
{this.state.pollerror && (
946+
<h4>Error while casting ballot: {this.state.pollerror}</h4>
947+
)}
931948
</React.Fragment>
932949
)}
933950
</Dialog>

0 commit comments

Comments
 (0)