@scrabble-solver/scrabble-solver 2.10.0 → 2.10.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.
Files changed (48) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +5 -5
  3. package/.next/cache/.tsbuildinfo +1 -1
  4. package/.next/cache/eslint/.cache_8dgz12 +1 -1
  5. package/.next/cache/next-server.js.nft.json +1 -1
  6. package/.next/cache/webpack/client-production/0.pack +0 -0
  7. package/.next/cache/webpack/client-production/index.pack +0 -0
  8. package/.next/cache/webpack/edge-server-production/0.pack +0 -0
  9. package/.next/cache/webpack/edge-server-production/index.pack +0 -0
  10. package/.next/cache/webpack/server-production/0.pack +0 -0
  11. package/.next/cache/webpack/server-production/index.pack +0 -0
  12. package/.next/next-server.js.nft.json +1 -1
  13. package/.next/prerender-manifest.json +1 -1
  14. package/.next/routes-manifest.json +1 -1
  15. package/.next/server/chunks/413.js +49 -31
  16. package/.next/server/chunks/515.js +97 -71
  17. package/.next/server/chunks/939.js +1 -0
  18. package/.next/server/middleware-build-manifest.js +1 -1
  19. package/.next/server/pages/404.html +2 -2
  20. package/.next/server/pages/404.js.nft.json +1 -1
  21. package/.next/server/pages/500.html +2 -2
  22. package/.next/server/pages/_app.js.nft.json +1 -1
  23. package/.next/server/pages/_error.js.nft.json +1 -1
  24. package/.next/server/pages/api/dictionary/[locale]/[word].js +1 -1
  25. package/.next/server/pages/api/solve.js +20 -23
  26. package/.next/server/pages/index.html +2 -2
  27. package/.next/server/pages/index.js.nft.json +1 -1
  28. package/.next/server/pages/index.json +1 -1
  29. package/.next/server/pages-manifest.json +1 -1
  30. package/.next/static/chunks/368-8b386c3106556f62.js +1 -0
  31. package/.next/static/chunks/pages/_app-8f0df20f771045ed.js +1 -0
  32. package/.next/static/hf94cues-LcXZRCpAzQ6w/_buildManifest.js +1 -0
  33. package/.next/static/{vscqn7BEtAxJteWSwNnas → hf94cues-LcXZRCpAzQ6w}/_ssgManifest.js +0 -0
  34. package/.next/trace +52 -52
  35. package/package.json +9 -9
  36. package/src/components/Board/hooks/useGrid.ts +6 -6
  37. package/src/components/Rack/Rack.tsx +16 -4
  38. package/src/components/Results/Results.tsx +2 -2
  39. package/src/components/Settings/components/AutoGroupTilesSetting/AutoGroupTilesSetting.tsx +2 -2
  40. package/src/components/Settings/components/LocaleSetting/options.ts +9 -1
  41. package/src/i18n/fr.json +1 -1
  42. package/src/lib/getRemainingTilesGroups.ts +24 -26
  43. package/src/sdk/findWordDefinitions.ts +1 -1
  44. package/src/state/sagas.ts +19 -5
  45. package/src/state/selectors.ts +10 -2
  46. package/.next/static/chunks/368-d423e70be6c0c473.js +0 -1
  47. package/.next/static/chunks/pages/_app-3f5508a5f544d9eb.js +0 -1
  48. package/.next/static/vscqn7BEtAxJteWSwNnas/_buildManifest.js +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scrabble-solver/scrabble-solver",
3
- "version": "2.10.0",
3
+ "version": "2.10.1",
4
4
  "description": "Scrabble Solver 2 - App",
