@scrabble-solver/scrabble-solver 2.13.9 → 2.13.11

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.
Files changed (83) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +6 -6
  3. package/.next/cache/.tsbuildinfo +1 -1
  4. package/.next/cache/eslint/.cache_8dgz12 +1 -1
  5. package/.next/cache/webpack/client-production/0.pack +0 -0
  6. package/.next/cache/webpack/client-production/index.pack +0 -0
  7. package/.next/cache/webpack/edge-server-production/0.pack +0 -0
  8. package/.next/cache/webpack/edge-server-production/index.pack +0 -0
  9. package/.next/cache/webpack/server-production/0.pack +0 -0
  10. package/.next/cache/webpack/server-production/index.pack +0 -0
  11. package/.next/prerender-manifest.js +1 -1
  12. package/.next/prerender-manifest.json +1 -1
  13. package/.next/routes-manifest.json +1 -1
  14. package/.next/server/chunks/807.js +1 -1
  15. package/.next/server/chunks/911.js +1 -1
  16. package/.next/server/chunks/977.js +1 -1
  17. package/.next/server/middleware-build-manifest.js +1 -1
  18. package/.next/server/pages/404.html +1 -1
  19. package/.next/server/pages/500.html +1 -1
  20. package/.next/server/pages/_app.js +1 -1
  21. package/.next/server/pages/_error.js +1 -1
  22. package/.next/server/pages/api/dictionary/[locale]/[word].js +1 -1
  23. package/.next/server/pages/api/solve.js +1 -1
  24. package/.next/server/pages/api/verify.js +1 -1
  25. package/.next/server/pages/index.html +1 -1
  26. package/.next/server/pages/index.js +1 -1
  27. package/.next/server/pages/index.json +1 -1
  28. package/.next/static/{eLvYNd4B2hzSgBZ_PuZcQ → 5T-kyZzpLLGYA9Qzg0-Sn}/_buildManifest.js +1 -1
  29. package/.next/static/chunks/pages/{404-01653a877b233143.js → 404-0c9f3e0f8b15f487.js} +1 -1
  30. package/.next/static/chunks/pages/_app-264cd7dc7c7b5cc2.js +17 -0
  31. package/.next/static/chunks/pages/index-65bfe83d121535ab.js +1 -0
  32. package/.next/static/css/{841a5b5f0b2fb131.css → 2f727b21d1331ea5.css} +2 -2
  33. package/.next/trace +45 -44
  34. package/package.json +10 -9
  35. package/src/api/isBoardValid.ts +1 -1
  36. package/src/api/isCellValid.ts +2 -2
  37. package/src/api/isRowValid.ts +1 -1
  38. package/src/components/Board/components/Actions/Actions.tsx +15 -3
  39. package/src/components/Board/components/Cell/Cell.tsx +1 -1
  40. package/src/components/Board/components/ToggleDirectionButton/ToggleDirectionButton.tsx +9 -2
  41. package/src/components/Board/hooks/useBackgroundImage.tsx +3 -3
  42. package/src/components/Board/hooks/useBoardStyle.ts +3 -5
  43. package/src/components/Board/hooks/useGrid.ts +13 -9
  44. package/src/components/Key/Key.module.scss +3 -2
  45. package/src/components/Keys/Arrows/Arrows.module.scss +41 -0
  46. package/src/components/Keys/Arrows/Arrows.tsx +31 -0
  47. package/src/components/Keys/Arrows/index.ts +1 -0
  48. package/src/components/Keys/index.ts +1 -0
  49. package/src/components/Rack/components/InputPrompt/InputPrompt.tsx +1 -1
  50. package/src/components/Results/HeaderButton.tsx +3 -6
  51. package/src/components/Results/Results.module.scss +2 -6
  52. package/src/components/Results/Results.tsx +6 -2
  53. package/src/components/Results/getCoordinatesColumn.ts +0 -1
  54. package/src/components/Results/getLocaleColumns.ts +0 -7
  55. package/src/components/Results/types.ts +0 -1
  56. package/src/components/index.ts +2 -0
  57. package/src/components/keys.tsx +26 -0
  58. package/src/hooks/useAppLayout.ts +8 -8
  59. package/src/i18n/languages/english.json +4 -2
  60. package/src/i18n/languages/french.json +4 -2
  61. package/src/i18n/languages/german.json +4 -2
  62. package/src/i18n/languages/persian.json +4 -2
  63. package/src/i18n/languages/polish.json +4 -2
  64. package/src/i18n/languages/romanian.json +3 -1
  65. package/src/i18n/languages/spanish.json +4 -2
  66. package/src/lib/getCellSize.ts +2 -2
  67. package/src/lib/groupResults.ts +4 -7
  68. package/src/lib/index.ts +1 -0
  69. package/src/lib/sortGroupedResults.ts +23 -0
  70. package/src/lib/sortResults.ts +17 -5
  71. package/src/modals/KeyMapModal/KeyMapModal.tsx +19 -9
  72. package/src/modals/SettingsModal/components/ShowCoordinatesSetting/ShowCoordinatesSetting.tsx +1 -1
  73. package/src/pages/api/dictionary/[locale]/[word].ts +1 -4
  74. package/src/state/sagas.ts +6 -6
  75. package/src/state/selectors.ts +8 -5
  76. package/src/state/slices/boardInitialState.ts +2 -2
  77. package/src/state/slices/boardSlice.ts +2 -2
  78. package/src/styles/variables.scss +2 -2
  79. package/src/types/index.ts +11 -1
  80. package/.next/static/chunks/pages/_app-735b5863675c1b5d.js +0 -17
  81. package/.next/static/chunks/pages/index-36c448d585a58425.js +0 -1
  82. package/src/modals/KeyMapModal/keys.tsx +0 -46
  83. /package/.next/static/{eLvYNd4B2hzSgBZ_PuZcQ → 5T-kyZzpLLGYA9Qzg0-Sn}/_ssgManifest.js +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scrabble-solver/scrabble-solver",
