@scrabble-solver/scrabble-solver 2.8.7 → 2.8.8
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 +9 -9
- 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-production/0.pack +0 -0
- package/.next/cache/webpack/client-production/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/next-server.js.nft.json +1 -1
- package/.next/prerender-manifest.json +1 -1
- package/.next/routes-manifest.json +1 -1
- package/.next/server/chunks/413.js +89 -22
- package/.next/server/chunks/515.js +105 -21
- package/.next/server/middleware-build-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.nft.json +1 -1
- package/.next/server/pages/_document.js.nft.json +1 -1
- package/.next/server/pages/_error.js.nft.json +1 -1
- package/.next/server/pages/index.html +2 -2
- package/.next/server/pages/index.js.nft.json +1 -1
- package/.next/server/pages/index.json +1 -1
- package/.next/static/chunks/615-d258f6c528c18622.js +1 -0
- package/.next/static/chunks/pages/{404-30c06e61d256c5b2.js → 404-8eb3ba4f0ba17e08.js} +1 -1
- package/.next/static/chunks/pages/_app-4a663fd3d5ca4524.js +1 -0
- package/.next/static/chunks/pages/{index-fcb69802550afb81.js → index-1a9826d740cc8830.js} +1 -1
- package/.next/static/css/180c6c26317ac90f.css +1 -0
- package/.next/static/z3J3qmq1nazbDv_ENIkCo/_buildManifest.js +1 -0
- package/.next/static/{z_0_lqfmiI_ISokr6NNRq → z3J3qmq1nazbDv_ENIkCo}/_ssgManifest.js +0 -0
- package/.next/trace +41 -40
- package/package.json +9 -9
- package/src/components/Board/components/Cell/Cell.module.scss +34 -9
- package/src/components/Board/components/Cell/Cell.tsx +23 -4
- package/src/components/Board/components/Cell/CellPure.tsx +29 -1
- package/src/components/Board/hooks/useGrid.ts +1 -0
- package/src/i18n/de.json +1 -0
- package/src/i18n/en.json +1 -0
- package/src/i18n/es.json +1 -0
- package/src/i18n/fr.json +1 -0
- package/src/i18n/pl.json +1 -0
- package/src/icons/Flag.svg +4 -0
- package/src/icons/Star.svg +4 -0
- package/src/icons/index.ts +2 -0
- package/src/state/rootReducer.ts +10 -1
- package/src/state/sagas.ts +21 -1
- package/src/state/selectors.ts +41 -11
- package/src/state/slices/cellFilterInitialState.ts +7 -0
- package/src/state/slices/cellFilterSlice.ts +24 -0
- package/src/state/slices/index.ts +2 -0
- package/src/types/index.ts +1 -0
- package/.next/static/chunks/56-cf37c430261bbea5.js +0 -1
- package/.next/static/chunks/pages/_app-0b12a65bea70a0df.js +0 -1
- package/.next/static/css/1f39b55d50f5b30b.css +0 -1
- package/.next/static/z_0_lqfmiI_ISokr6NNRq/_buildManifest.js +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scrabble-solver/scrabble-solver",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.8",
|
|
4
4
|
"description": "Scrabble Solver 2 - App",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=16"
|
|
@@ -30,13 +30,13 @@
|
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@popperjs/core": "^2.11.6",
|
|
32
32
|
"@reduxjs/toolkit": "^1.8.5",
|
|
33
|
-
"@scrabble-solver/configs": "^2.8.
|
|
34
|
-
"@scrabble-solver/constants": "^2.8.
|
|
35
|
-
"@scrabble-solver/dictionaries": "^2.8.
|
|
36
|
-
"@scrabble-solver/logger": "^2.8.
|
|
37
|
-
"@scrabble-solver/solver": "^2.8.
|
|
38
|
-
"@scrabble-solver/types": "^2.8.
|
|
39
|
-
"@scrabble-solver/word-definitions": "^2.8.
|
|
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",
|
|
40
40
|
"classnames": "^2.3.2",
|
|
41
41
|
"next": "^12.3.1",
|
|
42
42
|
"normalize.css": "^8.0.1",
|
|
@@ -68,5 +68,5 @@
|
|
|
68
68
|
"env-cmd": "^10.1.0",
|
|
69
69
|
"sass": "^1.55.0"
|
|
70
70
|
},
|
|
71
|
-
"gitHead": "
|
|
71
|
+
"gitHead": "e6ced6e98204e49ba69750242643bda52a453635"
|
|
72
72
|
}
|
|
@@ -13,7 +13,6 @@ $icon-size: 16px;
|
|
|
13
13
|
transition: var(--transition);
|
|
14
14
|
background-clip: padding-box;
|
|
15
15
|
|
|
16
|
-
&.bonusStart,
|
|
17
16
|
&.bonusWord2,
|
|
18
17
|
&.bonusWord3 {
|
|
19
18
|
position: relative;
|
|
@@ -33,12 +32,7 @@ $icon-size: 16px;
|
|
|
33
32
|
}
|
|
34
33
|
|
|
35
34
|
&.bonusStart {
|
|
36
|
-
background-color: var(--color--
|
|
37
|
-
|
|
38
|
-
&:before {
|
|
39
|
-
content: '★';
|
|
40
|
-
color: var(--color--foreground--secondary);
|
|
41
|
-
}
|
|
35
|
+
background-color: var(--color--violet--light);
|
|
42
36
|
}
|
|
43
37
|
|
|
44
38
|
&.bonusCharacter1 {
|
|
@@ -168,6 +162,7 @@ $icon-size: 16px;
|
|
|
168
162
|
}
|
|
169
163
|
|
|
170
164
|
&,
|
|
165
|
+
.filterCell,
|
|
171
166
|
.toggleDirection {
|
|
172
167
|
width: $icon-size;
|
|
173
168
|
height: $icon-size;
|
|
@@ -180,8 +175,6 @@ $icon-size: 16px;
|
|
|
180
175
|
}
|
|
181
176
|
|
|
182
177
|
.toggleDirection {
|
|
183
|
-
transition: var(--transition);
|
|
184
|
-
|
|
185
178
|
&.right {
|
|
186
179
|
transform: rotate(-90deg);
|
|
187
180
|
}
|
|
@@ -194,3 +187,35 @@ $icon-size: 16px;
|
|
|
194
187
|
font-weight: bold;
|
|
195
188
|
}
|
|
196
189
|
}
|
|
190
|
+
|
|
191
|
+
.filterCell {
|
|
192
|
+
color: var(--color--inactive);
|
|
193
|
+
|
|
194
|
+
&.filtered {
|
|
195
|
+
color: var(--color--foreground--secondary);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.iconContainer {
|
|
200
|
+
position: absolute;
|
|
201
|
+
top: 0;
|
|
202
|
+
right: 0;
|
|
203
|
+
bottom: 0;
|
|
204
|
+
left: 0;
|
|
205
|
+
display: flex;
|
|
206
|
+
align-items: center;
|
|
207
|
+
justify-content: center;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.flagContainer {
|
|
211
|
+
background-color: var(--color--primary);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.flag,
|
|
215
|
+
.star {
|
|
216
|
+
$size: 24px;
|
|
217
|
+
|
|
218
|
+
width: $size;
|
|
219
|
+
height: $size;
|
|
220
|
+
color: white;
|
|
221
|
+
}
|
|
@@ -4,7 +4,15 @@ import { FunctionComponent, RefObject, useCallback, useMemo } from 'react';
|
|
|
4
4
|
import { useDispatch } from 'react-redux';
|
|
5
5
|
|
|
6
6
|
import { getTileSizes } from 'lib';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
boardSlice,
|
|
9
|
+
cellFilterSlice,
|
|
10
|
+
selectCellBonus,
|
|
11
|
+
selectCellIsFiltered,
|
|
12
|
+
selectTilePoints,
|
|
13
|
+
useTranslate,
|
|
14
|
+
useTypedSelector,
|
|
15
|
+
} from 'state';
|
|
8
16
|
|
|
9
17
|
import CellPure from './CellPure';
|
|
10
18
|
|
|
@@ -34,10 +42,19 @@ const Cell: FunctionComponent<Props> = ({
|
|
|
34
42
|
const translate = useTranslate();
|
|
35
43
|
const bonus = useTypedSelector((state) => selectCellBonus(state, cell));
|
|
36
44
|
const points = useTypedSelector((state) => selectTilePoints(state, cell.tile));
|
|
45
|
+
const isFiltered = useTypedSelector((state) => selectCellIsFiltered(state, cell));
|
|
37
46
|
const { tileFontSize } = getTileSizes(size);
|
|
38
47
|
const isEmpty = tile.character === EMPTY_CELL;
|
|
39
48
|
const style = useMemo(() => ({ fontSize: tileFontSize }), [tileFontSize]);
|
|
40
49
|
|
|
50
|
+
const handleDirectionToggleClick = useCallback(() => {
|
|
51
|
+
if (inputRef.current) {
|
|
52
|
+
inputRef.current.focus();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
onDirectionToggle();
|
|
56
|
+
}, [onDirectionToggle]);
|
|
57
|
+
|
|
41
58
|
const handleFocus = useCallback(() => onFocus(x, y), [x, y, onFocus]);
|
|
42
59
|
|
|
43
60
|
const handleToggleBlankClick = useCallback(() => {
|
|
@@ -48,13 +65,13 @@ const Cell: FunctionComponent<Props> = ({
|
|
|
48
65
|
dispatch(boardSlice.actions.toggleCellIsBlank({ x, y }));
|
|
49
66
|
}, [dispatch, x, y]);
|
|
50
67
|
|
|
51
|
-
const
|
|
68
|
+
const handleToggleFilterCellClick = useCallback(() => {
|
|
52
69
|
if (inputRef.current) {
|
|
53
70
|
inputRef.current.focus();
|
|
54
71
|
}
|
|
55
72
|
|
|
56
|
-
|
|
57
|
-
}, [
|
|
73
|
+
dispatch(cellFilterSlice.actions.toggle({ x, y }));
|
|
74
|
+
}, [dispatch, x, y]);
|
|
58
75
|
|
|
59
76
|
return (
|
|
60
77
|
<CellPure
|
|
@@ -65,6 +82,7 @@ const Cell: FunctionComponent<Props> = ({
|
|
|
65
82
|
inputRef={inputRef}
|
|
66
83
|
isCenter={isCenter}
|
|
67
84
|
isEmpty={isEmpty}
|
|
85
|
+
isFiltered={isFiltered}
|
|
68
86
|
points={points}
|
|
69
87
|
size={size}
|
|
70
88
|
style={style}
|
|
@@ -73,6 +91,7 @@ const Cell: FunctionComponent<Props> = ({
|
|
|
73
91
|
onDirectionToggleClick={handleDirectionToggleClick}
|
|
74
92
|
onFocus={handleFocus}
|
|
75
93
|
onToggleBlankClick={handleToggleBlankClick}
|
|
94
|
+
onToggleFilterCellClick={handleToggleFilterCellClick}
|
|
76
95
|
/>
|
|
77
96
|
);
|
|
78
97
|
};
|
|
@@ -2,7 +2,7 @@ import { Bonus, Cell, Tile as TileModel } from '@scrabble-solver/types';
|
|
|
2
2
|
import classNames from 'classnames';
|
|
3
3
|
import { CSSProperties, FocusEventHandler, FunctionComponent, memo, MouseEventHandler, RefObject } from 'react';
|
|
4
4
|
|
|
5
|
-
import { ArrowDown } from 'icons';
|
|
5
|
+
import { ArrowDown, Flag, Star } from 'icons';
|
|
6
6
|
import { Translate } from 'types';
|
|
7
7
|
|
|
8
8
|
import Tile from '../../../Tile';
|
|
@@ -19,6 +19,7 @@ interface Props {
|
|
|
19
19
|
inputRef: RefObject<HTMLInputElement>;
|
|
20
20
|
isCenter: boolean;
|
|
21
21
|
isEmpty: boolean;
|
|
22
|
+
isFiltered: boolean;
|
|
22
23
|
points?: number;
|
|
23
24
|
size: number;
|
|
24
25
|
style?: CSSProperties;
|
|
@@ -27,6 +28,7 @@ interface Props {
|
|
|
27
28
|
onDirectionToggleClick: MouseEventHandler<HTMLButtonElement>;
|
|
28
29
|
onFocus: FocusEventHandler<HTMLInputElement>;
|
|
29
30
|
onToggleBlankClick: MouseEventHandler<HTMLButtonElement>;
|
|
31
|
+
onToggleFilterCellClick: MouseEventHandler<HTMLButtonElement>;
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
const CellPure: FunctionComponent<Props> = ({
|
|
@@ -37,6 +39,7 @@ const CellPure: FunctionComponent<Props> = ({
|
|
|
37
39
|
inputRef,
|
|
38
40
|
isCenter,
|
|
39
41
|
isEmpty,
|
|
42
|
+
isFiltered,
|
|
40
43
|
points,
|
|
41
44
|
size,
|
|
42
45
|
style,
|
|
@@ -45,6 +48,7 @@ const CellPure: FunctionComponent<Props> = ({
|
|
|
45
48
|
onDirectionToggleClick,
|
|
46
49
|
onFocus,
|
|
47
50
|
onToggleBlankClick,
|
|
51
|
+
onToggleFilterCellClick,
|
|
48
52
|
}) => (
|
|
49
53
|
<div
|
|
50
54
|
className={classNames(styles.cell, getBonusClassname(cell, bonus, isCenter), className, {
|
|
@@ -52,6 +56,18 @@ const CellPure: FunctionComponent<Props> = ({
|
|
|
52
56
|
})}
|
|
53
57
|
style={style}
|
|
54
58
|
>
|
|
59
|
+
{isCenter && (
|
|
60
|
+
<div className={classNames(styles.iconContainer)}>
|
|
61
|
+
<Star className={styles.star} />
|
|
62
|
+
</div>
|
|
63
|
+
)}
|
|
64
|
+
|
|
65
|
+
{isFiltered && (
|
|
66
|
+
<div className={classNames(styles.iconContainer, styles.flagContainer)}>
|
|
67
|
+
<Flag className={styles.flag} />
|
|
68
|
+
</div>
|
|
69
|
+
)}
|
|
70
|
+
|
|
55
71
|
<Tile
|
|
56
72
|
className={styles.tile}
|
|
57
73
|
character={isEmpty ? undefined : tile.character}
|
|
@@ -74,6 +90,18 @@ const CellPure: FunctionComponent<Props> = ({
|
|
|
74
90
|
/>
|
|
75
91
|
</Button>
|
|
76
92
|
|
|
93
|
+
{isEmpty && (
|
|
94
|
+
<Button
|
|
95
|
+
className={classNames(styles.filterCell, {
|
|
96
|
+
[styles.filtered]: isFiltered,
|
|
97
|
+
})}
|
|
98
|
+
tooltip={translate('cell.filter-cell')}
|
|
99
|
+
onClick={onToggleFilterCellClick}
|
|
100
|
+
>
|
|
101
|
+
<Flag />
|
|
102
|
+
</Button>
|
|
103
|
+
)}
|
|
104
|
+
|
|
77
105
|
{!isEmpty && (
|
|
78
106
|
<Button
|
|
79
107
|
className={classNames(styles.blank, {
|
package/src/i18n/de.json
CHANGED
package/src/i18n/en.json
CHANGED
package/src/i18n/es.json
CHANGED
package/src/i18n/fr.json
CHANGED
package/src/i18n/pl.json
CHANGED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<!-- https://icons.getbootstrap.com/icons/flag-fill/ -->
|
|
2
|
+
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
|
3
|
+
<path d="M14.778.085A.5.5 0 0 1 15 .5V8a.5.5 0 0 1-.314.464L14.5 8l.186.464-.003.001-.006.003-.023.009a12.435 12.435 0 0 1-.397.15c-.264.095-.631.223-1.047.35-.816.252-1.879.523-2.71.523-.847 0-1.548-.28-2.158-.525l-.028-.01C7.68 8.71 7.14 8.5 6.5 8.5c-.7 0-1.638.23-2.437.477A19.626 19.626 0 0 0 3 9.342V15.5a.5.5 0 0 1-1 0V.5a.5.5 0 0 1 1 0v.282c.226-.079.496-.17.79-.26C4.606.272 5.67 0 6.5 0c.84 0 1.524.277 2.121.519l.043.018C9.286.788 9.828 1 10.5 1c.7 0 1.638-.23 2.437-.477a19.587 19.587 0 0 0 1.349-.476l.019-.007.004-.002h.001" fill="currentColor" />
|
|
4
|
+
</svg>
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<!-- https://icons.getbootstrap.com/icons/star-fill/ -->
|
|
2
|
+
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
|
3
|
+
<path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z" fill="currentColor" />
|
|
4
|
+
</svg>
|
package/src/icons/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ export { default as Cog } from './Cog.svg';
|
|
|
8
8
|
export { default as Cross } from './Cross.svg';
|
|
9
9
|
export { default as DashCircleFill } from './DashCircleFill.svg';
|
|
10
10
|
export { default as Eraser } from './Eraser.svg';
|
|
11
|
+
export { default as Flag } from './Flag.svg';
|
|
11
12
|
export { default as FlagEs } from './FlagEs.svg';
|
|
12
13
|
export { default as FlagFr } from './FlagFr.svg';
|
|
13
14
|
export { default as FlagGb } from './FlagGb.svg';
|
|
@@ -20,3 +21,4 @@ export { default as Play } from './Play.svg';
|
|
|
20
21
|
export { default as Sack } from './Sack.svg';
|
|
21
22
|
export { default as SortDown } from './SortDown.svg';
|
|
22
23
|
export { default as SortUp } from './SortUp.svg';
|
|
24
|
+
export { default as Star } from './Star.svg';
|
package/src/state/rootReducer.ts
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
import { combineReducers } from 'redux';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
boardSlice,
|
|
5
|
+
dictionarySlice,
|
|
6
|
+
cellFilterSlice,
|
|
7
|
+
rackSlice,
|
|
8
|
+
resultsSlice,
|
|
9
|
+
settingsSlice,
|
|
10
|
+
solveSlice,
|
|
11
|
+
} from './slices';
|
|
4
12
|
|
|
5
13
|
const rootReducer = combineReducers({
|
|
6
14
|
board: boardSlice.reducer,
|
|
15
|
+
cellFilter: cellFilterSlice.reducer,
|
|
7
16
|
dictionary: dictionarySlice.reducer,
|
|
8
17
|
rack: rackSlice.reducer,
|
|
9
18
|
results: resultsSlice.reducer,
|
package/src/state/sagas.ts
CHANGED
|
@@ -9,12 +9,21 @@ import { initialize, reset } from './actions';
|
|
|
9
9
|
import {
|
|
10
10
|
selectAutoGroupTiles,
|
|
11
11
|
selectBoard,
|
|
12
|
+
selectCellIsFiltered,
|
|
12
13
|
selectCharacters,
|
|
13
14
|
selectConfig,
|
|
14
15
|
selectDictionary,
|
|
15
16
|
selectLocale,
|
|
16
17
|
} from './selectors';
|
|
17
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
boardSlice,
|
|
20
|
+
cellFilterSlice,
|
|
21
|
+
dictionarySlice,
|
|
22
|
+
rackSlice,
|
|
23
|
+
resultsSlice,
|
|
24
|
+
settingsSlice,
|
|
25
|
+
solveSlice,
|
|
26
|
+
} from './slices';
|
|
18
27
|
|
|
19
28
|
const SUBMIT_DELAY = 150;
|
|
20
29
|
|
|
@@ -26,6 +35,7 @@ const memoizedFindWordDefinitions = memoize(findWordDefinitions);
|
|
|
26
35
|
type AnyGenerator = Generator<any, any, any>;
|
|
27
36
|
|
|
28
37
|
export function* rootSaga(): AnyGenerator {
|
|
38
|
+
yield takeEvery(boardSlice.actions.changeCellValue.type, onCellValueChange);
|
|
29
39
|
yield takeEvery(resultsSlice.actions.applyResult.type, onApplyResult);
|
|
30
40
|
yield takeEvery(resultsSlice.actions.changeResultCandidate.type, onResultCandidateChange);
|
|
31
41
|
yield takeEvery(settingsSlice.actions.changeConfigId.type, onConfigIdChange);
|
|
@@ -36,9 +46,18 @@ export function* rootSaga(): AnyGenerator {
|
|
|
36
46
|
yield takeLatest(solveSlice.actions.submit.type, onSubmit);
|
|
37
47
|
}
|
|
38
48
|
|
|
49
|
+
function* onCellValueChange({ payload }: PayloadAction<{ value: string; x: number; y: number }>): AnyGenerator {
|
|
50
|
+
const isFiltered = yield select((state) => selectCellIsFiltered(state, payload));
|
|
51
|
+
|
|
52
|
+
if (isFiltered) {
|
|
53
|
+
yield put(cellFilterSlice.actions.toggle(payload));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
39
57
|
function* onApplyResult({ payload: result }: PayloadAction<Result>): AnyGenerator {
|
|
40
58
|
const autoGroupTiles = yield select(selectAutoGroupTiles);
|
|
41
59
|
yield put(boardSlice.actions.applyResult(result));
|
|
60
|
+
yield put(cellFilterSlice.actions.reset());
|
|
42
61
|
yield put(rackSlice.actions.removeTiles(result.tiles));
|
|
43
62
|
yield put(rackSlice.actions.groupTiles(autoGroupTiles));
|
|
44
63
|
}
|
|
@@ -72,6 +91,7 @@ function* onInitialize(): AnyGenerator {
|
|
|
72
91
|
|
|
73
92
|
function* onReset(): AnyGenerator {
|
|
74
93
|
yield put(boardSlice.actions.reset());
|
|
94
|
+
yield put(cellFilterSlice.actions.reset());
|
|
75
95
|
yield put(dictionarySlice.actions.reset());
|
|
76
96
|
yield put(rackSlice.actions.reset());
|
|
77
97
|
yield put(resultsSlice.actions.reset());
|
package/src/state/selectors.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createSelector } from '@reduxjs/toolkit';
|
|
2
2
|
import { getLocaleConfig } from '@scrabble-solver/configs';
|
|
3
|
-
import { Cell, Config, Tile } from '@scrabble-solver/types';
|
|
3
|
+
import { Cell, Config, Result, Tile } from '@scrabble-solver/types';
|
|
4
4
|
|
|
5
5
|
import i18n from 'i18n';
|
|
6
6
|
import { findCell, getRemainingTiles, getRemainingTilesGroups, sortResults, unorderedArraysEqual } from 'lib';
|
|
@@ -10,6 +10,8 @@ import { RootState } from './types';
|
|
|
10
10
|
|
|
11
11
|
const selectCell = (_: unknown, cell: Cell): Cell => cell;
|
|
12
12
|
|
|
13
|
+
const selectPoint = (_: unknown, point: { x: number; y: number }): { x: number; y: number } => point;
|
|
14
|
+
|
|
13
15
|
const selectCharacter = (_: unknown, character: string | null): string | null => character;
|
|
14
16
|
|
|
15
17
|
const selectTile = (_: unknown, tile: Tile | null): Tile | null => tile;
|
|
@@ -18,6 +20,8 @@ const selectBoardRoot = (state: RootState): RootState['board'] => state.board;
|
|
|
18
20
|
|
|
19
21
|
const selectDictionaryRoot = (state: RootState): RootState['dictionary'] => state.dictionary;
|
|
20
22
|
|
|
23
|
+
const selectCellFilterRoot = (state: RootState): RootState['cellFilter'] => state.cellFilter;
|
|
24
|
+
|
|
21
25
|
const selectRackRoot = (state: RootState): RootState['rack'] => state.rack;
|
|
22
26
|
|
|
23
27
|
const selectResultsRoot = (state: RootState): RootState['results'] => state.results;
|
|
@@ -38,6 +42,12 @@ export const selectConfigId = createSelector([selectSettingsRoot], (settings) =>
|
|
|
38
42
|
|
|
39
43
|
export const selectConfig = createSelector([selectConfigId, selectLocale], getLocaleConfig);
|
|
40
44
|
|
|
45
|
+
export const selectCellFilter = selectCellFilterRoot;
|
|
46
|
+
|
|
47
|
+
export const selectCellIsFiltered = createSelector([selectCellFilter, selectPoint], (cellFilter, { x, y }) => {
|
|
48
|
+
return cellFilter.some((cell) => cell.x === x && cell.y === y);
|
|
49
|
+
});
|
|
50
|
+
|
|
41
51
|
export const selectResults = createSelector([selectResultsRoot], (results) => results.results);
|
|
42
52
|
|
|
43
53
|
export const selectResultsQuery = createSelector([selectResultsRoot], (results) => results.query);
|
|
@@ -51,20 +61,40 @@ export const selectSortedResults = createSelector(
|
|
|
51
61
|
sortResults,
|
|
52
62
|
);
|
|
53
63
|
|
|
64
|
+
const filterResultsByQuery = (results: Result[], query: string): Result[] => {
|
|
65
|
+
if (query.trim().length === 0) {
|
|
66
|
+
return results;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let regExp: RegExp | undefined;
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
regExp = new RegExp(query, 'gi');
|
|
73
|
+
} catch {
|
|
74
|
+
return results;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return results.filter((result) => {
|
|
78
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
79
|
+
return regExp!.test(result.word);
|
|
80
|
+
});
|
|
81
|
+
};
|
|
82
|
+
|
|
54
83
|
export const selectSortedFilteredResults = createSelector(
|
|
55
|
-
[selectSortedResults, selectResultsQuery],
|
|
56
|
-
(results, query) => {
|
|
57
|
-
if (!results
|
|
84
|
+
[selectSortedResults, selectResultsQuery, selectCellFilter],
|
|
85
|
+
(results, query, cellFilter) => {
|
|
86
|
+
if (!results) {
|
|
58
87
|
return results;
|
|
59
88
|
}
|
|
60
89
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
90
|
+
const filteredByQuery = filterResultsByQuery(results, query);
|
|
91
|
+
|
|
92
|
+
if (!cellFilter) {
|
|
93
|
+
return filteredByQuery;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return filteredByQuery.filter((result) => {
|
|
97
|
+
return cellFilter.every(({ x, y }) => result.cells.some((cell) => cell.x === x && cell.y === y));
|
|
68
98
|
});
|
|
69
99
|
},
|
|
70
100
|
);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
|
2
|
+
|
|
3
|
+
import cellFilterInitialState, { Point } from './cellFilterInitialState';
|
|
4
|
+
|
|
5
|
+
const cellFilterSlice = createSlice({
|
|
6
|
+
initialState: cellFilterInitialState,
|
|
7
|
+
name: 'cellFilter',
|
|
8
|
+
reducers: {
|
|
9
|
+
toggle: (state, action: PayloadAction<Point>) => {
|
|
10
|
+
const { x, y } = action.payload;
|
|
11
|
+
const has = state.some((point) => point.x === x && point.y === y);
|
|
12
|
+
|
|
13
|
+
if (has) {
|
|
14
|
+
return state.filter((point) => point.x !== x || point.y !== y);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return [...state, action.payload];
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
reset: () => cellFilterInitialState,
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export default cellFilterSlice;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { default as boardInitialState } from './boardInitialState';
|
|
2
2
|
export { default as boardSlice } from './boardSlice';
|
|
3
|
+
export { default as cellFilterInitialState } from './cellFilterInitialState';
|
|
4
|
+
export { default as cellFilterSlice } from './cellFilterSlice';
|
|
3
5
|
export { default as dictionaryInitialState } from './dictionaryInitialState';
|
|
4
6
|
export { default as dictionarySlice } from './dictionarySlice';
|
|
5
7
|
export { default as rackInitialState } from './rackInitialState';
|