scrabble-solver 2.8.8 → 2.8.10

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 (222) hide show
  1. package/.eslintrc.js +3 -1
  2. package/.eslintrc.prettier-only.js +1 -1
  3. package/.prettierignore +3 -0
  4. package/.prettierrc +3 -3
  5. package/babel.config.js +1 -1
  6. package/lerna.json +1 -1
  7. package/package.json +17 -17
  8. package/packages/configs/package.json +3 -3
  9. package/packages/constants/package.json +1 -1
  10. package/packages/dictionaries/package-lock.json +9 -9
  11. package/packages/dictionaries/package.json +5 -5
  12. package/packages/logger/package-lock.json +2 -2
  13. package/packages/logger/package.json +1 -1
  14. package/packages/scrabble-solver/.next/BUILD_ID +1 -1
  15. package/packages/scrabble-solver/.next/build-manifest.json +12 -12
  16. package/packages/scrabble-solver/.next/cache/.tsbuildinfo +1 -1
  17. package/packages/scrabble-solver/.next/cache/eslint/.cache_8dgz12 +1 -1
  18. package/packages/scrabble-solver/.next/cache/next-server.js.nft.json +1 -1
  19. package/packages/scrabble-solver/.next/cache/webpack/client-production/0.pack +0 -0
  20. package/packages/scrabble-solver/.next/cache/webpack/client-production/index.pack +0 -0
  21. package/packages/scrabble-solver/.next/cache/webpack/server-production/0.pack +0 -0
  22. package/packages/scrabble-solver/.next/cache/webpack/server-production/index.pack +0 -0
  23. package/packages/scrabble-solver/.next/next-server.js.nft.json +1 -1
  24. package/packages/scrabble-solver/.next/prerender-manifest.json +1 -1
  25. package/packages/scrabble-solver/.next/routes-manifest.json +1 -1
  26. package/packages/scrabble-solver/.next/server/chunks/413.js +350 -73
  27. package/packages/scrabble-solver/.next/server/chunks/429.js +2 -13
  28. package/packages/scrabble-solver/.next/server/chunks/44.js +802 -0
  29. package/packages/scrabble-solver/.next/server/chunks/515.js +767 -322
  30. package/packages/scrabble-solver/.next/server/chunks/911.js +77 -25
  31. package/packages/scrabble-solver/.next/server/middleware-build-manifest.js +1 -1
  32. package/packages/scrabble-solver/.next/server/pages/404.html +2 -2
  33. package/packages/scrabble-solver/.next/server/pages/404.js.nft.json +1 -1
  34. package/packages/scrabble-solver/.next/server/pages/500.html +2 -2
  35. package/packages/scrabble-solver/.next/server/pages/_app.js.nft.json +1 -1
  36. package/packages/scrabble-solver/.next/server/pages/_document.js.nft.json +1 -1
  37. package/packages/scrabble-solver/.next/server/pages/_error.js.nft.json +1 -1
  38. package/packages/scrabble-solver/.next/server/pages/api/solve.js +226 -927
  39. package/packages/scrabble-solver/.next/server/pages/api/solve.js.nft.json +1 -1
  40. package/packages/scrabble-solver/.next/server/pages/api/verify.js +217 -0
  41. package/packages/scrabble-solver/.next/server/pages/api/verify.js.nft.json +1 -0
  42. package/packages/scrabble-solver/.next/server/pages/index.html +3 -3
  43. package/packages/scrabble-solver/.next/server/pages/index.js +8 -2
  44. package/packages/scrabble-solver/.next/server/pages/index.js.nft.json +1 -1
  45. package/packages/scrabble-solver/.next/server/pages/index.json +1 -1
  46. package/packages/scrabble-solver/.next/server/pages-manifest.json +1 -0
  47. package/packages/scrabble-solver/.next/static/A8A_Lmg8cM-Bkf-Jo1CLh/_buildManifest.js +1 -0
  48. package/packages/scrabble-solver/.next/static/{z3J3qmq1nazbDv_ENIkCo → A8A_Lmg8cM-Bkf-Jo1CLh}/_ssgManifest.js +0 -0
  49. package/packages/scrabble-solver/.next/static/chunks/317-95ab9051449362fa.js +1 -0
  50. package/packages/scrabble-solver/.next/static/chunks/758-eff80059a1365d5d.js +1 -0
  51. package/packages/scrabble-solver/.next/static/chunks/pages/{404-8eb3ba4f0ba17e08.js → 404-90c624da3c83fd17.js} +1 -1
  52. package/packages/scrabble-solver/.next/static/chunks/pages/_app-0e358b5622cf9e66.js +1 -0
  53. package/packages/scrabble-solver/.next/static/chunks/pages/index-0cc5e6eda5adac73.js +1 -0
  54. package/packages/scrabble-solver/.next/static/css/9ac903004135f4b1.css +1 -0
  55. package/packages/scrabble-solver/.next/static/css/{cdbc9e0afcff5473.css → ad2a08918868cad8.css} +1 -1
  56. package/packages/scrabble-solver/.next/trace +42 -41
  57. package/packages/scrabble-solver/package-lock.json +298 -298
  58. package/packages/scrabble-solver/package.json +11 -11
  59. package/packages/scrabble-solver/src/components/Badge/Badge.module.scss +13 -0
  60. package/packages/scrabble-solver/src/components/Badge/Badge.tsx +15 -0
  61. package/packages/scrabble-solver/src/components/Badge/index.ts +1 -0
  62. package/packages/scrabble-solver/src/components/Board/Board.tsx +4 -2
  63. package/packages/scrabble-solver/src/components/Board/BoardPure.tsx +25 -5
  64. package/packages/scrabble-solver/src/components/Board/hooks/useGrid.ts +212 -91
  65. package/packages/scrabble-solver/src/components/Dictionary/Dictionary.tsx +8 -1
  66. package/packages/scrabble-solver/src/components/NavButtons/NavButtons.tsx +33 -14
  67. package/packages/scrabble-solver/src/components/Rack/Rack.tsx +51 -11
  68. package/packages/scrabble-solver/src/components/Rack/RackTile.tsx +33 -16
  69. package/packages/scrabble-solver/src/components/RemainingTiles/RemainingTiles.module.scss +8 -7
  70. package/packages/scrabble-solver/src/components/RemainingTiles/RemainingTiles.tsx +13 -4
  71. package/packages/scrabble-solver/src/components/Results/Results.tsx +19 -3
  72. package/packages/scrabble-solver/src/components/Sidebar/Sidebar.tsx +2 -2
  73. package/packages/scrabble-solver/src/components/Sidebar/components/Section/Section.module.scss +0 -1
  74. package/packages/scrabble-solver/src/components/Sidebar/components/Section/Section.tsx +1 -1
  75. package/packages/scrabble-solver/src/components/SquareButton/Link.tsx +1 -1
  76. package/packages/scrabble-solver/src/components/SquareButton/SquareButton.module.scss +5 -0
  77. package/packages/scrabble-solver/src/components/Tile/Tile.module.scss +4 -0
  78. package/packages/scrabble-solver/src/components/Tile/Tile.tsx +13 -4
  79. package/packages/scrabble-solver/src/components/Tile/TilePure.tsx +3 -4
  80. package/packages/scrabble-solver/src/components/Words/Words.module.scss +35 -0
  81. package/packages/scrabble-solver/src/components/Words/Words.tsx +57 -0
  82. package/packages/scrabble-solver/src/components/Words/index.ts +1 -0
  83. package/packages/scrabble-solver/src/components/index.ts +2 -0
  84. package/packages/scrabble-solver/src/i18n/de.json +4 -1
  85. package/packages/scrabble-solver/src/i18n/en.json +4 -1
  86. package/packages/scrabble-solver/src/i18n/es.json +4 -1
  87. package/packages/scrabble-solver/src/i18n/fr.json +4 -1
  88. package/packages/scrabble-solver/src/i18n/pl.json +4 -1
  89. package/packages/scrabble-solver/src/icons/BookHalf.svg +4 -0
  90. package/packages/scrabble-solver/src/icons/Check.svg +4 -0
  91. package/packages/scrabble-solver/src/icons/Cross.svg +2 -2
  92. package/packages/scrabble-solver/src/icons/CrossFill.svg +4 -0
  93. package/packages/scrabble-solver/src/icons/index.ts +3 -0
  94. package/packages/scrabble-solver/src/lib/extractCharacters.ts +26 -0
  95. package/packages/scrabble-solver/src/lib/extractInputValue.ts +17 -0
  96. package/packages/scrabble-solver/src/lib/index.ts +2 -0
  97. package/packages/scrabble-solver/src/lib/isCtrl.ts +1 -1
  98. package/packages/scrabble-solver/src/lib/memoize.ts +15 -1
  99. package/packages/scrabble-solver/src/pages/api/solve.ts +3 -4
  100. package/packages/scrabble-solver/src/pages/api/verify.ts +71 -0
  101. package/packages/scrabble-solver/src/pages/index.tsx +5 -0
  102. package/packages/scrabble-solver/src/sdk/fetchJson.ts +36 -0
  103. package/packages/scrabble-solver/src/sdk/findWordDefinitions.ts +4 -3
  104. package/packages/scrabble-solver/src/sdk/index.ts +1 -0
  105. package/packages/scrabble-solver/src/sdk/solve.ts +8 -7
  106. package/packages/scrabble-solver/src/sdk/verify.ts +23 -0
  107. package/packages/scrabble-solver/src/state/rootReducer.ts +2 -0
  108. package/packages/scrabble-solver/src/state/sagas.ts +35 -8
  109. package/packages/scrabble-solver/src/state/selectors.ts +17 -1
  110. package/packages/scrabble-solver/src/state/slices/dictionaryInitialState.ts +10 -2
  111. package/packages/scrabble-solver/src/state/slices/dictionarySlice.ts +10 -16
  112. package/packages/scrabble-solver/src/state/slices/index.ts +2 -0
  113. package/packages/scrabble-solver/src/state/slices/rackSlice.ts +7 -0
  114. package/packages/scrabble-solver/src/state/slices/solveInitialState.ts +14 -2
  115. package/packages/scrabble-solver/src/state/slices/solveSlice.ts +7 -4
  116. package/packages/scrabble-solver/src/state/slices/verifyInitialState.ts +12 -0
  117. package/packages/scrabble-solver/src/state/slices/verifySlice.ts +31 -0
  118. package/packages/scrabble-solver/src/styles/variables.scss +2 -1
  119. package/packages/scrabble-solver/src/types/index.ts +6 -1
  120. package/packages/solver/build/fillPattern.d.ts +5 -0
  121. package/packages/solver/build/fillPattern.js +52 -0
  122. package/packages/solver/build/generateEndIndices.d.ts +3 -0
  123. package/packages/solver/build/generateEndIndices.js +16 -0
  124. package/packages/solver/build/generateHorizontalPatterns.d.ts +3 -0
  125. package/packages/solver/build/generateHorizontalPatterns.js +18 -0
  126. package/packages/solver/build/generatePattern.d.ts +8 -0
  127. package/packages/solver/build/generatePattern.js +22 -0
  128. package/packages/solver/build/generatePatterns.d.ts +3 -0
  129. package/packages/solver/build/generatePatterns.js +13 -0
  130. package/packages/solver/build/generateStartIndices.d.ts +3 -0
  131. package/packages/solver/build/generateStartIndices.js +15 -0
  132. package/packages/solver/build/generateVectors.d.ts +7 -0
  133. package/packages/solver/build/generateVectors.js +6 -0
  134. package/packages/solver/build/generateVerticalPatterns.d.ts +3 -0
  135. package/packages/solver/build/generateVerticalPatterns.js +18 -0
  136. package/packages/solver/build/getCellsScore.d.ts +3 -0
  137. package/packages/solver/build/getCellsScore.js +16 -0
  138. package/packages/solver/build/getPatternHash.d.ts +3 -0
  139. package/packages/solver/build/getPatternHash.js +13 -0
  140. package/packages/solver/build/getPatternScore.d.ts +3 -0
  141. package/packages/solver/build/getPatternScore.js +15 -0
  142. package/packages/solver/build/getUniquePatterns.js +5 -11
  143. package/packages/solver/build/index.d.ts +1 -1
  144. package/packages/solver/build/index.js +3 -3
  145. package/packages/solver/build/solve.d.ts +4 -0
  146. package/packages/solver/build/solve.js +22 -0
  147. package/packages/solver/package-lock.json +9 -9
  148. package/packages/solver/package.json +6 -6
  149. package/packages/solver/src/fillPattern.test.ts +107 -0
  150. package/packages/solver/src/fillPattern.ts +70 -0
  151. package/packages/solver/src/generateEndIndices.test.ts +23 -0
  152. package/packages/solver/src/generateEndIndices.ts +21 -0
  153. package/packages/solver/src/generateHorizontalPatterns.test.ts +31 -0
  154. package/packages/solver/src/generateHorizontalPatterns.ts +17 -0
  155. package/packages/solver/src/generatePattern.ts +35 -0
  156. package/packages/solver/src/generatePatterns.ts +13 -0
  157. package/packages/solver/src/generateStartIndices.test.ts +19 -0
  158. package/packages/solver/src/generateStartIndices.ts +19 -0
  159. package/packages/solver/src/generateVectors.test.ts +12 -0
  160. package/packages/solver/src/generateVectors.ts +12 -0
  161. package/packages/solver/src/generateVerticalPatterns.test.ts +32 -0
  162. package/packages/solver/src/generateVerticalPatterns.ts +17 -0
  163. package/packages/solver/src/getCellsScore.ts +22 -0
  164. package/packages/solver/src/getPatternHash.test.ts +17 -0
  165. package/packages/solver/src/getPatternHash.ts +14 -0
  166. package/packages/solver/src/getPatternScore.test.ts +60 -0
  167. package/packages/solver/src/getPatternScore.ts +15 -0
  168. package/packages/solver/src/getUniquePatterns.ts +1 -10
  169. package/packages/solver/src/index.ts +1 -1
  170. package/packages/solver/src/{Solver.test.ts → solve.test.ts} +17 -16
  171. package/packages/solver/src/solve.ts +23 -0
  172. package/packages/types/build/Board.d.ts +1 -0
  173. package/packages/types/build/Board.js +19 -3
  174. package/packages/types/build/Config.js +1 -1
  175. package/packages/types/build/FinalPattern.d.ts +7 -0
  176. package/packages/types/build/FinalPattern.js +16 -0
  177. package/packages/types/build/HorizontalPattern.js +2 -5
  178. package/packages/types/build/Pattern.d.ts +1 -4
  179. package/packages/types/build/Pattern.js +2 -5
  180. package/packages/types/build/Result.js +3 -5
  181. package/packages/types/build/Tile.d.ts +2 -2
  182. package/packages/types/build/VerticalPattern.js +2 -5
  183. package/packages/types/build/index.d.ts +2 -0
  184. package/packages/types/build/index.js +5 -1
  185. package/packages/types/build/isError.d.ts +2 -0
  186. package/packages/types/build/isError.js +13 -0
  187. package/packages/types/package.json +2 -2
  188. package/packages/types/src/Board.ts +25 -3
  189. package/packages/types/src/Config.ts +1 -1
  190. package/packages/types/src/FinalPattern.ts +16 -0
  191. package/packages/types/src/HorizontalPattern.ts +5 -5
  192. package/packages/types/src/Pattern.ts +5 -5
  193. package/packages/types/src/Result.ts +3 -5
  194. package/packages/types/src/Tile.ts +2 -2
  195. package/packages/types/src/VerticalPattern.ts +5 -5
  196. package/packages/types/src/index.ts +2 -0
  197. package/packages/types/src/isError.ts +11 -0
  198. package/packages/word-definitions/package-lock.json +8 -8
  199. package/packages/word-definitions/package.json +2 -2
  200. package/packages/word-lists/package-lock.json +2 -2
  201. package/packages/word-lists/package.json +2 -2
  202. package/packages/scrabble-solver/.next/static/chunks/615-d258f6c528c18622.js +0 -1
  203. package/packages/scrabble-solver/.next/static/chunks/758-f333b1dcdb941547.js +0 -1
  204. package/packages/scrabble-solver/.next/static/chunks/pages/_app-4a663fd3d5ca4524.js +0 -1
  205. package/packages/scrabble-solver/.next/static/chunks/pages/index-1a9826d740cc8830.js +0 -1
  206. package/packages/scrabble-solver/.next/static/css/180c6c26317ac90f.css +0 -1
  207. package/packages/scrabble-solver/.next/static/z3J3qmq1nazbDv_ENIkCo/_buildManifest.js +0 -1
  208. package/packages/solver/build/PatternsFiller.d.ts +0 -12
  209. package/packages/solver/build/PatternsFiller.js +0 -70
  210. package/packages/solver/build/PatternsGenerator.d.ts +0 -32
  211. package/packages/solver/build/PatternsGenerator.js +0 -66
  212. package/packages/solver/build/ScoresCalculator.d.ts +0 -17
  213. package/packages/solver/build/ScoresCalculator.js +0 -37
  214. package/packages/solver/build/Solver.d.ts +0 -10
  215. package/packages/solver/build/Solver.js +0 -30
  216. package/packages/solver/src/PatternsFiller.test.ts +0 -92
  217. package/packages/solver/src/PatternsFiller.ts +0 -91
  218. package/packages/solver/src/PatternsGenerator.test.ts +0 -89
  219. package/packages/solver/src/PatternsGenerator.ts +0 -103
  220. package/packages/solver/src/ScoresCalculator.test.ts +0 -70
  221. package/packages/solver/src/ScoresCalculator.ts +0 -52
  222. package/packages/solver/src/Solver.ts +0 -39
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scrabble-solver/scrabble-solver",
3
- "version": "2.8.8",
3
+ "version": "2.8.10",
4
4
  "description": "Scrabble Solver 2 - App",
