@scrabble-solver/scrabble-solver 2.11.3 → 2.11.5

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 (137) 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 +736 -913
  18. package/.next/server/chunks/44.js +2 -30
  19. package/.next/server/chunks/636.js +286 -0
  20. package/.next/server/chunks/675.js +550 -0
  21. package/.next/server/middleware-build-manifest.js +1 -1
  22. package/.next/server/pages/404.html +2 -2
  23. package/.next/server/pages/404.js.nft.json +1 -1
  24. package/.next/server/pages/500.html +1 -1
  25. package/.next/server/pages/_app.js +73 -9
  26. package/.next/server/pages/_app.js.nft.json +1 -1
  27. package/.next/server/pages/_document.js.nft.json +1 -1
  28. package/.next/server/pages/_error.js +1 -280
  29. package/.next/server/pages/_error.js.nft.json +1 -1
  30. package/.next/server/pages/api/solve.js +22 -2
  31. package/.next/server/pages/index.html +1 -1
  32. package/.next/server/pages/index.js +382 -313
  33. package/.next/server/pages/index.js.nft.json +1 -1
  34. package/.next/server/pages/index.json +1 -1
  35. package/.next/static/UzQCOB6CHhyOupkEq8oZM/_buildManifest.js +1 -0
  36. package/.next/static/chunks/pages/{404-448ba28510855455.js → 404-d30fe85d005ce32b.js} +1 -1
  37. package/.next/static/chunks/pages/_app-e27464a187a58684.js +28 -0
  38. package/.next/static/chunks/pages/index-3fd280f406cc00fd.js +1 -0
  39. package/.next/static/css/4bd04cebe207859c.css +1 -0
  40. package/.next/static/css/5b3b78170f4c5875.css +2 -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 -1
  48. package/src/components/Board/hooks/useGrid.ts +5 -3
  49. package/src/components/Button/Button.module.scss +1 -1
  50. package/src/components/Loading/Loading.module.scss +1 -1
  51. package/src/components/Loading/Loading.tsx +1 -1
  52. package/src/components/Logo/Logo.tsx +10 -12
  53. package/src/components/Logo/LogoBlueprint.tsx +21 -0
  54. package/src/components/Logo/index.ts +1 -1
  55. package/src/components/Modal/Modal.module.scss +1 -6
  56. package/src/components/Modal/Modal.tsx +15 -8
  57. package/src/components/NavButtons/NavButtons.tsx +2 -2
  58. package/src/components/Rack/Rack.module.scss +59 -0
  59. package/src/components/Results/HeaderButton.tsx +6 -6
  60. package/src/components/Results/Results.module.scss +3 -0
  61. package/src/components/Results/Results.tsx +7 -7
  62. package/src/components/Results/useColumns.ts +2 -5
  63. package/src/components/Solver/Solver.tsx +6 -23
  64. package/src/components/Tile/Tile.module.scss +2 -1
  65. package/src/components/Tile/Tile.tsx +8 -4
  66. package/src/components/index.ts +0 -3
  67. package/src/hooks/index.ts +6 -0
  68. package/src/hooks/useAppLayout.ts +62 -12
  69. package/src/hooks/useEffectOnce.ts +5 -0
  70. package/src/hooks/useIsTouchDevice.ts +1 -1
  71. package/src/hooks/useLatest.ts +13 -0
  72. package/src/hooks/useLocalStorage.ts +51 -0
  73. package/src/hooks/useMedia.ts +36 -0
  74. package/src/hooks/useMediaQueries.ts +13 -0
  75. package/src/hooks/useMediaQuery.ts +2 -1
  76. package/src/hooks/useOnWindowResize.ts +13 -0
  77. package/src/hooks/useViewportSize.ts +19 -0
  78. package/src/i18n/constants.ts +14 -14
  79. package/src/i18n/de.json +2 -2
  80. package/src/i18n/en.json +2 -2
  81. package/src/i18n/es.json +2 -2
  82. package/src/i18n/fa.json +1 -1
  83. package/src/i18n/fr.json +2 -2
  84. package/src/i18n/pl.json +2 -2
  85. package/src/lib/arrayEquals.ts +5 -0
  86. package/src/lib/index.ts +1 -0
  87. package/src/lib/zipCharactersAndTiles.ts +3 -1
  88. package/src/modals/DictionaryModal/DictionaryModal.tsx +2 -2
  89. package/src/modals/KeyMapModal/KeyMapModal.tsx +2 -2
  90. package/src/modals/KeyMapModal/keys.tsx +0 -2
  91. package/src/modals/MenuModal/MenuModal.module.scss +28 -4
  92. package/src/modals/MenuModal/MenuModal.tsx +4 -4
  93. package/src/modals/RemainingTilesModal/RemainingTilesModal.tsx +2 -2
  94. package/src/modals/ResultsModal/ResultsModal.module.scss +1 -5
  95. package/src/modals/ResultsModal/ResultsModal.tsx +10 -2
  96. package/src/modals/SettingsModal/SettingsModal.tsx +2 -2
  97. package/src/modals/SettingsModal/components/AutoGroupTilesSetting/lib.ts +3 -1
  98. package/src/modals/SettingsModal/components/LocaleSetting/LocaleSetting.module.scss +1 -1
  99. package/src/modals/WordsModal/WordsModal.tsx +2 -2
  100. package/src/pages/index.module.scss +3 -21
  101. package/src/pages/index.tsx +51 -67
  102. package/src/parameters/index.ts +29 -2
  103. package/src/state/localStorage.ts +13 -2
  104. package/src/state/sagas.ts +16 -8
  105. package/src/state/slices/boardInitialState.ts +5 -1
  106. package/src/state/slices/boardSlice.ts +2 -2
  107. package/src/state/slices/rackInitialState.ts +8 -2
  108. package/src/state/slices/rackSlice.ts +16 -13
  109. package/src/state/slices/settingsInitialState.ts +9 -4
  110. package/src/state/slices/settingsSlice.ts +3 -1
  111. package/src/styles/animations.scss +0 -20
  112. package/src/styles/global.scss +0 -7
  113. package/src/styles/mixins.scss +0 -59
  114. package/src/styles/variables.scss +11 -0
  115. package/src/types/index.ts +4 -0
  116. package/.next/static/USLkKOoHbITebIEHkMGX_/_buildManifest.js +0 -1
  117. package/.next/static/chunks/pages/_app-21c83ddb81fc09d0.js +0 -28
  118. package/.next/static/chunks/pages/index-0858deea02b2a417.js +0 -1
  119. package/.next/static/css/885da289cec275b3.css +0 -1
  120. package/.next/static/css/ea1c8134fe9a143e.css +0 -2
  121. package/src/components/LogoSplashScreen/LogoSplashScreen.module.scss +0 -65
  122. package/src/components/LogoSplashScreen/LogoSplashScreen.tsx +0 -31
  123. package/src/components/LogoSplashScreen/index.ts +0 -1
  124. package/src/components/Sizer/Sizer.module.scss +0 -10
  125. package/src/components/Sizer/Sizer.tsx +0 -10
  126. package/src/components/Sizer/index.ts +0 -1
  127. package/src/components/SplashScreen/SplashScreen.module.scss +0 -14
  128. package/src/components/SplashScreen/SplashScreen.tsx +0 -19
  129. package/src/components/SplashScreen/index.ts +0 -1
  130. package/src/hooks/useLocalStorage/index.ts +0 -1
  131. package/src/hooks/useLocalStorage/useLocalStorage.ts +0 -13
  132. package/src/hooks/useLocalStorage/useLocalStorageBoard.ts +0 -29
  133. package/src/hooks/useLocalStorage/useLocalStorageConfigId.ts +0 -29
  134. package/src/hooks/useLocalStorage/useLocalStorageLocale.ts +0 -32
  135. package/src/hooks/useLocalStorage/useLocalStorageRack.ts +0 -29
  136. /package/.next/static/{USLkKOoHbITebIEHkMGX_ → UzQCOB6CHhyOupkEq8oZM}/_ssgManifest.js +0 -0
  137. /package/{src/components/Logo/Logo.svg → public/logo.svg} +0 -0