3
- "version": "2.13.9",
3
+ "version": "2.13.11",
4
4
  "description": "Scrabble Solver 2 - App",
5
5
  "engines": {
6
6
  "node": ">=16"
@@ -30,13 +30,13 @@
30
30
  "@floating-ui/react": "^0.26.17",
31
31
  "@kamilmielnik/trie": "^3.0.0",
32
32
  "@reduxjs/toolkit": "^2.2.5",
33
- "@scrabble-solver/configs": "^2.13.9",
34
- "@scrabble-solver/constants": "^2.13.9",
35
- "@scrabble-solver/dictionaries": "^2.13.9",
36
- "@scrabble-solver/logger": "^2.13.9",
37
- "@scrabble-solver/solver": "^2.13.9",
38
- "@scrabble-solver/types": "^2.13.9",
39
- "@scrabble-solver/word-definitions": "^2.13.9",
33
+ "@scrabble-solver/configs": "^2.13.11",
34
+ "@scrabble-solver/constants": "^2.13.11",
35
+ "@scrabble-solver/dictionaries": "^2.13.11",
36
+ "@scrabble-solver/logger": "^2.13.11",
37
+ "@scrabble-solver/solver": "^2.13.11",
38
+ "@scrabble-solver/types": "^2.13.11",
39
+ "@scrabble-solver/word-definitions": "^2.13.11",
40
40
  "classnames": "^2.5.1",
41
41
  "env-cmd": "^10.1.0",
42
42
  "include-media": "^2.0.0",
@@ -52,6 +52,7 @@
52
52
  "react-window": "^1.8.10",
53
53
  "redux-saga": "^1.3.0",
54
54
  "store2": "^2.14.3",
55
+ "use-debounce": "^10.0.1",
55
56
  "workbox-expiration": "^7.1.0",
56
57
  "workbox-precaching": "^7.1.0",
57
58
  "workbox-routing": "^7.1.0",
@@ -72,5 +73,5 @@
72
73
  "@types/redux-saga": "^0.10.5",
73
74
  "sass": "^1.77.5"
74
75
  },
75
- "gitHead": "d09676296f8fafadb7da0cf1e11fb240aaf309ee"
76
+ "gitHead": "1b418c5dadc1ff365839cd7825f57eb6d182a815"
76
77
  }
@@ -3,7 +3,7 @@ import { BoardJson, CellJson, Config } from '@scrabble-solver/types';
3
3
  import isRowValid from './isRowValid';
4
4
 
