@scrabble-solver/scrabble-solver 2.11.8 → 2.11.9
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/131.js +153 -115
- package/.next/server/chunks/277.js +1378 -682
- package/.next/server/chunks/44.js +3 -0
- package/.next/server/chunks/50.js +20 -78
- package/.next/server/chunks/911.js +14 -14
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/404.js.nft.json +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/pages/_app.js +8 -0
- package/.next/server/pages/_app.js.nft.json +1 -1
- package/.next/server/pages/api/solve.js +43 -11
- package/.next/server/pages/index.html +1 -1
- package/.next/server/pages/index.js +144 -11
- package/.next/server/pages/index.js.nft.json +1 -1
- package/.next/server/pages/index.json +1 -1
- package/.next/server/pages-manifest.json +2 -2
- package/.next/static/9oRWxnZ1xFLSs55FJtiYi/_buildManifest.js +1 -0
- package/.next/static/chunks/pages/{404-ca203fa27afc37d8.js → 404-b4b5ce15153d4825.js} +1 -1
- package/.next/static/chunks/pages/_app-b0231bed954dd413.js +28 -0
- package/.next/static/chunks/pages/index-4e8566409753e1c3.js +1 -0
- package/.next/static/css/{c6e0e01f44fc0425.css → 60e8258da7362a1a.css} +1 -1
- package/.next/static/css/fcc46fec97b11afc.css +2 -0
- package/.next/trace +52 -50
- package/package.json +14 -13
- package/src/components/Board/Board.module.scss +18 -4
- package/src/components/Board/Board.tsx +145 -76
- package/src/components/Board/BoardPure.tsx +32 -40
- package/src/components/Board/components/Actions/Actions.module.scss +6 -17
- package/src/components/Board/components/Actions/Actions.tsx +36 -18
- package/src/components/Board/components/Cell/Cell.module.scss +12 -13
- package/src/components/Board/components/Cell/Cell.tsx +53 -3
- package/src/components/Board/components/InputPrompt/InputPrompt.module.scss +47 -0
- package/src/components/Board/components/InputPrompt/InputPrompt.tsx +81 -0
- package/src/components/Board/components/InputPrompt/index.ts +1 -0
- package/src/components/Board/components/ToggleDirectionButton/ToggleDirectionButton.module.scss +21 -0
- package/src/components/Board/components/ToggleDirectionButton/ToggleDirectionButton.tsx +34 -0
- package/src/components/Board/components/ToggleDirectionButton/index.ts +1 -0
- package/src/components/Board/components/index.ts +2 -0
- package/src/components/Board/hooks/index.ts +4 -0
- package/src/components/Board/hooks/useBoardStyle.ts +27 -0
- package/src/components/Board/hooks/useFloatingActions.ts +22 -0
- package/src/components/Board/hooks/useFloatingFocus.ts +10 -0
- package/src/components/Board/hooks/useFloatingInputPrompt.ts +19 -0
- package/src/components/Board/hooks/useGrid.ts +2 -1
- package/src/components/NavButtons/NavButtons.tsx +2 -2
- package/src/components/Rack/Rack.module.scss +6 -6
- package/src/components/Rack/Rack.tsx +98 -23
- package/src/components/Rack/components/InputPrompt/InputPrompt.module.scss +22 -0
- package/src/components/Rack/components/InputPrompt/InputPrompt.tsx +89 -0
- package/src/components/Rack/components/InputPrompt/index.ts +1 -0
- package/src/components/Rack/components/RackTile/RackTile.module.scss +11 -0
- package/src/components/Rack/{RackTile.tsx → components/RackTile/RackTile.tsx} +47 -7
- package/src/components/Rack/components/RackTile/index.ts +1 -0
- package/src/components/Rack/components/index.ts +2 -0
- package/src/components/Radio/Radio.module.scss +0 -8
- package/src/components/Solver/Solver.module.scss +0 -20
- package/src/components/Solver/Solver.tsx +2 -4
- package/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.tsx +2 -10
- package/src/components/Solver/components/index.ts +0 -1
- package/src/components/Tile/Tile.module.scss +1 -0
- package/src/components/Tile/Tile.tsx +8 -6
- package/src/components/Tile/TilePure.tsx +8 -0
- package/src/hooks/useAppLayout.ts +3 -1
- package/src/hooks/useLocalStorage.ts +8 -0
- package/src/i18n/de.json +6 -1
- package/src/i18n/en.json +6 -1
- package/src/i18n/es.json +6 -1
- package/src/i18n/fa.json +6 -1
- package/src/i18n/fr.json +6 -1
- package/src/i18n/pl.json +6 -1
- package/src/icons/Keyboard.svg +4 -3
- package/src/icons/KeyboardFill.svg +4 -0
- package/src/icons/index.ts +1 -0
- package/src/lib/extractCharacters.test.ts +26 -0
- package/src/lib/extractCharacters.ts +11 -9
- package/src/lib/extractCharactersByCase.test.ts +31 -0
- package/src/lib/extractCharactersByCase.ts +31 -0
- package/src/lib/index.ts +3 -1
- package/src/lib/isUpperCase.ts +7 -0
- package/src/modals/SettingsModal/SettingsModal.tsx +5 -1
- package/src/modals/SettingsModal/components/InputModeSetting/InputModeSetting.module.scss +12 -0
- package/src/modals/SettingsModal/components/InputModeSetting/InputModeSetting.tsx +55 -0
- package/src/modals/SettingsModal/components/InputModeSetting/index.ts +1 -0
- package/src/modals/SettingsModal/components/InputModeSetting/lib.ts +13 -0
- package/src/modals/SettingsModal/components/InputModeSetting/types.ts +7 -0
- package/src/modals/SettingsModal/components/index.ts +1 -0
- package/src/state/localStorage.ts +10 -1
- package/src/state/selectors.ts +2 -0
- package/src/state/slices/settingsInitialState.ts +4 -1
- package/src/state/slices/settingsSlice.ts +6 -1
- package/src/styles/mixins.scss +1 -0
- package/src/types/index.ts +7 -0
- package/.next/static/5ttGCAW8jcIKxpR8om9fK/_buildManifest.js +0 -1
- package/.next/static/chunks/pages/_app-76a8840b6244d5a2.js +0 -28
- package/.next/static/chunks/pages/index-6894f40e6cac9243.js +0 -1
- package/.next/static/css/af871fef886ef5b7.css +0 -2
- package/src/components/Solver/components/FloatingSolveButton/FloatingSolveButton.module.scss +0 -7
- package/src/components/Solver/components/FloatingSolveButton/FloatingSolveButton.tsx +0 -53
- package/src/components/Solver/components/FloatingSolveButton/index.ts +0 -1
- /package/.next/static/{5ttGCAW8jcIKxpR8om9fK → 9oRWxnZ1xFLSs55FJtiYi}/_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 {
|
|
@@ -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);
|
package/src/i18n/de.json
CHANGED
|
@@ -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",
|
package/src/i18n/en.json
CHANGED
|
@@ -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",
|
package/src/i18n/es.json
CHANGED
|
@@ -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",
|
package/src/i18n/fa.json
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
+
"cell.enter-word": "کلمه را وارد کنید",
|
|
2
3
|
"cell.filter-cell": "مقصد",
|
|
3
4
|
"cell.set-blank": "علامت گذاری به عنوان خالی",
|
|
4
5
|
"cell.set-not-blank": "علامت گذاری به عنوان غیر خالی",
|
|
@@ -22,7 +23,7 @@
|
|
|
22
23
|
"dictionary.empty-state.no-results": "کلمه در فرهنگ لغت یافت نشد.",
|
|
23
24
|
"dictionary.empty-state.not-allowed": "این کلمه مجاز نیست.",
|
|
24
25
|
"dictionary.empty-state.uninitialized": "معنی لغت اینجا نمایش داده خواهد شد.",
|
|
25
|
-
"dictionary.input.placeholder": "جستجو در فرهنگ لغت
|
|
26
|
+
"dictionary.input.placeholder": "جستجو در فرهنگ لغت …",
|
|
26
27
|
"dictionary.input.title": "کلمات جدا شده با کاما",
|
|
27
28
|
"empty-state.error": "خطا",
|
|
28
29
|
"empty-state.info": "اطلاعات",
|
|
@@ -43,6 +44,7 @@
|
|
|
43
44
|
"menu": "منو",
|
|
44
45
|
"rack.placeholder": "لیستحرف",
|
|
45
46
|
"rack.tile.location": "({{index}}) کاشی: طاقچه",
|
|
47
|
+
"rack.touchscreen.placeholder": "لیستحرف…",
|
|
46
48
|
"remaining-tiles": "کاشی های باقی مانده",
|
|
47
49
|
"results": "نتایج",
|
|
48
50
|
"results.empty-state.no-results": "کلمه قابل استفاده پیدا نشد.",
|
|
@@ -58,6 +60,9 @@
|
|
|
58
60
|
"settings.autoGroupTiles.right": "در سمت راست",
|
|
59
61
|
"settings.autoGroupTiles.null": "کنار هم قرار نده",
|
|
60
62
|
"settings.game": "بازی",
|
|
63
|
+
"settings.inputMode": "حالت ورودی",
|
|
64
|
+
"settings.inputMode.keyboard": "صفحه کلید",
|
|
65
|
+
"settings.inputMode.touchscreen": "صفحه لمسی",
|
|
61
66
|
"settings.language": "زبان",
|
|
62
67
|
"words": "کلمات ساخته شده",
|
|
63
68
|
"words.invalid": "نا معتبر",
|
package/src/i18n/fr.json
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
+
"cell.enter-word": "Entrez un mot",
|
|
2
3
|
"cell.filter-cell": "Destination cible",
|
|
3
4
|
"cell.set-blank": "Marquer comme vide",
|
|
4
5
|
"cell.set-not-blank": "Marquer comme non vide",
|
|
@@ -22,7 +23,7 @@
|
|
|
22
23
|
"dictionary.empty-state.no-results": "Impossible de trouver une définition pour ce mot dans le dictionaire.",
|
|
23
24
|
"dictionary.empty-state.not-allowed": "Ce mot n'est pas pas acceptable.",
|
|
24
25
|
"dictionary.empty-state.uninitialized": "La définition dictionaire sera affichée ici.",
|
|
25
|
-
"dictionary.input.placeholder": "Rechercher dans le dictionnaire
|
|
26
|
+
"dictionary.input.placeholder": "Rechercher dans le dictionnaire…",
|
|
26
27
|
"dictionary.input.title": "Mots séparées par des virgules",
|
|
27
28
|
"empty-state.error": "Erreur",
|
|
28
29
|
"empty-state.info": "Info",
|
|
@@ -43,6 +44,7 @@
|
|
|
43
44
|
"menu": "Menu",
|
|
44
45
|
"rack.placeholder": "Lettres",
|
|
45
46
|
"rack.tile.location": "Chevalet: la case ({{index}})",
|
|
47
|
+
"rack.touchscreen.placeholder": "Lettres…",
|
|
46
48
|
"remaining-tiles": "Cases restantes",
|
|
47
49
|
"results": "Résultats",
|
|
48
50
|
"results.empty-state.no-results": "Pas de résultats - impossible de générer des mots.",
|
|
@@ -58,6 +60,9 @@
|
|
|
58
60
|
"settings.autoGroupTiles.right": "Vers la gauche",
|
|
59
61
|
"settings.autoGroupTiles.null": "Ne pas grouper",
|
|
60
62
|
"settings.game": "Jeu",
|
|
63
|
+
"settings.inputMode": "Mode de saisie",
|
|
64
|
+
"settings.inputMode.keyboard": "Clavier",
|
|
65
|
+
"settings.inputMode.touchscreen": "Écran tactile",
|
|
61
66
|
"settings.language": "Langue",
|
|
62
67
|
"words": "Mots créés",
|
|
63
68
|
"words.invalid": "Incorrect",
|