@scrabble-solver/scrabble-solver 2.15.11 → 2.15.13

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 (309) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +16 -16
  3. package/.next/cache/.previewinfo +1 -0
  4. package/.next/cache/.rscinfo +1 -1
  5. package/.next/cache/.tsbuildinfo +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/client-production/index.pack.old +0 -0
  9. package/.next/cache/webpack/edge-server-production/0.pack +0 -0
  10. package/.next/cache/webpack/edge-server-production/index.pack +0 -0
  11. package/.next/cache/webpack/edge-server-production/index.pack.old +0 -0
  12. package/.next/cache/webpack/server-production/0.pack +0 -0
  13. package/.next/cache/webpack/server-production/index.pack +0 -0
  14. package/.next/cache/webpack/server-production/index.pack.old +0 -0
  15. package/.next/diagnostics/framework.json +1 -1
  16. package/.next/next-minimal-server.js.nft.json +1 -1
  17. package/.next/next-server.js.nft.json +1 -1
  18. package/.next/prerender-manifest.json +4 -4
  19. package/.next/required-server-files.json +23 -12
  20. package/.next/routes-manifest.json +7 -3
  21. package/.next/server/chunks/106.js +1 -0
  22. package/.next/server/chunks/30.js +4 -4
  23. package/.next/server/chunks/318.js +1 -0
  24. package/.next/server/chunks/50.js +19 -0
  25. package/.next/server/chunks/812.js +1 -1
  26. package/.next/server/chunks/929.js +1 -0
  27. package/.next/server/chunks/974.js +1 -1
  28. package/.next/server/middleware-build-manifest.js +1 -1
  29. package/.next/server/pages/404.html +1 -1
  30. package/.next/server/pages/404.js.nft.json +1 -1
  31. package/.next/server/pages/500.html +1 -1
  32. package/.next/server/pages/_app.js +1 -1
  33. package/.next/server/pages/_app.js.nft.json +1 -1
  34. package/.next/server/pages/_document.js +1 -1
  35. package/.next/server/pages/_document.js.nft.json +1 -1
  36. package/.next/server/pages/_error.js +1 -1
  37. package/.next/server/pages/_error.js.nft.json +1 -1
  38. package/.next/server/pages/api/dictionary/[locale]/[word].js +2 -2
  39. package/.next/server/pages/api/dictionary/[locale]/[word].js.nft.json +1 -1
  40. package/.next/server/pages/api/dictionary/[locale].js +1 -1
  41. package/.next/server/pages/api/dictionary/[locale].js.nft.json +1 -1
  42. package/.next/server/pages/api/solve.js +1 -1
  43. package/.next/server/pages/api/solve.js.nft.json +1 -1
  44. package/.next/server/pages/api/verify.js +1 -1
  45. package/.next/server/pages/api/verify.js.nft.json +1 -1
  46. package/.next/server/pages/api/visit.js +1 -1
  47. package/.next/server/pages/api/visit.js.nft.json +1 -1
  48. package/.next/server/pages/index.html +1 -1
  49. package/.next/server/pages/index.js +1 -1
  50. package/.next/server/pages/index.js.nft.json +1 -1
  51. package/.next/server/pages/index.json +1 -1
  52. package/.next/server/pages-manifest.json +1 -1
  53. package/.next/server/webpack-api-runtime.js +1 -1
  54. package/.next/server/webpack-runtime.js +1 -1
  55. package/.next/static/{47JHul8F9NSWCNSEuahuL → MhrqAqLI9_L8obb7tgjvi}/_buildManifest.js +1 -1
  56. package/.next/static/chunks/framework-aad31c68dd0bb0ea.js +1 -0
  57. package/.next/static/chunks/main-eab5847b41f0af4a.js +1 -0
  58. package/.next/static/chunks/pages/{404-590e2a3839c1d9e0.js → 404-04457ede98c6b53e.js} +1 -1
  59. package/.next/static/chunks/pages/_app-bf81f201c021fdc1.js +1 -0
  60. package/.next/static/chunks/pages/{_error-7ea2d37f66343175.js → _error-a9800cedd835bcad.js} +1 -1
  61. package/.next/static/chunks/pages/index-79a390f3c2a2499f.js +1 -0
  62. package/.next/static/css/{6682db14f926d4c7.css → 04a3767982ec10e8.css} +1 -1
  63. package/.next/static/css/{d875648f38121a28.css → 1fae874a25934f54.css} +1 -1
  64. package/.next/trace +24 -23
  65. package/coverage/clover.xml +6 -0
  66. package/coverage/coverage-final.json +1 -0
  67. package/coverage/lcov-report/base.css +224 -0
  68. package/coverage/lcov-report/block-navigation.js +87 -0
  69. package/coverage/lcov-report/favicon.png +0 -0
  70. package/coverage/lcov-report/index.html +101 -0
  71. package/coverage/lcov-report/prettify.css +1 -0
  72. package/coverage/lcov-report/prettify.js +2 -0
  73. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  74. package/coverage/lcov-report/sorter.js +196 -0
  75. package/coverage/lcov.info +0 -0
  76. package/package.json +18 -18
  77. package/src/api/getServerLoggingData.ts +1 -1
  78. package/src/api/isBoardValid.ts +1 -1
  79. package/src/api/isCellValid.ts +1 -1
  80. package/src/api/isRowValid.ts +1 -1
  81. package/src/components/Alert/Alert.tsx +1 -1
  82. package/src/components/Badge/Badge.tsx +1 -1
  83. package/src/components/Board/Board.tsx +8 -8
  84. package/src/components/Board/BoardPure.tsx +11 -11
  85. package/src/components/Board/components/Actions/Actions.tsx +3 -3
  86. package/src/components/Board/components/Actions/lib.ts +3 -3
  87. package/src/components/Board/components/Cell/Cell.tsx +7 -7
  88. package/src/components/Board/components/InputPrompt/InputPrompt.tsx +2 -2
  89. package/src/components/Board/components/ToggleDirectionButton/ToggleDirectionButton.tsx +2 -2
  90. package/src/components/Board/hooks/useBackgroundImage.tsx +1 -1
  91. package/src/components/Board/hooks/useBoardStyle.ts +1 -1
  92. package/src/components/Board/hooks/useGrid.ts +13 -13
  93. package/src/components/Board/lib.ts +37 -0
  94. package/src/components/Board/selectors.ts +8 -0
  95. package/src/components/Button/Button.tsx +1 -1
  96. package/src/components/Button/Link.tsx +1 -1
  97. package/src/components/Dictionary/Dictionary.tsx +10 -3
  98. package/src/components/DictionaryInput/DictionaryInput.tsx +1 -1
  99. package/src/components/EmptyState/EmptyState.tsx +3 -3
  100. package/src/components/IconButton/IconButton.tsx +1 -1
  101. package/src/components/IconButton/Link.tsx +1 -1
  102. package/src/components/Key/Key.tsx +1 -1
  103. package/src/components/Keys/Arrows/Arrows.tsx +1 -1
  104. package/src/components/Keys/index.tsx +1 -1
  105. package/src/components/Loading/Loading.tsx +6 -8
  106. package/src/components/Logo/LogoBlueprint.tsx +1 -1
  107. package/src/components/Modal/Modal.tsx +4 -2
  108. package/src/components/Modal/components/Section/Section.tsx +1 -1
  109. package/src/components/NavButtons/NavButtons.tsx +4 -3
  110. package/src/components/NavButtons/selectors.ts +11 -0
  111. package/src/components/NotFound/NotFound.tsx +1 -1
  112. package/src/components/PlainTiles/PlainTiles.tsx +1 -1
  113. package/src/components/PlainTiles/Tile.tsx +1 -1
  114. package/src/components/PlainTiles/lib.ts +90 -0
  115. package/src/components/Progress/Progress.tsx +1 -1
  116. package/src/components/Rack/Rack.tsx +10 -26
  117. package/src/components/Rack/components/InputPrompt/InputPrompt.tsx +5 -8
  118. package/src/components/Rack/components/InputPrompt/lib.test.ts +27 -0
  119. package/src/components/Rack/components/InputPrompt/lib.ts +19 -0
  120. package/src/components/Rack/components/RackTile/RackTile.tsx +9 -9
  121. package/src/components/Rack/selectors.ts +9 -0
  122. package/src/components/Radio/Radio.tsx +1 -1
  123. package/src/components/Results/Cell.tsx +2 -2
  124. package/src/components/Results/Header.tsx +1 -1
  125. package/src/components/Results/HeaderButton.tsx +2 -2
  126. package/src/components/Results/Result.tsx +5 -6
  127. package/src/components/Results/Results.tsx +6 -6
  128. package/src/components/Results/SolveButton.tsx +3 -3
  129. package/src/components/Results/types.ts +2 -2
  130. package/src/components/ResultsInput/ResultsInput.tsx +1 -1
  131. package/src/components/SeoMessage/SeoMessage.tsx +1 -1
  132. package/src/components/Solver/Solver.tsx +4 -4
  133. package/src/components/Solver/components/InsertButton/InsertButton.tsx +1 -1
  134. package/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.tsx +5 -5
  135. package/src/components/Spinner/Spinner.tsx +1 -1
  136. package/src/components/Tile/Tile.tsx +7 -7
  137. package/src/components/Tile/TilePure.tsx +8 -8
  138. package/src/components/Tooltip/Tooltip.tsx +1 -1
  139. package/src/components/Tooltip/TooltipContent.tsx +1 -1
  140. package/src/components/Tooltip/TooltipTrigger.tsx +1 -1
  141. package/src/components/Tooltip/context.ts +1 -1
  142. package/src/components/Tooltip/useTooltip.ts +1 -1
  143. package/src/hooks/useAppLayout.ts +1 -1
  144. package/src/hooks/useColumns.ts +28 -1
  145. package/src/hooks/useEffectOnce.ts +1 -1
  146. package/src/hooks/useLocalStorage.ts +8 -0
  147. package/src/i18n/constants.ts +1 -1
  148. package/src/i18n/i18n.ts +1 -1
  149. package/src/i18n/languages/english.json +3 -0
  150. package/src/i18n/languages/french.json +3 -0
  151. package/src/i18n/languages/german.json +3 -0
  152. package/src/i18n/languages/persian.json +3 -0
  153. package/src/i18n/languages/polish.json +3 -0
  154. package/src/i18n/languages/romanian.json +3 -0
  155. package/src/i18n/languages/spanish.json +3 -0
  156. package/src/i18n/languages/turkish.json +3 -0
  157. package/src/lib/createComparator.ts +1 -1
  158. package/src/lib/createKeyComparator.ts +1 -1
  159. package/src/lib/createKeyboardNavigation.ts +1 -1
  160. package/src/lib/createStringComparator.ts +1 -1
  161. package/src/lib/extractCharacters.test.ts +42 -15
  162. package/src/lib/extractCharacters.ts +27 -25
  163. package/src/lib/findCell.ts +1 -1
  164. package/src/lib/index.ts +2 -17
  165. package/src/lib/isCtrl.ts +1 -1
  166. package/src/lib/localeTransliterate.test.ts +14 -0
  167. package/src/lib/localeTransliterate.ts +18 -0
  168. package/src/lib/numberComparator.ts +1 -1
  169. package/src/lib/reverseComparator.ts +1 -1
  170. package/src/lib/zipCharactersAndTiles.ts +2 -2
  171. package/src/modals/DictionaryModal/DictionaryModal.tsx +1 -1
  172. package/src/modals/KeyMapModal/KeyMapModal.tsx +1 -1
  173. package/src/modals/KeyMapModal/components/Mapping/Mapping.tsx +1 -1
  174. package/src/modals/MenuModal/MenuModal.tsx +1 -1
  175. package/src/modals/RemainingTilesModal/RemainingTilesModal.tsx +3 -2
  176. package/src/modals/RemainingTilesModal/components/Character/Character.tsx +2 -2
  177. package/src/{lib/getRemainingTilesGroups.ts → modals/RemainingTilesModal/lib.ts} +26 -9
  178. package/src/modals/RemainingTilesModal/selectors.ts +7 -0
  179. package/src/modals/ResultsModal/ResultsModal.tsx +4 -4
  180. package/src/modals/SettingsModal/SettingsModal.tsx +6 -1
  181. package/src/modals/SettingsModal/components/AutoGroupTilesSetting/AutoGroupTilesSetting.tsx +1 -1
  182. package/src/modals/SettingsModal/components/AutoGroupTilesSetting/lib.ts +1 -1
  183. package/src/modals/SettingsModal/components/ConfigSetting/ConfigSetting.tsx +1 -1
  184. package/src/modals/SettingsModal/components/ConfigSetting/lib.ts +1 -1
  185. package/src/modals/SettingsModal/components/InputModeSetting/InputModeSetting.tsx +1 -1
  186. package/src/modals/SettingsModal/components/InputModeSetting/lib.ts +1 -1
  187. package/src/modals/SettingsModal/components/LocaleSetting/LocaleSetting.tsx +2 -2
  188. package/src/modals/SettingsModal/components/RemoveCellFiltersSetting/RemoveCellFiltersSetting.module.scss +12 -0
  189. package/src/modals/SettingsModal/components/RemoveCellFiltersSetting/RemoveCellFiltersSetting.tsx +53 -0
  190. package/src/modals/SettingsModal/components/RemoveCellFiltersSetting/index.ts +1 -0
  191. package/src/modals/SettingsModal/components/RemoveCellFiltersSetting/lib.ts +13 -0
  192. package/src/modals/SettingsModal/components/ShowCoordinatesSetting/ShowCoordinatesSetting.tsx +2 -2
  193. package/src/modals/SettingsModal/components/index.ts +1 -0
  194. package/src/modals/WordsModal/WordsModal.tsx +4 -3
  195. package/src/pages/_app.tsx +2 -2
  196. package/src/pages/_document.tsx +1 -1
  197. package/src/pages/api/dictionary/[locale]/[word].ts +2 -2
  198. package/src/pages/api/dictionary/[locale]/index.ts +2 -2
  199. package/src/pages/api/solve.ts +11 -2
  200. package/src/pages/api/verify.ts +2 -2
  201. package/src/pages/api/visit.ts +1 -1
  202. package/src/pages/index.tsx +34 -43
  203. package/src/parameters/index.ts +1 -0
  204. package/src/sdk/findWordDefinitions.ts +1 -1
  205. package/src/sdk/getDictionary.ts +1 -1
  206. package/src/sdk/solve.ts +1 -1
  207. package/src/sdk/verify.ts +1 -1
  208. package/src/service-worker/dictionaries/getDictionary.ts +1 -1
  209. package/src/service-worker/dictionaries/getDictionaryUrl.ts +1 -1
  210. package/src/service-worker/dictionaries/revalidateDictionary.ts +1 -1
  211. package/src/service-worker/getTrie.ts +1 -1
  212. package/src/service-worker/routeSolveRequests.ts +2 -2
  213. package/src/service-worker/routeVerifyRequests.ts +1 -1
  214. package/src/state/board/index.ts +4 -0
  215. package/src/state/{slices/boardInitialState.ts → board/initialState.ts} +3 -6
  216. package/src/state/board/selectors.ts +3 -0
  217. package/src/state/{slices/boardSlice.ts → board/slice.ts} +5 -4
  218. package/src/state/board/types.ts +3 -0
  219. package/src/state/cellFilters/index.ts +4 -0
  220. package/src/state/cellFilters/initialState.ts +3 -0
  221. package/src/state/cellFilters/lib.ts +8 -0
  222. package/src/state/cellFilters/selectors.ts +13 -0
  223. package/src/state/{slices/cellFilterSlice.ts → cellFilters/slice.ts} +15 -14
  224. package/src/state/cellFilters/types.ts +3 -0
  225. package/src/state/dictionary/index.ts +4 -0
  226. package/src/state/dictionary/initialState.ts +8 -0
  227. package/src/state/dictionary/selectors.ts +16 -0
  228. package/src/state/{slices/dictionarySlice.ts → dictionary/slice.ts} +3 -3
  229. package/src/state/dictionary/types.ts +8 -0
  230. package/src/state/index.ts +12 -5
  231. package/src/{lib/getRemainingTiles.ts → state/lib.ts} +3 -4
  232. package/src/state/localStorage.ts +11 -2
  233. package/src/state/rack/index.ts +4 -0
  234. package/src/state/rack/initialState.ts +12 -0
  235. package/src/state/rack/selectors.ts +7 -0
  236. package/src/state/{slices/rackSlice.ts → rack/slice.ts} +4 -4
  237. package/src/state/rack/types.ts +3 -0
  238. package/src/state/results/index.ts +4 -0
  239. package/src/state/results/initialState.ts +13 -0
  240. package/src/state/results/lib.ts +96 -0
  241. package/src/state/results/selectors.ts +75 -0
  242. package/src/state/{slices/resultsSlice.ts → results/slice.ts} +4 -4
  243. package/src/state/results/types.ts +10 -0
  244. package/src/state/sagas.ts +22 -23
  245. package/src/state/selectors.ts +15 -235
  246. package/src/state/settings/index.ts +4 -0
  247. package/src/state/{slices/settingsInitialState.ts → settings/initialState.ts} +4 -11
  248. package/src/state/settings/lib.ts +42 -0
  249. package/src/state/settings/selectors.ts +69 -0
  250. package/src/state/{slices/settingsSlice.ts → settings/slice.ts} +9 -4
  251. package/src/state/settings/types.ts +12 -0
  252. package/src/state/solve/index.ts +4 -0
  253. package/src/state/solve/initialState.ts +12 -0
  254. package/src/state/solve/selectors.ts +14 -0
  255. package/src/state/{slices/solveSlice.ts → solve/slice.ts} +3 -3
  256. package/src/state/solve/types.ts +10 -0
  257. package/src/state/store.ts +9 -11
  258. package/src/state/types.ts +16 -18
  259. package/src/state/useTranslate.ts +2 -2
  260. package/src/state/useTypedSelector.ts +2 -2
  261. package/src/state/verify/index.ts +4 -0
  262. package/src/state/verify/initialState.ts +12 -0
  263. package/src/state/verify/selectors.ts +9 -0
  264. package/src/state/{slices/verifySlice.ts → verify/slice.ts} +3 -3
  265. package/src/state/verify/types.ts +10 -0
  266. package/src/styles/global.scss +5 -0
  267. package/src/types/api.ts +1 -1
  268. package/src/types/index.ts +8 -3
  269. package/tsconfig.tsbuildinfo +1 -1
  270. package/.eslintrc.js +0 -10
  271. package/.next/server/chunks/60.js +0 -1
  272. package/.next/server/chunks/968.js +0 -1
  273. package/.next/static/chunks/framework-288d1abd95de88d9.js +0 -1
  274. package/.next/static/chunks/main-016492249b3393e2.js +0 -1
  275. package/.next/static/chunks/pages/_app-0e951de0aebb6505.js +0 -1
  276. package/.next/static/chunks/pages/index-c1d5a66d0f4794a6.js +0 -1
  277. package/src/components/Board/lib/getBonusColor.ts +0 -16
  278. package/src/components/Board/lib/getPositionInGrid.ts +0 -13
  279. package/src/components/Board/lib/index.ts +0 -2
  280. package/src/components/PlainTiles/lib/createPlainTile.ts +0 -41
  281. package/src/components/PlainTiles/lib/createPlainTiles.ts +0 -25
  282. package/src/components/PlainTiles/lib/getViewbox.ts +0 -17
  283. package/src/components/PlainTiles/lib/getX.ts +0 -5
  284. package/src/components/PlainTiles/lib/getY.ts +0 -5
  285. package/src/components/PlainTiles/lib/index.ts +0 -6
  286. package/src/components/PlainTiles/lib/randomize.ts +0 -1
  287. package/src/lib/createArray.ts +0 -1
  288. package/src/lib/createGridOf.ts +0 -9
  289. package/src/lib/detectLocale.ts +0 -27
  290. package/src/lib/extractCharactersByCase.test.ts +0 -29
  291. package/src/lib/extractCharactersByCase.ts +0 -29
  292. package/src/lib/getCellSize.ts +0 -10
  293. package/src/lib/getCoordinates.ts +0 -16
  294. package/src/lib/getRemainingTilesCount.ts +0 -11
  295. package/src/lib/getTotalRemainingTilesCount.ts +0 -11
  296. package/src/lib/groupResults.ts +0 -35
  297. package/src/lib/guessLocale.ts +0 -20
  298. package/src/lib/isUpperCase.ts +0 -5
  299. package/src/lib/resultMatchesCellFilter.ts +0 -21
  300. package/src/lib/sortGroupedResults.ts +0 -21
  301. package/src/lib/sortResults.ts +0 -41
  302. package/src/state/slices/cellFilterInitialState.ts +0 -5
  303. package/src/state/slices/dictionaryInitialState.ts +0 -15
  304. package/src/state/slices/index.ts +0 -16
  305. package/src/state/slices/rackInitialState.ts +0 -9
  306. package/src/state/slices/resultsInitialState.ts +0 -20
  307. package/src/state/slices/solveInitialState.ts +0 -21
  308. package/src/state/slices/verifyInitialState.ts +0 -19
  309. /package/.next/static/{47JHul8F9NSWCNSEuahuL → MhrqAqLI9_L8obb7tgjvi}/_ssgManifest.js +0 -0
