@scrabble-solver/scrabble-solver 2.10.1 → 2.10.3

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 (112) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +26 -26
  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/7.pack_ +0 -0
  7. package/.next/cache/webpack/client-production/0.pack +0 -0
  8. package/.next/cache/webpack/client-production/index.pack +0 -0
  9. package/.next/cache/webpack/edge-server-production/0.pack +0 -0
  10. package/.next/cache/webpack/edge-server-production/index.pack +0 -0
  11. package/.next/cache/webpack/server-production/0.pack +0 -0
  12. package/.next/cache/webpack/server-production/index.pack +0 -0
  13. package/.next/images-manifest.json +1 -1
  14. package/.next/next-server.js.nft.json +1 -1
  15. package/.next/prerender-manifest.json +1 -1
  16. package/.next/required-server-files.json +1 -1
  17. package/.next/routes-manifest.json +1 -1
  18. package/.next/server/chunks/131.js +1399 -117
  19. package/.next/server/chunks/{515.js → 176.js} +771 -679
  20. package/.next/server/chunks/210.js +122 -0
  21. package/.next/server/chunks/{939.js → 290.js} +3 -3
  22. package/.next/server/chunks/44.js +33 -33
  23. package/.next/server/chunks/50.js +5 -9
  24. package/.next/server/chunks/{413.js → 579.js} +136 -87
  25. package/.next/server/chunks/664.js +537 -350
  26. package/.next/server/chunks/859.js +56 -23
  27. package/.next/server/chunks/865.js +1 -1
  28. package/.next/server/middleware-build-manifest.js +1 -1
  29. package/.next/server/pages/404.html +2 -2
  30. package/.next/server/pages/404.js.nft.json +1 -1
  31. package/.next/server/pages/500.html +2 -2
  32. package/.next/server/pages/_app.js +23 -5
  33. package/.next/server/pages/_app.js.nft.json +1 -1
  34. package/.next/server/pages/_document.js +2 -2
  35. package/.next/server/pages/_error.js +206 -25
  36. package/.next/server/pages/_error.js.nft.json +1 -1
  37. package/.next/server/pages/api/dictionary/[locale]/[word].js +12 -11
  38. package/.next/server/pages/api/dictionary/[locale]/[word].js.nft.json +1 -1
  39. package/.next/server/pages/api/dictionary/[locale].js +10 -10
  40. package/.next/server/pages/api/dictionary/[locale].js.nft.json +1 -1
  41. package/.next/server/pages/api/solve.js +14 -13
  42. package/.next/server/pages/api/solve.js.nft.json +1 -1
  43. package/.next/server/pages/api/verify.js +10 -10
  44. package/.next/server/pages/api/verify.js.nft.json +1 -1
  45. package/.next/server/pages/api/visit.js +3 -3
  46. package/.next/server/pages/api/visit.js.nft.json +1 -1
  47. package/.next/server/pages/index.html +2 -2
  48. package/.next/server/pages/index.js +44 -128
  49. package/.next/server/pages/index.js.nft.json +1 -1
  50. package/.next/server/pages/index.json +1 -1
  51. package/.next/server/pages-manifest.json +4 -4
  52. package/.next/static/FJkPF91uL-OCAJKTTpVSP/_buildManifest.js +1 -0
  53. package/.next/static/{hf94cues-LcXZRCpAzQ6w → FJkPF91uL-OCAJKTTpVSP}/_ssgManifest.js +0 -0
  54. package/.next/static/chunks/361-d16f336a9752a55a.js +1 -0
  55. package/.next/static/chunks/724-eb48df4d1ba3df8b.js +5 -0
  56. package/.next/static/chunks/framework-2c79e2a64abdb08b.js +33 -0
  57. package/.next/static/chunks/main-f11614d8aa7ee555.js +1 -0
  58. package/.next/static/chunks/pages/404-24f9617eeb8d6dc1.js +1 -0
  59. package/.next/static/chunks/pages/_app-959e495f0f221247.js +24 -0
  60. package/.next/static/chunks/pages/_error-8353112a01355ec2.js +1 -0
  61. package/.next/static/chunks/pages/index-1e30dafa41bddb80.js +1 -0
  62. package/.next/static/chunks/webpack-59c5c889f52620d6.js +1 -0
  63. package/.next/static/css/aafd07997120f1e4.css +1 -0
  64. package/.next/static/css/cb5b206454513f3c.css +1 -0
  65. package/.next/static/css/eb9d57f7103525ab.css +1 -0
  66. package/.next/trace +52 -52
  67. package/package.json +21 -21
  68. package/public/og.png +0 -0
  69. package/src/components/Board/Board.module.scss +1 -2
  70. package/src/components/Board/BoardPure.tsx +2 -0
  71. package/src/components/Board/components/Cell/Cell.module.scss +77 -54
  72. package/src/components/Board/components/Cell/Cell.tsx +9 -0
  73. package/src/components/Board/components/Cell/CellPure.tsx +11 -2
  74. package/src/components/NavButtons/NavButtons.tsx +11 -1
  75. package/src/components/Rack/RackTile.tsx +10 -1
  76. package/src/components/RemainingTiles/Character.module.scss +0 -1
  77. package/src/components/RemainingTiles/Character.tsx +1 -0
  78. package/src/components/Solver/Solver.module.scss +85 -0
  79. package/src/components/Solver/Solver.tsx +75 -0
  80. package/src/components/Solver/index.ts +1 -0
  81. package/src/components/Tile/Tile.module.scss +51 -7
  82. package/src/components/Tile/Tile.tsx +3 -0
  83. package/src/components/Tile/TilePure.tsx +8 -1
  84. package/src/components/index.ts +1 -0
  85. package/src/hooks/index.ts +1 -0
  86. package/src/hooks/useIsTouchDevice.ts +7 -0
  87. package/src/icons/ExclamationSquareFill.svg +4 -0
  88. package/src/icons/index.ts +1 -0
  89. package/src/pages/api/dictionary/[locale]/[word].ts +2 -1
  90. package/src/pages/index.module.scss +1 -75
  91. package/src/pages/index.tsx +11 -73
  92. package/src/parameters/index.ts +2 -0
  93. package/src/service-worker/average.ts +9 -0
  94. package/src/service-worker/routeSolveRequests.ts +46 -7
  95. package/src/state/selectors.ts +20 -0
  96. package/src/state/slices/boardInitialState.ts +27 -0
  97. package/src/styles/mixins.scss +6 -1
  98. package/src/styles/variables.scss +6 -3
  99. package/.next/InjectManifest.js.nft.json +0 -1
  100. package/.next/static/chunks/368-8b386c3106556f62.js +0 -1
  101. package/.next/static/chunks/546-447e243fc9de2c59.js +0 -1
  102. package/.next/static/chunks/framework-4556c45dd113b893.js +0 -1
  103. package/.next/static/chunks/main-a75cf611e061d8f8.js +0 -1
  104. package/.next/static/chunks/pages/404-932294135c3206dd.js +0 -1
  105. package/.next/static/chunks/pages/_app-8f0df20f771045ed.js +0 -1
  106. package/.next/static/chunks/pages/_error-a4ba2246ff8fb532.js +0 -1
  107. package/.next/static/chunks/pages/index-8af7a9d7a2cd98a7.js +0 -1
  108. package/.next/static/chunks/webpack-5752944655d749a0.js +0 -1
  109. package/.next/static/css/6b1833fd19d3a74a.css +0 -1
  110. package/.next/static/css/a6154e4ca046ca13.css +0 -1
  111. package/.next/static/css/bad53af6f8616677.css +0 -1
  112. package/.next/static/hf94cues-LcXZRCpAzQ6w/_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.1",
