@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.
Files changed (148) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +7 -7
  3. package/.next/cache/.tsbuildinfo +1 -1
  4. package/.next/cache/eslint/.cache_8dgz12 +1 -1
  5. package/.next/cache/next-server.js.nft.json +1 -1
  6. package/.next/cache/webpack/client-production/0.pack +0 -0
  7. package/.next/cache/webpack/client-production/index.pack +0 -0
  8. package/.next/cache/webpack/edge-server-production/0.pack +0 -0
  9. package/.next/cache/webpack/edge-server-production/index.pack +0 -0
  10. package/.next/cache/webpack/server-production/0.pack +0 -0
  11. package/.next/cache/webpack/server-production/index.pack +0 -0
  12. package/.next/export-marker.json +1 -1
  13. package/.next/next-server.js.nft.json +1 -1
  14. package/.next/prerender-manifest.json +1 -1
  15. package/.next/routes-manifest.json +1 -1
  16. package/.next/server/chunks/131.js +1 -1
  17. package/.next/server/chunks/277.js +851 -1179
  18. package/.next/server/chunks/636.js +286 -0
  19. package/.next/server/chunks/675.js +550 -0
  20. package/.next/server/middleware-build-manifest.js +1 -1
  21. package/.next/server/pages/404.html +1 -5
  22. package/.next/server/pages/404.js.nft.json +1 -1
  23. package/.next/server/pages/500.html +1 -1
  24. package/.next/server/pages/_app.js +73 -9
  25. package/.next/server/pages/_app.js.nft.json +1 -1
  26. package/.next/server/pages/_document.js.nft.json +1 -1
  27. package/.next/server/pages/_error.js +1 -280
  28. package/.next/server/pages/_error.js.nft.json +1 -1
  29. package/.next/server/pages/api/solve.js +22 -2
  30. package/.next/server/pages/index.html +1 -1
  31. package/.next/server/pages/index.js +381 -314
  32. package/.next/server/pages/index.js.nft.json +1 -1
  33. package/.next/server/pages/index.json +1 -1
  34. package/.next/server/pages-manifest.json +1 -1
  35. package/.next/static/Jmk00rVXCbdjFgP77tKXQ/_buildManifest.js +1 -0
  36. package/.next/static/chunks/pages/{404-448ba28510855455.js → 404-8176f4acd0cfeb42.js} +1 -1
  37. package/.next/static/chunks/pages/_app-b4fa92112b8f0385.js +28 -0
  38. package/.next/static/chunks/pages/index-ccd762f8f5028729.js +1 -0
  39. package/.next/static/css/1cd302e7648d209c.css +2 -0
  40. package/.next/static/css/34adfcf12a7d9bb6.css +1 -0
  41. package/.next/trace +50 -53
  42. package/next.config.js +1 -0
  43. package/package.json +12 -13
  44. package/src/@types/svg.d.ts +1 -1
  45. package/src/components/Board/Board.tsx +48 -44
  46. package/src/components/Board/components/Actions/Actions.tsx +4 -2
  47. package/src/components/Board/components/Cell/Cell.module.scss +59 -5
  48. package/src/components/Board/hooks/useGrid.ts +5 -3
  49. package/src/components/Button/Button.module.scss +1 -1
  50. package/src/components/Dictionary/Dictionary.module.scss +0 -1
  51. package/src/components/EmptyState/EmptyState.module.scss +0 -1
  52. package/src/components/Key/Key.module.scss +1 -1
  53. package/src/components/Loading/Loading.module.scss +1 -1
  54. package/src/components/Loading/Loading.tsx +1 -1
  55. package/src/components/Logo/Logo.tsx +10 -12
  56. package/src/components/Logo/LogoBlueprint.tsx +21 -0
  57. package/src/components/Logo/index.ts +1 -1
  58. package/src/components/Modal/Modal.module.scss +1 -6
  59. package/src/components/Modal/Modal.tsx +15 -8
  60. package/src/components/NavButtons/NavButtons.tsx +2 -2
  61. package/src/components/PlainTiles/PlainTiles.tsx +0 -10
  62. package/src/components/PlainTiles/Tile.tsx +1 -4
  63. package/src/components/Rack/Rack.module.scss +59 -0
  64. package/src/components/Results/HeaderButton.tsx +6 -6
  65. package/src/components/Results/Results.module.scss +3 -1
  66. package/src/components/Results/Results.tsx +7 -7
  67. package/src/components/Results/useColumns.ts +2 -5
  68. package/src/components/Solver/Solver.tsx +6 -23
  69. package/src/components/Tile/Tile.module.scss +2 -1
  70. package/src/components/Tile/Tile.tsx +8 -4
  71. package/src/components/index.ts +0 -5
  72. package/src/hooks/index.ts +6 -0
  73. package/src/hooks/useAppLayout.ts +62 -12
  74. package/src/hooks/useDirection.ts +2 -2
  75. package/src/hooks/useEffectOnce.ts +5 -0
  76. package/src/hooks/useIsTouchDevice.ts +1 -1
  77. package/src/hooks/useLanguage.ts +2 -2
  78. package/src/hooks/useLatest.ts +13 -0
  79. package/src/hooks/useLocalStorage.ts +51 -0
  80. package/src/hooks/useMedia.ts +36 -0
  81. package/src/hooks/useMediaQueries.ts +13 -0
  82. package/src/hooks/useMediaQuery.ts +2 -1
  83. package/src/hooks/useOnWindowResize.ts +13 -0
  84. package/src/hooks/useViewportSize.ts +19 -0
  85. package/src/i18n/constants.ts +14 -22
  86. package/src/lib/arrayEquals.ts +5 -0
  87. package/src/lib/index.ts +1 -0
  88. package/src/lib/zipCharactersAndTiles.ts +3 -1
  89. package/src/modals/DictionaryModal/DictionaryModal.tsx +2 -2
  90. package/src/modals/KeyMapModal/KeyMapModal.tsx +2 -2
  91. package/src/modals/KeyMapModal/components/Mapping/Mapping.module.scss +0 -1
  92. package/src/modals/KeyMapModal/keys.tsx +0 -2
  93. package/src/modals/MenuModal/MenuModal.module.scss +28 -4
  94. package/src/modals/MenuModal/MenuModal.tsx +4 -4
  95. package/src/modals/RemainingTilesModal/RemainingTilesModal.tsx +2 -2
  96. package/src/modals/ResultsModal/ResultsModal.module.scss +1 -5
  97. package/src/modals/ResultsModal/ResultsModal.tsx +10 -2
  98. package/src/modals/SettingsModal/SettingsModal.tsx +2 -2
  99. package/src/modals/SettingsModal/components/AutoGroupTilesSetting/lib.ts +3 -1
  100. package/src/modals/SettingsModal/components/ConfigSetting/ConfigSetting.module.scss +0 -1
  101. package/src/modals/SettingsModal/components/LocaleSetting/LocaleSetting.module.scss +1 -6
  102. package/src/modals/WordsModal/WordsModal.tsx +2 -2
  103. package/src/pages/index.module.scss +3 -21
  104. package/src/pages/index.tsx +51 -69
  105. package/src/parameters/index.ts +29 -2
  106. package/src/state/localStorage.ts +13 -2
  107. package/src/state/sagas.ts +16 -8
  108. package/src/state/slices/boardInitialState.ts +5 -1
  109. package/src/state/slices/boardSlice.ts +2 -2
  110. package/src/state/slices/rackInitialState.ts +8 -2
  111. package/src/state/slices/rackSlice.ts +16 -13
  112. package/src/state/slices/settingsInitialState.ts +9 -4
  113. package/src/state/slices/settingsSlice.ts +3 -1
  114. package/src/styles/animations.scss +0 -20
  115. package/src/styles/global.scss +4 -15
  116. package/src/styles/mixins.scss +0 -60
  117. package/src/styles/variables.scss +14 -5
  118. package/src/types/index.ts +4 -0
  119. package/.next/static/MvHZRF4XuJ7g8LLLRkf8U/_buildManifest.js +0 -1
  120. package/.next/static/chunks/pages/_app-66d80a5594aab8dc.js +0 -28
  121. package/.next/static/chunks/pages/index-0858deea02b2a417.js +0 -1
  122. package/.next/static/css/885da289cec275b3.css +0 -1
  123. package/.next/static/css/ea1c8134fe9a143e.css +0 -2
  124. package/src/components/LogoSplashScreen/LogoSplashScreen.module.scss +0 -65
  125. package/src/components/LogoSplashScreen/LogoSplashScreen.tsx +0 -31
  126. package/src/components/LogoSplashScreen/index.ts +0 -1
  127. package/src/components/Sizer/Sizer.module.scss +0 -10
  128. package/src/components/Sizer/Sizer.tsx +0 -10
  129. package/src/components/Sizer/index.ts +0 -1
  130. package/src/components/SplashScreen/SplashScreen.module.scss +0 -14
  131. package/src/components/SplashScreen/SplashScreen.tsx +0 -19
  132. package/src/components/SplashScreen/index.ts +0 -1
  133. package/src/components/SvgFontCss/SvgFontCss.tsx +0 -14
  134. package/src/components/SvgFontCss/createCss.ts +0 -11
  135. package/src/components/SvgFontCss/createStyle.ts +0 -9
  136. package/src/components/SvgFontCss/createSvg.ts +0 -10
  137. package/src/components/SvgFontCss/index.ts +0 -1
  138. package/src/components/SvgFontFix/SvgFontFix.module.scss +0 -5
  139. package/src/components/SvgFontFix/SvgFontFix.tsx +0 -21
  140. package/src/components/SvgFontFix/index.ts +0 -1
  141. package/src/hooks/useLocalStorage/index.ts +0 -1
  142. package/src/hooks/useLocalStorage/useLocalStorage.ts +0 -13
  143. package/src/hooks/useLocalStorage/useLocalStorageBoard.ts +0 -29
  144. package/src/hooks/useLocalStorage/useLocalStorageConfigId.ts +0 -29
  145. package/src/hooks/useLocalStorage/useLocalStorageLocale.ts +0 -32
  146. package/src/hooks/useLocalStorage/useLocalStorageRack.ts +0 -29
  147. /package/.next/static/{MvHZRF4XuJ7g8LLLRkf8U → Jmk00rVXCbdjFgP77tKXQ}/_ssgManifest.js +0 -0
  148. /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 { ResultColumn, SortDirection } from 'types';
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 handleOrderChange = (columnId: ResultColumn) => {
25
- dispatch(resultsSlice.actions.sort(columnId));
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={() => handleOrderChange(column.id)}
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
- {hasResults && results.length > 0 && !isOutdated && <ResultsInput className={styles.input} />}
129
+ {showInput && <ResultsInput className={styles.input} />}
130
130
 
131
131
  {isLoading && <Loading />}
132
132
  </div>
@@ -1,4 +1,4 @@
1
- import { useMediaQuery } from 'hooks';
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 = useMediaQuery('<xs');
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
- // eslint-disable-next-line max-statements
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 { columnWidth, componentsSpacing, isBoardFullWidth, showColumn, showCompactControls, showFloatingSolveButton } =
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={cellSizeSafe} className={styles.board} />
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} ref={bottomContainerRef}>
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-bottom-right-radius: inherit;
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> = (event) => {
72
- ref.current?.select();
73
- onKeyDown(event);
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) {
@@ -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';
@@ -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
- import { COMPONENTS_SPACING, COMPONENTS_SPACING_SMALL, SOLVER_COLUMN_WIDTH } from 'parameters';
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 useMediaQuery from './useMediaQuery';
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 = useMediaQuery('<xs');
9
- const isLessThanS = useMediaQuery('<s');
10
- const isLessThanM = useMediaQuery('<m');
11
- const isLessThanL = useMediaQuery('<l');
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
- columnWidth: showColumn ? SOLVER_COLUMN_WIDTH : 0,
18
- componentsSpacing: isLessThanXl ? COMPONENTS_SPACING_SMALL : COMPONENTS_SPACING,
19
- isBoardFullWidth: isLessThanM,
63
+ cellSize,
64
+ dictionaryHeight,
20
65
  isModalFullWidth: isLessThanS,
21
- showColumn,
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: isLessThanL,
74
+ showResultsInModal,
26
75
  showShortNav: isLessThanS,
27
76
  showTilePoints: !isLessThanXs,
77
+ tileSize,
28
78
  };
