scrabble-solver 2.11.8 → 2.12.0
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/lerna.json +1 -1
- package/package.json +14 -14
- package/packages/configs/build/literaki/literaki.js +7 -7
- package/packages/configs/build/scrabble/scrabble.js +7 -7
- package/packages/configs/package.json +3 -3
- package/packages/configs/src/literaki/literaki.ts +7 -7
- package/packages/configs/src/scrabble/scrabble.ts +8 -8
- package/packages/constants/package.json +1 -1
- package/packages/dictionaries/package-lock.json +10 -3
- package/packages/dictionaries/package.json +4 -4
- package/packages/logger/package-lock.json +227 -6
- package/packages/logger/package.json +1 -1
- package/packages/scrabble-solver/.next/BUILD_ID +1 -1
- package/packages/scrabble-solver/.next/build-manifest.json +7 -7
- 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/131.js +153 -115
- package/packages/scrabble-solver/.next/server/chunks/277.js +1426 -718
- package/packages/scrabble-solver/.next/server/chunks/44.js +6 -3
- package/packages/scrabble-solver/.next/server/chunks/50.js +20 -78
- package/packages/scrabble-solver/.next/server/chunks/911.js +14 -14
- package/packages/scrabble-solver/.next/server/middleware-build-manifest.js +1 -1
- package/packages/scrabble-solver/.next/server/pages/404.html +1 -1
- package/packages/scrabble-solver/.next/server/pages/404.js.nft.json +1 -1
- package/packages/scrabble-solver/.next/server/pages/500.html +1 -1
- package/packages/scrabble-solver/.next/server/pages/_app.js +16 -0
- package/packages/scrabble-solver/.next/server/pages/_app.js.nft.json +1 -1
- package/packages/scrabble-solver/.next/server/pages/_document.js.nft.json +1 -1
- package/packages/scrabble-solver/.next/server/pages/api/solve.js +43 -11
- package/packages/scrabble-solver/.next/server/pages/index.html +1 -1
- package/packages/scrabble-solver/.next/server/pages/index.js +152 -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 +2 -2
- package/packages/scrabble-solver/.next/static/chunks/pages/{404-ca203fa27afc37d8.js → 404-b4b5ce15153d4825.js} +1 -1
- package/packages/scrabble-solver/.next/static/chunks/pages/_app-bea4539a6b8042de.js +32 -0
- package/packages/scrabble-solver/.next/static/chunks/pages/index-4e8566409753e1c3.js +1 -0
- package/packages/scrabble-solver/.next/static/css/58053f9594647860.css +2 -0
- package/packages/scrabble-solver/.next/static/css/{c6e0e01f44fc0425.css → 60e8258da7362a1a.css} +1 -1
- package/packages/scrabble-solver/.next/static/fsjQvvJ13WNxBdMioL4sc/_buildManifest.js +1 -0
- package/packages/scrabble-solver/.next/trace +52 -50
- package/packages/scrabble-solver/package-lock.json +5138 -304
- package/packages/scrabble-solver/package.json +15 -12
- package/packages/scrabble-solver/src/components/Board/Board.module.scss +18 -4
- package/packages/scrabble-solver/src/components/Board/Board.tsx +145 -76
- package/packages/scrabble-solver/src/components/Board/BoardPure.tsx +32 -40
- package/packages/scrabble-solver/src/components/Board/components/Actions/Actions.module.scss +6 -17
- package/packages/scrabble-solver/src/components/Board/components/Actions/Actions.tsx +36 -18
- package/packages/scrabble-solver/src/components/Board/components/Cell/Cell.module.scss +12 -13
- package/packages/scrabble-solver/src/components/Board/components/Cell/Cell.tsx +53 -3
- package/packages/scrabble-solver/src/components/Board/components/InputPrompt/InputPrompt.module.scss +48 -0
- package/packages/scrabble-solver/src/components/Board/components/InputPrompt/InputPrompt.tsx +81 -0
- package/packages/scrabble-solver/src/components/Board/components/InputPrompt/index.ts +1 -0
- package/packages/scrabble-solver/src/components/Board/components/ToggleDirectionButton/ToggleDirectionButton.module.scss +21 -0
- package/packages/scrabble-solver/src/components/Board/components/ToggleDirectionButton/ToggleDirectionButton.tsx +34 -0
- package/packages/scrabble-solver/src/components/Board/components/ToggleDirectionButton/index.ts +1 -0
- package/packages/scrabble-solver/src/components/Board/components/index.ts +2 -0
- package/packages/scrabble-solver/src/components/Board/hooks/index.ts +4 -0
- package/packages/scrabble-solver/src/components/Board/hooks/useBoardStyle.ts +27 -0
- package/packages/scrabble-solver/src/components/Board/hooks/useFloatingActions.ts +22 -0
- package/packages/scrabble-solver/src/components/Board/hooks/useFloatingFocus.ts +10 -0
- package/packages/scrabble-solver/src/components/Board/hooks/useFloatingInputPrompt.ts +19 -0
- package/packages/scrabble-solver/src/components/Board/hooks/useGrid.ts +2 -1
- package/packages/scrabble-solver/src/components/NavButtons/NavButtons.tsx +2 -2
- package/packages/scrabble-solver/src/components/Rack/Rack.module.scss +6 -6
- package/packages/scrabble-solver/src/components/Rack/Rack.tsx +98 -23
- package/packages/scrabble-solver/src/components/Rack/components/InputPrompt/InputPrompt.module.scss +22 -0
- package/packages/scrabble-solver/src/components/Rack/components/InputPrompt/InputPrompt.tsx +89 -0
- package/packages/scrabble-solver/src/components/Rack/components/InputPrompt/index.ts +1 -0
- package/packages/scrabble-solver/src/components/Rack/components/RackTile/RackTile.module.scss +11 -0
- package/packages/scrabble-solver/src/components/Rack/{RackTile.tsx → components/RackTile/RackTile.tsx} +47 -7
- package/packages/scrabble-solver/src/components/Rack/components/RackTile/index.ts +1 -0
- package/packages/scrabble-solver/src/components/Rack/components/index.ts +2 -0
- package/packages/scrabble-solver/src/components/Radio/Radio.module.scss +0 -8
- package/packages/scrabble-solver/src/components/Results/Cell.tsx +4 -3
- package/packages/scrabble-solver/src/components/Results/Result.tsx +6 -2
- package/packages/scrabble-solver/src/components/Results/Results.module.scss +6 -0
- package/packages/scrabble-solver/src/components/Solver/Solver.module.scss +0 -20
- package/packages/scrabble-solver/src/components/Solver/Solver.tsx +2 -4
- package/packages/scrabble-solver/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.tsx +2 -10
- package/packages/scrabble-solver/src/components/Solver/components/index.ts +0 -1
- package/packages/scrabble-solver/src/components/Tile/Tile.module.scss +1 -0
- package/packages/scrabble-solver/src/components/Tile/Tile.tsx +8 -6
- package/packages/scrabble-solver/src/components/Tile/TilePure.tsx +8 -0
- package/packages/scrabble-solver/src/hooks/useAppLayout.ts +3 -1
- package/packages/scrabble-solver/src/hooks/useLocalStorage.ts +8 -0
- package/packages/scrabble-solver/src/i18n/de.json +6 -1
- package/packages/scrabble-solver/src/i18n/en.json +6 -1
- package/packages/scrabble-solver/src/i18n/es.json +6 -1
- package/packages/scrabble-solver/src/i18n/fa.json +6 -1
- package/packages/scrabble-solver/src/i18n/fr.json +6 -1
- package/packages/scrabble-solver/src/i18n/pl.json +6 -1
- package/packages/scrabble-solver/src/icons/Keyboard.svg +4 -3
- package/packages/scrabble-solver/src/icons/KeyboardFill.svg +4 -0
- package/packages/scrabble-solver/src/icons/index.ts +1 -0
- package/packages/scrabble-solver/src/lib/extractCharacters.test.ts +26 -0
- package/packages/scrabble-solver/src/lib/extractCharacters.ts +11 -9
- package/packages/scrabble-solver/src/lib/extractCharactersByCase.test.ts +31 -0
- package/packages/scrabble-solver/src/lib/extractCharactersByCase.ts +31 -0
- package/packages/scrabble-solver/src/lib/index.ts +3 -1
- package/packages/scrabble-solver/src/lib/isUpperCase.ts +7 -0
- package/packages/scrabble-solver/src/modals/SettingsModal/SettingsModal.tsx +5 -1
- package/packages/scrabble-solver/src/modals/SettingsModal/components/InputModeSetting/InputModeSetting.module.scss +12 -0
- package/packages/scrabble-solver/src/modals/SettingsModal/components/InputModeSetting/InputModeSetting.tsx +55 -0
- package/packages/scrabble-solver/src/modals/SettingsModal/components/InputModeSetting/index.ts +1 -0
- package/packages/scrabble-solver/src/modals/SettingsModal/components/InputModeSetting/lib.ts +13 -0
- package/packages/scrabble-solver/src/modals/SettingsModal/components/InputModeSetting/types.ts +7 -0
- package/packages/scrabble-solver/src/modals/SettingsModal/components/index.ts +1 -0
- package/packages/scrabble-solver/src/state/localStorage.ts +10 -1
- package/packages/scrabble-solver/src/state/selectors.ts +2 -0
- package/packages/scrabble-solver/src/state/slices/settingsInitialState.ts +4 -1
- package/packages/scrabble-solver/src/state/slices/settingsSlice.ts +6 -1
- package/packages/scrabble-solver/src/styles/mixins.scss +1 -0
- package/packages/scrabble-solver/src/styles/variables.scss +1 -0
- package/packages/scrabble-solver/src/types/index.ts +7 -0
- package/packages/solver/build/getCellsScore.js +3 -2
- package/packages/solver/package-lock.json +10 -3
- package/packages/solver/package.json +5 -5
- package/packages/solver/src/getCellsScore.ts +3 -2
- package/packages/types/build/Config.d.ts +2 -0
- package/packages/types/build/Config.js +3 -0
- package/packages/types/build/ConfigJson.d.ts +2 -0
- package/packages/types/package.json +2 -2
- package/packages/types/src/Config.ts +5 -0
- package/packages/types/src/ConfigJson.ts +2 -0
- package/packages/word-definitions/package-lock.json +2 -2
- package/packages/word-definitions/package.json +2 -2
- package/packages/word-lists/package-lock.json +403 -11
- package/packages/word-lists/package.json +2 -2
- package/packages/scrabble-solver/.next/static/5ttGCAW8jcIKxpR8om9fK/_buildManifest.js +0 -1
- package/packages/scrabble-solver/.next/static/chunks/pages/_app-76a8840b6244d5a2.js +0 -28
- package/packages/scrabble-solver/.next/static/chunks/pages/index-6894f40e6cac9243.js +0 -1
- package/packages/scrabble-solver/.next/static/css/af871fef886ef5b7.css +0 -2
- package/packages/scrabble-solver/src/components/Solver/components/FloatingSolveButton/FloatingSolveButton.module.scss +0 -7
- package/packages/scrabble-solver/src/components/Solver/components/FloatingSolveButton/FloatingSolveButton.tsx +0 -53
- package/packages/scrabble-solver/src/components/Solver/components/FloatingSolveButton/index.ts +0 -1
- /package/packages/scrabble-solver/.next/static/{5ttGCAW8jcIKxpR8om9fK → fsjQvvJ13WNxBdMioL4sc}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/* eslint-disable max-lines, max-statements */
|
|
2
|
+
|
|
3
|
+
import classNames from 'classnames';
|
|
4
|
+
import {
|
|
5
|
+
CSSProperties,
|
|
6
|
+
ChangeEventHandler,
|
|
7
|
+
FormEventHandler,
|
|
8
|
+
forwardRef,
|
|
9
|
+
useCallback,
|
|
10
|
+
useEffect,
|
|
11
|
+
useState,
|
|
12
|
+
} from 'react';
|
|
13
|
+
import { useDispatch } from 'react-redux';
|
|
14
|
+
|
|
15
|
+
import { useAppLayout } from 'hooks';
|
|
16
|
+
import { extractCharactersByCase } from 'lib';
|
|
17
|
+
import { rackSlice, selectConfig, useTranslate, useTypedSelector } from 'state';
|
|
18
|
+
|
|
19
|
+
import styles from './InputPrompt.module.scss';
|
|
20
|
+
|
|
21
|
+
interface Props {
|
|
22
|
+
className?: string;
|
|
23
|
+
style?: CSSProperties;
|
|
24
|
+
value: string;
|
|
25
|
+
onBlur: () => void;
|
|
26
|
+
onChange: ChangeEventHandler<HTMLInputElement>;
|
|
27
|
+
onSubmit: FormEventHandler<HTMLFormElement>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const InputPrompt = forwardRef<HTMLFormElement, Props>(
|
|
31
|
+
({ className, style, value, onBlur, onChange, onSubmit, ...props }, ref) => {
|
|
32
|
+
const dispatch = useDispatch();
|
|
33
|
+
const translate = useTranslate();
|
|
34
|
+
const { rackHeight, rackWidth } = useAppLayout();
|
|
35
|
+
const config = useTypedSelector(selectConfig);
|
|
36
|
+
const [inputRef, setInputRef] = useState<HTMLInputElement | null>(null);
|
|
37
|
+
|
|
38
|
+
const handleSubmit: FormEventHandler<HTMLFormElement> = useCallback(
|
|
39
|
+
(event) => {
|
|
40
|
+
event.preventDefault();
|
|
41
|
+
const charactersByCase = extractCharactersByCase(config, value);
|
|
42
|
+
const characters = Array.from({ length: config.maximumCharactersCount }, (_, index) => {
|
|
43
|
+
return typeof charactersByCase[index] === 'string' ? charactersByCase[index] : null;
|
|
44
|
+
});
|
|
45
|
+
dispatch(rackSlice.actions.changeCharacters({ characters, index: 0 }));
|
|
46
|
+
onSubmit(event);
|
|
47
|
+
},
|
|
48
|
+
[config, value, onSubmit],
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (inputRef) {
|
|
53
|
+
inputRef.focus();
|
|
54
|
+
inputRef.select();
|
|
55
|
+
}
|
|
56
|
+
}, [inputRef]);
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<form
|
|
60
|
+
className={classNames(styles.form, className)}
|
|
61
|
+
ref={ref}
|
|
62
|
+
style={{
|
|
63
|
+
width: rackWidth,
|
|
64
|
+
height: rackHeight,
|
|
65
|
+
...style,
|
|
66
|
+
}}
|
|
67
|
+
onSubmit={handleSubmit}
|
|
68
|
+
{...props}
|
|
69
|
+
>
|
|
70
|
+
<input
|
|
71
|
+
autoCapitalize="none"
|
|
72
|
+
autoComplete="off"
|
|
73
|
+
autoCorrect="off"
|
|
74
|
+
className={styles.input}
|
|
75
|
+
placeholder={translate('rack.touchscreen.placeholder')}
|
|
76
|
+
ref={setInputRef}
|
|
77
|
+
spellCheck={false}
|
|
78
|
+
value={value}
|
|
79
|
+
onBlur={onBlur}
|
|
80
|
+
onChange={onChange}
|
|
81
|
+
/>
|
|
82
|
+
</form>
|
|
83
|
+
);
|
|
84
|
+
},
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
export default Object.assign(InputPrompt, {
|
|
88
|
+
styles,
|
|
89
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './InputPrompt';
|
|
@@ -4,10 +4,13 @@ import classNames from 'classnames';
|
|
|
4
4
|
import {
|
|
5
5
|
ChangeEvent,
|
|
6
6
|
ChangeEventHandler,
|
|
7
|
+
FocusEventHandler,
|
|
7
8
|
FunctionComponent,
|
|
8
9
|
KeyboardEventHandler,
|
|
10
|
+
MouseEventHandler,
|
|
9
11
|
MutableRefObject,
|
|
10
12
|
RefObject,
|
|
13
|
+
TouchEventHandler,
|
|
11
14
|
useCallback,
|
|
12
15
|
useMemo,
|
|
13
16
|
} from 'react';
|
|
@@ -19,14 +22,15 @@ import {
|
|
|
19
22
|
selectCharacterIsValid,
|
|
20
23
|
selectCharacterPoints,
|
|
21
24
|
selectConfig,
|
|
25
|
+
selectInputMode,
|
|
22
26
|
selectLocale,
|
|
23
27
|
useTranslate,
|
|
24
28
|
useTypedSelector,
|
|
25
29
|
} from 'state';
|
|
26
30
|
|
|
27
|
-
import Tile from '
|
|
31
|
+
import Tile from '../../../Tile';
|
|
28
32
|
|
|
29
|
-
import styles from './
|
|
33
|
+
import styles from './RackTile.module.scss';
|
|
30
34
|
|
|
31
35
|
interface Props {
|
|
32
36
|
activeIndexRef: MutableRefObject<number | undefined>;
|
|
@@ -38,6 +42,7 @@ interface Props {
|
|
|
38
42
|
tile: TileModel | null;
|
|
39
43
|
onChange: ChangeEventHandler<HTMLInputElement>;
|
|
40
44
|
onKeyDown: KeyboardEventHandler<HTMLInputElement>;
|
|
45
|
+
onFocus: () => void;
|
|
41
46
|
}
|
|
42
47
|
|
|
43
48
|
const RackTile: FunctionComponent<Props> = ({
|
|
@@ -50,17 +55,28 @@ const RackTile: FunctionComponent<Props> = ({
|
|
|
50
55
|
tile,
|
|
51
56
|
onChange,
|
|
52
57
|
onKeyDown,
|
|
58
|
+
onFocus,
|
|
53
59
|
}) => {
|
|
54
60
|
const dispatch = useDispatch();
|
|
55
61
|
const translate = useTranslate();
|
|
56
62
|
const locale = useTypedSelector(selectLocale);
|
|
57
63
|
const config = useTypedSelector(selectConfig);
|
|
64
|
+
const inputMode = useTypedSelector(selectInputMode);
|
|
58
65
|
const points = useTypedSelector((state) => selectCharacterPoints(state, character));
|
|
59
66
|
const isValid = useTypedSelector((state) => selectCharacterIsValid(state, character));
|
|
60
67
|
|
|
61
|
-
const handleFocus = useCallback(
|
|
62
|
-
|
|
63
|
-
|
|
68
|
+
const handleFocus: FocusEventHandler<HTMLInputElement> = useCallback(
|
|
69
|
+
(event) => {
|
|
70
|
+
if (inputMode === 'touchscreen') {
|
|
71
|
+
event.preventDefault();
|
|
72
|
+
event.target.blur();
|
|
73
|
+
onFocus();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
activeIndexRef.current = index;
|
|
77
|
+
},
|
|
78
|
+
[activeIndexRef, index, inputMode, onFocus],
|
|
79
|
+
);
|
|
64
80
|
|
|
65
81
|
const handleChange = useCallback(
|
|
66
82
|
(event: ChangeEvent<HTMLInputElement>) => {
|
|
@@ -94,13 +110,35 @@ const RackTile: FunctionComponent<Props> = ({
|
|
|
94
110
|
});
|
|
95
111
|
}, [index, onKeyDown]);
|
|
96
112
|
|
|
113
|
+
const handleMouseDown: MouseEventHandler<HTMLInputElement> = useCallback(
|
|
114
|
+
(event) => {
|
|
115
|
+
if (inputMode === 'touchscreen') {
|
|
116
|
+
event.preventDefault();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
onFocus();
|
|
120
|
+
},
|
|
121
|
+
[inputMode, onFocus],
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
const handleTouchStart: TouchEventHandler<HTMLInputElement> = useCallback(
|
|
125
|
+
(event) => {
|
|
126
|
+
if (inputMode === 'touchscreen') {
|
|
127
|
+
event.preventDefault();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
onFocus();
|
|
131
|
+
},
|
|
132
|
+
[inputMode, onFocus],
|
|
133
|
+
);
|
|
134
|
+
|
|
97
135
|
return (
|
|
98
136
|
<Tile
|
|
99
137
|
aria-label={translate('rack.tile.location', {
|
|
100
138
|
index: (index + 1).toLocaleString(locale),
|
|
101
139
|
})}
|
|
102
|
-
autoFocus={index === 0}
|
|
103
|
-
className={classNames(styles.
|
|
140
|
+
autoFocus={inputMode === 'keyboard' && index === 0}
|
|
141
|
+
className={classNames(styles.rackTile, className)}
|
|
104
142
|
character={character === null ? undefined : character}
|
|
105
143
|
highlighted={tile !== null}
|
|
106
144
|
inputRef={inputRef}
|
|
@@ -115,6 +153,8 @@ const RackTile: FunctionComponent<Props> = ({
|
|
|
115
153
|
onChange={handleChange}
|
|
116
154
|
onFocus={handleFocus}
|
|
117
155
|
onKeyDown={handleKeyDown}
|
|
156
|
+
onMouseDown={handleMouseDown}
|
|
157
|
+
onTouchStart={handleTouchStart}
|
|
118
158
|
/>
|
|
119
159
|
);
|
|
120
160
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './RackTile';
|
|
@@ -15,13 +15,6 @@ $radio-box-size: $radio-size + 2 * $radio-inner-border;
|
|
|
15
15
|
transition: var(--transition);
|
|
16
16
|
cursor: pointer;
|
|
17
17
|
|
|
18
|
-
&:hover,
|
|
19
|
-
&.checked {
|
|
20
|
-
.icon {
|
|
21
|
-
box-shadow: var(--box-shadow);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
18
|
&.checked {
|
|
26
19
|
.icon {
|
|
27
20
|
&::after {
|
|
@@ -48,7 +41,6 @@ $radio-box-size: $radio-size + 2 * $radio-inner-border;
|
|
|
48
41
|
height: $radio-box-size;
|
|
49
42
|
border-radius: 50%;
|
|
50
43
|
border: $radio-inner-border solid var(--color--primary);
|
|
51
|
-
box-shadow: var(--box-shadow--null);
|
|
52
44
|
pointer-events: none;
|
|
53
45
|
|
|
54
46
|
&::after {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import classNames from 'classnames';
|
|
2
|
-
import { FunctionComponent } from 'react';
|
|
2
|
+
import { FunctionComponent, ReactNode } from 'react';
|
|
3
3
|
|
|
4
4
|
import { selectLocale, useTranslate, useTypedSelector } from 'state';
|
|
5
5
|
import { TranslationKey } from 'types';
|
|
@@ -9,13 +9,14 @@ import { useTooltip } from '../Tooltip';
|
|
|
9
9
|
import styles from './Results.module.scss';
|
|
10
10
|
|
|
11
11
|
interface Props {
|
|
12
|
+
children?: ReactNode;
|
|
12
13
|
className?: string;
|
|
13
14
|
translationKey: TranslationKey;
|
|
14
15
|
tooltip?: string | number;
|
|
15
16
|
value: string | number;
|
|
16
17
|
}
|
|
17
18
|
|
|
18
|
-
const Cell: FunctionComponent<Props> = ({ className, translationKey, tooltip, value }) => {
|
|
19
|
+
const Cell: FunctionComponent<Props> = ({ children, className, translationKey, tooltip, value }) => {
|
|
19
20
|
const translate = useTranslate();
|
|
20
21
|
const locale = useTypedSelector(selectLocale);
|
|
21
22
|
const formattedValue = value.toLocaleString(locale);
|
|
@@ -23,7 +24,7 @@ const Cell: FunctionComponent<Props> = ({ className, translationKey, tooltip, va
|
|
|
23
24
|
|
|
24
25
|
return (
|
|
25
26
|
<div className={classNames(styles.cell, className)} {...triggerProps}>
|
|
26
|
-
{formattedValue}
|
|
27
|
+
{children || formattedValue}
|
|
27
28
|
</div>
|
|
28
29
|
);
|
|
29
30
|
};
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import classNames from 'classnames';
|
|
2
2
|
import { CSSProperties, FocusEventHandler, MouseEventHandler, ReactElement, useRef } from 'react';
|
|
3
|
+
import Highlighter from 'react-highlight-words';
|
|
3
4
|
|
|
4
5
|
import { LOCALE_FEATURES } from 'i18n';
|
|
5
6
|
import { noop } from 'lib';
|
|
6
|
-
import { selectIsResultMatching, selectLocale, useTypedSelector } from 'state';
|
|
7
|
+
import { selectIsResultMatching, selectLocale, selectResultsQuery, useTypedSelector } from 'state';
|
|
7
8
|
import { ResultColumn } from 'types';
|
|
8
9
|
|
|
9
10
|
import Cell from './Cell';
|
|
@@ -30,6 +31,7 @@ const Result = ({ data, index, style }: Props): ReactElement => {
|
|
|
30
31
|
const ref = useRef<HTMLButtonElement>(null);
|
|
31
32
|
const columns = useColumns();
|
|
32
33
|
const locale = useTypedSelector(selectLocale);
|
|
34
|
+
const query = useTypedSelector(selectResultsQuery);
|
|
33
35
|
const { consonants, direction, separator, vowels } = LOCALE_FEATURES[locale];
|
|
34
36
|
const result = results[index];
|
|
35
37
|
const isMatching = useTypedSelector((state) => selectIsResultMatching(state, index));
|
|
@@ -60,7 +62,9 @@ const Result = ({ data, index, style }: Props): ReactElement => {
|
|
|
60
62
|
>
|
|
61
63
|
<span className={styles.resultContent}>
|
|
62
64
|
{enabledColumns[ResultColumn.Word] && (
|
|
63
|
-
<Cell className={styles.word} translationKey="common.word" value={result.word}
|
|
65
|
+
<Cell className={styles.word} translationKey="common.word" value={result.word}>
|
|
66
|
+
<Highlighter highlightClassName={styles.highlight} searchWords={[query]} textToHighlight={result.word} />
|
|
67
|
+
</Cell>
|
|
64
68
|
)}
|
|
65
69
|
|
|
66
70
|
{enabledColumns[ResultColumn.TilesCount] && (
|
|
@@ -220,3 +220,9 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
|
|
|
220
220
|
border-bottom-left-radius: var(--border--radius);
|
|
221
221
|
border-bottom-right-radius: var(--border--radius);
|
|
222
222
|
}
|
|
223
|
+
|
|
224
|
+
.highlight {
|
|
225
|
+
background-color: var(--color--mark);
|
|
226
|
+
color: var(--color--foreground);
|
|
227
|
+
border-radius: var(--border--radius);
|
|
228
|
+
}
|
|
@@ -113,23 +113,3 @@
|
|
|
113
113
|
.emptyState {
|
|
114
114
|
margin-top: var(--spacing);
|
|
115
115
|
}
|
|
116
|
-
|
|
117
|
-
.solve {
|
|
118
|
-
--spacing: var(--spacing--l);
|
|
119
|
-
|
|
120
|
-
position: fixed;
|
|
121
|
-
bottom: var(--spacing);
|
|
122
|
-
z-index: 1;
|
|
123
|
-
|
|
124
|
-
@include media('<xs') {
|
|
125
|
-
--spacing: var(--spacing--m);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
[dir='ltr'] & {
|
|
129
|
-
right: var(--spacing);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
[dir='rtl'] & {
|
|
133
|
-
left: var(--spacing);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
@@ -22,7 +22,7 @@ import DictionaryInput from '../DictionaryInput';
|
|
|
22
22
|
import Rack from '../Rack';
|
|
23
23
|
import Results from '../Results';
|
|
24
24
|
|
|
25
|
-
import {
|
|
25
|
+
import { ResultCandidatePicker } from './components';
|
|
26
26
|
import styles from './Solver.module.scss';
|
|
27
27
|
|
|
28
28
|
interface Props {
|
|
@@ -34,7 +34,7 @@ const Solver: FunctionComponent<Props> = ({ className, onShowResults }) => {
|
|
|
34
34
|
const dispatch = useDispatch();
|
|
35
35
|
const translate = useTranslate();
|
|
36
36
|
const isTouchDevice = useIsTouchDevice();
|
|
37
|
-
const { maxControlsWidth, showCompactControls,
|
|
37
|
+
const { maxControlsWidth, showCompactControls, tileSize } = useAppLayout();
|
|
38
38
|
const error = useTypedSelector(selectSolveError);
|
|
39
39
|
const isOutdated = useTypedSelector(selectAreResultsOutdated);
|
|
40
40
|
const resultCandidate = useTypedSelector(selectResultCandidate);
|
|
@@ -134,8 +134,6 @@ const Solver: FunctionComponent<Props> = ({ className, onShowResults }) => {
|
|
|
134
134
|
)}
|
|
135
135
|
</div>
|
|
136
136
|
</div>
|
|
137
|
-
|
|
138
|
-
{showFloatingSolveButton && <FloatingSolveButton className={styles.solve} onClick={handleSubmit} />}
|
|
139
137
|
</div>
|
|
140
138
|
);
|
|
141
139
|
};
|
|
@@ -2,7 +2,6 @@ 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';
|
|
6
5
|
import { ChevronDown, ChevronLeft, ChevronRight } from 'icons';
|
|
7
6
|
import {
|
|
8
7
|
resultsSlice,
|
|
@@ -38,7 +37,6 @@ const ResultCandidatePicker: FunctionComponent<Props> = ({ className, onResultCl
|
|
|
38
37
|
const isPreviousDisabled = !results || index <= 0 || disabled;
|
|
39
38
|
const isNextDisabled = !results || index >= results.length - 1 || disabled;
|
|
40
39
|
const bothEnabled = !isPreviousDisabled && !isNextDisabled;
|
|
41
|
-
const { showFloatingSolveButton } = useAppLayout();
|
|
42
40
|
|
|
43
41
|
const handleNextClick = () => {
|
|
44
42
|
if (!isNextDisabled) {
|
|
@@ -91,14 +89,8 @@ const ResultCandidatePicker: FunctionComponent<Props> = ({ className, onResultCl
|
|
|
91
89
|
{!resultCandidate && <div className={styles.word}> </div>}
|
|
92
90
|
|
|
93
91
|
<div className={styles.iconContainer}>
|
|
94
|
-
{
|
|
95
|
-
|
|
96
|
-
{!showFloatingSolveButton && (
|
|
97
|
-
<>
|
|
98
|
-
{isLoading && <Spinner className={styles.loading} />}
|
|
99
|
-
{!isLoading && <ChevronDown className={styles.icon} />}
|
|
100
|
-
</>
|
|
101
|
-
)}
|
|
92
|
+
{isLoading && <Spinner className={styles.loading} />}
|
|
93
|
+
{!isLoading && <ChevronDown className={styles.icon} />}
|
|
102
94
|
</div>
|
|
103
95
|
</button>
|
|
104
96
|
|
|
@@ -5,7 +5,9 @@ import {
|
|
|
5
5
|
FocusEventHandler,
|
|
6
6
|
FunctionComponent,
|
|
7
7
|
KeyboardEventHandler,
|
|
8
|
+
MouseEventHandler,
|
|
8
9
|
Ref,
|
|
10
|
+
TouchEventHandler,
|
|
9
11
|
useCallback,
|
|
10
12
|
useEffect,
|
|
11
13
|
useMemo,
|
|
@@ -37,6 +39,8 @@ interface Props {
|
|
|
37
39
|
onChange?: ChangeEventHandler<HTMLInputElement>;
|
|
38
40
|
onFocus?: FocusEventHandler<HTMLInputElement>;
|
|
39
41
|
onKeyDown?: KeyboardEventHandler<HTMLInputElement>;
|
|
42
|
+
onMouseDown?: MouseEventHandler<HTMLInputElement>;
|
|
43
|
+
onTouchStart?: TouchEventHandler<HTMLInputElement>;
|
|
40
44
|
}
|
|
41
45
|
|
|
42
46
|
const Tile: FunctionComponent<Props> = ({
|
|
@@ -57,6 +61,8 @@ const Tile: FunctionComponent<Props> = ({
|
|
|
57
61
|
onChange,
|
|
58
62
|
onFocus = noop,
|
|
59
63
|
onKeyDown = noop,
|
|
64
|
+
onMouseDown = noop,
|
|
65
|
+
onTouchStart = noop,
|
|
60
66
|
}) => {
|
|
61
67
|
const locale = useTypedSelector(selectLocale);
|
|
62
68
|
const { animateTile, showTilePoints } = useAppLayout();
|
|
@@ -77,12 +83,6 @@ const Tile: FunctionComponent<Props> = ({
|
|
|
77
83
|
[onKeyDown],
|
|
78
84
|
);
|
|
79
85
|
|
|
80
|
-
useEffect(() => {
|
|
81
|
-
if (autoFocus && ref.current) {
|
|
82
|
-
ref.current.focus();
|
|
83
|
-
}
|
|
84
|
-
}, [autoFocus, ref]);
|
|
85
|
-
|
|
86
86
|
useEffect(() => {
|
|
87
87
|
if (!ref.current?.parentElement || !character || !animateTile) {
|
|
88
88
|
return;
|
|
@@ -117,6 +117,8 @@ const Tile: FunctionComponent<Props> = ({
|
|
|
117
117
|
onChange={onChange}
|
|
118
118
|
onFocus={onFocus}
|
|
119
119
|
onKeyDown={handleKeyDown}
|
|
120
|
+
onMouseDown={onMouseDown}
|
|
121
|
+
onTouchStart={onTouchStart}
|
|
120
122
|
/>
|
|
121
123
|
);
|
|
122
124
|
};
|
|
@@ -6,7 +6,9 @@ import {
|
|
|
6
6
|
FunctionComponent,
|
|
7
7
|
KeyboardEventHandler,
|
|
8
8
|
memo,
|
|
9
|
+
MouseEventHandler,
|
|
9
10
|
Ref,
|
|
11
|
+
TouchEventHandler,
|
|
10
12
|
} from 'react';
|
|
11
13
|
|
|
12
14
|
import { ExclamationSquareFill } from 'icons';
|
|
@@ -34,6 +36,8 @@ interface Props {
|
|
|
34
36
|
onChange?: ChangeEventHandler<HTMLInputElement>;
|
|
35
37
|
onFocus?: FocusEventHandler<HTMLInputElement>;
|
|
36
38
|
onKeyDown?: KeyboardEventHandler<HTMLInputElement>;
|
|
39
|
+
onMouseDown?: MouseEventHandler<HTMLInputElement>;
|
|
40
|
+
onTouchStart?: TouchEventHandler<HTMLInputElement>;
|
|
37
41
|
}
|
|
38
42
|
|
|
39
43
|
const TilePure: FunctionComponent<Props> = ({
|
|
@@ -57,6 +61,8 @@ const TilePure: FunctionComponent<Props> = ({
|
|
|
57
61
|
onChange,
|
|
58
62
|
onFocus,
|
|
59
63
|
onKeyDown,
|
|
64
|
+
onMouseDown,
|
|
65
|
+
onTouchStart,
|
|
60
66
|
}) => (
|
|
61
67
|
<div
|
|
62
68
|
className={classNames(styles.tile, className, {
|
|
@@ -90,6 +96,8 @@ const TilePure: FunctionComponent<Props> = ({
|
|
|
90
96
|
onChange={onChange}
|
|
91
97
|
onFocus={onFocus}
|
|
92
98
|
onKeyDown={onKeyDown}
|
|
99
|
+
onMouseDown={onMouseDown}
|
|
100
|
+
onTouchStart={onTouchStart}
|
|
93
101
|
/>
|
|
94
102
|
|
|
95
103
|
{canShowPoints && (
|
|
@@ -56,6 +56,7 @@ const useAppLayout = () => {
|
|
|
56
56
|
const resultsHeight = isLessThanL
|
|
57
57
|
? viewportHeight - dictionaryHeight - BUTTON_HEIGHT - MODAL_HEADER_HEIGHT - 5 * componentsSpacing
|
|
58
58
|
: boardSize - componentsSpacing - dictionaryHeight;
|
|
59
|
+
const rackWidth = tileSize * config.maximumCharactersCount;
|
|
59
60
|
|
|
60
61
|
return {
|
|
61
62
|
actionsWidth: 2 * BUTTON_HEIGHT - BORDER_WIDTH,
|
|
@@ -67,10 +68,11 @@ const useAppLayout = () => {
|
|
|
67
68
|
logoHeight,
|
|
68
69
|
logoWidth: logoHeight * LOGO_ASPECT_RATIO,
|
|
69
70
|
maxControlsWidth,
|
|
71
|
+
rackHeight: tileSize,
|
|
72
|
+
rackWidth,
|
|
70
73
|
resultsHeight,
|
|
71
74
|
resultsWidth: isLessThanL ? modalWidth - 2 * componentsSpacing : SOLVER_COLUMN_WIDTH,
|
|
72
75
|
showCompactControls: !showColumn,
|
|
73
|
-
showFloatingSolveButton: isTouchDevice,
|
|
74
76
|
showKeyMap: !isTouchDevice,
|
|
75
77
|
showResultsInModal,
|
|
76
78
|
showShortNav: isLessThanS,
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
selectAutoGroupTiles,
|
|
6
6
|
selectBoard,
|
|
7
7
|
selectConfigId,
|
|
8
|
+
selectInputMode,
|
|
8
9
|
selectLocale,
|
|
9
10
|
selectRack,
|
|
10
11
|
useTypedSelector,
|
|
@@ -14,6 +15,7 @@ const useLocalStorage = () => {
|
|
|
14
15
|
const autoGroupTiles = useTypedSelector(selectAutoGroupTiles);
|
|
15
16
|
const board = useTypedSelector(selectBoard);
|
|
16
17
|
const configId = useTypedSelector(selectConfigId);
|
|
18
|
+
const inputMode = useTypedSelector(selectInputMode);
|
|
17
19
|
const locale = useTypedSelector(selectLocale);
|
|
18
20
|
const rack = useTypedSelector(selectRack);
|
|
19
21
|
|
|
@@ -35,6 +37,12 @@ const useLocalStorage = () => {
|
|
|
35
37
|
}
|
|
36
38
|
}, [configId]);
|
|
37
39
|
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (inputMode) {
|
|
42
|
+
localStorage.setInputMode(inputMode);
|
|
43
|
+
}
|
|
44
|
+
}, [inputMode]);
|
|
45
|
+
|
|
38
46
|
useEffect(() => {
|
|
39
47
|
if (locale) {
|
|
40
48
|
localStorage.setLocale(locale);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
+
"cell.enter-word": "Wort eingeben",
|
|
2
3
|
"cell.filter-cell": "Zielort",
|
|
3
4
|
"cell.set-blank": "Als Blanko markieren",
|
|
4
5
|
"cell.set-not-blank": "Nicht als Blanko markieren",
|
|
@@ -22,7 +23,7 @@
|
|
|
22
23
|
"dictionary.empty-state.no-results": "Wort kann nicht im Wörterbuch gefunden werden.",
|
|
23
24
|
"dictionary.empty-state.not-allowed": "Dieses Wort ist nicht erlaubt.",
|
|
24
25
|
"dictionary.empty-state.uninitialized": "Die Wörterbuchdéfinition wird hier angezeigt.",
|
|
25
|
-
"dictionary.input.placeholder": "Durchsuche Wörterbuch
|
|
26
|
+
"dictionary.input.placeholder": "Durchsuche Wörterbuch…",
|
|
26
27
|
"dictionary.input.title": "Durch Kommas getrennte Wörter",
|
|
27
28
|
"empty-state.error": "Fehler",
|
|
28
29
|
"empty-state.info": "Info",
|
|
@@ -43,6 +44,7 @@
|
|
|
43
44
|
"menu": "Menü",
|
|
44
45
|
"rack.placeholder": "Steine…",
|
|
45
46
|
"rack.tile.location": "Ablage: Stein ({{index}})",
|
|
47
|
+
"rack.touchscreen.placeholder": "Steine…",
|
|
46
48
|
"remaining-tiles": "Restliche Steine",
|
|
47
49
|
"results": "Ergebnisse",
|
|
48
50
|
"results.empty-state.no-results": "Keine Ergebnisse - kein Wort konnte generiert werden.",
|
|
@@ -58,6 +60,9 @@
|
|
|
58
60
|
"settings.autoGroupTiles.right": "Rechte Seite",
|
|
59
61
|
"settings.autoGroupTiles.null": "Nicht gruppieren",
|
|
60
62
|
"settings.game": "Spiel",
|
|
63
|
+
"settings.inputMode": "Eingabemodus",
|
|
64
|
+
"settings.inputMode.keyboard": "Tastatur",
|
|
65
|
+
"settings.inputMode.touchscreen": "Touchscreen",
|
|
61
66
|
"settings.language": "Sprache",
|
|
62
67
|
"words": "Gebildete Wörter",
|
|
63
68
|
"words.invalid": "Falsch",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
+
"cell.enter-word": "Enter word",
|
|
2
3
|
"cell.filter-cell": "Target destination",
|
|
3
4
|
"cell.set-blank": "Mark it a blank",
|
|
4
5
|
"cell.set-not-blank": "Mark it not a blank",
|
|
@@ -22,7 +23,7 @@
|
|
|
22
23
|
"dictionary.empty-state.no-results": "Unable to find word definition in the dictionary.",
|
|
23
24
|
"dictionary.empty-state.not-allowed": "This word is not allowed.",
|
|
24
25
|
"dictionary.empty-state.uninitialized": "Word definition will be shown here.",
|
|
25
|
-
"dictionary.input.placeholder": "Search dictionary
|
|
26
|
+
"dictionary.input.placeholder": "Search dictionary…",
|
|
26
27
|
"dictionary.input.title": "Comma-separated words",
|
|
27
28
|
"empty-state.error": "Error",
|
|
28
29
|
"empty-state.info": "Info",
|
|
@@ -43,6 +44,7 @@
|
|
|
43
44
|
"menu": "Menu",
|
|
44
45
|
"rack.placeholder": "Letters",
|
|
45
46
|
"rack.tile.location": "Rack: tile ({{index}})",
|
|
47
|
+
"rack.touchscreen.placeholder": "Letters…",
|
|
46
48
|
"remaining-tiles": "Remaining tiles",
|
|
47
49
|
"results": "Results",
|
|
48
50
|
"results.empty-state.no-results": "No results - unable to generate any words.",
|
|
@@ -58,6 +60,9 @@
|
|
|
58
60
|
"settings.autoGroupTiles.right": "On the right",
|
|
59
61
|
"settings.autoGroupTiles.null": "Do not group",
|
|
60
62
|
"settings.game": "Game",
|
|
63
|
+
"settings.inputMode": "Input mode",
|
|
64
|
+
"settings.inputMode.keyboard": "Keyboard",
|
|
65
|
+
"settings.inputMode.touchscreen": "Touchscreen",
|
|
61
66
|
"settings.language": "Language",
|
|
62
67
|
"words": "Created words",
|
|
63
68
|
"words.invalid": "Invalid",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
+
"cell.enter-word": "Ingresar palabra",
|
|
2
3
|
"cell.filter-cell": "Destino objetivo",
|
|
3
4
|
"cell.set-blank": "Marcar como en blanco",
|
|
4
5
|
"cell.set-not-blank": "Marcar como no en blanco",
|
|
@@ -22,7 +23,7 @@
|
|
|
22
23
|
"dictionary.empty-state.no-results": "No se puede encontrar la definición de palabra en el diccionario.",
|
|
23
24
|
"dictionary.empty-state.not-allowed": "Esta palabra no es aceptable.",
|
|
24
25
|
"dictionary.empty-state.uninitialized": "Aquí se mostrará la definición del diccionario.",
|
|
25
|
-
"dictionary.input.placeholder": "Busca el diccionario
|
|
26
|
+
"dictionary.input.placeholder": "Busca el diccionario…",
|
|
26
27
|
"dictionary.input.title": "Palabras separadas por comas",
|
|
27
28
|
"empty-state.error": "Error",
|
|
28
29
|
"empty-state.info": "Info",
|
|
@@ -43,6 +44,7 @@
|
|
|
43
44
|
"menu": "Menú",
|
|
44
45
|
"rack.placeholder": "Letras…",
|
|
45
46
|
"rack.tile.location": "Estante: espacio ({{index}})",
|
|
47
|
+
"rack.touchscreen.placeholder": "Letras… (p.ej. LLabcCHhRR)",
|
|
46
48
|
"remaining-tiles": "Casillas restantes",
|
|
47
49
|
"results": "Resultados",
|
|
48
50
|
"results.empty-state.no-results": "No hay resultados; no se pueden generar palabras",
|
|
@@ -58,6 +60,9 @@
|
|
|
58
60
|
"settings.autoGroupTiles.right": "A la derecha",
|
|
59
61
|
"settings.autoGroupTiles.null": "No agrupar",
|
|
60
62
|
"settings.game": "Juego",
|
|
63
|
+
"settings.inputMode": "Modo de entrada",
|
|
64
|
+
"settings.inputMode.keyboard": "Teclado",
|
|
65
|
+
"settings.inputMode.touchscreen": "Pantalla táctil",
|
|
61
66
|
"settings.language": "Idioma",
|
|
62
67
|
"words": "Palabras creadas",
|
|
63
68
|
"words.invalid": "Incorrecto",
|