3
+ "version": "2.10.3",
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.1",
35
- "@scrabble-solver/constants": "^2.10.1",
36
- "@scrabble-solver/dictionaries": "^2.10.1",
37
- "@scrabble-solver/logger": "^2.10.1",
38
- "@scrabble-solver/solver": "^2.10.1",
39
- "@scrabble-solver/types": "^2.10.1",
40
- "@scrabble-solver/word-definitions": "^2.10.1",
33
+ "@reduxjs/toolkit": "^1.9.2",
34
+ "@scrabble-solver/configs": "^2.10.3",
35
+ "@scrabble-solver/constants": "^2.10.3",
36
+ "@scrabble-solver/dictionaries": "^2.10.3",
37
+ "@scrabble-solver/logger": "^2.10.3",
38
+ "@scrabble-solver/solver": "^2.10.3",
39
+ "@scrabble-solver/types": "^2.10.3",
40
+ "@scrabble-solver/word-definitions": "^2.10.3",
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": "506586e8bb9e71f9f784dabffae2229c587c5e5a"
77
+ "gitHead": "50a961d5f3fe68c6b646a3cd1a3fb28f9e9b5252"
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,115 @@ $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-bottom: var(--border--width) dotted var(--border--color--light);
13
12
  transition: var(--transition);
