@sudobility/sudojo_lib 0.0.200 → 0.0.203
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/dist/admin/adminApi.d.ts +34 -0
- package/dist/admin/adminApi.d.ts.map +1 -0
- package/dist/admin/adminApi.js +59 -0
- package/dist/admin/adminApi.js.map +1 -0
- package/dist/admin/boardRules.d.ts +75 -0
- package/dist/admin/boardRules.d.ts.map +1 -0
- package/dist/admin/boardRules.js +127 -0
- package/dist/admin/boardRules.js.map +1 -0
- package/dist/admin/boardStrings.d.ts +55 -0
- package/dist/admin/boardStrings.d.ts.map +1 -0
- package/dist/admin/boardStrings.js +99 -0
- package/dist/admin/boardStrings.js.map +1 -0
- package/dist/admin/examples.d.ts +58 -0
- package/dist/admin/examples.d.ts.map +1 -0
- package/dist/admin/examples.js +104 -0
- package/dist/admin/examples.js.map +1 -0
- package/dist/admin/extraction.d.ts +87 -0
- package/dist/admin/extraction.d.ts.map +1 -0
- package/dist/admin/extraction.js +141 -0
- package/dist/admin/extraction.js.map +1 -0
- package/dist/admin/index.d.ts +32 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +22 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/jobs.d.ts +108 -0
- package/dist/admin/jobs.d.ts.map +1 -0
- package/dist/admin/jobs.js +435 -0
- package/dist/admin/jobs.js.map +1 -0
- package/dist/admin/regenerateHints.d.ts +16 -0
- package/dist/admin/regenerateHints.d.ts.map +1 -0
- package/dist/admin/regenerateHints.js +43 -0
- package/dist/admin/regenerateHints.js.map +1 -0
- package/dist/admin/types.d.ts +42 -0
- package/dist/admin/types.d.ts.map +1 -0
- package/dist/admin/types.js +21 -0
- package/dist/admin/types.js.map +1 -0
- package/dist/admin/useAdminJob.d.ts +28 -0
- package/dist/admin/useAdminJob.d.ts.map +1 -0
- package/dist/admin/useAdminJob.js +99 -0
- package/dist/admin/useAdminJob.js.map +1 -0
- package/dist/admin/useAdminStats.d.ts +46 -0
- package/dist/admin/useAdminStats.d.ts.map +1 -0
- package/dist/admin/useAdminStats.js +91 -0
- package/dist/admin/useAdminStats.js.map +1 -0
- package/dist/admin/useBoardGenerator.d.ts +29 -0
- package/dist/admin/useBoardGenerator.d.ts.map +1 -0
- package/dist/admin/useBoardGenerator.js +38 -0
- package/dist/admin/useBoardGenerator.js.map +1 -0
- package/dist/admin/useBoardTechniquesUpdater.d.ts +14 -0
- package/dist/admin/useBoardTechniquesUpdater.d.ts.map +1 -0
- package/dist/admin/useBoardTechniquesUpdater.js +28 -0
- package/dist/admin/useBoardTechniquesUpdater.js.map +1 -0
- package/dist/admin/useExampleCreator.d.ts +29 -0
- package/dist/admin/useExampleCreator.d.ts.map +1 -0
- package/dist/admin/useExampleCreator.js +58 -0
- package/dist/admin/useExampleCreator.js.map +1 -0
- package/dist/admin/useSingleBoardExtractor.d.ts +18 -0
- package/dist/admin/useSingleBoardExtractor.d.ts.map +1 -0
- package/dist/admin/useSingleBoardExtractor.js +19 -0
- package/dist/admin/useSingleBoardExtractor.js.map +1 -0
- package/dist/admin/useTechniqueExtractor.d.ts +26 -0
- package/dist/admin/useTechniqueExtractor.d.ts.map +1 -0
- package/dist/admin/useTechniqueExtractor.js +37 -0
- package/dist/admin/useTechniqueExtractor.js.map +1 -0
- package/dist/context/SudojoApiContext.d.ts +58 -0
- package/dist/context/SudojoApiContext.d.ts.map +1 -0
- package/dist/context/SudojoApiContext.js +52 -0
- package/dist/context/SudojoApiContext.js.map +1 -0
- package/dist/context/SudojoApiProvider.d.ts +21 -0
- package/dist/context/SudojoApiProvider.d.ts.map +1 -0
- package/dist/context/SudojoApiProvider.js +15 -0
- package/dist/context/SudojoApiProvider.js.map +1 -0
- package/dist/context/index.d.ts +3 -0
- package/dist/context/index.d.ts.map +1 -1
- package/dist/context/index.js +2 -0
- package/dist/context/index.js.map +1 -1
- package/dist/hooks/index.d.ts +24 -1
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +22 -0
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/useBoardEntry.d.ts +70 -6
- package/dist/hooks/useBoardEntry.d.ts.map +1 -1
- package/dist/hooks/useBoardEntry.js +128 -28
- package/dist/hooks/useBoardEntry.js.map +1 -1
- package/dist/hooks/useBoardScan.d.ts +27 -0
- package/dist/hooks/useBoardScan.d.ts.map +1 -0
- package/dist/hooks/useBoardScan.js +27 -0
- package/dist/hooks/useBoardScan.js.map +1 -0
- package/dist/hooks/useCommunities.d.ts +6 -4
- package/dist/hooks/useCommunities.d.ts.map +1 -1
- package/dist/hooks/useCommunities.js +6 -1
- package/dist/hooks/useCommunities.js.map +1 -1
- package/dist/hooks/useCompletionTrigger.d.ts +23 -0
- package/dist/hooks/useCompletionTrigger.d.ts.map +1 -0
- package/dist/hooks/useCompletionTrigger.js +39 -0
- package/dist/hooks/useCompletionTrigger.js.map +1 -0
- package/dist/hooks/useDailyGame.d.ts +11 -6
- package/dist/hooks/useDailyGame.d.ts.map +1 -1
- package/dist/hooks/useDailyGame.js +20 -61
- package/dist/hooks/useDailyGame.js.map +1 -1
- package/dist/hooks/useDeleteAccount.d.ts +72 -0
- package/dist/hooks/useDeleteAccount.d.ts.map +1 -0
- package/dist/hooks/useDeleteAccount.js +71 -0
- package/dist/hooks/useDeleteAccount.js.map +1 -0
- package/dist/hooks/useEnteredGameSession.d.ts +73 -0
- package/dist/hooks/useEnteredGameSession.d.ts.map +1 -0
- package/dist/hooks/useEnteredGameSession.js +155 -0
- package/dist/hooks/useEnteredGameSession.js.map +1 -0
- package/dist/hooks/useGameSession.d.ts +8 -4
- package/dist/hooks/useGameSession.d.ts.map +1 -1
- package/dist/hooks/useGameSession.js +4 -1
- package/dist/hooks/useGameSession.js.map +1 -1
- package/dist/hooks/useGameTeaching.d.ts.map +1 -1
- package/dist/hooks/useGameTeaching.js +11 -13
- package/dist/hooks/useGameTeaching.js.map +1 -1
- package/dist/hooks/useHint.d.ts +7 -7
- package/dist/hooks/useHint.d.ts.map +1 -1
- package/dist/hooks/useHint.js +18 -9
- package/dist/hooks/useHint.js.map +1 -1
- package/dist/hooks/useIsSiteAdmin.d.ts +39 -0
- package/dist/hooks/useIsSiteAdmin.d.ts.map +1 -0
- package/dist/hooks/useIsSiteAdmin.js +31 -0
- package/dist/hooks/useIsSiteAdmin.js.map +1 -0
- package/dist/hooks/useLearning.d.ts +8 -8
- package/dist/hooks/useLearning.d.ts.map +1 -1
- package/dist/hooks/useLearning.js +5 -2
- package/dist/hooks/useLearning.js.map +1 -1
- package/dist/hooks/useLevelGame.d.ts +7 -7
- package/dist/hooks/useLevelGame.d.ts.map +1 -1
- package/dist/hooks/useLevelGame.js +13 -49
- package/dist/hooks/useLevelGame.js.map +1 -1
- package/dist/hooks/useLevels.d.ts +8 -8
- package/dist/hooks/useLevels.d.ts.map +1 -1
- package/dist/hooks/useLevels.js +7 -3
- package/dist/hooks/useLevels.js.map +1 -1
- package/dist/hooks/usePracticeGame.d.ts +53 -0
- package/dist/hooks/usePracticeGame.d.ts.map +1 -0
- package/dist/hooks/usePracticeGame.js +72 -0
- package/dist/hooks/usePracticeGame.js.map +1 -0
- package/dist/hooks/usePuzzleDistribution.d.ts +2 -0
- package/dist/hooks/usePuzzleDistribution.d.ts.map +1 -1
- package/dist/hooks/usePuzzleDistribution.js +4 -14
- package/dist/hooks/usePuzzleDistribution.js.map +1 -1
- package/dist/hooks/usePuzzleProgress.d.ts +37 -0
- package/dist/hooks/usePuzzleProgress.d.ts.map +1 -0
- package/dist/hooks/usePuzzleProgress.js +48 -0
- package/dist/hooks/usePuzzleProgress.js.map +1 -0
- package/dist/hooks/usePuzzleSession.d.ts +215 -0
- package/dist/hooks/usePuzzleSession.d.ts.map +1 -0
- package/dist/hooks/usePuzzleSession.js +358 -0
- package/dist/hooks/usePuzzleSession.js.map +1 -0
- package/dist/hooks/useResumeGame.d.ts +70 -0
- package/dist/hooks/useResumeGame.d.ts.map +1 -0
- package/dist/hooks/useResumeGame.js +123 -0
- package/dist/hooks/useResumeGame.js.map +1 -0
- package/dist/hooks/useStrategies.d.ts +39 -0
- package/dist/hooks/useStrategies.d.ts.map +1 -0
- package/dist/hooks/useStrategies.js +34 -0
- package/dist/hooks/useStrategies.js.map +1 -0
- package/dist/hooks/useSudoku.d.ts +5 -0
- package/dist/hooks/useSudoku.d.ts.map +1 -1
- package/dist/hooks/useSudoku.js +7 -1
- package/dist/hooks/useSudoku.js.map +1 -1
- package/dist/hooks/useTechniqueByPath.d.ts +46 -0
- package/dist/hooks/useTechniqueByPath.d.ts.map +1 -0
- package/dist/hooks/useTechniqueByPath.js +47 -0
- package/dist/hooks/useTechniqueByPath.js.map +1 -0
- package/dist/hooks/useTechniqueExample.d.ts +60 -0
- package/dist/hooks/useTechniqueExample.d.ts.map +1 -0
- package/dist/hooks/useTechniqueExample.js +63 -0
- package/dist/hooks/useTechniqueExample.js.map +1 -0
- package/dist/hooks/useTechniques.d.ts +8 -8
- package/dist/hooks/useTechniques.d.ts.map +1 -1
- package/dist/hooks/useTechniques.js +9 -21
- package/dist/hooks/useTechniques.js.map +1 -1
- package/dist/index.d.ts +418 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +276 -10
- package/dist/index.js.map +1 -1
- package/dist/types/currentGame.d.ts +16 -1
- package/dist/types/currentGame.d.ts.map +1 -1
- package/dist/types/currentGame.js.map +1 -1
- package/dist/utils/auth.d.ts +6 -0
- package/dist/utils/auth.d.ts.map +1 -1
- package/dist/utils/auth.js +8 -0
- package/dist/utils/auth.js.map +1 -1
- package/dist/utils/community.d.ts +64 -0
- package/dist/utils/community.d.ts.map +1 -0
- package/dist/utils/community.js +66 -0
- package/dist/utils/community.js.map +1 -0
- package/dist/utils/date.d.ts +37 -0
- package/dist/utils/date.d.ts.map +1 -0
- package/dist/utils/date.js +68 -0
- package/dist/utils/date.js.map +1 -0
- package/dist/utils/entityTranslate.d.ts +120 -0
- package/dist/utils/entityTranslate.d.ts.map +1 -0
- package/dist/utils/entityTranslate.js +146 -0
- package/dist/utils/entityTranslate.js.map +1 -0
- package/dist/utils/gameFetchStatus.d.ts +74 -0
- package/dist/utils/gameFetchStatus.d.ts.map +1 -0
- package/dist/utils/gameFetchStatus.js +118 -0
- package/dist/utils/gameFetchStatus.js.map +1 -0
- package/dist/utils/gamification.d.ts +36 -0
- package/dist/utils/gamification.d.ts.map +1 -0
- package/dist/utils/gamification.js +33 -0
- package/dist/utils/gamification.js.map +1 -0
- package/dist/utils/hintAccess.d.ts +64 -0
- package/dist/utils/hintAccess.d.ts.map +1 -0
- package/dist/utils/hintAccess.js +57 -0
- package/dist/utils/hintAccess.js.map +1 -0
- package/dist/utils/index.d.ts +31 -10
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +20 -9
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/language.d.ts +123 -0
- package/dist/utils/language.d.ts.map +1 -0
- package/dist/utils/language.js +95 -0
- package/dist/utils/language.js.map +1 -0
- package/dist/utils/localizedHint.d.ts +60 -0
- package/dist/utils/localizedHint.d.ts.map +1 -1
- package/dist/utils/localizedHint.js +90 -0
- package/dist/utils/localizedHint.js.map +1 -1
- package/dist/utils/progress.d.ts +10 -1
- package/dist/utils/progress.d.ts.map +1 -1
- package/dist/utils/progress.js +24 -0
- package/dist/utils/progress.js.map +1 -1
- package/dist/utils/scannedBoard.d.ts +79 -0
- package/dist/utils/scannedBoard.d.ts.map +1 -0
- package/dist/utils/scannedBoard.js +96 -0
- package/dist/utils/scannedBoard.js.map +1 -0
- package/dist/utils/shareUrl.d.ts +38 -15
- package/dist/utils/shareUrl.d.ts.map +1 -1
- package/dist/utils/shareUrl.js +75 -26
- package/dist/utils/shareUrl.js.map +1 -1
- package/dist/utils/subscription.d.ts +118 -0
- package/dist/utils/subscription.d.ts.map +1 -1
- package/dist/utils/subscription.js +212 -0
- package/dist/utils/subscription.js.map +1 -1
- package/dist/utils/sudokuPresenter.d.ts +42 -0
- package/dist/utils/sudokuPresenter.d.ts.map +1 -1
- package/dist/utils/sudokuPresenter.js +114 -1
- package/dist/utils/sudokuPresenter.js.map +1 -1
- package/dist/utils/sudokuScrambler.d.ts +8 -0
- package/dist/utils/sudokuScrambler.d.ts.map +1 -1
- package/dist/utils/sudokuScrambler.js +12 -0
- package/dist/utils/sudokuScrambler.js.map +1 -1
- package/dist/utils/technique.d.ts +112 -1
- package/dist/utils/technique.d.ts.map +1 -1
- package/dist/utils/technique.js +293 -1
- package/dist/utils/technique.js.map +1 -1
- package/dist/utils/techniqueBitmask.d.ts +31 -0
- package/dist/utils/techniqueBitmask.d.ts.map +1 -0
- package/dist/utils/techniqueBitmask.js +53 -0
- package/dist/utils/techniqueBitmask.js.map +1 -0
- package/dist/utils/techniqueExample.d.ts +42 -0
- package/dist/utils/techniqueExample.d.ts.map +1 -0
- package/dist/utils/techniqueExample.js +57 -0
- package/dist/utils/techniqueExample.js.map +1 -0
- package/dist/utils/theme.d.ts +2 -0
- package/dist/utils/theme.d.ts.map +1 -1
- package/dist/utils/theme.js +2 -0
- package/dist/utils/theme.js.map +1 -1
- package/package.json +7 -7
|
@@ -3,31 +3,18 @@
|
|
|
3
3
|
* Allows users to enter given clues for a custom puzzle
|
|
4
4
|
*/
|
|
5
5
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
6
|
+
import { createEmptyBoard } from '../types';
|
|
6
7
|
import { useSolverValidate } from '@sudobility/sudojo_client';
|
|
8
|
+
import { MIN_CLUES } from '@sudobility/sudojo_types';
|
|
9
|
+
import { cellsToPuzzleString, countClues } from '../utils/sudokuScrambler';
|
|
10
|
+
import { useResolvedSudojoApi } from '../context/SudojoApiContext';
|
|
11
|
+
import { hasScannedInput } from '../utils/scannedBoard';
|
|
12
|
+
import { parsePencilmarksString, pencilmarksToString, } from '../utils/techniqueWalkthrough';
|
|
7
13
|
/**
|
|
8
14
|
* Create an empty 81-cell board for entry mode
|
|
9
15
|
*/
|
|
10
|
-
function
|
|
11
|
-
return
|
|
12
|
-
index,
|
|
13
|
-
solution: null,
|
|
14
|
-
given: null,
|
|
15
|
-
input: null,
|
|
16
|
-
pencilmarks: null,
|
|
17
|
-
}));
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Convert cells to 81-character puzzle string
|
|
21
|
-
* '0' for empty, '1'-'9' for given values
|
|
22
|
-
*/
|
|
23
|
-
function cellsToPuzzleString(cells) {
|
|
24
|
-
return cells.map(cell => cell.given?.toString() ?? '0').join('');
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Count the number of non-empty cells (clues)
|
|
28
|
-
*/
|
|
29
|
-
function countClues(cells) {
|
|
30
|
-
return cells.filter(cell => cell.given !== null).length;
|
|
16
|
+
function createEmptyCells() {
|
|
17
|
+
return createEmptyBoard().cells;
|
|
31
18
|
}
|
|
32
19
|
/**
|
|
33
20
|
* Create a board from an 81-character puzzle string
|
|
@@ -35,7 +22,7 @@ function countClues(cells) {
|
|
|
35
22
|
*/
|
|
36
23
|
function puzzleStringToCells(puzzle) {
|
|
37
24
|
if (puzzle.length !== 81) {
|
|
38
|
-
return
|
|
25
|
+
return createEmptyCells();
|
|
39
26
|
}
|
|
40
27
|
return Array.from({ length: 81 }, (_, index) => {
|
|
41
28
|
const char = puzzle.charAt(index);
|
|
@@ -63,8 +50,17 @@ function classifyValidationError(errorMsg) {
|
|
|
63
50
|
}
|
|
64
51
|
return 'enter.errors.validationFailed';
|
|
65
52
|
}
|
|
66
|
-
|
|
67
|
-
|
|
53
|
+
function createEmptyPencilmarks() {
|
|
54
|
+
return Array.from({ length: 81 }, () => []);
|
|
55
|
+
}
|
|
56
|
+
const EMPTY_INPUT = '0'.repeat(81);
|
|
57
|
+
export function useBoardEntry(options = {}) {
|
|
58
|
+
const { networkClient, baseUrl, token } = useResolvedSudojoApi(options, 'useBoardEntry');
|
|
59
|
+
const [cells, setCells] = useState(createEmptyCells);
|
|
60
|
+
const [pencilmarks, setPencilmarks] = useState(createEmptyPencilmarks);
|
|
61
|
+
const [autopencil, setAutopencil] = useState(false);
|
|
62
|
+
const [scannedInput, setScannedInput] = useState(null);
|
|
63
|
+
const [isPencilMode, setIsPencilMode] = useState(false);
|
|
68
64
|
const [selectedIndex, setSelectedIndex] = useState(null);
|
|
69
65
|
const [validationError, setValidationError] = useState(null);
|
|
70
66
|
const [validatedPuzzle, setValidatedPuzzle] = useState(null);
|
|
@@ -74,7 +70,7 @@ export function useBoardEntry({ networkClient, baseUrl, token, }) {
|
|
|
74
70
|
const clueCount = useMemo(() => countClues(cells), [cells]);
|
|
75
71
|
// Use the solver validate hook
|
|
76
72
|
const { data: validateData, error: validateError, isLoading: isValidating, } = useSolverValidate(networkClient, baseUrl, token, { original: puzzleString }, {
|
|
77
|
-
enabled: shouldValidate && clueCount >=
|
|
73
|
+
enabled: shouldValidate && clueCount >= MIN_CLUES,
|
|
78
74
|
retry: false,
|
|
79
75
|
});
|
|
80
76
|
// Track whether we've already processed this validation result
|
|
@@ -91,11 +87,14 @@ export function useBoardEntry({ networkClient, baseUrl, token, }) {
|
|
|
91
87
|
return;
|
|
92
88
|
processedDataRef.current = validateData;
|
|
93
89
|
// ValidateData has board.solution
|
|
94
|
-
const
|
|
90
|
+
const board = validateData.data?.board;
|
|
91
|
+
const solution = board?.solution;
|
|
95
92
|
if (validateData.success && solution) {
|
|
96
93
|
setValidatedPuzzle({
|
|
97
94
|
puzzle: puzzleString,
|
|
98
95
|
solution,
|
|
96
|
+
level: board.level,
|
|
97
|
+
difficultyScore: board.difficulty_score,
|
|
99
98
|
});
|
|
100
99
|
setValidationError(null);
|
|
101
100
|
}
|
|
@@ -146,6 +145,14 @@ export function useBoardEntry({ networkClient, baseUrl, token, }) {
|
|
|
146
145
|
}
|
|
147
146
|
return newCells;
|
|
148
147
|
});
|
|
148
|
+
// A given replaces the cell's pencilmarks
|
|
149
|
+
setPencilmarks(prev => {
|
|
150
|
+
if (!prev[selectedIndex]?.length)
|
|
151
|
+
return prev;
|
|
152
|
+
const next = [...prev];
|
|
153
|
+
next[selectedIndex] = [];
|
|
154
|
+
return next;
|
|
155
|
+
});
|
|
149
156
|
// Clear validation state when board changes
|
|
150
157
|
setValidationError(null);
|
|
151
158
|
setValidatedPuzzle(null);
|
|
@@ -173,7 +180,7 @@ export function useBoardEntry({ networkClient, baseUrl, token, }) {
|
|
|
173
180
|
// Trigger validation
|
|
174
181
|
const validate = useCallback(() => {
|
|
175
182
|
// Check minimum clues
|
|
176
|
-
if (clueCount <
|
|
183
|
+
if (clueCount < MIN_CLUES) {
|
|
177
184
|
setValidationError('enter.errors.notEnoughClues');
|
|
178
185
|
return;
|
|
179
186
|
}
|
|
@@ -187,7 +194,11 @@ export function useBoardEntry({ networkClient, baseUrl, token, }) {
|
|
|
187
194
|
}, [clueCount]);
|
|
188
195
|
// Reset to empty board
|
|
189
196
|
const reset = useCallback(() => {
|
|
190
|
-
setCells(
|
|
197
|
+
setCells(createEmptyCells());
|
|
198
|
+
setPencilmarks(createEmptyPencilmarks());
|
|
199
|
+
setAutopencil(false);
|
|
200
|
+
setScannedInput(null);
|
|
201
|
+
setIsPencilMode(false);
|
|
191
202
|
setSelectedIndex(null);
|
|
192
203
|
setValidationError(null);
|
|
193
204
|
setValidatedPuzzle(null);
|
|
@@ -198,6 +209,9 @@ export function useBoardEntry({ networkClient, baseUrl, token, }) {
|
|
|
198
209
|
// Set cells from puzzle string
|
|
199
210
|
const setCellsFromPuzzle = useCallback((puzzle) => {
|
|
200
211
|
setCells(puzzleStringToCells(puzzle));
|
|
212
|
+
setPencilmarks(createEmptyPencilmarks());
|
|
213
|
+
setAutopencil(false);
|
|
214
|
+
setScannedInput(null);
|
|
201
215
|
setSelectedIndex(null);
|
|
202
216
|
setValidationError(null);
|
|
203
217
|
setValidatedPuzzle(null);
|
|
@@ -205,6 +219,77 @@ export function useBoardEntry({ networkClient, baseUrl, token, }) {
|
|
|
205
219
|
processedDataRef.current = null;
|
|
206
220
|
processedErrorRef.current = null;
|
|
207
221
|
}, []);
|
|
222
|
+
// Toggle a pencilmark digit on the selected (non-given) cell
|
|
223
|
+
const togglePencilmark = useCallback((digit) => {
|
|
224
|
+
if (selectedIndex === null || digit < 1 || digit > 9)
|
|
225
|
+
return;
|
|
226
|
+
if (cells[selectedIndex]?.given !== null)
|
|
227
|
+
return;
|
|
228
|
+
setPencilmarks(prev => {
|
|
229
|
+
const next = [...prev];
|
|
230
|
+
const current = next[selectedIndex] ?? [];
|
|
231
|
+
next[selectedIndex] = current.includes(digit)
|
|
232
|
+
? current.filter(d => d !== digit)
|
|
233
|
+
: [...current, digit].sort((a, b) => a - b);
|
|
234
|
+
return next;
|
|
235
|
+
});
|
|
236
|
+
}, [selectedIndex, cells]);
|
|
237
|
+
const togglePencilMode = useCallback(() => {
|
|
238
|
+
setIsPencilMode(prev => !prev);
|
|
239
|
+
}, []);
|
|
240
|
+
const setPencilMode = useCallback((on) => {
|
|
241
|
+
setIsPencilMode(on);
|
|
242
|
+
}, []);
|
|
243
|
+
const enterDigit = useCallback((value) => {
|
|
244
|
+
if (isPencilMode) {
|
|
245
|
+
togglePencilmark(value);
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
setGiven(value);
|
|
249
|
+
}
|
|
250
|
+
}, [isPencilMode, togglePencilmark, setGiven]);
|
|
251
|
+
const toggleGiven = useCallback((value) => {
|
|
252
|
+
if (selectedIndex !== null && cells[selectedIndex]?.given === value) {
|
|
253
|
+
erase();
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
setGiven(value);
|
|
257
|
+
}
|
|
258
|
+
}, [selectedIndex, cells, erase, setGiven]);
|
|
259
|
+
// Load a scanned board (givens, pencilmarks, player digits)
|
|
260
|
+
const applyScan = useCallback((scanned) => {
|
|
261
|
+
setCellsFromPuzzle(scanned.original);
|
|
262
|
+
const parsed = parsePencilmarksString(scanned.pencilmarks);
|
|
263
|
+
setPencilmarks(parsed.length === 81
|
|
264
|
+
? parsed.map(pm => (pm ? [...pm].sort((a, b) => a - b) : []))
|
|
265
|
+
: createEmptyPencilmarks());
|
|
266
|
+
setAutopencil(scanned.autopencil);
|
|
267
|
+
setScannedInput(hasScannedInput(scanned) ? scanned.user : null);
|
|
268
|
+
}, [setCellsFromPuzzle]);
|
|
269
|
+
// Entry cells with pencilmarks merged in (givens never show pencilmarks)
|
|
270
|
+
const displayCells = useMemo(() => cells.map((cell, i) => {
|
|
271
|
+
if (cell.given !== null)
|
|
272
|
+
return cell;
|
|
273
|
+
const pm = pencilmarks[i];
|
|
274
|
+
return pm && pm.length > 0 ? { ...cell, pencilmarks: pm } : cell;
|
|
275
|
+
}), [cells, pencilmarks]);
|
|
276
|
+
const hasPencilmarks = useMemo(() => pencilmarks.some(pm => pm.length > 0), [pencilmarks]);
|
|
277
|
+
const pencilmarksString = useMemo(() => pencilmarksToString(pencilmarks.map(pm => [...pm])), [pencilmarks]);
|
|
278
|
+
const initialPlayState = useMemo(() => {
|
|
279
|
+
if (!scannedInput && !hasPencilmarks)
|
|
280
|
+
return undefined;
|
|
281
|
+
// A given corrected after the scan wins over a scanned digit there.
|
|
282
|
+
const input = scannedInput
|
|
283
|
+
? Array.from(scannedInput, (ch, i) => cells[i]?.given !== null ? '0' : ch).join('')
|
|
284
|
+
: EMPTY_INPUT;
|
|
285
|
+
const state = { input };
|
|
286
|
+
if (hasPencilmarks) {
|
|
287
|
+
state.pencilmarks = pencilmarksString;
|
|
288
|
+
state.autopencil = autopencil;
|
|
289
|
+
}
|
|
290
|
+
return state;
|
|
291
|
+
}, [scannedInput, hasPencilmarks, pencilmarksString, autopencil, cells]);
|
|
292
|
+
const canValidate = clueCount >= MIN_CLUES && !isValidating;
|
|
208
293
|
return {
|
|
209
294
|
cells,
|
|
210
295
|
selectedIndex,
|
|
@@ -219,6 +304,21 @@ export function useBoardEntry({ networkClient, baseUrl, token, }) {
|
|
|
219
304
|
reset,
|
|
220
305
|
clueCount,
|
|
221
306
|
setCellsFromPuzzle,
|
|
307
|
+
pencilmarks,
|
|
308
|
+
autopencil,
|
|
309
|
+
hasPencilmarks,
|
|
310
|
+
pencilmarksString,
|
|
311
|
+
togglePencilmark,
|
|
312
|
+
displayCells,
|
|
313
|
+
isPencilMode,
|
|
314
|
+
togglePencilMode,
|
|
315
|
+
setPencilMode,
|
|
316
|
+
enterDigit,
|
|
317
|
+
toggleGiven,
|
|
318
|
+
canValidate,
|
|
319
|
+
applyScan,
|
|
320
|
+
scannedInput,
|
|
321
|
+
initialPlayState,
|
|
222
322
|
};
|
|
223
323
|
}
|
|
224
324
|
//# sourceMappingURL=useBoardEntry.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useBoardEntry.js","sourceRoot":"","sources":["../../src/hooks/useBoardEntry.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Hook for managing Sudoku board entry mode state\n * Allows users to enter given clues for a custom puzzle\n */\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport type { SudokuCell } from '../types';\nimport { useSolverValidate } from '@sudobility/sudojo_client';\nimport type { NetworkClient } from '@sudobility/types';\n\nexport interface ValidatedPuzzle {\n puzzle: string;\n solution: string;\n}\n\nexport interface UseBoardEntryOptions {\n networkClient: NetworkClient;\n baseUrl: string;\n token: string;\n}\n\nexport interface UseBoardEntryReturn {\n /** 81 cells with given values only */\n cells: SudokuCell[];\n /** Currently selected cell index */\n selectedIndex: number | null;\n /** Whether validation is in progress */\n isValidating: boolean;\n /** Validation error message key */\n validationError: string | null;\n /** Validated puzzle data (puzzle + solution) */\n validatedPuzzle: ValidatedPuzzle | null;\n /** Select a cell by index */\n selectCell: (index: number) => void;\n /** Set a given value at the selected cell */\n setGiven: (value: number) => void;\n /** Erase the selected cell */\n erase: () => void;\n /** Get the puzzle as an 81-character string */\n getPuzzleString: () => string;\n /** Trigger validation */\n validate: () => void;\n /** Reset to empty board */\n reset: () => void;\n /** Number of clues entered */\n clueCount: number;\n /** Set cells from an 81-character puzzle string */\n setCellsFromPuzzle: (puzzle: string) => void;\n}\n\n/**\n * Create an empty 81-cell board for entry mode\n */\nfunction createEmptyBoard(): SudokuCell[] {\n return Array.from({ length: 81 }, (_, index) => ({\n index,\n solution: null,\n given: null,\n input: null,\n pencilmarks: null,\n }));\n}\n\n/**\n * Convert cells to 81-character puzzle string\n * '0' for empty, '1'-'9' for given values\n */\nfunction cellsToPuzzleString(cells: SudokuCell[]): string {\n return cells.map(cell => cell.given?.toString() ?? '0').join('');\n}\n\n/**\n * Count the number of non-empty cells (clues)\n */\nfunction countClues(cells: SudokuCell[]): number {\n return cells.filter(cell => cell.given !== null).length;\n}\n\n/**\n * Create a board from an 81-character puzzle string\n * '0' or '.' for empty, '1'-'9' for given values\n */\nfunction puzzleStringToCells(puzzle: string): SudokuCell[] {\n if (puzzle.length !== 81) {\n return createEmptyBoard();\n }\n\n return Array.from({ length: 81 }, (_, index) => {\n const char = puzzle.charAt(index);\n const value = parseInt(char, 10);\n const given = value >= 1 && value <= 9 ? value : null;\n\n return {\n index,\n solution: null,\n given,\n input: null,\n pencilmarks: null,\n };\n });\n}\n\n/**\n * Classify a validation error message into a translation key\n */\nfunction classifyValidationError(errorMsg: string): string {\n const lower = errorMsg.toLowerCase();\n if (lower.includes('multiple') || lower.includes('not unique')) {\n return 'enter.errors.multipleSolutions';\n }\n if (lower.includes('cannot solve') || lower.includes('no solution')) {\n return 'enter.errors.noSolution';\n }\n return 'enter.errors.validationFailed';\n}\n\nexport function useBoardEntry({\n networkClient,\n baseUrl,\n token,\n}: UseBoardEntryOptions): UseBoardEntryReturn {\n const [cells, setCells] = useState<SudokuCell[]>(createEmptyBoard);\n const [selectedIndex, setSelectedIndex] = useState<number | null>(null);\n const [validationError, setValidationError] = useState<string | null>(null);\n const [validatedPuzzle, setValidatedPuzzle] =\n useState<ValidatedPuzzle | null>(null);\n const [shouldValidate, setShouldValidate] = useState(false);\n\n // Compute puzzle string for validation\n const puzzleString = useMemo(() => cellsToPuzzleString(cells), [cells]);\n const clueCount = useMemo(() => countClues(cells), [cells]);\n\n // Use the solver validate hook\n const {\n data: validateData,\n error: validateError,\n isLoading: isValidating,\n } = useSolverValidate(\n networkClient,\n baseUrl,\n token,\n { original: puzzleString },\n {\n enabled: shouldValidate && clueCount >= 17,\n retry: false,\n }\n );\n\n // Track whether we've already processed this validation result\n const processedDataRef = useRef<typeof validateData | null>(null);\n const processedErrorRef = useRef<typeof validateError | null>(null);\n\n // Handle validation result\n useEffect(() => {\n if (!shouldValidate || isValidating) return;\n if (!validateData) return;\n // Avoid re-processing the same result\n if (processedDataRef.current === validateData) return;\n processedDataRef.current = validateData;\n\n // ValidateData has board.solution\n const solution = validateData.data?.board?.solution;\n if (validateData.success && solution) {\n setValidatedPuzzle({\n puzzle: puzzleString,\n solution,\n });\n setValidationError(null);\n } else if (validateData.error) {\n // Validation failed with specific error\n setValidationError(classifyValidationError(validateData.error));\n setValidatedPuzzle(null);\n } else {\n // Unexpected response - success but no solution\n console.error('Unexpected validation response:', validateData);\n setValidationError('enter.errors.validationFailed');\n setValidatedPuzzle(null);\n }\n setShouldValidate(false);\n }, [validateData, shouldValidate, isValidating, puzzleString]);\n\n // Handle network/query errors (e.g. 400 responses throw NetworkError)\n useEffect(() => {\n if (!shouldValidate || isValidating) return;\n if (!validateError) return;\n if (processedErrorRef.current === validateError) return;\n processedErrorRef.current = validateError;\n\n // Extract error message from NetworkError response body or message\n const networkErr = validateError as {\n response?: { error?: string };\n message?: string;\n };\n const errorMsg = networkErr.response?.error ?? networkErr.message ?? '';\n setValidationError(classifyValidationError(errorMsg));\n setValidatedPuzzle(null);\n setShouldValidate(false);\n }, [validateError, shouldValidate, isValidating]);\n\n // Select a cell\n const selectCell = useCallback((index: number) => {\n if (index >= 0 && index < 81) {\n setSelectedIndex(index);\n }\n }, []);\n\n // Set a given value at the selected cell\n const setGiven = useCallback(\n (value: number) => {\n if (selectedIndex === null || value < 1 || value > 9) return;\n\n setCells(prevCells => {\n const newCells = [...prevCells];\n const existing = newCells[selectedIndex];\n if (existing) {\n newCells[selectedIndex] = { ...existing, given: value };\n }\n return newCells;\n });\n\n // Clear validation state when board changes\n setValidationError(null);\n setValidatedPuzzle(null);\n },\n [selectedIndex]\n );\n\n // Erase the selected cell\n const erase = useCallback(() => {\n if (selectedIndex === null) return;\n\n setCells(prevCells => {\n const newCells = [...prevCells];\n const existing = newCells[selectedIndex];\n if (existing) {\n newCells[selectedIndex] = { ...existing, given: null };\n }\n return newCells;\n });\n\n // Clear validation state when board changes\n setValidationError(null);\n setValidatedPuzzle(null);\n }, [selectedIndex]);\n\n // Get puzzle string\n const getPuzzleString = useCallback(() => {\n return puzzleString;\n }, [puzzleString]);\n\n // Trigger validation\n const validate = useCallback(() => {\n // Check minimum clues\n if (clueCount < 17) {\n setValidationError('enter.errors.notEnoughClues');\n return;\n }\n\n // Clear previous state and trigger validation\n setValidationError(null);\n setValidatedPuzzle(null);\n // Clear processed refs so cached React Query results are re-processed\n processedDataRef.current = null;\n processedErrorRef.current = null;\n setShouldValidate(true);\n }, [clueCount]);\n\n // Reset to empty board\n const reset = useCallback(() => {\n setCells(createEmptyBoard());\n setSelectedIndex(null);\n setValidationError(null);\n setValidatedPuzzle(null);\n setShouldValidate(false);\n processedDataRef.current = null;\n processedErrorRef.current = null;\n }, []);\n\n // Set cells from puzzle string\n const setCellsFromPuzzle = useCallback((puzzle: string) => {\n setCells(puzzleStringToCells(puzzle));\n setSelectedIndex(null);\n setValidationError(null);\n setValidatedPuzzle(null);\n setShouldValidate(false);\n processedDataRef.current = null;\n processedErrorRef.current = null;\n }, []);\n\n return {\n cells,\n selectedIndex,\n isValidating,\n validationError,\n validatedPuzzle,\n selectCell,\n setGiven,\n erase,\n getPuzzleString,\n validate,\n reset,\n clueCount,\n setCellsFromPuzzle,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"useBoardEntry.js","sourceRoot":"","sources":["../../src/hooks/useBoardEntry.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Hook for managing Sudoku board entry mode state\n * Allows users to enter given clues for a custom puzzle\n */\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport type { SudokuCell } from '../types';\nimport { createEmptyBoard } from '../types';\nimport { useSolverValidate } from '@sudobility/sudojo_client';\nimport { MIN_CLUES } from '@sudobility/sudojo_types';\nimport { cellsToPuzzleString, countClues } from '../utils/sudokuScrambler';\nimport type { NetworkClient } from '@sudobility/types';\nimport { useResolvedSudojoApi } from '../context/SudojoApiContext';\nimport { hasScannedInput, type ScannedBoard } from '../utils/scannedBoard';\nimport {\n parsePencilmarksString,\n pencilmarksToString,\n} from '../utils/techniqueWalkthrough';\n\nexport interface ValidatedPuzzle {\n puzzle: string;\n solution: string;\n /** Difficulty level (1-12) the solver rated the puzzle at */\n level?: number;\n /** Solver's difficulty_score for the puzzle, when the API sends it */\n difficultyScore?: number | undefined;\n}\n\nexport interface UseBoardEntryOptions {\n /** Network client for API calls (default: SudojoApiProvider) */\n networkClient?: NetworkClient | undefined;\n /** Base URL for the Sudojo API (default: SudojoApiProvider) */\n baseUrl?: string | undefined;\n /** Access token (default: SudojoApiProvider) */\n token?: string | null | undefined;\n}\n\n/**\n * What the game should apply once the entered puzzle is validated: the\n * scanned player digits and/or the entry pencilmarks. Pass the fields to the\n * game as initialInput / initialPencilmarks / initialAutoPencilmarks (or to\n * useSudoku's applyHintData).\n */\nexport interface BoardEntryPlayState {\n /** 81-char player input ('0' = none). '0'.repeat(81) when only pencilmarks exist */\n input?: string;\n /** 81 comma-separated pencilmark entries */\n pencilmarks?: string;\n /** Whether the pencilmarks were auto-filled */\n autopencil?: boolean;\n}\n\nexport interface UseBoardEntryReturn {\n /** 81 cells with given values only */\n cells: SudokuCell[];\n /** Currently selected cell index */\n selectedIndex: number | null;\n /** Whether validation is in progress */\n isValidating: boolean;\n /** Validation error message key */\n validationError: string | null;\n /** Validated puzzle data (puzzle + solution + solver rating) */\n validatedPuzzle: ValidatedPuzzle | null;\n /** Select a cell by index */\n selectCell: (index: number) => void;\n /** Set a given value at the selected cell */\n setGiven: (value: number) => void;\n /** Erase the selected cell */\n erase: () => void;\n /** Get the puzzle as an 81-character string */\n getPuzzleString: () => string;\n /** Trigger validation */\n validate: () => void;\n /** Reset to empty board */\n reset: () => void;\n /** Number of clues entered */\n clueCount: number;\n /** Set cells from an 81-character puzzle string (clears pencilmarks and scanned input) */\n setCellsFromPuzzle: (puzzle: string) => void;\n\n // --- Entry pencilmarks ---------------------------------------------------\n /** Per-cell entry pencilmarks (81 sorted digit arrays; [] = none) */\n pencilmarks: number[][];\n /** Whether the pencilmarks were auto-filled (from a scan) */\n autopencil: boolean;\n /** Whether any cell has pencilmarks */\n hasPencilmarks: boolean;\n /** Pencilmarks as 81 comma-separated entries (e.g. \"12,,9,...\") */\n pencilmarksString: string;\n /** Toggle a pencilmark digit on the selected cell (ignored on givens) */\n togglePencilmark: (digit: number) => void;\n /** `cells` with the entry pencilmarks merged in, for display */\n displayCells: SudokuCell[];\n /** Whether digit input goes to pencilmarks instead of givens */\n isPencilMode: boolean;\n /** Toggle pencil mode */\n togglePencilMode: () => void;\n /** Set pencil mode */\n setPencilMode: (on: boolean) => void;\n /**\n * Route a digit: in pencil mode toggle a pencilmark, otherwise set a given\n * (web/RN entry keypad behavior).\n */\n enterDigit: (value: number) => void;\n\n // --- Givens --------------------------------------------------------------\n /**\n * Set a given at the selected cell, or erase it when the cell already holds\n * that digit (extension keypad behavior).\n */\n toggleGiven: (value: number) => void;\n /** Whether Validate may run (enough clues and not already validating) */\n canValidate: boolean;\n\n // --- Scans ---------------------------------------------------------------\n /**\n * Load a scanned board: givens from `original`, pencilmarks and autopencil\n * from the scan, and the player's digits as `scannedInput`.\n */\n applyScan: (scanned: ScannedBoard) => void;\n /** The scanned player digits (81 chars), or null when the scan had none */\n scannedInput: string | null;\n /**\n * What the game should apply once validated (scanned input and/or entry\n * pencilmarks), or undefined when there is nothing to restore.\n */\n initialPlayState: BoardEntryPlayState | undefined;\n}\n\n/**\n * Create an empty 81-cell board for entry mode\n */\nfunction createEmptyCells(): SudokuCell[] {\n return createEmptyBoard().cells;\n}\n\n/**\n * Create a board from an 81-character puzzle string\n * '0' or '.' for empty, '1'-'9' for given values\n */\nfunction puzzleStringToCells(puzzle: string): SudokuCell[] {\n if (puzzle.length !== 81) {\n return createEmptyCells();\n }\n\n return Array.from({ length: 81 }, (_, index) => {\n const char = puzzle.charAt(index);\n const value = parseInt(char, 10);\n const given = value >= 1 && value <= 9 ? value : null;\n\n return {\n index,\n solution: null,\n given,\n input: null,\n pencilmarks: null,\n };\n });\n}\n\n/**\n * Classify a validation error message into a translation key\n */\nfunction classifyValidationError(errorMsg: string): string {\n const lower = errorMsg.toLowerCase();\n if (lower.includes('multiple') || lower.includes('not unique')) {\n return 'enter.errors.multipleSolutions';\n }\n if (lower.includes('cannot solve') || lower.includes('no solution')) {\n return 'enter.errors.noSolution';\n }\n return 'enter.errors.validationFailed';\n}\n\nfunction createEmptyPencilmarks(): number[][] {\n return Array.from({ length: 81 }, () => []);\n}\n\nconst EMPTY_INPUT = '0'.repeat(81);\n\nexport function useBoardEntry(\n options: UseBoardEntryOptions = {}\n): UseBoardEntryReturn {\n const { networkClient, baseUrl, token } = useResolvedSudojoApi(\n options,\n 'useBoardEntry'\n );\n const [cells, setCells] = useState<SudokuCell[]>(createEmptyCells);\n const [pencilmarks, setPencilmarks] = useState<number[][]>(\n createEmptyPencilmarks\n );\n const [autopencil, setAutopencil] = useState(false);\n const [scannedInput, setScannedInput] = useState<string | null>(null);\n const [isPencilMode, setIsPencilMode] = useState(false);\n const [selectedIndex, setSelectedIndex] = useState<number | null>(null);\n const [validationError, setValidationError] = useState<string | null>(null);\n const [validatedPuzzle, setValidatedPuzzle] =\n useState<ValidatedPuzzle | null>(null);\n const [shouldValidate, setShouldValidate] = useState(false);\n\n // Compute puzzle string for validation\n const puzzleString = useMemo(() => cellsToPuzzleString(cells), [cells]);\n const clueCount = useMemo(() => countClues(cells), [cells]);\n\n // Use the solver validate hook\n const {\n data: validateData,\n error: validateError,\n isLoading: isValidating,\n } = useSolverValidate(\n networkClient,\n baseUrl,\n token,\n { original: puzzleString },\n {\n enabled: shouldValidate && clueCount >= MIN_CLUES,\n retry: false,\n }\n );\n\n // Track whether we've already processed this validation result\n const processedDataRef = useRef<typeof validateData | null>(null);\n const processedErrorRef = useRef<typeof validateError | null>(null);\n\n // Handle validation result\n useEffect(() => {\n if (!shouldValidate || isValidating) return;\n if (!validateData) return;\n // Avoid re-processing the same result\n if (processedDataRef.current === validateData) return;\n processedDataRef.current = validateData;\n\n // ValidateData has board.solution\n const board = validateData.data?.board;\n const solution = board?.solution;\n if (validateData.success && solution) {\n setValidatedPuzzle({\n puzzle: puzzleString,\n solution,\n level: board.level,\n difficultyScore: board.difficulty_score,\n });\n setValidationError(null);\n } else if (validateData.error) {\n // Validation failed with specific error\n setValidationError(classifyValidationError(validateData.error));\n setValidatedPuzzle(null);\n } else {\n // Unexpected response - success but no solution\n console.error('Unexpected validation response:', validateData);\n setValidationError('enter.errors.validationFailed');\n setValidatedPuzzle(null);\n }\n setShouldValidate(false);\n }, [validateData, shouldValidate, isValidating, puzzleString]);\n\n // Handle network/query errors (e.g. 400 responses throw NetworkError)\n useEffect(() => {\n if (!shouldValidate || isValidating) return;\n if (!validateError) return;\n if (processedErrorRef.current === validateError) return;\n processedErrorRef.current = validateError;\n\n // Extract error message from NetworkError response body or message\n const networkErr = validateError as {\n response?: { error?: string };\n message?: string;\n };\n const errorMsg = networkErr.response?.error ?? networkErr.message ?? '';\n setValidationError(classifyValidationError(errorMsg));\n setValidatedPuzzle(null);\n setShouldValidate(false);\n }, [validateError, shouldValidate, isValidating]);\n\n // Select a cell\n const selectCell = useCallback((index: number) => {\n if (index >= 0 && index < 81) {\n setSelectedIndex(index);\n }\n }, []);\n\n // Set a given value at the selected cell\n const setGiven = useCallback(\n (value: number) => {\n if (selectedIndex === null || value < 1 || value > 9) return;\n\n setCells(prevCells => {\n const newCells = [...prevCells];\n const existing = newCells[selectedIndex];\n if (existing) {\n newCells[selectedIndex] = { ...existing, given: value };\n }\n return newCells;\n });\n // A given replaces the cell's pencilmarks\n setPencilmarks(prev => {\n if (!prev[selectedIndex]?.length) return prev;\n const next = [...prev];\n next[selectedIndex] = [];\n return next;\n });\n\n // Clear validation state when board changes\n setValidationError(null);\n setValidatedPuzzle(null);\n },\n [selectedIndex]\n );\n\n // Erase the selected cell\n const erase = useCallback(() => {\n if (selectedIndex === null) return;\n\n setCells(prevCells => {\n const newCells = [...prevCells];\n const existing = newCells[selectedIndex];\n if (existing) {\n newCells[selectedIndex] = { ...existing, given: null };\n }\n return newCells;\n });\n\n // Clear validation state when board changes\n setValidationError(null);\n setValidatedPuzzle(null);\n }, [selectedIndex]);\n\n // Get puzzle string\n const getPuzzleString = useCallback(() => {\n return puzzleString;\n }, [puzzleString]);\n\n // Trigger validation\n const validate = useCallback(() => {\n // Check minimum clues\n if (clueCount < MIN_CLUES) {\n setValidationError('enter.errors.notEnoughClues');\n return;\n }\n\n // Clear previous state and trigger validation\n setValidationError(null);\n setValidatedPuzzle(null);\n // Clear processed refs so cached React Query results are re-processed\n processedDataRef.current = null;\n processedErrorRef.current = null;\n setShouldValidate(true);\n }, [clueCount]);\n\n // Reset to empty board\n const reset = useCallback(() => {\n setCells(createEmptyCells());\n setPencilmarks(createEmptyPencilmarks());\n setAutopencil(false);\n setScannedInput(null);\n setIsPencilMode(false);\n setSelectedIndex(null);\n setValidationError(null);\n setValidatedPuzzle(null);\n setShouldValidate(false);\n processedDataRef.current = null;\n processedErrorRef.current = null;\n }, []);\n\n // Set cells from puzzle string\n const setCellsFromPuzzle = useCallback((puzzle: string) => {\n setCells(puzzleStringToCells(puzzle));\n setPencilmarks(createEmptyPencilmarks());\n setAutopencil(false);\n setScannedInput(null);\n setSelectedIndex(null);\n setValidationError(null);\n setValidatedPuzzle(null);\n setShouldValidate(false);\n processedDataRef.current = null;\n processedErrorRef.current = null;\n }, []);\n\n // Toggle a pencilmark digit on the selected (non-given) cell\n const togglePencilmark = useCallback(\n (digit: number) => {\n if (selectedIndex === null || digit < 1 || digit > 9) return;\n if (cells[selectedIndex]?.given !== null) return;\n setPencilmarks(prev => {\n const next = [...prev];\n const current = next[selectedIndex] ?? [];\n next[selectedIndex] = current.includes(digit)\n ? current.filter(d => d !== digit)\n : [...current, digit].sort((a, b) => a - b);\n return next;\n });\n },\n [selectedIndex, cells]\n );\n\n const togglePencilMode = useCallback(() => {\n setIsPencilMode(prev => !prev);\n }, []);\n\n const setPencilMode = useCallback((on: boolean) => {\n setIsPencilMode(on);\n }, []);\n\n const enterDigit = useCallback(\n (value: number) => {\n if (isPencilMode) {\n togglePencilmark(value);\n } else {\n setGiven(value);\n }\n },\n [isPencilMode, togglePencilmark, setGiven]\n );\n\n const toggleGiven = useCallback(\n (value: number) => {\n if (selectedIndex !== null && cells[selectedIndex]?.given === value) {\n erase();\n } else {\n setGiven(value);\n }\n },\n [selectedIndex, cells, erase, setGiven]\n );\n\n // Load a scanned board (givens, pencilmarks, player digits)\n const applyScan = useCallback(\n (scanned: ScannedBoard) => {\n setCellsFromPuzzle(scanned.original);\n const parsed = parsePencilmarksString(scanned.pencilmarks);\n setPencilmarks(\n parsed.length === 81\n ? parsed.map(pm => (pm ? [...pm].sort((a, b) => a - b) : []))\n : createEmptyPencilmarks()\n );\n setAutopencil(scanned.autopencil);\n setScannedInput(hasScannedInput(scanned) ? scanned.user : null);\n },\n [setCellsFromPuzzle]\n );\n\n // Entry cells with pencilmarks merged in (givens never show pencilmarks)\n const displayCells = useMemo(\n () =>\n cells.map((cell, i) => {\n if (cell.given !== null) return cell;\n const pm = pencilmarks[i];\n return pm && pm.length > 0 ? { ...cell, pencilmarks: pm } : cell;\n }),\n [cells, pencilmarks]\n );\n\n const hasPencilmarks = useMemo(\n () => pencilmarks.some(pm => pm.length > 0),\n [pencilmarks]\n );\n\n const pencilmarksString = useMemo(\n () => pencilmarksToString(pencilmarks.map(pm => [...pm])),\n [pencilmarks]\n );\n\n const initialPlayState = useMemo((): BoardEntryPlayState | undefined => {\n if (!scannedInput && !hasPencilmarks) return undefined;\n // A given corrected after the scan wins over a scanned digit there.\n const input = scannedInput\n ? Array.from(scannedInput, (ch, i) =>\n cells[i]?.given !== null ? '0' : ch\n ).join('')\n : EMPTY_INPUT;\n const state: BoardEntryPlayState = { input };\n if (hasPencilmarks) {\n state.pencilmarks = pencilmarksString;\n state.autopencil = autopencil;\n }\n return state;\n }, [scannedInput, hasPencilmarks, pencilmarksString, autopencil, cells]);\n\n const canValidate = clueCount >= MIN_CLUES && !isValidating;\n\n return {\n cells,\n selectedIndex,\n isValidating,\n validationError,\n validatedPuzzle,\n selectCell,\n setGiven,\n erase,\n getPuzzleString,\n validate,\n reset,\n clueCount,\n setCellsFromPuzzle,\n pencilmarks,\n autopencil,\n hasPencilmarks,\n pencilmarksString,\n togglePencilmark,\n displayCells,\n isPencilMode,\n togglePencilMode,\n setPencilMode,\n enterDigit,\n toggleGiven,\n canValidate,\n applyScan,\n scannedInput,\n initialPlayState,\n };\n}\n"]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook for scanning a Sudoku board from an image.
|
|
3
|
+
*
|
|
4
|
+
* The request goes through sudojo_client's `useSudojoOcrExtract`; this hook
|
|
5
|
+
* turns the response into a ScannedBoard (givens, the player's digits and
|
|
6
|
+
* pencilmarks) with `scannedBoardFromResponse`.
|
|
7
|
+
*/
|
|
8
|
+
import type { OcrSource } from '@sudobility/sudojo_types';
|
|
9
|
+
import type { NetworkClient } from '@sudobility/types';
|
|
10
|
+
import { type ScannedBoard } from '../utils/scannedBoard';
|
|
11
|
+
export interface UseBoardScanOptions {
|
|
12
|
+
/** Network client for API calls (default: SudojoApiProvider) */
|
|
13
|
+
networkClient?: NetworkClient | undefined;
|
|
14
|
+
/** Base URL for the Sudojo API (default: SudojoApiProvider) */
|
|
15
|
+
baseUrl?: string | undefined;
|
|
16
|
+
}
|
|
17
|
+
export interface UseBoardScanReturn {
|
|
18
|
+
/**
|
|
19
|
+
* Read the board in an image.
|
|
20
|
+
* @throws ScanBoardError
|
|
21
|
+
*/
|
|
22
|
+
scan: (token: string, image: string, source?: OcrSource) => Promise<ScannedBoard>;
|
|
23
|
+
/** Whether a scan is in flight */
|
|
24
|
+
isScanning: boolean;
|
|
25
|
+
}
|
|
26
|
+
export declare function useBoardScan(options?: UseBoardScanOptions): UseBoardScanReturn;
|
|
27
|
+
//# sourceMappingURL=useBoardScan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useBoardScan.d.ts","sourceRoot":"","sources":["../../src/hooks/useBoardScan.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvD,OAAO,EAEL,KAAK,YAAY,EAElB,MAAM,uBAAuB,CAAC;AAE/B,MAAM,WAAW,mBAAmB;IAClC,gEAAgE;IAChE,aAAa,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IAC1C,+DAA+D;IAC/D,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC9B;AAED,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,IAAI,EAAE,CACJ,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,SAAS,KACf,OAAO,CAAC,YAAY,CAAC,CAAC;IAC3B,kCAAkC;IAClC,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,wBAAgB,YAAY,CAC1B,OAAO,GAAE,mBAAwB,GAChC,kBAAkB,CA4BpB"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook for scanning a Sudoku board from an image.
|
|
3
|
+
*
|
|
4
|
+
* The request goes through sudojo_client's `useSudojoOcrExtract`; this hook
|
|
5
|
+
* turns the response into a ScannedBoard (givens, the player's digits and
|
|
6
|
+
* pencilmarks) with `scannedBoardFromResponse`.
|
|
7
|
+
*/
|
|
8
|
+
import { useCallback } from 'react';
|
|
9
|
+
import { useSudojoOcrExtract } from '@sudobility/sudojo_client';
|
|
10
|
+
import { useResolvedSudojoApi } from '../context/SudojoApiContext';
|
|
11
|
+
import { ScanBoardError, scannedBoardFromResponse, } from '../utils/scannedBoard';
|
|
12
|
+
export function useBoardScan(options = {}) {
|
|
13
|
+
const { networkClient, baseUrl } = useResolvedSudojoApi(options, 'useBoardScan');
|
|
14
|
+
const { mutateAsync, isPending } = useSudojoOcrExtract(networkClient, baseUrl);
|
|
15
|
+
const scan = useCallback(async (token, image, source = 'library') => {
|
|
16
|
+
let response;
|
|
17
|
+
try {
|
|
18
|
+
response = await mutateAsync({ token, image, source });
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
throw new ScanBoardError('REQUEST_FAILED', error instanceof Error ? error.message : String(error), { cause: error });
|
|
22
|
+
}
|
|
23
|
+
return scannedBoardFromResponse(response);
|
|
24
|
+
}, [mutateAsync]);
|
|
25
|
+
return { scan, isScanning: isPending };
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=useBoardScan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useBoardScan.js","sourceRoot":"","sources":["../../src/hooks/useBoardScan.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AACpC,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAGhE,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,EACL,cAAc,EAEd,wBAAwB,GACzB,MAAM,uBAAuB,CAAC;AAuB/B,MAAM,UAAU,YAAY,CAC1B,UAA+B,EAAE;IAEjC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,GAAG,oBAAoB,CACrD,OAAO,EACP,cAAc,CACf,CAAC;IACF,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,GAAG,mBAAmB,CACpD,aAAa,EACb,OAAO,CACR,CAAC;IAEF,MAAM,IAAI,GAAG,WAAW,CACtB,KAAK,EAAE,KAAa,EAAE,KAAa,EAAE,SAAoB,SAAS,EAAE,EAAE;QACpE,IAAI,QAAQ,CAAC;QACb,IAAI,CAAC;YACH,QAAQ,GAAG,MAAM,WAAW,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;QACzD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,cAAc,CACtB,gBAAgB,EAChB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EACtD,EAAE,KAAK,EAAE,KAAK,EAAE,CACjB,CAAC;QACJ,CAAC;QACD,OAAO,wBAAwB,CAAC,QAAQ,CAAC,CAAC;IAC5C,CAAC,EACD,CAAC,WAAW,CAAC,CACd,CAAC;IAEF,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC;AACzC,CAAC","sourcesContent":["/**\n * Hook for scanning a Sudoku board from an image.\n *\n * The request goes through sudojo_client's `useSudojoOcrExtract`; this hook\n * turns the response into a ScannedBoard (givens, the player's digits and\n * pencilmarks) with `scannedBoardFromResponse`.\n */\n\nimport { useCallback } from 'react';\nimport { useSudojoOcrExtract } from '@sudobility/sudojo_client';\nimport type { OcrSource } from '@sudobility/sudojo_types';\nimport type { NetworkClient } from '@sudobility/types';\nimport { useResolvedSudojoApi } from '../context/SudojoApiContext';\nimport {\n ScanBoardError,\n type ScannedBoard,\n scannedBoardFromResponse,\n} from '../utils/scannedBoard';\n\nexport interface UseBoardScanOptions {\n /** Network client for API calls (default: SudojoApiProvider) */\n networkClient?: NetworkClient | undefined;\n /** Base URL for the Sudojo API (default: SudojoApiProvider) */\n baseUrl?: string | undefined;\n}\n\nexport interface UseBoardScanReturn {\n /**\n * Read the board in an image.\n * @throws ScanBoardError\n */\n scan: (\n token: string,\n image: string,\n source?: OcrSource\n ) => Promise<ScannedBoard>;\n /** Whether a scan is in flight */\n isScanning: boolean;\n}\n\nexport function useBoardScan(\n options: UseBoardScanOptions = {}\n): UseBoardScanReturn {\n const { networkClient, baseUrl } = useResolvedSudojoApi(\n options,\n 'useBoardScan'\n );\n const { mutateAsync, isPending } = useSudojoOcrExtract(\n networkClient,\n baseUrl\n );\n\n const scan = useCallback(\n async (token: string, image: string, source: OcrSource = 'library') => {\n let response;\n try {\n response = await mutateAsync({ token, image, source });\n } catch (error) {\n throw new ScanBoardError(\n 'REQUEST_FAILED',\n error instanceof Error ? error.message : String(error),\n { cause: error }\n );\n }\n return scannedBoardFromResponse(response);\n },\n [mutateAsync]\n );\n\n return { scan, isScanning: isPending };\n}\n"]}
|
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
import type { Community } from '@sudobility/sudojo_types';
|
|
5
5
|
import type { NetworkClient } from '@sudobility/types';
|
|
6
6
|
export interface UseCommunitiesOptions {
|
|
7
|
-
/** Network client for API calls */
|
|
8
|
-
networkClient
|
|
9
|
-
/** Base URL for the Sudojo API */
|
|
10
|
-
baseUrl
|
|
7
|
+
/** Network client for API calls (default: SudojoApiProvider) */
|
|
8
|
+
networkClient?: NetworkClient | undefined;
|
|
9
|
+
/** Base URL for the Sudojo API (default: SudojoApiProvider) */
|
|
10
|
+
baseUrl?: string | undefined;
|
|
11
11
|
/** Access token for authentication (optional for this public endpoint) */
|
|
12
12
|
token?: string;
|
|
13
13
|
/** Language code to filter communities (e.g., "en", "ja") */
|
|
@@ -26,6 +26,8 @@ export interface UseCommunitiesResult {
|
|
|
26
26
|
refetch: () => void;
|
|
27
27
|
/** Communities grouped by platform type */
|
|
28
28
|
communitiesByPlatform: Map<string, Community[]>;
|
|
29
|
+
/** Platform ids present in communitiesByPlatform, in display order (sortCommunityPlatforms) */
|
|
30
|
+
sortedPlatforms: string[];
|
|
29
31
|
}
|
|
30
32
|
/**
|
|
31
33
|
* Hook for fetching communities filtered by language.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useCommunities.d.ts","sourceRoot":"","sources":["../../src/hooks/useCommunities.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"useCommunities.d.ts","sourceRoot":"","sources":["../../src/hooks/useCommunities.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAKvD,MAAM,WAAW,qBAAqB;IACpC,gEAAgE;IAChE,aAAa,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IAC1C,+DAA+D;IAC/D,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,0EAA0E;IAC1E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kCAAkC;IAClC,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACnC,iDAAiD;IACjD,WAAW,EAAE,SAAS,EAAE,CAAC;IACzB,sCAAsC;IACtC,SAAS,EAAE,OAAO,CAAC;IACnB,8BAA8B;IAC9B,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,0BAA0B;IAC1B,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,2CAA2C;IAC3C,qBAAqB,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;IAChD,+FAA+F;IAC/F,eAAe,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,qBAAqB,GAC7B,oBAAoB,CAmDtB"}
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
* Hook for fetching communities filtered by language
|
|
3
3
|
*/
|
|
4
4
|
import { useMemo } from 'react';
|
|
5
|
+
import { useResolvedSudojoApi } from '../context/SudojoApiContext';
|
|
5
6
|
import { useSudojoCommunities } from '@sudobility/sudojo_client';
|
|
7
|
+
import { sortCommunityPlatforms } from '../utils/community';
|
|
6
8
|
/**
|
|
7
9
|
* Hook for fetching communities filtered by language.
|
|
8
10
|
*
|
|
@@ -14,7 +16,8 @@ import { useSudojoCommunities } from '@sudobility/sudojo_client';
|
|
|
14
16
|
* @returns Communities data and utilities
|
|
15
17
|
*/
|
|
16
18
|
export function useCommunities(options) {
|
|
17
|
-
const {
|
|
19
|
+
const { language, enabled = true } = options;
|
|
20
|
+
const { networkClient, baseUrl, token } = useResolvedSudojoApi(options, 'useCommunities');
|
|
18
21
|
const queryParams = useMemo(() => {
|
|
19
22
|
if (language === undefined)
|
|
20
23
|
return undefined;
|
|
@@ -36,6 +39,7 @@ export function useCommunities(options) {
|
|
|
36
39
|
}
|
|
37
40
|
return byPlatform;
|
|
38
41
|
}, [communities]);
|
|
42
|
+
const sortedPlatforms = useMemo(() => sortCommunityPlatforms(communitiesByPlatform.keys()), [communitiesByPlatform]);
|
|
39
43
|
return {
|
|
40
44
|
communities,
|
|
41
45
|
isLoading,
|
|
@@ -44,6 +48,7 @@ export function useCommunities(options) {
|
|
|
44
48
|
refetch();
|
|
45
49
|
},
|
|
46
50
|
communitiesByPlatform,
|
|
51
|
+
sortedPlatforms,
|
|
47
52
|
};
|
|
48
53
|
}
|
|
49
54
|
//# sourceMappingURL=useCommunities.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useCommunities.js","sourceRoot":"","sources":["../../src/hooks/useCommunities.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAGhC,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"useCommunities.js","sourceRoot":"","sources":["../../src/hooks/useCommunities.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAGhC,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AA8B5D;;;;;;;;;GASG;AACH,MAAM,UAAU,cAAc,CAC5B,OAA8B;IAE9B,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,IAAI,EAAE,GAAG,OAAO,CAAC;IAC7C,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,oBAAoB,CAC5D,OAAO,EACP,gBAAgB,CACjB,CAAC;IAEF,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,EAAE;QAC/B,IAAI,QAAQ,KAAK,SAAS;YAAE,OAAO,SAAS,CAAC;QAC7C,OAAO,EAAE,QAAQ,EAAE,CAAC;IACtB,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;IAEf,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,oBAAoB,CAC9D,aAAa,EACb,OAAO,EACP,KAAK,EACL,WAAW,EACX,EAAE,OAAO,EAAE,CACZ,CAAC;IAEF,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,EAAE;QAC/B,IAAI,CAAC,IAAI,EAAE,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI;YAAE,OAAO,EAAE,CAAC;QAC5C,OAAO,IAAI,CAAC,IAAI,CAAC;IACnB,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IAEX,MAAM,qBAAqB,GAAG,OAAO,CAAC,GAAG,EAAE;QACzC,MAAM,UAAU,GAAG,IAAI,GAAG,EAAuB,CAAC;QAClD,KAAK,MAAM,SAAS,IAAI,WAAW,EAAE,CAAC;YACpC,MAAM,GAAG,GAAG,SAAS,CAAC,QAAQ,CAAC;YAC/B,MAAM,QAAQ,GAAG,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;YAC3C,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACzB,UAAU,CAAC,GAAG,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;QAChC,CAAC;QACD,OAAO,UAAU,CAAC;IACpB,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC;IAElB,MAAM,eAAe,GAAG,OAAO,CAC7B,GAAG,EAAE,CAAC,sBAAsB,CAAC,qBAAqB,CAAC,IAAI,EAAE,CAAC,EAC1D,CAAC,qBAAqB,CAAC,CACxB,CAAC;IAEF,OAAO;QACL,WAAW;QACX,SAAS;QACT,KAAK,EAAE,KAAK,IAAI,IAAI;QACpB,OAAO,EAAE,GAAG,EAAE;YACZ,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,qBAAqB;QACrB,eAAe;KAChB,CAAC;AACJ,CAAC","sourcesContent":["/**\n * Hook for fetching communities filtered by language\n */\n\nimport { useMemo } from 'react';\nimport type { Community } from '@sudobility/sudojo_types';\nimport type { NetworkClient } from '@sudobility/types';\nimport { useResolvedSudojoApi } from '../context/SudojoApiContext';\nimport { useSudojoCommunities } from '@sudobility/sudojo_client';\nimport { sortCommunityPlatforms } from '../utils/community';\n\nexport interface UseCommunitiesOptions {\n /** Network client for API calls (default: SudojoApiProvider) */\n networkClient?: NetworkClient | undefined;\n /** Base URL for the Sudojo API (default: SudojoApiProvider) */\n baseUrl?: string | undefined;\n /** Access token for authentication (optional for this public endpoint) */\n token?: string;\n /** Language code to filter communities (e.g., \"en\", \"ja\") */\n language?: string;\n /** Whether to enable the query */\n enabled?: boolean;\n}\n\nexport interface UseCommunitiesResult {\n /** All communities for the specified language */\n communities: Community[];\n /** Whether communities are loading */\n isLoading: boolean;\n /** Error if loading failed */\n error: Error | null;\n /** Refetch communities */\n refetch: () => void;\n /** Communities grouped by platform type */\n communitiesByPlatform: Map<string, Community[]>;\n /** Platform ids present in communitiesByPlatform, in display order (sortCommunityPlatforms) */\n sortedPlatforms: string[];\n}\n\n/**\n * Hook for fetching communities filtered by language.\n *\n * Unlike techniques/levels where the same records are translated,\n * communities are entirely different records per language.\n * Switching language triggers a new fetch with different results.\n *\n * @param options - Hook options\n * @returns Communities data and utilities\n */\nexport function useCommunities(\n options: UseCommunitiesOptions\n): UseCommunitiesResult {\n const { language, enabled = true } = options;\n const { networkClient, baseUrl, token } = useResolvedSudojoApi(\n options,\n 'useCommunities'\n );\n\n const queryParams = useMemo(() => {\n if (language === undefined) return undefined;\n return { language };\n }, [language]);\n\n const { data, isLoading, error, refetch } = useSudojoCommunities(\n networkClient,\n baseUrl,\n token,\n queryParams,\n { enabled }\n );\n\n const communities = useMemo(() => {\n if (!data?.success || !data.data) return [];\n return data.data;\n }, [data]);\n\n const communitiesByPlatform = useMemo(() => {\n const byPlatform = new Map<string, Community[]>();\n for (const community of communities) {\n const key = community.platform;\n const existing = byPlatform.get(key) ?? [];\n existing.push(community);\n byPlatform.set(key, existing);\n }\n return byPlatform;\n }, [communities]);\n\n const sortedPlatforms = useMemo(\n () => sortCommunityPlatforms(communitiesByPlatform.keys()),\n [communitiesByPlatform]\n );\n\n return {\n communities,\n isLoading,\n error: error ?? null,\n refetch: () => {\n refetch();\n },\n communitiesByPlatform,\n sortedPlatforms,\n };\n}\n"]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fire a callback once each time a puzzle becomes completed.
|
|
3
|
+
*
|
|
4
|
+
* Shared latch from the web and RN SudokuGame (and the extension side panel):
|
|
5
|
+
* `onComplete` runs on the false -> true transition of `isCompleted`, never
|
|
6
|
+
* again while it stays true, and the latch re-arms when `isCompleted` goes
|
|
7
|
+
* back to false (a new board, an undo), as in the apps' game components.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* @param isCompleted - Whether the puzzle is currently completed
|
|
11
|
+
* @param onComplete - Called once per completion (latest callback is used)
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* useCompletionTrigger(isCompleted, () => {
|
|
16
|
+
* const finalTime = stopTimer();
|
|
17
|
+
* setShowCelebration(true);
|
|
18
|
+
* onComplete?.(finalTime);
|
|
19
|
+
* });
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export declare function useCompletionTrigger(isCompleted: boolean, onComplete: () => void): void;
|
|
23
|
+
//# sourceMappingURL=useCompletionTrigger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useCompletionTrigger.d.ts","sourceRoot":"","sources":["../../src/hooks/useCompletionTrigger.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAClC,WAAW,EAAE,OAAO,EACpB,UAAU,EAAE,MAAM,IAAI,GACrB,IAAI,CAeN"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fire a callback once each time a puzzle becomes completed.
|
|
3
|
+
*
|
|
4
|
+
* Shared latch from the web and RN SudokuGame (and the extension side panel):
|
|
5
|
+
* `onComplete` runs on the false -> true transition of `isCompleted`, never
|
|
6
|
+
* again while it stays true, and the latch re-arms when `isCompleted` goes
|
|
7
|
+
* back to false (a new board, an undo), as in the apps' game components.
|
|
8
|
+
*/
|
|
9
|
+
import { useEffect, useRef } from 'react';
|
|
10
|
+
/**
|
|
11
|
+
* @param isCompleted - Whether the puzzle is currently completed
|
|
12
|
+
* @param onComplete - Called once per completion (latest callback is used)
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```tsx
|
|
16
|
+
* useCompletionTrigger(isCompleted, () => {
|
|
17
|
+
* const finalTime = stopTimer();
|
|
18
|
+
* setShowCelebration(true);
|
|
19
|
+
* onComplete?.(finalTime);
|
|
20
|
+
* });
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export function useCompletionTrigger(isCompleted, onComplete) {
|
|
24
|
+
const completedRef = useRef(false);
|
|
25
|
+
const onCompleteRef = useRef(onComplete);
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
onCompleteRef.current = onComplete;
|
|
28
|
+
});
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
if (isCompleted && !completedRef.current) {
|
|
31
|
+
completedRef.current = true;
|
|
32
|
+
onCompleteRef.current();
|
|
33
|
+
}
|
|
34
|
+
else if (!isCompleted) {
|
|
35
|
+
completedRef.current = false;
|
|
36
|
+
}
|
|
37
|
+
}, [isCompleted]);
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=useCompletionTrigger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useCompletionTrigger.js","sourceRoot":"","sources":["../../src/hooks/useCompletionTrigger.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAE1C;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,oBAAoB,CAClC,WAAoB,EACpB,UAAsB;IAEtB,MAAM,YAAY,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IACnC,MAAM,aAAa,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;IACzC,SAAS,CAAC,GAAG,EAAE;QACb,aAAa,CAAC,OAAO,GAAG,UAAU,CAAC;IACrC,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,WAAW,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,CAAC;YACzC,YAAY,CAAC,OAAO,GAAG,IAAI,CAAC;YAC5B,aAAa,CAAC,OAAO,EAAE,CAAC;QAC1B,CAAC;aAAM,IAAI,CAAC,WAAW,EAAE,CAAC;YACxB,YAAY,CAAC,OAAO,GAAG,KAAK,CAAC;QAC/B,CAAC;IACH,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC;AACpB,CAAC","sourcesContent":["/**\n * Fire a callback once each time a puzzle becomes completed.\n *\n * Shared latch from the web and RN SudokuGame (and the extension side panel):\n * `onComplete` runs on the false -> true transition of `isCompleted`, never\n * again while it stays true, and the latch re-arms when `isCompleted` goes\n * back to false (a new board, an undo), as in the apps' game components.\n */\n\nimport { useEffect, useRef } from 'react';\n\n/**\n * @param isCompleted - Whether the puzzle is currently completed\n * @param onComplete - Called once per completion (latest callback is used)\n *\n * @example\n * ```tsx\n * useCompletionTrigger(isCompleted, () => {\n * const finalTime = stopTimer();\n * setShowCelebration(true);\n * onComplete?.(finalTime);\n * });\n * ```\n */\nexport function useCompletionTrigger(\n isCompleted: boolean,\n onComplete: () => void\n): void {\n const completedRef = useRef(false);\n const onCompleteRef = useRef(onComplete);\n useEffect(() => {\n onCompleteRef.current = onComplete;\n });\n\n useEffect(() => {\n if (isCompleted && !completedRef.current) {\n completedRef.current = true;\n onCompleteRef.current();\n } else if (!isCompleted) {\n completedRef.current = false;\n }\n }, [isCompleted]);\n}\n"]}
|
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import type { Daily } from '@sudobility/sudojo_types';
|
|
6
6
|
import type { NetworkClient } from '@sudobility/types';
|
|
7
|
-
import type
|
|
7
|
+
import { type GameFetchStatus } from '../utils/gameFetchStatus';
|
|
8
8
|
export interface UseDailyGameOptions {
|
|
9
|
-
/** Network client for API calls */
|
|
10
|
-
networkClient
|
|
11
|
-
/** Base URL for the Sudojo API */
|
|
12
|
-
baseUrl
|
|
9
|
+
/** Network client for API calls (default: SudojoApiProvider) */
|
|
10
|
+
networkClient?: NetworkClient | undefined;
|
|
11
|
+
/** Base URL for the Sudojo API (default: SudojoApiProvider) */
|
|
12
|
+
baseUrl?: string | undefined;
|
|
13
13
|
/** Access token for authentication */
|
|
14
|
-
token
|
|
14
|
+
token?: string | undefined;
|
|
15
15
|
/** Whether subscription is currently active */
|
|
16
16
|
subscriptionActive?: boolean;
|
|
17
17
|
/** Whether to enable the query */
|
|
@@ -37,6 +37,11 @@ export interface UseDailyGameResult {
|
|
|
37
37
|
* Automatically refetches when auth token or subscription status changes.
|
|
38
38
|
* Returns status indicating whether auth or subscription is required.
|
|
39
39
|
*
|
|
40
|
+
* "Today" is the **UTC** date (getUtcDateString), the same day the API's
|
|
41
|
+
* `/dailies/today` serves, computed once per mount. (It used to be the local
|
|
42
|
+
* date, which asked for tomorrow's or yesterday's daily near midnight.)
|
|
43
|
+
* `dailyDate` is the daily's own `YYYY-MM-DD`, never timezone-shifted.
|
|
44
|
+
*
|
|
40
45
|
* @param options - Hook options
|
|
41
46
|
* @returns Daily puzzle data and status
|
|
42
47
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useDailyGame.d.ts","sourceRoot":"","sources":["../../src/hooks/useDailyGame.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"useDailyGame.d.ts","sourceRoot":"","sources":["../../src/hooks/useDailyGame.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAIvD,OAAO,EAEL,KAAK,eAAe,EAErB,MAAM,0BAA0B,CAAC;AAElC,MAAM,WAAW,mBAAmB;IAClC,gEAAgE;IAChE,aAAa,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IAC1C,+DAA+D;IAC/D,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,sCAAsC;IACtC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,+CAA+C;IAC/C,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,kCAAkC;IAClC,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,kBAAkB;IACjC,2BAA2B;IAC3B,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,kDAAkD;IAClD,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,uCAAuC;IACvC,MAAM,EAAE,eAAe,CAAC;IACxB,8BAA8B;IAC9B,SAAS,EAAE,OAAO,CAAC;IACnB,4BAA4B;IAC5B,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,+BAA+B;IAC/B,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,kBAAkB,CAsE7E"}
|