scrabble-solver 2.11.0 → 2.11.2

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 (155) hide show
  1. package/lerna.json +1 -1
  2. package/package.json +1 -1
  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 +2 -2
  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 +7 -7
  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/routes-manifest.json +1 -1
  23. package/packages/scrabble-solver/.next/server/chunks/277.js +523 -611
  24. package/packages/scrabble-solver/.next/server/middleware-build-manifest.js +1 -1
  25. package/packages/scrabble-solver/.next/server/pages/404.html +2 -2
  26. package/packages/scrabble-solver/.next/server/pages/404.js.nft.json +1 -1
  27. package/packages/scrabble-solver/.next/server/pages/500.html +1 -1
  28. package/packages/scrabble-solver/.next/server/pages/_app.js +0 -24
  29. package/packages/scrabble-solver/.next/server/pages/_app.js.nft.json +1 -1
  30. package/packages/scrabble-solver/.next/server/pages/_document.js.nft.json +1 -1
  31. package/packages/scrabble-solver/.next/server/pages/api/dictionary/[locale]/[word].js +0 -1
  32. package/packages/scrabble-solver/.next/server/pages/api/dictionary/[locale].js +0 -1
  33. package/packages/scrabble-solver/.next/server/pages/api/solve.js +28 -8
  34. package/packages/scrabble-solver/.next/server/pages/api/verify.js +0 -1
  35. package/packages/scrabble-solver/.next/server/pages/api/visit.js +0 -1
  36. package/packages/scrabble-solver/.next/server/pages/index.html +1 -1
  37. package/packages/scrabble-solver/.next/server/pages/index.js +49 -90
  38. package/packages/scrabble-solver/.next/server/pages/index.js.nft.json +1 -1
  39. package/packages/scrabble-solver/.next/server/pages/index.json +1 -1
  40. package/packages/scrabble-solver/.next/static/Mdvi3FY0PqkILKLbPlVBU/_buildManifest.js +1 -0
  41. package/packages/scrabble-solver/.next/static/chunks/pages/{404-e0f30450e9920dc3.js → 404-448ba28510855455.js} +1 -1
  42. package/packages/scrabble-solver/.next/static/chunks/pages/_app-495e6f4ccc278bb2.js +28 -0
  43. package/packages/scrabble-solver/.next/static/chunks/pages/index-5ecc51900ca29685.js +1 -0
  44. package/packages/scrabble-solver/.next/static/css/17b0a2db8742105f.css +1 -0
  45. package/packages/scrabble-solver/.next/static/css/e1ffeb2558330c55.css +2 -0
  46. package/packages/scrabble-solver/.next/trace +54 -55
  47. package/packages/scrabble-solver/package-lock.json +2 -79
  48. package/packages/scrabble-solver/package.json +8 -13
  49. package/packages/scrabble-solver/src/components/{Solver/components/EmptyState/EmptyState.module.scss → Alert/Alert.module.scss} +11 -7
  50. package/packages/scrabble-solver/src/components/{Solver/components/EmptyState/EmptyState.tsx → Alert/Alert.tsx} +8 -6
  51. package/packages/scrabble-solver/src/components/Alert/index.ts +1 -0
  52. package/packages/scrabble-solver/src/components/Board/Board.module.scss +55 -0
  53. package/packages/scrabble-solver/src/components/Board/BoardPure.tsx +4 -0
  54. package/packages/scrabble-solver/src/components/Board/components/Cell/Cell.module.scss +51 -2
  55. package/packages/scrabble-solver/src/components/Board/components/Cell/Cell.tsx +12 -0
  56. package/packages/scrabble-solver/src/components/Board/components/Cell/CellPure.tsx +12 -0
  57. package/packages/scrabble-solver/src/components/Board/hooks/useGrid.ts +9 -26
  58. package/packages/scrabble-solver/src/components/Board/lib/getPositionInGrid.ts +1 -1
  59. package/packages/scrabble-solver/src/components/Dictionary/Dictionary.module.scss +4 -0
  60. package/packages/scrabble-solver/src/components/DictionaryInput/DictionaryInput.module.scss +1 -0
  61. package/packages/scrabble-solver/src/components/EmptyState/EmptyState.module.scss +2 -1
  62. package/packages/scrabble-solver/src/components/EmptyState/EmptyState.tsx +1 -2
  63. package/packages/scrabble-solver/src/components/Loading/Loading.module.scss +1 -1
  64. package/packages/scrabble-solver/src/components/Logo/Logo.tsx +5 -0
  65. package/packages/scrabble-solver/src/components/LogoSplashScreen/LogoSplashScreen.module.scss +4 -1
  66. package/packages/scrabble-solver/src/components/Modal/Modal.module.scss +14 -0
  67. package/packages/scrabble-solver/src/components/Modal/Modal.tsx +4 -1
  68. package/packages/scrabble-solver/src/components/PlainTiles/PlainTiles.module.scss +1 -1
  69. package/packages/scrabble-solver/src/components/PlainTiles/Tile.tsx +3 -3
  70. package/packages/scrabble-solver/src/components/Rack/Rack.module.scss +25 -0
  71. package/packages/scrabble-solver/src/components/Rack/Rack.tsx +5 -4
  72. package/packages/scrabble-solver/src/components/Rack/RackTile.tsx +6 -13
  73. package/packages/scrabble-solver/src/components/Results/HeaderButton.tsx +5 -12
  74. package/packages/scrabble-solver/src/components/Results/Result.tsx +5 -3
  75. package/packages/scrabble-solver/src/components/Results/Results.module.scss +41 -1
  76. package/packages/scrabble-solver/src/components/Results/Results.tsx +29 -43
  77. package/packages/scrabble-solver/src/components/Results/types.ts +1 -1
  78. package/packages/scrabble-solver/src/components/ResultsInput/ResultsInput.module.scss +1 -0
  79. package/packages/scrabble-solver/src/components/Solver/Solver.module.scss +9 -4
  80. package/packages/scrabble-solver/src/components/Solver/Solver.tsx +17 -19
  81. package/packages/scrabble-solver/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.tsx +5 -6
  82. package/packages/scrabble-solver/src/components/Solver/components/index.ts +0 -1
  83. package/packages/scrabble-solver/src/components/Tile/Tile.module.scss +42 -61
  84. package/packages/scrabble-solver/src/components/Tile/Tile.tsx +4 -3
  85. package/packages/scrabble-solver/src/components/Tile/TilePure.tsx +4 -13
  86. package/packages/scrabble-solver/src/components/Tooltip/Tooltip.module.scss +1 -72
  87. package/packages/scrabble-solver/src/components/Tooltip/useTooltip.tsx +25 -35
  88. package/packages/scrabble-solver/src/components/index.ts +1 -0
  89. package/packages/scrabble-solver/src/hooks/index.ts +0 -1
  90. package/packages/scrabble-solver/src/hooks/useAppLayout.ts +3 -1
  91. package/packages/scrabble-solver/src/i18n/de.json +0 -1
  92. package/packages/scrabble-solver/src/i18n/en.json +0 -1
  93. package/packages/scrabble-solver/src/i18n/es.json +0 -1
  94. package/packages/scrabble-solver/src/i18n/fa.json +0 -1
  95. package/packages/scrabble-solver/src/i18n/fr.json +0 -1
  96. package/packages/scrabble-solver/src/i18n/pl.json +0 -1
  97. package/packages/scrabble-solver/src/lib/createRegExp.ts +13 -0
  98. package/packages/scrabble-solver/src/lib/groupResults.ts +38 -0
  99. package/packages/scrabble-solver/src/lib/guessLocale.ts +22 -0
  100. package/packages/scrabble-solver/src/lib/index.ts +4 -2
  101. package/packages/scrabble-solver/src/lib/sortResults.ts +6 -10
  102. package/packages/scrabble-solver/src/modals/DictionaryModal/DictionaryModal.module.scss +6 -2
  103. package/packages/scrabble-solver/src/modals/KeyMapModal/KeyMapModal.tsx +5 -21
  104. package/packages/scrabble-solver/src/modals/KeyMapModal/components/Mapping/Mapping.module.scss +4 -0
  105. package/packages/scrabble-solver/src/modals/MenuModal/MenuModal.module.scss +7 -0
  106. package/packages/scrabble-solver/src/modals/RemainingTilesModal/components/Character/Character.module.scss +1 -0
  107. package/packages/scrabble-solver/src/modals/ResultsModal/ResultsModal.module.scss +3 -3
  108. package/packages/scrabble-solver/src/modals/ResultsModal/ResultsModal.tsx +23 -11
  109. package/packages/scrabble-solver/src/modals/SettingsModal/components/LocaleSetting/LocaleSetting.module.scss +2 -0
  110. package/packages/scrabble-solver/src/modals/WordsModal/WordsModal.module.scss +8 -1
  111. package/packages/scrabble-solver/src/pages/api/dictionary/[locale]/[word].ts +0 -1
  112. package/packages/scrabble-solver/src/pages/api/dictionary/[locale]/index.ts +0 -1
  113. package/packages/scrabble-solver/src/pages/api/solve.ts +0 -1
  114. package/packages/scrabble-solver/src/pages/api/verify.ts +0 -1
  115. package/packages/scrabble-solver/src/pages/api/visit.ts +0 -1
  116. package/packages/scrabble-solver/src/pages/index.tsx +13 -15
  117. package/packages/scrabble-solver/src/parameters/index.ts +2 -2
  118. package/packages/scrabble-solver/src/state/createAppStore.ts +26 -10
  119. package/packages/scrabble-solver/src/state/sagas.ts +1 -0
  120. package/packages/scrabble-solver/src/state/selectors.ts +37 -37
  121. package/packages/scrabble-solver/src/state/slices/boardInitialState.ts +3 -1
  122. package/packages/scrabble-solver/src/state/slices/cellFilterInitialState.ts +3 -3
  123. package/packages/scrabble-solver/src/state/slices/cellFilterSlice.ts +3 -1
  124. package/packages/scrabble-solver/src/state/slices/dictionaryInitialState.ts +2 -2
  125. package/packages/scrabble-solver/src/state/slices/rackInitialState.ts +3 -1
  126. package/packages/scrabble-solver/src/state/slices/resultsInitialState.ts +11 -4
  127. package/packages/scrabble-solver/src/state/slices/settingsInitialState.ts +10 -21
  128. package/packages/scrabble-solver/src/state/slices/solveInitialState.ts +2 -2
  129. package/packages/scrabble-solver/src/state/slices/solveSlice.ts +2 -0
  130. package/packages/scrabble-solver/src/state/slices/verifyInitialState.ts +13 -4
  131. package/packages/scrabble-solver/src/state/types.ts +20 -2
  132. package/packages/scrabble-solver/src/styles/mixins.scss +25 -1
  133. package/packages/scrabble-solver/src/styles/variables.scss +17 -1
  134. package/packages/scrabble-solver/src/types/index.ts +10 -2
  135. package/packages/solver/package-lock.json +2 -2
  136. package/packages/solver/package.json +5 -5
  137. package/packages/types/build/Bonus.d.ts +2 -1
  138. package/packages/types/package.json +2 -2
  139. package/packages/types/src/Bonus.ts +3 -1
  140. package/packages/word-definitions/package-lock.json +8 -8
  141. package/packages/word-definitions/package.json +2 -2
  142. package/packages/word-lists/package-lock.json +2 -2
  143. package/packages/word-lists/package.json +2 -2
  144. package/packages/scrabble-solver/.next/static/45ye7793DY705HOcuK9lJ/_buildManifest.js +0 -1
  145. package/packages/scrabble-solver/.next/static/chunks/pages/_app-d7acee5e526752d9.js +0 -28
  146. package/packages/scrabble-solver/.next/static/chunks/pages/index-35d2c1c79a201ae2.js +0 -1
  147. package/packages/scrabble-solver/.next/static/css/a48caa6f57de6e98.css +0 -1
  148. package/packages/scrabble-solver/.next/static/css/c49bbe944ddd1b39.css +0 -2
  149. package/packages/scrabble-solver/src/components/Board/types/index.ts +0 -4
  150. package/packages/scrabble-solver/src/components/Solver/components/EmptyState/index.ts +0 -1
  151. package/packages/scrabble-solver/src/components/Tooltip/constants.ts +0 -28
  152. package/packages/scrabble-solver/src/hooks/useUniqueId.ts +0 -9
  153. package/packages/scrabble-solver/src/lib/isCtrl.ts +0 -7
  154. package/packages/scrabble-solver/src/state/rootReducer.ts +0 -25
  155. /package/packages/scrabble-solver/.next/static/{45ye7793DY705HOcuK9lJ → Mdvi3FY0PqkILKLbPlVBU}/_ssgManifest.js +0 -0