5
5
  "engines": {
6
6
  "node": ">=16"
@@ -31,13 +31,13 @@
31
31
  "@kamilmielnik/trie": "^2.0.1",
32
32
  "@popperjs/core": "^2.11.6",
33
33
  "@reduxjs/toolkit": "^1.8.6",
34
- "@scrabble-solver/configs": "^2.10.0",
35
- "@scrabble-solver/constants": "^2.10.0",
36
- "@scrabble-solver/dictionaries": "^2.10.0",
37
- "@scrabble-solver/logger": "^2.10.0",
38
- "@scrabble-solver/solver": "^2.10.0",
39
- "@scrabble-solver/types": "^2.10.0",
40
- "@scrabble-solver/word-definitions": "^2.10.0",
34
+ "@scrabble-solver/configs": "^2.10.1",
35
+ "@scrabble-solver/constants": "^2.10.1",
36
+ "@scrabble-solver/dictionaries": "^2.10.1",
37
+ "@scrabble-solver/logger": "^2.10.1",
38
+ "@scrabble-solver/solver": "^2.10.1",
39
+ "@scrabble-solver/types": "^2.10.1",
40
+ "@scrabble-solver/word-definitions": "^2.10.1",
41
41
  "classnames": "^2.3.2",
42
42
  "next": "^12.3.1",
43
43
  "normalize.css": "^8.0.1",
@@ -74,5 +74,5 @@
74
74
  "sass": "^1.55.0",
75
75
  "workbox-webpack-plugin": "^6.5.4"
76
76
  },
77
- "gitHead": "7fd0f6bf536fca88c18ce02e320da71b0c6d6f61"
77
+ "gitHead": "506586e8bb9e71f9f784dabffae2229c587c5e5a"
78
78
  }
@@ -17,8 +17,9 @@ import { useDispatch } from 'react-redux';
17
17
  import { useLatest } from 'react-use';
18
18
  import { AnyAction } from 'redux';
19
19
 
20
+ import { LOCALE_FEATURES } from 'i18n';
20
21
  import { createGridOf, createKeyboardNavigation, extractCharacters, extractInputValue, isCtrl } from 'lib';
21
- import { boardSlice, selectConfig, useTypedSelector } from 'state';
22
+ import { boardSlice, selectConfig, selectLocale, useTypedSelector } from 'state';
22
23
  import { Direction } from 'types';
23
24
 
24
25
  import { getPositionInGrid } from '../lib';
@@ -44,6 +45,7 @@ const useGrid = (rows: Cell[][]): [State, Actions] => {
44
45
  const width = rows[0].length;
45
46
  const dispatch = useDispatch();
46
47
  const config = useTypedSelector(selectConfig);
48
+ const locale = useTypedSelector(selectLocale);
47
49
  const refs = useMemo(
48
50
  () => createGridOf<RefObject<HTMLInputElement>>(width, height, () => createRef()),
49
51
  [width, height],
@@ -235,8 +237,7 @@ const useGrid = (rows: Cell[][]): [State, Actions] => {
235
237
  if (isCtrl(event)) {
236
238
  onDirectionToggle();
237
239
  } else {
238
- const isRtl = document.body.parentElement?.dir === 'rtl';
239
- changeActiveIndex(isRtl ? 1 : -1, 0);
240
+ changeActiveIndex(LOCALE_FEATURES[locale].direction === 'ltr' ? -1 : 1, 0);
240
241
  }
241
242
  },
242
243
  onArrowRight: (event) => {
@@ -245,8 +246,7 @@ const useGrid = (rows: Cell[][]): [State, Actions] => {
245
246
  if (isCtrl(event)) {
246
247
  onDirectionToggle();
247
248
  } else {
248
- const isRtl = document.body.parentElement?.dir === 'rtl';
249
- changeActiveIndex(isRtl ? -1 : 1, 0);
249
+ changeActiveIndex(LOCALE_FEATURES[locale].direction === 'ltr' ? 1 : -1, 0);
250
250
  }
251
251
  },
252
252
  onArrowUp: (event) => {
@@ -333,7 +333,7 @@ const useGrid = (rows: Cell[][]): [State, Actions] => {
333
333
  dispatch(boardSlice.actions.toggleCellIsBlank(position));
334
334
  },
335
335
  });
336
- }, [changeActiveIndex, config, dispatch, onDirectionToggle, rows]);
336
+ }, [changeActiveIndex, config, dispatch, locale, onDirectionToggle, rows]);
337
337
 
