@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.
- package/.next/BUILD_ID +1 -1
- package/.next/build-manifest.json +19 -31
- package/.next/cache/.tsbuildinfo +1 -1
- package/.next/cache/eslint/.cache_8dgz12 +1 -1
- package/.next/cache/next-server.js.nft.json +1 -1
- package/.next/cache/webpack/client-development/0.pack +0 -0
- package/.next/cache/webpack/client-development/1.pack +0 -0
- package/.next/cache/webpack/client-development/2.pack +0 -0
- package/.next/cache/webpack/client-development/3.pack +0 -0
- package/.next/cache/webpack/client-development/4.pack +0 -0
- package/.next/cache/webpack/client-development/index.pack +0 -0
- package/.next/cache/webpack/client-development/index.pack.old +0 -0
- package/.next/cache/webpack/client-production/0.pack +0 -0
- package/.next/cache/webpack/client-production/index.pack +0 -0
- package/.next/cache/webpack/edge-server-production/0.pack +0 -0
- package/.next/cache/webpack/edge-server-production/index.pack +0 -0
- package/.next/cache/webpack/server-development/0.pack +0 -0
- package/.next/cache/webpack/server-development/10.pack_ +0 -0
- package/.next/cache/webpack/server-development/5.pack_ +0 -0
- package/.next/cache/webpack/server-development/8.pack_ +0 -0
- package/.next/cache/webpack/server-development/index.pack +0 -0
- package/.next/cache/webpack/server-production/0.pack +0 -0
- package/.next/cache/webpack/server-production/index.pack +0 -0
- package/.next/images-manifest.json +1 -1
- package/.next/next-server.js.nft.json +1 -1
- package/.next/prerender-manifest.json +1 -1
- package/.next/required-server-files.json +1 -1
- package/.next/routes-manifest.json +1 -1
- package/.next/server/chunks/131.js +1399 -117
- package/.next/server/chunks/{515.js → 176.js} +850 -732
- package/.next/server/chunks/210.js +122 -0
- package/.next/server/chunks/{939.js → 290.js} +4 -3
- package/.next/server/chunks/44.js +33 -33
- package/.next/server/chunks/50.js +5 -9
- package/.next/server/chunks/{413.js → 579.js} +184 -117
- package/.next/server/chunks/664.js +537 -350
- package/.next/server/chunks/859.js +56 -23
- package/.next/server/chunks/865.js +1 -1
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/.next/server/pages/404.html +2 -2
- package/.next/server/pages/404.js.nft.json +1 -1
- package/.next/server/pages/500.html +2 -2
- package/.next/server/pages/_app.js +23 -5
- package/.next/server/pages/_app.js.nft.json +1 -1
- package/.next/server/pages/_document.js +2 -2
- package/.next/server/pages/_error.js +206 -25
- package/.next/server/pages/_error.js.nft.json +1 -1
- package/.next/server/pages/api/dictionary/[locale]/[word].js +11 -11
- package/.next/server/pages/api/dictionary/[locale]/[word].js.nft.json +1 -1
- package/.next/server/pages/api/dictionary/[locale].js +10 -10
- package/.next/server/pages/api/dictionary/[locale].js.nft.json +1 -1
- package/.next/server/pages/api/solve.js +34 -36
- package/.next/server/pages/api/solve.js.nft.json +1 -1
- package/.next/server/pages/api/verify.js +10 -10
- package/.next/server/pages/api/verify.js.nft.json +1 -1
- package/.next/server/pages/api/visit.js +3 -3
- package/.next/server/pages/api/visit.js.nft.json +1 -1
- package/.next/server/pages/index.html +2 -2
- package/.next/server/pages/index.js +44 -128
- package/.next/server/pages/index.js.nft.json +1 -1
- package/.next/server/pages/index.json +1 -1
- package/.next/server/pages-manifest.json +4 -4
- package/.next/static/FjrbXpI5fkt4lmko-vL_7/_buildManifest.js +1 -0
- package/.next/static/{vscqn7BEtAxJteWSwNnas → FjrbXpI5fkt4lmko-vL_7}/_ssgManifest.js +0 -0
- package/.next/static/chunks/361-d16f336a9752a55a.js +1 -0
- package/.next/static/chunks/724-eb48df4d1ba3df8b.js +5 -0
- package/.next/static/chunks/amp.js +720 -0
- package/.next/static/chunks/framework-2c79e2a64abdb08b.js +33 -0
- package/.next/static/chunks/main-f11614d8aa7ee555.js +1 -0
- package/.next/static/chunks/main.js +1076 -0
- package/.next/static/chunks/pages/404-24f9617eeb8d6dc1.js +1 -0
- package/.next/static/chunks/pages/_app-959e495f0f221247.js +24 -0
- package/.next/static/chunks/pages/_app.js +2121 -0
- package/.next/static/chunks/pages/_error-8353112a01355ec2.js +1 -0
- package/.next/static/chunks/pages/_error.js +28 -0
- package/.next/static/chunks/pages/index-1e30dafa41bddb80.js +1 -0
- package/.next/static/chunks/pages/index.js +5314 -0
- package/.next/static/chunks/react-refresh.js +62 -0
- package/.next/static/chunks/webpack-59c5c889f52620d6.js +1 -0
- package/.next/static/chunks/webpack.js +1237 -0
- package/.next/static/css/aafd07997120f1e4.css +1 -0
- package/.next/static/css/c8d26240c04079b9.css +1 -0
- package/.next/static/css/eb9d57f7103525ab.css +1 -0
- package/.next/static/development/_buildManifest.js +1 -0
- package/.next/static/development/_ssgManifest.js +1 -0
- package/.next/static/webpack/fb4b50d5e70ee127.webpack.hot-update.json +1 -0
- package/.next/static/webpack/pages/index.fb4b50d5e70ee127.hot-update.js +171 -0
- package/.next/static/webpack/webpack.fb4b50d5e70ee127.hot-update.js +18 -0
- package/.next/trace +52 -52
- package/package.json +21 -21
- package/public/og.png +0 -0
- package/src/components/Board/Board.module.scss +1 -2
- package/src/components/Board/BoardPure.tsx +2 -0
- package/src/components/Board/components/Cell/Cell.module.scss +65 -55
- package/src/components/Board/components/Cell/Cell.tsx +9 -0
- package/src/components/Board/components/Cell/CellPure.tsx +11 -2
- package/src/components/Board/hooks/useGrid.ts +6 -6
- package/src/components/NavButtons/NavButtons.tsx +11 -1
- package/src/components/Rack/Rack.tsx +16 -4
- package/src/components/Rack/RackTile.tsx +10 -1
- package/src/components/RemainingTiles/Character.module.scss +0 -1
- package/src/components/RemainingTiles/Character.tsx +1 -0
- package/src/components/Results/Results.tsx +2 -2
- package/src/components/Settings/components/AutoGroupTilesSetting/AutoGroupTilesSetting.tsx +2 -2
- package/src/components/Settings/components/LocaleSetting/options.ts +9 -1
- package/src/components/Solver/Solver.module.scss +85 -0
- package/src/components/Solver/Solver.tsx +75 -0
- package/src/components/Solver/index.ts +1 -0
- package/src/components/Tile/Tile.module.scss +51 -7
- package/src/components/Tile/Tile.tsx +3 -0
- package/src/components/Tile/TilePure.tsx +8 -1
- package/src/components/index.ts +1 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useIsTouchDevice.ts +7 -0
- package/src/i18n/fr.json +1 -1
- package/src/icons/ExclamationSquareFill.svg +4 -0
- package/src/icons/index.ts +1 -0
- package/src/lib/getRemainingTilesGroups.ts +24 -26
- package/src/pages/index.module.scss +1 -75
- package/src/pages/index.tsx +11 -73
- package/src/parameters/index.ts +2 -0
- package/src/sdk/findWordDefinitions.ts +1 -1
- package/src/service-worker/average.ts +9 -0
- package/src/service-worker/routeSolveRequests.ts +46 -7
- package/src/state/sagas.ts +19 -5
- package/src/state/selectors.ts +30 -2
- package/src/state/slices/boardInitialState.ts +27 -0
- package/src/styles/mixins.scss +6 -1
- package/src/styles/variables.scss +6 -3
- package/.next/InjectManifest.js.nft.json +0 -1
- package/.next/static/chunks/368-d423e70be6c0c473.js +0 -1
- package/.next/static/chunks/546-447e243fc9de2c59.js +0 -1
- package/.next/static/chunks/framework-4556c45dd113b893.js +0 -1
- package/.next/static/chunks/main-a75cf611e061d8f8.js +0 -1
- package/.next/static/chunks/pages/404-932294135c3206dd.js +0 -1
- package/.next/static/chunks/pages/_app-3f5508a5f544d9eb.js +0 -1
- package/.next/static/chunks/pages/_error-a4ba2246ff8fb532.js +0 -1
- package/.next/static/chunks/pages/index-8af7a9d7a2cd98a7.js +0 -1
- package/.next/static/chunks/webpack-5752944655d749a0.js +0 -1
- package/.next/static/css/6b1833fd19d3a74a.css +0 -1
- package/.next/static/css/a6154e4ca046ca13.css +0 -1
- package/.next/static/css/bad53af6f8616677.css +0 -1
- 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.
|
|
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.
|
|
34
|
-
"@scrabble-solver/configs": "^2.10.
|
|
35
|
-
"@scrabble-solver/constants": "^2.10.
|
|
36
|
-
"@scrabble-solver/dictionaries": "^2.10.
|
|
37
|
-
"@scrabble-solver/logger": "^2.10.
|
|
38
|
-
"@scrabble-solver/solver": "^2.10.
|
|
39
|
-
"@scrabble-solver/types": "^2.10.
|
|
40
|
-
"@scrabble-solver/word-definitions": "^2.10.
|
|
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": "^
|
|
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.
|
|
49
|
+
"react-redux": "^8.0.5",
|
|
50
50
|
"react-use": "^17.4.0",
|
|
51
|
-
"react-window": "^1.8.
|
|
52
|
-
"redux": "^4.2.
|
|
53
|
-
"redux-saga": "^1.2.
|
|
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.
|
|
62
|
+
"@svgr/webpack": "^6.5.1",
|
|
63
63
|
"@types/classnames": "^2.3.0",
|
|
64
|
-
"@types/react": "^18.0.
|
|
65
|
-
"@types/react-dom": "^18.0.
|
|
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.
|
|
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": "^
|
|
72
|
+
"@types/uuid": "^9.0.0",
|
|
73
73
|
"env-cmd": "^10.1.0",
|
|
74
|
-
"sass": "^1.
|
|
74
|
+
"sass": "^1.57.1",
|
|
75
75
|
"workbox-webpack-plugin": "^6.5.4"
|
|
76
76
|
},
|
|
77
|
-
"gitHead": "
|
|
77
|
+
"gitHead": "4feaabfad2a10079c28e04f446c98dbf0383d520"
|
|
78
78
|
}
|
package/public/og.png
CHANGED
|
Binary file
|
|
@@ -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
|
-
&.
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
&.bottom {
|
|
17
|
+
border-bottom: none;
|
|
18
|
+
}
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
25
|
+
&:before {
|
|
26
|
+
background-color: var(--color--violet--light);
|
|
27
|
+
}
|
|
36
28
|
}
|
|
37
29
|
|
|
38
30
|
&.bonusCharacter1 {
|
|
39
|
-
|
|
31
|
+
&:before {
|
|
32
|
+
background-color: var(--color--yellow--light);
|
|
33
|
+
}
|
|
40
34
|
}
|
|
41
35
|
|
|
42
36
|
&.bonusCharacter2 {
|
|
43
|
-
|
|
37
|
+
&:before {
|
|
38
|
+
background-color: var(--color--green--light);
|
|
39
|
+
}
|
|
44
40
|
}
|
|
45
41
|
|
|
46
42
|
&.bonusCharacter3 {
|
|
47
|
-
|
|
43
|
+
&:before {
|
|
44
|
+
background-color: var(--color--blue--light);
|
|
45
|
+
}
|
|
48
46
|
}
|
|
49
47
|
|
|
50
48
|
&.bonusCharacter5 {
|
|
51
|
-
|
|
49
|
+
&:before {
|
|
50
|
+
background-color: var(--color--red--light);
|
|
51
|
+
}
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
&.bonusCharacterMultiplier2 {
|
|
55
|
-
|
|
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
|
-
|
|
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
|
-
&.
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
105
|
-
|
|
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
|
-
|
|
108
|
-
|
|
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),
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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}
|
|
@@ -61,7 +61,7 @@ const Results: FunctionComponent<Props> = ({ height, width }) => {
|
|
|
61
61
|
</EmptyState>
|
|
62
62
|
)}
|
|
63
63
|
|
|
64
|
-
{typeof
|
|
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
|
|
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
|
|
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={
|
|
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;
|