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