@scrabble-solver/scrabble-solver 2.11.1 → 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.
- package/.next/BUILD_ID +1 -1
- package/.next/build-manifest.json +6 -6
- package/.next/cache/.tsbuildinfo +1 -1
- package/.next/cache/eslint/.cache_8dgz12 +1 -1
- package/.next/cache/next-server.js.nft.json +1 -1
- package/.next/cache/webpack/client-production/0.pack +0 -0
- package/.next/cache/webpack/client-production/index.pack +0 -0
- package/.next/cache/webpack/edge-server-production/0.pack +0 -0
- package/.next/cache/webpack/edge-server-production/index.pack +0 -0
- package/.next/cache/webpack/server-production/0.pack +0 -0
- package/.next/cache/webpack/server-production/index.pack +0 -0
- package/.next/next-server.js.nft.json +1 -1
- package/.next/prerender-manifest.json +1 -1
- package/.next/routes-manifest.json +1 -1
- package/.next/server/chunks/277.js +203 -173
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/pages/404.html +2 -2
- package/.next/server/pages/404.js.nft.json +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/pages/_app.js.nft.json +1 -1
- package/.next/server/pages/_document.js.nft.json +1 -1
- package/.next/server/pages/api/dictionary/[locale]/[word].js +0 -1
- package/.next/server/pages/api/dictionary/[locale].js +0 -1
- package/.next/server/pages/api/solve.js +29 -8
- package/.next/server/pages/api/verify.js +0 -1
- package/.next/server/pages/api/visit.js +0 -1
- package/.next/server/pages/index.html +1 -1
- package/.next/server/pages/index.js +46 -39
- package/.next/server/pages/index.js.nft.json +1 -1
- package/.next/server/pages/index.json +1 -1
- package/.next/static/{esK8DG-6aS5V7QFRtR3YE → Mdvi3FY0PqkILKLbPlVBU}/_buildManifest.js +1 -1
- package/.next/static/chunks/pages/_app-495e6f4ccc278bb2.js +28 -0
- package/.next/static/chunks/pages/index-5ecc51900ca29685.js +1 -0
- package/.next/static/css/17b0a2db8742105f.css +1 -0
- package/.next/static/css/e1ffeb2558330c55.css +2 -0
- package/.next/trace +54 -53
- package/package.json +9 -9
- package/src/components/Board/components/Cell/Cell.module.scss +10 -2
- package/src/components/Board/hooks/useGrid.ts +1 -2
- package/src/components/Board/lib/getPositionInGrid.ts +1 -1
- package/src/components/LogoSplashScreen/LogoSplashScreen.module.scss +4 -1
- package/src/components/Modal/Modal.module.scss +12 -0
- package/src/components/Modal/Modal.tsx +4 -1
- package/src/components/Results/HeaderButton.tsx +5 -12
- package/src/components/Results/Result.tsx +5 -3
- package/src/components/Results/Results.module.scss +13 -1
- package/src/components/Results/Results.tsx +29 -43
- package/src/components/Results/types.ts +1 -1
- package/src/components/Solver/Solver.module.scss +4 -0
- package/src/components/Solver/Solver.tsx +9 -12
- package/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.tsx +5 -6
- package/src/components/Tile/Tile.module.scss +40 -71
- package/src/components/Tile/Tile.tsx +4 -3
- package/src/components/Tile/TilePure.tsx +4 -13
- package/src/hooks/useAppLayout.ts +3 -1
- package/src/i18n/de.json +0 -1
- package/src/i18n/en.json +0 -1
- package/src/i18n/es.json +0 -1
- package/src/i18n/fa.json +0 -1
- package/src/i18n/fr.json +0 -1
- package/src/i18n/pl.json +0 -1
- package/src/lib/createRegExp.ts +13 -0
- package/src/lib/groupResults.ts +38 -0
- package/src/lib/guessLocale.ts +22 -0
- package/src/lib/index.ts +4 -1
- package/src/lib/sortResults.ts +6 -10
- package/src/modals/DictionaryModal/DictionaryModal.module.scss +0 -1
- package/src/modals/MenuModal/MenuModal.module.scss +7 -0
- package/src/modals/ResultsModal/ResultsModal.module.scss +2 -3
- package/src/modals/ResultsModal/ResultsModal.tsx +23 -11
- package/src/pages/api/dictionary/[locale]/[word].ts +0 -1
- package/src/pages/api/dictionary/[locale]/index.ts +0 -1
- package/src/pages/api/solve.ts +0 -1
- package/src/pages/api/verify.ts +0 -1
- package/src/pages/api/visit.ts +0 -1
- package/src/pages/index.tsx +13 -15
- package/src/parameters/index.ts +2 -2
- package/src/state/sagas.ts +1 -0
- package/src/state/selectors.ts +37 -37
- package/src/state/slices/boardInitialState.ts +3 -1
- package/src/state/slices/cellFilterInitialState.ts +3 -3
- package/src/state/slices/cellFilterSlice.ts +3 -1
- package/src/state/slices/dictionaryInitialState.ts +2 -2
- package/src/state/slices/rackInitialState.ts +3 -1
- package/src/state/slices/resultsInitialState.ts +11 -4
- package/src/state/slices/settingsInitialState.ts +10 -21
- package/src/state/slices/solveInitialState.ts +2 -2
- package/src/state/slices/solveSlice.ts +2 -0
- package/src/state/slices/verifyInitialState.ts +13 -4
- package/src/styles/mixins.scss +3 -1
- package/src/styles/variables.scss +1 -0
- package/src/types/index.ts +10 -2
- package/.next/static/chunks/pages/_app-270526803bc274eb.js +0 -28
- package/.next/static/chunks/pages/index-c6e7754ccf3532df.js +0 -1
- package/.next/static/css/ad39b36eab07e613.css +0 -1
- package/.next/static/css/e5803e581e4c0451.css +0 -2
- package/src/components/Board/types/index.ts +0 -4
- /package/.next/static/{esK8DG-6aS5V7QFRtR3YE → Mdvi3FY0PqkILKLbPlVBU}/_ssgManifest.js +0 -0
|
@@ -4,8 +4,17 @@
|
|
|
4
4
|
--background-color: transparent;
|
|
5
5
|
|
|
6
6
|
position: relative;
|
|
7
|
-
|
|
7
|
+
display: flex;
|
|
8
|
+
align-items: center;
|
|
9
|
+
justify-content: center;
|
|
10
|
+
padding: 0;
|
|
11
|
+
background-color: var(--background-color);
|
|
8
12
|
border-radius: var(--border--radius);
|
|
13
|
+
font-weight: bold;
|
|
14
|
+
text-transform: uppercase;
|
|
15
|
+
text-align: center;
|
|
16
|
+
transition: var(--transition);
|
|
17
|
+
user-select: none;
|
|
9
18
|
|
|
10
19
|
&.points1 {
|
|
11
20
|
--background-color: var(--color--yellow);
|
|
@@ -24,13 +33,30 @@
|
|
|
24
33
|
--background-color: var(--color--red);
|
|
25
34
|
}
|
|
26
35
|
|
|
36
|
+
&.raised {
|
|
37
|
+
--shadow--size: 2px;
|
|
38
|
+
--shadow--blur: 2px;
|
|
39
|
+
--shadow--spread: -1px;
|
|
40
|
+
--shadow--color: rgba(34, 34, 34, 0.8);
|
|
41
|
+
|
|
42
|
+
box-shadow: inset calc(-1 * var(--shadow--size)) calc(-1 * var(--shadow--size)) var(--shadow--blur)
|
|
43
|
+
var(--shadow--spread) var(--shadow--color);
|
|
44
|
+
|
|
45
|
+
@include media('<xs') {
|
|
46
|
+
--shadow--size: 1px;
|
|
47
|
+
--shadow--spread: 0;
|
|
48
|
+
--shadow--blur: 1px;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
27
52
|
&.blank {
|
|
28
53
|
--background-color: var(--color--white);
|
|
29
54
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
55
|
+
color: var(--color--foreground);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
&.empty {
|
|
59
|
+
color: var(--color--inactive);
|
|
34
60
|
}
|
|
35
61
|
|
|
36
62
|
&.highlighted {
|
|
@@ -38,85 +64,28 @@
|
|
|
38
64
|
|
|
39
65
|
color: var(--color--primary--opposite);
|
|
40
66
|
|
|
41
|
-
.placeholder,
|
|
42
|
-
.character {
|
|
43
|
-
color: inherit;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
67
|
.points {
|
|
47
68
|
color: inherit;
|
|
48
69
|
}
|
|
49
70
|
}
|
|
50
|
-
|
|
51
|
-
&:not(.disabled) {
|
|
52
|
-
.input::selection {
|
|
53
|
-
--background--color: transparent;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
.input,
|
|
59
|
-
.character,
|
|
60
|
-
.placeholder {
|
|
61
|
-
padding: 0;
|
|
62
|
-
font-weight: bold;
|
|
63
|
-
text-transform: uppercase;
|
|
64
|
-
text-align: center;
|
|
65
|
-
caret-color: transparent;
|
|
66
|
-
box-sizing: border-box;
|
|
67
71
|
}
|
|
68
72
|
|
|
69
73
|
.input {
|
|
70
|
-
width: 100%;
|
|
71
|
-
height: 100%;
|
|
72
|
-
background-color: transparent;
|
|
73
|
-
color: transparent;
|
|
74
|
-
border: none;
|
|
75
|
-
font-size: 16px; // prevent iOS from automatically zooming in on focus
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
.character,
|
|
79
|
-
.placeholder {
|
|
80
74
|
position: absolute;
|
|
81
75
|
top: 0;
|
|
82
76
|
right: 0;
|
|
83
77
|
bottom: 0;
|
|
84
78
|
left: 0;
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
background-color:
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
user-select: none;
|
|
93
|
-
|
|
94
|
-
.empty & {
|
|
95
|
-
color: var(--color--inactive);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
.raised & {
|
|
99
|
-
--shadow--size: 2px;
|
|
100
|
-
--shadow--blur: 2px;
|
|
101
|
-
--shadow--spread: -1px;
|
|
102
|
-
--shadow--color: rgba(34, 34, 34, 0.8);
|
|
103
|
-
|
|
104
|
-
box-shadow: inset calc(-1 * var(--shadow--size)) calc(-1 * var(--shadow--size)) var(--shadow--blur)
|
|
105
|
-
var(--shadow--spread) var(--shadow--color);
|
|
106
|
-
|
|
107
|
-
@include media('<xs') {
|
|
108
|
-
--shadow--size: 1px;
|
|
109
|
-
--shadow--spread: 0;
|
|
110
|
-
--shadow--blur: 1px;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
.character {
|
|
116
|
-
opacity: 1;
|
|
79
|
+
width: 100%;
|
|
80
|
+
height: 100%;
|
|
81
|
+
border: none;
|
|
82
|
+
background-color: transparent;
|
|
83
|
+
color: transparent;
|
|
84
|
+
caret-color: transparent;
|
|
85
|
+
font-size: 16px; // prevent iOS from automatically zooming in on focus
|
|
117
86
|
|
|
118
|
-
|
|
119
|
-
|
|
87
|
+
&::selection {
|
|
88
|
+
--background--color: transparent;
|
|
120
89
|
}
|
|
121
90
|
}
|
|
122
91
|
|
|
@@ -60,8 +60,10 @@ const Tile: FunctionComponent<Props> = ({
|
|
|
60
60
|
const locale = useTypedSelector(selectLocale);
|
|
61
61
|
const { animateTile, showTilePoints } = useAppLayout();
|
|
62
62
|
const { pointsFontSize, tileFontSize, tileSize } = getTileSizes(size);
|
|
63
|
-
const style = useMemo(
|
|
64
|
-
|
|
63
|
+
const style = useMemo(
|
|
64
|
+
() => ({ fontSize: tileFontSize, height: tileSize, width: tileSize }),
|
|
65
|
+
[tileSize, tileFontSize],
|
|
66
|
+
);
|
|
65
67
|
const pointsStyle = useMemo(() => ({ fontSize: pointsFontSize }), [pointsFontSize]);
|
|
66
68
|
const ref = useRef<HTMLInputElement>(null);
|
|
67
69
|
const mergedRef = useMergeRefs(inputRef ? [ref, inputRef] : [ref]);
|
|
@@ -98,7 +100,6 @@ const Tile: FunctionComponent<Props> = ({
|
|
|
98
100
|
autoFocus={autoFocus}
|
|
99
101
|
canShowPoints={canShowPoints}
|
|
100
102
|
character={character}
|
|
101
|
-
characterStyle={characterStyle}
|
|
102
103
|
className={className}
|
|
103
104
|
disabled={disabled}
|
|
104
105
|
highlighted={highlighted}
|
|
@@ -18,7 +18,6 @@ interface Props {
|
|
|
18
18
|
autoFocus?: boolean;
|
|
19
19
|
canShowPoints?: boolean;
|
|
20
20
|
character?: string;
|
|
21
|
-
characterStyle?: CSSProperties;
|
|
22
21
|
className?: string;
|
|
23
22
|
disabled?: boolean;
|
|
24
23
|
highlighted?: boolean;
|
|
@@ -42,7 +41,6 @@ const TilePure: FunctionComponent<Props> = ({
|
|
|
42
41
|
autoFocus,
|
|
43
42
|
canShowPoints,
|
|
44
43
|
character,
|
|
45
|
-
characterStyle,
|
|
46
44
|
className,
|
|
47
45
|
disabled,
|
|
48
46
|
highlighted,
|
|
@@ -66,15 +64,18 @@ const TilePure: FunctionComponent<Props> = ({
|
|
|
66
64
|
[styles.disabled]: disabled,
|
|
67
65
|
[styles.empty]: !character,
|
|
68
66
|
[styles.highlighted]: highlighted,
|
|
69
|
-
[styles.
|
|
67
|
+
[styles.placeholder]: !character,
|
|
70
68
|
[styles.points1]: points === 1,
|
|
71
69
|
[styles.points2]: points === 2,
|
|
72
70
|
[styles.points3]: points === 3,
|
|
73
71
|
[styles.points4]: points === 4,
|
|
74
72
|
[styles.points5]: typeof points === 'number' && points >= 5,
|
|
73
|
+
[styles.raised]: raised,
|
|
75
74
|
})}
|
|
76
75
|
style={style}
|
|
77
76
|
>
|
|
77
|
+
{character || placeholder}
|
|
78
|
+
|
|
78
79
|
<input
|
|
79
80
|
aria-label={ariaLabel}
|
|
80
81
|
autoCapitalize="none"
|
|
@@ -92,16 +93,6 @@ const TilePure: FunctionComponent<Props> = ({
|
|
|
92
93
|
onKeyDown={onKeyDown}
|
|
93
94
|
/>
|
|
94
95
|
|
|
95
|
-
{placeholder && (
|
|
96
|
-
<div className={styles.placeholder} style={characterStyle} tabIndex={-1}>
|
|
97
|
-
{placeholder}
|
|
98
|
-
</div>
|
|
99
|
-
)}
|
|
100
|
-
|
|
101
|
-
<div className={styles.character} style={characterStyle} tabIndex={-1}>
|
|
102
|
-
{character}
|
|
103
|
-
</div>
|
|
104
|
-
|
|
105
96
|
{canShowPoints && (
|
|
106
97
|
<span className={styles.points} style={pointsStyle}>
|
|
107
98
|
{pointsFormatted}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { COMPONENTS_SPACING, COMPONENTS_SPACING_SMALL } from 'parameters';
|
|
1
|
+
import { COMPONENTS_SPACING, COMPONENTS_SPACING_SMALL, SOLVER_COLUMN_WIDTH } from 'parameters';
|
|
2
2
|
|
|
3
3
|
import useIsTouchDevice from './useIsTouchDevice';
|
|
4
4
|
import useMediaQuery from './useMediaQuery';
|
|
@@ -14,8 +14,10 @@ const useAppLayout = () => {
|
|
|
14
14
|
|
|
15
15
|
return {
|
|
16
16
|
animateTile: !isLessThanXs,
|
|
17
|
+
columnWidth: showColumn ? SOLVER_COLUMN_WIDTH : 0,
|
|
17
18
|
componentsSpacing: isLessThanXl ? COMPONENTS_SPACING_SMALL : COMPONENTS_SPACING,
|
|
18
19
|
isBoardFullWidth: isLessThanM,
|
|
20
|
+
isModalFullWidth: isLessThanS,
|
|
19
21
|
showColumn,
|
|
20
22
|
showCompactControls: !showColumn,
|
|
21
23
|
showFloatingSolveButton: isTouchDevice,
|
package/src/i18n/de.json
CHANGED
|
@@ -45,7 +45,6 @@
|
|
|
45
45
|
"rack.tile.location": "Ablage: Stein ({{index}})",
|
|
46
46
|
"remaining-tiles": "Restliche Steine",
|
|
47
47
|
"results": "Ergebnisse",
|
|
48
|
-
"results.empty-state.no-filtered-results": "Keine Ergebnisse für diese Anfrage.",
|
|
49
48
|
"results.empty-state.no-results": "Keine Ergebnisse - kein Wort konnte generiert werden.",
|
|
50
49
|
"results.empty-state.outdated": "Ergebnisse sind alt. Klicken zum Aktualisieren.",
|
|
51
50
|
"results.empty-state.uninitialized": "Wörter die aus deinen Buchstaben generiert wurden erscheinen hier.",
|
package/src/i18n/en.json
CHANGED
|
@@ -45,7 +45,6 @@
|
|
|
45
45
|
"rack.tile.location": "Rack: tile ({{index}})",
|
|
46
46
|
"remaining-tiles": "Remaining tiles",
|
|
47
47
|
"results": "Results",
|
|
48
|
-
"results.empty-state.no-filtered-results": "No result matches this query.",
|
|
49
48
|
"results.empty-state.no-results": "No results - unable to generate any words.",
|
|
50
49
|
"results.empty-state.outdated": "Results are outdated. Click below to update.",
|
|
51
50
|
"results.empty-state.uninitialized": "Words generated from your letters will be shown here.",
|
package/src/i18n/es.json
CHANGED
|
@@ -45,7 +45,6 @@
|
|
|
45
45
|
"rack.tile.location": "Estante: espacio ({{index}})",
|
|
46
46
|
"remaining-tiles": "Casillas restantes",
|
|
47
47
|
"results": "Resultados",
|
|
48
|
-
"results.empty-state.no-filtered-results": "Ningún resultado coincide con esta consulta.",
|
|
49
48
|
"results.empty-state.no-results": "No hay resultados; no se pueden generar palabras",
|
|
50
49
|
"results.empty-state.outdated": "Los resultados están desactualizados. Haga clic a continuación para actualizar.",
|
|
51
50
|
"results.empty-state.uninitialized": "Aquí se mostrarán las palabras generadas a partir de sus letras.",
|
package/src/i18n/fa.json
CHANGED
|
@@ -45,7 +45,6 @@
|
|
|
45
45
|
"rack.tile.location": "({{index}}) کاشی: طاقچه",
|
|
46
46
|
"remaining-tiles": "کاشی های باقی مانده",
|
|
47
47
|
"results": "نتایج",
|
|
48
|
-
"results.empty-state.no-filtered-results": "پاسخی یافت نشد.",
|
|
49
48
|
"results.empty-state.no-results": "کلمه قابل استفاده پیدا نشد.",
|
|
50
49
|
"results.empty-state.outdated": "نتایج به روز نیستند، برای بروز رسانی کلیک کنید.",
|
|
51
50
|
"results.empty-state.uninitialized": "کلمات تولید شده از حروف شما اینجا نمایش داده خواهد شد.",
|
package/src/i18n/fr.json
CHANGED
|
@@ -45,7 +45,6 @@
|
|
|
45
45
|
"rack.tile.location": "Chevalet: la case ({{index}})",
|
|
46
46
|
"remaining-tiles": "Cases restantes",
|
|
47
47
|
"results": "Résultats",
|
|
48
|
-
"results.empty-state.no-filtered-results": "Aucun résultat ne correspond à cette requête",
|
|
49
48
|
"results.empty-state.no-results": "Pas de résultats - impossible de générer des mots.",
|
|
50
49
|
"results.empty-state.outdated": "Les résultats sont dépassé. Cliquer ci-dessous pour mettre à jour.",
|
|
51
50
|
"results.empty-state.uninitialized": "Les mots générés à partir de vos lettres seront affichés ici.",
|
package/src/i18n/pl.json
CHANGED
|
@@ -45,7 +45,6 @@
|
|
|
45
45
|
"rack.tile.location": "Stojak: płytka ({{index}})",
|
|
46
46
|
"remaining-tiles": "Pozostałe płytki",
|
|
47
47
|
"results": "Wyniki",
|
|
48
|
-
"results.empty-state.no-filtered-results": "Żaden wynik nie pasuje do tej kwerendy.",
|
|
49
48
|
"results.empty-state.no-results": "Brak wyników - nie można wygenerować żadnego słowa.",
|
|
50
49
|
"results.empty-state.outdated": "Wyniki są nieaktualne. Kliknij poniżej, aby zaktualizować.",
|
|
51
50
|
"results.empty-state.uninitialized": "Tu zostaną wyświetlone słowa wygenerowane z Twoich liter.",
|
|
@@ -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;
|
package/src/lib/index.ts
CHANGED
|
@@ -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,8 +15,10 @@ 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
23
|
export { default as isMac } from './isMac';
|
|
21
24
|
export { default as isRegExp } from './isRegExp';
|
package/src/lib/sortResults.ts
CHANGED
|
@@ -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 =
|
|
31
|
-
|
|
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;
|
|
@@ -9,15 +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
|
|
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
19
|
border-radius: var(--border--radius);
|
|
20
|
-
box-shadow: var(--box-shadow);
|
|
21
20
|
|
|
22
21
|
@media (max-height: 600px) {
|
|
23
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
|
-
|
|
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(
|
|
20
|
+
const results = useTypedSelector(selectResults);
|
|
26
21
|
const resultCandidate = useTypedSelector(selectResultCandidate);
|
|
27
|
-
const index =
|
|
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
|
|
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} />
|
|
@@ -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
|
|
package/src/pages/api/solve.ts
CHANGED
|
@@ -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
|
|
package/src/pages/api/verify.ts
CHANGED
|
@@ -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
|
|
package/src/pages/api/visit.ts
CHANGED
|
@@ -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
|
|
package/src/pages/index.tsx
CHANGED
|
@@ -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<
|
|
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
|
-
<
|
|
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
|
-
</
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
package/src/parameters/index.ts
CHANGED
|
@@ -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 = [
|
package/src/state/sagas.ts
CHANGED
|
@@ -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
|
|