@@ -0,0 +1,3 @@
1
+ import type { RootState } from '../types';
2
+
3
+ export const selectBoard = (state: RootState) => state.board;
@@ -1,10 +1,11 @@
1
- import { createSlice, PayloadAction } from '@reduxjs/toolkit';
1
+ import { createSlice, type PayloadAction } from '@reduxjs/toolkit';
2
2
  import { games } from '@scrabble-solver/configs';
3
3
  import { EMPTY_CELL } from '@scrabble-solver/constants';
4
- import { Board, Cell, Result, Tile } from '@scrabble-solver/types';
4
+ import { Board, Cell, type Result, Tile } from '@scrabble-solver/types';
5
5
 
6
- import { boardInitialState } from './boardInitialState';
7
- import { settingsSlice } from './settingsSlice';
6
+ import { settingsSlice } from '../settings';
7
+
8
+ import { boardInitialState } from './initialState';
8
9
 
9
10
  export const boardSlice = createSlice({
10
11
  initialState: boardInitialState,
@@ -0,0 +1,3 @@
1
+ import { type Board } from '@scrabble-solver/types';
2
+
3
+ export type BoardState = Board;
@@ -0,0 +1,4 @@
1
+ export * from './initialState';
2
+ export * from './selectors';
3
+ export * from './slice';
4
+ export * from './types';
@@ -0,0 +1,3 @@
1
+ import type { CellFiltersState } from './types';
2
+
3
+ export const cellFiltersInitialState: CellFiltersState = [];
@@ -0,0 +1,8 @@
1
+ import type { CellFilterType } from 'types';
2
+
3
+ export const toggleCellFilterState = (type: CellFilterType): CellFilterType | null => {
4
+ const chain: (CellFilterType | null)[] = ['include', 'exclude', null];
5
+ const index = chain.indexOf(type);
6
+ const nextIndex = (index + 1) % chain.length;
7
+ return chain[nextIndex];
8
+ };
@@ -0,0 +1,13 @@
1
+ import { createSelector } from '@reduxjs/toolkit';
2
+
3
+ import type { Point } from 'types';
4
+
5
+ import type { RootState } from '../types';
6
+
7
+ const selectPoint = (_: unknown, point: Point): Point => point;
8
+
9
+ export const selectCellFilters = (state: RootState) => state.cellFilters;
10
+
11
+ export const selectCellFilter = createSelector([selectCellFilters, selectPoint], (cellFilters, point) => {
12
+ return cellFilters.find((cell) => cell.x === point.x && cell.y === point.y);
13
+ });
@@ -1,12 +1,14 @@
1
- import { createSlice, PayloadAction } from '@reduxjs/toolkit';
1
+ import { createSlice, type PayloadAction } from '@reduxjs/toolkit';
2
+ import type { Cell } from '@scrabble-solver/types';
2
3
 
3
- import { CellFilterType, Point } from 'types';
4
+ import type { Point } from 'types';
4
5
 
5
- import { cellFilterInitialState } from './cellFilterInitialState';
6
+ import { cellFiltersInitialState } from './initialState';
7
+ import { toggleCellFilterState } from './lib';
6
8
 
7
- export const cellFilterSlice = createSlice({
8
- initialState: cellFilterInitialState,
9
- name: 'cellFilter',
9
+ export const cellFiltersSlice = createSlice({
10
+ initialState: cellFiltersInitialState,
11
+ name: 'cellFilters',
10
12
  reducers: {
11
13
  toggle: (state, action: PayloadAction<Point>) => {
12
14
  const { x, y } = action.payload;
@@ -37,13 +39,12 @@ export const cellFilterSlice = createSlice({
37
39
  return state.filter((point) => point.x !== x || point.y !== y);
38
40
  },
39
41
 
40
- reset: () => cellFilterInitialState,
42
+ removeCells: (state, action: PayloadAction<Cell[]>) => {
43
+ const cellsToRemove = action.payload;
44
+ return state.filter((entry) =>
45
+ cellsToRemove.every((cell) => cell.x !== entry.x || cell.y !== entry.y));
46
+ },
47
+
48
+ reset: () => cellFiltersInitialState,
41
49
  },
42
50
  });
43
-
44
- const toggleCellFilterState = (type: CellFilterType): CellFilterType | null => {
45
- const chain: (CellFilterType | null)[] = ['include', 'exclude', null];
46
- const index = chain.indexOf(type);
47
- const nextIndex = (index + 1) % chain.length;
48
- return chain[nextIndex];
49
- };
@@ -0,0 +1,3 @@
1
+ import type { CellFilter } from 'types';
2
+
3
+ export type CellFiltersState = CellFilter[];
@@ -0,0 +1,4 @@
1
+ export * from './initialState';
2
+ export * from './selectors';
3
+ export * from './slice';
4
+ export * from './types';
@@ -0,0 +1,8 @@
1
+ import { type DictionaryState } from './types';
2
+
3
+ export const dictionaryInitialState: DictionaryState = {
4
+ error: undefined,
5
+ input: '',
6
+ isLoading: false,
7
+ results: [],
8
+ };
@@ -0,0 +1,16 @@
1
+ import { createSelector } from '@reduxjs/toolkit';
2
+ import { isError } from '@scrabble-solver/types';
3
+
4
+ import type { RootState } from '../types';
5
+
6
+ export const selectDictionary = (state: RootState) => state.dictionary;
7
+
8
+ export const selectDictionaryInput = createSelector([selectDictionary], (dictionary) => dictionary.input);
9
+
10
+ export const selectDictionaryResults = createSelector([selectDictionary], (dictionary) => dictionary.results);
11
+
12
+ export const selectDictionaryIsLoading = createSelector([selectDictionary], (dictionary) => dictionary.isLoading);
13
+
14
+ export const selectDictionaryError = createSelector([selectDictionary], (dictionary) => {
15
+ return isError(dictionary.error) ? dictionary.error : undefined;
16
+ });
@@ -1,7 +1,7 @@
1
- import { createSlice, PayloadAction } from '@reduxjs/toolkit';
2
- import { WordDefinition } from '@scrabble-solver/types';
1
+ import { createSlice, type PayloadAction } from '@reduxjs/toolkit';
2
+ import { type WordDefinition } from '@scrabble-solver/types';
3
3
 
4
- import { dictionaryInitialState } from './dictionaryInitialState';
4
+ import { dictionaryInitialState } from './initialState';
5
5
 
6
6
  export const dictionarySlice = createSlice({
7
7
  initialState: dictionaryInitialState,
@@ -0,0 +1,8 @@
1
+ import { type WordDefinition } from '@scrabble-solver/types';
2
+
3
+ export interface DictionaryState {
4
+ error: unknown;
5
+ input: string;
6
+ isLoading: boolean;
7
+ results: WordDefinition[];
8
+ }
@@ -1,7 +1,14 @@
1
1
  export * from './actions';
2
- export { localStorage } from './localStorage';
2
+ export * from './board';
3
+ export * from './cellFilters';
4
+ export * from './dictionary';
5
+ export * from './localStorage';
6
+ export * from './rack';
7
+ export * from './results';
3
8
  export * from './selectors';
4
- export * from './slices';
5
- export { store } from './store';
6
- export { useTranslate } from './useTranslate';
7
- export { useTypedSelector } from './useTypedSelector';
9
+ export * from './settings';
10
+ export * from './solve';
11
+ export * from './store';
12
+ export * from './useTranslate';
13
+ export * from './useTypedSelector';
14
+ export * from './verify';
@@ -1,9 +1,8 @@
1
1
  import { BLANK } from '@scrabble-solver/constants';
2
- import { Board, Config } from '@scrabble-solver/types';
2
+ import { type Board, type Config } from '@scrabble-solver/types';
3
3
 
4
- import { RemainingTile } from 'types';
5
-
6
- import { createKeyComparator } from './createKeyComparator';
4
+ import { createKeyComparator } from 'lib';
5
+ import { type RemainingTile } from 'types';
7
6
 
8
7
  export const getRemainingTiles = (
9
8
  config: Config,
@@ -1,12 +1,13 @@
1
- import { Board, BoardJson, Game, Locale, ShowCoordinates } from '@scrabble-solver/types';
1
+ import { Board, type BoardJson, type Game, type Locale, type ShowCoordinates } from '@scrabble-solver/types';
2
2
  import store2 from 'store2';
3
3
 
4
- import { AutoGroupTiles, InputMode, Rack } from 'types';
4
+ import type { AutoGroupTiles, InputMode, Rack, RemoveCellFilters } from 'types';
5
5
 
6
6
  const AUTO_GROUP_TILES = 'auto-group-tiles';
7
7
  const BOARD = 'board';
8
8
  const GAME_ID = 'config-id';
9
9
  const INPUT_MODE = 'input-mode';
10
+ const REMOVE_CELL_FILTERS = 'remove-cell-filters';
10
11
  const LOCALE = 'locale';
11
12
  const RACK = 'rack';
12
13
  const SHOW_COORDINATES = 'show-coordinates';
@@ -71,4 +72,12 @@ export const localStorage = {
71
72
  setShowCoordinates(showCoordinates: ShowCoordinates | undefined): void {
72
73
  store.set(SHOW_COORDINATES, showCoordinates, true);
73
74
  },
75
+
76
+ getRemoveCellFilters(): RemoveCellFilters | undefined {
77
+ return store.get(REMOVE_CELL_FILTERS) as RemoveCellFilters | undefined;
78
+ },
79
+
80
+ setRemoveCellFilters(removeCellFilters: RemoveCellFilters | undefined): void {
81
+ store.set(REMOVE_CELL_FILTERS, removeCellFilters, true);
82
+ },
74
83
  };
@@ -0,0 +1,4 @@
1
+ export * from './initialState';
2
+ export * from './selectors';
3
+ export * from './slice';
4
+ export * from './types';
@@ -0,0 +1,12 @@
1
+ import { getConfig } from '@scrabble-solver/configs';
2
+
3
+ import { localStorage } from '../localStorage';
4
+ import { settingsInitialState } from '../settings';
5
+
6
+ import type { RackState } from './types';
7
+
8
+ const defaultConfig = getConfig(settingsInitialState.game, settingsInitialState.locale);
9
+
10
+ export const rackDefaultState: RackState = Array(defaultConfig.rackSize).fill(null);
11
+
12
+ export const rackInitialState: RackState = localStorage.getRack() ?? rackDefaultState;
@@ -0,0 +1,7 @@
1
+ import { createSelector } from '@reduxjs/toolkit';
2
+
3
+ import { type RootState } from '../types';
4
+
5
+ export const selectRack = (state: RootState) => state.rack;
6
+
7
+ export const selectCharacters = createSelector(selectRack, (rack) => rack.filter((tile) => tile !== null));
@@ -1,10 +1,10 @@
1
- import { createSlice, PayloadAction } from '@reduxjs/toolkit';
2
- import { Tile } from '@scrabble-solver/types';
1
+ import { createSlice, type PayloadAction } from '@reduxjs/toolkit';
2
+ import { type Tile } from '@scrabble-solver/types';
3
3
 
4
4
  import { arrayEquals, createNullMovingComparator, inverseDirection, zipCharactersAndTiles } from 'lib';
5
- import { AutoGroupTiles, Rack } from 'types';
5
+ import { type AutoGroupTiles, type Rack } from 'types';
6
6
 
7
- import { rackInitialState, rackDefaultState } from './rackInitialState';
7
+ import { rackDefaultState, rackInitialState } from './initialState';
8
8
 
9
9
  export const rackSlice = createSlice({
10
10
  initialState: rackInitialState,
@@ -0,0 +1,3 @@
1
+ import { type Rack } from 'types';
2
+
3
+ export type RackState = Rack;
@@ -0,0 +1,4 @@
1
+ export * from './initialState';
2
+ export * from './selectors';
3
+ export * from './slice';
4
+ export * from './types';
@@ -0,0 +1,13 @@
1
+ import { ResultColumnId, SortDirection } from 'types';
2
+
3
+ import type { ResultsState } from './types';
4
+
5
+ export const resultsInitialState: ResultsState = {
6
+ candidate: null,
7
+ query: '',
8
+ results: undefined,
9
+ sort: {
10
+ column: ResultColumnId.Points,
11
+ direction: SortDirection.Descending,
12
+ },
13
+ };
@@ -0,0 +1,96 @@
1
+ import { type Result, type ShowCoordinates } from '@scrabble-solver/types';
2
+
3
+ import { createKeyComparator, createRegExp, createStringComparator, getCoordinate, reverseComparator } from 'lib';
4
+ import { type CellFilter, type Comparator, type GroupedResults, ResultColumnId, type Sort, SortDirection } from 'types';
5
+
6
+ export const getCoordinates = (result: Result, showCoordinates: ShowCoordinates): string => {
7
+ if (showCoordinates === 'hidden') {
8
+ return '';
9
+ }
10
+
11
+ const firstCell = result.cells[0];
12
+ const x = getCoordinate(firstCell.x, showCoordinates === 'original' ? 'letter' : 'number');
13
+ const y = getCoordinate(firstCell.y, showCoordinates === 'original' ? 'number' : 'letter');
14
+
15
+ return result.isHorizontal() ? `${y}${x}` : `${x}${y}`;
16
+ };
17
+
18
+ export const groupResults = (
19
+ results: Result[] | undefined,
20
+ query: string,
21
+ cellFilters: CellFilter[],
22
+ ): GroupedResults | undefined => {
23
+ if (typeof results === 'undefined') {
24
+ return undefined;
25
+ }
26
+
27
+ const regExp = createRegExp(query);
28
+
29
+ const { matching, other } = results.reduce<GroupedResults>(
30
+ (groupedResults, result) => {
31
+ const matchesQuery = () => Boolean(result.word.match(regExp));
32
+
33
+ if (resultMatchesCellFilter(result, cellFilters) && matchesQuery()) {
34
+ groupedResults.matching.push(result);
35
+ } else {
36
+ groupedResults.other.push(result);
37
+ }
38
+
39
+ return groupedResults;
40
+ },
41
+ { matching: [], other: [] },
42
+ );
43
+
44
+ return { matching, other };
45
+ };
46
+
47
+ export const resultMatchesCellFilter = (result: Result, cellFilters: CellFilter[]): boolean => {
48
+ const excludeFilters = cellFilters.filter((filter) => filter.type === 'exclude');
49
+ const matchesExcludeFilters = excludeFilters.every(({ x, y }) => {
50
+ return result.cells.every((cell) => cell.x !== x || cell.y !== y);
51
+ });
52
+
53
+ if (!matchesExcludeFilters) {
54
+ return false;
55
+ }
56
+
57
+ const includeFilter = cellFilters.filter((filter) => filter.type === 'include');
58
+ const matchesIncludeFilters = includeFilter.every(({ x, y }) => {
59
+ return result.cells.some((cell) => cell.x === x && cell.y === y);
60
+ });
61
+
62
+ return matchesExcludeFilters && matchesIncludeFilters;
63
+ };
64
+
65
+ const comparators: Record<ResultColumnId, (locale: string, showCoordinates: ShowCoordinates) => Comparator<Result>> = {
66
+ [ResultColumnId.BlanksCount]: (locale: string) => createKeyComparator('blanksCount', locale),
67
+ [ResultColumnId.ConsonantsCount]: (locale: string) => createKeyComparator('consonantsCount', locale),
68
+ [ResultColumnId.Coordinates]: (locale: string, showCoordinates: ShowCoordinates) => (a, b) => {
69
+ const stringComparator = createStringComparator(locale);
70
+ const aValue = getCoordinates(a, showCoordinates);
71
+ const bValue = getCoordinates(b, showCoordinates);
72
+ return stringComparator(aValue, bValue);
73
+ },
74
+ [ResultColumnId.Points]: (locale: string) => createKeyComparator('points', locale),
75
+ [ResultColumnId.TilesCount]: (locale: string) => createKeyComparator('tilesCount', locale),
76
+ [ResultColumnId.VowelsCount]: (locale: string) => createKeyComparator('vowelsCount', locale),
77
+ [ResultColumnId.Word]: (locale: string) => createKeyComparator('word', locale),
78
+ [ResultColumnId.WordsCount]: (locale: string) => createKeyComparator('wordsCount', locale),
79
+ };
80
+
81
+ export const sortResults = (
82
+ results: Result[] | undefined,
83
+ sort: Sort,
84
+ locale: string,
85
+ showCoordinates: ShowCoordinates,
86
+ ): Result[] | undefined => {
87
+ if (typeof results === 'undefined') {
88
+ return undefined;
89
+ }
90
+
91
+ const createComparator = comparators[sort.column];
92
+ const comparator = createComparator(locale, showCoordinates);
93
+ const finalComparator = sort.direction === SortDirection.Descending ? reverseComparator(comparator) : comparator;
94
+ const sortedResults = [...results].sort(finalComparator);
95
+ return sortedResults;
96
+ };
@@ -0,0 +1,75 @@
1
+ import { createSelector } from '@reduxjs/toolkit';
2
+
3
+ import { createRegExp } from 'lib';
4
+
5
+ import { selectCellFilters } from '../cellFilters';
6
+ import { selectLocale, selectShowCoordinates } from '../settings';
7
+ import type { RootState } from '../types';
8
+
9
+ import { getCoordinates, groupResults, resultMatchesCellFilter, sortResults } from './lib';
10
+
11
+ const selectResultIndex = (_: unknown, index: number): number => index;
12
+
13
+ export const selectResults = (state: RootState) => state.results;
14
+
15
+ export const selectResultCandidate = createSelector([selectResults], (results) => results.candidate);
16
+
17
+ export const selectResultsQuery = createSelector([selectResults], (results) => results.query);
18
+
19
+ const selectResultsResults = createSelector([selectResults], (results) => results.results);
20
+
21
+ export const selectResultsSort = createSelector([selectResults], (results) => results.sort);
22
+
23
+ export const selectResultCandidateCells = createSelector(
24
+ [selectResultCandidate],
25
+ (resultCandidate) => resultCandidate?.cells ?? [],
26
+ );
27
+
28
+ export const selectResultCandidateTiles = createSelector(
29
+ [selectResultCandidate],
30
+ (resultCandidate) => resultCandidate?.tiles ?? [],
31
+ );
32
+
33
+ const selectSortedResults = createSelector(
34
+ [selectResultsResults, selectResultsSort, selectLocale, selectShowCoordinates],
35
+ sortResults,
36
+ );
37
+
38
+ const selectGroupedSortedResults = createSelector(
39
+ [selectSortedResults, selectResultsQuery, selectCellFilters],
40
+ groupResults,
41
+ );
42
+
43
+ export const selectProcessedResults = createSelector([selectGroupedSortedResults], (results) => {
44
+ return results ? [...results.matching, ...results.other] : undefined;
45
+ });
46
+
47
+ export const selectIsResultMatching = createSelector(
48
+ [selectProcessedResults, selectResultsQuery, selectCellFilters, selectResultIndex],
49
+ (results, query, cellFilters, index) => {
50
+ if (!results) {
51
+ return false;
52
+ }
53
+
54
+ const result = results[index];
55
+ const regExp = createRegExp(query);
56
+
57
+ if (!regExp.test(result.word)) {
58
+ return false;
59
+ }
60
+
61
+ return resultMatchesCellFilter(result, cellFilters);
62
+ },
63
+ );
64
+
65
+ export const selectResultCoordinates = createSelector(
66
+ [selectProcessedResults, selectShowCoordinates, selectResultIndex],
67
+ (results, showCoordinates, index) => {
68
+ if (!results) {
69
+ return '';
70
+ }
71
+
72
+ const result = results[index];
73
+ return getCoordinates(result, showCoordinates);
74
+ },
75
+ );
@@ -1,9 +1,9 @@
1
- import { createSlice, PayloadAction } from '@reduxjs/toolkit';
2
- import { Result } from '@scrabble-solver/types';
1
+ import { createSlice, type PayloadAction } from '@reduxjs/toolkit';
2
+ import { type Result } from '@scrabble-solver/types';
3
3
 
4
- import { ResultColumnId, SortDirection } from 'types';
4
+ import { type ResultColumnId, SortDirection } from 'types';
5
5
 
6
- import { resultsInitialState } from './resultsInitialState';
6
+ import { resultsInitialState } from './initialState';
7
7
 
8
8
  const toggleDirection = (direction: SortDirection): SortDirection => {
9
9
  return direction === SortDirection.Ascending ? SortDirection.Descending : SortDirection.Ascending;
@@ -0,0 +1,10 @@
1
+ import { type Result } from '@scrabble-solver/types';
2
+
3
+ import type { Sort } from 'types';
4
+
5
+ export interface ResultsState {
6
+ candidate: Result | null;
7
+ query: string;
8
+ results: Result[] | undefined;
9
+ sort: Sort;
10
+ }
@@ -3,9 +3,9 @@
3
3
  /* eslint-disable @typescript-eslint/no-unsafe-call */
4
4
  /* eslint-disable @typescript-eslint/no-unsafe-member-access */
5
5
 
6
- import { PayloadAction } from '@reduxjs/toolkit';
6
+ import { type PayloadAction } from '@reduxjs/toolkit';
7
7
  import { hasConfig, languages } from '@scrabble-solver/configs';
8
- import { Board, Locale, Result } from '@scrabble-solver/types';
8
+ import { Board, type Locale, type Result } from '@scrabble-solver/types';
9
9
  import { call, delay, put, select, takeEvery, takeLatest } from 'redux-saga/effects';
10
10
 
11
11
  import { LOCALE_FEATURES } from 'i18n';
@@ -13,27 +13,21 @@ import { memoize } from 'lib';
13
13
  import { findWordDefinitions, solve, verify, visit } from 'sdk';
14
14
 
15
15
  import { initialize, reset } from './actions';
16
+ import { boardSlice, selectBoard } from './board';
17
+ import { cellFiltersSlice, selectCellFilter } from './cellFilters';
18
+ import { dictionarySlice, selectDictionary } from './dictionary';
19
+ import { rackSlice, selectCharacters, selectRack } from './rack';
20
+ import { resultsSlice } from './results';
16
21
  import {
17
- selectBoard,
18
- selectCellFilter,
19
- selectCharacters,
20
22
  selectConfig,
21
- selectDictionary,
22
23
  selectGame,
23
24
  selectLocale,
24
25
  selectLocaleAutoGroupTiles,
25
- selectRack,
26
- } from './selectors';
27
- import {
28
- boardSlice,
29
- cellFilterSlice,
30
- dictionarySlice,
31
- rackSlice,
32
- resultsSlice,
26
+ selectRemoveCellFilters,
33
27
  settingsSlice,
34
- solveSlice,
35
- verifySlice,
36
- } from './slices';
28
+ } from './settings';
29
+ import { solveSlice } from './solve';
30
+ import { verifySlice } from './verify';
37
31
 
38
32
  const SUBMIT_DELAY = 150;
39
33
 
@@ -62,7 +56,7 @@ function* onCellValueChange({ payload }: PayloadAction<{ value: string; x: numbe
62
56
  const filter = yield select((state) => selectCellFilter(state, payload));
63
57
 
64
58
  if (filter) {
65
- yield put(cellFilterSlice.actions.cancel(payload));
59
+ yield put(cellFiltersSlice.actions.cancel(payload));
66
60
  }
67
61
 
68
62
  yield put(resultsSlice.actions.changeResultCandidate(null));
@@ -75,8 +69,13 @@ function* onRackValueChange(): AnyGenerator {
75
69
 
76
70
  function* onApplyResult({ payload: result }: PayloadAction<Result>): AnyGenerator {
77
71
  const autoGroupTiles = yield select(selectLocaleAutoGroupTiles);
72
+ const removeCellFilters = yield select(selectRemoveCellFilters);
78
73
  yield put(boardSlice.actions.applyResult(result));
79
- yield put(cellFilterSlice.actions.reset());
74
+ if (removeCellFilters === 'never') {
75
+ yield put(cellFiltersSlice.actions.removeCells(result.cells));
76
+ } else {
77
+ yield put(cellFiltersSlice.actions.reset());
78
+ }
80
79
  yield put(rackSlice.actions.removeTiles(result.tiles));
81
80
  yield put(rackSlice.actions.groupTiles(autoGroupTiles));
82
81
  yield put(verifySlice.actions.submit());
@@ -92,8 +91,8 @@ function* onGameChange(): AnyGenerator {
92
91
  }
93
92
 
94
93
  yield put(resultsSlice.actions.reset());
94
+ yield* resetRack();
95
95
  yield put(verifySlice.actions.submit());
96
- yield* ensureProperTilesCount();
97
96
  }
98
97
 
99
98
  function* onDictionarySubmit(): AnyGenerator {
@@ -126,7 +125,7 @@ function* onInitialize(): AnyGenerator {
126
125
  yield call(visit);
127
126
 
128
127
  if (!board.isEmpty()) {
129
- yield* ensureProperTilesCount();
128
+ yield* resetRack();
130
129
  yield put(verifySlice.actions.submit());
131
130
  }
132
131
  }
@@ -135,7 +134,7 @@ function* onReset(): AnyGenerator {
135
134
  const config = yield select(selectConfig);
136
135
 
137
136
  yield put(boardSlice.actions.init(Board.create(config.boardWidth, config.boardHeight)));
138
- yield put(cellFilterSlice.actions.reset());
137
+ yield put(cellFiltersSlice.actions.reset());
139
138
  yield put(dictionarySlice.actions.reset());
140
139
  yield put(rackSlice.actions.reset());
141
140
  yield put(resultsSlice.actions.reset());
@@ -223,7 +222,7 @@ function* onVerify(): AnyGenerator {
223
222
  }
224
223
  }
225
224
 
226
- function* ensureProperTilesCount(): AnyGenerator {
225
+ function* resetRack(): AnyGenerator {
227
226
  const { config } = yield select(selectConfig);
228
227
  const rack = yield select(selectRack);
229
228