@scrabble-solver/scrabble-solver 2.11.0 → 2.11.2
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 +7 -7
- package/.next/cache/.tsbuildinfo +1 -1
- package/.next/cache/eslint/.cache_8dgz12 +1 -1
- package/.next/cache/next-server.js.nft.json +1 -1
- package/.next/cache/webpack/client-production/0.pack +0 -0
- package/.next/cache/webpack/client-production/index.pack +0 -0
- package/.next/cache/webpack/edge-server-production/0.pack +0 -0
- package/.next/cache/webpack/edge-server-production/index.pack +0 -0
- package/.next/cache/webpack/server-production/0.pack +0 -0
- package/.next/cache/webpack/server-production/index.pack +0 -0
- package/.next/next-server.js.nft.json +1 -1
- package/.next/prerender-manifest.json +1 -1
- package/.next/routes-manifest.json +1 -1
- package/.next/server/chunks/277.js +523 -611
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/pages/404.html +2 -2
- package/.next/server/pages/404.js.nft.json +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/pages/_app.js +0 -24
- package/.next/server/pages/_app.js.nft.json +1 -1
- package/.next/server/pages/_document.js.nft.json +1 -1
- package/.next/server/pages/api/dictionary/[locale]/[word].js +0 -1
- package/.next/server/pages/api/dictionary/[locale].js +0 -1
- package/.next/server/pages/api/solve.js +28 -8
- package/.next/server/pages/api/verify.js +0 -1
- package/.next/server/pages/api/visit.js +0 -1
- package/.next/server/pages/index.html +1 -1
- package/.next/server/pages/index.js +49 -90
- package/.next/server/pages/index.js.nft.json +1 -1
- package/.next/server/pages/index.json +1 -1
- package/.next/static/Mdvi3FY0PqkILKLbPlVBU/_buildManifest.js +1 -0
- package/.next/static/chunks/pages/{404-e0f30450e9920dc3.js → 404-448ba28510855455.js} +1 -1
- package/.next/static/chunks/pages/_app-495e6f4ccc278bb2.js +28 -0
- package/.next/static/chunks/pages/index-5ecc51900ca29685.js +1 -0
- package/.next/static/css/17b0a2db8742105f.css +1 -0
- package/.next/static/css/e1ffeb2558330c55.css +2 -0
- package/.next/trace +54 -55
- package/package.json +9 -14
- 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 +51 -2
- 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 +9 -26
- package/src/components/Board/lib/getPositionInGrid.ts +1 -1
- package/src/components/Dictionary/Dictionary.module.scss +4 -0
- 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/Logo/Logo.tsx +5 -0
- package/src/components/LogoSplashScreen/LogoSplashScreen.module.scss +4 -1
- package/src/components/Modal/Modal.module.scss +14 -0
- package/src/components/Modal/Modal.tsx +4 -1
- 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/HeaderButton.tsx +5 -12
- package/src/components/Results/Result.tsx +5 -3
- package/src/components/Results/Results.module.scss +41 -1
- package/src/components/Results/Results.tsx +29 -43
- package/src/components/Results/types.ts +1 -1
- package/src/components/ResultsInput/ResultsInput.module.scss +1 -0
- package/src/components/Solver/Solver.module.scss +9 -4
- package/src/components/Solver/Solver.tsx +17 -19
- package/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.tsx +5 -6
- package/src/components/Solver/components/index.ts +0 -1
- package/src/components/Tile/Tile.module.scss +42 -61
- package/src/components/Tile/Tile.tsx +4 -3
- package/src/components/Tile/TilePure.tsx +4 -13
- package/src/components/Tooltip/Tooltip.module.scss +1 -72
- package/src/components/Tooltip/useTooltip.tsx +25 -35
- package/src/components/index.ts +1 -0
- package/src/hooks/index.ts +0 -1
- package/src/hooks/useAppLayout.ts +3 -1
- package/src/i18n/de.json +0 -1
- package/src/i18n/en.json +0 -1
- package/src/i18n/es.json +0 -1
- package/src/i18n/fa.json +0 -1
- package/src/i18n/fr.json +0 -1
- package/src/i18n/pl.json +0 -1
- package/src/lib/createRegExp.ts +13 -0
- package/src/lib/groupResults.ts +38 -0
- package/src/lib/guessLocale.ts +22 -0
- package/src/lib/index.ts +4 -2
- package/src/lib/sortResults.ts +6 -10
- package/src/modals/DictionaryModal/DictionaryModal.module.scss +6 -2
- package/src/modals/KeyMapModal/KeyMapModal.tsx +5 -21
- package/src/modals/KeyMapModal/components/Mapping/Mapping.module.scss +4 -0
- package/src/modals/MenuModal/MenuModal.module.scss +7 -0
- package/src/modals/RemainingTilesModal/components/Character/Character.module.scss +1 -0
- package/src/modals/ResultsModal/ResultsModal.module.scss +3 -3
- package/src/modals/ResultsModal/ResultsModal.tsx +23 -11
- package/src/modals/SettingsModal/components/LocaleSetting/LocaleSetting.module.scss +2 -0
- package/src/modals/WordsModal/WordsModal.module.scss +8 -1
- package/src/pages/api/dictionary/[locale]/[word].ts +0 -1
- package/src/pages/api/dictionary/[locale]/index.ts +0 -1
- package/src/pages/api/solve.ts +0 -1
- package/src/pages/api/verify.ts +0 -1
- package/src/pages/api/visit.ts +0 -1
- package/src/pages/index.tsx +13 -15
- package/src/parameters/index.ts +2 -2
- package/src/state/createAppStore.ts +26 -10
- package/src/state/sagas.ts +1 -0
- package/src/state/selectors.ts +37 -37
- package/src/state/slices/boardInitialState.ts +3 -1
- package/src/state/slices/cellFilterInitialState.ts +3 -3
- package/src/state/slices/cellFilterSlice.ts +3 -1
- package/src/state/slices/dictionaryInitialState.ts +2 -2
- package/src/state/slices/rackInitialState.ts +3 -1
- package/src/state/slices/resultsInitialState.ts +11 -4
- package/src/state/slices/settingsInitialState.ts +10 -21
- package/src/state/slices/solveInitialState.ts +2 -2
- package/src/state/slices/solveSlice.ts +2 -0
- package/src/state/slices/verifyInitialState.ts +13 -4
- package/src/state/types.ts +20 -2
- package/src/styles/mixins.scss +25 -1
- package/src/styles/variables.scss +17 -1
- package/src/types/index.ts +10 -2
- package/.next/static/45ye7793DY705HOcuK9lJ/_buildManifest.js +0 -1
- package/.next/static/chunks/pages/_app-d7acee5e526752d9.js +0 -28
- package/.next/static/chunks/pages/index-35d2c1c79a201ae2.js +0 -1
- package/.next/static/css/a48caa6f57de6e98.css +0 -1
- package/.next/static/css/c49bbe944ddd1b39.css +0 -2
- package/src/components/Board/types/index.ts +0 -4
- package/src/components/Solver/components/EmptyState/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/static/{45ye7793DY705HOcuK9lJ → Mdvi3FY0PqkILKLbPlVBU}/_ssgManifest.js +0 -0
|
@@ -14,12 +14,12 @@ interface Props {
|
|
|
14
14
|
|
|
15
15
|
const Tile: FunctionComponent<Props> = ({ character, className, color, fontFamily, points, size, transform, x, y }) => (
|
|
16
16
|
<g className={className} transform={transform}>
|
|
17
|
-
<rect fill={color} height={size} width={size} x={x} y={y} />
|
|
17
|
+
<rect fill={color} height={size} rx={size * 0.15} width={size} x={x} y={y} />
|
|
18
18
|
|
|
19
19
|
<text
|
|
20
20
|
dominantBaseline="central"
|
|
21
21
|
fontFamily={fontFamily}
|
|
22
|
-
fontSize={0.6
|
|
22
|
+
fontSize={size * 0.6}
|
|
23
23
|
fontWeight="bold"
|
|
24
24
|
textAnchor="middle"
|
|
25
25
|
x={x + size / 2}
|
|
@@ -32,7 +32,7 @@ const Tile: FunctionComponent<Props> = ({ character, className, color, fontFamil
|
|
|
32
32
|
<text
|
|
33
33
|
dominantBaseline="text-after-edge"
|
|
34
34
|
fontFamily={fontFamily}
|
|
35
|
-
fontSize={0.25
|
|
35
|
+
fontSize={size * 0.25}
|
|
36
36
|
fontWeight="bold"
|
|
37
37
|
textAnchor="end"
|
|
38
38
|
x={x + size * 0.9}
|
|
@@ -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}
|
|
@@ -3,13 +3,7 @@ import { ReactElement } from 'react';
|
|
|
3
3
|
import { useDispatch } from 'react-redux';
|
|
4
4
|
|
|
5
5
|
import { SortDown, SortUp } from 'icons';
|
|
6
|
-
import {
|
|
7
|
-
resultsSlice,
|
|
8
|
-
selectResultsSortColumn,
|
|
9
|
-
selectResultsSortDirection,
|
|
10
|
-
useTranslate,
|
|
11
|
-
useTypedSelector,
|
|
12
|
-
} from 'state';
|
|
6
|
+
import { resultsSlice, selectResultsSort, useTranslate, useTypedSelector } from 'state';
|
|
13
7
|
import { ResultColumn, SortDirection } from 'types';
|
|
14
8
|
|
|
15
9
|
import { useTooltip } from '../Tooltip';
|
|
@@ -24,8 +18,7 @@ interface Props {
|
|
|
24
18
|
const HeaderButton = ({ column }: Props): ReactElement => {
|
|
25
19
|
const dispatch = useDispatch();
|
|
26
20
|
const translate = useTranslate();
|
|
27
|
-
const
|
|
28
|
-
const sortDirection = useTypedSelector(selectResultsSortDirection);
|
|
21
|
+
const sort = useTypedSelector(selectResultsSort);
|
|
29
22
|
const triggerProps = useTooltip(translate(column.translationKey));
|
|
30
23
|
|
|
31
24
|
const handleOrderChange = (columnId: ResultColumn) => {
|
|
@@ -44,10 +37,10 @@ const HeaderButton = ({ column }: Props): ReactElement => {
|
|
|
44
37
|
<span className={styles.cell}>
|
|
45
38
|
<span className={styles.headerButtonLabel}>{translate(column.translationKey)}</span>
|
|
46
39
|
|
|
47
|
-
{
|
|
40
|
+
{sort.column === column.id && (
|
|
48
41
|
<>
|
|
49
|
-
{
|
|
50
|
-
{
|
|
42
|
+
{sort.direction === SortDirection.Ascending && <SortUp className={styles.sortIcon} />}
|
|
43
|
+
{sort.direction === SortDirection.Descending && <SortDown className={styles.sortIcon} />}
|
|
51
44
|
</>
|
|
52
45
|
)}
|
|
53
46
|
</span>
|
|
@@ -3,7 +3,7 @@ import { CSSProperties, FocusEventHandler, MouseEventHandler, ReactElement, useR
|
|
|
3
3
|
|
|
4
4
|
import { LOCALE_FEATURES } from 'i18n';
|
|
5
5
|
import { noop } from 'lib';
|
|
6
|
-
import { selectLocale, useTypedSelector } from 'state';
|
|
6
|
+
import { selectIsResultMatching, selectLocale, useTypedSelector } from 'state';
|
|
7
7
|
import { ResultColumn } from 'types';
|
|
8
8
|
|
|
9
9
|
import Cell from './Cell';
|
|
@@ -20,7 +20,7 @@ interface Props {
|
|
|
20
20
|
const Result = ({ data, index, style }: Props): ReactElement => {
|
|
21
21
|
const {
|
|
22
22
|
highlightedIndex,
|
|
23
|
-
results,
|
|
23
|
+
results = [],
|
|
24
24
|
onBlur = noop,
|
|
25
25
|
onClick = noop,
|
|
26
26
|
onFocus = noop,
|
|
@@ -28,11 +28,12 @@ const Result = ({ data, index, style }: Props): ReactElement => {
|
|
|
28
28
|
onMouseLeave = noop,
|
|
29
29
|
} = data;
|
|
30
30
|
const ref = useRef<HTMLButtonElement>(null);
|
|
31
|
+
const columns = useColumns();
|
|
31
32
|
const locale = useTypedSelector(selectLocale);
|
|
32
33
|
const { consonants, vowels } = LOCALE_FEATURES[locale];
|
|
33
34
|
const result = results[index];
|
|
35
|
+
const isMatching = useTypedSelector((state) => selectIsResultMatching(state, index));
|
|
34
36
|
const otherWords = result.words.slice(1).join(' / ').toLocaleUpperCase();
|
|
35
|
-
const columns = useColumns();
|
|
36
37
|
const enabledColumns = Object.fromEntries(columns.map((column) => [column.id, true]));
|
|
37
38
|
|
|
38
39
|
const handleClick: MouseEventHandler = (event) => onClick(result, event);
|
|
@@ -46,6 +47,7 @@ const Result = ({ data, index, style }: Props): ReactElement => {
|
|
|
46
47
|
aria-label={result.word}
|
|
47
48
|
className={classNames(styles.result, {
|
|
48
49
|
[styles.highlighted]: index === highlightedIndex,
|
|
50
|
+
[styles.notMatching]: !isMatching,
|
|
49
51
|
})}
|
|
50
52
|
ref={ref}
|
|
51
53
|
style={style}
|
|
@@ -9,7 +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
|
-
|
|
12
|
+
border-radius: var(--border--radius);
|
|
13
13
|
font-family: var(--font--family--title);
|
|
14
14
|
}
|
|
15
15
|
|
|
@@ -29,6 +29,9 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
.list {
|
|
32
|
+
@include scrollbars;
|
|
33
|
+
|
|
34
|
+
scrollbar-gutter: stable;
|
|
32
35
|
transition: var(--transition);
|
|
33
36
|
|
|
34
37
|
&.outdated {
|
|
@@ -44,6 +47,8 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
|
|
|
44
47
|
justify-content: space-between;
|
|
45
48
|
font-weight: 700;
|
|
46
49
|
border-bottom: var(--border);
|
|
50
|
+
border-top-left-radius: inherit;
|
|
51
|
+
border-top-right-radius: inherit;
|
|
47
52
|
}
|
|
48
53
|
|
|
49
54
|
.headerButton {
|
|
@@ -60,6 +65,26 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
|
|
|
60
65
|
background-color: var(--color--primary);
|
|
61
66
|
color: var(--color--primary--opposite);
|
|
62
67
|
}
|
|
68
|
+
|
|
69
|
+
&:first-child {
|
|
70
|
+
[dir='ltr'] & {
|
|
71
|
+
border-top-left-radius: inherit;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
[dir='rtl'] & {
|
|
75
|
+
border-top-right-radius: inherit;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
&:last-child {
|
|
80
|
+
[dir='ltr'] & {
|
|
81
|
+
border-top-right-radius: inherit;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
[dir='rtl'] & {
|
|
85
|
+
border-top-left-radius: inherit;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
63
88
|
}
|
|
64
89
|
|
|
65
90
|
.headerButtonLabel {
|
|
@@ -89,6 +114,19 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
|
|
|
89
114
|
color: var(--color--primary--opposite);
|
|
90
115
|
}
|
|
91
116
|
}
|
|
117
|
+
|
|
118
|
+
&.notMatching {
|
|
119
|
+
color: var(--color--inactive);
|
|
120
|
+
|
|
121
|
+
&:focus,
|
|
122
|
+
&:hover,
|
|
123
|
+
&.highlighted {
|
|
124
|
+
&:not(:disabled) {
|
|
125
|
+
background-color: var(--color--primary--light);
|
|
126
|
+
color: var(--color--primary--opposite);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
92
130
|
}
|
|
93
131
|
|
|
94
132
|
.resultContent {
|
|
@@ -175,4 +213,6 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
|
|
|
175
213
|
|
|
176
214
|
.input {
|
|
177
215
|
border-top: var(--border);
|
|
216
|
+
border-bottom-left-radius: var(--border--radius);
|
|
217
|
+
border-bottom-right-radius: var(--border--radius);
|
|
178
218
|
}
|
|
@@ -9,9 +9,8 @@ import {
|
|
|
9
9
|
selectAreResultsOutdated,
|
|
10
10
|
selectIsLoading,
|
|
11
11
|
selectLocale,
|
|
12
|
+
selectResults,
|
|
12
13
|
selectSolveError,
|
|
13
|
-
selectSortedFilteredResults,
|
|
14
|
-
selectSortedResults,
|
|
15
14
|
useTranslate,
|
|
16
15
|
useTypedSelector,
|
|
17
16
|
} from 'state';
|
|
@@ -38,9 +37,7 @@ const Results: FunctionComponent<Props> = ({ callbacks, className, highlightedIn
|
|
|
38
37
|
const translate = useTranslate();
|
|
39
38
|
const locale = useTypedSelector(selectLocale);
|
|
40
39
|
const { direction } = LOCALE_FEATURES[locale];
|
|
41
|
-
const
|
|
42
|
-
const filteredResults = useTypedSelector(selectSortedFilteredResults);
|
|
43
|
-
const results = filteredResults || [];
|
|
40
|
+
const results = useTypedSelector(selectResults);
|
|
44
41
|
const isLoading = useTypedSelector(selectIsLoading);
|
|
45
42
|
const isOutdated = useTypedSelector(selectAreResultsOutdated);
|
|
46
43
|
const error = useTypedSelector(selectSolveError);
|
|
@@ -50,8 +47,7 @@ const Results: FunctionComponent<Props> = ({ callbacks, className, highlightedIn
|
|
|
50
47
|
const columns = useColumns();
|
|
51
48
|
const scrollToIndex = typeof highlightedIndex === 'number' ? highlightedIndex : 0;
|
|
52
49
|
const scrollToIndexRef = useLatest(scrollToIndex);
|
|
53
|
-
const hasResults =
|
|
54
|
-
typeof error === 'undefined' && typeof filteredResults !== 'undefined' && typeof allResults !== 'undefined';
|
|
50
|
+
const hasResults = typeof error === 'undefined' && typeof results !== 'undefined';
|
|
55
51
|
|
|
56
52
|
useEffect(() => {
|
|
57
53
|
// without setTimeout, the initial scrolling offset is calculated
|
|
@@ -65,7 +61,7 @@ const Results: FunctionComponent<Props> = ({ callbacks, className, highlightedIn
|
|
|
65
61
|
return () => {
|
|
66
62
|
globalThis.clearTimeout(timeout);
|
|
67
63
|
};
|
|
68
|
-
}, [
|
|
64
|
+
}, [results, listRef, scrollToIndexRef]);
|
|
69
65
|
|
|
70
66
|
return (
|
|
71
67
|
<div className={classNames(styles.results, className)}>
|
|
@@ -84,7 +80,7 @@ const Results: FunctionComponent<Props> = ({ callbacks, className, highlightedIn
|
|
|
84
80
|
</EmptyState>
|
|
85
81
|
)}
|
|
86
82
|
|
|
87
|
-
{typeof error === 'undefined' && typeof
|
|
83
|
+
{typeof error === 'undefined' && typeof results === 'undefined' && (
|
|
88
84
|
<EmptyState className={styles.emptyState} variant="info">
|
|
89
85
|
{translate('results.empty-state.uninitialized')}
|
|
90
86
|
|
|
@@ -102,45 +98,35 @@ const Results: FunctionComponent<Props> = ({ callbacks, className, highlightedIn
|
|
|
102
98
|
</EmptyState>
|
|
103
99
|
)}
|
|
104
100
|
|
|
105
|
-
{!isOutdated && (
|
|
106
|
-
|
|
107
|
-
{
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
itemData={itemData}
|
|
129
|
-
itemSize={RESULTS_ITEM_HEIGHT}
|
|
130
|
-
ref={setListRef}
|
|
131
|
-
width={width}
|
|
132
|
-
>
|
|
133
|
-
{Result}
|
|
134
|
-
</FixedSizeList>
|
|
135
|
-
</div>
|
|
136
|
-
)}
|
|
137
|
-
</>
|
|
101
|
+
{!isOutdated && results.length === 0 && (
|
|
102
|
+
<EmptyState className={styles.emptyState} variant="warning">
|
|
103
|
+
{translate('results.empty-state.no-results')}
|
|
104
|
+
</EmptyState>
|
|
105
|
+
)}
|
|
106
|
+
|
|
107
|
+
{!isOutdated && results.length > 0 && (
|
|
108
|
+
<div className={styles.listContainer}>
|
|
109
|
+
<FixedSizeList
|
|
110
|
+
className={classNames(styles.list, {
|
|
111
|
+
[styles.outdated]: isOutdated,
|
|
112
|
+
})}
|
|
113
|
+
direction={direction}
|
|
114
|
+
height={height}
|
|
115
|
+
itemCount={results.length}
|
|
116
|
+
itemData={itemData}
|
|
117
|
+
itemSize={RESULTS_ITEM_HEIGHT}
|
|
118
|
+
ref={setListRef}
|
|
119
|
+
width={width}
|
|
120
|
+
>
|
|
121
|
+
{Result}
|
|
122
|
+
</FixedSizeList>
|
|
123
|
+
</div>
|
|
138
124
|
)}
|
|
139
125
|
</>
|
|
140
126
|
)}
|
|
141
127
|
</div>
|
|
142
128
|
|
|
143
|
-
{hasResults &&
|
|
129
|
+
{hasResults && results.length > 0 && !isOutdated && <ResultsInput className={styles.input} />}
|
|
144
130
|
|
|
145
131
|
{isLoading && <Loading />}
|
|
146
132
|
</div>
|
|
@@ -63,6 +63,10 @@
|
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
.results {
|
|
67
|
+
box-shadow: var(--box-shadow);
|
|
68
|
+
}
|
|
69
|
+
|
|
66
70
|
.dictionaryContainer {
|
|
67
71
|
flex: 0 0 auto;
|
|
68
72
|
height: var(--dictionary--height);
|
|
@@ -70,16 +74,21 @@
|
|
|
70
74
|
flex-direction: column;
|
|
71
75
|
background-color: var(--color--background--element);
|
|
72
76
|
border: var(--border);
|
|
77
|
+
border-radius: var(--border--radius);
|
|
73
78
|
box-shadow: var(--box-shadow);
|
|
74
79
|
}
|
|
75
80
|
|
|
76
81
|
.dictionary {
|
|
77
82
|
flex: 1;
|
|
78
83
|
border-bottom: var(--border);
|
|
84
|
+
border-top-left-radius: var(--border--radius);
|
|
85
|
+
border-top-right-radius: var(--border--radius);
|
|
79
86
|
}
|
|
80
87
|
|
|
81
88
|
.dictionaryInput {
|
|
82
89
|
flex: 0 0 auto;
|
|
90
|
+
border-bottom-left-radius: var(--border--radius);
|
|
91
|
+
border-bottom-right-radius: var(--border--radius);
|
|
83
92
|
}
|
|
84
93
|
|
|
85
94
|
.bottomContainer {
|
|
@@ -97,10 +106,6 @@
|
|
|
97
106
|
min-width: 0;
|
|
98
107
|
}
|
|
99
108
|
|
|
100
|
-
.rack {
|
|
101
|
-
border: var(--border);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
109
|
.controls {
|
|
105
110
|
width: 100%;
|
|
106
111
|
}
|
|
@@ -11,21 +11,21 @@ import {
|
|
|
11
11
|
selectAreResultsOutdated,
|
|
12
12
|
selectConfig,
|
|
13
13
|
selectResultCandidate,
|
|
14
|
+
selectResults,
|
|
14
15
|
selectSolveError,
|
|
15
|
-
selectSortedFilteredResults,
|
|
16
|
-
selectSortedResults,
|
|
17
16
|
solveSlice,
|
|
18
17
|
useTranslate,
|
|
19
18
|
useTypedSelector,
|
|
20
19
|
} from 'state';
|
|
21
20
|
|
|
21
|
+
import Alert from '../Alert';
|
|
22
22
|
import Board from '../Board';
|
|
23
23
|
import Dictionary from '../Dictionary';
|
|
24
24
|
import DictionaryInput from '../DictionaryInput';
|
|
25
25
|
import Rack from '../Rack';
|
|
26
26
|
import Results from '../Results';
|
|
27
27
|
|
|
28
|
-
import {
|
|
28
|
+
import { FloatingSolveButton, ResultCandidatePicker } from './components';
|
|
29
29
|
import styles from './Solver.module.scss';
|
|
30
30
|
|
|
31
31
|
interface Props {
|
|
@@ -40,18 +40,16 @@ const Solver: FunctionComponent<Props> = ({ className, height, width, onShowResu
|
|
|
40
40
|
const dispatch = useDispatch();
|
|
41
41
|
const translate = useTranslate();
|
|
42
42
|
const isTouchDevice = useIsTouchDevice();
|
|
43
|
-
const { componentsSpacing, isBoardFullWidth, showColumn, showCompactControls, showFloatingSolveButton } =
|
|
43
|
+
const { columnWidth, componentsSpacing, isBoardFullWidth, showColumn, showCompactControls, showFloatingSolveButton } =
|
|
44
44
|
useAppLayout();
|
|
45
|
-
const [bottomContainerRef, { height: bottomContainerHeight }] = useMeasure<HTMLDivElement>();
|
|
46
|
-
const [columnRef, { width: columnWidth }] = useMeasure<HTMLDivElement>();
|
|
47
|
-
const maxBoardWidth = width - columnWidth - (showColumn ? componentsSpacing : 0) - 2 * componentsSpacing;
|
|
48
|
-
const maxBoardHeight = isBoardFullWidth ? Number.POSITIVE_INFINITY : Math.max(height - bottomContainerHeight, 0);
|
|
49
45
|
const config = useTypedSelector(selectConfig);
|
|
50
46
|
const error = useTypedSelector(selectSolveError);
|
|
51
47
|
const isOutdated = useTypedSelector(selectAreResultsOutdated);
|
|
52
48
|
const resultCandidate = useTypedSelector(selectResultCandidate);
|
|
53
|
-
const
|
|
54
|
-
const
|
|
49
|
+
const results = useTypedSelector(selectResults);
|
|
50
|
+
const [bottomContainerRef, { height: bottomContainerHeight }] = useMeasure<HTMLDivElement>();
|
|
51
|
+
const maxBoardWidth = width - columnWidth - (showColumn ? componentsSpacing : 0) - 2 * componentsSpacing;
|
|
52
|
+
const maxBoardHeight = isBoardFullWidth ? Number.POSITIVE_INFINITY : Math.max(height - bottomContainerHeight, 0);
|
|
55
53
|
const [bestResult] = results || [];
|
|
56
54
|
const cellWidth = (maxBoardWidth - (config.boardWidth + 1) * BORDER_WIDTH) / config.boardWidth;
|
|
57
55
|
const cellHeight = (maxBoardHeight - (config.boardHeight + 1) * BORDER_WIDTH) / config.boardHeight;
|
|
@@ -111,13 +109,13 @@ const Solver: FunctionComponent<Props> = ({ className, height, width, onShowResu
|
|
|
111
109
|
<div className={classNames(styles.solver, className)}>
|
|
112
110
|
<div className={styles.container}>
|
|
113
111
|
<div className={styles.content}>
|
|
114
|
-
<form
|
|
112
|
+
<form className={styles.boardContainer} onSubmit={handleSubmit}>
|
|
115
113
|
<Board cellSize={cellSizeSafe} className={styles.board} />
|
|
116
114
|
<input className={styles.submitInput} tabIndex={-1} type="submit" />
|
|
117
115
|
</form>
|
|
118
116
|
|
|
119
|
-
<div className={styles.column}
|
|
120
|
-
<Results callbacks={callbacks} />
|
|
117
|
+
<div className={styles.column}>
|
|
118
|
+
<Results callbacks={callbacks} className={styles.results} />
|
|
121
119
|
|
|
122
120
|
<div className={styles.dictionaryContainer}>
|
|
123
121
|
<Dictionary className={styles.dictionary} />
|
|
@@ -129,7 +127,7 @@ const Solver: FunctionComponent<Props> = ({ className, height, width, onShowResu
|
|
|
129
127
|
|
|
130
128
|
<div className={styles.bottomContainer} ref={bottomContainerRef}>
|
|
131
129
|
<div className={styles.bottomContent}>
|
|
132
|
-
<form
|
|
130
|
+
<form onSubmit={handleSubmit}>
|
|
133
131
|
<Rack className={styles.rack} tileSize={tileSize} />
|
|
134
132
|
<input className={styles.submitInput} tabIndex={-1} type="submit" />
|
|
135
133
|
</form>
|
|
@@ -139,15 +137,15 @@ const Solver: FunctionComponent<Props> = ({ className, height, width, onShowResu
|
|
|
139
137
|
<ResultCandidatePicker onResultClick={onShowResults} />
|
|
140
138
|
|
|
141
139
|
{error && (
|
|
142
|
-
<
|
|
140
|
+
<Alert className={styles.emptyState} variant="error">
|
|
143
141
|
{error.message}
|
|
144
|
-
</
|
|
142
|
+
</Alert>
|
|
145
143
|
)}
|
|
146
144
|
|
|
147
|
-
{
|
|
148
|
-
<
|
|
145
|
+
{results && results.length === 0 && !isOutdated && (
|
|
146
|
+
<Alert className={styles.emptyState} variant="warning">
|
|
149
147
|
{translate('results.empty-state.no-results')}
|
|
150
|
-
</
|
|
148
|
+
</Alert>
|
|
151
149
|
)}
|
|
152
150
|
</div>
|
|
153
151
|
)}
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
selectIsLoading,
|
|
11
11
|
selectLocale,
|
|
12
12
|
selectResultCandidate,
|
|
13
|
-
|
|
13
|
+
selectResults,
|
|
14
14
|
useTranslate,
|
|
15
15
|
useTypedSelector,
|
|
16
16
|
} from 'state';
|
|
@@ -31,13 +31,12 @@ const ResultCandidatePicker: FunctionComponent<Props> = ({ className, onResultCl
|
|
|
31
31
|
const locale = useTypedSelector(selectLocale);
|
|
32
32
|
const isLoading = useTypedSelector(selectIsLoading);
|
|
33
33
|
const isOutdated = useTypedSelector(selectAreResultsOutdated);
|
|
34
|
-
const
|
|
35
|
-
const results = sortedResults || [];
|
|
34
|
+
const results = useTypedSelector(selectResults);
|
|
36
35
|
const resultCandidate = useTypedSelector(selectResultCandidate);
|
|
37
|
-
const index = resultCandidate ? results.findIndex((result) => result.id === resultCandidate.id) : -1;
|
|
36
|
+
const index = resultCandidate && results ? results.findIndex((result) => result.id === resultCandidate.id) : -1;
|
|
38
37
|
const disabled = isOutdated || !resultCandidate;
|
|
39
|
-
const isPreviousDisabled = index <= 0 || disabled;
|
|
40
|
-
const isNextDisabled = index >= results.length - 1 || disabled;
|
|
38
|
+
const isPreviousDisabled = !results || index <= 0 || disabled;
|
|
39
|
+
const isNextDisabled = !results || index >= results.length - 1 || disabled;
|
|
41
40
|
const bothEnabled = !isPreviousDisabled && !isNextDisabled;
|
|
42
41
|
const { showFloatingSolveButton } = useAppLayout();
|
|
43
42
|
|