@scrabble-solver/scrabble-solver 2.18.3 → 2.18.5
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 +19 -19
- 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 +2 -2
- 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 +1 -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 +6 -6
- package/.next/routes-manifest.json +1 -1
- package/.next/server/chunks/30.js +1 -1
- package/.next/server/chunks/546.js +1 -1
- package/.next/server/chunks/612.js +5 -0
- package/.next/server/chunks/712.js +1 -1
- package/.next/server/chunks/715.js +1 -0
- package/.next/server/chunks/853.js +2 -2
- package/.next/server/chunks/961.js +1 -1
- 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 +2 -2
- package/.next/server/pages/500.html +2 -2
- package/.next/server/pages/_app.js.nft.json +1 -1
- package/.next/server/pages/_document.js +2 -2
- package/.next/server/pages/_document.js.nft.json +1 -1
- package/.next/server/pages/_error.js +1 -1
- package/.next/server/pages/_error.js.nft.json +1 -1
- package/.next/server/pages/api/dictionary/[locale]/[word].js +2 -2
- 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 +1 -1
- package/.next/server/pages/api/visit.js.nft.json +1 -1
- package/.next/server/pages/index.html +2 -2
- package/.next/server/pages/index.js +1 -1
- package/.next/server/pages/index.js.nft.json +1 -1
- package/.next/server/pages/index.json +1 -1
- package/.next/server/pages/index.meta +1 -0
- package/.next/server/pages/not-found.html +2 -2
- package/.next/server/route-cache/PAGES/8a5edab282632443219e051e4ade2d1d5bbc671c781051bf1437897cbdfea0f1/$/index.html +29 -0
- package/.next/server/route-cache/PAGES/8a5edab282632443219e051e4ade2d1d5bbc671c781051bf1437897cbdfea0f1/$/index.json +1 -0
- package/.next/server/route-cache/PAGES/8a5edab282632443219e051e4ade2d1d5bbc671c781051bf1437897cbdfea0f1/$/index.meta +1 -0
- package/.next/static/{8WnIUTe-yJvPD6stltvYI → YHx536pklAqlc9UGAXCj8}/_buildManifest.js +1 -1
- package/.next/static/chunks/101-fee295c7587c0836.js +3 -0
- package/.next/static/chunks/{256.6a20b23a766cfebd.js → 256.2a4fa7f278e0a97d.js} +1 -1
- package/.next/static/chunks/693.d295da7c7b873986.js +1 -0
- package/.next/static/chunks/827.09f660ca0a951d12.js +1 -0
- package/.next/static/chunks/980.dd35e807b18063c1.js +1 -0
- package/.next/static/chunks/framework-988fd6f19d2744e8.js +1 -0
- package/.next/static/chunks/main-d397eb8af33c5163.js +5 -0
- package/.next/static/chunks/pages/_app-0c68f165d6de8507.js +1 -0
- package/.next/static/chunks/pages/index-ea21d96a88f25000.js +1 -0
- package/.next/static/chunks/{webpack-d7138f1ef6b8c9d6.js → webpack-31879a8fb6dcffd0.js} +1 -1
- package/.next/static/css/{92f85ce358a0a468.css → 4dbcacc1122ac6b9.css} +1 -1
- package/.next/static/css/8b8087461767360a.css +1 -0
- package/.next/static/css/{65bcc9440e067113.css → 98051ed25076b896.css} +1 -1
- package/.next/trace +22 -22
- package/.next/trace-build +1 -1
- package/package.json +19 -19
- package/src/api/BadRequestError.ts +6 -0
- package/src/api/index.ts +2 -1
- package/src/api/withApiLog.ts +85 -0
- package/src/components/Button/Button.module.scss +1 -1
- package/src/components/Solver/Solver.module.scss +4 -4
- package/src/components/Solver/Solver.tsx +2 -2
- package/src/hooks/useColumns.ts +5 -7
- package/src/modals/ResultsModal/ResultsModal.tsx +2 -2
- package/src/modals/WordsModal/WordsModal.module.scss +1 -1
- package/src/modals/WordsModal/WordsModal.tsx +2 -2
- package/src/modals/WordsModal/components/WordsTable/WordsTable.tsx +2 -2
- package/src/pages/api/dictionary/[locale]/[word].ts +22 -30
- package/src/pages/api/dictionary/[locale]/index.ts +36 -42
- package/src/pages/api/solve.ts +32 -42
- package/src/pages/api/verify.ts +35 -43
- package/src/pages/api/visit.ts +38 -13
- package/src/pages/index.tsx +2 -2
- package/src/parameters/index.ts +0 -2
- package/src/sdk/solve.ts +3 -9
- package/src/sdk/verify.ts +2 -10
- package/src/sdk/visit.ts +8 -3
- package/src/state/sagas.ts +7 -1
- package/src/styles/_tokens.scss +0 -2
- package/src/styles/mixins.scss +0 -5
- package/src/styles/variables.module.scss +0 -1
- package/src/styles/variables.module.scss.d.ts +0 -1
- package/src/styles/variables.scss +1 -1
- package/src/types/api.ts +7 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/.next/server/chunks/392.js +0 -1
- package/.next/static/chunks/101-829ab8a8d310e7a0.js +0 -3
- package/.next/static/chunks/693.677c88e0eb1340b5.js +0 -1
- package/.next/static/chunks/827.c81b92b383a748c9.js +0 -1
- package/.next/static/chunks/980.7ce17464fc56648a.js +0 -1
- package/.next/static/chunks/framework-626cbd48ac5aca57.js +0 -1
- package/.next/static/chunks/main-7e52da35a4beea0b.js +0 -5
- package/.next/static/chunks/pages/_app-e3fe880e24c024ab.js +0 -1
- package/.next/static/chunks/pages/index-b073a1686818dd26.js +0 -1
- package/.next/static/css/a691348b788f8bc0.css +0 -1
- package/src/api/getServerLoggingData.ts +0 -17
- package/src/hooks/useIsCompactLayout.ts +0 -14
- /package/.next/static/{8WnIUTe-yJvPD6stltvYI → YHx536pklAqlc9UGAXCj8}/_ssgManifest.js +0 -0
package/src/pages/api/solve.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getConfig, hasConfig } from '@scrabble-solver/configs';
|
|
2
2
|
import { BLANK } from '@scrabble-solver/constants';
|
|
3
3
|
import { dictionaries } from '@scrabble-solver/dictionaries';
|
|
4
|
-
import {
|
|
4
|
+
import { logEvent } from '@scrabble-solver/logger';
|
|
5
5
|
import { solve as solveScrabble } from '@scrabble-solver/solver';
|
|
6
6
|
import {
|
|
7
7
|
Board,
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
} from '@scrabble-solver/types';
|
|
16
16
|
import { type NextApiRequest, type NextApiResponse } from 'next';
|
|
17
17
|
|
|
18
|
-
import {
|
|
18
|
+
import { type ApiContext, BadRequestError, isBoardValid, isCharacterValid, withApiLog } from '@/api';
|
|
19
19
|
import { isStringArray } from '@/lib/isStringArray';
|
|
20
20
|
|
|
21
21
|
interface RequestData {
|
|
@@ -26,71 +26,63 @@ interface RequestData {
|
|
|
26
26
|
locale: Locale;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const results = solveScrabble(gaddag, config, board, tiles);
|
|
51
|
-
response.status(200).send(results);
|
|
52
|
-
} catch (error) {
|
|
53
|
-
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
54
|
-
logger.error('solve - error', { error, meta });
|
|
55
|
-
response.status(500).send({ error: 'Server error', message });
|
|
56
|
-
}
|
|
57
|
-
};
|
|
29
|
+
export default withApiLog('solve', solve);
|
|
30
|
+
|
|
31
|
+
async function solve(request: NextApiRequest, response: NextApiResponse, { ip, getElapsedMs }: ApiContext) {
|
|
32
|
+
const { board, characters, config, game, locale } = parseRequest(request);
|
|
33
|
+
const gaddag = await dictionaries.get(locale);
|
|
34
|
+
const tiles = characters.map((character) => new Tile({ character, isBlank: character === BLANK }));
|
|
35
|
+
const results = solveScrabble(gaddag, config, board, tiles);
|
|
36
|
+
response.status(200).send(results);
|
|
37
|
+
|
|
38
|
+
logEvent({
|
|
39
|
+
type: 'solve',
|
|
40
|
+
ip,
|
|
41
|
+
ms: getElapsedMs(),
|
|
42
|
+
locale,
|
|
43
|
+
game,
|
|
44
|
+
tiles: board.getTilesCount(),
|
|
45
|
+
blanks: board.getBlanksCount(),
|
|
46
|
+
rack: characters.join(','),
|
|
47
|
+
results: results.length,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
58
50
|
|
|
59
|
-
|
|
51
|
+
function parseRequest(request: NextApiRequest): RequestData {
|
|
60
52
|
const { board: boardJson, characters, game, locale } = request.body;
|
|
61
53
|
|
|
62
54
|
if (!isLocale(locale)) {
|
|
63
|
-
throw new
|
|
55
|
+
throw new BadRequestError('Invalid "locale" parameter');
|
|
64
56
|
}
|
|
65
57
|
|
|
66
58
|
if (!isGame(game)) {
|
|
67
|
-
throw new
|
|
59
|
+
throw new BadRequestError('Invalid "game" parameter');
|
|
68
60
|
}
|
|
69
61
|
|
|
70
62
|
if (!isStringArray(characters) || characters.length === 0) {
|
|
71
|
-
throw new
|
|
63
|
+
throw new BadRequestError('Invalid "characters" parameter');
|
|
72
64
|
}
|
|
73
65
|
|
|
74
66
|
if (!hasConfig(game, locale)) {
|
|
75
|
-
throw new
|
|
67
|
+
throw new BadRequestError(`No game "${game}" in "${locale}"`);
|
|
76
68
|
}
|
|
77
69
|
|
|
78
70
|
const config = getConfig(game, locale);
|
|
79
71
|
|
|
80
72
|
for (const character of characters) {
|
|
81
73
|
if (!isCharacterValid(character)) {
|
|
82
|
-
throw new
|
|
74
|
+
throw new BadRequestError('Invalid "characters" parameter');
|
|
83
75
|
}
|
|
84
76
|
}
|
|
85
77
|
|
|
86
78
|
const blanksCount = characters.filter((character) => character === BLANK).length;
|
|
87
79
|
|
|
88
80
|
if (blanksCount > config.blanksCount) {
|
|
89
|
-
throw new
|
|
81
|
+
throw new BadRequestError('Too many blank tiles passed');
|
|
90
82
|
}
|
|
91
83
|
|
|
92
84
|
if (!isBoardJson(boardJson) || !isBoardValid(boardJson, config)) {
|
|
93
|
-
throw new
|
|
85
|
+
throw new BadRequestError('Invalid "board" parameter');
|
|
94
86
|
}
|
|
95
87
|
|
|
96
88
|
const board = Board.fromJson(boardJson);
|
|
@@ -102,12 +94,10 @@ const parseRequest = (request: NextApiRequest): RequestData => {
|
|
|
102
94
|
game,
|
|
103
95
|
locale,
|
|
104
96
|
};
|
|
105
|
-
}
|
|
97
|
+
}
|
|
106
98
|
|
|
107
99
|
export const config = {
|
|
108
100
|
api: {
|
|
109
101
|
responseLimit: '25mb',
|
|
110
102
|
},
|
|
111
103
|
};
|
|
112
|
-
|
|
113
|
-
export default solve;
|
package/src/pages/api/verify.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { getConfig, hasConfig } from '@scrabble-solver/configs';
|
|
2
2
|
import { dictionaries } from '@scrabble-solver/dictionaries';
|
|
3
|
-
import {
|
|
3
|
+
import { logEvent } from '@scrabble-solver/logger';
|
|
4
4
|
import { Board, type Config, type Game, type Locale, isBoardJson, isGame, isLocale } from '@scrabble-solver/types';
|
|
5
5
|
import { type NextApiRequest, type NextApiResponse } from 'next';
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { type ApiContext, BadRequestError, isBoardValid, withApiLog } from '@/api';
|
|
8
8
|
import { type VerifiedWord } from '@/types';
|
|
9
9
|
|
|
10
10
|
interface RequestData {
|
|
@@ -14,63 +14,57 @@ interface RequestData {
|
|
|
14
14
|
locale: Locale;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
const meta = getServerLoggingData(request);
|
|
17
|
+
export default withApiLog('verify', verify);
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
async function verify(request: NextApiRequest, response: NextApiResponse, { ip, getElapsedMs }: ApiContext) {
|
|
20
|
+
const { board, game, locale } = parseRequest(request);
|
|
21
|
+
const gaddag = await dictionaries.get(locale);
|
|
22
|
+
const words = board.getWords().sort((a, b) => a.word.localeCompare(b.word, locale));
|
|
23
|
+
const invalidWords: VerifiedWord[] = [];
|
|
24
|
+
const validWords: VerifiedWord[] = [];
|
|
22
25
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
boardTilesCount: board.getTilesCount(),
|
|
29
|
-
game,
|
|
30
|
-
locale,
|
|
31
|
-
},
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
const gaddag = await dictionaries.get(locale);
|
|
35
|
-
const words = board.getWords().sort((a, b) => a.word.localeCompare(b.word, locale));
|
|
36
|
-
const invalidWords: VerifiedWord[] = [];
|
|
37
|
-
const validWords: VerifiedWord[] = [];
|
|
38
|
-
|
|
39
|
-
for (const word of words) {
|
|
40
|
-
if (gaddag.has(word.word)) {
|
|
41
|
-
validWords.push({ ...word, isValid: true });
|
|
42
|
-
} else {
|
|
43
|
-
invalidWords.push({ ...word, isValid: false });
|
|
44
|
-
}
|
|
26
|
+
for (const word of words) {
|
|
27
|
+
if (gaddag.has(word.word)) {
|
|
28
|
+
validWords.push({ ...word, isValid: true });
|
|
29
|
+
} else {
|
|
30
|
+
invalidWords.push({ ...word, isValid: false });
|
|
45
31
|
}
|
|
46
|
-
|
|
47
|
-
response.status(200).send({ invalidWords, validWords });
|
|
48
|
-
} catch (error) {
|
|
49
|
-
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
50
|
-
logger.error('verify - error', { error, meta });
|
|
51
|
-
response.status(500).send({ error: 'Server error', message });
|
|
52
32
|
}
|
|
53
|
-
};
|
|
54
33
|
|
|
55
|
-
|
|
34
|
+
response.status(200).send({ invalidWords, validWords });
|
|
35
|
+
|
|
36
|
+
logEvent({
|
|
37
|
+
type: 'verify',
|
|
38
|
+
ip,
|
|
39
|
+
ms: getElapsedMs(),
|
|
40
|
+
locale,
|
|
41
|
+
game,
|
|
42
|
+
tiles: board.getTilesCount(),
|
|
43
|
+
blanks: board.getBlanksCount(),
|
|
44
|
+
valid: validWords.length,
|
|
45
|
+
invalid: invalidWords.length,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function parseRequest(request: NextApiRequest): RequestData {
|
|
56
50
|
const { board: boardJson, game, locale } = request.body;
|
|
57
51
|
|
|
58
52
|
if (!isLocale(locale)) {
|
|
59
|
-
throw new
|
|
53
|
+
throw new BadRequestError('Invalid "locale" parameter');
|
|
60
54
|
}
|
|
61
55
|
|
|
62
56
|
if (!isGame(game)) {
|
|
63
|
-
throw new
|
|
57
|
+
throw new BadRequestError('Invalid "game" parameter');
|
|
64
58
|
}
|
|
65
59
|
|
|
66
60
|
if (!hasConfig(game, locale)) {
|
|
67
|
-
throw new
|
|
61
|
+
throw new BadRequestError(`No game "${game}" in "${locale}"`);
|
|
68
62
|
}
|
|
69
63
|
|
|
70
64
|
const config = getConfig(game, locale);
|
|
71
65
|
|
|
72
66
|
if (!isBoardJson(boardJson) || !isBoardValid(boardJson, config)) {
|
|
73
|
-
throw new
|
|
67
|
+
throw new BadRequestError('Invalid "board" parameter');
|
|
74
68
|
}
|
|
75
69
|
|
|
76
70
|
const board = Board.fromJson(boardJson);
|
|
@@ -81,6 +75,4 @@ const parseRequest = (request: NextApiRequest): RequestData => {
|
|
|
81
75
|
game,
|
|
82
76
|
locale,
|
|
83
77
|
};
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export default verify;
|
|
78
|
+
}
|
package/src/pages/api/visit.ts
CHANGED
|
@@ -1,19 +1,44 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { logEvent } from '@scrabble-solver/logger';
|
|
2
|
+
import { isGame, isLocale, isObject } from '@scrabble-solver/types';
|
|
2
3
|
import { type NextApiRequest, type NextApiResponse } from 'next';
|
|
3
4
|
|
|
4
|
-
import {
|
|
5
|
+
import { type ApiContext, withApiLog } from '@/api';
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
interface RequestData {
|
|
8
|
+
game: string | undefined;
|
|
9
|
+
locale: string | undefined;
|
|
10
|
+
referrer: string | undefined;
|
|
11
|
+
url: string | undefined;
|
|
12
|
+
}
|
|
8
13
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
const URL_MAX_LENGTH = 256;
|
|
15
|
+
|
|
16
|
+
export default withApiLog('visit', visit);
|
|
17
|
+
|
|
18
|
+
function visit(request: NextApiRequest, response: NextApiResponse, { ip, ua }: ApiContext) {
|
|
19
|
+
const { game, locale, referrer, url } = parseRequest(request);
|
|
20
|
+
response.status(200).send(true);
|
|
21
|
+
logEvent({ type: 'visit', ip, ua, referrer, locale, game, url });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// A visit is only a ping - unexpected fields are ignored, never rejected.
|
|
25
|
+
function parseRequest(request: NextApiRequest): RequestData {
|
|
26
|
+
const body: unknown = request.body;
|
|
27
|
+
|
|
28
|
+
if (!isObject(body)) {
|
|
29
|
+
return { game: undefined, locale: undefined, referrer: undefined, url: undefined };
|
|
16
30
|
}
|
|
17
|
-
};
|
|
18
31
|
|
|
19
|
-
|
|
32
|
+
const { game, locale, referrer, url } = body;
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
game: isGame(game) ? game : undefined,
|
|
36
|
+
locale: isLocale(locale) ? locale : undefined,
|
|
37
|
+
referrer: parseUrl(referrer),
|
|
38
|
+
url: parseUrl(url),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function parseUrl(value: unknown): string | undefined {
|
|
43
|
+
return typeof value === 'string' && value.length > 0 ? value.slice(0, URL_MAX_LENGTH) : undefined;
|
|
44
|
+
}
|
package/src/pages/index.tsx
CHANGED
|
@@ -14,9 +14,9 @@ import { NavButtons } from '@/components/NavButtons';
|
|
|
14
14
|
import { Solver } from '@/components/Solver';
|
|
15
15
|
import { useDirection } from '@/hooks/useDirection';
|
|
16
16
|
import { useEffectOnce } from '@/hooks/useEffectOnce';
|
|
17
|
-
import { useIsCompactLayout } from '@/hooks/useIsCompactLayout';
|
|
18
17
|
import { useLanguage } from '@/hooks/useLanguage';
|
|
19
18
|
import { useLocalStorage } from '@/hooks/useLocalStorage';
|
|
19
|
+
import { useMediaQuery } from '@/hooks/useMediaQuery';
|
|
20
20
|
import { LOCALE_FEATURES } from '@/i18n/constants';
|
|
21
21
|
import { schedulePreloadModals } from '@/modals/preload';
|
|
22
22
|
import { CONFIG_PENDING_CLASS, GITHUB_PROJECT_URL, NPM_PACKAGE_URL, SITE_DESCRIPTION, SITE_URL } from '@/parameters';
|
|
@@ -94,7 +94,7 @@ const Index: FunctionComponent<Props> = ({ version }) => {
|
|
|
94
94
|
const config = useTypedSelector(selectConfig);
|
|
95
95
|
const locale = useTypedSelector(selectLocale);
|
|
96
96
|
const isHydrated = useTypedSelector(selectIsHydrated);
|
|
97
|
-
const isCompactLayout =
|
|
97
|
+
const isCompactLayout = useMediaQuery('<l');
|
|
98
98
|
const previousIsCompactLayout = useRef(isCompactLayout);
|
|
99
99
|
const [modals, setModals] = useState<Record<Modal, boolean>>({
|
|
100
100
|
dictionary: false,
|
package/src/parameters/index.ts
CHANGED
|
@@ -8,8 +8,6 @@ export const BREAKPOINTS = {
|
|
|
8
8
|
xl: Number(variables.breakpointXl),
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
-
export const BREAKPOINT_HEIGHT_L = Number(variables.breakpointHeightL);
|
|
12
|
-
|
|
13
11
|
export const GITHUB_PROJECT_URL = 'https://github.com/kamilmielnik/scrabble-solver';
|
|
14
12
|
|
|
15
13
|
export const NPM_PACKAGE_URL = 'https://www.npmjs.com/package/scrabble-solver';
|
package/src/sdk/solve.ts
CHANGED
|
@@ -1,17 +1,11 @@
|
|
|
1
|
-
import { Board,
|
|
1
|
+
import { Board, Result, type ResultJson } from '@scrabble-solver/types';
|
|
2
2
|
|
|
3
3
|
import { solveLocally } from '@/solver-worker';
|
|
4
|
+
import { type SolveRequestPayload } from '@/types';
|
|
4
5
|
|
|
5
6
|
import { fetchJson } from './fetchJson';
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
board: BoardJson;
|
|
9
|
-
characters: string[];
|
|
10
|
-
game: Game;
|
|
11
|
-
locale: Locale;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export const solve = async ({ board, characters, game, locale }: Payload): Promise<Result[]> => {
|
|
8
|
+
export const solve = async ({ board, characters, game, locale }: SolveRequestPayload): Promise<Result[]> => {
|
|
15
9
|
const payload = { board, characters, game, locale };
|
|
16
10
|
const json =
|
|
17
11
|
(await solveLocally(payload)) ??
|
package/src/sdk/verify.ts
CHANGED
|
@@ -1,22 +1,14 @@
|
|
|
1
|
-
import { type BoardJson, type Game, type Locale } from '@scrabble-solver/types';
|
|
2
|
-
|
|
3
1
|
import { verifyLocally } from '@/solver-worker';
|
|
4
|
-
import { type VerifiedWord } from '@/types';
|
|
2
|
+
import { type VerifiedWord, type VerifyRequestPayload } from '@/types';
|
|
5
3
|
|
|
6
4
|
import { fetchJson } from './fetchJson';
|
|
7
5
|
|
|
8
|
-
interface Payload {
|
|
9
|
-
board: BoardJson;
|
|
10
|
-
game: Game;
|
|
11
|
-
locale: Locale;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
6
|
interface Response {
|
|
15
7
|
invalidWords: VerifiedWord[];
|
|
16
8
|
validWords: VerifiedWord[];
|
|
17
9
|
}
|
|
18
10
|
|
|
19
|
-
export const verify = async ({ board, game, locale }:
|
|
11
|
+
export const verify = async ({ board, game, locale }: VerifyRequestPayload): Promise<Response> => {
|
|
20
12
|
const payload = { board, game, locale };
|
|
21
13
|
return (
|
|
22
14
|
(await verifyLocally(payload)) ??
|
package/src/sdk/visit.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { type VisitRequestPayload } from '@/types';
|
|
2
|
+
|
|
3
|
+
import { fetchJson } from './fetchJson';
|
|
4
|
+
|
|
5
|
+
export function visit(payload: VisitRequestPayload): Promise<boolean> {
|
|
6
|
+
return fetchJson<boolean>('/api/visit', {
|
|
3
7
|
method: 'PUT',
|
|
8
|
+
body: JSON.stringify(payload),
|
|
4
9
|
});
|
|
5
|
-
}
|
|
10
|
+
}
|
package/src/state/sagas.ts
CHANGED
|
@@ -204,7 +204,13 @@ function* hydratePersistedTranslations(locale: Locale, version: string): AnyGene
|
|
|
204
204
|
|
|
205
205
|
function* visitWhenIdle(): AnyGenerator {
|
|
206
206
|
yield call(waitForIdleOrFirstIntent);
|
|
207
|
-
yield
|
|
207
|
+
const locale = yield select(selectLocale);
|
|
208
|
+
const game = yield select(selectGame);
|
|
209
|
+
|
|
210
|
+
try {
|
|
211
|
+
yield call(visit, { game, locale, referrer: document.referrer, url: location.href });
|
|
212
|
+
// oxlint-disable-next-line no-empty
|
|
213
|
+
} catch {}
|
|
208
214
|
}
|
|
209
215
|
|
|
210
216
|
function* prefetchDictionaryOnFirstIntent(): AnyGenerator {
|
package/src/styles/_tokens.scss
CHANGED
package/src/styles/mixins.scss
CHANGED
|
@@ -10,11 +10,6 @@
|
|
|
10
10
|
),
|
|
11
11
|
$media-expressions: (
|
|
12
12
|
touch: '(hover: none)',
|
|
13
|
-
/**
|
|
14
|
-
* Keep in sync with useIsCompactLayout
|
|
15
|
-
*/
|
|
16
|
-
compact: '(max-width: #{$breakpoint-l - 1px}), (max-height: #{$breakpoint-height-l - 1px})',
|
|
17
|
-
desktop: '(min-width: #{$breakpoint-l}) and (min-height: #{$breakpoint-height-l})',
|
|
18
13
|
)
|
|
19
14
|
);
|
|
20
15
|
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
breakpointM: strip-unit($breakpoint-m);
|
|
7
7
|
breakpointL: strip-unit($breakpoint-l);
|
|
8
8
|
breakpointXl: strip-unit($breakpoint-xl);
|
|
9
|
-
breakpointHeightL: strip-unit($breakpoint-height-l);
|
|
10
9
|
|
|
11
10
|
borderColorLight: $border-color-light;
|
|
12
11
|
borderWidth: strip-unit($border-width);
|
|
@@ -153,7 +153,7 @@ $easeOutSine: cubic-bezier(0.61, 1, 0.88, 1);
|
|
|
153
153
|
--components-spacing: var(--spacing--l);
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
-
@include media('
|
|
156
|
+
@include media('<l') {
|
|
157
157
|
--dictionary--height: var(--dictionary--height--mobile);
|
|
158
158
|
--logo--height: #{$logo-height-small};
|
|
159
159
|
--solver-column-total: 0px;
|