29
79
  };
30
80
 
@@ -1,9 +1,9 @@
1
- import { useLayoutEffect } from 'react';
1
+ import { useEffect } from 'react';
2
2
 
3
3
  import { noop } from 'lib';
4
4
 
5
5
  const useDirection = (direction: 'ltr' | 'rtl') => {
6
- useLayoutEffect(() => {
6
+ useEffect(() => {
7
7
  const html = document.body.parentElement;
8
8
 
9
9
  if (!html) {
@@ -0,0 +1,5 @@
1
+ import { EffectCallback, useEffect } from 'react';
2
+
3
+ const useEffectOnce = (effect: EffectCallback) => useEffect(effect, []);
4
+
5
+ export default useEffectOnce;
@@ -1,4 +1,4 @@
1
- import { useMedia } from 'react-use';
1
+ import useMedia from './useMedia';
2
2
 
3
3
  const useIsTouchDevice = () => {
4
4
  return useMedia('(hover: none)', false);
@@ -1,9 +1,9 @@
1
- import { useLayoutEffect } from 'react';
1
+ import { useEffect } from 'react';
2
2
 
3
3
  import { noop } from 'lib';
4
4
 
5
5
  const useLanguage = (language: string) => {
6
- useLayoutEffect(() => {
6
+ useEffect(() => {
7
7
  const html = document.body.parentElement;
8
8
 
9
9
  if (!html) {
@@ -0,0 +1,13 @@
1
+ import { useRef } from 'react';
2
+
3
+ interface Latest<T> {
4
+ readonly current: T;
5
+ }
6
+
7
+ const useLatest = <T>(value: T): Latest<T> => {
8
+ const ref = useRef(value);
9
+ ref.current = value;
10
+ return ref;
11
+ };
12
+
13
+ export default useLatest;
@@ -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;