package/src/i18n/pl.json CHANGED
@@ -21,7 +21,7 @@
21
21
  "dictionary.empty-state.no-definitions": "Słowo istnieje w słowniku ale nie posiada definicji.",
22
22
  "dictionary.empty-state.no-results": "Nie udało się znaleźć definicji słowa w słowniku.",
23
23
  "dictionary.empty-state.not-allowed": "To słowo nie jest dopuszczalne w grach.",
24
- "dictionary.empty-state.uninitialized": "Tu zostanie wyświetlona słownikowa definicja ostatnio podświetlonego słowa.",
24
+ "dictionary.empty-state.uninitialized": "Tu zostanie wyświetlona definicja słowa.",
25
25
  "dictionary.input.placeholder": "Szukaj w słowniku...",
26
26
  "dictionary.input.title": "Słowa rozdzielone przecinkiem",
27
27
  "empty-state.error": "Błąd",
@@ -46,7 +46,7 @@
46
46
  "remaining-tiles": "Pozostałe płytki",
47
47
  "results": "Wyniki",
48
48
  "results.empty-state.no-results": "Brak wyników - nie można wygenerować żadnego słowa.",
49
- "results.empty-state.outdated": "Wyniki są nieaktualne. Kliknij poniżej, aby zaktualizować.",
49
+ "results.empty-state.outdated": "Wyniki są nieaktualne.",
50
50
  "results.empty-state.uninitialized": "Tu zostaną wyświetlone słowa wygenerowane z Twoich liter.",
