@scrabble-solver/scrabble-solver 2.10.0 → 2.10.2

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 (144) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +19 -31
  3. package/.next/cache/.tsbuildinfo +1 -1
  4. package/.next/cache/eslint/.cache_8dgz12 +1 -1
  5. package/.next/cache/next-server.js.nft.json +1 -1
  6. package/.next/cache/webpack/client-development/0.pack +0 -0
  7. package/.next/cache/webpack/client-development/1.pack +0 -0
  8. package/.next/cache/webpack/client-development/2.pack +0 -0
  9. package/.next/cache/webpack/client-development/3.pack +0 -0
  10. package/.next/cache/webpack/client-development/4.pack +0 -0
  11. package/.next/cache/webpack/client-development/index.pack +0 -0
  12. package/.next/cache/webpack/client-development/index.pack.old +0 -0
  13. package/.next/cache/webpack/client-production/0.pack +0 -0
  14. package/.next/cache/webpack/client-production/index.pack +0 -0
  15. package/.next/cache/webpack/edge-server-production/0.pack +0 -0
  16. package/.next/cache/webpack/edge-server-production/index.pack +0 -0
  17. package/.next/cache/webpack/server-development/0.pack +0 -0
  18. package/.next/cache/webpack/server-development/10.pack_ +0 -0
  19. package/.next/cache/webpack/server-development/5.pack_ +0 -0
  20. package/.next/cache/webpack/server-development/8.pack_ +0 -0
  21. package/.next/cache/webpack/server-development/index.pack +0 -0
  22. package/.next/cache/webpack/server-production/0.pack +0 -0
  23. package/.next/cache/webpack/server-production/index.pack +0 -0
  24. package/.next/images-manifest.json +1 -1
  25. package/.next/next-server.js.nft.json +1 -1
  26. package/.next/prerender-manifest.json +1 -1
  27. package/.next/required-server-files.json +1 -1
  28. package/.next/routes-manifest.json +1 -1
  29. package/.next/server/chunks/131.js +1399 -117
  30. package/.next/server/chunks/{515.js → 176.js} +850 -732
  31. package/.next/server/chunks/210.js +122 -0
  32. package/.next/server/chunks/{939.js → 290.js} +4 -3
  33. package/.next/server/chunks/44.js +33 -33
  34. package/.next/server/chunks/50.js +5 -9
  35. package/.next/server/chunks/{413.js → 579.js} +184 -117
  36. package/.next/server/chunks/664.js +537 -350
  37. package/.next/server/chunks/859.js +56 -23
  38. package/.next/server/chunks/865.js +1 -1
  39. package/.next/server/middleware-build-manifest.js +1 -1
  40. package/.next/server/middleware-react-loadable-manifest.js +1 -1
  41. package/.next/server/pages/404.html +2 -2
  42. package/.next/server/pages/404.js.nft.json +1 -1
  43. package/.next/server/pages/500.html +2 -2
  44. package/.next/server/pages/_app.js +23 -5
  45. package/.next/server/pages/_app.js.nft.json +1 -1
  46. package/.next/server/pages/_document.js +2 -2
  47. package/.next/server/pages/_error.js +206 -25
  48. package/.next/server/pages/_error.js.nft.json +1 -1
  49. package/.next/server/pages/api/dictionary/[locale]/[word].js +11 -11
  50. package/.next/server/pages/api/dictionary/[locale]/[word].js.nft.json +1 -1
  51. package/.next/server/pages/api/dictionary/[locale].js +10 -10
  52. package/.next/server/pages/api/dictionary/[locale].js.nft.json +1 -1
  53. package/.next/server/pages/api/solve.js +34 -36
  54. package/.next/server/pages/api/solve.js.nft.json +1 -1
  55. package/.next/server/pages/api/verify.js +10 -10
  56. package/.next/server/pages/api/verify.js.nft.json +1 -1
  57. package/.next/server/pages/api/visit.js +3 -3
  58. package/.next/server/pages/api/visit.js.nft.json +1 -1
  59. package/.next/server/pages/index.html +2 -2
  60. package/.next/server/pages/index.js +44 -128
  61. package/.next/server/pages/index.js.nft.json +1 -1
  62. package/.next/server/pages/index.json +1 -1
  63. package/.next/server/pages-manifest.json +4 -4
  64. package/.next/static/FjrbXpI5fkt4lmko-vL_7/_buildManifest.js +1 -0
  65. package/.next/static/{vscqn7BEtAxJteWSwNnas → FjrbXpI5fkt4lmko-vL_7}/_ssgManifest.js +0 -0
  66. package/.next/static/chunks/361-d16f336a9752a55a.js +1 -0
  67. package/.next/static/chunks/724-eb48df4d1ba3df8b.js +5 -0
  68. package/.next/static/chunks/amp.js +720 -0
  69. package/.next/static/chunks/framework-2c79e2a64abdb08b.js +33 -0
  70. package/.next/static/chunks/main-f11614d8aa7ee555.js +1 -0
  71. package/.next/static/chunks/main.js +1076 -0
  72. package/.next/static/chunks/pages/404-24f9617eeb8d6dc1.js +1 -0
  73. package/.next/static/chunks/pages/_app-959e495f0f221247.js +24 -0
  74. package/.next/static/chunks/pages/_app.js +2121 -0
  75. package/.next/static/chunks/pages/_error-8353112a01355ec2.js +1 -0
  76. package/.next/static/chunks/pages/_error.js +28 -0
  77. package/.next/static/chunks/pages/index-1e30dafa41bddb80.js +1 -0
  78. package/.next/static/chunks/pages/index.js +5314 -0
  79. package/.next/static/chunks/react-refresh.js +62 -0
  80. package/.next/static/chunks/webpack-59c5c889f52620d6.js +1 -0
  81. package/.next/static/chunks/webpack.js +1237 -0
  82. package/.next/static/css/aafd07997120f1e4.css +1 -0
  83. package/.next/static/css/c8d26240c04079b9.css +1 -0
  84. package/.next/static/css/eb9d57f7103525ab.css +1 -0
  85. package/.next/static/development/_buildManifest.js +1 -0
  86. package/.next/static/development/_ssgManifest.js +1 -0
  87. package/.next/static/webpack/fb4b50d5e70ee127.webpack.hot-update.json +1 -0
  88. package/.next/static/webpack/pages/index.fb4b50d5e70ee127.hot-update.js +171 -0
  89. package/.next/static/webpack/webpack.fb4b50d5e70ee127.hot-update.js +18 -0
  90. package/.next/trace +52 -52
  91. package/package.json +21 -21
  92. package/public/og.png +0 -0
  93. package/src/components/Board/Board.module.scss +1 -2
  94. package/src/components/Board/BoardPure.tsx +2 -0
  95. package/src/components/Board/components/Cell/Cell.module.scss +65 -55
  96. package/src/components/Board/components/Cell/Cell.tsx +9 -0
  97. package/src/components/Board/components/Cell/CellPure.tsx +11 -2
  98. package/src/components/Board/hooks/useGrid.ts +6 -6
  99. package/src/components/NavButtons/NavButtons.tsx +11 -1
  100. package/src/components/Rack/Rack.tsx +16 -4
  101. package/src/components/Rack/RackTile.tsx +10 -1
  102. package/src/components/RemainingTiles/Character.module.scss +0 -1
  103. package/src/components/RemainingTiles/Character.tsx +1 -0
  104. package/src/components/Results/Results.tsx +2 -2
  105. package/src/components/Settings/components/AutoGroupTilesSetting/AutoGroupTilesSetting.tsx +2 -2
  106. package/src/components/Settings/components/LocaleSetting/options.ts +9 -1
  107. package/src/components/Solver/Solver.module.scss +85 -0
  108. package/src/components/Solver/Solver.tsx +75 -0
  109. package/src/components/Solver/index.ts +1 -0
  110. package/src/components/Tile/Tile.module.scss +51 -7
  111. package/src/components/Tile/Tile.tsx +3 -0
  112. package/src/components/Tile/TilePure.tsx +8 -1
  113. package/src/components/index.ts +1 -0
  114. package/src/hooks/index.ts +1 -0
  115. package/src/hooks/useIsTouchDevice.ts +7 -0
  116. package/src/i18n/fr.json +1 -1
  117. package/src/icons/ExclamationSquareFill.svg +4 -0
  118. package/src/icons/index.ts +1 -0
  119. package/src/lib/getRemainingTilesGroups.ts +24 -26
  120. package/src/pages/index.module.scss +1 -75
  121. package/src/pages/index.tsx +11 -73
  122. package/src/parameters/index.ts +2 -0
  123. package/src/sdk/findWordDefinitions.ts +1 -1
  124. package/src/service-worker/average.ts +9 -0
  125. package/src/service-worker/routeSolveRequests.ts +46 -7
  126. package/src/state/sagas.ts +19 -5
  127. package/src/state/selectors.ts +30 -2
  128. package/src/state/slices/boardInitialState.ts +27 -0
  129. package/src/styles/mixins.scss +6 -1
  130. package/src/styles/variables.scss +6 -3
  131. package/.next/InjectManifest.js.nft.json +0 -1
  132. package/.next/static/chunks/368-d423e70be6c0c473.js +0 -1
  133. package/.next/static/chunks/546-447e243fc9de2c59.js +0 -1
  134. package/.next/static/chunks/framework-4556c45dd113b893.js +0 -1
  135. package/.next/static/chunks/main-a75cf611e061d8f8.js +0 -1
  136. package/.next/static/chunks/pages/404-932294135c3206dd.js +0 -1
  137. package/.next/static/chunks/pages/_app-3f5508a5f544d9eb.js +0 -1
  138. package/.next/static/chunks/pages/_error-a4ba2246ff8fb532.js +0 -1
  139. package/.next/static/chunks/pages/index-8af7a9d7a2cd98a7.js +0 -1
  140. package/.next/static/chunks/webpack-5752944655d749a0.js +0 -1
  141. package/.next/static/css/6b1833fd19d3a74a.css +0 -1
  142. package/.next/static/css/a6154e4ca046ca13.css +0 -1
  143. package/.next/static/css/bad53af6f8616677.css +0 -1
  144. package/.next/static/vscqn7BEtAxJteWSwNnas/_buildManifest.js +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scrabble-solver/scrabble-solver",
