scrabble-solver 2.10.11 → 2.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/lerna.json +1 -1
  2. package/package.json +8 -8
  3. package/packages/configs/package.json +3 -3
  4. package/packages/constants/package.json +1 -1
  5. package/packages/dictionaries/package-lock.json +2 -2
  6. package/packages/dictionaries/package.json +4 -4
  7. package/packages/logger/package-lock.json +8 -8
  8. package/packages/logger/package.json +1 -1
  9. package/packages/scrabble-solver/.next/BUILD_ID +1 -1
  10. package/packages/scrabble-solver/.next/build-manifest.json +12 -12
  11. package/packages/scrabble-solver/.next/cache/.tsbuildinfo +1 -1
  12. package/packages/scrabble-solver/.next/cache/eslint/.cache_8dgz12 +1 -1
  13. package/packages/scrabble-solver/.next/cache/next-server.js.nft.json +1 -1
  14. package/packages/scrabble-solver/.next/cache/webpack/client-production/0.pack +0 -0
  15. package/packages/scrabble-solver/.next/cache/webpack/client-production/index.pack +0 -0
  16. package/packages/scrabble-solver/.next/cache/webpack/edge-server-production/0.pack +0 -0
  17. package/packages/scrabble-solver/.next/cache/webpack/edge-server-production/index.pack +0 -0
  18. package/packages/scrabble-solver/.next/cache/webpack/server-production/0.pack +0 -0
  19. package/packages/scrabble-solver/.next/cache/webpack/server-production/index.pack +0 -0
  20. package/packages/scrabble-solver/.next/next-server.js.nft.json +1 -1
  21. package/packages/scrabble-solver/.next/prerender-manifest.json +1 -1
  22. package/packages/scrabble-solver/.next/required-server-files.json +1 -1
  23. package/packages/scrabble-solver/.next/routes-manifest.json +1 -1
  24. package/packages/scrabble-solver/.next/server/chunks/{176.js → 277.js} +734 -590
  25. package/packages/scrabble-solver/.next/server/chunks/{290.js → 417.js} +3 -3
  26. package/packages/scrabble-solver/.next/server/chunks/50.js +371 -343
  27. package/packages/scrabble-solver/.next/server/chunks/664.js +15 -15
  28. package/packages/scrabble-solver/.next/server/chunks/859.js +17 -10
  29. package/packages/scrabble-solver/.next/server/middleware-build-manifest.js +1 -1
  30. package/packages/scrabble-solver/.next/server/next-font-manifest.js +1 -0
  31. package/packages/scrabble-solver/.next/server/pages/404.html +2 -2
  32. package/packages/scrabble-solver/.next/server/pages/404.js.nft.json +1 -1
  33. package/packages/scrabble-solver/.next/server/pages/500.html +1 -1
  34. package/packages/scrabble-solver/.next/server/pages/_app.js +4 -4
  35. package/packages/scrabble-solver/.next/server/pages/_app.js.nft.json +1 -1
  36. package/packages/scrabble-solver/.next/server/pages/_document.js +2 -2
  37. package/packages/scrabble-solver/.next/server/pages/_document.js.nft.json +1 -1
  38. package/packages/scrabble-solver/.next/server/pages/_error.js +4 -4
  39. package/packages/scrabble-solver/.next/server/pages/api/dictionary/[locale]/[word].js +4 -4
  40. package/packages/scrabble-solver/.next/server/pages/api/dictionary/[locale]/[word].js.nft.json +1 -1
  41. package/packages/scrabble-solver/.next/server/pages/api/dictionary/[locale].js +3 -3
  42. package/packages/scrabble-solver/.next/server/pages/api/dictionary/[locale].js.nft.json +1 -1
  43. package/packages/scrabble-solver/.next/server/pages/api/solve.js +9 -10
  44. package/packages/scrabble-solver/.next/server/pages/api/solve.js.nft.json +1 -1
  45. package/packages/scrabble-solver/.next/server/pages/api/verify.js +3 -3
  46. package/packages/scrabble-solver/.next/server/pages/api/verify.js.nft.json +1 -1
  47. package/packages/scrabble-solver/.next/server/pages/api/visit.js +3 -3
  48. package/packages/scrabble-solver/.next/server/pages/api/visit.js.nft.json +1 -1
  49. package/packages/scrabble-solver/.next/server/pages/index.html +1 -1
  50. package/packages/scrabble-solver/.next/server/pages/index.js +253 -159
  51. package/packages/scrabble-solver/.next/server/pages/index.js.nft.json +1 -1
  52. package/packages/scrabble-solver/.next/server/pages/index.json +1 -1
  53. package/packages/scrabble-solver/.next/static/45ye7793DY705HOcuK9lJ/_buildManifest.js +1 -0
  54. package/packages/scrabble-solver/.next/static/chunks/main-0ecb9ccfcb6c9b24.js +1 -0
  55. package/packages/scrabble-solver/.next/static/chunks/pages/404-e0f30450e9920dc3.js +1 -0
  56. package/packages/scrabble-solver/.next/static/chunks/pages/_app-d7acee5e526752d9.js +28 -0
  57. package/packages/scrabble-solver/.next/static/chunks/pages/{_error-8353112a01355ec2.js → _error-54de1933a164a1ff.js} +1 -1
  58. package/packages/scrabble-solver/.next/static/chunks/pages/index-35d2c1c79a201ae2.js +1 -0
  59. package/packages/scrabble-solver/.next/static/css/a48caa6f57de6e98.css +1 -0
  60. package/packages/scrabble-solver/.next/static/css/c49bbe944ddd1b39.css +2 -0
  61. package/packages/scrabble-solver/.next/trace +55 -55
  62. package/packages/scrabble-solver/package-lock.json +224 -233
  63. package/packages/scrabble-solver/package.json +11 -11
  64. package/packages/scrabble-solver/src/components/Board/components/Cell/Cell.module.scss +1 -0
  65. package/packages/scrabble-solver/src/components/Board/hooks/useGrid.ts +1 -1
  66. package/packages/scrabble-solver/src/components/Dictionary/Dictionary.module.scss +13 -8
  67. package/packages/scrabble-solver/src/components/Dictionary/Dictionary.tsx +5 -5
  68. package/packages/scrabble-solver/src/components/Loading/Loading.tsx +1 -1
  69. package/packages/scrabble-solver/src/components/Modal/Modal.module.scss +0 -1
  70. package/packages/scrabble-solver/src/components/NavButtons/NavButtons.tsx +4 -5
  71. package/packages/scrabble-solver/src/components/Results/Results.module.scss +5 -2
  72. package/packages/scrabble-solver/src/components/Results/Results.tsx +11 -11
  73. package/packages/scrabble-solver/src/components/Solver/Solver.module.scss +1 -0
  74. package/packages/scrabble-solver/src/components/Solver/Solver.tsx +13 -26
  75. package/packages/scrabble-solver/src/components/Solver/components/FloatingSolveButton/FloatingSolveButton.module.scss +7 -0
  76. package/packages/scrabble-solver/src/components/Solver/components/{SolveButton/SolveButton.tsx → FloatingSolveButton/FloatingSolveButton.tsx} +6 -6
  77. package/packages/scrabble-solver/src/components/Solver/components/FloatingSolveButton/index.ts +1 -0
  78. package/packages/scrabble-solver/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.module.scss +19 -4
  79. package/packages/scrabble-solver/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.tsx +13 -1
  80. package/packages/scrabble-solver/src/components/Solver/components/index.ts +1 -1
  81. package/packages/scrabble-solver/src/components/Spinner/Spinner.module.scss +11 -0
  82. package/packages/scrabble-solver/src/components/Spinner/Spinner.tsx +19 -0
  83. package/packages/scrabble-solver/src/components/Spinner/index.ts +1 -0
  84. package/packages/scrabble-solver/src/components/Tile/Tile.tsx +5 -5
  85. package/packages/scrabble-solver/src/components/index.ts +1 -0
  86. package/packages/scrabble-solver/src/hooks/index.ts +1 -0
  87. package/packages/scrabble-solver/src/hooks/useAppLayout.ts +29 -0
  88. package/packages/scrabble-solver/src/i18n/de.json +1 -0
  89. package/packages/scrabble-solver/src/i18n/en.json +1 -0
  90. package/packages/scrabble-solver/src/i18n/es.json +1 -0
  91. package/packages/scrabble-solver/src/i18n/fa.json +1 -0
  92. package/packages/scrabble-solver/src/i18n/fr.json +1 -0
  93. package/packages/scrabble-solver/src/i18n/index.ts +1 -1
  94. package/packages/scrabble-solver/src/i18n/pl.json +1 -0
  95. package/packages/scrabble-solver/src/modals/DictionaryModal/DictionaryModal.module.scss +18 -0
  96. package/packages/scrabble-solver/src/modals/DictionaryModal/DictionaryModal.tsx +27 -0
  97. package/packages/scrabble-solver/src/modals/DictionaryModal/index.ts +1 -0
  98. package/packages/scrabble-solver/src/modals/MenuModal/MenuModal.tsx +7 -1
  99. package/packages/scrabble-solver/src/modals/ResultsModal/ResultsModal.module.scss +18 -0
  100. package/packages/scrabble-solver/src/modals/ResultsModal/ResultsModal.tsx +8 -7
  101. package/packages/scrabble-solver/src/modals/index.ts +1 -0
  102. package/packages/scrabble-solver/src/pages/api/solve.ts +9 -10
  103. package/packages/scrabble-solver/src/pages/index.tsx +20 -15
  104. package/packages/scrabble-solver/src/state/localStorage.ts +0 -9
  105. package/packages/scrabble-solver/src/styles/animations.scss +10 -0
  106. package/packages/scrabble-solver/src/styles/global.scss +1 -1
  107. package/packages/scrabble-solver/src/styles/variables.scss +1 -1
  108. package/packages/scrabble-solver/src/types/index.ts +1 -0
  109. package/packages/solver/package-lock.json +2 -2
  110. package/packages/solver/package.json +5 -5
  111. package/packages/types/package.json +2 -2
  112. package/packages/word-definitions/build/lib/normalizeDefinition.js +1 -1
  113. package/packages/word-definitions/package-lock.json +8 -8
  114. package/packages/word-definitions/package.json +2 -2
  115. package/packages/word-definitions/src/lib/normalizeDefinition.ts +1 -1
  116. package/packages/word-definitions/src/parse/__tests__/expected/en-US.oe.json +7 -0
  117. package/packages/word-definitions/src/parse/__tests__/input/en-US.oe.html +1337 -0
  118. package/packages/word-definitions/src/parse/parse.test.ts +1 -0
  119. package/packages/word-lists/package-lock.json +8 -8
  120. package/packages/word-lists/package.json +2 -2
  121. package/packages/configs/package-lock.json +0 -58
  122. package/packages/constants/package-lock.json +0 -13
  123. package/packages/scrabble-solver/.next/server/font-loader-manifest.js +0 -1
  124. package/packages/scrabble-solver/.next/static/chunks/main-74c4d6b2b5c362f3.js +0 -1
  125. package/packages/scrabble-solver/.next/static/chunks/pages/404-6c1a6e3251710371.js +0 -1
  126. package/packages/scrabble-solver/.next/static/chunks/pages/_app-d98e480ff8c583de.js +0 -28
  127. package/packages/scrabble-solver/.next/static/chunks/pages/index-bd1c7d3872c37456.js +0 -1
  128. package/packages/scrabble-solver/.next/static/css/a9b55372a26cf77d.css +0 -1
  129. package/packages/scrabble-solver/.next/static/css/b8954b85e2fa5b63.css +0 -2
  130. package/packages/scrabble-solver/.next/static/msKI0ZURgJImoGBJvCBiF/_buildManifest.js +0 -1
  131. package/packages/scrabble-solver/src/components/Solver/components/SolveButton/SolveButton.module.scss +0 -4
  132. package/packages/scrabble-solver/src/components/Solver/components/SolveButton/index.ts +0 -1
  133. package/packages/types/package-lock.json +0 -30
  134. /package/packages/scrabble-solver/.next/server/{font-loader-manifest.json → next-font-manifest.json} +0 -0
  135. /package/packages/scrabble-solver/.next/static/{msKI0ZURgJImoGBJvCBiF → 45ye7793DY705HOcuK9lJ}/_ssgManifest.js +0 -0
