@scrabble-solver/scrabble-solver 2.8.8 → 2.8.9

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 (83) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +10 -10
  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/server-production/0.pack +0 -0
  9. package/.next/cache/webpack/server-production/index.pack +0 -0
  10. package/.next/next-server.js.nft.json +1 -1
  11. package/.next/prerender-manifest.json +1 -1
  12. package/.next/routes-manifest.json +1 -1
  13. package/.next/server/chunks/413.js +179 -44
  14. package/.next/server/chunks/44.js +802 -0
  15. package/.next/server/chunks/515.js +359 -95
  16. package/.next/server/chunks/911.js +53 -23
  17. package/.next/server/middleware-build-manifest.js +1 -1
  18. package/.next/server/pages/404.html +2 -2
  19. package/.next/server/pages/404.js.nft.json +1 -1
  20. package/.next/server/pages/500.html +2 -2
  21. package/.next/server/pages/_app.js.nft.json +1 -1
  22. package/.next/server/pages/_error.js.nft.json +1 -1
  23. package/.next/server/pages/api/solve.js +193 -927
  24. package/.next/server/pages/api/solve.js.nft.json +1 -1
  25. package/.next/server/pages/api/verify.js +217 -0
  26. package/.next/server/pages/api/verify.js.nft.json +1 -0
  27. package/.next/server/pages/index.html +3 -3
  28. package/.next/server/pages/index.js +7 -1
  29. package/.next/server/pages/index.js.nft.json +1 -1
  30. package/.next/server/pages/index.json +1 -1
  31. package/.next/server/pages-manifest.json +1 -0
  32. package/.next/static/chunks/317-a33dd38e9b9a17ed.js +1 -0
  33. package/.next/static/chunks/pages/{404-8eb3ba4f0ba17e08.js → 404-90c624da3c83fd17.js} +1 -1
  34. package/.next/static/chunks/pages/_app-f8f360878e1c2aff.js +1 -0
  35. package/.next/static/chunks/pages/index-ecea697d3e5d8a6f.js +1 -0
  36. package/.next/static/css/64dc2ce1811912f1.css +1 -0
  37. package/.next/static/css/{cdbc9e0afcff5473.css → ad2a08918868cad8.css} +1 -1
  38. package/.next/static/yCxjzzYpw5JjJE53PO_s6/_buildManifest.js +1 -0
  39. package/.next/static/{z3J3qmq1nazbDv_ENIkCo → yCxjzzYpw5JjJE53PO_s6}/_ssgManifest.js +0 -0
  40. package/.next/trace +42 -41
  41. package/package.json +9 -9
  42. package/src/components/Badge/Badge.module.scss +13 -0
  43. package/src/components/Badge/Badge.tsx +15 -0
  44. package/src/components/Badge/index.ts +1 -0
  45. package/src/components/NavButtons/NavButtons.tsx +33 -14
  46. package/src/components/RemainingTiles/RemainingTiles.module.scss +8 -7
  47. package/src/components/RemainingTiles/RemainingTiles.tsx +13 -4
  48. package/src/components/Sidebar/Sidebar.tsx +2 -2
  49. package/src/components/Sidebar/components/Section/Section.module.scss +0 -1
  50. package/src/components/Sidebar/components/Section/Section.tsx +1 -1
  51. package/src/components/SquareButton/SquareButton.module.scss +5 -0
  52. package/src/components/Words/Words.module.scss +35 -0
  53. package/src/components/Words/Words.tsx +57 -0
  54. package/src/components/Words/index.ts +1 -0
  55. package/src/components/index.ts +2 -0
  56. package/src/i18n/de.json +4 -1
  57. package/src/i18n/en.json +4 -1
  58. package/src/i18n/es.json +4 -1
  59. package/src/i18n/fr.json +4 -1
  60. package/src/i18n/pl.json +4 -1
  61. package/src/icons/BookHalf.svg +4 -0
  62. package/src/icons/Check.svg +4 -0
  63. package/src/icons/Cross.svg +2 -2
  64. package/src/icons/CrossFill.svg +4 -0
  65. package/src/icons/index.ts +3 -0
  66. package/src/pages/api/solve.ts +2 -3
  67. package/src/pages/api/verify.ts +71 -0
  68. package/src/pages/index.tsx +5 -0
  69. package/src/sdk/index.ts +1 -0
  70. package/src/sdk/verify.ts +24 -0
  71. package/src/state/rootReducer.ts +2 -0
  72. package/src/state/sagas.ts +33 -6
  73. package/src/state/selectors.ts +8 -0
  74. package/src/state/slices/index.ts +2 -0
  75. package/src/state/slices/verifyInitialState.ts +12 -0
  76. package/src/state/slices/verifySlice.ts +31 -0
  77. package/src/styles/variables.scss +2 -1
  78. package/src/types/index.ts +4 -1
  79. package/.next/static/chunks/615-d258f6c528c18622.js +0 -1
  80. package/.next/static/chunks/pages/_app-4a663fd3d5ca4524.js +0 -1
  81. package/.next/static/chunks/pages/index-1a9826d740cc8830.js +0 -1
  82. package/.next/static/css/180c6c26317ac90f.css +0 -1
  83. package/.next/static/z3J3qmq1nazbDv_ENIkCo/_buildManifest.js +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scrabble-solver/scrabble-solver",
