@scrabble-solver/scrabble-solver 2.11.1 → 2.11.3
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/210.js +109 -0
- package/.next/server/chunks/277.js +380 -258
- package/.next/server/chunks/44.js +47 -0
- package/.next/server/chunks/987.js +91 -0
- 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 +1 -73
- 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 +3 -4
- package/.next/server/pages/api/dictionary/[locale]/[word].js.nft.json +1 -1
- package/.next/server/pages/api/dictionary/[locale].js +3 -4
- package/.next/server/pages/api/dictionary/[locale].js.nft.json +1 -1
- package/.next/server/pages/api/solve.js +38 -13
- package/.next/server/pages/api/solve.js.nft.json +1 -1
- package/.next/server/pages/api/verify.js +5 -5
- package/.next/server/pages/api/verify.js.nft.json +1 -1
- package/.next/server/pages/api/visit.js +3 -4
- package/.next/server/pages/api/visit.js.nft.json +1 -1
- package/.next/server/pages/index.html +1 -1
- package/.next/server/pages/index.js +141 -237
- package/.next/server/pages/index.js.nft.json +1 -1
- package/.next/server/pages/index.json +1 -1
- package/.next/static/{esK8DG-6aS5V7QFRtR3YE → USLkKOoHbITebIEHkMGX_}/_buildManifest.js +1 -1
- package/.next/static/chunks/pages/_app-21c83ddb81fc09d0.js +28 -0
- package/.next/static/chunks/pages/index-0858deea02b2a417.js +1 -0
- package/.next/static/css/885da289cec275b3.css +1 -0
- package/.next/static/css/ea1c8134fe9a143e.css +2 -0
- package/.next/trace +53 -53
- package/package.json +9 -9
- package/src/api/index.ts +1 -0
- package/src/api/isCellValid.ts +3 -2
- package/src/api/isCharacterValid.ts +13 -0
- 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/Button/Button.module.scss +14 -1
- package/src/components/LogoSplashScreen/LogoSplashScreen.module.scss +4 -1
- package/src/components/Modal/Modal.module.scss +21 -1
- package/src/components/Modal/Modal.tsx +4 -1
- package/src/components/NotFound/NotFound.module.scss +13 -4
- package/src/components/NotFound/NotFound.tsx +4 -7
- package/src/components/Rack/Rack.tsx +3 -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 +53 -79
- package/src/components/Tile/Tile.tsx +1 -3
- package/src/components/Tile/TilePure.tsx +4 -14
- package/src/hooks/useAppLayout.ts +3 -1
- package/src/i18n/constants.ts +65 -0
- package/src/i18n/de.json +1 -1
- package/src/i18n/en.json +1 -1
- package/src/i18n/es.json +1 -1
- package/src/i18n/fa.json +1 -1
- package/src/i18n/fr.json +1 -1
- package/src/i18n/i18n.module.scss +27 -0
- package/src/i18n/pl.json +1 -1
- package/src/icons/DashCircleFill.svg +1 -0
- package/src/icons/EyeFill.svg +5 -0
- package/src/icons/index.ts +3 -2
- 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 +23 -0
- package/src/modals/MenuModal/MenuModal.tsx +8 -2
- package/src/modals/RemainingTilesModal/RemainingTilesModal.tsx +4 -1
- package/src/modals/RemainingTilesModal/components/Character/Character.module.scss +8 -0
- package/src/modals/ResultsModal/ResultsModal.module.scss +2 -3
- package/src/modals/ResultsModal/ResultsModal.tsx +47 -11
- package/src/modals/SettingsModal/components/LocaleSetting/LocaleSetting.module.scss +3 -44
- package/src/modals/SettingsModal/components/LocaleSetting/LocaleSetting.tsx +4 -2
- 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 +3 -3
- 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 +5 -1
- package/src/styles/variables.scss +13 -0
- package/src/types/index.ts +11 -2
- package/.next/server/chunks/417.js +0 -221
- package/.next/server/chunks/664.js +0 -621
- 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/src/modals/SettingsModal/components/LocaleSetting/options.ts +0 -68
- /package/.next/static/{esK8DG-6aS5V7QFRtR3YE → USLkKOoHbITebIEHkMGX_}/_ssgManifest.js +0 -0
|
@@ -11,9 +11,8 @@ import {
|
|
|
11
11
|
selectAreResultsOutdated,
|
|
12
12
|
selectConfig,
|
|
13
13
|
selectResultCandidate,
|
|
14
|
+
selectResults,
|
|
14
15
|
selectSolveError,
|
|
15
|
-
selectSortedFilteredResults,
|
|
16
|
-
selectSortedResults,
|
|
17
16
|
solveSlice,
|
|
18
17
|
useTranslate,
|
|
19
18
|
useTypedSelector,
|
|
@@ -41,18 +40,16 @@ const Solver: FunctionComponent<Props> = ({ className, height, width, onShowResu
|
|
|
41
40
|
const dispatch = useDispatch();
|
|
42
41
|
const translate = useTranslate();
|
|
43
42
|
const isTouchDevice = useIsTouchDevice();
|
|
44
|
-
const { componentsSpacing, isBoardFullWidth, showColumn, showCompactControls, showFloatingSolveButton } =
|
|
43
|
+
const { columnWidth, componentsSpacing, isBoardFullWidth, showColumn, showCompactControls, showFloatingSolveButton } =
|
|
45
44
|
useAppLayout();
|
|
46
|
-
const [bottomContainerRef, { height: bottomContainerHeight }] = useMeasure<HTMLDivElement>();
|
|
47
|
-
const [columnRef, { width: columnWidth }] = useMeasure<HTMLDivElement>();
|
|
48
|
-
const maxBoardWidth = width - columnWidth - (showColumn ? componentsSpacing : 0) - 2 * componentsSpacing;
|
|
49
|
-
const maxBoardHeight = isBoardFullWidth ? Number.POSITIVE_INFINITY : Math.max(height - bottomContainerHeight, 0);
|
|
50
45
|
const config = useTypedSelector(selectConfig);
|
|
51
46
|
const error = useTypedSelector(selectSolveError);
|
|
52
47
|
const isOutdated = useTypedSelector(selectAreResultsOutdated);
|
|
53
48
|
const resultCandidate = useTypedSelector(selectResultCandidate);
|
|
54
|
-
const
|
|
55
|
-
const
|
|
49
|
+
const results = useTypedSelector(selectResults);
|
|
50
|
+
const [bottomContainerRef, { height: bottomContainerHeight }] = useMeasure<HTMLDivElement>();
|
|
51
|
+
const maxBoardWidth = width - columnWidth - (showColumn ? componentsSpacing : 0) - 2 * componentsSpacing;
|
|
52
|
+
const maxBoardHeight = isBoardFullWidth ? Number.POSITIVE_INFINITY : Math.max(height - bottomContainerHeight, 0);
|
|
56
53
|
const [bestResult] = results || [];
|
|
57
54
|
const cellWidth = (maxBoardWidth - (config.boardWidth + 1) * BORDER_WIDTH) / config.boardWidth;
|
|
58
55
|
const cellHeight = (maxBoardHeight - (config.boardHeight + 1) * BORDER_WIDTH) / config.boardHeight;
|
|
@@ -117,8 +114,8 @@ const Solver: FunctionComponent<Props> = ({ className, height, width, onShowResu
|
|
|
117
114
|
<input className={styles.submitInput} tabIndex={-1} type="submit" />
|
|
118
115
|
</form>
|
|
119
116
|
|
|
120
|
-
<div className={styles.column}
|
|
121
|
-
<Results callbacks={callbacks} />
|
|
117
|
+
<div className={styles.column}>
|
|
118
|
+
<Results callbacks={callbacks} className={styles.results} />
|
|
122
119
|
|
|
123
120
|
<div className={styles.dictionaryContainer}>
|
|
124
121
|
<Dictionary className={styles.dictionary} />
|
|
@@ -145,7 +142,7 @@ const Solver: FunctionComponent<Props> = ({ className, height, width, onShowResu
|
|
|
145
142
|
</Alert>
|
|
146
143
|
)}
|
|
147
144
|
|
|
148
|
-
{
|
|
145
|
+
{results && results.length === 0 && !isOutdated && (
|
|
149
146
|
<Alert className={styles.emptyState} variant="warning">
|
|
150
147
|
{translate('results.empty-state.no-results')}
|
|
151
148
|
</Alert>
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
selectIsLoading,
|
|
11
11
|
selectLocale,
|
|
12
12
|
selectResultCandidate,
|
|
13
|
-
|
|
13
|
+
selectResults,
|
|
14
14
|
useTranslate,
|
|
15
15
|
useTypedSelector,
|
|
16
16
|
} from 'state';
|
|
@@ -31,13 +31,12 @@ const ResultCandidatePicker: FunctionComponent<Props> = ({ className, onResultCl
|
|
|
31
31
|
const locale = useTypedSelector(selectLocale);
|
|
32
32
|
const isLoading = useTypedSelector(selectIsLoading);
|
|
33
33
|
const isOutdated = useTypedSelector(selectAreResultsOutdated);
|
|
34
|
-
const
|
|
35
|
-
const results = sortedResults || [];
|
|
34
|
+
const results = useTypedSelector(selectResults);
|
|
36
35
|
const resultCandidate = useTypedSelector(selectResultCandidate);
|
|
37
|
-
const index = resultCandidate ? results.findIndex((result) => result.id === resultCandidate.id) : -1;
|
|
36
|
+
const index = resultCandidate && results ? results.findIndex((result) => result.id === resultCandidate.id) : -1;
|
|
38
37
|
const disabled = isOutdated || !resultCandidate;
|
|
39
|
-
const isPreviousDisabled = index <= 0 || disabled;
|
|
40
|
-
const isNextDisabled = index >= results.length - 1 || disabled;
|
|
38
|
+
const isPreviousDisabled = !results || index <= 0 || disabled;
|
|
39
|
+
const isNextDisabled = !results || index >= results.length - 1 || disabled;
|
|
41
40
|
const bothEnabled = !isPreviousDisabled && !isNextDisabled;
|
|
42
41
|
const { showFloatingSolveButton } = useAppLayout();
|
|
43
42
|
|
|
@@ -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,29 @@
|
|
|
24
33
|
--background-color: var(--color--red);
|
|
25
34
|
}
|
|
26
35
|
|
|
36
|
+
&.raised {
|
|
37
|
+
box-shadow: var(--box-shadow--raised);
|
|
38
|
+
|
|
39
|
+
@include media('<xs') {
|
|
40
|
+
box-shadow: var(--box-shadow--raised--subtle);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
27
44
|
&.blank {
|
|
28
45
|
--background-color: var(--color--white);
|
|
29
46
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
47
|
+
color: var(--color--foreground);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
&.empty {
|
|
51
|
+
color: var(--color--inactive);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
&.invalid {
|
|
55
|
+
--background-color: var(--color--foreground);
|
|
56
|
+
--shadow--color: var(--box-shadow--color--inverse);
|
|
57
|
+
|
|
58
|
+
color: var(--color--white);
|
|
34
59
|
}
|
|
35
60
|
|
|
36
61
|
&.highlighted {
|
|
@@ -38,85 +63,28 @@
|
|
|
38
63
|
|
|
39
64
|
color: var(--color--primary--opposite);
|
|
40
65
|
|
|
41
|
-
.placeholder,
|
|
42
|
-
.character {
|
|
43
|
-
color: inherit;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
66
|
.points {
|
|
47
67
|
color: inherit;
|
|
48
68
|
}
|
|
49
69
|
}
|
|
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
70
|
}
|
|
68
71
|
|
|
69
72
|
.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
73
|
position: absolute;
|
|
81
74
|
top: 0;
|
|
82
75
|
right: 0;
|
|
83
76
|
bottom: 0;
|
|
84
77
|
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;
|
|
78
|
+
width: 100%;
|
|
79
|
+
height: 100%;
|
|
80
|
+
border: none;
|
|
81
|
+
background-color: transparent;
|
|
82
|
+
color: transparent;
|
|
83
|
+
caret-color: transparent;
|
|
84
|
+
font-size: 16px; // prevent iOS from automatically zooming in on focus
|
|
117
85
|
|
|
118
|
-
|
|
119
|
-
|
|
86
|
+
&::selection {
|
|
87
|
+
--background--color: transparent;
|
|
120
88
|
}
|
|
121
89
|
}
|
|
122
90
|
|
|
@@ -145,23 +113,29 @@
|
|
|
145
113
|
}
|
|
146
114
|
|
|
147
115
|
.alert {
|
|
148
|
-
|
|
116
|
+
--size: 30%;
|
|
149
117
|
|
|
150
118
|
position: absolute;
|
|
151
|
-
width:
|
|
152
|
-
height:
|
|
153
|
-
background
|
|
119
|
+
width: var(--size);
|
|
120
|
+
height: var(--size);
|
|
121
|
+
background: radial-gradient(
|
|
122
|
+
var(--color--error--opposite),
|
|
123
|
+
var(--color--error--opposite) 85%,
|
|
124
|
+
transparent 85%,
|
|
125
|
+
transparent
|
|
126
|
+
);
|
|
154
127
|
color: var(--color--error);
|
|
128
|
+
pointer-events: none;
|
|
155
129
|
|
|
156
130
|
[dir='ltr'] & {
|
|
157
131
|
top: 0;
|
|
158
132
|
right: 0;
|
|
159
|
-
border-
|
|
133
|
+
border-bottom-right-radius: inherit;
|
|
160
134
|
}
|
|
161
135
|
|
|
162
136
|
[dir='rtl'] & {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
border-
|
|
137
|
+
top: 0;
|
|
138
|
+
left: 0;
|
|
139
|
+
border-top-left-radius: inherit;
|
|
166
140
|
}
|
|
167
141
|
}
|
|
@@ -59,9 +59,8 @@ const Tile: FunctionComponent<Props> = ({
|
|
|
59
59
|
}) => {
|
|
60
60
|
const locale = useTypedSelector(selectLocale);
|
|
61
61
|
const { animateTile, showTilePoints } = useAppLayout();
|
|
62
|
-
const { pointsFontSize,
|
|
62
|
+
const { pointsFontSize, tileSize } = getTileSizes(size);
|
|
63
63
|
const style = useMemo(() => ({ height: tileSize, width: tileSize }), [tileSize]);
|
|
64
|
-
const characterStyle = useMemo(() => ({ fontSize: tileFontSize }), [tileFontSize]);
|
|
65
64
|
const pointsStyle = useMemo(() => ({ fontSize: pointsFontSize }), [pointsFontSize]);
|
|
66
65
|
const ref = useRef<HTMLInputElement>(null);
|
|
67
66
|
const mergedRef = useMergeRefs(inputRef ? [ref, inputRef] : [ref]);
|
|
@@ -98,7 +97,6 @@ const Tile: FunctionComponent<Props> = ({
|
|
|
98
97
|
autoFocus={autoFocus}
|
|
99
98
|
canShowPoints={canShowPoints}
|
|
100
99
|
character={character}
|
|
101
|
-
characterStyle={characterStyle}
|
|
102
100
|
className={className}
|
|
103
101
|
disabled={disabled}
|
|
104
102
|
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,
|
|
@@ -63,18 +61,20 @@ const TilePure: FunctionComponent<Props> = ({
|
|
|
63
61
|
<div
|
|
64
62
|
className={classNames(styles.tile, className, {
|
|
65
63
|
[styles.blank]: isBlank,
|
|
66
|
-
[styles.disabled]: disabled,
|
|
67
64
|
[styles.empty]: !character,
|
|
65
|
+
[styles.invalid]: !isValid,
|
|
68
66
|
[styles.highlighted]: highlighted,
|
|
69
|
-
[styles.raised]: raised,
|
|
70
67
|
[styles.points1]: points === 1,
|
|
71
68
|
[styles.points2]: points === 2,
|
|
72
69
|
[styles.points3]: points === 3,
|
|
73
70
|
[styles.points4]: points === 4,
|
|
74
71
|
[styles.points5]: typeof points === 'number' && points >= 5,
|
|
72
|
+
[styles.raised]: raised,
|
|
75
73
|
})}
|
|
76
74
|
style={style}
|
|
77
75
|
>
|
|
76
|
+
{character || placeholder}
|
|
77
|
+
|
|
78
78
|
<input
|
|
79
79
|
aria-label={ariaLabel}
|
|
80
80
|
autoCapitalize="none"
|
|
@@ -92,16 +92,6 @@ const TilePure: FunctionComponent<Props> = ({
|
|
|
92
92
|
onKeyDown={onKeyDown}
|
|
93
93
|
/>
|
|
94
94
|
|
|
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
95
|
{canShowPoints && (
|
|
106
96
|
<span className={styles.points} style={pointsStyle}>
|
|
107
97
|
{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/constants.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { Locale } from '@scrabble-solver/types';
|
|
2
|
+
import { FunctionComponent, SVGAttributes } from 'react';
|
|
3
|
+
|
|
4
|
+
import { FlagDe, FlagEs, FlagFa, FlagFr, FlagGb, FlagPl, FlagUs } from 'icons';
|
|
5
|
+
|
|
6
|
+
import styles from './i18n.module.scss';
|
|
2
7
|
|
|
3
8
|
interface LocaleFeatures {
|
|
4
9
|
direction: 'ltr' | 'rtl';
|
|
@@ -51,3 +56,63 @@ export const LOCALE_FEATURES: Record<Locale, LocaleFeatures> = {
|
|
|
51
56
|
vowels: true,
|
|
52
57
|
},
|
|
53
58
|
};
|
|
59
|
+
|
|
60
|
+
interface Flag {
|
|
61
|
+
className: string;
|
|
62
|
+
Icon: FunctionComponent<SVGAttributes<SVGElement>>;
|
|
63
|
+
label: string;
|
|
64
|
+
name: string;
|
|
65
|
+
value: Locale;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const LOCALE_FLAGS: Record<Locale, Flag> = {
|
|
69
|
+
[Locale.EN_GB]: {
|
|
70
|
+
className: styles.gb,
|
|
71
|
+
Icon: FlagGb,
|
|
72
|
+
label: 'English (GB)',
|
|
73
|
+
name: 'English (GB)',
|
|
74
|
+
value: Locale.EN_GB,
|
|
75
|
+
},
|
|
76
|
+
[Locale.EN_US]: {
|
|
77
|
+
className: styles.us,
|
|
78
|
+
Icon: FlagUs,
|
|
79
|
+
label: 'English (US)',
|
|
80
|
+
name: 'English (US)',
|
|
81
|
+
value: Locale.EN_US,
|
|
82
|
+
},
|
|
83
|
+
[Locale.FA_IR]: {
|
|
84
|
+
className: styles.fa,
|
|
85
|
+
Icon: FlagFa,
|
|
86
|
+
label: 'فارسی',
|
|
87
|
+
name: 'Persian',
|
|
88
|
+
value: Locale.FA_IR,
|
|
89
|
+
},
|
|
90
|
+
[Locale.FR_FR]: {
|
|
91
|
+
className: styles.fr,
|
|
92
|
+
Icon: FlagFr,
|
|
93
|
+
label: 'Français',
|
|
94
|
+
name: 'French',
|
|
95
|
+
value: Locale.FR_FR,
|
|
96
|
+
},
|
|
97
|
+
[Locale.DE_DE]: {
|
|
98
|
+
className: styles.de,
|
|
99
|
+
Icon: FlagDe,
|
|
100
|
+
label: 'Deutsch',
|
|
101
|
+
name: 'German',
|
|
102
|
+
value: Locale.DE_DE,
|
|
103
|
+
},
|
|
104
|
+
[Locale.PL_PL]: {
|
|
105
|
+
className: styles.pl,
|
|
106
|
+
Icon: FlagPl,
|
|
107
|
+
label: 'Polski',
|
|
108
|
+
name: 'Polish',
|
|
109
|
+
value: Locale.PL_PL,
|
|
110
|
+
},
|
|
111
|
+
[Locale.ES_ES]: {
|
|
112
|
+
className: styles.es,
|
|
113
|
+
Icon: FlagEs,
|
|
114
|
+
label: 'Español',
|
|
115
|
+
name: 'Spanish',
|
|
116
|
+
value: Locale.ES_ES,
|
|
117
|
+
},
|
|
118
|
+
};
|
package/src/i18n/de.json
CHANGED
|
@@ -45,12 +45,12 @@
|
|
|
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.",
|
|
52
51
|
"results.input.placeholder": "Suchergebnisse... (RegExp)",
|
|
53
52
|
"results.insert": "Hinzufügen",
|
|
53
|
+
"results.preview": "Vorschau",
|
|
54
54
|
"results.solve": "Lösen",
|
|
55
55
|
"settings": "Einstellungen",
|
|
56
56
|
"settings.autoGroupTiles": "Restliche Steine gruppieren",
|
package/src/i18n/en.json
CHANGED
|
@@ -45,12 +45,12 @@
|
|
|
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.",
|
|
52
51
|
"results.input.placeholder": "Search results... (RegExp)",
|
|
53
52
|
"results.insert": "Insert",
|
|
53
|
+
"results.preview": "Preview",
|
|
54
54
|
"results.solve": "Solve",
|
|
55
55
|
"settings": "Settings",
|
|
56
56
|
"settings.autoGroupTiles": "Group remaining tiles",
|
package/src/i18n/es.json
CHANGED
|
@@ -45,12 +45,12 @@
|
|
|
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.",
|
|
52
51
|
"results.input.placeholder": "Busque una solución... (RegExp)",
|
|
53
52
|
"results.insert": "Insertar",
|
|
53
|
+
"results.preview": "Vista previa",
|
|
54
54
|
"results.solve": "Resolver",
|
|
55
55
|
"settings": "Configuración",
|
|
56
56
|
"settings.autoGroupTiles": "Agrupar casillas restantes",
|
package/src/i18n/fa.json
CHANGED
|
@@ -45,12 +45,12 @@
|
|
|
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": "کلمات تولید شده از حروف شما اینجا نمایش داده خواهد شد.",
|
|
52
51
|
"results.input.placeholder": "جستجو در نتایج (RegExp)",
|
|
53
52
|
"results.insert": "وارد کردن",
|
|
53
|
+
"results.preview": "پیش نمایش",
|
|
54
54
|
"results.solve": "حل کن",
|
|
55
55
|
"settings": "تنظیمات",
|
|
56
56
|
"settings.autoGroupTiles": "کاشی های باقی مانده ی طاقچه را کنار هم قرار بده",
|
package/src/i18n/fr.json
CHANGED
|
@@ -45,12 +45,12 @@
|
|
|
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.",
|
|
52
51
|
"results.input.placeholder": "Rechercher les résultats... (RegExp)",
|
|
53
52
|
"results.insert": "Inserer",
|
|
53
|
+
"results.preview": "Prévisualisation",
|
|
54
54
|
"results.solve": "Résoudre",
|
|
55
55
|
"settings": "Options",
|
|
56
56
|
"settings.autoGroupTiles": "Grouper les cases restantes",
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
.de {
|
|
2
|
+
--aspect-ratio: var(--flag--de--aspect-ratio);
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.es {
|
|
6
|
+
--aspect-ratio: var(--flag--es--aspect-ratio);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.fa {
|
|
10
|
+
--aspect-ratio: var(--flag--fa--aspect-ratio);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.fr {
|
|
14
|
+
--aspect-ratio: var(--flag--fr--aspect-ratio);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.gb {
|
|
18
|
+
--aspect-ratio: var(--flag--gb--aspect-ratio);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.pl {
|
|
22
|
+
--aspect-ratio: var(--flag--pl--aspect-ratio);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.us {
|
|
26
|
+
--aspect-ratio: var(--flag--us--aspect-ratio);
|
|
27
|
+
}
|
package/src/i18n/pl.json
CHANGED
|
@@ -45,12 +45,12 @@
|
|
|
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.",
|
|
52
51
|
"results.input.placeholder": "Szukaj rozwiązania... (RegExp)",
|
|
53
52
|
"results.insert": "Wstaw",
|
|
53
|
+
"results.preview": "Podgląd",
|
|
54
54
|
"results.solve": "Rozwiąż",
|
|
55
55
|
"settings": "Opcje",
|
|
56
56
|
"settings.autoGroupTiles": "Grupuj pozostałe płytki",
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<!-- https://icons.getbootstrap.com/icons/eye-fill/ -->
|
|
2
|
+
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
|
3
|
+
<path d="M10.5 8a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 0z" fill="currentColor" />
|
|
4
|
+
<path d="M0 8s3-5.5 8-5.5S16 8 16 8s-3 5.5-8 5.5S0 8 0 8zm8 3.5a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7z" fill="currentColor" />
|
|
5
|
+
</svg>
|
package/src/icons/index.ts
CHANGED
|
@@ -18,15 +18,16 @@ export { default as DashCircleFill } from './DashCircleFill.svg';
|
|
|
18
18
|
export { default as Eraser } from './Eraser.svg';
|
|
19
19
|
export { default as ExclamationSquareFill } from './ExclamationSquareFill.svg';
|
|
20
20
|
export { default as ExclamationTriangleFill } from './ExclamationTriangleFill.svg';
|
|
21
|
+
export { default as EyeFill } from './EyeFill.svg';
|
|
21
22
|
export { default as Flag } from './Flag.svg';
|
|
22
|
-
export { default as
|
|
23
|
+
export { default as FlagDe } from './FlagDe.svg';
|
|
23
24
|
export { default as FlagEs } from './FlagEs.svg';
|
|
24
25
|
export { default as FlagFa } from './FlagFa.svg';
|
|
26
|
+
export { default as FlagFill } from './FlagFill.svg';
|
|
25
27
|
export { default as FlagFr } from './FlagFr.svg';
|
|
26
28
|
export { default as FlagGb } from './FlagGb.svg';
|
|
27
29
|
export { default as FlagPl } from './FlagPl.svg';
|
|
28
30
|
export { default as FlagUs } from './FlagUs.svg';
|
|
29
|
-
export { default as FlagDe } from './FlagDe.svg';
|
|
30
31
|
export { default as Github } from './Github.svg';
|
|
31
32
|
export { default as InfoCircleFill } from './InfoCircleFill.svg';
|
|
32
33
|
export { default as Keyboard } from './Keyboard.svg';
|
|
@@ -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;
|