@scrabble-solver/scrabble-solver 2.8.8 → 2.8.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 +10 -10
- 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 +179 -44
- package/.next/server/chunks/44.js +802 -0
- package/.next/server/chunks/515.js +359 -95
- package/.next/server/chunks/911.js +53 -23
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/pages/404.html +2 -2
- package/.next/server/pages/404.js.nft.json +1 -1
- package/.next/server/pages/500.html +2 -2
- package/.next/server/pages/_app.js.nft.json +1 -1
- package/.next/server/pages/_error.js.nft.json +1 -1
- package/.next/server/pages/api/solve.js +193 -927
- package/.next/server/pages/api/solve.js.nft.json +1 -1
- package/.next/server/pages/api/verify.js +217 -0
- package/.next/server/pages/api/verify.js.nft.json +1 -0
- package/.next/server/pages/index.html +3 -3
- package/.next/server/pages/index.js +7 -1
- package/.next/server/pages/index.js.nft.json +1 -1
- package/.next/server/pages/index.json +1 -1
- package/.next/server/pages-manifest.json +1 -0
- package/.next/static/chunks/317-a33dd38e9b9a17ed.js +1 -0
- package/.next/static/chunks/pages/{404-8eb3ba4f0ba17e08.js → 404-90c624da3c83fd17.js} +1 -1
- package/.next/static/chunks/pages/_app-f8f360878e1c2aff.js +1 -0
- package/.next/static/chunks/pages/index-ecea697d3e5d8a6f.js +1 -0
- package/.next/static/css/64dc2ce1811912f1.css +1 -0
- package/.next/static/css/{cdbc9e0afcff5473.css → ad2a08918868cad8.css} +1 -1
- package/.next/static/yCxjzzYpw5JjJE53PO_s6/_buildManifest.js +1 -0
- package/.next/static/{z3J3qmq1nazbDv_ENIkCo → yCxjzzYpw5JjJE53PO_s6}/_ssgManifest.js +0 -0
- package/.next/trace +42 -41
- package/package.json +9 -9
- package/src/components/Badge/Badge.module.scss +13 -0
- package/src/components/Badge/Badge.tsx +15 -0
- package/src/components/Badge/index.ts +1 -0
- package/src/components/NavButtons/NavButtons.tsx +33 -14
- package/src/components/RemainingTiles/RemainingTiles.module.scss +8 -7
- package/src/components/RemainingTiles/RemainingTiles.tsx +13 -4
- package/src/components/Sidebar/Sidebar.tsx +2 -2
- package/src/components/Sidebar/components/Section/Section.module.scss +0 -1
- package/src/components/Sidebar/components/Section/Section.tsx +1 -1
- package/src/components/SquareButton/SquareButton.module.scss +5 -0
- package/src/components/Words/Words.module.scss +35 -0
- package/src/components/Words/Words.tsx +57 -0
- package/src/components/Words/index.ts +1 -0
- package/src/components/index.ts +2 -0
- package/src/i18n/de.json +4 -1
- package/src/i18n/en.json +4 -1
- package/src/i18n/es.json +4 -1
- package/src/i18n/fr.json +4 -1
- package/src/i18n/pl.json +4 -1
- package/src/icons/BookHalf.svg +4 -0
- package/src/icons/Check.svg +4 -0
- package/src/icons/Cross.svg +2 -2
- package/src/icons/CrossFill.svg +4 -0
- package/src/icons/index.ts +3 -0
- package/src/pages/api/solve.ts +2 -3
- package/src/pages/api/verify.ts +71 -0
- package/src/pages/index.tsx +5 -0
- package/src/sdk/index.ts +1 -0
- package/src/sdk/verify.ts +24 -0
- package/src/state/rootReducer.ts +2 -0
- package/src/state/sagas.ts +33 -6
- package/src/state/selectors.ts +8 -0
- package/src/state/slices/index.ts +2 -0
- package/src/state/slices/verifyInitialState.ts +12 -0
- package/src/state/slices/verifySlice.ts +31 -0
- package/src/styles/variables.scss +2 -1
- package/src/types/index.ts +4 -1
- package/.next/static/chunks/615-d258f6c528c18622.js +0 -1
- package/.next/static/chunks/pages/_app-4a663fd3d5ca4524.js +0 -1
- package/.next/static/chunks/pages/index-1a9826d740cc8830.js +0 -1
- package/.next/static/css/180c6c26317ac90f.css +0 -1
- package/.next/static/z3J3qmq1nazbDv_ENIkCo/_buildManifest.js +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scrabble-solver/scrabble-solver",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.9",
|
|
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.9",
|
|
34
|
+
"@scrabble-solver/constants": "^2.8.9",
|
|
35
|
+
"@scrabble-solver/dictionaries": "^2.8.9",
|
|
36
|
+
"@scrabble-solver/logger": "^2.8.9",
|
|
37
|
+
"@scrabble-solver/solver": "^2.8.9",
|
|
38
|
+
"@scrabble-solver/types": "^2.8.9",
|
|
39
|
+
"@scrabble-solver/word-definitions": "^2.8.9",
|
|
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": "8557cc2c5214e6689a5c59373b228b28f5dd8ed4"
|
|
72
72
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
.badge {
|
|
2
|
+
display: inline-flex;
|
|
3
|
+
align-items: center;
|
|
4
|
+
justify-content: center;
|
|
5
|
+
padding: var(--spacing--xs) var(--spacing--s);
|
|
6
|
+
border-radius: var(--border--radius);
|
|
7
|
+
background-color: var(--color--violet--light);
|
|
8
|
+
box-shadow: va(--box-shadow);
|
|
9
|
+
line-height: var(--line-height);
|
|
10
|
+
font-size: var(--font--size--s);
|
|
11
|
+
font-weight: bold;
|
|
12
|
+
color: white;
|
|
13
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import classNames from 'classnames';
|
|
2
|
+
import { FunctionComponent, ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
import styles from './Badge.module.scss';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
children?: ReactNode;
|
|
8
|
+
className?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const Badge: FunctionComponent<Props> = ({ children, className }) => (
|
|
12
|
+
<div className={classNames(styles.badge, className)}>{children}</div>
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
export default Badge;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './Badge';
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import classNames from 'classnames';
|
|
2
2
|
import { FunctionComponent } from 'react';
|
|
3
3
|
|
|
4
|
-
import { Cog, Eraser, Github, Keyboard, Sack } from 'icons';
|
|
4
|
+
import { BookHalf, Cog, Eraser, Github, Keyboard, Sack } from 'icons';
|
|
5
5
|
import { GITHUB_PROJECT_URL } from 'parameters';
|
|
6
|
-
import { selectHasOverusedTiles, useTranslate, useTypedSelector } from 'state';
|
|
6
|
+
import { selectHasInvalidWords, selectHasOverusedTiles, useTranslate, useTypedSelector } from 'state';
|
|
7
7
|
|
|
8
8
|
import SquareButton from '../SquareButton';
|
|
9
9
|
|
|
@@ -14,27 +14,26 @@ interface Props {
|
|
|
14
14
|
onShowKeyMap: () => void;
|
|
15
15
|
onShowRemainingTiles: () => void;
|
|
16
16
|
onShowSettings: () => void;
|
|
17
|
+
onShowWords: () => void;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
const NavButtons: FunctionComponent<Props> = ({
|
|
20
|
+
const NavButtons: FunctionComponent<Props> = ({
|
|
21
|
+
onClear,
|
|
22
|
+
onShowKeyMap,
|
|
23
|
+
onShowRemainingTiles,
|
|
24
|
+
onShowSettings,
|
|
25
|
+
onShowWords,
|
|
26
|
+
}) => {
|
|
20
27
|
const translate = useTranslate();
|
|
21
28
|
const hasOverusedTiles = useTypedSelector(selectHasOverusedTiles);
|
|
29
|
+
const hasInvalidWords = useTypedSelector(selectHasInvalidWords);
|
|
22
30
|
|
|
23
31
|
return (
|
|
24
32
|
<div className={styles.navButtons}>
|
|
25
|
-
<SquareButton.
|
|
26
|
-
className={styles.button}
|
|
27
|
-
href={GITHUB_PROJECT_URL}
|
|
28
|
-
Icon={Github}
|
|
29
|
-
rel="noopener noreferrer"
|
|
30
|
-
target="_blank"
|
|
31
|
-
tooltip={translate('github')}
|
|
32
|
-
/>
|
|
33
|
+
<SquareButton className={styles.button} Icon={Eraser} tooltip={translate('common.clear')} onClick={onClear} />
|
|
33
34
|
|
|
34
35
|
<div className={styles.separator} />
|
|
35
36
|
|
|
36
|
-
<SquareButton className={styles.button} Icon={Keyboard} tooltip={translate('keyMap')} onClick={onShowKeyMap} />
|
|
37
|
-
|
|
38
37
|
<SquareButton
|
|
39
38
|
className={classNames(styles.button, {
|
|
40
39
|
[styles.error]: hasOverusedTiles,
|
|
@@ -44,9 +43,29 @@ const NavButtons: FunctionComponent<Props> = ({ onClear, onShowKeyMap, onShowRem
|
|
|
44
43
|
onClick={onShowRemainingTiles}
|
|
45
44
|
/>
|
|
46
45
|
|
|
46
|
+
<SquareButton
|
|
47
|
+
className={classNames(styles.button, {
|
|
48
|
+
[styles.error]: hasInvalidWords,
|
|
49
|
+
})}
|
|
50
|
+
Icon={BookHalf}
|
|
51
|
+
tooltip={translate('words')}
|
|
52
|
+
onClick={onShowWords}
|
|
53
|
+
/>
|
|
54
|
+
|
|
47
55
|
<div className={styles.separator} />
|
|
48
56
|
|
|
49
|
-
<SquareButton
|
|
57
|
+
<SquareButton.Link
|
|
58
|
+
className={styles.button}
|
|
59
|
+
href={GITHUB_PROJECT_URL}
|
|
60
|
+
Icon={Github}
|
|
61
|
+
rel="noopener noreferrer"
|
|
62
|
+
target="_blank"
|
|
63
|
+
tooltip={translate('github')}
|
|
64
|
+
/>
|
|
65
|
+
|
|
66
|
+
<div className={styles.separator} />
|
|
67
|
+
|
|
68
|
+
<SquareButton className={styles.button} Icon={Keyboard} tooltip={translate('keyMap')} onClick={onShowKeyMap} />
|
|
50
69
|
|
|
51
70
|
<SquareButton className={styles.button} Icon={Cog} tooltip={translate('settings')} onClick={onShowSettings} />
|
|
52
71
|
</div>
|
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
.group {
|
|
2
|
-
margin-bottom: var(--spacing--l);
|
|
3
|
-
}
|
|
4
|
-
|
|
5
1
|
.title {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
2
|
+
display: flex;
|
|
3
|
+
align-items: center;
|
|
4
|
+
justify-content: space-between;
|
|
5
|
+
flex-wrap: wrap;
|
|
9
6
|
}
|
|
10
7
|
|
|
11
8
|
.content {
|
|
@@ -13,3 +10,7 @@
|
|
|
13
10
|
grid-template-columns: repeat(5, 1fr);
|
|
14
11
|
gap: var(--spacing--m);
|
|
15
12
|
}
|
|
13
|
+
|
|
14
|
+
.badge {
|
|
15
|
+
margin-left: var(--spacing--m);
|
|
16
|
+
}
|
|
@@ -2,6 +2,7 @@ import { FunctionComponent } from 'react';
|
|
|
2
2
|
|
|
3
3
|
import { selectRemainingTilesGroups, useTranslate, useTypedSelector } from 'state';
|
|
4
4
|
|
|
5
|
+
import Badge from '../Badge';
|
|
5
6
|
import Sidebar from '../Sidebar';
|
|
6
7
|
|
|
7
8
|
import Character from './Character';
|
|
@@ -20,15 +21,23 @@ const RemainingTiles: FunctionComponent<Props> = ({ className, isOpen, onClose }
|
|
|
20
21
|
return (
|
|
21
22
|
<Sidebar className={className} isOpen={isOpen} title={translate('remaining-tiles')} onClose={onClose}>
|
|
22
23
|
{groups.map(({ remainingCount, tiles, translationKey, totalCount }) => (
|
|
23
|
-
<
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
<Sidebar.Section
|
|
25
|
+
key={translationKey}
|
|
26
|
+
title={
|
|
27
|
+
<span className={styles.title}>
|
|
28
|
+
<span>{translate(translationKey)}</span>
|
|
29
|
+
<Badge className={styles.badge}>
|
|
30
|
+
{remainingCount} / {totalCount}
|
|
31
|
+
</Badge>
|
|
32
|
+
</span>
|
|
33
|
+
}
|
|
34
|
+
>
|
|
26
35
|
<div className={styles.content}>
|
|
27
36
|
{tiles.map((tile) => {
|
|
28
37
|
return <Character key={tile.character} tile={tile} />;
|
|
29
38
|
})}
|
|
30
39
|
</div>
|
|
31
|
-
</
|
|
40
|
+
</Sidebar.Section>
|
|
32
41
|
))}
|
|
33
42
|
</Sidebar>
|
|
34
43
|
);
|
|
@@ -2,7 +2,7 @@ import classNames from 'classnames';
|
|
|
2
2
|
import { FunctionComponent, ReactNode } from 'react';
|
|
3
3
|
import Modal from 'react-modal';
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { CrossFill } from 'icons';
|
|
6
6
|
import { TRANSITION_DURATION_LONG } from 'parameters';
|
|
7
7
|
import { useTranslate } from 'state';
|
|
8
8
|
|
|
@@ -41,7 +41,7 @@ const Sidebar: FunctionComponent<Props> = ({ children, className, isOpen, title,
|
|
|
41
41
|
|
|
42
42
|
<SquareButton
|
|
43
43
|
className={styles.closeButton}
|
|
44
|
-
Icon={
|
|
44
|
+
Icon={CrossFill}
|
|
45
45
|
tooltip={translate('common.close')}
|
|
46
46
|
onClick={onClose}
|
|
47
47
|
/>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
.title {
|
|
2
|
+
display: flex;
|
|
3
|
+
align-items: center;
|
|
4
|
+
justify-content: space-between;
|
|
5
|
+
flex-wrap: wrap;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.badge {
|
|
9
|
+
margin-left: var(--spacing--m);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.word {
|
|
13
|
+
display: flex;
|
|
14
|
+
align-items: center;
|
|
15
|
+
|
|
16
|
+
& + & {
|
|
17
|
+
margin-top: var(--spacing--s);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.invalid {
|
|
22
|
+
color: var(--color--error);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.valid {
|
|
26
|
+
color: var(--color--success);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.icon {
|
|
30
|
+
$size: 24px;
|
|
31
|
+
|
|
32
|
+
width: $size;
|
|
33
|
+
height: $size;
|
|
34
|
+
margin-right: var(--spacing--s);
|
|
35
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import classNames from 'classnames';
|
|
2
|
+
import { FunctionComponent } from 'react';
|
|
3
|
+
|
|
4
|
+
import { Check, Cross } from 'icons';
|
|
5
|
+
import { selectVerify, useTranslate, useTypedSelector } from 'state';
|
|
6
|
+
|
|
7
|
+
import Badge from '../Badge';
|
|
8
|
+
import Sidebar from '../Sidebar';
|
|
9
|
+
|
|
10
|
+
import styles from './Words.module.scss';
|
|
11
|
+
|
|
12
|
+
interface Props {
|
|
13
|
+
className?: string;
|
|
14
|
+
isOpen: boolean;
|
|
15
|
+
onClose: () => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const Words: FunctionComponent<Props> = ({ className, isOpen, onClose }) => {
|
|
19
|
+
const translate = useTranslate();
|
|
20
|
+
const { invalidWords, validWords } = useTypedSelector(selectVerify);
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<Sidebar className={className} isOpen={isOpen} title={translate('words')} onClose={onClose}>
|
|
24
|
+
<Sidebar.Section
|
|
25
|
+
title={
|
|
26
|
+
<span className={styles.title}>
|
|
27
|
+
<span>{translate('words.invalid')}</span>
|
|
28
|
+
<Badge className={styles.badge}>{invalidWords.length}</Badge>
|
|
29
|
+
</span>
|
|
30
|
+
}
|
|
31
|
+
>
|
|
32
|
+
{invalidWords.map((word, index) => (
|
|
33
|
+
<div className={styles.word} key={index}>
|
|
34
|
+
<Cross className={classNames(styles.icon, styles.invalid)} /> {word}
|
|
35
|
+
</div>
|
|
36
|
+
))}
|
|
37
|
+
</Sidebar.Section>
|
|
38
|
+
|
|
39
|
+
<Sidebar.Section
|
|
40
|
+
title={
|
|
41
|
+
<span className={styles.title}>
|
|
42
|
+
<span>{translate('words.valid')}</span>
|
|
43
|
+
<Badge className={styles.badge}>{validWords.length}</Badge>
|
|
44
|
+
</span>
|
|
45
|
+
}
|
|
46
|
+
>
|
|
47
|
+
{validWords.map((word, index) => (
|
|
48
|
+
<div className={styles.word} key={index}>
|
|
49
|
+
<Check className={classNames(styles.icon, styles.valid)} /> {word}
|
|
50
|
+
</div>
|
|
51
|
+
))}
|
|
52
|
+
</Sidebar.Section>
|
|
53
|
+
</Sidebar>
|
|
54
|
+
);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export default Words;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './Words';
|
package/src/components/index.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { default as Badge } from './Badge';
|
|
1
2
|
export { default as Board } from './Board';
|
|
2
3
|
export { default as Button } from './Button';
|
|
3
4
|
export { default as Checkbox } from './Checkbox';
|
|
@@ -25,3 +26,4 @@ export { default as SvgFontCss } from './SvgFontCss';
|
|
|
25
26
|
export { default as Tile } from './Tile';
|
|
26
27
|
export { useTooltip } from './Tooltip';
|
|
27
28
|
export { default as Well } from './Well';
|
|
29
|
+
export { default as Words } from './Words';
|
package/src/i18n/de.json
CHANGED
|
@@ -49,5 +49,8 @@
|
|
|
49
49
|
"settings.autoGroupTiles.right": "Rechte Seite",
|
|
50
50
|
"settings.autoGroupTiles.null": "Nicht gruppieren",
|
|
51
51
|
"settings.game": "Spiel",
|
|
52
|
-
"settings.language": "Sprache"
|
|
52
|
+
"settings.language": "Sprache",
|
|
53
|
+
"words": "Gebildete Wörter",
|
|
54
|
+
"words.invalid": "Falsch",
|
|
55
|
+
"words.valid": "Korrekt"
|
|
53
56
|
}
|
package/src/i18n/en.json
CHANGED
|
@@ -49,5 +49,8 @@
|
|
|
49
49
|
"settings.autoGroupTiles.right": "On the right",
|
|
50
50
|
"settings.autoGroupTiles.null": "Do not group",
|
|
51
51
|
"settings.game": "Game",
|
|
52
|
-
"settings.language": "Language"
|
|
52
|
+
"settings.language": "Language",
|
|
53
|
+
"words": "Created words",
|
|
54
|
+
"words.invalid": "Invalid",
|
|
55
|
+
"words.valid": "Valid"
|
|
53
56
|
}
|
package/src/i18n/es.json
CHANGED
|
@@ -49,5 +49,8 @@
|
|
|
49
49
|
"settings.autoGroupTiles.right": "A la derecha",
|
|
50
50
|
"settings.autoGroupTiles.null": "No agrupar",
|
|
51
51
|
"settings.game": "Juego",
|
|
52
|
-
"settings.language": "Idioma"
|
|
52
|
+
"settings.language": "Idioma",
|
|
53
|
+
"words": "Palabras creadas",
|
|
54
|
+
"words.invalid": "Incorrecto",
|
|
55
|
+
"words.valid": "Correcto"
|
|
53
56
|
}
|
package/src/i18n/fr.json
CHANGED
|
@@ -49,5 +49,8 @@
|
|
|
49
49
|
"settings.autoGroupTiles.right": "Vers la gauche",
|
|
50
50
|
"settings.autoGroupTiles.null": "Ne pas grouper",
|
|
51
51
|
"settings.game": "Jeu",
|
|
52
|
-
"settings.language": "Langue"
|
|
52
|
+
"settings.language": "Langue",
|
|
53
|
+
"words": "Mots créés",
|
|
54
|
+
"words.invalid": "Incorrect",
|
|
55
|
+
"words.valid": "Corriger"
|
|
53
56
|
}
|
package/src/i18n/pl.json
CHANGED
|
@@ -49,5 +49,8 @@
|
|
|
49
49
|
"settings.autoGroupTiles.right": "Po prawej",
|
|
50
50
|
"settings.autoGroupTiles.null": "Nie grupuj",
|
|
51
51
|
"settings.game": "Gra",
|
|
52
|
-
"settings.language": "Język"
|
|
52
|
+
"settings.language": "Język",
|
|
53
|
+
"words": "Utworzone słowa",
|
|
54
|
+
"words.invalid": "Niepoprawne",
|
|
55
|
+
"words.valid": "Poprawne"
|
|
53
56
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<!-- https://icons.getbootstrap.com/icons/book-half/ -->
|
|
2
|
+
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
|
3
|
+
<path d="M8.5 2.687c.654-.689 1.782-.886 3.112-.752 1.234.124 2.503.523 3.388.893v9.923c-.918-.35-2.107-.692-3.287-.81-1.094-.111-2.278-.039-3.213.492V2.687zM8 1.783C7.015.936 5.587.81 4.287.94c-1.514.153-3.042.672-3.994 1.105A.5.5 0 0 0 0 2.5v11a.5.5 0 0 0 .707.455c.882-.4 2.303-.881 3.68-1.02 1.409-.142 2.59.087 3.223.877a.5.5 0 0 0 .78 0c.633-.79 1.814-1.019 3.222-.877 1.378.139 2.8.62 3.681 1.02A.5.5 0 0 0 16 13.5v-11a.5.5 0 0 0-.293-.455c-.952-.433-2.48-.952-3.994-1.105C10.413.809 8.985.936 8 1.783z" fill="currentColor" />
|
|
4
|
+
</svg>
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<!-- https://icons.getbootstrap.com/icons/check/ -->
|
|
2
|
+
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
|
3
|
+
<path d="M10.97 4.97a.75.75 0 0 1 1.07 1.05l-3.99 4.99a.75.75 0 0 1-1.08.02L4.324 8.384a.75.75 0 1 1 1.06-1.06l2.094 2.093 3.473-4.425a.267.267 0 0 1 .02-.022z" fill="currentColor" />
|
|
4
|
+
</svg>
|
package/src/icons/Cross.svg
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- https://icons.getbootstrap.com/icons/x
|
|
1
|
+
<!-- https://icons.getbootstrap.com/icons/x/ -->
|
|
2
2
|
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
|
3
|
-
<path d="
|
|
3
|
+
<path d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z" fill="currentColor" />
|
|
4
4
|
</svg>
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<!-- https://icons.getbootstrap.com/icons/x-square-fill/ -->
|
|
2
|
+
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
|
3
|
+
<path d="M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm3.354 4.646L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 1 1 .708-.708z" fill="currentColor" />
|
|
4
|
+
</svg>
|
package/src/icons/index.ts
CHANGED
|
@@ -2,10 +2,13 @@ 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 BookHalf } from './BookHalf.svg';
|
|
6
|
+
export { default as Check } from './Check.svg';
|
|
5
7
|
export { default as CheckboxChecked } from './CheckboxChecked.svg';
|
|
6
8
|
export { default as CheckboxEmpty } from './CheckboxEmpty.svg';
|
|
7
9
|
export { default as Cog } from './Cog.svg';
|
|
8
10
|
export { default as Cross } from './Cross.svg';
|
|
11
|
+
export { default as CrossFill } from './CrossFill.svg';
|
|
9
12
|
export { default as DashCircleFill } from './DashCircleFill.svg';
|
|
10
13
|
export { default as Eraser } from './Eraser.svg';
|
|
11
14
|
export { default as Flag } from './Flag.svg';
|
package/src/pages/api/solve.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { getLocaleConfig, isConfigId } from '@scrabble-solver/configs';
|
|
|
2
2
|
import { BLANK } from '@scrabble-solver/constants';
|
|
3
3
|
import { dictionaries } from '@scrabble-solver/dictionaries';
|
|
4
4
|
import logger from '@scrabble-solver/logger';
|
|
5
|
-
import
|
|
5
|
+
import { solve as solveScrabble } from '@scrabble-solver/solver';
|
|
6
6
|
import { Board, Config, isBoardJson, isLocale, Locale, Tile } from '@scrabble-solver/types';
|
|
7
7
|
import { NextApiRequest, NextApiResponse } from 'next';
|
|
8
8
|
|
|
@@ -36,8 +36,7 @@ const solve = async (request: NextApiRequest, response: NextApiResponse): Promis
|
|
|
36
36
|
|
|
37
37
|
const trie = await dictionaries.get(locale);
|
|
38
38
|
const tiles = characters.map((character) => new Tile({ character, isBlank: character === BLANK }));
|
|
39
|
-
const
|
|
40
|
-
const results = solver.solve(board, tiles);
|
|
39
|
+
const results = solveScrabble(trie, config, board, tiles);
|
|
41
40
|
response.status(200).send(results.map((result) => result.toJson()));
|
|
42
41
|
} catch (error) {
|
|
43
42
|
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { getLocaleConfig, isConfigId } from '@scrabble-solver/configs';
|
|
2
|
+
import { dictionaries } from '@scrabble-solver/dictionaries';
|
|
3
|
+
import logger from '@scrabble-solver/logger';
|
|
4
|
+
import { Board, Config, isBoardJson, isLocale, Locale } from '@scrabble-solver/types';
|
|
5
|
+
import { NextApiRequest, NextApiResponse } from 'next';
|
|
6
|
+
|
|
7
|
+
import { getServerLoggingData, isBoardValid } from 'api';
|
|
8
|
+
|
|
9
|
+
interface RequestData {
|
|
10
|
+
board: Board;
|
|
11
|
+
config: Config;
|
|
12
|
+
locale: Locale;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const verify = async (request: NextApiRequest, response: NextApiResponse): Promise<void> => {
|
|
16
|
+
const meta = getServerLoggingData(request);
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const { board, locale } = parseRequest(request);
|
|
20
|
+
|
|
21
|
+
logger.info('verify - request', {
|
|
22
|
+
meta,
|
|
23
|
+
payload: {
|
|
24
|
+
board: board.toString(),
|
|
25
|
+
boardBlanksCount: board.getBlanksCount(),
|
|
26
|
+
boardTilesCount: board.getTilesCount(),
|
|
27
|
+
configId: request.body.configId,
|
|
28
|
+
locale,
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const trie = await dictionaries.get(locale);
|
|
33
|
+
const words = board.getWords().sort((a, b) => a.localeCompare(b));
|
|
34
|
+
const invalidWords = words.filter((word) => !trie.has(word));
|
|
35
|
+
const validWords = words.filter((word) => trie.has(word));
|
|
36
|
+
response.status(200).send({ invalidWords, validWords });
|
|
37
|
+
} catch (error) {
|
|
38
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
39
|
+
logger.error('verify - error', { error, meta });
|
|
40
|
+
response.status(500).send({ error: 'Server error', message });
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const parseRequest = (request: NextApiRequest): RequestData => {
|
|
46
|
+
const { board: boardJson, configId, locale } = request.body;
|
|
47
|
+
|
|
48
|
+
if (!isLocale(locale)) {
|
|
49
|
+
throw new Error('Invalid "locale" parameter');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!isConfigId(configId)) {
|
|
53
|
+
throw new Error('Invalid "configId" parameter');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const config = getLocaleConfig(configId, locale);
|
|
57
|
+
|
|
58
|
+
if (!isBoardJson(boardJson) || !isBoardValid(boardJson, config)) {
|
|
59
|
+
throw new Error('Invalid "board" parameter');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const board = Board.fromJson(boardJson);
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
board,
|
|
66
|
+
config,
|
|
67
|
+
locale,
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export default verify;
|
package/src/pages/index.tsx
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
Settings,
|
|
20
20
|
Splash,
|
|
21
21
|
Well,
|
|
22
|
+
Words,
|
|
22
23
|
} from 'components';
|
|
23
24
|
import { useIsTablet, useLocalStorage } from 'hooks';
|
|
24
25
|
import { getCellSize } from 'lib';
|
|
@@ -39,6 +40,7 @@ const Index: FunctionComponent<Props> = ({ version }) => {
|
|
|
39
40
|
const [showKeyMap, setShowKeyMap] = useState(false);
|
|
40
41
|
const [showRemainingTiles, setShowRemainingTiles] = useState(false);
|
|
41
42
|
const [showSettings, setShowSettings] = useState(false);
|
|
43
|
+
const [showWords, setShowWords] = useState(false);
|
|
42
44
|
const [boardRef, { height: boardHeight }] = useMeasure<HTMLDivElement>();
|
|
43
45
|
const [contentRef, { height: contentHeight, width: contentWidth }] = useMeasure<HTMLDivElement>();
|
|
44
46
|
const [resultsContainerRef, { height: resultsContainerHeight, width: resultsContainerWidth }] =
|
|
@@ -85,6 +87,7 @@ const Index: FunctionComponent<Props> = ({ version }) => {
|
|
|
85
87
|
onShowKeyMap={() => setShowKeyMap(true)}
|
|
86
88
|
onShowRemainingTiles={() => setShowRemainingTiles(true)}
|
|
87
89
|
onShowSettings={() => setShowSettings(true)}
|
|
90
|
+
onShowWords={() => setShowWords(true)}
|
|
88
91
|
/>
|
|
89
92
|
</div>
|
|
90
93
|
|
|
@@ -122,6 +125,8 @@ const Index: FunctionComponent<Props> = ({ version }) => {
|
|
|
122
125
|
|
|
123
126
|
<KeyMap isOpen={showKeyMap} onClose={() => setShowKeyMap(false)} />
|
|
124
127
|
|
|
128
|
+
<Words isOpen={showWords} onClose={() => setShowWords(false)} />
|
|
129
|
+
|
|
125
130
|
<RemainingTiles isOpen={showRemainingTiles} onClose={() => setShowRemainingTiles(false)} />
|
|
126
131
|
|
|
127
132
|
<Splash forceShow={!isInitialized} onAnimationEnd={handleSplashAnimationEnd} />
|
package/src/sdk/index.ts
CHANGED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { BoardJson, Locale } from '@scrabble-solver/types';
|
|
2
|
+
|
|
3
|
+
interface Payload {
|
|
4
|
+
board: BoardJson;
|
|
5
|
+
configId: string;
|
|
6
|
+
locale: Locale;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface Response {
|
|
10
|
+
invalidWords: string[];
|
|
11
|
+
validWords: string[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const verify = ({ board, configId, locale }: Payload): Promise<Response> => {
|
|
15
|
+
return fetch('/api/verify', {
|
|
16
|
+
method: 'POST',
|
|
17
|
+
headers: {
|
|
18
|
+
'Content-Type': 'application/json',
|
|
19
|
+
},
|
|
20
|
+
body: JSON.stringify({ board, configId, locale }),
|
|
21
|
+
}).then((response) => response.json());
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default verify;
|
package/src/state/rootReducer.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
resultsSlice,
|
|
9
9
|
settingsSlice,
|
|
10
10
|
solveSlice,
|
|
11
|
+
verifySlice,
|
|
11
12
|
} from './slices';
|
|
12
13
|
|
|
13
14
|
const rootReducer = combineReducers({
|
|
@@ -18,6 +19,7 @@ const rootReducer = combineReducers({
|
|
|
18
19
|
results: resultsSlice.reducer,
|
|
19
20
|
settings: settingsSlice.reducer,
|
|
20
21
|
solve: solveSlice.reducer,
|
|
22
|
+
verify: verifySlice.reducer,
|
|
21
23
|
});
|
|
22
24
|
|
|
23
25
|
export default rootReducer;
|