51
51
  "results.input.placeholder": "Szukaj rozwiązania... (RegExp)",
52
52
  "results.insert": "Wstaw",
@@ -0,0 +1,5 @@
1
+ const arrayEquals = <T>(array1: T[], array2: T[]): boolean => {
2
+ return array1.length === array2.length && array1.every((value, index) => value === array2[index]);
3
+ };
4
+
5
+ export default arrayEquals;
package/src/lib/index.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export { default as arrayEquals } from './arrayEquals';
1
2
  export { default as canUseDom } from './canUseDom';
2
3
  export { default as createArray } from './createArray';
3
4
  export { default as createComparator } from './createComparator';
@@ -1,12 +1,14 @@
1
1
  import { BLANK } from '@scrabble-solver/constants';
2
2
  import { Tile } from '@scrabble-solver/types';
3
3
 
4
+ import { Rack } from 'types';
5
+
4
6
  interface CharacterTilePair {
5
7
  character: string | null;
6
8
  tile: Tile | null;
7
9
  }
8
10
 
9
- const zipCharactersAndTiles = (characters: (string | null)[], tiles: Tile[]): CharacterTilePair[] => {
11
+ const zipCharactersAndTiles = (characters: Rack, tiles: Tile[]): CharacterTilePair[] => {
10
12
  let remainingTiles = [...tiles];
11
13
 
12
14
  return characters.map((character) => {
@@ -1,4 +1,4 @@
1
- import { FunctionComponent } from 'react';
1
+ import { FunctionComponent, memo } from 'react';
2
2
 
3
3
  import { Dictionary, DictionaryInput, Modal } from 'components';
4
4
  import { useTranslate } from 'state';
@@ -24,4 +24,4 @@ const DictionaryModal: FunctionComponent<Props> = ({ className, isOpen, onClose
24
24
  );
25
25
  };
26
26
 
27
- export default DictionaryModal;
27
+ export default memo(DictionaryModal);
@@ -1,4 +1,4 @@
1
- import { FunctionComponent } from 'react';
1
+ import { FunctionComponent, memo } from 'react';
2
2
 
3
3
  import { Modal } from 'components';
4
4
  import { useTranslate } from 'state';
@@ -35,4 +35,4 @@ const KeyMapModal: FunctionComponent<Props> = ({ className, isOpen, onClose }) =
35
35
  );
36
36
  };
37
37
 
38
- export default KeyMapModal;
38
+ export default memo(KeyMapModal);
@@ -1,5 +1,3 @@
1
- import React from 'react';
2
-
3
1
  import { Key } from 'components';
4
2
  import { ArrowDown, ArrowLeft, ArrowRight, ArrowUp } from 'icons';
5
3
  import { isMac } from 'lib';
@@ -23,11 +23,35 @@
23
23
  justify-content: space-between;
24
24
  }
25
25
 
26
- .flag {
27
- --height: var(--button--icon--size);
26
+ .settingsLabel {
27
+ @include ellipsis;
28
+
29
+ --quatar-flag-aspect-ratio: 2.545;
30
+ --highest-flag-aspect-ratio: var(--quatar-flag-aspect-ratio);
31
+ --aspect-ratio: var(--highest-flag-aspect-ratio);
32
+ --padding: calc(var(--spacing--l) + var(--button--icon--size) * var(--aspect-ratio));
33
+
34
+ [dir='ltr'] & {
35
+ padding-right: var(--padding);
36
+ }
37
+
38
+ [dir='rtl'] & {
39
+ padding-left: var(--padding);
40
+ }
41
+ }
28
42
 
29
- width: calc(var(--height) * var(--aspect--ratio));
30
- height: var(--height);
43
+ .flag {
44
+ position: fixed;
45
+ width: calc(var(--button--icon--size) * var(--aspect-ratio));
46
+ height: var(--button--icon--size);
31
47
  border-radius: var(--border--radius);
32
48
  box-shadow: 0 0 0 1px var(--box-shadow--color);
49
+
50
+ [dir='ltr'] & {
51
+ right: calc(2 * var(--spacing--l));
52
+ }
53
+
54
+ [dir='rtl'] & {
55
+ left: calc(2 * var(--spacing--l));
56
+ }
33
57
  }
@@ -1,4 +1,4 @@
1
- import { FunctionComponent } from 'react';
1
+ import { FunctionComponent, memo } from 'react';
2
2
 
3
3
  import { Button, Modal } from 'components';
4
4
  import { LOCALE_FLAGS } from 'i18n';
@@ -18,7 +18,7 @@ interface Props {
18
18
  onShowWords: () => void;
19
19
  }
20
20
 
21
- const Menu: FunctionComponent<Props> = ({
21
+ const MenuModal: FunctionComponent<Props> = ({
22
22
  className,
23
23
  isOpen,
24
24
  onClose,
@@ -63,7 +63,7 @@ const Menu: FunctionComponent<Props> = ({
63
63
 
64
64
  <Button aria-label={translate('settings')} className={styles.button} Icon={Cog} onClick={onShowSettings}>
65
65
  <div className={styles.settings}>
66
- <span>{translate('settings')}</span>
66
+ <div className={styles.settingsLabel}>{translate('settings')}</div>
67
67
  <Flag.Icon className={styles.flag} />
68
68
  </div>
69
69
  </Button>
@@ -71,4 +71,4 @@ const Menu: FunctionComponent<Props> = ({
71
71
  );
72
72
  };
73
73
 
74
- export default Menu;
74
+ export default memo(MenuModal);
@@ -1,4 +1,4 @@
1
- import { FunctionComponent } from 'react';
1
+ import { FunctionComponent, memo } from 'react';
2
2
 
3
3
  import { Badge, Modal } from 'components';
4
4
  import { LOCALE_FEATURES } from 'i18n';
@@ -56,4 +56,4 @@ const RemainingTilesModal: FunctionComponent<Props> = ({ className, isOpen, onCl
56
56
  );
57
57
  };
58
58
 
59
- export default RemainingTilesModal;
59
+ export default memo(RemainingTilesModal);
@@ -13,12 +13,8 @@
13
13
  }
14
14
 
15
15
  .dictionary {
16
- flex: 0 1 calc(var(--dictionary--height) - var(--text-input--height));
16
+ flex: 0 0 var(--dictionary--height--mobile);
17
17
  background-color: var(--color--background--element);
18
18
  border: var(--border);
19
19
  border-radius: var(--border--radius);
20
-
21
- @media (max-height: 600px) {
22
- display: none;
23
- }
24
20
  }
@@ -1,8 +1,9 @@
1
1
  import { Result } from '@scrabble-solver/types';
2
- import { FunctionComponent, useMemo } from 'react';
2
+ import { FunctionComponent, memo, useEffect, useMemo } from 'react';
3
3
  import { useDispatch } from 'react-redux';
4
4
 
5
5
  import { Button, Dictionary, Modal, Results } from 'components';
6
+ import { useAppLayout } from 'hooks';
6
7
  import { Check, EyeFill } from 'icons';
7
8
  import { resultsSlice, selectResultCandidate, selectResults, useTranslate, useTypedSelector } from 'state';
8
9
 
@@ -17,6 +18,7 @@ interface Props {
17
18
  const ResultsModal: FunctionComponent<Props> = ({ className, isOpen, onClose }) => {
18
19
  const dispatch = useDispatch();
19
20
  const translate = useTranslate();
21
+ const { showResultsInModal } = useAppLayout();
20
22
  const results = useTypedSelector(selectResults);
21
23
  const resultCandidate = useTypedSelector(selectResultCandidate);
22
24
  const index = results ? results.findIndex((result) => result.id === resultCandidate?.id) : -1;
@@ -49,6 +51,12 @@ const ResultsModal: FunctionComponent<Props> = ({ className, isOpen, onClose })
49
51
  onClose();
50
52
  };
51
53
 
54
+ useEffect(() => {
55
+ if (isOpen && !showResultsInModal) {
56
+ onClose();
57
+ }
58
+ }, [isOpen, onClose, showResultsInModal]);
59
+
52
60
  return (
53
61
  <Modal
54
62
  className={className}
@@ -88,4 +96,4 @@ const ResultsModal: FunctionComponent<Props> = ({ className, isOpen, onClose })
88
96
  );
89
97
  };
90
98
 
91
- export default ResultsModal;
99
+ export default memo(ResultsModal);
@@ -1,4 +1,4 @@
1
- import { FunctionComponent } from 'react';
1
+ import { FunctionComponent, memo } from 'react';
2
2
 
3
3
  import { Modal } from 'components';
4
4
  import { useTranslate } from 'state';
@@ -31,4 +31,4 @@ const SettingsModal: FunctionComponent<Props> = ({ className, isOpen, onClose })
31
31
  );
32
32
  };
33
33
 
34
- export default SettingsModal;
34
+ export default memo(SettingsModal);
@@ -1,6 +1,8 @@
1
+ import { AutoGroupTiles } from 'types';
2
+
1
3
  import { NULL_VALUE } from './constants';
2
4
 
3
- export const parseValue = (value: string): 'left' | 'right' | null => {
5
+ export const parseValue = (value: string): AutoGroupTiles => {
4
6
  if (value === 'left' || value === 'right') {
5
7
  return value;
6
8
  }
@@ -20,7 +20,7 @@
20
20
  .flag {
21
21
  --height: 32px;
22
22
 
23
- width: calc(var(--height) * var(--aspect--ratio));
23
+ width: calc(var(--height) * var(--aspect-ratio));
24
24
  height: var(--height);
25
25
  border-radius: var(--border--radius);
26
26
  box-shadow: var(--box-shadow);
@@ -1,5 +1,5 @@
1
1
  import classNames from 'classnames';
2
- import { FunctionComponent } from 'react';
2
+ import { FunctionComponent, memo } from 'react';
3
3
 
4
4
  import { Badge, Modal } from 'components';
5
5
  import { Check, Cross } from 'icons';
@@ -53,4 +53,4 @@ const WordsModal: FunctionComponent<Props> = ({ className, isOpen, onClose }) =>
53
53
  );
54
54
  };
55
55
 
56
- export default WordsModal;
56
+ export default memo(WordsModal);
@@ -1,18 +1,3 @@
1
- @import 'styles/mixins';
2
-
3
- .index {
4
- display: flex;
5
- flex-direction: column;
6
- height: 100%;
7
- opacity: 0;
8
- overflow: auto;
9
- transition: var(--transition--long);
10
-
11
- &.initialized {
12
- opacity: 1;
13
- }
14
- }
15
-
16
1
  .nav {
17
2
  position: relative;
18
3
  z-index: 1;
@@ -23,7 +8,7 @@
23
8
  width: 100%;
24
9
  display: flex;
25
10
  align-items: flex-start;
26
- padding: var(--spacing--l);
11
+ padding: var(--nav--padding);
27
12
  }
28
13
 
29
14
  .navLogo {
@@ -36,12 +21,9 @@
36
21
  }
37
22
 
38
23
  .logo {
39
- height: 60px;
24
+ width: calc(var(--logo--aspect-ratio) * var(--logo--height));
25
+ height: var(--logo--height);
40
26
  user-select: none;
41
-
42
- @include media('<l') {
43
- height: 48px;
44
- }
45
27
  }
46
28
 
47
29
  .solver {
@@ -1,13 +1,11 @@
1
- import classNames from 'classnames';
2
1
  import fs from 'fs';
3
2
  import path from 'path';
4
- import { FunctionComponent, useEffect, useState } from 'react';
3
+ import { FunctionComponent, useCallback, useState } from 'react';
5
4
  import ReactModal from 'react-modal';
6
5
  import { useDispatch } from 'react-redux';
7
- import { useEffectOnce, useMeasure } from 'react-use';
8
6
 
9
- import { Logo, LogoSplashScreen, NavButtons, Solver, SvgFontFix } from 'components';
10
- import { useAppLayout, useDirection, useLanguage, useLocalStorage } from 'hooks';
7
+ import { Logo, NavButtons, Solver, SvgFontFix } from 'components';
8
+ import { useDirection, useEffectOnce, useLanguage, useLocalStorage } from 'hooks';
11
9
  import { LOCALE_FEATURES } from 'i18n';
12
10
  import {
13
11
  DictionaryModal,
@@ -33,7 +31,6 @@ interface Props {
33
31
  const Index: FunctionComponent<Props> = ({ version }) => {
34
32
  const dispatch = useDispatch();
35
33
  const locale = useTypedSelector(selectLocale);
36
- const { showResultsInModal } = useAppLayout();
37
34
  const [showDictionary, setShowDictionary] = useState(false);
38
35
  const [showKeyMap, setShowKeyMap] = useState(false);
39
36
  const [showMenu, setShowMenu] = useState(false);
@@ -41,38 +38,36 @@ const Index: FunctionComponent<Props> = ({ version }) => {
41
38
  const [showResults, setShowResults] = useState(false);
42
39
  const [showSettings, setShowSettings] = useState(false);
43
40
  const [showWords, setShowWords] = useState(false);
44
- const [isInitialized, setIsInitialized] = useState(false);
45
- const [indexRef, { height: indexHeight, width: indexWidth }] = useMeasure<HTMLDivElement>();
46
- const [navRef, { height: navHeight }] = useMeasure<HTMLElement>();
47
- const solverHeight = indexHeight - navHeight;
48
- const solverWidth = indexWidth;
49
41
  const [isClient, setIsClient] = useState(false);
50
42
 
51
- const handleClear = () => {
52
- dispatch(reset());
53
- };
43
+ const handleShowResults = useCallback(() => setShowResults(true), []);
44
+ const handleClear = useCallback(() => dispatch(reset()), [dispatch]);
45
+ const handleHideDictionary = useCallback(() => setShowDictionary(false), []);
46
+ const handleHideKeyMap = useCallback(() => setShowKeyMap(false), []);
47
+ const handleHideMenu = useCallback(() => setShowMenu(false), []);
48
+ const handleHideRemainingTiles = useCallback(() => setShowRemainingTiles(false), []);
49
+ const handleHideResults = useCallback(() => setShowResults(false), []);
50
+ const handleHideSettings = useCallback(() => setShowSettings(false), []);
51
+ const handleHideWords = useCallback(() => setShowWords(false), []);
52
+ const handleShowDictionary = useCallback(() => setShowDictionary(true), []);
53
+ const handleShowKeyMap = useCallback(() => setShowKeyMap(true), []);
54
+ const handleShowMenu = useCallback(() => setShowMenu(true), []);
55
+ const handleShowRemainingTiles = useCallback(() => setShowRemainingTiles(true), []);
56
+ const handleShowSettings = useCallback(() => setShowSettings(true), []);
57
+ const handleShowWords = useCallback(() => setShowWords(true), []);
54
58
 
55
59
  useDirection(LOCALE_FEATURES[locale].direction);
56
60
  useLanguage(locale);
57
61
  useLocalStorage();
58
62
 
59
63
  useEffectOnce(() => {
60
- setIsClient(true);
61
- dispatch(initialize());
62
- setIsInitialized(true);
63
- });
64
-
65
- useEffect(() => {
66
- if (!showResultsInModal) {
67
- setShowResults(false);
68
- }
69
- }, [showResultsInModal]);
70
-
71
- useEffect(() => {
72
64
  if (process.env.NODE_ENV === 'production') {
73
65
  registerServiceWorker();
74
66
  }
75
- }, []);
67
+
68
+ setIsClient(true);
69
+ dispatch(initialize());
70
+ });
76
71
 
77
72
  if (!isClient) {
78
73
  return null;
@@ -82,58 +77,47 @@ const Index: FunctionComponent<Props> = ({ version }) => {
82
77
  <>
83
78
  <SvgFontFix />
84
79
 
85
- <div className={classNames(styles.index, { [styles.initialized]: isInitialized })} ref={indexRef}>
86
- <nav className={styles.nav} ref={navRef}>
87
- <div className={styles.navContent}>
88
- <div className={styles.navLogo}>
89
- <a className={styles.logoContainer} href="/" title={version}>
90
- <Logo className={styles.logo} />
91
- </a>
92
- </div>
93
-
94
- <NavButtons
95
- onClear={handleClear}
96
- onShowKeyMap={() => setShowKeyMap(true)}
97
- onShowMenu={() => setShowMenu(true)}
98
- onShowRemainingTiles={() => setShowRemainingTiles(true)}
99
- onShowSettings={() => setShowSettings(true)}
100
- onShowWords={() => setShowWords(true)}
101
- />
80
+ <nav className={styles.nav}>
81
+ <div className={styles.navContent}>
82
+ <div className={styles.navLogo}>
83
+ <a className={styles.logoContainer} href="/" title={version}>
84
+ <Logo className={styles.logo} />
85
+ </a>
102
86
  </div>
103
- </nav>
104
-
105
- {solverHeight > 0 && solverWidth > 0 && (
106
- <Solver
107
- className={styles.solver}
108
- height={solverHeight}
109
- width={solverWidth}
110
- onShowResults={() => setShowResults(true)}
87
+
88
+ <NavButtons
89
+ onClear={handleClear}
90
+ onShowKeyMap={handleShowKeyMap}
91
+ onShowMenu={handleShowMenu}
92
+ onShowRemainingTiles={handleShowRemainingTiles}
93
+ onShowSettings={handleShowSettings}
94
+ onShowWords={handleShowWords}
111
95
  />
112
- )}
113
- </div>
96
+ </div>
97
+ </nav>
98
+
99
+ <Solver className={styles.solver} onShowResults={handleShowResults} />
114
100
 
115
101
  <MenuModal
116
102
  isOpen={showMenu}
117
- onClose={() => setShowMenu(false)}
118
- onShowDictionary={() => setShowDictionary(true)}
119
- onShowRemainingTiles={() => setShowRemainingTiles(true)}
120
- onShowSettings={() => setShowSettings(true)}
121
- onShowWords={() => setShowWords(true)}
103
+ onClose={handleHideMenu}
104
+ onShowDictionary={handleShowDictionary}
105
+ onShowRemainingTiles={handleShowRemainingTiles}
106
+ onShowSettings={handleShowSettings}
107
+ onShowWords={handleShowWords}
122
108
  />
123
109
 
124
- <SettingsModal isOpen={showSettings} onClose={() => setShowSettings(false)} />
125
-
126
- <KeyMapModal isOpen={showKeyMap} onClose={() => setShowKeyMap(false)} />
110
+ <SettingsModal isOpen={showSettings} onClose={handleHideSettings} />
127
111
 
128
- <WordsModal isOpen={showWords} onClose={() => setShowWords(false)} />
112
+ <KeyMapModal isOpen={showKeyMap} onClose={handleHideKeyMap} />
129
113
 
130
- <RemainingTilesModal isOpen={showRemainingTiles} onClose={() => setShowRemainingTiles(false)} />
114
+ <WordsModal isOpen={showWords} onClose={handleHideWords} />
131
115
 
132
- <ResultsModal isOpen={showResults} onClose={() => setShowResults(false)} />
116
+ <RemainingTilesModal isOpen={showRemainingTiles} onClose={handleHideRemainingTiles} />
133
117
 
134
- <DictionaryModal isOpen={showDictionary} onClose={() => setShowDictionary(false)} />
118
+ <ResultsModal isOpen={showResults} onClose={handleHideResults} />
135
119
 
136
- <LogoSplashScreen forceShow={!isInitialized} />
120
+ <DictionaryModal isOpen={showDictionary} onClose={handleHideDictionary} />
137
121
  </>
138
122
  );
139
123
  };
@@ -19,8 +19,14 @@ export const COLOR_GREEN = '#bae3ba';
19
19
  export const COLOR_RED = '#f7c2aa';
20
20
  export const COLOR_YELLOW = '#efe3ae';
21
21
 
22
- export const COMPONENTS_SPACING = 40;
23
- export const COMPONENTS_SPACING_SMALL = 20;
22
+ export const SPACING_XS = 2;
23
+ export const SPACING_S = 5;
24
+ export const SPACING_M = 10;
25
+ export const SPACING_L = 20;
26
+ export const SPACING_XL = 40;
27
+
28
+ export const COMPONENTS_SPACING = SPACING_XL;
29
+ export const COMPONENTS_SPACING_SMALL = SPACING_L;
24
30
 
25
31
  export const BOARD_CELL_ACTIONS_OFFSET = 3;
26
32
  export const BOARD_CELL_BORDER_WIDTH = 1;
@@ -36,6 +42,23 @@ export const BOARD_TILE_SIZE_MIN = 20;
36
42
 
37
43
  export const BORDER_WIDTH = 1;
38
44
 
45
+ export const BUTTON_ICON_SIZE = 24;
46
+ export const BUTTON_PADDING_VERTICAL = SPACING_M;
47
+ export const BUTTON_HEIGHT = BUTTON_ICON_SIZE + 2 * BUTTON_PADDING_VERTICAL + 2 * BORDER_WIDTH;
48
+
49
+ export const DICTIONARY_HEIGHT = 260;
50
+ export const DICTIONARY_HEIGHT_MOBILE = 110;
51
+
52
+ export const LOGO_ASPECT_RATIO = 682 / 166;
53
+ export const LOGO_HEIGHT = 60;
54
+ export const LOGO_HEIGHT_SMALL = 48;
55
+ export const LOGO_SRC = '/logo.svg';
56
+
57
+ export const MODAL_WIDTH = 370;
58
+ export const MODAL_HEADER_HEIGHT = 45;
59
+
60
+ export const NAV_PADDING = SPACING_L;
61
+
39
62
  export const TILE_SIZE = 80;
40
63
 
41
64
  export const PLAIN_TILES_COLOR_DEFAULT = COLOR_GREEN;
@@ -67,10 +90,14 @@ export const RACK_TILE_SIZE_MAX = 80;
67
90
 
68
91
  export const REMAINING_TILES_TILE_SIZE = 50;
69
92
 
93
+ export const RESULTS_HEADER_HEIGHT = 35;
94
+
70
95
  export const RESULTS_ITEM_HEIGHT = 40;
71
96
 
72
97
  export const SOLVER_COLUMN_WIDTH = 580;
73
98
 
99
+ export const TEXT_INPUT_HEIGHT = 40;
100
+
74
101
  export const TILE_APPEAR_DURATION = 200;
75
102
 
76
103
  export const TILE_APPEAR_KEYFRAMES = [
@@ -1,6 +1,9 @@
1
1
  import { Board, Locale } from '@scrabble-solver/types';
2
2
  import store2 from 'store2';
3
3
 
4
+ import { AutoGroupTiles, Rack } from 'types';
5
+
6
+ const AUTO_GROUP_TILES = 'auto-group-tiles';
4
7
  const BOARD = 'board';
5
8
  const CONFIG_ID = 'config-id';
6
9
  const LOCALE = 'locale';
@@ -9,6 +12,14 @@ const RACK = 'rack';
9
12
  const store = store2.namespace('scrabble-solver');
10
13
 
11
14
  const localStorage = {
15
+ getAutoGroupTiles(): AutoGroupTiles | undefined {
16
+ return store.get(AUTO_GROUP_TILES);
17
+ },
18
+
19
+ setAutoGroupTiles(autoGroupTiles: AutoGroupTiles | undefined): void {
20
+ store.set(AUTO_GROUP_TILES, autoGroupTiles, true);
21
+ },
22
+
12
23
  getBoard(): Board | undefined {
13
24
  const serialized = store.get(BOARD);
14
25
  return serialized ? Board.fromJson(JSON.parse(serialized)) : serialized;
@@ -35,11 +46,11 @@ const localStorage = {
35
46
  store.set(LOCALE, locale, true);
36
47
  },
37
48
 
38
- getRack(): (string | null)[] | undefined {
49
+ getRack(): Rack | undefined {
39
50
  return store.get(RACK);
40
51
  },
41
52
 
42
- setRack(rack: (string | null)[] | undefined): void {
53
+ setRack(rack: Rack | undefined): void {
43
54
  store.set(RACK, rack, true);
44
55
  },
45
56
  };
@@ -1,3 +1,5 @@
1
+ /* eslint-disable max-lines */
2
+
1
3
  import { PayloadAction } from '@reduxjs/toolkit';
2
4
  import { COMMA_ARABIC, COMMA_LATIN } from '@scrabble-solver/constants';
3
5
  import { Locale, Result } from '@scrabble-solver/types';
@@ -15,6 +17,7 @@ import {
15
17
  selectDictionary,
16
18
  selectLocale,
17
19
  selectLocaleAutoGroupTiles,
20
+ selectRack,
18
21
  } from './selectors';
19
22
  import {
20
23
  boardSlice,
@@ -105,9 +108,14 @@ function* onDictionarySubmit(): AnyGenerator {
105
108
  }
106
109
 
107
110
  function* onInitialize(): AnyGenerator {
111
+ const board = yield select(selectBoard);
112
+
108
113
  yield call(visit);
109
- yield* ensureProperTilesCount();
110
- yield put(verifySlice.actions.submit());
114
+
115
+ if (!board.isEmpty()) {
116
+ yield* ensureProperTilesCount();
117
+ yield put(verifySlice.actions.submit());
118
+ }
111
119
  }
112
120
 
113
121
  function* onReset(): AnyGenerator {
@@ -191,13 +199,13 @@ function* onVerify(): AnyGenerator {
191
199
 
192
200
  function* ensureProperTilesCount(): AnyGenerator {
193
201
  const { config } = yield select(selectConfig);
194
- const characters = yield select(selectCharacters);
202
+ const rack = yield select(selectRack);
195
203
 
196
- if (config.maximumCharactersCount > characters.length) {
197
- const differenceCount = Math.abs(config.maximumCharactersCount - characters.length);
198
- yield put(rackSlice.actions.init([...characters, ...Array(differenceCount).fill(null)]));
199
- } else if (config.maximumCharactersCount < characters.length) {
200
- const nonNulls = characters.filter(Boolean).slice(0, config.maximumCharactersCount);
204
+ if (config.maximumCharactersCount > rack.length) {
205
+ const differenceCount = Math.abs(config.maximumCharactersCount - rack.length);
206
+ yield put(rackSlice.actions.init([...rack, ...Array(differenceCount).fill(null)]));
207
+ } else if (config.maximumCharactersCount < rack.length) {
208
+ const nonNulls = rack.filter(Boolean).slice(0, config.maximumCharactersCount);
201
209
  const differenceCount = Math.abs(config.maximumCharactersCount - nonNulls.length);
202
210
  const autoGroupTiles = yield select(selectLocaleAutoGroupTiles);
203
211
  yield put(rackSlice.actions.init([...nonNulls, ...Array(differenceCount).fill(null)]));
@@ -1,13 +1,17 @@
1
1
  import { getLocaleConfig } from '@scrabble-solver/configs';
2
2
  import { Board } from '@scrabble-solver/types';
3
3
 
4
+ import localStorage from '../localStorage';
5
+
4
6
  import settingsInitialState from './settingsInitialState';
5
7
 
6
8
  export type BoardState = Board;
7
9
 
8
10
  const { configId, locale } = settingsInitialState;
9
11
  const { boardHeight, boardWidth } = getLocaleConfig(configId, locale);
10
- const boardInitialState: BoardState = Board.create(boardWidth, boardHeight);
12
+ export const boardDefaultState = Board.create(boardWidth, boardHeight);
13
+
14
+ const boardInitialState: BoardState = localStorage.getBoard() || boardDefaultState;
11
15
 
12
16
  // const createOxyphenbutazone = () => {
13
17
  // // Tiles: oypbaze
@@ -2,7 +2,7 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit';
2
2
  import { EMPTY_CELL } from '@scrabble-solver/constants';
3
3
  import { Board, Cell, Result, Tile } from '@scrabble-solver/types';
4
4
 
5
- import boardInitialState from './boardInitialState';
5
+ import boardInitialState, { boardDefaultState } from './boardInitialState';
6
6
 
7
7
  const boardSlice = createSlice({
8
8
  initialState: boardInitialState,
@@ -43,7 +43,7 @@ const boardSlice = createSlice({
43
43
  },
44
44
 
45
45
  reset: () => {
46
- return boardInitialState;
46
+ return boardDefaultState;
47
47
  },
48
48
 
49
49
  toggleCellIsBlank: (state, action: PayloadAction<{ x: number; y: number }>) => {