@testgorilla/tgo-typing-test 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/.eslintrc.json +46 -0
  2. package/jest.config.ts +21 -0
  3. package/ng-package.json +15 -0
  4. package/package.json +7 -22
  5. package/project.json +36 -0
  6. package/src/lib/components/tgo-typing-replay-input/tgo-typing-replay-input.component.html +30 -0
  7. package/src/lib/components/tgo-typing-replay-input/tgo-typing-replay-input.component.spec.ts +250 -0
  8. package/src/lib/components/tgo-typing-replay-input/tgo-typing-replay-input.component.ts +47 -0
  9. package/src/lib/components/tgo-typing-test/tgo-typing-test.component.html +72 -0
  10. package/src/lib/components/tgo-typing-test/tgo-typing-test.component.spec.ts +699 -0
  11. package/src/lib/components/tgo-typing-test/tgo-typing-test.component.ts +287 -0
  12. package/src/lib/helpers/config.ts +28 -0
  13. package/src/lib/helpers/constants/default-config.ts +103 -0
  14. package/src/lib/helpers/controllers/input-controller.ts +710 -0
  15. package/src/lib/helpers/controllers/quotes-controller.ts +183 -0
  16. package/src/lib/helpers/observables/banner-event.ts +18 -0
  17. package/src/lib/helpers/observables/config-event.ts +31 -0
  18. package/src/lib/helpers/observables/timer-event.ts +18 -0
  19. package/src/lib/helpers/states/active-page.ts +9 -0
  20. package/src/lib/helpers/states/composition.ts +29 -0
  21. package/src/lib/helpers/states/page-transition.ts +9 -0
  22. package/src/lib/helpers/states/slow-timer.ts +16 -0
  23. package/src/lib/helpers/states/test-active.ts +9 -0
  24. package/src/lib/helpers/states/time.ts +13 -0
  25. package/src/lib/helpers/test/caps-warning.ts +50 -0
  26. package/src/lib/helpers/test/caret.ts +92 -0
  27. package/src/lib/helpers/test/custom-text.ts +73 -0
  28. package/src/lib/helpers/test/english-punctuation.ts +38 -0
  29. package/src/lib/helpers/test/focus.ts +39 -0
  30. package/src/lib/helpers/test/manual-restart-tracker.ts +13 -0
  31. package/src/lib/helpers/test/out-of-focus.ts +19 -0
  32. package/src/lib/helpers/test/replay.ts +265 -0
  33. package/src/lib/helpers/test/test-input.ts +320 -0
  34. package/src/lib/helpers/test/test-logic.ts +1039 -0
  35. package/src/lib/helpers/test/test-state.ts +17 -0
  36. package/src/lib/helpers/test/test-stats.ts +442 -0
  37. package/src/lib/helpers/test/test-timer.ts +209 -0
  38. package/src/lib/helpers/test/test-ui.ts +370 -0
  39. package/src/lib/helpers/test/test-words.ts +72 -0
  40. package/src/lib/helpers/test/timer-progress.ts +16 -0
  41. package/src/lib/helpers/test/tts.ts +42 -0
  42. package/src/lib/helpers/test/weak-spot.ts +74 -0
  43. package/src/lib/helpers/test/wordset.ts +109 -0
  44. package/src/lib/styles/animations.scss +101 -0
  45. package/src/lib/styles/caret.scss +108 -0
  46. package/src/lib/styles/core.scss +498 -0
  47. package/src/lib/styles/index.scss +19 -0
  48. package/src/lib/styles/inputs.scss +290 -0
  49. package/src/lib/styles/popups.scss +1311 -0
  50. package/src/lib/styles/test.scss +1008 -0
  51. package/src/lib/styles/z_media-queries.scss +848 -0
  52. package/src/lib/types/types.d.ts +731 -0
  53. package/src/lib/utils/misc.ts +776 -0
  54. package/src/test-setup.ts +20 -0
  55. package/tsconfig.json +16 -0
  56. package/tsconfig.lib.json +14 -0
  57. package/tsconfig.lib.prod.json +9 -0
  58. package/tsconfig.spec.json +11 -0
  59. package/esm2022/index.mjs +0 -3
  60. package/esm2022/lib/components/tgo-typing-replay-input/tgo-typing-replay-input.component.mjs +0 -45
  61. package/esm2022/lib/components/tgo-typing-test/tgo-typing-test.component.mjs +0 -299
  62. package/esm2022/lib/helpers/config.mjs +0 -24
  63. package/esm2022/lib/helpers/constants/default-config.mjs +0 -103
  64. package/esm2022/lib/helpers/controllers/input-controller.mjs +0 -586
  65. package/esm2022/lib/helpers/controllers/quotes-controller.mjs +0 -118
  66. package/esm2022/lib/helpers/observables/config-event.mjs +0 -16
  67. package/esm2022/lib/helpers/observables/timer-event.mjs +0 -16
  68. package/esm2022/lib/helpers/states/active-page.mjs +0 -8
  69. package/esm2022/lib/helpers/states/composition.mjs +0 -20
  70. package/esm2022/lib/helpers/states/page-transition.mjs +0 -8
  71. package/esm2022/lib/helpers/states/slow-timer.mjs +0 -15
  72. package/esm2022/lib/helpers/states/test-active.mjs +0 -8
  73. package/esm2022/lib/helpers/states/time.mjs +0 -11
  74. package/esm2022/lib/helpers/test/caps-warning.mjs +0 -50
  75. package/esm2022/lib/helpers/test/caret.mjs +0 -80
  76. package/esm2022/lib/helpers/test/custom-text.mjs +0 -59
  77. package/esm2022/lib/helpers/test/english-punctuation.mjs +0 -29
  78. package/esm2022/lib/helpers/test/focus.mjs +0 -35
  79. package/esm2022/lib/helpers/test/manual-restart-tracker.mjs +0 -11
  80. package/esm2022/lib/helpers/test/out-of-focus.mjs +0 -14
  81. package/esm2022/lib/helpers/test/replay.mjs +0 -217
  82. package/esm2022/lib/helpers/test/test-input.mjs +0 -264
  83. package/esm2022/lib/helpers/test/test-logic.mjs +0 -927
  84. package/esm2022/lib/helpers/test/test-state.mjs +0 -13
  85. package/esm2022/lib/helpers/test/test-stats.mjs +0 -342
  86. package/esm2022/lib/helpers/test/test-timer.mjs +0 -207
  87. package/esm2022/lib/helpers/test/test-ui.mjs +0 -341
  88. package/esm2022/lib/helpers/test/test-words.mjs +0 -69
  89. package/esm2022/lib/helpers/test/timer-progress.mjs +0 -15
  90. package/esm2022/lib/helpers/test/weak-spot.mjs +0 -65
  91. package/esm2022/lib/helpers/test/wordset.mjs +0 -100
  92. package/esm2022/lib/utils/misc.mjs +0 -673
  93. package/esm2022/testgorilla-tgo-typing-test.mjs +0 -5
  94. package/fesm2022/testgorilla-tgo-typing-test.mjs +0 -4707
  95. package/fesm2022/testgorilla-tgo-typing-test.mjs.map +0 -1
  96. package/lib/components/tgo-typing-replay-input/tgo-typing-replay-input.component.d.ts +0 -14
  97. package/lib/components/tgo-typing-test/tgo-typing-test.component.d.ts +0 -54
  98. package/lib/helpers/config.d.ts +0 -98
  99. package/lib/helpers/constants/default-config.d.ts +0 -3
  100. package/lib/helpers/controllers/input-controller.d.ts +0 -16
  101. package/lib/helpers/controllers/quotes-controller.d.ts +0 -20
  102. package/lib/helpers/observables/config-event.d.ts +0 -5
  103. package/lib/helpers/observables/timer-event.d.ts +0 -4
  104. package/lib/helpers/states/active-page.d.ts +0 -2
  105. package/lib/helpers/states/composition.d.ts +0 -10
  106. package/lib/helpers/states/page-transition.d.ts +0 -2
  107. package/lib/helpers/states/slow-timer.d.ts +0 -3
  108. package/lib/helpers/states/test-active.d.ts +0 -2
  109. package/lib/helpers/states/time.d.ts +0 -3
  110. package/lib/helpers/test/caps-warning.d.ts +0 -5
  111. package/lib/helpers/test/caret.d.ts +0 -11
  112. package/lib/helpers/test/custom-text.d.ts +0 -16
  113. package/lib/helpers/test/english-punctuation.d.ts +0 -3
  114. package/lib/helpers/test/focus.d.ts +0 -7
  115. package/lib/helpers/test/manual-restart-tracker.d.ts +0 -3
  116. package/lib/helpers/test/out-of-focus.d.ts +0 -4
  117. package/lib/helpers/test/replay.d.ts +0 -20
  118. package/lib/helpers/test/test-input.d.ts +0 -86
  119. package/lib/helpers/test/test-logic.d.ts +0 -25
  120. package/lib/helpers/test/test-state.d.ts +0 -7
  121. package/lib/helpers/test/test-stats.d.ts +0 -92
  122. package/lib/helpers/test/test-timer.d.ts +0 -6
  123. package/lib/helpers/test/test-ui.d.ts +0 -27
  124. package/lib/helpers/test/test-words.d.ts +0 -23
  125. package/lib/helpers/test/timer-progress.d.ts +0 -3
  126. package/lib/helpers/test/weak-spot.d.ts +0 -3
  127. package/lib/helpers/test/wordset.d.ts +0 -7
  128. package/lib/utils/misc.d.ts +0 -81
  129. /package/{assets → src/assets}/typing-test-languages/english.json +0 -0
  130. /package/{assets → src/assets}/typing-test-languages/english_punctuation.json +0 -0
  131. /package/{assets → src/assets}/typing-test-languages/quotes/english_version_1.json +0 -0
  132. /package/{assets → src/assets}/typing-test-languages/quotes/english_version_2.json +0 -0
  133. /package/{assets → src/assets}/typing-test-languages/quotes/filtered_sources.json +0 -0
  134. /package/{index.d.ts → src/index.ts} +0 -0
