@scrabble-solver/scrabble-solver 2.13.8 → 2.13.9
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 +6 -6
- package/.next/cache/.tsbuildinfo +1 -1
- package/.next/cache/eslint/.cache_8dgz12 +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/edge-server-production/0.pack +0 -0
- package/.next/cache/webpack/edge-server-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/prerender-manifest.js +1 -1
- package/.next/prerender-manifest.json +1 -1
- package/.next/routes-manifest.json +1 -1
- package/.next/server/chunks/807.js +1 -1
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/pages/index.html +1 -1
- package/.next/server/pages/index.js +1 -1
- package/.next/server/pages/index.json +1 -1
- package/.next/static/chunks/pages/{404-63b972b24be99c62.js → 404-01653a877b233143.js} +1 -1
- package/.next/static/chunks/pages/_app-735b5863675c1b5d.js +17 -0
- package/.next/static/chunks/pages/{index-7b73be2915cc7099.js → index-36c448d585a58425.js} +1 -1
- package/.next/static/css/841a5b5f0b2fb131.css +2 -0
- package/.next/static/{7zESQYo9UAqNh9LV0b7Sd → eLvYNd4B2hzSgBZ_PuZcQ}/_buildManifest.js +1 -1
- package/.next/trace +44 -44
- package/LICENSE +1 -1
- package/package.json +9 -9
- package/src/components/Board/Board.tsx +3 -3
- package/src/components/Board/BoardPure.tsx +27 -19
- package/src/components/Board/components/Actions/Actions.tsx +8 -12
- package/src/components/Board/components/Actions/lib.ts +30 -0
- package/src/components/Board/hooks/useBackgroundImage.tsx +3 -24
- package/src/components/Radio/Radio.module.scss +2 -1
- package/src/components/Tile/Tile.module.scss +0 -2
- package/src/components/Tile/Tile.tsx +1 -15
- package/src/components/Tooltip/Tooltip.module.scss +1 -0
- package/src/hooks/useAppLayout.ts +0 -1
- package/src/i18n/languages/english.json +2 -1
- package/src/i18n/languages/french.json +2 -1
- package/src/i18n/languages/german.json +2 -1
- package/src/i18n/languages/persian.json +2 -1
- package/src/i18n/languages/polish.json +2 -1
- package/src/i18n/languages/romanian.json +2 -1
- package/src/i18n/languages/spanish.json +2 -1
- package/src/icons/Ban.svg +4 -0
- package/src/icons/index.ts +1 -0
- package/src/lib/groupResults.ts +4 -7
- package/src/lib/index.ts +1 -0
- package/src/lib/resultMatchesCellFilter.ts +23 -0
- package/src/parameters/index.ts +0 -9
- package/src/state/sagas.ts +4 -4
- package/src/state/selectors.ts +5 -8
- package/src/state/slices/cellFilterInitialState.ts +2 -2
- package/src/state/slices/cellFilterSlice.ts +29 -4
- package/src/types/index.ts +10 -1
- package/.next/static/chunks/pages/_app-a2848b7efa6bb6b0.js +0 -17
- package/.next/static/css/b37850c8d5270d91.css +0 -2
- /package/.next/static/{7zESQYo9UAqNh9LV0b7Sd → eLvYNd4B2hzSgBZ_PuZcQ}/_ssgManifest.js +0 -0
package/LICENSE
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scrabble-solver/scrabble-solver",
|
|
3
|
-
"version": "2.13.
|
|
3
|
+
"version": "2.13.9",
|
|
4
4
|
"description": "Scrabble Solver 2 - App",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=16"
|
|
@@ -30,13 +30,13 @@
|
|
|
30
30
|
"@floating-ui/react": "^0.26.17",
|
|
31
31
|
"@kamilmielnik/trie": "^3.0.0",
|
|
32
32
|
"@reduxjs/toolkit": "^2.2.5",
|
|
33
|
-
"@scrabble-solver/configs": "^2.13.
|
|
34
|
-
"@scrabble-solver/constants": "^2.13.
|
|
35
|
-
"@scrabble-solver/dictionaries": "^2.13.
|
|
36
|
-
"@scrabble-solver/logger": "^2.13.
|
|
37
|
-
"@scrabble-solver/solver": "^2.13.
|
|
38
|
-
"@scrabble-solver/types": "^2.13.
|
|
39
|
-
"@scrabble-solver/word-definitions": "^2.13.
|
|
33
|
+
"@scrabble-solver/configs": "^2.13.9",
|
|
34
|
+
"@scrabble-solver/constants": "^2.13.9",
|
|
35
|
+
"@scrabble-solver/dictionaries": "^2.13.9",
|
|
36
|
+
"@scrabble-solver/logger": "^2.13.9",
|
|
37
|
+
"@scrabble-solver/solver": "^2.13.9",
|
|
38
|
+
"@scrabble-solver/types": "^2.13.9",
|
|
39
|
+
"@scrabble-solver/word-definitions": "^2.13.9",
|
|
40
40
|
"classnames": "^2.5.1",
|
|
41
41
|
"env-cmd": "^10.1.0",
|
|
42
42
|
"include-media": "^2.0.0",
|
|
@@ -72,5 +72,5 @@
|
|
|
72
72
|
"@types/redux-saga": "^0.10.5",
|
|
73
73
|
"sass": "^1.77.5"
|
|
74
74
|
},
|
|
75
|
-
"gitHead": "
|
|
75
|
+
"gitHead": "d09676296f8fafadb7da0cf1e11fb240aaf309ee"
|
|
76
76
|
}
|
|
@@ -9,7 +9,7 @@ import { useDispatch } from 'react-redux';
|
|
|
9
9
|
|
|
10
10
|
import { useAppLayout } from 'hooks';
|
|
11
11
|
import { LOCALE_FEATURES } from 'i18n';
|
|
12
|
-
import { TRANSITION } from 'parameters';
|
|
12
|
+
import { BORDER_WIDTH, TRANSITION } from 'parameters';
|
|
13
13
|
import {
|
|
14
14
|
boardSlice,
|
|
15
15
|
cellFilterSlice,
|
|
@@ -176,8 +176,8 @@ const Board: FunctionComponent<Props> = ({ className }) => {
|
|
|
176
176
|
ref={floatingFocus.refs.setFloating}
|
|
177
177
|
style={{
|
|
178
178
|
position: floatingFocus.strategy,
|
|
179
|
-
top: floatingFocus.y ? floatingFocus.y + cellSize : 0,
|
|
180
|
-
left: floatingFocus.x ?? 0,
|
|
179
|
+
top: (floatingFocus.y ? floatingFocus.y + cellSize : 0) - (showCoordinates === 'hidden' ? 0 : BORDER_WIDTH),
|
|
180
|
+
left: (floatingFocus.x ?? 0) - (showCoordinates === 'hidden' ? 0 : BORDER_WIDTH),
|
|
181
181
|
width: cellSize,
|
|
182
182
|
height: cellSize,
|
|
183
183
|
opacity: hasFocus ? 1 : 0,
|
|
@@ -12,10 +12,10 @@ import {
|
|
|
12
12
|
memo,
|
|
13
13
|
} from 'react';
|
|
14
14
|
|
|
15
|
-
import { FlagFill } from 'icons';
|
|
15
|
+
import { Ban, FlagFill } from 'icons';
|
|
16
16
|
import { getCoordinate } from 'lib';
|
|
17
17
|
import { BORDER_WIDTH } from 'parameters';
|
|
18
|
-
import {
|
|
18
|
+
import { CellFilterEntry } from 'types';
|
|
19
19
|
|
|
20
20
|
import styles from './Board.module.scss';
|
|
21
21
|
import { Cell } from './components';
|
|
@@ -26,7 +26,7 @@ interface Props {
|
|
|
26
26
|
coordinatesFontSize: number;
|
|
27
27
|
coordinatesSize: number;
|
|
28
28
|
direction: 'ltr' | 'rtl';
|
|
29
|
-
filteredCells:
|
|
29
|
+
filteredCells: CellFilterEntry[];
|
|
30
30
|
inputRefs: RefObject<HTMLInputElement>[][];
|
|
31
31
|
rows: CellModel[][];
|
|
32
32
|
showCoordinates: ShowCoordinates;
|
|
@@ -87,22 +87,30 @@ const BoardPure = forwardRef<HTMLDivElement, Props>(
|
|
|
87
87
|
</>
|
|
88
88
|
)}
|
|
89
89
|
|
|
90
|
-
{
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
90
|
+
{/* The dynamic changes to the board presentation need to be outside of useBackgroundImage
|
|
91
|
+
to prevent flickering on blob URL change (i.e. when flagging a field,
|
|
92
|
+
but not when changing game type since user's attention is not on the board
|
|
93
|
+
when that happens)*/}
|
|
94
|
+
{filteredCells.map(({ x, y, type }) => {
|
|
95
|
+
const Icon = type === 'exclude' ? Ban : FlagFill;
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<div
|
|
99
|
+
className={styles.iconContainer}
|
|
100
|
+
key={[x, y].join('-')}
|
|
101
|
+
style={{
|
|
102
|
+
height: cellSize,
|
|
103
|
+
width: cellSize,
|
|
104
|
+
left: direction === 'ltr' ? coordinatesSize + BORDER_WIDTH + x * (cellSize + BORDER_WIDTH) : undefined,
|
|
105
|
+
right: direction === 'rtl' ? coordinatesSize + BORDER_WIDTH + x * (cellSize + BORDER_WIDTH) : undefined,
|
|
106
|
+
top: coordinatesSize + BORDER_WIDTH + y * (cellSize + BORDER_WIDTH),
|
|
107
|
+
}}
|
|
108
|
+
>
|
|
109
|
+
<div className={styles.iconBackground} />
|
|
110
|
+
<Icon className={styles.icon} />
|
|
111
|
+
</div>
|
|
112
|
+
);
|
|
113
|
+
})}
|
|
106
114
|
|
|
107
115
|
{rows.map((cells, y) => (
|
|
108
116
|
<Fragment key={y}>
|
|
@@ -3,21 +3,16 @@ import { Cell } from '@scrabble-solver/types';
|
|
|
3
3
|
import classNames from 'classnames';
|
|
4
4
|
import { forwardRef, HTMLProps, MouseEventHandler } from 'react';
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { Keyboard, Square, SquareFill } from 'icons';
|
|
7
7
|
import { findCell } from 'lib';
|
|
8
|
-
import {
|
|
9
|
-
selectCellIsFiltered,
|
|
10
|
-
selectInputMode,
|
|
11
|
-
selectResultCandidateCells,
|
|
12
|
-
useTranslate,
|
|
13
|
-
useTypedSelector,
|
|
14
|
-
} from 'state';
|
|
8
|
+
import { selectCellFilter, selectInputMode, selectResultCandidateCells, useTranslate, useTypedSelector } from 'state';
|
|
15
9
|
import { Direction } from 'types';
|
|
16
10
|
|
|
17
11
|
import Button from '../../../Button';
|
|
18
12
|
import ToggleDirectionButton from '../ToggleDirectionButton';
|
|
19
13
|
|
|
20
14
|
import styles from './Actions.module.scss';
|
|
15
|
+
import { getNextCellFilter } from './lib';
|
|
21
16
|
|
|
22
17
|
interface Props extends HTMLProps<HTMLDivElement> {
|
|
23
18
|
cell: Cell;
|
|
@@ -35,10 +30,11 @@ const Actions = forwardRef<HTMLDivElement, Props>(
|
|
|
35
30
|
) => {
|
|
36
31
|
const translate = useTranslate();
|
|
37
32
|
const inputMode = useTypedSelector(selectInputMode);
|
|
38
|
-
const
|
|
33
|
+
const filter = useTypedSelector((state) => selectCellFilter(state, cell));
|
|
39
34
|
const resultCandidateCells = useTypedSelector(selectResultCandidateCells);
|
|
40
35
|
const isBlank = cell.tile.isBlank;
|
|
41
36
|
const isEmpty = cell.tile.character === EMPTY_CELL || Boolean(findCell(resultCandidateCells, cell.x, cell.y));
|
|
37
|
+
const { Icon, labelTranslationKey } = getNextCellFilter(filter);
|
|
42
38
|
|
|
43
39
|
// On iOS it helps with losing focus too early which makes Actions disappear
|
|
44
40
|
const handleMouseDown: MouseEventHandler = (event) => event.preventDefault();
|
|
@@ -67,10 +63,10 @@ const Actions = forwardRef<HTMLDivElement, Props>(
|
|
|
67
63
|
|
|
68
64
|
{isEmpty && (
|
|
69
65
|
<Button
|
|
70
|
-
aria-label={translate(
|
|
66
|
+
aria-label={translate(labelTranslationKey)}
|
|
71
67
|
className={classNames(styles.action)}
|
|
72
|
-
Icon={
|
|
73
|
-
tooltip={translate(
|
|
68
|
+
Icon={Icon}
|
|
69
|
+
tooltip={translate(labelTranslationKey)}
|
|
74
70
|
onClick={onToggleFilterCell}
|
|
75
71
|
onMouseDown={handleMouseDown}
|
|
76
72
|
/>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { FunctionComponent, SVGAttributes } from 'react';
|
|
2
|
+
|
|
3
|
+
import { Ban, Eraser, FlagFill } from 'icons';
|
|
4
|
+
import { CellFilterEntry, TranslationKey } from 'types';
|
|
5
|
+
|
|
6
|
+
export const getNextCellFilter = (
|
|
7
|
+
filter: CellFilterEntry | undefined,
|
|
8
|
+
): {
|
|
9
|
+
Icon: FunctionComponent<SVGAttributes<SVGElement>>;
|
|
10
|
+
labelTranslationKey: TranslationKey;
|
|
11
|
+
} => {
|
|
12
|
+
if (filter?.type === 'exclude') {
|
|
13
|
+
return {
|
|
14
|
+
Icon: Eraser,
|
|
15
|
+
labelTranslationKey: 'common.clear',
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (filter?.type === 'include') {
|
|
20
|
+
return {
|
|
21
|
+
Icon: Ban,
|
|
22
|
+
labelTranslationKey: 'cell.filter-cell.exclude',
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
Icon: FlagFill,
|
|
28
|
+
labelTranslationKey: 'cell.filter-cell.include',
|
|
29
|
+
};
|
|
30
|
+
};
|
|
@@ -7,16 +7,9 @@ import { Provider } from 'react-redux';
|
|
|
7
7
|
|
|
8
8
|
import { useAppLayout, useMediaQueries } from 'hooks';
|
|
9
9
|
import { LOCALE_FEATURES } from 'i18n';
|
|
10
|
-
import {
|
|
10
|
+
import { Star } from 'icons';
|
|
11
11
|
import { dataUrlToBlob, getTileSizes } from 'lib';
|
|
12
|
-
import {
|
|
13
|
-
BORDER_COLOR_LIGHT,
|
|
14
|
-
BORDER_RADIUS,
|
|
15
|
-
BORDER_WIDTH,
|
|
16
|
-
COLOR_BACKGROUND,
|
|
17
|
-
COLOR_BONUS_START,
|
|
18
|
-
COLOR_FILTERED,
|
|
19
|
-
} from 'parameters';
|
|
12
|
+
import { BORDER_COLOR_LIGHT, BORDER_RADIUS, BORDER_WIDTH, COLOR_BACKGROUND, COLOR_BONUS_START } from 'parameters';
|
|
20
13
|
import { selectConfig, selectLocale, selectShowCoordinates, store, useTypedSelector } from 'state';
|
|
21
14
|
import { Point } from 'types';
|
|
22
15
|
|
|
@@ -30,7 +23,6 @@ const BONUS = 'b';
|
|
|
30
23
|
const BONUS_WORD_2 = 'b2';
|
|
31
24
|
const BONUS_WORD_3 = 'b3';
|
|
32
25
|
const BONUS_WORD_4 = 'b4';
|
|
33
|
-
const CELL_FILTER = 'c';
|
|
34
26
|
|
|
35
27
|
const useBackgroundImage = () => {
|
|
36
28
|
const { boardSize, cellSize, coordinatesSize } = useAppLayout();
|
|
@@ -131,19 +123,6 @@ const useBackgroundImage = () => {
|
|
|
131
123
|
x4
|
|
132
124
|
</text>
|
|
133
125
|
</symbol>
|
|
134
|
-
|
|
135
|
-
<symbol id={CELL_FILTER}>
|
|
136
|
-
<rect
|
|
137
|
-
fill={COLOR_FILTERED}
|
|
138
|
-
height={bonusSize}
|
|
139
|
-
rx={borderRadius}
|
|
140
|
-
width={bonusSize}
|
|
141
|
-
x={bonusOffset}
|
|
142
|
-
y={bonusOffset}
|
|
143
|
-
/>
|
|
144
|
-
|
|
145
|
-
<FlagFill color="white" height={iconSize} width={iconSize} x={iconOffset} y={iconOffset} />
|
|
146
|
-
</symbol>
|
|
147
126
|
</defs>
|
|
148
127
|
|
|
149
128
|
{showCoordinates === 'hidden' && (
|
|
@@ -230,7 +209,7 @@ const useBackgroundImage = () => {
|
|
|
230
209
|
</Provider>,
|
|
231
210
|
);
|
|
232
211
|
|
|
233
|
-
const encodedSvg = globalThis.btoa(backgroundSvg);
|
|
212
|
+
const encodedSvg = useMemo(() => globalThis.btoa(backgroundSvg), [backgroundSvg]);
|
|
234
213
|
const dataUrl = `data:image/svg+xml;base64,${encodedSvg}`;
|
|
235
214
|
const blob = useMemo(() => dataUrlToBlob(dataUrl), [dataUrl]);
|
|
236
215
|
const blobUrl = useMemo(() => URL.createObjectURL(blob), [blob]);
|
|
@@ -25,6 +25,7 @@ $radio-box-size: $radio-size + 2 * $radio-inner-border;
|
|
|
25
25
|
|
|
26
26
|
&.disabled {
|
|
27
27
|
opacity: var(--opacity--disabled);
|
|
28
|
+
cursor: not-allowed;
|
|
28
29
|
}
|
|
29
30
|
}
|
|
30
31
|
|
|
@@ -35,7 +36,7 @@ $radio-box-size: $radio-size + 2 * $radio-inner-border;
|
|
|
35
36
|
width: 100%;
|
|
36
37
|
height: 100%;
|
|
37
38
|
opacity: 0;
|
|
38
|
-
cursor:
|
|
39
|
+
cursor: inherit;
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
.icon {
|
|
@@ -9,14 +9,12 @@ import {
|
|
|
9
9
|
Ref,
|
|
10
10
|
TouchEventHandler,
|
|
11
11
|
useCallback,
|
|
12
|
-
useEffect,
|
|
13
12
|
useMemo,
|
|
14
13
|
useRef,
|
|
15
14
|
} from 'react';
|
|
16
15
|
|
|
17
16
|
import { useAppLayout } from 'hooks';
|
|
18
17
|
import { getTileSizes, noop } from 'lib';
|
|
19
|
-
import { EASE_OUT_CUBIC, TILE_APPEAR_DURATION, TILE_APPEAR_KEYFRAMES } from 'parameters';
|
|
20
18
|
import { selectLocale, useTypedSelector } from 'state';
|
|
21
19
|
|
|
22
20
|
import TilePure from './TilePure';
|
|
@@ -65,7 +63,7 @@ const Tile: FunctionComponent<Props> = ({
|
|
|
65
63
|
onTouchStart = noop,
|
|
66
64
|
}) => {
|
|
67
65
|
const locale = useTypedSelector(selectLocale);
|
|
68
|
-
const {
|
|
66
|
+
const { showTilePoints } = useAppLayout();
|
|
69
67
|
const { pointsFontSize, tileSize } = getTileSizes(size);
|
|
70
68
|
const style = useMemo(() => ({ height: tileSize, width: tileSize }), [tileSize]);
|
|
71
69
|
const pointsStyle = useMemo(() => ({ fontSize: pointsFontSize }), [pointsFontSize]);
|
|
@@ -83,18 +81,6 @@ const Tile: FunctionComponent<Props> = ({
|
|
|
83
81
|
[onKeyDown],
|
|
84
82
|
);
|
|
85
83
|
|
|
86
|
-
useEffect(() => {
|
|
87
|
-
if (!ref.current?.parentElement || !character || !animateTile) {
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
ref.current.parentElement.animate(TILE_APPEAR_KEYFRAMES, {
|
|
92
|
-
duration: TILE_APPEAR_DURATION,
|
|
93
|
-
easing: EASE_OUT_CUBIC,
|
|
94
|
-
fill: 'forwards',
|
|
95
|
-
});
|
|
96
|
-
}, [character, animateTile]);
|
|
97
|
-
|
|
98
84
|
return (
|
|
99
85
|
<TilePure
|
|
100
86
|
aria-label={ariaLabel}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"cell.enter-word": "Enter word",
|
|
3
|
-
"cell.filter-cell": "
|
|
3
|
+
"cell.filter-cell.exclude": "Exclude destination",
|
|
4
|
+
"cell.filter-cell.include": "Target destination",
|
|
4
5
|
"cell.set-blank": "Mark it a blank",
|
|
5
6
|
"cell.set-not-blank": "Mark it not a blank",
|
|
6
7
|
"cell.tile.location": "Board: tile ({{x}}, {{y}})",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"cell.enter-word": "Entrez un mot",
|
|
3
|
-
"cell.filter-cell": "
|
|
3
|
+
"cell.filter-cell.exclude": "Exclure la destination",
|
|
4
|
+
"cell.filter-cell.include": "Destination cible",
|
|
4
5
|
"cell.set-blank": "Marquer comme vide",
|
|
5
6
|
"cell.set-not-blank": "Marquer comme non vide",
|
|
6
7
|
"cell.tile.location": "Plateau: la case ({{x}}, {{y}})",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"cell.enter-word": "Wort eingeben",
|
|
3
|
-
"cell.filter-cell": "
|
|
3
|
+
"cell.filter-cell.exclude": "Ziel ausschließen",
|
|
4
|
+
"cell.filter-cell.include": "Ziel anvisieren",
|
|
4
5
|
"cell.set-blank": "Als Blanko markieren",
|
|
5
6
|
"cell.set-not-blank": "Nicht als Blanko markieren",
|
|
6
7
|
"cell.tile.location": "Brett: Stein ({{x}}, {{y}})",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"cell.enter-word": "کلمه را وارد کنید",
|
|
3
|
-
"cell.filter-cell": "مقصد",
|
|
3
|
+
"cell.filter-cell.exclude": "مقصد را حذف کنید",
|
|
4
|
+
"cell.filter-cell.include": "مقصد مورد نظر",
|
|
4
5
|
"cell.set-blank": "علامت گذاری به عنوان خالی",
|
|
5
6
|
"cell.set-not-blank": "علامت گذاری به عنوان غیر خالی",
|
|
6
7
|
"cell.tile.location": "({{x}}، {{y}}) کاشی: صفحه",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"cell.enter-word": "Wprowadź słowo",
|
|
3
|
-
"cell.filter-cell": "
|
|
3
|
+
"cell.filter-cell.exclude": "Wyklucz miejsce",
|
|
4
|
+
"cell.filter-cell.include": "Miejsce docelowe",
|
|
4
5
|
"cell.set-blank": "Oznacz jako blank",
|
|
5
6
|
"cell.set-not-blank": "Oznacz jako nie blank",
|
|
6
7
|
"cell.tile.location": "Plansza: płytka ({{x}}, {{y}})",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"cell.enter-word": "Introducere cuvant",
|
|
3
|
-
"cell.filter-cell": "
|
|
3
|
+
"cell.filter-cell.exclude": "Excludeți destinația",
|
|
4
|
+
"cell.filter-cell.include": "Destinație țintă",
|
|
4
5
|
"cell.set-blank": "Marcare camp liber",
|
|
5
6
|
"cell.set-not-blank": "Marcare camp ocupat ",
|
|
6
7
|
"cell.tile.location": "Tabla: camp ({{x}}, {{y}})",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"cell.enter-word": "Ingresar palabra",
|
|
3
|
-
"cell.filter-cell": "
|
|
3
|
+
"cell.filter-cell.exclude": "Excluir destino",
|
|
4
|
+
"cell.filter-cell.include": "Destino objetivo",
|
|
4
5
|
"cell.set-blank": "Marcar como en blanco",
|
|
5
6
|
"cell.set-not-blank": "Marcar como no en blanco",
|
|
6
7
|
"cell.tile.location": "Tablero: espacio ({{x}}, {{y}})",
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<!-- https://icons.getbootstrap.com/icons/ban/ -->
|
|
2
|
+
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
|
3
|
+
<path d="M15 8a6.97 6.97 0 0 0-1.71-4.584l-9.874 9.875A7 7 0 0 0 15 8M2.71 12.584l9.874-9.875a7 7 0 0 0-9.874 9.874ZM16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0" fill="currentColor" />
|
|
4
|
+
</svg>
|
package/src/icons/index.ts
CHANGED
|
@@ -2,6 +2,7 @@ export { default as ArrowDown } from './ArrowDown.svg';
|
|
|
2
2
|
export { default as ArrowLeft } from './ArrowLeft.svg';
|
|
3
3
|
export { default as ArrowRight } from './ArrowRight.svg';
|
|
4
4
|
export { default as ArrowUp } from './ArrowUp.svg';
|
|
5
|
+
export { default as Ban } from './Ban.svg';
|
|
5
6
|
export { default as BookHalf } from './BookHalf.svg';
|
|
6
7
|
export { default as CardChecklist } from './CardChecklist.svg';
|
|
7
8
|
export { default as Check } from './Check.svg';
|
package/src/lib/groupResults.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Result } from '@scrabble-solver/types';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { CellFilterEntry } from 'types';
|
|
4
4
|
|
|
5
5
|
import createRegExp from './createRegExp';
|
|
6
|
+
import resultMatchesCellFilter from './resultMatchesCellFilter';
|
|
6
7
|
|
|
7
8
|
interface GroupedResults {
|
|
8
9
|
matching: Result[];
|
|
@@ -12,7 +13,7 @@ interface GroupedResults {
|
|
|
12
13
|
const groupResults = (
|
|
13
14
|
results: Result[] | undefined,
|
|
14
15
|
query: string,
|
|
15
|
-
cellFilter:
|
|
16
|
+
cellFilter: CellFilterEntry[],
|
|
16
17
|
): GroupedResults | undefined => {
|
|
17
18
|
if (typeof results === 'undefined') {
|
|
18
19
|
return results;
|
|
@@ -23,12 +24,8 @@ const groupResults = (
|
|
|
23
24
|
return results.reduce<GroupedResults>(
|
|
24
25
|
(groupedResults, result) => {
|
|
25
26
|
const matchesQuery = () => regExp.test(result.word);
|
|
26
|
-
const matchesCellFilter = () =>
|
|
27
|
-
cellFilter.every(({ x, y }) => {
|
|
28
|
-
return result.cells.some((cell) => cell.x === x && cell.y === y);
|
|
29
|
-
});
|
|
30
27
|
|
|
31
|
-
if (
|
|
28
|
+
if (resultMatchesCellFilter(result, cellFilter) && matchesQuery()) {
|
|
32
29
|
groupedResults.matching.push(result);
|
|
33
30
|
} else {
|
|
34
31
|
groupedResults.other.push(result);
|
package/src/lib/index.ts
CHANGED
|
@@ -32,6 +32,7 @@ export { default as isUpperCase } from './isUpperCase';
|
|
|
32
32
|
export { default as memoize } from './memoize';
|
|
33
33
|
export { default as noop } from './noop';
|
|
34
34
|
export { default as numberComparator } from './numberComparator';
|
|
35
|
+
export { default as resultMatchesCellFilter } from './resultMatchesCellFilter';
|
|
35
36
|
export { default as reverseComparator } from './reverseComparator';
|
|
36
37
|
export { default as sortResults } from './sortResults';
|
|
37
38
|
export { default as unorderedArraysEqual } from './unorderedArraysEqual';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Result } from '@scrabble-solver/types';
|
|
2
|
+
|
|
3
|
+
import { CellFilterEntry } from 'types';
|
|
4
|
+
|
|
5
|
+
const resultMatchesCellFilter = (result: Result, cellFilter: CellFilterEntry[]) => {
|
|
6
|
+
const excludeFilters = cellFilter.filter((filter) => filter.type === 'exclude');
|
|
7
|
+
const matchesExcludeFilters = excludeFilters.every(({ x, y }) => {
|
|
8
|
+
return result.cells.every((cell) => cell.x !== x || cell.y !== y);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
if (!matchesExcludeFilters) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const includeFilter = cellFilter.filter((filter) => filter.type === 'include');
|
|
16
|
+
const matchesIncludeFilters = includeFilter.every(({ x, y }) => {
|
|
17
|
+
return result.cells.some((cell) => cell.x === x && cell.y === y);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
return matchesExcludeFilters && matchesIncludeFilters;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export default resultMatchesCellFilter;
|
package/src/parameters/index.ts
CHANGED
|
@@ -6,7 +6,6 @@ export const BREAKPOINTS = {
|
|
|
6
6
|
xl: 1400,
|
|
7
7
|
};
|
|
8
8
|
|
|
9
|
-
export const EASE_OUT_CUBIC = 'cubic-bezier(0.33, 1, 0.68, 1)'; // https://easings.net/#easeOutCubic
|
|
10
9
|
export const TRANSITION = 'var(--transition)';
|
|
11
10
|
|
|
12
11
|
export const GITHUB_PROJECT_URL = 'https://github.com/kamilmielnik/scrabble-solver';
|
|
@@ -128,11 +127,3 @@ export const RESULTS_HEADER_HEIGHT = RESULTS_ITEM_HEIGHT;
|
|
|
128
127
|
export const SOLVER_COLUMN_WIDTH = 580;
|
|
129
128
|
|
|
130
129
|
export const TEXT_INPUT_HEIGHT = 40;
|
|
131
|
-
|
|
132
|
-
export const TILE_APPEAR_DURATION = 200;
|
|
133
|
-
|
|
134
|
-
export const TILE_APPEAR_KEYFRAMES = [
|
|
135
|
-
{ transform: 'translateY(0)' },
|
|
136
|
-
{ transform: 'translateY(10%)', offset: 0.5 },
|
|
137
|
-
{ transform: 'translateY(0)' },
|
|
138
|
-
];
|
package/src/state/sagas.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { findWordDefinitions, solve, verify, visit } from 'sdk';
|
|
|
12
12
|
import { initialize, reset } from './actions';
|
|
13
13
|
import {
|
|
14
14
|
selectBoard,
|
|
15
|
-
|
|
15
|
+
selectCellFilter,
|
|
16
16
|
selectCharacters,
|
|
17
17
|
selectConfig,
|
|
18
18
|
selectDictionary,
|
|
@@ -56,10 +56,10 @@ export function* rootSaga(): AnyGenerator {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
function* onCellValueChange({ payload }: PayloadAction<{ value: string; x: number; y: number }>): AnyGenerator {
|
|
59
|
-
const
|
|
59
|
+
const filter = yield select((state) => selectCellFilter(state, payload));
|
|
60
60
|
|
|
61
|
-
if (
|
|
62
|
-
yield put(cellFilterSlice.actions.
|
|
61
|
+
if (filter) {
|
|
62
|
+
yield put(cellFilterSlice.actions.cancel(payload));
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
yield put(resultsSlice.actions.changeResultCandidate(null));
|
package/src/state/selectors.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
getRemainingTiles,
|
|
13
13
|
getRemainingTilesGroups,
|
|
14
14
|
groupResults,
|
|
15
|
+
resultMatchesCellFilter,
|
|
15
16
|
sortResults,
|
|
16
17
|
unorderedArraysEqual,
|
|
17
18
|
} from 'lib';
|
|
@@ -75,8 +76,8 @@ export const selectConfig = createSelector([selectGame, selectLocale], getConfig
|
|
|
75
76
|
|
|
76
77
|
export const selectFilteredCells = selectCellFilterRoot;
|
|
77
78
|
|
|
78
|
-
export const
|
|
79
|
-
return cellFilter.
|
|
79
|
+
export const selectCellFilter = createSelector([selectFilteredCells, selectPoint], (cellFilter, { x, y }) => {
|
|
80
|
+
return cellFilter.find((cell) => cell.x === x && cell.y === y);
|
|
80
81
|
});
|
|
81
82
|
|
|
82
83
|
export const selectCellIsValid = createSelector([selectConfig, selectCell], (config, cell) => {
|
|
@@ -106,7 +107,7 @@ export const selectResults = createSelector([selectGroupedResults], (groupedResu
|
|
|
106
107
|
|
|
107
108
|
export const selectIsResultMatching = createSelector(
|
|
108
109
|
[selectResults, selectResultsQuery, selectFilteredCells, selectResultIndex],
|
|
109
|
-
(results, query,
|
|
110
|
+
(results, query, cellFilter, index) => {
|
|
110
111
|
if (!results) {
|
|
111
112
|
return false;
|
|
112
113
|
}
|
|
@@ -118,11 +119,7 @@ export const selectIsResultMatching = createSelector(
|
|
|
118
119
|
return false;
|
|
119
120
|
}
|
|
120
121
|
|
|
121
|
-
|
|
122
|
-
return filteredCells.every(({ x, y }) => result.cells.some((cell) => cell.x === x && cell.y === y));
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return true;
|
|
122
|
+
return resultMatchesCellFilter(result, cellFilter);
|
|
126
123
|
},
|
|
127
124
|
);
|
|
128
125
|
|