@scrabble-solver/scrabble-solver 2.16.7 → 2.18.0
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/.next/BUILD_ID +1 -1
- package/.next/build-manifest.json +22 -20
- package/.next/cache/.previewinfo +1 -1
- package/.next/cache/.rscinfo +1 -1
- package/.next/cache/.tsbuildinfo +1 -1
- package/.next/cache/webpack/client-production/0.pack +0 -0
- package/.next/cache/webpack/client-production/index.pack +0 -0
- package/.next/cache/webpack/client-production/index.pack.old +0 -0
- package/.next/cache/webpack/edge-server-production/index.pack +0 -0
- package/.next/cache/webpack/edge-server-production/index.pack.old +0 -0
- package/.next/cache/webpack/server-production/0.pack +0 -0
- package/.next/cache/webpack/server-production/index.pack +0 -0
- package/.next/cache/webpack/server-production/index.pack.old +0 -0
- package/.next/diagnostics/framework.json +1 -1
- package/.next/dynamic-css-manifest.json +8 -1
- package/.next/next-minimal-server.js.nft.json +1 -1
- package/.next/next-server.js.nft.json +1 -1
- package/.next/prerender-manifest.json +4 -4
- package/.next/react-loadable-manifest.json +176 -1
- package/.next/required-server-files.js +3 -2
- package/.next/required-server-files.json +3 -2
- package/.next/routes-manifest.json +1 -1
- package/.next/server/chunks/30.js +1 -1
- package/.next/server/chunks/309.js +1 -0
- package/.next/server/chunks/316.js +1 -0
- package/.next/server/chunks/344.js +1 -0
- package/.next/server/chunks/371.js +1 -0
- package/.next/server/chunks/392.js +1 -1
- package/.next/server/chunks/465.js +1 -0
- package/.next/server/chunks/546.js +1 -0
- package/.next/server/chunks/623.js +1 -0
- package/.next/server/chunks/624.js +1 -0
- package/.next/server/chunks/641.js +1 -0
- package/.next/server/chunks/671.js +1 -1
- package/.next/server/chunks/692.js +1 -0
- package/.next/server/chunks/705.js +1 -0
- package/.next/server/chunks/712.js +1 -1
- package/.next/server/chunks/846.js +1 -0
- package/.next/server/chunks/853.js +15 -0
- package/.next/server/chunks/886.js +1 -0
- package/.next/server/chunks/916.js +1 -0
- package/.next/server/chunks/953.js +1 -0
- package/.next/server/chunks/961.js +1 -0
- package/.next/server/chunks/968.js +1 -0
- package/.next/server/dynamic-css-manifest.js +1 -1
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/.next/server/pages/404.html +28 -1
- package/.next/server/pages/500.html +28 -1
- package/.next/server/pages/_app.js +1 -1
- package/.next/server/pages/_app.js.nft.json +1 -1
- package/.next/server/pages/_document.js +28 -1
- package/.next/server/pages/_document.js.nft.json +1 -1
- package/.next/server/pages/_error.js +28 -1
- package/.next/server/pages/_error.js.nft.json +1 -1
- package/.next/server/pages/api/dictionary/[locale]/[word].js +1 -1
- package/.next/server/pages/api/dictionary/[locale]/[word].js.nft.json +1 -1
- package/.next/server/pages/api/dictionary/[locale].js +1 -1
- package/.next/server/pages/api/dictionary/[locale].js.nft.json +1 -1
- package/.next/server/pages/api/solve.js +1 -1
- package/.next/server/pages/api/solve.js.nft.json +1 -1
- package/.next/server/pages/api/verify.js +1 -1
- package/.next/server/pages/api/verify.js.nft.json +1 -1
- package/.next/server/pages/api/visit.js.nft.json +1 -1
- package/.next/server/pages/index.html +28 -1
- package/.next/server/pages/index.js +30 -1
- package/.next/server/pages/index.js.nft.json +1 -1
- package/.next/server/pages/index.json +1 -1
- package/.next/server/pages/not-found.html +28 -1
- package/.next/server/pages/not-found.js.nft.json +1 -1
- package/.next/server/pages-manifest.json +1 -1
- package/.next/server/webpack-api-runtime.js +1 -1
- package/.next/server/webpack-runtime.js +1 -1
- package/.next/static/FL82obwzFHRzvV6z76I-U/_buildManifest.js +1 -0
- package/.next/static/chunks/101-829ab8a8d310e7a0.js +3 -0
- package/.next/static/chunks/106.e414e88694add27f.js +1 -0
- package/.next/static/chunks/127.125d896a19061d71.js +1 -0
- package/.next/static/chunks/18.8b91e137d239e15c.js +1 -0
- package/.next/static/chunks/277.6785dfaf22fdb0b7.js +1 -0
- package/.next/static/chunks/307.67f52f13e3d60d4e.js +1 -0
- package/.next/static/chunks/341.79535ba85bf1986f.js +1 -0
- package/.next/static/chunks/363.01eed3233f92b6a4.js +1 -0
- package/.next/static/chunks/422.1559ae6756912ca5.js +1 -0
- package/.next/static/chunks/541.dcd9e8b804f88da5.js +1 -0
- package/.next/static/chunks/583.7a46807be14c377e.js +1 -0
- package/.next/static/chunks/631.98d42abad0a60af6.js +1 -0
- package/.next/static/chunks/663.d2c9ca36b26695b8.js +1 -0
- package/.next/static/chunks/711.ba7b8f900dfd40d0.js +1 -0
- package/.next/static/chunks/744.b06c9a3de4a1d877.js +1 -0
- package/.next/static/chunks/820.97b82f751ca49579.js +1 -0
- package/.next/static/chunks/865.a9b563a45848a83c.js +1 -0
- package/.next/static/chunks/912.ff8037ba9d4db5c8.js +1 -0
- package/.next/static/chunks/939.19b33a3d3c351d73.js +1 -0
- package/.next/static/chunks/990.61296e5870b91f11.js +1 -0
- package/.next/static/chunks/framework-626cbd48ac5aca57.js +1 -0
- package/.next/static/chunks/main-b92e08f3fc96dd8d.js +5 -0
- package/.next/static/chunks/pages/_app-ba5d628e81a15461.js +1 -0
- package/.next/static/chunks/pages/{_error-afeda7c5231b6c3c.js → _error-f0290a98eb3ed0ec.js} +1 -1
- package/.next/static/chunks/pages/index-5f9fa6ac3cd3d97b.js +1 -0
- package/.next/static/chunks/pages/not-found-23d61fd3ed791cad.js +1 -0
- package/.next/static/chunks/webpack-e34c3ae12275e978.js +1 -0
- package/.next/static/css/2d03f0da2c678dfc.css +1 -0
- package/.next/static/css/7b6724d2df70f48f.css +1 -0
- package/.next/static/css/abc2425d3ba607bc.css +1 -0
- package/.next/static/css/b736be3bd02b5d99.css +1 -0
- package/.next/static/css/b74cd7de82dabcdd.css +1 -0
- package/.next/static/css/c403ce739a9c0d83.css +1 -0
- package/.next/static/css/ca311e6c030663d4.css +1 -0
- package/.next/static/css/dee878eb64099d92.css +1 -0
- package/.next/static/css/fbfdce70731d0d2b.css +1 -0
- package/.next/trace +21 -21
- package/.next/trace-build +1 -1
- package/empty-module.js +2 -0
- package/next.config.js +13 -8
- package/package.json +35 -16
- package/public/llms.txt +11 -0
- package/public/robots.txt +4 -0
- package/scripts/generate-scss-types.ts +112 -0
- package/src/@types/scss.d.ts +6 -9
- package/src/@types/svg.d.ts +1 -1
- package/src/components/Alert/Alert.module.scss.d.ts +13 -0
- package/src/components/Alert/Alert.tsx +4 -1
- package/src/components/Badge/Badge.module.scss.d.ts +6 -0
- package/src/components/Board/Board.module.scss +10 -0
- package/src/components/Board/Board.module.scss.d.ts +17 -0
- package/src/components/Board/Board.tsx +14 -26
- package/src/components/Board/BoardPure.tsx +54 -49
- package/src/components/Board/components/Actions/Actions.module.scss +18 -18
- package/src/components/Board/components/Actions/Actions.module.scss.d.ts +7 -0
- package/src/components/Board/components/Actions/Actions.tsx +6 -3
- package/src/components/Board/components/Actions/lib.ts +3 -1
- package/src/components/Board/components/Cell/Cell.module.scss.d.ts +13 -0
- package/src/components/Board/components/Cell/Cell.tsx +32 -34
- package/src/components/Board/components/InputPrompt/InputPrompt.module.scss.d.ts +10 -0
- package/src/components/Board/components/InputPrompt/InputPrompt.tsx +1 -1
- package/src/components/Board/components/ToggleDirectionButton/ToggleDirectionButton.module.scss.d.ts +8 -0
- package/src/components/Board/components/ToggleDirectionButton/ToggleDirectionButton.tsx +2 -2
- package/src/components/Board/getBoardBackground.ts +175 -0
- package/src/components/Board/hooks/index.ts +0 -1
- package/src/components/Board/hooks/useBoardStyle.ts +10 -20
- package/src/components/Board/hooks/useFloatingActions.ts +3 -6
- package/src/components/Board/hooks/useGrid.ts +23 -9
- package/src/components/Board/selectors.ts +1 -1
- package/src/components/Button/Button.module.scss.d.ts +12 -0
- package/src/components/Dictionary/Dictionary.module.scss +8 -22
- package/src/components/Dictionary/Dictionary.module.scss.d.ts +14 -0
- package/src/components/Dictionary/Dictionary.tsx +1 -10
- package/src/components/DictionaryInput/DictionaryInput.module.scss.d.ts +7 -0
- package/src/components/DictionaryInput/DictionaryInput.tsx +1 -1
- package/src/components/EmptyState/EmptyState.module.scss.d.ts +7 -0
- package/src/components/EmptyState/EmptyState.tsx +1 -1
- package/src/components/IconButton/IconButton.module.scss.d.ts +8 -0
- package/src/components/Key/Key.module.scss.d.ts +6 -0
- package/src/components/Keys/Arrows/Arrows.module.scss.d.ts +11 -0
- package/src/components/Keys/Arrows/Arrows.tsx +4 -1
- package/src/components/Keys/index.tsx +1 -1
- package/src/components/Loading/Loading.module.scss.d.ts +9 -0
- package/src/components/Loading/Loading.tsx +1 -1
- package/src/components/Logo/Logo.tsx +4 -9
- package/src/components/Logo/LogoBlueprint.tsx +1 -0
- package/src/components/Modal/Modal.module.scss.d.ts +15 -0
- package/src/components/Modal/Modal.tsx +16 -3
- package/src/components/Modal/components/Section/Section.module.scss.d.ts +7 -0
- package/src/components/NavButtons/NavButtons.module.scss +20 -0
- package/src/components/NavButtons/NavButtons.module.scss.d.ts +13 -0
- package/src/components/NavButtons/NavButtons.tsx +29 -45
- package/src/components/NotFound/NotFound.module.scss.d.ts +9 -0
- package/src/components/NotFound/NotFound.tsx +1 -1
- package/src/components/PlainTiles/PlainTiles.module.scss.d.ts +11 -0
- package/src/components/PlainTiles/PlainTiles.tsx +0 -1
- package/src/components/PlainTiles/Tile.tsx +3 -4
- package/src/components/PlainTiles/lib.ts +4 -19
- package/src/components/PlainTiles/types.ts +0 -1
- package/src/components/Progress/Progress.module.scss +7 -0
- package/src/components/Progress/Progress.module.scss.d.ts +6 -0
- package/src/components/Progress/Progress.tsx +2 -11
- package/src/components/Rack/Rack.module.scss +3 -0
- package/src/components/Rack/Rack.module.scss.d.ts +10 -0
- package/src/components/Rack/Rack.tsx +8 -11
- package/src/components/Rack/components/InputPrompt/InputPrompt.module.scss +2 -0
- package/src/components/Rack/components/InputPrompt/InputPrompt.module.scss.d.ts +7 -0
- package/src/components/Rack/components/InputPrompt/InputPrompt.tsx +1 -13
- package/src/components/Rack/components/InputPrompt/lib.ts +1 -1
- package/src/components/Rack/components/RackTile/RackTile.module.scss.d.ts +6 -0
- package/src/components/Rack/components/RackTile/RackTile.tsx +5 -4
- package/src/components/Rack/selectors.ts +1 -1
- package/src/components/Radio/Radio.module.scss.d.ts +12 -0
- package/src/components/Results/Cell.tsx +3 -12
- package/src/components/Results/Header.tsx +9 -18
- package/src/components/Results/HeaderButton.tsx +4 -5
- package/src/components/Results/Result.tsx +4 -15
- package/src/components/Results/Results.module.scss +4 -7
- package/src/components/Results/Results.module.scss.d.ts +27 -0
- package/src/components/Results/Results.tsx +14 -5
- package/src/components/Results/SolveButton.tsx +1 -1
- package/src/components/Results/types.ts +3 -3
- package/src/components/ResultsInput/ResultsInput.module.scss.d.ts +7 -0
- package/src/components/ResultsInput/ResultsInput.tsx +1 -1
- package/src/components/Solver/Solver.module.scss +6 -5
- package/src/components/Solver/Solver.module.scss.d.ts +20 -0
- package/src/components/Solver/Solver.tsx +23 -24
- package/src/components/Solver/components/InsertButton/InsertButton.module.scss.d.ts +7 -0
- package/src/components/Solver/components/InsertButton/InsertButton.tsx +1 -1
- package/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.module.scss.d.ts +18 -0
- package/src/components/Solver/components/ResultCandidatePicker/ResultCandidatePicker.tsx +3 -1
- package/src/components/Spinner/Spinner.module.scss.d.ts +10 -0
- package/src/components/Tile/Tile.module.scss +3 -0
- package/src/components/Tile/Tile.module.scss.d.ts +19 -0
- package/src/components/Tile/Tile.tsx +5 -14
- package/src/components/Tile/TilePure.tsx +2 -12
- package/src/components/Tooltip/Tooltip.module.scss.d.ts +7 -0
- package/src/components/Tooltip/Tooltip.tsx +14 -3
- package/src/components/Tooltip/useTooltip.ts +2 -1
- package/src/hooks/useColumns.ts +4 -7
- package/src/hooks/useDeferredRender.ts +20 -0
- package/src/hooks/useDirection.ts +4 -3
- package/src/hooks/useLanguage.ts +2 -2
- package/src/hooks/useLocalStorage.ts +32 -8
- package/src/hooks/useMedia.ts +26 -26
- package/src/i18n/constants.ts +2 -15
- package/src/i18n/i18n.ts +18 -17
- package/src/i18n/languages/english.json +0 -1
- package/src/i18n/languages/french.json +0 -1
- package/src/i18n/languages/german.json +0 -1
- package/src/i18n/languages/persian.json +0 -1
- package/src/i18n/languages/polish.json +0 -1
- package/src/i18n/languages/romanian.json +0 -1
- package/src/i18n/languages/spanish.json +0 -1
- package/src/i18n/languages/turkish.json +0 -1
- package/src/i18n/localeIcons.ts +24 -0
- package/src/lib/localeTransliterate.test.ts +5 -0
- package/src/lib/localeTransliterate.ts +26 -6
- package/src/lib/waitForIdleOrFirstIntent.ts +33 -0
- package/src/modals/DictionaryModal/DictionaryModal.module.scss.d.ts +8 -0
- package/src/modals/DictionaryModal/DictionaryModal.tsx +3 -1
- package/src/modals/KeyMapModal/KeyMapModal.tsx +3 -1
- package/src/modals/KeyMapModal/components/Mapping/Mapping.module.scss.d.ts +11 -0
- package/src/modals/MenuModal/MenuModal.module.scss +5 -0
- package/src/modals/MenuModal/MenuModal.module.scss.d.ts +10 -0
- package/src/modals/MenuModal/MenuModal.tsx +16 -5
- package/src/modals/RemainingTilesModal/RemainingTilesModal.module.scss +4 -1
- package/src/modals/RemainingTilesModal/RemainingTilesModal.module.scss.d.ts +9 -0
- package/src/modals/RemainingTilesModal/RemainingTilesModal.tsx +4 -6
- package/src/modals/RemainingTilesModal/components/Character/Character.module.scss +1 -0
- package/src/modals/RemainingTilesModal/components/Character/Character.module.scss.d.ts +12 -0
- package/src/modals/RemainingTilesModal/components/Character/Character.tsx +5 -12
- package/src/modals/ResultsModal/ResultsModal.module.scss.d.ts +8 -0
- package/src/modals/ResultsModal/ResultsModal.tsx +8 -4
- package/src/modals/SettingsModal/SettingsModal.tsx +2 -2
- package/src/modals/SettingsModal/components/AutoGroupTilesSetting/AutoGroupTilesSetting.module.scss.d.ts +7 -0
- package/src/modals/SettingsModal/components/AutoGroupTilesSetting/AutoGroupTilesSetting.tsx +1 -1
- package/src/modals/SettingsModal/components/ConfigSetting/ConfigSetting.module.scss.d.ts +7 -0
- package/src/modals/SettingsModal/components/ConfigSetting/ConfigSetting.tsx +1 -1
- package/src/modals/SettingsModal/components/HighlightUnreachableCellsSetting/HighlightUnreachableCellsSetting.module.scss.d.ts +7 -0
- package/src/modals/SettingsModal/components/HighlightUnreachableCellsSetting/HighlightUnreachableCellsSetting.tsx +1 -1
- package/src/modals/SettingsModal/components/InputModeSetting/InputModeSetting.module.scss.d.ts +7 -0
- package/src/modals/SettingsModal/components/InputModeSetting/InputModeSetting.tsx +1 -1
- package/src/modals/SettingsModal/components/LocaleSetting/LocaleSetting.module.scss.d.ts +8 -0
- package/src/modals/SettingsModal/components/LocaleSetting/LocaleSetting.tsx +24 -21
- package/src/modals/SettingsModal/components/RemoveCellFiltersSetting/RemoveCellFiltersSetting.module.scss.d.ts +7 -0
- package/src/modals/SettingsModal/components/RemoveCellFiltersSetting/RemoveCellFiltersSetting.tsx +1 -1
- package/src/modals/SettingsModal/components/ShowCoordinatesSetting/ShowCoordinatesSetting.module.scss.d.ts +7 -0
- package/src/modals/SettingsModal/components/ShowCoordinatesSetting/ShowCoordinatesSetting.tsx +1 -5
- package/src/modals/WordsModal/WordsModal.module.scss.d.ts +11 -0
- package/src/modals/WordsModal/WordsModal.tsx +4 -2
- package/src/modals/preload.ts +24 -0
- package/src/pages/_app.tsx +5 -8
- package/src/pages/_document.tsx +91 -5
- package/src/pages/api/dictionary/[locale]/[word].ts +2 -2
- package/src/pages/api/dictionary/[locale]/index.ts +80 -3
- package/src/pages/api/solve.ts +3 -3
- package/src/pages/api/verify.ts +12 -3
- package/src/pages/index.module.scss +1 -0
- package/src/pages/index.module.scss.d.ts +11 -0
- package/src/pages/index.tsx +98 -38
- package/src/pages/not-found.tsx +1 -1
- package/src/parameters/index.ts +24 -93
- package/src/sdk/solve.ts +13 -7
- package/src/sdk/verify.ts +12 -6
- package/src/service-worker/appShell.test.ts +86 -0
- package/src/service-worker/appShell.ts +39 -0
- package/src/service-worker/index.ts +4 -5
- package/src/serviceWorkerManager.ts +5 -19
- package/src/solver-worker/client.test.ts +151 -0
- package/src/solver-worker/client.ts +106 -0
- package/src/solver-worker/dictionaries/constants.ts +9 -0
- package/src/solver-worker/dictionaries/getDictionary.ts +21 -0
- package/src/solver-worker/dictionaries/index.ts +2 -0
- package/src/solver-worker/dictionaries/revalidateDictionary.test.ts +199 -0
- package/src/solver-worker/dictionaries/revalidateDictionary.ts +86 -0
- package/src/solver-worker/dictionaries/revalidatedAt.ts +43 -0
- package/src/solver-worker/getGaddag.test.ts +109 -0
- package/src/solver-worker/getGaddag.ts +62 -0
- package/src/solver-worker/index.ts +1 -0
- package/src/solver-worker/index.worker.ts +124 -0
- package/src/solver-worker/messages.ts +20 -0
- package/src/state/actions.ts +1 -1
- package/src/state/app/index.ts +4 -0
- package/src/state/app/initialState.ts +6 -0
- package/src/state/app/selectors.ts +5 -0
- package/src/state/app/slice.ts +20 -0
- package/src/state/app/types.ts +4 -0
- package/src/state/board/initialState.ts +1 -2
- package/src/state/i18n/index.ts +4 -0
- package/src/state/i18n/initialState.ts +5 -0
- package/src/state/i18n/selectors.ts +3 -0
- package/src/state/i18n/slice.ts +16 -0
- package/src/state/i18n/types.ts +7 -0
- package/src/state/index.ts +3 -0
- package/src/state/lib.ts +1 -1
- package/src/state/localStorage.ts +86 -29
- package/src/state/rack/initialState.ts +1 -2
- package/src/state/rack/slice.ts +4 -1
- package/src/state/results/lib.ts +5 -5
- package/src/state/results/selectors.ts +1 -1
- package/src/state/sagas.ts +139 -13
- package/src/state/selectors.ts +1 -1
- package/src/state/settings/initialState.ts +7 -10
- package/src/state/settings/selectors.ts +7 -2
- package/src/state/settings/slice.ts +5 -2
- package/src/state/store.ts +4 -0
- package/src/state/types.ts +4 -0
- package/src/state/useTypedStore.ts +6 -0
- package/src/styles/_tokens.scss +1 -0
- package/src/styles/global.scss +9 -0
- package/src/styles/variables.module.scss +0 -21
- package/src/styles/variables.module.scss.d.ts +27 -0
- package/src/styles/variables.scss +46 -2
- package/src/types/api.ts +1 -2
- package/src/types/index.ts +1 -2
- package/tsconfig.json +4 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/.next/server/chunks/318.js +0 -3
- package/.next/server/chunks/791.js +0 -15
- package/.next/static/chunks/framework-3cf15b8041c59dd3.js +0 -1
- package/.next/static/chunks/main-852475ca502b4e49.js +0 -5
- package/.next/static/chunks/pages/_app-a5bbe02710f13d05.js +0 -7
- package/.next/static/chunks/pages/index-ec0f6aeeaf7be227.js +0 -1
- package/.next/static/chunks/pages/not-found-9e076427bad69d90.js +0 -1
- package/.next/static/chunks/webpack-1a7a166b34ed8d24.js +0 -1
- package/.next/static/css/0af35adb4bab56f0.css +0 -2
- package/.next/static/css/a78e04fe95452380.css +0 -1
- package/.next/static/vYpMmuC_yEurZGcyGVnLf/_buildManifest.js +0 -1
- package/src/app-layout/AppLayoutContext.tsx +0 -25
- package/src/app-layout/index.ts +0 -1
- package/src/app-layout/useAppLayoutValue.ts +0 -112
- package/src/components/Board/hooks/useBackgroundImage.tsx +0 -279
- package/src/components/index.ts +0 -26
- package/src/hooks/index.ts +0 -12
- package/src/hooks/useOnWindowResize.ts +0 -11
- package/src/hooks/useViewportSize.ts +0 -17
- package/src/i18n/index.ts +0 -2
- package/src/icons/Flag.svg +0 -4
- package/src/icons/Star.svg +0 -4
- package/src/icons/index.ts +0 -49
- package/src/lib/getTileSizes.ts +0 -13
- package/src/lib/index.ts +0 -24
- package/src/modals/index.ts +0 -7
- package/src/sdk/getDictionary.ts +0 -9
- package/src/service-worker/average.ts +0 -7
- package/src/service-worker/dictionaries/constants.ts +0 -5
- package/src/service-worker/dictionaries/getDictionary.ts +0 -20
- package/src/service-worker/dictionaries/index.ts +0 -2
- package/src/service-worker/dictionaries/revalidateDictionary.ts +0 -33
- package/src/service-worker/getTrie.ts +0 -24
- package/src/service-worker/routeSolveRequests.ts +0 -91
- package/src/service-worker/routeVerifyRequests.ts +0 -37
- /package/.next/static/{vYpMmuC_yEurZGcyGVnLf → FL82obwzFHRzvV6z76I-U}/_ssgManifest.js +0 -0
- /package/src/{service-worker → solver-worker}/dictionaries/expirationManager.ts +0 -0
- /package/src/{service-worker → solver-worker}/dictionaries/getDictionaryUrl.ts +0 -0
package/src/pages/index.tsx
CHANGED
|
@@ -1,31 +1,53 @@
|
|
|
1
|
-
/* eslint-disable max-statements */
|
|
1
|
+
/* eslint-disable max-lines, max-statements */
|
|
2
2
|
|
|
3
|
+
import { games } from '@scrabble-solver/configs';
|
|
3
4
|
import { isObject } from '@scrabble-solver/types';
|
|
4
5
|
import { execSync } from 'child_process';
|
|
5
6
|
import fs from 'fs';
|
|
7
|
+
import dynamic from 'next/dynamic';
|
|
6
8
|
import path from 'path';
|
|
7
|
-
import { type FunctionComponent, useCallback, useState } from 'react';
|
|
8
|
-
import ReactModal from 'react-modal';
|
|
9
|
+
import { type FunctionComponent, useCallback, useEffect, useState } from 'react';
|
|
9
10
|
import { useDispatch } from 'react-redux';
|
|
10
11
|
|
|
11
|
-
import { Logo
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
WordsModal,
|
|
22
|
-
} from '@/modals';
|
|
12
|
+
import { Logo } from '@/components/Logo';
|
|
13
|
+
import { NavButtons } from '@/components/NavButtons';
|
|
14
|
+
import { Solver } from '@/components/Solver';
|
|
15
|
+
import { useDirection } from '@/hooks/useDirection';
|
|
16
|
+
import { useEffectOnce } from '@/hooks/useEffectOnce';
|
|
17
|
+
import { useLanguage } from '@/hooks/useLanguage';
|
|
18
|
+
import { useLocalStorage } from '@/hooks/useLocalStorage';
|
|
19
|
+
import { LOCALE_FEATURES } from '@/i18n/constants';
|
|
20
|
+
import { schedulePreloadModals } from '@/modals/preload';
|
|
21
|
+
import { CONFIG_PENDING_CLASS } from '@/parameters';
|
|
23
22
|
import { registerServiceWorker } from '@/serviceWorkerManager';
|
|
24
|
-
import { initialize, reset, selectConfig, selectLocale, useTypedSelector } from '@/state';
|
|
23
|
+
import { initialize, reset, selectConfig, selectIsHydrated, selectLocale, useTypedSelector } from '@/state';
|
|
25
24
|
|
|
26
25
|
import styles from './index.module.scss';
|
|
27
26
|
|
|
28
|
-
|
|
27
|
+
const DictionaryModal = dynamic(() => import('@/modals/DictionaryModal').then((module) => module.DictionaryModal), {
|
|
28
|
+
ssr: false,
|
|
29
|
+
});
|
|
30
|
+
const KeyMapModal = dynamic(() => import('@/modals/KeyMapModal').then((module) => module.KeyMapModal), {
|
|
31
|
+
ssr: false,
|
|
32
|
+
});
|
|
33
|
+
const MenuModal = dynamic(() => import('@/modals/MenuModal').then((module) => module.MenuModal), {
|
|
34
|
+
ssr: false,
|
|
35
|
+
});
|
|
36
|
+
const RemainingTilesModal = dynamic(
|
|
37
|
+
() => import('@/modals/RemainingTilesModal').then((module) => module.RemainingTilesModal),
|
|
38
|
+
{
|
|
39
|
+
ssr: false,
|
|
40
|
+
},
|
|
41
|
+
);
|
|
42
|
+
const ResultsModal = dynamic(() => import('@/modals/ResultsModal').then((module) => module.ResultsModal), {
|
|
43
|
+
ssr: false,
|
|
44
|
+
});
|
|
45
|
+
const SettingsModal = dynamic(() => import('@/modals/SettingsModal').then((module) => module.SettingsModal), {
|
|
46
|
+
ssr: false,
|
|
47
|
+
});
|
|
48
|
+
const WordsModal = dynamic(() => import('@/modals/WordsModal').then((module) => module.WordsModal), {
|
|
49
|
+
ssr: false,
|
|
50
|
+
});
|
|
29
51
|
|
|
30
52
|
interface Props {
|
|
31
53
|
version: string;
|
|
@@ -37,7 +59,7 @@ const Index: FunctionComponent<Props> = ({ version }) => {
|
|
|
37
59
|
const dispatch = useDispatch();
|
|
38
60
|
const config = useTypedSelector(selectConfig);
|
|
39
61
|
const locale = useTypedSelector(selectLocale);
|
|
40
|
-
const
|
|
62
|
+
const isHydrated = useTypedSelector(selectIsHydrated);
|
|
41
63
|
const [modals, setModals] = useState<Record<Modal, boolean>>({
|
|
42
64
|
dictionary: false,
|
|
43
65
|
keyMap: false,
|
|
@@ -47,9 +69,26 @@ const Index: FunctionComponent<Props> = ({ version }) => {
|
|
|
47
69
|
settings: false,
|
|
48
70
|
words: false,
|
|
49
71
|
});
|
|
72
|
+
const [mountedModals, setMountedModals] = useState<Partial<Record<Modal, boolean>>>({});
|
|
50
73
|
|
|
51
74
|
const patchModals = useCallback((patch: Partial<Record<Modal, boolean>>) => {
|
|
52
75
|
setModals((current) => ({ ...current, ...patch }));
|
|
76
|
+
setMountedModals((current) => {
|
|
77
|
+
const newModals = Object.keys(patch) as Modal[];
|
|
78
|
+
const newOpenedModals = newModals.filter((modal) => patch[modal] && !current[modal]);
|
|
79
|
+
|
|
80
|
+
if (newOpenedModals.length === 0) {
|
|
81
|
+
return current;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const mounted = { ...current };
|
|
85
|
+
|
|
86
|
+
for (const modal of newOpenedModals) {
|
|
87
|
+
mounted[modal] = true;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return mounted;
|
|
91
|
+
});
|
|
53
92
|
}, []);
|
|
54
93
|
|
|
55
94
|
const handleClear = useCallback(() => dispatch(reset()), [dispatch]);
|
|
@@ -73,20 +112,39 @@ const Index: FunctionComponent<Props> = ({ version }) => {
|
|
|
73
112
|
useLanguage(locale);
|
|
74
113
|
useLocalStorage();
|
|
75
114
|
|
|
115
|
+
useEffect(() => {
|
|
116
|
+
const root = document.documentElement;
|
|
117
|
+
const variables = [
|
|
118
|
+
{ name: '--board-cols', value: config.boardWidth, defaultValue: games.scrabble.boardWidth },
|
|
119
|
+
{ name: '--board-rows', value: config.boardHeight, defaultValue: games.scrabble.boardHeight },
|
|
120
|
+
{ name: '--rack-size', value: config.rackSize, defaultValue: games.scrabble.rackSize },
|
|
121
|
+
];
|
|
122
|
+
|
|
123
|
+
for (const { name, value, defaultValue } of variables) {
|
|
124
|
+
const current = root.style.getPropertyValue(name) || String(defaultValue);
|
|
125
|
+
|
|
126
|
+
if (current !== String(value)) {
|
|
127
|
+
root.style.setProperty(name, String(value));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}, [config.boardHeight, config.boardWidth, config.rackSize]);
|
|
131
|
+
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
if (isHydrated) {
|
|
134
|
+
document.documentElement.classList.remove(CONFIG_PENDING_CLASS);
|
|
135
|
+
}
|
|
136
|
+
}, [isHydrated]);
|
|
137
|
+
|
|
76
138
|
useEffectOnce(() => {
|
|
77
139
|
if (process.env.NODE_ENV === 'production') {
|
|
78
140
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
79
141
|
registerServiceWorker();
|
|
80
142
|
}
|
|
81
143
|
|
|
82
|
-
|
|
83
|
-
|
|
144
|
+
dispatch(initialize({ version }));
|
|
145
|
+
schedulePreloadModals();
|
|
84
146
|
});
|
|
85
147
|
|
|
86
|
-
if (!isClient) {
|
|
87
|
-
return null;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
148
|
return (
|
|
91
149
|
<>
|
|
92
150
|
<nav className={styles.nav}>
|
|
@@ -112,28 +170,30 @@ const Index: FunctionComponent<Props> = ({ version }) => {
|
|
|
112
170
|
<Solver className={styles.solver} onShowResults={handleShowResults} />
|
|
113
171
|
</main>
|
|
114
172
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
173
|
+
{mountedModals.menu && (
|
|
174
|
+
<MenuModal
|
|
175
|
+
isOpen={modals.menu}
|
|
176
|
+
onClose={handleCloseMenu}
|
|
177
|
+
onShowDictionary={handleShowDictionary}
|
|
178
|
+
onShowRemainingTiles={handleShowRemainingTiles}
|
|
179
|
+
onShowSettings={handleShowSettings}
|
|
180
|
+
onShowWords={handleShowWords}
|
|
181
|
+
/>
|
|
182
|
+
)}
|
|
123
183
|
|
|
124
|
-
<SettingsModal isOpen={modals.settings} onClose={handleCloseSettings} />
|
|
184
|
+
{mountedModals.settings && <SettingsModal isOpen={modals.settings} onClose={handleCloseSettings} />}
|
|
125
185
|
|
|
126
|
-
<KeyMapModal isOpen={modals.keyMap} onClose={handleCloseKeyMap} />
|
|
186
|
+
{mountedModals.keyMap && <KeyMapModal isOpen={modals.keyMap} onClose={handleCloseKeyMap} />}
|
|
127
187
|
|
|
128
|
-
<WordsModal isOpen={modals.words} onClose={handleCloseWords} />
|
|
188
|
+
{mountedModals.words && <WordsModal isOpen={modals.words} onClose={handleCloseWords} />}
|
|
129
189
|
|
|
130
|
-
{config.supportsRemainingTiles && (
|
|
190
|
+
{config.supportsRemainingTiles && mountedModals.remainingTiles && (
|
|
131
191
|
<RemainingTilesModal isOpen={modals.remainingTiles} onClose={handleCloseRemainingTiles} />
|
|
132
192
|
)}
|
|
133
193
|
|
|
134
|
-
<ResultsModal isOpen={modals.results} onClose={handleCloseResults} />
|
|
194
|
+
{mountedModals.results && <ResultsModal isOpen={modals.results} onClose={handleCloseResults} />}
|
|
135
195
|
|
|
136
|
-
<DictionaryModal isOpen={modals.dictionary} onClose={handleCloseDictionary} />
|
|
196
|
+
{mountedModals.dictionary && <DictionaryModal isOpen={modals.dictionary} onClose={handleCloseDictionary} />}
|
|
137
197
|
</>
|
|
138
198
|
);
|
|
139
199
|
};
|
package/src/pages/not-found.tsx
CHANGED
package/src/parameters/index.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import variables from '@/styles/variables.module.scss';
|
|
2
|
-
import { ResultColumnId } from '@/types';
|
|
3
2
|
|
|
4
3
|
export const BREAKPOINTS = {
|
|
5
4
|
xs: Number(variables.breakpointXs),
|
|
@@ -9,97 +8,52 @@ export const BREAKPOINTS = {
|
|
|
9
8
|
xl: Number(variables.breakpointXl),
|
|
10
9
|
};
|
|
11
10
|
|
|
12
|
-
export const TRANSITION = 'var(--transition)';
|
|
13
|
-
|
|
14
11
|
export const GITHUB_PROJECT_URL = 'https://github.com/kamilmielnik/scrabble-solver';
|
|
15
12
|
|
|
16
|
-
|
|
13
|
+
// Must match the class name in global.scss
|
|
14
|
+
export const CONFIG_PENDING_CLASS = 'config-pending';
|
|
15
|
+
|
|
17
16
|
export const TRANSITION_DURATION_LONG = Number(variables.transitionDurationLong);
|
|
18
17
|
|
|
19
|
-
export const COLOR_BLUE =
|
|
20
|
-
export const COLOR_GREEN =
|
|
21
|
-
export const COLOR_RED =
|
|
22
|
-
export const COLOR_YELLOW =
|
|
23
|
-
|
|
24
|
-
export const COLOR_BONUS_CHARACTER_1 = variables.colorYellowLight;
|
|
25
|
-
export const COLOR_BONUS_CHARACTER_2 = variables.colorGreenLight;
|
|
26
|
-
export const COLOR_BONUS_CHARACTER_3 = variables.colorBlueLight;
|
|
27
|
-
export const COLOR_BONUS_CHARACTER_5 = variables.colorRedLight;
|
|
28
|
-
export const COLOR_BONUS_CHARACTER_MULTIPLIER_2 = variables.colorBonusCharacterMultiplier2;
|
|
29
|
-
export const COLOR_BONUS_CHARACTER_MULTIPLIER_3 = variables.colorBonusCharacterMultiplier3;
|
|
30
|
-
export const COLOR_BONUS_CHARACTER_MULTIPLIER_4 = variables.colorBonusCharacterMultiplier4;
|
|
31
|
-
export const COLOR_BONUS_START = variables.colorVioletLight;
|
|
32
|
-
export const COLOR_BONUS_WORD_MULTIPLIER_2 = variables.colorBonusWordMultiplier2;
|
|
33
|
-
export const COLOR_BONUS_WORD_MULTIPLIER_3 = variables.colorBonusWordMultiplier3;
|
|
34
|
-
export const COLOR_BONUS_WORD_MULTIPLIER_4 = variables.colorBonusWordMultiplier4;
|
|
35
|
-
export const COLOR_FILTERED = variables.colorForegroundSecondary;
|
|
18
|
+
export const COLOR_BLUE = 'var(--color--blue)';
|
|
19
|
+
export const COLOR_GREEN = 'var(--color--green)';
|
|
20
|
+
export const COLOR_RED = 'var(--color--red)';
|
|
21
|
+
export const COLOR_YELLOW = 'var(--color--yellow)';
|
|
22
|
+
|
|
36
23
|
export const COLOR_BACKGROUND = variables.colorBackground;
|
|
24
|
+
export const COLOR_BONUS_START = variables.colorVioletLight;
|
|
37
25
|
|
|
38
26
|
export const COLOR_BONUS_CHARACTER: Record<number, string> = {
|
|
39
|
-
1:
|
|
40
|
-
2:
|
|
41
|
-
3:
|
|
42
|
-
5:
|
|
27
|
+
1: variables.colorYellowLight,
|
|
28
|
+
2: variables.colorGreenLight,
|
|
29
|
+
3: variables.colorBlueLight,
|
|
30
|
+
5: variables.colorRedLight,
|
|
43
31
|
};
|
|
44
32
|
|
|
45
33
|
export const COLOR_BONUS_CHARACTER_MULTIPLIER: Record<number, string> = {
|
|
46
|
-
2:
|
|
47
|
-
3:
|
|
48
|
-
4:
|
|
34
|
+
2: variables.colorBonusCharacterMultiplier2,
|
|
35
|
+
3: variables.colorBonusCharacterMultiplier3,
|
|
36
|
+
4: variables.colorBonusCharacterMultiplier4,
|
|
49
37
|
};
|
|
50
38
|
|
|
51
39
|
export const COLOR_BONUS_WORD: Record<number, string> = {
|
|
52
|
-
2:
|
|
53
|
-
3:
|
|
54
|
-
4:
|
|
40
|
+
2: variables.colorBonusWordMultiplier2,
|
|
41
|
+
3: variables.colorBonusWordMultiplier3,
|
|
42
|
+
4: variables.colorBonusWordMultiplier4,
|
|
55
43
|
};
|
|
56
44
|
|
|
57
|
-
export const SPACING_XS = Number(variables.spacingXs);
|
|
58
|
-
export const SPACING_S = Number(variables.spacingS);
|
|
59
|
-
export const SPACING_M = Number(variables.spacingM);
|
|
60
|
-
export const SPACING_L = Number(variables.spacingL);
|
|
61
|
-
export const SPACING_XL = Number(variables.spacingXl);
|
|
62
|
-
|
|
63
|
-
export const COMPONENTS_SPACING = SPACING_XL;
|
|
64
|
-
export const COMPONENTS_SPACING_SMALL = SPACING_L;
|
|
65
|
-
|
|
66
|
-
export const RACK_TILE_SIZE_MAX = 80;
|
|
67
|
-
|
|
68
45
|
export const BOARD_CELL_ACTIONS_OFFSET = 3;
|
|
69
|
-
export const BOARD_CELL_BORDER_WIDTH = 1;
|
|
70
|
-
export const BOARD_TILE_FONT_SIZE_MIN = 14;
|
|
71
|
-
export const BOARD_TILE_FONT_SIZE_POINTS_MIN = 10;
|
|
72
|
-
export const BOARD_TILE_SIZE_MAX = RACK_TILE_SIZE_MAX;
|
|
73
46
|
|
|
74
|
-
export const BORDER_COLOR = variables.borderColor;
|
|
75
47
|
export const BORDER_COLOR_LIGHT = variables.borderColorLight;
|
|
76
|
-
export const BORDER_RADIUS = Number(variables.borderRadius);
|
|
77
48
|
export const BORDER_WIDTH = Number(variables.borderWidth);
|
|
78
49
|
|
|
79
|
-
export const
|
|
80
|
-
export const BUTTON_PADDING_VERTICAL = SPACING_M;
|
|
81
|
-
export const BUTTON_HEIGHT = BUTTON_ICON_SIZE + 2 * BUTTON_PADDING_VERTICAL + 2 * BORDER_WIDTH;
|
|
82
|
-
|
|
83
|
-
export const DICTIONARY_HEIGHT = Number(variables.dictionaryHeight);
|
|
84
|
-
export const DICTIONARY_HEIGHT_MOBILE = Number(variables.dictionaryHeightMobile);
|
|
85
|
-
|
|
86
|
-
export const LOGO_ASPECT_RATIO = 682 / 166;
|
|
87
|
-
export const LOGO_HEIGHT = Number(variables.logoHeight);
|
|
88
|
-
export const LOGO_HEIGHT_SMALL = Number(variables.logoHeightSmall);
|
|
50
|
+
export const LOGO_HEIGHT = 166;
|
|
89
51
|
export const LOGO_SRC = '/logo.svg';
|
|
52
|
+
export const LOGO_WIDTH = 682;
|
|
90
53
|
|
|
91
|
-
export const
|
|
92
|
-
export const MODAL_HEADER_HEIGHT = 45;
|
|
93
|
-
|
|
94
|
-
export const NAV_PADDING = SPACING_L;
|
|
95
|
-
|
|
96
|
-
export const TILE_SIZE = 80;
|
|
97
|
-
|
|
98
|
-
export const TOOLTIP_DURATION = TRANSITION_DURATION;
|
|
54
|
+
export const TOOLTIP_DURATION = Number(variables.transitionDuration);
|
|
99
55
|
|
|
100
56
|
export const PLAIN_TILES_COLOR_DEFAULT = COLOR_GREEN;
|
|
101
|
-
export const PLAIN_TILES_PADDING_HORIZONTAL = 0;
|
|
102
|
-
export const PLAIN_TILES_PADDING_VERTICAL = 0;
|
|
103
57
|
export const PLAIN_TILES_POINTS_COLORS: Record<number, string> = {
|
|
104
58
|
1: COLOR_YELLOW,
|
|
105
59
|
2: COLOR_GREEN,
|
|
@@ -113,31 +67,8 @@ export const PLAIN_TILES_POINTS_COLORS: Record<number, string> = {
|
|
|
113
67
|
10: COLOR_RED,
|
|
114
68
|
};
|
|
115
69
|
export const PLAIN_TILES_TILE_MARGIN = 6;
|
|
116
|
-
export const PLAIN_TILES_TILE_MAX_ROTATE = 0;
|
|
117
|
-
export const PLAIN_TILES_TILE_MAX_SCATTER = 0;
|
|
118
70
|
export const PLAIN_TILES_TILE_SIZE = 80;
|
|
119
|
-
export const PLAIN_TILES_VERSION_TILE_COLOR = COLOR_GREEN;
|
|
120
|
-
export const PLAIN_TILES_VERSION_TILE_SIZE = TILE_SIZE;
|
|
121
|
-
|
|
122
|
-
export const PROGRESS_COLOR_VALUE = 'var(--color--violet--light)';
|
|
123
|
-
export const PROGRESS_COLOR_BACKGROUND = 'var(--color--inactive)';
|
|
124
|
-
|
|
125
|
-
export const REMAINING_TILES_TILE_SIZE = 50;
|
|
126
|
-
|
|
127
|
-
export const RESULTS_COLUMN_WIDTH: Record<ResultColumnId, number | undefined> = {
|
|
128
|
-
[ResultColumnId.BlanksCount]: 55,
|
|
129
|
-
[ResultColumnId.ConsonantsCount]: 55,
|
|
130
|
-
[ResultColumnId.Coordinates]: 55,
|
|
131
|
-
[ResultColumnId.Points]: 80,
|
|
132
|
-
[ResultColumnId.TilesCount]: 55,
|
|
133
|
-
[ResultColumnId.VowelsCount]: 55,
|
|
134
|
-
[ResultColumnId.Word]: undefined,
|
|
135
|
-
[ResultColumnId.WordsCount]: 55,
|
|
136
|
-
};
|
|
137
|
-
export const RESULTS_ITEM_HEIGHT = Number(variables.resultsItemHeight);
|
|
138
71
|
|
|
139
|
-
export const
|
|
140
|
-
|
|
141
|
-
export const SOLVER_COLUMN_WIDTH = Number(variables.solverColumnWidth);
|
|
72
|
+
export const RESULTS_ITEM_HEIGHT = Number(variables.resultsItemHeight);
|
|
142
73
|
|
|
143
|
-
export const
|
|
74
|
+
export const RESULTS_OVERSCAN_COUNT = 10;
|
package/src/sdk/solve.ts
CHANGED
|
@@ -1,19 +1,25 @@
|
|
|
1
|
-
import { type BoardJson, type Locale, Result, type ResultJson } from '@scrabble-solver/types';
|
|
1
|
+
import { Board, type BoardJson, type Game, type Locale, Result, type ResultJson } from '@scrabble-solver/types';
|
|
2
|
+
|
|
3
|
+
import { solveLocally } from '@/solver-worker';
|
|
2
4
|
|
|
3
5
|
import { fetchJson } from './fetchJson';
|
|
4
6
|
|
|
5
7
|
interface Payload {
|
|
6
8
|
board: BoardJson;
|
|
7
9
|
characters: string[];
|
|
8
|
-
game:
|
|
10
|
+
game: Game;
|
|
9
11
|
locale: Locale;
|
|
10
12
|
}
|
|
11
13
|
|
|
12
14
|
export const solve = async ({ board, characters, game, locale }: Payload): Promise<Result[]> => {
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
const payload = { board, characters, game, locale };
|
|
16
|
+
const json =
|
|
17
|
+
(await solveLocally(payload)) ??
|
|
18
|
+
(await fetchJson<ResultJson[]>('/api/solve', {
|
|
19
|
+
method: 'POST',
|
|
20
|
+
body: JSON.stringify(payload),
|
|
21
|
+
}));
|
|
17
22
|
|
|
18
|
-
|
|
23
|
+
const decodedBoard = Board.fromJson(board);
|
|
24
|
+
return json.map((result) => Result.fromJson(result, decodedBoard));
|
|
19
25
|
};
|
package/src/sdk/verify.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { type BoardJson, type Locale } from '@scrabble-solver/types';
|
|
1
|
+
import { type BoardJson, type Game, type Locale } from '@scrabble-solver/types';
|
|
2
|
+
|
|
3
|
+
import { verifyLocally } from '@/solver-worker';
|
|
2
4
|
|
|
3
5
|
import { fetchJson } from './fetchJson';
|
|
4
6
|
|
|
5
7
|
interface Payload {
|
|
6
8
|
board: BoardJson;
|
|
7
|
-
game:
|
|
9
|
+
game: Game;
|
|
8
10
|
locale: Locale;
|
|
9
11
|
}
|
|
10
12
|
|
|
@@ -14,8 +16,12 @@ interface Response {
|
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
export const verify = async ({ board, game, locale }: Payload): Promise<Response> => {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
const payload = { board, game, locale };
|
|
20
|
+
return (
|
|
21
|
+
(await verifyLocally(payload)) ??
|
|
22
|
+
fetchJson<Response>('/api/verify', {
|
|
23
|
+
method: 'POST',
|
|
24
|
+
body: JSON.stringify(payload),
|
|
25
|
+
})
|
|
26
|
+
);
|
|
21
27
|
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { afterAll, beforeEach, describe, expect, it } from 'bun:test';
|
|
2
|
+
|
|
3
|
+
import { cacheAppShell, respondToNavigation } from './appShell';
|
|
4
|
+
|
|
5
|
+
const entries = new Map<string, Response>();
|
|
6
|
+
|
|
7
|
+
let respond: () => Promise<Response> = () => Promise.resolve(html('shell'));
|
|
8
|
+
|
|
9
|
+
const globals = { caches: globalThis.caches, fetch: globalThis.fetch };
|
|
10
|
+
|
|
11
|
+
Object.assign(globalThis, {
|
|
12
|
+
caches: createCacheStorage(entries),
|
|
13
|
+
fetch: () => respond(),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterAll(() => {
|
|
17
|
+
Object.assign(globalThis, globals);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe('respondToNavigation', () => {
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
entries.clear();
|
|
23
|
+
respond = () => Promise.resolve(html('shell'));
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('serves the network response while online', async () => {
|
|
27
|
+
await cacheAppShell();
|
|
28
|
+
respond = () => Promise.resolve(html('deployed shell'));
|
|
29
|
+
const response = await respondToNavigation({ request: navigation('/') });
|
|
30
|
+
expect(await response.text()).toEqual('deployed shell');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('serves the shell cached on install when the network is gone', async () => {
|
|
34
|
+
await cacheAppShell();
|
|
35
|
+
respond = () => Promise.reject(new Error('offline'));
|
|
36
|
+
const response = await respondToNavigation({ request: navigation('/') });
|
|
37
|
+
expect(await response.text()).toEqual('shell');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('serves the shell for any navigated path', async () => {
|
|
41
|
+
await cacheAppShell();
|
|
42
|
+
respond = () => Promise.reject(new Error('offline'));
|
|
43
|
+
const response = await respondToNavigation({ request: navigation('/not-found') });
|
|
44
|
+
expect(await response.text()).toEqual('shell');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The cached shell links to the chunks precached next to it, so a navigation
|
|
49
|
+
* must not replace it with HTML from a build this worker did not precache.
|
|
50
|
+
*/
|
|
51
|
+
it('keeps the installed shell when a newer one is served from the network', async () => {
|
|
52
|
+
await cacheAppShell();
|
|
53
|
+
respond = () => Promise.resolve(html('deployed shell'));
|
|
54
|
+
await respondToNavigation({ request: navigation('/') });
|
|
55
|
+
|
|
56
|
+
respond = () => Promise.reject(new Error('offline'));
|
|
57
|
+
const response = await respondToNavigation({ request: navigation('/') });
|
|
58
|
+
expect(await response.text()).toEqual('shell');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('rethrows when there is nothing to fall back on', async () => {
|
|
62
|
+
respond = () => Promise.reject(new Error('offline'));
|
|
63
|
+
const error = await respondToNavigation({ request: navigation('/') }).catch((reason: Error) => reason);
|
|
64
|
+
expect(error).toEqual(new Error('offline'));
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
function navigation(url: string): Request {
|
|
69
|
+
return new Request(new URL(url, 'https://scrabble-solver.org'));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function html(body: string): Response {
|
|
73
|
+
return new Response(body, { headers: { 'Content-Type': 'text/html; charset=utf-8' } });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function createCacheStorage(cacheEntries: Map<string, Response>): CacheStorage {
|
|
77
|
+
return {
|
|
78
|
+
open: (name: string) =>
|
|
79
|
+
Promise.resolve({
|
|
80
|
+
add: async (url: string) => {
|
|
81
|
+
cacheEntries.set(`${name} ${url}`, await globalThis.fetch(url));
|
|
82
|
+
},
|
|
83
|
+
match: (url: string) => Promise.resolve(cacheEntries.get(`${name} ${url}`)?.clone()),
|
|
84
|
+
}),
|
|
85
|
+
} as unknown as CacheStorage;
|
|
86
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { NavigationRoute, registerRoute } from 'workbox-routing';
|
|
2
|
+
|
|
3
|
+
const APP_SHELL_CACHE = 'app-shell';
|
|
4
|
+
const APP_SHELL_URL = '/';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Caching the shell on install instead of on navigation pins it to the same build
|
|
8
|
+
* as the chunks precached alongside it, and covers the first visit, whose
|
|
9
|
+
* navigation happens before this worker controls the page.
|
|
10
|
+
*/
|
|
11
|
+
export async function cacheAppShell(): Promise<void> {
|
|
12
|
+
const cache = await caches.open(APP_SHELL_CACHE);
|
|
13
|
+
await cache.add(APP_SHELL_URL);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function routeNavigations(): void {
|
|
17
|
+
registerRoute(new NavigationRoute(respondToNavigation, { denylist: [/^\/api\//] }));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A deploy has to reach online users on their next navigation (see the revalidation
|
|
22
|
+
* headers in next.config.js), so the shell only stands in when the network is gone.
|
|
23
|
+
* Solving keeps working from there, off the dictionary the solver worker holds in
|
|
24
|
+
* the Cache API.
|
|
25
|
+
*/
|
|
26
|
+
export async function respondToNavigation({ request }: { request: Request }): Promise<Response> {
|
|
27
|
+
try {
|
|
28
|
+
return await fetch(request);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
const cache = await caches.open(APP_SHELL_CACHE);
|
|
31
|
+
const appShell = await cache.match(APP_SHELL_URL);
|
|
32
|
+
|
|
33
|
+
if (!appShell) {
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return appShell;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
import { cleanupOutdatedCaches, precacheAndRoute } from 'workbox-precaching';
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import { routeVerifyRequests } from './routeVerifyRequests';
|
|
5
|
+
import { cacheAppShell, routeNavigations } from './appShell';
|
|
7
6
|
|
|
8
7
|
declare const self: ServiceWorkerGlobalScope;
|
|
9
8
|
|
|
10
|
-
self.addEventListener('install', () => {
|
|
9
|
+
self.addEventListener('install', (event) => {
|
|
10
|
+
event.waitUntil(cacheAppShell());
|
|
11
11
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
12
12
|
self.skipWaiting();
|
|
13
13
|
});
|
|
@@ -19,5 +19,4 @@ self.addEventListener('activate', () => {
|
|
|
19
19
|
|
|
20
20
|
cleanupOutdatedCaches();
|
|
21
21
|
precacheAndRoute(self.__WB_MANIFEST);
|
|
22
|
-
|
|
23
|
-
routeVerifyRequests();
|
|
22
|
+
routeNavigations();
|
|
@@ -1,26 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
let serviceWorker: Workbox | null = null;
|
|
1
|
+
import { waitForIdle } from '@/lib/waitForIdleOrFirstIntent';
|
|
4
2
|
|
|
5
3
|
export const registerServiceWorker = async () => {
|
|
6
4
|
if (!globalThis.navigator || !('serviceWorker' in globalThis.navigator)) {
|
|
7
5
|
return;
|
|
8
6
|
}
|
|
9
7
|
|
|
10
|
-
|
|
11
|
-
await
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export const getServiceWorker = async (): Promise<Workbox | null> => {
|
|
15
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
16
|
-
return null;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
if (serviceWorker) {
|
|
20
|
-
return serviceWorker;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
await registerServiceWorker();
|
|
24
|
-
|
|
25
|
-
return serviceWorker;
|
|
8
|
+
// Registration waits for load+idle so the install's precache downloads never compete with page resources
|
|
9
|
+
await waitForIdle();
|
|
10
|
+
const { Workbox } = await import('workbox-window');
|
|
11
|
+
await new Workbox('/service-worker.js').register();
|
|
26
12
|
};
|