Skip to content

Commit

Permalink
feat: rework the typing test page
Browse files Browse the repository at this point in the history
  • Loading branch information
aradzie committed Nov 7, 2024
1 parent c71afb5 commit b84d5fb
Show file tree
Hide file tree
Showing 15 changed files with 117 additions and 100 deletions.
7 changes: 2 additions & 5 deletions packages/page-typing-test/lib/TypingTestPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import { KeyboardProvider } from "@keybr/keyboard";
import { FakePhoneticModel } from "@keybr/phonetic-model";
import { PhoneticModelLoader } from "@keybr/phonetic-model-loader";
import { FakeSettingsContext } from "@keybr/settings";
import { fireEvent, render } from "@testing-library/react";
import { render } from "@testing-library/react";
import { TypingTestPage } from "./TypingTestPage.tsx";

test("render", async () => {
test("render", () => {
PhoneticModelLoader.loader = FakePhoneticModel.loader;

const r = render(
Expand All @@ -20,8 +20,5 @@ test("render", async () => {
</FakeIntlProvider>,
);

fireEvent.click(await r.findByTitle("Settings", { exact: false }));
fireEvent.click(await r.findByTitle("Save settings", { exact: false }));

r.unmount();
});
10 changes: 0 additions & 10 deletions packages/page-typing-test/lib/components/LineTemplate.module.less
Original file line number Diff line number Diff line change
@@ -1,13 +1,3 @@
.line {
display: flex;
align-items: center;
border-block-end: var(--separator-border);
}

.text {
flex: auto;
}

.stats {
text-align: end;
}
27 changes: 3 additions & 24 deletions packages/page-typing-test/lib/components/LineTemplate.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,12 @@
import { formatDuration, Value } from "@keybr/widget";
import { memo, type ReactNode } from "react";
import { type Progress, type SessionLine } from "../session/index.ts";
import { type SessionLine } from "../session/index.ts";
import * as styles from "./LineTemplate.module.less";

export const LineTemplate = memo(function LineTemplate({
children,
progress,
}: {
readonly children: ReactNode;
} & SessionLine): ReactNode {
return (
<div className={styles.line}>
<div className={styles.text}>{children}</div>
<div className={styles.stats}>{progress && <Stats {...progress} />}</div>
</div>
);
} & SessionLine) {
return <div className={styles.line}>{children}</div>;
});

function Stats({ length, time, progress }: Progress): ReactNode {
return (
<>
<Value
value={formatDuration(time, { showMillis: true })}
title="Time passed."
/>
{" / "}
<Value value={`${length}`} title="Characters inputted." />
{" / "}
<Value value={`${Math.floor(progress * 100)}%`} title="Progress made." />
</>
);
}
4 changes: 2 additions & 2 deletions packages/page-typing-test/lib/components/Replay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import { Box, useDocumentVisibility } from "@keybr/widget";
import { type ReactNode, useEffect, useMemo, useState } from "react";
import { ReplayState, Session, type TestResult } from "../session/index.ts";
import { useCompositeSettings } from "../settings.ts";
import { Progress } from "./Progress.tsx";
import * as styles from "./Replay.module.less";
import { ReplayProgress } from "./ReplayProgress.tsx";

export function Replay({
result: { steps, events },
Expand All @@ -29,7 +29,7 @@ export function Replay({
);
return (
<div className={styles.root}>
<Progress stepper={stepper} />
<ReplayProgress stepper={stepper} />
<Box className={styles.text} alignItems="center" justifyContent="center">
<StaticText settings={textDisplay} lines={lines} cursor={true} />
</Box>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import {
import { useEffect, useState } from "react";
import { type ReplayState } from "../session/index.ts";

export function Progress({ stepper }: { readonly stepper: ReplayState }) {
export function ReplayProgress({ stepper }: { readonly stepper: ReplayState }) {
const { formatSpeed } = useFormatter();
const { progress, time } = useProgress(stepper);
const { progress, time } = useReplayProgress(stepper);
return (
<Para align="center">
<NameValue
Expand All @@ -26,7 +26,7 @@ export function Progress({ stepper }: { readonly stepper: ReplayState }) {
);
}

function useProgress(stepper: ReplayState) {
function useReplayProgress(stepper: ReplayState) {
const { state, progress } = stepper;
const [time, setTime] = useState(0);
useEffect(() => {
Expand Down
5 changes: 1 addition & 4 deletions packages/page-typing-test/lib/components/ReportScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,6 @@ export function ReportScreen({ result }: { readonly result: TestResult }) {
<Button
label="Next test"
icon={<Icon shape={mdiSkipNext} />}
title="Try another test."
onClick={handleNext}
/>
</Field>
Expand All @@ -154,14 +153,12 @@ export function ReportScreen({ result }: { readonly result: TestResult }) {
function Indicator({
name,
value,
title,
}: {
readonly name: ReactNode;
readonly value: ReactNode;
readonly title?: string;
}) {
return (
<div className={styles.indicator} title={title}>
<div className={styles.indicator}>
<div className={styles.indicatorValue}>
<Value>{value}</Value>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ export function SettingsScreen() {
<Button
icon={<Icon shape={mdiCheckCircle} />}
label="Done"
title="Save settings and return to the test."
onClick={() => {
setView("test");
}}
Expand Down
18 changes: 18 additions & 0 deletions packages/page-typing-test/lib/components/TestProgress.module.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
.root {
position: relative;
background-color: var(--primary-l1);
}

.bar {
position: absolute;
inset-inline-start: 0;
inset-block-start: 0;
inset-block-end: 0;
background-color: var(--primary-d1);
}

.info {
position: relative;
inset: 0;
text-align: center;
}
34 changes: 34 additions & 0 deletions packages/page-typing-test/lib/components/TestProgress.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { useIntlNumbers } from "@keybr/intl";
import { useFormatter } from "@keybr/lesson-ui";
import { formatDuration, Para, Value, withDeferred } from "@keybr/widget";
import { memo } from "react";
import { type Progress } from "../session/index.ts";
import * as styles from "./TestProgress.module.less";

export const TestProgress0 = memo(function TestProgress({
progress: { length, time, progress, speed },
}: {
readonly progress: Progress;
}) {
const { formatNumber, formatPercents } = useIntlNumbers();
const { formatSpeed } = useFormatter();
return (
<Para className={styles.root}>
<div
className={styles.bar}
style={{ inlineSize: `${Math.floor(progress * 100)}%` }}
/>
<div className={styles.info}>
<Value value={formatDuration(time, { showMillis: true })} />
{" / "}
<Value value={formatNumber(length)} />
{" / "}
<Value value={formatPercents(progress)} />
{" / "}
<Value value={formatSpeed(speed)} />
</div>
</Para>
);
});

export const TestProgress = withDeferred(TestProgress0);
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
.text {
max-inline-size: 60rem;
margin: -1rem;
padding: 1rem;
overflow: hidden;
backdrop-filter: blur(10px);
}
62 changes: 33 additions & 29 deletions packages/page-typing-test/lib/components/TestScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Screen } from "@keybr/pages-shared";
import { type LineList, makeStats } from "@keybr/textinput";
import { TextArea } from "@keybr/textinput-ui";
import { type Focusable, Spacer, useView } from "@keybr/widget";
import { Box, type Focusable, Spacer, useView } from "@keybr/widget";
import { useEffect, useRef, useState } from "react";
import {
type TextGenerator,
Expand All @@ -11,6 +11,7 @@ import { Session, type TestResult } from "../session/index.ts";
import { type CompositeSettings, useCompositeSettings } from "../settings.ts";
import { views } from "../views.tsx";
import { LineTemplate } from "./LineTemplate.tsx";
import { TestProgress } from "./TestProgress.tsx";
import * as styles from "./TestScreen.module.less";
import { Toolbar } from "./Toolbar.tsx";

Expand All @@ -34,10 +35,9 @@ function Controller({
const { setView } = useView(views);
const settings = useCompositeSettings();
const focusRef = useRef<Focusable>(null);
const [session, setSession] = useState<Session>(() =>
nextTest(settings, generator),
);
const [session, setSession] = useState(() => nextTest(settings, generator));
const [lines, setLines] = useState<LineList>(Session.emptyLines);
const [progress, setProgress] = useState(Session.emptyProgress);
useEffect(() => {
generator.reset(mark);
const session = nextTest(settings, generator);
Expand All @@ -55,36 +55,40 @@ function Controller({
}}
/>
<Spacer size={10} />
<div className={styles.text}>
<TextArea
focusRef={focusRef}
settings={settings.textDisplay}
lines={lines}
wrap={false}
onFocus={() => {
generator.reset(mark);
const session = nextTest(settings, generator);
setSession(session);
setLines(session.getLines());
}}
onKeyDown={session.handleKeyDown}
onKeyUp={session.handleKeyUp}
onInput={(event) => {
const { completed } = session.handleInput(event);
setLines(session.getLines());
if (completed) {
setView("report", { result: makeResult(session) });
}
}}
lineTemplate={LineTemplate}
/>
</div>
<Box alignItems="center" justifyContent="center">
<div className={styles.text}>
<TextArea
focusRef={focusRef}
settings={settings.textDisplay}
lines={lines}
wrap={false}
onFocus={() => {
generator.reset(mark);
const session = nextTest(settings, generator);
setSession(session);
setLines(session.getLines());
}}
onKeyDown={session.handleKeyDown}
onKeyUp={session.handleKeyUp}
onInput={(event) => {
const { progress, completed } = session.handleInput(event);
setLines(session.getLines());
setProgress(progress);
if (completed) {
setView("report", { result: makeResult(session) });
}
}}
lineTemplate={LineTemplate}
/>
<TestProgress progress={progress} />
</div>
</Box>
</Screen>
);
}

function nextTest(settings: CompositeSettings, generator: TextGenerator) {
return new Session({ ...settings, numLines: 7, numCols: 55 }, generator);
return new Session({ ...settings, numLines: 5, numCols: 55 }, generator);
}

function makeResult(session: Session): TestResult {
Expand Down
22 changes: 8 additions & 14 deletions packages/page-typing-test/lib/components/Toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,14 @@ export const Toolbar = memo(function Toolbar({
}): ReactNode {
return (
<FieldList>
<Field.Filler />
<Field>
<DurationSwitcher onChange={onChange} />
</Field>
<Field.Filler />
<Field>
<IconButton
icon={<Icon shape={mdiCog} />}
title="Settings..."
onClick={onConfigure}
/>
<IconButton icon={<Icon shape={mdiCog} />} onClick={onConfigure} />
</Field>
<Field.Filler />
</FieldList>
);
});
Expand All @@ -35,11 +32,12 @@ export const DurationSwitcher = memo(function DurationSwitcher({
onChange,
}: {
readonly onChange: () => void;
}): ReactNode {
}) {
const { settings, updateSettings } = useSettings();
const compositeSettings = toCompositeSettings(settings);
const children: ReactNode[] = [];
durations.forEach(({ duration, label }, index) => {
for (let index = 0; index < durations.length; index++) {
const { duration, label } = durations[index];
if (index > 0) {
children.push(<span key={children.length}>{" | "}</span>);
}
Expand All @@ -66,10 +64,6 @@ export const DurationSwitcher = memo(function DurationSwitcher({
{label}
</Link>,
);
});
return (
<>
<span>Test duration:</span> {children}
</>
);
}
return <>{children}</>;
});

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,6 @@ function Content({ wordList }: { readonly wordList: WordList }): ReactNode {
max={typingTestProps.wordList.wordListSize.max}
step={1}
value={settings.get(typingTestProps.wordList.wordListSize)}
title={formatMessage({
id: "settings.wordListSize.description",
defaultMessage: "Chose how many common words to use.",
})}
onChange={(value) => {
updateSettings(
settings.set(typingTestProps.wordList.wordListSize, value),
Expand Down
12 changes: 11 additions & 1 deletion packages/page-typing-test/lib/session/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,17 @@ import {
} from "./types.ts";

export class Session {
static readonly emptyLines = { text: "", lines: [] } satisfies SessionLines;
static readonly emptyLines = {
text: "",
lines: [],
} satisfies SessionLines;
static readonly emptyProgress = {
time: 0,
length: 0,
progress: 0,
speed: 0,
} satisfies Progress;

/** A list of events to replay. */
#events: AnyEvent[] = [];
/** The currently visible lines. */
Expand Down

0 comments on commit b84d5fb

Please sign in to comment.