@@ -0,0 +1,38 @@
1
+ import { Result } from '@scrabble-solver/types';
2
+
3
+ import { Point } from 'types';
4
+
5
+ import createRegExp from './createRegExp';
6
+
7
+ interface GroupedResults {
8
+ matching: Result[];
9
+ other: Result[];
10
+ }
11
+
12
+ const groupResults = (
13
+ results: Result[] | undefined,
14
+ query: string,
15
+ cellFilter: Point[],
16
+ ): GroupedResults | undefined => {
17
+ if (typeof results === 'undefined') {
18
+ return results;
19
+ }
20
+
21
+ return results.reduce<GroupedResults>(
22
+ ({ matching, other }, result) => {
23
+ const matchesQuery = createRegExp(query).test(result.word);
24
+ const matchesCellFilter = cellFilter.every(({ x, y }) => {
25
+ return result.cells.some((cell) => cell.x === x && cell.y === y);
26
+ });
27
+ const isMatching = matchesQuery && matchesCellFilter;
28
+
29
+ return {
30
+ matching: isMatching ? [...matching, result] : matching,
31
+ other: isMatching ? other : [...other, result],
32
+ };
33
+ },
34
+ { matching: [], other: [] },
35
+ );
36
+ };
37
+
38
+ export default groupResults;
@@ -0,0 +1,22 @@
1
+ import { Locale } from '@scrabble-solver/types';
2
+
3
+ const guessLocale = (): Locale => {
4
+ if (!globalThis.navigator) {
5
+ return Locale.EN_US;
6
+ }
7
+
8
+ const locales = Object.values(Locale);
9
+ const exactMatch = locales.find((locale) => globalThis.navigator.language === locale);
10
+
11
+ if (exactMatch) {
12
+ return exactMatch;
13
+ }
14
+
15
+ const partialMatch = locales.find((locale) => {
16
+ return globalThis.navigator.language === locale.substring(0, 2);
17
+ });
18
+
19
+ return partialMatch || Locale.EN_US;
20
+ };
21
+
22
+ export default guessLocale;
@@ -5,6 +5,7 @@ export { default as createGridOf } from './createGridOf';
5
5
  export { default as createKeyboardNavigation } from './createKeyboardNavigation';