@@ -0,0 +1,29 @@
1
+ import { COMPONENTS_SPACING, COMPONENTS_SPACING_SMALL } from 'parameters';
2
+
3
+ import useIsTouchDevice from './useIsTouchDevice';
4
+ import useMediaQuery from './useMediaQuery';
5
+
6
+ const useAppLayout = () => {
7
+ const isTouchDevice = useIsTouchDevice();
8
+ const isLessThanXs = useMediaQuery('<xs');
9
+ const isLessThanS = useMediaQuery('<s');
10
+ const isLessThanM = useMediaQuery('<m');
11
+ const isLessThanL = useMediaQuery('<l');
12
+ const isLessThanXl = useMediaQuery('<xl');
13
+ const showColumn = !isLessThanL;
14
+
15
+ return {
16
+ animateTile: !isLessThanXs,
17
+ componentsSpacing: isLessThanXl ? COMPONENTS_SPACING_SMALL : COMPONENTS_SPACING,
18
+ isBoardFullWidth: isLessThanM,
19
+ showColumn,
20
+ showCompactControls: !showColumn,
21
+ showFloatingSolveButton: isTouchDevice,
22
+ showKeyMap: !isTouchDevice,
23
+ showResultsInModal: isLessThanL,
24
+ showShortNav: isLessThanS,
25
+ showTilePoints: !isLessThanXs,
26
+ };
27
+ };
28
+
29
+ export default useAppLayout;
@@ -17,6 +17,7 @@
17
17
  "common.vowels": "Vokale",
