@scrabble-solver/scrabble-solver 2.13.12 → 2.13.13
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 +14 -14
- package/.next/cache/.tsbuildinfo +1 -1
- package/.next/cache/eslint/.cache_8dgz12 +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/client-production/index.pack.old +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/edge-server-production/index.pack.old +0 -0
- package/.next/cache/webpack/server-production/0.pack +0 -0
- package/.next/cache/webpack/server-production/index.pack +0 -0
- package/.next/cache/webpack/server-production/index.pack.old +0 -0
- package/.next/prerender-manifest.js +1 -1
- package/.next/prerender-manifest.json +1 -1
- package/.next/routes-manifest.json +1 -1
- package/.next/server/chunks/807.js +1 -1
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/pages/api/solve.js +1 -1
- package/.next/server/pages/index.html +1 -1
- package/.next/server/pages/index.js +1 -1
- package/.next/server/pages/index.json +1 -1
- package/.next/server/pages-manifest.json +1 -1
- package/.next/static/chunks/{main-b5b360c6afb66b05.js → main-8b0b4e610892a916.js} +1 -1
- package/.next/static/chunks/pages/{404-0c9f3e0f8b15f487.js → 404-b0c2ccded2455be0.js} +1 -1
- package/.next/static/chunks/pages/_app-42ce6b4032e931ff.js +17 -0
- package/.next/static/chunks/pages/index-3718075f2ba2220c.js +1 -0
- package/.next/static/chunks/webpack-c4acd79e87956a0e.js +1 -0
- package/.next/static/css/2adc9736d823979b.css +2 -0
- package/.next/static/{N8hSsS6Ppzlj3ebHMSZvI → qwJjm2FeDHHGY92CY5oQQ}/_buildManifest.js +1 -1
- package/.next/trace +45 -45
- package/package.json +13 -13
- package/src/components/Board/Board.module.scss +3 -9
- package/src/components/Board/Board.tsx +2 -2
- package/src/components/Board/BoardPure.tsx +3 -2
- package/src/components/Board/components/ToggleDirectionButton/ToggleDirectionButton.tsx +2 -0
- package/src/components/Board/hooks/useBackgroundImage.tsx +2 -6
- package/src/components/Board/hooks/useBoardStyle.ts +10 -4
- package/src/components/Dictionary/Dictionary.module.scss +10 -7
- package/src/components/Dictionary/Dictionary.tsx +38 -36
- package/src/components/Loading/Loading.module.scss +1 -0
- package/src/components/Loading/Loading.tsx +1 -1
- package/src/components/Modal/components/Section/Section.tsx +3 -2
- package/src/components/NavButtons/NavButtons.tsx +1 -0
- package/src/components/Rack/Rack.tsx +1 -0
- package/src/components/Results/Cell.tsx +7 -3
- package/src/components/Results/Header.tsx +99 -0
- package/src/components/Results/HeaderButton.tsx +18 -13
- package/src/components/Results/Result.tsx +23 -16
- package/src/components/Results/Results.module.scss +24 -12
- package/src/components/Results/Results.tsx +3 -9
- package/src/components/Results/types.ts +0 -8
- package/src/components/Solver/Solver.tsx +1 -1
- package/src/components/Tile/TilePure.tsx +1 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useAppLayout.ts +12 -1
- package/src/hooks/useColumns.ts +47 -0
- package/src/icons/GeoAlt.svg +5 -0
- package/src/icons/OneTwoThree.svg +4 -0
- package/src/icons/SquareA.svg +6 -0
- package/src/icons/SquareB.svg +6 -0
- package/src/icons/Squares.svg +34 -0
- package/src/icons/Words.svg +22 -0
- package/src/icons/index.ts +6 -0
- package/src/lib/groupResults.ts +1 -1
- package/src/lib/index.ts +0 -1
- package/src/lib/sortResults.ts +10 -10
- package/src/modals/KeyMapModal/KeyMapModal.tsx +8 -9
- package/src/modals/RemainingTilesModal/RemainingTilesModal.tsx +1 -0
- package/src/modals/SettingsModal/SettingsModal.tsx +5 -5
- package/src/modals/WordsModal/WordsModal.tsx +2 -0
- package/src/parameters/index.ts +12 -0
- package/src/state/selectors.ts +26 -1
- package/src/state/slices/resultsInitialState.ts +2 -2
- package/src/state/slices/resultsSlice.ts +2 -2
- package/src/state/useTranslate.ts +5 -1
- package/src/styles/variables.scss +1 -0
- package/src/types/index.ts +11 -2
- package/.next/static/chunks/pages/_app-8246f5b39b6a5e59.js +0 -17
- package/.next/static/chunks/pages/index-65bfe83d121535ab.js +0 -1
- package/.next/static/chunks/webpack-6ef43a8d4a395f49.js +0 -1
- package/.next/static/css/2f727b21d1331ea5.css +0 -2
- package/src/components/Results/getCoordinatesColumn.ts +0 -14
- package/src/components/Results/getLocaleColumns.ts +0 -58
- package/src/components/Results/useColumns.ts +0 -44
- package/src/lib/dataUrlToBlob.ts +0 -20
- /package/.next/static/{N8hSsS6Ppzlj3ebHMSZvI → qwJjm2FeDHHGY92CY5oQQ}/_ssgManifest.js +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scrabble-solver/scrabble-solver",
|
|
3
|
-
"version": "2.13.
|
|
3
|
+
"version": "2.13.13",
|
|
4
4
|
"description": "Scrabble Solver 2 - App",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=16"
|
|
@@ -27,21 +27,21 @@
|
|
|
27
27
|
"start": "env-cmd next start -p 3333"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@floating-ui/react": "^0.26.
|
|
30
|
+
"@floating-ui/react": "^0.26.19",
|
|
31
31
|
"@kamilmielnik/trie": "^3.0.0",
|
|
32
|
-
"@reduxjs/toolkit": "^2.2.
|
|
33
|
-
"@scrabble-solver/configs": "^2.13.
|
|
34
|
-
"@scrabble-solver/constants": "^2.13.
|
|
35
|
-
"@scrabble-solver/dictionaries": "^2.13.
|
|
36
|
-
"@scrabble-solver/logger": "^2.13.
|
|
37
|
-
"@scrabble-solver/solver": "^2.13.
|
|
38
|
-
"@scrabble-solver/types": "^2.13.
|
|
39
|
-
"@scrabble-solver/word-definitions": "^2.13.
|
|
32
|
+
"@reduxjs/toolkit": "^2.2.6",
|
|
33
|
+
"@scrabble-solver/configs": "^2.13.13",
|
|
34
|
+
"@scrabble-solver/constants": "^2.13.13",
|
|
35
|
+
"@scrabble-solver/dictionaries": "^2.13.13",
|
|
36
|
+
"@scrabble-solver/logger": "^2.13.13",
|
|
37
|
+
"@scrabble-solver/solver": "^2.13.13",
|
|
38
|
+
"@scrabble-solver/types": "^2.13.13",
|
|
39
|
+
"@scrabble-solver/word-definitions": "^2.13.13",
|
|
40
40
|
"classnames": "^2.5.1",
|
|
41
41
|
"env-cmd": "^10.1.0",
|
|
42
42
|
"include-media": "^2.0.0",
|
|
43
43
|
"include-media-query-builder": "^1.1.0",
|
|
44
|
-
"next": "^14.2.
|
|
44
|
+
"next": "^14.2.5",
|
|
45
45
|
"normalize.css": "^8.0.1",
|
|
46
46
|
"react": "^18.3.1",
|
|
47
47
|
"react-cool-onclickoutside": "^1.7.0",
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"@types/react-window": "^1.8.8",
|
|
72
72
|
"@types/redux": "^3.6.31",
|
|
73
73
|
"@types/redux-saga": "^0.10.5",
|
|
74
|
-
"sass": "^1.77.
|
|
74
|
+
"sass": "^1.77.8"
|
|
75
75
|
},
|
|
76
|
-
"gitHead": "
|
|
76
|
+
"gitHead": "8cfc206dec35314cd879a3ce8f9b0c60c3fd231a"
|
|
77
77
|
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
display: grid;
|
|
6
6
|
gap: var(--border--width);
|
|
7
7
|
box-shadow: var(--box-shadow);
|
|
8
|
+
background-color: white;
|
|
8
9
|
border: var(--border);
|
|
9
10
|
border-radius: var(--border--radius);
|
|
10
11
|
}
|
|
@@ -64,16 +65,9 @@
|
|
|
64
65
|
color: var(--color--white);
|
|
65
66
|
}
|
|
66
67
|
|
|
67
|
-
.
|
|
68
|
+
.coordinate {
|
|
69
|
+
position: relative;
|
|
68
70
|
display: flex;
|
|
69
71
|
align-items: center;
|
|
70
72
|
justify-content: center;
|
|
71
|
-
font-size: var(--font--size--h2);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
.coordinateRow {
|
|
75
|
-
display: flex;
|
|
76
|
-
align-items: center;
|
|
77
|
-
justify-content: center;
|
|
78
|
-
font-size: var(--font--size--h2);
|
|
79
73
|
}
|
|
@@ -176,8 +176,8 @@ const Board: FunctionComponent<Props> = ({ className }) => {
|
|
|
176
176
|
ref={floatingFocus.refs.setFloating}
|
|
177
177
|
style={{
|
|
178
178
|
position: floatingFocus.strategy,
|
|
179
|
-
top:
|
|
180
|
-
left:
|
|
179
|
+
top: floatingFocus.y + cellSize - (showCoordinates === 'hidden' ? 0 : BORDER_WIDTH),
|
|
180
|
+
left: floatingFocus.x - (showCoordinates === 'hidden' ? 0 : BORDER_WIDTH),
|
|
181
181
|
width: cellSize,
|
|
182
182
|
height: cellSize,
|
|
183
183
|
opacity: hasFocus ? 1 : 0,
|
|
@@ -61,6 +61,7 @@ const BoardPure = forwardRef<HTMLDivElement, Props>(
|
|
|
61
61
|
) => (
|
|
62
62
|
<div
|
|
63
63
|
className={classNames(styles.board, className)}
|
|
64
|
+
data-testid="board"
|
|
64
65
|
ref={ref}
|
|
65
66
|
style={style}
|
|
66
67
|
onBlur={onBlur}
|
|
@@ -73,7 +74,7 @@ const BoardPure = forwardRef<HTMLDivElement, Props>(
|
|
|
73
74
|
|
|
74
75
|
{rows[0].map((_column, index) => (
|
|
75
76
|
<div
|
|
76
|
-
className={styles.
|
|
77
|
+
className={styles.coordinate}
|
|
77
78
|
key={index}
|
|
78
79
|
style={{
|
|
79
80
|
width: cellSize,
|
|
@@ -116,7 +117,7 @@ const BoardPure = forwardRef<HTMLDivElement, Props>(
|
|
|
116
117
|
<Fragment key={y}>
|
|
117
118
|
{showCoordinates !== 'hidden' && (
|
|
118
119
|
<div
|
|
119
|
-
className={styles.
|
|
120
|
+
className={styles.coordinate}
|
|
120
121
|
style={{
|
|
121
122
|
width: coordinatesSize,
|
|
122
123
|
height: cellSize,
|
|
@@ -23,6 +23,8 @@ const ToggleDirectionButton: FunctionComponent<Props> = ({ className, direction,
|
|
|
23
23
|
<Button
|
|
24
24
|
aria-label={translate('cell.toggle-direction')}
|
|
25
25
|
className={classNames(styles.button, className)}
|
|
26
|
+
data-direction={direction}
|
|
27
|
+
data-testid="toggle-direction-button"
|
|
26
28
|
Icon={ArrowDown}
|
|
27
29
|
iconClassName={classNames(styles.icon, {
|
|
28
30
|
[styles.right]: direction === 'horizontal',
|
|
@@ -8,7 +8,7 @@ import { Provider } from 'react-redux';
|
|
|
8
8
|
import { useAppLayout, useMediaQueries } from 'hooks';
|
|
9
9
|
import { LOCALE_FEATURES } from 'i18n';
|
|
10
10
|
import { Star } from 'icons';
|
|
11
|
-
import {
|
|
11
|
+
import { getTileSizes } from 'lib';
|
|
12
12
|
import { BORDER_COLOR_LIGHT, BORDER_RADIUS, BORDER_WIDTH, COLOR_BACKGROUND, COLOR_BONUS_START } from 'parameters';
|
|
13
13
|
import { selectConfig, selectLocale, selectShowCoordinates, store, useTypedSelector } from 'state';
|
|
14
14
|
import { Point } from 'types';
|
|
@@ -211,11 +211,7 @@ const useBackgroundImage = () => {
|
|
|
211
211
|
|
|
212
212
|
const encodedSvg = useMemo(() => globalThis.btoa(backgroundSvg), [backgroundSvg]);
|
|
213
213
|
const dataUrl = `data:image/svg+xml;base64,${encodedSvg}`;
|
|
214
|
-
|
|
215
|
-
const blobUrl = useMemo(() => URL.createObjectURL(blob), [blob]);
|
|
216
|
-
const url = `url(${blobUrl})`;
|
|
217
|
-
|
|
218
|
-
return url;
|
|
214
|
+
return dataUrl;
|
|
219
215
|
};
|
|
220
216
|
|
|
221
217
|
export default useBackgroundImage;
|
|
@@ -2,6 +2,7 @@ import { CSSProperties, useMemo } from 'react';
|
|
|
2
2
|
|
|
3
3
|
import { useAppLayout } from 'hooks';
|
|
4
4
|
import { getTileSizes } from 'lib';
|
|
5
|
+
import { BORDER_WIDTH } from 'parameters';
|
|
5
6
|
import { selectConfig, selectShowCoordinates, useTypedSelector } from 'state';
|
|
6
7
|
|
|
7
8
|
import useBackgroundImage from './useBackgroundImage';
|
|
@@ -10,16 +11,21 @@ const useBoardStyle = () => {
|
|
|
10
11
|
const config = useTypedSelector(selectConfig);
|
|
11
12
|
const { cellSize } = useAppLayout();
|
|
12
13
|
const { tileFontSize } = getTileSizes(cellSize);
|
|
13
|
-
const backgroundImage = useBackgroundImage();
|
|
14
14
|
const showCoordinates = useTypedSelector(selectShowCoordinates);
|
|
15
|
+
const backgroundImage = useBackgroundImage();
|
|
16
|
+
const coordinatesSize = 0.5 * cellSize - BORDER_WIDTH;
|
|
15
17
|
const boardStyle = useMemo<CSSProperties>(
|
|
16
18
|
() => ({
|
|
17
|
-
backgroundImage
|
|
19
|
+
backgroundImage: `url(${backgroundImage})`,
|
|
18
20
|
fontSize: tileFontSize,
|
|
19
21
|
gridTemplateColumns:
|
|
20
|
-
showCoordinates === 'hidden'
|
|
22
|
+
showCoordinates === 'hidden'
|
|
23
|
+
? `repeat(${config.boardSize}, 1fr)`
|
|
24
|
+
: `${coordinatesSize}px repeat(${config.boardSize}, 1fr)`,
|
|
21
25
|
gridTemplateRows:
|
|
22
|
-
showCoordinates === 'hidden'
|
|
26
|
+
showCoordinates === 'hidden'
|
|
27
|
+
? `repeat(${config.boardSize}, 1fr)`
|
|
28
|
+
: `${coordinatesSize}px repeat(${config.boardSize}, 1fr)`,
|
|
23
29
|
}),
|
|
24
30
|
[backgroundImage, config.boardSize, tileFontSize],
|
|
25
31
|
);
|
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
@import 'styles/mixins';
|
|
2
2
|
|
|
3
3
|
.dictionary {
|
|
4
|
-
@include scrollbars;
|
|
5
|
-
|
|
6
4
|
position: relative;
|
|
7
|
-
max-height: var(--dictionary--height);
|
|
8
|
-
height: var(--dictionary--height);
|
|
9
|
-
overflow-y: auto;
|
|
10
|
-
word-break: break-word;
|
|
11
5
|
transition: var(--transition);
|
|
6
|
+
word-break: break-word;
|
|
7
|
+
overflow: hidden;
|
|
12
8
|
|
|
13
9
|
&.isAllowed {
|
|
14
10
|
background-color: var(--color--green--light);
|
|
@@ -19,6 +15,13 @@
|
|
|
19
15
|
}
|
|
20
16
|
}
|
|
21
17
|
|
|
18
|
+
.content {
|
|
19
|
+
@include scrollbars;
|
|
20
|
+
|
|
21
|
+
height: 100%;
|
|
22
|
+
overflow-y: auto;
|
|
23
|
+
}
|
|
24
|
+
|
|
22
25
|
.result {
|
|
23
26
|
transition: var(--transition);
|
|
24
27
|
|
|
@@ -43,7 +46,7 @@
|
|
|
43
46
|
}
|
|
44
47
|
}
|
|
45
48
|
|
|
46
|
-
.
|
|
49
|
+
.resultContent {
|
|
47
50
|
padding: var(--spacing--l);
|
|
48
51
|
}
|
|
49
52
|
|
|
@@ -25,49 +25,51 @@ const Dictionary: FunctionComponent<Props> = ({ className }) => {
|
|
|
25
25
|
[styles.isNotAllowed]: isLastAllowed === false,
|
|
26
26
|
})}
|
|
27
27
|
>
|
|
28
|
-
|
|
28
|
+
<div className={styles.content}>
|
|
29
|
+
{typeof error !== 'undefined' && !isLoading && <EmptyState variant="error">{error.message}</EmptyState>}
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
{typeof error === 'undefined' && !isLoading && results.length === 0 && (
|
|
32
|
+
<EmptyState variant="info">{translate('dictionary.empty-state.uninitialized')}</EmptyState>
|
|
33
|
+
)}
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
35
|
+
{results.map(({ definitions, exists, isAllowed, word }) => (
|
|
36
|
+
<div
|
|
37
|
+
className={classNames(styles.result, {
|
|
38
|
+
[styles.isAllowed]: isAllowed === true,
|
|
39
|
+
[styles.isNotAllowed]: isAllowed === false,
|
|
40
|
+
})}
|
|
41
|
+
key={word}
|
|
42
|
+
>
|
|
43
|
+
<div className={styles.resultContent}>
|
|
44
|
+
{word && <h2 className={styles.word}>{word}</h2>}
|
|
44
45
|
|
|
45
|
-
|
|
46
|
+
{isAllowed === false && <div>{translate('dictionary.empty-state.not-allowed')}</div>}
|
|
46
47
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
48
|
+
{isAllowed === true && definitions.length === 0 && (
|
|
49
|
+
<>
|
|
50
|
+
<div>
|
|
51
|
+
{exists
|
|
52
|
+
? translate('dictionary.empty-state.no-definitions')
|
|
53
|
+
: translate('dictionary.empty-state.no-results')}
|
|
54
|
+
</div>
|
|
55
|
+
</>
|
|
56
|
+
)}
|
|
56
57
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
58
|
+
{isAllowed === true && definitions.length > 0 && (
|
|
59
|
+
<ul className={styles.definitions}>
|
|
60
|
+
{definitions.map((result, index) => (
|
|
61
|
+
<li key={index} className={styles.definition}>
|
|
62
|
+
{result}
|
|
63
|
+
</li>
|
|
64
|
+
))}
|
|
65
|
+
</ul>
|
|
66
|
+
)}
|
|
66
67
|
|
|
67
|
-
|
|
68
|
+
{!isLoading && isAllowed === null && <div>{translate('dictionary.empty-state.no-results')}</div>}
|
|
69
|
+
</div>
|
|
68
70
|
</div>
|
|
69
|
-
|
|
70
|
-
|
|
71
|
+
))}
|
|
72
|
+
</div>
|
|
71
73
|
|
|
72
74
|
{isLoading && <Loading />}
|
|
73
75
|
</div>
|
|
@@ -29,7 +29,7 @@ const Loading: FunctionComponent<Props> = ({ className, wave = true }) => {
|
|
|
29
29
|
const content = useMemo(() => prepareContent(message), [message]);
|
|
30
30
|
|
|
31
31
|
return (
|
|
32
|
-
<div aria-label={translation} className={classNames(styles.loading, className)} role="status">
|
|
32
|
+
<div aria-label={translation} className={classNames(styles.loading, className)} data-testid="loading" role="status">
|
|
33
33
|
<div className={styles.dim} />
|
|
34
34
|
<div className={styles.text}>
|
|
35
35
|
<PlainTiles className={classNames(styles.tiles)} content={content} dropShadow wave={wave} />
|
|
@@ -6,11 +6,12 @@ import styles from './Section.module.scss';
|
|
|
6
6
|
interface Props {
|
|
7
7
|
children: ReactNode;
|
|
8
8
|
className?: string;
|
|
9
|
+
label: string;
|
|
9
10
|
title: ReactNode;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
const Section: FunctionComponent<Props> = ({ children, className, title }) => (
|
|
13
|
-
<section className={classNames(styles.section, className)}>
|
|
13
|
+
const Section: FunctionComponent<Props> = ({ children, className, label, title }) => (
|
|
14
|
+
<section aria-label={label} className={classNames(styles.section, className)}>
|
|
14
15
|
<h2 className={styles.heading}>{title}</h2>
|
|
15
16
|
<div>{children}</div>
|
|
16
17
|
</section>
|
|
@@ -168,6 +168,7 @@ const Rack: FunctionComponent<Props> = ({ className, tileSize }) => {
|
|
|
168
168
|
className={classNames(styles.rack, className, {
|
|
169
169
|
[styles.hidden]: showInputPrompt,
|
|
170
170
|
})}
|
|
171
|
+
data-testid="rack"
|
|
171
172
|
ref={ref}
|
|
172
173
|
style={{ fontSize: tileFontSize }}
|
|
173
174
|
onPaste={handlePaste}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import classNames from 'classnames';
|
|
2
|
-
import { FunctionComponent, ReactNode } from 'react';
|
|
2
|
+
import { CSSProperties, FunctionComponent, ReactNode } from 'react';
|
|
3
3
|
|
|
4
4
|
import { selectLocale, useTranslate, useTypedSelector } from 'state';
|
|
5
5
|
import { TranslationKey } from 'types';
|
|
@@ -11,19 +11,23 @@ import styles from './Results.module.scss';
|
|
|
11
11
|
interface Props {
|
|
12
12
|
children?: ReactNode;
|
|
13
13
|
className?: string;
|
|
14
|
+
dataTestId?: string;
|
|
15
|
+
style?: CSSProperties;
|
|
14
16
|
translationKey: TranslationKey;
|
|
15
17
|
tooltip?: string | number;
|
|
16
18
|
value: string | number;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
|
-
const Cell: FunctionComponent<Props> = ({ children, className, translationKey, tooltip, value }) => {
|
|
21
|
+
const Cell: FunctionComponent<Props> = ({ children, className, dataTestId, style, translationKey, tooltip, value }) => {
|
|
20
22
|
const translate = useTranslate();
|
|
21
23
|
const locale = useTypedSelector(selectLocale);
|
|
22
24
|
const formattedValue = value.toLocaleString(locale);
|
|
23
25
|
|
|
24
26
|
return (
|
|
25
27
|
<Tooltip tooltip={`${translate(translationKey)}: ${tooltip || formattedValue}`}>
|
|
26
|
-
<div className={classNames(styles.cell, className)}
|
|
28
|
+
<div className={classNames(styles.cell, className)} data-testid={dataTestId} style={style}>
|
|
29
|
+
{children || formattedValue}
|
|
30
|
+
</div>
|
|
27
31
|
</Tooltip>
|
|
28
32
|
);
|
|
29
33
|
};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { FunctionComponent } from 'react';
|
|
2
|
+
|
|
3
|
+
import { useAppLayout, useColumns } from 'hooks';
|
|
4
|
+
import { GeoAlt, OneTwoThree, Square, SquareA, SquareB, Squares, Words } from 'icons';
|
|
5
|
+
import { RESULTS_COLUMN_WIDTH } from 'parameters';
|
|
6
|
+
import { ResultColumnId } from 'types';
|
|
7
|
+
|
|
8
|
+
import HeaderButton from './HeaderButton';
|
|
9
|
+
import styles from './Results.module.scss';
|
|
10
|
+
|
|
11
|
+
const Results: FunctionComponent = () => {
|
|
12
|
+
const { resultWordWidth } = useAppLayout();
|
|
13
|
+
const columns = useColumns();
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<div className={styles.header}>
|
|
17
|
+
{columns[ResultColumnId.Coordinates] && (
|
|
18
|
+
<HeaderButton
|
|
19
|
+
className={styles.coordinates}
|
|
20
|
+
Icon={GeoAlt}
|
|
21
|
+
id={ResultColumnId.Coordinates}
|
|
22
|
+
style={{ flexBasis: RESULTS_COLUMN_WIDTH[ResultColumnId.Coordinates] }}
|
|
23
|
+
translationKey="settings.showCoordinates"
|
|
24
|
+
/>
|
|
25
|
+
)}
|
|
26
|
+
|
|
27
|
+
{columns[ResultColumnId.Word] && (
|
|
28
|
+
<HeaderButton
|
|
29
|
+
className={styles.word}
|
|
30
|
+
id={ResultColumnId.Word}
|
|
31
|
+
style={{ flexBasis: resultWordWidth }}
|
|
32
|
+
translationKey="common.word"
|
|
33
|
+
/>
|
|
34
|
+
)}
|
|
35
|
+
|
|
36
|
+
{columns[ResultColumnId.TilesCount] && (
|
|
37
|
+
<HeaderButton
|
|
38
|
+
className={styles.stat}
|
|
39
|
+
Icon={Squares}
|
|
40
|
+
id={ResultColumnId.TilesCount}
|
|
41
|
+
style={{ flexBasis: RESULTS_COLUMN_WIDTH[ResultColumnId.TilesCount] }}
|
|
42
|
+
translationKey="common.tiles"
|
|
43
|
+
/>
|
|
44
|
+
)}
|
|
45
|
+
|
|
46
|
+
{columns[ResultColumnId.VowelsCount] && (
|
|
47
|
+
<HeaderButton
|
|
48
|
+
className={styles.stat}
|
|
49
|
+
Icon={SquareA}
|
|
50
|
+
id={ResultColumnId.VowelsCount}
|
|
51
|
+
style={{ flexBasis: RESULTS_COLUMN_WIDTH[ResultColumnId.VowelsCount] }}
|
|
52
|
+
translationKey="common.vowels"
|
|
53
|
+
/>
|
|
54
|
+
)}
|
|
55
|
+
|
|
56
|
+
{columns[ResultColumnId.ConsonantsCount] && (
|
|
57
|
+
<HeaderButton
|
|
58
|
+
className={styles.stat}
|
|
59
|
+
Icon={SquareB}
|
|
60
|
+
id={ResultColumnId.ConsonantsCount}
|
|
61
|
+
style={{ flexBasis: RESULTS_COLUMN_WIDTH[ResultColumnId.ConsonantsCount] }}
|
|
62
|
+
translationKey="common.consonants"
|
|
63
|
+
/>
|
|
64
|
+
)}
|
|
65
|
+
|
|
66
|
+
{columns[ResultColumnId.BlanksCount] && (
|
|
67
|
+
<HeaderButton
|
|
68
|
+
className={styles.stat}
|
|
69
|
+
Icon={Square}
|
|
70
|
+
id={ResultColumnId.BlanksCount}
|
|
71
|
+
style={{ flexBasis: RESULTS_COLUMN_WIDTH[ResultColumnId.BlanksCount] }}
|
|
72
|
+
translationKey="common.blanks"
|
|
73
|
+
/>
|
|
74
|
+
)}
|
|
75
|
+
|
|
76
|
+
{columns[ResultColumnId.WordsCount] && (
|
|
77
|
+
<HeaderButton
|
|
78
|
+
className={styles.stat}
|
|
79
|
+
Icon={Words}
|
|
80
|
+
id={ResultColumnId.WordsCount}
|
|
81
|
+
style={{ flexBasis: RESULTS_COLUMN_WIDTH[ResultColumnId.WordsCount] }}
|
|
82
|
+
translationKey="common.words"
|
|
83
|
+
/>
|
|
84
|
+
)}
|
|
85
|
+
|
|
86
|
+
{columns[ResultColumnId.Points] && (
|
|
87
|
+
<HeaderButton
|
|
88
|
+
className={styles.points}
|
|
89
|
+
Icon={OneTwoThree}
|
|
90
|
+
id={ResultColumnId.Points}
|
|
91
|
+
style={{ flexBasis: RESULTS_COLUMN_WIDTH[ResultColumnId.Points] }}
|
|
92
|
+
translationKey="common.points"
|
|
93
|
+
/>
|
|
94
|
+
)}
|
|
95
|
+
</div>
|
|
96
|
+
);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export default Results;
|
|
@@ -1,42 +1,47 @@
|
|
|
1
1
|
import classNames from 'classnames';
|
|
2
|
-
import { ReactElement, useCallback } from 'react';
|
|
2
|
+
import { CSSProperties, FunctionComponent, ReactElement, SVGAttributes, useCallback } from 'react';
|
|
3
3
|
import { useDispatch } from 'react-redux';
|
|
4
4
|
|
|
5
5
|
import { SortDown, SortUp } from 'icons';
|
|
6
6
|
import { resultsSlice, selectResultsSort, useTranslate, useTypedSelector } from 'state';
|
|
7
|
-
import { SortDirection } from 'types';
|
|
7
|
+
import { ResultColumnId, SortDirection, TranslationKey } from 'types';
|
|
8
8
|
|
|
9
9
|
import { Tooltip } from '../Tooltip';
|
|
10
10
|
|
|
11
11
|
import styles from './Results.module.scss';
|
|
12
|
-
import { Column } from './types';
|
|
13
12
|
|
|
14
13
|
interface Props {
|
|
15
|
-
|
|
14
|
+
className: string;
|
|
15
|
+
Icon?: FunctionComponent<SVGAttributes<SVGElement>>;
|
|
16
|
+
id: ResultColumnId;
|
|
17
|
+
translationKey: TranslationKey;
|
|
18
|
+
style?: CSSProperties;
|
|
16
19
|
}
|
|
17
20
|
|
|
18
|
-
const HeaderButton = ({
|
|
21
|
+
const HeaderButton = ({ className, Icon, id, translationKey, style }: Props): ReactElement => {
|
|
19
22
|
const dispatch = useDispatch();
|
|
20
23
|
const translate = useTranslate();
|
|
21
24
|
const sort = useTypedSelector(selectResultsSort);
|
|
22
25
|
|
|
23
26
|
const handleClick = useCallback(() => {
|
|
24
|
-
dispatch(resultsSlice.actions.sort(
|
|
25
|
-
}, [
|
|
27
|
+
dispatch(resultsSlice.actions.sort(id));
|
|
28
|
+
}, [dispatch, id]);
|
|
26
29
|
|
|
27
30
|
return (
|
|
28
|
-
<Tooltip tooltip={translate(
|
|
31
|
+
<Tooltip tooltip={translate(translationKey)}>
|
|
29
32
|
<button
|
|
30
|
-
aria-label={translate(
|
|
31
|
-
className={classNames(styles.headerButton,
|
|
32
|
-
|
|
33
|
+
aria-label={translate(translationKey)}
|
|
34
|
+
className={classNames(styles.headerButton, className)}
|
|
35
|
+
style={style}
|
|
33
36
|
type="button"
|
|
34
37
|
onClick={handleClick}
|
|
35
38
|
>
|
|
36
39
|
<span className={styles.cell}>
|
|
37
|
-
<
|
|
40
|
+
{Icon && <Icon className={styles.headerButtonIcon} />}
|
|
38
41
|
|
|
39
|
-
{
|
|
42
|
+
{!Icon && <span className={styles.headerButtonLabel}>{translate(translationKey)}</span>}
|
|
43
|
+
|
|
44
|
+
{sort.column === id && (
|
|
40
45
|
<>
|
|
41
46
|
{sort.direction === SortDirection.Ascending && <SortUp className={styles.sortIcon} />}
|
|
42
47
|
{sort.direction === SortDirection.Descending && <SortDown className={styles.sortIcon} />}
|