@@ -0,0 +1,183 @@
1
+ import { subscribe } from '../observables/config-event';
2
+ import { shuffle } from '../../utils/misc';
3
+ import { MonkeyTypes } from '../../types/types';
4
+
5
+ interface Quote {
6
+ text: string;
7
+ source: string;
8
+ length: number;
9
+ id: number;
10
+ }
11
+
12
+ interface QuoteData {
13
+ language: string;
14
+ quotes: Quote[];
15
+ groups: number[][];
16
+ }
17
+
18
+ interface QuoteCollection {
19
+ quotes: MonkeyTypes.Quote[];
20
+ length: number;
21
+ language: string | null;
22
+ groups: MonkeyTypes.Quote[][];
23
+ }
24
+
25
+ interface FilteredSources {
26
+ sources: string[];
27
+ }
28
+
29
+ const defaultQuoteCollection: QuoteCollection = {
30
+ quotes: [],
31
+ length: 0,
32
+ language: null,
33
+ groups: [],
34
+ };
35
+
36
+ function normalizeLanguage(language: string): string {
37
+ return language.replace(/_\d*k$/g, '');
38
+ }
39
+
40
+ class QuotesController {
41
+ private quoteCollection: QuoteCollection = defaultQuoteCollection;
42
+
43
+ private quoteQueue: MonkeyTypes.Quote[] = [];
44
+ private queueIndex = 0;
45
+
46
+ async getQuotes(
47
+ language: string,
48
+ testVersion: number,
49
+ quoteLengths?: number[]
50
+ ): Promise<QuoteCollection> {
51
+ const normalizedLanguage = normalizeLanguage(language);
52
+
53
+ if (this.quoteCollection.language !== normalizedLanguage) {
54
+ try {
55
+ const data: QuoteData = await fetch(
56
+ `/../assets/typing-test-languages/quotes/${normalizedLanguage}_version_${testVersion}.json`
57
+ ).then(res => res.json());
58
+
59
+ const filteredSources: FilteredSources = await fetch(
60
+ `/../assets/typing-test-languages/quotes/filtered_sources.json`
61
+ ).then(res => res.json());
62
+
63
+ if (data.quotes === undefined || data.quotes.length === 0) {
64
+ return defaultQuoteCollection;
65
+ }
66
+
67
+ this.quoteCollection = {
68
+ quotes: [],
69
+ length: data.quotes.length,
70
+ groups: [],
71
+ language: data.language,
72
+ };
73
+
74
+ // Transform JSON Quote schema to MonkeyTypes Quote schema
75
+ data.quotes.forEach((quote: Quote) => {
76
+ const monkeyTypeQuote: MonkeyTypes.Quote = {
77
+ text: quote.text,
78
+ source: quote.source,
79
+ length: quote.length,
80
+ id: quote.id,
81
+ language: data.language,
82
+ group: 0,
83
+ };
84
+
85
+ this.quoteCollection.quotes.push(monkeyTypeQuote);
86
+ });
87
+
88
+ if (filteredSources) {
89
+ this.quoteCollection.quotes = this.quoteCollection.quotes.filter(quote =>
90
+ filteredSources.sources.includes(quote.source)
91
+ );
92
+ }
93
+
94
+ data.groups.forEach((quoteGroup, groupIndex) => {
95
+ const lower = quoteGroup[0];
96
+ const upper = quoteGroup[1];
97
+
98
+ this.quoteCollection.groups[groupIndex] = this.quoteCollection.quotes.filter(quote => {
99
+ if (quote.length >= lower && quote.length <= upper) {
100
+ quote.group = groupIndex;
101
+ return true;
102
+ }
103
+ return false;
104
+ });
105
+ });
106
+
107
+ if (quoteLengths !== undefined) {
108
+ this.updateQuoteQueue(quoteLengths);
109
+ }
110
+ } catch (e) {
111
+ if (!defaultQuoteCollection.quotes.length) {
112
+ throw e;
113
+ }
114
+ return defaultQuoteCollection;
115
+ }
116
+ }
117
+
118
+ return this.quoteCollection;
119
+ }
120
+
121
+ getQuoteById(id: number): MonkeyTypes.Quote | undefined {
122
+ const targetQuote = this.quoteCollection.quotes.find((quote: MonkeyTypes.Quote) => {
123
+ return quote.id === id;
124
+ });
125
+
126
+ return targetQuote;
127
+ }
128
+
129
+ updateQuoteQueue(quoteGroups: number[]): void {
130
+ this.quoteQueue = [];
131
+
132
+ quoteGroups.forEach(group => {
133
+ if (group < 0) {
134
+ return;
135
+ }
136
+ this.quoteCollection.groups[group]?.forEach(quote => {
137
+ this.quoteQueue.push(quote);
138
+ });
139
+ });
140
+
141
+ shuffle(this.quoteQueue);
142
+ this.queueIndex = 0;
143
+ }
144
+
145
+ getRandomQuote(): MonkeyTypes.Quote | null {
146
+ if (this.quoteQueue.length === 0) {
147
+ return null;
148
+ }
149
+
150
+ if (this.queueIndex >= this.quoteQueue.length) {
151
+ this.queueIndex = 0;
152
+ shuffle(this.quoteQueue);
153
+ }
154
+
155
+ const randomQuote = this.quoteQueue[this.queueIndex];
156
+
157
+ this.queueIndex += 1;
158
+
159
+ return randomQuote;
160
+ }
161
+
162
+ getCurrentQuote(): MonkeyTypes.Quote | null {
163
+ if (this.quoteQueue.length === 0) {
164
+ return null;
165
+ }
166
+
167
+ return this.quoteQueue[this.queueIndex];
168
+ }
169
+
170
+ resetQuotes() {
171
+ this.quoteCollection = defaultQuoteCollection;
172
+ }
173
+ }
174
+
175
+ const quoteController = new QuotesController();
176
+
177
+ subscribe((key, newValue) => {
178
+ if (key === 'quoteLength') {
179
+ quoteController.updateQuoteQueue(newValue as number[]);
180
+ }
181
+ });
182
+
183
+ export default quoteController;
@@ -0,0 +1,18 @@
1
+ type SubscribeFunction = () => void;
2
+
3
+ const subscribers: SubscribeFunction[] = [];
4
+
5
+ export function subscribe(fn: SubscribeFunction): void {
6
+ subscribers.push(fn);
7
+ }
8
+
9
+ export function dispatch(): void {
10
+ subscribers.forEach((fn) => {
11
+ try {
12
+ fn();
13
+ } catch (e) {
14
+ console.error("Banner event subscriber threw an error");
15
+ console.error(e);
16
+ }
17
+ });
18
+ }
@@ -0,0 +1,31 @@
1
+ import { MonkeyTypes } from '../../types/types';
2
+ type SubscribeFunction = (
3
+ key: string,
4
+ newValue?: MonkeyTypes.ConfigValues,
5
+ nosave?: boolean,
6
+ previousValue?: MonkeyTypes.ConfigValues,
7
+ fullConfig?: MonkeyTypes.Config
8
+ ) => void;
9
+
10
+ const subscribers: SubscribeFunction[] = [];
11
+
12
+ export function subscribe(fn: SubscribeFunction): void {
13
+ subscribers.push(fn);
14
+ }
15
+
16
+ export function dispatch(
17
+ key: string,
18
+ newValue?: MonkeyTypes.ConfigValues,
19
+ nosave?: boolean,
20
+ previousValue?: MonkeyTypes.ConfigValues,
21
+ fullConfig?: MonkeyTypes.Config
22
+ ): void {
23
+ subscribers.forEach(fn => {
24
+ try {
25
+ fn(key, newValue, nosave, previousValue, fullConfig);
26
+ } catch (e) {
27
+ console.error('Config event subscriber threw an error');
28
+ console.error(e);
29
+ }
30
+ });
31
+ }
@@ -0,0 +1,18 @@
1
+ type SubscribeFunction = (key: string, value?: string, value2?: string) => void;
2
+
3
+ const subscribers: SubscribeFunction[] = [];
4
+
5
+ export function subscribe(fn: SubscribeFunction): void {
6
+ subscribers.push(fn);
7
+ }
8
+
9
+ export function dispatch(key: string, value?: string, value2?: string): void {
10
+ subscribers.forEach((fn) => {
11
+ try {
12
+ fn(key, value, value2);
13
+ } catch (e) {
14
+ console.error("Timer event subscriber threw an error");
15
+ console.error(e);
16
+ }
17
+ });
18
+ }
@@ -0,0 +1,9 @@
1
+ let activePage = 'test';
2
+
3
+ export function get(): string {
4
+ return activePage;
5
+ }
6
+
7
+ export function set(active: string): void {
8
+ activePage = active;
9
+ }
@@ -0,0 +1,29 @@
1
+ interface CompositionState {
2
+ composing: boolean;
3
+ startPos: number;
4
+ }
5
+
6
+ const compositionState = {
7
+ composing: false,
8
+ startPos: -1,
9
+ };
10
+
11
+ export function get(): CompositionState {
12
+ return compositionState;
13
+ }
14
+
15
+ export function getComposing(): boolean {
16
+ return compositionState.composing;
17
+ }
18
+
19
+ export function getStartPos(): number {
20
+ return compositionState.startPos;
21
+ }
22
+
23
+ export function setComposing(isComposing: boolean): void {
24
+ compositionState.composing = isComposing;
25
+ }
26
+
27
+ export function setStartPos(pos: number): void {
28
+ compositionState.startPos = pos;
29
+ }
@@ -0,0 +1,9 @@
1
+ let transition = true;
2
+
3
+ export function set(val: boolean): void {
4
+ transition = val;
5
+ }
6
+
7
+ export function get(): boolean {
8
+ return transition;
9
+ }
@@ -0,0 +1,16 @@
1
+ let slowTimer = false;
2
+
3
+ export function set(): void {
4
+ if (slowTimer) return;
5
+ slowTimer = true;
6
+ console.error('Slow timer, disabling animations');
7
+ // Notifications.add("Slow timer detected", -1, 5);
8
+ }
9
+
10
+ export function clear(): void {
11
+ slowTimer = false;
12
+ }
13
+
14
+ export function get(): boolean {
15
+ return slowTimer;
16
+ }
@@ -0,0 +1,9 @@
1
+ let testActive = false;
2
+
3
+ export function get(): boolean {
4
+ return testActive;
5
+ }
6
+
7
+ export function set(active: boolean): void {
8
+ testActive = active;
9
+ }
@@ -0,0 +1,13 @@
1
+ let time = 0;
2
+
3
+ export function get(): number {
4
+ return time;
5
+ }
6
+
7
+ export function set(active: number): void {
8
+ time = active;
9
+ }
10
+
11
+ export function increment(): void {
12
+ time++;
13
+ }
@@ -0,0 +1,50 @@
1
+ import { BehaviorSubject } from 'rxjs';
2
+ import Config from '../../helpers/config';
3
+ export let capsState = false;
4
+
5
+ export const showCaps = new BehaviorSubject<boolean>(false);
6
+
7
+ function show(): void {
8
+ showCaps.next(true);
9
+ }
10
+
11
+ function hide(): void {
12
+ showCaps.next(false);
13
+ }
14
+
15
+ export function capsKeydown(event: KeyboardEvent) {
16
+ if (event.getModifierState('CapsLock')) {
17
+ capsState = true;
18
+ } else {
19
+ capsState = false;
20
+ }
21
+
22
+ try {
23
+ if (Config.capsLockWarning && capsState) {
24
+ show();
25
+ } else {
26
+ hide();
27
+ }
28
+ } catch {
29
+ console.error('Error showing caps warning');
30
+ }
31
+ }
32
+
33
+ export function capsKeyup(event: KeyboardEvent) {
34
+ // filthy fix but optional chaining refuses to work
35
+ if (event.getModifierState('CapsLock')) {
36
+ capsState = true;
37
+ } else {
38
+ capsState = false;
39
+ }
40
+
41
+ try {
42
+ if (Config.capsLockWarning && capsState) {
43
+ show();
44
+ } else {
45
+ hide();
46
+ }
47
+ } catch {
48
+ console.error('Error showing caps warning');
49
+ }
50
+ }
@@ -0,0 +1,92 @@
1
+ import * as Misc from '../../utils/misc';
2
+ import Config from '../config';
3
+ import * as TestInput from './test-input';
4
+ import * as SlowTimer from '../states/slow-timer';
5
+ import { BehaviorSubject } from 'rxjs';
6
+
7
+ export let caretAnimating = true;
8
+ export const caretShow = new BehaviorSubject<boolean>(true);
9
+ export const caretAnimation = new BehaviorSubject<string>('caretFlashSmooth');
10
+ export const caretLeft = new BehaviorSubject<number>(0);
11
+ export const caretTop = new BehaviorSubject<number>(0);
12
+
13
+ export function stopAnimation(): void {
14
+ if (caretAnimating === true) {
15
+ caretAnimation.next('none');
16
+ caretAnimating = false;
17
+ }
18
+ }
19
+
20
+ export function startAnimation(): void {
21
+ if (caretAnimating === false) {
22
+ if (Config.smoothCaret && !SlowTimer.get()) {
23
+ caretAnimation.next('caretFlashSmooth');
24
+ } else {
25
+ caretAnimation.next('caretFlashHard');
26
+ }
27
+ caretAnimating = true;
28
+ }
29
+ }
30
+
31
+ export function hide(): void {
32
+ caretShow.next(false);
33
+ }
34
+
35
+ export async function updatePosition(): Promise<void> {
36
+ let caretWidth = Math.round(document.querySelector('#caret')?.getBoundingClientRect().width ?? 0);
37
+
38
+ if (['block', 'outline', 'underline'].includes(Config.caretStyle)) {
39
+ caretWidth /= 3;
40
+ }
41
+
42
+ const inputLen = TestInput.input.current.length;
43
+
44
+ let currentLetterIndex = inputLen - 1;
45
+ if (currentLetterIndex == -1) {
46
+ currentLetterIndex = 0;
47
+ }
48
+
49
+ const currentWordNodeList = document?.querySelector('#words .active')?.querySelectorAll('letter');
50
+
51
+ if (!currentWordNodeList) return;
52
+
53
+ let currentLetter: HTMLElement = currentWordNodeList[currentLetterIndex] as HTMLElement;
54
+ if (inputLen > currentWordNodeList.length) {
55
+ currentLetter = currentWordNodeList[currentWordNodeList.length - 1] as HTMLElement;
56
+ }
57
+
58
+ const currentLanguage = await Misc.getCurrentLanguage(Config.language);
59
+ const isLanguageLeftToRight = currentLanguage.leftToRight;
60
+ const currentLetterPosLeft = isLanguageLeftToRight
61
+ ? currentLetter.offsetLeft
62
+ : currentLetter.offsetLeft + (currentLetter.getBoundingClientRect().width ?? 0);
63
+ const currentLetterPosTop = currentLetter.offsetTop;
64
+ const letterHeight = currentLetter.getBoundingClientRect().height as number;
65
+ let newTop = 0;
66
+ let newLeft = 0;
67
+
68
+ newTop = currentLetterPosTop - Math.round(letterHeight / 5);
69
+
70
+ if (inputLen == 0) {
71
+ newLeft = isLanguageLeftToRight
72
+ ? currentLetterPosLeft - caretWidth / 2
73
+ : currentLetterPosLeft + caretWidth / 2;
74
+ } else {
75
+ newLeft = isLanguageLeftToRight
76
+ ? currentLetterPosLeft +
77
+ (currentLetter.getBoundingClientRect().width as number) -
78
+ caretWidth / 2
79
+ : currentLetterPosLeft -
80
+ (currentLetter.getBoundingClientRect().width as number) +
81
+ caretWidth / 2;
82
+ }
83
+
84
+ caretTop.next(newTop);
85
+ caretLeft.next(newLeft);
86
+ }
87
+
88
+ export function show(): void {
89
+ caretShow.next(true);
90
+ updatePosition();
91
+ startAnimation();
92
+ }
@@ -0,0 +1,73 @@
1
+ export let text = [
2
+ "The",
3
+ "quick",
4
+ "brown",
5
+ "fox",
6
+ "jumps",
7
+ "over",
8
+ "the",
9
+ "lazy",
10
+ "dog",
11
+ ];
12
+ export let isWordRandom = false;
13
+ export let isTimeRandom = false;
14
+ export let word = -1;
15
+ export let time = -1;
16
+ export let delimiter = " ";
17
+
18
+ export function setText(txt: string[]): void {
19
+ text = txt;
20
+ }
21
+
22
+ export function setIsWordRandom(val: boolean): void {
23
+ isWordRandom = val;
24
+ }
25
+
26
+ export function setIsTimeRandom(val: boolean): void {
27
+ isTimeRandom = val;
28
+ }
29
+
30
+ export function setTime(val: number): void {
31
+ time = val;
32
+ }
33
+
34
+ export function setWord(val: number): void {
35
+ word = val;
36
+ }
37
+
38
+ export function setDelimiter(val: string): void {
39
+ delimiter = val;
40
+ }
41
+
42
+ type CustomTextObject = Record<string, string>;
43
+
44
+ export function getCustomText(name: string): string[] {
45
+ const customText = getCustomTextObject();
46
+
47
+ return customText[name].split(/ +/);
48
+ }
49
+
50
+ export function setCustomText(name: string, text: string | string[]): void {
51
+ const customText = getCustomTextObject();
52
+
53
+ if (typeof text === "string") customText[name] = text;
54
+ else customText[name] = text.join(" ");
55
+
56
+ window.localStorage.setItem("customText", JSON.stringify(customText));
57
+ }
58
+
59
+ export function deleteCustomText(name: string): void {
60
+ const customText = getCustomTextObject();
61
+
62
+ if (customText[name]) delete customText[name];
63
+
64
+ window.localStorage.setItem("customText", JSON.stringify(customText));
65
+ }
66
+
67
+ function getCustomTextObject(): CustomTextObject {
68
+ return JSON.parse(window.localStorage.getItem("customText") ?? "{}");
69
+ }
70
+
71
+ export function getCustomTextNames(): string[] {
72
+ return Object.keys(getCustomTextObject());
73
+ }
@@ -0,0 +1,38 @@
1
+ let pairsList: string[] = [];
2
+
3
+ export async function getList(): Promise<string[]> {
4
+ if (pairsList.length === 0) {
5
+ return fetch("/../assets/typing-test-languages/english_punctuation.json")
6
+ .then(res => res.json())
7
+ .then(json => {
8
+ pairsList = json;
9
+ return pairsList;
10
+ });
11
+ }
12
+ return pairsList;
13
+ }
14
+
15
+ // Check if word is in the group of pairs so it can be replaced
16
+ export async function check(word: string): Promise<boolean> {
17
+ const list = await getList();
18
+ if (
19
+ list.find((a) => word.match(RegExp(`^([\\W]*${a[0]}[\\W]*)$`, "gi"))) ===
20
+ undefined
21
+ ) {
22
+ return false;
23
+ }
24
+ return true;
25
+ }
26
+
27
+ export async function replace(word: string): Promise<string> {
28
+ const list = await getList();
29
+ const replacement = list.find((a) =>
30
+ word.match(RegExp(`^([\\W]*${a[0]}[\\W]*)$`, "gi"))
31
+ );
32
+ return replacement
33
+ ? word.replace(
34
+ RegExp(`^(?:([\\W]*)(${replacement[0]})([\\W]*))$`, "gi"),
35
+ replacement[1]
36
+ )
37
+ : word;
38
+ }
@@ -0,0 +1,39 @@
1
+ import * as Caret from './caret';
2
+
3
+ const unfocusPx = 3;
4
+ let state = false;
5
+ let middle: HTMLElement;
6
+ let capsWarning: HTMLElement;
7
+
8
+ export function setMiddleElement(middleElem: HTMLElement) {
9
+ middle = middleElem;
10
+ }
11
+
12
+ export function setCapsWarningElement(capsWarningElem: HTMLElement) {
13
+ capsWarning = capsWarningElem;
14
+ }
15
+
16
+ export function set(foc: boolean, withCursor = false): void {
17
+ if (foc && !state) {
18
+ state = true;
19
+ Caret.stopAnimation();
20
+ middle.classList.add('focus');
21
+ capsWarning.classList.add('focus');
22
+ } else if (!foc && state) {
23
+ state = false;
24
+ Caret.startAnimation();
25
+ middle.classList.remove('focus');
26
+ capsWarning.classList.remove('focus');
27
+ }
28
+ }
29
+
30
+ export function mousemove(event: { movementX: number; movementY: number }) {
31
+ if (!state) return;
32
+ // To avoid mouse/desk vibration from creating a flashy effect, we'll unfocus @ >5px instead of >0px
33
+ if (
34
+ middle.classList.contains('focus') &&
35
+ (event.movementX > unfocusPx || event.movementY > unfocusPx)
36
+ ) {
37
+ set(false);
38
+ }
39
+ }
@@ -0,0 +1,13 @@
1
+ let state = false;
2
+
3
+ export function set(): void {
4
+ state = true;
5
+ }
6
+
7
+ export function reset(): void {
8
+ state = false;
9
+ }
10
+
11
+ export function get(): boolean {
12
+ return state;
13
+ }
@@ -0,0 +1,19 @@
1
+ import { BehaviorSubject } from 'rxjs';
2
+ import * as Misc from '../../utils/misc';
3
+
4
+ const outOfFocusTimeouts: (number | NodeJS.Timeout)[] = [];
5
+
6
+ export const outOfFocusShow = new BehaviorSubject<boolean>(false);
7
+
8
+ export function hide(): void {
9
+ outOfFocusShow.next(false);
10
+ Misc.clearTimeouts(outOfFocusTimeouts);
11
+ }
12
+
13
+ export function show(): void {
14
+ outOfFocusTimeouts.push(
15
+ setTimeout(() => {
16
+ outOfFocusShow.next(true);
17
+ }, 1000)
18
+ );
19
+ }