Skip to content

Commit cd071c1

Browse files
authored
Add a scoreboard widget to the backstages of physical auditoriums (#178)
1 parent 1054ccf commit cd071c1

File tree

4 files changed

+61
-15
lines changed

4 files changed

+61
-15
lines changed

src/Scheduler.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,8 @@ export class Scheduler {
318318
}
319319

320320
private async _execute(task: ITask) {
321+
// This is undefined when we're either just plain missing a talk room when we shouldn't,
322+
// or when the auditorium is physical (in which case talks don't have rooms).
321323
const confTalk: Talk | undefined = this.conference.getTalk(task.talk.id);
322324
const confAud = this.conference.getAuditorium(task.talk.auditoriumId);
323325
const confAudBackstage = this.conference.getAuditoriumBackstage(task.talk.auditoriumId);

src/commands/WidgetsCommand.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,17 @@ export class WidgetsCommand implements ICommand {
4141
await client.sendStateEvent(talk.roomId, scoreboardWidget.type, scoreboardWidget.state_key, scoreboardWidget.content);
4242
await client.sendStateEvent(talk.roomId, talkLayout.type, talkLayout.state_key, talkLayout.content);
4343
}
44+
45+
if ((await aud.getDefinition()).isPhysical) {
46+
// For physical auditoriums, the talks don't have anywhere to display a Q&A scoreboard.
47+
// So what we do instead is add a Q&A scoreboard to the backstage room, so that an organiser can read off
48+
// any questions if necessary.
49+
const backstage = conference.getAuditoriumBackstage(await aud.getId());
50+
const audScoreboardWidget = await LiveWidget.scoreboardForAuditorium(aud, client);
51+
const backstageLayout = LiveWidget.layoutForPhysicalAudBackstage(audScoreboardWidget);
52+
await client.sendStateEvent(backstage.roomId, audScoreboardWidget.type, audScoreboardWidget.state_key, audScoreboardWidget.content);
53+
await client.sendStateEvent(backstage.roomId, backstageLayout.type, backstageLayout.state_key, backstageLayout.content);
54+
}
4455
}
4556

4657
public async run(conference: Conference, client: MatrixClient, roomId: string, event: any, args: string[]) {

src/models/LiveWidget.ts

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,17 @@ export class LiveWidget {
104104
}
105105

106106
public static async scoreboardForTalk(talk: Talk, client: MatrixClient): Promise<IStateEvent<IWidget>> {
107-
const widgetId = sha256(JSON.stringify(await talk.getDefinition()) + "_SCOREBOARD");
108-
const aud = await config.RUNTIME.conference.getAuditorium(await talk.getAuditoriumId());
109-
const title = aud ? `Messages from ${await aud.getCanonicalAlias()}` : `Messages from ${await talk.getAuditoriumId()}`;
107+
const aud = config.RUNTIME.conference.getAuditorium(await talk.getAuditoriumId());
108+
if (aud === undefined) {
109+
throw new Error(`No auditorium ${await talk.getAuditoriumId()} for talk ${await talk.getId()}`);
110+
}
111+
return this.scoreboardForAuditorium(aud, client, talk);
112+
}
113+
114+
public static async scoreboardForAuditorium(aud: Auditorium, client: MatrixClient, talk?: Talk): Promise<IStateEvent<IWidget>> {
115+
// note: this is a little bit awkward, but there's nothing special about the widget ID, it just needs to be unique
116+
const widgetId = sha256(JSON.stringify([await aud.getId(), talk ? await talk.getId() : ""]) + "_SCOREBOARD");
117+
const title = `Messages from ${await aud.getCanonicalAlias()}`;
110118
return {
111119
type: "im.vector.modular.widgets",
112120
state_key: widgetId,
@@ -120,8 +128,8 @@ export class LiveWidget {
120128
url: config.webserver.publicBaseUrl + "/widgets/scoreboard.html?widgetId=$matrix_widget_id&auditoriumId=$auditoriumId&talkId=$talkId&theme=$theme",
121129
data: {
122130
title: title,
123-
auditoriumId: await talk.getAuditoriumId(),
124-
talkId: await talk.getId(),
131+
auditoriumId: await aud.getId(),
132+
talkId: talk ? await talk.getId() : null
125133
},
126134
} as IWidget,
127135
};
@@ -168,6 +176,23 @@ export class LiveWidget {
168176
};
169177
}
170178

179+
public static layoutForPhysicalAudBackstage(scoreboard: IStateEvent<IWidget>): IStateEvent<ILayout> {
180+
return {
181+
type: "io.element.widgets.layout",
182+
state_key: "",
183+
content: {
184+
widgets: {
185+
[scoreboard.state_key]: {
186+
container: "top",
187+
index: 0,
188+
width: 100,
189+
height: 60,
190+
},
191+
},
192+
},
193+
};
194+
}
195+
171196
public static layoutForTalk(qa: IStateEvent<IWidget>, scoreboard: IStateEvent<IWidget> | null): IStateEvent<ILayout> {
172197
const val: IStateEvent<ILayout> = {
173198
type: "io.element.widgets.layout",

src/web.ts

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -197,23 +197,31 @@ export async function renderScoreboardWidget(req: Request, res: Response) {
197197
if (!audId || Array.isArray(audId)) {
198198
return res.sendStatus(404);
199199
}
200-
const talkId = req.query?.['talkId'] as string;
201-
if (!talkId || Array.isArray(talkId)) {
202-
return res.sendStatus(404);
203-
}
204200

205201
const aud = config.RUNTIME.conference.getAuditorium(audId);
206202
if (!aud) {
207203
return res.sendStatus(404);
208204
}
209205

210-
const talk = config.RUNTIME.conference.getTalk(talkId);
211-
if (!talk) {
212-
return res.sendStatus(404);
213-
}
206+
if (!(await aud.getDefinition()).isPhysical) {
207+
// For physical auditoriums, the widget sits in the backstage room and so there isn't any talk ID to cross-reference, so skip
208+
// these checks for physical auditoriums.
209+
// I'm not sure why we want to check a talk ID anyway — 'security'?
210+
// But I'll leave it be.
214211

215-
if (await talk.getAuditoriumId() !== await aud.getId()) {
216-
return res.sendStatus(404);
212+
const talkId = req.query?.['talkId'] as string;
213+
if (!talkId || Array.isArray(talkId)) {
214+
return res.sendStatus(404);
215+
}
216+
217+
const talk = config.RUNTIME.conference.getTalk(talkId);
218+
if (!talk) {
219+
return res.sendStatus(404);
220+
}
221+
222+
if (await talk.getAuditoriumId() !== await aud.getId()) {
223+
return res.sendStatus(404);
224+
}
217225
}
218226

219227
return res.render('scoreboard.liquid', {

0 commit comments

Comments
 (0)