@scrabble-solver/scrabble-solver 2.11.1 → 2.11.3
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/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/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/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/210.js +109 -0
- package/.next/server/chunks/277.js +380 -258
- package/.next/server/chunks/44.js +47 -0
- package/.next/server/chunks/987.js +91 -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 +1 -1
- package/.next/server/pages/_app.js +1 -73
- package/.next/server/pages/_app.js.nft.json +1 -1
- package/.next/server/pages/_document.js.nft.json +1 -1
- package/.next/server/pages/api/dictionary/[locale]/[word].js +3 -4
- package/.next/server/pages/api/dictionary/[locale]/[word].js.nft.json +1 -1
- package/.next/server/pages/api/dictionary/[locale].js +3 -4
- package/.next/server/pages/api/dictionary/[locale].js.nft.json +1 -1
- package/.next/server/pages/api/solve.js +38 -13
- package/.next/server/pages/api/solve.js.nft.json +1 -1
- package/.next/server/pages/api/verify.js +5 -5
- package/.next/server/pages/api/verify.js.nft.json +1 -1
- package/.next/server/pages/api/visit.js +3 -4
- package/.next/server/pages/api/visit.js.nft.json +1 -1
- package/.next/server/pages/index.html +1 -1
- package/.next/server/pages/index.js +141 -237
- package/.next/server/pages/index.js.nft.json +1 -1
- package/.next/server/pages/index.json +1 -1
- package/.next/static/{esK8DG-6aS5V7QFRtR3YE → USLkKOoHbITebIEHkMGX_}/_buildManifest.js +1 -1
- package/.next/static/chunks/pages/_app-21c83ddb81fc09d0.js +28 -0
- package/.next/static/chunks/pages/index-0858deea02b2a417.js +1 -0
- package/.next/static/css/885da289cec275b3.css +1 -0
- package/.next/static/css/ea1c8134fe9a143e.css +2 -0
- package/.next/trace +53 -53
- package/package.json +9 -9
- package/src/api/index.ts +1 -0
- package/src/api/isCellValid.ts +3 -2
- package/src/api/isCharacterValid.ts +13 -0
- package/src/components/Board/components/Cell/Cell.module.scss +10 -2
- package/src/components/Board/hooks/useGrid.ts +1 -2
- package/src/components/Board/lib/getPositionInGrid.ts +1 -1
- package/src/components/Button/Button.module.scss +14 -1
- package/src/components/LogoSplashScreen/LogoSplashScreen.module.scss +4 -1
- package/src/components/Modal/Modal.module.scss +21 -1
- package/src/components/Modal/Modal.tsx +4 -1
- package/src/components/NotFound/NotFound.module.scss +13 -4
- package/src/components/NotFound/NotFound.tsx +4 -7
- package/src/components/Rack/Rack.tsx +3 -1
- package/src/components/Results/HeaderButton.tsx +5 -12
- package/src/components/Results/Result.tsx +5 -3
- package/src/components/Results/Results.module.scss +13 -1
- package/src/components/Results/Results.tsx +29 -43
- package/src/components/Results/types.ts +1 -1
- package/src/components/Solver/Solver.module.scss +4 -0
- package/src/components/Solver/Solver.tsx +9 -12
- package/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.tsx +5 -6
- package/src/components/Tile/Tile.module.scss +53 -79
- package/src/components/Tile/Tile.tsx +1 -3
- package/src/components/Tile/TilePure.tsx +4 -14
- package/src/hooks/useAppLayout.ts +3 -1
- package/src/i18n/constants.ts +65 -0
- package/src/i18n/de.json +1 -1
- package/src/i18n/en.json +1 -1
- package/src/i18n/es.json +1 -1
- package/src/i18n/fa.json +1 -1
- package/src/i18n/fr.json +1 -1
- package/src/i18n/i18n.module.scss +27 -0
- package/src/i18n/pl.json +1 -1
- package/src/icons/DashCircleFill.svg +1 -0
- package/src/icons/EyeFill.svg +5 -0
- package/src/icons/index.ts +3 -2
- package/src/lib/createRegExp.ts +13 -0
- package/src/lib/groupResults.ts +38 -0
- package/src/lib/guessLocale.ts +22 -0
- package/src/lib/index.ts +4 -1
- package/src/lib/sortResults.ts +6 -10
- package/src/modals/DictionaryModal/DictionaryModal.module.scss +0 -1
- package/src/modals/MenuModal/MenuModal.module.scss +23 -0
- package/src/modals/MenuModal/MenuModal.tsx +8 -2
- package/src/modals/RemainingTilesModal/RemainingTilesModal.tsx +4 -1
- package/src/modals/RemainingTilesModal/components/Character/Character.module.scss +8 -0
- package/src/modals/ResultsModal/ResultsModal.module.scss +2 -3
- package/src/modals/ResultsModal/ResultsModal.tsx +47 -11
- package/src/modals/SettingsModal/components/LocaleSetting/LocaleSetting.module.scss +3 -44
- package/src/modals/SettingsModal/components/LocaleSetting/LocaleSetting.tsx +4 -2
- package/src/pages/api/dictionary/[locale]/[word].ts +0 -1
- package/src/pages/api/dictionary/[locale]/index.ts +0 -1
- package/src/pages/api/solve.ts +3 -3
- package/src/pages/api/verify.ts +0 -1
- package/src/pages/api/visit.ts +0 -1
- package/src/pages/index.tsx +13 -15
- package/src/parameters/index.ts +2 -2
- package/src/state/sagas.ts +1 -0
- package/src/state/selectors.ts +37 -37
- package/src/state/slices/boardInitialState.ts +3 -1
- package/src/state/slices/cellFilterInitialState.ts +3 -3
- package/src/state/slices/cellFilterSlice.ts +3 -1
- package/src/state/slices/dictionaryInitialState.ts +2 -2
- package/src/state/slices/rackInitialState.ts +3 -1
- package/src/state/slices/resultsInitialState.ts +11 -4
- package/src/state/slices/settingsInitialState.ts +10 -21
- package/src/state/slices/solveInitialState.ts +2 -2
- package/src/state/slices/solveSlice.ts +2 -0
- package/src/state/slices/verifyInitialState.ts +13 -4
- package/src/styles/mixins.scss +5 -1
- package/src/styles/variables.scss +13 -0
- package/src/types/index.ts +11 -2
- package/.next/server/chunks/417.js +0 -221
- package/.next/server/chunks/664.js +0 -621
- package/.next/static/chunks/pages/_app-270526803bc274eb.js +0 -28
- package/.next/static/chunks/pages/index-c6e7754ccf3532df.js +0 -1
- package/.next/static/css/ad39b36eab07e613.css +0 -1
- package/.next/static/css/e5803e581e4c0451.css +0 -2
- package/src/components/Board/types/index.ts +0 -4
- package/src/modals/SettingsModal/components/LocaleSetting/options.ts +0 -68
- /package/.next/static/{esK8DG-6aS5V7QFRtR3YE → USLkKOoHbITebIEHkMGX_}/_ssgManifest.js +0 -0
package/src/lib/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ export { default as createGridOf } from './createGridOf';
|
|
|
5
5
|
export { default as createKeyboardNavigation } from './createKeyboardNavigation';
|
|
6
6
|
export { default as createKeyComparator } from './createKeyComparator';
|
|
7
7
|
export { default as createNullMovingComparator } from './createNullMovingComparator';
|
|
8
|
+
export { default as createRegExp } from './createRegExp';
|
|
8
9
|
export { default as createStringComparator } from './createStringComparator';
|
|
9
10
|
export { default as detectLocale } from './detectLocale';
|
|
10
11
|
export { default as extractCharacters } from './extractCharacters';
|
|
@@ -14,8 +15,10 @@ export { default as getCellSize } from './getCellSize';
|
|
|
14
15
|
export { default as getRemainingTiles } from './getRemainingTiles';
|
|
15
16
|
export { default as getRemainingTilesCount } from './getRemainingTilesCount';
|
|
16
17
|
export { default as getRemainingTilesGroups } from './getRemainingTilesGroups';
|
|
17
|
-
export { default as getTotalRemainingTilesCount } from './getTotalRemainingTilesCount';
|
|
18
18
|
export { default as getTileSizes } from './getTileSizes';
|
|
19
|
+
export { default as getTotalRemainingTilesCount } from './getTotalRemainingTilesCount';
|
|
20
|
+
export { default as groupResults } from './groupResults';
|
|
21
|
+
export { default as guessLocale } from './guessLocale';
|
|
19
22
|
export { default as inverseDirection } from './inverseDirection';
|
|
20
23
|
export { default as isMac } from './isMac';
|
|
21
24
|
export { default as isRegExp } from './isRegExp';
|
package/src/lib/sortResults.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Result } from '@scrabble-solver/types';
|
|
2
2
|
|
|
3
|
-
import { Comparator, ResultColumn, SortDirection } from 'types';
|
|
3
|
+
import { Comparator, ResultColumn, Sort, SortDirection } from 'types';
|
|
4
4
|
|
|
5
5
|
import createKeyComparator from './createKeyComparator';
|
|
6
6
|
import reverseComparator from './reverseComparator';
|
|
@@ -15,20 +15,16 @@ const comparators: Record<ResultColumn, (locale: string) => Comparator<Result>>
|
|
|
15
15
|
[ResultColumn.WordsCount]: (locale: string) => createKeyComparator('wordsCount', locale),
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
-
const sortResults = (
|
|
19
|
-
results: Result[] | undefined,
|
|
20
|
-
column: ResultColumn,
|
|
21
|
-
sortDirection: SortDirection,
|
|
22
|
-
locale: string,
|
|
23
|
-
): Result[] | undefined => {
|
|
18
|
+
const sortResults = (results: Result[] | undefined, sort: Sort, locale: string): Result[] | undefined => {
|
|
24
19
|
if (typeof results === 'undefined') {
|
|
25
20
|
return results;
|
|
26
21
|
}
|
|
27
22
|
|
|
28
|
-
const createComparator = comparators[column];
|
|
23
|
+
const createComparator = comparators[sort.column];
|
|
29
24
|
const comparator = createComparator(locale);
|
|
30
|
-
const finalComparator =
|
|
31
|
-
|
|
25
|
+
const finalComparator = sort.direction === SortDirection.Descending ? reverseComparator(comparator) : comparator;
|
|
26
|
+
const sortedResults = [...results].sort(finalComparator);
|
|
27
|
+
return sortedResults;
|
|
32
28
|
};
|
|
33
29
|
|
|
34
30
|
export default sortResults;
|
|
@@ -4,7 +4,30 @@
|
|
|
4
4
|
width: 100%;
|
|
5
5
|
text-transform: none;
|
|
6
6
|
|
|
7
|
+
&,
|
|
8
|
+
&:active,
|
|
9
|
+
&:focus,
|
|
10
|
+
&:hover {
|
|
11
|
+
box-shadow: 0 1px 1px 0 var(--box-shadow--color);
|
|
12
|
+
}
|
|
13
|
+
|
|
7
14
|
& + & {
|
|
8
15
|
margin-top: var(--spacing--l);
|
|
9
16
|
}
|
|
10
17
|
}
|
|
18
|
+
|
|
19
|
+
.settings {
|
|
20
|
+
width: 100%;
|
|
21
|
+
display: flex;
|
|
22
|
+
align-items: center;
|
|
23
|
+
justify-content: space-between;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.flag {
|
|
27
|
+
--height: var(--button--icon--size);
|
|
28
|
+
|
|
29
|
+
width: calc(var(--height) * var(--aspect--ratio));
|
|
30
|
+
height: var(--height);
|
|
31
|
+
border-radius: var(--border--radius);
|
|
32
|
+
box-shadow: 0 0 0 1px var(--box-shadow--color);
|
|
33
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { FunctionComponent } from 'react';
|
|
2
2
|
|
|
3
3
|
import { Button, Modal } from 'components';
|
|
4
|
+
import { LOCALE_FLAGS } from 'i18n';
|
|
4
5
|
import { BookHalf, CardChecklist, Cog, Github, Sack } from 'icons';
|
|
5
6
|
import { GITHUB_PROJECT_URL } from 'parameters';
|
|
6
|
-
import { useTranslate } from 'state';
|
|
7
|
+
import { selectLocale, useTranslate, useTypedSelector } from 'state';
|
|
7
8
|
|
|
8
9
|
import styles from './MenuModal.module.scss';
|
|
9
10
|
|
|
@@ -27,6 +28,8 @@ const Menu: FunctionComponent<Props> = ({
|
|
|
27
28
|
onShowWords,
|
|
28
29
|
}) => {
|
|
29
30
|
const translate = useTranslate();
|
|
31
|
+
const locale = useTypedSelector(selectLocale);
|
|
32
|
+
const Flag = LOCALE_FLAGS[locale];
|
|
30
33
|
|
|
31
34
|
return (
|
|
32
35
|
<Modal className={className} isOpen={isOpen} title={translate('menu')} onClose={onClose}>
|
|
@@ -59,7 +62,10 @@ const Menu: FunctionComponent<Props> = ({
|
|
|
59
62
|
</Button.Link>
|
|
60
63
|
|
|
61
64
|
<Button aria-label={translate('settings')} className={styles.button} Icon={Cog} onClick={onShowSettings}>
|
|
62
|
-
{
|
|
65
|
+
<div className={styles.settings}>
|
|
66
|
+
<span>{translate('settings')}</span>
|
|
67
|
+
<Flag.Icon className={styles.flag} />
|
|
68
|
+
</div>
|
|
63
69
|
</Button>
|
|
64
70
|
</Modal>
|
|
65
71
|
);
|
|
@@ -2,6 +2,8 @@ import { FunctionComponent } from 'react';
|
|
|
2
2
|
|
|
3
3
|
import { Badge, Modal } from 'components';
|
|
4
4
|
import { LOCALE_FEATURES } from 'i18n';
|
|
5
|
+
import { getTileSizes } from 'lib';
|
|
6
|
+
import { REMAINING_TILES_TILE_SIZE } from 'parameters';
|
|
5
7
|
import { selectLocale, selectRemainingTilesGroups, useTranslate, useTypedSelector } from 'state';
|
|
6
8
|
|
|
7
9
|
import { Character } from './components';
|
|
@@ -17,6 +19,7 @@ const RemainingTilesModal: FunctionComponent<Props> = ({ className, isOpen, onCl
|
|
|
17
19
|
const translate = useTranslate();
|
|
18
20
|
const locale = useTypedSelector(selectLocale);
|
|
19
21
|
const groups = useTypedSelector(selectRemainingTilesGroups);
|
|
22
|
+
const { tileFontSize } = getTileSizes(REMAINING_TILES_TILE_SIZE);
|
|
20
23
|
const { direction } = LOCALE_FEATURES[locale];
|
|
21
24
|
|
|
22
25
|
return (
|
|
@@ -37,7 +40,7 @@ const RemainingTilesModal: FunctionComponent<Props> = ({ className, isOpen, onCl
|
|
|
37
40
|
</span>
|
|
38
41
|
}
|
|
39
42
|
>
|
|
40
|
-
<div className={styles.content}>
|
|
43
|
+
<div className={styles.content} style={{ fontSize: tileFontSize }}>
|
|
41
44
|
{tiles.map((tile) => {
|
|
42
45
|
return (
|
|
43
46
|
<div className={styles.character} key={tile.character}>
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
@import 'styles/mixins';
|
|
2
|
+
|
|
1
3
|
.character {
|
|
2
4
|
display: flex;
|
|
3
5
|
flex-direction: column;
|
|
@@ -29,9 +31,15 @@
|
|
|
29
31
|
.remaining {
|
|
30
32
|
height: 6px;
|
|
31
33
|
margin-top: var(--spacing--m);
|
|
34
|
+
box-shadow: var(--box-shadow--raised);
|
|
35
|
+
|
|
36
|
+
@include media('<xs') {
|
|
37
|
+
box-shadow: var(--box-shadow--raised--subtle);
|
|
38
|
+
}
|
|
32
39
|
}
|
|
33
40
|
|
|
34
41
|
.count {
|
|
35
42
|
padding: var(--spacing--xs) 0;
|
|
43
|
+
font-size: var(--font--size--m);
|
|
36
44
|
white-space: nowrap;
|
|
37
45
|
}
|
|
@@ -9,15 +9,14 @@
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
.results {
|
|
12
|
-
flex: 1;
|
|
12
|
+
flex: 1 1 auto;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
.dictionary {
|
|
16
|
-
flex: 0
|
|
16
|
+
flex: 0 1 calc(var(--dictionary--height) - var(--text-input--height));
|
|
17
17
|
background-color: var(--color--background--element);
|
|
18
18
|
border: var(--border);
|
|
19
19
|
border-radius: var(--border--radius);
|
|
20
|
-
box-shadow: var(--box-shadow);
|
|
21
20
|
|
|
22
21
|
@media (max-height: 600px) {
|
|
23
22
|
display: none;
|
|
@@ -2,14 +2,9 @@ import { Result } from '@scrabble-solver/types';
|
|
|
2
2
|
import { FunctionComponent, useMemo } from 'react';
|
|
3
3
|
import { useDispatch } from 'react-redux';
|
|
4
4
|
|
|
5
|
-
import { Dictionary, Modal, Results } from 'components';
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
selectResultCandidate,
|
|
9
|
-
selectSortedFilteredResults,
|
|
10
|
-
useTranslate,
|
|
11
|
-
useTypedSelector,
|
|
12
|
-
} from 'state';
|
|
5
|
+
import { Button, Dictionary, Modal, Results } from 'components';
|
|
6
|
+
import { Check, EyeFill } from 'icons';
|
|
7
|
+
import { resultsSlice, selectResultCandidate, selectResults, useTranslate, useTypedSelector } from 'state';
|
|
13
8
|
|
|
14
9
|
import styles from './ResultsModal.module.scss';
|
|
15
10
|
|
|
@@ -22,9 +17,9 @@ interface Props {
|
|
|
22
17
|
const ResultsModal: FunctionComponent<Props> = ({ className, isOpen, onClose }) => {
|
|
23
18
|
const dispatch = useDispatch();
|
|
24
19
|
const translate = useTranslate();
|
|
25
|
-
const results = useTypedSelector(
|
|
20
|
+
const results = useTypedSelector(selectResults);
|
|
26
21
|
const resultCandidate = useTypedSelector(selectResultCandidate);
|
|
27
|
-
const index =
|
|
22
|
+
const index = results ? results.findIndex((result) => result.id === resultCandidate?.id) : -1;
|
|
28
23
|
const highlightedIndex = index === -1 ? undefined : index;
|
|
29
24
|
|
|
30
25
|
const callbacks = useMemo(
|
|
@@ -42,8 +37,49 @@ const ResultsModal: FunctionComponent<Props> = ({ className, isOpen, onClose })
|
|
|
42
37
|
[dispatch, onClose, resultCandidate],
|
|
43
38
|
);
|
|
44
39
|
|
|
40
|
+
const handleInsert = () => {
|
|
41
|
+
if (resultCandidate) {
|
|
42
|
+
dispatch(resultsSlice.actions.applyResult(resultCandidate));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
onClose();
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const handlePreview = () => {
|
|
49
|
+
onClose();
|
|
50
|
+
};
|
|
51
|
+
|
|
45
52
|
return (
|
|
46
|
-
<Modal
|
|
53
|
+
<Modal
|
|
54
|
+
className={className}
|
|
55
|
+
footer={
|
|
56
|
+
<>
|
|
57
|
+
<Button
|
|
58
|
+
aria-label={translate('results.insert')}
|
|
59
|
+
disabled={!resultCandidate}
|
|
60
|
+
Icon={Check}
|
|
61
|
+
tooltip={translate('results.insert')}
|
|
62
|
+
variant="primary"
|
|
63
|
+
onClick={handleInsert}
|
|
64
|
+
>
|
|
65
|
+
{translate('results.insert')}
|
|
66
|
+
</Button>
|
|
67
|
+
|
|
68
|
+
<Button
|
|
69
|
+
aria-label={translate('results.preview')}
|
|
70
|
+
disabled={!resultCandidate}
|
|
71
|
+
Icon={EyeFill}
|
|
72
|
+
tooltip={translate('results.preview')}
|
|
73
|
+
onClick={handlePreview}
|
|
74
|
+
>
|
|
75
|
+
{translate('results.preview')}
|
|
76
|
+
</Button>
|
|
77
|
+
</>
|
|
78
|
+
}
|
|
79
|
+
isOpen={isOpen}
|
|
80
|
+
title={translate('results')}
|
|
81
|
+
onClose={onClose}
|
|
82
|
+
>
|
|
47
83
|
<div className={styles.content}>
|
|
48
84
|
<Results callbacks={callbacks} className={styles.results} highlightedIndex={highlightedIndex} />
|
|
49
85
|
<Dictionary className={styles.dictionary} />
|
|
@@ -18,52 +18,11 @@
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
.flag {
|
|
21
|
-
|
|
21
|
+
--height: 32px;
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
width: calc(var(--height) * var(--aspect--ratio));
|
|
24
|
+
height: var(--height);
|
|
24
25
|
border-radius: var(--border--radius);
|
|
25
26
|
box-shadow: var(--box-shadow);
|
|
26
27
|
transition: var(--transition);
|
|
27
|
-
|
|
28
|
-
&.de {
|
|
29
|
-
$aspect-ratio: 1.6;
|
|
30
|
-
|
|
31
|
-
width: $height * $aspect-ratio;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
&.es {
|
|
35
|
-
$aspect-ratio: 1.5;
|
|
36
|
-
|
|
37
|
-
width: $height * $aspect-ratio;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
&.fa {
|
|
41
|
-
$aspect-ratio: 1.75;
|
|
42
|
-
|
|
43
|
-
width: $height * $aspect-ratio;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
&.fr {
|
|
47
|
-
$aspect-ratio: 1.6;
|
|
48
|
-
|
|
49
|
-
width: $height * $aspect-ratio;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
&.gb {
|
|
53
|
-
$aspect-ratio: 2;
|
|
54
|
-
|
|
55
|
-
width: $height * $aspect-ratio;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
&.pl {
|
|
59
|
-
$aspect-ratio: 1.6;
|
|
60
|
-
|
|
61
|
-
width: $height * $aspect-ratio;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
&.us {
|
|
65
|
-
$aspect-ratio: 1.9;
|
|
66
|
-
|
|
67
|
-
width: $height * $aspect-ratio;
|
|
68
|
-
}
|
|
69
28
|
}
|
|
@@ -4,16 +4,18 @@ import { ChangeEvent, FunctionComponent } from 'react';
|
|
|
4
4
|
import { useDispatch } from 'react-redux';
|
|
5
5
|
|
|
6
6
|
import { Radio } from 'components';
|
|
7
|
+
import { LOCALE_FLAGS } from 'i18n';
|
|
7
8
|
import { selectLocale, settingsSlice, useTypedSelector } from 'state';
|
|
8
9
|
|
|
9
10
|
import styles from './LocaleSetting.module.scss';
|
|
10
|
-
import options from './options';
|
|
11
11
|
|
|
12
12
|
interface Props {
|
|
13
13
|
className?: string;
|
|
14
14
|
disabled: boolean;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
const OPTIONS = Object.values(LOCALE_FLAGS).sort((a, b) => a.name.localeCompare(b.name));
|
|
18
|
+
|
|
17
19
|
const LocaleSetting: FunctionComponent<Props> = ({ className, disabled }) => {
|
|
18
20
|
const dispatch = useDispatch();
|
|
19
21
|
const locale = useTypedSelector(selectLocale);
|
|
@@ -25,7 +27,7 @@ const LocaleSetting: FunctionComponent<Props> = ({ className, disabled }) => {
|
|
|
25
27
|
|
|
26
28
|
return (
|
|
27
29
|
<div className={className}>
|
|
28
|
-
{
|
|
30
|
+
{OPTIONS.map(({ Icon, ...option }) => (
|
|
29
31
|
<Radio
|
|
30
32
|
checked={locale === option.value}
|
|
31
33
|
className={classNames(styles.option, className, {
|
|
@@ -37,7 +37,6 @@ const dictionary = async (request: NextApiRequest, response: NextApiResponse): P
|
|
|
37
37
|
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
38
38
|
logger.error('dictionary - error', { error, meta });
|
|
39
39
|
response.status(500).send({ error: 'Server error', message });
|
|
40
|
-
throw error;
|
|
41
40
|
}
|
|
42
41
|
};
|
|
43
42
|
|
|
@@ -28,7 +28,6 @@ const dictionary = async (request: NextApiRequest, response: NextApiResponse): P
|
|
|
28
28
|
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
29
29
|
logger.error('dictionary - error', { error, meta });
|
|
30
30
|
response.status(500).send({ error: 'Server error', message });
|
|
31
|
-
throw error;
|
|
32
31
|
}
|
|
33
32
|
};
|
|
34
33
|
|
package/src/pages/api/solve.ts
CHANGED
|
@@ -6,7 +6,7 @@ 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
|
|
|
9
|
-
import { getServerLoggingData, isBoardValid } from 'api';
|
|
9
|
+
import { getServerLoggingData, isBoardValid, isCharacterValid } from 'api';
|
|
10
10
|
import { isStringArray } from 'lib';
|
|
11
11
|
|
|
12
12
|
interface RequestData {
|
|
@@ -42,7 +42,6 @@ const solve = async (request: NextApiRequest, response: NextApiResponse): Promis
|
|
|
42
42
|
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
43
43
|
logger.error('solve - error', { error, meta });
|
|
44
44
|
response.status(500).send({ error: 'Server error', message });
|
|
45
|
-
throw error;
|
|
46
45
|
}
|
|
47
46
|
};
|
|
48
47
|
|
|
@@ -64,10 +63,11 @@ const parseRequest = (request: NextApiRequest): RequestData => {
|
|
|
64
63
|
const config = getLocaleConfig(configId, locale);
|
|
65
64
|
|
|
66
65
|
for (const character of characters) {
|
|
67
|
-
if (!
|
|
66
|
+
if (!isCharacterValid(character)) {
|
|
68
67
|
throw new Error('Invalid "characters" parameter');
|
|
69
68
|
}
|
|
70
69
|
}
|
|
70
|
+
|
|
71
71
|
const blanksCount = characters.filter((character) => character === BLANK).length;
|
|
72
72
|
|
|
73
73
|
if (blanksCount > config.blanksCount) {
|
package/src/pages/api/verify.ts
CHANGED
|
@@ -38,7 +38,6 @@ const verify = async (request: NextApiRequest, response: NextApiResponse): Promi
|
|
|
38
38
|
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
39
39
|
logger.error('verify - error', { error, meta });
|
|
40
40
|
response.status(500).send({ error: 'Server error', message });
|
|
41
|
-
throw error;
|
|
42
41
|
}
|
|
43
42
|
};
|
|
44
43
|
|
package/src/pages/api/visit.ts
CHANGED
|
@@ -13,7 +13,6 @@ const visit = async (request: NextApiRequest, response: NextApiResponse): Promis
|
|
|
13
13
|
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
14
14
|
logger.error('visit - error', { error, meta });
|
|
15
15
|
response.status(500).send({ error: 'Server error', message });
|
|
16
|
-
throw error;
|
|
17
16
|
}
|
|
18
17
|
};
|
|
19
18
|
|
package/src/pages/index.tsx
CHANGED
|
@@ -18,7 +18,6 @@ import {
|
|
|
18
18
|
SettingsModal,
|
|
19
19
|
WordsModal,
|
|
20
20
|
} from 'modals';
|
|
21
|
-
import { INITIALIZATION_DURATION } from 'parameters';
|
|
22
21
|
import { registerServiceWorker } from 'serviceWorkerManager';
|
|
23
22
|
import { initialize, reset, selectLocale, useTypedSelector } from 'state';
|
|
24
23
|
|
|
@@ -44,7 +43,7 @@ const Index: FunctionComponent<Props> = ({ version }) => {
|
|
|
44
43
|
const [showWords, setShowWords] = useState(false);
|
|
45
44
|
const [isInitialized, setIsInitialized] = useState(false);
|
|
46
45
|
const [indexRef, { height: indexHeight, width: indexWidth }] = useMeasure<HTMLDivElement>();
|
|
47
|
-
const [navRef, { height: navHeight }] = useMeasure<
|
|
46
|
+
const [navRef, { height: navHeight }] = useMeasure<HTMLElement>();
|
|
48
47
|
const solverHeight = indexHeight - navHeight;
|
|
49
48
|
const solverWidth = indexWidth;
|
|
50
49
|
const [isClient, setIsClient] = useState(false);
|
|
@@ -60,10 +59,7 @@ const Index: FunctionComponent<Props> = ({ version }) => {
|
|
|
60
59
|
useEffectOnce(() => {
|
|
61
60
|
setIsClient(true);
|
|
62
61
|
dispatch(initialize());
|
|
63
|
-
|
|
64
|
-
globalThis.setTimeout(() => {
|
|
65
|
-
setIsInitialized(true);
|
|
66
|
-
}, INITIALIZATION_DURATION);
|
|
62
|
+
setIsInitialized(true);
|
|
67
63
|
});
|
|
68
64
|
|
|
69
65
|
useEffect(() => {
|
|
@@ -87,7 +83,7 @@ const Index: FunctionComponent<Props> = ({ version }) => {
|
|
|
87
83
|
<SvgFontFix />
|
|
88
84
|
|
|
89
85
|
<div className={classNames(styles.index, { [styles.initialized]: isInitialized })} ref={indexRef}>
|
|
90
|
-
<
|
|
86
|
+
<nav className={styles.nav} ref={navRef}>
|
|
91
87
|
<div className={styles.navContent}>
|
|
92
88
|
<div className={styles.navLogo}>
|
|
93
89
|
<a className={styles.logoContainer} href="/" title={version}>
|
|
@@ -104,14 +100,16 @@ const Index: FunctionComponent<Props> = ({ version }) => {
|
|
|
104
100
|
onShowWords={() => setShowWords(true)}
|
|
105
101
|
/>
|
|
106
102
|
</div>
|
|
107
|
-
</
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
103
|
+
</nav>
|
|
104
|
+
|
|
105
|
+
{solverHeight > 0 && solverWidth > 0 && (
|
|
106
|
+
<Solver
|
|
107
|
+
className={styles.solver}
|
|
108
|
+
height={solverHeight}
|
|
109
|
+
width={solverWidth}
|
|
110
|
+
onShowResults={() => setShowResults(true)}
|
|
111
|
+
/>
|
|
112
|
+
)}
|
|
115
113
|
</div>
|
|
116
114
|
|
|
117
115
|
<MenuModal
|
package/src/parameters/index.ts
CHANGED
|
@@ -11,8 +11,6 @@ export const TRANSITION = 'var(--transition)';
|
|
|
11
11
|
|
|
12
12
|
export const GITHUB_PROJECT_URL = 'https://github.com/kamilmielnik/scrabble-solver';
|
|
13
13
|
|
|
14
|
-
export const INITIALIZATION_DURATION = 100;
|
|
15
|
-
|
|
16
14
|
export const TRANSITION_DURATION = 100;
|
|
17
15
|
export const TRANSITION_DURATION_LONG = 250;
|
|
18
16
|
|
|
@@ -71,6 +69,8 @@ export const REMAINING_TILES_TILE_SIZE = 50;
|
|
|
71
69
|
|
|
72
70
|
export const RESULTS_ITEM_HEIGHT = 40;
|
|
73
71
|
|
|
72
|
+
export const SOLVER_COLUMN_WIDTH = 580;
|
|
73
|
+
|
|
74
74
|
export const TILE_APPEAR_DURATION = 200;
|
|
75
75
|
|
|
76
76
|
export const TILE_APPEAR_KEYFRAMES = [
|
package/src/state/sagas.ts
CHANGED
|
@@ -116,6 +116,7 @@ function* onReset(): AnyGenerator {
|
|
|
116
116
|
yield put(dictionarySlice.actions.reset());
|
|
117
117
|
yield put(rackSlice.actions.reset());
|
|
118
118
|
yield put(resultsSlice.actions.reset());
|
|
119
|
+
yield put(solveSlice.actions.reset());
|
|
119
120
|
yield put(verifySlice.actions.submit());
|
|
120
121
|
}
|
|
121
122
|
|
package/src/state/selectors.ts
CHANGED
|
@@ -1,17 +1,29 @@
|
|
|
1
|
+
/* eslint-disable max-lines */
|
|
2
|
+
|
|
1
3
|
import { createSelector } from '@reduxjs/toolkit';
|
|
2
4
|
import { getLocaleConfig } from '@scrabble-solver/configs';
|
|
3
5
|
import { BLANK } from '@scrabble-solver/constants';
|
|
4
|
-
import { Cell, Config, isError,
|
|
6
|
+
import { Cell, Config, isError, Tile } from '@scrabble-solver/types';
|
|
5
7
|
|
|
6
8
|
import i18n, { LOCALE_FEATURES } from 'i18n';
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
+
import {
|
|
10
|
+
createRegExp,
|
|
11
|
+
findCell,
|
|
12
|
+
getRemainingTiles,
|
|
13
|
+
getRemainingTilesGroups,
|
|
14
|
+
groupResults,
|
|
15
|
+
sortResults,
|
|
16
|
+
unorderedArraysEqual,
|
|
17
|
+
} from 'lib';
|
|
18
|
+
import { Point, Translations } from 'types';
|
|
9
19
|
|
|
10
20
|
import { RootState } from './types';
|
|
11
21
|
|
|
12
22
|
const selectCell = (_: unknown, cell: Cell): Cell => cell;
|
|
13
23
|
|
|
14
|
-
const selectPoint = (_: unknown, point:
|
|
24
|
+
const selectPoint = (_: unknown, point: Point): Point => point;
|
|
25
|
+
|
|
26
|
+
const selectResultIndex = (_: unknown, index: number): number => index;
|
|
15
27
|
|
|
16
28
|
const selectCharacter = (_: unknown, character: string | null): string | null => character;
|
|
17
29
|
|
|
@@ -71,54 +83,42 @@ export const selectCellIsValid = createSelector([selectConfig, selectCell], (con
|
|
|
71
83
|
return config.tiles.some((tile) => tile.character === cell.tile.character);
|
|
72
84
|
});
|
|
73
85
|
|
|
74
|
-
export const
|
|
86
|
+
export const selectResultsRaw = createSelector([selectResultsRoot], (results) => results.results);
|
|
75
87
|
|
|
76
88
|
export const selectResultsQuery = createSelector([selectResultsRoot], (results) => results.query);
|
|
77
89
|
|
|
78
|
-
export const
|
|
90
|
+
export const selectResultsSort = createSelector([selectResultsRoot], (results) => results.sort);
|
|
79
91
|
|
|
80
|
-
export const
|
|
92
|
+
export const selectSortedResults = createSelector([selectResultsRaw, selectResultsSort, selectLocale], sortResults);
|
|
81
93
|
|
|
82
|
-
export const
|
|
83
|
-
[
|
|
84
|
-
|
|
94
|
+
export const selectGroupedResults = createSelector(
|
|
95
|
+
[selectSortedResults, selectResultsQuery, selectCellFilter],
|
|
96
|
+
groupResults,
|
|
85
97
|
);
|
|
86
98
|
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
let regExp: RegExp | undefined;
|
|
93
|
-
|
|
94
|
-
try {
|
|
95
|
-
regExp = new RegExp(query, 'gi');
|
|
96
|
-
} catch {
|
|
97
|
-
return results;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return results.filter((result) => {
|
|
101
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
102
|
-
return regExp!.test(result.word);
|
|
103
|
-
});
|
|
104
|
-
};
|
|
99
|
+
export const selectResults = createSelector([selectGroupedResults], (groupedResults) => {
|
|
100
|
+
return groupedResults ? [...groupedResults.matching, ...groupedResults.other] : groupedResults;
|
|
101
|
+
});
|
|
105
102
|
|
|
106
|
-
export const
|
|
107
|
-
[
|
|
108
|
-
(results, query, cellFilter) => {
|
|
103
|
+
export const selectIsResultMatching = createSelector(
|
|
104
|
+
[selectResults, selectResultsQuery, selectCellFilter, selectResultIndex],
|
|
105
|
+
(results, query, cellFilter, index) => {
|
|
109
106
|
if (!results) {
|
|
110
|
-
return
|
|
107
|
+
return false;
|
|
111
108
|
}
|
|
112
109
|
|
|
113
|
-
const
|
|
110
|
+
const result = results[index];
|
|
111
|
+
const regExp = createRegExp(query);
|
|
114
112
|
|
|
115
|
-
if (!
|
|
116
|
-
return
|
|
113
|
+
if (!regExp.test(result.word)) {
|
|
114
|
+
return false;
|
|
117
115
|
}
|
|
118
116
|
|
|
119
|
-
|
|
117
|
+
if (cellFilter) {
|
|
120
118
|
return cellFilter.every(({ x, y }) => result.cells.some((cell) => cell.x === x && cell.y === y));
|
|
121
|
-
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return true;
|
|
122
122
|
},
|
|
123
123
|
);
|
|
124
124
|
|
|
@@ -3,9 +3,11 @@ import { Board } from '@scrabble-solver/types';
|
|
|
3
3
|
|
|
4
4
|
import settingsInitialState from './settingsInitialState';
|
|
5
5
|
|
|
6
|
+
export type BoardState = Board;
|
|
7
|
+
|
|
6
8
|
const { configId, locale } = settingsInitialState;
|
|
7
9
|
const { boardHeight, boardWidth } = getLocaleConfig(configId, locale);
|
|
8
|
-
const boardInitialState:
|
|
10
|
+
const boardInitialState: BoardState = Board.create(boardWidth, boardHeight);
|
|
9
11
|
|
|
10
12
|
// const createOxyphenbutazone = () => {
|
|
11
13
|
// // Tiles: oypbaze
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Point } from 'types';
|
|
2
2
|
|
|
3
|
-
export type
|
|
3
|
+
export type CellFilterState = Point[];
|
|
4
4
|
|
|
5
|
-
const cellFilterInitialState:
|
|
5
|
+
const cellFilterInitialState: CellFilterState = [];
|
|
6
6
|
|
|
7
7
|
export default cellFilterInitialState;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import { Point } from 'types';
|
|
4
|
+
|
|
5
|
+
import cellFilterInitialState from './cellFilterInitialState';
|
|
4
6
|
|
|
5
7
|
const cellFilterSlice = createSlice({
|
|
6
8
|
initialState: cellFilterInitialState,
|