3
- "version": "2.8.8",
3
+ "version": "2.8.9",
4
4
  "description": "Scrabble Solver 2 - App",
5
5
  "engines": {
6
6
  "node": ">=16"
@@ -30,13 +30,13 @@
30
30
  "dependencies": {
31
31
  "@popperjs/core": "^2.11.6",
32
32
  "@reduxjs/toolkit": "^1.8.5",
33
- "@scrabble-solver/configs": "^2.8.8",
34
- "@scrabble-solver/constants": "^2.8.8",
35
- "@scrabble-solver/dictionaries": "^2.8.8",
36
- "@scrabble-solver/logger": "^2.8.8",
37
- "@scrabble-solver/solver": "^2.8.8",
38
- "@scrabble-solver/types": "^2.8.8",
39
- "@scrabble-solver/word-definitions": "^2.8.8",
33
+ "@scrabble-solver/configs": "^2.8.9",
34
+ "@scrabble-solver/constants": "^2.8.9",
35
+ "@scrabble-solver/dictionaries": "^2.8.9",
36
+ "@scrabble-solver/logger": "^2.8.9",
37
+ "@scrabble-solver/solver": "^2.8.9",
38
+ "@scrabble-solver/types": "^2.8.9",
39
+ "@scrabble-solver/word-definitions": "^2.8.9",
40
40
  "classnames": "^2.3.2",
41
41
  "next": "^12.3.1",
42
42
  "normalize.css": "^8.0.1",
@@ -68,5 +68,5 @@
68
68
  "env-cmd": "^10.1.0",
69
69
  "sass": "^1.55.0"
70
70
  },
71
- "gitHead": "e6ced6e98204e49ba69750242643bda52a453635"
71
+ "gitHead": "8557cc2c5214e6689a5c59373b228b28f5dd8ed4"
72
72
  }
@@ -0,0 +1,13 @@
1
+ .badge {
2
+ display: inline-flex;
3
+ align-items: center;
4
+ justify-content: center;
5
+ padding: var(--spacing--xs) var(--spacing--s);
6
+ border-radius: var(--border--radius);
7
+ background-color: var(--color--violet--light);
8
+ box-shadow: va(--box-shadow);
9
+ line-height: var(--line-height);
10
+ font-size: var(--font--size--s);
11
+ font-weight: bold;
12
+ color: white;
13
+ }
@@ -0,0 +1,15 @@
1
+ import classNames from 'classnames';
2
+ import { FunctionComponent, ReactNode } from 'react';
3
+
4
+ import styles from './Badge.module.scss';
5
+
6
+ interface Props {
7
+ children?: ReactNode;
8
+ className?: string;
9
+ }
10
+
11
+ const Badge: FunctionComponent<Props> = ({ children, className }) => (
12
+ <div className={classNames(styles.badge, className)}>{children}</div>
13
+ );
14
+
15
+ export default Badge;
@@ -0,0 +1 @@
1
+ export { default } from './Badge';
@@ -1,9 +1,9 @@
1
1
  import classNames from 'classnames';