338
338
  const onPaste = useCallback<ClipboardEventHandler>(
339
339
  (event) => {
@@ -2,6 +2,7 @@ import classNames from 'classnames';
2
2
  import { ChangeEvent, ClipboardEvent, createRef, FunctionComponent, useCallback, useMemo, useRef } from 'react';
3
3
  import { useDispatch } from 'react-redux';
4
4
 
5
+ import { LOCALE_FEATURES } from 'i18n';
5
6
  import {
6
7
  createArray,
7
8
  createKeyboardNavigation,
@@ -10,7 +11,7 @@ import {
10
11
  isCtrl,
11
12
  zipCharactersAndTiles,
12
13
  } from 'lib';
13
- import { rackSlice, selectConfig, selectRack, selectResultCandidateTiles, useTypedSelector } from 'state';
14
+ import { rackSlice, selectConfig, selectLocale, selectRack, selectResultCandidateTiles, useTypedSelector } from 'state';
14
15
 
15
16
  import styles from './Rack.module.scss';
16
17
  import RackTile from './RackTile';
@@ -22,12 +23,14 @@ interface Props {
22
23
  const Rack: FunctionComponent<Props> = ({ className }) => {
23
24
  const dispatch = useDispatch();
24
25
  const config = useTypedSelector(selectConfig);
26
+ const locale = useTypedSelector(selectLocale);
25
27
  const rack = useTypedSelector(selectRack);
26
28
  const resultCandidateTiles = useTypedSelector(selectResultCandidateTiles);
27
29
  const tiles = useMemo(() => zipCharactersAndTiles(rack, resultCandidateTiles), [rack, resultCandidateTiles]);
28
30
  const tilesCount = tiles.length;
29
31
  const tilesRefs = useMemo(() => createArray(tilesCount).map(() => createRef<HTMLInputElement>()), [tilesCount]);
30
32
  const activeIndexRef = useRef<number>();
33
+ const { direction } = LOCALE_FEATURES[locale];
31
34
 
32
35
  const changeActiveIndex = useCallback(
33
36
  (offset: number) => {
@@ -73,18 +76,27 @@ const Rack: FunctionComponent<Props> = ({ className }) => {
73
76
  return createKeyboardNavigation({
74
77
  onArrowLeft: (event) => {
75
78
  event.preventDefault();
76
- const direction = document.body.parentElement?.dir || 'ltr';
77
79
  changeActiveIndex(direction === 'ltr' ? -1 : 1);
78
80
  },
79
81
  onArrowRight: (event) => {
80
82
  event.preventDefault();
81
- const direction = document.body.parentElement?.dir || 'ltr';
82
83
  changeActiveIndex(direction === 'ltr' ? 1 : -1);
83
84
  },
84
85
  onBackspace: (event) => {
85
86
  event.preventDefault();
86
87
  changeActiveIndex(-1);
87
88
  },
89
+ onDelete: (event) => {
90
+ const index = activeIndexRef.current;
91
+
92
+ if (typeof index === 'undefined') {
93
+ return;
94
+ }
95
+
96
+ event.preventDefault();
97
+ dispatch(rackSlice.actions.changeCharacters({ characters: [null], index }));
98
+ changeActiveIndex(1);
99
+ },
88
100
  onKeyDown: (event) => {
89
101
  if (isCtrl(event) && config.isTwoCharacterTilePrefix(event.key)) {
90
102
  changeActiveIndex(1);
@@ -97,7 +109,7 @@ const Rack: FunctionComponent<Props> = ({ className }) => {
97
109
  }
98
110
  },
99
111
  });
100
- }, [changeActiveIndex, config]);
112
+ }, [changeActiveIndex, config, direction]);
101
113
 
102
114
  return (
103
115
  <div className={classNames(styles.rack, className)} onPaste={handlePaste}>
@@ -61,7 +61,7 @@ const Results: FunctionComponent<Props> = ({ height, width }) => {
61
61
  </EmptyState>
62
62
  )}
63
63
 
64
- {typeof results === 'undefined' && typeof error === 'undefined' && (
64
+ {typeof error === 'undefined' && typeof results === 'undefined' && (
65
65
  <EmptyState className={styles.emptyState} type="info">
66
66
  {translate('results.empty-state.uninitialized')}
67
67
 
@@ -69,7 +69,7 @@ const Results: FunctionComponent<Props> = ({ height, width }) => {
69
69
  </EmptyState>
70
70
  )}
71
71
 
72
- {typeof results !== 'undefined' && typeof allResults !== 'undefined' && typeof error === 'undefined' && (
72
+ {typeof error === 'undefined' && typeof results !== 'undefined' && typeof allResults !== 'undefined' && (
73
73
  <>
74
74
  {isOutdated && (
75
75
  <EmptyState className={styles.emptyState} type="info">
@@ -17,7 +17,7 @@ interface Props {
17
17
  const AutoGroupTilesSetting: FunctionComponent<Props> = ({ className, disabled }) => {
18
18
  const dispatch = useDispatch();
19
19
  const translate = useTranslate();
20
- const configId = useTypedSelector(selectAutoGroupTiles);
20
+ const value = useTypedSelector(selectAutoGroupTiles);
21
21
 
22
22
  const options = [
23
23
  {
@@ -43,7 +43,7 @@ const AutoGroupTilesSetting: FunctionComponent<Props> = ({ className, disabled }
43
43
  <div className={className}>
44
44
  {options.map((option) => (
45
45
  <Radio
46
- checked={configId === parseValue(option.value)}
46
+ checked={value === parseValue(option.value)}
47
47
  className={styles.option}
48
48
  disabled={disabled}
49
49
  id="autoGroupTiles"
@@ -9,6 +9,7 @@ interface Option {
9
9
  className: string;
10
10
  Icon: FunctionComponent<SVGAttributes<SVGElement>>;
11
11
  label: string;
12
+ name: string;
12
13
  value: Locale;
13
14
  }
14
15
 
@@ -17,44 +18,51 @@ const options: Option[] = [
17
18
  className: styles.gb,
18
19
  Icon: FlagGb,
19
20
  label: 'English (GB)',
21
+ name: 'English (GB)',
20
22
  value: Locale.EN_GB,
21
23
  },
22
24
  {
23
25
  className: styles.us,
24
26
  Icon: FlagUs,
25
27
  label: 'English (US)',
28
+ name: 'English (US)',
26
29
  value: Locale.EN_US,
27
30
  },
28
31
  {
29
32
  className: styles.fa,
30
33
  Icon: FlagFa,
31
34
  label: 'فارسی',
35
+ name: 'Persian',
32
36
  value: Locale.FA_IR,
33
37
  },
34
38
  {
35
39
  className: styles.fr,
36
40
  Icon: FlagFr,
37
41
  label: 'Français',
42
+ name: 'French',
38
43
  value: Locale.FR_FR,
39
44
  },
40
45
  {
41
46
  className: styles.de,
42
47
  Icon: FlagDe,
43
48
  label: 'Deutsch',
49
+ name: 'German',
44
50
  value: Locale.DE_DE,
45
51
  },
46
52
  {
47
53
  className: styles.pl,
48
54
  Icon: FlagPl,
49
55
  label: 'Polski',
56
+ name: 'Polish',
50
57
  value: Locale.PL_PL,
51
58
  },
52
59
  {
53
60
  className: styles.es,
54
61
  Icon: FlagEs,
55
62
  label: 'Español',
63
+ name: 'Spanish',
56
64
  value: Locale.ES_ES,
57
65
  },
58
- ];
66
+ ].sort((a, b) => a.name.localeCompare(b.name));
59
67
 
60
68
  export default options;
package/src/i18n/fr.json CHANGED
@@ -40,7 +40,7 @@
40
40
  "results.empty-state.no-filtered-results": "Aucun résultat ne correspond à cette requête",
41
41
  "results.empty-state.no-results": "Pas de résultats - impossible de générer des mots.",
42
42
  "results.empty-state.outdated": "Les résultats sont dépassé. Cliquer ci-dessous pour mettre à jour.",
43
- "results.empty-state.uninitialized": "Words generated from your letters will be shown here.",
43
+ "results.empty-state.uninitialized": "Les mots générés à partir de vos lettres seront affichés ici.",
44
44
  "results.input.placeholder": "Rechercher les résultats... (RegExp)",
45
45
  "results.solve": "Résoudre",
46
46
  "settings": "Options",
@@ -8,32 +8,29 @@ import getTotalRemainingTilesCount from './getTotalRemainingTilesCount';
8
8
  const getRemainingTilesGroups = (remainingTiles: RemainingTile[]): RemainingTilesGroup[] => {
9
9
  const consonants = remainingTiles.filter(isConsonant);
10
10
  const vowels = remainingTiles.filter(isVowel);
11
+ const other = remainingTiles.filter(isOther);
11
12
  const groups: RemainingTilesGroup[] = [];
12
13
 
13
- if (consonants.length + vowels.length > 0) {
14
- groups.push({
15
- remainingCount: getRemainingTilesCount(vowels),
16
- tiles: vowels,
17
- translationKey: 'common.vowels',
18
- totalCount: getTotalRemainingTilesCount(vowels),
19
- });
20
-
21
- groups.push({
22
- remainingCount: getRemainingTilesCount(consonants),
23
- tiles: consonants,
24
- translationKey: 'common.consonants',
25
- totalCount: getTotalRemainingTilesCount(consonants),
26
- });
27
- } else {
28
- const tiles = remainingTiles.filter(isLetter);
29
-
30
- groups.push({
31
- remainingCount: getRemainingTilesCount(tiles),
32
- tiles,
33
- translationKey: 'common.tiles',
34
- totalCount: getTotalRemainingTilesCount(tiles),
35
- });
36
- }
14
+ groups.push({
15
+ remainingCount: getRemainingTilesCount(vowels),
16
+ tiles: vowels,
17
+ translationKey: 'common.vowels',
18
+ totalCount: getTotalRemainingTilesCount(vowels),
19
+ });
20
+
21
+ groups.push({
22
+ remainingCount: getRemainingTilesCount(consonants),
23
+ tiles: consonants,
24
+ translationKey: 'common.consonants',
25
+ totalCount: getTotalRemainingTilesCount(consonants),
26
+ });
27
+
28
+ groups.push({
29
+ remainingCount: getRemainingTilesCount(other),
30
+ tiles: other,
31
+ translationKey: 'common.tiles',
32
+ totalCount: getTotalRemainingTilesCount(other),
33
+ });
37
34
 
38
35
  const twoCharacterTiles = remainingTiles.filter(isTwoCharacter);
39
36
  const blanks = remainingTiles.filter(isBlank);
@@ -59,10 +56,11 @@ const isConsonant = (tile: RemainingTile): boolean => CONSONANTS.includes(tile.c
59
56
 
60
57
  const isVowel = (tile: RemainingTile): boolean => VOWELS.includes(tile.character);
61
58
 
62
- const isLetter = (tile: RemainingTile): boolean => !isBlank(tile) && !isTwoCharacter(tile);
63
-
64
59
  const isTwoCharacter = (tile: RemainingTile): boolean => tile.character.length === 2;
65
60
 
66
61
  const isBlank = (tile: RemainingTile): boolean => tile.character === BLANK;
67
62
 
63
+ const isOther = (tile: RemainingTile) =>
64
+ !isConsonant(tile) && !isVowel(tile) && !isBlank(tile) && !isTwoCharacter(tile);
65
+
68
66
  export default getRemainingTilesGroups;
@@ -3,7 +3,7 @@ import { Locale, WordDefinition, WordDefinitionJson } from '@scrabble-solver/typ
3
3
  import fetchJson from './fetchJson';
4
4
 
5
5
  const findWordDefinitions = async (locale: Locale, word: string): Promise<WordDefinition[]> => {
6
- const json = await fetchJson<WordDefinitionJson[]>(`/api/dictionary/${locale}/${word}`);
6
+ const json = await fetchJson<WordDefinitionJson[]>(`/api/dictionary/${locale}/${encodeURIComponent(word)}`);
7
7
  return json.map(WordDefinition.fromJson);
8
8
  };
9
9
 
@@ -7,13 +7,13 @@ import { findWordDefinitions, solve, verify, visit } from 'sdk';
7
7
 
8
8
  import { initialize, reset } from './actions';
9
9
  import {
10
- selectAutoGroupTiles,
11
10
  selectBoard,
12
11
  selectCellIsFiltered,
13
12
  selectCharacters,
14
13
  selectConfig,
15
14
  selectDictionary,
16
15
  selectLocale,
16
+ selectLocaleAutoGroupTiles,
17
17
  } from './selectors';
18
18
  import {
19
19
  boardSlice,
@@ -65,7 +65,7 @@ function* onRackValueChange(): AnyGenerator {
65
65
  }
66
66
 
67
67
  function* onApplyResult({ payload: result }: PayloadAction<Result>): AnyGenerator {
68
- const autoGroupTiles = yield select(selectAutoGroupTiles);
68
+ const autoGroupTiles = yield select(selectLocaleAutoGroupTiles);
69
69
  yield put(boardSlice.actions.applyResult(result));
70
70
  yield put(cellFilterSlice.actions.reset());
71
71
  yield put(rackSlice.actions.removeTiles(result.tiles));
@@ -74,8 +74,15 @@ function* onApplyResult({ payload: result }: PayloadAction<Result>): AnyGenerato
74
74
  }
75
75
 
76
76
  function* onConfigIdChange(): AnyGenerator {
77
+ const characters = yield select(selectCharacters);
78
+
79
+ if (characters.length > 0) {
80
+ yield put(solveSlice.actions.submit());
81
+ } else {
82
+ yield put(resultsSlice.actions.reset());
83
+ }
84
+
77
85
  yield put(resultsSlice.actions.reset());
78
- yield put(solveSlice.actions.submit());
79
86
  yield put(verifySlice.actions.submit());
80
87
  yield* ensureProperTilesCount();
81
88
  }
@@ -112,9 +119,16 @@ function* onReset(): AnyGenerator {
112
119
  }
113
120
 
114
121
  function* onLocaleChange(): AnyGenerator {
122
+ const characters = yield select(selectCharacters);
123
+
124
+ if (characters.length > 0) {
125
+ yield put(solveSlice.actions.submit());
126
+ } else {
127
+ yield put(resultsSlice.actions.reset());
128
+ }
129
+
115
130
  yield put(dictionarySlice.actions.reset());
116
131
  yield put(resultsSlice.actions.changeResultCandidate(null));
117
- yield put(solveSlice.actions.submit());
118
132
  yield put(verifySlice.actions.submit());
119
133
  }
120
134
 
@@ -181,7 +195,7 @@ function* ensureProperTilesCount(): AnyGenerator {
181
195
  } else if (config.maximumCharactersCount < characters.length) {
182
196
  const nonNulls = characters.filter(Boolean).slice(0, config.maximumCharactersCount);
183
197
  const differenceCount = Math.abs(config.maximumCharactersCount - nonNulls.length);
184
- const autoGroupTiles = yield select(selectAutoGroupTiles);
198
+ const autoGroupTiles = yield select(selectLocaleAutoGroupTiles);
185
199
  yield put(rackSlice.actions.init([...nonNulls, ...Array(differenceCount).fill(null)]));
186
200
  yield put(rackSlice.actions.groupTiles(autoGroupTiles));
187
201
  }
@@ -2,7 +2,7 @@ import { createSelector } from '@reduxjs/toolkit';
2
2
  import { getLocaleConfig } from '@scrabble-solver/configs';
3
3
  import { Cell, Config, isError, Result, Tile } from '@scrabble-solver/types';
4
4
 
5
- import i18n from 'i18n';
5
+ import i18n, { LOCALE_FEATURES } from 'i18n';
6
6
  import { findCell, getRemainingTiles, getRemainingTilesGroups, sortResults, unorderedArraysEqual } from 'lib';
7
7
  import { Translations } from 'types';
8
8
 
@@ -38,9 +38,17 @@ export const selectDictionaryError = createSelector([selectDictionaryRoot], (dic
38
38
  return isError(dictionary.error) ? dictionary.error : undefined;
39
39
  });
40
40
 
41
+ export const selectLocale = createSelector([selectSettingsRoot], (settings) => settings.locale);
42
+
41
43
  export const selectAutoGroupTiles = createSelector([selectSettingsRoot], (settings) => settings.autoGroupTiles);
42
44
 
43
- export const selectLocale = createSelector([selectSettingsRoot], (settings) => settings.locale);
45
+ export const selectLocaleAutoGroupTiles = createSelector([selectLocale, selectSettingsRoot], (locale, settings) => {
46
+ if (LOCALE_FEATURES[locale].direction === 'ltr' || settings.autoGroupTiles === null) {
47
+ return settings.autoGroupTiles;
48
+ }
49
+
50
+ return settings.autoGroupTiles === 'left' ? 'right' : 'left';
51
+ });
44
52
 
45
53
  export const selectBoard = selectBoardRoot;
46
54