6
6
  export { default as createKeyComparator } from './createKeyComparator';
7
7
  export { default as createNullMovingComparator } from './createNullMovingComparator';
8
+ export { default as createRegExp } from './createRegExp';
8
9
  export { default as createStringComparator } from './createStringComparator';
9
10
  export { default as detectLocale } from './detectLocale';
10
11
  export { default as extractCharacters } from './extractCharacters';
@@ -14,10 +15,11 @@ export { default as getCellSize } from './getCellSize';
14
15
  export { default as getRemainingTiles } from './getRemainingTiles';
15
16
  export { default as getRemainingTilesCount } from './getRemainingTilesCount';
16
17
  export { default as getRemainingTilesGroups } from './getRemainingTilesGroups';
17
- export { default as getTotalRemainingTilesCount } from './getTotalRemainingTilesCount';
18
18
  export { default as getTileSizes } from './getTileSizes';
19
+ export { default as getTotalRemainingTilesCount } from './getTotalRemainingTilesCount';
20
+ export { default as groupResults } from './groupResults';
21
+ export { default as guessLocale } from './guessLocale';
19
22
  export { default as inverseDirection } from './inverseDirection';
20
- export { default as isCtrl } from './isCtrl';
21
23
  export { default as isMac } from './isMac';
22
24
  export { default as isRegExp } from './isRegExp';