18
18
  "common.word": "Wort",
19
19
  "common.words": "Wörter",
20
+ "dictionary": "Wörterbuch",
20
21
  "dictionary.empty-state.no-definitions": "Wort existiert im Wörterbuch aber hat keine Definition.",
21
22
  "dictionary.empty-state.no-results": "Wort kann nicht im Wörterbuch gefunden werden.",
22
23
  "dictionary.empty-state.not-allowed": "Dieses Wort ist nicht erlaubt.",
@@ -17,6 +17,7 @@
17
17
  "common.vowels": "Vowels",
18
18
  "common.word": "Word",
19
19
  "common.words": "Words",
20
+ "dictionary": "Dictionary",
20
21
  "dictionary.empty-state.no-definitions": "Word exists in the dictionary but it does not have a definition.",
21
22
  "dictionary.empty-state.no-results": "Unable to find word definition in the dictionary.",
22
23
  "dictionary.empty-state.not-allowed": "This word is not allowed.",
@@ -17,6 +17,7 @@
17
17
  "common.vowels": "Vocales",
18
18
  "common.word": "Palabra",
19
19
  "common.words": "Palabras",
20
+ "dictionary": "Diccionario",
20
21
  "dictionary.empty-state.no-definitions": "La palabra existe en el diccionario pero no tiene una definición.",