14
13
  background-clip: padding-box;
15
14
 
16
- &.bonusWord2,
17
- &.bonusWord3 {
18
- position: relative;
15
+ [dir='ltr'] & {
16
+ border-right: var(--border--width) dotted var(--border--color--light);
17
+ }
19
18
 
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;
19
+ [dir='rtl'] & {
20
+ border-left: var(--border--width) dotted var(--border--color--light);
21
+ }
22
+
23
+ &.bottom {
24
+ border-bottom: none;
25
+ }
26
+
27
+ &.right {
28
+ [dir='ltr'] & {
29
+ border-right: none;
30
+ }
31
+
32
+ [dir='rtl'] & {
33
+ border-left: none;
31
34
  }
32
35
  }
33
36
 
34
37
  &.bonusStart {
35
- background-color: var(--color--violet--light);
38
+ &:before {
39
+ background-color: var(--color--violet--light);
40
+ }
36
41
  }
37
42
 
38
43
  &.bonusCharacter1 {
39
- background-color: var(--color--yellow--light);
44
+ &:before {
45
+ background-color: var(--color--yellow--light);
46
+ }
40
47
  }
41
48
 
42
49
  &.bonusCharacter2 {
43
- background-color: var(--color--green--light);
50
+ &:before {
51
+ background-color: var(--color--green--light);
52
+ }
44
53
  }
45
54
 
46
55
  &.bonusCharacter3 {
47
- background-color: var(--color--blue--light);
56
+ &:before {
57
+ background-color: var(--color--blue--light);
58
+ }
48
59
  }
49
60
 
50
61
  &.bonusCharacter5 {
51
- background-color: var(--color--red--light);
62
+ &:before {
63
+ background-color: var(--color--red--light);
64
+ }
52
65
  }
53
66
 
54
67
  &.bonusCharacterMultiplier2 {
55
- background-color: var(--color--light-blue--light);
68
+ &:before {
69
+ background-color: var(--color--light-blue--light);
70
+ }
56
71
  }
57
72
 
58
73
  &.bonusCharacterMultiplier3 {
59
- background-color: var(--color--dark-blue--light);
60
- }
61
-
62
- &.bonusWord2,
63
- &.bonusWord3 {
64
- background-color: var(--color--inactive);
65
-
66
74
  &:before {
67
- font-size: 75%;
68
- color: white;
69
-
70
- [lang='fa-IR'] & {
71
- font-family: 'Open Sans', sans-serif;
72
- }
75
+ background-color: var(--color--dark-blue--light);
73
76
  }
74
77
  }
75
78
 
76
79
  &.bonusWord2 {
77
80
  &:before {
78
81
  content: 'x2';
82
+ background-color: var(--color--orange);
79
83
  }
80
84
  }
81
85
 
82
86
  &.bonusWord3 {
83
87
  &:before {
84
88
  content: 'x3';
89
+ background-color: var(--color--pink);
85
90
  }
86
91
  }
87
92
 
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);
93
+ &.candidate {
94
+ &:before {
95
+ content: ' ';
96
+ background-color: var(--color--primary);
97
+ }
102
98
  }