23
25
  export { default as isStringArray } from './isStringArray';
@@ -1,6 +1,6 @@
1
1
  import { Result } from '@scrabble-solver/types';
2
2
 
3
- import { Comparator, ResultColumn, SortDirection } from 'types';
3
+ import { Comparator, ResultColumn, Sort, SortDirection } from 'types';
4
4
 
5
5
  import createKeyComparator from './createKeyComparator';
6
6
  import reverseComparator from './reverseComparator';
@@ -15,20 +15,16 @@ const comparators: Record<ResultColumn, (locale: string) => Comparator<Result>>
15
15
  [ResultColumn.WordsCount]: (locale: string) => createKeyComparator('wordsCount', locale),
16
16
  };
17
17
 
18
- const sortResults = (
19
- results: Result[] | undefined,
20
- column: ResultColumn,
21
- sortDirection: SortDirection,
22
- locale: string,
23
- ): Result[] | undefined => {
18
+ const sortResults = (results: Result[] | undefined, sort: Sort, locale: string): Result[] | undefined => {
24
19
  if (typeof results === 'undefined') {
25
20
  return results;
26
21
  }
27
22
 
28
- const createComparator = comparators[column];
23
+ const createComparator = comparators[sort.column];
29
24
  const comparator = createComparator(locale);
30
- const finalComparator = sortDirection === SortDirection.Descending ? reverseComparator(comparator) : comparator;
31
- return [...results].sort(finalComparator);
25
+ const finalComparator = sort.direction === SortDirection.Descending ? reverseComparator(comparator) : comparator;
26
+ const sortedResults = [...results].sort(finalComparator);
27
+ return sortedResults;
32
28
  };
33
29
 
34
30
  export default sortResults;
@@ -4,15 +4,19 @@
4
4
  flex-direction: column;
5
5
  background-color: var(--color--background--element);
6
6
  border: var(--border);
7
- box-shadow: var(--box-shadow);
7
+ border-radius: var(--border--radius);
8
8
  }
9
9
 
10
10
  .dictionary {
11
11
  flex: 1;
12
- border-bottom: var(--border);
13
12
  max-height: initial;
13
+ border-bottom: var(--border);
14
+ border-top-left-radius: var(--border--radius);
15
+ border-top-right-radius: var(--border--radius);
14
16
  }
15
17
 
16
18
  .dictionaryInput {
17
19
  flex: 0 0 auto;
20
+ border-bottom-left-radius: var(--border--radius);
21
+ border-bottom-right-radius: var(--border--radius);
18
22
  }
@@ -1,10 +1,10 @@
1
1
  import { FunctionComponent } from 'react';
2
2
 
3
- import { Key, Modal } from 'components';
4
- import { selectConfig, useTranslate, useTypedSelector } from 'state';
3
+ import { Modal } from 'components';
4
+ import { useTranslate } from 'state';
5
5
 
6
6
  import { Mapping } from './components';
7
- import { ARROWS, BACKSPACE, CTRL, DEL, ENTER, SPACE } from './keys';
7
+ import { ARROWS, BACKSPACE, DEL, ENTER, SPACE } from './keys';
8
8
 
