@scrabble-solver/scrabble-solver 2.13.8 → 2.13.10
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/_app.js +1 -1
- package/.next/server/pages/_error.js +1 -1
- package/.next/server/pages/api/solve.js +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/{7zESQYo9UAqNh9LV0b7Sd → 0kOqO_aASkcT2xjhiptyo}/_buildManifest.js +1 -1
- package/.next/static/chunks/pages/{404-63b972b24be99c62.js → 404-b447c5ca188dd7c1.js} +1 -1
- package/.next/static/chunks/pages/_app-0bbddaa93fde16ea.js +17 -0
- package/.next/static/chunks/pages/index-24b84719cf22731c.js +1 -0
- package/.next/static/css/841a5b5f0b2fb131.css +2 -0
- package/.next/trace +44 -44
- package/LICENSE +1 -1
- package/package.json +10 -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/Results/Results.tsx +5 -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 +7 -13
- package/src/lib/index.ts +2 -0
- package/src/lib/resultMatchesCellFilter.ts +23 -0
- package/src/lib/sortGroupedResults.ts +22 -0
- package/src/parameters/index.ts +0 -9
- package/src/state/sagas.ts +4 -4
- package/src/state/selectors.ts +13 -13
- package/src/state/slices/cellFilterInitialState.ts +2 -2
- package/src/state/slices/cellFilterSlice.ts +29 -4
- package/src/types/index.ts +18 -1
- package/.next/static/chunks/pages/_app-a2848b7efa6bb6b0.js +0 -17
- package/.next/static/chunks/pages/index-7b73be2915cc7099.js +0 -1
- package/.next/static/css/b37850c8d5270d91.css +0 -2
- /package/.next/static/{7zESQYo9UAqNh9LV0b7Sd → 0kOqO_aASkcT2xjhiptyo}/_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.10",
|
|
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.10",
|
|
34
|
+
"@scrabble-solver/constants": "^2.13.10",
|
|
35
|
+
"@scrabble-solver/dictionaries": "^2.13.10",
|
|
36
|
+
"@scrabble-solver/logger": "^2.13.10",
|
|
37
|
+
"@scrabble-solver/solver": "^2.13.10",
|
|
38
|
+
"@scrabble-solver/types": "^2.13.10",
|
|
39
|
+
"@scrabble-solver/word-definitions": "^2.13.10",
|
|
40
40
|
"classnames": "^2.5.1",
|
|
41
41
|
"env-cmd": "^10.1.0",
|
|
42
42
|
"include-media": "^2.0.0",
|
|
@@ -52,6 +52,7 @@
|
|
|
52
52
|
"react-window": "^1.8.10",
|
|
53
53
|
"redux-saga": "^1.3.0",
|
|
54
54
|
"store2": "^2.14.3",
|
|
55
|
+
"use-debounce": "^10.0.1",
|
|
55
56
|
"workbox-expiration": "^7.1.0",
|
|
56
57
|
"workbox-precaching": "^7.1.0",
|
|
57
58
|
"workbox-routing": "^7.1.0",
|
|
@@ -72,5 +73,5 @@
|
|
|
72
73
|
"@types/redux-saga": "^0.10.5",
|
|
73
74
|
"sass": "^1.77.5"
|
|
74
75
|
},
|
|
75
|
-
"gitHead": "
|
|
76
|
+
"gitHead": "ebc7207c650ab1fdbb0bca3df53a445eaa6de268"
|
|
76
77
|
}
|
|
@@ -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 {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import classNames from 'classnames';
|
|
2
2
|
import { FunctionComponent, useEffect, useMemo, useState } from 'react';
|
|
3
3
|
import { FixedSizeList } from 'react-window';
|
|
4
|
+
import { useDebounce } from 'use-debounce';
|
|
4
5
|
|
|
5
6
|
import { useAppLayout, useLatest } from 'hooks';
|
|
6
7
|
import { LOCALE_FEATURES } from 'i18n';
|
|
@@ -32,6 +33,8 @@ interface Props {
|
|
|
32
33
|
highlightedIndex?: number;
|
|
33
34
|
}
|
|
34
35
|
|
|
36
|
+
const IS_LOADING_DEBOUNCE = 100;
|
|
37
|
+
|
|
35
38
|
const Results: FunctionComponent<Props> = ({ callbacks, className, highlightedIndex }) => {
|
|
36
39
|
const translate = useTranslate();
|
|
37
40
|
const { resultsHeight, resultsWidth } = useAppLayout();
|
|
@@ -39,6 +42,7 @@ const Results: FunctionComponent<Props> = ({ callbacks, className, highlightedIn
|
|
|
39
42
|
const { direction } = LOCALE_FEATURES[locale];
|
|
40
43
|
const results = useTypedSelector(selectResults);
|
|
41
44
|
const isLoading = useTypedSelector(selectIsLoading);
|
|
45
|
+
const [isLoadingDebounced] = useDebounce(isLoading, IS_LOADING_DEBOUNCE);
|
|
42
46
|
const isOutdated = useTypedSelector(selectAreResultsOutdated);
|
|
43
47
|
const error = useTypedSelector(selectSolveError);
|
|
44
48
|
const itemData = useMemo(() => ({ ...callbacks, highlightedIndex, results }), [callbacks, highlightedIndex, results]);
|
|
@@ -128,7 +132,7 @@ const Results: FunctionComponent<Props> = ({ callbacks, className, highlightedIn
|
|
|
128
132
|
|
|
129
133
|
{showInput && <ResultsInput className={styles.input} />}
|
|
130
134
|
|
|
131
|
-
{
|
|
135
|
+
{isLoadingDebounced && <Loading />}
|
|
132
136
|
</div>
|
|
133
137
|
);
|
|
134
138
|
};
|
|
@@ -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,18 +1,14 @@
|
|
|
1
1
|
import { Result } from '@scrabble-solver/types';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { CellFilterEntry, GroupedResults } from 'types';
|
|
4
4
|
|
|
5
5
|
import createRegExp from './createRegExp';
|
|
6
|
-
|
|
7
|
-
interface GroupedResults {
|
|
8
|
-
matching: Result[];
|
|
9
|
-
other: Result[];
|
|
10
|
-
}
|
|
6
|
+
import resultMatchesCellFilter from './resultMatchesCellFilter';
|
|
11
7
|
|
|
12
8
|
const groupResults = (
|
|
13
9
|
results: Result[] | undefined,
|
|
14
10
|
query: string,
|
|
15
|
-
cellFilter:
|
|
11
|
+
cellFilter: CellFilterEntry[],
|
|
16
12
|
): GroupedResults | undefined => {
|
|
17
13
|
if (typeof results === 'undefined') {
|
|
18
14
|
return results;
|
|
@@ -20,15 +16,11 @@ const groupResults = (
|
|
|
20
16
|
|
|
21
17
|
const regExp = createRegExp(query);
|
|
22
18
|
|
|
23
|
-
|
|
19
|
+
const { matching, other } = results.reduce<GroupedResults>(
|
|
24
20
|
(groupedResults, result) => {
|
|
25
21
|
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
22
|
|
|
31
|
-
if (
|
|
23
|
+
if (resultMatchesCellFilter(result, cellFilter) && matchesQuery()) {
|
|
32
24
|
groupedResults.matching.push(result);
|
|
33
25
|
} else {
|
|
34
26
|
groupedResults.other.push(result);
|
|
@@ -38,6 +30,8 @@ const groupResults = (
|
|
|
38
30
|
},
|
|
39
31
|
{ matching: [], other: [] },
|
|
40
32
|
);
|
|
33
|
+
|
|
34
|
+
return { matching, other };
|
|
41
35
|
};
|
|
42
36
|
|
|
43
37
|
export default groupResults;
|
package/src/lib/index.ts
CHANGED
|
@@ -32,7 +32,9 @@ 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';
|
|
37
|
+
export { default as sortGroupedResults } from './sortGroupedResults';
|
|
36
38
|
export { default as sortResults } from './sortResults';
|
|
37
39
|
export { default as unorderedArraysEqual } from './unorderedArraysEqual';
|
|
38
40
|
export { default as zipCharactersAndTiles } from './zipCharactersAndTiles';
|
|
@@ -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;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Locale } from '@scrabble-solver/types';
|
|
2
|
+
|
|
3
|
+
import { GroupedResults, Sort } from 'types';
|
|
4
|
+
|
|
5
|
+
import sortResults from './sortResults';
|
|
6
|
+
|
|
7
|
+
const sortGroupedResults = (
|
|
8
|
+
results: GroupedResults | undefined,
|
|
9
|
+
sort: Sort,
|
|
10
|
+
locale: Locale,
|
|
11
|
+
): GroupedResults | undefined => {
|
|
12
|
+
if (typeof results === 'undefined') {
|
|
13
|
+
return results;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
matching: sortResults(results.matching, sort, locale) ?? [],
|
|
18
|
+
other: sortResults(results.other, sort, locale) ?? [],
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default sortGroupedResults;
|
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));
|