@scrabble-solver/scrabble-solver 2.16.1 → 2.16.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 +6 -6
- package/.next/cache/.previewinfo +1 -1
- package/.next/cache/.rscinfo +1 -1
- package/.next/cache/.tsbuildinfo +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/client-production/index.pack.old +0 -0
- package/.next/cache/webpack/edge-server-production/index.pack +0 -0
- package/.next/cache/webpack/edge-server-production/index.pack.old +0 -0
- package/.next/cache/webpack/server-production/0.pack +0 -0
- package/.next/cache/webpack/server-production/index.pack +0 -0
- package/.next/cache/webpack/server-production/index.pack.old +0 -0
- package/.next/dev/build-manifest.json +31 -0
- package/.next/dev/cache/webpack/client-development/0.pack.gz +0 -0
- package/.next/dev/cache/webpack/client-development/index.pack.gz +0 -0
- package/.next/dev/logs/next-development.log +6 -0
- package/.next/dev/react-loadable-manifest.json +1 -0
- package/.next/dev/server/middleware-build-manifest.js +31 -0
- package/.next/dev/server/middleware-react-loadable-manifest.js +1 -0
- package/.next/dev/server/next-font-manifest.js +1 -0
- package/.next/dev/server/next-font-manifest.json +1 -0
- package/.next/dev/server/pages/_app.js +4082 -0
- package/.next/dev/server/pages/_error.js +4126 -0
- package/.next/dev/server/pages/index.js +4804 -0
- package/.next/dev/server/pages-manifest.json +6 -0
- package/.next/dev/server/webpack-runtime.js +231 -0
- package/.next/dev/static/chunks/pages/_app.js +5131 -0
- package/.next/dev/static/chunks/webpack.js +1238 -0
- package/.next/dev/static/development/_buildManifest.js +1 -0
- package/.next/dev/static/development/_ssgManifest.js +1 -0
- package/.next/prerender-manifest.json +4 -4
- package/.next/routes-manifest.json +1 -1
- package/.next/server/chunks/712.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/server/pages/not-found.html +1 -1
- package/.next/static/{-2mw8IrOEpCcsQa6oPYhR → a98wTJsZR6CASwFn19QFv}/_buildManifest.js +1 -1
- package/.next/static/chunks/pages/_app-31e560b7b14739d6.js +7 -0
- package/.next/static/chunks/pages/index-35f70f1627836521.js +1 -0
- package/.next/static/css/0af35adb4bab56f0.css +2 -0
- package/.next/static/css/a78e04fe95452380.css +1 -0
- package/.next/trace +21 -21
- package/.next/trace-build +1 -1
- package/package.json +9 -9
- package/src/components/Board/Board.tsx +3 -1
- package/src/components/Board/BoardPure.tsx +3 -0
- package/src/components/Board/components/Cell/Cell.module.scss +6 -0
- package/src/components/Board/components/Cell/Cell.tsx +11 -2
- package/src/components/Board/lib.ts +83 -2
- package/src/components/Board/selectors.ts +20 -1
- package/src/components/Rack/components/RackTile/RackTile.tsx +4 -1
- package/src/components/Tile/TilePure.tsx +20 -18
- package/src/i18n/languages/english.json +3 -0
- package/src/i18n/languages/french.json +3 -0
- package/src/i18n/languages/german.json +3 -0
- package/src/i18n/languages/persian.json +3 -0
- package/src/i18n/languages/polish.json +3 -0
- package/src/i18n/languages/romanian.json +3 -0
- package/src/i18n/languages/spanish.json +3 -0
- package/src/i18n/languages/turkish.json +3 -0
- package/src/modals/RemainingTilesModal/components/Character/Character.module.scss +4 -0
- package/src/modals/RemainingTilesModal/components/Character/Character.tsx +35 -2
- package/src/modals/SettingsModal/SettingsModal.tsx +8 -0
- package/src/modals/SettingsModal/components/AutoGroupTilesSetting/AutoGroupTilesSetting.tsx +4 -4
- package/src/modals/SettingsModal/components/HighlightUnreachableCellsSetting/HighlightUnreachableCellsSetting.module.scss +12 -0
- package/src/modals/SettingsModal/components/HighlightUnreachableCellsSetting/HighlightUnreachableCellsSetting.tsx +51 -0
- package/src/modals/SettingsModal/components/HighlightUnreachableCellsSetting/index.ts +1 -0
- package/src/modals/SettingsModal/components/index.ts +1 -0
- package/src/state/hoveredTile/index.ts +4 -0
- package/src/state/hoveredTile/initialState.ts +5 -0
- package/src/state/hoveredTile/selectors.ts +3 -0
- package/src/state/hoveredTile/slice.ts +16 -0
- package/src/state/hoveredTile/types.ts +3 -0
- package/src/state/index.ts +1 -0
- package/src/state/localStorage.ts +1 -0
- package/src/state/settings/initialState.ts +1 -0
- package/src/state/settings/selectors.ts +5 -0
- package/src/state/settings/slice.ts +5 -0
- package/src/state/settings/types.ts +1 -0
- package/src/state/store.ts +2 -0
- package/src/state/types.ts +2 -0
- package/src/types/index.ts +3 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/.next/static/chunks/pages/_app-9c9aaea7b6881765.js +0 -7
- package/.next/static/chunks/pages/index-e9ea63e97865edcf.js +0 -1
- package/.next/static/css/78726ae7cf7a5497.css +0 -2
- package/.next/static/css/addf6fd4f75742d0.css +0 -1
- /package/.next/static/{-2mw8IrOEpCcsQa6oPYhR → a98wTJsZR6CASwFn19QFv}/_ssgManifest.js +0 -0
package/.next/trace-build
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
[{"name":"run-typescript","duration":
|
|
1
|
+
[{"name":"run-typescript","duration":1895961,"timestamp":7659483428,"id":13,"parentId":1,"tags":{},"startTime":1777629158133,"traceId":"f4a8e837f3a22937"},{"name":"run-webpack","duration":5953008,"timestamp":7661384973,"id":15,"parentId":1,"tags":{},"startTime":1777629160034,"traceId":"f4a8e837f3a22937"},{"name":"static-check","duration":168518,"timestamp":7667411752,"id":2050,"parentId":1,"tags":{},"startTime":1777629166061,"traceId":"f4a8e837f3a22937"},{"name":"static-generation","duration":812958,"timestamp":7667588532,"id":2068,"parentId":1,"tags":{},"startTime":1777629166238,"traceId":"f4a8e837f3a22937"},{"name":"collect-build-traces","duration":3550761,"timestamp":7667580695,"id":2065,"parentId":1,"tags":{},"startTime":1777629166230,"traceId":"f4a8e837f3a22937"},{"name":"telemetry-flush","duration":19,"timestamp":7671132686,"id":2080,"parentId":1,"tags":{},"startTime":1777629169782,"traceId":"f4a8e837f3a22937"},{"name":"next-build","duration":11879532,"timestamp":7659253176,"id":1,"tags":{"buildMode":"default","version":"16.2.4","bundler":"webpack","has-custom-webpack-config":"true","use-build-worker":"false"},"startTime":1777629157902,"traceId":"f4a8e837f3a22937"}]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scrabble-solver/scrabble-solver",
|
|
3
|
-
"version": "2.16.
|
|
3
|
+
"version": "2.16.2",
|
|
4
4
|
"description": "Scrabble Solver 2 - App",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -29,13 +29,13 @@
|
|
|
29
29
|
"@floating-ui/react": "^0.27.19",
|
|
30
30
|
"@kamilmielnik/trie": "^5.0.0",
|
|
31
31
|
"@reduxjs/toolkit": "^2.11.2",
|
|
32
|
-
"@scrabble-solver/configs": "^2.16.
|
|
33
|
-
"@scrabble-solver/constants": "^2.16.
|
|
34
|
-
"@scrabble-solver/dictionaries": "^2.16.
|
|
35
|
-
"@scrabble-solver/logger": "^2.16.
|
|
36
|
-
"@scrabble-solver/solver": "^2.16.
|
|
37
|
-
"@scrabble-solver/types": "^2.16.
|
|
38
|
-
"@scrabble-solver/word-definitions": "^2.16.
|
|
32
|
+
"@scrabble-solver/configs": "^2.16.2",
|
|
33
|
+
"@scrabble-solver/constants": "^2.16.2",
|
|
34
|
+
"@scrabble-solver/dictionaries": "^2.16.2",
|
|
35
|
+
"@scrabble-solver/logger": "^2.16.2",
|
|
36
|
+
"@scrabble-solver/solver": "^2.16.2",
|
|
37
|
+
"@scrabble-solver/types": "^2.16.2",
|
|
38
|
+
"@scrabble-solver/word-definitions": "^2.16.2",
|
|
39
39
|
"classnames": "^2.5.1",
|
|
40
40
|
"env-cmd": "^11.0.0",
|
|
41
41
|
"include-media": "^2.0.0",
|
|
@@ -70,5 +70,5 @@
|
|
|
70
70
|
"@types/redux": "^3.6.31",
|
|
71
71
|
"sass": "^1.99.0"
|
|
72
72
|
},
|
|
73
|
-
"gitHead": "
|
|
73
|
+
"gitHead": "c49ad756f6977190c078ccbc8efee071cf542c28"
|
|
74
74
|
}
|
|
@@ -25,7 +25,7 @@ import styles from './Board.module.scss';
|
|
|
25
25
|
import { BoardPure } from './BoardPure';
|
|
26
26
|
import { Actions, InputPrompt } from './components';
|
|
27
27
|
import { useBoardStyle, useFloatingActions, useFloatingFocus, useFloatingInputPrompt, useGrid } from './hooks';
|
|
28
|
-
import { selectRowsWithCandidate } from './selectors';
|
|
28
|
+
import { selectReachableCells, selectRowsWithCandidate } from './selectors';
|
|
29
29
|
|
|
30
30
|
interface Props {
|
|
31
31
|
className?: string;
|
|
@@ -35,6 +35,7 @@ export const Board: FunctionComponent<Props> = ({ className }) => {
|
|
|
35
35
|
const dispatch = useDispatch();
|
|
36
36
|
const locale = useTypedSelector(selectLocale);
|
|
37
37
|
const rows = useTypedSelector(selectRowsWithCandidate);
|
|
38
|
+
const reachableCells = useTypedSelector(selectReachableCells);
|
|
38
39
|
const inputMode = useTypedSelector(selectInputMode);
|
|
39
40
|
const cellFilters = useTypedSelector(selectCellFilters);
|
|
40
41
|
const showCoordinates = useTypedSelector(selectShowCoordinates);
|
|
@@ -157,6 +158,7 @@ export const Board: FunctionComponent<Props> = ({ className }) => {
|
|
|
157
158
|
coordinatesSize={coordinatesSize}
|
|
158
159
|
direction={LOCALE_FEATURES[locale].direction}
|
|
159
160
|
inputRefs={inputRefs}
|
|
161
|
+
reachableCells={reachableCells}
|
|
160
162
|
ref={ref}
|
|
161
163
|
rows={rows}
|
|
162
164
|
showCoordinates={showCoordinates}
|
|
@@ -28,6 +28,7 @@ interface Props {
|
|
|
28
28
|
coordinatesSize: number;
|
|
29
29
|
direction: 'ltr' | 'rtl';
|
|
30
30
|
inputRefs: RefObject<HTMLInputElement | null>[][];
|
|
31
|
+
reachableCells: boolean[][] | null;
|
|
31
32
|
rows: CellModel[][];
|
|
32
33
|
showCoordinates: ShowCoordinates;
|
|
33
34
|
style?: CSSProperties;
|
|
@@ -48,6 +49,7 @@ const BoardPureBase = forwardRef<HTMLDivElement, Props>(
|
|
|
48
49
|
direction,
|
|
49
50
|
cellFilters,
|
|
50
51
|
inputRefs,
|
|
52
|
+
reachableCells,
|
|
51
53
|
rows,
|
|
52
54
|
showCoordinates,
|
|
53
55
|
style,
|
|
@@ -137,6 +139,7 @@ const BoardPureBase = forwardRef<HTMLDivElement, Props>(
|
|
|
137
139
|
cellRight={x < rows[y].length - 1 ? rows[y][x + 1] : undefined}
|
|
138
140
|
cellTop={y > 0 ? rows[y - 1][x] : undefined}
|
|
139
141
|
inputRef={inputRefs[y][x]}
|
|
142
|
+
isReachable={reachableCells ? reachableCells[y][x] : true}
|
|
140
143
|
key={x}
|
|
141
144
|
size={cellSize}
|
|
142
145
|
onChange={onChange}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EMPTY_CELL } from '@scrabble-solver/constants';
|
|
1
|
+
import { BLANK, EMPTY_CELL } from '@scrabble-solver/constants';
|
|
2
2
|
import { type Cell as CellModel } from '@scrabble-solver/types';
|
|
3
3
|
import classNames from 'classnames';
|
|
4
4
|
import {
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
import {
|
|
15
15
|
selectCellIsValid,
|
|
16
16
|
selectConfig,
|
|
17
|
+
selectHoveredCharacter,
|
|
17
18
|
selectInputMode,
|
|
18
19
|
selectLocale,
|
|
19
20
|
selectTilePoints,
|
|
@@ -33,6 +34,7 @@ interface Props {
|
|
|
33
34
|
cellTop?: CellModel;
|
|
34
35
|
className?: string;
|
|
35
36
|
inputRef: RefObject<HTMLInputElement | null>;
|
|
37
|
+
isReachable?: boolean;
|
|
36
38
|
size: number;
|
|
37
39
|
onChange: ChangeEventHandler<HTMLInputElement>;
|
|
38
40
|
onFocus: (x: number, y: number) => void;
|
|
@@ -46,6 +48,7 @@ export const Cell: FunctionComponent<Props> = ({
|
|
|
46
48
|
cellTop,
|
|
47
49
|
className,
|
|
48
50
|
inputRef,
|
|
51
|
+
isReachable = true,
|
|
49
52
|
size,
|
|
50
53
|
onChange,
|
|
51
54
|
onFocus,
|
|
@@ -57,7 +60,12 @@ export const Cell: FunctionComponent<Props> = ({
|
|
|
57
60
|
const inputMode = useTypedSelector(selectInputMode);
|
|
58
61
|
const points = useTypedSelector((state) => selectTilePoints(state, cell.tile));
|
|
59
62
|
const isValid = useTypedSelector((state) => selectCellIsValid(state, cell));
|
|
63
|
+
const hoveredCharacter = useTypedSelector(selectHoveredCharacter);
|
|
60
64
|
const isEmpty = tile.character === EMPTY_CELL;
|
|
65
|
+
const isHoverMatch =
|
|
66
|
+
!isEmpty &&
|
|
67
|
+
hoveredCharacter !== null &&
|
|
68
|
+
(hoveredCharacter === BLANK ? tile.isBlank : !tile.isBlank && tile.character === hoveredCharacter);
|
|
61
69
|
|
|
62
70
|
const handleFocus: FocusEventHandler<HTMLInputElement> = useCallback(
|
|
63
71
|
(event) => {
|
|
@@ -106,9 +114,10 @@ export const Cell: FunctionComponent<Props> = ({
|
|
|
106
114
|
[styles.sharpTopRight]: cellTop?.hasTile() || cellRight?.hasTile(),
|
|
107
115
|
[styles.sharpBottomLeft]: cellBottom?.hasTile() || cellLeft?.hasTile(),
|
|
108
116
|
[styles.sharpBottomRight]: cellBottom?.hasTile() || cellRight?.hasTile(),
|
|
117
|
+
[styles.unreachable]: !isReachable,
|
|
109
118
|
})}
|
|
110
119
|
character={isEmpty ? undefined : tile.character}
|
|
111
|
-
highlighted={cell.isCandidate()}
|
|
120
|
+
highlighted={cell.isCandidate() || isHoverMatch}
|
|
112
121
|
inputRef={inputRef}
|
|
113
122
|
isBlank={tile.isBlank}
|
|
114
123
|
isValid={isValid}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { BONUS_WORD } from '@scrabble-solver/constants';
|
|
2
|
-
import { type Bonus } from '@scrabble-solver/types';
|
|
2
|
+
import { type Board, type Bonus, type Config } from '@scrabble-solver/types';
|
|
3
3
|
|
|
4
4
|
import { COLOR_BONUS_CHARACTER, COLOR_BONUS_CHARACTER_MULTIPLIER, COLOR_BONUS_WORD } from '@/parameters';
|
|
5
|
-
import { type Point } from '@/types';
|
|
5
|
+
import { type Direction, type Point } from '@/types';
|
|
6
6
|
|
|
7
7
|
export const createGrid = <T>(width: number, height: number, getInitialValue: (x: number, y: number) => T): T[][] => {
|
|
8
8
|
return Array.from({ length: height }).map((_row, y) => {
|
|
@@ -35,3 +35,84 @@ export const getPositionInGrid = <T>(grid: T[][], constraint: (value: T) => bool
|
|
|
35
35
|
|
|
36
36
|
return undefined;
|
|
37
37
|
};
|
|
38
|
+
|
|
39
|
+
export const getReachableCells = (config: Config, board: Board, charactersCount: number): boolean[][] => {
|
|
40
|
+
const { boardHeight, boardWidth } = config;
|
|
41
|
+
const reachable = createGrid(boardWidth, boardHeight, () => false);
|
|
42
|
+
|
|
43
|
+
if (charactersCount === 0) {
|
|
44
|
+
return reachable;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const isBoardEmpty = board.isEmpty();
|
|
48
|
+
const center = board.center;
|
|
49
|
+
|
|
50
|
+
const isAnchor = (x: number, y: number, playDirection: Direction): boolean => {
|
|
51
|
+
if (isBoardEmpty) {
|
|
52
|
+
return x === center.x && y === center.y;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (playDirection === 'horizontal') {
|
|
56
|
+
const above = y > 0 && !board.rows[y - 1][x].isEmpty;
|
|
57
|
+
const below = y < boardHeight - 1 && !board.rows[y + 1][x].isEmpty;
|
|
58
|
+
return above || below;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const left = x > 0 && !board.rows[y][x - 1].isEmpty;
|
|
62
|
+
const right = x < boardWidth - 1 && !board.rows[y][x + 1].isEmpty;
|
|
63
|
+
return left || right;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const isReachableInDirection = (x: number, y: number, direction: Direction): boolean => {
|
|
67
|
+
const isHorizontal = direction === 'horizontal';
|
|
68
|
+
const length = isHorizontal ? boardWidth : boardHeight;
|
|
69
|
+
const startOffset = isHorizontal ? x : y;
|
|
70
|
+
const cellAt = (offset: number) => (isHorizontal ? board.rows[y][offset] : board.rows[offset][x]);
|
|
71
|
+
|
|
72
|
+
if (isAnchor(x, y, direction)) {
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
for (const step of [-1, 1] as const) {
|
|
77
|
+
let newTiles = 1;
|
|
78
|
+
|
|
79
|
+
for (let offset = startOffset + step; offset >= 0 && offset < length; offset += step) {
|
|
80
|
+
const cell = cellAt(offset);
|
|
81
|
+
|
|
82
|
+
if (cell.hasTile()) {
|
|
83
|
+
if (newTiles <= charactersCount) {
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
newTiles += 1;
|
|
90
|
+
|
|
91
|
+
if (newTiles > charactersCount) {
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (isAnchor(cell.x, cell.y, direction)) {
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return false;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
for (let y = 0; y < boardHeight; ++y) {
|
|
105
|
+
for (let x = 0; x < boardWidth; ++x) {
|
|
106
|
+
if (board.rows[y][x].hasTile()) {
|
|
107
|
+
reachable[y][x] = true;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (isReachableInDirection(x, y, 'horizontal') || isReachableInDirection(x, y, 'vertical')) {
|
|
112
|
+
reachable[y][x] = true;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return reachable;
|
|
118
|
+
};
|
|
@@ -1,8 +1,27 @@
|
|
|
1
1
|
import { createSelector } from '@reduxjs/toolkit';
|
|
2
2
|
|
|
3
3
|
import { findCell } from '@/lib';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
selectBoard,
|
|
6
|
+
selectCharacters,
|
|
7
|
+
selectConfig,
|
|
8
|
+
selectHighlightUnreachableCells,
|
|
9
|
+
selectResultCandidateCells,
|
|
10
|
+
} from '@/state';
|
|
11
|
+
|
|
12
|
+
import { getReachableCells } from './lib';
|
|
5
13
|
|
|
6
14
|
export const selectRowsWithCandidate = createSelector([selectBoard, selectResultCandidateCells], (board, cells) => {
|
|
7
15
|
return board.rows.map((row, y) => row.map((cell, x) => findCell(cells, x, y) || cell));
|
|
8
16
|
});
|
|
17
|
+
|
|
18
|
+
export const selectReachableCells = createSelector(
|
|
19
|
+
[selectConfig, selectBoard, selectCharacters, selectHighlightUnreachableCells],
|
|
20
|
+
(config, board, characters, highlightUnreachableCells): boolean[][] | null => {
|
|
21
|
+
if (!highlightUnreachableCells || characters.length === 0) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return getReachableCells(config, board, characters.length);
|
|
26
|
+
},
|
|
27
|
+
);
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
selectCharacterIsValid,
|
|
22
22
|
selectCharacterPoints,
|
|
23
23
|
selectConfig,
|
|
24
|
+
selectHoveredCharacter,
|
|
24
25
|
selectInputMode,
|
|
25
26
|
selectLocale,
|
|
26
27
|
useTranslate,
|
|
@@ -63,6 +64,8 @@ export const RackTile: FunctionComponent<Props> = ({
|
|
|
63
64
|
const inputMode = useTypedSelector(selectInputMode);
|
|
64
65
|
const points = useTypedSelector((state) => selectCharacterPoints(state, character));
|
|
65
66
|
const isValid = useTypedSelector((state) => selectCharacterIsValid(state, character));
|
|
67
|
+
const hoveredCharacter = useTypedSelector(selectHoveredCharacter);
|
|
68
|
+
const isHoverMatch = character !== null && hoveredCharacter !== null && hoveredCharacter === character;
|
|
66
69
|
|
|
67
70
|
const handleFocus: FocusEventHandler<HTMLInputElement> = useCallback(
|
|
68
71
|
(event) => {
|
|
@@ -141,7 +144,7 @@ export const RackTile: FunctionComponent<Props> = ({
|
|
|
141
144
|
autoFocus={inputMode === 'keyboard' && index === 0}
|
|
142
145
|
className={classNames(styles.rackTile, className)}
|
|
143
146
|
character={character === null ? undefined : character}
|
|
144
|
-
highlighted={tile !== null}
|
|
147
|
+
highlighted={tile !== null || isHoverMatch}
|
|
145
148
|
inputRef={inputRef}
|
|
146
149
|
isBlank={character === BLANK}
|
|
147
150
|
isValid={isValid}
|
|
@@ -82,24 +82,26 @@ const TilePureBase: FunctionComponent<Props> = ({
|
|
|
82
82
|
>
|
|
83
83
|
{character || placeholder}
|
|
84
84
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
85
|
+
{!disabled && (
|
|
86
|
+
<input
|
|
87
|
+
aria-label={ariaLabel}
|
|
88
|
+
autoCapitalize="none"
|
|
89
|
+
autoComplete="off"
|
|
90
|
+
autoCorrect="off"
|
|
91
|
+
autoFocus={autoFocus}
|
|
92
|
+
className={styles.input}
|
|
93
|
+
disabled={disabled}
|
|
94
|
+
ref={inputRef}
|
|
95
|
+
spellCheck={false}
|
|
96
|
+
tabIndex={tabIndex}
|
|
97
|
+
value={character || ''}
|
|
98
|
+
onChange={onChange}
|
|
99
|
+
onFocus={onFocus}
|
|
100
|
+
onKeyDown={onKeyDown}
|
|
101
|
+
onMouseDown={onMouseDown}
|
|
102
|
+
onTouchStart={onTouchStart}
|
|
103
|
+
/>
|
|
104
|
+
)}
|
|
103
105
|
|
|
104
106
|
{canShowPoints && (
|
|
105
107
|
<span className={styles.points} style={pointsStyle}>
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
"common.consonants": "Consonants",
|
|
14
14
|
"common.loading": "Loading",
|
|
15
15
|
"common.next": "Next",
|
|
16
|
+
"common.off": "Off",
|
|
17
|
+
"common.on": "On",
|
|
16
18
|
"common.points": "Points",
|
|
17
19
|
"common.previous": "Previous",
|
|
18
20
|
"common.space": "Spacebar",
|
|
@@ -64,6 +66,7 @@
|
|
|
64
66
|
"settings.autoGroupTiles.right": "On the right",
|
|
65
67
|
"settings.autoGroupTiles.null": "Do not group",
|
|
66
68
|
"settings.game": "Game",
|
|
69
|
+
"settings.highlightUnreachableCells": "Highlight unreachable cells",
|
|
67
70
|
"settings.inputMode": "Input mode",
|
|
68
71
|
"settings.inputMode.keyboard": "Keyboard",
|
|
69
72
|
"settings.inputMode.touchscreen": "Touchscreen",
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
"common.consonants": "Consonnes",
|
|
14
14
|
"common.loading": "Chargement",
|
|
15
15
|
"common.next": "Suivant",
|
|
16
|
+
"common.off": "Désactivé",
|
|
17
|
+
"common.on": "Activé",
|
|
16
18
|
"common.points": "Points",
|
|
17
19
|
"common.previous": "Précédent",
|
|
18
20
|
"common.space": "Barre d'espace",
|
|
@@ -64,6 +66,7 @@
|
|
|
64
66
|
"settings.autoGroupTiles.right": "Vers la gauche",
|
|
65
67
|
"settings.autoGroupTiles.null": "Ne pas grouper",
|
|
66
68
|
"settings.game": "Jeu",
|
|
69
|
+
"settings.highlightUnreachableCells": "Mettre en évidence les cases inatteignables",
|
|
67
70
|
"settings.inputMode": "Mode de saisie",
|
|
68
71
|
"settings.inputMode.keyboard": "Clavier",
|
|
69
72
|
"settings.inputMode.touchscreen": "Écran tactile",
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
"common.consonants": "Konsonanten",
|
|
14
14
|
"common.loading": "Laden",
|
|
15
15
|
"common.next": "Weiter",
|
|
16
|
+
"common.off": "Aus",
|
|
17
|
+
"common.on": "Ein",
|
|
16
18
|
"common.points": "Punkte",
|
|
17
19
|
"common.previous": "Zurück",
|
|
18
20
|
"common.space": "Leertaste",
|
|
@@ -64,6 +66,7 @@
|
|
|
64
66
|
"settings.autoGroupTiles.right": "Rechte Seite",
|
|
65
67
|
"settings.autoGroupTiles.null": "Nicht gruppieren",
|
|
66
68
|
"settings.game": "Spiel",
|
|
69
|
+
"settings.highlightUnreachableCells": "Unerreichbare Felder hervorheben",
|
|
67
70
|
"settings.inputMode": "Eingabemodus",
|
|
68
71
|
"settings.inputMode.keyboard": "Tastatur",
|
|
69
72
|
"settings.inputMode.touchscreen": "Touchscreen",
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
"common.consonants": "حروف صامت",
|
|
14
14
|
"common.loading": "در حال بارگزاری",
|
|
15
15
|
"common.next": "بعدی",
|
|
16
|
+
"common.off": "خاموش",
|
|
17
|
+
"common.on": "روشن",
|
|
16
18
|
"common.points": "امتیازات",
|
|
17
19
|
"common.previous": "قبلی",
|
|
18
20
|
"common.space": "دکمه اسپیس",
|
|
@@ -64,6 +66,7 @@
|
|
|
64
66
|
"settings.autoGroupTiles.right": "در سمت راست",
|
|
65
67
|
"settings.autoGroupTiles.null": "کنار هم قرار نده",
|
|
66
68
|
"settings.game": "بازی",
|
|
69
|
+
"settings.highlightUnreachableCells": "خانههای غیرقابل دسترس را برجسته کن",
|
|
67
70
|
"settings.inputMode": "حالت ورودی",
|
|
68
71
|
"settings.inputMode.keyboard": "صفحه کلید",
|
|
69
72
|
"settings.inputMode.touchscreen": "صفحه لمسی",
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
"common.consonants": "Spółgłoski",
|
|
14
14
|
"common.loading": "Ładowanie",
|
|
15
15
|
"common.next": "Następne",
|
|
16
|
+
"common.off": "Wyłączone",
|
|
17
|
+
"common.on": "Włączone",
|
|
16
18
|
"common.points": "Punkty",
|
|
17
19
|
"common.previous": "Poprzednie",
|
|
18
20
|
"common.space": "Spacja",
|
|
@@ -64,6 +66,7 @@
|
|
|
64
66
|
"settings.autoGroupTiles.right": "Po prawej",
|
|
65
67
|
"settings.autoGroupTiles.null": "Nie grupuj",
|
|
66
68
|
"settings.game": "Gra",
|
|
69
|
+
"settings.highlightUnreachableCells": "Wyróżnij nieosiągalne pola",
|
|
67
70
|
"settings.inputMode": "Tryb wpisywania",
|
|
68
71
|
"settings.inputMode.keyboard": "Klawiatura",
|
|
69
72
|
"settings.inputMode.touchscreen": "Ekran dotykowy",
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
"common.consonants": "Consoane",
|
|
14
14
|
"common.loading": "Incarcare",
|
|
15
15
|
"common.next": "Pasul urmator",
|
|
16
|
+
"common.off": "Dezactivat",
|
|
17
|
+
"common.on": "Activat",
|
|
16
18
|
"common.points": "Puncte",
|
|
17
19
|
"common.previous": "Pasul anterior",
|
|
18
20
|
"common.space": "Bara de spațiu",
|
|
@@ -64,6 +66,7 @@
|
|
|
64
66
|
"settings.autoGroupTiles.right": "In partea dreapta",
|
|
65
67
|
"settings.autoGroupTiles.null": "Nu se grupeaza",
|
|
66
68
|
"settings.game": "Joc",
|
|
69
|
+
"settings.highlightUnreachableCells": "Evidențiază celulele inaccesibile",
|
|
67
70
|
"settings.inputMode": "Mod introducere",
|
|
68
71
|
"settings.inputMode.keyboard": "Tastatura",
|
|
69
72
|
"settings.inputMode.touchscreen": "Ecran",
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
"common.consonants": "Consonantes",
|
|
14
14
|
"common.loading": "Cargando",
|
|
15
15
|
"common.next": "Siguiente",
|
|
16
|
+
"common.off": "Desactivado",
|
|
17
|
+
"common.on": "Activado",
|
|
16
18
|
"common.points": "Puntos",
|
|
17
19
|
"common.previous": "Anterior",
|
|
18
20
|
"common.space": "Barra espaciadora",
|
|
@@ -64,6 +66,7 @@
|
|
|
64
66
|
"settings.autoGroupTiles.right": "A la derecha",
|
|
65
67
|
"settings.autoGroupTiles.null": "No agrupar",
|
|
66
68
|
"settings.game": "Juego",
|
|
69
|
+
"settings.highlightUnreachableCells": "Resaltar casillas inalcanzables",
|
|
67
70
|
"settings.inputMode": "Modo de entrada",
|
|
68
71
|
"settings.inputMode.keyboard": "Teclado",
|
|
69
72
|
"settings.inputMode.touchscreen": "Pantalla táctil",
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
"common.consonants": "Ünlü harfler",
|
|
14
14
|
"common.loading": "Yükleniyor",
|
|
15
15
|
"common.next": "Sonraki",
|
|
16
|
+
"common.off": "Kapalı",
|
|
17
|
+
"common.on": "Açık",
|
|
16
18
|
"common.points": "Puanlar",
|
|
17
19
|
"common.previous": "Öncesi",
|
|
18
20
|
"common.space": "Boşluk çubuğu",
|
|
@@ -64,6 +66,7 @@
|
|
|
64
66
|
"settings.autoGroupTiles.right": "Sağdaki",
|
|
65
67
|
"settings.autoGroupTiles.null": "Gruplamayın",
|
|
66
68
|
"settings.game": "Oyun",
|
|
69
|
+
"settings.highlightUnreachableCells": "Ulaşılamayan kareleri vurgula",
|
|
67
70
|
"settings.inputMode": "Giriş Methodu",
|
|
68
71
|
"settings.inputMode.keyboard": "Klavye",
|
|
69
72
|
"settings.inputMode.touchscreen": "Dokunmatik ekran",
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import { BLANK } from '@scrabble-solver/constants';
|
|
2
2
|
import classNames from 'classnames';
|
|
3
|
-
import { type FunctionComponent } from 'react';
|
|
3
|
+
import { type FunctionComponent, useCallback, useEffect } from 'react';
|
|
4
|
+
import { useDispatch } from 'react-redux';
|
|
4
5
|
|
|
5
6
|
import { Progress, Tile } from '@/components';
|
|
6
7
|
import { LOCALE_FEATURES } from '@/i18n';
|
|
7
8
|
import { REMAINING_TILES_TILE_SIZE } from '@/parameters';
|
|
8
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
hoveredTileSlice,
|
|
11
|
+
selectCharacterPoints,
|
|
12
|
+
selectHoveredCharacter,
|
|
13
|
+
selectLocale,
|
|
14
|
+
useTypedSelector,
|
|
15
|
+
} from '@/state';
|
|
9
16
|
import { type RemainingTile } from '@/types';
|
|
10
17
|
|
|
11
18
|
import styles from './Character.module.scss';
|
|
@@ -15,7 +22,9 @@ interface Props {
|
|
|
15
22
|
}
|
|
16
23
|
|
|
17
24
|
export const Character: FunctionComponent<Props> = ({ tile }) => {
|
|
25
|
+
const dispatch = useDispatch();
|
|
18
26
|
const locale = useTypedSelector(selectLocale);
|
|
27
|
+
const hoveredCharacter = useTypedSelector(selectHoveredCharacter);
|
|
19
28
|
const { direction } = LOCALE_FEATURES[locale];
|
|
20
29
|
const { character, count, usedCount } = tile;
|
|
21
30
|
|
|
@@ -27,19 +36,43 @@ export const Character: FunctionComponent<Props> = ({ tile }) => {
|
|
|
27
36
|
const points = useTypedSelector((state) => selectCharacterPoints(state, character));
|
|
28
37
|
const current = direction === 'ltr' ? remainingCount : count;
|
|
29
38
|
const total = direction === 'ltr' ? count : remainingCount;
|
|
39
|
+
const isUsed = usedCount > 0;
|
|
40
|
+
const isHovered = isUsed && hoveredCharacter === character;
|
|
41
|
+
|
|
42
|
+
const handleMouseEnter = useCallback(() => {
|
|
43
|
+
if (!isUsed) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
dispatch(hoveredTileSlice.actions.set(character));
|
|
47
|
+
}, [character, dispatch, isUsed]);
|
|
48
|
+
|
|
49
|
+
const handleMouseLeave = useCallback(() => {
|
|
50
|
+
dispatch(hoveredTileSlice.actions.clear());
|
|
51
|
+
}, [dispatch]);
|
|
52
|
+
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
return () => {
|
|
55
|
+
dispatch(hoveredTileSlice.actions.clear());
|
|
56
|
+
};
|
|
57
|
+
}, [dispatch]);
|
|
30
58
|
|
|
31
59
|
return (
|
|
32
60
|
<div
|
|
33
61
|
className={classNames(styles.character, {
|
|
34
62
|
[styles.finished]: remainingCount <= 0,
|
|
35
63
|
[styles.overused]: remainingCount < 0,
|
|
64
|
+
[styles.unused]: !isUsed,
|
|
36
65
|
})}
|
|
66
|
+
data-testid={character === BLANK ? 'remaining-tile-blank' : `remaining-tile-${character}`}
|
|
67
|
+
onMouseEnter={handleMouseEnter}
|
|
68
|
+
onMouseLeave={handleMouseLeave}
|
|
37
69
|
>
|
|
38
70
|
<Tile
|
|
39
71
|
aria-label={character}
|
|
40
72
|
character={character}
|
|
41
73
|
className={styles.tile}
|
|
42
74
|
disabled
|
|
75
|
+
highlighted={isHovered}
|
|
43
76
|
isBlank={character === BLANK}
|
|
44
77
|
isValid={remainingCount >= 0}
|
|
45
78
|
points={points}
|
|
@@ -7,6 +7,7 @@ import { useTranslate } from '@/state';
|
|
|
7
7
|
import {
|
|
8
8
|
AutoGroupTilesSetting,
|
|
9
9
|
ConfigSetting,
|
|
10
|
+
HighlightUnreachableCellsSetting,
|
|
10
11
|
InputModeSetting,
|
|
11
12
|
LocaleSetting,
|
|
12
13
|
ShowCoordinatesSetting,
|
|
@@ -50,6 +51,13 @@ const SettingsModalBase: FunctionComponent<Props> = ({ className, isOpen, onClos
|
|
|
50
51
|
<Modal.Section label={translate('settings.removeCellFilters')} title={translate('settings.removeCellFilters')}>
|
|
51
52
|
<RemoveCellFiltersSetting disabled={!isOpen} />
|
|
52
53
|
</Modal.Section>
|
|
54
|
+
|
|
55
|
+
<Modal.Section
|
|
56
|
+
label={translate('settings.highlightUnreachableCells')}
|
|
57
|
+
title={translate('settings.highlightUnreachableCells')}
|
|
58
|
+
>
|
|
59
|
+
<HighlightUnreachableCellsSetting disabled={!isOpen} />
|
|
60
|
+
</Modal.Section>
|
|
53
61
|
</Modal>
|
|
54
62
|
);
|
|
55
63
|
};
|
|
@@ -19,6 +19,10 @@ export const AutoGroupTilesSetting: FunctionComponent<Props> = ({ className, dis
|
|
|
19
19
|
const value = useTypedSelector(selectAutoGroupTiles);
|
|
20
20
|
|
|
21
21
|
const options = [
|
|
22
|
+
{
|
|
23
|
+
label: translate('settings.autoGroupTiles.null'),
|
|
24
|
+
value: NULL_VALUE,
|
|
25
|
+
},
|
|
22
26
|
{
|
|
23
27
|
label: translate('settings.autoGroupTiles.left'),
|
|
24
28
|
value: 'left',
|
|
@@ -27,10 +31,6 @@ export const AutoGroupTilesSetting: FunctionComponent<Props> = ({ className, dis
|
|
|
27
31
|
label: translate('settings.autoGroupTiles.right'),
|
|
28
32
|
value: 'right',
|
|
29
33
|
},
|
|
30
|
-
{
|
|
31
|
-
label: translate('settings.autoGroupTiles.null'),
|
|
32
|
-
value: NULL_VALUE,
|
|
33
|
-
},
|
|
34
34
|
];
|
|
35
35
|
|
|
36
36
|
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
|