@scrabble-solver/scrabble-solver 2.10.11 → 2.11.1
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 +12 -12
- 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/required-server-files.json +1 -1
- package/.next/routes-manifest.json +1 -1
- package/.next/server/chunks/{176.js → 277.js} +956 -930
- package/.next/server/chunks/{290.js → 417.js} +3 -3
- package/.next/server/chunks/50.js +371 -343
- package/.next/server/chunks/664.js +15 -15
- package/.next/server/chunks/859.js +17 -10
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/next-font-manifest.js +1 -0
- 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 +4 -28
- package/.next/server/pages/_app.js.nft.json +1 -1
- package/.next/server/pages/_document.js +2 -2
- package/.next/server/pages/_document.js.nft.json +1 -1
- package/.next/server/pages/_error.js +4 -4
- package/.next/server/pages/api/dictionary/[locale]/[word].js +4 -4
- package/.next/server/pages/api/dictionary/[locale]/[word].js.nft.json +1 -1
- package/.next/server/pages/api/dictionary/[locale].js +3 -3
- package/.next/server/pages/api/dictionary/[locale].js.nft.json +1 -1
- package/.next/server/pages/api/solve.js +9 -11
- package/.next/server/pages/api/solve.js.nft.json +1 -1
- package/.next/server/pages/api/verify.js +3 -3
- package/.next/server/pages/api/verify.js.nft.json +1 -1
- package/.next/server/pages/api/visit.js +3 -3
- 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 +256 -210
- package/.next/server/pages/index.js.nft.json +1 -1
- package/.next/server/pages/index.json +1 -1
- package/.next/static/chunks/main-0ecb9ccfcb6c9b24.js +1 -0
- package/.next/static/chunks/pages/404-448ba28510855455.js +1 -0
- package/.next/static/chunks/pages/_app-270526803bc274eb.js +28 -0
- package/.next/static/chunks/pages/{_error-8353112a01355ec2.js → _error-54de1933a164a1ff.js} +1 -1
- package/.next/static/chunks/pages/index-c6e7754ccf3532df.js +1 -0
- package/.next/static/css/ad39b36eab07e613.css +1 -0
- package/.next/static/css/e5803e581e4c0451.css +2 -0
- package/.next/static/esK8DG-6aS5V7QFRtR3YE/_buildManifest.js +1 -0
- package/.next/trace +53 -55
- package/package.json +12 -17
- package/src/components/{Solver/components/EmptyState/EmptyState.module.scss → Alert/Alert.module.scss} +11 -7
- package/src/components/{Solver/components/EmptyState/EmptyState.tsx → Alert/Alert.tsx} +8 -6
- package/src/components/Alert/index.ts +1 -0
- package/src/components/Board/Board.module.scss +55 -0
- package/src/components/Board/BoardPure.tsx +4 -0
- package/src/components/Board/components/Cell/Cell.module.scss +42 -0
- package/src/components/Board/components/Cell/Cell.tsx +12 -0
- package/src/components/Board/components/Cell/CellPure.tsx +12 -0
- package/src/components/Board/hooks/useGrid.ts +8 -24
- package/src/components/Dictionary/Dictionary.module.scss +17 -8
- package/src/components/Dictionary/Dictionary.tsx +5 -5
- package/src/components/DictionaryInput/DictionaryInput.module.scss +1 -0
- package/src/components/EmptyState/EmptyState.module.scss +2 -1
- package/src/components/EmptyState/EmptyState.tsx +1 -2
- package/src/components/Loading/Loading.module.scss +1 -1
- package/src/components/Loading/Loading.tsx +1 -1
- package/src/components/Logo/Logo.tsx +5 -0
- package/src/components/Modal/Modal.module.scss +2 -1
- package/src/components/NavButtons/NavButtons.tsx +4 -5
- package/src/components/PlainTiles/PlainTiles.module.scss +1 -1
- package/src/components/PlainTiles/Tile.tsx +3 -3
- package/src/components/Rack/Rack.module.scss +25 -0
- package/src/components/Rack/Rack.tsx +5 -4
- package/src/components/Rack/RackTile.tsx +6 -13
- package/src/components/Results/Results.module.scss +33 -2
- package/src/components/Results/Results.tsx +11 -11
- package/src/components/ResultsInput/ResultsInput.module.scss +1 -0
- package/src/components/Solver/Solver.module.scss +6 -4
- package/src/components/Solver/Solver.tsx +16 -28
- package/src/components/Solver/components/FloatingSolveButton/FloatingSolveButton.module.scss +7 -0
- package/src/components/Solver/components/{SolveButton/SolveButton.tsx → FloatingSolveButton/FloatingSolveButton.tsx} +6 -6
- package/src/components/Solver/components/FloatingSolveButton/index.ts +1 -0
- package/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.module.scss +19 -4
- package/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.tsx +13 -1
- package/src/components/Solver/components/index.ts +1 -2
- package/src/components/Spinner/Spinner.module.scss +11 -0
- package/src/components/Spinner/Spinner.tsx +19 -0
- package/src/components/Spinner/index.ts +1 -0
- package/src/components/Tile/Tile.module.scss +14 -2
- package/src/components/Tile/Tile.tsx +5 -5
- package/src/components/Tooltip/Tooltip.module.scss +1 -72
- package/src/components/Tooltip/useTooltip.tsx +25 -35
- package/src/components/index.ts +2 -0
- package/src/hooks/index.ts +1 -1
- package/src/hooks/useAppLayout.ts +29 -0
- package/src/i18n/de.json +1 -0
- package/src/i18n/en.json +1 -0
- package/src/i18n/es.json +1 -0
- package/src/i18n/fa.json +1 -0
- package/src/i18n/fr.json +1 -0
- package/src/i18n/index.ts +1 -1
- package/src/i18n/pl.json +1 -0
- package/src/lib/index.ts +0 -1
- package/src/modals/DictionaryModal/DictionaryModal.module.scss +23 -0
- package/src/modals/DictionaryModal/DictionaryModal.tsx +27 -0
- package/src/modals/DictionaryModal/index.ts +1 -0
- package/src/modals/KeyMapModal/KeyMapModal.tsx +5 -21
- package/src/modals/KeyMapModal/components/Mapping/Mapping.module.scss +4 -0
- package/src/modals/MenuModal/MenuModal.tsx +7 -1
- package/src/modals/RemainingTilesModal/components/Character/Character.module.scss +1 -0
- package/src/modals/ResultsModal/ResultsModal.module.scss +19 -0
- package/src/modals/ResultsModal/ResultsModal.tsx +8 -7
- package/src/modals/SettingsModal/components/LocaleSetting/LocaleSetting.module.scss +2 -0
- package/src/modals/WordsModal/WordsModal.module.scss +8 -1
- package/src/modals/index.ts +1 -0
- package/src/pages/api/solve.ts +9 -10
- package/src/pages/index.tsx +20 -15
- package/src/state/createAppStore.ts +26 -10
- package/src/state/localStorage.ts +0 -9
- package/src/state/types.ts +20 -2
- package/src/styles/animations.scss +10 -0
- package/src/styles/global.scss +1 -1
- package/src/styles/mixins.scss +22 -0
- package/src/styles/variables.scss +17 -2
- package/src/types/index.ts +1 -0
- package/.next/server/font-loader-manifest.js +0 -1
- package/.next/static/chunks/main-74c4d6b2b5c362f3.js +0 -1
- package/.next/static/chunks/pages/404-6c1a6e3251710371.js +0 -1
- package/.next/static/chunks/pages/_app-d98e480ff8c583de.js +0 -28
- package/.next/static/chunks/pages/index-bd1c7d3872c37456.js +0 -1
- package/.next/static/css/a9b55372a26cf77d.css +0 -1
- package/.next/static/css/b8954b85e2fa5b63.css +0 -2
- package/.next/static/msKI0ZURgJImoGBJvCBiF/_buildManifest.js +0 -1
- package/src/components/Solver/components/EmptyState/index.ts +0 -1
- package/src/components/Solver/components/SolveButton/SolveButton.module.scss +0 -4
- package/src/components/Solver/components/SolveButton/index.ts +0 -1
- package/src/components/Tooltip/constants.ts +0 -28
- package/src/hooks/useUniqueId.ts +0 -9
- package/src/lib/isCtrl.ts +0 -7
- package/src/state/rootReducer.ts +0 -25
- /package/.next/server/{font-loader-manifest.json → next-font-manifest.json} +0 -0
- /package/.next/static/{msKI0ZURgJImoGBJvCBiF → esK8DG-6aS5V7QFRtR3YE}/_ssgManifest.js +0 -0
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
.rack {
|
|
4
4
|
display: flex;
|
|
5
5
|
box-shadow: var(--box-shadow);
|
|
6
|
+
border-radius: var(--border--radius);
|
|
6
7
|
}
|
|
7
8
|
|
|
8
9
|
.tile {
|
|
@@ -15,3 +16,27 @@
|
|
|
15
16
|
z-index: 2;
|
|
16
17
|
}
|
|
17
18
|
}
|
|
19
|
+
|
|
20
|
+
.sharpLeft {
|
|
21
|
+
[dir='ltr'] & {
|
|
22
|
+
border-top-left-radius: 0;
|
|
23
|
+
border-bottom-left-radius: 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
[dir='rtl'] & {
|
|
27
|
+
border-top-right-radius: 0;
|
|
28
|
+
border-bottom-right-radius: 0;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.sharpRight {
|
|
33
|
+
[dir='ltr'] & {
|
|
34
|
+
border-top-right-radius: 0;
|
|
35
|
+
border-bottom-right-radius: 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
[dir='rtl'] & {
|
|
39
|
+
border-top-left-radius: 0;
|
|
40
|
+
border-bottom-left-radius: 0;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
createKeyboardNavigation,
|
|
9
9
|
extractCharacters,
|
|
10
10
|
extractInputValue,
|
|
11
|
-
isCtrl,
|
|
12
11
|
zipCharactersAndTiles,
|
|
13
12
|
} from 'lib';
|
|
14
13
|
import { rackSlice, selectConfig, selectLocale, selectRack, selectResultCandidateTiles, useTypedSelector } from 'state';
|
|
@@ -99,9 +98,7 @@ const Rack: FunctionComponent<Props> = ({ className, tileSize }) => {
|
|
|
99
98
|
changeActiveIndex(1);
|
|
100
99
|
},
|
|
101
100
|
onKeyDown: (event) => {
|
|
102
|
-
if (
|
|
103
|
-
changeActiveIndex(1);
|
|
104
|
-
} else if (event.currentTarget.value === event.key) {
|
|
101
|
+
if (event.currentTarget.value === event.key) {
|
|
105
102
|
// change event did not fire because the same character was typed over the current one
|
|
106
103
|
// but we still want to move the caret
|
|
107
104
|
event.preventDefault();
|
|
@@ -118,6 +115,10 @@ const Rack: FunctionComponent<Props> = ({ className, tileSize }) => {
|
|
|
118
115
|
<RackTile
|
|
119
116
|
activeIndexRef={activeIndexRef}
|
|
120
117
|
character={character}
|
|
118
|
+
className={classNames({
|
|
119
|
+
[styles.sharpLeft]: index !== 0,
|
|
120
|
+
[styles.sharpRight]: index !== tiles.length - 1,
|
|
121
|
+
})}
|
|
121
122
|
index={index}
|
|
122
123
|
inputRef={tilesRefs[index]}
|
|
123
124
|
key={index}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { BLANK } from '@scrabble-solver/constants';
|
|
2
2
|
import { Tile as TileModel } from '@scrabble-solver/types';
|
|
3
|
+
import classNames from 'classnames';
|
|
3
4
|
import {
|
|
4
5
|
ChangeEvent,
|
|
5
6
|
ChangeEventHandler,
|
|
@@ -12,7 +13,7 @@ import {
|
|
|
12
13
|
} from 'react';
|
|
13
14
|
import { useDispatch } from 'react-redux';
|
|
14
15
|
|
|
15
|
-
import { createKeyboardNavigation, extractCharacters, extractInputValue
|
|
16
|
+
import { createKeyboardNavigation, extractCharacters, extractInputValue } from 'lib';
|
|
16
17
|
import {
|
|
17
18
|
rackSlice,
|
|
18
19
|
selectCharacterIsValid,
|
|
@@ -30,6 +31,7 @@ import styles from './Rack.module.scss';
|
|
|
30
31
|
interface Props {
|
|
31
32
|
activeIndexRef: MutableRefObject<number | undefined>;
|
|
32
33
|
character: string | null;
|
|
34
|
+
className?: string;
|
|
33
35
|
index: number;
|
|
34
36
|
inputRef: RefObject<HTMLInputElement>;
|
|
35
37
|
size: number;
|
|
@@ -41,6 +43,7 @@ interface Props {
|
|
|
41
43
|
const RackTile: FunctionComponent<Props> = ({
|
|
42
44
|
activeIndexRef,
|
|
43
45
|
character,
|
|
46
|
+
className,
|
|
44
47
|
index,
|
|
45
48
|
inputRef,
|
|
46
49
|
size,
|
|
@@ -77,17 +80,7 @@ const RackTile: FunctionComponent<Props> = ({
|
|
|
77
80
|
event.preventDefault();
|
|
78
81
|
dispatch(rackSlice.actions.changeCharacter({ character: null, index }));
|
|
79
82
|
},
|
|
80
|
-
onKeyDown
|
|
81
|
-
if (isCtrl(event) && config.isTwoCharacterTilePrefix(event.key)) {
|
|
82
|
-
event.preventDefault();
|
|
83
|
-
event.stopPropagation();
|
|
84
|
-
const twoTilesCharacter = config.getTwoCharacterTileByPrefix(event.key);
|
|
85
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
86
|
-
dispatch(rackSlice.actions.changeCharacter({ character: twoTilesCharacter!, index }));
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
onKeyDown(event);
|
|
90
|
-
},
|
|
83
|
+
onKeyDown,
|
|
91
84
|
});
|
|
92
85
|
}, [index, onKeyDown]);
|
|
93
86
|
|
|
@@ -97,7 +90,7 @@ const RackTile: FunctionComponent<Props> = ({
|
|
|
97
90
|
index: (index + 1).toLocaleString(locale),
|
|
98
91
|
})}
|
|
99
92
|
autoFocus={index === 0}
|
|
100
|
-
className={styles.tile}
|
|
93
|
+
className={classNames(styles.tile, className)}
|
|
101
94
|
character={character === null ? undefined : character}
|
|
102
95
|
highlighted={tile !== null}
|
|
103
96
|
inputRef={inputRef}
|
|
@@ -9,6 +9,7 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
|
|
|
9
9
|
height: 100%;
|
|
10
10
|
background: var(--color--background--element);
|
|
11
11
|
border: var(--border);
|
|
12
|
+
border-radius: var(--border--radius);
|
|
12
13
|
box-shadow: var(--box-shadow);
|
|
13
14
|
font-family: var(--font--family--title);
|
|
14
15
|
}
|
|
@@ -21,8 +22,6 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
|
|
|
21
22
|
flex: 1;
|
|
22
23
|
position: relative;
|
|
23
24
|
height: 100%;
|
|
24
|
-
border-top: var(--border);
|
|
25
|
-
border-bottom: var(--border);
|
|
26
25
|
}
|
|
27
26
|
|
|
28
27
|
.listContainer {
|
|
@@ -31,6 +30,9 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
|
|
|
31
30
|
}
|
|
32
31
|
|
|
33
32
|
.list {
|
|
33
|
+
@include scrollbars;
|
|
34
|
+
|
|
35
|
+
scrollbar-gutter: stable;
|
|
34
36
|
transition: var(--transition);
|
|
35
37
|
|
|
36
38
|
&.outdated {
|
|
@@ -45,6 +47,9 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
|
|
|
45
47
|
align-items: center;
|
|
46
48
|
justify-content: space-between;
|
|
47
49
|
font-weight: 700;
|
|
50
|
+
border-bottom: var(--border);
|
|
51
|
+
border-top-left-radius: inherit;
|
|
52
|
+
border-top-right-radius: inherit;
|
|
48
53
|
}
|
|
49
54
|
|
|
50
55
|
.headerButton {
|
|
@@ -61,6 +66,26 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
|
|
|
61
66
|
background-color: var(--color--primary);
|
|
62
67
|
color: var(--color--primary--opposite);
|
|
63
68
|
}
|
|
69
|
+
|
|
70
|
+
&:first-child {
|
|
71
|
+
[dir='ltr'] & {
|
|
72
|
+
border-top-left-radius: inherit;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
[dir='rtl'] & {
|
|
76
|
+
border-top-right-radius: inherit;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
&:last-child {
|
|
81
|
+
[dir='ltr'] & {
|
|
82
|
+
border-top-right-radius: inherit;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
[dir='rtl'] & {
|
|
86
|
+
border-top-left-radius: inherit;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
64
89
|
}
|
|
65
90
|
|
|
66
91
|
.headerButtonLabel {
|
|
@@ -173,3 +198,9 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
|
|
|
173
198
|
width: $size;
|
|
174
199
|
height: $size;
|
|
175
200
|
}
|
|
201
|
+
|
|
202
|
+
.input {
|
|
203
|
+
border-top: var(--border);
|
|
204
|
+
border-bottom-left-radius: var(--border--radius);
|
|
205
|
+
border-bottom-right-radius: var(--border--radius);
|
|
206
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import classNames from 'classnames';
|
|
2
2
|
import { FunctionComponent, useEffect, useMemo, useState } from 'react';
|
|
3
|
-
import { useMeasure } from 'react-use';
|
|
3
|
+
import { useLatest, useMeasure } from 'react-use';
|
|
4
4
|
import { FixedSizeList } from 'react-window';
|
|
5
5
|
|
|
6
6
|
import { LOCALE_FEATURES } from 'i18n';
|
|
@@ -30,10 +30,11 @@ import useColumns from './useColumns';
|
|
|
30
30
|
|
|
31
31
|
interface Props {
|
|
32
32
|
callbacks: ResultCallbacks;
|
|
33
|
+
className?: string;
|
|
33
34
|
highlightedIndex?: number;
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
const Results: FunctionComponent<Props> = ({ callbacks, highlightedIndex }) => {
|
|
37
|
+
const Results: FunctionComponent<Props> = ({ callbacks, className, highlightedIndex }) => {
|
|
37
38
|
const translate = useTranslate();
|
|
38
39
|
const locale = useTypedSelector(selectLocale);
|
|
39
40
|
const { direction } = LOCALE_FEATURES[locale];
|
|
@@ -48,23 +49,26 @@ const Results: FunctionComponent<Props> = ({ callbacks, highlightedIndex }) => {
|
|
|
48
49
|
const [listRef, setListRef] = useState<FixedSizeList<ResultData> | null>(null);
|
|
49
50
|
const columns = useColumns();
|
|
50
51
|
const scrollToIndex = typeof highlightedIndex === 'number' ? highlightedIndex : 0;
|
|
52
|
+
const scrollToIndexRef = useLatest(scrollToIndex);
|
|
53
|
+
const hasResults =
|
|
54
|
+
typeof error === 'undefined' && typeof filteredResults !== 'undefined' && typeof allResults !== 'undefined';
|
|
51
55
|
|
|
52
56
|
useEffect(() => {
|
|
53
57
|
// without setTimeout, the initial scrolling offset is calculated
|
|
54
58
|
// incorrectly, as the list is not fully rendered by the browser yet
|
|
55
59
|
const timeout = globalThis.setTimeout(() => {
|
|
56
60
|
if (listRef) {
|
|
57
|
-
listRef.scrollToItem(
|
|
61
|
+
listRef.scrollToItem(scrollToIndexRef.current, 'center');
|
|
58
62
|
}
|
|
59
63
|
}, 0);
|
|
60
64
|
|
|
61
65
|
return () => {
|
|
62
66
|
globalThis.clearTimeout(timeout);
|
|
63
67
|
};
|
|
64
|
-
}, [listRef,
|
|
68
|
+
}, [allResults, listRef, scrollToIndexRef]);
|
|
65
69
|
|
|
66
70
|
return (
|
|
67
|
-
<div className={styles.results}>
|
|
71
|
+
<div className={classNames(styles.results, className)}>
|
|
68
72
|
<div className={styles.header}>
|
|
69
73
|
{columns.map((column) => (
|
|
70
74
|
<HeaderButton column={column} key={column.id} />
|
|
@@ -88,9 +92,7 @@ const Results: FunctionComponent<Props> = ({ callbacks, highlightedIndex }) => {
|
|
|
88
92
|
</EmptyState>
|
|
89
93
|
)}
|
|
90
94
|
|
|
91
|
-
{
|
|
92
|
-
typeof filteredResults !== 'undefined' &&
|
|
93
|
-
typeof allResults !== 'undefined' && (
|
|
95
|
+
{hasResults && (
|
|
94
96
|
<>
|
|
95
97
|
{isOutdated && (
|
|
96
98
|
<EmptyState className={styles.emptyState} variant="info">
|
|
@@ -138,9 +140,7 @@ const Results: FunctionComponent<Props> = ({ callbacks, highlightedIndex }) => {
|
|
|
138
140
|
)}
|
|
139
141
|
</div>
|
|
140
142
|
|
|
141
|
-
{
|
|
142
|
-
<>{allResults.length > 0 && !isOutdated && <ResultsInput />}</>
|
|
143
|
-
)}
|
|
143
|
+
{hasResults && allResults.length > 0 && !isOutdated && <ResultsInput className={styles.input} />}
|
|
144
144
|
|
|
145
145
|
{isLoading && <Loading />}
|
|
146
146
|
</div>
|
|
@@ -70,16 +70,21 @@
|
|
|
70
70
|
flex-direction: column;
|
|
71
71
|
background-color: var(--color--background--element);
|
|
72
72
|
border: var(--border);
|
|
73
|
+
border-radius: var(--border--radius);
|
|
73
74
|
box-shadow: var(--box-shadow);
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
.dictionary {
|
|
77
78
|
flex: 1;
|
|
78
79
|
border-bottom: var(--border);
|
|
80
|
+
border-top-left-radius: var(--border--radius);
|
|
81
|
+
border-top-right-radius: var(--border--radius);
|
|
79
82
|
}
|
|
80
83
|
|
|
81
84
|
.dictionaryInput {
|
|
82
85
|
flex: 0 0 auto;
|
|
86
|
+
border-bottom-left-radius: var(--border--radius);
|
|
87
|
+
border-bottom-right-radius: var(--border--radius);
|
|
83
88
|
}
|
|
84
89
|
|
|
85
90
|
.bottomContainer {
|
|
@@ -97,10 +102,6 @@
|
|
|
97
102
|
min-width: 0;
|
|
98
103
|
}
|
|
99
104
|
|
|
100
|
-
.rack {
|
|
101
|
-
border: var(--border);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
105
|
.controls {
|
|
105
106
|
width: 100%;
|
|
106
107
|
}
|
|
@@ -114,6 +115,7 @@
|
|
|
114
115
|
|
|
115
116
|
position: fixed;
|
|
116
117
|
bottom: var(--spacing);
|
|
118
|
+
z-index: 1;
|
|
117
119
|
|
|
118
120
|
@include media('<xs') {
|
|
119
121
|
--spacing: var(--spacing--m);
|
|
@@ -4,15 +4,8 @@ import { FunctionComponent, SyntheticEvent, useEffect, useMemo } from 'react';
|
|
|
4
4
|
import { useDispatch } from 'react-redux';
|
|
5
5
|
import { useMeasure } from 'react-use';
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
BOARD_TILE_SIZE_MAX,
|
|
10
|
-
BOARD_TILE_SIZE_MIN,
|
|
11
|
-
BORDER_WIDTH,
|
|
12
|
-
COMPONENTS_SPACING,
|
|
13
|
-
COMPONENTS_SPACING_SMALL,
|
|
14
|
-
RACK_TILE_SIZE_MAX,
|
|
15
|
-
} from 'parameters';
|
|
7
|
+
import { useAppLayout, useIsTouchDevice } from 'hooks';
|
|
8
|
+
import { BOARD_TILE_SIZE_MAX, BOARD_TILE_SIZE_MIN, BORDER_WIDTH, RACK_TILE_SIZE_MAX } from 'parameters';
|
|
16
9
|
import {
|
|
17
10
|
resultsSlice,
|
|
18
11
|
selectAreResultsOutdated,
|
|
@@ -26,13 +19,14 @@ import {
|
|
|
26
19
|
useTypedSelector,
|
|
27
20
|
} from 'state';
|
|
28
21
|
|
|
22
|
+
import Alert from '../Alert';
|
|
29
23
|
import Board from '../Board';
|
|
30
24
|
import Dictionary from '../Dictionary';
|
|
31
25
|
import DictionaryInput from '../DictionaryInput';
|
|
32
26
|
import Rack from '../Rack';
|
|
33
27
|
import Results from '../Results';
|
|
34
28
|
|
|
35
|
-
import {
|
|
29
|
+
import { FloatingSolveButton, ResultCandidatePicker } from './components';
|
|
36
30
|
import styles from './Solver.module.scss';
|
|
37
31
|
|
|
38
32
|
interface Props {
|
|
@@ -47,14 +41,12 @@ const Solver: FunctionComponent<Props> = ({ className, height, width, onShowResu
|
|
|
47
41
|
const dispatch = useDispatch();
|
|
48
42
|
const translate = useTranslate();
|
|
49
43
|
const isTouchDevice = useIsTouchDevice();
|
|
44
|
+
const { componentsSpacing, isBoardFullWidth, showColumn, showCompactControls, showFloatingSolveButton } =
|
|
45
|
+
useAppLayout();
|
|
50
46
|
const [bottomContainerRef, { height: bottomContainerHeight }] = useMeasure<HTMLDivElement>();
|
|
51
47
|
const [columnRef, { width: columnWidth }] = useMeasure<HTMLDivElement>();
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
const isLessThanM = useMediaQuery('<m');
|
|
55
|
-
const componentsSpacing = isLessThanXl ? COMPONENTS_SPACING_SMALL : COMPONENTS_SPACING;
|
|
56
|
-
const maxBoardWidth = width - columnWidth - (isLessThanL ? 0 : componentsSpacing) - 2 * componentsSpacing;
|
|
57
|
-
const maxBoardHeight = Math.max(height - bottomContainerHeight, isLessThanM ? Number.POSITIVE_INFINITY : 0);
|
|
48
|
+
const maxBoardWidth = width - columnWidth - (showColumn ? componentsSpacing : 0) - 2 * componentsSpacing;
|
|
49
|
+
const maxBoardHeight = isBoardFullWidth ? Number.POSITIVE_INFINITY : Math.max(height - bottomContainerHeight, 0);
|
|
58
50
|
const config = useTypedSelector(selectConfig);
|
|
59
51
|
const error = useTypedSelector(selectSolveError);
|
|
60
52
|
const isOutdated = useTypedSelector(selectAreResultsOutdated);
|
|
@@ -107,18 +99,14 @@ const Solver: FunctionComponent<Props> = ({ className, height, width, onShowResu
|
|
|
107
99
|
const handleSubmit = (event: SyntheticEvent) => {
|
|
108
100
|
event.preventDefault();
|
|
109
101
|
|
|
110
|
-
if (isLessThanL) {
|
|
111
|
-
onShowResults();
|
|
112
|
-
}
|
|
113
|
-
|
|
114
102
|
dispatch(solveSlice.actions.submit());
|
|
115
103
|
};
|
|
116
104
|
|
|
117
105
|
useEffect(() => {
|
|
118
|
-
if (
|
|
106
|
+
if (showCompactControls && bestResult && !isOutdated) {
|
|
119
107
|
dispatch(resultsSlice.actions.changeResultCandidate(bestResult));
|
|
120
108
|
}
|
|
121
|
-
}, [bestResult, dispatch,
|
|
109
|
+
}, [bestResult, dispatch, showCompactControls, isOutdated]);
|
|
122
110
|
|
|
123
111
|
return (
|
|
124
112
|
<div className={classNames(styles.solver, className)}>
|
|
@@ -147,27 +135,27 @@ const Solver: FunctionComponent<Props> = ({ className, height, width, onShowResu
|
|
|
147
135
|
<input className={styles.submitInput} tabIndex={-1} type="submit" />
|
|
148
136
|
</form>
|
|
149
137
|
|
|
150
|
-
{
|
|
138
|
+
{showCompactControls && (
|
|
151
139
|
<div className={styles.controls} style={{ maxWidth: maxControlsWidth }}>
|
|
152
140
|
<ResultCandidatePicker onResultClick={onShowResults} />
|
|
153
141
|
|
|
154
142
|
{error && (
|
|
155
|
-
<
|
|
143
|
+
<Alert className={styles.emptyState} variant="error">
|
|
156
144
|
{error.message}
|
|
157
|
-
</
|
|
145
|
+
</Alert>
|
|
158
146
|
)}
|
|
159
147
|
|
|
160
148
|
{allResults && allResults.length === 0 && !isOutdated && (
|
|
161
|
-
<
|
|
149
|
+
<Alert className={styles.emptyState} variant="warning">
|
|
162
150
|
{translate('results.empty-state.no-results')}
|
|
163
|
-
</
|
|
151
|
+
</Alert>
|
|
164
152
|
)}
|
|
165
153
|
</div>
|
|
166
154
|
)}
|
|
167
155
|
</div>
|
|
168
156
|
</div>
|
|
169
157
|
|
|
170
|
-
{
|
|
158
|
+
{showFloatingSolveButton && <FloatingSolveButton className={styles.solve} onClick={handleSubmit} />}
|
|
171
159
|
</div>
|
|
172
160
|
);
|
|
173
161
|
};
|
|
@@ -14,15 +14,16 @@ import {
|
|
|
14
14
|
} from 'state';
|
|
15
15
|
|
|
16
16
|
import Button from '../../../Button';
|
|
17
|
+
import Spinner from '../../../Spinner';
|
|
17
18
|
|
|
18
|
-
import styles from './
|
|
19
|
+
import styles from './FloatingSolveButton.module.scss';
|
|
19
20
|
|
|
20
21
|
interface Props {
|
|
21
22
|
className?: string;
|
|
22
23
|
onClick?: MouseEventHandler;
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
const
|
|
26
|
+
const FloatingSolveButton: FunctionComponent<Props> = ({ className, onClick = noop }) => {
|
|
26
27
|
const dispatch = useDispatch();
|
|
27
28
|
const translate = useTranslate();
|
|
28
29
|
const isLoading = useTypedSelector(selectIsLoading);
|
|
@@ -38,10 +39,9 @@ const SolveButton: FunctionComponent<Props> = ({ className, onClick = noop }) =>
|
|
|
38
39
|
return (
|
|
39
40
|
<Button
|
|
40
41
|
aria-label={translate('results.solve')}
|
|
41
|
-
className={classNames(styles.
|
|
42
|
+
className={classNames(styles.floatingSolveButton, className)}
|
|
42
43
|
disabled={isLoading || !isOutdated || !hasTiles}
|
|
43
|
-
Icon={Search}
|
|
44
|
-
iconClassName={styles.icon}
|
|
44
|
+
Icon={isLoading ? Spinner : Search}
|
|
45
45
|
tooltip={translate('results.solve')}
|
|
46
46
|
type="submit"
|
|
47
47
|
variant="primary"
|
|
@@ -50,4 +50,4 @@ const SolveButton: FunctionComponent<Props> = ({ className, onClick = noop }) =>
|
|
|
50
50
|
);
|
|
51
51
|
};
|
|
52
52
|
|
|
53
|
-
export default
|
|
53
|
+
export default FloatingSolveButton;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './FloatingSolveButton';
|
package/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.module.scss
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
@import 'styles/mixins';
|
|
2
2
|
|
|
3
3
|
.resultCandidatePicker {
|
|
4
|
+
position: relative;
|
|
4
5
|
display: flex;
|
|
5
6
|
gap: var(--spacing--l);
|
|
6
7
|
|
|
@@ -137,14 +138,28 @@
|
|
|
137
138
|
}
|
|
138
139
|
}
|
|
139
140
|
|
|
140
|
-
.
|
|
141
|
-
|
|
141
|
+
.spinnerContainer {
|
|
142
|
+
position: absolute;
|
|
143
|
+
top: 0;
|
|
144
|
+
right: 0;
|
|
145
|
+
bottom: 0;
|
|
146
|
+
left: 0;
|
|
147
|
+
display: flex;
|
|
148
|
+
align-items: center;
|
|
149
|
+
justify-content: center;
|
|
150
|
+
}
|
|
142
151
|
|
|
143
|
-
|
|
144
|
-
|
|
152
|
+
.loading,
|
|
153
|
+
.icon {
|
|
154
|
+
width: var(--button--icon--size);
|
|
155
|
+
height: var(--button--icon--size);
|
|
145
156
|
color: var(--color--inactive);
|
|
146
157
|
}
|
|
147
158
|
|
|
159
|
+
.loading {
|
|
160
|
+
border-color: var(--color--inactive);
|
|
161
|
+
}
|
|
162
|
+
|
|
148
163
|
.insert {
|
|
149
164
|
flex: 0 0 auto;
|
|
150
165
|
}
|
|
@@ -2,10 +2,12 @@ import classNames from 'classnames';
|
|
|
2
2
|
import { FunctionComponent, HTMLProps, MouseEventHandler } from 'react';
|
|
3
3
|
import { useDispatch } from 'react-redux';
|
|
4
4
|
|
|
5
|
+
import { useAppLayout } from 'hooks';
|
|
5
6
|
import { ChevronDown, ChevronLeft, ChevronRight } from 'icons';
|
|
6
7
|
import {
|
|
7
8
|
resultsSlice,
|
|
8
9
|
selectAreResultsOutdated,
|
|
10
|
+
selectIsLoading,
|
|
9
11
|
selectLocale,
|
|
10
12
|
selectResultCandidate,
|
|
11
13
|
selectSortedResults,
|
|
@@ -14,6 +16,7 @@ import {
|
|
|
14
16
|
} from 'state';
|
|
15
17
|
|
|
16
18
|
import Button from '../../../Button';
|
|
19
|
+
import Spinner from '../../../Spinner';
|
|
17
20
|
import InsertButton from '../InsertButton';
|
|
18
21
|
|
|
19
22
|
import styles from './ResultCandidatePicker.module.scss';
|
|
@@ -26,6 +29,7 @@ const ResultCandidatePicker: FunctionComponent<Props> = ({ className, onResultCl
|
|
|
26
29
|
const dispatch = useDispatch();
|
|
27
30
|
const translate = useTranslate();
|
|
28
31
|
const locale = useTypedSelector(selectLocale);
|
|
32
|
+
const isLoading = useTypedSelector(selectIsLoading);
|
|
29
33
|
const isOutdated = useTypedSelector(selectAreResultsOutdated);
|
|
30
34
|
const sortedResults = useTypedSelector(selectSortedResults);
|
|
31
35
|
const results = sortedResults || [];
|
|
@@ -35,6 +39,7 @@ const ResultCandidatePicker: FunctionComponent<Props> = ({ className, onResultCl
|
|
|
35
39
|
const isPreviousDisabled = index <= 0 || disabled;
|
|
36
40
|
const isNextDisabled = index >= results.length - 1 || disabled;
|
|
37
41
|
const bothEnabled = !isPreviousDisabled && !isNextDisabled;
|
|
42
|
+
const { showFloatingSolveButton } = useAppLayout();
|
|
38
43
|
|
|
39
44
|
const handleNextClick = () => {
|
|
40
45
|
if (!isNextDisabled) {
|
|
@@ -87,7 +92,14 @@ const ResultCandidatePicker: FunctionComponent<Props> = ({ className, onResultCl
|
|
|
87
92
|
{!resultCandidate && <div className={styles.word}> </div>}
|
|
88
93
|
|
|
89
94
|
<div className={styles.iconContainer}>
|
|
90
|
-
<ChevronDown className={styles.icon} />
|
|
95
|
+
{showFloatingSolveButton && <ChevronDown className={styles.icon} />}
|
|
96
|
+
|
|
97
|
+
{!showFloatingSolveButton && (
|
|
98
|
+
<>
|
|
99
|
+
{isLoading && <Spinner className={styles.loading} />}
|
|
100
|
+
{!isLoading && <ChevronDown className={styles.icon} />}
|
|
101
|
+
</>
|
|
102
|
+
)}
|
|
91
103
|
</div>
|
|
92
104
|
</button>
|
|
93
105
|
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
export { default as
|
|
1
|
+
export { default as FloatingSolveButton } from './FloatingSolveButton';
|
|
2
2
|
export { default as InsertButton } from './InsertButton';
|
|
3
3
|
export { default as ResultCandidatePicker } from './ResultCandidatePicker';
|
|
4
|
-
export { default as SolveButton } from './SolveButton';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
@import 'styles/animations';
|
|
2
|
+
|
|
3
|
+
.spinner {
|
|
4
|
+
width: var(--button--icon--size);
|
|
5
|
+
height: var(--button--icon--size);
|
|
6
|
+
border: var(--border);
|
|
7
|
+
border-width: 2px;
|
|
8
|
+
border-radius: 50%;
|
|
9
|
+
border-right-color: transparent !important;
|
|
10
|
+
animation: 750ms linear infinite rotate;
|
|
11
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import classNames from 'classnames';
|
|
2
|
+
import { FunctionComponent } from 'react';
|
|
3
|
+
|
|
4
|
+
import { useTranslate } from 'state';
|
|
5
|
+
|
|
6
|
+
import styles from './Spinner.module.scss';
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
className?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const Spinner: FunctionComponent<Props> = ({ className }) => {
|
|
13
|
+
const translate = useTranslate();
|
|
14
|
+
const translation = translate('common.loading');
|
|
15
|
+
|
|
16
|
+
return <div aria-label={translation} className={classNames(styles.spinner, className)} role="status" />;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default Spinner;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './Spinner';
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
position: relative;
|
|
7
7
|
transition: var(--transition);
|
|
8
|
+
border-radius: var(--border--radius);
|
|
8
9
|
|
|
9
10
|
&.points1 {
|
|
10
11
|
--background-color: var(--color--yellow);
|
|
@@ -85,6 +86,7 @@
|
|
|
85
86
|
align-items: center;
|
|
86
87
|
justify-content: center;
|
|
87
88
|
background-color: var(--background-color);
|
|
89
|
+
border-radius: inherit;
|
|
88
90
|
transition: var(--transition);
|
|
89
91
|
pointer-events: none;
|
|
90
92
|
user-select: none;
|
|
@@ -94,10 +96,18 @@
|
|
|
94
96
|
}
|
|
95
97
|
|
|
96
98
|
.raised & {
|
|
97
|
-
|
|
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);
|
|
98
106
|
|
|
99
107
|
@include media('<xs') {
|
|
100
|
-
|
|
108
|
+
--shadow--size: 1px;
|
|
109
|
+
--shadow--spread: 0;
|
|
110
|
+
--shadow--blur: 1px;
|
|
101
111
|
}
|
|
102
112
|
}
|
|
103
113
|
}
|
|
@@ -146,10 +156,12 @@
|
|
|
146
156
|
[dir='ltr'] & {
|
|
147
157
|
top: 0;
|
|
148
158
|
right: 0;
|
|
159
|
+
border-top-right-radius: inherit;
|
|
149
160
|
}
|
|
150
161
|
|
|
151
162
|
[dir='rtl'] & {
|
|
152
163
|
bottom: 0;
|
|
153
164
|
right: 0;
|
|
165
|
+
border-bottom-right-radius: inherit;
|
|
154
166
|
}
|
|
155
167
|
}
|