scrabble-solver 2.11.5 → 2.11.7
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/README.md +2 -2
- package/lerna.json +1 -1
- package/nx.json +19 -0
- package/package.json +1 -1
- 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 +2 -2
- package/packages/logger/package.json +1 -1
- package/packages/scrabble-solver/.next/BUILD_ID +1 -1
- package/packages/scrabble-solver/.next/build-manifest.json +11 -11
- 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/277.js +778 -746
- package/packages/scrabble-solver/.next/server/middleware-build-manifest.js +1 -1
- package/packages/scrabble-solver/.next/server/pages/404.html +1 -5
- 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 +9 -2
- package/packages/scrabble-solver/.next/server/pages/_app.js.nft.json +1 -1
- package/packages/scrabble-solver/.next/server/pages/api/solve.js +29 -1
- package/packages/scrabble-solver/.next/server/pages/index.html +1 -1
- package/packages/scrabble-solver/.next/server/pages/index.js +10 -4
- 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/framework-2c5cac93e8c637b5.js +49 -0
- package/packages/scrabble-solver/.next/static/chunks/pages/{404-d30fe85d005ce32b.js → 404-ca203fa27afc37d8.js} +1 -1
- package/packages/scrabble-solver/.next/static/chunks/pages/_app-e89a3c225b87516a.js +28 -0
- package/packages/scrabble-solver/.next/static/chunks/pages/index-58744f49bf6b891f.js +1 -0
- package/packages/scrabble-solver/.next/static/css/34adfcf12a7d9bb6.css +1 -0
- package/packages/scrabble-solver/.next/static/css/edaeaa48321b4cf2.css +2 -0
- package/packages/scrabble-solver/.next/static/uhB6d-q63uRC6RubwepLq/_buildManifest.js +1 -0
- package/packages/scrabble-solver/.next/trace +50 -50
- package/packages/scrabble-solver/package-lock.json +2 -2
- package/packages/scrabble-solver/package.json +8 -8
- package/packages/scrabble-solver/src/components/Board/Board.module.scss +2 -59
- package/packages/scrabble-solver/src/components/Board/Board.tsx +22 -10
- package/packages/scrabble-solver/src/components/Board/BoardPure.tsx +13 -8
- package/packages/scrabble-solver/src/components/Board/components/Cell/Cell.module.scss +8 -144
- package/packages/scrabble-solver/src/components/Board/components/Cell/Cell.tsx +18 -37
- package/packages/scrabble-solver/src/components/Board/hooks/index.ts +1 -0
- package/packages/scrabble-solver/src/components/Board/hooks/useBackgroundImage.tsx +170 -0
- package/packages/scrabble-solver/src/components/Board/lib/getBonusColor.ts +18 -0
- package/packages/scrabble-solver/src/components/Board/lib/index.ts +1 -0
- package/packages/scrabble-solver/src/components/Dictionary/Dictionary.module.scss +0 -1
- package/packages/scrabble-solver/src/components/DictionaryInput/DictionaryInput.tsx +5 -3
- package/packages/scrabble-solver/src/components/EmptyState/EmptyState.module.scss +0 -1
- package/packages/scrabble-solver/src/components/Key/Key.module.scss +1 -1
- package/packages/scrabble-solver/src/components/PlainTiles/PlainTiles.tsx +0 -10
- package/packages/scrabble-solver/src/components/PlainTiles/Tile.tsx +1 -4
- package/packages/scrabble-solver/src/components/Results/Cell.tsx +2 -2
- package/packages/scrabble-solver/src/components/Results/Result.tsx +4 -8
- package/packages/scrabble-solver/src/components/Results/Results.module.scss +5 -4
- package/packages/scrabble-solver/src/components/Solver/Solver.tsx +2 -2
- package/packages/scrabble-solver/src/components/Tooltip/Tooltip.module.scss +2 -0
- package/packages/scrabble-solver/src/components/index.ts +0 -2
- package/packages/scrabble-solver/src/hooks/useAppLayout.ts +1 -0
- package/packages/scrabble-solver/src/hooks/useDirection.ts +2 -2
- package/packages/scrabble-solver/src/hooks/useLanguage.ts +2 -2
- package/packages/scrabble-solver/src/i18n/constants.ts +25 -16
- package/packages/scrabble-solver/src/i18n/de.json +2 -2
- package/packages/scrabble-solver/src/i18n/en.json +2 -2
- package/packages/scrabble-solver/src/i18n/es.json +2 -2
- package/packages/scrabble-solver/src/i18n/fa.json +2 -2
- package/packages/scrabble-solver/src/i18n/fr.json +2 -2
- package/packages/scrabble-solver/src/i18n/pl.json +2 -2
- package/packages/scrabble-solver/src/lib/dataUrlToBlob.ts +20 -0
- package/packages/scrabble-solver/src/lib/index.ts +1 -0
- package/packages/scrabble-solver/src/modals/KeyMapModal/components/Mapping/Mapping.module.scss +0 -1
- package/packages/scrabble-solver/src/modals/SettingsModal/components/ConfigSetting/ConfigSetting.module.scss +0 -1
- package/packages/scrabble-solver/src/modals/SettingsModal/components/LocaleSetting/LocaleSetting.module.scss +0 -5
- package/packages/scrabble-solver/src/pages/_app.tsx +1 -3
- package/packages/scrabble-solver/src/pages/index.tsx +1 -3
- package/packages/scrabble-solver/src/parameters/index.ts +33 -2
- package/packages/scrabble-solver/src/state/index.ts +1 -1
- package/packages/scrabble-solver/src/state/sagas.ts +3 -4
- package/packages/scrabble-solver/src/state/store.ts +34 -0
- package/packages/scrabble-solver/src/styles/global.scss +4 -8
- package/packages/scrabble-solver/src/styles/mixins.scss +0 -1
- package/packages/scrabble-solver/src/styles/variables.scss +5 -5
- package/packages/solver/package-lock.json +2 -2
- package/packages/solver/package.json +5 -5
- package/packages/types/package.json +2 -2
- package/packages/word-definitions/package-lock.json +156 -6
- package/packages/word-definitions/package.json +2 -2
- package/packages/word-lists/package-lock.json +2 -2
- package/packages/word-lists/package.json +2 -2
- package/screencast.gif +0 -0
- package/img/screencast.gif +0 -0
- package/packages/scrabble-solver/.next/static/UzQCOB6CHhyOupkEq8oZM/_buildManifest.js +0 -1
- package/packages/scrabble-solver/.next/static/chunks/framework-2c79e2a64abdb08b.js +0 -33
- package/packages/scrabble-solver/.next/static/chunks/pages/_app-e27464a187a58684.js +0 -28
- package/packages/scrabble-solver/.next/static/chunks/pages/index-3fd280f406cc00fd.js +0 -1
- package/packages/scrabble-solver/.next/static/css/4bd04cebe207859c.css +0 -1
- package/packages/scrabble-solver/.next/static/css/5b3b78170f4c5875.css +0 -2
- package/packages/scrabble-solver/src/components/Board/components/Cell/CellPure.tsx +0 -93
- package/packages/scrabble-solver/src/components/Board/components/Cell/lib.ts +0 -59
- package/packages/scrabble-solver/src/components/SvgFontCss/SvgFontCss.tsx +0 -14
- package/packages/scrabble-solver/src/components/SvgFontCss/createCss.ts +0 -11
- package/packages/scrabble-solver/src/components/SvgFontCss/createStyle.ts +0 -9
- package/packages/scrabble-solver/src/components/SvgFontCss/createSvg.ts +0 -10
- package/packages/scrabble-solver/src/components/SvgFontCss/index.ts +0 -1
- package/packages/scrabble-solver/src/components/SvgFontFix/SvgFontFix.module.scss +0 -5
- package/packages/scrabble-solver/src/components/SvgFontFix/SvgFontFix.tsx +0 -21
- package/packages/scrabble-solver/src/components/SvgFontFix/index.ts +0 -1
- package/packages/scrabble-solver/src/state/createAppStore.ts +0 -38
- /package/packages/scrabble-solver/.next/static/{UzQCOB6CHhyOupkEq8oZM → uhB6d-q63uRC6RubwepLq}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/* eslint-disable max-statements */
|
|
2
|
+
|
|
3
|
+
import { BONUS_CHARACTER, BONUS_WORD } from '@scrabble-solver/constants';
|
|
4
|
+
import { useMemo } from 'react';
|
|
5
|
+
import { renderToString } from 'react-dom/server';
|
|
6
|
+
import { Provider } from 'react-redux';
|
|
7
|
+
|
|
8
|
+
import { useAppLayout } from 'hooks';
|
|
9
|
+
import { FlagFill, Star } from 'icons';
|
|
10
|
+
import { dataUrlToBlob, getTileSizes } from 'lib';
|
|
11
|
+
import { BORDER_COLOR_LIGHT, BORDER_RADIUS, BORDER_WIDTH, COLOR_BONUS_START, COLOR_FILTERED } from 'parameters';
|
|
12
|
+
import { selectCellFilter, selectConfig, store, useTypedSelector } from 'state';
|
|
13
|
+
import { Point } from 'types';
|
|
14
|
+
|
|
15
|
+
import { getBonusColor } from '../lib';
|
|
16
|
+
|
|
17
|
+
const HORIZONTAL_LINE = 'h';
|
|
18
|
+
const VERTICAL_LINE = 'v';
|
|
19
|
+
const BONUS = 'b';
|
|
20
|
+
const BONUS_WORD_2 = 'b2';
|
|
21
|
+
const BONUS_WORD_3 = 'b3';
|
|
22
|
+
const CELL_FILTER = 'c';
|
|
23
|
+
|
|
24
|
+
const useBackgroundImage = () => {
|
|
25
|
+
const { boardSize, cellSize } = useAppLayout();
|
|
26
|
+
const config = useTypedSelector(selectConfig);
|
|
27
|
+
const center = { x: Math.floor(config.boardWidth / 2), y: Math.floor(config.boardHeight / 2) };
|
|
28
|
+
const cellFilter = useTypedSelector(selectCellFilter);
|
|
29
|
+
const viewBoxHeight = boardSize;
|
|
30
|
+
const viewBoxWidth = boardSize;
|
|
31
|
+
const bonusSize = cellSize * 0.8;
|
|
32
|
+
const bonusOffset = cellSize * 0.1;
|
|
33
|
+
const iconSize = cellSize * 0.4;
|
|
34
|
+
const iconOffset = (cellSize - iconSize) / 2;
|
|
35
|
+
const { tileFontSize } = getTileSizes(cellSize);
|
|
36
|
+
const fontSize = tileFontSize * 0.6;
|
|
37
|
+
const fontOffset = cellSize / 2;
|
|
38
|
+
const characterBonuses = config.bonuses.filter((bonus) => bonus.type === BONUS_CHARACTER);
|
|
39
|
+
const word2Bonuses = config.bonuses.filter((bonus) => bonus.type === BONUS_WORD && bonus.multiplier === 2);
|
|
40
|
+
const word3Bonuses = config.bonuses.filter((bonus) => bonus.type === BONUS_WORD && bonus.multiplier === 3);
|
|
41
|
+
|
|
42
|
+
const getX = (point: Point): number => point.x * (cellSize + BORDER_WIDTH);
|
|
43
|
+
|
|
44
|
+
const getY = (point: Point): number => point.y * (cellSize + BORDER_WIDTH);
|
|
45
|
+
|
|
46
|
+
const backgroundSvg = renderToString(
|
|
47
|
+
<Provider store={store}>
|
|
48
|
+
<svg
|
|
49
|
+
height={viewBoxHeight}
|
|
50
|
+
viewBox={`0 0 ${viewBoxWidth} ${viewBoxHeight}`}
|
|
51
|
+
width={viewBoxWidth}
|
|
52
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
53
|
+
>
|
|
54
|
+
<defs>
|
|
55
|
+
<symbol id={HORIZONTAL_LINE}>
|
|
56
|
+
<rect fill={BORDER_COLOR_LIGHT} height={1} width={viewBoxWidth} />
|
|
57
|
+
</symbol>
|
|
58
|
+
|
|
59
|
+
<symbol id={VERTICAL_LINE}>
|
|
60
|
+
<rect fill={BORDER_COLOR_LIGHT} height={viewBoxHeight} width={1} />
|
|
61
|
+
</symbol>
|
|
62
|
+
|
|
63
|
+
<symbol id={BONUS}>
|
|
64
|
+
<rect height={bonusSize} rx={BORDER_RADIUS} width={bonusSize} x={bonusOffset} y={bonusOffset} />
|
|
65
|
+
</symbol>
|
|
66
|
+
|
|
67
|
+
<symbol id={BONUS_WORD_2}>
|
|
68
|
+
<rect height={bonusSize} rx={BORDER_RADIUS} width={bonusSize} x={bonusOffset} y={bonusOffset} />
|
|
69
|
+
|
|
70
|
+
<text
|
|
71
|
+
dominantBaseline="central"
|
|
72
|
+
fill="white"
|
|
73
|
+
fontFamily="system-ui, sans-serif"
|
|
74
|
+
fontSize={fontSize}
|
|
75
|
+
fontWeight="bold"
|
|
76
|
+
textAnchor="middle"
|
|
77
|
+
x={fontOffset}
|
|
78
|
+
y={fontOffset}
|
|
79
|
+
>
|
|
80
|
+
x2
|
|
81
|
+
</text>
|
|
82
|
+
</symbol>
|
|
83
|
+
|
|
84
|
+
<symbol id={BONUS_WORD_3}>
|
|
85
|
+
<rect height={bonusSize} rx={BORDER_RADIUS} width={bonusSize} x={bonusOffset} y={bonusOffset} />
|
|
86
|
+
|
|
87
|
+
<text
|
|
88
|
+
dominantBaseline="central"
|
|
89
|
+
fill="white"
|
|
90
|
+
fontFamily="system-ui, sans-serif"
|
|
91
|
+
fontSize={fontSize}
|
|
92
|
+
fontWeight="bold"
|
|
93
|
+
textAnchor="middle"
|
|
94
|
+
x={fontOffset}
|
|
95
|
+
y={fontOffset}
|
|
96
|
+
>
|
|
97
|
+
x3
|
|
98
|
+
</text>
|
|
99
|
+
</symbol>
|
|
100
|
+
|
|
101
|
+
<symbol id={CELL_FILTER}>
|
|
102
|
+
<rect
|
|
103
|
+
fill={COLOR_FILTERED}
|
|
104
|
+
height={bonusSize}
|
|
105
|
+
rx={BORDER_RADIUS}
|
|
106
|
+
width={bonusSize}
|
|
107
|
+
x={bonusOffset}
|
|
108
|
+
y={bonusOffset}
|
|
109
|
+
/>
|
|
110
|
+
|
|
111
|
+
<FlagFill color="white" height={iconSize} width={iconSize} x={iconOffset} y={iconOffset} />
|
|
112
|
+
</symbol>
|
|
113
|
+
</defs>
|
|
114
|
+
|
|
115
|
+
<rect fill="white" height={viewBoxHeight} rx={BORDER_RADIUS} width={viewBoxWidth} x="0" y="0" />
|
|
116
|
+
|
|
117
|
+
{Array.from({ length: config.boardHeight - 1 }).map((_value, index) => (
|
|
118
|
+
<use key={index} href={`#${HORIZONTAL_LINE}`} y={(index + 1) * (cellSize + BORDER_WIDTH) - BORDER_WIDTH} />
|
|
119
|
+
))}
|
|
120
|
+
|
|
121
|
+
{Array.from({ length: config.boardWidth - 1 }).map((_value, index) => (
|
|
122
|
+
<use key={index} href={`#${VERTICAL_LINE}`} x={(index + 1) * (cellSize + BORDER_WIDTH) - BORDER_WIDTH} />
|
|
123
|
+
))}
|
|
124
|
+
|
|
125
|
+
{characterBonuses.map((bonus, index) => (
|
|
126
|
+
<use fill={getBonusColor(bonus)} key={index} href={`#${BONUS}`} x={getX(bonus)} y={getY(bonus)} />
|
|
127
|
+
))}
|
|
128
|
+
|
|
129
|
+
{word2Bonuses.map((bonus, index) => (
|
|
130
|
+
<use fill={getBonusColor(bonus)} key={index} href={`#${BONUS_WORD_2}`} x={getX(bonus)} y={getY(bonus)} />
|
|
131
|
+
))}
|
|
132
|
+
|
|
133
|
+
{word3Bonuses.map((bonus, index) => (
|
|
134
|
+
<use fill={getBonusColor(bonus)} key={index} href={`#${BONUS_WORD_3}`} x={getX(bonus)} y={getY(bonus)} />
|
|
135
|
+
))}
|
|
136
|
+
|
|
137
|
+
<rect
|
|
138
|
+
fill={COLOR_BONUS_START}
|
|
139
|
+
height={bonusSize}
|
|
140
|
+
rx={BORDER_RADIUS}
|
|
141
|
+
width={bonusSize}
|
|
142
|
+
x={getX(center) + bonusOffset}
|
|
143
|
+
y={getY(center) + bonusOffset}
|
|
144
|
+
/>
|
|
145
|
+
|
|
146
|
+
<Star
|
|
147
|
+
color="white"
|
|
148
|
+
height={iconSize}
|
|
149
|
+
width={iconSize}
|
|
150
|
+
x={getX(center) + iconOffset}
|
|
151
|
+
y={getY(center) + iconOffset}
|
|
152
|
+
/>
|
|
153
|
+
|
|
154
|
+
{cellFilter.map((cell, index) => (
|
|
155
|
+
<use key={index} href={`#${CELL_FILTER}`} x={getX(cell)} y={getY(cell)} />
|
|
156
|
+
))}
|
|
157
|
+
</svg>
|
|
158
|
+
</Provider>,
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
const encodedSvg = globalThis.btoa(backgroundSvg);
|
|
162
|
+
const dataUrl = `data:image/svg+xml;base64,${encodedSvg}`;
|
|
163
|
+
const blob = useMemo(() => dataUrlToBlob(dataUrl), [dataUrl]);
|
|
164
|
+
const blobUrl = useMemo(() => URL.createObjectURL(blob), [blob]);
|
|
165
|
+
const url = `url(${blobUrl})`;
|
|
166
|
+
|
|
167
|
+
return url;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
export default useBackgroundImage;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { BONUS_WORD } from '@scrabble-solver/constants';
|
|
2
|
+
import { Bonus } from '@scrabble-solver/types';
|
|
3
|
+
|
|
4
|
+
import { COLOR_BONUS_CHARACTER, COLOR_BONUS_CHARACTER_MULTIPLIER, COLOR_BONUS_WORD } from 'parameters';
|
|
5
|
+
|
|
6
|
+
const getBonusColor = (bonus: Bonus): string => {
|
|
7
|
+
if (bonus.type === BONUS_WORD) {
|
|
8
|
+
return COLOR_BONUS_WORD[bonus.multiplier];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (bonus.score) {
|
|
12
|
+
return COLOR_BONUS_CHARACTER[bonus.score];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return COLOR_BONUS_CHARACTER_MULTIPLIER[bonus.multiplier];
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export default getBonusColor;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { COMMA_ARABIC, COMMA_LATIN } from '@scrabble-solver/constants';
|
|
2
1
|
import classNames from 'classnames';
|
|
3
2
|
import { ChangeEvent, FormEvent, FunctionComponent } from 'react';
|
|
4
3
|
import { useDispatch } from 'react-redux';
|
|
5
4
|
|
|
6
|
-
import {
|
|
5
|
+
import { LOCALE_FEATURES } from 'i18n';
|
|
6
|
+
import { dictionarySlice, selectDictionary, selectLocale, useTranslate, useTypedSelector } from 'state';
|
|
7
7
|
|
|
8
8
|
import styles from './DictionaryInput.module.scss';
|
|
9
9
|
|
|
@@ -14,7 +14,9 @@ interface Props {
|
|
|
14
14
|
const DictionaryInput: FunctionComponent<Props> = ({ className }) => {
|
|
15
15
|
const dispatch = useDispatch();
|
|
16
16
|
const translate = useTranslate();
|
|
17
|
+
const locale = useTypedSelector(selectLocale);
|
|
17
18
|
const { input } = useTypedSelector(selectDictionary);
|
|
19
|
+
const { comma } = LOCALE_FEATURES[locale];
|
|
18
20
|
|
|
19
21
|
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
|
|
20
22
|
dispatch(dictionarySlice.actions.changeInput(event.target.value));
|
|
@@ -29,7 +31,7 @@ const DictionaryInput: FunctionComponent<Props> = ({ className }) => {
|
|
|
29
31
|
<form className={classNames(styles.dictionaryInput, className)} onSubmit={handleSubmit}>
|
|
30
32
|
<input
|
|
31
33
|
className={styles.input}
|
|
32
|
-
pattern={`.*[^\\s${
|
|
34
|
+
pattern={`.*[^\\s${comma}].*`}
|
|
33
35
|
placeholder={translate('dictionary.input.placeholder')}
|
|
34
36
|
required
|
|
35
37
|
title={translate('dictionary.input.title')}
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
import classNames from 'classnames';
|
|
2
2
|
import { CSSProperties, FunctionComponent, useMemo } from 'react';
|
|
3
3
|
|
|
4
|
-
import { LOCALE_FEATURES } from 'i18n';
|
|
5
|
-
import { selectLocale, useTypedSelector } from 'state';
|
|
6
|
-
|
|
7
|
-
import SvgFontCss from '../SvgFontCss';
|
|
8
|
-
|
|
9
4
|
import { createPlainTiles, getViewbox } from './lib';
|
|
10
5
|
import styles from './PlainTiles.module.scss';
|
|
11
6
|
import Tile from './Tile';
|
|
@@ -21,8 +16,6 @@ interface Props {
|
|
|
21
16
|
}
|
|
22
17
|
|
|
23
18
|
const PlainTiles: FunctionComponent<Props> = ({ className, color, content, dropShadow, showPoints, style, wave }) => {
|
|
24
|
-
const locale = useTypedSelector(selectLocale);
|
|
25
|
-
const { fontFamily } = LOCALE_FEATURES[locale];
|
|
26
19
|
const tiles = useMemo(() => createPlainTiles({ color, content, showPoints }), [color, content, showPoints]);
|
|
27
20
|
|
|
28
21
|
return (
|
|
@@ -35,14 +28,11 @@ const PlainTiles: FunctionComponent<Props> = ({ className, color, content, dropS
|
|
|
35
28
|
viewBox={getViewbox(content)}
|
|
36
29
|
xmlns="http://www.w3.org/2000/svg"
|
|
37
30
|
>
|
|
38
|
-
<SvgFontCss fontFamily={fontFamily} />
|
|
39
|
-
|
|
40
31
|
{tiles.map((tile, index) => (
|
|
41
32
|
<Tile
|
|
42
33
|
character={tile.character}
|
|
43
34
|
className={styles.tile}
|
|
44
35
|
color={tile.color}
|
|
45
|
-
fontFamily={fontFamily}
|
|
46
36
|
key={index}
|
|
47
37
|
points={tile.points}
|
|
48
38
|
size={tile.size}
|
|
@@ -4,7 +4,6 @@ interface Props {
|
|
|
4
4
|
character: string;
|
|
5
5
|
className?: string;
|
|
6
6
|
color: string;
|
|
7
|
-
fontFamily: string;
|
|
8
7
|
points?: number;
|
|
9
8
|
size: number;
|
|
10
9
|
transform?: string;
|
|
@@ -12,13 +11,12 @@ interface Props {
|
|
|
12
11
|
y: number;
|
|
13
12
|
}
|
|
14
13
|
|
|
15
|
-
const Tile: FunctionComponent<Props> = ({ character, className, color,
|
|
14
|
+
const Tile: FunctionComponent<Props> = ({ character, className, color, points, size, transform, x, y }) => (
|
|
16
15
|
<g className={className} transform={transform}>
|
|
17
16
|
<rect fill={color} height={size} rx={size * 0.15} width={size} x={x} y={y} />
|
|
18
17
|
|
|
19
18
|
<text
|
|
20
19
|
dominantBaseline="central"
|
|
21
|
-
fontFamily={fontFamily}
|
|
22
20
|
fontSize={size * 0.6}
|
|
23
21
|
fontWeight="bold"
|
|
24
22
|
textAnchor="middle"
|
|
@@ -31,7 +29,6 @@ const Tile: FunctionComponent<Props> = ({ character, className, color, fontFamil
|
|
|
31
29
|
{typeof points === 'number' && (
|
|
32
30
|
<text
|
|
33
31
|
dominantBaseline="text-after-edge"
|
|
34
|
-
fontFamily={fontFamily}
|
|
35
32
|
fontSize={size * 0.25}
|
|
36
33
|
fontWeight="bold"
|
|
37
34
|
textAnchor="end"
|
|
@@ -22,9 +22,9 @@ const Cell: FunctionComponent<Props> = ({ className, translationKey, tooltip, va
|
|
|
22
22
|
const triggerProps = useTooltip(`${translate(translationKey)}: ${tooltip || formattedValue}`);
|
|
23
23
|
|
|
24
24
|
return (
|
|
25
|
-
<
|
|
25
|
+
<div className={classNames(styles.cell, className)} {...triggerProps}>
|
|
26
26
|
{formattedValue}
|
|
27
|
-
</
|
|
27
|
+
</div>
|
|
28
28
|
);
|
|
29
29
|
};
|
|
30
30
|
|
|
@@ -30,10 +30,10 @@ const Result = ({ data, index, style }: Props): ReactElement => {
|
|
|
30
30
|
const ref = useRef<HTMLButtonElement>(null);
|
|
31
31
|
const columns = useColumns();
|
|
32
32
|
const locale = useTypedSelector(selectLocale);
|
|
33
|
-
const { consonants, vowels } = LOCALE_FEATURES[locale];
|
|
33
|
+
const { consonants, direction, separator, vowels } = LOCALE_FEATURES[locale];
|
|
34
34
|
const result = results[index];
|
|
35
35
|
const isMatching = useTypedSelector((state) => selectIsResultMatching(state, index));
|
|
36
|
-
const
|
|
36
|
+
const words = direction === 'rtl' ? [...result.words].reverse() : result.words;
|
|
37
37
|
const enabledColumns = Object.fromEntries(columns.map((column) => [column.id, true]));
|
|
38
38
|
|
|
39
39
|
const handleClick: MouseEventHandler = (event) => onClick(result, event);
|
|
@@ -60,11 +60,7 @@ const Result = ({ data, index, style }: Props): ReactElement => {
|
|
|
60
60
|
>
|
|
61
61
|
<span className={styles.resultContent}>
|
|
62
62
|
{enabledColumns[ResultColumn.Word] && (
|
|
63
|
-
<Cell
|
|
64
|
-
className={styles.word}
|
|
65
|
-
translationKey="common.word"
|
|
66
|
-
value={`${result.word.toLocaleUpperCase()}${otherWords.length > 0 ? ` (${otherWords})` : ''}`}
|
|
67
|
-
/>
|
|
63
|
+
<Cell className={styles.word} translationKey="common.word" value={result.word} />
|
|
68
64
|
)}
|
|
69
65
|
|
|
70
66
|
{enabledColumns[ResultColumn.TilesCount] && (
|
|
@@ -87,7 +83,7 @@ const Result = ({ data, index, style }: Props): ReactElement => {
|
|
|
87
83
|
<Cell
|
|
88
84
|
className={styles.stat}
|
|
89
85
|
translationKey="common.words"
|
|
90
|
-
tooltip={`${result.wordsCount} (${
|
|
86
|
+
tooltip={`${result.wordsCount.toLocaleString(locale)} (${words.join(separator)})`}
|
|
91
87
|
value={result.wordsCount}
|
|
92
88
|
/>
|
|
93
89
|
)}
|
|
@@ -10,7 +10,6 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
|
|
|
10
10
|
background: var(--color--background--element);
|
|
11
11
|
border: var(--border);
|
|
12
12
|
border-radius: var(--border--radius);
|
|
13
|
-
font-family: var(--font--family--title);
|
|
14
13
|
}
|
|
15
14
|
|
|
16
15
|
.emptyState {
|
|
@@ -61,7 +60,6 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
|
|
|
61
60
|
cursor: pointer;
|
|
62
61
|
text-transform: uppercase;
|
|
63
62
|
transition: var(--transition);
|
|
64
|
-
padding: var(--spacing--s) 0;
|
|
65
63
|
|
|
66
64
|
&:focus,
|
|
67
65
|
&:hover {
|
|
@@ -136,6 +134,7 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
|
|
|
136
134
|
display: flex;
|
|
137
135
|
align-items: center;
|
|
138
136
|
justify-content: space-between;
|
|
137
|
+
height: 100%;
|
|
139
138
|
|
|
140
139
|
.word {
|
|
141
140
|
@include ellipsis;
|
|
@@ -146,8 +145,10 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
|
|
|
146
145
|
display: flex;
|
|
147
146
|
align-items: center;
|
|
148
147
|
justify-content: center;
|
|
148
|
+
height: 100%;
|
|
149
149
|
padding: 0 var(--spacing--s);
|
|
150
150
|
gap: var(--spacing--s);
|
|
151
|
+
line-height: var(--results--item--height);
|
|
151
152
|
|
|
152
153
|
.result &:first-child,
|
|
153
154
|
.headerButton:first-child & {
|
|
@@ -181,12 +182,12 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
|
|
|
181
182
|
}
|
|
182
183
|
|
|
183
184
|
.word {
|
|
184
|
-
flex: 1 0
|
|
185
|
+
flex: 1 0;
|
|
185
186
|
text-transform: uppercase;
|
|
186
187
|
}
|
|
187
188
|
|
|
188
189
|
.stat {
|
|
189
|
-
$width:
|
|
190
|
+
$width: 55px;
|
|
190
191
|
|
|
191
192
|
flex: 0 0 $width;
|
|
192
193
|
max-width: $width;
|
|
@@ -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 {
|
|
37
|
+
const { maxControlsWidth, showCompactControls, showFloatingSolveButton, tileSize } = useAppLayout();
|
|
38
38
|
const error = useTypedSelector(selectSolveError);
|
|
39
39
|
const isOutdated = useTypedSelector(selectAreResultsOutdated);
|
|
40
40
|
const resultCandidate = useTypedSelector(selectResultCandidate);
|
|
@@ -93,7 +93,7 @@ const Solver: FunctionComponent<Props> = ({ className, onShowResults }) => {
|
|
|
93
93
|
<div className={styles.container}>
|
|
94
94
|
<div className={styles.content}>
|
|
95
95
|
<form className={styles.boardContainer} onSubmit={handleSubmit}>
|
|
96
|
-
<Board
|
|
96
|
+
<Board className={styles.board} />
|
|
97
97
|
<input className={styles.submitInput} tabIndex={-1} type="submit" />
|
|
98
98
|
</form>
|
|
99
99
|
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
.tooltip {
|
|
2
|
+
max-width: var(--tooltip--max-width);
|
|
2
3
|
padding: var(--spacing--s) var(--spacing--m);
|
|
3
4
|
box-shadow: var(--box-shadow);
|
|
4
5
|
border-radius: var(--border--radius);
|
|
5
6
|
background-color: var(--color--tooltip--background);
|
|
6
7
|
color: var(--color--tooltip--foreground);
|
|
7
8
|
z-index: var(--z-index--tooltip);
|
|
9
|
+
text-align: center;
|
|
8
10
|
}
|
|
9
11
|
|
|
10
12
|
.arrow {
|
|
@@ -22,7 +22,5 @@ export { default as ResultsInput } from './ResultsInput';
|
|
|
22
22
|
export { default as SeoMessage } from './SeoMessage';
|
|
23
23
|
export { default as Solver } from './Solver';
|
|
24
24
|
export { default as Spinner } from './Spinner';
|
|
25
|
-
export { default as SvgFontCss } from './SvgFontCss';
|
|
26
|
-
export { default as SvgFontFix } from './SvgFontFix';
|
|
27
25
|
export { default as Tile } from './Tile';
|
|
28
26
|
export { useTooltip } from './Tooltip';
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
2
|
|
|
3
3
|
import { noop } from 'lib';
|
|
4
4
|
|
|
5
5
|
const useDirection = (direction: 'ltr' | 'rtl') => {
|
|
6
|
-
|
|
6
|
+
useEffect(() => {
|
|
7
7
|
const html = document.body.parentElement;
|
|
8
8
|
|
|
9
9
|
if (!html) {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
2
|
|
|
3
3
|
import { noop } from 'lib';
|
|
4
4
|
|
|
5
5
|
const useLanguage = (language: string) => {
|
|
6
|
-
|
|
6
|
+
useEffect(() => {
|
|
7
7
|
const html = document.body.parentElement;
|
|
8
8
|
|
|
9
9
|
if (!html) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { COMMA_ARABIC, COMMA_LATIN } from '@scrabble-solver/constants';
|
|
1
2
|
import { Locale } from '@scrabble-solver/types';
|
|
2
3
|
import { FunctionComponent, SVGAttributes } from 'react';
|
|
3
4
|
|
|
@@ -6,53 +7,61 @@ import { FlagDe, FlagEs, FlagFa, FlagFr, FlagGb, FlagPl, FlagUs } from 'icons';
|
|
|
6
7
|
import styles from './i18n.module.scss';
|
|
7
8
|
|
|
8
9
|
interface LocaleFeatures {
|
|
9
|
-
|
|
10
|
-
fontFamily: string;
|
|
10
|
+
comma: string;
|
|
11
11
|
consonants: boolean;
|
|
12
|
+
direction: 'ltr' | 'rtl';
|
|
13
|
+
separator: string;
|
|
12
14
|
vowels: boolean;
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
export const LOCALE_FEATURES: Record<Locale, LocaleFeatures> = {
|
|
16
18
|
[Locale.DE_DE]: {
|
|
17
|
-
|
|
18
|
-
fontFamily: 'Open Sans',
|
|
19
|
+
comma: COMMA_LATIN,
|
|
19
20
|
consonants: true,
|
|
21
|
+
direction: 'ltr',
|
|
22
|
+
separator: `${COMMA_LATIN} `,
|
|
20
23
|
vowels: true,
|
|
21
24
|
},
|
|
22
25
|
[Locale.EN_GB]: {
|
|
23
|
-
|
|
24
|
-
fontFamily: 'Open Sans',
|
|
26
|
+
comma: COMMA_LATIN,
|
|
25
27
|
consonants: true,
|
|
28
|
+
direction: 'ltr',
|
|
29
|
+
separator: `${COMMA_LATIN} `,
|
|
26
30
|
vowels: true,
|
|
27
31
|
},
|
|
28
32
|
[Locale.EN_US]: {
|
|
29
|
-
|
|
30
|
-
fontFamily: 'Open Sans',
|
|
33
|
+
comma: COMMA_LATIN,
|
|
31
34
|
consonants: true,
|
|
35
|
+
direction: 'ltr',
|
|
36
|
+
separator: `${COMMA_LATIN} `,
|
|
32
37
|
vowels: true,
|
|
33
38
|
},
|
|
34
39
|
[Locale.ES_ES]: {
|
|
35
|
-
|
|
36
|
-
fontFamily: 'Open Sans',
|
|
40
|
+
comma: COMMA_LATIN,
|
|
37
41
|
consonants: true,
|
|
42
|
+
direction: 'ltr',
|
|
43
|
+
separator: `${COMMA_LATIN} `,
|
|
38
44
|
vowels: true,
|
|
39
45
|
},
|
|
40
46
|
[Locale.FA_IR]: {
|
|
41
|
-
|
|
42
|
-
fontFamily: 'Vazirmatn',
|
|
47
|
+
comma: COMMA_ARABIC,
|
|
43
48
|
consonants: false,
|
|
49
|
+
direction: 'rtl',
|
|
50
|
+
separator: `${COMMA_ARABIC} `,
|
|
44
51
|
vowels: false,
|
|
45
52
|
},
|
|
46
53
|
[Locale.FR_FR]: {
|
|
47
|
-
|
|
48
|
-
fontFamily: 'Open Sans',
|
|
54
|
+
comma: COMMA_LATIN,
|
|
49
55
|
consonants: true,
|
|
56
|
+
direction: 'ltr',
|
|
57
|
+
separator: `${COMMA_LATIN} `,
|
|
50
58
|
vowels: true,
|
|
51
59
|
},
|
|
52
60
|
[Locale.PL_PL]: {
|
|
53
|
-
|
|
54
|
-
fontFamily: 'Open Sans',
|
|
61
|
+
comma: COMMA_LATIN,
|
|
55
62
|
consonants: true,
|
|
63
|
+
direction: 'ltr',
|
|
64
|
+
separator: `${COMMA_LATIN} `,
|
|
56
65
|
vowels: true,
|
|
57
66
|
},
|
|
58
67
|
};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
"cell.filter-cell": "Zielort
|
|
2
|
+
"cell.filter-cell": "Zielort",
|
|
3
3
|
"cell.set-blank": "Als Blanko markieren",
|
|
4
4
|
"cell.set-not-blank": "Nicht als Blanko markieren",
|
|
5
5
|
"cell.tile.location": "Brett: Stein ({{x}}, {{y}})",
|
|
6
|
-
"cell.toggle-direction": "Schreibrichtung
|
|
6
|
+
"cell.toggle-direction": "Schreibrichtung",
|
|
7
7
|
"common.blanks": "Blankos",
|
|
8
8
|
"common.clear": "Löschen",
|
|
9
9
|
"common.close": "Schließen",
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
"cell.filter-cell": "Target destination
|
|
2
|
+
"cell.filter-cell": "Target destination",
|
|
3
3
|
"cell.set-blank": "Mark it a blank",
|
|
4
4
|
"cell.set-not-blank": "Mark it not a blank",
|
|
5
5
|
"cell.tile.location": "Board: tile ({{x}}, {{y}})",
|
|
6
|
-
"cell.toggle-direction": "Typing direction
|
|
6
|
+
"cell.toggle-direction": "Typing direction",
|
|
7
7
|
"common.blanks": "Blanks",
|
|
8
8
|
"common.clear": "Clear",
|
|
9
9
|
"common.close": "Close",
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
"cell.filter-cell": "Destino objetivo
|
|
2
|
+
"cell.filter-cell": "Destino objetivo",
|
|
3
3
|
"cell.set-blank": "Marcar como en blanco",
|
|
4
4
|
"cell.set-not-blank": "Marcar como no en blanco",
|
|
5
5
|
"cell.tile.location": "Tablero: espacio ({{x}}, {{y}})",
|
|
6
|
-
"cell.toggle-direction": "Dirección de escritura
|
|
6
|
+
"cell.toggle-direction": "Dirección de escritura",
|
|
7
7
|
"common.blanks": "Blancos",
|
|
8
8
|
"common.clear": "Borrar",
|
|
9
9
|
"common.close": "Cerrar",
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
"cell.filter-cell": "مقصد
|
|
2
|
+
"cell.filter-cell": "مقصد",
|
|
3
3
|
"cell.set-blank": "علامت گذاری به عنوان خالی",
|
|
4
4
|
"cell.set-not-blank": "علامت گذاری به عنوان غیر خالی",
|
|
5
|
-
"cell.toggle-direction": "جهت تایپ - کلیک برای تغییر",
|
|
6
5
|
"cell.tile.location": "({{x}}، {{y}}) کاشی: صفحه",
|
|
6
|
+
"cell.toggle-direction": "جهت تایپ",
|
|
7
7
|
"common.blanks": "خالی",
|
|
8
8
|
"common.clear": "پاک کردن",
|
|
9
9
|
"common.close": "بستن",
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
"cell.filter-cell": "Destination cible
|
|
2
|
+
"cell.filter-cell": "Destination cible",
|
|
3
3
|
"cell.set-blank": "Marquer comme vide",
|
|
4
4
|
"cell.set-not-blank": "Marquer comme non vide",
|
|
5
5
|
"cell.tile.location": "Plateau: la case ({{x}}, {{y}})",
|
|
6
|
-
"cell.toggle-direction": "Direction d'écriture
|
|
6
|
+
"cell.toggle-direction": "Direction d'écriture",
|
|
7
7
|
"common.blanks": "Cases vides",
|
|
8
8
|
"common.clear": "Effacer",
|
|
9
9
|
"common.close": "Fermer",
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
"cell.filter-cell": "Miejsce docelowe
|
|
2
|
+
"cell.filter-cell": "Miejsce docelowe",
|
|
3
3
|
"cell.set-blank": "Oznacz jako blank",
|
|
4
4
|
"cell.set-not-blank": "Oznacz jako nie blank",
|
|
5
5
|
"cell.tile.location": "Plansza: płytka ({{x}}, {{y}})",
|
|
6
|
-
"cell.toggle-direction": "Kierunek wpisywania
|
|
6
|
+
"cell.toggle-direction": "Kierunek wpisywania",
|
|
7
7
|
"common.blanks": "Blanki",
|
|
8
8
|
"common.clear": "Wyczyść",
|
|
9
9
|
"common.close": "Zamknij",
|