21
22
  "dictionary.empty-state.no-results": "No se puede encontrar la definición de palabra en el diccionario.",
22
23
  "dictionary.empty-state.not-allowed": "Esta palabra no es aceptable.",
@@ -17,6 +17,7 @@
17
17
  "common.vowels": "حروف مصوت",
18
18
  "common.word": "کلمه",
19
19
  "common.words": "کلمات",
20
+ "dictionary": "فرهنگ لغت",
20
21
  "dictionary.empty-state.no-definitions": "کلمه در فرهنگ لغت وجود دارد، ولی معنایی برای آن ثبت نشده است.",
21
22
  "dictionary.empty-state.no-results": "کلمه در فرهنگ لغت یافت نشد.",
22
23
  "dictionary.empty-state.not-allowed": "این کلمه مجاز نیست.",
@@ -17,6 +17,7 @@
17
17
  "common.vowels": "Voyelles",
18
18
  "common.word": "Mot",
19
19
  "common.words": "Mots",
20
+ "dictionary": "Dictionnaire",
20
21
  "dictionary.empty-state.no-definitions": "Le mot existe dans le dictionary mais n'a pas de définition.",
21
22
  "dictionary.empty-state.no-results": "Impossible de trouver une définition pour ce mot dans le dictionaire.",
22
23
  "dictionary.empty-state.not-allowed": "Ce mot n'est pas pas acceptable.",
@@ -1,2 +1,2 @@
1
- export { default } from './i18n';
2
1
  export * from './constants';
2
+ export { default } from './i18n';
@@ -17,6 +17,7 @@
17
17
  "common.vowels": "Samogłoski",
18
18
  "common.word": "Słowo",
19
19
  "common.words": "Słowa",
20
+ "dictionary": "Słownik",
20
21
  "dictionary.empty-state.no-definitions": "Słowo istnieje w słowniku ale nie posiada definicji.",
21
22
  "dictionary.empty-state.no-results": "Nie udało się znaleźć definicji słowa w słowniku.",
22
23
  "dictionary.empty-state.not-allowed": "To słowo nie jest dopuszczalne w grach.",
@@ -0,0 +1,18 @@
1
+ .content {
2
+ height: 100%;
3
+ display: flex;
4
+ flex-direction: column;
5
+ background-color: var(--color--background--element);
6
+ border: var(--border);
7
+ box-shadow: var(--box-shadow);
8
+ }
9
+
10
+ .dictionary {
11
+ flex: 1;
12
+ border-bottom: var(--border);
13
+ max-height: initial;
14
+ }
15
+
16
+ .dictionaryInput {
17
+ flex: 0 0 auto;
18
+ }
@@ -0,0 +1,27 @@
1
+ import { FunctionComponent } from 'react';
2
+
3
+ import { Dictionary, DictionaryInput, Modal } from 'components';
4
+ import { useTranslate } from 'state';
5
+
6
+ import styles from './DictionaryModal.module.scss';
7
+
8
+ interface Props {
9
+ className?: string;
10
+ isOpen: boolean;
11
+ onClose: () => void;
12
+ }
13
+
14
+ const DictionaryModal: FunctionComponent<Props> = ({ className, isOpen, onClose }) => {
15
+ const translate = useTranslate();
16
+
17
+ return (
18
+ <Modal className={className} isOpen={isOpen} title={translate('dictionary')} onClose={onClose}>
19
+ <div className={styles.content}>
20
+ <Dictionary className={styles.dictionary} />
21
+ <DictionaryInput className={styles.dictionaryInput} />
22
+ </div>
23
+ </Modal>
24
+ );
25
+ };
26
+
27
+ export default DictionaryModal;
@@ -0,0 +1 @@
1
+ export { default } from './DictionaryModal';
@@ -1,7 +1,7 @@
1
1
  import { FunctionComponent } from 'react';
2
2
 
3
3
  import { Button, Modal } from 'components';
4
- import { CardChecklist, Cog, Github, Sack } from 'icons';
4
+ import { BookHalf, CardChecklist, Cog, Github, Sack } from 'icons';
5
5
  import { GITHUB_PROJECT_URL } from 'parameters';
6
6
  import { useTranslate } from 'state';
7
7
 
