@scrabble-solver/scrabble-solver 2.11.4 → 2.11.6
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 +7 -7
- 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/export-marker.json +1 -1
- 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/131.js +1 -1
- package/.next/server/chunks/277.js +851 -1179
- package/.next/server/chunks/636.js +286 -0
- package/.next/server/chunks/675.js +550 -0
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/pages/404.html +1 -5
- package/.next/server/pages/404.js.nft.json +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/pages/_app.js +73 -9
- package/.next/server/pages/_app.js.nft.json +1 -1
- package/.next/server/pages/_document.js.nft.json +1 -1
- package/.next/server/pages/_error.js +1 -280
- package/.next/server/pages/_error.js.nft.json +1 -1
- package/.next/server/pages/api/solve.js +22 -2
- package/.next/server/pages/index.html +1 -1
- package/.next/server/pages/index.js +381 -314
- 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/Jmk00rVXCbdjFgP77tKXQ/_buildManifest.js +1 -0
- package/.next/static/chunks/pages/{404-448ba28510855455.js → 404-8176f4acd0cfeb42.js} +1 -1
- package/.next/static/chunks/pages/_app-b4fa92112b8f0385.js +28 -0
- package/.next/static/chunks/pages/index-ccd762f8f5028729.js +1 -0
- package/.next/static/css/1cd302e7648d209c.css +2 -0
- package/.next/static/css/34adfcf12a7d9bb6.css +1 -0
- package/.next/trace +50 -53
- package/next.config.js +1 -0
- package/package.json +12 -13
- package/src/@types/svg.d.ts +1 -1
- package/src/components/Board/Board.tsx +48 -44
- package/src/components/Board/components/Actions/Actions.tsx +4 -2
- package/src/components/Board/components/Cell/Cell.module.scss +59 -5
- package/src/components/Board/hooks/useGrid.ts +5 -3
- package/src/components/Button/Button.module.scss +1 -1
- package/src/components/Dictionary/Dictionary.module.scss +0 -1
- package/src/components/EmptyState/EmptyState.module.scss +0 -1
- package/src/components/Key/Key.module.scss +1 -1
- package/src/components/Loading/Loading.module.scss +1 -1
- package/src/components/Loading/Loading.tsx +1 -1
- package/src/components/Logo/Logo.tsx +10 -12
- package/src/components/Logo/LogoBlueprint.tsx +21 -0
- package/src/components/Logo/index.ts +1 -1
- package/src/components/Modal/Modal.module.scss +1 -6
- package/src/components/Modal/Modal.tsx +15 -8
- package/src/components/NavButtons/NavButtons.tsx +2 -2
- package/src/components/PlainTiles/PlainTiles.tsx +0 -10
- package/src/components/PlainTiles/Tile.tsx +1 -4
- package/src/components/Rack/Rack.module.scss +59 -0
- package/src/components/Results/HeaderButton.tsx +6 -6
- package/src/components/Results/Results.module.scss +3 -1
- package/src/components/Results/Results.tsx +7 -7
- package/src/components/Results/useColumns.ts +2 -5
- package/src/components/Solver/Solver.tsx +6 -23
- package/src/components/Tile/Tile.module.scss +2 -1
- package/src/components/Tile/Tile.tsx +8 -4
- package/src/components/index.ts +0 -5
- package/src/hooks/index.ts +6 -0
- package/src/hooks/useAppLayout.ts +62 -12
- package/src/hooks/useDirection.ts +2 -2
- package/src/hooks/useEffectOnce.ts +5 -0
- package/src/hooks/useIsTouchDevice.ts +1 -1
- package/src/hooks/useLanguage.ts +2 -2
- package/src/hooks/useLatest.ts +13 -0
- package/src/hooks/useLocalStorage.ts +51 -0
- package/src/hooks/useMedia.ts +36 -0
- package/src/hooks/useMediaQueries.ts +13 -0
- package/src/hooks/useMediaQuery.ts +2 -1
- package/src/hooks/useOnWindowResize.ts +13 -0
- package/src/hooks/useViewportSize.ts +19 -0
- package/src/i18n/constants.ts +14 -22
- package/src/lib/arrayEquals.ts +5 -0
- package/src/lib/index.ts +1 -0
- package/src/lib/zipCharactersAndTiles.ts +3 -1
- package/src/modals/DictionaryModal/DictionaryModal.tsx +2 -2
- package/src/modals/KeyMapModal/KeyMapModal.tsx +2 -2
- package/src/modals/KeyMapModal/components/Mapping/Mapping.module.scss +0 -1
- package/src/modals/KeyMapModal/keys.tsx +0 -2
- package/src/modals/MenuModal/MenuModal.module.scss +28 -4
- package/src/modals/MenuModal/MenuModal.tsx +4 -4
- package/src/modals/RemainingTilesModal/RemainingTilesModal.tsx +2 -2
- package/src/modals/ResultsModal/ResultsModal.module.scss +1 -5
- package/src/modals/ResultsModal/ResultsModal.tsx +10 -2
- package/src/modals/SettingsModal/SettingsModal.tsx +2 -2
- package/src/modals/SettingsModal/components/AutoGroupTilesSetting/lib.ts +3 -1
- package/src/modals/SettingsModal/components/ConfigSetting/ConfigSetting.module.scss +0 -1
- package/src/modals/SettingsModal/components/LocaleSetting/LocaleSetting.module.scss +1 -6
- package/src/modals/WordsModal/WordsModal.tsx +2 -2
- package/src/pages/index.module.scss +3 -21
- package/src/pages/index.tsx +51 -69
- package/src/parameters/index.ts +29 -2
- package/src/state/localStorage.ts +13 -2
- package/src/state/sagas.ts +16 -8
- package/src/state/slices/boardInitialState.ts +5 -1
- package/src/state/slices/boardSlice.ts +2 -2
- package/src/state/slices/rackInitialState.ts +8 -2
- package/src/state/slices/rackSlice.ts +16 -13
- package/src/state/slices/settingsInitialState.ts +9 -4
- package/src/state/slices/settingsSlice.ts +3 -1
- package/src/styles/animations.scss +0 -20
- package/src/styles/global.scss +4 -15
- package/src/styles/mixins.scss +0 -60
- package/src/styles/variables.scss +14 -5
- package/src/types/index.ts +4 -0
- package/.next/static/MvHZRF4XuJ7g8LLLRkf8U/_buildManifest.js +0 -1
- package/.next/static/chunks/pages/_app-66d80a5594aab8dc.js +0 -28
- package/.next/static/chunks/pages/index-0858deea02b2a417.js +0 -1
- package/.next/static/css/885da289cec275b3.css +0 -1
- package/.next/static/css/ea1c8134fe9a143e.css +0 -2
- package/src/components/LogoSplashScreen/LogoSplashScreen.module.scss +0 -65
- package/src/components/LogoSplashScreen/LogoSplashScreen.tsx +0 -31
- package/src/components/LogoSplashScreen/index.ts +0 -1
- package/src/components/Sizer/Sizer.module.scss +0 -10
- package/src/components/Sizer/Sizer.tsx +0 -10
- package/src/components/Sizer/index.ts +0 -1
- package/src/components/SplashScreen/SplashScreen.module.scss +0 -14
- package/src/components/SplashScreen/SplashScreen.tsx +0 -19
- package/src/components/SplashScreen/index.ts +0 -1
- package/src/components/SvgFontCss/SvgFontCss.tsx +0 -14
- package/src/components/SvgFontCss/createCss.ts +0 -11
- package/src/components/SvgFontCss/createStyle.ts +0 -9
- package/src/components/SvgFontCss/createSvg.ts +0 -10
- package/src/components/SvgFontCss/index.ts +0 -1
- package/src/components/SvgFontFix/SvgFontFix.module.scss +0 -5
- package/src/components/SvgFontFix/SvgFontFix.tsx +0 -21
- package/src/components/SvgFontFix/index.ts +0 -1
- package/src/hooks/useLocalStorage/index.ts +0 -1
- package/src/hooks/useLocalStorage/useLocalStorage.ts +0 -13
- package/src/hooks/useLocalStorage/useLocalStorageBoard.ts +0 -29
- package/src/hooks/useLocalStorage/useLocalStorageConfigId.ts +0 -29
- package/src/hooks/useLocalStorage/useLocalStorageLocale.ts +0 -32
- package/src/hooks/useLocalStorage/useLocalStorageRack.ts +0 -29
- /package/.next/static/{MvHZRF4XuJ7g8LLLRkf8U → Jmk00rVXCbdjFgP77tKXQ}/_ssgManifest.js +0 -0
- /package/{src/components/Logo/Logo.svg → public/logo.svg} +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import classNames from 'classnames';
|
|
2
|
-
import { ReactElement } from 'react';
|
|
2
|
+
import { ReactElement, useCallback } from 'react';
|
|
3
3
|
import { useDispatch } from 'react-redux';
|
|
4
4
|
|
|
5
5
|
import { SortDown, SortUp } from 'icons';
|
|
6
6
|
import { resultsSlice, selectResultsSort, useTranslate, useTypedSelector } from 'state';
|
|
7
|
-
import {
|
|
7
|
+
import { SortDirection } from 'types';
|
|
8
8
|
|
|
9
9
|
import { useTooltip } from '../Tooltip';
|
|
10
10
|
|
|
@@ -21,9 +21,9 @@ const HeaderButton = ({ column }: Props): ReactElement => {
|
|
|
21
21
|
const sort = useTypedSelector(selectResultsSort);
|
|
22
22
|
const triggerProps = useTooltip(translate(column.translationKey));
|
|
23
23
|
|
|
24
|
-
const
|
|
25
|
-
dispatch(resultsSlice.actions.sort(
|
|
26
|
-
};
|
|
24
|
+
const handleClick = useCallback(() => {
|
|
25
|
+
dispatch(resultsSlice.actions.sort(column.id));
|
|
26
|
+
}, [column.id, dispatch]);
|
|
27
27
|
|
|
28
28
|
return (
|
|
29
29
|
<button
|
|
@@ -31,7 +31,7 @@ const HeaderButton = ({ column }: Props): ReactElement => {
|
|
|
31
31
|
className={classNames(styles.headerButton, column.className)}
|
|
32
32
|
key={column.id}
|
|
33
33
|
type="button"
|
|
34
|
-
onClick={
|
|
34
|
+
onClick={handleClick}
|
|
35
35
|
{...triggerProps}
|
|
36
36
|
>
|
|
37
37
|
<span className={styles.cell}>
|
|
@@ -10,7 +10,6 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
|
|
|
10
10
|
background: var(--color--background--element);
|
|
11
11
|
border: var(--border);
|
|
12
12
|
border-radius: var(--border--radius);
|
|
13
|
-
font-family: var(--font--family--title);
|
|
14
13
|
}
|
|
15
14
|
|
|
16
15
|
.emptyState {
|
|
@@ -26,6 +25,9 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
|
|
|
26
25
|
.listContainer {
|
|
27
26
|
position: absolute;
|
|
28
27
|
top: 0;
|
|
28
|
+
left: 0;
|
|
29
|
+
right: 0;
|
|
30
|
+
bottom: 0;
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
.list {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import classNames from 'classnames';
|
|
2
2
|
import { FunctionComponent, useEffect, useMemo, useState } from 'react';
|
|
3
|
-
import { useLatest, useMeasure } from 'react-use';
|
|
4
3
|
import { FixedSizeList } from 'react-window';
|
|
5
4
|
|
|
5
|
+
import { useAppLayout, useLatest } from 'hooks';
|
|
6
6
|
import { LOCALE_FEATURES } from 'i18n';
|
|
7
|
-
import { RESULTS_ITEM_HEIGHT } from 'parameters';
|
|
7
|
+
import { BORDER_WIDTH, RESULTS_HEADER_HEIGHT, RESULTS_ITEM_HEIGHT, TEXT_INPUT_HEIGHT } from 'parameters';
|
|
8
8
|
import {
|
|
9
9
|
selectAreResultsOutdated,
|
|
10
10
|
selectIsLoading,
|
|
@@ -18,7 +18,6 @@ import {
|
|
|
18
18
|
import EmptyState from '../EmptyState';
|
|
19
19
|
import Loading from '../Loading';
|
|
20
20
|
import ResultsInput from '../ResultsInput';
|
|
21
|
-
import Sizer from '../Sizer';
|
|
22
21
|
|
|
23
22
|
import HeaderButton from './HeaderButton';
|
|
24
23
|
import Result from './Result';
|
|
@@ -35,6 +34,7 @@ interface Props {
|
|
|
35
34
|
|
|
36
35
|
const Results: FunctionComponent<Props> = ({ callbacks, className, highlightedIndex }) => {
|
|
37
36
|
const translate = useTranslate();
|
|
37
|
+
const { resultsHeight, resultsWidth } = useAppLayout();
|
|
38
38
|
const locale = useTypedSelector(selectLocale);
|
|
39
39
|
const { direction } = LOCALE_FEATURES[locale];
|
|
40
40
|
const results = useTypedSelector(selectResults);
|
|
@@ -42,12 +42,14 @@ const Results: FunctionComponent<Props> = ({ callbacks, className, highlightedIn
|
|
|
42
42
|
const isOutdated = useTypedSelector(selectAreResultsOutdated);
|
|
43
43
|
const error = useTypedSelector(selectSolveError);
|
|
44
44
|
const itemData = useMemo(() => ({ ...callbacks, highlightedIndex, results }), [callbacks, highlightedIndex, results]);
|
|
45
|
-
const [sizerRef, { height, width }] = useMeasure<HTMLDivElement>();
|
|
46
45
|
const [listRef, setListRef] = useState<FixedSizeList<ResultData> | null>(null);
|
|
47
46
|
const columns = useColumns();
|
|
48
47
|
const scrollToIndex = typeof highlightedIndex === 'number' ? highlightedIndex : 0;
|
|
49
48
|
const scrollToIndexRef = useLatest(scrollToIndex);
|
|
50
49
|
const hasResults = typeof error === 'undefined' && typeof results !== 'undefined';
|
|
50
|
+
const showInput = hasResults && results.length > 0 && !isOutdated;
|
|
51
|
+
const height = resultsHeight - RESULTS_HEADER_HEIGHT - (showInput ? TEXT_INPUT_HEIGHT : 0) - 2 * BORDER_WIDTH;
|
|
52
|
+
const width = resultsWidth - 2 * BORDER_WIDTH;
|
|
51
53
|
|
|
52
54
|
useEffect(() => {
|
|
53
55
|
// without setTimeout, the initial scrolling offset is calculated
|
|
@@ -72,8 +74,6 @@ const Results: FunctionComponent<Props> = ({ callbacks, className, highlightedIn
|
|
|
72
74
|
</div>
|
|
73
75
|
|
|
74
76
|
<div className={styles.content}>
|
|
75
|
-
<Sizer ref={sizerRef} />
|
|
76
|
-
|
|
77
77
|
{typeof error !== 'undefined' && (
|
|
78
78
|
<EmptyState className={styles.emptyState} variant="error">
|
|
79
79
|
{error.message}
|
|
@@ -126,7 +126,7 @@ const Results: FunctionComponent<Props> = ({ callbacks, className, highlightedIn
|
|
|
126
126
|
)}
|
|
127
127
|
</div>
|
|
128
128
|
|
|
129
|
-
{
|
|
129
|
+
{showInput && <ResultsInput className={styles.input} />}
|
|
130
130
|
|
|
131
131
|
{isLoading && <Loading />}
|
|
132
132
|
</div>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useMediaQueries } from 'hooks';
|
|
2
2
|
import { LOCALE_FEATURES } from 'i18n';
|
|
3
3
|
import { selectLocale, useTypedSelector } from 'state';
|
|
4
4
|
import { ResultColumn } from 'types';
|
|
@@ -17,10 +17,7 @@ const COLUMNS_L = [...COLUMNS_XS];
|
|
|
17
17
|
const useColumns = (): Column[] => {
|
|
18
18
|
const locale = useTypedSelector(selectLocale);
|
|
19
19
|
const localeColumns = getLocaleColumns(LOCALE_FEATURES[locale]);
|
|
20
|
-
const isLessThanXs =
|
|
21
|
-
const isLessThanS = useMediaQuery('<s');
|
|
22
|
-
const isLessThanM = useMediaQuery('<m');
|
|
23
|
-
const isLessThanL = useMediaQuery('<l');
|
|
20
|
+
const { isLessThanXs, isLessThanS, isLessThanM, isLessThanL } = useMediaQueries();
|
|
24
21
|
|
|
25
22
|
if (isLessThanXs) {
|
|
26
23
|
return localeColumns.filter((column) => COLUMNS_XS.includes(column.id));
|
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
import { Result } from '@scrabble-solver/types';
|
|
2
2
|
import classNames from 'classnames';
|
|
3
|
-
import { FunctionComponent, SyntheticEvent, useEffect, useMemo } from 'react';
|
|
3
|
+
import { FunctionComponent, memo, SyntheticEvent, useEffect, useMemo } from 'react';
|
|
4
4
|
import { useDispatch } from 'react-redux';
|
|
5
|
-
import { useMeasure } from 'react-use';
|
|
6
5
|
|
|
7
6
|
import { useAppLayout, useIsTouchDevice } from 'hooks';
|
|
8
|
-
import { BOARD_TILE_SIZE_MAX, BOARD_TILE_SIZE_MIN, BORDER_WIDTH, RACK_TILE_SIZE_MAX } from 'parameters';
|
|
9
7
|
import {
|
|
10
8
|
resultsSlice,
|
|
11
9
|
selectAreResultsOutdated,
|
|
12
|
-
selectConfig,
|
|
13
10
|
selectResultCandidate,
|
|
14
11
|
selectResults,
|
|
15
12
|
selectSolveError,
|
|
@@ -30,33 +27,19 @@ import styles from './Solver.module.scss';
|
|
|
30
27
|
|
|
31
28
|
interface Props {
|
|
32
29
|
className?: string;
|
|
33
|
-
height: number;
|
|
34
|
-
width: number;
|
|
35
30
|
onShowResults: () => void;
|
|
36
31
|
}
|
|
37
32
|
|
|
38
|
-
|
|
39
|
-
const Solver: FunctionComponent<Props> = ({ className, height, width, onShowResults }) => {
|
|
33
|
+
const Solver: FunctionComponent<Props> = ({ className, onShowResults }) => {
|
|
40
34
|
const dispatch = useDispatch();
|
|
41
35
|
const translate = useTranslate();
|
|
42
36
|
const isTouchDevice = useIsTouchDevice();
|
|
43
|
-
const {
|
|
44
|
-
useAppLayout();
|
|
45
|
-
const config = useTypedSelector(selectConfig);
|
|
37
|
+
const { cellSize, maxControlsWidth, showCompactControls, showFloatingSolveButton, tileSize } = useAppLayout();
|
|
46
38
|
const error = useTypedSelector(selectSolveError);
|
|
47
39
|
const isOutdated = useTypedSelector(selectAreResultsOutdated);
|
|
48
40
|
const resultCandidate = useTypedSelector(selectResultCandidate);
|
|
49
41
|
const results = useTypedSelector(selectResults);
|
|
50
|
-
const [bottomContainerRef, { height: bottomContainerHeight }] = useMeasure<HTMLDivElement>();
|
|
51
|
-
const maxBoardWidth = width - columnWidth - (showColumn ? componentsSpacing : 0) - 2 * componentsSpacing;
|
|
52
|
-
const maxBoardHeight = isBoardFullWidth ? Number.POSITIVE_INFINITY : Math.max(height - bottomContainerHeight, 0);
|
|
53
42
|
const [bestResult] = results || [];
|
|
54
|
-
const cellWidth = (maxBoardWidth - (config.boardWidth + 1) * BORDER_WIDTH) / config.boardWidth;
|
|
55
|
-
const cellHeight = (maxBoardHeight - (config.boardHeight + 1) * BORDER_WIDTH) / config.boardHeight;
|
|
56
|
-
const cellSize = Math.min(cellWidth, cellHeight);
|
|
57
|
-
const cellSizeSafe = Math.min(Math.max(cellSize, BOARD_TILE_SIZE_MIN), BOARD_TILE_SIZE_MAX);
|
|
58
|
-
const tileSize = Math.min((maxBoardWidth - 2 * BORDER_WIDTH) / config.maximumCharactersCount, RACK_TILE_SIZE_MAX);
|
|
59
|
-
const maxControlsWidth = tileSize * config.maximumCharactersCount + 2 * BORDER_WIDTH;
|
|
60
43
|
const touchCallbacks = useMemo(
|
|
61
44
|
() => ({
|
|
62
45
|
onClick: (result: Result) => {
|
|
@@ -110,7 +93,7 @@ const Solver: FunctionComponent<Props> = ({ className, height, width, onShowResu
|
|
|
110
93
|
<div className={styles.container}>
|
|
111
94
|
<div className={styles.content}>
|
|
112
95
|
<form className={styles.boardContainer} onSubmit={handleSubmit}>
|
|
113
|
-
<Board cellSize={
|
|
96
|
+
<Board cellSize={cellSize} className={styles.board} />
|
|
114
97
|
<input className={styles.submitInput} tabIndex={-1} type="submit" />
|
|
115
98
|
</form>
|
|
116
99
|
|
|
@@ -125,7 +108,7 @@ const Solver: FunctionComponent<Props> = ({ className, height, width, onShowResu
|
|
|
125
108
|
</div>
|
|
126
109
|
</div>
|
|
127
110
|
|
|
128
|
-
<div className={styles.bottomContainer}
|
|
111
|
+
<div className={styles.bottomContainer}>
|
|
129
112
|
<div className={styles.bottomContent}>
|
|
130
113
|
<form onSubmit={handleSubmit}>
|
|
131
114
|
<Rack className={styles.rack} tileSize={tileSize} />
|
|
@@ -157,4 +140,4 @@ const Solver: FunctionComponent<Props> = ({ className, height, width, onShowResu
|
|
|
157
140
|
);
|
|
158
141
|
};
|
|
159
142
|
|
|
160
|
-
export default Solver;
|
|
143
|
+
export default memo(Solver);
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
text-transform: uppercase;
|
|
15
15
|
text-align: center;
|
|
16
16
|
transition: var(--transition);
|
|
17
|
+
transition-property: background-color, color, box-shadow;
|
|
17
18
|
user-select: none;
|
|
18
19
|
|
|
19
20
|
&.points1 {
|
|
@@ -130,7 +131,7 @@
|
|
|
130
131
|
[dir='ltr'] & {
|
|
131
132
|
top: 0;
|
|
132
133
|
right: 0;
|
|
133
|
-
border-
|
|
134
|
+
border-top-right-radius: inherit;
|
|
134
135
|
}
|
|
135
136
|
|
|
136
137
|
[dir='rtl'] & {
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
FunctionComponent,
|
|
7
7
|
KeyboardEventHandler,
|
|
8
8
|
Ref,
|
|
9
|
+
useCallback,
|
|
9
10
|
useEffect,
|
|
10
11
|
useMemo,
|
|
11
12
|
useRef,
|
|
@@ -68,10 +69,13 @@ const Tile: FunctionComponent<Props> = ({
|
|
|
68
69
|
const canShowPoints = showTilePoints && (!isEmpty || isBlank) && typeof points !== 'undefined';
|
|
69
70
|
const pointsFormatted = typeof points === 'number' ? points.toLocaleString(locale) : '';
|
|
70
71
|
|
|
71
|
-
const handleKeyDown: KeyboardEventHandler<HTMLInputElement> = (
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
const handleKeyDown: KeyboardEventHandler<HTMLInputElement> = useCallback(
|
|
73
|
+
(event) => {
|
|
74
|
+
ref.current?.select();
|
|
75
|
+
onKeyDown(event);
|
|
76
|
+
},
|
|
77
|
+
[onKeyDown],
|
|
78
|
+
);
|
|
75
79
|
|
|
76
80
|
useEffect(() => {
|
|
77
81
|
if (autoFocus && ref.current) {
|
package/src/components/index.ts
CHANGED
|
@@ -10,7 +10,6 @@ export { default as IconButton } from './IconButton';
|
|
|
10
10
|
export { default as Key } from './Key';
|
|
11
11
|
export { default as Loading } from './Loading';
|
|
12
12
|
export { default as Logo } from './Logo';
|
|
13
|
-
export { default as LogoSplashScreen } from './LogoSplashScreen';
|
|
14
13
|
export { default as Modal } from './Modal';
|
|
15
14
|
export { default as NavButtons } from './NavButtons';
|
|
16
15
|
export { default as NotFound } from './NotFound';
|
|
@@ -21,11 +20,7 @@ export { default as Radio } from './Radio';
|
|
|
21
20
|
export { default as Results } from './Results';
|
|
22
21
|
export { default as ResultsInput } from './ResultsInput';
|
|
23
22
|
export { default as SeoMessage } from './SeoMessage';
|
|
24
|
-
export { default as Sizer } from './Sizer';
|
|
25
23
|
export { default as Solver } from './Solver';
|
|
26
24
|
export { default as Spinner } from './Spinner';
|
|
27
|
-
export { default as SplashScreen } from './SplashScreen';
|
|
28
|
-
export { default as SvgFontCss } from './SvgFontCss';
|
|
29
|
-
export { default as SvgFontFix } from './SvgFontFix';
|
|
30
25
|
export { default as Tile } from './Tile';
|
|
31
26
|
export { useTooltip } from './Tooltip';
|
package/src/hooks/index.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
export { default as useAppLayout } from './useAppLayout';
|
|
2
2
|
export { default as useDirection } from './useDirection';
|
|
3
|
+
export { default as useEffectOnce } from './useEffectOnce';
|
|
3
4
|
export { default as useIsTouchDevice } from './useIsTouchDevice';
|
|
4
5
|
export { default as useLanguage } from './useLanguage';
|
|
6
|
+
export { default as useLatest } from './useLatest';
|
|
5
7
|
export { default as useLocalStorage } from './useLocalStorage';
|
|
8
|
+
export { default as useMedia } from './useMedia';
|
|
9
|
+
export { default as useMediaQueries } from './useMediaQueries';
|
|
6
10
|
export { default as useMediaQuery } from './useMediaQuery';
|
|
11
|
+
export { default as useOnWindowResize } from './useOnWindowResize';
|
|
7
12
|
export { default as usePortal } from './usePortal';
|
|
13
|
+
export { default as useViewportSize } from './useViewportSize';
|
|
@@ -1,30 +1,80 @@
|
|
|
1
|
-
|
|
1
|
+
/* eslint-disable max-statements */
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
BOARD_TILE_SIZE_MAX,
|
|
5
|
+
BOARD_TILE_SIZE_MIN,
|
|
6
|
+
BORDER_WIDTH,
|
|
7
|
+
BUTTON_HEIGHT,
|
|
8
|
+
COMPONENTS_SPACING,
|
|
9
|
+
COMPONENTS_SPACING_SMALL,
|
|
10
|
+
DICTIONARY_HEIGHT,
|
|
11
|
+
DICTIONARY_HEIGHT_MOBILE,
|
|
12
|
+
LOGO_ASPECT_RATIO,
|
|
13
|
+
LOGO_HEIGHT,
|
|
14
|
+
LOGO_HEIGHT_SMALL,
|
|
15
|
+
MODAL_HEADER_HEIGHT,
|
|
16
|
+
MODAL_WIDTH,
|
|
17
|
+
NAV_PADDING,
|
|
18
|
+
RACK_TILE_SIZE_MAX,
|
|
19
|
+
SOLVER_COLUMN_WIDTH,
|
|
20
|
+
} from 'parameters';
|
|
21
|
+
import { selectConfig, useTypedSelector } from 'state';
|
|
2
22
|
|
|
3
23
|
import useIsTouchDevice from './useIsTouchDevice';
|
|
4
|
-
import
|
|
24
|
+
import useMediaQueries from './useMediaQueries';
|
|
25
|
+
import useViewportSize from './useViewportSize';
|
|
5
26
|
|
|
6
27
|
const useAppLayout = () => {
|
|
28
|
+
const { viewportHeight, viewportWidth } = useViewportSize();
|
|
29
|
+
const config = useTypedSelector(selectConfig);
|
|
7
30
|
const isTouchDevice = useIsTouchDevice();
|
|
8
|
-
const isLessThanXs =
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const isLessThanXl = useMediaQuery('<xl');
|
|
31
|
+
const { isLessThanXs, isLessThanS, isLessThanM, isLessThanL, isLessThanXl } = useMediaQueries();
|
|
32
|
+
const isBoardFullWidth = isLessThanM;
|
|
33
|
+
const showResultCandidatePicker = isLessThanL;
|
|
34
|
+
const componentsSpacing = isLessThanXl ? COMPONENTS_SPACING_SMALL : COMPONENTS_SPACING;
|
|
13
35
|
const showColumn = !isLessThanL;
|
|
36
|
+
const columnWidth = showColumn ? SOLVER_COLUMN_WIDTH : 0;
|
|
37
|
+
const logoHeight = isLessThanL ? LOGO_HEIGHT_SMALL : LOGO_HEIGHT;
|
|
38
|
+
const navHeight = 2 * NAV_PADDING + logoHeight;
|
|
39
|
+
const solverHeight = viewportHeight - navHeight;
|
|
40
|
+
const solverWidth = viewportWidth;
|
|
41
|
+
const maxBoardWidth = solverWidth - columnWidth - (showColumn ? componentsSpacing : 0) - 2 * componentsSpacing;
|
|
42
|
+
const tileSize = Math.min((maxBoardWidth - 2 * BORDER_WIDTH) / config.maximumCharactersCount, RACK_TILE_SIZE_MAX);
|
|
43
|
+
const candidatePickerHeight = showResultCandidatePicker ? BUTTON_HEIGHT + componentsSpacing : 0;
|
|
44
|
+
const bottomContainerHeight = candidatePickerHeight + tileSize + 2 * componentsSpacing;
|
|
45
|
+
const maxBoardHeight = isBoardFullWidth
|
|
46
|
+
? Number.POSITIVE_INFINITY
|
|
47
|
+
: Math.max(solverHeight - bottomContainerHeight, 0);
|
|
48
|
+
const cellWidth = (maxBoardWidth - (config.boardWidth + 1) * BORDER_WIDTH) / config.boardWidth;
|
|
49
|
+
const cellHeight = (maxBoardHeight - (config.boardHeight + 1) * BORDER_WIDTH) / config.boardHeight;
|
|
50
|
+
const cellSize = Math.min(Math.max(Math.min(cellWidth, cellHeight), BOARD_TILE_SIZE_MIN), BOARD_TILE_SIZE_MAX);
|
|
51
|
+
const boardSize = (cellSize + BORDER_WIDTH) * config.boardWidth + BORDER_WIDTH;
|
|
52
|
+
const maxControlsWidth = tileSize * config.maximumCharactersCount + 2 * BORDER_WIDTH;
|
|
53
|
+
const showResultsInModal = isLessThanL;
|
|
54
|
+
const dictionaryHeight = showResultsInModal ? DICTIONARY_HEIGHT_MOBILE : DICTIONARY_HEIGHT;
|
|
55
|
+
const modalWidth = isLessThanS ? viewportWidth : MODAL_WIDTH;
|
|
56
|
+
const resultsHeight = isLessThanL
|
|
57
|
+
? viewportHeight - dictionaryHeight - BUTTON_HEIGHT - MODAL_HEADER_HEIGHT - 5 * componentsSpacing
|
|
58
|
+
: boardSize - componentsSpacing - dictionaryHeight;
|
|
14
59
|
|
|
15
60
|
return {
|
|
61
|
+
actionsWidth: 2 * BUTTON_HEIGHT - BORDER_WIDTH,
|
|
16
62
|
animateTile: !isLessThanXs,
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
isBoardFullWidth: isLessThanM,
|
|
63
|
+
cellSize,
|
|
64
|
+
dictionaryHeight,
|
|
20
65
|
isModalFullWidth: isLessThanS,
|
|
21
|
-
|
|
66
|
+
logoHeight,
|
|
67
|
+
logoWidth: logoHeight * LOGO_ASPECT_RATIO,
|
|
68
|
+
maxControlsWidth,
|
|
69
|
+
resultsHeight,
|
|
70
|
+
resultsWidth: isLessThanL ? modalWidth - 2 * componentsSpacing : SOLVER_COLUMN_WIDTH,
|
|
22
71
|
showCompactControls: !showColumn,
|
|
23
72
|
showFloatingSolveButton: isTouchDevice,
|
|
24
73
|
showKeyMap: !isTouchDevice,
|
|
25
|
-
showResultsInModal
|
|
74
|
+
showResultsInModal,
|
|
26
75
|
showShortNav: isLessThanS,
|
|
27
76
|
showTilePoints: !isLessThanXs,
|
|
77
|
+
tileSize,
|
|
28
78
|
};
|
|
29
79
|
};
|
|
30
80
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
2
|
|
|
3
3
|
import { noop } from 'lib';
|
|
4
4
|
|
|
5
5
|
const useDirection = (direction: 'ltr' | 'rtl') => {
|
|
6
|
-
|
|
6
|
+
useEffect(() => {
|
|
7
7
|
const html = document.body.parentElement;
|
|
8
8
|
|
|
9
9
|
if (!html) {
|
package/src/hooks/useLanguage.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
2
|
|
|
3
3
|
import { noop } from 'lib';
|
|
4
4
|
|
|
5
5
|
const useLanguage = (language: string) => {
|
|
6
|
-
|
|
6
|
+
useEffect(() => {
|
|
7
7
|
const html = document.body.parentElement;
|
|
8
8
|
|
|
9
9
|
if (!html) {
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
localStorage,
|
|
5
|
+
selectAutoGroupTiles,
|
|
6
|
+
selectBoard,
|
|
7
|
+
selectConfigId,
|
|
8
|
+
selectLocale,
|
|
9
|
+
selectRack,
|
|
10
|
+
useTypedSelector,
|
|
11
|
+
} from 'state';
|
|
12
|
+
|
|
13
|
+
const useLocalStorage = () => {
|
|
14
|
+
const autoGroupTiles = useTypedSelector(selectAutoGroupTiles);
|
|
15
|
+
const board = useTypedSelector(selectBoard);
|
|
16
|
+
const configId = useTypedSelector(selectConfigId);
|
|
17
|
+
const locale = useTypedSelector(selectLocale);
|
|
18
|
+
const rack = useTypedSelector(selectRack);
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (autoGroupTiles) {
|
|
22
|
+
localStorage.setAutoGroupTiles(autoGroupTiles);
|
|
23
|
+
}
|
|
24
|
+
}, [autoGroupTiles]);
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
if (board) {
|
|
28
|
+
localStorage.setBoard(board);
|
|
29
|
+
}
|
|
30
|
+
}, [board]);
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (configId) {
|
|
34
|
+
localStorage.setConfigId(configId);
|
|
35
|
+
}
|
|
36
|
+
}, [configId]);
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (locale) {
|
|
40
|
+
localStorage.setLocale(locale);
|
|
41
|
+
}
|
|
42
|
+
}, [locale]);
|
|
43
|
+
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
if (rack) {
|
|
46
|
+
localStorage.setRack(rack);
|
|
47
|
+
}
|
|
48
|
+
}, [rack]);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export default useLocalStorage;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
const getInitialState = (query: string, defaultState?: boolean) => {
|
|
4
|
+
if (typeof defaultState !== 'undefined') {
|
|
5
|
+
return defaultState;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
if (typeof window === 'undefined') {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return window.matchMedia(query).matches;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const useMedia = (query: string, defaultState?: boolean) => {
|
|
16
|
+
const [state, setState] = useState(getInitialState(query, defaultState));
|
|
17
|
+
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
const mediaQuery = window.matchMedia(query);
|
|
20
|
+
|
|
21
|
+
const handleChange = () => {
|
|
22
|
+
setState(mediaQuery.matches);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
setState(mediaQuery.matches);
|
|
26
|
+
mediaQuery.addEventListener('change', handleChange);
|
|
27
|
+
|
|
28
|
+
return () => {
|
|
29
|
+
mediaQuery.removeEventListener('change', handleChange);
|
|
30
|
+
};
|
|
31
|
+
}, [query]);
|
|
32
|
+
|
|
33
|
+
return state;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export default useMedia;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import useMediaQuery from './useMediaQuery';
|
|
2
|
+
|
|
3
|
+
const useMediaQueries = () => {
|
|
4
|
+
const isLessThanXs = useMediaQuery('<xs');
|
|
5
|
+
const isLessThanS = useMediaQuery('<s');
|
|
6
|
+
const isLessThanM = useMediaQuery('<m');
|
|
7
|
+
const isLessThanL = useMediaQuery('<l');
|
|
8
|
+
const isLessThanXl = useMediaQuery('<xl');
|
|
9
|
+
|
|
10
|
+
return { isLessThanXs, isLessThanS, isLessThanM, isLessThanL, isLessThanXl };
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export default useMediaQueries;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { buildMediaQuery } from 'include-media-query-builder';
|
|
2
|
-
import { useMedia } from 'react-use';
|
|
3
2
|
|
|
4
3
|
import { BREAKPOINTS } from 'parameters';
|
|
5
4
|
|
|
5
|
+
import useMedia from './useMedia';
|
|
6
|
+
|
|
6
7
|
const useMediaQuery = (query: string | string[], defaultState?: boolean | undefined): boolean => {
|
|
7
8
|
const mediaQuery = buildMediaQuery(BREAKPOINTS, query);
|
|
8
9
|
return useMedia(mediaQuery, defaultState);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
const useOnWindowResize = (onResize: (event: Event) => void) => {
|
|
4
|
+
useEffect(() => {
|
|
5
|
+
window.addEventListener('resize', onResize);
|
|
6
|
+
|
|
7
|
+
return () => {
|
|
8
|
+
window.removeEventListener('resize', onResize);
|
|
9
|
+
};
|
|
10
|
+
}, [onResize]);
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export default useOnWindowResize;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { useCallback, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
import useOnWindowResize from './useOnWindowResize';
|
|
4
|
+
|
|
5
|
+
const useViewportSize = () => {
|
|
6
|
+
const [viewportHeight, setViewportHeight] = useState(typeof window === 'undefined' ? 0 : window.innerHeight);
|
|
7
|
+
const [viewportWidth, setViewportWidth] = useState(typeof window === 'undefined' ? 0 : window.innerWidth);
|
|
8
|
+
|
|
9
|
+
const handleWindowResize = useCallback(() => {
|
|
10
|
+
setViewportHeight(window.innerHeight);
|
|
11
|
+
setViewportWidth(window.innerWidth);
|
|
12
|
+
}, []);
|
|
13
|
+
|
|
14
|
+
useOnWindowResize(handleWindowResize);
|
|
15
|
+
|
|
16
|
+
return { viewportHeight, viewportWidth };
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default useViewportSize;
|