@scrabble-solver/scrabble-solver 2.8.7 → 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.
- package/.next/BUILD_ID +1 -1
- package/.next/build-manifest.json +10 -10
- 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/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/413.js +257 -55
- package/.next/server/chunks/44.js +802 -0
- package/.next/server/chunks/515.js +452 -104
- package/.next/server/chunks/911.js +53 -23
- 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 +2 -2
- package/.next/server/pages/_app.js.nft.json +1 -1
- package/.next/server/pages/_document.js.nft.json +1 -1
- package/.next/server/pages/_error.js.nft.json +1 -1
- package/.next/server/pages/api/solve.js +193 -927
- package/.next/server/pages/api/solve.js.nft.json +1 -1
- package/.next/server/pages/api/verify.js +217 -0
- package/.next/server/pages/api/verify.js.nft.json +1 -0
- package/.next/server/pages/index.html +3 -3
- package/.next/server/pages/index.js +7 -1
- package/.next/server/pages/index.js.nft.json +1 -1
- package/.next/server/pages/index.json +1 -1
- package/.next/server/pages-manifest.json +1 -0
- package/.next/static/chunks/317-a33dd38e9b9a17ed.js +1 -0
- package/.next/static/chunks/pages/{404-30c06e61d256c5b2.js → 404-90c624da3c83fd17.js} +1 -1
- package/.next/static/chunks/pages/_app-f8f360878e1c2aff.js +1 -0
- package/.next/static/chunks/pages/index-ecea697d3e5d8a6f.js +1 -0
- package/.next/static/css/64dc2ce1811912f1.css +1 -0
- package/.next/static/css/{cdbc9e0afcff5473.css → ad2a08918868cad8.css} +1 -1
- package/.next/static/yCxjzzYpw5JjJE53PO_s6/_buildManifest.js +1 -0
- package/.next/static/{z_0_lqfmiI_ISokr6NNRq → yCxjzzYpw5JjJE53PO_s6}/_ssgManifest.js +0 -0
- package/.next/trace +42 -40
- package/package.json +9 -9
- package/src/components/Badge/Badge.module.scss +13 -0
- package/src/components/Badge/Badge.tsx +15 -0
- package/src/components/Badge/index.ts +1 -0
- package/src/components/Board/components/Cell/Cell.module.scss +34 -9
- package/src/components/Board/components/Cell/Cell.tsx +23 -4
- package/src/components/Board/components/Cell/CellPure.tsx +29 -1
- package/src/components/Board/hooks/useGrid.ts +1 -0
- package/src/components/NavButtons/NavButtons.tsx +33 -14
- package/src/components/RemainingTiles/RemainingTiles.module.scss +8 -7
- package/src/components/RemainingTiles/RemainingTiles.tsx +13 -4
- package/src/components/Sidebar/Sidebar.tsx +2 -2
- package/src/components/Sidebar/components/Section/Section.module.scss +0 -1
- package/src/components/Sidebar/components/Section/Section.tsx +1 -1
- package/src/components/SquareButton/SquareButton.module.scss +5 -0
- package/src/components/Words/Words.module.scss +35 -0
- package/src/components/Words/Words.tsx +57 -0
- package/src/components/Words/index.ts +1 -0
- package/src/components/index.ts +2 -0
- package/src/i18n/de.json +5 -1
- package/src/i18n/en.json +5 -1
- package/src/i18n/es.json +5 -1
- package/src/i18n/fr.json +5 -1
- package/src/i18n/pl.json +5 -1
- package/src/icons/BookHalf.svg +4 -0
- package/src/icons/Check.svg +4 -0
- package/src/icons/Cross.svg +2 -2
- package/src/icons/CrossFill.svg +4 -0
- package/src/icons/Flag.svg +4 -0
- package/src/icons/Star.svg +4 -0
- package/src/icons/index.ts +5 -0
- package/src/pages/api/solve.ts +2 -3
- package/src/pages/api/verify.ts +71 -0
- package/src/pages/index.tsx +5 -0
- package/src/sdk/index.ts +1 -0
- package/src/sdk/verify.ts +24 -0
- package/src/state/rootReducer.ts +12 -1
- package/src/state/sagas.ts +54 -7
- package/src/state/selectors.ts +49 -11
- package/src/state/slices/cellFilterInitialState.ts +7 -0
- package/src/state/slices/cellFilterSlice.ts +24 -0
- package/src/state/slices/index.ts +4 -0
- package/src/state/slices/verifyInitialState.ts +12 -0
- package/src/state/slices/verifySlice.ts +31 -0
- package/src/styles/variables.scss +2 -1
- package/src/types/index.ts +5 -1
- package/.next/static/chunks/56-cf37c430261bbea5.js +0 -1
- package/.next/static/chunks/pages/_app-0b12a65bea70a0df.js +0 -1
- package/.next/static/chunks/pages/index-fcb69802550afb81.js +0 -1
- package/.next/static/css/1f39b55d50f5b30b.css +0 -1
- package/.next/static/z_0_lqfmiI_ISokr6NNRq/_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.
|
|
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.
|
|
34
|
-
"@scrabble-solver/constants": "^2.8.
|
|
35
|
-
"@scrabble-solver/dictionaries": "^2.8.
|
|
36
|
-
"@scrabble-solver/logger": "^2.8.
|
|
37
|
-
"@scrabble-solver/solver": "^2.8.
|
|
38
|
-
"@scrabble-solver/types": "^2.8.
|
|
39
|
-
"@scrabble-solver/word-definitions": "^2.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": "
|
|
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';
|
|
@@ -13,7 +13,6 @@ $icon-size: 16px;
|
|
|
13
13
|
transition: var(--transition);
|
|
14
14
|
background-clip: padding-box;
|
|
15
15
|
|
|
16
|
-
&.bonusStart,
|
|
17
16
|
&.bonusWord2,
|
|
18
17
|
&.bonusWord3 {
|
|
19
18
|
position: relative;
|
|
@@ -33,12 +32,7 @@ $icon-size: 16px;
|
|
|
33
32
|
}
|
|
34
33
|
|
|
35
34
|
&.bonusStart {
|
|
36
|
-
background-color: var(--color--
|
|
37
|
-
|
|
38
|
-
&:before {
|
|
39
|
-
content: '★';
|
|
40
|
-
color: var(--color--foreground--secondary);
|
|
41
|
-
}
|
|
35
|
+
background-color: var(--color--violet--light);
|
|
42
36
|
}
|
|
43
37
|
|
|
44
38
|
&.bonusCharacter1 {
|
|
@@ -168,6 +162,7 @@ $icon-size: 16px;
|
|
|
168
162
|
}
|
|
169
163
|
|
|
170
164
|
&,
|
|
165
|
+
.filterCell,
|
|
171
166
|
.toggleDirection {
|
|
172
167
|
width: $icon-size;
|
|
173
168
|
height: $icon-size;
|
|
@@ -180,8 +175,6 @@ $icon-size: 16px;
|
|
|
180
175
|
}
|
|
181
176
|
|
|
182
177
|
.toggleDirection {
|
|
183
|
-
transition: var(--transition);
|
|
184
|
-
|
|
185
178
|
&.right {
|
|
186
179
|
transform: rotate(-90deg);
|
|
187
180
|
}
|
|
@@ -194,3 +187,35 @@ $icon-size: 16px;
|
|
|
194
187
|
font-weight: bold;
|
|
195
188
|
}
|
|
196
189
|
}
|
|
190
|
+
|
|
191
|
+
.filterCell {
|
|
192
|
+
color: var(--color--inactive);
|
|
193
|
+
|
|
194
|
+
&.filtered {
|
|
195
|
+
color: var(--color--foreground--secondary);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.iconContainer {
|
|
200
|
+
position: absolute;
|
|
201
|
+
top: 0;
|
|
202
|
+
right: 0;
|
|
203
|
+
bottom: 0;
|
|
204
|
+
left: 0;
|
|
205
|
+
display: flex;
|
|
206
|
+
align-items: center;
|
|
207
|
+
justify-content: center;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.flagContainer {
|
|
211
|
+
background-color: var(--color--primary);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.flag,
|
|
215
|
+
.star {
|
|
216
|
+
$size: 24px;
|
|
217
|
+
|
|
218
|
+
width: $size;
|
|
219
|
+
height: $size;
|
|
220
|
+
color: white;
|
|
221
|
+
}
|
|
@@ -4,7 +4,15 @@ import { FunctionComponent, RefObject, useCallback, useMemo } from 'react';
|
|
|
4
4
|
import { useDispatch } from 'react-redux';
|
|
5
5
|
|
|
6
6
|
import { getTileSizes } from 'lib';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
boardSlice,
|
|
9
|
+
cellFilterSlice,
|
|
10
|
+
selectCellBonus,
|
|
11
|
+
selectCellIsFiltered,
|
|
12
|
+
selectTilePoints,
|
|
13
|
+
useTranslate,
|
|
14
|
+
useTypedSelector,
|
|
15
|
+
} from 'state';
|
|
8
16
|
|
|
9
17
|
import CellPure from './CellPure';
|
|
10
18
|
|
|
@@ -34,10 +42,19 @@ const Cell: FunctionComponent<Props> = ({
|
|
|
34
42
|
const translate = useTranslate();
|
|
35
43
|
const bonus = useTypedSelector((state) => selectCellBonus(state, cell));
|
|
36
44
|
const points = useTypedSelector((state) => selectTilePoints(state, cell.tile));
|
|
45
|
+
const isFiltered = useTypedSelector((state) => selectCellIsFiltered(state, cell));
|
|
37
46
|
const { tileFontSize } = getTileSizes(size);
|
|
38
47
|
const isEmpty = tile.character === EMPTY_CELL;
|
|
39
48
|
const style = useMemo(() => ({ fontSize: tileFontSize }), [tileFontSize]);
|
|
40
49
|
|
|
50
|
+
const handleDirectionToggleClick = useCallback(() => {
|
|
51
|
+
if (inputRef.current) {
|
|
52
|
+
inputRef.current.focus();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
onDirectionToggle();
|
|
56
|
+
}, [onDirectionToggle]);
|
|
57
|
+
|
|
41
58
|
const handleFocus = useCallback(() => onFocus(x, y), [x, y, onFocus]);
|
|
42
59
|
|
|
43
60
|
const handleToggleBlankClick = useCallback(() => {
|
|
@@ -48,13 +65,13 @@ const Cell: FunctionComponent<Props> = ({
|
|
|
48
65
|
dispatch(boardSlice.actions.toggleCellIsBlank({ x, y }));
|
|
49
66
|
}, [dispatch, x, y]);
|
|
50
67
|
|
|
51
|
-
const
|
|
68
|
+
const handleToggleFilterCellClick = useCallback(() => {
|
|
52
69
|
if (inputRef.current) {
|
|
53
70
|
inputRef.current.focus();
|
|
54
71
|
}
|
|
55
72
|
|
|
56
|
-
|
|
57
|
-
}, [
|
|
73
|
+
dispatch(cellFilterSlice.actions.toggle({ x, y }));
|
|
74
|
+
}, [dispatch, x, y]);
|
|
58
75
|
|
|
59
76
|
return (
|
|
60
77
|
<CellPure
|
|
@@ -65,6 +82,7 @@ const Cell: FunctionComponent<Props> = ({
|
|
|
65
82
|
inputRef={inputRef}
|
|
66
83
|
isCenter={isCenter}
|
|
67
84
|
isEmpty={isEmpty}
|
|
85
|
+
isFiltered={isFiltered}
|
|
68
86
|
points={points}
|
|
69
87
|
size={size}
|
|
70
88
|
style={style}
|
|
@@ -73,6 +91,7 @@ const Cell: FunctionComponent<Props> = ({
|
|
|
73
91
|
onDirectionToggleClick={handleDirectionToggleClick}
|
|
74
92
|
onFocus={handleFocus}
|
|
75
93
|
onToggleBlankClick={handleToggleBlankClick}
|
|
94
|
+
onToggleFilterCellClick={handleToggleFilterCellClick}
|
|
76
95
|
/>
|
|
77
96
|
);
|
|
78
97
|
};
|
|
@@ -2,7 +2,7 @@ import { Bonus, Cell, Tile as TileModel } from '@scrabble-solver/types';
|
|
|
2
2
|
import classNames from 'classnames';
|
|
3
3
|
import { CSSProperties, FocusEventHandler, FunctionComponent, memo, MouseEventHandler, RefObject } from 'react';
|
|
4
4
|
|
|
5
|
-
import { ArrowDown } from 'icons';
|
|
5
|
+
import { ArrowDown, Flag, Star } from 'icons';
|
|
6
6
|
import { Translate } from 'types';
|
|
7
7
|
|
|
8
8
|
import Tile from '../../../Tile';
|
|
@@ -19,6 +19,7 @@ interface Props {
|
|
|
19
19
|
inputRef: RefObject<HTMLInputElement>;
|
|
20
20
|
isCenter: boolean;
|
|
21
21
|
isEmpty: boolean;
|
|
22
|
+
isFiltered: boolean;
|
|
22
23
|
points?: number;
|
|
23
24
|
size: number;
|
|
24
25
|
style?: CSSProperties;
|
|
@@ -27,6 +28,7 @@ interface Props {
|
|
|
27
28
|
onDirectionToggleClick: MouseEventHandler<HTMLButtonElement>;
|
|
28
29
|
onFocus: FocusEventHandler<HTMLInputElement>;
|
|
29
30
|
onToggleBlankClick: MouseEventHandler<HTMLButtonElement>;
|
|
31
|
+
onToggleFilterCellClick: MouseEventHandler<HTMLButtonElement>;
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
const CellPure: FunctionComponent<Props> = ({
|
|
@@ -37,6 +39,7 @@ const CellPure: FunctionComponent<Props> = ({
|
|
|
37
39
|
inputRef,
|
|
38
40
|
isCenter,
|
|
39
41
|
isEmpty,
|
|
42
|
+
isFiltered,
|
|
40
43
|
points,
|
|
41
44
|
size,
|
|
42
45
|
style,
|
|
@@ -45,6 +48,7 @@ const CellPure: FunctionComponent<Props> = ({
|
|
|
45
48
|
onDirectionToggleClick,
|
|
46
49
|
onFocus,
|
|
47
50
|
onToggleBlankClick,
|
|
51
|
+
onToggleFilterCellClick,
|
|
48
52
|
}) => (
|
|
49
53
|
<div
|
|
50
54
|
className={classNames(styles.cell, getBonusClassname(cell, bonus, isCenter), className, {
|
|
@@ -52,6 +56,18 @@ const CellPure: FunctionComponent<Props> = ({
|
|
|
52
56
|
})}
|
|
53
57
|
style={style}
|
|
54
58
|
>
|
|
59
|
+
{isCenter && (
|
|
60
|
+
<div className={classNames(styles.iconContainer)}>
|
|
61
|
+
<Star className={styles.star} />
|
|
62
|
+
</div>
|
|
63
|
+
)}
|
|
64
|
+
|
|
65
|
+
{isFiltered && (
|
|
66
|
+
<div className={classNames(styles.iconContainer, styles.flagContainer)}>
|
|
67
|
+
<Flag className={styles.flag} />
|
|
68
|
+
</div>
|
|
69
|
+
)}
|
|
70
|
+
|
|
55
71
|
<Tile
|
|
56
72
|
className={styles.tile}
|
|
57
73
|
character={isEmpty ? undefined : tile.character}
|
|
@@ -74,6 +90,18 @@ const CellPure: FunctionComponent<Props> = ({
|
|
|
74
90
|
/>
|
|
75
91
|
</Button>
|
|
76
92
|
|
|
93
|
+
{isEmpty && (
|
|
94
|
+
<Button
|
|
95
|
+
className={classNames(styles.filterCell, {
|
|
96
|
+
[styles.filtered]: isFiltered,
|
|
97
|
+
})}
|
|
98
|
+
tooltip={translate('cell.filter-cell')}
|
|
99
|
+
onClick={onToggleFilterCellClick}
|
|
100
|
+
>
|
|
101
|
+
<Flag />
|
|
102
|
+
</Button>
|
|
103
|
+
)}
|
|
104
|
+
|
|
77
105
|
{!isEmpty && (
|
|
78
106
|
<Button
|
|
79
107
|
className={classNames(styles.blank, {
|
|
@@ -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> = ({
|
|
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.
|
|
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
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
<
|
|
24
|
-
|
|
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
|
-
</
|
|
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 {
|
|
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={
|
|
44
|
+
Icon={CrossFill}
|
|
45
45
|
tooltip={translate('common.close')}
|
|
46
46
|
onClick={onClose}
|
|
47
47
|
/>
|
|
@@ -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';
|
package/src/components/index.ts
CHANGED
|
@@ -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
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
+
"cell.filter-cell": "Zielort - klicken zum Wechseln",
|
|
2
3
|
"cell.set-blank": "Als Blanko markieren",
|
|
3
4
|
"cell.set-not-blank": "Nicht als Blanko markieren",
|
|
4
5
|
"cell.toggle-direction": "Schreibrichtung - klicken zum Wechseln",
|
|
@@ -48,5 +49,8 @@
|
|
|
48
49
|
"settings.autoGroupTiles.right": "Rechte Seite",
|
|
49
50
|
"settings.autoGroupTiles.null": "Nicht gruppieren",
|
|
50
51
|
"settings.game": "Spiel",
|
|
51
|
-
"settings.language": "Sprache"
|
|
52
|
+
"settings.language": "Sprache",
|
|
53
|
+
"words": "Gebildete Wörter",
|
|
54
|
+
"words.invalid": "Falsch",
|
|
55
|
+
"words.valid": "Korrekt"
|
|
52
56
|
}
|
package/src/i18n/en.json
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
+
"cell.filter-cell": "Target destination - click to toggle",
|
|
2
3
|
"cell.set-blank": "Mark it a blank",
|
|
3
4
|
"cell.set-not-blank": "Mark it not a blank",
|
|
4
5
|
"cell.toggle-direction": "Typing direction - click to toggle",
|
|
@@ -48,5 +49,8 @@
|
|
|
48
49
|
"settings.autoGroupTiles.right": "On the right",
|
|
49
50
|
"settings.autoGroupTiles.null": "Do not group",
|
|
50
51
|
"settings.game": "Game",
|
|
51
|
-
"settings.language": "Language"
|
|
52
|
+
"settings.language": "Language",
|
|
53
|
+
"words": "Created words",
|
|
54
|
+
"words.invalid": "Invalid",
|
|
55
|
+
"words.valid": "Valid"
|
|
52
56
|
}
|
package/src/i18n/es.json
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
+
"cell.filter-cell": "Destino objetivo: haga clic para alternar",
|
|
2
3
|
"cell.set-blank": "Marcar como en blanco",
|
|
3
4
|
"cell.set-not-blank": "Marcar como no en blanco",
|
|
4
5
|
"cell.toggle-direction": "Dirección de escritura: haga clic para alternar",
|
|
@@ -48,5 +49,8 @@
|
|
|
48
49
|
"settings.autoGroupTiles.right": "A la derecha",
|
|
49
50
|
"settings.autoGroupTiles.null": "No agrupar",
|
|
50
51
|
"settings.game": "Juego",
|
|
51
|
-
"settings.language": "Idioma"
|
|
52
|
+
"settings.language": "Idioma",
|
|
53
|
+
"words": "Palabras creadas",
|
|
54
|
+
"words.invalid": "Incorrecto",
|
|
55
|
+
"words.valid": "Correcto"
|
|
52
56
|
}
|
package/src/i18n/fr.json
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
+
"cell.filter-cell": "Destination cible - cliquer pour changer",
|
|
2
3
|
"cell.set-blank": "Marquer comme vide",
|
|
3
4
|
"cell.set-not-blank": "Marquer comme non vide",
|
|
4
5
|
"cell.toggle-direction": "Direction d'écriture - cliquer pour changer",
|
|
@@ -48,5 +49,8 @@
|
|
|
48
49
|
"settings.autoGroupTiles.right": "Vers la gauche",
|
|
49
50
|
"settings.autoGroupTiles.null": "Ne pas grouper",
|
|
50
51
|
"settings.game": "Jeu",
|
|
51
|
-
"settings.language": "Langue"
|
|
52
|
+
"settings.language": "Langue",
|
|
53
|
+
"words": "Mots créés",
|
|
54
|
+
"words.invalid": "Incorrect",
|
|
55
|
+
"words.valid": "Corriger"
|
|
52
56
|
}
|
package/src/i18n/pl.json
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
+
"cell.filter-cell": "Miejsce docelowe - kliknij aby zmienić",
|
|
2
3
|
"cell.set-blank": "Oznacz jako blank",
|
|
3
4
|
"cell.set-not-blank": "Oznacz jako nie blank",
|
|
4
5
|
"cell.toggle-direction": "Kierunek wpisywania - kliknij aby zmienić",
|
|
@@ -48,5 +49,8 @@
|
|
|
48
49
|
"settings.autoGroupTiles.right": "Po prawej",
|
|
49
50
|
"settings.autoGroupTiles.null": "Nie grupuj",
|
|
50
51
|
"settings.game": "Gra",
|
|
51
|
-
"settings.language": "Język"
|
|
52
|
+
"settings.language": "Język",
|
|
53
|
+
"words": "Utworzone słowa",
|
|
54
|
+
"words.invalid": "Niepoprawne",
|
|
55
|
+
"words.valid": "Poprawne"
|
|
52
56
|
}
|