@@ -11,6 +11,7 @@ interface Props {
11
11
  className?: string;
12
12
  isOpen: boolean;
13
13
  onClose: () => void;
14
+ onShowDictionary: () => void;
14
15
  onShowRemainingTiles: () => void;
15
16
  onShowSettings: () => void;
16
17
  onShowWords: () => void;
@@ -20,6 +21,7 @@ const Menu: FunctionComponent<Props> = ({
20
21
  className,
21
22
  isOpen,
22
23
  onClose,
24
+ onShowDictionary,
23
25
  onShowRemainingTiles,
24
26
  onShowSettings,
25
27
  onShowWords,
@@ -41,6 +43,10 @@ const Menu: FunctionComponent<Props> = ({
41
43
  {translate('words')}
42
44
  </Button>
43
45
 
46
+ <Button aria-label={translate('dictionary')} className={styles.button} Icon={BookHalf} onClick={onShowDictionary}>
47
+ {translate('dictionary')}
48
+ </Button>
49
+
44
50
  <Button.Link
45
51
  aria-label={translate('github')}
46
52
  className={styles.button}
@@ -1,6 +1,24 @@
1
+ @import 'styles/mixins';
2
+
1
3
  .content {
2
4
  position: relative;
3
5
  display: flex;
4
6
  flex-direction: column;
5
7
  height: 100%;
8
+ gap: var(--spacing--l);
9
+ }
10
+
11
+ .results {
12
+ flex: 1;
13
+ }
14
+
15
+ .dictionary {
16
+ flex: 0 0 calc(var(--dictionary--height) - var(--text-input--height));
17
+ background-color: var(--color--background--element);
18
+ border: var(--border);
19
+ box-shadow: var(--box-shadow);
20
+
21
+ @media (max-height: 600px) {
22
+ display: none;
23
+ }
6
24
  }
@@ -2,8 +2,7 @@ import { Result } from '@scrabble-solver/types';
2
2
  import { FunctionComponent, useMemo } from 'react';
3
3
  import { useDispatch } from 'react-redux';
4
4
 
5
- import { Modal, Results } from 'components';
6
- import { useMediaQuery } from 'hooks';
5
+ import { Dictionary, Modal, Results } from 'components';
7
6
  import {
8
7
  resultsSlice,
9
8
  selectResultCandidate,
@@ -23,7 +22,6 @@ interface Props {
23
22
  const ResultsModal: FunctionComponent<Props> = ({ className, isOpen, onClose }) => {
24
23
  const dispatch = useDispatch();
25
24
  const translate = useTranslate();
26
- const isLessThanS = useMediaQuery('<s');
27
25
  const results = useTypedSelector(selectSortedFilteredResults);
28
26
  const resultCandidate = useTypedSelector(selectResultCandidate);
29
27
  const index = (results || []).findIndex((result) => result.id === resultCandidate?.id);
@@ -32,20 +30,23 @@ const ResultsModal: FunctionComponent<Props> = ({ className, isOpen, onClose })
32
30
  const callbacks = useMemo(
33
31
  () => ({
34
32
  onClick: (result: Result) => {
35
- dispatch(resultsSlice.actions.changeResultCandidate(result));
33
+ const isSelected = result === resultCandidate;
36
34
 
37
- if (isLessThanS) {
35
+ if (isSelected) {
38
36
  onClose();
37
+ } else {
38
+ dispatch(resultsSlice.actions.changeResultCandidate(result));
39
39
  }
40
40
  },
41
41
  }),
42
- [dispatch, isLessThanS, onClose],
42
+ [dispatch, onClose, resultCandidate],
43
43
  );
44
44
 
45
45
  return (
46
46
  <Modal className={className} isOpen={isOpen} title={translate('results')} onClose={onClose}>
47
47
  <div className={styles.content}>
48
- <Results callbacks={callbacks} highlightedIndex={highlightedIndex} />
48
+ <Results callbacks={callbacks} className={styles.results} highlightedIndex={highlightedIndex} />
49
+ <Dictionary className={styles.dictionary} />
49
50
  </div>
50
51
  </Modal>
51
52
  );
@@ -1,3 +1,4 @@
1
+ export { default as DictionaryModal } from './DictionaryModal';
1
2
  export { default as KeyMapModal } from './KeyMapModal';
2
3
  export { default as MenuModal } from './MenuModal';
3
4
  export { default as RemainingTilesModal } from './RemainingTilesModal';
@@ -57,30 +57,29 @@ const parseRequest = (request: NextApiRequest): RequestData => {
57
57
  throw new Error('Invalid "configId" parameter');
58
58
  }
59
59
 
60
- const config = getLocaleConfig(configId, locale);
61
-
62
- if (!isBoardJson(boardJson) || !isBoardValid(boardJson, config)) {
63
- throw new Error('Invalid "board" parameter');
64
- }
65
-
66
60
  if (!isStringArray(characters) || characters.length === 0) {
67
61
  throw new Error('Invalid "characters" parameter');
68
62
  }
69
63
 
64
+ const config = getLocaleConfig(configId, locale);
65
+
70
66
  for (const character of characters) {
71
67
  if (!config.hasCharacter(character) && character !== BLANK) {
72
68
  throw new Error('Invalid "characters" parameter');
73
69
  }
74
70
  }
75
-
76
- const board = Board.fromJson(boardJson);
77
- const blankTilesCount = characters.filter((character) => character === BLANK).length;
78
- const blanksCount = board.getBlanksCount() + blankTilesCount;
71
+ const blanksCount = characters.filter((character) => character === BLANK).length;
79
72
 
80
73
  if (blanksCount > config.blanksCount) {
81
74
  throw new Error('Too many blank tiles passed');
82
75
  }
83
76
 
77
+ if (!isBoardJson(boardJson) || !isBoardValid(boardJson, config)) {
78
+ throw new Error('Invalid "board" parameter');
79
+ }
80
+
81
+ const board = Board.fromJson(boardJson);
82
+
84
83
  return {
85
84
  board,
86
85
  characters,
@@ -1,18 +1,26 @@
1
1
  import classNames from 'classnames';
2
2
  import fs from 'fs';
3
3
  import path from 'path';
4
- import { AnimationEvent, FunctionComponent, useEffect, useState } from 'react';
4
+ import { FunctionComponent, useEffect, useState } from 'react';
5
5
  import ReactModal from 'react-modal';
6
6
  import { useDispatch } from 'react-redux';
7
7
  import { useEffectOnce, useMeasure } from 'react-use';
8
8
 
9
9
  import { Logo, LogoSplashScreen, NavButtons, Solver, SvgFontFix } from 'components';
10
- import { useDirection, useLanguage, useLocalStorage, useMediaQuery } from 'hooks';
10
+ import { useAppLayout, useDirection, useLanguage, useLocalStorage } from 'hooks';
11
11
  import { LOCALE_FEATURES } from 'i18n';
12
- import { KeyMapModal, MenuModal, RemainingTilesModal, ResultsModal, SettingsModal, WordsModal } from 'modals';
12
+ import {
13
+ DictionaryModal,
14
+ KeyMapModal,
15
+ MenuModal,
16
+ RemainingTilesModal,
17
+ ResultsModal,
18
+ SettingsModal,
19
+ WordsModal,
20
+ } from 'modals';
13
21
  import { INITIALIZATION_DURATION } from 'parameters';
14
22
  import { registerServiceWorker } from 'serviceWorkerManager';
15
- import { initialize, localStorage, reset, selectLocale, useTypedSelector } from 'state';
23
+ import { initialize, reset, selectLocale, useTypedSelector } from 'state';
16
24
 
17
25
  import styles from './index.module.scss';
18
26
 
@@ -26,7 +34,8 @@ interface Props {
26
34
  const Index: FunctionComponent<Props> = ({ version }) => {
27
35
  const dispatch = useDispatch();
28
36
  const locale = useTypedSelector(selectLocale);
29
- const isLessThanL = useMediaQuery('<l');
37
+ const { showResultsInModal } = useAppLayout();
38
+ const [showDictionary, setShowDictionary] = useState(false);
30
39
  const [showKeyMap, setShowKeyMap] = useState(false);
31
40
  const [showMenu, setShowMenu] = useState(false);
32
41
  const [showRemainingTiles, setShowRemainingTiles] = useState(false);
@@ -44,13 +53,6 @@ const Index: FunctionComponent<Props> = ({ version }) => {
44
53
  dispatch(reset());
45
54
  };
46
55
 
47
- const handleSplashAnimationEnd = (event: AnimationEvent<HTMLDivElement>) => {
48
- if (event.target === event.currentTarget && !localStorage.getHasVisited()) {
49
- setShowSettings(true);
50
- localStorage.setHasVisited(true);
51
- }
52
- };
53
-
54
56
  useDirection(LOCALE_FEATURES[locale].direction);
55
57
  useLanguage(locale);
56
58
  useLocalStorage();
@@ -65,10 +67,10 @@ const Index: FunctionComponent<Props> = ({ version }) => {
65
67
  });
66
68
 
67
69
  useEffect(() => {
68
- if (!isLessThanL) {
70
+ if (!showResultsInModal) {
69
71
  setShowResults(false);
70
72
  }
71
- }, [isLessThanL]);
73
+ }, [showResultsInModal]);
72
74
 
73
75
  useEffect(() => {
74
76
  if (process.env.NODE_ENV === 'production') {
@@ -115,6 +117,7 @@ const Index: FunctionComponent<Props> = ({ version }) => {
115
117
  <MenuModal
116
118
  isOpen={showMenu}
117
119
  onClose={() => setShowMenu(false)}
120
+ onShowDictionary={() => setShowDictionary(true)}
118
121
  onShowRemainingTiles={() => setShowRemainingTiles(true)}
119
122
  onShowSettings={() => setShowSettings(true)}
120
123
  onShowWords={() => setShowWords(true)}
@@ -130,7 +133,9 @@ const Index: FunctionComponent<Props> = ({ version }) => {
130
133
 
131
134
  <ResultsModal isOpen={showResults} onClose={() => setShowResults(false)} />
132
135
 
133
- <LogoSplashScreen forceShow={!isInitialized} onAnimationEnd={handleSplashAnimationEnd} />
136
+ <DictionaryModal isOpen={showDictionary} onClose={() => setShowDictionary(false)} />
137
+
138
+ <LogoSplashScreen forceShow={!isInitialized} />
134
139
  </>
135
140
  );
136
141
  };
@@ -3,7 +3,6 @@ import store2 from 'store2';
3
3
 
4
4
  const BOARD = 'board';
5
5
  const CONFIG_ID = 'config-id';
6
- const HAS_VISITED = 'has-visited';
7
6
  const LOCALE = 'locale';
8
7
  const RACK = 'rack';
9
8
 
@@ -28,14 +27,6 @@ const localStorage = {
28
27
  store.set(CONFIG_ID, configId, true);
29
28
  },
30
29
 
31
- getHasVisited(): boolean {
32
- return Boolean(store.get(HAS_VISITED));
33
- },
34
-
35
- setHasVisited(hasVisited: boolean): void {
36
- store.set(HAS_VISITED, hasVisited, true);
37
- },
38
-
39
30
  getLocale(): Locale | undefined {
40
31
  return store.get(LOCALE);
41
32
  },
@@ -28,6 +28,16 @@
28
28
  }
29
29
  }
30
30
 
31
+ @keyframes rotate {
32
+ 0% {
33
+ transform: rotate(0deg);
34
+ }
35
+
36
+ 100% {
37
+ transform: rotate(360deg);
38
+ }
39
+ }
40
+
31
41
  @keyframes pulse {
32
42
  0% {
33
43
  transform: scale(1);
@@ -54,5 +54,5 @@ h4 {
54
54
 
55
55
  [lang='fa-IR'] {
56
56
  --font--family: 'Vazirmatn', sans-serif;
57
- --font--family--title: 'Vazirmatn', sans-serif;
57
+ --font--family--title: var(--font--family);
58
58
  }
@@ -86,10 +86,10 @@ $easeOutSine: cubic-bezier(0.61, 1, 0.88, 1);
86
86
  --z-index--close-button: 101;
87
87
  --z-index--tooltip: 102;
88
88
 
89
+ --button--icon--size: 24px;
89
90
  --dictionary--height: 260px;
90
91
  --modal--width: 370px;
91
92
  --solver-column--width: 580px;
92
93
  --square-button--size: 32px;
93
- --button--icon--size: 24px;
94
94
  --text-input--height: 40px;
95
95
  }
@@ -50,6 +50,7 @@ export type TranslationKey =
50
50
  | 'common.vowels'
51
51
  | 'common.word'
52
52
  | 'common.words'
53
+ | 'dictionary'
53
54
  | 'dictionary.empty-state.no-definitions'
54
55
  | 'dictionary.empty-state.no-results'
55
56
  | 'dictionary.empty-state.not-allowed'
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@scrabble-solver/solver",
3
- "version": "2.10.11",
3
+ "version": "2.11.0",
4
4
  "lockfileVersion": 2,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@scrabble-solver/solver",
9
- "version": "2.10.11",
9
+ "version": "2.11.0",
10
10
  "license": "CC-BY-NC-ND-4.0",
11
11
  "dependencies": {
12
12
  "@kamilmielnik/trie": "^2.0.1"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scrabble-solver/solver",
3
- "version": "2.10.11",
3
+ "version": "2.11.0",
4
4
  "description": "Scrabble Solver 2 - Solver",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -25,12 +25,12 @@
25
25
  },
26
26
  "dependencies": {
27
27
  "@kamilmielnik/trie": "^2.0.1",
28
- "@scrabble-solver/types": "^2.10.11"
28
+ "@scrabble-solver/types": "^2.11.0"
29
29
  },
30
30
  "devDependencies": {
31
- "@scrabble-solver/configs": "^2.10.11",
32
- "@scrabble-solver/constants": "^2.10.11",
33
- "@scrabble-solver/dictionaries": "^2.10.11"
31
+ "@scrabble-solver/configs": "^2.11.0",
32
+ "@scrabble-solver/constants": "^2.11.0",
33
+ "@scrabble-solver/dictionaries": "^2.11.0"
34
34
  },
35
35
  "gitHead": "1e2d1ad49a288d4a44196d8e3336740da132c637"
36
36
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scrabble-solver/types",
3
- "version": "2.10.11",
3
+ "version": "2.11.0",
4
4
  "description": "Scrabble Solver 2 - Types",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -24,7 +24,7 @@
24
24
  "clean:force": "npm run clean && rimraf package-lock.json"
25
25
  },
26
26
  "dependencies": {
27
- "@scrabble-solver/constants": "^2.10.11"
27
+ "@scrabble-solver/constants": "^2.11.0"
28
28
  },
29
29
  "gitHead": "1e2d1ad49a288d4a44196d8e3336740da132c637"
30
30
  }
@@ -7,7 +7,7 @@ const striptags_1 = __importDefault(require("striptags"));
7
7
  const EMPHASIS_TAGS = ['a', 'b', 'em', 'internalXref'];
8
8
  const normalizeHtmlTags = (definition) => (0, striptags_1.default)((0, striptags_1.default)(definition, EMPHASIS_TAGS), undefined, '"');
9
9
  const normalizeLineBreaks = (definition) => definition.replace(/\r\n/g, '\n');
10
- const normalizeWhitespace = (definition) => definition.replace(/[ ]+/g, ' ');
10
+ const normalizeWhitespace = (definition) => definition.replace(/[^\S\n ]+/g, ' ').replace(/[ ]+/g, ' ');
11
11
  const normalizeQuotes = (definition) => definition.replace(/\."/g, '".');
12
12
  /**
13
13
  * `(1.2) definition` -> `definition`
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@scrabble-solver/word-definitions",
3
- "version": "2.10.11",
3
+ "version": "2.11.0",
4
4
  "lockfileVersion": 2,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@scrabble-solver/word-definitions",
9
- "version": "2.10.11",
9
+ "version": "2.11.0",
10
10
  "license": "CC-BY-NC-ND-4.0",
11
11
  "dependencies": {
12
12
  "cheerio": "^1.0.0-rc.12",
@@ -27,9 +27,9 @@
27
27
  }
28
28
  },
29
29
  "node_modules/@types/node": {
30
- "version": "18.14.2",
31
- "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.2.tgz",
32
- "integrity": "sha512-1uEQxww3DaghA0RxqHx0O0ppVlo43pJhepY51OxuQIKHpjbnYLA7vcdwioNPzIqmC2u3I/dmylcqjlh0e7AyUA==",
30
+ "version": "18.15.0",
31
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.0.tgz",
32
+ "integrity": "sha512-z6nr0TTEOBGkzLGmbypWOGnpSpSIBorEhC4L+4HeQ2iezKCi4f77kyslRwvHeNitymGQ+oFyIWGP96l/DPSV9w==",
33
33
  "dev": true
34
34
  },
35
35
  "node_modules/boolbase": {
@@ -249,9 +249,9 @@
249
249
  }
250
250
  },
251
251
  "@types/node": {
252
- "version": "18.14.2",
253
- "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.2.tgz",
254
- "integrity": "sha512-1uEQxww3DaghA0RxqHx0O0ppVlo43pJhepY51OxuQIKHpjbnYLA7vcdwioNPzIqmC2u3I/dmylcqjlh0e7AyUA==",
252
+ "version": "18.15.0",
253
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.0.tgz",
254
+ "integrity": "sha512-z6nr0TTEOBGkzLGmbypWOGnpSpSIBorEhC4L+4HeQ2iezKCi4f77kyslRwvHeNitymGQ+oFyIWGP96l/DPSV9w==",
255
255
  "dev": true
256
256
  },
257
257
  "boolbase": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scrabble-solver/word-definitions",
3
- "version": "2.10.11",
3
+ "version": "2.11.0",
4
4
  "description": "Scrabble Solver 2 - Word definitions",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -23,7 +23,7 @@
23
23
  "clean": "rimraf build/ node_modules/ package-lock.json"
24
24
  },
25
25
  "dependencies": {
26
- "@scrabble-solver/types": "^2.10.11",
26
+ "@scrabble-solver/types": "^2.11.0",
27
27
  "cheerio": "^1.0.0-rc.12",
28
28
  "follow-redirects": "^1.15.2",
29
29
  "striptags": "^3.2.0"
@@ -8,7 +8,7 @@ const normalizeHtmlTags: Normalize = (definition) => striptags(striptags(definit
8
8
 
9
9
  const normalizeLineBreaks: Normalize = (definition) => definition.replace(/\r\n/g, '\n');
10
10
 
11
- const normalizeWhitespace: Normalize = (definition) => definition.replace(/[ ]+/g, ' ');
11
+ const normalizeWhitespace: Normalize = (definition) => definition.replace(/[^\S\n ]+/g, ' ').replace(/[ ]+/g, ' ');
12
12
 
13
13
  const normalizeQuotes: Normalize = (definition) => definition.replace(/\."/g, '".');
14
14
 
@@ -0,0 +1,7 @@
1
+ {
2
+ "definitions": [
3
+ "oersted",
4
+ "Kenzaburo 1935– Japanese writer"
5
+ ],
6
+ "exists": true
7
+ }