Zoom improvements and accessibility additions

This commit is contained in:
anhefti 2021-07-07 08:29:27 +02:00
parent c8932e9ce8
commit 5724a750b7
14 changed files with 197 additions and 76 deletions

View file

@ -35,7 +35,10 @@ public class ProctoringServiceSettings implements Entity {
TOWN_HALL,
ONE_TO_ONE,
BROADCAST,
ENABLE_CHAT
ENABLE_CHAT,
WAITING_ROOM,
SEND_REJOIN_COLLECTING_ROOM,
RESET_BROADCAST_ON_LAVE
}
public static final String ATTR_ENABLE_PROCTORING = "enableProctoring";

View file

@ -12,10 +12,12 @@ import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.rap.rwt.RWT;
import org.eclipse.swt.SWT;
import org.eclipse.swt.internal.widgets.IControlAdapter;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.swt.widgets.Text;
@ -27,6 +29,8 @@ import org.springframework.stereotype.Component;
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport;
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
import ch.ethz.seb.sebserver.gui.service.page.ComposerService;
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
import ch.ethz.seb.sebserver.gui.service.page.PageService;
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
@ -43,6 +47,11 @@ public class LoginPage implements TemplateComposer {
private static final Logger log = LoggerFactory.getLogger(LoginPage.class);
private static final LocTextKey TEXT_REGISTER = new LocTextKey("sebserver.login.register");
private static final LocTextKey TEXT_LOGIN = new LocTextKey("sebserver.login.login");
private static final LocTextKey TEXT_PWD = new LocTextKey("sebserver.login.pwd");
private static final LocTextKey TEXT_USERNAME = new LocTextKey("sebserver.login.username");
private final PageService pageService;
private final AuthorizationContextHolder authorizationContextHolder;
private final WidgetFactory widgetFactory;
@ -67,7 +76,6 @@ public class LoginPage implements TemplateComposer {
public void compose(final PageContext pageContext) {
final Composite parent = pageContext.getParent();
WidgetFactory.setTestId(parent, "login-page");
WidgetFactory.setARIARole(parent, "composite");
final Composite loginGroup = new Composite(parent, SWT.NONE);
final GridLayout rowLayout = new GridLayout();
@ -76,16 +84,17 @@ public class LoginPage implements TemplateComposer {
loginGroup.setLayout(rowLayout);
loginGroup.setData(RWT.CUSTOM_VARIANT, WidgetFactory.CustomVariant.LOGIN.key);
final Label name = this.widgetFactory.labelLocalized(loginGroup, "sebserver.login.username");
final Label name = this.widgetFactory.labelLocalized(loginGroup, TEXT_USERNAME);
name.setLayoutData(new GridData(300, -1));
name.setAlignment(SWT.BOTTOM);
final Text loginName = this.widgetFactory.textInput(loginGroup);
final Text loginName = this.widgetFactory.textInput(loginGroup, TEXT_USERNAME);
loginName.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false));
GridData gridData = new GridData(SWT.FILL, SWT.TOP, false, false);
gridData.verticalIndent = 10;
final Label pwd = this.widgetFactory.labelLocalized(loginGroup, "sebserver.login.pwd");
final Label pwd = this.widgetFactory.labelLocalized(loginGroup, TEXT_PWD);
pwd.setLayoutData(gridData);
final Text loginPassword = this.widgetFactory.passwordInput(loginGroup);
final Text loginPassword = this.widgetFactory.passwordInput(loginGroup, TEXT_PWD);
loginPassword.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false));
final Composite buttons = new Composite(loginGroup, SWT.NONE);
@ -93,7 +102,7 @@ public class LoginPage implements TemplateComposer {
buttons.setLayout(new GridLayout(2, false));
buttons.setData(RWT.CUSTOM_VARIANT, WidgetFactory.CustomVariant.LOGIN_BACK.key);
final Button loginButton = this.widgetFactory.buttonLocalized(buttons, "sebserver.login.login");
final Button loginButton = this.widgetFactory.buttonLocalized(buttons, TEXT_LOGIN);
gridData = new GridData(SWT.LEFT, SWT.TOP, false, false);
gridData.verticalIndent = 10;
loginButton.setLayoutData(gridData);
@ -127,12 +136,17 @@ public class LoginPage implements TemplateComposer {
});
if (this.registeringEnabled) {
final Button registerButton = this.widgetFactory.buttonLocalized(buttons, "sebserver.login.register");
final Button registerButton = this.widgetFactory.buttonLocalized(buttons, TEXT_REGISTER);
gridData = new GridData(SWT.LEFT, SWT.TOP, false, false);
gridData.verticalIndent = 10;
registerButton.setLayoutData(gridData);
registerButton.addListener(SWT.Selection, event -> pageContext.forwardToPage(this.defaultRegisterPage));
}
ComposerService.traversePageTree(
parent,
comp -> comp instanceof Control,
comp -> comp.getAdapter(IControlAdapter.class).setTabIndex(0));
}
private void login(

View file

@ -119,10 +119,10 @@ public final class TextFieldBuilder extends FieldBuilder<String> {
}
final Text textInput = (this.isNumber)
? builder.widgetFactory.numberInput(fieldGrid, this.numberCheck, readonly)
? builder.widgetFactory.numberInput(fieldGrid, this.numberCheck, readonly, this.label)
: (this.isArea)
? builder.widgetFactory.textAreaInput(fieldGrid, readonly)
: builder.widgetFactory.textInput(fieldGrid, this.isPassword, readonly);
? builder.widgetFactory.textAreaInput(fieldGrid, readonly, this.label)
: builder.widgetFactory.textInput(fieldGrid, this.isPassword, readonly, this.label);
if (builder.pageService.getFormTooltipMode() == PageService.FormTooltipMode.INPUT) {
builder.pageService.getPolyglotPageService().injectI18nTooltip(

View file

@ -46,6 +46,7 @@ import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.AuthorizationContextHolder;
import ch.ethz.seb.sebserver.gui.widget.Message;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.AriaRole;
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
@Lazy
@ -285,6 +286,8 @@ public class DefaultPageLayout implements TemplateComposer {
log.error("Invalid markup for 'Imprint'", e);
}
});
WidgetFactory.setARIARole(imprint, AriaRole.link);
}
if (StringUtils.isNoneBlank(i18nSupport.getText(ABOUT_TEXT_KEY, ""))) {
final Label about = this.widgetFactory.labelLocalized(
@ -299,6 +302,8 @@ public class DefaultPageLayout implements TemplateComposer {
log.error("Invalid markup for 'About'", e);
}
});
WidgetFactory.setARIARole(about, AriaRole.link);
}
if (StringUtils.isNoneBlank(i18nSupport.getText(HELP_TEXT_KEY, ""))) {
final Label help = this.widgetFactory.labelLocalized(
@ -318,6 +323,7 @@ public class DefaultPageLayout implements TemplateComposer {
}
});
WidgetFactory.setARIARole(help, AriaRole.link);
}
this.widgetFactory.labelLocalized(
footerRight,

View file

@ -321,7 +321,9 @@ public class TableFilter<ROW> {
final Composite innerComposite = createInnerComposite(parent);
final GridData gridData = new GridData(SWT.FILL, SWT.END, true, true);
this.textInput = TableFilter.this.entityTable.widgetFactory.textInput(innerComposite);
this.textInput = TableFilter.this.entityTable.widgetFactory.textInput(
innerComposite,
super.attribute.columnName);
this.textInput.setLayoutData(gridData);
return this;
}

View file

@ -8,9 +8,11 @@
package ch.ethz.seb.sebserver.gui.widget;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.util.Tuple;
import ch.ethz.seb.sebserver.gui.service.page.PageService;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.rap.rwt.widgets.DropDown;
import org.eclipse.swt.SWT;
@ -25,10 +27,9 @@ import org.eclipse.swt.widgets.Text;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import ch.ethz.seb.sebserver.gbl.Constants;
import ch.ethz.seb.sebserver.gbl.util.Tuple;
import ch.ethz.seb.sebserver.gui.service.page.PageService;
public final class MultiSelectionCombo extends Composite implements Selection {
@ -68,7 +69,7 @@ public final class MultiSelectionCombo extends Composite implements Selection {
setLayout(gridLayout);
this.addListener(SWT.Resize, this::adaptColumnWidth);
this.textInput = widgetFactory.textInput(this);
this.textInput = widgetFactory.textInput(this, "selection");
this.textCell = new GridData(SWT.LEFT, SWT.CENTER, true, true);
this.textInput.setLayoutData(this.textCell);
this.dropDown = new DropDown(this.textInput, SWT.NONE);

View file

@ -152,7 +152,8 @@ public final class ThresholdList extends Composite {
} else {
Double.parseDouble(s);
}
});
},
VALUE_TEXT_KEY);
final GridData valueCell = new GridData(SWT.FILL, SWT.CENTER, true, false);
valueInput.setLayoutData(valueCell);