5
5
  const isBoardValid = (board: BoardJson, config: Config): boolean => {
6
- if (board.length !== config.boardHeight) {
6
+ if (board.length !== config.boardSize) {
7
7
  return false;
8
8
  }
9
9
 
@@ -5,11 +5,11 @@ import isCharacterValid from './isCharacterValid';
5
5
  const isCellValid = (cell: CellJson, config: Config): boolean => {
6
6
  const { isEmpty, tile, x, y } = cell;
7
7
 
8
- if (x < 0 || x >= config.boardWidth) {
8
+ if (x < 0 || x >= config.boardSize) {
9
9
  return false;
10
10
  }
11
11
 
12
- if (y < 0 || y >= config.boardHeight) {
12
+ if (y < 0 || y >= config.boardSize) {
13
13
  return false;
14
14
  }
15
15
 
@@ -3,7 +3,7 @@ import { CellJson, Config } from '@scrabble-solver/types';
3
3
  import isCellValid from './isCellValid';
4
4
 
5
5
  const isRowValid = (row: CellJson[], config: Config): boolean => {
6
- if (row.length !== config.boardWidth) {
6
+ if (row.length !== config.boardSize) {
7
7
  return false;
8
8
  }
9
9
 
@@ -3,8 +3,9 @@ import { Cell } from '@scrabble-solver/types';
3
3
  import classNames from 'classnames';
4
4
  import { forwardRef, HTMLProps, MouseEventHandler } from 'react';
5
5
 
6
+ import { useIsTouchDevice } from 'hooks';
6
7
  import { Keyboard, Square, SquareFill } from 'icons';
7
- import { findCell } from 'lib';
8
+ import { findCell, isMac } from 'lib';
8
9
  import { selectCellFilter, selectInputMode, selectResultCandidateCells, useTranslate, useTypedSelector } from 'state';
9
10
  import { Direction } from 'types';
10
11
 
@@ -29,6 +30,7 @@ const Actions = forwardRef<HTMLDivElement, Props>(
29
30
  ref,
30
31
  ) => {
31
32
  const translate = useTranslate();
33
+ const isTouchDevice = useIsTouchDevice();
32
34
  const inputMode = useTypedSelector(selectInputMode);
33
35
  const filter = useTypedSelector((state) => selectCellFilter(state, cell));
34
36
  const resultCandidateCells = useTypedSelector(selectResultCandidateCells);
@@ -66,7 +68,12 @@ const Actions = forwardRef<HTMLDivElement, Props>(
66
68
  aria-label={translate(labelTranslationKey)}
67
69
  className={classNames(styles.action)}
68
70
  Icon={Icon}
69
- tooltip={translate(labelTranslationKey)}
71
+ tooltip={
72
+ <>
73
+ <span>{translate(labelTranslationKey)}</span>
74
+ {!isTouchDevice && <span> ({isMac() ? '⌘' : 'Ctrl'} + G)</span>}
75
+ </>
76
+ }
70
77
  onClick={onToggleFilterCell}
71
78
  onMouseDown={handleMouseDown}
72
79
  />
@@ -77,7 +84,12 @@ const Actions = forwardRef<HTMLDivElement, Props>(
77
84
  aria-label={isBlank ? translate('cell.set-not-blank') : translate('cell.set-blank')}
78
85
  className={styles.action}
79
86
  Icon={isBlank ? SquareFill : Square}
80
- tooltip={isBlank ? translate('cell.set-not-blank') : translate('cell.set-blank')}
87
+ tooltip={
88
+ <>
89
+ <span>{isBlank ? translate('cell.set-not-blank') : translate('cell.set-blank')}</span>
90
+ {!isTouchDevice && <span> ({translate('common.space')})</span>}
91
+ </>
92
+ }
81
93
  onClick={onToggleBlank}
82
94
  onMouseDown={handleMouseDown}
83
95
  />
@@ -101,7 +101,7 @@ const Cell: FunctionComponent<Props> = ({
101
101
  })}
102
102
  className={classNames(styles.tile, className, {
103
103
  [styles.first3]: x < 3,
104
- [styles.last3]: config.boardWidth - x - 1 < 3,
104
+ [styles.last3]: config.boardSize - x - 1 < 3,
105
105
  [styles.sharpTopLeft]: cellTop?.hasTile() || cellLeft?.hasTile(),
106
106
  [styles.sharpTopRight]: cellTop?.hasTile() || cellRight?.hasTile(),
107
107
  [styles.sharpBottomLeft]: cellBottom?.hasTile() || cellLeft?.hasTile(),
@@ -1,6 +1,7 @@
1
1
  import classNames from 'classnames';
2
2
  import { ButtonHTMLAttributes, FunctionComponent } from 'react';
3
3
 
4
+ import { useIsTouchDevice } from 'hooks';
4
5
  import { ArrowDown } from 'icons';
5
6
  import { useTranslate } from 'state';
6
7
  import { Direction } from 'types';
@@ -16,16 +17,22 @@ interface Props extends ButtonHTMLAttributes<HTMLButtonElement> {
16
17
 
17
18
  const ToggleDirectionButton: FunctionComponent<Props> = ({ className, direction, ...props }) => {
18
19
  const translate = useTranslate();
20
+ const isTouchDevice = useIsTouchDevice();
19
21
 
20
22
  return (
21
23
  <Button
22
- aria-label={translate('cell.toggle-direction')}
24
+ aria-label={translate('keyMap.board.toggle-cell-filter')}
23
25
  className={classNames(styles.button, className)}
24
26
  Icon={ArrowDown}
25
27
  iconClassName={classNames(styles.icon, {
26
28
  [styles.right]: direction === 'horizontal',
27
29
  })}
28
- tooltip={translate('cell.toggle-direction')}
30
+ tooltip={
31
+ <>
32
+ <span>{translate('keyMap.board.toggle-cell-filter')}</span>
33
+ {!isTouchDevice && <span> ({translate('common.arrows')})</span>}
34
+ </>
35
+ }
29
36
  {...props}
30
37
  />
31
38
  );
@@ -32,7 +32,7 @@ const useBackgroundImage = () => {
32
32
  const { isLessThanXs } = useMediaQueries();
33
33
  const borderRadius = isLessThanXs ? BORDER_RADIUS_XS : BORDER_RADIUS;
34
34
  const config = useTypedSelector(selectConfig);
35
- const center = { x: Math.floor(config.boardWidth / 2), y: Math.floor(config.boardHeight / 2) };
35
+ const center = { x: Math.floor(config.boardSize / 2), y: Math.floor(config.boardSize / 2) };
36
36
  const viewBoxHeight = boardSize;
37
37
  const viewBoxWidth = boardSize;
38
38
  const bonusSize = cellSize * 0.8;
@@ -157,7 +157,7 @@ const useBackgroundImage = () => {
157
157
  </>
158
158
  )}
159
159
 
160
- {Array.from({ length: config.boardHeight - 1 }).map((_value, index) => (
160
+ {Array.from({ length: config.boardSize - 1 }).map((_value, index) => (
161
161
  <use
162
162
  key={index}
163
163
  href={`#${HORIZONTAL_LINE}`}
@@ -165,7 +165,7 @@ const useBackgroundImage = () => {
165
165
  />
166
166
  ))}
167
167
 
168
- {Array.from({ length: config.boardWidth - 1 }).map((_value, index) => (
168
+ {Array.from({ length: config.boardSize - 1 }).map((_value, index) => (
169
169
  <use
170
170
  key={index}
171
171
  href={`#${VERTICAL_LINE}`}
@@ -17,13 +17,11 @@ const useBoardStyle = () => {
17
17
  backgroundImage,
18
18
  fontSize: tileFontSize,
19
19
  gridTemplateColumns:
20
- showCoordinates === 'hidden' ? `repeat(${config.boardWidth}, 1fr)` : `0.5fr repeat(${config.boardWidth}, 1fr)`,
20
+ showCoordinates === 'hidden' ? `repeat(${config.boardSize}, 1fr)` : `0.5fr repeat(${config.boardSize}, 1fr)`,
21
21
  gridTemplateRows:
22
- showCoordinates === 'hidden'
23
- ? `repeat(${config.boardHeight}, 1fr)`
24
- : `0.5fr repeat(${config.boardHeight}, 1fr)`,
22
+ showCoordinates === 'hidden' ? `repeat(${config.boardSize}, 1fr)` : `0.5fr repeat(${config.boardSize}, 1fr)`,
25
23
  }),
26
- [backgroundImage, config.boardHeight, config.boardWidth, tileFontSize],
24
+ [backgroundImage, config.boardSize, tileFontSize],
27
25
  );
28
26
 
29
27
  return boardStyle;
@@ -18,7 +18,7 @@ import { useDispatch } from 'react-redux';
18
18
  import { useLatest } from 'hooks';
19
19
  import { LOCALE_FEATURES } from 'i18n';
20
20
  import { createGridOf, createKeyboardNavigation, extractCharacters, extractInputValue, isCtrl } from 'lib';
21
- import { boardSlice, selectConfig, selectLocale, useTypedSelector } from 'state';
21
+ import { boardSlice, cellFilterSlice, selectConfig, selectLocale, useTypedSelector } from 'state';
22
22
  import { Direction, Point } from 'types';
23
23
 
24
24
  import { getPositionInGrid } from '../lib';
@@ -106,7 +106,7 @@ const useGrid = (rows: Cell[][]): [State, Actions] => {
106
106
  };
107
107
 
108
108
  characters.forEach((character) => {
109
- if (x >= config.boardWidth || y >= config.boardHeight) {
109
+ if (x >= config.boardSize || y >= config.boardSize) {
110
110
  return;
111
111
  }
112
112
 
@@ -299,15 +299,8 @@ const useGrid = (rows: Cell[][]): [State, Actions] => {
299
299
 
300
300
  const { x, y } = position;
301
301
  const character = event.key.toLowerCase();
302
- const isTogglingBlank = isCtrl(event) && character === 'b';
303
302
  const twoCharacterTile = config.getTwoCharacterTileByPrefix(character);
304
303
 
305
- if (isTogglingBlank) {
306
- event.preventDefault();
307
- dispatch(boardSlice.actions.toggleCellIsBlank(position));
308
- return;
309
- }
310
-
311
304
  if (isCtrl(event) && twoCharacterTile) {
312
305
  event.preventDefault();
313
306
  dispatch(boardSlice.actions.changeCellValue({ x, y, value: twoCharacterTile }));
@@ -316,6 +309,17 @@ const useGrid = (rows: Cell[][]): [State, Actions] => {
316
309
  }
317
310
 
318
311
  const cell = rows[y][x];
312
+
313
+ if (isCtrl(event) && character === 'g') {
314
+ event.preventDefault();
315
+
316
+ if (!cell.hasTile()) {
317
+ dispatch(cellFilterSlice.actions.toggle(position));
318
+ }
319
+
320
+ return;
321
+ }
322
+
319
323
  const twoCharacterCandidate = cell.tile.character + character;
320
324
 
321
325
  if (config.twoCharacterTiles.includes(twoCharacterCandidate)) {
@@ -5,6 +5,7 @@
5
5
  height: var(--key--height);
6
6
  padding: var(--spacing--s) var(--spacing--m);
7
7
  background-color: var(--color--white);
8
+ color: var(--color--foreground);
8
9
  border: var(--border);
9
10
  border-radius: var(--border--radius);
10
11
  box-shadow: var(--box-shadow);
@@ -12,10 +13,10 @@
12
13
  line-height: calc(var(--key--height) - 2 * (var(--spacing--s) + var(--border--width)));
13
14
  vertical-align: middle;
14
15
  text-align: center;
15
- white-space: nowrap;
16
+ white-space: pre-wrap;
16
17
 
17
18
  & + & {
18
- margin-left: var(--spacing--xs);
19
+ margin-inline-start: var(--spacing--xs);
19
20
  }
20
21
 
21
22
  :global(svg) {
@@ -0,0 +1,41 @@
1
+ .arrows {
2
+ display: inline-grid;
3
+ grid-template-columns: repeat(3, 1fr);
4
+ grid-template-rows: repeat(2, 1fr);
5
+ gap: var(--spacing--xs);
6
+ gap: 1px;
7
+ }
8
+
9
+ .arrow {
10
+ margin-inline-start: 0 !important;
11
+ max-width: var(--key--height--small);
12
+ padding: 0;
13
+ }
14
+
15
+ .up {
16
+ grid-row: 1;
17
+ grid-column: 2;
18
+ }
19
+
20
+ .left {
21
+ grid-row: 2;
22
+ grid-column: 1;
23
+
24
+ [dir='rtl'] & {
25
+ grid-column: 3;
26
+ }
27
+ }
28
+
29
+ .right {
30
+ grid-row: 2;
31
+ grid-column: 3;
32
+
33
+ [dir='rtl'] & {
34
+ grid-column: 1;
35
+ }
36
+ }
37
+
38
+ .down {
39
+ grid-row: 2;
40
+ grid-column: 2;
41
+ }
@@ -0,0 +1,31 @@
1
+ import classNames from 'classnames';
2
+ import { FunctionComponent } from 'react';
3
+
4
+ import { ArrowDown, ArrowLeft, ArrowRight, ArrowUp } from 'icons';
5
+
6
+ import Key from '../../Key';
7
+
8
+ import styles from './Arrows.module.scss';
9
+
10
+ interface Props {
11
+ className?: string;
12
+ }
13
+
14
+ const Arrows: FunctionComponent<Props> = ({ className }) => (
15
+ <div className={classNames(styles.arrows, className)}>
16
+ <Key className={classNames(styles.arrow, styles.left)}>
17
+ <ArrowLeft />
18
+ </Key>
19
+ <Key className={classNames(styles.arrow, styles.up)}>
20
+ <ArrowUp />
21
+ </Key>
22
+ <Key className={classNames(styles.arrow, styles.right)}>
23
+ <ArrowRight />
24
+ </Key>
25
+ <Key className={classNames(styles.arrow, styles.down)}>
26
+ <ArrowDown />
27
+ </Key>
28
+ </div>
29
+ );
30
+
31
+ export default Arrows;
@@ -0,0 +1 @@
1
+ export { default } from './Arrows';
@@ -0,0 +1 @@
1
+ export { default as Arrows } from './Arrows';
@@ -39,7 +39,7 @@ const InputPrompt = forwardRef<HTMLFormElement, Props>(
39
39
  (event) => {
40
40
  event.preventDefault();
41
41
  const charactersByCase = extractCharactersByCase(config, value);
42
- const characters = Array.from({ length: config.maximumCharactersCount }, (_, index) => {
42
+ const characters = Array.from({ length: config.rackSize }, (_, index) => {
43
43
  return typeof charactersByCase[index] === 'string' ? charactersByCase[index] : null;
44
44
  });
45
45
  dispatch(rackSlice.actions.changeCharacters({ characters, index: 0 }));
@@ -13,10 +13,9 @@ import { Column } from './types';
13
13
 
14
14
  interface Props {
15
15
  column: Column;
16
- sortable?: boolean;
17
16
  }
18
17
 
19
- const HeaderButton = ({ column, sortable }: Props): ReactElement => {
18
+ const HeaderButton = ({ column }: Props): ReactElement => {
20
19
  const dispatch = useDispatch();
21
20
  const translate = useTranslate();
22
21
  const sort = useTypedSelector(selectResultsSort);
@@ -29,12 +28,10 @@ const HeaderButton = ({ column, sortable }: Props): ReactElement => {
29
28
  <Tooltip tooltip={translate(column.translationKey)}>
30
29
  <button
31
30
  aria-label={translate(column.translationKey)}
32
- className={classNames(styles.headerButton, column.className, {
33
- [styles.sortable]: sortable,
34
- })}
31
+ className={classNames(styles.headerButton, column.className)}
35
32
  key={column.id}
36
33
  type="button"
37
- onClick={sortable ? handleClick : undefined}
34
+ onClick={handleClick}
38
35
  >
39
36
  <span className={styles.cell}>
40
37
  <span className={styles.headerButtonLabel}>{translate(column.translationKey)}</span>
@@ -52,6 +52,8 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
52
52
 
53
53
  .headerButton {
54
54
  @include button-reset;
55
+ @include focus-effect;
56
+ cursor: pointer;
55
57
 
56
58
  text-transform: uppercase;
57
59
  transition: var(--transition);
@@ -64,12 +66,6 @@ $row-padding-horizontal: calc(var(--spacing--m) + var(--spacing--s));
64
66
  &:last-child {
65
67
  border-start-end-radius: inherit;
66
68
  }
67
- }
68
-
69
- .sortable {
70
- @include focus-effect;
71
-
72
- cursor: pointer;
73
69
 
74
70
  &:focus,
75
71
  &:hover {
@@ -1,6 +1,7 @@
1
1
  import classNames from 'classnames';
2
2
  import { FunctionComponent, useEffect, useMemo, useState } from 'react';
3
3
  import { FixedSizeList } from 'react-window';
4
+ import { useDebounce } from 'use-debounce';
4
5
 
5
6
  import { useAppLayout, useLatest } from 'hooks';
6
7
  import { LOCALE_FEATURES } from 'i18n';
@@ -32,6 +33,8 @@ interface Props {
32
33
  highlightedIndex?: number;
33
34
  }
34
35
 
36
+ const IS_LOADING_DEBOUNCE = 100;
37
+
35
38
  const Results: FunctionComponent<Props> = ({ callbacks, className, highlightedIndex }) => {
36
39
  const translate = useTranslate();
37
40
  const { resultsHeight, resultsWidth } = useAppLayout();
@@ -39,6 +42,7 @@ const Results: FunctionComponent<Props> = ({ callbacks, className, highlightedIn
39
42
  const { direction } = LOCALE_FEATURES[locale];
40
43
  const results = useTypedSelector(selectResults);
41
44
  const isLoading = useTypedSelector(selectIsLoading);
45
+ const [isLoadingDebounced] = useDebounce(isLoading, IS_LOADING_DEBOUNCE);
42
46
  const isOutdated = useTypedSelector(selectAreResultsOutdated);
43
47
  const error = useTypedSelector(selectSolveError);
44
48
  const itemData = useMemo(() => ({ ...callbacks, highlightedIndex, results }), [callbacks, highlightedIndex, results]);
@@ -69,7 +73,7 @@ const Results: FunctionComponent<Props> = ({ callbacks, className, highlightedIn
69
73
  <div className={classNames(styles.results, className)}>
70
74
  <div className={styles.header}>
71
75
  {columns.map((column) => (
72
- <HeaderButton column={column} key={column.id} sortable={column.sortable} />
76
+ <HeaderButton column={column} key={column.id} />
73
77
  ))}
74
78
  </div>
75
79
 
@@ -128,7 +132,7 @@ const Results: FunctionComponent<Props> = ({ callbacks, className, highlightedIn
128
132
 
129
133
  {showInput && <ResultsInput className={styles.input} />}
130
134
 
131
- {isLoading && <Loading />}
135
+ {isLoadingDebounced && <Loading />}
132
136
  </div>
133
137
  );
134
138
  };
@@ -8,7 +8,6 @@ const getCoordinatesColumn = (): Column => {
8
8
  className: styles.coordinates,
9
9
  id: ResultColumn.Coordinates,
10
10
  translationKey: 'settings.showCoordinates',
11
- sortable: false,
12
11
  };
13
12
  };
14
13
 
@@ -10,13 +10,11 @@ const getLocaleColumns = (options: { consonants: boolean; vowels: boolean }): Co
10
10
  className: styles.word,
11
11
  id: ResultColumn.Word,
12
12
  translationKey: 'common.word',
13
- sortable: true,
14
13
  },
15
14
  {
16
15
  className: styles.stat,
17
16
  id: ResultColumn.TilesCount,
18
17
  translationKey: 'common.tiles',
19
- sortable: true,
20
18
  },
21
19
  ];
22
20
 
@@ -25,7 +23,6 @@ const getLocaleColumns = (options: { consonants: boolean; vowels: boolean }): Co
25
23
  className: styles.stat,
26
24
  id: ResultColumn.ConsonantsCount,
27
25
  translationKey: 'common.consonants',
28
- sortable: true,
29
26
  });
30
27
  }
31
28
 
@@ -34,7 +31,6 @@ const getLocaleColumns = (options: { consonants: boolean; vowels: boolean }): Co
34
31
  className: styles.stat,
35
32
  id: ResultColumn.VowelsCount,
36
33
  translationKey: 'common.vowels',
37
- sortable: true,
38
34
  });
39
35
  }
40
36
 
@@ -43,19 +39,16 @@ const getLocaleColumns = (options: { consonants: boolean; vowels: boolean }): Co
43
39
  className: styles.stat,
44
40
  id: ResultColumn.BlanksCount,
45
41
  translationKey: 'common.blanks',
46
- sortable: true,
47
42
  },
48
43
  {
49
44
  className: styles.stat,
50
45
  id: ResultColumn.WordsCount,
51
46
  translationKey: 'common.words',
52
- sortable: true,
53
47
  },
54
48
  {
55
49
  className: styles.points,
56
50
  id: ResultColumn.Points,
57
51
  translationKey: 'common.points',
58
- sortable: true,
59
52
  },
60
53
  );
61
54
 
@@ -7,7 +7,6 @@ export interface Column {
7
7
  className: string;
8
8
  id: ResultColumn;
9
9
  translationKey: TranslationKey;
10
- sortable?: boolean;
11
10
  }
12
11
 
13
12
  export interface ResultCallbacks {
@@ -22,4 +22,6 @@ export { default as SeoMessage } from './SeoMessage';
22
22
  export { default as Solver } from './Solver';
23
23
  export { default as Spinner } from './Spinner';
24
24
  export { default as Tile } from './Tile';
25
+ export * from './keys';
26
+ export * from './Keys';
25
27
  export * from './Tooltip';
@@ -0,0 +1,26 @@
1
+ import { FunctionComponent } from 'react';
2
+
3
+ import { isMac } from 'lib';
4
+ import { useTranslate } from 'state';
5
+
6
+ import Key from './Key';
7
+
8
+ interface Props {
9
+ className?: string;
10
+ }
11
+
12
+ export const Backspace: FunctionComponent<Props> = ({ className }) => <Key className={className}>← Backspace</Key>;
13
+
14
+ export const Ctrl: FunctionComponent<Props> = ({ className }) => (
15
+ <Key className={className}>{isMac() ? '⌘' : 'Ctrl'}</Key>
16
+ );
17
+
18
+ export const Del: FunctionComponent<Props> = ({ className }) => <Key className={className}>Del</Key>;
19
+
20
+ export const Enter: FunctionComponent<Props> = ({ className }) => <Key className={className}>Enter ⏎</Key>;
21
+
22
+ export const Space: FunctionComponent<Props> = ({ className }) => {
23
+ const translate = useTranslate();
24
+
25
+ return <Key className={className}>{` ${translate('common.space')} `}</Key>;
26
+ };
@@ -39,7 +39,7 @@ const useAppLayout = () => {
39
39
  const solverHeight = viewportHeight - navHeight;
40
40
  const solverWidth = viewportWidth;
41
41
  const maxBoardWidth = solverWidth - columnWidth - (showColumn ? componentsSpacing : 0) - 2 * componentsSpacing;
42
- const tileSize = Math.min((maxBoardWidth - 2 * BORDER_WIDTH) / config.maximumCharactersCount, RACK_TILE_SIZE_MAX);
42
+ const tileSize = Math.min((maxBoardWidth - 2 * BORDER_WIDTH) / config.rackSize, RACK_TILE_SIZE_MAX);
43
43
  const candidatePickerHeight = showResultCandidatePicker ? BUTTON_HEIGHT + componentsSpacing : 0;
44
44
  const bottomContainerHeight = candidatePickerHeight + tileSize + 2 * componentsSpacing;
45
45
  const maxBoardHeight = isBoardFullWidth
@@ -49,25 +49,25 @@ const useAppLayout = () => {
49
49
  const coordinatesSizeRatio = showCoordinates === 'hidden' ? 0 : 0.5;
50
50
  const coordinatesBorderWidth = showCoordinates === 'hidden' ? 0 : 1;
51
51
  const cellWidth =
52
- (maxBoardWidth - (config.boardWidth + 1 + coordinatesBorderWidth) * BORDER_WIDTH) /
53
- (config.boardWidth + coordinatesSizeRatio);
52
+ (maxBoardWidth - (config.boardSize + 1 + coordinatesBorderWidth) * BORDER_WIDTH) /
53
+ (config.boardSize + coordinatesSizeRatio);
54
54
  const cellHeight =
55
- (maxBoardHeight - (config.boardHeight + 1 + coordinatesBorderWidth) * BORDER_WIDTH) /
56
- (config.boardHeight + coordinatesSizeRatio);
55
+ (maxBoardHeight - (config.boardSize + 1 + coordinatesBorderWidth) * BORDER_WIDTH) /
56
+ (config.boardSize + coordinatesSizeRatio);
57
57
  const cellSize = Math.min(Math.min(cellWidth, cellHeight), BOARD_TILE_SIZE_MAX);
58
58
  const coordinatesSize = coordinatesSizeRatio * cellSize;
59
59
  const boardSize =
60
- (cellSize + BORDER_WIDTH) * config.boardWidth +
60
+ (cellSize + BORDER_WIDTH) * config.boardSize +
61
61
  BORDER_WIDTH +
62
62
  (showCoordinates === 'hidden' ? 0 : coordinatesSize + BORDER_WIDTH);
63
- const maxControlsWidth = tileSize * config.maximumCharactersCount + 2 * BORDER_WIDTH;
63
+ const maxControlsWidth = tileSize * config.rackSize + 2 * BORDER_WIDTH;
64
64
  const showResultsInModal = isLessThanL;
65
65
  const dictionaryHeight = showResultsInModal ? DICTIONARY_HEIGHT_MOBILE : DICTIONARY_HEIGHT;
66
66
  const modalWidth = isLessThanS ? viewportWidth : MODAL_WIDTH;
67
67
  const resultsHeight = isLessThanL
68
68
  ? viewportHeight - dictionaryHeight - BUTTON_HEIGHT - MODAL_HEADER_HEIGHT - 5 * componentsSpacing
69
69
  : boardSize - componentsSpacing - dictionaryHeight;
70
- const rackWidth = tileSize * config.maximumCharactersCount;
70
+ const rackWidth = tileSize * config.rackSize;
71
71
 
72
72
  return {
73
73
  actionsWidth: 2 * BUTTON_HEIGHT - BORDER_WIDTH,
@@ -5,7 +5,7 @@
5
5
  "cell.set-blank": "Mark it a blank",
6
6
  "cell.set-not-blank": "Mark it not a blank",
7
7
  "cell.tile.location": "Board: tile ({{x}}, {{y}})",
8
- "cell.toggle-direction": "Typing direction",
8
+ "common.arrows": "Arrow keys",
9
9
  "common.blanks": "Blanks",
10
10
  "common.clear": "Clear",
11
11
  "common.close": "Close",
@@ -14,6 +14,7 @@
14
14
  "common.next": "Next",
15
15
  "common.points": "Points",
16
16
  "common.previous": "Previous",
17
+ "common.space": "Spacebar",
17
18
  "common.tiles": "Tiles",
18
19
  "common.two-letter-tiles": "Two-letter",
19
20
  "common.vowels": "Vowels",
@@ -34,6 +35,7 @@
34
35
  "keyMap": "Keyboard shortcuts",
35
36
  "keyMap.board": "Board",
36
37
  "keyMap.board.toggle-blank": "Mark/unmark tile as a blank",
38
+ "keyMap.board.toggle-cell-filter": "Toggle destination filter",
37
39
  "keyMap.board.toggle-direction": "Toggle typing direction",
38
40
  "keyMap.board-and-rack": "Board & rack",
39
41
  "keyMap.board-and-rack.insert-two-letter-tile": "Insert two-letter tile",
@@ -41,7 +43,7 @@
41
43
  "keyMap.board-and-rack.remove-tile": "Remove tile",
42
44
  "keyMap.board-and-rack.submit": "Start solving",
43
45
  "keyMap.rack": "Rack",
44
- "keyMap.rack.insert-blank": "Insert blank (spacebar)",
46
+ "keyMap.rack.insert-blank": "Insert blank",
45
47
  "menu": "Menu",
46
48
  "rack.placeholder": "Letters",
47
49
  "rack.tile.location": "Rack: tile ({{index}})",