103
99
 
104
- &.candidate {
105
- background-color: var(--color--primary);
100
+ &:before {
101
+ $size: 80%;
102
+
103
+ display: flex;
104
+ justify-content: center;
105
+ align-items: center;
106
+ position: absolute;
107
+ top: (100% - $size) / 2;
108
+ left: (100% - $size) / 2;
109
+ width: $size;
110
+ height: $size;
111
+ border-radius: var(--border--radius);
112
+ pointer-events: none;
113
+ font-size: 60%;
114
+ font-weight: bold;
115
+ color: white;
116
+ content: ' ';
106
117
 
107
- &:before {
108
- display: none;
118
+ [lang='fa-IR'] & {
119
+ font-family: 'Open Sans', sans-serif;
109
120
  }
110
121
  }
111
122
 
@@ -207,6 +218,8 @@ $icon-size: 16px;
207
218
  }
208
219
 
209
220
  .toggleDirection {
221
+ transition: var(--transition);
222
+
210
223
  &.right {
211
224
  transform: rotate(-90deg);
212
225
 
@@ -249,9 +262,19 @@ $icon-size: 16px;
249
262
 
250
263
  .flag,
251
264
  .star {
265
+ color: white;
266
+ }
267
+
268
+ .star {
269
+ $size: 40%;
270
+
271
+ width: $size;
272
+ height: $size;
273
+ }
274
+
275
+ .flag {
252
276
  $size: 50%;
253
277
 
254
278
  width: $size;
255
279
  height: $size;
256
- color: white;
257
280
  }
@@ -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}
@@ -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>
@@ -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}
@@ -0,0 +1,85 @@
1
+ @import 'styles/mixins';
2
+
3
+ .solver {
4
+ display: flex;
5
+ flex-direction: column;
6
+ height: 100%;
7
+ }
8
+
9
+ .contentWrapper {
10
+ $board-size: 15;
11
+ $tile-size-max: 60px;
12
+ $tile-border-size: 1px;
13
+
14
+ flex: 1;
15
+ padding: 0 var(--spacing--xl);
16
+ max-height: $board-size * ($tile-size-max + $tile-border-size) + $tile-border-size;
17
+ }
18
+
19
+ .content {
20
+ display: flex;
21
+ align-items: center;
22
+ justify-content: center;
23
+ height: 100%;
24
+ gap: var(--spacing--xl);
25
+
26
+ @include tablet {
27
+ gap: var(--spacing--l);
28
+ }
29
+ }
30
+
31
+ .boardContainer {
32
+ display: flex;
33
+ position: relative;
34
+ }
35
+
36
+ .sidebar {
37
+ display: flex;
38
+ flex-direction: column;
39
+ flex: 0 0 var(--sidebar--width);
40
+ gap: var(--spacing--xl);
41
+
42
+ @include tablet {
43
+ gap: var(--spacing--l);
44
+ }
45
+ }
46
+
47
+ .dictionary {
48
+ display: flex;
49
+ flex-direction: column;
50
+ }
51
+
52
+ .dictionaryOutput {
53
+ flex: 1;
54
+ border-bottom: var(--border);
55
+ }
56
+
57
+ .dictionaryInput {
58
+ flex: 0 0 auto;
59
+ }
60
+
61
+ .resultsContainer {
62
+ flex: 1;
63
+ position: relative;
64
+ }
65
+
66
+ .rackContainer {
67
+ position: relative;
68
+ z-index: 1;
69
+ flex: 0 0 auto;
70
+ display: flex;
71
+ justify-content: center;
72
+ margin: var(--spacing--xl) var(--spacing--l);
73
+
74
+ @include tablet {
75
+ margin: var(--spacing--l);
76
+ }
77
+ }
78
+
79
+ .rack {
80
+ border: var(--border);
81
+ }
82
+
83
+ .submitInput {
84
+ display: none;
85
+ }
@@ -0,0 +1,75 @@
1
+ import classNames from 'classnames';
2
+ import { FormEvent, FunctionComponent } from 'react';
3
+ import { useDispatch } from 'react-redux';
4
+ import { useMeasure } from 'react-use';
5
+
6
+ import { useIsTablet } from 'hooks';
7
+ import { getCellSize } from 'lib';
8
+ import { COMPONENTS_SPACING, COMPONENTS_SPACING_MOBILE, DICTIONARY_HEIGHT } from 'parameters';
9
+ import { selectConfig, solveSlice, useTypedSelector } from 'state';
10
+
11
+ import Board from '../Board';
12
+ import Dictionary from '../Dictionary';
13
+ import DictionaryInput from '../DictionaryInput';
14
+ import Rack from '../Rack';
15
+ import Results from '../Results';
16
+ import Well from '../Well';
17
+
18
+ import styles from './Solver.module.scss';
19
+
20
+ interface Props {
21
+ className?: string;
22
+ }
23
+
24
+ const Solver: FunctionComponent<Props> = ({ className }) => {
25
+ const dispatch = useDispatch();
26
+ const isTablet = useIsTablet();
27
+ const [boardRef, { height: boardHeight }] = useMeasure<HTMLDivElement>();
28
+ const [contentRef, { height: contentHeight, width: contentWidth }] = useMeasure<HTMLDivElement>();
29
+ const [resultsContainerRef, { height: resultsContainerHeight, width: resultsContainerWidth }] =
30
+ useMeasure<HTMLDivElement>();
31
+ const config = useTypedSelector(selectConfig);
32
+ const cellSize = getCellSize(config, contentWidth - resultsContainerWidth, contentHeight);
33
+ const componentsSpacing = isTablet ? COMPONENTS_SPACING_MOBILE : COMPONENTS_SPACING;
34
+ const resultsHeight = boardHeight - DICTIONARY_HEIGHT - componentsSpacing;
35
+
36
+ const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
37
+ event.preventDefault();
38
+ dispatch(solveSlice.actions.submit());
39
+ };
40
+
41
+ return (
42
+ <div className={classNames(styles.solver, className)}>
43
+ <div className={styles.contentWrapper}>
44
+ <div className={styles.content} ref={contentRef}>
45
+ <form className={styles.boardContainer} onSubmit={handleSubmit}>
46
+ {contentWidth > 0 && <Board cellSize={cellSize} innerRef={boardRef} />}
47
+ <input className={styles.submitInput} tabIndex={-1} type="submit" />
48
+ </form>
49
+
50
+ <div className={styles.sidebar} style={{ height: boardHeight + 1 }}>
51
+ <Well className={styles.resultsContainer} ref={resultsContainerRef}>
52
+ {resultsContainerWidth > 0 && resultsContainerHeight > 0 && (
53
+ <Results height={resultsHeight} width={resultsContainerWidth} />
54
+ )}
55
+ </Well>
56
+
57
+ <Well>
58
+ <div className={styles.dictionary} style={{ height: DICTIONARY_HEIGHT }}>
59
+ <Dictionary className={styles.dictionaryOutput} />
60
+ <DictionaryInput className={styles.dictionaryInput} />
61
+ </div>
62
+ </Well>
63
+ </div>
64
+ </div>
65
+ </div>
66
+
67
+ <form className={styles.rackContainer} onSubmit={handleSubmit}>
68
+ <Rack className={styles.rack} />
69
+ <input className={styles.submitInput} tabIndex={-1} type="submit" />
70
+ </form>
71
+ </div>
72
+ );
73
+ };
74
+
75
+ export default Solver;
@@ -0,0 +1 @@
1
+ export { default } from './Solver';