5
5
  "engines": {
6
6
  "node": ">=16"
@@ -29,20 +29,20 @@
29
29
  },
30
30
  "dependencies": {
31
31
  "@popperjs/core": "^2.11.6",
32
- "@reduxjs/toolkit": "^1.8.5",
33
- "@scrabble-solver/configs": "^2.8.8",
34
- "@scrabble-solver/constants": "^2.8.8",
35
- "@scrabble-solver/dictionaries": "^2.8.8",
36
- "@scrabble-solver/logger": "^2.8.8",
37
- "@scrabble-solver/solver": "^2.8.8",
38
- "@scrabble-solver/types": "^2.8.8",
39
- "@scrabble-solver/word-definitions": "^2.8.8",
32
+ "@reduxjs/toolkit": "^1.8.6",
33
+ "@scrabble-solver/configs": "^2.8.10",
34
+ "@scrabble-solver/constants": "^2.8.10",
35
+ "@scrabble-solver/dictionaries": "^2.8.10",
36
+ "@scrabble-solver/logger": "^2.8.10",
37
+ "@scrabble-solver/solver": "^2.8.10",
38
+ "@scrabble-solver/types": "^2.8.10",
39
+ "@scrabble-solver/word-definitions": "^2.8.10",
40
40
  "classnames": "^2.3.2",
41
41
  "next": "^12.3.1",
42
42
  "normalize.css": "^8.0.1",
43
43
  "react": "^18.2.0",
44
44
  "react-dom": "^18.2.0",
45
- "react-modal": "^3.15.1",
45
+ "react-modal": "^3.16.1",
46
46
  "react-popper": "^2.3.0",
47
47
  "react-portal": "^4.2.2",
48
48
  "react-redux": "^8.0.4",
@@ -54,7 +54,7 @@
54
54
  "uuid": "^9.0.0"
55
55
  },
