Skip to content

Commit

Permalink
Fix keyboard capture for out-of-fullscreen feedback. Fixes #18.
Browse files Browse the repository at this point in the history
  • Loading branch information
willcrichton committed Feb 14, 2023
1 parent 78a791d commit 1da2152
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 28 deletions.
2 changes: 1 addition & 1 deletion js/packages/quiz-embed/src/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,6 @@ $error-color: #e16969;

.bug-report {
position: absolute;
z-index: 999;
max-width: 70%;
right: 0.5em;
top: 0.5em;
Expand All @@ -164,6 +163,7 @@ $error-color: #e16969;
border-radius: 4px;
box-shadow: 1px 1px 4px $light-border-color;
position: relative;
z-index: 1000;

.close {
position: absolute;
Expand Down
61 changes: 35 additions & 26 deletions js/packages/quiz/src/components/quiz.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,35 @@ export interface QuizViewProps {
onFinish?: (answers: TaggedAnswer[]) => void;
}

export let useCaptureMdbookShortcuts = (capture: boolean) => {
useLayoutEffect(() => {
if (capture) {
let captureKeyboard = (e: KeyboardEvent) => e.stopPropagation();

// This gets added specifically to document.documentElement rather than document
// so bubbling events will hit this listener before ones added via document.addEventListener(...).
// All of the problematic mdBook interactions are created that way, so we ensure that
// the keyboard event does not propagate to those listeners.
//
// However, some widgets like Codemirror require keydown events but on local elements.
// So we can't just stopPropagation in the capture phase, or those widgets will break.
// This is the compromise!
document.documentElement.addEventListener(
"keydown",
captureKeyboard,
false
);

return () =>
document.documentElement.removeEventListener(
"keydown",
captureKeyboard,
false
);
}
}, [capture]);
};

export let QuizView: React.FC<QuizViewProps> = observer(
({ quiz, name, fullscreen, cacheAnswers, allowRetry, onFinish }) => {
let [quizHash] = useState(() => hash.MD5(quiz));
Expand Down Expand Up @@ -254,37 +283,17 @@ export let QuizView: React.FC<QuizViewProps> = observer(
// Don't allow any keyboard inputs to reach external listeners
// while the quiz is active (e.g. to avoid using the search box).
let ended = state.index == quiz.questions.length;
let showFullscreen = fullscreen && state.started && !ended;
let inProgress = state.started && !ended;
useCaptureMdbookShortcuts(inProgress);

// Restore the user's scroll position after leaving fullscreen mode
let [lastTop, setLastTop] = useState<number | undefined>();
let showFullscreen = inProgress && (fullscreen ?? false);
useLayoutEffect(() => {
document.body.style.overflowY = showFullscreen ? "hidden" : "auto";

if (showFullscreen) {
let captureKeyboard = (e: KeyboardEvent) => e.stopPropagation();

// This gets added specifically to document.documentElement rather than document
// so bubbling events will hit this listener before ones added via document.addEventListener(...).
// All of the problematic mdBook interactions are created that way, so we ensure that
// the keyboard event does not propagate to those listeners.
//
// However, some widgets like Codemirror require keydown events but on local elements.
// So we can't just stopPropagation in the capture phase, or those widgets will break.
// This is the compromise!
document.documentElement.addEventListener(
"keydown",
captureKeyboard,
false
);

setLastTop(window.scrollY + 100);

return () =>
document.documentElement.removeEventListener(
"keydown",
captureKeyboard,
false
);
} else if (fullscreen && lastTop !== undefined) {
} else if (fullscreen && lastTop !== undefined) {
window.scrollTo(0, lastTop);
}
}, [showFullscreen]);
Expand Down
6 changes: 6 additions & 0 deletions js/packages/quiz/src/questions/mod.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { RegisterOptions, useForm } from "react-hook-form";

import { MarkdownView } from "../components/markdown";
import { MoreInfo } from "../components/more-info";
import { useCaptureMdbookShortcuts } from "../lib";
import { MultipleChoiceMethods } from "./multiple-choice";
import { ShortAnswerMethods } from "./short-answer";
import { TracingMethods } from "./tracing";
Expand Down Expand Up @@ -45,6 +46,11 @@ let BugReporter = ({
question: number;
}) => {
let [show, setShow] = useState(false);

// Disable mdbook shortcuts if the bug reporter is opened and we're not
// fullscreen
useCaptureMdbookShortcuts(show);

let onSubmit: React.FormEventHandler<HTMLFormElement> = event => {
let data = new FormData(event.target as any);
let feedback = data.get("feedback")!.toString();
Expand Down
2 changes: 1 addition & 1 deletion js/packages/quiz/src/questions/tracing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export let TracingMethods: QuestionMethods<TracingPrompt, TracingAnswer> = {
<>
<p>
Determine whether the program will pass the compiler. If it passes,
write the expected output of the program if it were executed.
write the expected output of the program if it were executed.
{/* If the program does not pass, indicate the last line number involved in the
compiler error. */}
</p>
Expand Down

0 comments on commit 1da2152

Please sign in to comment.