3
- "version": "2.10.0",
3
+ "version": "2.10.2",
4
4
  "description": "Scrabble Solver 2 - App",
5
5
  "engines": {
6
6
  "node": ">=16"
@@ -30,27 +30,27 @@
30
30
  "dependencies": {
31
31
  "@kamilmielnik/trie": "^2.0.1",
32
32
  "@popperjs/core": "^2.11.6",
33
- "@reduxjs/toolkit": "^1.8.6",
34
- "@scrabble-solver/configs": "^2.10.0",
35
- "@scrabble-solver/constants": "^2.10.0",
36
- "@scrabble-solver/dictionaries": "^2.10.0",
37
- "@scrabble-solver/logger": "^2.10.0",
38
- "@scrabble-solver/solver": "^2.10.0",
39
- "@scrabble-solver/types": "^2.10.0",
40
- "@scrabble-solver/word-definitions": "^2.10.0",
33
+ "@reduxjs/toolkit": "^1.9.2",
34
+ "@scrabble-solver/configs": "^2.10.2",
35
+ "@scrabble-solver/constants": "^2.10.2",
36
+ "@scrabble-solver/dictionaries": "^2.10.2",
37
+ "@scrabble-solver/logger": "^2.10.2",
38
+ "@scrabble-solver/solver": "^2.10.2",
39
+ "@scrabble-solver/types": "^2.10.2",
40
+ "@scrabble-solver/word-definitions": "^2.10.2",
41
41
  "classnames": "^2.3.2",
42
- "next": "^12.3.1",
42
+ "next": "^13.1.6",
43
43
  "normalize.css": "^8.0.1",
44
44
  "react": "^18.2.0",
45
45
  "react-dom": "^18.2.0",
46
46
  "react-modal": "^3.16.1",
47
47
  "react-popper": "^2.3.0",
48
48
  "react-portal": "^4.2.2",
49
- "react-redux": "^8.0.4",
49
+ "react-redux": "^8.0.5",
50
50
  "react-use": "^17.4.0",
51
- "react-window": "^1.8.7",
52
- "redux": "^4.2.0",
53
- "redux-saga": "^1.2.1",
51
+ "react-window": "^1.8.8",
52
+ "redux": "^4.2.1",
53
+ "redux-saga": "^1.2.2",
54
54
  "store2": "^2.14.2",
55
55
  "uuid": "^9.0.0",
56
56
  "workbox-expiration": "^6.5.4",
@@ -59,20 +59,20 @@
59
59
  "workbox-window": "^6.5.4"
60
60
  },
61
61
  "devDependencies": {
62
- "@svgr/webpack": "^6.5.0",
62
+ "@svgr/webpack": "^6.5.1",
63
63
  "@types/classnames": "^2.3.0",
64
- "@types/react": "^18.0.21",
65
- "@types/react-dom": "^18.0.6",
64
+ "@types/react": "^18.0.27",
65
+ "@types/react-dom": "^18.0.10",
66
66
  "@types/react-modal": "^3.13.1",
67
67
  "@types/react-portal": "^4.0.4",
68
- "@types/react-redux": "^7.1.24",
68
+ "@types/react-redux": "^7.1.25",
69
69
  "@types/react-window": "^1.8.5",
70
70
  "@types/redux": "^3.6.31",
71
71
  "@types/redux-saga": "^0.10.5",
72
- "@types/uuid": "^8.3.4",
72
+ "@types/uuid": "^9.0.0",
73
73
  "env-cmd": "^10.1.0",
74
- "sass": "^1.55.0",
74
+ "sass": "^1.57.1",
75
75
  "workbox-webpack-plugin": "^6.5.4"
76
76
  },
77
- "gitHead": "7fd0f6bf536fca88c18ce02e320da71b0c6d6f61"
77
+ "gitHead": "4feaabfad2a10079c28e04f446c98dbf0383d520"
78
78
  }