2
2
  import { FunctionComponent } from 'react';
3
3
 
4
- import { Cog, Eraser, Github, Keyboard, Sack } from 'icons';
4
+ import { BookHalf, Cog, Eraser, Github, Keyboard, Sack } from 'icons';
5
5
  import { GITHUB_PROJECT_URL } from 'parameters';
6
- import { selectHasOverusedTiles, useTranslate, useTypedSelector } from 'state';
6
+ import { selectHasInvalidWords, selectHasOverusedTiles, useTranslate, useTypedSelector } from 'state';
7
7
 
8
8
  import SquareButton from '../SquareButton';
9
9
 
@@ -14,27 +14,26 @@ interface Props {
14
14
  onShowKeyMap: () => void;
15
15
  onShowRemainingTiles: () => void;
16
16
  onShowSettings: () => void;
17
+ onShowWords: () => void;
17
18
  }
18
19
 
19
- const NavButtons: FunctionComponent<Props> = ({ onClear, onShowKeyMap, onShowRemainingTiles, onShowSettings }) => {
20
+ const NavButtons: FunctionComponent<Props> = ({
21
+ onClear,
22
+ onShowKeyMap,
23
+ onShowRemainingTiles,
24
+ onShowSettings,
25
+ onShowWords,
26
+ }) => {
20
27
  const translate = useTranslate();
21
28
  const hasOverusedTiles = useTypedSelector(selectHasOverusedTiles);
29
+ const hasInvalidWords = useTypedSelector(selectHasInvalidWords);
22
30
 
23
31
  return (
24
32
  <div className={styles.navButtons}>
25
- <SquareButton.Link
26
- className={styles.button}
27
- href={GITHUB_PROJECT_URL}
28
- Icon={Github}
29
- rel="noopener noreferrer"
30
- target="_blank"
31
- tooltip={translate('github')}
32
- />
33
+ <SquareButton className={styles.button} Icon={Eraser} tooltip={translate('common.clear')} onClick={onClear} />
33
34
 
34
35
  <div className={styles.separator} />
35
36
 
36
- <SquareButton className={styles.button} Icon={Keyboard} tooltip={translate('keyMap')} onClick={onShowKeyMap} />
37
-
38
37
  <SquareButton
39
38
  className={classNames(styles.button, {
40
39
  [styles.error]: hasOverusedTiles,
@@ -44,9 +43,29 @@ const NavButtons: FunctionComponent<Props> = ({ onClear, onShowKeyMap, onShowRem
44
43
  onClick={onShowRemainingTiles}
45
44
  />
46
45
 
46
+ <SquareButton
47
+ className={classNames(styles.button, {
48
+ [styles.error]: hasInvalidWords,
49
+ })}
50
+ Icon={BookHalf}
51
+ tooltip={translate('words')}
52
+ onClick={onShowWords}
53
+ />
54
+
47
55
  <div className={styles.separator} />
48
56
 
49
- <SquareButton className={styles.button} Icon={Eraser} tooltip={translate('common.clear')} onClick={onClear} />
57
+ <SquareButton.Link
58
+ className={styles.button}
59
+ href={GITHUB_PROJECT_URL}
60
+ Icon={Github}
61
+ rel="noopener noreferrer"
62
+ target="_blank"
63
+ tooltip={translate('github')}
64
+ />
65
+
66
+ <div className={styles.separator} />
67
+
68
+ <SquareButton className={styles.button} Icon={Keyboard} tooltip={translate('keyMap')} onClick={onShowKeyMap} />
50
69
 
51
70
  <SquareButton className={styles.button} Icon={Cog} tooltip={translate('settings')} onClick={onShowSettings} />
52
71
  </div>
@@ -1,11 +1,8 @@
1
- .group {
2
- margin-bottom: var(--spacing--l);
3
- }
4
-
5
1
  .title {
6
- margin-bottom: var(--spacing--m);
7
- font-size: var(--font--size--h2);
8
- font-weight: 300;
2
+ display: flex;
3
+ align-items: center;
4
+ justify-content: space-between;
5
+ flex-wrap: wrap;
9
6
  }
10
7
 
11
8
  .content {
@@ -13,3 +10,7 @@
13
10
  grid-template-columns: repeat(5, 1fr);
14
11
  gap: var(--spacing--m);
15
12
  }
13
+
14
+ .badge {
15
+ margin-left: var(--spacing--m);
16
+ }
@@ -2,6 +2,7 @@ import { FunctionComponent } from 'react';
2
2
 
3
3
  import { selectRemainingTilesGroups, useTranslate, useTypedSelector } from 'state';
4
4
 
5
+ import Badge from '../Badge';
5
6
  import Sidebar from '../Sidebar';
6
7
 
7
8
  import Character from './Character';
@@ -20,15 +21,23 @@ const RemainingTiles: FunctionComponent<Props> = ({ className, isOpen, onClose }
20
21
  return (
21
22
  <Sidebar className={className} isOpen={isOpen} title={translate('remaining-tiles')} onClose={onClose}>
22
23
  {groups.map(({ remainingCount, tiles, translationKey, totalCount }) => (
23
- <div className={styles.group} key={translationKey}>
24
- <h2 className={styles.title}>{`${translate(translationKey)} (${remainingCount} / ${totalCount})`}</h2>
25
-
24
+ <Sidebar.Section
25
+ key={translationKey}
26
+ title={
27
+ <span className={styles.title}>
28
+ <span>{translate(translationKey)}</span>
29
+ <Badge className={styles.badge}>
30
+ {remainingCount} / {totalCount}
31
+ </Badge>
32
+ </span>
33
+ }
34
+ >
26
35
  <div className={styles.content}>
27
36
  {tiles.map((tile) => {
28
37
  return <Character key={tile.character} tile={tile} />;
29
38
  })}
30
39
  </div>
31
- </div>
40
+ </Sidebar.Section>
32
41
  ))}
33
42
  </Sidebar>
34
43
  );
@@ -2,7 +2,7 @@ import classNames from 'classnames';
2
2
  import { FunctionComponent, ReactNode } from 'react';
3
3
  import Modal from 'react-modal';
4
4
 
5
- import { Cross } from 'icons';
5
+ import { CrossFill } from 'icons';
6
6
  import { TRANSITION_DURATION_LONG } from 'parameters';
7
7
  import { useTranslate } from 'state';
8
8
 
@@ -41,7 +41,7 @@ const Sidebar: FunctionComponent<Props> = ({ children, className, isOpen, title,
41
41
 
42
42
  <SquareButton
43
43
  className={styles.closeButton}
44
- Icon={Cross}
44
+ Icon={CrossFill}
45
45
  tooltip={translate('common.close')}
46
46
  onClick={onClose}
47
47
  />
@@ -7,6 +7,5 @@
7
7
  .heading {
8
8
  margin-bottom: var(--spacing--m);
9
9
  font-size: var(--font--size--h2);
10
- font-family: var(--font--family--title);
11
10
  font-weight: 300;
12
11
  }
@@ -6,7 +6,7 @@ import styles from './Section.module.scss';
6
6
  interface Props {
7
7
  children: ReactNode;
8
8
  className?: string;
9
- title: string;
9
+ title: ReactNode;
10
10
  }
11
11
 
12
12
  const Section: FunctionComponent<Props> = ({ children, className, title }) => (
@@ -18,6 +18,11 @@
18
18
  &:active {
19
19
  color: var(--color--foreground);
20
20
  }
21
+
22
+ &[disabled] {
23
+ pointer-events: none;
24
+ opacity: 0.25;
25
+ }
21
26
  }
22
27
 
23
28
  .content {
@@ -0,0 +1,35 @@
1
+ .title {
2
+ display: flex;
3
+ align-items: center;
4
+ justify-content: space-between;
5
+ flex-wrap: wrap;
6
+ }
7
+
8
+ .badge {
9
+ margin-left: var(--spacing--m);
10
+ }
11
+
12
+ .word {
13
+ display: flex;
14
+ align-items: center;
15
+
16
+ & + & {
17
+ margin-top: var(--spacing--s);
18
+ }
19
+ }
20
+
21
+ .invalid {
22
+ color: var(--color--error);
23
+ }
24
+
25
+ .valid {
26
+ color: var(--color--success);
27
+ }
28
+
29
+ .icon {
30
+ $size: 24px;
31
+
32
+ width: $size;
33
+ height: $size;
34
+ margin-right: var(--spacing--s);
35
+ }
@@ -0,0 +1,57 @@
1
+ import classNames from 'classnames';
2
+ import { FunctionComponent } from 'react';
3
+
4
+ import { Check, Cross } from 'icons';
5
+ import { selectVerify, useTranslate, useTypedSelector } from 'state';
6
+
7
+ import Badge from '../Badge';
8
+ import Sidebar from '../Sidebar';
9
+
10
+ import styles from './Words.module.scss';
11
+
12
+ interface Props {
13
+ className?: string;
14
+ isOpen: boolean;
15
+ onClose: () => void;
16
+ }
17
+
18
+ const Words: FunctionComponent<Props> = ({ className, isOpen, onClose }) => {
19
+ const translate = useTranslate();
20
+ const { invalidWords, validWords } = useTypedSelector(selectVerify);
21
+
22
+ return (
23
+ <Sidebar className={className} isOpen={isOpen} title={translate('words')} onClose={onClose}>
24
+ <Sidebar.Section
25
+ title={
26
+ <span className={styles.title}>
27
+ <span>{translate('words.invalid')}</span>
28
+ <Badge className={styles.badge}>{invalidWords.length}</Badge>
29
+ </span>
30
+ }
31
+ >
32
+ {invalidWords.map((word, index) => (
33
+ <div className={styles.word} key={index}>
34
+ <Cross className={classNames(styles.icon, styles.invalid)} /> {word}
35
+ </div>
36
+ ))}
37
+ </Sidebar.Section>
38
+
39
+ <Sidebar.Section
40
+ title={
41
+ <span className={styles.title}>
42
+ <span>{translate('words.valid')}</span>
43
+ <Badge className={styles.badge}>{validWords.length}</Badge>
44
+ </span>
45
+ }
46
+ >
47
+ {validWords.map((word, index) => (
48
+ <div className={styles.word} key={index}>
49
+ <Check className={classNames(styles.icon, styles.valid)} /> {word}
50
+ </div>
51
+ ))}
52
+ </Sidebar.Section>
53
+ </Sidebar>
54
+ );
55
+ };
56
+
57
+ export default Words;
@@ -0,0 +1 @@
1
+ export { default } from './Words';
@@ -1,3 +1,4 @@
1
+ export { default as Badge } from './Badge';
1
2
  export { default as Board } from './Board';
2
3
  export { default as Button } from './Button';
3
4
  export { default as Checkbox } from './Checkbox';
@@ -25,3 +26,4 @@ export { default as SvgFontCss } from './SvgFontCss';
25
26
  export { default as Tile } from './Tile';
26
27
  export { useTooltip } from './Tooltip';
27
28
  export { default as Well } from './Well';
29
+ export { default as Words } from './Words';
package/src/i18n/de.json CHANGED
@@ -49,5 +49,8 @@
49
49
  "settings.autoGroupTiles.right": "Rechte Seite",
50
50
  "settings.autoGroupTiles.null": "Nicht gruppieren",
51
51
  "settings.game": "Spiel",
52
- "settings.language": "Sprache"
52
+ "settings.language": "Sprache",
53
+ "words": "Gebildete Wörter",
54
+ "words.invalid": "Falsch",
55
+ "words.valid": "Korrekt"
53
56
  }
package/src/i18n/en.json CHANGED
@@ -49,5 +49,8 @@
49
49
  "settings.autoGroupTiles.right": "On the right",
50
50
  "settings.autoGroupTiles.null": "Do not group",
51
51
  "settings.game": "Game",
52
- "settings.language": "Language"
52
+ "settings.language": "Language",
53
+ "words": "Created words",
54
+ "words.invalid": "Invalid",
55
+ "words.valid": "Valid"
53
56
  }
package/src/i18n/es.json CHANGED
@@ -49,5 +49,8 @@
49
49
  "settings.autoGroupTiles.right": "A la derecha",
50
50
  "settings.autoGroupTiles.null": "No agrupar",
51
51
  "settings.game": "Juego",
52
- "settings.language": "Idioma"
52
+ "settings.language": "Idioma",
53
+ "words": "Palabras creadas",
54
+ "words.invalid": "Incorrecto",
55
+ "words.valid": "Correcto"
53
56
  }
package/src/i18n/fr.json CHANGED
@@ -49,5 +49,8 @@
49
49
  "settings.autoGroupTiles.right": "Vers la gauche",
50
50
  "settings.autoGroupTiles.null": "Ne pas grouper",
51
51
  "settings.game": "Jeu",
52
- "settings.language": "Langue"
52
+ "settings.language": "Langue",
53
+ "words": "Mots créés",
54
+ "words.invalid": "Incorrect",
55
+ "words.valid": "Corriger"
53
56
  }
package/src/i18n/pl.json CHANGED
@@ -49,5 +49,8 @@
49
49
  "settings.autoGroupTiles.right": "Po prawej",
50
50
  "settings.autoGroupTiles.null": "Nie grupuj",
51
51
  "settings.game": "Gra",
52
- "settings.language": "Język"
52
+ "settings.language": "Język",
53
+ "words": "Utworzone słowa",
54
+ "words.invalid": "Niepoprawne",
55
+ "words.valid": "Poprawne"
53
56
  }
@@ -0,0 +1,4 @@
1
+ <!-- https://icons.getbootstrap.com/icons/book-half/ -->
2
+ <svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
3
+ <path d="M8.5 2.687c.654-.689 1.782-.886 3.112-.752 1.234.124 2.503.523 3.388.893v9.923c-.918-.35-2.107-.692-3.287-.81-1.094-.111-2.278-.039-3.213.492V2.687zM8 1.783C7.015.936 5.587.81 4.287.94c-1.514.153-3.042.672-3.994 1.105A.5.5 0 0 0 0 2.5v11a.5.5 0 0 0 .707.455c.882-.4 2.303-.881 3.68-1.02 1.409-.142 2.59.087 3.223.877a.5.5 0 0 0 .78 0c.633-.79 1.814-1.019 3.222-.877 1.378.139 2.8.62 3.681 1.02A.5.5 0 0 0 16 13.5v-11a.5.5 0 0 0-.293-.455c-.952-.433-2.48-.952-3.994-1.105C10.413.809 8.985.936 8 1.783z" fill="currentColor" />
4
+ </svg>
@@ -0,0 +1,4 @@
1
+ <!-- https://icons.getbootstrap.com/icons/check/ -->
2
+ <svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
3
+ <path d="M10.97 4.97a.75.75 0 0 1 1.07 1.05l-3.99 4.99a.75.75 0 0 1-1.08.02L4.324 8.384a.75.75 0 1 1 1.06-1.06l2.094 2.093 3.473-4.425a.267.267 0 0 1 .02-.022z" fill="currentColor" />
4
+ </svg>
@@ -1,4 +1,4 @@
1
- <!-- https://icons.getbootstrap.com/icons/x-square-fill/ -->
1
+ <!-- https://icons.getbootstrap.com/icons/x/ -->
2
2
  <svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
3
- <path d="M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm3.354 4.646L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 1 1 .708-.708z" fill="currentColor" />
3
+ <path d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z" fill="currentColor" />
4
4
  </svg>
@@ -0,0 +1,4 @@
1
+ <!-- https://icons.getbootstrap.com/icons/x-square-fill/ -->
2
+ <svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
3
+ <path d="M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm3.354 4.646L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 1 1 .708-.708z" fill="currentColor" />
4
+ </svg>
@@ -2,10 +2,13 @@ export { default as ArrowDown } from './ArrowDown.svg';
2
2
  export { default as ArrowLeft } from './ArrowLeft.svg';
3
3
  export { default as ArrowRight } from './ArrowRight.svg';
4
4
  export { default as ArrowUp } from './ArrowUp.svg';
5
+ export { default as BookHalf } from './BookHalf.svg';
6
+ export { default as Check } from './Check.svg';
5
7
  export { default as CheckboxChecked } from './CheckboxChecked.svg';
6
8
  export { default as CheckboxEmpty } from './CheckboxEmpty.svg';
7
9
  export { default as Cog } from './Cog.svg';
8
10
  export { default as Cross } from './Cross.svg';
11
+ export { default as CrossFill } from './CrossFill.svg';
9
12
  export { default as DashCircleFill } from './DashCircleFill.svg';
10
13
  export { default as Eraser } from './Eraser.svg';
11
14
  export { default as Flag } from './Flag.svg';
@@ -2,7 +2,7 @@ import { getLocaleConfig, isConfigId } from '@scrabble-solver/configs';
2
2
  import { BLANK } from '@scrabble-solver/constants';
3
3
  import { dictionaries } from '@scrabble-solver/dictionaries';
4
4
  import logger from '@scrabble-solver/logger';
5
- import Solver from '@scrabble-solver/solver';
5
+ import { solve as solveScrabble } from '@scrabble-solver/solver';
6
6
  import { Board, Config, isBoardJson, isLocale, Locale, Tile } from '@scrabble-solver/types';
7
7
  import { NextApiRequest, NextApiResponse } from 'next';
8
8
 
@@ -36,8 +36,7 @@ const solve = async (request: NextApiRequest, response: NextApiResponse): Promis
36
36
 
37
37
  const trie = await dictionaries.get(locale);
38
38
  const tiles = characters.map((character) => new Tile({ character, isBlank: character === BLANK }));
39
- const solver = new Solver(config, trie);
40
- const results = solver.solve(board, tiles);
39
+ const results = solveScrabble(trie, config, board, tiles);
41
40
  response.status(200).send(results.map((result) => result.toJson()));
42
41
  } catch (error) {
43
42
  const message = error instanceof Error ? error.message : 'Unknown error';
@@ -0,0 +1,71 @@
1
+ import { getLocaleConfig, isConfigId } from '@scrabble-solver/configs';
2
+ import { dictionaries } from '@scrabble-solver/dictionaries';
3
+ import logger from '@scrabble-solver/logger';
4
+ import { Board, Config, isBoardJson, isLocale, Locale } from '@scrabble-solver/types';
5
+ import { NextApiRequest, NextApiResponse } from 'next';
6
+
7
+ import { getServerLoggingData, isBoardValid } from 'api';
8
+
9
+ interface RequestData {
10
+ board: Board;
11
+ config: Config;
12
+ locale: Locale;
13
+ }
14
+
15
+ const verify = async (request: NextApiRequest, response: NextApiResponse): Promise<void> => {
16
+ const meta = getServerLoggingData(request);
17
+
18
+ try {
19
+ const { board, locale } = parseRequest(request);
20
+
21
+ logger.info('verify - request', {
22
+ meta,
23
+ payload: {
24
+ board: board.toString(),
25
+ boardBlanksCount: board.getBlanksCount(),
26
+ boardTilesCount: board.getTilesCount(),
27
+ configId: request.body.configId,
28
+ locale,
29
+ },
30
+ });
31
+
32
+ const trie = await dictionaries.get(locale);
33
+ const words = board.getWords().sort((a, b) => a.localeCompare(b));
34
+ const invalidWords = words.filter((word) => !trie.has(word));
35
+ const validWords = words.filter((word) => trie.has(word));
36
+ response.status(200).send({ invalidWords, validWords });
37
+ } catch (error) {
38
+ const message = error instanceof Error ? error.message : 'Unknown error';
39
+ logger.error('verify - error', { error, meta });
40
+ response.status(500).send({ error: 'Server error', message });
41
+ throw error;
42
+ }
43
+ };
44
+
45
+ const parseRequest = (request: NextApiRequest): RequestData => {
46
+ const { board: boardJson, configId, locale } = request.body;
47
+
48
+ if (!isLocale(locale)) {
49
+ throw new Error('Invalid "locale" parameter');
50
+ }
51
+
52
+ if (!isConfigId(configId)) {
53
+ throw new Error('Invalid "configId" parameter');
54
+ }
55
+
56
+ const config = getLocaleConfig(configId, locale);
57
+
58
+ if (!isBoardJson(boardJson) || !isBoardValid(boardJson, config)) {
59
+ throw new Error('Invalid "board" parameter');
60
+ }
61
+
62
+ const board = Board.fromJson(boardJson);
63
+
64
+ return {
65
+ board,
66
+ config,
67
+ locale,
68
+ };
69
+ };
70
+
71
+ export default verify;
@@ -19,6 +19,7 @@ import {
19
19
  Settings,
20
20
  Splash,
21
21
  Well,
22
+ Words,
22
23
  } from 'components';
23
24
  import { useIsTablet, useLocalStorage } from 'hooks';
24
25
  import { getCellSize } from 'lib';
@@ -39,6 +40,7 @@ const Index: FunctionComponent<Props> = ({ version }) => {
39
40
  const [showKeyMap, setShowKeyMap] = useState(false);
40
41
  const [showRemainingTiles, setShowRemainingTiles] = useState(false);
41
42
  const [showSettings, setShowSettings] = useState(false);
43
+ const [showWords, setShowWords] = useState(false);
42
44
  const [boardRef, { height: boardHeight }] = useMeasure<HTMLDivElement>();
43
45
  const [contentRef, { height: contentHeight, width: contentWidth }] = useMeasure<HTMLDivElement>();
44
46
  const [resultsContainerRef, { height: resultsContainerHeight, width: resultsContainerWidth }] =
@@ -85,6 +87,7 @@ const Index: FunctionComponent<Props> = ({ version }) => {
85
87
  onShowKeyMap={() => setShowKeyMap(true)}
86
88
  onShowRemainingTiles={() => setShowRemainingTiles(true)}
87
89
  onShowSettings={() => setShowSettings(true)}
90
+ onShowWords={() => setShowWords(true)}
88
91
  />
89
92
  </div>
90
93
 
@@ -122,6 +125,8 @@ const Index: FunctionComponent<Props> = ({ version }) => {
122
125
 
123
126
  <KeyMap isOpen={showKeyMap} onClose={() => setShowKeyMap(false)} />
124
127
 
128
+ <Words isOpen={showWords} onClose={() => setShowWords(false)} />
129
+
125
130
  <RemainingTiles isOpen={showRemainingTiles} onClose={() => setShowRemainingTiles(false)} />
126
131
 
127
132
  <Splash forceShow={!isInitialized} onAnimationEnd={handleSplashAnimationEnd} />
package/src/sdk/index.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export { default as findWordDefinitions } from './findWordDefinitions';
2
2
  export { default as solve } from './solve';
3
+ export { default as verify } from './verify';
3
4
  export { default as visit } from './visit';
@@ -0,0 +1,24 @@
1
+ import { BoardJson, Locale } from '@scrabble-solver/types';
2
+
3
+ interface Payload {
4
+ board: BoardJson;
5
+ configId: string;
6
+ locale: Locale;
7
+ }
8
+
9
+ interface Response {
10
+ invalidWords: string[];
11
+ validWords: string[];
12
+ }
13
+
14
+ const verify = ({ board, configId, locale }: Payload): Promise<Response> => {
15
+ return fetch('/api/verify', {
16
+ method: 'POST',
17
+ headers: {
18
+ 'Content-Type': 'application/json',
19
+ },
20
+ body: JSON.stringify({ board, configId, locale }),
21
+ }).then((response) => response.json());
22
+ };
23
+
24
+ export default verify;
@@ -8,6 +8,7 @@ import {
8
8
  resultsSlice,
9
9
  settingsSlice,
10
10
  solveSlice,
11
+ verifySlice,
11
12
  } from './slices';
12
13
 
13
14
  const rootReducer = combineReducers({
@@ -18,6 +19,7 @@ const rootReducer = combineReducers({
18
19
  results: resultsSlice.reducer,
19
20
  settings: settingsSlice.reducer,
20
21
  solve: solveSlice.reducer,
22
+ verify: verifySlice.reducer,
21
23
  });
22
24
 
23
25
  export default rootReducer;