9
9
  interface Props {
10
10
  className?: string;
@@ -14,7 +14,6 @@ interface Props {
14
14
 
15
15
  const KeyMapModal: FunctionComponent<Props> = ({ className, isOpen, onClose }) => {
16
16
  const translate = useTranslate();
17
- const config = useTypedSelector(selectConfig);
18
17
 
19
18
  return (
20
19
  <Modal className={className} isOpen={isOpen} title={translate('keyMap')} onClose={onClose}>
@@ -22,26 +21,11 @@ const KeyMapModal: FunctionComponent<Props> = ({ className, isOpen, onClose }) =
22
21
  <Mapping description={translate('keyMap.board-and-rack.navigate')} mapping={[ARROWS]} />
23
22
  <Mapping description={translate('keyMap.board-and-rack.remove-tile')} mapping={[DEL, BACKSPACE]} />
24
23
  <Mapping description={translate('keyMap.board-and-rack.submit')} mapping={[ENTER]} />
25
- {config.twoCharacterTiles.length > 0 && (
26
- <Mapping
27
- description={translate('keyMap.board-and-rack.insert-two-letter-tile')}
28
- mapping={[
29
- [
30
- CTRL,
31
- <>
32
- {config.twoCharacterTiles.map(([firstLetter]) => (
33
- <Key key={firstLetter}>{firstLetter.toUpperCase()}</Key>
34
- ))}
35
- </>,
36
- ],
37
- ]}
38
- />
39
- )}
40
24
  </Modal.Section>
41
25
 
42
26
  <Modal.Section title={translate('keyMap.board')}>
43
- <Mapping description={translate('keyMap.board.toggle-blank')} mapping={[SPACE, [CTRL, <Key key="b">B</Key>]]} />
44
- <Mapping description={translate('keyMap.board.toggle-direction')} mapping={[[CTRL, ARROWS]]} />
27
+ <Mapping description={translate('keyMap.board.toggle-blank')} mapping={[SPACE]} />
28
+ <Mapping description={translate('keyMap.board.toggle-direction')} mapping={[ARROWS]} />
45
29
  </Modal.Section>
46
30
 
47
31
  <Modal.Section title={translate('keyMap.rack')}>
@@ -29,6 +29,10 @@
29
29
  }
30
30
 
31
31
  .group {
32
+ [dir='rtl'] & {
33
+ flex-direction: row-reverse;
34
+ }
35
+
32
36
  &:first-child {
33
37
  margin-left: 0;
34
38
  }
@@ -4,6 +4,13 @@
4
4
  width: 100%;
5
5
  text-transform: none;
6
6
 
7
+ &,
8
+ &:active,
9
+ &:focus,
10
+ &:hover {
11
+ box-shadow: 0px 1px 1px 0px var(--box-shadow--color);
12
+ }
13
+
7
14
  & + & {
8
15
  margin-top: var(--spacing--l);
9
16
  }
@@ -19,6 +19,7 @@
19
19
  width: 100%;
20
20
  height: 100%;
21
21
  content: ' ';
22
+ border-radius: inherit;
22
23
  box-shadow: var(--box-shadow--error);
23
24
  }
24
25
  }
@@ -9,14 +9,14 @@
9
9
  }
10
10
 
11
11
  .results {
12
- flex: 1;
12
+ flex: 1 1 auto;
13
13
  }
14
14
 
15
15
  .dictionary {
16
- flex: 0 0 calc(var(--dictionary--height) - var(--text-input--height));
16
+ flex: 0 1 calc(var(--dictionary--height) - var(--text-input--height));
17
17
  background-color: var(--color--background--element);
18
18
  border: var(--border);
19
- box-shadow: var(--box-shadow);
19
+ border-radius: var(--border--radius);
20
20
 
21
21
  @media (max-height: 600px) {
22
22
  display: none;
@@ -2,14 +2,9 @@ import { Result } from '@scrabble-solver/types';
2
2
  import { FunctionComponent, useMemo } from 'react';
3
3
  import { useDispatch } from 'react-redux';
4
4
 
5
- import { Dictionary, Modal, Results } from 'components';
6
- import {
7
- resultsSlice,
8
- selectResultCandidate,
9
- selectSortedFilteredResults,
10
- useTranslate,
11
- useTypedSelector,
12
- } from 'state';
5
+ import { Button, Dictionary, Modal, Results } from 'components';
6
+ import { Check } from 'icons';
7
+ import { resultsSlice, selectResultCandidate, selectResults, useTranslate, useTypedSelector } from 'state';
13
8
 
14
9
  import styles from './ResultsModal.module.scss';
15
10
 
@@ -22,9 +17,9 @@ interface Props {
22
17
  const ResultsModal: FunctionComponent<Props> = ({ className, isOpen, onClose }) => {
23
18
  const dispatch = useDispatch();
24
19
  const translate = useTranslate();
25
- const results = useTypedSelector(selectSortedFilteredResults);
20
+ const results = useTypedSelector(selectResults);
26
21
  const resultCandidate = useTypedSelector(selectResultCandidate);
27
- const index = (results || []).findIndex((result) => result.id === resultCandidate?.id);
22
+ const index = results ? results.findIndex((result) => result.id === resultCandidate?.id) : -1;
28
23
  const highlightedIndex = index === -1 ? undefined : index;
29
24
 
30
25
  const callbacks = useMemo(
@@ -43,7 +38,24 @@ const ResultsModal: FunctionComponent<Props> = ({ className, isOpen, onClose })
43
38
  );
44
39
 
45
40
  return (
46
- <Modal className={className} isOpen={isOpen} title={translate('results')} onClose={onClose}>
41
+ <Modal
42
+ className={className}
43
+ footer={
44
+ <Button
45
+ aria-label={translate('results.insert')}
46
+ disabled={!resultCandidate}
47
+ Icon={Check}
48
+ tooltip={translate('results.insert')}
49
+ variant="primary"
50
+ onClick={onClose}
51
+ >
52
+ {translate('results.insert')}
53
+ </Button>
54
+ }
55
+ isOpen={isOpen}
56
+ title={translate('results')}
57
+ onClose={onClose}
58
+ >
47
59
  <div className={styles.content}>
48
60
  <Results callbacks={callbacks} className={styles.results} highlightedIndex={highlightedIndex} />
49
61
  <Dictionary className={styles.dictionary} />
@@ -21,6 +21,8 @@
21
21
  $height: 32px;
22
22
 
23
23
  height: $height;
24
+ border-radius: var(--border--radius);
25
+ box-shadow: var(--box-shadow);
24
26
  transition: var(--transition);
25
27
 
26
28
  &.de {
@@ -37,5 +37,12 @@
37
37
 
38
38
  width: $size;
39
39
  height: $size;
40
- margin-right: var(--spacing--s);
40
+
41
+ [dir='ltr'] & {
42
+ margin-right: var(--spacing--s);
43
+ }
44
+
45
+ [dir='rtl'] & {
46
+ margin-left: var(--spacing--s);
47
+ }
41
48
  }
@@ -37,7 +37,6 @@ const dictionary = async (request: NextApiRequest, response: NextApiResponse): P
37
37
  const message = error instanceof Error ? error.message : 'Unknown error';
38
38
  logger.error('dictionary - error', { error, meta });
39
39
  response.status(500).send({ error: 'Server error', message });
40
- throw error;
41
40
  }
42
41
  };
43
42
 
@@ -28,7 +28,6 @@ const dictionary = async (request: NextApiRequest, response: NextApiResponse): P
28
28
  const message = error instanceof Error ? error.message : 'Unknown error';
29
29
  logger.error('dictionary - error', { error, meta });
30
30
  response.status(500).send({ error: 'Server error', message });
31
- throw error;
32
31
  }
33
32
  };
34
33
 
@@ -42,7 +42,6 @@ const solve = async (request: NextApiRequest, response: NextApiResponse): Promis
42
42
  const message = error instanceof Error ? error.message : 'Unknown error';
43
43
  logger.error('solve - error', { error, meta });
44
44
  response.status(500).send({ error: 'Server error', message });
45
- throw error;
46
45
  }
47
46
  };