View file

@ -70,6 +70,10 @@ public class WidgetFactory {
private static final String ADD_HTML_ATTR_TEST_ID = "test-id";
private static final String SUB_TITLE_TExT_SUFFIX = ".subtitle";
public enum AriaRole {
link
}
private static final Logger log = LoggerFactory.getLogger(WidgetFactory.class);
public static final int TEXT_AREA_INPUT_MIN_HEIGHT = 100;
@ -363,18 +367,21 @@ public class WidgetFactory {
public Button buttonLocalized(final Composite parent, final String locTextKey) {
final Button button = new Button(parent, SWT.NONE);
setAttribute(button, "role", "button");
this.polyglotPageService.injectI18n(button, new LocTextKey(locTextKey));
return button;
}
public Button buttonLocalized(final Composite parent, final LocTextKey locTextKey) {
final Button button = new Button(parent, SWT.NONE);
setAttribute(button, "role", "button");
this.polyglotPageService.injectI18n(button, locTextKey);
return button;
}
public Button buttonLocalized(final Composite parent, final CustomVariant variant, final String locTextKey) {
final Button button = new Button(parent, SWT.NONE);
setAttribute(button, "role", "button");
this.polyglotPageService.injectI18n(button, new LocTextKey(locTextKey));
button.setData(RWT.CUSTOM_VARIANT, variant.key);
return button;
@ -387,6 +394,7 @@ public class WidgetFactory {
final LocTextKey toolTipKey) {
final Button button = new Button(parent, type);
setAttribute(button, "role", "button");
this.polyglotPageService.injectI18n(button, locTextKey, toolTipKey);
return button;
}
@ -453,42 +461,85 @@ public class WidgetFactory {
return labelLocalized;
}
public Text textInput(final Composite content) {
return textInput(content, false, false);
public Text textInput(final Composite content, final LocTextKey label) {
return textInput(content, false, false, this.i18nSupport.getText(label));
}
public Text textLabel(final Composite content) {
return textInput(content, false, true);
public Text textInput(final Composite content, final String label) {
return textInput(content, false, false, label);
}
public Text passwordInput(final Composite content) {
return textInput(content, true, false);
public Text passwordInput(final Composite content, final LocTextKey label) {
return textInput(content, true, false, this.i18nSupport.getText(label));
}
public Text textAreaInput(final Composite content, final boolean readonly) {
return readonly
public Text passwordInput(final Composite content, final String label) {
return textInput(content, true, false, label);
}
public Text textAreaInput(
final Composite content,
final boolean readonly,
final LocTextKey label) {
return textAreaInput(content, readonly, this.i18nSupport.getText(label));
}
public Text textAreaInput(
final Composite content,
final boolean readonly,
final String label) {
final Text input = readonly
? new Text(content, SWT.LEFT | SWT.MULTI)
: new Text(content, SWT.LEFT | SWT.BORDER | SWT.MULTI);
if (label != null) {
WidgetFactory.setAttribute(input, "aria-label", label);
}
return input;
}
public Text textInput(final Composite content, final boolean password, final boolean readonly) {
return readonly
public Text textInput(
final Composite content,
final boolean password,
final boolean readonly,
final LocTextKey label) {
return textInput(content, password, readonly, this.i18nSupport.getText(label));
}
public Text textInput(
final Composite content,
final boolean password,
final boolean readonly,
final String label) {
final Text input = readonly
? new Text(content, SWT.LEFT)
: new Text(content, (password)
? SWT.LEFT | SWT.BORDER | SWT.PASSWORD
: SWT.LEFT | SWT.BORDER);
}
public Text numberInput(final Composite content, final Consumer<String> numberCheck) {
return numberInput(content, numberCheck, false);
}
public Text numberInput(final Composite content, final Consumer<String> numberCheck, final boolean readonly) {
if (readonly) {
return new Text(content, SWT.LEFT | SWT.READ_ONLY);
if (label != null) {
WidgetFactory.setAttribute(input, "aria-label", label);
}
return input;
}
final Text numberInput = new Text(content, SWT.RIGHT | SWT.BORDER);
public Text numberInput(final Composite content, final Consumer<String> numberCheck, final LocTextKey label) {
return numberInput(content, numberCheck, false, label);
}
public Text numberInput(
final Composite content,
final Consumer<String> numberCheck,
final boolean readonly,
final LocTextKey label) {
final Text numberInput = new Text(content, (readonly) ? SWT.LEFT | SWT.READ_ONLY : SWT.RIGHT | SWT.BORDER);
if (label != null) {
WidgetFactory.setAttribute(numberInput, "aria-label", this.i18nSupport.getText(label));
}
if (numberCheck != null) {
numberInput.addListener(SWT.Verify, event -> {
final String value = event.text;
@ -884,11 +935,11 @@ public class WidgetFactory {
setAttribute(widget, ADD_HTML_ATTR_TEST_ID, value);
}
public static void setARIARole(final Widget widget, final String value) {
setAttribute(widget, ADD_HTML_ATTR_ARIA_ROLE, value);
public static void setARIARole(final Widget widget, final AriaRole role) {
setAttribute(widget, ADD_HTML_ATTR_ARIA_ROLE, role.name());
}
private static void setAttribute(final Widget widget, final String name, final String value) {
public static void setAttribute(final Widget widget, final String name, final String value) {
if (!widget.isDisposed()) {
final String $el = widget instanceof Text ? "$input" : "$el";
final String id = WidgetUtil.getId(widget);

View file

@ -27,6 +27,7 @@ import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamStatus;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringRoomConnection;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringFeature;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
import ch.ethz.seb.sebserver.gbl.model.session.ClientInstruction.InstructionType;
@ -230,7 +231,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
} else if (remoteProctoringRoom.townhallRoom) {
closeTownhall(examId, settings, examProctoringService);
} else {
closeCollectingRoom(examId, roomName, examProctoringService);
closeCollectingRoom(examId, roomName, settings, examProctoringService);
}
});
}
@ -377,11 +378,13 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
.getActiveConnectionTokens(examId)
.getOrThrow();
// Send default settings to clients
this.sendReconfigurationInstructions(
examId,
connectionTokens,
examProctoringService.getDefaultReconfigInstructionAttributes());
// Send default settings to clients if fearture is enabled
if (proctoringSettings.enabledFeatures.contains(ProctoringFeature.RESET_BROADCAST_ON_LAVE)) {
this.sendReconfigurationInstructions(
examId,
connectionTokens,
examProctoringService.getDefaultReconfigInstructionAttributes());
}
// Close and delete town-hall room
this.remoteProctoringRoomDAO
@ -403,6 +406,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
private void closeCollectingRoom(
final Long examId,
final String roomName,
final ProctoringServiceSettings proctoringSettings,
final ExamProctoringService examProctoringService) {
// get all connections of the room
@ -412,11 +416,13 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
.map(cc -> cc.connectionToken)
.collect(Collectors.toList());
// Send default settings to clients
this.sendReconfigurationInstructions(
examId,
connectionTokens,
examProctoringService.getDefaultReconfigInstructionAttributes());
// Send default settings to clients if feature is enabled
if (proctoringSettings.enabledFeatures.contains(ProctoringFeature.RESET_BROADCAST_ON_LAVE)) {
this.sendReconfigurationInstructions(
examId,
connectionTokens,
examProctoringService.getDefaultReconfigInstructionAttributes());
}
}
private void cleanupBreakOutRooms(final ClientConnectionRecord cc) {
@ -453,11 +459,13 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
final ExamProctoringService examProctoringService,
final RemoteProctoringRoom remoteProctoringRoom) {
// Send default settings to clients
this.sendReconfigurationInstructions(
examId,
remoteProctoringRoom.breakOutConnections,
examProctoringService.getDefaultReconfigInstructionAttributes());
// Send default settings to clients if feature is enabled
if (proctoringSettings.enabledFeatures.contains(ProctoringFeature.RESET_BROADCAST_ON_LAVE)) {
this.sendReconfigurationInstructions(
examId,
remoteProctoringRoom.breakOutConnections,
examProctoringService.getDefaultReconfigInstructionAttributes());
}
// Dispose the proctoring room on service side
examProctoringService

View file

@ -57,6 +57,7 @@ import ch.ethz.seb.sebserver.gbl.async.CircuitBreaker;
import ch.ethz.seb.sebserver.gbl.client.ClientCredentials;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringRoomConnection;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringFeature;
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringServerType;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
@ -443,8 +444,13 @@ public class ZoomProctoringService implements ExamProctoringService {
return Result.tryCatch(() -> {
if (!proctoringSettings.enabledFeatures.contains(ProctoringFeature.SEND_REJOIN_COLLECTING_ROOM)) {
// do nothing if the rejoin feature is not enabled
return;
}
if (this.remoteProctoringRoomDAO.isTownhallRoomActive(proctoringSettings.examId)) {
// do nothing is the town-hall of this exam is open. The clients will automatically join
// do nothing if the town-hall of this exam is open. The clients will automatically join
// the meeting once the town-hall has been closed
return;
}
@ -502,6 +508,7 @@ public class ZoomProctoringService implements ExamProctoringService {
proctoringSettings.serverURL,
credentials,
roomName);
final UserResponse userResponse = this.jsonMapper.readValue(
createUser.getBody(),
UserResponse.class);
@ -514,7 +521,9 @@ public class ZoomProctoringService implements ExamProctoringService {
userResponse.id,
subject,
duration,
meetingPwd);
meetingPwd,
proctoringSettings.enabledFeatures.contains(ProctoringFeature.WAITING_ROOM));
final MeetingResponse meetingResponse = this.jsonMapper.readValue(
createMeeting.getBody(),
MeetingResponse.class);
@ -525,6 +534,7 @@ public class ZoomProctoringService implements ExamProctoringService {
userResponse.id,
meetingResponse.start_url,
meetingResponse.join_url);
final String additionalZoomRoomDataString = this.jsonMapper
.writeValueAsString(additionalZoomRoomData);
@ -563,23 +573,29 @@ public class ZoomProctoringService implements ExamProctoringService {
final StringBuilder builder = new StringBuilder();
final Encoder urlEncoder = Base64.getUrlEncoder().withoutPadding();
final String jwtHeaderPart = urlEncoder.encodeToString(
ZOOM_ACCESS_TOKEN_HEADER.getBytes(StandardCharsets.UTF_8));
final String jwtHeaderPart = urlEncoder
.encodeToString(ZOOM_ACCESS_TOKEN_HEADER.getBytes(StandardCharsets.UTF_8));
final String jwtPayload = String.format(
ZOOM_API_ACCESS_TOKEN_PAYLOAD.replaceAll(" ", "").replaceAll("\n", ""),
ZOOM_API_ACCESS_TOKEN_PAYLOAD
.replaceAll(" ", "")
.replaceAll("\n", ""),
credentials.clientIdAsString(),
expTime);
final String jwtPayloadPart = urlEncoder.encodeToString(
jwtPayload.getBytes(StandardCharsets.UTF_8));
final String jwtPayloadPart = urlEncoder
.encodeToString(jwtPayload.getBytes(StandardCharsets.UTF_8));
final String message = jwtHeaderPart + "." + jwtPayloadPart;
final Mac sha256_HMAC = Mac.getInstance(TOKEN_ENCODE_ALG);
final SecretKeySpec secret_key = new SecretKeySpec(
Utils.toByteArray(decryptedSecret),
TOKEN_ENCODE_ALG);
sha256_HMAC.init(secret_key);
final String hash = urlEncoder.encodeToString(
sha256_HMAC.doFinal(Utils.toByteArray(message)));
final String hash = urlEncoder
.encodeToString(sha256_HMAC.doFinal(Utils.toByteArray(message)));
builder.append(message)
.append(".")
@ -710,7 +726,8 @@ public class ZoomProctoringService implements ExamProctoringService {
final String userId,
final String topic,
final int duration,
final CharSequence password) {
final CharSequence password,
final boolean waitingRoom) {
try {
@ -723,7 +740,8 @@ public class ZoomProctoringService implements ExamProctoringService {
final CreateMeetingRequest createRoomRequest = new CreateMeetingRequest(
topic,
duration,
password);
password,
waitingRoom);
final String body = this.zoomProctoringService.jsonMapper.writeValueAsString(createRoomRequest);
final HttpHeaders headers = getHeaders(credentials);

View file

@ -123,7 +123,8 @@ public interface ZoomRoomRequestResponse {
public CreateMeetingRequest(
final String topic,
final int duration,
final CharSequence password) {
final CharSequence password,
final boolean waitingRoom) {
this.type = 2; // Scheduled Meeting
this.start_time = DateTime.now(DateTimeZone.UTC).toString("yyyy-MM-dd'T'HH:mm:ss");
@ -131,18 +132,25 @@ public interface ZoomRoomRequestResponse {
this.timezone = DateTimeZone.UTC.getID();
this.topic = topic;
this.password = password;
this.settings = new Settings();
this.settings = new Settings(waitingRoom);
}
@JsonIgnoreProperties(ignoreUnknown = true)
static class Settings {
@JsonProperty final boolean host_video = true;
@JsonProperty final boolean participant_video = true;
@JsonProperty final boolean join_before_host = true;
@JsonProperty final boolean host_video = false;
@JsonProperty final boolean participant_video = false;
@JsonProperty final boolean mute_upon_entry = true;
@JsonProperty final boolean join_before_host;
@JsonProperty final int jbh_time = 0;
@JsonProperty final boolean use_pmi = false;
@JsonProperty final String audio = "voip";
@JsonProperty final boolean waiting_room = false;
@JsonProperty final boolean waiting_room;
@JsonProperty final boolean allow_multiple_devices = false;
public Settings(final boolean waitingRoom) {
this.join_before_host = !waitingRoom;
this.waiting_room = waitingRoom;
}
}
}

View file

@ -99,6 +99,9 @@
})
window.addEventListener('unload', () => {
ZoomMtg.muteAll({
muteAll: true
});
ZoomMtg.endMeeting({});
});
</script>

View file

@ -666,6 +666,9 @@ sebserver.exam.proctoring.form.features.TOWN_HALL=Town-Hall Room
sebserver.exam.proctoring.form.features.ONE_TO_ONE=One to One Room
sebserver.exam.proctoring.form.features.BROADCAST=Broadcasting Feature
sebserver.exam.proctoring.form.features.ENABLE_CHAT=Chat Feature
sebserver.exam.proctoring.form.features.WAITING_ROOM=Enable waiting room for collecting rooms
sebserver.exam.proctoring.form.features.SEND_REJOIN_COLLECTING_ROOM=Force rejoin for collecting rooms
sebserver.exam.proctoring.form.features.RESET_BROADCAST_ON_LAVE=Reset broadcast on leave
sebserver.exam.proctoring.type.servertype.JITSI_MEET=Jitsi Meet Server
sebserver.exam.proctoring.type.servertype.JITSI_MEET.tooltip=Use a Jitsi Meet server for proctoring

View file

@ -166,6 +166,9 @@ public class ZoomWindowScriptResolverTest {
+ " })\r\n"
+ " \r\n"
+ " window.addEventListener('unload', () => {\r\n"
+ " ZoomMtg.muteAll({\r\n"
+ " muteAll: true\r\n"
+ " });\r\n"
+ " ZoomMtg.endMeeting({});\r\n"
+ " });\r\n"
+ " </script>\r\n"