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

10
package-lock.json generated
View File

@@ -17951,6 +17951,11 @@
"react-is": "^16.12.0 || ^17.0.0" "react-is": "^16.12.0 || ^17.0.0"
} }
}, },
"react-simple-keyboard": {
"version": "3.4.65",
"resolved": "https://registry.npmjs.org/react-simple-keyboard/-/react-simple-keyboard-3.4.65.tgz",
"integrity": "sha512-SSxAXk/I6ry4oWi/Z2e1s21mtwBuNJLiMOJIprTf2OAniVxEf+PH5zupWu07wbNCMDsMv3uhdaJAR/W3kVMieg=="
},
"react-test-renderer": { "react-test-renderer": {
"version": "17.0.2", "version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-17.0.2.tgz", "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-17.0.2.tgz",
@@ -18749,6 +18754,11 @@
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
"integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA=="
}, },
"simple-keyboard-layouts": {
"version": "3.1.54",
"resolved": "https://registry.npmjs.org/simple-keyboard-layouts/-/simple-keyboard-layouts-3.1.54.tgz",
"integrity": "sha512-JZ2zR0rq3rctFjmYo33YiXoZ535Y8m/LBk2qBWP/MZAOjRi2XpXaJnhQrWnVYQv6CP2kOlZZvEQAzn3DhZlQFA=="
},
"simple-swizzle": { "simple-swizzle": {
"version": "0.2.2", "version": "0.2.2",
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",

View File

@@ -1,6 +1,6 @@
{ {
"name": "parandum", "name": "parandum",
"version": "2.1.8", "version": "2.1.9",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@babel/core": "^7.16.0", "@babel/core": "^7.16.0",
@@ -27,7 +27,9 @@
"react-router-dom": "^5.3.0", "react-router-dom": "^5.3.0",
"react-scripts": "^5.0.0-next.47", "react-scripts": "^5.0.0-next.47",
"react-select": "^5.2.1", "react-select": "^5.2.1",
"react-simple-keyboard": "^3.4.65",
"react-xarrows": "^2.0.2", "react-xarrows": "^2.0.2",
"simple-keyboard-layouts": "^3.1.54",
"styled-components": "^5.3.3", "styled-components": "^5.3.3",
"typescript": "^4.5.2", "typescript": "^4.5.2",
"universal-cookie": "^4.0.4", "universal-cookie": "^4.0.4",

View File

@@ -60,6 +60,10 @@ export default withRouter(class Progress extends React.Component {
sound: false, sound: false,
ignoreCaps: false, ignoreCaps: false,
numberOfAnswers: null, numberOfAnswers: null,
virtualKeyboardLanguage: "english",
virtualKeyboardLayoutName: "default",
showVirtualKeyboard: false,
showVirtualKeyboardOptions: false,
} }
constructor(props) { constructor(props) {
@@ -73,6 +77,12 @@ export default withRouter(class Progress extends React.Component {
createProgressWithIncorrect: props.functions.httpsCallable("createProgressWithIncorrect"), createProgressWithIncorrect: props.functions.httpsCallable("createProgressWithIncorrect"),
}, },
navbarItems: [ navbarItems: [
{
type: "button",
onClick: this.showVirtualKeyboardOptions,
icon: <KeyboardRoundedIcon />,
hideTextMobile: true,
},
{ {
type: "button", type: "button",
onClick: this.showSettings, onClick: this.showSettings,
@@ -89,6 +99,16 @@ export default withRouter(class Progress extends React.Component {
...this.changeableStateItems, ...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; let isMounted = true;
Object.defineProperty(this, "isMounted", { Object.defineProperty(this, "isMounted", {
get: () => isMounted, get: () => isMounted,
@@ -300,15 +320,34 @@ export default withRouter(class Progress extends React.Component {
this.setState({ this.setState({
answerInput: event.target.value, 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.canProceed) {
if (this.state.currentAnswerStatus === null) { if (this.state.currentAnswerStatus === null) {
this.processAnswer(); this.processAnswer(referrer);
} else { } else {
this.nextQuestion(); this.nextQuestion(referrer);
} }
} }
} }
@@ -325,7 +364,7 @@ export default withRouter(class Progress extends React.Component {
return newString; return newString;
} }
processAnswer = async () => { processAnswer = async (referrer="physical") => {
if (this.state.canProceed) { if (this.state.canProceed) {
this.startLoading(); this.startLoading();
@@ -484,7 +523,7 @@ export default withRouter(class Progress extends React.Component {
await Promise.all(promises); await Promise.all(promises);
this.setState(newState, () => { this.setState(newState, () => {
if (!newState.duration) this.answerInput.focus() if (!newState.duration && referrer === "physical") this.answerInput.focus();
}); });
}).catch((error) => { }).catch((error) => {
console.log(`Couldn't process answer: ${error}`); console.log(`Couldn't process answer: ${error}`);
@@ -497,7 +536,11 @@ export default withRouter(class Progress extends React.Component {
} }
showNextQuestion = (newState, currentState) => { 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.currentCorrect = [];
newState.currentPrompt = currentState.nextPrompt; newState.currentPrompt = currentState.nextPrompt;
newState.currentSound = currentState.nextSound; newState.currentSound = currentState.nextSound;
@@ -505,7 +548,7 @@ export default withRouter(class Progress extends React.Component {
return newState; return newState;
} }
nextQuestion = () => { nextQuestion = (referrer="physical") => {
if (this.state.canProceed) { if (this.state.canProceed) {
this.startLoading(); this.startLoading();
@@ -520,7 +563,12 @@ export default withRouter(class Progress extends React.Component {
newState = this.showNextQuestion(newState, this.state); 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() { render() {
return ( return (
<div> <div>
@@ -665,7 +740,7 @@ export default withRouter(class Progress extends React.Component {
autoCorrect="off" autoCorrect="off"
/> />
<Button <Button
onClick={() => this.processAnswer()} onClick={() => this.proceed()}
icon={<ArrowForwardRoundedIcon />} icon={<ArrowForwardRoundedIcon />}
className="button--round" className="button--round"
disabled={!this.state.canProceed} disabled={!this.state.canProceed}
@@ -827,7 +902,7 @@ export default withRouter(class Progress extends React.Component {
autoCorrect="off" autoCorrect="off"
/> />
<Button <Button
onClick={() => this.nextQuestion()} onClick={() => this.proceed()}
icon={<ArrowForwardRoundedIcon />} icon={<ArrowForwardRoundedIcon />}
className="button--round" className="button--round"
disabled={!this.state.canProceed} disabled={!this.state.canProceed}
@@ -892,7 +967,7 @@ export default withRouter(class Progress extends React.Component {
this.state.showSettings && this.state.showSettings &&
<> <>
<div className="overlay" onClick={this.hideSettings}></div> <div className="overlay" onClick={this.hideSettings}></div>
<div className="overlay-content progress-settings-overlay-content"> <div className="overlay-content">
<SettingsContent <SettingsContent
sound={this.props.sound} sound={this.props.sound}
theme={this.props.theme} theme={this.props.theme}
@@ -946,6 +1021,53 @@ export default withRouter(class Progress extends React.Component {
loading={this.state.loading} 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> </div>

View File

@@ -446,7 +446,7 @@ export default withRouter(class SetPage extends React.Component {
<> <>
<h1>Select a Group</h1> <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) => Object.keys(this.state.groups).map((groupId) =>
<Button <Button

View File

@@ -82,6 +82,16 @@ main .button {
display: none; 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) { @media screen and (max-width: 700px) {
.button.button--hide-text-mobile { .button.button--hide-text-mobile {
background: none; background: none;

View File

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

View File

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

View File

@@ -67,19 +67,6 @@
margin: 0; 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 { .form-submit {
display: none; display: none;
} }
@@ -128,6 +115,23 @@
margin: 0 0 -2px 0; 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) { @media screen and (max-width: 880px) {
.current-prompt { .current-prompt {
@@ -139,9 +143,6 @@
} }
@media screen and (max-width: 700px) { @media screen and (max-width: 700px) {
.progress-settings-overlay-content > .settings-themes-container {
width: 100%;
}
.current-prompt { .current-prompt {
font-size: 30px; font-size: 30px;
} }
@@ -151,9 +152,6 @@
} }
@media screen and (max-width: 500px) { @media screen and (max-width: 500px) {
.progress-settings-overlay-content {
width: 75%;
}
.current-prompt { .current-prompt {
font-size: 26px; font-size: 26px;
} }
@@ -161,15 +159,3 @@
font-size: 24px; 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; border-right: 8px solid transparent;
} }
.set-page-group-overlay-content { .set-page-group-button-container {
width: 70%; width: 80%;
max-width: 800px;
height: 70%;
max-height: 600px;
align-items: center;
} }
.set-page-overlay-group-container { .set-page-group-overlay-content {
width: 80%; min-width: min(500px,80%);
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: center;
justify-content: center;
column-gap: 24px;
row-gap: 8px;
} }
.no-groups-message-list { .no-groups-message-list {
padding: 0 18px; padding: 0 18px;
margin: 4px 0; 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 { .user-groups-overlay-content {
width: 40%; max-width: min(500px,80%);
max-width: 500px; padding-top: 64px;
height: 50%; padding-bottom: 64px;
max-height: 400px;
align-items: center;
} }
.user-groups-overlay-input-container { .user-groups-overlay-input-container {
@@ -11,7 +9,7 @@
flex-direction: row; flex-direction: row;
flex-wrap: wrap; flex-wrap: wrap;
align-items: center; align-items: center;
width: 80%; width: 90%;
} }
.user-groups-overlay-input-container > *:first-child { .user-groups-overlay-input-container > *:first-child {
@@ -56,28 +54,13 @@
text-decoration: none; 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) { @media screen and (max-width: 560px) {
.user-groups-overlay-input-container {
width: 90%;
}
.user-groups-overlay-input-container > .button { .user-groups-overlay-input-container > .button {
margin-top: 4px; margin-top: 4px;
} }
.user-groups-overlay-input-container > input { .user-groups-overlay-input-container > input {
width: 100%; width: 100%;
} }
}
@media screen and (max-width: 420px) {
.user-groups-overlay-input-container { .user-groups-overlay-input-container {
width: 100%; width: 100%;
} }