scrabble-solver 2.10.5 → 2.10.7
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/README.md +12 -12
- package/lerna.json +1 -1
- package/package.json +10 -10
- package/packages/configs/package-lock.json +4 -4
- package/packages/configs/package.json +3 -3
- package/packages/constants/build/index.d.ts +2 -0
- package/packages/constants/build/index.js +3 -1
- package/packages/constants/package-lock.json +2 -2
- package/packages/constants/package.json +1 -1
- package/packages/constants/src/index.ts +4 -0
- package/packages/dictionaries/package-lock.json +2 -2
- package/packages/dictionaries/package.json +4 -4
- package/packages/logger/package-lock.json +2 -2
- package/packages/logger/package.json +1 -1
- package/packages/scrabble-solver/.next/BUILD_ID +1 -1
- package/packages/scrabble-solver/.next/build-manifest.json +10 -10
- package/packages/scrabble-solver/.next/cache/.tsbuildinfo +1 -1
- package/packages/scrabble-solver/.next/cache/eslint/.cache_8dgz12 +1 -1
- package/packages/scrabble-solver/.next/cache/next-server.js.nft.json +1 -1
- package/packages/scrabble-solver/.next/cache/webpack/client-production/0.pack +0 -0
- package/packages/scrabble-solver/.next/cache/webpack/client-production/index.pack +0 -0
- package/packages/scrabble-solver/.next/cache/webpack/edge-server-production/0.pack +0 -0
- package/packages/scrabble-solver/.next/cache/webpack/edge-server-production/index.pack +0 -0
- package/packages/scrabble-solver/.next/cache/webpack/server-production/0.pack +0 -0
- package/packages/scrabble-solver/.next/cache/webpack/server-production/index.pack +0 -0
- package/packages/scrabble-solver/.next/next-server.js.nft.json +1 -1
- package/packages/scrabble-solver/.next/prerender-manifest.json +1 -1
- package/packages/scrabble-solver/.next/routes-manifest.json +1 -1
- package/packages/scrabble-solver/.next/server/chunks/176.js +551 -447
- package/packages/scrabble-solver/.next/server/chunks/290.js +3 -1
- package/packages/scrabble-solver/.next/server/chunks/579.js +24 -16
- package/packages/scrabble-solver/.next/server/middleware-build-manifest.js +1 -1
- package/packages/scrabble-solver/.next/server/pages/404.html +2 -2
- package/packages/scrabble-solver/.next/server/pages/404.js.nft.json +1 -1
- package/packages/scrabble-solver/.next/server/pages/500.html +2 -2
- package/packages/scrabble-solver/.next/server/pages/_app.js.nft.json +1 -1
- package/packages/scrabble-solver/.next/server/pages/api/dictionary/[locale]/[word].js +18 -16
- package/packages/scrabble-solver/.next/server/pages/index.html +2 -2
- package/packages/scrabble-solver/.next/server/pages/index.js +18 -11
- package/packages/scrabble-solver/.next/server/pages/index.js.nft.json +1 -1
- package/packages/scrabble-solver/.next/server/pages/index.json +1 -1
- package/packages/scrabble-solver/.next/server/pages-manifest.json +1 -1
- package/packages/scrabble-solver/.next/static/6RggBFm8kHrh-k1-CG3um/_buildManifest.js +1 -0
- package/packages/scrabble-solver/.next/static/chunks/509-6ad4482d4351452c.js +1 -0
- package/packages/scrabble-solver/.next/static/chunks/pages/{404-7619583a9e7188b1.js → 404-67383848027ec49b.js} +1 -1
- package/packages/scrabble-solver/.next/static/chunks/pages/_app-c58cfa832b76cc87.js +24 -0
- package/packages/scrabble-solver/.next/static/chunks/pages/index-146039f501e49c08.js +1 -0
- package/packages/scrabble-solver/.next/static/css/4482c4a0064d3807.css +1 -0
- package/packages/scrabble-solver/.next/static/css/9d1013ec684361b9.css +1 -0
- package/packages/scrabble-solver/.next/trace +55 -55
- package/packages/scrabble-solver/package-lock.json +37 -37
- package/packages/scrabble-solver/package.json +13 -13
- package/packages/scrabble-solver/src/components/Board/components/Cell/Button.tsx +1 -0
- package/packages/scrabble-solver/src/components/Board/components/Cell/Cell.tsx +6 -0
- package/packages/scrabble-solver/src/components/Board/components/Cell/CellPure.tsx +10 -1
- package/packages/scrabble-solver/src/components/Button/Button.module.scss +2 -5
- package/packages/scrabble-solver/src/components/Button/Button.tsx +1 -0
- package/packages/scrabble-solver/src/components/Button/Link.tsx +1 -0
- package/packages/scrabble-solver/src/components/{SquareButton/SquareButton.module.scss → IconButton/IconButton.module.scss} +2 -3
- package/packages/scrabble-solver/src/components/{SquareButton/SquareButton.tsx → IconButton/IconButton.tsx} +7 -6
- package/packages/scrabble-solver/src/components/{SquareButton → IconButton}/Link.tsx +3 -2
- package/packages/scrabble-solver/src/components/IconButton/index.ts +1 -0
- package/packages/scrabble-solver/src/components/Logo/Logo.svg +44 -15
- package/packages/scrabble-solver/src/components/Modal/Modal.tsx +3 -2
- package/packages/scrabble-solver/src/components/NavButtons/NavButtons.tsx +37 -9
- package/packages/scrabble-solver/src/components/Rack/RackTile.tsx +5 -0
- package/packages/scrabble-solver/src/components/Results/HeaderButton.tsx +1 -0
- package/packages/scrabble-solver/src/components/Results/Result.tsx +1 -0
- package/packages/scrabble-solver/src/components/Results/Results.module.scss +0 -1
- package/packages/scrabble-solver/src/components/Results/SolveButton.tsx +1 -0
- package/packages/scrabble-solver/src/components/Solver/Solver.module.scss +11 -19
- package/packages/scrabble-solver/src/components/Solver/Solver.tsx +18 -36
- package/packages/scrabble-solver/src/components/Solver/components/EmptyState/EmptyState.module.scss +0 -2
- package/packages/scrabble-solver/src/components/Solver/components/InsertButton/InsertButton.module.scss +15 -0
- package/packages/scrabble-solver/src/components/Solver/components/{ApplyButton/ApplyButton.tsx → InsertButton/InsertButton.tsx} +10 -6
- package/packages/scrabble-solver/src/components/Solver/components/InsertButton/index.ts +1 -0
- package/packages/scrabble-solver/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.module.scss +143 -0
- package/packages/scrabble-solver/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.tsx +98 -0
- package/packages/scrabble-solver/src/components/Solver/components/SolveButton/SolveButton.tsx +11 -1
- package/packages/scrabble-solver/src/components/Solver/components/index.ts +2 -1
- package/packages/scrabble-solver/src/components/Tile/Tile.tsx +3 -0
- package/packages/scrabble-solver/src/components/Tile/TilePure.tsx +3 -0
- package/packages/scrabble-solver/src/components/Tooltip/useTooltip.tsx +14 -2
- package/packages/scrabble-solver/src/components/index.ts +1 -2
- package/packages/scrabble-solver/src/i18n/de.json +5 -0
- package/packages/scrabble-solver/src/i18n/en.json +5 -0
- package/packages/scrabble-solver/src/i18n/es.json +5 -0
- package/packages/scrabble-solver/src/i18n/fa.json +5 -0
- package/packages/scrabble-solver/src/i18n/fr.json +5 -0
- package/packages/scrabble-solver/src/i18n/pl.json +5 -0
- package/packages/scrabble-solver/src/icons/ArrowDown.svg +1 -1
- package/packages/scrabble-solver/src/icons/ArrowLeft.svg +1 -1
- package/packages/scrabble-solver/src/icons/ArrowRight.svg +1 -1
- package/packages/scrabble-solver/src/icons/ArrowUp.svg +1 -1
- package/packages/scrabble-solver/src/icons/ChevronDown.svg +1 -1
- package/packages/scrabble-solver/src/icons/ChevronLeft.svg +4 -0
- package/packages/scrabble-solver/src/icons/ChevronRight.svg +4 -0
- package/packages/scrabble-solver/src/icons/List.svg +1 -1
- package/packages/scrabble-solver/src/icons/index.ts +2 -0
- package/packages/scrabble-solver/src/modals/MenuModal/MenuModal.module.scss +2 -3
- package/packages/scrabble-solver/src/modals/MenuModal/MenuModal.tsx +9 -3
- package/packages/scrabble-solver/src/modals/RemainingTilesModal/components/Character/Character.tsx +1 -0
- package/packages/scrabble-solver/src/modals/SettingsModal/components/AutoGroupTilesSetting/AutoGroupTilesSetting.tsx +2 -2
- package/packages/scrabble-solver/src/modals/SettingsModal/components/ConfigSetting/ConfigSetting.tsx +2 -2
- package/packages/scrabble-solver/src/modals/SettingsModal/components/LocaleSetting/LocaleSetting.tsx +2 -2
- package/packages/scrabble-solver/src/pages/api/dictionary/[locale]/[word].ts +3 -1
- package/packages/scrabble-solver/src/pages/index.tsx +10 -6
- package/packages/scrabble-solver/src/state/sagas.ts +5 -2
- package/packages/scrabble-solver/src/state/useTranslate.ts +5 -2
- package/packages/scrabble-solver/src/styles/mixins.scss +6 -0
- package/packages/scrabble-solver/src/types/index.ts +6 -1
- package/packages/solver/package-lock.json +2 -2
- package/packages/solver/package.json +5 -5
- package/packages/types/package-lock.json +3 -3
- package/packages/types/package.json +2 -2
- package/packages/word-definitions/package-lock.json +8 -8
- package/packages/word-definitions/package.json +2 -2
- package/packages/word-lists/package-lock.json +2 -2
- package/packages/word-lists/package.json +2 -2
- package/packages/scrabble-solver/.next/static/WILX-RgqlLTd4ZoPs8C_E/_buildManifest.js +0 -1
- package/packages/scrabble-solver/.next/static/chunks/144-6768fe9a92865ec8.js +0 -1
- package/packages/scrabble-solver/.next/static/chunks/pages/_app-fa0661b072fc6af9.js +0 -24
- package/packages/scrabble-solver/.next/static/chunks/pages/index-1a6bbb880f28606a.js +0 -1
- package/packages/scrabble-solver/.next/static/css/d80ffccf2315791a.css +0 -1
- package/packages/scrabble-solver/.next/static/css/e737d5d7fbed2434.css +0 -1
- package/packages/scrabble-solver/src/components/ResultCandidatePicker/ResultCandidatePicker.module.scss +0 -76
- package/packages/scrabble-solver/src/components/ResultCandidatePicker/ResultCandidatePicker.tsx +0 -38
- package/packages/scrabble-solver/src/components/Solver/components/ApplyButton/ApplyButton.module.scss +0 -5
- package/packages/scrabble-solver/src/components/Solver/components/ApplyButton/index.ts +0 -1
- package/packages/scrabble-solver/src/components/SquareButton/index.ts +0 -1
- /package/packages/scrabble-solver/.next/static/{WILX-RgqlLTd4ZoPs8C_E → 6RggBFm8kHrh-k1-CG3um}/_ssgManifest.js +0 -0
- /package/packages/scrabble-solver/src/components/{ResultCandidatePicker → Solver/components/ResultCandidatePicker}/index.ts +0 -0
|
@@ -6,7 +6,7 @@ import { CardChecklist, Cog, Eraser, Github, Keyboard, List, Sack } from 'icons'
|
|
|
6
6
|
import { GITHUB_PROJECT_URL } from 'parameters';
|
|
7
7
|
import { selectHasInvalidWords, selectHasOverusedTiles, useTranslate, useTypedSelector } from 'state';
|
|
8
8
|
|
|
9
|
-
import
|
|
9
|
+
import IconButton from '../IconButton';
|
|
10
10
|
|
|
11
11
|
import styles from './NavButtons.module.scss';
|
|
12
12
|
|
|
@@ -37,13 +37,25 @@ const NavButtons: FunctionComponent<Props> = ({
|
|
|
37
37
|
return (
|
|
38
38
|
<div className={styles.navButtons}>
|
|
39
39
|
<div className={styles.group}>
|
|
40
|
-
<
|
|
40
|
+
<IconButton
|
|
41
|
+
aria-label={translate('common.clear')}
|
|
42
|
+
className={styles.button}
|
|
43
|
+
Icon={Eraser}
|
|
44
|
+
tooltip={translate('common.clear')}
|
|
45
|
+
onClick={onClear}
|
|
46
|
+
/>
|
|
41
47
|
</div>
|
|
42
48
|
|
|
43
49
|
<div className={styles.separator} />
|
|
44
50
|
|
|
45
51
|
<div className={styles.group}>
|
|
46
|
-
<
|
|
52
|
+
<IconButton
|
|
53
|
+
aria-label={translate('menu')}
|
|
54
|
+
className={styles.button}
|
|
55
|
+
Icon={List}
|
|
56
|
+
tooltip={translate('menu')}
|
|
57
|
+
onClick={onShowMenu}
|
|
58
|
+
/>
|
|
47
59
|
</div>
|
|
48
60
|
</div>
|
|
49
61
|
);
|
|
@@ -52,13 +64,20 @@ const NavButtons: FunctionComponent<Props> = ({
|
|
|
52
64
|
return (
|
|
53
65
|
<div className={styles.navButtons}>
|
|
54
66
|
<div className={styles.group}>
|
|
55
|
-
<
|
|
67
|
+
<IconButton
|
|
68
|
+
aria-label={translate('common.clear')}
|
|
69
|
+
className={styles.button}
|
|
70
|
+
Icon={Eraser}
|
|
71
|
+
tooltip={translate('common.clear')}
|
|
72
|
+
onClick={onClear}
|
|
73
|
+
/>
|
|
56
74
|
</div>
|
|
57
75
|
|
|
58
76
|
<div className={styles.separator} />
|
|
59
77
|
|
|
60
78
|
<div className={styles.group}>
|
|
61
|
-
<
|
|
79
|
+
<IconButton
|
|
80
|
+
aria-label={translate('remaining-tiles')}
|
|
62
81
|
className={classNames(styles.button, {
|
|
63
82
|
[styles.error]: hasOverusedTiles,
|
|
64
83
|
})}
|
|
@@ -67,7 +86,8 @@ const NavButtons: FunctionComponent<Props> = ({
|
|
|
67
86
|
onClick={onShowRemainingTiles}
|
|
68
87
|
/>
|
|
69
88
|
|
|
70
|
-
<
|
|
89
|
+
<IconButton
|
|
90
|
+
aria-label={translate('words')}
|
|
71
91
|
className={classNames(styles.button, {
|
|
72
92
|
[styles.error]: hasInvalidWords,
|
|
73
93
|
})}
|
|
@@ -80,7 +100,8 @@ const NavButtons: FunctionComponent<Props> = ({
|
|
|
80
100
|
<div className={styles.separator} />
|
|
81
101
|
|
|
82
102
|
<div className={styles.group}>
|
|
83
|
-
<
|
|
103
|
+
<IconButton.Link
|
|
104
|
+
aria-label={translate('github')}
|
|
84
105
|
className={styles.button}
|
|
85
106
|
href={GITHUB_PROJECT_URL}
|
|
86
107
|
Icon={Github}
|
|
@@ -94,7 +115,8 @@ const NavButtons: FunctionComponent<Props> = ({
|
|
|
94
115
|
|
|
95
116
|
<div className={styles.group}>
|
|
96
117
|
{!isTouchDevice && (
|
|
97
|
-
<
|
|
118
|
+
<IconButton
|
|
119
|
+
aria-label={translate('keyMap')}
|
|
98
120
|
className={styles.button}
|
|
99
121
|
Icon={Keyboard}
|
|
100
122
|
tooltip={translate('keyMap')}
|
|
@@ -102,7 +124,13 @@ const NavButtons: FunctionComponent<Props> = ({
|
|
|
102
124
|
/>
|
|
103
125
|
)}
|
|
104
126
|
|
|
105
|
-
<
|
|
127
|
+
<IconButton
|
|
128
|
+
aria-label={translate('settings')}
|
|
129
|
+
className={styles.button}
|
|
130
|
+
Icon={Cog}
|
|
131
|
+
tooltip={translate('settings')}
|
|
132
|
+
onClick={onShowSettings}
|
|
133
|
+
/>
|
|
106
134
|
</div>
|
|
107
135
|
</div>
|
|
108
136
|
);
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
selectCharacterIsValid,
|
|
19
19
|
selectCharacterPoints,
|
|
20
20
|
selectConfig,
|
|
21
|
+
selectLocale,
|
|
21
22
|
useTranslate,
|
|
22
23
|
useTypedSelector,
|
|
23
24
|
} from 'state';
|
|
@@ -49,6 +50,7 @@ const RackTile: FunctionComponent<Props> = ({
|
|
|
49
50
|
}) => {
|
|
50
51
|
const dispatch = useDispatch();
|
|
51
52
|
const translate = useTranslate();
|
|
53
|
+
const locale = useTypedSelector(selectLocale);
|
|
52
54
|
const config = useTypedSelector(selectConfig);
|
|
53
55
|
const points = useTypedSelector((state) => selectCharacterPoints(state, character));
|
|
54
56
|
const isValid = useTypedSelector((state) => selectCharacterIsValid(state, character));
|
|
@@ -91,6 +93,9 @@ const RackTile: FunctionComponent<Props> = ({
|
|
|
91
93
|
|
|
92
94
|
return (
|
|
93
95
|
<Tile
|
|
96
|
+
aria-label={translate('rack.tile.location', {
|
|
97
|
+
index: (index + 1).toLocaleString(locale),
|
|
98
|
+
})}
|
|
94
99
|
autoFocus={index === 0}
|
|
95
100
|
className={styles.tile}
|
|
96
101
|
character={character === null ? undefined : character}
|
|
@@ -47,6 +47,10 @@
|
|
|
47
47
|
margin: 0 auto;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
.submitInput {
|
|
51
|
+
display: none;
|
|
52
|
+
}
|
|
53
|
+
|
|
50
54
|
.column {
|
|
51
55
|
display: flex;
|
|
52
56
|
flex-direction: column;
|
|
@@ -58,6 +62,11 @@
|
|
|
58
62
|
}
|
|
59
63
|
}
|
|
60
64
|
|
|
65
|
+
.resultsContainer {
|
|
66
|
+
flex: 1;
|
|
67
|
+
position: relative;
|
|
68
|
+
}
|
|
69
|
+
|
|
61
70
|
.dictionary {
|
|
62
71
|
display: flex;
|
|
63
72
|
flex-direction: column;
|
|
@@ -72,11 +81,6 @@
|
|
|
72
81
|
flex: 0 0 auto;
|
|
73
82
|
}
|
|
74
83
|
|
|
75
|
-
.resultsContainer {
|
|
76
|
-
flex: 1;
|
|
77
|
-
position: relative;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
84
|
.bottomContainer {
|
|
81
85
|
flex: 0 0 auto;
|
|
82
86
|
display: flex;
|
|
@@ -101,24 +105,12 @@
|
|
|
101
105
|
border: var(--border);
|
|
102
106
|
}
|
|
103
107
|
|
|
104
|
-
.submitInput {
|
|
105
|
-
display: none;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
108
|
.controls {
|
|
109
109
|
width: 100%;
|
|
110
|
-
display: flex;
|
|
111
|
-
align-items: flex-start;
|
|
112
|
-
justify-content: center;
|
|
113
|
-
gap: var(--spacing--l);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
.resultCandidatePicker {
|
|
117
|
-
flex: 1;
|
|
118
110
|
}
|
|
119
111
|
|
|
120
|
-
.
|
|
121
|
-
|
|
112
|
+
.emptyState {
|
|
113
|
+
margin-top: var(--spacing);
|
|
122
114
|
}
|
|
123
115
|
|
|
124
116
|
.solve {
|
|
@@ -32,11 +32,10 @@ import Board from '../Board';
|
|
|
32
32
|
import Dictionary from '../Dictionary';
|
|
33
33
|
import DictionaryInput from '../DictionaryInput';
|
|
34
34
|
import Rack from '../Rack';
|
|
35
|
-
import ResultCandidatePicker from '../ResultCandidatePicker';
|
|
36
35
|
import Results from '../Results';
|
|
37
36
|
import Well from '../Well';
|
|
38
37
|
|
|
39
|
-
import {
|
|
38
|
+
import { EmptyState, ResultCandidatePicker, SolveButton } from './components';
|
|
40
39
|
import styles from './Solver.module.scss';
|
|
41
40
|
|
|
42
41
|
interface Props {
|
|
@@ -55,14 +54,19 @@ const Solver: FunctionComponent<Props> = ({ className, height, width, onShowResu
|
|
|
55
54
|
const [resultsContainerRef, { width: resultsContainerWidth }] = useMeasure<HTMLDivElement>();
|
|
56
55
|
const isLessThanXl = useMediaQuery('<xl');
|
|
57
56
|
const isLessThanL = useMediaQuery('<l');
|
|
57
|
+
const isLessThanM = useMediaQuery('<m');
|
|
58
58
|
const componentsSpacing = isLessThanXl ? COMPONENTS_SPACING_SMALL : COMPONENTS_SPACING;
|
|
59
59
|
const maxBoardWidth = width - resultsContainerWidth - (isLessThanL ? 0 : componentsSpacing) - 2 * componentsSpacing;
|
|
60
|
-
const maxBoardHeight = Math.max(
|
|
60
|
+
const maxBoardHeight = Math.max(
|
|
61
|
+
height - bottomContainerHeight,
|
|
62
|
+
isLessThanL ? 0 : COLUMN_MIN_HEIGHT,
|
|
63
|
+
isLessThanM ? Number.POSITIVE_INFINITY : 0,
|
|
64
|
+
);
|
|
61
65
|
const config = useTypedSelector(selectConfig);
|
|
62
|
-
const
|
|
66
|
+
const error = useTypedSelector(selectSolveError);
|
|
63
67
|
const isOutdated = useTypedSelector(selectAreResultsOutdated);
|
|
68
|
+
const resultCandidate = useTypedSelector(selectResultCandidate);
|
|
64
69
|
const allResults = useTypedSelector(selectSortedResults);
|
|
65
|
-
const error = useTypedSelector(selectSolveError);
|
|
66
70
|
const results = useTypedSelector(selectSortedFilteredResults);
|
|
67
71
|
const [bestResult] = results || [];
|
|
68
72
|
const cellWidth = (maxBoardWidth - (config.boardWidth + 1) * BORDER_WIDTH) / config.boardWidth;
|
|
@@ -120,10 +124,10 @@ const Solver: FunctionComponent<Props> = ({ className, height, width, onShowResu
|
|
|
120
124
|
};
|
|
121
125
|
|
|
122
126
|
useEffect(() => {
|
|
123
|
-
if (bestResult) {
|
|
127
|
+
if (isLessThanL && bestResult && !isOutdated) {
|
|
124
128
|
dispatch(resultsSlice.actions.changeResultCandidate(bestResult));
|
|
125
129
|
}
|
|
126
|
-
}, [bestResult, dispatch]);
|
|
130
|
+
}, [bestResult, dispatch, isLessThanL, isOutdated]);
|
|
127
131
|
|
|
128
132
|
return (
|
|
129
133
|
<div className={classNames(styles.solver, className)}>
|
|
@@ -160,41 +164,19 @@ const Solver: FunctionComponent<Props> = ({ className, height, width, onShowResu
|
|
|
160
164
|
|
|
161
165
|
{isLessThanL && (
|
|
162
166
|
<div className={styles.controls} style={{ maxWidth: maxControlsWidth }}>
|
|
163
|
-
{
|
|
164
|
-
|
|
167
|
+
<ResultCandidatePicker onResultClick={onShowResults} />
|
|
168
|
+
|
|
169
|
+
{error && (
|
|
170
|
+
<EmptyState className={styles.emptyState} variant="error">
|
|
165
171
|
{error.message}
|
|
166
172
|
</EmptyState>
|
|
167
173
|
)}
|
|
168
174
|
|
|
169
|
-
{
|
|
170
|
-
<EmptyState variant="
|
|
171
|
-
{translate('results.empty-state.
|
|
175
|
+
{allResults && allResults.length === 0 && !isOutdated && (
|
|
176
|
+
<EmptyState className={styles.emptyState} variant="warning">
|
|
177
|
+
{translate('results.empty-state.no-results')}
|
|
172
178
|
</EmptyState>
|
|
173
179
|
)}
|
|
174
|
-
|
|
175
|
-
{typeof error === 'undefined' && typeof results !== 'undefined' && typeof allResults !== 'undefined' && (
|
|
176
|
-
<>
|
|
177
|
-
{(isOutdated || !resultCandidate) && (
|
|
178
|
-
<EmptyState variant="info" onClick={onShowResults}>
|
|
179
|
-
{translate('results.empty-state.outdated')}
|
|
180
|
-
</EmptyState>
|
|
181
|
-
)}
|
|
182
|
-
|
|
183
|
-
{!isOutdated && allResults.length === 0 && (
|
|
184
|
-
<EmptyState variant="warning" onClick={onShowResults}>
|
|
185
|
-
{translate('results.empty-state.no-results')}
|
|
186
|
-
</EmptyState>
|
|
187
|
-
)}
|
|
188
|
-
|
|
189
|
-
{!isOutdated && allResults.length > 0 && resultCandidate && (
|
|
190
|
-
<ResultCandidatePicker className={styles.resultCandidatePicker} onClick={onShowResults} />
|
|
191
|
-
)}
|
|
192
|
-
</>
|
|
193
|
-
)}
|
|
194
|
-
|
|
195
|
-
{allResults && allResults.length > 0 && !isOutdated && resultCandidate && (
|
|
196
|
-
<ApplyButton className={classNames(styles.submit, styles.apply)} />
|
|
197
|
-
)}
|
|
198
180
|
</div>
|
|
199
181
|
)}
|
|
200
182
|
</div>
|
|
@@ -1,20 +1,23 @@
|
|
|
1
|
+
import classNames from 'classnames';
|
|
1
2
|
import { FunctionComponent } from 'react';
|
|
2
3
|
import { useDispatch } from 'react-redux';
|
|
3
4
|
|
|
4
5
|
import { Check } from 'icons';
|
|
5
|
-
import { resultsSlice, selectResultCandidate, useTypedSelector } from 'state';
|
|
6
|
+
import { resultsSlice, selectAreResultsOutdated, selectResultCandidate, useTranslate, useTypedSelector } from 'state';
|
|
6
7
|
|
|
7
8
|
import Button from '../../../Button';
|
|
8
9
|
|
|
9
|
-
import styles from './
|
|
10
|
+
import styles from './InsertButton.module.scss';
|
|
10
11
|
|
|
11
12
|
interface Props {
|
|
12
13
|
className?: string;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
const
|
|
16
|
+
const InsertButton: FunctionComponent<Props> = ({ className }) => {
|
|
16
17
|
const dispatch = useDispatch();
|
|
18
|
+
const translate = useTranslate();
|
|
17
19
|
const resultCandidate = useTypedSelector(selectResultCandidate);
|
|
20
|
+
const isOutdated = useTypedSelector(selectAreResultsOutdated);
|
|
18
21
|
|
|
19
22
|
const handleClick = () => {
|
|
20
23
|
if (resultCandidate) {
|
|
@@ -24,8 +27,9 @@ const ApplyButton: FunctionComponent<Props> = ({ className }) => {
|
|
|
24
27
|
|
|
25
28
|
return (
|
|
26
29
|
<Button
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
aria-label={translate('results.insert')}
|
|
31
|
+
className={classNames(styles.insertButton, className)}
|
|
32
|
+
disabled={isOutdated || !resultCandidate}
|
|
29
33
|
Icon={Check}
|
|
30
34
|
iconClassName={styles.icon}
|
|
31
35
|
type="submit"
|
|
@@ -35,4 +39,4 @@ const ApplyButton: FunctionComponent<Props> = ({ className }) => {
|
|
|
35
39
|
);
|
|
36
40
|
};
|
|
37
41
|
|
|
38
|
-
export default
|
|
42
|
+
export default InsertButton;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './InsertButton';
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
@import 'styles/mixins';
|
|
2
|
+
|
|
3
|
+
.resultCandidatePicker {
|
|
4
|
+
display: flex;
|
|
5
|
+
align-items: center;
|
|
6
|
+
gap: var(--spacing--l);
|
|
7
|
+
|
|
8
|
+
@include media('<xs') {
|
|
9
|
+
gap: var(--spacing--m);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.buttons {
|
|
14
|
+
flex: 0 0 auto;
|
|
15
|
+
display: flex;
|
|
16
|
+
border-radius: var(--border--radius);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.button {
|
|
20
|
+
padding: var(--spacing--m);
|
|
21
|
+
|
|
22
|
+
&:focus-within {
|
|
23
|
+
z-index: 1;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
&:first-child {
|
|
27
|
+
border-right: none;
|
|
28
|
+
border-top-right-radius: 0;
|
|
29
|
+
border-bottom-right-radius: 0;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
&:last-child {
|
|
33
|
+
border-top-left-radius: 0;
|
|
34
|
+
border-bottom-left-radius: 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
[dir='rtl'] & {
|
|
38
|
+
transform: rotate(180deg);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.resultCandidate {
|
|
43
|
+
@include button-reset;
|
|
44
|
+
@include focus-effect;
|
|
45
|
+
|
|
46
|
+
flex: 1;
|
|
47
|
+
display: flex;
|
|
48
|
+
align-items: center;
|
|
49
|
+
min-width: 0;
|
|
50
|
+
width: 100%;
|
|
51
|
+
border: var(--border);
|
|
52
|
+
border-radius: var(--border--radius);
|
|
53
|
+
background-color: white;
|
|
54
|
+
box-shadow: var(--box-shadow);
|
|
55
|
+
cursor: pointer;
|
|
56
|
+
|
|
57
|
+
&[disabled] {
|
|
58
|
+
@include disabled;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.content {
|
|
63
|
+
display: flex;
|
|
64
|
+
align-items: center;
|
|
65
|
+
width: 100%;
|
|
66
|
+
overflow: hidden;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.points,
|
|
70
|
+
.word {
|
|
71
|
+
padding: var(--spacing--m) var(--spacing--l);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.points {
|
|
75
|
+
flex: 0 0 auto;
|
|
76
|
+
font-weight: bold;
|
|
77
|
+
|
|
78
|
+
[dir='ltr'] & {
|
|
79
|
+
border-right: var(--border);
|
|
80
|
+
|
|
81
|
+
@include media('<xs') {
|
|
82
|
+
border-right: none;
|
|
83
|
+
padding-right: var(--spacing--xs);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
[dir='rtl'] & {
|
|
88
|
+
border-left: var(--border);
|
|
89
|
+
|
|
90
|
+
@include media('<xs') {
|
|
91
|
+
border-left: none;
|
|
92
|
+
padding-left: var(--spacing--xs);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.word {
|
|
98
|
+
@include ellipsis;
|
|
99
|
+
|
|
100
|
+
flex: 1;
|
|
101
|
+
text-transform: uppercase;
|
|
102
|
+
text-align: center;
|
|
103
|
+
white-space: pre-wrap;
|
|
104
|
+
|
|
105
|
+
[dir='ltr'] & {
|
|
106
|
+
@include media('<xs') {
|
|
107
|
+
padding: var(--spacing--m);
|
|
108
|
+
text-align: left;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
[dir='rtl'] & {
|
|
113
|
+
@include media('<xs') {
|
|
114
|
+
padding: var(--spacing--m);
|
|
115
|
+
text-align: right;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.iconContainer {
|
|
121
|
+
flex: 0 0 auto;
|
|
122
|
+
display: flex;
|
|
123
|
+
|
|
124
|
+
[dir='ltr'] & {
|
|
125
|
+
padding-right: var(--spacing--l);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
[dir='rtl'] & {
|
|
129
|
+
padding-left: var(--spacing--l);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.icon {
|
|
134
|
+
$size: 20px;
|
|
135
|
+
|
|
136
|
+
width: $size;
|
|
137
|
+
height: $size;
|
|
138
|
+
color: var(--color--inactive);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.insert {
|
|
142
|
+
flex: 0 0 auto;
|
|
143
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import classNames from 'classnames';
|
|
2
|
+
import { FunctionComponent, HTMLProps, MouseEventHandler } from 'react';
|
|
3
|
+
import { useDispatch } from 'react-redux';
|
|
4
|
+
|
|
5
|
+
import { ChevronDown, ChevronLeft, ChevronRight } from 'icons';
|
|
6
|
+
import {
|
|
7
|
+
resultsSlice,
|
|
8
|
+
selectAreResultsOutdated,
|
|
9
|
+
selectLocale,
|
|
10
|
+
selectResultCandidate,
|
|
11
|
+
selectSortedResults,
|
|
12
|
+
useTranslate,
|
|
13
|
+
useTypedSelector,
|
|
14
|
+
} from 'state';
|
|
15
|
+
|
|
16
|
+
import Button from '../../../Button';
|
|
17
|
+
import InsertButton from '../InsertButton';
|
|
18
|
+
|
|
19
|
+
import styles from './ResultCandidatePicker.module.scss';
|
|
20
|
+
|
|
21
|
+
interface Props extends HTMLProps<HTMLDivElement> {
|
|
22
|
+
onResultClick: MouseEventHandler<HTMLButtonElement>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const ResultCandidatePicker: FunctionComponent<Props> = ({ className, onResultClick, ...props }) => {
|
|
26
|
+
const dispatch = useDispatch();
|
|
27
|
+
const translate = useTranslate();
|
|
28
|
+
const locale = useTypedSelector(selectLocale);
|
|
29
|
+
const isOutdated = useTypedSelector(selectAreResultsOutdated);
|
|
30
|
+
const sortedResults = useTypedSelector(selectSortedResults);
|
|
31
|
+
const results = sortedResults || [];
|
|
32
|
+
const resultCandidate = useTypedSelector(selectResultCandidate);
|
|
33
|
+
const index = resultCandidate ? results.findIndex((result) => result.id === resultCandidate.id) : -1;
|
|
34
|
+
const disabled = isOutdated || !resultCandidate;
|
|
35
|
+
const isPreviousDisabled = index <= 0 || disabled;
|
|
36
|
+
const isNextDisabled = index >= results.length - 1 || disabled;
|
|
37
|
+
|
|
38
|
+
const handleNextClick = () => {
|
|
39
|
+
if (!isNextDisabled) {
|
|
40
|
+
const nextResult = results[index + 1];
|
|
41
|
+
dispatch(resultsSlice.actions.changeResultCandidate(nextResult));
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const handlePreviousClick = () => {
|
|
46
|
+
if (!isPreviousDisabled) {
|
|
47
|
+
const previousResult = results[index - 1];
|
|
48
|
+
dispatch(resultsSlice.actions.changeResultCandidate(previousResult));
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<div className={classNames(styles.resultCandidatePicker, className)} {...props}>
|
|
54
|
+
<div className={styles.buttons}>
|
|
55
|
+
<Button
|
|
56
|
+
aria-label={translate('common.previous')}
|
|
57
|
+
className={styles.button}
|
|
58
|
+
disabled={isPreviousDisabled}
|
|
59
|
+
Icon={ChevronLeft}
|
|
60
|
+
onClick={handlePreviousClick}
|
|
61
|
+
/>
|
|
62
|
+
|
|
63
|
+
<Button
|
|
64
|
+
aria-label={translate('common.next')}
|
|
65
|
+
className={styles.button}
|
|
66
|
+
disabled={isNextDisabled}
|
|
67
|
+
Icon={ChevronRight}
|
|
68
|
+
onClick={handleNextClick}
|
|
69
|
+
/>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<button
|
|
73
|
+
aria-label={translate('results')}
|
|
74
|
+
className={styles.resultCandidate}
|
|
75
|
+
disabled={disabled}
|
|
76
|
+
type="button"
|
|
77
|
+
onClick={onResultClick}
|
|
78
|
+
>
|
|
79
|
+
{resultCandidate && (
|
|
80
|
+
<>
|
|
81
|
+
<div className={styles.points}>{resultCandidate.points.toLocaleString(locale)}</div>
|
|
82
|
+
<div className={styles.word}>{resultCandidate.word}</div>
|
|
83
|
+
</>
|
|
84
|
+
)}
|
|
85
|
+
|
|
86
|
+
{!resultCandidate && <div className={styles.word}> </div>}
|
|
87
|
+
|
|
88
|
+
<div className={styles.iconContainer}>
|
|
89
|
+
<ChevronDown className={styles.icon} />
|
|
90
|
+
</div>
|
|
91
|
+
</button>
|
|
92
|
+
|
|
93
|
+
<InsertButton className={styles.insert} />
|
|
94
|
+
</div>
|
|
95
|
+
);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export default ResultCandidatePicker;
|
package/packages/scrabble-solver/src/components/Solver/components/SolveButton/SolveButton.tsx
CHANGED
|
@@ -4,7 +4,14 @@ import { useDispatch } from 'react-redux';
|
|
|
4
4
|
|
|
5
5
|
import { Search } from 'icons';
|
|
6
6
|
import { noop } from 'lib';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
selectAreResultsOutdated,
|
|
9
|
+
selectIsLoading,
|
|
10
|
+
selectRack,
|
|
11
|
+
solveSlice,
|
|
12
|
+
useTranslate,
|
|
13
|
+
useTypedSelector,
|
|
14
|
+
} from 'state';
|
|
8
15
|
|
|
9
16
|
import Button from '../../../Button';
|
|
10
17
|
|
|
@@ -17,6 +24,7 @@ interface Props {
|
|
|
17
24
|
|
|
18
25
|
const SolveButton: FunctionComponent<Props> = ({ className, onClick = noop }) => {
|
|
19
26
|
const dispatch = useDispatch();
|
|
27
|
+
const translate = useTranslate();
|
|
20
28
|
const isLoading = useTypedSelector(selectIsLoading);
|
|
21
29
|
const isOutdated = useTypedSelector(selectAreResultsOutdated);
|
|
22
30
|
const rack = useTypedSelector(selectRack);
|
|
@@ -29,10 +37,12 @@ const SolveButton: FunctionComponent<Props> = ({ className, onClick = noop }) =>
|
|
|
29
37
|
|
|
30
38
|
return (
|
|
31
39
|
<Button
|
|
40
|
+
aria-label={translate('results.solve')}
|
|
32
41
|
className={classNames(styles.solveButton, className)}
|
|
33
42
|
disabled={isLoading || !isOutdated || !hasTiles}
|
|
34
43
|
Icon={Search}
|
|
35
44
|
iconClassName={styles.icon}
|
|
45
|
+
tooltip={translate('results.solve')}
|
|
36
46
|
type="submit"
|
|
37
47
|
variant="primary"
|
|
38
48
|
onClick={handleClick}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
export { default as ApplyButton } from './ApplyButton';
|
|
2
1
|
export { default as EmptyState } from './EmptyState';
|
|
2
|
+
export { default as InsertButton } from './InsertButton';
|
|
3
|
+
export { default as ResultCandidatePicker } from './ResultCandidatePicker';
|
|
3
4
|
export { default as SolveButton } from './SolveButton';
|