Zoom improvements and accessibility additions
This commit is contained in:
parent
c8932e9ce8
commit
5724a750b7
14 changed files with 197 additions and 76 deletions
|
@ -35,7 +35,10 @@ public class ProctoringServiceSettings implements Entity {
|
||||||
TOWN_HALL,
|
TOWN_HALL,
|
||||||
ONE_TO_ONE,
|
ONE_TO_ONE,
|
||||||
BROADCAST,
|
BROADCAST,
|
||||||
ENABLE_CHAT
|
ENABLE_CHAT,
|
||||||
|
WAITING_ROOM,
|
||||||
|
SEND_REJOIN_COLLECTING_ROOM,
|
||||||
|
RESET_BROADCAST_ON_LAVE
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final String ATTR_ENABLE_PROCTORING = "enableProctoring";
|
public static final String ATTR_ENABLE_PROCTORING = "enableProctoring";
|
||||||
|
|
|
@ -12,10 +12,12 @@ import org.apache.commons.lang3.BooleanUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.eclipse.rap.rwt.RWT;
|
import org.eclipse.rap.rwt.RWT;
|
||||||
import org.eclipse.swt.SWT;
|
import org.eclipse.swt.SWT;
|
||||||
|
import org.eclipse.swt.internal.widgets.IControlAdapter;
|
||||||
import org.eclipse.swt.layout.GridData;
|
import org.eclipse.swt.layout.GridData;
|
||||||
import org.eclipse.swt.layout.GridLayout;
|
import org.eclipse.swt.layout.GridLayout;
|
||||||
import org.eclipse.swt.widgets.Button;
|
import org.eclipse.swt.widgets.Button;
|
||||||
import org.eclipse.swt.widgets.Composite;
|
import org.eclipse.swt.widgets.Composite;
|
||||||
|
import org.eclipse.swt.widgets.Control;
|
||||||
import org.eclipse.swt.widgets.Label;
|
import org.eclipse.swt.widgets.Label;
|
||||||
import org.eclipse.swt.widgets.MessageBox;
|
import org.eclipse.swt.widgets.MessageBox;
|
||||||
import org.eclipse.swt.widgets.Text;
|
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.gbl.profile.GuiProfile;
|
||||||
import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport;
|
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.PageContext;
|
||||||
import ch.ethz.seb.sebserver.gui.service.page.PageService;
|
import ch.ethz.seb.sebserver.gui.service.page.PageService;
|
||||||
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
|
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 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 PageService pageService;
|
||||||
private final AuthorizationContextHolder authorizationContextHolder;
|
private final AuthorizationContextHolder authorizationContextHolder;
|
||||||
private final WidgetFactory widgetFactory;
|
private final WidgetFactory widgetFactory;
|
||||||
|
@ -67,7 +76,6 @@ public class LoginPage implements TemplateComposer {
|
||||||
public void compose(final PageContext pageContext) {
|
public void compose(final PageContext pageContext) {
|
||||||
final Composite parent = pageContext.getParent();
|
final Composite parent = pageContext.getParent();
|
||||||
WidgetFactory.setTestId(parent, "login-page");
|
WidgetFactory.setTestId(parent, "login-page");
|
||||||
WidgetFactory.setARIARole(parent, "composite");
|
|
||||||
|
|
||||||
final Composite loginGroup = new Composite(parent, SWT.NONE);
|
final Composite loginGroup = new Composite(parent, SWT.NONE);
|
||||||
final GridLayout rowLayout = new GridLayout();
|
final GridLayout rowLayout = new GridLayout();
|
||||||
|
@ -76,16 +84,17 @@ public class LoginPage implements TemplateComposer {
|
||||||
loginGroup.setLayout(rowLayout);
|
loginGroup.setLayout(rowLayout);
|
||||||
loginGroup.setData(RWT.CUSTOM_VARIANT, WidgetFactory.CustomVariant.LOGIN.key);
|
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.setLayoutData(new GridData(300, -1));
|
||||||
name.setAlignment(SWT.BOTTOM);
|
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));
|
loginName.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false));
|
||||||
|
|
||||||
GridData gridData = new GridData(SWT.FILL, SWT.TOP, false, false);
|
GridData gridData = new GridData(SWT.FILL, SWT.TOP, false, false);
|
||||||
gridData.verticalIndent = 10;
|
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);
|
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));
|
loginPassword.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false));
|
||||||
|
|
||||||
final Composite buttons = new Composite(loginGroup, SWT.NONE);
|
final Composite buttons = new Composite(loginGroup, SWT.NONE);
|
||||||
|
@ -93,7 +102,7 @@ public class LoginPage implements TemplateComposer {
|
||||||
buttons.setLayout(new GridLayout(2, false));
|
buttons.setLayout(new GridLayout(2, false));
|
||||||
buttons.setData(RWT.CUSTOM_VARIANT, WidgetFactory.CustomVariant.LOGIN_BACK.key);
|
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 = new GridData(SWT.LEFT, SWT.TOP, false, false);
|
||||||
gridData.verticalIndent = 10;
|
gridData.verticalIndent = 10;
|
||||||
loginButton.setLayoutData(gridData);
|
loginButton.setLayoutData(gridData);
|
||||||
|
@ -127,12 +136,17 @@ public class LoginPage implements TemplateComposer {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.registeringEnabled) {
|
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 = new GridData(SWT.LEFT, SWT.TOP, false, false);
|
||||||
gridData.verticalIndent = 10;
|
gridData.verticalIndent = 10;
|
||||||
registerButton.setLayoutData(gridData);
|
registerButton.setLayoutData(gridData);
|
||||||
registerButton.addListener(SWT.Selection, event -> pageContext.forwardToPage(this.defaultRegisterPage));
|
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(
|
private void login(
|
||||||
|
|
|
@ -119,10 +119,10 @@ public final class TextFieldBuilder extends FieldBuilder<String> {
|
||||||
}
|
}
|
||||||
|
|
||||||
final Text textInput = (this.isNumber)
|
final Text textInput = (this.isNumber)
|
||||||
? builder.widgetFactory.numberInput(fieldGrid, this.numberCheck, readonly)
|
? builder.widgetFactory.numberInput(fieldGrid, this.numberCheck, readonly, this.label)
|
||||||
: (this.isArea)
|
: (this.isArea)
|
||||||
? builder.widgetFactory.textAreaInput(fieldGrid, readonly)
|
? builder.widgetFactory.textAreaInput(fieldGrid, readonly, this.label)
|
||||||
: builder.widgetFactory.textInput(fieldGrid, this.isPassword, readonly);
|
: builder.widgetFactory.textInput(fieldGrid, this.isPassword, readonly, this.label);
|
||||||
|
|
||||||
if (builder.pageService.getFormTooltipMode() == PageService.FormTooltipMode.INPUT) {
|
if (builder.pageService.getFormTooltipMode() == PageService.FormTooltipMode.INPUT) {
|
||||||
builder.pageService.getPolyglotPageService().injectI18nTooltip(
|
builder.pageService.getPolyglotPageService().injectI18nTooltip(
|
||||||
|
|
|
@ -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.service.remote.webservice.auth.AuthorizationContextHolder;
|
||||||
import ch.ethz.seb.sebserver.gui.widget.Message;
|
import ch.ethz.seb.sebserver.gui.widget.Message;
|
||||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
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;
|
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
|
@ -285,6 +286,8 @@ public class DefaultPageLayout implements TemplateComposer {
|
||||||
log.error("Invalid markup for 'Imprint'", e);
|
log.error("Invalid markup for 'Imprint'", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
WidgetFactory.setARIARole(imprint, AriaRole.link);
|
||||||
}
|
}
|
||||||
if (StringUtils.isNoneBlank(i18nSupport.getText(ABOUT_TEXT_KEY, ""))) {
|
if (StringUtils.isNoneBlank(i18nSupport.getText(ABOUT_TEXT_KEY, ""))) {
|
||||||
final Label about = this.widgetFactory.labelLocalized(
|
final Label about = this.widgetFactory.labelLocalized(
|
||||||
|
@ -299,6 +302,8 @@ public class DefaultPageLayout implements TemplateComposer {
|
||||||
log.error("Invalid markup for 'About'", e);
|
log.error("Invalid markup for 'About'", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
WidgetFactory.setARIARole(about, AriaRole.link);
|
||||||
}
|
}
|
||||||
if (StringUtils.isNoneBlank(i18nSupport.getText(HELP_TEXT_KEY, ""))) {
|
if (StringUtils.isNoneBlank(i18nSupport.getText(HELP_TEXT_KEY, ""))) {
|
||||||
final Label help = this.widgetFactory.labelLocalized(
|
final Label help = this.widgetFactory.labelLocalized(
|
||||||
|
@ -318,6 +323,7 @@ public class DefaultPageLayout implements TemplateComposer {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
WidgetFactory.setARIARole(help, AriaRole.link);
|
||||||
}
|
}
|
||||||
this.widgetFactory.labelLocalized(
|
this.widgetFactory.labelLocalized(
|
||||||
footerRight,
|
footerRight,
|
||||||
|
|
|
@ -321,7 +321,9 @@ public class TableFilter<ROW> {
|
||||||
final Composite innerComposite = createInnerComposite(parent);
|
final Composite innerComposite = createInnerComposite(parent);
|
||||||
final GridData gridData = new GridData(SWT.FILL, SWT.END, true, true);
|
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);
|
this.textInput.setLayoutData(gridData);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,9 +8,11 @@
|
||||||
|
|
||||||
package ch.ethz.seb.sebserver.gui.widget;
|
package ch.ethz.seb.sebserver.gui.widget;
|
||||||
|
|
||||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
import java.util.ArrayList;
|
||||||
import ch.ethz.seb.sebserver.gbl.util.Tuple;
|
import java.util.Arrays;
|
||||||
import ch.ethz.seb.sebserver.gui.service.page.PageService;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.eclipse.rap.rwt.widgets.DropDown;
|
import org.eclipse.rap.rwt.widgets.DropDown;
|
||||||
import org.eclipse.swt.SWT;
|
import org.eclipse.swt.SWT;
|
||||||
|
@ -25,10 +27,9 @@ import org.eclipse.swt.widgets.Text;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||||
import java.util.Arrays;
|
import ch.ethz.seb.sebserver.gbl.util.Tuple;
|
||||||
import java.util.List;
|
import ch.ethz.seb.sebserver.gui.service.page.PageService;
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
public final class MultiSelectionCombo extends Composite implements Selection {
|
public final class MultiSelectionCombo extends Composite implements Selection {
|
||||||
|
|
||||||
|
@ -68,7 +69,7 @@ public final class MultiSelectionCombo extends Composite implements Selection {
|
||||||
setLayout(gridLayout);
|
setLayout(gridLayout);
|
||||||
|
|
||||||
this.addListener(SWT.Resize, this::adaptColumnWidth);
|
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.textCell = new GridData(SWT.LEFT, SWT.CENTER, true, true);
|
||||||
this.textInput.setLayoutData(this.textCell);
|
this.textInput.setLayoutData(this.textCell);
|
||||||
this.dropDown = new DropDown(this.textInput, SWT.NONE);
|
this.dropDown = new DropDown(this.textInput, SWT.NONE);
|
||||||
|
|
|
@ -152,7 +152,8 @@ public final class ThresholdList extends Composite {
|
||||||
} else {
|
} else {
|
||||||
Double.parseDouble(s);
|
Double.parseDouble(s);
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
VALUE_TEXT_KEY);
|
||||||
final GridData valueCell = new GridData(SWT.FILL, SWT.CENTER, true, false);
|
final GridData valueCell = new GridData(SWT.FILL, SWT.CENTER, true, false);
|
||||||
valueInput.setLayoutData(valueCell);
|
valueInput.setLayoutData(valueCell);
|
||||||
|
|
||||||
|
|
|
@ -70,6 +70,10 @@ public class WidgetFactory {
|
||||||
private static final String ADD_HTML_ATTR_TEST_ID = "test-id";
|
private static final String ADD_HTML_ATTR_TEST_ID = "test-id";
|
||||||
private static final String SUB_TITLE_TExT_SUFFIX = ".subtitle";
|
private static final String SUB_TITLE_TExT_SUFFIX = ".subtitle";
|
||||||
|
|
||||||
|
public enum AriaRole {
|
||||||
|
link
|
||||||
|
}
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(WidgetFactory.class);
|
private static final Logger log = LoggerFactory.getLogger(WidgetFactory.class);
|
||||||
|
|
||||||
public static final int TEXT_AREA_INPUT_MIN_HEIGHT = 100;
|
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) {
|
public Button buttonLocalized(final Composite parent, final String locTextKey) {
|
||||||
final Button button = new Button(parent, SWT.NONE);
|
final Button button = new Button(parent, SWT.NONE);
|
||||||
|
setAttribute(button, "role", "button");
|
||||||
this.polyglotPageService.injectI18n(button, new LocTextKey(locTextKey));
|
this.polyglotPageService.injectI18n(button, new LocTextKey(locTextKey));
|
||||||
return button;
|
return button;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Button buttonLocalized(final Composite parent, final LocTextKey locTextKey) {
|
public Button buttonLocalized(final Composite parent, final LocTextKey locTextKey) {
|
||||||
final Button button = new Button(parent, SWT.NONE);
|
final Button button = new Button(parent, SWT.NONE);
|
||||||
|
setAttribute(button, "role", "button");
|
||||||
this.polyglotPageService.injectI18n(button, locTextKey);
|
this.polyglotPageService.injectI18n(button, locTextKey);
|
||||||
return button;
|
return button;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Button buttonLocalized(final Composite parent, final CustomVariant variant, final String locTextKey) {
|
public Button buttonLocalized(final Composite parent, final CustomVariant variant, final String locTextKey) {
|
||||||
final Button button = new Button(parent, SWT.NONE);
|
final Button button = new Button(parent, SWT.NONE);
|
||||||
|
setAttribute(button, "role", "button");
|
||||||
this.polyglotPageService.injectI18n(button, new LocTextKey(locTextKey));
|
this.polyglotPageService.injectI18n(button, new LocTextKey(locTextKey));
|
||||||
button.setData(RWT.CUSTOM_VARIANT, variant.key);
|
button.setData(RWT.CUSTOM_VARIANT, variant.key);
|
||||||
return button;
|
return button;
|
||||||
|
@ -387,6 +394,7 @@ public class WidgetFactory {
|
||||||
final LocTextKey toolTipKey) {
|
final LocTextKey toolTipKey) {
|
||||||
|
|
||||||
final Button button = new Button(parent, type);
|
final Button button = new Button(parent, type);
|
||||||
|
setAttribute(button, "role", "button");
|
||||||
this.polyglotPageService.injectI18n(button, locTextKey, toolTipKey);
|
this.polyglotPageService.injectI18n(button, locTextKey, toolTipKey);
|
||||||
return button;
|
return button;
|
||||||
}
|
}
|
||||||
|
@ -453,42 +461,85 @@ public class WidgetFactory {
|
||||||
return labelLocalized;
|
return labelLocalized;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Text textInput(final Composite content) {
|
public Text textInput(final Composite content, final LocTextKey label) {
|
||||||
return textInput(content, false, false);
|
return textInput(content, false, false, this.i18nSupport.getText(label));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Text textLabel(final Composite content) {
|
public Text textInput(final Composite content, final String label) {
|
||||||
return textInput(content, false, true);
|
return textInput(content, false, false, label);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Text passwordInput(final Composite content) {
|
public Text passwordInput(final Composite content, final LocTextKey label) {
|
||||||
return textInput(content, true, false);
|
return textInput(content, true, false, this.i18nSupport.getText(label));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Text textAreaInput(final Composite content, final boolean readonly) {
|
public Text passwordInput(final Composite content, final String label) {
|
||||||
return readonly
|
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.MULTI)
|
||||||
: new Text(content, SWT.LEFT | SWT.BORDER | 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) {
|
public Text textInput(
|
||||||
return readonly
|
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, SWT.LEFT)
|
||||||
: new Text(content, (password)
|
: new Text(content, (password)
|
||||||
? SWT.LEFT | SWT.BORDER | SWT.PASSWORD
|
? SWT.LEFT | SWT.BORDER | SWT.PASSWORD
|
||||||
: SWT.LEFT | SWT.BORDER);
|
: SWT.LEFT | SWT.BORDER);
|
||||||
}
|
|
||||||
|
|
||||||
public Text numberInput(final Composite content, final Consumer<String> numberCheck) {
|
if (label != null) {
|
||||||
return numberInput(content, numberCheck, false);
|
WidgetFactory.setAttribute(input, "aria-label", label);
|
||||||
}
|
|
||||||
|
|
||||||
public Text numberInput(final Composite content, final Consumer<String> numberCheck, final boolean readonly) {
|
|
||||||
if (readonly) {
|
|
||||||
return new Text(content, SWT.LEFT | SWT.READ_ONLY);
|
|
||||||
}
|
}
|
||||||
|
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) {
|
if (numberCheck != null) {
|
||||||
numberInput.addListener(SWT.Verify, event -> {
|
numberInput.addListener(SWT.Verify, event -> {
|
||||||
final String value = event.text;
|
final String value = event.text;
|
||||||
|
@ -884,11 +935,11 @@ public class WidgetFactory {
|
||||||
setAttribute(widget, ADD_HTML_ATTR_TEST_ID, value);
|
setAttribute(widget, ADD_HTML_ATTR_TEST_ID, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setARIARole(final Widget widget, final String value) {
|
public static void setARIARole(final Widget widget, final AriaRole role) {
|
||||||
setAttribute(widget, ADD_HTML_ATTR_ARIA_ROLE, value);
|
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()) {
|
if (!widget.isDisposed()) {
|
||||||
final String $el = widget instanceof Text ? "$input" : "$el";
|
final String $el = widget instanceof Text ? "$input" : "$el";
|
||||||
final String id = WidgetUtil.getId(widget);
|
final String id = WidgetUtil.getId(widget);
|
||||||
|
|
|
@ -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.Exam.ExamStatus;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringRoomConnection;
|
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;
|
||||||
|
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.ClientConnection;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientInstruction.InstructionType;
|
import ch.ethz.seb.sebserver.gbl.model.session.ClientInstruction.InstructionType;
|
||||||
|
@ -230,7 +231,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
|
||||||
} else if (remoteProctoringRoom.townhallRoom) {
|
} else if (remoteProctoringRoom.townhallRoom) {
|
||||||
closeTownhall(examId, settings, examProctoringService);
|
closeTownhall(examId, settings, examProctoringService);
|
||||||
} else {
|
} else {
|
||||||
closeCollectingRoom(examId, roomName, examProctoringService);
|
closeCollectingRoom(examId, roomName, settings, examProctoringService);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -377,11 +378,13 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
|
||||||
.getActiveConnectionTokens(examId)
|
.getActiveConnectionTokens(examId)
|
||||||
.getOrThrow();
|
.getOrThrow();
|
||||||
|
|
||||||
// Send default settings to clients
|
// Send default settings to clients if fearture is enabled
|
||||||
this.sendReconfigurationInstructions(
|
if (proctoringSettings.enabledFeatures.contains(ProctoringFeature.RESET_BROADCAST_ON_LAVE)) {
|
||||||
examId,
|
this.sendReconfigurationInstructions(
|
||||||
connectionTokens,
|
examId,
|
||||||
examProctoringService.getDefaultReconfigInstructionAttributes());
|
connectionTokens,
|
||||||
|
examProctoringService.getDefaultReconfigInstructionAttributes());
|
||||||
|
}
|
||||||
|
|
||||||
// Close and delete town-hall room
|
// Close and delete town-hall room
|
||||||
this.remoteProctoringRoomDAO
|
this.remoteProctoringRoomDAO
|
||||||
|
@ -403,6 +406,7 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
|
||||||
private void closeCollectingRoom(
|
private void closeCollectingRoom(
|
||||||
final Long examId,
|
final Long examId,
|
||||||
final String roomName,
|
final String roomName,
|
||||||
|
final ProctoringServiceSettings proctoringSettings,
|
||||||
final ExamProctoringService examProctoringService) {
|
final ExamProctoringService examProctoringService) {
|
||||||
|
|
||||||
// get all connections of the room
|
// get all connections of the room
|
||||||
|
@ -412,11 +416,13 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
|
||||||
.map(cc -> cc.connectionToken)
|
.map(cc -> cc.connectionToken)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
// Send default settings to clients
|
// Send default settings to clients if feature is enabled
|
||||||
this.sendReconfigurationInstructions(
|
if (proctoringSettings.enabledFeatures.contains(ProctoringFeature.RESET_BROADCAST_ON_LAVE)) {
|
||||||
examId,
|
this.sendReconfigurationInstructions(
|
||||||
connectionTokens,
|
examId,
|
||||||
examProctoringService.getDefaultReconfigInstructionAttributes());
|
connectionTokens,
|
||||||
|
examProctoringService.getDefaultReconfigInstructionAttributes());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void cleanupBreakOutRooms(final ClientConnectionRecord cc) {
|
private void cleanupBreakOutRooms(final ClientConnectionRecord cc) {
|
||||||
|
@ -453,11 +459,13 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
|
||||||
final ExamProctoringService examProctoringService,
|
final ExamProctoringService examProctoringService,
|
||||||
final RemoteProctoringRoom remoteProctoringRoom) {
|
final RemoteProctoringRoom remoteProctoringRoom) {
|
||||||
|
|
||||||
// Send default settings to clients
|
// Send default settings to clients if feature is enabled
|
||||||
this.sendReconfigurationInstructions(
|
if (proctoringSettings.enabledFeatures.contains(ProctoringFeature.RESET_BROADCAST_ON_LAVE)) {
|
||||||
examId,
|
this.sendReconfigurationInstructions(
|
||||||
remoteProctoringRoom.breakOutConnections,
|
examId,
|
||||||
examProctoringService.getDefaultReconfigInstructionAttributes());
|
remoteProctoringRoom.breakOutConnections,
|
||||||
|
examProctoringService.getDefaultReconfigInstructionAttributes());
|
||||||
|
}
|
||||||
|
|
||||||
// Dispose the proctoring room on service side
|
// Dispose the proctoring room on service side
|
||||||
examProctoringService
|
examProctoringService
|
||||||
|
|
|
@ -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.client.ClientCredentials;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringRoomConnection;
|
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;
|
||||||
|
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.exam.ProctoringServiceSettings.ProctoringServerType;
|
||||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
|
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.ClientConnectionData;
|
||||||
|
@ -443,8 +444,13 @@ public class ZoomProctoringService implements ExamProctoringService {
|
||||||
|
|
||||||
return Result.tryCatch(() -> {
|
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)) {
|
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
|
// the meeting once the town-hall has been closed
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -502,6 +508,7 @@ public class ZoomProctoringService implements ExamProctoringService {
|
||||||
proctoringSettings.serverURL,
|
proctoringSettings.serverURL,
|
||||||
credentials,
|
credentials,
|
||||||
roomName);
|
roomName);
|
||||||
|
|
||||||
final UserResponse userResponse = this.jsonMapper.readValue(
|
final UserResponse userResponse = this.jsonMapper.readValue(
|
||||||
createUser.getBody(),
|
createUser.getBody(),
|
||||||
UserResponse.class);
|
UserResponse.class);
|
||||||
|
@ -514,7 +521,9 @@ public class ZoomProctoringService implements ExamProctoringService {
|
||||||
userResponse.id,
|
userResponse.id,
|
||||||
subject,
|
subject,
|
||||||
duration,
|
duration,
|
||||||
meetingPwd);
|
meetingPwd,
|
||||||
|
proctoringSettings.enabledFeatures.contains(ProctoringFeature.WAITING_ROOM));
|
||||||
|
|
||||||
final MeetingResponse meetingResponse = this.jsonMapper.readValue(
|
final MeetingResponse meetingResponse = this.jsonMapper.readValue(
|
||||||
createMeeting.getBody(),
|
createMeeting.getBody(),
|
||||||
MeetingResponse.class);
|
MeetingResponse.class);
|
||||||
|
@ -525,6 +534,7 @@ public class ZoomProctoringService implements ExamProctoringService {
|
||||||
userResponse.id,
|
userResponse.id,
|
||||||
meetingResponse.start_url,
|
meetingResponse.start_url,
|
||||||
meetingResponse.join_url);
|
meetingResponse.join_url);
|
||||||
|
|
||||||
final String additionalZoomRoomDataString = this.jsonMapper
|
final String additionalZoomRoomDataString = this.jsonMapper
|
||||||
.writeValueAsString(additionalZoomRoomData);
|
.writeValueAsString(additionalZoomRoomData);
|
||||||
|
|
||||||
|
@ -563,23 +573,29 @@ public class ZoomProctoringService implements ExamProctoringService {
|
||||||
final StringBuilder builder = new StringBuilder();
|
final StringBuilder builder = new StringBuilder();
|
||||||
final Encoder urlEncoder = Base64.getUrlEncoder().withoutPadding();
|
final Encoder urlEncoder = Base64.getUrlEncoder().withoutPadding();
|
||||||
|
|
||||||
final String jwtHeaderPart = urlEncoder.encodeToString(
|
final String jwtHeaderPart = urlEncoder
|
||||||
ZOOM_ACCESS_TOKEN_HEADER.getBytes(StandardCharsets.UTF_8));
|
.encodeToString(ZOOM_ACCESS_TOKEN_HEADER.getBytes(StandardCharsets.UTF_8));
|
||||||
|
|
||||||
final String jwtPayload = String.format(
|
final String jwtPayload = String.format(
|
||||||
ZOOM_API_ACCESS_TOKEN_PAYLOAD.replaceAll(" ", "").replaceAll("\n", ""),
|
ZOOM_API_ACCESS_TOKEN_PAYLOAD
|
||||||
|
.replaceAll(" ", "")
|
||||||
|
.replaceAll("\n", ""),
|
||||||
credentials.clientIdAsString(),
|
credentials.clientIdAsString(),
|
||||||
expTime);
|
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 String message = jwtHeaderPart + "." + jwtPayloadPart;
|
||||||
|
|
||||||
final Mac sha256_HMAC = Mac.getInstance(TOKEN_ENCODE_ALG);
|
final Mac sha256_HMAC = Mac.getInstance(TOKEN_ENCODE_ALG);
|
||||||
final SecretKeySpec secret_key = new SecretKeySpec(
|
final SecretKeySpec secret_key = new SecretKeySpec(
|
||||||
Utils.toByteArray(decryptedSecret),
|
Utils.toByteArray(decryptedSecret),
|
||||||
TOKEN_ENCODE_ALG);
|
TOKEN_ENCODE_ALG);
|
||||||
|
|
||||||
sha256_HMAC.init(secret_key);
|
sha256_HMAC.init(secret_key);
|
||||||
final String hash = urlEncoder.encodeToString(
|
final String hash = urlEncoder
|
||||||
sha256_HMAC.doFinal(Utils.toByteArray(message)));
|
.encodeToString(sha256_HMAC.doFinal(Utils.toByteArray(message)));
|
||||||
|
|
||||||
builder.append(message)
|
builder.append(message)
|
||||||
.append(".")
|
.append(".")
|
||||||
|
@ -710,7 +726,8 @@ public class ZoomProctoringService implements ExamProctoringService {
|
||||||
final String userId,
|
final String userId,
|
||||||
final String topic,
|
final String topic,
|
||||||
final int duration,
|
final int duration,
|
||||||
final CharSequence password) {
|
final CharSequence password,
|
||||||
|
final boolean waitingRoom) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
|
@ -723,7 +740,8 @@ public class ZoomProctoringService implements ExamProctoringService {
|
||||||
final CreateMeetingRequest createRoomRequest = new CreateMeetingRequest(
|
final CreateMeetingRequest createRoomRequest = new CreateMeetingRequest(
|
||||||
topic,
|
topic,
|
||||||
duration,
|
duration,
|
||||||
password);
|
password,
|
||||||
|
waitingRoom);
|
||||||
|
|
||||||
final String body = this.zoomProctoringService.jsonMapper.writeValueAsString(createRoomRequest);
|
final String body = this.zoomProctoringService.jsonMapper.writeValueAsString(createRoomRequest);
|
||||||
final HttpHeaders headers = getHeaders(credentials);
|
final HttpHeaders headers = getHeaders(credentials);
|
||||||
|
|
|
@ -123,7 +123,8 @@ public interface ZoomRoomRequestResponse {
|
||||||
public CreateMeetingRequest(
|
public CreateMeetingRequest(
|
||||||
final String topic,
|
final String topic,
|
||||||
final int duration,
|
final int duration,
|
||||||
final CharSequence password) {
|
final CharSequence password,
|
||||||
|
final boolean waitingRoom) {
|
||||||
|
|
||||||
this.type = 2; // Scheduled Meeting
|
this.type = 2; // Scheduled Meeting
|
||||||
this.start_time = DateTime.now(DateTimeZone.UTC).toString("yyyy-MM-dd'T'HH:mm:ss");
|
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.timezone = DateTimeZone.UTC.getID();
|
||||||
this.topic = topic;
|
this.topic = topic;
|
||||||
this.password = password;
|
this.password = password;
|
||||||
this.settings = new Settings();
|
this.settings = new Settings(waitingRoom);
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
static class Settings {
|
static class Settings {
|
||||||
@JsonProperty final boolean host_video = true;
|
@JsonProperty final boolean host_video = false;
|
||||||
@JsonProperty final boolean participant_video = true;
|
@JsonProperty final boolean participant_video = false;
|
||||||
@JsonProperty final boolean join_before_host = true;
|
@JsonProperty final boolean mute_upon_entry = true;
|
||||||
|
@JsonProperty final boolean join_before_host;
|
||||||
@JsonProperty final int jbh_time = 0;
|
@JsonProperty final int jbh_time = 0;
|
||||||
@JsonProperty final boolean use_pmi = false;
|
@JsonProperty final boolean use_pmi = false;
|
||||||
@JsonProperty final String audio = "voip";
|
@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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -99,6 +99,9 @@
|
||||||
})
|
})
|
||||||
|
|
||||||
window.addEventListener('unload', () => {
|
window.addEventListener('unload', () => {
|
||||||
|
ZoomMtg.muteAll({
|
||||||
|
muteAll: true
|
||||||
|
});
|
||||||
ZoomMtg.endMeeting({});
|
ZoomMtg.endMeeting({});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -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.ONE_TO_ONE=One to One Room
|
||||||
sebserver.exam.proctoring.form.features.BROADCAST=Broadcasting Feature
|
sebserver.exam.proctoring.form.features.BROADCAST=Broadcasting Feature
|
||||||
sebserver.exam.proctoring.form.features.ENABLE_CHAT=Chat 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=Jitsi Meet Server
|
||||||
sebserver.exam.proctoring.type.servertype.JITSI_MEET.tooltip=Use a Jitsi Meet server for proctoring
|
sebserver.exam.proctoring.type.servertype.JITSI_MEET.tooltip=Use a Jitsi Meet server for proctoring
|
||||||
|
|
|
@ -166,6 +166,9 @@ public class ZoomWindowScriptResolverTest {
|
||||||
+ " })\r\n"
|
+ " })\r\n"
|
||||||
+ " \r\n"
|
+ " \r\n"
|
||||||
+ " window.addEventListener('unload', () => {\r\n"
|
+ " window.addEventListener('unload', () => {\r\n"
|
||||||
|
+ " ZoomMtg.muteAll({\r\n"
|
||||||
|
+ " muteAll: true\r\n"
|
||||||
|
+ " });\r\n"
|
||||||
+ " ZoomMtg.endMeeting({});\r\n"
|
+ " ZoomMtg.endMeeting({});\r\n"
|
||||||
+ " });\r\n"
|
+ " });\r\n"
|
||||||
+ " </script>\r\n"
|
+ " </script>\r\n"
|
||||||
|
|
Loading…
Reference in a new issue