48
47
 
@@ -38,7 +38,6 @@ const verify = async (request: NextApiRequest, response: NextApiResponse): Promi
38
38
  const message = error instanceof Error ? error.message : 'Unknown error';
39
39
  logger.error('verify - error', { error, meta });
40
40
  response.status(500).send({ error: 'Server error', message });
41
- throw error;
42
41
  }
43
42
  };
44
43
 
@@ -13,7 +13,6 @@ const visit = async (request: NextApiRequest, response: NextApiResponse): Promis
13
13
  const message = error instanceof Error ? error.message : 'Unknown error';
14
14
  logger.error('visit - error', { error, meta });
15
15
  response.status(500).send({ error: 'Server error', message });
16
- throw error;
17
16
  }
18
17
  };
19
18
 
@@ -18,7 +18,6 @@ import {
18
18
  SettingsModal,
19
19
  WordsModal,
20
20
  } from 'modals';
21
- import { INITIALIZATION_DURATION } from 'parameters';
22
21
  import { registerServiceWorker } from 'serviceWorkerManager';
23
22
  import { initialize, reset, selectLocale, useTypedSelector } from 'state';
24
23
 
@@ -44,7 +43,7 @@ const Index: FunctionComponent<Props> = ({ version }) => {
44
43
  const [showWords, setShowWords] = useState(false);
45
44
  const [isInitialized, setIsInitialized] = useState(false);
46
45
  const [indexRef, { height: indexHeight, width: indexWidth }] = useMeasure<HTMLDivElement>();
47
- const [navRef, { height: navHeight }] = useMeasure<HTMLDivElement>();
46
+ const [navRef, { height: navHeight }] = useMeasure<HTMLElement>();
48
47
  const solverHeight = indexHeight - navHeight;
49
48
  const solverWidth = indexWidth;
50
49
  const [isClient, setIsClient] = useState(false);
@@ -60,10 +59,7 @@ const Index: FunctionComponent<Props> = ({ version }) => {
60
59
  useEffectOnce(() => {
61
60
  setIsClient(true);
62
61
  dispatch(initialize());
63
-
64
- globalThis.setTimeout(() => {
65
- setIsInitialized(true);
66
- }, INITIALIZATION_DURATION);
62
+ setIsInitialized(true);
67
63
  });
68
64
 
69
65
  useEffect(() => {
@@ -87,7 +83,7 @@ const Index: FunctionComponent<Props> = ({ version }) => {
87
83
  <SvgFontFix />
88
84
 
89
85
  <div className={classNames(styles.index, { [styles.initialized]: isInitialized })} ref={indexRef}>
90
- <div className={styles.nav} ref={navRef}>
86
+ <nav className={styles.nav} ref={navRef}>
91
87
  <div className={styles.navContent}>
92
88
  <div className={styles.navLogo}>
93
89
  <a className={styles.logoContainer} href="/" title={version}>
@@ -104,14 +100,16 @@ const Index: FunctionComponent<Props> = ({ version }) => {
104
100
  onShowWords={() => setShowWords(true)}
105
101
  />
106
102
  </div>
107
- </div>
108
-
109
- <Solver
110
- className={styles.solver}
111
- height={solverHeight}
112
- width={solverWidth}
113
- onShowResults={() => setShowResults(true)}
114
- />
103
+ </nav>
104
+
105
+ {solverHeight > 0 && solverWidth > 0 && (
106
+ <Solver
107
+ className={styles.solver}
108
+ height={solverHeight}
109
+ width={solverWidth}
110
+ onShowResults={() => setShowResults(true)}
111
+ />
112
+ )}
115
113
  </div>
116
114
 
117
115
  <MenuModal
@@ -11,8 +11,6 @@ export const TRANSITION = 'var(--transition)';
11
11
 
12
12
  export const GITHUB_PROJECT_URL = 'https://github.com/kamilmielnik/scrabble-solver';
13
13
 
14
- export const INITIALIZATION_DURATION = 100;
15
-
16
14
  export const TRANSITION_DURATION = 100;
17
15
  export const TRANSITION_DURATION_LONG = 250;
18
16
 
@@ -71,6 +69,8 @@ export const REMAINING_TILES_TILE_SIZE = 50;
71
69
 
72
70
  export const RESULTS_ITEM_HEIGHT = 40;
73
71
 
72
+ export const SOLVER_COLUMN_WIDTH = 580;
73
+
74
74
  export const TILE_APPEAR_DURATION = 200;
75
75
 
76
76
  export const TILE_APPEAR_KEYFRAMES = [
@@ -1,21 +1,37 @@
1
- import { applyMiddleware, compose, createStore } from 'redux';
1
+ import { configureStore } from '@reduxjs/toolkit';
2
2
  import reduxSaga from 'redux-saga';
3
3
 
4
- import rootReducer from './rootReducer';
5
4
  import { rootSaga } from './sagas';
6
- import { RootState } from './types';
5
+ import {
6
+ boardSlice,
7
+ cellFilterSlice,
8
+ dictionarySlice,
9
+ rackSlice,
10
+ resultsSlice,
11
+ settingsSlice,
12
+ solveSlice,
13
+ verifySlice,
14
+ } from './slices';
7
15
 
8
16
  const sagaMiddleware = reduxSaga();
9
- const initialState: Partial<RootState> | undefined = undefined;
10
17
 
11
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
12
- // @ts-ignore
13
- // eslint-disable-next-line no-underscore-dangle
14
- const composeEnhancers = globalThis.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
18
+ const createAppStore = () => {
19
+ const store = configureStore({
20
+ reducer: {
21
+ board: boardSlice.reducer,
22
+ cellFilter: cellFilterSlice.reducer,
23
+ dictionary: dictionarySlice.reducer,
24
+ rack: rackSlice.reducer,
25
+ results: resultsSlice.reducer,
26
+ settings: settingsSlice.reducer,
27
+ solve: solveSlice.reducer,
28
+ verify: verifySlice.reducer,
29
+ },
30
+ middleware: [sagaMiddleware],
31
+ });
15
32
 
16
- const createAppStore = (): ReturnType<typeof createStore> => {
17
- const store = createStore(rootReducer, initialState, composeEnhancers(applyMiddleware(sagaMiddleware)));
18
33
  sagaMiddleware.run(rootSaga);
34
+
19
35
  return store;
20
36
  };
21
37
 
@@ -116,6 +116,7 @@ function* onReset(): AnyGenerator {
116
116
  yield put(dictionarySlice.actions.reset());
117
117
  yield put(rackSlice.actions.reset());
118
118
  yield put(resultsSlice.actions.reset());
119
+ yield put(solveSlice.actions.reset());
119
120
  yield put(verifySlice.actions.submit());
120
121
  }
121
122
 
@@ -1,17 +1,29 @@
1
+ /* eslint-disable max-lines */
2
+
1
3
  import { createSelector } from '@reduxjs/toolkit';
2
4
  import { getLocaleConfig } from '@scrabble-solver/configs';
3
5
  import { BLANK } from '@scrabble-solver/constants';
4
- import { Cell, Config, isError, Result, Tile } from '@scrabble-solver/types';
6
+ import { Cell, Config, isError, Tile } from '@scrabble-solver/types';
5
7
 
6
8
  import i18n, { LOCALE_FEATURES } from 'i18n';
7
- import { findCell, getRemainingTiles, getRemainingTilesGroups, sortResults, unorderedArraysEqual } from 'lib';
8
- import { Translations } from 'types';
9
+ import {
10
+ createRegExp,
11
+ findCell,
12
+ getRemainingTiles,
13
+ getRemainingTilesGroups,
14
+ groupResults,
15
+ sortResults,
16
+ unorderedArraysEqual,
17
+ } from 'lib';
18
+ import { Point, Translations } from 'types';
9
19
 
10
20
  import { RootState } from './types';
11
21
 
12
22
  const selectCell = (_: unknown, cell: Cell): Cell => cell;
13
23
 
14
- const selectPoint = (_: unknown, point: { x: number; y: number }): { x: number; y: number } => point;
24
+ const selectPoint = (_: unknown, point: Point): Point => point;
25
+
26
+ const selectResultIndex = (_: unknown, index: number): number => index;
15
27
 
16
28
  const selectCharacter = (_: unknown, character: string | null): string | null => character;
17
29
 
@@ -71,54 +83,42 @@ export const selectCellIsValid = createSelector([selectConfig, selectCell], (con
71
83
  return config.tiles.some((tile) => tile.character === cell.tile.character);
72
84
  });
73
85
 
74
- export const selectResults = createSelector([selectResultsRoot], (results) => results.results);
86
+ export const selectResultsRaw = createSelector([selectResultsRoot], (results) => results.results);
75
87
 
76
88
  export const selectResultsQuery = createSelector([selectResultsRoot], (results) => results.query);
77
89
 
78
- export const selectResultsSortColumn = createSelector([selectResultsRoot], (results) => results.sort.column);
90
+ export const selectResultsSort = createSelector([selectResultsRoot], (results) => results.sort);
79
91
 
80
- export const selectResultsSortDirection = createSelector([selectResultsRoot], (results) => results.sort.direction);
92
+ export const selectSortedResults = createSelector([selectResultsRaw, selectResultsSort, selectLocale], sortResults);
81
93
 
82
- export const selectSortedResults = createSelector(
83
- [selectResults, selectResultsSortColumn, selectResultsSortDirection, selectLocale],
84
- sortResults,
94
+ export const selectGroupedResults = createSelector(
95
+ [selectSortedResults, selectResultsQuery, selectCellFilter],
96
+ groupResults,
85
97
  );
86
98
 
87
- const filterResultsByQuery = (results: Result[], query: string): Result[] => {
88
- if (query.trim().length === 0) {
89
- return results;
90
- }
91
-
92
- let regExp: RegExp | undefined;
93
-
94
- try {
95
- regExp = new RegExp(query, 'gi');
96
- } catch {
97
- return results;
98
- }
99
-
100
- return results.filter((result) => {
101
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
102
- return regExp!.test(result.word);
103
- });
104
- };
99
+ export const selectResults = createSelector([selectGroupedResults], (groupedResults) => {
100
+ return groupedResults ? [...groupedResults.matching, ...groupedResults.other] : groupedResults;
101
+ });
105
102
 
106
- export const selectSortedFilteredResults = createSelector(
107
- [selectSortedResults, selectResultsQuery, selectCellFilter],
108
- (results, query, cellFilter) => {
103
+ export const selectIsResultMatching = createSelector(
104
+ [selectResults, selectResultsQuery, selectCellFilter, selectResultIndex],
105
+ (results, query, cellFilter, index) => {
109
106
  if (!results) {
110
- return results;
107
+ return false;
111
108
  }
112
109
 
113
- const filteredByQuery = filterResultsByQuery(results, query);
110
+ const result = results[index];
111
+ const regExp = createRegExp(query);
114
112
 
115
- if (!cellFilter) {
116
- return filteredByQuery;
113
+ if (!regExp.test(result.word)) {
114
+ return false;
117
115
  }
118
116
 
119
- return filteredByQuery.filter((result) => {
117
+ if (cellFilter) {
120
118
  return cellFilter.every(({ x, y }) => result.cells.some((cell) => cell.x === x && cell.y === y));
121
- });
119
+ }
120
+
121
+ return true;
122
122
  },
123
123
  );
124
124
 
@@ -3,9 +3,11 @@ import { Board } from '@scrabble-solver/types';
3
3
 
4
4
  import settingsInitialState from './settingsInitialState';
5
5
 
6
+ export type BoardState = Board;
7
+
6
8
  const { configId, locale } = settingsInitialState;
7
9
  const { boardHeight, boardWidth } = getLocaleConfig(configId, locale);
8
- const boardInitialState: Board = Board.create(boardWidth, boardHeight);
10
+ const boardInitialState: BoardState = Board.create(boardWidth, boardHeight);
9
11
 
10
12
  // const createOxyphenbutazone = () => {
11
13
  // // Tiles: oypbaze
@@ -1,7 +1,7 @@
1
- import { Cell } from '@scrabble-solver/types';
1
+ import { Point } from 'types';
2
2
 
3
- export type Point = Pick<Cell, 'x' | 'y'>;
3
+ export type CellFilterState = Point[];
4
4
 
5
- const cellFilterInitialState: Point[] = [];
5
+ const cellFilterInitialState: CellFilterState = [];
6
6
 
7
7
  export default cellFilterInitialState;
@@ -1,6 +1,8 @@
1
1
  import { createSlice, PayloadAction } from '@reduxjs/toolkit';
2
2
 
3
- import cellFilterInitialState, { Point } from './cellFilterInitialState';
3
+ import { Point } from 'types';
4
+
5
+ import cellFilterInitialState from './cellFilterInitialState';
4
6
 
5
7
  const cellFilterSlice = createSlice({
6
8
  initialState: cellFilterInitialState,