package/public/og.png CHANGED
Binary file
@@ -1,8 +1,7 @@
1
1
  .board {
2
2
  display: table;
3
3
  box-shadow: var(--box-shadow);
4
- border-top: var(--border);
5
- border-left: var(--border);
4
+ border: var(--border);
6
5
  }
7
6
 
8
7
  .row {
@@ -53,7 +53,9 @@ const BoardPure: FunctionComponent<Props> = ({
53
53
  cell={cell}
54
54
  direction={direction}
55
55
  inputRef={refs[y][x]}
56
+ isBottom={y === rows.length - 1}
56
57
  isCenter={center.x === x && center.y === y}
58
+ isRight={x === cells.length - 1}
57
59
  key={x}
58
60
  size={cellSize}
59
61
  onChange={onChange}
@@ -8,104 +8,102 @@ $icon-size: 16px;
8
8
  position: relative;
9
9
  display: table-cell;
10
10
  background-color: white;
11
- border-right: var(--border);
12
- border-bottom: var(--border);
11
+ border-right: var(--border--width) dotted var(--border--color--light);
12
+ border-bottom: var(--border--width) dotted var(--border--color--light);
13
13
  transition: var(--transition);
14
14
  background-clip: padding-box;
15
15
 
16
- &.bonusWord2,
17
- &.bonusWord3 {
18
- position: relative;
16
+ &.bottom {
17
+ border-bottom: none;
18
+ }
19
19
 
20
- &:before {
21
- display: flex;
22
- justify-content: center;
23
- align-items: center;
24
- position: absolute;
25
- top: 0;
26
- bottom: 0;
27
- left: 0;
28
- right: 0;
29
- font-weight: bold;
30
- pointer-events: none;
31
- }
20
+ &.right {
21
+ border-right: none;
32
22
  }
33
23
 
34
24
  &.bonusStart {
35
- background-color: var(--color--violet--light);
25
+ &:before {
26
+ background-color: var(--color--violet--light);
27
+ }
36
28
  }
37
29
 
38
30
  &.bonusCharacter1 {
39
- background-color: var(--color--yellow--light);
31
+ &:before {
32
+ background-color: var(--color--yellow--light);
33
+ }
40
34
  }
41
35
 
42
36
  &.bonusCharacter2 {
43
- background-color: var(--color--green--light);
37
+ &:before {
38
+ background-color: var(--color--green--light);
39
+ }
44
40
  }
45
41
 
46
42
  &.bonusCharacter3 {
47
- background-color: var(--color--blue--light);
43
+ &:before {
44
+ background-color: var(--color--blue--light);
45
+ }
48
46
  }
49
47
 
50
48
  &.bonusCharacter5 {
51
- background-color: var(--color--red--light);
49
+ &:before {
50
+ background-color: var(--color--red--light);
51
+ }
52
52
  }
53
53
 
54
54
  &.bonusCharacterMultiplier2 {
55
- background-color: var(--color--light-blue--light);
55
+ &:before {
56
+ background-color: var(--color--light-blue--light);
57
+ }
56
58
  }
57
59
 
58
60
  &.bonusCharacterMultiplier3 {
59
- background-color: var(--color--dark-blue--light);
60
- }
61
-
62
- &.bonusWord2,
63
- &.bonusWord3 {
64
- background-color: var(--color--inactive);
65
-
66
61
  &:before {
67
- font-size: 75%;
68
- color: white;
69
-
70
- [lang='fa-IR'] & {
71
- font-family: 'Open Sans', sans-serif;
72
- }
62
+ background-color: var(--color--dark-blue--light);
73
63
  }
74
64
  }
75
65
 
76
66
  &.bonusWord2 {
77
67
  &:before {
78
68
  content: 'x2';
69
+ background-color: var(--color--orange);
79
70
  }
80
71
  }
81
72
 
82
73
  &.bonusWord3 {
83
74
  &:before {
84
75
  content: 'x3';
76
+ background-color: var(--color--pink);
85
77
  }
86
78
  }
87
79
 
88
- &.characterPoints1 {
89
- background-color: var(--color--yellow);
90
- }
91
-
92
- &.characterPoints2 {
93
- background-color: var(--color--green);
94
- }
95
-
96
- &.characterPoints3 {
97
- background-color: var(--color--blue);
98
- }
99
-
100
- &.characterPoints5 {
101
- background-color: var(--color--red);
80
+ &.candidate {
81
+ &:before {
82
+ content: ' ';
83
+ background-color: var(--color--primary);
84
+ }
102
85
  }
103
86
 
104
- &.candidate {
105
- background-color: var(--color--primary);
87
+ &:before {
88
+ $size: 80%;
89
+
90
+ display: flex;
91
+ justify-content: center;
92
+ align-items: center;
93
+ position: absolute;
94
+ top: (100% - $size) / 2;
95
+ left: (100% - $size) / 2;
96
+ width: $size;
97
+ height: $size;
98
+ border-radius: var(--border--radius);
99
+ pointer-events: none;
100
+ font-size: 60%;
101
+ font-weight: bold;
102
+ color: white;
103
+ content: ' ';
106
104
 
107
- &:before {
108
- display: none;
105
+ [lang='fa-IR'] & {
106
+ font-family: 'Open Sans', sans-serif;
109
107
  }
110
108
  }
111
109
 
@@ -207,6 +205,8 @@ $icon-size: 16px;
207
205
  }
208
206
 
209
207
  .toggleDirection {
208
+ transition: var(--transition);
209
+
210
210
  &.right {
211
211
  transform: rotate(-90deg);
212
212
 
@@ -249,9 +249,19 @@ $icon-size: 16px;
249
249
 
250
250
  .flag,
251
251
  .star {
252
+ color: white;
253
+ }
254
+
255
+ .star {
256
+ $size: 40%;
257
+
258
+ width: $size;
259
+ height: $size;
260
+ }
261
+
262
+ .flag {
252
263
  $size: 50%;
253
264
 
254
265
  width: $size;
255
266
  height: $size;
256
- color: white;
257
267
  }
@@ -9,6 +9,7 @@ import {
9
9
  cellFilterSlice,
10
10
  selectCellBonus,
11
11
  selectCellIsFiltered,
12
+ selectCellIsValid,
12
13
  selectTilePoints,
13
14
  useTranslate,
14
15
  useTypedSelector,
@@ -21,7 +22,9 @@ interface Props {
21
22
  className?: string;
22
23
  direction: 'horizontal' | 'vertical';
23
24
  inputRef: RefObject<HTMLInputElement>;
25
+ isBottom: boolean;
24
26
  isCenter: boolean;
27
+ isRight: boolean;
25
28
  size: number;
26
29
  onChange: ChangeEventHandler<HTMLInputElement>;
27
30
  onDirectionToggle: () => void;
@@ -33,7 +36,9 @@ const Cell: FunctionComponent<Props> = ({
33
36
  className,
34
37
  direction,
35
38
  inputRef,
39
+ isBottom,
36
40
  isCenter,
41
+ isRight,
37
42
  size,
38
43
  onChange,
39
44
  onDirectionToggle,
@@ -45,6 +50,7 @@ const Cell: FunctionComponent<Props> = ({
45
50
  const bonus = useTypedSelector((state) => selectCellBonus(state, cell));
46
51
  const points = useTypedSelector((state) => selectTilePoints(state, cell.tile));
47
52
  const isFiltered = useTypedSelector((state) => selectCellIsFiltered(state, cell));
53
+ const isValid = useTypedSelector((state) => selectCellIsValid(state, cell));
48
54
  const { tileFontSize } = getTileSizes(size);
49
55
  const isEmpty = tile.character === EMPTY_CELL;
50
56
  const style = useMemo(() => ({ fontSize: tileFontSize }), [tileFontSize]);
@@ -82,9 +88,12 @@ const Cell: FunctionComponent<Props> = ({
82
88
  className={className}
83
89
  direction={direction}
84
90
  inputRef={inputRef}
91
+ isBottom={isBottom}
85
92
  isCenter={isCenter}
93
+ isRight={isRight}
86
94
  isEmpty={isEmpty}
87
95
  isFiltered={isFiltered}
96
+ isValid={isValid}
88
97
  points={points}
89
98
  size={size}
90
99
  style={style}
@@ -25,9 +25,12 @@ interface Props {
25
25
  className?: string;
26
26
  direction: 'horizontal' | 'vertical';
27
27
  inputRef: RefObject<HTMLInputElement>;
28
+ isBottom: boolean;
28
29
  isCenter: boolean;
30
+ isRight: boolean;
29
31
  isEmpty: boolean;
30
32
  isFiltered: boolean;
33
+ isValid: boolean;
31
34
  points?: number;
32
35
  size: number;
33
36
  style?: CSSProperties;
@@ -46,9 +49,12 @@ const CellPure: FunctionComponent<Props> = ({
46
49
  className,
47
50
  direction,
48
51
  inputRef,
52
+ isBottom,
49
53
  isCenter,
54
+ isRight,
50
55
  isEmpty,
51
56
  isFiltered,
57
+ isValid,
52
58
  points,
53
59
  size,
54
60
  style,
@@ -61,12 +67,14 @@ const CellPure: FunctionComponent<Props> = ({
61
67
  onToggleFilterCellClick,
62
68
  }) => (
63
69
  <div
64
- className={classNames(styles.cell, getBonusClassname(cell, bonus, isCenter), className, {
70
+ className={classNames(styles.cell, className, getBonusClassname(cell, bonus, isCenter), {
71
+ [styles.bottom]: isBottom,
65
72
  [styles.candidate]: cell.isCandidate(),
73
+ [styles.right]: isRight,
66
74
  })}
67
75
  style={style}
68
76
  >
69
- {isCenter && (
77
+ {isCenter && isEmpty && (
70
78
  <div className={classNames(styles.iconContainer)}>
71
79
  <Star className={styles.star} />
72
80
  </div>
@@ -84,6 +92,7 @@ const CellPure: FunctionComponent<Props> = ({
84
92
  highlighted={cell.isCandidate()}
85
93
  inputRef={inputRef}
86
94
  isBlank={tile.isBlank}
95
+ isValid={isValid}
87
96
  points={points}
88
97
  raised={!isEmpty}
89
98
  size={size}
@@ -17,8 +17,9 @@ import { useDispatch } from 'react-redux';
17
17
  import { useLatest } from 'react-use';
18
18
  import { AnyAction } from 'redux';
19
19
 
20
+ import { LOCALE_FEATURES } from 'i18n';
20
21
  import { createGridOf, createKeyboardNavigation, extractCharacters, extractInputValue, isCtrl } from 'lib';
21
- import { boardSlice, selectConfig, useTypedSelector } from 'state';
22
+ import { boardSlice, selectConfig, selectLocale, useTypedSelector } from 'state';
22
23
  import { Direction } from 'types';
23
24
 
24
25
  import { getPositionInGrid } from '../lib';
@@ -44,6 +45,7 @@ const useGrid = (rows: Cell[][]): [State, Actions] => {
44
45
  const width = rows[0].length;
45
46
  const dispatch = useDispatch();
46
47
  const config = useTypedSelector(selectConfig);
48
+ const locale = useTypedSelector(selectLocale);
47
49
  const refs = useMemo(
48
50
  () => createGridOf<RefObject<HTMLInputElement>>(width, height, () => createRef()),
49
51
  [width, height],
@@ -235,8 +237,7 @@ const useGrid = (rows: Cell[][]): [State, Actions] => {
235
237
  if (isCtrl(event)) {
236
238
  onDirectionToggle();
237
239
  } else {
238
- const isRtl = document.body.parentElement?.dir === 'rtl';
239
- changeActiveIndex(isRtl ? 1 : -1, 0);
240
+ changeActiveIndex(LOCALE_FEATURES[locale].direction === 'ltr' ? -1 : 1, 0);
240
241
  }
241
242
  },
242
243
  onArrowRight: (event) => {
@@ -245,8 +246,7 @@ const useGrid = (rows: Cell[][]): [State, Actions] => {
245
246
  if (isCtrl(event)) {
246
247
  onDirectionToggle();
247
248
  } else {
248
- const isRtl = document.body.parentElement?.dir === 'rtl';
249
- changeActiveIndex(isRtl ? -1 : 1, 0);
249
+ changeActiveIndex(LOCALE_FEATURES[locale].direction === 'ltr' ? 1 : -1, 0);
250
250
  }
251
251
  },
252
252
  onArrowUp: (event) => {
@@ -333,7 +333,7 @@ const useGrid = (rows: Cell[][]): [State, Actions] => {
333
333
  dispatch(boardSlice.actions.toggleCellIsBlank(position));
334
334
  },
335
335
  });
336
- }, [changeActiveIndex, config, dispatch, onDirectionToggle, rows]);
336
+ }, [changeActiveIndex, config, dispatch, locale, onDirectionToggle, rows]);
337
337
 
338
338
  const onPaste = useCallback<ClipboardEventHandler>(
339
339
  (event) => {
@@ -1,6 +1,7 @@
1
1
  import classNames from 'classnames';
2
2
  import { FunctionComponent } from 'react';
3
3
 
4
+ import { useIsTouchDevice } from 'hooks';
4
5
  import { BookHalf, Cog, Eraser, Github, Keyboard, Sack } from 'icons';
5
6
  import { GITHUB_PROJECT_URL } from 'parameters';
6
7
  import { selectHasInvalidWords, selectHasOverusedTiles, useTranslate, useTypedSelector } from 'state';
@@ -27,6 +28,7 @@ const NavButtons: FunctionComponent<Props> = ({
27
28
  const translate = useTranslate();
28
29
  const hasOverusedTiles = useTypedSelector(selectHasOverusedTiles);
29
30
  const hasInvalidWords = useTypedSelector(selectHasInvalidWords);
31
+ const isTouchDevice = useIsTouchDevice();
30
32
 
31
33
  return (
32
34
  <div className={styles.navButtons}>
@@ -72,7 +74,15 @@ const NavButtons: FunctionComponent<Props> = ({
72
74
  <div className={styles.separator} />
73
75
 
74
76
  <div className={styles.group}>
75
- <SquareButton className={styles.button} Icon={Keyboard} tooltip={translate('keyMap')} onClick={onShowKeyMap} />
77
+ {!isTouchDevice && (
78
+ <SquareButton
79
+ className={styles.button}
80
+ Icon={Keyboard}
81
+ tooltip={translate('keyMap')}
82
+ onClick={onShowKeyMap}
83
+ />
84
+ )}
85
+
76
86
  <SquareButton className={styles.button} Icon={Cog} tooltip={translate('settings')} onClick={onShowSettings} />
77
87
  </div>
78
88
  </div>
@@ -2,6 +2,7 @@ import classNames from 'classnames';
2
2
  import { ChangeEvent, ClipboardEvent, createRef, FunctionComponent, useCallback, useMemo, useRef } from 'react';
3
3
  import { useDispatch } from 'react-redux';
4
4
 
5
+ import { LOCALE_FEATURES } from 'i18n';
5
6
  import {
6
7
  createArray,
7
8
  createKeyboardNavigation,
@@ -10,7 +11,7 @@ import {
10
11
  isCtrl,
11
12
  zipCharactersAndTiles,
12
13
  } from 'lib';
13
- import { rackSlice, selectConfig, selectRack, selectResultCandidateTiles, useTypedSelector } from 'state';
14
+ import { rackSlice, selectConfig, selectLocale, selectRack, selectResultCandidateTiles, useTypedSelector } from 'state';
14
15
 
15
16
  import styles from './Rack.module.scss';
16
17
  import RackTile from './RackTile';
@@ -22,12 +23,14 @@ interface Props {
22
23
  const Rack: FunctionComponent<Props> = ({ className }) => {
23
24
  const dispatch = useDispatch();
24
25
  const config = useTypedSelector(selectConfig);
26
+ const locale = useTypedSelector(selectLocale);
25
27
  const rack = useTypedSelector(selectRack);
26
28
  const resultCandidateTiles = useTypedSelector(selectResultCandidateTiles);
27
29
  const tiles = useMemo(() => zipCharactersAndTiles(rack, resultCandidateTiles), [rack, resultCandidateTiles]);
28
30
  const tilesCount = tiles.length;
29
31
  const tilesRefs = useMemo(() => createArray(tilesCount).map(() => createRef<HTMLInputElement>()), [tilesCount]);
30
32
  const activeIndexRef = useRef<number>();
33
+ const { direction } = LOCALE_FEATURES[locale];
31
34
 
32
35
  const changeActiveIndex = useCallback(
33
36
  (offset: number) => {
@@ -73,18 +76,27 @@ const Rack: FunctionComponent<Props> = ({ className }) => {
73
76
  return createKeyboardNavigation({
74
77
  onArrowLeft: (event) => {
75
78
  event.preventDefault();
76
- const direction = document.body.parentElement?.dir || 'ltr';
77
79
  changeActiveIndex(direction === 'ltr' ? -1 : 1);
78
80
  },
79
81
  onArrowRight: (event) => {
80
82
  event.preventDefault();
81
- const direction = document.body.parentElement?.dir || 'ltr';
82
83
  changeActiveIndex(direction === 'ltr' ? 1 : -1);
83
84
  },
84
85
  onBackspace: (event) => {
85
86
  event.preventDefault();
86
87
  changeActiveIndex(-1);
87
88
  },
89
+ onDelete: (event) => {
90
+ const index = activeIndexRef.current;
91
+
92
+ if (typeof index === 'undefined') {
93
+ return;
94
+ }
95
+
96
+ event.preventDefault();
97
+ dispatch(rackSlice.actions.changeCharacters({ characters: [null], index }));
98
+ changeActiveIndex(1);
99
+ },
88
100
  onKeyDown: (event) => {
89
101
  if (isCtrl(event) && config.isTwoCharacterTilePrefix(event.key)) {
90
102
  changeActiveIndex(1);
@@ -97,7 +109,7 @@ const Rack: FunctionComponent<Props> = ({ className }) => {
97
109
  }
98
110
  },
99
111
  });
100
- }, [changeActiveIndex, config]);
112
+ }, [changeActiveIndex, config, direction]);
101
113
 
102
114
  return (
103
115
  <div className={classNames(styles.rack, className)} onPaste={handlePaste}>
@@ -14,7 +14,14 @@ import { useDispatch } from 'react-redux';
14
14
 
15
15
  import { createKeyboardNavigation, extractCharacters, extractInputValue, isCtrl } from 'lib';
16
16
  import { TILE_SIZE } from 'parameters';
17
- import { rackSlice, selectCharacterPoints, selectConfig, useTranslate, useTypedSelector } from 'state';
17
+ import {
18
+ rackSlice,
19
+ selectCharacterIsValid,
20
+ selectCharacterPoints,
21
+ selectConfig,
22
+ useTranslate,
23
+ useTypedSelector,
24
+ } from 'state';
18
25
 
19
26
  import Tile from '../Tile';
20
27
 
@@ -43,6 +50,7 @@ const RackTile: FunctionComponent<Props> = ({
43
50
  const translate = useTranslate();
44
51
  const config = useTypedSelector(selectConfig);
45
52
  const points = useTypedSelector((state) => selectCharacterPoints(state, character));
53
+ const isValid = useTypedSelector((state) => selectCharacterIsValid(state, character));
46
54
 
47
55
  const handleFocus = useCallback(() => {
48
56
  activeIndexRef.current = index;
@@ -88,6 +96,7 @@ const RackTile: FunctionComponent<Props> = ({
88
96
  highlighted={tile !== null}
89
97
  inputRef={inputRef}
90
98
  isBlank={character === BLANK}
99
+ isValid={isValid}
91
100
  key={index}
92
101
  placeholder={translate('rack.placeholder')[index]}
93
102
  points={points}
@@ -9,7 +9,6 @@
9
9
  }
10
10
 
11
11
  &.overused {
12
- color: red;
13
12
  opacity: 1;
14
13
 
15
14
  .tile {
@@ -37,6 +37,7 @@ const Character: FunctionComponent<Props> = ({ tile }) => {
37
37
  className={styles.tile}
38
38
  disabled
39
39
  isBlank={character === BLANK}
40
+ isValid={remainingCount >= 0}
40
41
  points={points}
41
42
  raised
42
43
  size={REMAINING_TILES_TILE_SIZE}
@@ -61,7 +61,7 @@ const Results: FunctionComponent<Props> = ({ height, width }) => {
61
61
  </EmptyState>
62
62
  )}
63
63
 
64
- {typeof results === 'undefined' && typeof error === 'undefined' && (
64
+ {typeof error === 'undefined' && typeof results === 'undefined' && (
65
65
  <EmptyState className={styles.emptyState} type="info">
66
66
  {translate('results.empty-state.uninitialized')}
67
67
 
@@ -69,7 +69,7 @@ const Results: FunctionComponent<Props> = ({ height, width }) => {
69
69
  </EmptyState>
70
70
  )}
71
71
 
72
- {typeof results !== 'undefined' && typeof allResults !== 'undefined' && typeof error === 'undefined' && (
72
+ {typeof error === 'undefined' && typeof results !== 'undefined' && typeof allResults !== 'undefined' && (
73
73
  <>
74
74
  {isOutdated && (
75
75
  <EmptyState className={styles.emptyState} type="info">
@@ -17,7 +17,7 @@ interface Props {
17
17
  const AutoGroupTilesSetting: FunctionComponent<Props> = ({ className, disabled }) => {
18
18
  const dispatch = useDispatch();
19
19
  const translate = useTranslate();
20
- const configId = useTypedSelector(selectAutoGroupTiles);
20
+ const value = useTypedSelector(selectAutoGroupTiles);
21
21
 
22
22
  const options = [
23
23
  {
@@ -43,7 +43,7 @@ const AutoGroupTilesSetting: FunctionComponent<Props> = ({ className, disabled }
43
43
  <div className={className}>
44
44
  {options.map((option) => (
45
45
  <Radio
46
- checked={configId === parseValue(option.value)}
46
+ checked={value === parseValue(option.value)}
47
47
  className={styles.option}
48
48
  disabled={disabled}
49
49
  id="autoGroupTiles"
@@ -9,6 +9,7 @@ interface Option {
9
9
  className: string;
10
10
  Icon: FunctionComponent<SVGAttributes<SVGElement>>;
11
11
  label: string;
12
+ name: string;
12
13
  value: Locale;
13
14
  }
14
15
 
@@ -17,44 +18,51 @@ const options: Option[] = [
17
18
  className: styles.gb,
18
19
  Icon: FlagGb,
19
20
  label: 'English (GB)',
21
+ name: 'English (GB)',
20
22
  value: Locale.EN_GB,
21
23
  },
22
24
  {
23
25
  className: styles.us,
24
26
  Icon: FlagUs,
25
27
  label: 'English (US)',
28
+ name: 'English (US)',
26
29
  value: Locale.EN_US,
27
30
  },
28
31
  {
29
32
  className: styles.fa,
30
33
  Icon: FlagFa,
31
34
  label: 'فارسی',
35
+ name: 'Persian',
32
36
  value: Locale.FA_IR,
33
37
  },
34
38
  {
35
39
  className: styles.fr,
36
40
  Icon: FlagFr,
37
41
  label: 'Français',
42
+ name: 'French',
38
43
  value: Locale.FR_FR,
39
44
  },
40
45
  {
41
46
  className: styles.de,
42
47
  Icon: FlagDe,
43
48
  label: 'Deutsch',
49
+ name: 'German',
44
50
  value: Locale.DE_DE,
45
51
  },
46
52
  {
47
53
  className: styles.pl,
48
54
  Icon: FlagPl,
49
55
  label: 'Polski',
56
+ name: 'Polish',
50
57
  value: Locale.PL_PL,
51
58
  },
52
59
  {
53
60
  className: styles.es,
54
61
  Icon: FlagEs,
55
62
  label: 'Español',
63
+ name: 'Spanish',
56
64
  value: Locale.ES_ES,
57
65
  },
58
- ];
66
+ ].sort((a, b) => a.name.localeCompare(b.name));
59
67
 
60
68
  export default options;