@scrabble-solver/scrabble-solver 2.8.5 → 2.8.7
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 +183 -94
- package/.next/server/chunks/{206.js → 429.js} +2 -4137
- package/.next/server/chunks/515.js +111 -78
- package/.next/server/chunks/{907.js → 911.js} +134 -367
- package/.next/server/chunks/939.js +218 -0
- 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 +3 -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/api/dictionary/[locale]/[word].js +33 -17
- package/.next/server/pages/api/dictionary/[locale]/[word].js.nft.json +1 -1
- package/.next/server/pages/api/solve.js +404 -58
- package/.next/server/pages/api/solve.js.nft.json +1 -1
- package/.next/server/pages/api/visit.js +3 -2
- package/.next/server/pages/api/visit.js.nft.json +1 -1
- package/.next/server/pages/index.html +3 -7
- package/.next/server/pages/index.js +12 -14
- package/.next/server/pages/index.js.nft.json +1 -1
- package/.next/server/pages/index.json +1 -1
- package/.next/static/chunks/56-cf37c430261bbea5.js +1 -0
- package/.next/static/chunks/pages/_app-0b12a65bea70a0df.js +1 -0
- package/.next/static/chunks/pages/index-fcb69802550afb81.js +1 -0
- package/.next/static/css/1f39b55d50f5b30b.css +1 -0
- package/.next/static/css/751e8a14776d05d8.css +1 -0
- package/.next/static/z_0_lqfmiI_ISokr6NNRq/_buildManifest.js +1 -0
- package/.next/static/{TzKQ3IntkvaYmHBkWpfoi → z_0_lqfmiI_ISokr6NNRq}/_ssgManifest.js +0 -0
- package/.next/trace +40 -42
- package/package.json +9 -9
- package/src/api/index.ts +3 -9
- package/src/api/isBoardValid.ts +43 -0
- package/src/api/isCellValid.ts +26 -0
- package/src/api/isRowValid.ts +19 -0
- package/src/components/Board/Board.tsx +3 -1
- package/src/components/Board/BoardPure.tsx +4 -1
- package/src/components/Board/components/Cell/Cell.module.scss +28 -11
- package/src/components/Board/components/Cell/Cell.tsx +12 -1
- package/src/components/Board/components/Cell/CellPure.tsx +3 -1
- package/src/components/Board/components/Cell/lib.ts +10 -2
- package/src/components/Dictionary/Dictionary.module.scss +20 -0
- package/src/components/Dictionary/Dictionary.tsx +40 -29
- package/src/components/Results/Cell.tsx +3 -2
- package/src/components/Results/Result.tsx +16 -6
- package/src/components/ResultsInput/ResultsInput.tsx +11 -3
- package/src/hooks/useIsTablet.ts +2 -2
- package/src/lib/getRemainingTiles.ts +1 -1
- package/src/lib/index.ts +2 -1
- package/src/lib/isRegExp.ts +11 -0
- package/src/lib/isStringArray.ts +5 -0
- package/src/lib/sortResults.ts +5 -5
- package/src/pages/_app.tsx +6 -3
- package/src/pages/api/dictionary/[locale]/[word].ts +35 -11
- package/src/pages/api/solve.ts +39 -19
- package/src/pages/api/visit.ts +1 -0
- package/src/pages/index.module.scss +5 -11
- package/src/pages/index.tsx +5 -5
- package/src/sdk/{findWordDefinition.ts → findWordDefinitions.ts} +3 -3
- package/src/sdk/index.ts +1 -1
- package/src/state/sagas.ts +11 -11
- package/src/state/selectors.ts +6 -2
- package/src/state/slices/dictionaryInitialState.ts +3 -3
- package/src/state/slices/dictionarySlice.ts +4 -10
- package/.next/static/TzKQ3IntkvaYmHBkWpfoi/_buildManifest.js +0 -1
- package/.next/static/chunks/56-2d34867599a0ac66.js +0 -1
- package/.next/static/chunks/pages/_app-6ffa2ab900772b67.js +0 -1
- package/.next/static/chunks/pages/index-13ea7770a65c69ee.js +0 -1
- package/.next/static/css/3159cfe62ff742a3.css +0 -1
- package/.next/static/css/551d09cac435debb.css +0 -1
- package/src/api/validateBoard.ts +0 -45
- package/src/api/validateCell.ts +0 -40
- package/src/api/validateCharacter.ts +0 -14
- package/src/api/validateCharacters.ts +0 -24
- package/src/api/validateConfigId.ts +0 -9
- package/src/api/validateLocale.ts +0 -15
- package/src/api/validateRow.ts +0 -17
- package/src/api/validateTile.ts +0 -21
- package/src/api/validateWord.ts +0 -11
- package/src/lib/isLocale.ts +0 -7
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.7",
|
|
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.7",
|
|
34
|
+
"@scrabble-solver/constants": "^2.8.7",
|
|
35
|
+
"@scrabble-solver/dictionaries": "^2.8.7",
|
|
36
|
+
"@scrabble-solver/logger": "^2.8.7",
|
|
37
|
+
"@scrabble-solver/solver": "^2.8.7",
|
|
38
|
+
"@scrabble-solver/types": "^2.8.7",
|
|
39
|
+
"@scrabble-solver/word-definitions": "^2.8.7",
|
|
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": "879bf9b68f27fa3d530d0582fd8fbbcd5c8b150b"
|
|
72
72
|
}
|
package/src/api/index.ts
CHANGED
|
@@ -1,10 +1,4 @@
|
|
|
1
1
|
export { default as getServerLoggingData } from './getServerLoggingData';
|
|
2
|
-
export { default as
|
|
3
|
-
export { default as
|
|
4
|
-
export { default as
|
|
5
|
-
export { default as validateCharacters } from './validateCharacters';
|
|
6
|
-
export { default as validateConfigId } from './validateConfigId';
|
|
7
|
-
export { default as validateLocale } from './validateLocale';
|
|
8
|
-
export { default as validateRow } from './validateRow';
|
|
9
|
-
export { default as validateTile } from './validateTile';
|
|
10
|
-
export { default as validateWord } from './validateWord';
|
|
2
|
+
export { default as isBoardValid } from './isBoardValid';
|
|
3
|
+
export { default as isCellValid } from './isCellValid';
|
|
4
|
+
export { default as isRowValid } from './isRowValid';
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { BoardJson, CellJson, Config } from '@scrabble-solver/types';
|
|
2
|
+
|
|
3
|
+
import isRowValid from './isRowValid';
|
|
4
|
+
|
|
5
|
+
const isBoardValid = (board: BoardJson, config: Config): boolean => {
|
|
6
|
+
if (board.length !== config.boardHeight) {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
for (const row of board) {
|
|
11
|
+
if (!isRowValid(row, config)) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return areTwoCharacterTilesValid(board, config);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const areTwoCharacterTilesValid = (board: CellJson[][], config: Config): boolean => {
|
|
20
|
+
const cells: CellJson[] = board
|
|
21
|
+
.flat()
|
|
22
|
+
.filter((cell) => cell && cell.tile && config.isTwoCharacterTilePrefix(cell.tile.character));
|
|
23
|
+
|
|
24
|
+
for (const cell of cells) {
|
|
25
|
+
for (const characters of config.twoCharacterTiles) {
|
|
26
|
+
const canCheckDown = cell.y + 1 < board.length;
|
|
27
|
+
const canCheckRight = cell.x + 1 < board[0].length;
|
|
28
|
+
const cellDown = board[cell.y + 1][cell.x];
|
|
29
|
+
const cellRight = board[cell.y][cell.x + 1];
|
|
30
|
+
const collidesDown = canCheckDown && cellDown.tile && cellDown.tile.character === characters[1];
|
|
31
|
+
const collidesRight = canCheckRight && cellRight.tile && cellRight.tile.character === characters[1];
|
|
32
|
+
const collides = collidesDown || collidesRight;
|
|
33
|
+
|
|
34
|
+
if (cell.tile && characters.startsWith(cell.tile.character) && collides) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return true;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export default isBoardValid;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { BLANK } from '@scrabble-solver/constants';
|
|
2
|
+
import { CellJson, Config } from '@scrabble-solver/types';
|
|
3
|
+
|
|
4
|
+
const isCellValid = (cell: CellJson, config: Config): boolean => {
|
|
5
|
+
const { isEmpty, tile, x, y } = cell;
|
|
6
|
+
|
|
7
|
+
if (x < 0 || x >= config.boardWidth) {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (y < 0 || y >= config.boardHeight) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (isEmpty && tile !== null) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (tile !== null && !config.hasCharacter(tile.character) && tile.character !== BLANK) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return true;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export default isCellValid;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { CellJson, Config } from '@scrabble-solver/types';
|
|
2
|
+
|
|
3
|
+
import isCellValid from './isCellValid';
|
|
4
|
+
|
|
5
|
+
const isRowValid = (row: CellJson[], config: Config): boolean => {
|
|
6
|
+
if (row.length !== config.boardWidth) {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
for (const cell of row) {
|
|
11
|
+
if (!isCellValid(cell, config)) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return true;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default isRowValid;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { FunctionComponent, Ref } from 'react';
|
|
2
2
|
|
|
3
|
-
import { selectRowsWithCandidate, useTypedSelector } from 'state';
|
|
3
|
+
import { selectBoard, selectRowsWithCandidate, useTypedSelector } from 'state';
|
|
4
4
|
|
|
5
5
|
import BoardPure from './BoardPure';
|
|
6
6
|
import { useGrid } from './hooks';
|
|
@@ -13,12 +13,14 @@ interface Props {
|
|
|
13
13
|
|
|
14
14
|
const Board: FunctionComponent<Props> = ({ cellSize, className, innerRef }) => {
|
|
15
15
|
const rows = useTypedSelector(selectRowsWithCandidate);
|
|
16
|
+
const board = useTypedSelector(selectBoard);
|
|
16
17
|
const [{ lastDirection, refs }, { onDirectionToggle, onFocus, onKeyDown }] = useGrid(rows);
|
|
17
18
|
|
|
18
19
|
return (
|
|
19
20
|
<BoardPure
|
|
20
21
|
className={className}
|
|
21
22
|
cellSize={cellSize}
|
|
23
|
+
center={board.center}
|
|
22
24
|
innerRef={innerRef}
|
|
23
25
|
lastDirection={lastDirection}
|
|
24
26
|
refs={refs}
|
|
@@ -8,6 +8,7 @@ import { Cell as CellComponent } from './components';
|
|
|
8
8
|
interface Props {
|
|
9
9
|
className?: string;
|
|
10
10
|
cellSize: number;
|
|
11
|
+
center: Cell;
|
|
11
12
|
innerRef?: Ref<HTMLDivElement>;
|
|
12
13
|
lastDirection: 'horizontal' | 'vertical';
|
|
13
14
|
refs: RefObject<HTMLInputElement>[][];
|
|
@@ -19,11 +20,12 @@ interface Props {
|
|
|
19
20
|
|
|
20
21
|
const BoardPure: FunctionComponent<Props> = ({
|
|
21
22
|
className,
|
|
23
|
+
cellSize,
|
|
24
|
+
center,
|
|
22
25
|
innerRef,
|
|
23
26
|
lastDirection,
|
|
24
27
|
refs,
|
|
25
28
|
rows,
|
|
26
|
-
cellSize,
|
|
27
29
|
onDirectionToggle,
|
|
28
30
|
onFocus,
|
|
29
31
|
onKeyDown,
|
|
@@ -37,6 +39,7 @@ const BoardPure: FunctionComponent<Props> = ({
|
|
|
37
39
|
cell={cell}
|
|
38
40
|
direction={lastDirection}
|
|
39
41
|
inputRef={refs[y][x]}
|
|
42
|
+
isCenter={center.x === x && center.y === y}
|
|
40
43
|
key={x}
|
|
41
44
|
size={cellSize}
|
|
42
45
|
onDirectionToggle={onDirectionToggle}
|
|
@@ -13,6 +13,34 @@ $icon-size: 16px;
|
|
|
13
13
|
transition: var(--transition);
|
|
14
14
|
background-clip: padding-box;
|
|
15
15
|
|
|
16
|
+
&.bonusStart,
|
|
17
|
+
&.bonusWord2,
|
|
18
|
+
&.bonusWord3 {
|
|
19
|
+
position: relative;
|
|
20
|
+
|
|
21
|
+
&:before {
|
|
22
|
+
display: flex;
|
|
23
|
+
justify-content: center;
|
|
24
|
+
align-items: center;
|
|
25
|
+
position: absolute;
|
|
26
|
+
top: 0;
|
|
27
|
+
bottom: 0;
|
|
28
|
+
left: 0;
|
|
29
|
+
right: 0;
|
|
30
|
+
font-weight: bold;
|
|
31
|
+
pointer-events: none;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
&.bonusStart {
|
|
36
|
+
background-color: var(--color--red--light);
|
|
37
|
+
|
|
38
|
+
&:before {
|
|
39
|
+
content: '★';
|
|
40
|
+
color: var(--color--foreground--secondary);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
16
44
|
&.bonusCharacter1 {
|
|
17
45
|
background-color: var(--color--yellow--light);
|
|
18
46
|
}
|
|
@@ -39,22 +67,11 @@ $icon-size: 16px;
|
|
|
39
67
|
|
|
40
68
|
&.bonusWord2,
|
|
41
69
|
&.bonusWord3 {
|
|
42
|
-
position: relative;
|
|
43
70
|
background-color: var(--color--inactive);
|
|
44
71
|
|
|
45
72
|
&:before {
|
|
46
|
-
display: flex;
|
|
47
|
-
justify-content: center;
|
|
48
|
-
align-items: center;
|
|
49
|
-
position: absolute;
|
|
50
|
-
top: 0;
|
|
51
|
-
bottom: 0;
|
|
52
|
-
left: 0;
|
|
53
|
-
right: 0;
|
|
54
73
|
font-size: 75%;
|
|
55
|
-
font-weight: bold;
|
|
56
74
|
color: white;
|
|
57
|
-
pointer-events: none;
|
|
58
75
|
}
|
|
59
76
|
}
|
|
60
77
|
|
|
@@ -13,12 +13,22 @@ interface Props {
|
|
|
13
13
|
className?: string;
|
|
14
14
|
direction: 'horizontal' | 'vertical';
|
|
15
15
|
inputRef: RefObject<HTMLInputElement>;
|
|
16
|
+
isCenter: boolean;
|
|
16
17
|
size: number;
|
|
17
18
|
onDirectionToggle: () => void;
|
|
18
19
|
onFocus: (x: number, y: number) => void;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
const Cell: FunctionComponent<Props> = ({
|
|
22
|
+
const Cell: FunctionComponent<Props> = ({
|
|
23
|
+
cell,
|
|
24
|
+
className,
|
|
25
|
+
direction,
|
|
26
|
+
inputRef,
|
|
27
|
+
isCenter,
|
|
28
|
+
size,
|
|
29
|
+
onDirectionToggle,
|
|
30
|
+
onFocus,
|
|
31
|
+
}) => {
|
|
22
32
|
const { tile, x, y } = cell;
|
|
23
33
|
const dispatch = useDispatch();
|
|
24
34
|
const translate = useTranslate();
|
|
@@ -53,6 +63,7 @@ const Cell: FunctionComponent<Props> = ({ cell, className, direction, inputRef,
|
|
|
53
63
|
className={className}
|
|
54
64
|
direction={direction}
|
|
55
65
|
inputRef={inputRef}
|
|
66
|
+
isCenter={isCenter}
|
|
56
67
|
isEmpty={isEmpty}
|
|
57
68
|
points={points}
|
|
58
69
|
size={size}
|
|
@@ -17,6 +17,7 @@ interface Props {
|
|
|
17
17
|
className?: string;
|
|
18
18
|
direction: 'horizontal' | 'vertical';
|
|
19
19
|
inputRef: RefObject<HTMLInputElement>;
|
|
20
|
+
isCenter: boolean;
|
|
20
21
|
isEmpty: boolean;
|
|
21
22
|
points?: number;
|
|
22
23
|
size: number;
|
|
@@ -34,6 +35,7 @@ const CellPure: FunctionComponent<Props> = ({
|
|
|
34
35
|
className,
|
|
35
36
|
direction,
|
|
36
37
|
inputRef,
|
|
38
|
+
isCenter,
|
|
37
39
|
isEmpty,
|
|
38
40
|
points,
|
|
39
41
|
size,
|
|
@@ -45,7 +47,7 @@ const CellPure: FunctionComponent<Props> = ({
|
|
|
45
47
|
onToggleBlankClick,
|
|
46
48
|
}) => (
|
|
47
49
|
<div
|
|
48
|
-
className={classNames(styles.cell, getBonusClassname(cell, bonus), className, {
|
|
50
|
+
className={classNames(styles.cell, getBonusClassname(cell, bonus, isCenter), className, {
|
|
49
51
|
[styles.candidate]: cell.isCandidate(),
|
|
50
52
|
})}
|
|
51
53
|
style={style}
|
|
@@ -20,8 +20,16 @@ const CHARACTER_MULTIPLIER_CLASSNAMES: Record<number, string> = {
|
|
|
20
20
|
3: styles.bonusCharacterMultiplier3,
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
-
export const getBonusClassname = (cell: Cell, bonus: Bonus | undefined): string | undefined => {
|
|
24
|
-
if (!
|
|
23
|
+
export const getBonusClassname = (cell: Cell, bonus: Bonus | undefined, isCenter: boolean): string | undefined => {
|
|
24
|
+
if (!cell.isEmpty) {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (isCenter) {
|
|
29
|
+
return styles.bonusStart;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!bonus) {
|
|
25
33
|
return undefined;
|
|
26
34
|
}
|
|
27
35
|
|
|
@@ -7,6 +7,26 @@
|
|
|
7
7
|
font-size: var(--font--size--s);
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
.result {
|
|
11
|
+
transition: var(--transition);
|
|
12
|
+
|
|
13
|
+
&.isAllowed {
|
|
14
|
+
& + & {
|
|
15
|
+
.content {
|
|
16
|
+
padding-top: 0;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
&.isNotAllowed {
|
|
22
|
+
& + & {
|
|
23
|
+
.content {
|
|
24
|
+
padding-top: 0;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
10
30
|
.content {
|
|
11
31
|
padding: var(--spacing--l);
|
|
12
32
|
}
|
|
@@ -14,44 +14,55 @@ interface Props {
|
|
|
14
14
|
|
|
15
15
|
const Dictionary: FunctionComponent<Props> = ({ className }) => {
|
|
16
16
|
const translate = useTranslate();
|
|
17
|
-
const {
|
|
17
|
+
const { results, isLoading } = useTypedSelector(selectDictionary);
|
|
18
|
+
const isFirstAllowed = results.length > 0 ? results[0].isAllowed : undefined;
|
|
18
19
|
|
|
19
20
|
return (
|
|
20
21
|
<div
|
|
21
22
|
className={classNames(styles.dictionary, className, {
|
|
22
|
-
[styles.isAllowed]:
|
|
23
|
-
[styles.isNotAllowed]:
|
|
23
|
+
[styles.isAllowed]: isFirstAllowed === true,
|
|
24
|
+
[styles.isNotAllowed]: isFirstAllowed === false,
|
|
24
25
|
})}
|
|
25
26
|
>
|
|
26
|
-
{
|
|
27
|
-
<
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
{
|
|
35
|
-
|
|
36
|
-
{isAllowed === true && (
|
|
37
|
-
<>
|
|
38
|
-
{definitions.length === 0 && <div>{translate('dictionary.empty-state.no-definitions')}</div>}
|
|
39
|
-
|
|
40
|
-
{definitions.length > 0 && (
|
|
41
|
-
<ul className={styles.definitions}>
|
|
42
|
-
{definitions.map((result, index) => (
|
|
43
|
-
<li key={index} className={styles.definition}>
|
|
44
|
-
{result}
|
|
45
|
-
</li>
|
|
46
|
-
))}
|
|
47
|
-
</ul>
|
|
48
|
-
)}
|
|
49
|
-
</>
|
|
27
|
+
{results.map(({ definitions, isAllowed, word }) => (
|
|
28
|
+
<div
|
|
29
|
+
className={classNames(styles.result, {
|
|
30
|
+
[styles.isAllowed]: isAllowed === true,
|
|
31
|
+
[styles.isNotAllowed]: isAllowed === false,
|
|
32
|
+
})}
|
|
33
|
+
key={word}
|
|
34
|
+
>
|
|
35
|
+
{typeof word === 'undefined' && (
|
|
36
|
+
<EmptyState type="info">{translate('dictionary.empty-state.uninitialized')}</EmptyState>
|
|
50
37
|
)}
|
|
51
38
|
|
|
52
|
-
{
|
|
39
|
+
{typeof word !== 'undefined' && (
|
|
40
|
+
<div className={styles.content}>
|
|
41
|
+
{word && <h2 className={styles.word}>{word}</h2>}
|
|
42
|
+
|
|
43
|
+
{isAllowed === false && <div>{translate('dictionary.empty-state.not-allowed')}</div>}
|
|
44
|
+
|
|
45
|
+
{isAllowed === true && (
|
|
46
|
+
<>
|
|
47
|
+
{definitions.length === 0 && <div>{translate('dictionary.empty-state.no-definitions')}</div>}
|
|
48
|
+
|
|
49
|
+
{definitions.length > 0 && (
|
|
50
|
+
<ul className={styles.definitions}>
|
|
51
|
+
{definitions.map((result, index) => (
|
|
52
|
+
<li key={index} className={styles.definition}>
|
|
53
|
+
{result}
|
|
54
|
+
</li>
|
|
55
|
+
))}
|
|
56
|
+
</ul>
|
|
57
|
+
)}
|
|
58
|
+
</>
|
|
59
|
+
)}
|
|
60
|
+
|
|
61
|
+
{!isLoading && isAllowed === null && <div>{translate('dictionary.empty-state.no-results')}</div>}
|
|
62
|
+
</div>
|
|
63
|
+
)}
|
|
53
64
|
</div>
|
|
54
|
-
)}
|
|
65
|
+
))}
|
|
55
66
|
|
|
56
67
|
{isLoading && <Loading />}
|
|
57
68
|
</div>
|
|
@@ -11,12 +11,13 @@ import styles from './Results.module.scss';
|
|
|
11
11
|
interface Props {
|
|
12
12
|
className?: string;
|
|
13
13
|
translationKey: TranslationKey;
|
|
14
|
+
tooltip?: string | number;
|
|
14
15
|
value: string | number;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
const Cell: FunctionComponent<Props> = ({ className, translationKey, value }) => {
|
|
18
|
+
const Cell: FunctionComponent<Props> = ({ className, translationKey, tooltip, value }) => {
|
|
18
19
|
const translate = useTranslate();
|
|
19
|
-
const triggerProps = useTooltip(`${translate(translationKey)}: ${value}`);
|
|
20
|
+
const triggerProps = useTooltip(`${translate(translationKey)}: ${tooltip || value}`);
|
|
20
21
|
|
|
21
22
|
return (
|
|
22
23
|
<span className={classNames(styles.cell, className)} {...triggerProps}>
|
|
@@ -16,6 +16,7 @@ const Result = ({ index, style }: Props): ReactElement => {
|
|
|
16
16
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
17
17
|
const results = useTypedSelector(selectSortedFilteredResults)!;
|
|
18
18
|
const result = results[index];
|
|
19
|
+
const otherWords = result.words.slice(1).join(' / ').toLocaleUpperCase();
|
|
19
20
|
|
|
20
21
|
const handleClick = () => {
|
|
21
22
|
dispatch(resultsSlice.actions.applyResult(result));
|
|
@@ -49,12 +50,21 @@ const Result = ({ index, style }: Props): ReactElement => {
|
|
|
49
50
|
onMouseLeave={handleMouseLeave}
|
|
50
51
|
>
|
|
51
52
|
<span className={styles.resultContent}>
|
|
52
|
-
<Cell
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
<Cell className={styles.stat} translationKey="common.
|
|
53
|
+
<Cell
|
|
54
|
+
className={styles.word}
|
|
55
|
+
translationKey="common.word"
|
|
56
|
+
value={`${result.word.toLocaleUpperCase()}${otherWords.length > 0 ? ` (${otherWords})` : ''}`}
|
|
57
|
+
/>
|
|
58
|
+
<Cell className={styles.stat} translationKey="common.tiles" value={result.tilesCount} />
|
|
59
|
+
<Cell className={styles.stat} translationKey="common.consonants" value={result.consonantsCount} />
|
|
60
|
+
<Cell className={styles.stat} translationKey="common.vowels" value={result.vowelsCount} />
|
|
61
|
+
<Cell className={styles.stat} translationKey="common.blanks" value={result.blanksCount} />
|
|
62
|
+
<Cell
|
|
63
|
+
className={styles.stat}
|
|
64
|
+
translationKey="common.words"
|
|
65
|
+
tooltip={`${result.wordsCount} (${result.words.join(' / ').toLocaleUpperCase()})`}
|
|
66
|
+
value={result.wordsCount}
|
|
67
|
+
/>
|
|
58
68
|
<Cell className={styles.points} translationKey="common.points" value={result.points} />
|
|
59
69
|
</span>
|
|
60
70
|
</button>
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import classNames from 'classnames';
|
|
2
|
-
import { ChangeEvent, FunctionComponent } from 'react';
|
|
2
|
+
import { ChangeEvent, FunctionComponent, useState } from 'react';
|
|
3
3
|
import { useDispatch } from 'react-redux';
|
|
4
4
|
|
|
5
|
+
import { isRegExp } from 'lib';
|
|
5
6
|
import { resultsSlice, selectResultsQuery, useTranslate, useTypedSelector } from 'state';
|
|
6
7
|
|
|
7
8
|
import styles from './ResultsInput.module.scss';
|
|
@@ -14,9 +15,16 @@ const ResultsInput: FunctionComponent<Props> = ({ className }) => {
|
|
|
14
15
|
const dispatch = useDispatch();
|
|
15
16
|
const translate = useTranslate();
|
|
16
17
|
const value = useTypedSelector(selectResultsQuery);
|
|
18
|
+
const [localValue, setLocalValue] = useState(value);
|
|
17
19
|
|
|
18
20
|
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
|
|
19
|
-
|
|
21
|
+
const newValue = event.target.value;
|
|
22
|
+
|
|
23
|
+
setLocalValue(newValue);
|
|
24
|
+
|
|
25
|
+
if (isRegExp(newValue)) {
|
|
26
|
+
dispatch(resultsSlice.actions.changeQuery(newValue));
|
|
27
|
+
}
|
|
20
28
|
};
|
|
21
29
|
|
|
22
30
|
return (
|
|
@@ -25,7 +33,7 @@ const ResultsInput: FunctionComponent<Props> = ({ className }) => {
|
|
|
25
33
|
className={styles.input}
|
|
26
34
|
placeholder={translate('results.input.placeholder')}
|
|
27
35
|
type="text"
|
|
28
|
-
value={
|
|
36
|
+
value={localValue}
|
|
29
37
|
onChange={handleChange}
|
|
30
38
|
/>
|
|
31
39
|
</form>
|
package/src/hooks/useIsTablet.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { useMedia } from 'react-use';
|
|
2
2
|
|
|
3
3
|
const useIsTablet = (): boolean => {
|
|
4
|
-
const isTabletHeight = useMedia('(max-height: 800px)');
|
|
5
|
-
const isTabletWidth = useMedia('(max-width: 1024px)');
|
|
4
|
+
const isTabletHeight = useMedia('(max-height: 800px)', false);
|
|
5
|
+
const isTabletWidth = useMedia('(max-width: 1024px)', false);
|
|
6
6
|
const isTablet = isTabletHeight || isTabletWidth;
|
|
7
7
|
return isTablet;
|
|
8
8
|
};
|
|
@@ -11,7 +11,7 @@ const getRemainingTiles = (config: Config, board: Board, characters: string[]):
|
|
|
11
11
|
const remainingTiles = Object.fromEntries(config.tiles.map((tile) => [tile.character, { ...tile, usedCount: 0 }]));
|
|
12
12
|
const blank: RemainingTile = {
|
|
13
13
|
character: BLANK,
|
|
14
|
-
count: config.
|
|
14
|
+
count: config.blanksCount,
|
|
15
15
|
score: config.blankScore,
|
|
16
16
|
usedCount:
|
|
17
17
|
nonEmptyCells.filter((cell) => cell.tile.isBlank).length +
|
package/src/lib/index.ts
CHANGED
|
@@ -15,8 +15,9 @@ export { default as getTotalRemainingTilesCount } from './getTotalRemainingTiles
|
|
|
15
15
|
export { default as getTileSizes } from './getTileSizes';
|
|
16
16
|
export { default as inverseDirection } from './inverseDirection';
|
|
17
17
|
export { default as isCtrl } from './isCtrl';
|
|
18
|
-
export { default as isLocale } from './isLocale';
|
|
19
18
|
export { default as isMac } from './isMac';
|
|
19
|
+
export { default as isRegExp } from './isRegExp';
|
|
20
|
+
export { default as isStringArray } from './isStringArray';
|
|
20
21
|
export { default as memoize } from './memoize';
|
|
21
22
|
export { default as noop } from './noop';
|
|
22
23
|
export { default as numberComparator } from './numberComparator';
|
package/src/lib/sortResults.ts
CHANGED
|
@@ -6,13 +6,13 @@ import createKeyComparator from './createKeyComparator';
|
|
|
6
6
|
import reverseComparator from './reverseComparator';
|
|
7
7
|
|
|
8
8
|
const comparators: Record<ResultColumn, Comparator<Result>> = {
|
|
9
|
-
[ResultColumn.BlanksCount]: createKeyComparator('
|
|
10
|
-
[ResultColumn.ConsonantsCount]: createKeyComparator('
|
|
9
|
+
[ResultColumn.BlanksCount]: createKeyComparator('blanksCount'),
|
|
10
|
+
[ResultColumn.ConsonantsCount]: createKeyComparator('consonantsCount'),
|
|
11
11
|
[ResultColumn.Points]: createKeyComparator('points'),
|
|
12
|
-
[ResultColumn.TilesCount]: createKeyComparator('
|
|
13
|
-
[ResultColumn.VowelsCount]: createKeyComparator('
|
|
12
|
+
[ResultColumn.TilesCount]: createKeyComparator('tilesCount'),
|
|
13
|
+
[ResultColumn.VowelsCount]: createKeyComparator('vowelsCount'),
|
|
14
14
|
[ResultColumn.Word]: createKeyComparator('word'),
|
|
15
|
-
[ResultColumn.WordsCount]: createKeyComparator('
|
|
15
|
+
[ResultColumn.WordsCount]: createKeyComparator('wordsCount'),
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
const sortResults = (
|
package/src/pages/_app.tsx
CHANGED
|
@@ -7,7 +7,9 @@ import { createAppStore } from 'state';
|
|
|
7
7
|
|
|
8
8
|
import 'styles/global.scss';
|
|
9
9
|
|
|
10
|
-
const DESCRIPTION =
|
|
10
|
+
const DESCRIPTION =
|
|
11
|
+
// eslint-disable-next-line max-len
|
|
12
|
+
'Scrabble Solver 2 - Free and open-source analysis tool for Scrabble and Literaki. Quickly find top scoring words using given letters and board state. Available in English, French, German, Polish & Spanish.';
|
|
11
13
|
const KEYWORDS = [
|
|
12
14
|
'Scrabble',
|
|
13
15
|
'Solver',
|
|
@@ -54,8 +56,9 @@ const App: FunctionComponent<AppProps> = ({ Component, pageProps }) => (
|
|
|
54
56
|
|
|
55
57
|
<Provider store={store}>
|
|
56
58
|
<p style={{ fontSize: 0 }}>
|
|
57
|
-
Scrabble Solver 2 is
|
|
58
|
-
English, French, German, Polish &
|
|
59
|
+
Scrabble Solver 2 is a free and open-source analysis tool for Scrabble and Literaki. Quickly find top scoring
|
|
60
|
+
words using given letters and board state. Available in English, French, German, Polish & Spanish. Source code
|
|
61
|
+
is available on GitHub - contributions are welcome!
|
|
59
62
|
</p>
|
|
60
63
|
<Component {...pageProps} />
|
|
61
64
|
</Provider>
|