@scrabble-solver/scrabble-solver 2.10.10 → 2.11.0
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 +12 -12
- 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/required-server-files.json +1 -1
- package/.next/routes-manifest.json +1 -1
- package/.next/server/chunks/{176.js → 277.js} +860 -767
- package/.next/server/chunks/{290.js → 417.js} +3 -3
- package/.next/server/chunks/50.js +371 -343
- package/.next/server/chunks/664.js +15 -15
- package/.next/server/chunks/859.js +17 -10
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/next-font-manifest.js +1 -0
- 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 +4 -12
- package/.next/server/pages/_app.js.nft.json +1 -1
- package/.next/server/pages/_document.js +2 -2
- package/.next/server/pages/_document.js.nft.json +1 -1
- package/.next/server/pages/_error.js +4 -4
- package/.next/server/pages/api/dictionary/[locale]/[word].js +4 -4
- package/.next/server/pages/api/dictionary/[locale]/[word].js.nft.json +1 -1
- package/.next/server/pages/api/dictionary/[locale].js +3 -3
- package/.next/server/pages/api/dictionary/[locale].js.nft.json +1 -1
- package/.next/server/pages/api/solve.js +9 -14
- package/.next/server/pages/api/solve.js.nft.json +1 -1
- package/.next/server/pages/api/verify.js +3 -3
- package/.next/server/pages/api/verify.js.nft.json +1 -1
- package/.next/server/pages/api/visit.js +3 -3
- 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 +250 -175
- 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 -1
- package/.next/static/45ye7793DY705HOcuK9lJ/_buildManifest.js +1 -0
- package/.next/static/chunks/main-0ecb9ccfcb6c9b24.js +1 -0
- package/.next/static/chunks/pages/404-e0f30450e9920dc3.js +1 -0
- package/.next/static/chunks/pages/_app-d7acee5e526752d9.js +28 -0
- package/.next/static/chunks/pages/{_error-8353112a01355ec2.js → _error-54de1933a164a1ff.js} +1 -1
- package/.next/static/chunks/pages/index-35d2c1c79a201ae2.js +1 -0
- package/.next/static/css/a48caa6f57de6e98.css +1 -0
- package/.next/static/css/c49bbe944ddd1b39.css +2 -0
- package/.next/trace +55 -55
- package/package.json +12 -13
- package/src/components/Board/components/Cell/Cell.module.scss +1 -0
- package/src/components/Board/hooks/useGrid.ts +1 -1
- package/src/components/Dictionary/Dictionary.module.scss +13 -8
- package/src/components/Dictionary/Dictionary.tsx +5 -5
- package/src/components/Loading/Loading.tsx +1 -1
- package/src/components/Modal/Modal.module.scss +0 -1
- package/src/components/NavButtons/NavButtons.tsx +4 -5
- package/src/components/Results/Results.module.scss +15 -2
- package/src/components/Results/Results.tsx +78 -67
- package/src/components/ResultsInput/ResultsInput.tsx +6 -2
- package/src/components/Solver/Solver.module.scss +9 -7
- package/src/components/Solver/Solver.tsx +21 -49
- package/src/components/Solver/components/FloatingSolveButton/FloatingSolveButton.module.scss +7 -0
- package/src/components/Solver/components/{SolveButton/SolveButton.tsx → FloatingSolveButton/FloatingSolveButton.tsx} +6 -6
- package/src/components/Solver/components/FloatingSolveButton/index.ts +1 -0
- package/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.module.scss +19 -4
- package/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.tsx +13 -1
- package/src/components/Solver/components/index.ts +1 -1
- package/src/components/Spinner/Spinner.module.scss +11 -0
- package/src/components/Spinner/Spinner.tsx +19 -0
- package/src/components/Spinner/index.ts +1 -0
- package/src/components/Tile/Tile.tsx +9 -9
- package/src/components/index.ts +1 -1
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useAppLayout.ts +29 -0
- package/src/i18n/de.json +1 -0
- package/src/i18n/en.json +1 -0
- package/src/i18n/es.json +1 -0
- package/src/i18n/fa.json +1 -0
- package/src/i18n/fr.json +1 -0
- package/src/i18n/index.ts +1 -1
- package/src/i18n/pl.json +1 -0
- package/src/modals/DictionaryModal/DictionaryModal.module.scss +18 -0
- package/src/modals/DictionaryModal/DictionaryModal.tsx +27 -0
- package/src/modals/DictionaryModal/index.ts +1 -0
- package/src/modals/MenuModal/MenuModal.tsx +7 -1
- package/src/modals/ResultsModal/ResultsModal.module.scss +18 -1
- package/src/modals/ResultsModal/ResultsModal.tsx +10 -13
- package/src/modals/index.ts +1 -0
- package/src/pages/api/solve.ts +9 -10
- package/src/pages/index.tsx +20 -15
- package/src/parameters/index.ts +0 -6
- package/src/service-worker/routeSolveRequests.ts +4 -1
- package/src/state/localStorage.ts +0 -9
- package/src/styles/animations.scss +10 -0
- package/src/styles/global.scss +1 -1
- package/src/styles/variables.scss +1 -1
- package/src/types/index.ts +1 -0
- package/.next/server/font-loader-manifest.js +0 -1
- package/.next/static/chunks/main-74c4d6b2b5c362f3.js +0 -1
- package/.next/static/chunks/pages/404-d5ff00df1c687977.js +0 -1
- package/.next/static/chunks/pages/_app-3272e798504c40d8.js +0 -28
- package/.next/static/chunks/pages/index-5c2544930e46c5ce.js +0 -1
- package/.next/static/css/336e75db2b74b157.css +0 -2
- package/.next/static/css/ec4e47a6b1866fe5.css +0 -1
- package/.next/static/warzWo25tDxo_Eiv9T6f2/_buildManifest.js +0 -1
- package/src/components/Solver/components/SolveButton/SolveButton.module.scss +0 -4
- package/src/components/Solver/components/SolveButton/index.ts +0 -1
- package/src/components/Well/Well.module.scss +0 -6
- package/src/components/Well/Well.tsx +0 -17
- package/src/components/Well/index.ts +0 -1
- /package/.next/server/{font-loader-manifest.json → next-font-manifest.json} +0 -0
- /package/.next/static/{warzWo25tDxo_Eiv9T6f2 → 45ye7793DY705HOcuK9lJ}/_ssgManifest.js +0 -0
|
@@ -14,15 +14,16 @@ import {
|
|
|
14
14
|
} from 'state';
|
|
15
15
|
|
|
16
16
|
import Button from '../../../Button';
|
|
17
|
+
import Spinner from '../../../Spinner';
|
|
17
18
|
|
|
18
|
-
import styles from './
|
|
19
|
+
import styles from './FloatingSolveButton.module.scss';
|
|
19
20
|
|
|
20
21
|
interface Props {
|
|
21
22
|
className?: string;
|
|
22
23
|
onClick?: MouseEventHandler;
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
const
|
|
26
|
+
const FloatingSolveButton: FunctionComponent<Props> = ({ className, onClick = noop }) => {
|
|
26
27
|
const dispatch = useDispatch();
|
|
27
28
|
const translate = useTranslate();
|
|
28
29
|
const isLoading = useTypedSelector(selectIsLoading);
|
|
@@ -38,10 +39,9 @@ const SolveButton: FunctionComponent<Props> = ({ className, onClick = noop }) =>
|
|
|
38
39
|
return (
|
|
39
40
|
<Button
|
|
40
41
|
aria-label={translate('results.solve')}
|
|
41
|
-
className={classNames(styles.
|
|
42
|
+
className={classNames(styles.floatingSolveButton, className)}
|
|
42
43
|
disabled={isLoading || !isOutdated || !hasTiles}
|
|
43
|
-
Icon={Search}
|
|
44
|
-
iconClassName={styles.icon}
|
|
44
|
+
Icon={isLoading ? Spinner : Search}
|
|
45
45
|
tooltip={translate('results.solve')}
|
|
46
46
|
type="submit"
|
|
47
47
|
variant="primary"
|
|
@@ -50,4 +50,4 @@ const SolveButton: FunctionComponent<Props> = ({ className, onClick = noop }) =>
|
|
|
50
50
|
);
|
|
51
51
|
};
|
|
52
52
|
|
|
53
|
-
export default
|
|
53
|
+
export default FloatingSolveButton;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './FloatingSolveButton';
|
package/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.module.scss
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
@import 'styles/mixins';
|
|
2
2
|
|
|
3
3
|
.resultCandidatePicker {
|
|
4
|
+
position: relative;
|
|
4
5
|
display: flex;
|
|
5
6
|
gap: var(--spacing--l);
|
|
6
7
|
|
|
@@ -137,14 +138,28 @@
|
|
|
137
138
|
}
|
|
138
139
|
}
|
|
139
140
|
|
|
140
|
-
.
|
|
141
|
-
|
|
141
|
+
.spinnerContainer {
|
|
142
|
+
position: absolute;
|
|
143
|
+
top: 0;
|
|
144
|
+
right: 0;
|
|
145
|
+
bottom: 0;
|
|
146
|
+
left: 0;
|
|
147
|
+
display: flex;
|
|
148
|
+
align-items: center;
|
|
149
|
+
justify-content: center;
|
|
150
|
+
}
|
|
142
151
|
|
|
143
|
-
|
|
144
|
-
|
|
152
|
+
.loading,
|
|
153
|
+
.icon {
|
|
154
|
+
width: var(--button--icon--size);
|
|
155
|
+
height: var(--button--icon--size);
|
|
145
156
|
color: var(--color--inactive);
|
|
146
157
|
}
|
|
147
158
|
|
|
159
|
+
.loading {
|
|
160
|
+
border-color: var(--color--inactive);
|
|
161
|
+
}
|
|
162
|
+
|
|
148
163
|
.insert {
|
|
149
164
|
flex: 0 0 auto;
|
|
150
165
|
}
|
|
@@ -2,10 +2,12 @@ import classNames from 'classnames';
|
|
|
2
2
|
import { FunctionComponent, HTMLProps, MouseEventHandler } from 'react';
|
|
3
3
|
import { useDispatch } from 'react-redux';
|
|
4
4
|
|
|
5
|
+
import { useAppLayout } from 'hooks';
|
|
5
6
|
import { ChevronDown, ChevronLeft, ChevronRight } from 'icons';
|
|
6
7
|
import {
|
|
7
8
|
resultsSlice,
|
|
8
9
|
selectAreResultsOutdated,
|
|
10
|
+
selectIsLoading,
|
|
9
11
|
selectLocale,
|
|
10
12
|
selectResultCandidate,
|
|
11
13
|
selectSortedResults,
|
|
@@ -14,6 +16,7 @@ import {
|
|
|
14
16
|
} from 'state';
|
|
15
17
|
|
|
16
18
|
import Button from '../../../Button';
|
|
19
|
+
import Spinner from '../../../Spinner';
|
|
17
20
|
import InsertButton from '../InsertButton';
|
|
18
21
|
|
|
19
22
|
import styles from './ResultCandidatePicker.module.scss';
|
|
@@ -26,6 +29,7 @@ const ResultCandidatePicker: FunctionComponent<Props> = ({ className, onResultCl
|
|
|
26
29
|
const dispatch = useDispatch();
|
|
27
30
|
const translate = useTranslate();
|
|
28
31
|
const locale = useTypedSelector(selectLocale);
|
|
32
|
+
const isLoading = useTypedSelector(selectIsLoading);
|
|
29
33
|
const isOutdated = useTypedSelector(selectAreResultsOutdated);
|
|
30
34
|
const sortedResults = useTypedSelector(selectSortedResults);
|
|
31
35
|
const results = sortedResults || [];
|
|
@@ -35,6 +39,7 @@ const ResultCandidatePicker: FunctionComponent<Props> = ({ className, onResultCl
|
|
|
35
39
|
const isPreviousDisabled = index <= 0 || disabled;
|
|
36
40
|
const isNextDisabled = index >= results.length - 1 || disabled;
|
|
37
41
|
const bothEnabled = !isPreviousDisabled && !isNextDisabled;
|
|
42
|
+
const { showFloatingSolveButton } = useAppLayout();
|
|
38
43
|
|
|
39
44
|
const handleNextClick = () => {
|
|
40
45
|
if (!isNextDisabled) {
|
|
@@ -87,7 +92,14 @@ const ResultCandidatePicker: FunctionComponent<Props> = ({ className, onResultCl
|
|
|
87
92
|
{!resultCandidate && <div className={styles.word}> </div>}
|
|
88
93
|
|
|
89
94
|
<div className={styles.iconContainer}>
|
|
90
|
-
<ChevronDown className={styles.icon} />
|
|
95
|
+
{showFloatingSolveButton && <ChevronDown className={styles.icon} />}
|
|
96
|
+
|
|
97
|
+
{!showFloatingSolveButton && (
|
|
98
|
+
<>
|
|
99
|
+
{isLoading && <Spinner className={styles.loading} />}
|
|
100
|
+
{!isLoading && <ChevronDown className={styles.icon} />}
|
|
101
|
+
</>
|
|
102
|
+
)}
|
|
91
103
|
</div>
|
|
92
104
|
</button>
|
|
93
105
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { default as EmptyState } from './EmptyState';
|
|
2
|
+
export { default as FloatingSolveButton } from './FloatingSolveButton';
|
|
2
3
|
export { default as InsertButton } from './InsertButton';
|
|
3
4
|
export { default as ResultCandidatePicker } from './ResultCandidatePicker';
|
|
4
|
-
export { default as SolveButton } from './SolveButton';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
@import 'styles/animations';
|
|
2
|
+
|
|
3
|
+
.spinner {
|
|
4
|
+
width: var(--button--icon--size);
|
|
5
|
+
height: var(--button--icon--size);
|
|
6
|
+
border: var(--border);
|
|
7
|
+
border-width: 2px;
|
|
8
|
+
border-radius: 50%;
|
|
9
|
+
border-right-color: transparent !important;
|
|
10
|
+
animation: 750ms linear infinite rotate;
|
|
11
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import classNames from 'classnames';
|
|
2
|
+
import { FunctionComponent } from 'react';
|
|
3
|
+
|
|
4
|
+
import { useTranslate } from 'state';
|
|
5
|
+
|
|
6
|
+
import styles from './Spinner.module.scss';
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
className?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const Spinner: FunctionComponent<Props> = ({ className }) => {
|
|
13
|
+
const translate = useTranslate();
|
|
14
|
+
const translation = translate('common.loading');
|
|
15
|
+
|
|
16
|
+
return <div aria-label={translation} className={classNames(styles.spinner, className)} role="status" />;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default Spinner;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './Spinner';
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
+
import { useMergeRefs } from '@floating-ui/react';
|
|
1
2
|
import { EMPTY_CELL } from '@scrabble-solver/constants';
|
|
2
|
-
import mergeRefs from 'merge-refs';
|
|
3
3
|
import {
|
|
4
4
|
ChangeEventHandler,
|
|
5
5
|
FocusEventHandler,
|
|
6
6
|
FunctionComponent,
|
|
7
7
|
KeyboardEventHandler,
|
|
8
|
-
|
|
8
|
+
Ref,
|
|
9
9
|
useEffect,
|
|
10
10
|
useMemo,
|
|
11
11
|
useRef,
|
|
12
12
|
} from 'react';
|
|
13
13
|
|
|
14
|
-
import {
|
|
14
|
+
import { useAppLayout } from 'hooks';
|
|
15
15
|
import { getTileSizes, noop } from 'lib';
|
|
16
16
|
import { EASE_OUT_CUBIC, TILE_APPEAR_DURATION, TILE_APPEAR_KEYFRAMES } from 'parameters';
|
|
17
17
|
import { selectLocale, useTypedSelector } from 'state';
|
|
@@ -25,7 +25,7 @@ interface Props {
|
|
|
25
25
|
className?: string;
|
|
26
26
|
disabled?: boolean;
|
|
27
27
|
highlighted?: boolean;
|
|
28
|
-
inputRef?:
|
|
28
|
+
inputRef?: Ref<HTMLInputElement>;
|
|
29
29
|
isBlank?: boolean;
|
|
30
30
|
isValid?: boolean;
|
|
31
31
|
placeholder?: string;
|
|
@@ -58,15 +58,15 @@ const Tile: FunctionComponent<Props> = ({
|
|
|
58
58
|
onKeyDown = noop,
|
|
59
59
|
}) => {
|
|
60
60
|
const locale = useTypedSelector(selectLocale);
|
|
61
|
+
const { animateTile, showTilePoints } = useAppLayout();
|
|
61
62
|
const { pointsFontSize, tileFontSize, tileSize } = getTileSizes(size);
|
|
62
63
|
const style = useMemo(() => ({ height: tileSize, width: tileSize }), [tileSize]);
|
|
63
64
|
const characterStyle = useMemo(() => ({ fontSize: tileFontSize }), [tileFontSize]);
|
|
64
65
|
const pointsStyle = useMemo(() => ({ fontSize: pointsFontSize }), [pointsFontSize]);
|
|
65
66
|
const ref = useRef<HTMLInputElement>(null);
|
|
66
|
-
const mergedRef = inputRef ?
|
|
67
|
+
const mergedRef = useMergeRefs(inputRef ? [ref, inputRef] : [ref]);
|
|
67
68
|
const isEmpty = !character || character === EMPTY_CELL;
|
|
68
|
-
const
|
|
69
|
-
const canShowPoints = (isBlank || !isEmpty) && typeof points !== 'undefined' && !isLessThanXs;
|
|
69
|
+
const canShowPoints = showTilePoints && (!isEmpty || isBlank) && typeof points !== 'undefined';
|
|
70
70
|
const pointsFormatted = typeof points === 'number' ? points.toLocaleString(locale) : '';
|
|
71
71
|
|
|
72
72
|
const handleKeyDown: KeyboardEventHandler<HTMLInputElement> = (event) => {
|
|
@@ -81,7 +81,7 @@ const Tile: FunctionComponent<Props> = ({
|
|
|
81
81
|
}, [autoFocus, ref]);
|
|
82
82
|
|
|
83
83
|
useEffect(() => {
|
|
84
|
-
if (!ref.current?.parentElement || !character ||
|
|
84
|
+
if (!ref.current?.parentElement || !character || !animateTile) {
|
|
85
85
|
return;
|
|
86
86
|
}
|
|
87
87
|
|
|
@@ -90,7 +90,7 @@ const Tile: FunctionComponent<Props> = ({
|
|
|
90
90
|
easing: EASE_OUT_CUBIC,
|
|
91
91
|
fill: 'forwards',
|
|
92
92
|
});
|
|
93
|
-
}, [character,
|
|
93
|
+
}, [character, animateTile]);
|
|
94
94
|
|
|
95
95
|
return (
|
|
96
96
|
<TilePure
|
package/src/components/index.ts
CHANGED
|
@@ -22,9 +22,9 @@ export { default as ResultsInput } from './ResultsInput';
|
|
|
22
22
|
export { default as SeoMessage } from './SeoMessage';
|
|
23
23
|
export { default as Sizer } from './Sizer';
|
|
24
24
|
export { default as Solver } from './Solver';
|
|
25
|
+
export { default as Spinner } from './Spinner';
|
|
25
26
|
export { default as SplashScreen } from './SplashScreen';
|
|
26
27
|
export { default as SvgFontCss } from './SvgFontCss';
|
|
27
28
|
export { default as SvgFontFix } from './SvgFontFix';
|
|
28
29
|
export { default as Tile } from './Tile';
|
|
29
30
|
export { useTooltip } from './Tooltip';
|
|
30
|
-
export { default as Well } from './Well';
|
package/src/hooks/index.ts
CHANGED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { COMPONENTS_SPACING, COMPONENTS_SPACING_SMALL } from 'parameters';
|
|
2
|
+
|
|
3
|
+
import useIsTouchDevice from './useIsTouchDevice';
|
|
4
|
+
import useMediaQuery from './useMediaQuery';
|
|
5
|
+
|
|
6
|
+
const useAppLayout = () => {
|
|
7
|
+
const isTouchDevice = useIsTouchDevice();
|
|
8
|
+
const isLessThanXs = useMediaQuery('<xs');
|
|
9
|
+
const isLessThanS = useMediaQuery('<s');
|
|
10
|
+
const isLessThanM = useMediaQuery('<m');
|
|
11
|
+
const isLessThanL = useMediaQuery('<l');
|
|
12
|
+
const isLessThanXl = useMediaQuery('<xl');
|
|
13
|
+
const showColumn = !isLessThanL;
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
animateTile: !isLessThanXs,
|
|
17
|
+
componentsSpacing: isLessThanXl ? COMPONENTS_SPACING_SMALL : COMPONENTS_SPACING,
|
|
18
|
+
isBoardFullWidth: isLessThanM,
|
|
19
|
+
showColumn,
|
|
20
|
+
showCompactControls: !showColumn,
|
|
21
|
+
showFloatingSolveButton: isTouchDevice,
|
|
22
|
+
showKeyMap: !isTouchDevice,
|
|
23
|
+
showResultsInModal: isLessThanL,
|
|
24
|
+
showShortNav: isLessThanS,
|
|
25
|
+
showTilePoints: !isLessThanXs,
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export default useAppLayout;
|
package/src/i18n/de.json
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"common.vowels": "Vokale",
|
|
18
18
|
"common.word": "Wort",
|
|
19
19
|
"common.words": "Wörter",
|
|
20
|
+
"dictionary": "Wörterbuch",
|
|
20
21
|
"dictionary.empty-state.no-definitions": "Wort existiert im Wörterbuch aber hat keine Definition.",
|
|
21
22
|
"dictionary.empty-state.no-results": "Wort kann nicht im Wörterbuch gefunden werden.",
|
|
22
23
|
"dictionary.empty-state.not-allowed": "Dieses Wort ist nicht erlaubt.",
|
package/src/i18n/en.json
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"common.vowels": "Vowels",
|
|
18
18
|
"common.word": "Word",
|
|
19
19
|
"common.words": "Words",
|
|
20
|
+
"dictionary": "Dictionary",
|
|
20
21
|
"dictionary.empty-state.no-definitions": "Word exists in the dictionary but it does not have a definition.",
|
|
21
22
|
"dictionary.empty-state.no-results": "Unable to find word definition in the dictionary.",
|
|
22
23
|
"dictionary.empty-state.not-allowed": "This word is not allowed.",
|
package/src/i18n/es.json
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"common.vowels": "Vocales",
|
|
18
18
|
"common.word": "Palabra",
|
|
19
19
|
"common.words": "Palabras",
|
|
20
|
+
"dictionary": "Diccionario",
|
|
20
21
|
"dictionary.empty-state.no-definitions": "La palabra existe en el diccionario pero no tiene una definición.",
|
|
21
22
|
"dictionary.empty-state.no-results": "No se puede encontrar la definición de palabra en el diccionario.",
|
|
22
23
|
"dictionary.empty-state.not-allowed": "Esta palabra no es aceptable.",
|
package/src/i18n/fa.json
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"common.vowels": "حروف مصوت",
|
|
18
18
|
"common.word": "کلمه",
|
|
19
19
|
"common.words": "کلمات",
|
|
20
|
+
"dictionary": "فرهنگ لغت",
|
|
20
21
|
"dictionary.empty-state.no-definitions": "کلمه در فرهنگ لغت وجود دارد، ولی معنایی برای آن ثبت نشده است.",
|
|
21
22
|
"dictionary.empty-state.no-results": "کلمه در فرهنگ لغت یافت نشد.",
|
|
22
23
|
"dictionary.empty-state.not-allowed": "این کلمه مجاز نیست.",
|
package/src/i18n/fr.json
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"common.vowels": "Voyelles",
|
|
18
18
|
"common.word": "Mot",
|
|
19
19
|
"common.words": "Mots",
|
|
20
|
+
"dictionary": "Dictionnaire",
|
|
20
21
|
"dictionary.empty-state.no-definitions": "Le mot existe dans le dictionary mais n'a pas de définition.",
|
|
21
22
|
"dictionary.empty-state.no-results": "Impossible de trouver une définition pour ce mot dans le dictionaire.",
|
|
22
23
|
"dictionary.empty-state.not-allowed": "Ce mot n'est pas pas acceptable.",
|
package/src/i18n/index.ts
CHANGED
package/src/i18n/pl.json
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"common.vowels": "Samogłoski",
|
|
18
18
|
"common.word": "Słowo",
|
|
19
19
|
"common.words": "Słowa",
|
|
20
|
+
"dictionary": "Słownik",
|
|
20
21
|
"dictionary.empty-state.no-definitions": "Słowo istnieje w słowniku ale nie posiada definicji.",
|
|
21
22
|
"dictionary.empty-state.no-results": "Nie udało się znaleźć definicji słowa w słowniku.",
|
|
22
23
|
"dictionary.empty-state.not-allowed": "To słowo nie jest dopuszczalne w grach.",
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
.content {
|
|
2
|
+
height: 100%;
|
|
3
|
+
display: flex;
|
|
4
|
+
flex-direction: column;
|
|
5
|
+
background-color: var(--color--background--element);
|
|
6
|
+
border: var(--border);
|
|
7
|
+
box-shadow: var(--box-shadow);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.dictionary {
|
|
11
|
+
flex: 1;
|
|
12
|
+
border-bottom: var(--border);
|
|
13
|
+
max-height: initial;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.dictionaryInput {
|
|
17
|
+
flex: 0 0 auto;
|
|
18
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { FunctionComponent } from 'react';
|
|
2
|
+
|
|
3
|
+
import { Dictionary, DictionaryInput, Modal } from 'components';
|
|
4
|
+
import { useTranslate } from 'state';
|
|
5
|
+
|
|
6
|
+
import styles from './DictionaryModal.module.scss';
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
className?: string;
|
|
10
|
+
isOpen: boolean;
|
|
11
|
+
onClose: () => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const DictionaryModal: FunctionComponent<Props> = ({ className, isOpen, onClose }) => {
|
|
15
|
+
const translate = useTranslate();
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<Modal className={className} isOpen={isOpen} title={translate('dictionary')} onClose={onClose}>
|
|
19
|
+
<div className={styles.content}>
|
|
20
|
+
<Dictionary className={styles.dictionary} />
|
|
21
|
+
<DictionaryInput className={styles.dictionaryInput} />
|
|
22
|
+
</div>
|
|
23
|
+
</Modal>
|
|
24
|
+
);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export default DictionaryModal;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './DictionaryModal';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { FunctionComponent } from 'react';
|
|
2
2
|
|
|
3
3
|
import { Button, Modal } from 'components';
|
|
4
|
-
import { CardChecklist, Cog, Github, Sack } from 'icons';
|
|
4
|
+
import { BookHalf, CardChecklist, Cog, Github, Sack } from 'icons';
|
|
5
5
|
import { GITHUB_PROJECT_URL } from 'parameters';
|
|
6
6
|
import { useTranslate } from 'state';
|
|
7
7
|
|
|
@@ -11,6 +11,7 @@ interface Props {
|
|
|
11
11
|
className?: string;
|
|
12
12
|
isOpen: boolean;
|
|
13
13
|
onClose: () => void;
|
|
14
|
+
onShowDictionary: () => void;
|
|
14
15
|
onShowRemainingTiles: () => void;
|
|
15
16
|
onShowSettings: () => void;
|
|
16
17
|
onShowWords: () => void;
|
|
@@ -20,6 +21,7 @@ const Menu: FunctionComponent<Props> = ({
|
|
|
20
21
|
className,
|
|
21
22
|
isOpen,
|
|
22
23
|
onClose,
|
|
24
|
+
onShowDictionary,
|
|
23
25
|
onShowRemainingTiles,
|
|
24
26
|
onShowSettings,
|
|
25
27
|
onShowWords,
|
|
@@ -41,6 +43,10 @@ const Menu: FunctionComponent<Props> = ({
|
|
|
41
43
|
{translate('words')}
|
|
42
44
|
</Button>
|
|
43
45
|
|
|
46
|
+
<Button aria-label={translate('dictionary')} className={styles.button} Icon={BookHalf} onClick={onShowDictionary}>
|
|
47
|
+
{translate('dictionary')}
|
|
48
|
+
</Button>
|
|
49
|
+
|
|
44
50
|
<Button.Link
|
|
45
51
|
aria-label={translate('github')}
|
|
46
52
|
className={styles.button}
|
|
@@ -1,7 +1,24 @@
|
|
|
1
|
+
@import 'styles/mixins';
|
|
2
|
+
|
|
1
3
|
.content {
|
|
2
4
|
position: relative;
|
|
3
5
|
display: flex;
|
|
4
6
|
flex-direction: column;
|
|
5
7
|
height: 100%;
|
|
6
|
-
|
|
8
|
+
gap: var(--spacing--l);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.results {
|
|
12
|
+
flex: 1;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.dictionary {
|
|
16
|
+
flex: 0 0 calc(var(--dictionary--height) - var(--text-input--height));
|
|
17
|
+
background-color: var(--color--background--element);
|
|
18
|
+
border: var(--border);
|
|
19
|
+
box-shadow: var(--box-shadow);
|
|
20
|
+
|
|
21
|
+
@media (max-height: 600px) {
|
|
22
|
+
display: none;
|
|
23
|
+
}
|
|
7
24
|
}
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { Result } from '@scrabble-solver/types';
|
|
2
2
|
import { FunctionComponent, useMemo } from 'react';
|
|
3
3
|
import { useDispatch } from 'react-redux';
|
|
4
|
-
import { useMeasure } from 'react-use';
|
|
5
4
|
|
|
6
|
-
import { Modal, Results
|
|
7
|
-
import { useMediaQuery } from 'hooks';
|
|
5
|
+
import { Dictionary, Modal, Results } from 'components';
|
|
8
6
|
import {
|
|
9
7
|
resultsSlice,
|
|
10
8
|
selectResultCandidate,
|
|
@@ -24,8 +22,6 @@ interface Props {
|
|
|
24
22
|
const ResultsModal: FunctionComponent<Props> = ({ className, isOpen, onClose }) => {
|
|
25
23
|
const dispatch = useDispatch();
|
|
26
24
|
const translate = useTranslate();
|
|
27
|
-
const [sizerRef, { height, width }] = useMeasure<HTMLDivElement>();
|
|
28
|
-
const isLessThanS = useMediaQuery('<s');
|
|
29
25
|
const results = useTypedSelector(selectSortedFilteredResults);
|
|
30
26
|
const resultCandidate = useTypedSelector(selectResultCandidate);
|
|
31
27
|
const index = (results || []).findIndex((result) => result.id === resultCandidate?.id);
|
|
@@ -34,23 +30,24 @@ const ResultsModal: FunctionComponent<Props> = ({ className, isOpen, onClose })
|
|
|
34
30
|
const callbacks = useMemo(
|
|
35
31
|
() => ({
|
|
36
32
|
onClick: (result: Result) => {
|
|
37
|
-
|
|
33
|
+
const isSelected = result === resultCandidate;
|
|
38
34
|
|
|
39
|
-
if (
|
|
35
|
+
if (isSelected) {
|
|
40
36
|
onClose();
|
|
37
|
+
} else {
|
|
38
|
+
dispatch(resultsSlice.actions.changeResultCandidate(result));
|
|
41
39
|
}
|
|
42
40
|
},
|
|
43
41
|
}),
|
|
44
|
-
[dispatch,
|
|
42
|
+
[dispatch, onClose, resultCandidate],
|
|
45
43
|
);
|
|
46
44
|
|
|
47
45
|
return (
|
|
48
46
|
<Modal className={className} isOpen={isOpen} title={translate('results')} onClose={onClose}>
|
|
49
|
-
<
|
|
50
|
-
<
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
</Well>
|
|
47
|
+
<div className={styles.content}>
|
|
48
|
+
<Results callbacks={callbacks} className={styles.results} highlightedIndex={highlightedIndex} />
|
|
49
|
+
<Dictionary className={styles.dictionary} />
|
|
50
|
+
</div>
|
|
54
51
|
</Modal>
|
|
55
52
|
);
|
|
56
53
|
};
|
package/src/modals/index.ts
CHANGED
package/src/pages/api/solve.ts
CHANGED
|
@@ -57,30 +57,29 @@ const parseRequest = (request: NextApiRequest): RequestData => {
|
|
|
57
57
|
throw new Error('Invalid "configId" parameter');
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
const config = getLocaleConfig(configId, locale);
|
|
61
|
-
|
|
62
|
-
if (!isBoardJson(boardJson) || !isBoardValid(boardJson, config)) {
|
|
63
|
-
throw new Error('Invalid "board" parameter');
|
|
64
|
-
}
|
|
65
|
-
|
|
66
60
|
if (!isStringArray(characters) || characters.length === 0) {
|
|
67
61
|
throw new Error('Invalid "characters" parameter');
|
|
68
62
|
}
|
|
69
63
|
|
|
64
|
+
const config = getLocaleConfig(configId, locale);
|
|
65
|
+
|
|
70
66
|
for (const character of characters) {
|
|
71
67
|
if (!config.hasCharacter(character) && character !== BLANK) {
|
|
72
68
|
throw new Error('Invalid "characters" parameter');
|
|
73
69
|
}
|
|
74
70
|
}
|
|
75
|
-
|
|
76
|
-
const board = Board.fromJson(boardJson);
|
|
77
|
-
const blankTilesCount = characters.filter((character) => character === BLANK).length;
|
|
78
|
-
const blanksCount = board.getBlanksCount() + blankTilesCount;
|
|
71
|
+
const blanksCount = characters.filter((character) => character === BLANK).length;
|
|
79
72
|
|
|
80
73
|
if (blanksCount > config.blanksCount) {
|
|
81
74
|
throw new Error('Too many blank tiles passed');
|
|
82
75
|
}
|
|
83
76
|
|
|
77
|
+
if (!isBoardJson(boardJson) || !isBoardValid(boardJson, config)) {
|
|
78
|
+
throw new Error('Invalid "board" parameter');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const board = Board.fromJson(boardJson);
|
|
82
|
+
|
|
84
83
|
return {
|
|
85
84
|
board,
|
|
86
85
|
characters,
|
package/src/pages/index.tsx
CHANGED
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
import classNames from 'classnames';
|
|
2
2
|
import fs from 'fs';
|
|
3
3
|
import path from 'path';
|
|
4
|
-
import {
|
|
4
|
+
import { FunctionComponent, useEffect, useState } from 'react';
|
|
5
5
|
import ReactModal from 'react-modal';
|
|
6
6
|
import { useDispatch } from 'react-redux';
|
|
7
7
|
import { useEffectOnce, useMeasure } from 'react-use';
|
|
8
8
|
|
|
9
9
|
import { Logo, LogoSplashScreen, NavButtons, Solver, SvgFontFix } from 'components';
|
|
10
|
-
import { useDirection, useLanguage, useLocalStorage
|
|
10
|
+
import { useAppLayout, useDirection, useLanguage, useLocalStorage } from 'hooks';
|
|
11
11
|
import { LOCALE_FEATURES } from 'i18n';
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
DictionaryModal,
|
|
14
|
+
KeyMapModal,
|
|
15
|
+
MenuModal,
|
|
16
|
+
RemainingTilesModal,
|
|
17
|
+
ResultsModal,
|
|
18
|
+
SettingsModal,
|
|
19
|
+
WordsModal,
|
|
20
|
+
} from 'modals';
|
|
13
21
|
import { INITIALIZATION_DURATION } from 'parameters';
|
|
14
22
|
import { registerServiceWorker } from 'serviceWorkerManager';
|
|
15
|
-
import { initialize,
|
|
23
|
+
import { initialize, reset, selectLocale, useTypedSelector } from 'state';
|
|
16
24
|
|
|
17
25
|
import styles from './index.module.scss';
|
|
18
26
|
|
|
@@ -26,7 +34,8 @@ interface Props {
|
|
|
26
34
|
const Index: FunctionComponent<Props> = ({ version }) => {
|
|
27
35
|
const dispatch = useDispatch();
|
|
28
36
|
const locale = useTypedSelector(selectLocale);
|
|
29
|
-
const
|
|
37
|
+
const { showResultsInModal } = useAppLayout();
|
|
38
|
+
const [showDictionary, setShowDictionary] = useState(false);
|
|
30
39
|
const [showKeyMap, setShowKeyMap] = useState(false);
|
|
31
40
|
const [showMenu, setShowMenu] = useState(false);
|
|
32
41
|
const [showRemainingTiles, setShowRemainingTiles] = useState(false);
|
|
@@ -44,13 +53,6 @@ const Index: FunctionComponent<Props> = ({ version }) => {
|
|
|
44
53
|
dispatch(reset());
|
|
45
54
|
};
|
|
46
55
|
|
|
47
|
-
const handleSplashAnimationEnd = (event: AnimationEvent<HTMLDivElement>) => {
|
|
48
|
-
if (event.target === event.currentTarget && !localStorage.getHasVisited()) {
|
|
49
|
-
setShowSettings(true);
|
|
50
|
-
localStorage.setHasVisited(true);
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
|
|
54
56
|
useDirection(LOCALE_FEATURES[locale].direction);
|
|
55
57
|
useLanguage(locale);
|
|
56
58
|
useLocalStorage();
|
|
@@ -65,10 +67,10 @@ const Index: FunctionComponent<Props> = ({ version }) => {
|
|
|
65
67
|
});
|
|
66
68
|
|
|
67
69
|
useEffect(() => {
|
|
68
|
-
if (!
|
|
70
|
+
if (!showResultsInModal) {
|
|
69
71
|
setShowResults(false);
|
|
70
72
|
}
|
|
71
|
-
}, [
|
|
73
|
+
}, [showResultsInModal]);
|
|
72
74
|
|
|
73
75
|
useEffect(() => {
|
|
74
76
|
if (process.env.NODE_ENV === 'production') {
|
|
@@ -115,6 +117,7 @@ const Index: FunctionComponent<Props> = ({ version }) => {
|
|
|
115
117
|
<MenuModal
|
|
116
118
|
isOpen={showMenu}
|
|
117
119
|
onClose={() => setShowMenu(false)}
|
|
120
|
+
onShowDictionary={() => setShowDictionary(true)}
|
|
118
121
|
onShowRemainingTiles={() => setShowRemainingTiles(true)}
|
|
119
122
|
onShowSettings={() => setShowSettings(true)}
|
|
120
123
|
onShowWords={() => setShowWords(true)}
|
|
@@ -130,7 +133,9 @@ const Index: FunctionComponent<Props> = ({ version }) => {
|
|
|
130
133
|
|
|
131
134
|
<ResultsModal isOpen={showResults} onClose={() => setShowResults(false)} />
|
|
132
135
|
|
|
133
|
-
<
|
|
136
|
+
<DictionaryModal isOpen={showDictionary} onClose={() => setShowDictionary(false)} />
|
|
137
|
+
|
|
138
|
+
<LogoSplashScreen forceShow={!isInitialized} />
|
|
134
139
|
</>
|
|
135
140
|
);
|
|
136
141
|
};
|