@sudobility/sudojo_lib 0.0.201 → 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 +65 -5
- package/dist/hooks/useBoardEntry.d.ts.map +1 -1
- package/dist/hooks/useBoardEntry.js +124 -27
- 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 +9 -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
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for the admin batch jobs (board generation, technique
|
|
3
|
+
* extraction, example creation).
|
|
4
|
+
*
|
|
5
|
+
* Ported from `sudojo_app/src/utils/admin/types.ts`.
|
|
6
|
+
*/
|
|
7
|
+
/** Create a fresh, not-yet-aborted {@link AbortHandle}. */
|
|
8
|
+
export function createAbortHandle() {
|
|
9
|
+
let aborted = false;
|
|
10
|
+
return {
|
|
11
|
+
shouldAbort: () => aborted,
|
|
12
|
+
abort: () => {
|
|
13
|
+
aborted = true;
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/** Refresh the admin token every N boards during a long job. */
|
|
18
|
+
export const ADMIN_TOKEN_REFRESH_INTERVAL = 50;
|
|
19
|
+
/** Most recent log lines a job hook keeps (older lines are dropped). */
|
|
20
|
+
export const ADMIN_JOB_LOG_LIMIT = 200;
|
|
21
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/admin/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAuBH,2DAA2D;AAC3D,MAAM,UAAU,iBAAiB;IAC/B,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,OAAO;QACL,WAAW,EAAE,GAAG,EAAE,CAAC,OAAO;QAC1B,KAAK,EAAE,GAAG,EAAE;YACV,OAAO,GAAG,IAAI,CAAC;QACjB,CAAC;KACF,CAAC;AACJ,CAAC;AAED,gEAAgE;AAChE,MAAM,CAAC,MAAM,4BAA4B,GAAG,EAAE,CAAC;AAE/C,wEAAwE;AACxE,MAAM,CAAC,MAAM,mBAAmB,GAAG,GAAG,CAAC","sourcesContent":["/**\n * Shared types for the admin batch jobs (board generation, technique\n * extraction, example creation).\n *\n * Ported from `sudojo_app/src/utils/admin/types.ts`.\n */\n\nimport type { NetworkClient } from '@sudobility/types';\n\n/** Options every admin job hook takes. */\nexport interface AdminJobOptions {\n networkClient: NetworkClient;\n baseUrl: string;\n /**\n * Returns a valid admin ID token, or null when signed out. Called once when a\n * job starts and again every {@link ADMIN_TOKEN_REFRESH_INTERVAL} boards, so\n * a long job survives token expiry. Pass a function that forces a refresh\n * (e.g. Firebase `getIdToken(true)`) or that falls back to the current token.\n */\n getToken: () => Promise<string | null>;\n}\n\n/** Cooperative cancellation flag shared by a running job. */\nexport interface AbortHandle {\n shouldAbort: () => boolean;\n abort: () => void;\n}\n\n/** Create a fresh, not-yet-aborted {@link AbortHandle}. */\nexport function createAbortHandle(): AbortHandle {\n let aborted = false;\n return {\n shouldAbort: () => aborted,\n abort: () => {\n aborted = true;\n },\n };\n}\n\n/** Refresh the admin token every N boards during a long job. */\nexport const ADMIN_TOKEN_REFRESH_INTERVAL = 50;\n\n/** Most recent log lines a job hook keeps (older lines are dropped). */\nexport const ADMIN_JOB_LOG_LIMIT = 200;\n\n/** State every admin job hook exposes. */\nexport interface AdminJobState {\n /** True from `start` until the job finishes or is cancelled. */\n isRunning: boolean;\n /** Latest progress line ('' before the first run). */\n progress: string;\n /** Progress lines of the current run, oldest first, capped at {@link ADMIN_JOB_LOG_LIMIT}. */\n log: string[];\n /** Last error message of the current run, or null. */\n error: string | null;\n}\n"]}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal: run-state bookkeeping shared by the admin job hooks (progress,
|
|
3
|
+
* capped log, error, running flag, cancellation, token acquisition).
|
|
4
|
+
* Not exported from the package.
|
|
5
|
+
*/
|
|
6
|
+
import { type AdminJobContext } from './jobs';
|
|
7
|
+
import type { AdminApi } from './adminApi';
|
|
8
|
+
import { type AdminJobState } from './types';
|
|
9
|
+
/** Append a line, keeping at most {@link ADMIN_JOB_LOG_LIMIT} lines. */
|
|
10
|
+
export declare function appendLog(log: readonly string[], line: string): string[];
|
|
11
|
+
export interface AdminJobRunner {
|
|
12
|
+
state: AdminJobState;
|
|
13
|
+
/**
|
|
14
|
+
* Start a run: aborts any previous run, resets state, gets a token, then
|
|
15
|
+
* calls `body` with a job context and an `isLive()` check (false once the
|
|
16
|
+
* run is superseded or detached; gate extra state updates on it). Resolves
|
|
17
|
+
* when the run ends; never rejects.
|
|
18
|
+
*/
|
|
19
|
+
run: <T>(api: AdminApi, getToken: () => Promise<string | null>, body: (ctx: AdminJobContext, isLive: () => boolean) => Promise<T>) => Promise<T | undefined>;
|
|
20
|
+
/**
|
|
21
|
+
* Abort the current run. With `detach`, the hook shows "Stopped" and stops
|
|
22
|
+
* running at once, ignoring whatever the run still reports; without it, the
|
|
23
|
+
* run's own final message is shown when it ends.
|
|
24
|
+
*/
|
|
25
|
+
cancel: (detach: boolean) => void;
|
|
26
|
+
}
|
|
27
|
+
export declare function useAdminJob(): AdminJobRunner;
|
|
28
|
+
//# sourceMappingURL=useAdminJob.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useAdminJob.d.ts","sourceRoot":"","sources":["../../src/admin/useAdminJob.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,KAAK,eAAe,EAA2B,MAAM,QAAQ,CAAC;AACvE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAGL,KAAK,aAAa,EAEnB,MAAM,SAAS,CAAC;AAUjB,wEAAwE;AACxE,wBAAgB,SAAS,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAKxE;AASD,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,aAAa,CAAC;IACrB;;;;;OAKG;IACH,GAAG,EAAE,CAAC,CAAC,EACL,GAAG,EAAE,QAAQ,EACb,QAAQ,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,EACtC,IAAI,EAAE,CAAC,GAAG,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC,KAC9D,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IAC5B;;;;OAIG;IACH,MAAM,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;CACnC;AAED,wBAAgB,WAAW,IAAI,cAAc,CAqF5C"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal: run-state bookkeeping shared by the admin job hooks (progress,
|
|
3
|
+
* capped log, error, running flag, cancellation, token acquisition).
|
|
4
|
+
* Not exported from the package.
|
|
5
|
+
*/
|
|
6
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
7
|
+
import { createAdminTokenManager } from './jobs';
|
|
8
|
+
import { ADMIN_JOB_LOG_LIMIT, createAbortHandle, } from './types';
|
|
9
|
+
import { errorMessage } from './boardStrings';
|
|
10
|
+
const INITIAL_STATE = {
|
|
11
|
+
isRunning: false,
|
|
12
|
+
progress: '',
|
|
13
|
+
log: [],
|
|
14
|
+
error: null,
|
|
15
|
+
};
|
|
16
|
+
/** Append a line, keeping at most {@link ADMIN_JOB_LOG_LIMIT} lines. */
|
|
17
|
+
export function appendLog(log, line) {
|
|
18
|
+
const next = [...log, line];
|
|
19
|
+
return next.length > ADMIN_JOB_LOG_LIMIT
|
|
20
|
+
? next.slice(next.length - ADMIN_JOB_LOG_LIMIT)
|
|
21
|
+
: next;
|
|
22
|
+
}
|
|
23
|
+
export function useAdminJob() {
|
|
24
|
+
const [state, setState] = useState(INITIAL_STATE);
|
|
25
|
+
const activeRef = useRef(null);
|
|
26
|
+
const nextIdRef = useRef(0);
|
|
27
|
+
// Leaving the page stops the job (the web original kept looping).
|
|
28
|
+
useEffect(() => () => {
|
|
29
|
+
activeRef.current?.abort.abort();
|
|
30
|
+
activeRef.current = null;
|
|
31
|
+
}, []);
|
|
32
|
+
const run = useCallback(async (api, getToken, body) => {
|
|
33
|
+
activeRef.current?.abort.abort();
|
|
34
|
+
const active = {
|
|
35
|
+
id: ++nextIdRef.current,
|
|
36
|
+
abort: createAbortHandle(),
|
|
37
|
+
detached: false,
|
|
38
|
+
};
|
|
39
|
+
activeRef.current = active;
|
|
40
|
+
const isLive = () => activeRef.current === active && !active.detached;
|
|
41
|
+
const onProgress = (message) => {
|
|
42
|
+
if (!isLive())
|
|
43
|
+
return;
|
|
44
|
+
setState(prev => ({
|
|
45
|
+
...prev,
|
|
46
|
+
progress: message,
|
|
47
|
+
log: appendLog(prev.log, message),
|
|
48
|
+
}));
|
|
49
|
+
};
|
|
50
|
+
const onError = (error) => {
|
|
51
|
+
if (!isLive())
|
|
52
|
+
return;
|
|
53
|
+
const line = `Error: ${error.message}`;
|
|
54
|
+
setState(prev => ({
|
|
55
|
+
...prev,
|
|
56
|
+
progress: line,
|
|
57
|
+
log: appendLog(prev.log, line),
|
|
58
|
+
error: error.message,
|
|
59
|
+
}));
|
|
60
|
+
};
|
|
61
|
+
setState({ isRunning: true, progress: '', log: [], error: null });
|
|
62
|
+
try {
|
|
63
|
+
const token = await createAdminTokenManager(getToken, onProgress);
|
|
64
|
+
if (!token) {
|
|
65
|
+
onError(new Error('Not authenticated'));
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
return await body({ api, token, abort: active.abort, onProgress, onError }, isLive);
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
onError(err instanceof Error ? err : new Error(errorMessage(err)));
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
finally {
|
|
75
|
+
if (isLive()) {
|
|
76
|
+
activeRef.current = null;
|
|
77
|
+
setState(prev => ({ ...prev, isRunning: false }));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}, []);
|
|
81
|
+
const cancel = useCallback((detach) => {
|
|
82
|
+
const active = activeRef.current;
|
|
83
|
+
if (!active)
|
|
84
|
+
return;
|
|
85
|
+
active.abort.abort();
|
|
86
|
+
if (detach) {
|
|
87
|
+
active.detached = true;
|
|
88
|
+
activeRef.current = null;
|
|
89
|
+
setState(prev => ({
|
|
90
|
+
...prev,
|
|
91
|
+
isRunning: false,
|
|
92
|
+
progress: 'Stopped',
|
|
93
|
+
log: appendLog(prev.log, 'Stopped'),
|
|
94
|
+
}));
|
|
95
|
+
}
|
|
96
|
+
}, []);
|
|
97
|
+
return { state, run, cancel };
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=useAdminJob.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useAdminJob.js","sourceRoot":"","sources":["../../src/admin/useAdminJob.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Internal: run-state bookkeeping shared by the admin job hooks (progress,\n * capped log, error, running flag, cancellation, token acquisition).\n * Not exported from the package.\n */\n\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport { type AdminJobContext, createAdminTokenManager } from './jobs';\nimport type { AdminApi } from './adminApi';\nimport {\n type AbortHandle,\n ADMIN_JOB_LOG_LIMIT,\n type AdminJobState,\n createAbortHandle,\n} from './types';\nimport { errorMessage } from './boardStrings';\n\nconst INITIAL_STATE: AdminJobState = {\n isRunning: false,\n progress: '',\n log: [],\n error: null,\n};\n\n/** Append a line, keeping at most {@link ADMIN_JOB_LOG_LIMIT} lines. */\nexport function appendLog(log: readonly string[], line: string): string[] {\n const next = [...log, line];\n return next.length > ADMIN_JOB_LOG_LIMIT\n ? next.slice(next.length - ADMIN_JOB_LOG_LIMIT)\n : next;\n}\n\ninterface ActiveRun {\n id: number;\n abort: AbortHandle;\n /** Cancelled with `detach`: ignore any further output from this run. */\n detached: boolean;\n}\n\nexport interface AdminJobRunner {\n state: AdminJobState;\n /**\n * Start a run: aborts any previous run, resets state, gets a token, then\n * calls `body` with a job context and an `isLive()` check (false once the\n * run is superseded or detached; gate extra state updates on it). Resolves\n * when the run ends; never rejects.\n */\n run: <T>(\n api: AdminApi,\n getToken: () => Promise<string | null>,\n body: (ctx: AdminJobContext, isLive: () => boolean) => Promise<T>\n ) => Promise<T | undefined>;\n /**\n * Abort the current run. With `detach`, the hook shows \"Stopped\" and stops\n * running at once, ignoring whatever the run still reports; without it, the\n * run's own final message is shown when it ends.\n */\n cancel: (detach: boolean) => void;\n}\n\nexport function useAdminJob(): AdminJobRunner {\n const [state, setState] = useState<AdminJobState>(INITIAL_STATE);\n const activeRef = useRef<ActiveRun | null>(null);\n const nextIdRef = useRef(0);\n\n // Leaving the page stops the job (the web original kept looping).\n useEffect(\n () => () => {\n activeRef.current?.abort.abort();\n activeRef.current = null;\n },\n []\n );\n\n const run = useCallback<AdminJobRunner['run']>(\n async (api, getToken, body) => {\n activeRef.current?.abort.abort();\n const active: ActiveRun = {\n id: ++nextIdRef.current,\n abort: createAbortHandle(),\n detached: false,\n };\n activeRef.current = active;\n const isLive = () => activeRef.current === active && !active.detached;\n\n const onProgress = (message: string) => {\n if (!isLive()) return;\n setState(prev => ({\n ...prev,\n progress: message,\n log: appendLog(prev.log, message),\n }));\n };\n const onError = (error: Error) => {\n if (!isLive()) return;\n const line = `Error: ${error.message}`;\n setState(prev => ({\n ...prev,\n progress: line,\n log: appendLog(prev.log, line),\n error: error.message,\n }));\n };\n\n setState({ isRunning: true, progress: '', log: [], error: null });\n try {\n const token = await createAdminTokenManager(getToken, onProgress);\n if (!token) {\n onError(new Error('Not authenticated'));\n return undefined;\n }\n return await body(\n { api, token, abort: active.abort, onProgress, onError },\n isLive\n );\n } catch (err) {\n onError(err instanceof Error ? err : new Error(errorMessage(err)));\n return undefined;\n } finally {\n if (isLive()) {\n activeRef.current = null;\n setState(prev => ({ ...prev, isRunning: false }));\n }\n }\n },\n []\n );\n\n const cancel = useCallback((detach: boolean) => {\n const active = activeRef.current;\n if (!active) return;\n active.abort.abort();\n if (detach) {\n active.detached = true;\n activeRef.current = null;\n setState(prev => ({\n ...prev,\n isRunning: false,\n progress: 'Stopped',\n log: appendLog(prev.log, 'Stopped'),\n }));\n }\n }, []);\n\n return { state, run, cancel };\n}\n"]}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin: board/example counts and the "update puzzle stats" action, replacing
|
|
3
|
+
* AdminPage's hand-rolled loading state.
|
|
4
|
+
*/
|
|
5
|
+
import type { UpdateStatsData } from '@sudobility/sudojo_types';
|
|
6
|
+
import type { NetworkClient } from '@sudobility/types';
|
|
7
|
+
export interface UseAdminStatsOptions {
|
|
8
|
+
networkClient: NetworkClient;
|
|
9
|
+
baseUrl: string;
|
|
10
|
+
/** Admin ID token; queries stay idle and the action errors without one. */
|
|
11
|
+
token: string | null;
|
|
12
|
+
/** Load `/boards/counts` (boards and techniques sections). Default true. */
|
|
13
|
+
loadBoardCounts?: boolean;
|
|
14
|
+
/** Load `/examples/counts` and `/boards/counts/by-technique` (examples section). Default true. */
|
|
15
|
+
loadTechniqueCounts?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export interface UseAdminStatsResult {
|
|
18
|
+
totalBoards: number;
|
|
19
|
+
boardsWithoutTechniques: number;
|
|
20
|
+
/** First load of the board counts. */
|
|
21
|
+
isBoardCountsLoading: boolean;
|
|
22
|
+
/** Example counts by technique id. */
|
|
23
|
+
exampleCounts: Record<number, number>;
|
|
24
|
+
/** Board counts by technique id. */
|
|
25
|
+
boardCountsByTechnique: Record<number, number>;
|
|
26
|
+
/** First load of either technique counts map. */
|
|
27
|
+
isTechniqueCountsLoading: boolean;
|
|
28
|
+
/** Refetch every enabled count. */
|
|
29
|
+
refetchCounts: () => Promise<void>;
|
|
30
|
+
/** Recalculate level/technique percentages on the server. */
|
|
31
|
+
updatePuzzleStats: () => Promise<void>;
|
|
32
|
+
isUpdatingStats: boolean;
|
|
33
|
+
/** Status line of the last update ('' before the first). */
|
|
34
|
+
statsProgress: string;
|
|
35
|
+
/** Result of the last successful update. */
|
|
36
|
+
statsResult: UpdateStatsData | null;
|
|
37
|
+
}
|
|
38
|
+
/** Status line for an update-stats result. */
|
|
39
|
+
export declare function describeUpdateStatsResult(data: UpdateStatsData): string;
|
|
40
|
+
/**
|
|
41
|
+
* Counts refresh by themselves after the admin jobs save, because
|
|
42
|
+
* sudojo_client's create/update mutations invalidate the `boards` and
|
|
43
|
+
* `examples` queries.
|
|
44
|
+
*/
|
|
45
|
+
export declare function useAdminStats(options: UseAdminStatsOptions): UseAdminStatsResult;
|
|
46
|
+
//# sourceMappingURL=useAdminStats.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useAdminStats.d.ts","sourceRoot":"","sources":["../../src/admin/useAdminStats.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAIvD,MAAM,WAAW,oBAAoB;IACnC,aAAa,EAAE,aAAa,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,2EAA2E;IAC3E,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,4EAA4E;IAC5E,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,kGAAkG;IAClG,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,uBAAuB,EAAE,MAAM,CAAC;IAChC,sCAAsC;IACtC,oBAAoB,EAAE,OAAO,CAAC;IAC9B,sCAAsC;IACtC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,oCAAoC;IACpC,sBAAsB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/C,iDAAiD;IACjD,wBAAwB,EAAE,OAAO,CAAC;IAClC,mCAAmC;IACnC,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnC,6DAA6D;IAC7D,iBAAiB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,eAAe,EAAE,OAAO,CAAC;IACzB,4DAA4D;IAC5D,aAAa,EAAE,MAAM,CAAC;IACtB,4CAA4C;IAC5C,WAAW,EAAE,eAAe,GAAG,IAAI,CAAC;CACrC;AAED,8CAA8C;AAC9C,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,CAIvE;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,oBAAoB,GAC5B,mBAAmB,CAyGrB"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin: board/example counts and the "update puzzle stats" action, replacing
|
|
3
|
+
* AdminPage's hand-rolled loading state.
|
|
4
|
+
*/
|
|
5
|
+
import { useSudojoBoardCounts, useSudojoBoardCountsByTechnique, useSudojoExampleCounts, useSudojoUpdatePuzzleStats, } from '@sudobility/sudojo_client';
|
|
6
|
+
import { useCallback, useMemo, useState } from 'react';
|
|
7
|
+
import { normalizeTechniqueCounts } from './examples';
|
|
8
|
+
/** Status line for an update-stats result. */
|
|
9
|
+
export function describeUpdateStatsResult(data) {
|
|
10
|
+
const levelCount = Object.keys(data.levels).length;
|
|
11
|
+
const techCount = Object.keys(data.techniques).length;
|
|
12
|
+
return `Updated ${levelCount} levels and ${techCount} techniques.`;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Counts refresh by themselves after the admin jobs save, because
|
|
16
|
+
* sudojo_client's create/update mutations invalidate the `boards` and
|
|
17
|
+
* `examples` queries.
|
|
18
|
+
*/
|
|
19
|
+
export function useAdminStats(options) {
|
|
20
|
+
const { networkClient, baseUrl, token, loadBoardCounts = true, loadTechniqueCounts = true, } = options;
|
|
21
|
+
const authToken = token ?? '';
|
|
22
|
+
const boardCounts = useSudojoBoardCounts(networkClient, baseUrl, authToken, {
|
|
23
|
+
enabled: !!token && loadBoardCounts,
|
|
24
|
+
});
|
|
25
|
+
const exampleCountsQuery = useSudojoExampleCounts(networkClient, baseUrl, authToken, { enabled: !!token && loadTechniqueCounts });
|
|
26
|
+
const byTechniqueQuery = useSudojoBoardCountsByTechnique(networkClient, baseUrl, authToken, { enabled: !!token && loadTechniqueCounts });
|
|
27
|
+
const updateStats = useSudojoUpdatePuzzleStats(networkClient, baseUrl);
|
|
28
|
+
const [statsProgress, setStatsProgress] = useState('');
|
|
29
|
+
const [statsResult, setStatsResult] = useState(null);
|
|
30
|
+
const exampleData = exampleCountsQuery.data;
|
|
31
|
+
const exampleCounts = useMemo(() => normalizeTechniqueCounts(exampleData?.success ? exampleData.data : undefined), [exampleData]);
|
|
32
|
+
const byTechniqueData = byTechniqueQuery.data;
|
|
33
|
+
const boardCountsByTechnique = useMemo(() => normalizeTechniqueCounts(byTechniqueData?.success ? byTechniqueData.data : undefined), [byTechniqueData]);
|
|
34
|
+
const counts = boardCounts.data?.success ? boardCounts.data.data : null;
|
|
35
|
+
const { refetch: refetchBoardCounts } = boardCounts;
|
|
36
|
+
const { refetch: refetchExampleCounts } = exampleCountsQuery;
|
|
37
|
+
const { refetch: refetchByTechnique } = byTechniqueQuery;
|
|
38
|
+
const refetchCounts = useCallback(async () => {
|
|
39
|
+
if (!token)
|
|
40
|
+
return;
|
|
41
|
+
const pending = [];
|
|
42
|
+
if (loadBoardCounts)
|
|
43
|
+
pending.push(refetchBoardCounts());
|
|
44
|
+
if (loadTechniqueCounts) {
|
|
45
|
+
pending.push(refetchExampleCounts(), refetchByTechnique());
|
|
46
|
+
}
|
|
47
|
+
await Promise.all(pending);
|
|
48
|
+
}, [
|
|
49
|
+
token,
|
|
50
|
+
loadBoardCounts,
|
|
51
|
+
loadTechniqueCounts,
|
|
52
|
+
refetchBoardCounts,
|
|
53
|
+
refetchExampleCounts,
|
|
54
|
+
refetchByTechnique,
|
|
55
|
+
]);
|
|
56
|
+
const { mutateAsync: updateStatsAsync, isPending: isUpdatingStats } = updateStats;
|
|
57
|
+
const updatePuzzleStats = useCallback(async () => {
|
|
58
|
+
if (!token) {
|
|
59
|
+
setStatsProgress('Error: Not authenticated');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
setStatsProgress('Calculating puzzle stats...');
|
|
63
|
+
try {
|
|
64
|
+
const response = await updateStatsAsync({ token });
|
|
65
|
+
if (response.success && response.data) {
|
|
66
|
+
setStatsResult(response.data);
|
|
67
|
+
setStatsProgress(describeUpdateStatsResult(response.data));
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
setStatsProgress('Error: Failed to update stats');
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
setStatsProgress(`Error: ${err instanceof Error ? err.message : 'Unknown error'}`);
|
|
75
|
+
}
|
|
76
|
+
}, [token, updateStatsAsync]);
|
|
77
|
+
return {
|
|
78
|
+
totalBoards: counts?.total ?? 0,
|
|
79
|
+
boardsWithoutTechniques: counts?.withoutTechniques ?? 0,
|
|
80
|
+
isBoardCountsLoading: boardCounts.isLoading,
|
|
81
|
+
exampleCounts,
|
|
82
|
+
boardCountsByTechnique,
|
|
83
|
+
isTechniqueCountsLoading: exampleCountsQuery.isLoading || byTechniqueQuery.isLoading,
|
|
84
|
+
refetchCounts,
|
|
85
|
+
updatePuzzleStats,
|
|
86
|
+
isUpdatingStats,
|
|
87
|
+
statsProgress,
|
|
88
|
+
statsResult,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=useAdminStats.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useAdminStats.js","sourceRoot":"","sources":["../../src/admin/useAdminStats.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Admin: board/example counts and the \"update puzzle stats\" action, replacing\n * AdminPage's hand-rolled loading state.\n */\n\nimport {\n useSudojoBoardCounts,\n useSudojoBoardCountsByTechnique,\n useSudojoExampleCounts,\n useSudojoUpdatePuzzleStats,\n} from '@sudobility/sudojo_client';\nimport type { UpdateStatsData } from '@sudobility/sudojo_types';\nimport type { NetworkClient } from '@sudobility/types';\nimport { useCallback, useMemo, useState } from 'react';\nimport { normalizeTechniqueCounts } from './examples';\n\nexport interface UseAdminStatsOptions {\n networkClient: NetworkClient;\n baseUrl: string;\n /** Admin ID token; queries stay idle and the action errors without one. */\n token: string | null;\n /** Load `/boards/counts` (boards and techniques sections). Default true. */\n loadBoardCounts?: boolean;\n /** Load `/examples/counts` and `/boards/counts/by-technique` (examples section). Default true. */\n loadTechniqueCounts?: boolean;\n}\n\nexport interface UseAdminStatsResult {\n totalBoards: number;\n boardsWithoutTechniques: number;\n /** First load of the board counts. */\n isBoardCountsLoading: boolean;\n /** Example counts by technique id. */\n exampleCounts: Record<number, number>;\n /** Board counts by technique id. */\n boardCountsByTechnique: Record<number, number>;\n /** First load of either technique counts map. */\n isTechniqueCountsLoading: boolean;\n /** Refetch every enabled count. */\n refetchCounts: () => Promise<void>;\n\n /** Recalculate level/technique percentages on the server. */\n updatePuzzleStats: () => Promise<void>;\n isUpdatingStats: boolean;\n /** Status line of the last update ('' before the first). */\n statsProgress: string;\n /** Result of the last successful update. */\n statsResult: UpdateStatsData | null;\n}\n\n/** Status line for an update-stats result. */\nexport function describeUpdateStatsResult(data: UpdateStatsData): string {\n const levelCount = Object.keys(data.levels).length;\n const techCount = Object.keys(data.techniques).length;\n return `Updated ${levelCount} levels and ${techCount} techniques.`;\n}\n\n/**\n * Counts refresh by themselves after the admin jobs save, because\n * sudojo_client's create/update mutations invalidate the `boards` and\n * `examples` queries.\n */\nexport function useAdminStats(\n options: UseAdminStatsOptions\n): UseAdminStatsResult {\n const {\n networkClient,\n baseUrl,\n token,\n loadBoardCounts = true,\n loadTechniqueCounts = true,\n } = options;\n const authToken = token ?? '';\n\n const boardCounts = useSudojoBoardCounts(networkClient, baseUrl, authToken, {\n enabled: !!token && loadBoardCounts,\n });\n const exampleCountsQuery = useSudojoExampleCounts(\n networkClient,\n baseUrl,\n authToken,\n { enabled: !!token && loadTechniqueCounts }\n );\n const byTechniqueQuery = useSudojoBoardCountsByTechnique(\n networkClient,\n baseUrl,\n authToken,\n { enabled: !!token && loadTechniqueCounts }\n );\n const updateStats = useSudojoUpdatePuzzleStats(networkClient, baseUrl);\n\n const [statsProgress, setStatsProgress] = useState('');\n const [statsResult, setStatsResult] = useState<UpdateStatsData | null>(null);\n\n const exampleData = exampleCountsQuery.data;\n const exampleCounts = useMemo(\n () =>\n normalizeTechniqueCounts(\n exampleData?.success ? exampleData.data : undefined\n ),\n [exampleData]\n );\n const byTechniqueData = byTechniqueQuery.data;\n const boardCountsByTechnique = useMemo(\n () =>\n normalizeTechniqueCounts(\n byTechniqueData?.success ? byTechniqueData.data : undefined\n ),\n [byTechniqueData]\n );\n const counts = boardCounts.data?.success ? boardCounts.data.data : null;\n\n const { refetch: refetchBoardCounts } = boardCounts;\n const { refetch: refetchExampleCounts } = exampleCountsQuery;\n const { refetch: refetchByTechnique } = byTechniqueQuery;\n const refetchCounts = useCallback(async () => {\n if (!token) return;\n const pending: Promise<unknown>[] = [];\n if (loadBoardCounts) pending.push(refetchBoardCounts());\n if (loadTechniqueCounts) {\n pending.push(refetchExampleCounts(), refetchByTechnique());\n }\n await Promise.all(pending);\n }, [\n token,\n loadBoardCounts,\n loadTechniqueCounts,\n refetchBoardCounts,\n refetchExampleCounts,\n refetchByTechnique,\n ]);\n\n const { mutateAsync: updateStatsAsync, isPending: isUpdatingStats } =\n updateStats;\n const updatePuzzleStats = useCallback(async () => {\n if (!token) {\n setStatsProgress('Error: Not authenticated');\n return;\n }\n setStatsProgress('Calculating puzzle stats...');\n try {\n const response = await updateStatsAsync({ token });\n if (response.success && response.data) {\n setStatsResult(response.data);\n setStatsProgress(describeUpdateStatsResult(response.data));\n } else {\n setStatsProgress('Error: Failed to update stats');\n }\n } catch (err) {\n setStatsProgress(\n `Error: ${err instanceof Error ? err.message : 'Unknown error'}`\n );\n }\n }, [token, updateStatsAsync]);\n\n return {\n totalBoards: counts?.total ?? 0,\n boardsWithoutTechniques: counts?.withoutTechniques ?? 0,\n isBoardCountsLoading: boardCounts.isLoading,\n exampleCounts,\n boardCountsByTechnique,\n isTechniqueCountsLoading:\n exampleCountsQuery.isLoading || byTechniqueQuery.isLoading,\n refetchCounts,\n updatePuzzleStats,\n isUpdatingStats,\n statsProgress,\n statsResult,\n };\n}\n"]}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin: generate boards continuously until stopped.
|
|
3
|
+
*/
|
|
4
|
+
import type { AdminJobOptions, AdminJobState } from './types';
|
|
5
|
+
export type UseBoardGeneratorOptions = AdminJobOptions;
|
|
6
|
+
export interface GeneratedBoardInfo {
|
|
7
|
+
level: number;
|
|
8
|
+
/** Exact techniques bitmask */
|
|
9
|
+
techniques: bigint;
|
|
10
|
+
}
|
|
11
|
+
export interface UseBoardGeneratorResult extends AdminJobState {
|
|
12
|
+
/** Boards saved by the current/last run. */
|
|
13
|
+
generatedCount: number;
|
|
14
|
+
/** Last saved board of the current/last run. */
|
|
15
|
+
lastBoard: GeneratedBoardInfo | null;
|
|
16
|
+
/** Start generating; resolves with the number saved when the run ends. */
|
|
17
|
+
start: (options: {
|
|
18
|
+
symmetrical: boolean;
|
|
19
|
+
}) => Promise<number>;
|
|
20
|
+
/** Stop after the board in progress; shows "Stopped. Generated N boards." */
|
|
21
|
+
cancel: () => void;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Generate → validate → save boards until `cancel()` or a save fails.
|
|
25
|
+
* Board count queries refresh on their own (each save invalidates
|
|
26
|
+
* sudojo_client's `boards` queries).
|
|
27
|
+
*/
|
|
28
|
+
export declare function useBoardGenerator(options: UseBoardGeneratorOptions): UseBoardGeneratorResult;
|
|
29
|
+
//# sourceMappingURL=useBoardGenerator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useBoardGenerator.d.ts","sourceRoot":"","sources":["../../src/admin/useBoardGenerator.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAG9D,MAAM,MAAM,wBAAwB,GAAG,eAAe,CAAC;AAEvD,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,+BAA+B;IAC/B,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,uBAAwB,SAAQ,aAAa;IAC5D,4CAA4C;IAC5C,cAAc,EAAE,MAAM,CAAC;IACvB,gDAAgD;IAChD,SAAS,EAAE,kBAAkB,GAAG,IAAI,CAAC;IACrC,0EAA0E;IAC1E,KAAK,EAAE,CAAC,OAAO,EAAE;QAAE,WAAW,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9D,6EAA6E;IAC7E,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,wBAAwB,GAChC,uBAAuB,CAmCzB"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin: generate boards continuously until stopped.
|
|
3
|
+
*/
|
|
4
|
+
import { useCallback, useRef, useState } from 'react';
|
|
5
|
+
import { useAdminApi } from './adminApi';
|
|
6
|
+
import { runBoardGeneration } from './jobs';
|
|
7
|
+
import { useAdminJob } from './useAdminJob';
|
|
8
|
+
/**
|
|
9
|
+
* Generate → validate → save boards until `cancel()` or a save fails.
|
|
10
|
+
* Board count queries refresh on their own (each save invalidates
|
|
11
|
+
* sudojo_client's `boards` queries).
|
|
12
|
+
*/
|
|
13
|
+
export function useBoardGenerator(options) {
|
|
14
|
+
const api = useAdminApi(options.networkClient, options.baseUrl);
|
|
15
|
+
const job = useAdminJob();
|
|
16
|
+
const [generatedCount, setGeneratedCount] = useState(0);
|
|
17
|
+
const [lastBoard, setLastBoard] = useState(null);
|
|
18
|
+
const getTokenRef = useRef(options.getToken);
|
|
19
|
+
getTokenRef.current = options.getToken;
|
|
20
|
+
const { run, cancel: cancelJob } = job;
|
|
21
|
+
const start = useCallback(async ({ symmetrical }) => {
|
|
22
|
+
setGeneratedCount(0);
|
|
23
|
+
setLastBoard(null);
|
|
24
|
+
const count = await run(api, () => getTokenRef.current(), (ctx, isLive) => runBoardGeneration(ctx, {
|
|
25
|
+
symmetrical,
|
|
26
|
+
onBoardGenerated: (n, level, techniques) => {
|
|
27
|
+
if (!isLive())
|
|
28
|
+
return;
|
|
29
|
+
setGeneratedCount(n);
|
|
30
|
+
setLastBoard({ level, techniques });
|
|
31
|
+
},
|
|
32
|
+
}));
|
|
33
|
+
return count ?? 0;
|
|
34
|
+
}, [api, run]);
|
|
35
|
+
const cancel = useCallback(() => cancelJob(false), [cancelJob]);
|
|
36
|
+
return { ...job.state, generatedCount, lastBoard, start, cancel };
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=useBoardGenerator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useBoardGenerator.js","sourceRoot":"","sources":["../../src/admin/useBoardGenerator.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,kBAAkB,EAAE,MAAM,QAAQ,CAAC;AAE5C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAqB5C;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAC/B,OAAiC;IAEjC,MAAM,GAAG,GAAG,WAAW,CAAC,OAAO,CAAC,aAAa,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;IAChE,MAAM,GAAG,GAAG,WAAW,EAAE,CAAC;IAC1B,MAAM,CAAC,cAAc,EAAE,iBAAiB,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;IACxD,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAA4B,IAAI,CAAC,CAAC;IAC5E,MAAM,WAAW,GAAG,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC7C,WAAW,CAAC,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC;IAEvC,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,GAAG,CAAC;IAEvC,MAAM,KAAK,GAAG,WAAW,CACvB,KAAK,EAAE,EAAE,WAAW,EAA4B,EAAE,EAAE;QAClD,iBAAiB,CAAC,CAAC,CAAC,CAAC;QACrB,YAAY,CAAC,IAAI,CAAC,CAAC;QACnB,MAAM,KAAK,GAAG,MAAM,GAAG,CACrB,GAAG,EACH,GAAG,EAAE,CAAC,WAAW,CAAC,OAAO,EAAE,EAC3B,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,CACd,kBAAkB,CAAC,GAAG,EAAE;YACtB,WAAW;YACX,gBAAgB,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE;gBACzC,IAAI,CAAC,MAAM,EAAE;oBAAE,OAAO;gBACtB,iBAAiB,CAAC,CAAC,CAAC,CAAC;gBACrB,YAAY,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,CAAC;YACtC,CAAC;SACF,CAAC,CACL,CAAC;QACF,OAAO,KAAK,IAAI,CAAC,CAAC;IACpB,CAAC,EACD,CAAC,GAAG,EAAE,GAAG,CAAC,CACX,CAAC;IAEF,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC;IAEhE,OAAO,EAAE,GAAG,GAAG,CAAC,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;AACpE,CAAC","sourcesContent":["/**\n * Admin: generate boards continuously until stopped.\n */\n\nimport { useCallback, useRef, useState } from 'react';\nimport { useAdminApi } from './adminApi';\nimport { runBoardGeneration } from './jobs';\nimport type { AdminJobOptions, AdminJobState } from './types';\nimport { useAdminJob } from './useAdminJob';\n\nexport type UseBoardGeneratorOptions = AdminJobOptions;\n\nexport interface GeneratedBoardInfo {\n level: number;\n /** Exact techniques bitmask */\n techniques: bigint;\n}\n\nexport interface UseBoardGeneratorResult extends AdminJobState {\n /** Boards saved by the current/last run. */\n generatedCount: number;\n /** Last saved board of the current/last run. */\n lastBoard: GeneratedBoardInfo | null;\n /** Start generating; resolves with the number saved when the run ends. */\n start: (options: { symmetrical: boolean }) => Promise<number>;\n /** Stop after the board in progress; shows \"Stopped. Generated N boards.\" */\n cancel: () => void;\n}\n\n/**\n * Generate → validate → save boards until `cancel()` or a save fails.\n * Board count queries refresh on their own (each save invalidates\n * sudojo_client's `boards` queries).\n */\nexport function useBoardGenerator(\n options: UseBoardGeneratorOptions\n): UseBoardGeneratorResult {\n const api = useAdminApi(options.networkClient, options.baseUrl);\n const job = useAdminJob();\n const [generatedCount, setGeneratedCount] = useState(0);\n const [lastBoard, setLastBoard] = useState<GeneratedBoardInfo | null>(null);\n const getTokenRef = useRef(options.getToken);\n getTokenRef.current = options.getToken;\n\n const { run, cancel: cancelJob } = job;\n\n const start = useCallback(\n async ({ symmetrical }: { symmetrical: boolean }) => {\n setGeneratedCount(0);\n setLastBoard(null);\n const count = await run(\n api,\n () => getTokenRef.current(),\n (ctx, isLive) =>\n runBoardGeneration(ctx, {\n symmetrical,\n onBoardGenerated: (n, level, techniques) => {\n if (!isLive()) return;\n setGeneratedCount(n);\n setLastBoard({ level, techniques });\n },\n })\n );\n return count ?? 0;\n },\n [api, run]\n );\n\n const cancel = useCallback(() => cancelJob(false), [cancelJob]);\n\n return { ...job.state, generatedCount, lastBoard, start, cancel };\n}\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin: one-off board technique calls (exact bitmasks sent as strings).
|
|
3
|
+
*/
|
|
4
|
+
import type { Board, TechniqueId } from '@sudobility/sudojo_types';
|
|
5
|
+
import type { AdminJobOptions } from './types';
|
|
6
|
+
export type UseBoardTechniquesUpdaterOptions = AdminJobOptions;
|
|
7
|
+
export interface UseBoardTechniquesUpdaterResult {
|
|
8
|
+
/** Set a board's techniques bitmask and level. false on any failure or no token. */
|
|
9
|
+
updateBoardTechniques: (boardUuid: string, techniques: bigint, level: number | null) => Promise<boolean>;
|
|
10
|
+
/** Boards with the technique's bit set, any level. [] on any failure or no token. */
|
|
11
|
+
fetchBoardsWithTechnique: (techniqueId: TechniqueId, limit: number) => Promise<Board[]>;
|
|
12
|
+
}
|
|
13
|
+
export declare function useBoardTechniquesUpdater(options: UseBoardTechniquesUpdaterOptions): UseBoardTechniquesUpdaterResult;
|
|
14
|
+
//# sourceMappingURL=useBoardTechniquesUpdater.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useBoardTechniquesUpdater.d.ts","sourceRoot":"","sources":["../../src/admin/useBoardTechniquesUpdater.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAInE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C,MAAM,MAAM,gCAAgC,GAAG,eAAe,CAAC;AAE/D,MAAM,WAAW,+BAA+B;IAC9C,oFAAoF;IACpF,qBAAqB,EAAE,CACrB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,MAAM,GAAG,IAAI,KACjB,OAAO,CAAC,OAAO,CAAC,CAAC;IACtB,qFAAqF;IACrF,wBAAwB,EAAE,CACxB,WAAW,EAAE,WAAW,EACxB,KAAK,EAAE,MAAM,KACV,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;CACvB;AAED,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,gCAAgC,GACxC,+BAA+B,CA2BjC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin: one-off board technique calls (exact bitmasks sent as strings).
|
|
3
|
+
*/
|
|
4
|
+
import { useCallback, useRef } from 'react';
|
|
5
|
+
import { useAdminApi } from './adminApi';
|
|
6
|
+
import { fetchBoardsWithTechnique, updateBoardTechniques } from './jobs';
|
|
7
|
+
export function useBoardTechniquesUpdater(options) {
|
|
8
|
+
const api = useAdminApi(options.networkClient, options.baseUrl);
|
|
9
|
+
const getTokenRef = useRef(options.getToken);
|
|
10
|
+
getTokenRef.current = options.getToken;
|
|
11
|
+
const update = useCallback(async (boardUuid, techniques, level) => {
|
|
12
|
+
const token = await getTokenRef.current();
|
|
13
|
+
if (!token)
|
|
14
|
+
return false;
|
|
15
|
+
return updateBoardTechniques(api, token, boardUuid, techniques, level);
|
|
16
|
+
}, [api]);
|
|
17
|
+
const fetchWithTechnique = useCallback(async (techniqueId, limit) => {
|
|
18
|
+
const token = await getTokenRef.current();
|
|
19
|
+
if (!token)
|
|
20
|
+
return [];
|
|
21
|
+
return fetchBoardsWithTechnique(api, token, techniqueId, limit);
|
|
22
|
+
}, [api]);
|
|
23
|
+
return {
|
|
24
|
+
updateBoardTechniques: update,
|
|
25
|
+
fetchBoardsWithTechnique: fetchWithTechnique,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=useBoardTechniquesUpdater.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useBoardTechniquesUpdater.js","sourceRoot":"","sources":["../../src/admin/useBoardTechniquesUpdater.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,MAAM,QAAQ,CAAC;AAmBzE,MAAM,UAAU,yBAAyB,CACvC,OAAyC;IAEzC,MAAM,GAAG,GAAG,WAAW,CAAC,OAAO,CAAC,aAAa,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;IAChE,MAAM,WAAW,GAAG,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC7C,WAAW,CAAC,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC;IAEvC,MAAM,MAAM,GAAG,WAAW,CACxB,KAAK,EAAE,SAAiB,EAAE,UAAkB,EAAE,KAAoB,EAAE,EAAE;QACpE,MAAM,KAAK,GAAG,MAAM,WAAW,CAAC,OAAO,EAAE,CAAC;QAC1C,IAAI,CAAC,KAAK;YAAE,OAAO,KAAK,CAAC;QACzB,OAAO,qBAAqB,CAAC,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,KAAK,CAAC,CAAC;IACzE,CAAC,EACD,CAAC,GAAG,CAAC,CACN,CAAC;IAEF,MAAM,kBAAkB,GAAG,WAAW,CACpC,KAAK,EAAE,WAAwB,EAAE,KAAa,EAAE,EAAE;QAChD,MAAM,KAAK,GAAG,MAAM,WAAW,CAAC,OAAO,EAAE,CAAC;QAC1C,IAAI,CAAC,KAAK;YAAE,OAAO,EAAE,CAAC;QACtB,OAAO,wBAAwB,CAAC,GAAG,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,CAAC,CAAC;IAClE,CAAC,EACD,CAAC,GAAG,CAAC,CACN,CAAC;IAEF,OAAO;QACL,qBAAqB,EAAE,MAAM;QAC7B,wBAAwB,EAAE,kBAAkB;KAC7C,CAAC;AACJ,CAAC","sourcesContent":["/**\n * Admin: one-off board technique calls (exact bitmasks sent as strings).\n */\n\nimport type { Board, TechniqueId } from '@sudobility/sudojo_types';\nimport { useCallback, useRef } from 'react';\nimport { useAdminApi } from './adminApi';\nimport { fetchBoardsWithTechnique, updateBoardTechniques } from './jobs';\nimport type { AdminJobOptions } from './types';\n\nexport type UseBoardTechniquesUpdaterOptions = AdminJobOptions;\n\nexport interface UseBoardTechniquesUpdaterResult {\n /** Set a board's techniques bitmask and level. false on any failure or no token. */\n updateBoardTechniques: (\n boardUuid: string,\n techniques: bigint,\n level: number | null\n ) => Promise<boolean>;\n /** Boards with the technique's bit set, any level. [] on any failure or no token. */\n fetchBoardsWithTechnique: (\n techniqueId: TechniqueId,\n limit: number\n ) => Promise<Board[]>;\n}\n\nexport function useBoardTechniquesUpdater(\n options: UseBoardTechniquesUpdaterOptions\n): UseBoardTechniquesUpdaterResult {\n const api = useAdminApi(options.networkClient, options.baseUrl);\n const getTokenRef = useRef(options.getToken);\n getTokenRef.current = options.getToken;\n\n const update = useCallback(\n async (boardUuid: string, techniques: bigint, level: number | null) => {\n const token = await getTokenRef.current();\n if (!token) return false;\n return updateBoardTechniques(api, token, boardUuid, techniques, level);\n },\n [api]\n );\n\n const fetchWithTechnique = useCallback(\n async (techniqueId: TechniqueId, limit: number) => {\n const token = await getTokenRef.current();\n if (!token) return [];\n return fetchBoardsWithTechnique(api, token, techniqueId, limit);\n },\n [api]\n );\n\n return {\n updateBoardTechniques: update,\n fetchBoardsWithTechnique: fetchWithTechnique,\n };\n}\n"]}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin: create technique examples (each with a linked practice).
|
|
3
|
+
*/
|
|
4
|
+
import type { TechniqueId } from '@sudobility/sudojo_types';
|
|
5
|
+
import { type ExampleCreationRequest } from './jobs';
|
|
6
|
+
import type { AdminJobOptions, AdminJobState } from './types';
|
|
7
|
+
export type UseExampleCreatorOptions = AdminJobOptions;
|
|
8
|
+
export interface UseExampleCreatorResult extends AdminJobState {
|
|
9
|
+
/**
|
|
10
|
+
* Counts the run works from: `currentCounts` at start plus every example
|
|
11
|
+
* saved since. `{}` before the first run.
|
|
12
|
+
*/
|
|
13
|
+
counts: Record<number, number>;
|
|
14
|
+
/** Technique of a running `bit`/`level` run; null for `all` or when idle. */
|
|
15
|
+
creatingTechniqueId: TechniqueId | null;
|
|
16
|
+
/** Boards reset to techniques 0 by the current/last run. */
|
|
17
|
+
resetBoardCount: number;
|
|
18
|
+
/** Start a run; resolves with the final counts when it ends. */
|
|
19
|
+
start: (request: ExampleCreationRequest) => Promise<Record<number, number>>;
|
|
20
|
+
/** Stop now: shows "Stopped" and ignores the board in flight. */
|
|
21
|
+
cancel: () => void;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Wraps {@link runExampleCreation}. Example counts queries refresh on their
|
|
25
|
+
* own (each save invalidates sudojo_client's `examples` queries), and a reset
|
|
26
|
+
* board refreshes the `boards` counts.
|
|
27
|
+
*/
|
|
28
|
+
export declare function useExampleCreator(options: UseExampleCreatorOptions): UseExampleCreatorResult;
|
|
29
|
+
//# sourceMappingURL=useExampleCreator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useExampleCreator.d.ts","sourceRoot":"","sources":["../../src/admin/useExampleCreator.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAG5D,OAAO,EAAE,KAAK,sBAAsB,EAAsB,MAAM,QAAQ,CAAC;AACzE,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAG9D,MAAM,MAAM,wBAAwB,GAAG,eAAe,CAAC;AAEvD,MAAM,WAAW,uBAAwB,SAAQ,aAAa;IAC5D;;;OAGG;IACH,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,6EAA6E;IAC7E,mBAAmB,EAAE,WAAW,GAAG,IAAI,CAAC;IACxC,4DAA4D;IAC5D,eAAe,EAAE,MAAM,CAAC;IACxB,gEAAgE;IAChE,KAAK,EAAE,CAAC,OAAO,EAAE,sBAAsB,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC5E,iEAAiE;IACjE,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,wBAAwB,GAChC,uBAAuB,CAyDzB"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin: create technique examples (each with a linked practice).
|
|
3
|
+
*/
|
|
4
|
+
import { useCallback, useRef, useState } from 'react';
|
|
5
|
+
import { useAdminApi } from './adminApi';
|
|
6
|
+
import { runExampleCreation } from './jobs';
|
|
7
|
+
import { useAdminJob } from './useAdminJob';
|
|
8
|
+
/**
|
|
9
|
+
* Wraps {@link runExampleCreation}. Example counts queries refresh on their
|
|
10
|
+
* own (each save invalidates sudojo_client's `examples` queries), and a reset
|
|
11
|
+
* board refreshes the `boards` counts.
|
|
12
|
+
*/
|
|
13
|
+
export function useExampleCreator(options) {
|
|
14
|
+
const api = useAdminApi(options.networkClient, options.baseUrl);
|
|
15
|
+
const job = useAdminJob();
|
|
16
|
+
const [counts, setCounts] = useState({});
|
|
17
|
+
const [creatingTechniqueId, setCreatingTechniqueId] = useState(null);
|
|
18
|
+
const [resetBoardCount, setResetBoardCount] = useState(0);
|
|
19
|
+
const getTokenRef = useRef(options.getToken);
|
|
20
|
+
getTokenRef.current = options.getToken;
|
|
21
|
+
const startSeqRef = useRef(0);
|
|
22
|
+
const { run, cancel: cancelJob } = job;
|
|
23
|
+
const start = useCallback(async (request) => {
|
|
24
|
+
const seq = ++startSeqRef.current;
|
|
25
|
+
setCounts({ ...request.currentCounts });
|
|
26
|
+
setResetBoardCount(0);
|
|
27
|
+
setCreatingTechniqueId(request.mode === 'all' ? null : request.techniqueId);
|
|
28
|
+
const result = await run(api, () => getTokenRef.current(), (ctx, isLive) => runExampleCreation(ctx, request, {
|
|
29
|
+
onExampleSaved: (techniqueId, newCount) => {
|
|
30
|
+
if (isLive()) {
|
|
31
|
+
setCounts(prev => ({ ...prev, [techniqueId]: newCount }));
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
onBoardTechniquesReset: boardUuid => {
|
|
35
|
+
if (!isLive())
|
|
36
|
+
return;
|
|
37
|
+
setResetBoardCount(n => n + 1);
|
|
38
|
+
console.log(`Reset techniques for board ${boardUuid}`);
|
|
39
|
+
},
|
|
40
|
+
}));
|
|
41
|
+
if (startSeqRef.current === seq)
|
|
42
|
+
setCreatingTechniqueId(null);
|
|
43
|
+
return result ?? { ...request.currentCounts };
|
|
44
|
+
}, [api, run]);
|
|
45
|
+
const cancel = useCallback(() => {
|
|
46
|
+
cancelJob(true);
|
|
47
|
+
setCreatingTechniqueId(null);
|
|
48
|
+
}, [cancelJob]);
|
|
49
|
+
return {
|
|
50
|
+
...job.state,
|
|
51
|
+
counts,
|
|
52
|
+
creatingTechniqueId,
|
|
53
|
+
resetBoardCount,
|
|
54
|
+
start,
|
|
55
|
+
cancel,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=useExampleCreator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useExampleCreator.js","sourceRoot":"","sources":["../../src/admin/useExampleCreator.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Admin: create technique examples (each with a linked practice).\n */\n\nimport type { TechniqueId } from '@sudobility/sudojo_types';\nimport { useCallback, useRef, useState } from 'react';\nimport { useAdminApi } from './adminApi';\nimport { type ExampleCreationRequest, runExampleCreation } from './jobs';\nimport type { AdminJobOptions, AdminJobState } from './types';\nimport { useAdminJob } from './useAdminJob';\n\nexport type UseExampleCreatorOptions = AdminJobOptions;\n\nexport interface UseExampleCreatorResult extends AdminJobState {\n /**\n * Counts the run works from: `currentCounts` at start plus every example\n * saved since. `{}` before the first run.\n */\n counts: Record<number, number>;\n /** Technique of a running `bit`/`level` run; null for `all` or when idle. */\n creatingTechniqueId: TechniqueId | null;\n /** Boards reset to techniques 0 by the current/last run. */\n resetBoardCount: number;\n /** Start a run; resolves with the final counts when it ends. */\n start: (request: ExampleCreationRequest) => Promise<Record<number, number>>;\n /** Stop now: shows \"Stopped\" and ignores the board in flight. */\n cancel: () => void;\n}\n\n/**\n * Wraps {@link runExampleCreation}. Example counts queries refresh on their\n * own (each save invalidates sudojo_client's `examples` queries), and a reset\n * board refreshes the `boards` counts.\n */\nexport function useExampleCreator(\n options: UseExampleCreatorOptions\n): UseExampleCreatorResult {\n const api = useAdminApi(options.networkClient, options.baseUrl);\n const job = useAdminJob();\n const [counts, setCounts] = useState<Record<number, number>>({});\n const [creatingTechniqueId, setCreatingTechniqueId] =\n useState<TechniqueId | null>(null);\n const [resetBoardCount, setResetBoardCount] = useState(0);\n const getTokenRef = useRef(options.getToken);\n getTokenRef.current = options.getToken;\n const startSeqRef = useRef(0);\n\n const { run, cancel: cancelJob } = job;\n\n const start = useCallback(\n async (request: ExampleCreationRequest) => {\n const seq = ++startSeqRef.current;\n setCounts({ ...request.currentCounts });\n setResetBoardCount(0);\n setCreatingTechniqueId(\n request.mode === 'all' ? null : request.techniqueId\n );\n const result = await run(\n api,\n () => getTokenRef.current(),\n (ctx, isLive) =>\n runExampleCreation(ctx, request, {\n onExampleSaved: (techniqueId, newCount) => {\n if (isLive()) {\n setCounts(prev => ({ ...prev, [techniqueId]: newCount }));\n }\n },\n onBoardTechniquesReset: boardUuid => {\n if (!isLive()) return;\n setResetBoardCount(n => n + 1);\n console.log(`Reset techniques for board ${boardUuid}`);\n },\n })\n );\n if (startSeqRef.current === seq) setCreatingTechniqueId(null);\n return result ?? { ...request.currentCounts };\n },\n [api, run]\n );\n\n const cancel = useCallback(() => {\n cancelJob(true);\n setCreatingTechniqueId(null);\n }, [cancelJob]);\n\n return {\n ...job.state,\n counts,\n creatingTechniqueId,\n resetBoardCount,\n start,\n cancel,\n };\n}\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin: extract one board's techniques, or find a technique example in it.
|
|
3
|
+
*/
|
|
4
|
+
import type { TechniqueId } from '@sudobility/sudojo_types';
|
|
5
|
+
import { type BoardExtractionOutcome } from './extraction';
|
|
6
|
+
import type { AdminJobOptions } from './types';
|
|
7
|
+
export type UseSingleBoardExtractorOptions = AdminJobOptions;
|
|
8
|
+
export interface UseSingleBoardExtractorResult {
|
|
9
|
+
/**
|
|
10
|
+
* Walk `original` with /solve. Without `desiredTechnique`: the accumulated
|
|
11
|
+
* `{ techniques, level }`. With it: `{ type: 'found', … }` at the first
|
|
12
|
+
* matching hint, else the accumulated result. `{ error }` on invalid
|
|
13
|
+
* pencilmarks, a 400, no token, or after 200 steps.
|
|
14
|
+
*/
|
|
15
|
+
extract: (original: string, desiredTechnique?: TechniqueId) => Promise<BoardExtractionOutcome>;
|
|
16
|
+
}
|
|
17
|
+
export declare function useSingleBoardExtractor(options: UseSingleBoardExtractorOptions): UseSingleBoardExtractorResult;
|
|
18
|
+
//# sourceMappingURL=useSingleBoardExtractor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useSingleBoardExtractor.d.ts","sourceRoot":"","sources":["../../src/admin/useSingleBoardExtractor.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAG5D,OAAO,EACL,KAAK,sBAAsB,EAE5B,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C,MAAM,MAAM,8BAA8B,GAAG,eAAe,CAAC;AAE7D,MAAM,WAAW,6BAA6B;IAC5C;;;;;OAKG;IACH,OAAO,EAAE,CACP,QAAQ,EAAE,MAAM,EAChB,gBAAgB,CAAC,EAAE,WAAW,KAC3B,OAAO,CAAC,sBAAsB,CAAC,CAAC;CACtC;AAED,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,8BAA8B,GACtC,6BAA6B,CAsB/B"}
|