Add virtual keyboard during tests

Optional and with support for most languages
This commit is contained in:
2022-02-21 21:38:12 +00:00
parent 5457c4e23e
commit a0c426888b
10 changed files with 189 additions and 99 deletions

View File

@@ -60,6 +60,10 @@ export default withRouter(class Progress extends React.Component {
sound: false,
ignoreCaps: false,
numberOfAnswers: null,
virtualKeyboardLanguage: "english",
virtualKeyboardLayoutName: "default",
showVirtualKeyboard: false,
showVirtualKeyboardOptions: false,
}
constructor(props) {
@@ -73,6 +77,12 @@ export default withRouter(class Progress extends React.Component {
createProgressWithIncorrect: props.functions.httpsCallable("createProgressWithIncorrect"),
},
navbarItems: [
{
type: "button",
onClick: this.showVirtualKeyboardOptions,
icon: <KeyboardRoundedIcon />,
hideTextMobile: true,
},
{
type: "button",
onClick: this.showSettings,
@@ -88,6 +98,16 @@ export default withRouter(class Progress extends React.Component {
],
...this.changeableStateItems,
};
this.keyboardLayouts = new KeyboardLayouts();
this.keyboardLayoutsObj = this.keyboardLayouts.get();
this.keyboardLayoutNames = Object.keys(this.keyboardLayoutsObj).map(key =>
[
key,
// title case from camel case
key.charAt(0).toUpperCase() + key.slice(1).replace(/([A-Z])/g, " $1")
]
);
let isMounted = true;
Object.defineProperty(this, "isMounted", {
@@ -300,15 +320,34 @@ export default withRouter(class Progress extends React.Component {
this.setState({
answerInput: event.target.value,
});
this.virtualKeyboard.setInput(event.target.value);
}
}
proceed = () => {
onVirtualKeyboardChange = (answerInput) => {
this.setState({ answerInput });
};
onVirtualKeyboardKeyPress = (button) => {
if (button === "{shift}" || button === "{lock}") {
this.handleVirtualKeyboardShift();
} else if (button === "{enter}") {
this.proceed("virtual");
}
};
handleVirtualKeyboardShift = () => {
this.setState({
layoutName: this.state.virtualKeyboardLayoutName === "default" ? "shift" : "default",
});
}
proceed = (referrer="physical") => {
if (this.state.canProceed) {
if (this.state.currentAnswerStatus === null) {
this.processAnswer();
this.processAnswer(referrer);
} else {
this.nextQuestion();
this.nextQuestion(referrer);
}
}
}
@@ -325,7 +364,7 @@ export default withRouter(class Progress extends React.Component {
return newString;
}
processAnswer = async () => {
processAnswer = async (referrer="physical") => {
if (this.state.canProceed) {
this.startLoading();
@@ -484,7 +523,7 @@ export default withRouter(class Progress extends React.Component {
await Promise.all(promises);
this.setState(newState, () => {
if (!newState.duration) this.answerInput.focus()
if (!newState.duration && referrer === "physical") this.answerInput.focus();
});
}).catch((error) => {
console.log(`Couldn't process answer: ${error}`);
@@ -497,7 +536,11 @@ export default withRouter(class Progress extends React.Component {
}
showNextQuestion = (newState, currentState) => {
if (currentState.nextPrompt === null) newState.setComplete = true;
if (currentState.nextPrompt === null) {
newState.setComplete = true;
this.virtualKeyboard.clearInput();
newState.showVirtualKeyboard = false;
}
newState.currentCorrect = [];
newState.currentPrompt = currentState.nextPrompt;
newState.currentSound = currentState.nextSound;
@@ -505,7 +548,7 @@ export default withRouter(class Progress extends React.Component {
return newState;
}
nextQuestion = () => {
nextQuestion = (referrer="physical") => {
if (this.state.canProceed) {
this.startLoading();
@@ -520,7 +563,12 @@ export default withRouter(class Progress extends React.Component {
newState = this.showNextQuestion(newState, this.state);
}
this.setState(newState, () => (this.isMounted && !this.state.setComplete) && this.answerInput.focus());
this.setState(newState, () => {
this.virtualKeyboard.clearInput();
if (this.isMounted && !this.state.setComplete && referrer === "physical") {
this.answerInput.focus();
}
});
}
}
@@ -634,6 +682,33 @@ export default withRouter(class Progress extends React.Component {
});
}
showVirtualKeyboardOptions = () => {
this.setState({
showVirtualKeyboardOptions: true,
})
}
hideVirtualKeyboardOptions = () => {
this.setState({
showVirtualKeyboardOptions: false,
})
}
showVirtualKeyboard = (language) => {
this.setState({
showVirtualKeyboard: true,
showVirtualKeyboardOptions: false,
virtualKeyboardLanguage: language,
});
}
hideVirtualKeyboard = () => {
this.setState({
showVirtualKeyboard: false,
showVirtualKeyboardOptions: false,
});
}
render() {
return (
<div>
@@ -665,7 +740,7 @@ export default withRouter(class Progress extends React.Component {
autoCorrect="off"
/>
<Button
onClick={() => this.processAnswer()}
onClick={() => this.proceed()}
icon={<ArrowForwardRoundedIcon />}
className="button--round"
disabled={!this.state.canProceed}
@@ -827,7 +902,7 @@ export default withRouter(class Progress extends React.Component {
autoCorrect="off"
/>
<Button
onClick={() => this.nextQuestion()}
onClick={() => this.proceed()}
icon={<ArrowForwardRoundedIcon />}
className="button--round"
disabled={!this.state.canProceed}
@@ -892,7 +967,7 @@ export default withRouter(class Progress extends React.Component {
this.state.showSettings &&
<>
<div className="overlay" onClick={this.hideSettings}></div>
<div className="overlay-content progress-settings-overlay-content">
<div className="overlay-content">
<SettingsContent
sound={this.props.sound}
theme={this.props.theme}
@@ -946,6 +1021,53 @@ export default withRouter(class Progress extends React.Component {
loading={this.state.loading}
/>
}
{
this.state.showVirtualKeyboardOptions &&
<>
<div className="overlay" onClick={this.hideVirtualKeyboardOptions}></div>
<div className="overlay-content">
<h1>Virtual Keyboard</h1>
<Button
onClick={() => this.hideVirtualKeyboard()}
className="hide-virtual-keyboard-button"
icon={<KeyboardRoundedIcon/>}
>
Hide
</Button>
<h3>Or select a language:</h3>
<div className="inline-option-buttons-container">
{
this.keyboardLayoutNames.map(([layout, formattedName]) =>
<Button
onClick={() => this.showVirtualKeyboard(layout)}
className="button--no-background"
key={layout}
>
{formattedName}
</Button>
)
}
</div>
<Button
onClick={this.hideVirtualKeyboardOptions}
icon={<CloseRoundedIcon />}
className="button--no-background popup-close-button"
></Button>
</div>
</>
}
<div style={{display: this.state.showVirtualKeyboard ? "block" : "none" }}>
<Keyboard
keyboardRef={r => (this.virtualKeyboard = r)}
onChange={this.onVirtualKeyboardChange}
onKeyPress={this.onVirtualKeyboardKeyPress}
layout={this.keyboardLayoutsObj[this.state.virtualKeyboardLanguage].layout}
layoutName={this.state.layoutName}
/>
</div>
</>)
}
</div>

View File

@@ -446,7 +446,7 @@ export default withRouter(class SetPage extends React.Component {
<>
<h1>Select a Group</h1>
<div className="set-page-overlay-group-container">
<div className="inline-option-buttons-container set-page-group-button-container">
{
Object.keys(this.state.groups).map((groupId) =>
<Button

View File

@@ -82,6 +82,16 @@ main .button {
display: none;
}
.inline-option-buttons-container {
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: center;
justify-content: center;
column-gap: 24px;
row-gap: 8px;
}
@media screen and (max-width: 700px) {
.button.button--hide-text-mobile {
background: none;

View File

@@ -1,8 +1,5 @@
.confirmation-dialog {
width: 60%;
max-width: 290px;
height: 80%;
max-height: 130px;
max-width: min(290px,80%);
}
.confirmation-dialog > h3 {
@@ -11,4 +8,5 @@
.confirmation-dialog > .button-container {
justify-content: space-between;
width: 100%;
}

View File

@@ -31,13 +31,14 @@
z-index: 20;
background: var(--background-color);
border-radius: 12px;
display: flex;
flex-direction: column;
overflow: auto;
text-align: center;
flex-wrap: nowrap;
justify-content: center;
padding: 32px;
justify-items: center;
max-width: min(800px,80%);
max-height: min(600px,80%);
display: grid;
height: max-content;
}
.overlay-content > h1 {

View File

@@ -67,19 +67,6 @@
margin: 0;
}
.progress-settings-overlay-content {
width: 80%;
max-width: 700px;
height: 70%;
max-height: 500px;
align-items: center;
}
.progress-settings-overlay-content > .settings-themes-container {
justify-content: center;
width: 75%;
}
.form-submit {
display: none;
}
@@ -128,6 +115,23 @@
margin: 0 0 -2px 0;
}
.hide-virtual-keyboard-button {
margin: 16px auto;
}
.simple-keyboard.hg-theme-default {
background-color: var(--background-color);
}
.simple-keyboard.hg-theme-default .hg-button {
background-color: var(--background-color--light);
border: 1px solid var(--overlay-color);
box-shadow: none;
}
.simple-keyboard.hg-theme-default .hg-button:hover, .simple-keyboard.hg-theme-default .hg-button:focus {
background-color: var(--overlay-color);
}
@media screen and (max-width: 880px) {
.current-prompt {
@@ -139,9 +143,6 @@
}
@media screen and (max-width: 700px) {
.progress-settings-overlay-content > .settings-themes-container {
width: 100%;
}
.current-prompt {
font-size: 30px;
}
@@ -151,25 +152,10 @@
}
@media screen and (max-width: 500px) {
.progress-settings-overlay-content {
width: 75%;
}
.current-prompt {
font-size: 26px;
}
.answer-input-container > input {
font-size: 24px;
}
}
@media screen and (max-height: 700px) {
.progress-settings-overlay-content {
height: 75%;
}
}
@media screen and (max-height: 600px) {
.progress-settings-overlay-content {
justify-content: flex-start;
}
}

View File

@@ -26,37 +26,15 @@
border-right: 8px solid transparent;
}
.set-page-group-overlay-content {
width: 70%;
max-width: 800px;
height: 70%;
max-height: 600px;
align-items: center;
.set-page-group-button-container {
width: 80%;
}
.set-page-overlay-group-container {
width: 80%;
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: center;
justify-content: center;
column-gap: 24px;
row-gap: 8px;
.set-page-group-overlay-content {
min-width: min(500px,80%);
}
.no-groups-message-list {
padding: 0 18px;
margin: 4px 0;
}
@media screen and (max-width: 860px) {
.set-page-group-overlay-content {
width: 75%;
}
}
@media screen and (max-height: 700px) {
.set-page-group-overlay-content {
height: 75%;
}
}

View File

@@ -1,9 +1,7 @@
.user-groups-overlay-content {
width: 40%;
max-width: 500px;
height: 50%;
max-height: 400px;
align-items: center;
max-width: min(500px,80%);
padding-top: 64px;
padding-bottom: 64px;
}
.user-groups-overlay-input-container {
@@ -11,7 +9,7 @@
flex-direction: row;
flex-wrap: wrap;
align-items: center;
width: 80%;
width: 90%;
}
.user-groups-overlay-input-container > *:first-child {
@@ -56,28 +54,13 @@
text-decoration: none;
}
@media screen and (max-width: 1080px) {
.user-groups-overlay-content {
width: 50%;
}
}
@media screen and (max-width: 860px) {
.user-groups-overlay-content {
width: 70%;
}
}
@media screen and (max-width: 560px) {
.user-groups-overlay-input-container {
width: 90%;
}
.user-groups-overlay-input-container > .button {
margin-top: 4px;
}
.user-groups-overlay-input-container > input {
width: 100%;
}
}
@media screen and (max-width: 420px) {
.user-groups-overlay-input-container {
width: 100%;
}