56
56
  "devDependencies": {
57
- "@svgr/webpack": "^6.4.0",
57
+ "@svgr/webpack": "^6.5.0",
58
58
  "@types/classnames": "^2.3.0",
59
59
  "@types/react": "^18.0.21",
60
60
  "@types/react-dom": "^18.0.6",
@@ -0,0 +1,13 @@
1
+ .badge {
2
+ display: inline-flex;
3
+ align-items: center;
4
+ justify-content: center;
5
+ padding: var(--spacing--xs) var(--spacing--s);
6
+ border-radius: var(--border--radius);
7
+ background-color: var(--color--foreground--secondary);
8
+ box-shadow: va(--box-shadow);
9
+ line-height: var(--line-height);
10
+ font-size: var(--font--size--s);
11
+ font-weight: bold;
12
+ color: white;
13
+ }
@@ -0,0 +1,15 @@
1
+ import classNames from 'classnames';
2
+ import { FunctionComponent, ReactNode } from 'react';
3
+
4
+ import styles from './Badge.module.scss';
5
+
6
+ interface Props {
7
+ children?: ReactNode;
8
+ className?: string;
9
+ }
10
+
11
+ const Badge: FunctionComponent<Props> = ({ children, className }) => (
12
+ <div className={classNames(styles.badge, className)}>{children}</div>
13
+ );
14
+
15
+ export default Badge;
@@ -0,0 +1 @@
1
+ export { default } from './Badge';
@@ -14,20 +14,22 @@ interface Props {
14
14
  const Board: FunctionComponent<Props> = ({ cellSize, className, innerRef }) => {
15
15
  const rows = useTypedSelector(selectRowsWithCandidate);
16
16
  const board = useTypedSelector(selectBoard);
17
- const [{ lastDirection, refs }, { onDirectionToggle, onFocus, onKeyDown }] = useGrid(rows);
17
+ const [{ direction, refs }, { onChange, onDirectionToggle, onFocus, onKeyDown, onPaste }] = useGrid(rows);
18
18
 
19
19
  return (
20
20
  <BoardPure
21
21
  className={className}
22
22
  cellSize={cellSize}
23
23
  center={board.center}
24
+ direction={direction}
24
25
  innerRef={innerRef}
25
- lastDirection={lastDirection}
26
26
  refs={refs}
27
27
  rows={rows}
28
+ onChange={onChange}
28
29
  onDirectionToggle={onDirectionToggle}
29
30
  onFocus={onFocus}
30
31
  onKeyDown={onKeyDown}
32
+ onPaste={onPaste}
31
33
  />
32
34
  );
33
35
  };
@@ -1,6 +1,16 @@
1
1
  import { Cell } from '@scrabble-solver/types';
2
2
  import classNames from 'classnames';
3
- import { FunctionComponent, KeyboardEventHandler, memo, Ref, RefObject } from 'react';
3
+ import {
4
+ ChangeEventHandler,
5
+ ClipboardEventHandler,
6
+ FunctionComponent,
7
+ KeyboardEventHandler,
8
+ memo,
9
+ Ref,
10
+ RefObject,
11
+ } from 'react';
12
+
13
+ import { Direction } from 'types';
4
14
 
5
15
  import styles from './Board.module.scss';
6
16
  import { Cell as CellComponent } from './components';
@@ -9,35 +19,45 @@ interface Props {
9
19
  className?: string;
10
20
  cellSize: number;
11
21
  center: Cell;
22
+ direction: Direction;
12
23
  innerRef?: Ref<HTMLDivElement>;
13
- lastDirection: 'horizontal' | 'vertical';
14
24
  refs: RefObject<HTMLInputElement>[][];
15
25
  rows: Cell[][];
26
+ onChange: ChangeEventHandler<HTMLInputElement>;
16
27
  onDirectionToggle: () => void;
17
28
  onFocus: (x: number, y: number) => void;
18
29
  onKeyDown: KeyboardEventHandler<HTMLInputElement>;
30
+ onPaste: ClipboardEventHandler<HTMLInputElement>;
19
31
  }
20
32
 
21
33
  const BoardPure: FunctionComponent<Props> = ({
22
34
  className,
23
35
  cellSize,
24
36
  center,
37
+ direction,
25
38
  innerRef,
26
- lastDirection,
27
39
  refs,
28
40
  rows,
41
+ onChange,
29
42
  onDirectionToggle,
30
43
  onFocus,
31
44
  onKeyDown,
45
+ onPaste,
32
46
  }) => (
33
- <div className={classNames(styles.board, className)} ref={innerRef} onKeyDown={onKeyDown}>
47
+ <div
48
+ className={classNames(styles.board, className)}
49
+ ref={innerRef}
50
+ onChange={onChange}
51
+ onKeyDown={onKeyDown}
52
+ onPaste={onPaste}
53
+ >
34
54
  {rows.map((cells, y) => (
35
55
  <div className={styles.row} key={y}>
36
56
  {cells.map((cell, x) => (
37
57
  <CellComponent
38
58
  className={styles.cell}
39
59
  cell={cell}
40
- direction={lastDirection}
60
+ direction={direction}
41
61
  inputRef={refs[y][x]}
42
62
  isCenter={center.x === x && center.y === y}
43
63
  key={x}
@@ -1,25 +1,41 @@
1
1
  /* eslint-disable max-lines, max-statements */
2
- import { EMPTY_CELL } from '@scrabble-solver/constants';
3
- import { Cell } from '@scrabble-solver/types';
4
- import { createRef, KeyboardEventHandler, RefObject, useCallback, useMemo, useState, useRef } from 'react';
2
+ import { BLANK, EMPTY_CELL } from '@scrabble-solver/constants';
3
+ import { Board, Cell } from '@scrabble-solver/types';
4
+ import {
5
+ createRef,
6
+ KeyboardEventHandler,
7
+ RefObject,
8
+ useCallback,
9
+ useMemo,
10
+ useState,
11
+ useRef,
12
+ ChangeEventHandler,
13
+ ChangeEvent,
14
+ ClipboardEventHandler,
15
+ } from 'react';
5
16
  import { useDispatch } from 'react-redux';
6
17
  import { useLatest } from 'react-use';
7
18
 
8
- import { createGridOf, createKeyboardNavigation, isCtrl } from 'lib';
19
+ import { createGridOf, createKeyboardNavigation, extractCharacters, extractInputValue, isCtrl } from 'lib';
9
20
  import { boardSlice, selectConfig, useTypedSelector } from 'state';
21
+ import { Direction } from 'types';
10
22
 
11
23
  import { getPositionInGrid } from '../lib';
12
24
  import { Point } from '../types';
13
25
 
26
+ const toggleDirection = (direction: Direction) => (direction === 'vertical' ? 'horizontal' : 'vertical');
27
+
14
28
  interface State {
15
- lastDirection: 'horizontal' | 'vertical';
29
+ direction: Direction;
16
30
  refs: RefObject<HTMLInputElement>[][];
17
31
  }
18
32
 
19
33
  interface Actions {
20
- onFocus: (x: number, y: number) => void;
34
+ onChange: ChangeEventHandler<HTMLInputElement>;
21
35
  onDirectionToggle: () => void;
36
+ onFocus: (x: number, y: number) => void;
22
37
  onKeyDown: KeyboardEventHandler<HTMLInputElement>;
38
+ onPaste: ClipboardEventHandler<HTMLInputElement>;
23
39
  }
24
40
 
25
41
  const useGrid = (rows: Cell[][]): [State, Actions] => {
@@ -32,8 +48,8 @@ const useGrid = (rows: Cell[][]): [State, Actions] => {
32
48
  [width, height],
33
49
  );
34
50
  const activeIndexRef = useRef<Point>({ x: 0, y: 0 });
35
- const [lastDirection, setLastDirection] = useState<'horizontal' | 'vertical'>('horizontal');
36
- const lastDirectionRef = useLatest(lastDirection);
51
+ const [direction, setLastDirection] = useState<Direction>('horizontal');
52
+ const directionRef = useLatest(direction);
37
53
 
38
54
  const changeActiveIndex = useCallback(
39
55
  (offsetX: number, offsetY: number) => {
@@ -52,32 +68,156 @@ const useGrid = (rows: Cell[][]): [State, Actions] => {
52
68
  [refs],
53
69
  );
54
70
 
55
- const onDirectionToggle = useCallback(() => {
56
- setLastDirection((direction) => {
57
- return direction === 'vertical' ? 'horizontal' : 'vertical';
58
- });
59
- }, []);
60
-
61
- const onFocus = useCallback((x: number, y: number) => {
62
- activeIndexRef.current = { x, y };
63
- }, []);
64
-
65
- const onMoveFocus = useCallback(
66
- (direction: 'backward' | 'forward') => {
67
- const offset = direction === 'forward' ? 1 : -1;
68
-
69
- if (lastDirectionRef.current === 'horizontal') {
71
+ const moveFocus = useCallback(
72
+ (offset: number) => {
73
+ if (directionRef.current === 'horizontal') {
70
74
  changeActiveIndex(offset, 0);
71
75
  } else {
72
76
  changeActiveIndex(0, offset);
73
77
  }
74
78
  },
75
- [changeActiveIndex, lastDirectionRef],
79
+ [changeActiveIndex, directionRef],
80
+ );
81
+
82
+ const insertValue = useCallback(
83
+ (position: Point, value: string) => {
84
+ const characters = value ? extractCharacters(config, value).filter((character) => character !== BLANK) : [BLANK];
85
+ let board = new Board({ rows: rows.map((row) => row.map((cell) => cell.clone())) });
86
+ let { x, y } = position;
87
+
88
+ const scheduleMoveFocus = () => {
89
+ if (directionRef.current === 'horizontal') {
90
+ ++x;
91
+ } else {
92
+ ++y;
93
+ }
94
+ };
95
+
96
+ characters.forEach((character) => {
97
+ if (x >= config.boardWidth || y >= config.boardHeight) {
98
+ return;
99
+ }
100
+
101
+ const canCheckUp = y - 1 > 0;
102
+ const canCheckLeft = x > 0;
103
+ const canCheckRight = x + 1 < width;
104
+ const canCheckDown = y + 1 < height;
105
+
106
+ if (canCheckUp) {
107
+ const cellUp = board.rows[y - 1][x];
108
+ const twoCharacterCandidate = cellUp.tile.character + character;
109
+
110
+ if (!cellUp.tile.isBlank && config.twoCharacterTiles.includes(twoCharacterCandidate)) {
111
+ board = boardSlice.reducer(
112
+ board,
113
+ boardSlice.actions.changeCellValue({ x, y: y - 1, value: twoCharacterCandidate }),
114
+ );
115
+ return;
116
+ }
117
+ }
118
+
119
+ if (canCheckDown) {
120
+ const cellDown = board.rows[y + 1][x];
121
+ const twoCharacterCandidate = character + cellDown.tile.character;
122
+
123
+ if (!cellDown.tile.isBlank && config.twoCharacterTiles.includes(twoCharacterCandidate)) {
124
+ board = boardSlice.reducer(board, boardSlice.actions.changeCellValue({ x, y, value: character }));
125
+ board = boardSlice.reducer(board, boardSlice.actions.changeCellValue({ x, y: y + 1, value: EMPTY_CELL }));
126
+ scheduleMoveFocus();
127
+ return;
128
+ }
129
+ }
130
+
131
+ if (canCheckLeft) {
132
+ const cellLeft = board.rows[y][x - 1];
133
+ const twoCharacterCandidate = cellLeft.tile.character + character;
134
+
135
+ if (!cellLeft.tile.isBlank && config.twoCharacterTiles.includes(twoCharacterCandidate)) {
136
+ board = boardSlice.reducer(
137
+ board,
138
+ boardSlice.actions.changeCellValue({ x: x - 1, y, value: twoCharacterCandidate }),
139
+ );
140
+ return;
141
+ }
142
+ }
143
+
144
+ if (canCheckRight) {
145
+ const cellRight = board.rows[y][x + 1];
146
+ const twoCharacterCandidate = character + cellRight.tile.character;
147
+
148
+ if (!cellRight.tile.isBlank && config.twoCharacterTiles.includes(twoCharacterCandidate)) {
149
+ board = boardSlice.reducer(board, boardSlice.actions.changeCellValue({ x, y, value: character }));
150
+ board = boardSlice.reducer(board, boardSlice.actions.changeCellValue({ x: x + 1, y, value: EMPTY_CELL }));
151
+ scheduleMoveFocus();
152
+ return;
153
+ }
154
+ }
155
+
156
+ if (!canCheckDown || !canCheckRight) {
157
+ const cell = board.rows[y][x];
158
+ const twoCharacterCandidate = cell.tile.character + character;
159
+
160
+ if (!cell.tile.isBlank && config.twoCharacterTiles.includes(twoCharacterCandidate)) {
161
+ board = boardSlice.reducer(
162
+ board,
163
+ boardSlice.actions.changeCellValue({ x, y, value: twoCharacterCandidate }),
164
+ );
165
+ return;
166
+ }
167
+ }
168
+
169
+ board = boardSlice.reducer(board, boardSlice.actions.changeCellValue({ x, y, value: character }));
170
+ scheduleMoveFocus();
171
+ });
172
+
173
+ moveFocus(Math.abs(position.x - x) + Math.abs(position.y - y));
174
+ dispatch(boardSlice.actions.change(board));
175
+ },
176
+ [config, directionRef, dispatch, moveFocus, rows],
177
+ );
178
+
179
+ const onChange = useCallback(
180
+ (event: ChangeEvent<HTMLInputElement>) => {
181
+ const position = getInputRefPosition(event.target);
182
+
183
+ if (!position) {
184
+ return;
185
+ }
186
+
187
+ const value = extractInputValue(event.target);
188
+
189
+ if (!value) {
190
+ dispatch(boardSlice.actions.changeCellValue({ ...position, value: EMPTY_CELL }));
191
+ moveFocus(-1);
192
+ return;
193
+ }
194
+
195
+ if (value === EMPTY_CELL) {
196
+ const { x, y } = position;
197
+ const cell = rows[y][x];
198
+
199
+ if (cell.hasTile()) {
200
+ dispatch(boardSlice.actions.toggleCellIsBlank(position));
201
+ return;
202
+ }
203
+ }
204
+
205
+ insertValue(position, value);
206
+ },
207
+ [dispatch, insertValue, moveFocus, rows],
76
208
  );
77
209
 
210
+ const onDirectionToggle = useCallback(() => setLastDirection(toggleDirection), []);
211
+
212
+ const onFocus = useCallback((x: number, y: number) => {
213
+ activeIndexRef.current = { x, y };
214
+ }, []);
215
+
78
216
  const onKeyDown = useMemo(() => {
79
217
  return createKeyboardNavigation({
80
218
  onArrowDown: (event) => {
219
+ event.preventDefault();
220
+
81
221
  if (isCtrl(event)) {
82
222
  onDirectionToggle();
83
223
  } else {
@@ -85,6 +225,8 @@ const useGrid = (rows: Cell[][]): [State, Actions] => {
85
225
  }
86
226
  },
87
227
  onArrowLeft: (event) => {
228
+ event.preventDefault();
229
+
88
230
  if (isCtrl(event)) {
89
231
  onDirectionToggle();
90
232
  } else {
@@ -92,6 +234,8 @@ const useGrid = (rows: Cell[][]): [State, Actions] => {
92
234
  }
93
235
  },
94
236
  onArrowRight: (event) => {
237
+ event.preventDefault();
238
+
95
239
  if (isCtrl(event)) {
96
240
  onDirectionToggle();
97
241
  } else {
@@ -99,6 +243,8 @@ const useGrid = (rows: Cell[][]): [State, Actions] => {
99
243
  }
100
244
  },
101
245
  onArrowUp: (event) => {
246
+ event.preventDefault();
247
+
102
248
  if (isCtrl(event)) {
103
249
  onDirectionToggle();
104
250
  } else {
@@ -112,8 +258,9 @@ const useGrid = (rows: Cell[][]): [State, Actions] => {
112
258
  return;
113
259
  }
114
260
 
261
+ event.preventDefault();
115
262
  dispatch(boardSlice.actions.changeCellValue({ ...position, value: EMPTY_CELL }));
116
- onMoveFocus('backward');
263
+ moveFocus(-1);
117
264
  },
118
265
  onDelete: (event) => {
119
266
  const position = getInputRefPosition(event.target as HTMLInputElement);
@@ -122,8 +269,9 @@ const useGrid = (rows: Cell[][]): [State, Actions] => {
122
269
  return;
123
270
  }
124
271
 
272
+ event.preventDefault();
125
273
  dispatch(boardSlice.actions.changeCellValue({ ...position, value: EMPTY_CELL }));
126
- onMoveFocus('forward');
274
+ moveFocus(1);
127
275
  },
128
276
  onKeyDown: (event) => {
129
277
  const position = getInputRefPosition(event.target as HTMLInputElement);
@@ -138,6 +286,7 @@ const useGrid = (rows: Cell[][]): [State, Actions] => {
138
286
  const twoCharacterTile = config.getTwoCharacterTileByPrefix(character);
139
287
 
140
288
  if (isTogglingBlank) {
289
+ event.preventDefault();
141
290
  dispatch(boardSlice.actions.toggleCellIsBlank(position));
142
291
  return;
143
292
  }
@@ -145,75 +294,26 @@ const useGrid = (rows: Cell[][]): [State, Actions] => {
145
294
  if (isCtrl(event) && twoCharacterTile) {
146
295
  event.preventDefault();
147
296
  dispatch(boardSlice.actions.changeCellValue({ x, y, value: twoCharacterTile }));
148
- onMoveFocus('forward');
297
+ moveFocus(1);
149
298
  return;
150
299
  }
151
300
 
152
- if (!config.hasCharacter(character)) {
153
- return;
154
- }
155
-
156
- const canCheckUp = y - 1 > 0;
157
- const canCheckLeft = x > 0;
158
- const canCheckRight = x + 1 < width;
159
- const canCheckDown = y + 1 < height;
160
-
161
- if (canCheckUp) {
162
- const cellUp = rows[y - 1][x];
163
- const twoCharacterCandidate = cellUp.tile.character + character;
164
-
165
- if (config.twoCharacterTiles.includes(twoCharacterCandidate)) {
166
- dispatch(boardSlice.actions.changeCellValue({ ...position, y: y - 1, value: twoCharacterCandidate }));
167
- return;
168
- }
169
- }
170
-
171
- if (canCheckDown) {
172
- const cellDown = rows[y + 1][x];
173
- const twoCharacterCandidate = character + cellDown.tile.character;
174
-
175
- if (config.twoCharacterTiles.includes(twoCharacterCandidate)) {
176
- dispatch(boardSlice.actions.changeCellValue({ ...position, value: character }));
177
- dispatch(boardSlice.actions.changeCellValue({ ...position, y: y + 1, value: EMPTY_CELL }));
178
- onMoveFocus('forward');
179
- return;
180
- }
181
- }
182
-
183
- if (canCheckLeft) {
184
- const cellLeft = rows[y][x - 1];
185
- const twoCharacterCandidate = cellLeft.tile.character + character;
186
-
187
- if (config.twoCharacterTiles.includes(twoCharacterCandidate)) {
188
- dispatch(boardSlice.actions.changeCellValue({ ...position, x: x - 1, value: twoCharacterCandidate }));
189
- return;
190
- }
191
- }
192
-
193
- if (canCheckRight) {
194
- const cellRight = rows[y][x + 1];
195
- const twoCharacterCandidate = character + cellRight.tile.character;
301
+ const cell = rows[y][x];
302
+ const twoCharacterCandidate = cell.tile.character + character;
196
303
 
197
- if (config.twoCharacterTiles.includes(twoCharacterCandidate)) {
198
- dispatch(boardSlice.actions.changeCellValue({ ...position, value: character }));
199
- dispatch(boardSlice.actions.changeCellValue({ ...position, x: x + 1, value: EMPTY_CELL }));
200
- onMoveFocus('forward');
201
- return;
202
- }
304
+ if (config.twoCharacterTiles.includes(twoCharacterCandidate)) {
305
+ event.preventDefault();
306
+ dispatch(boardSlice.actions.changeCellValue({ ...position, value: twoCharacterCandidate }));
307
+ moveFocus(1);
308
+ return;
203
309
  }
204
310
 
205
- if (!canCheckDown || !canCheckRight) {
206
- const cell = rows[y][x];
207
- const twoCharacterCandidate = cell.tile.character + character;
208
-
209
- if (config.twoCharacterTiles.includes(twoCharacterCandidate)) {
210
- dispatch(boardSlice.actions.changeCellValue({ ...position, value: twoCharacterCandidate }));
211
- return;
212
- }
311
+ if (event.target instanceof HTMLInputElement && event.target.value === event.key) {
312
+ // change event did not fire because the same character was typed over the current one
313
+ // but we still want to move the caret
314
+ event.preventDefault();
315
+ moveFocus(1);
213
316
  }
214
-
215
- dispatch(boardSlice.actions.changeCellValue({ ...position, value: character }));
216
- onMoveFocus('forward');
217
317
  },
218
318
  onSpace: (event) => {
219
319
  const position = getInputRefPosition(event.target as HTMLInputElement);
@@ -222,14 +322,35 @@ const useGrid = (rows: Cell[][]): [State, Actions] => {
222
322
  return;
223
323
  }
224
324
 
325
+ event.preventDefault();
225
326
  dispatch(boardSlice.actions.toggleCellIsBlank(position));
226
327
  },
227
328
  });
228
- }, [changeActiveIndex, config, dispatch, lastDirectionRef, onDirectionToggle, rows]);
329
+ }, [changeActiveIndex, config, dispatch, onDirectionToggle, rows]);
330
+
331
+ const onPaste = useCallback<ClipboardEventHandler>(
332
+ (event) => {
333
+ if (!(event.target instanceof HTMLInputElement)) {
334
+ return;
335
+ }
336
+
337
+ const position = getInputRefPosition(event.target);
338
+
339
+ if (!position) {
340
+ return;
341
+ }
342
+
343
+ event.preventDefault();
344
+
345
+ const value = event.clipboardData.getData('text/plain').toLocaleLowerCase();
346
+ insertValue(position, value);
347
+ },
348
+ [insertValue],
349
+ );
229
350
 
230
351
  return [
231
- { lastDirection, refs },
232
- { onDirectionToggle, onFocus, onKeyDown },
352
+ { direction, refs },
353
+ { onChange, onDirectionToggle, onFocus, onKeyDown, onPaste },
233
354
  ];
234
355
  };
235
356
 
@@ -1,7 +1,7 @@
1
1
  import classNames from 'classnames';
2
2
  import { FunctionComponent } from 'react';
3
3
 
4
- import { selectDictionary, useTranslate, useTypedSelector } from 'state';
4
+ import { selectDictionary, selectDictionaryError, useTranslate, useTypedSelector } from 'state';
5
5
 
6
6
  import EmptyState from '../EmptyState';
7
7
  import Loading from '../Loading';
@@ -15,6 +15,7 @@ interface Props {
15
15
  const Dictionary: FunctionComponent<Props> = ({ className }) => {
16
16
  const translate = useTranslate();
17
17
  const { results, isLoading } = useTypedSelector(selectDictionary);
18
+ const error = useTypedSelector(selectDictionaryError);
18
19
  const isFirstAllowed = results.length > 0 ? results[0].isAllowed : undefined;
19
20
 
20
21
  return (
@@ -24,6 +25,12 @@ const Dictionary: FunctionComponent<Props> = ({ className }) => {
24
25
  [styles.isNotAllowed]: isFirstAllowed === false,
25
26
  })}
26
27
  >
28
+ {typeof error !== 'undefined' && (
29
+ <EmptyState className={styles.emptyState} type="error">
30
+ {error.message}
31
+ </EmptyState>
32
+ )}
33
+
27
34
  {results.map(({ definitions, isAllowed, word }) => (
28
35
  <div
29
36
  className={classNames(styles.result, {