@orjok/commons 1.0.2 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-DB3CMKPP.cjs +165 -0
- package/dist/chunk-DB3CMKPP.cjs.map +1 -0
- package/dist/chunk-ZDGYAVEC.js +165 -0
- package/dist/chunk-ZDGYAVEC.js.map +1 -0
- package/dist/hooks.cjs +492 -2
- package/dist/hooks.cjs.map +1 -1
- package/dist/hooks.d.cts +130 -2
- package/dist/hooks.d.ts +130 -2
- package/dist/hooks.js +491 -1
- package/dist/hooks.js.map +1 -1
- package/dist/index.cjs +590 -224
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +86 -14
- package/dist/index.d.ts +86 -14
- package/dist/index.js +549 -183
- package/dist/index.js.map +1 -1
- package/dist/{client-Bm9aiyTx.d.ts → practice-Dh2Tv80G.d.ts} +36 -5
- package/dist/{client-BaOC79Nc.d.cts → practice-QnWtC5A7.d.cts} +36 -5
- package/dist/testing.cjs +10 -0
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +2 -0
- package/dist/testing.d.ts +2 -0
- package/dist/testing.js +10 -0
- package/dist/testing.js.map +1 -1
- package/package.json +5 -3
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }// src/logic/media-url.ts
|
|
2
|
+
function isStorageKey(value) {
|
|
3
|
+
return !value.startsWith("http://") && !value.startsWith("https://");
|
|
4
|
+
}
|
|
5
|
+
var MediaUrlCache = class {
|
|
6
|
+
constructor(ttlMs = 50 * 60 * 1e3) {
|
|
7
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
8
|
+
this.ttlMs = ttlMs;
|
|
9
|
+
}
|
|
10
|
+
get(key) {
|
|
11
|
+
const entry = this.cache.get(key);
|
|
12
|
+
if (!entry) return void 0;
|
|
13
|
+
if (Date.now() >= entry.expiresAt) {
|
|
14
|
+
this.cache.delete(key);
|
|
15
|
+
return void 0;
|
|
16
|
+
}
|
|
17
|
+
return entry.url;
|
|
18
|
+
}
|
|
19
|
+
set(key, url) {
|
|
20
|
+
this.cache.set(key, { url, expiresAt: Date.now() + this.ttlMs });
|
|
21
|
+
}
|
|
22
|
+
clear() {
|
|
23
|
+
this.cache.clear();
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
var warnedNoStorage = false;
|
|
27
|
+
async function resolveMediaUrl(value, storage, cache) {
|
|
28
|
+
if (!value || value.length === 0 || value === "unchanged") return null;
|
|
29
|
+
if (!isStorageKey(value)) return value;
|
|
30
|
+
if (cache) {
|
|
31
|
+
const cached = cache.get(value);
|
|
32
|
+
if (cached) return cached;
|
|
33
|
+
}
|
|
34
|
+
if (!storage) {
|
|
35
|
+
if (!warnedNoStorage) {
|
|
36
|
+
warnedNoStorage = true;
|
|
37
|
+
console.warn("@orjok/commons: resolveMediaUrl called without a StorageProvider \u2014 returning raw key");
|
|
38
|
+
}
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
41
|
+
const { url } = await storage.getFileUrl(value);
|
|
42
|
+
_optionalChain([cache, 'optionalAccess', _ => _.set, 'call', _2 => _2(value, url)]);
|
|
43
|
+
return url;
|
|
44
|
+
}
|
|
45
|
+
async function resolveMediaUrls(obj, fields, storage, cache) {
|
|
46
|
+
const copy = { ...obj };
|
|
47
|
+
await Promise.all(
|
|
48
|
+
fields.map(async (field) => {
|
|
49
|
+
const val = copy[field];
|
|
50
|
+
if (typeof val === "string") {
|
|
51
|
+
copy[field] = await resolveMediaUrl(val, storage, cache);
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
);
|
|
55
|
+
return copy;
|
|
56
|
+
}
|
|
57
|
+
function _resetMediaUrlWarning() {
|
|
58
|
+
warnedNoStorage = false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// src/logic/practice.ts
|
|
62
|
+
function aggregateSubjectMetrics(progress) {
|
|
63
|
+
let totalPracticed = 0;
|
|
64
|
+
let totalMistakes = 0;
|
|
65
|
+
let totalCorrected = 0;
|
|
66
|
+
for (const p of progress) {
|
|
67
|
+
totalPracticed += _nullishCoalesce(p.practicedCount, () => ( 0));
|
|
68
|
+
totalMistakes += _nullishCoalesce(p.mistakeCount, () => ( 0));
|
|
69
|
+
totalCorrected += _nullishCoalesce(p.correctedCount, () => ( 0));
|
|
70
|
+
}
|
|
71
|
+
const accuracy = totalPracticed > 0 ? Math.round((totalPracticed - totalMistakes) / totalPracticed * 100) : 0;
|
|
72
|
+
return { totalPracticed, totalMistakes, totalCorrected, accuracy };
|
|
73
|
+
}
|
|
74
|
+
function aggregateChapterMetrics(progress) {
|
|
75
|
+
let totalPracticed = 0;
|
|
76
|
+
let totalMistakes = 0;
|
|
77
|
+
let totalCorrected = 0;
|
|
78
|
+
for (const p of progress) {
|
|
79
|
+
totalPracticed += _nullishCoalesce(p.practicedCount, () => ( 0));
|
|
80
|
+
totalMistakes += _nullishCoalesce(p.mistakeCount, () => ( 0));
|
|
81
|
+
totalCorrected += _nullishCoalesce(p.correctedCount, () => ( 0));
|
|
82
|
+
}
|
|
83
|
+
const accuracy = totalPracticed > 0 ? Math.round((totalPracticed - totalMistakes) / totalPracticed * 100) : 0;
|
|
84
|
+
return { totalPracticed, totalMistakes, totalCorrected, accuracy };
|
|
85
|
+
}
|
|
86
|
+
function aggregateTopicMetrics(progress) {
|
|
87
|
+
let totalPracticed = 0;
|
|
88
|
+
let totalMistakes = 0;
|
|
89
|
+
let totalCorrected = 0;
|
|
90
|
+
for (const p of progress) {
|
|
91
|
+
totalPracticed += _nullishCoalesce(p.practicedCount, () => ( 0));
|
|
92
|
+
totalMistakes += _nullishCoalesce(p.mistakeCount, () => ( 0));
|
|
93
|
+
totalCorrected += _nullishCoalesce(p.correctedCount, () => ( 0));
|
|
94
|
+
}
|
|
95
|
+
const accuracy = totalPracticed > 0 ? Math.round((totalPracticed - totalMistakes) / totalPracticed * 100) : 0;
|
|
96
|
+
return { totalPracticed, totalMistakes, totalCorrected, accuracy };
|
|
97
|
+
}
|
|
98
|
+
function calculateAccuracy(practiced, mistakes) {
|
|
99
|
+
if (practiced <= 0) return 0;
|
|
100
|
+
return Math.round((practiced - mistakes) / practiced * 100);
|
|
101
|
+
}
|
|
102
|
+
function isLanguageMatch(qLang, targetLang) {
|
|
103
|
+
if (!targetLang || targetLang === "All") return true;
|
|
104
|
+
if (!qLang) return true;
|
|
105
|
+
const q = qLang.trim().toLowerCase();
|
|
106
|
+
const t = targetLang.trim().toLowerCase();
|
|
107
|
+
if (t === "bangla" || t === "bn" || t === "bengali") {
|
|
108
|
+
return q === "bangla" || q === "bn" || q === "bengali" || q === "all";
|
|
109
|
+
}
|
|
110
|
+
if (t === "english" || t === "en") {
|
|
111
|
+
return q === "english" || q === "en" || q === "all";
|
|
112
|
+
}
|
|
113
|
+
return q === t || q === "all";
|
|
114
|
+
}
|
|
115
|
+
function isDifficultyMatch(qDiff, targetDiff) {
|
|
116
|
+
if (!targetDiff || targetDiff === "All") return true;
|
|
117
|
+
if (!qDiff) return true;
|
|
118
|
+
return qDiff.trim().toLowerCase() === targetDiff.trim().toLowerCase();
|
|
119
|
+
}
|
|
120
|
+
function isValidMCQ(q) {
|
|
121
|
+
if (!q || typeof q !== "object") return false;
|
|
122
|
+
const item = q;
|
|
123
|
+
if (!item.id || item.type === "WRITTEN") return false;
|
|
124
|
+
const rawOpts = item.options;
|
|
125
|
+
const opts = Array.isArray(rawOpts) ? rawOpts : rawOpts && typeof rawOpts === "object" && Array.isArray(rawOpts.items) ? rawOpts.items : [];
|
|
126
|
+
return opts.length >= 2;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// src/utils/shuffle.ts
|
|
130
|
+
function shuffle(array) {
|
|
131
|
+
const result = [...array];
|
|
132
|
+
let currentIndex = result.length;
|
|
133
|
+
while (currentIndex !== 0) {
|
|
134
|
+
const randomIndex = Math.floor(Math.random() * currentIndex);
|
|
135
|
+
currentIndex--;
|
|
136
|
+
[result[currentIndex], result[randomIndex]] = [result[randomIndex], result[currentIndex]];
|
|
137
|
+
}
|
|
138
|
+
return result;
|
|
139
|
+
}
|
|
140
|
+
function shuffleInPlace(array) {
|
|
141
|
+
let currentIndex = array.length;
|
|
142
|
+
while (currentIndex !== 0) {
|
|
143
|
+
const randomIndex = Math.floor(Math.random() * currentIndex);
|
|
144
|
+
currentIndex--;
|
|
145
|
+
[array[currentIndex], array[randomIndex]] = [array[randomIndex], array[currentIndex]];
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
exports.isStorageKey = isStorageKey; exports.MediaUrlCache = MediaUrlCache; exports.resolveMediaUrl = resolveMediaUrl; exports.resolveMediaUrls = resolveMediaUrls; exports._resetMediaUrlWarning = _resetMediaUrlWarning; exports.aggregateSubjectMetrics = aggregateSubjectMetrics; exports.aggregateChapterMetrics = aggregateChapterMetrics; exports.aggregateTopicMetrics = aggregateTopicMetrics; exports.calculateAccuracy = calculateAccuracy; exports.isLanguageMatch = isLanguageMatch; exports.isDifficultyMatch = isDifficultyMatch; exports.isValidMCQ = isValidMCQ; exports.shuffle = shuffle; exports.shuffleInPlace = shuffleInPlace;
|
|
165
|
+
//# sourceMappingURL=chunk-DB3CMKPP.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/orjok/orjok/packages/commons/dist/chunk-DB3CMKPP.cjs","../src/logic/media-url.ts","../src/logic/practice.ts","../src/utils/shuffle.ts"],"names":[],"mappings":"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","file":"/home/runner/work/orjok/orjok/packages/commons/dist/chunk-DB3CMKPP.cjs","sourcesContent":[null,"import type { StorageProvider } from \"../providers/storage\";\n\nexport function isStorageKey(value: string): boolean {\n return !value.startsWith(\"http://\") && !value.startsWith(\"https://\");\n}\n\ninterface CacheEntry {\n url: string;\n expiresAt: number;\n}\n\nexport class MediaUrlCache {\n private cache = new Map<string, CacheEntry>();\n private readonly ttlMs: number;\n\n constructor(ttlMs: number = 50 * 60 * 1000) {\n this.ttlMs = ttlMs;\n }\n\n get(key: string): string | undefined {\n const entry = this.cache.get(key);\n if (!entry) return undefined;\n if (Date.now() >= entry.expiresAt) {\n this.cache.delete(key);\n return undefined;\n }\n return entry.url;\n }\n\n set(key: string, url: string): void {\n this.cache.set(key, { url, expiresAt: Date.now() + this.ttlMs });\n }\n\n clear(): void {\n this.cache.clear();\n }\n}\n\nlet warnedNoStorage = false;\n\nexport async function resolveMediaUrl(\n value: string | null | undefined,\n storage: StorageProvider | undefined,\n cache?: MediaUrlCache,\n): Promise<string | null> {\n if (!value || value.length === 0 || value === \"unchanged\") return null;\n if (!isStorageKey(value)) return value;\n\n if (cache) {\n const cached = cache.get(value);\n if (cached) return cached;\n }\n\n if (!storage) {\n if (!warnedNoStorage) {\n warnedNoStorage = true;\n console.warn(\"@orjok/commons: resolveMediaUrl called without a StorageProvider — returning raw key\");\n }\n return value;\n }\n\n const { url } = await storage.getFileUrl(value);\n cache?.set(value, url);\n return url;\n}\n\nexport async function resolveMediaUrls<T extends Record<string, unknown>>(\n obj: T,\n fields: (keyof T)[],\n storage: StorageProvider | undefined,\n cache?: MediaUrlCache,\n): Promise<T> {\n const copy = { ...obj };\n await Promise.all(\n fields.map(async (field) => {\n const val = copy[field];\n if (typeof val === \"string\") {\n (copy as Record<string, unknown>)[field as string] = await resolveMediaUrl(val, storage, cache);\n }\n }),\n );\n return copy;\n}\n\n// Reset the one-time warning flag (for testing only)\nexport function _resetMediaUrlWarning(): void {\n warnedNoStorage = false;\n}\n","import type { UserSubjectProgress, UserChapterProgress, UserTopicProgress } from \"../types/models\";\n\nexport interface PracticeMetrics {\n totalPracticed: number;\n totalMistakes: number;\n totalCorrected: number;\n accuracy: number;\n}\n\nexport function aggregateSubjectMetrics(progress: UserSubjectProgress[]): PracticeMetrics {\n let totalPracticed = 0;\n let totalMistakes = 0;\n let totalCorrected = 0;\n\n for (const p of progress) {\n totalPracticed += p.practicedCount ?? 0;\n totalMistakes += p.mistakeCount ?? 0;\n totalCorrected += p.correctedCount ?? 0;\n }\n\n const accuracy = totalPracticed > 0\n ? Math.round(((totalPracticed - totalMistakes) / totalPracticed) * 100)\n : 0;\n\n return { totalPracticed, totalMistakes, totalCorrected, accuracy };\n}\n\nexport function aggregateChapterMetrics(progress: UserChapterProgress[]): PracticeMetrics {\n let totalPracticed = 0;\n let totalMistakes = 0;\n let totalCorrected = 0;\n\n for (const p of progress) {\n totalPracticed += p.practicedCount ?? 0;\n totalMistakes += p.mistakeCount ?? 0;\n totalCorrected += p.correctedCount ?? 0;\n }\n\n const accuracy = totalPracticed > 0\n ? Math.round(((totalPracticed - totalMistakes) / totalPracticed) * 100)\n : 0;\n\n return { totalPracticed, totalMistakes, totalCorrected, accuracy };\n}\n\nexport function aggregateTopicMetrics(progress: UserTopicProgress[]): PracticeMetrics {\n let totalPracticed = 0;\n let totalMistakes = 0;\n let totalCorrected = 0;\n\n for (const p of progress) {\n totalPracticed += p.practicedCount ?? 0;\n totalMistakes += p.mistakeCount ?? 0;\n totalCorrected += p.correctedCount ?? 0;\n }\n\n const accuracy = totalPracticed > 0\n ? Math.round(((totalPracticed - totalMistakes) / totalPracticed) * 100)\n : 0;\n\n return { totalPracticed, totalMistakes, totalCorrected, accuracy };\n}\n\nexport function calculateAccuracy(practiced: number, mistakes: number): number {\n if (practiced <= 0) return 0;\n return Math.round(((practiced - mistakes) / practiced) * 100);\n}\n\nexport function isLanguageMatch(qLang?: string | null, targetLang?: string): boolean {\n if (!targetLang || targetLang === \"All\") return true;\n if (!qLang) return true;\n const q = qLang.trim().toLowerCase();\n const t = targetLang.trim().toLowerCase();\n if (t === \"bangla\" || t === \"bn\" || t === \"bengali\") {\n return q === \"bangla\" || q === \"bn\" || q === \"bengali\" || q === \"all\";\n }\n if (t === \"english\" || t === \"en\") {\n return q === \"english\" || q === \"en\" || q === \"all\";\n }\n return q === t || q === \"all\";\n}\n\nexport function isDifficultyMatch(qDiff?: string | null, targetDiff?: string): boolean {\n if (!targetDiff || targetDiff === \"All\") return true;\n if (!qDiff) return true;\n return qDiff.trim().toLowerCase() === targetDiff.trim().toLowerCase();\n}\n\nexport function isValidMCQ(q: unknown): boolean {\n if (!q || typeof q !== \"object\") return false;\n const item = q as Record<string, unknown>;\n if (!item.id || item.type === \"WRITTEN\") return false;\n\n const rawOpts = item.options as unknown;\n const opts = Array.isArray(rawOpts)\n ? rawOpts\n : (rawOpts && typeof rawOpts === \"object\" && Array.isArray((rawOpts as { items?: unknown[] }).items))\n ? (rawOpts as { items: unknown[] }).items\n : [];\n\n return opts.length >= 2;\n}\n\n","export function shuffle<T>(array: T[]): T[] {\n const result = [...array];\n let currentIndex = result.length;\n while (currentIndex !== 0) {\n const randomIndex = Math.floor(Math.random() * currentIndex);\n currentIndex--;\n [result[currentIndex], result[randomIndex]] = [result[randomIndex]!, result[currentIndex]!];\n }\n return result;\n}\n\nexport function shuffleInPlace<T>(array: T[]): void {\n let currentIndex = array.length;\n while (currentIndex !== 0) {\n const randomIndex = Math.floor(Math.random() * currentIndex);\n currentIndex--;\n [array[currentIndex], array[randomIndex]] = [array[randomIndex]!, array[currentIndex]!];\n }\n}\n"]}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// src/logic/media-url.ts
|
|
2
|
+
function isStorageKey(value) {
|
|
3
|
+
return !value.startsWith("http://") && !value.startsWith("https://");
|
|
4
|
+
}
|
|
5
|
+
var MediaUrlCache = class {
|
|
6
|
+
constructor(ttlMs = 50 * 60 * 1e3) {
|
|
7
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
8
|
+
this.ttlMs = ttlMs;
|
|
9
|
+
}
|
|
10
|
+
get(key) {
|
|
11
|
+
const entry = this.cache.get(key);
|
|
12
|
+
if (!entry) return void 0;
|
|
13
|
+
if (Date.now() >= entry.expiresAt) {
|
|
14
|
+
this.cache.delete(key);
|
|
15
|
+
return void 0;
|
|
16
|
+
}
|
|
17
|
+
return entry.url;
|
|
18
|
+
}
|
|
19
|
+
set(key, url) {
|
|
20
|
+
this.cache.set(key, { url, expiresAt: Date.now() + this.ttlMs });
|
|
21
|
+
}
|
|
22
|
+
clear() {
|
|
23
|
+
this.cache.clear();
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
var warnedNoStorage = false;
|
|
27
|
+
async function resolveMediaUrl(value, storage, cache) {
|
|
28
|
+
if (!value || value.length === 0 || value === "unchanged") return null;
|
|
29
|
+
if (!isStorageKey(value)) return value;
|
|
30
|
+
if (cache) {
|
|
31
|
+
const cached = cache.get(value);
|
|
32
|
+
if (cached) return cached;
|
|
33
|
+
}
|
|
34
|
+
if (!storage) {
|
|
35
|
+
if (!warnedNoStorage) {
|
|
36
|
+
warnedNoStorage = true;
|
|
37
|
+
console.warn("@orjok/commons: resolveMediaUrl called without a StorageProvider \u2014 returning raw key");
|
|
38
|
+
}
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
41
|
+
const { url } = await storage.getFileUrl(value);
|
|
42
|
+
cache?.set(value, url);
|
|
43
|
+
return url;
|
|
44
|
+
}
|
|
45
|
+
async function resolveMediaUrls(obj, fields, storage, cache) {
|
|
46
|
+
const copy = { ...obj };
|
|
47
|
+
await Promise.all(
|
|
48
|
+
fields.map(async (field) => {
|
|
49
|
+
const val = copy[field];
|
|
50
|
+
if (typeof val === "string") {
|
|
51
|
+
copy[field] = await resolveMediaUrl(val, storage, cache);
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
);
|
|
55
|
+
return copy;
|
|
56
|
+
}
|
|
57
|
+
function _resetMediaUrlWarning() {
|
|
58
|
+
warnedNoStorage = false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// src/logic/practice.ts
|
|
62
|
+
function aggregateSubjectMetrics(progress) {
|
|
63
|
+
let totalPracticed = 0;
|
|
64
|
+
let totalMistakes = 0;
|
|
65
|
+
let totalCorrected = 0;
|
|
66
|
+
for (const p of progress) {
|
|
67
|
+
totalPracticed += p.practicedCount ?? 0;
|
|
68
|
+
totalMistakes += p.mistakeCount ?? 0;
|
|
69
|
+
totalCorrected += p.correctedCount ?? 0;
|
|
70
|
+
}
|
|
71
|
+
const accuracy = totalPracticed > 0 ? Math.round((totalPracticed - totalMistakes) / totalPracticed * 100) : 0;
|
|
72
|
+
return { totalPracticed, totalMistakes, totalCorrected, accuracy };
|
|
73
|
+
}
|
|
74
|
+
function aggregateChapterMetrics(progress) {
|
|
75
|
+
let totalPracticed = 0;
|
|
76
|
+
let totalMistakes = 0;
|
|
77
|
+
let totalCorrected = 0;
|
|
78
|
+
for (const p of progress) {
|
|
79
|
+
totalPracticed += p.practicedCount ?? 0;
|
|
80
|
+
totalMistakes += p.mistakeCount ?? 0;
|
|
81
|
+
totalCorrected += p.correctedCount ?? 0;
|
|
82
|
+
}
|
|
83
|
+
const accuracy = totalPracticed > 0 ? Math.round((totalPracticed - totalMistakes) / totalPracticed * 100) : 0;
|
|
84
|
+
return { totalPracticed, totalMistakes, totalCorrected, accuracy };
|
|
85
|
+
}
|
|
86
|
+
function aggregateTopicMetrics(progress) {
|
|
87
|
+
let totalPracticed = 0;
|
|
88
|
+
let totalMistakes = 0;
|
|
89
|
+
let totalCorrected = 0;
|
|
90
|
+
for (const p of progress) {
|
|
91
|
+
totalPracticed += p.practicedCount ?? 0;
|
|
92
|
+
totalMistakes += p.mistakeCount ?? 0;
|
|
93
|
+
totalCorrected += p.correctedCount ?? 0;
|
|
94
|
+
}
|
|
95
|
+
const accuracy = totalPracticed > 0 ? Math.round((totalPracticed - totalMistakes) / totalPracticed * 100) : 0;
|
|
96
|
+
return { totalPracticed, totalMistakes, totalCorrected, accuracy };
|
|
97
|
+
}
|
|
98
|
+
function calculateAccuracy(practiced, mistakes) {
|
|
99
|
+
if (practiced <= 0) return 0;
|
|
100
|
+
return Math.round((practiced - mistakes) / practiced * 100);
|
|
101
|
+
}
|
|
102
|
+
function isLanguageMatch(qLang, targetLang) {
|
|
103
|
+
if (!targetLang || targetLang === "All") return true;
|
|
104
|
+
if (!qLang) return true;
|
|
105
|
+
const q = qLang.trim().toLowerCase();
|
|
106
|
+
const t = targetLang.trim().toLowerCase();
|
|
107
|
+
if (t === "bangla" || t === "bn" || t === "bengali") {
|
|
108
|
+
return q === "bangla" || q === "bn" || q === "bengali" || q === "all";
|
|
109
|
+
}
|
|
110
|
+
if (t === "english" || t === "en") {
|
|
111
|
+
return q === "english" || q === "en" || q === "all";
|
|
112
|
+
}
|
|
113
|
+
return q === t || q === "all";
|
|
114
|
+
}
|
|
115
|
+
function isDifficultyMatch(qDiff, targetDiff) {
|
|
116
|
+
if (!targetDiff || targetDiff === "All") return true;
|
|
117
|
+
if (!qDiff) return true;
|
|
118
|
+
return qDiff.trim().toLowerCase() === targetDiff.trim().toLowerCase();
|
|
119
|
+
}
|
|
120
|
+
function isValidMCQ(q) {
|
|
121
|
+
if (!q || typeof q !== "object") return false;
|
|
122
|
+
const item = q;
|
|
123
|
+
if (!item.id || item.type === "WRITTEN") return false;
|
|
124
|
+
const rawOpts = item.options;
|
|
125
|
+
const opts = Array.isArray(rawOpts) ? rawOpts : rawOpts && typeof rawOpts === "object" && Array.isArray(rawOpts.items) ? rawOpts.items : [];
|
|
126
|
+
return opts.length >= 2;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// src/utils/shuffle.ts
|
|
130
|
+
function shuffle(array) {
|
|
131
|
+
const result = [...array];
|
|
132
|
+
let currentIndex = result.length;
|
|
133
|
+
while (currentIndex !== 0) {
|
|
134
|
+
const randomIndex = Math.floor(Math.random() * currentIndex);
|
|
135
|
+
currentIndex--;
|
|
136
|
+
[result[currentIndex], result[randomIndex]] = [result[randomIndex], result[currentIndex]];
|
|
137
|
+
}
|
|
138
|
+
return result;
|
|
139
|
+
}
|
|
140
|
+
function shuffleInPlace(array) {
|
|
141
|
+
let currentIndex = array.length;
|
|
142
|
+
while (currentIndex !== 0) {
|
|
143
|
+
const randomIndex = Math.floor(Math.random() * currentIndex);
|
|
144
|
+
currentIndex--;
|
|
145
|
+
[array[currentIndex], array[randomIndex]] = [array[randomIndex], array[currentIndex]];
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export {
|
|
150
|
+
isStorageKey,
|
|
151
|
+
MediaUrlCache,
|
|
152
|
+
resolveMediaUrl,
|
|
153
|
+
resolveMediaUrls,
|
|
154
|
+
_resetMediaUrlWarning,
|
|
155
|
+
aggregateSubjectMetrics,
|
|
156
|
+
aggregateChapterMetrics,
|
|
157
|
+
aggregateTopicMetrics,
|
|
158
|
+
calculateAccuracy,
|
|
159
|
+
isLanguageMatch,
|
|
160
|
+
isDifficultyMatch,
|
|
161
|
+
isValidMCQ,
|
|
162
|
+
shuffle,
|
|
163
|
+
shuffleInPlace
|
|
164
|
+
};
|
|
165
|
+
//# sourceMappingURL=chunk-ZDGYAVEC.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/logic/media-url.ts","../src/logic/practice.ts","../src/utils/shuffle.ts"],"sourcesContent":["import type { StorageProvider } from \"../providers/storage\";\n\nexport function isStorageKey(value: string): boolean {\n return !value.startsWith(\"http://\") && !value.startsWith(\"https://\");\n}\n\ninterface CacheEntry {\n url: string;\n expiresAt: number;\n}\n\nexport class MediaUrlCache {\n private cache = new Map<string, CacheEntry>();\n private readonly ttlMs: number;\n\n constructor(ttlMs: number = 50 * 60 * 1000) {\n this.ttlMs = ttlMs;\n }\n\n get(key: string): string | undefined {\n const entry = this.cache.get(key);\n if (!entry) return undefined;\n if (Date.now() >= entry.expiresAt) {\n this.cache.delete(key);\n return undefined;\n }\n return entry.url;\n }\n\n set(key: string, url: string): void {\n this.cache.set(key, { url, expiresAt: Date.now() + this.ttlMs });\n }\n\n clear(): void {\n this.cache.clear();\n }\n}\n\nlet warnedNoStorage = false;\n\nexport async function resolveMediaUrl(\n value: string | null | undefined,\n storage: StorageProvider | undefined,\n cache?: MediaUrlCache,\n): Promise<string | null> {\n if (!value || value.length === 0 || value === \"unchanged\") return null;\n if (!isStorageKey(value)) return value;\n\n if (cache) {\n const cached = cache.get(value);\n if (cached) return cached;\n }\n\n if (!storage) {\n if (!warnedNoStorage) {\n warnedNoStorage = true;\n console.warn(\"@orjok/commons: resolveMediaUrl called without a StorageProvider — returning raw key\");\n }\n return value;\n }\n\n const { url } = await storage.getFileUrl(value);\n cache?.set(value, url);\n return url;\n}\n\nexport async function resolveMediaUrls<T extends Record<string, unknown>>(\n obj: T,\n fields: (keyof T)[],\n storage: StorageProvider | undefined,\n cache?: MediaUrlCache,\n): Promise<T> {\n const copy = { ...obj };\n await Promise.all(\n fields.map(async (field) => {\n const val = copy[field];\n if (typeof val === \"string\") {\n (copy as Record<string, unknown>)[field as string] = await resolveMediaUrl(val, storage, cache);\n }\n }),\n );\n return copy;\n}\n\n// Reset the one-time warning flag (for testing only)\nexport function _resetMediaUrlWarning(): void {\n warnedNoStorage = false;\n}\n","import type { UserSubjectProgress, UserChapterProgress, UserTopicProgress } from \"../types/models\";\n\nexport interface PracticeMetrics {\n totalPracticed: number;\n totalMistakes: number;\n totalCorrected: number;\n accuracy: number;\n}\n\nexport function aggregateSubjectMetrics(progress: UserSubjectProgress[]): PracticeMetrics {\n let totalPracticed = 0;\n let totalMistakes = 0;\n let totalCorrected = 0;\n\n for (const p of progress) {\n totalPracticed += p.practicedCount ?? 0;\n totalMistakes += p.mistakeCount ?? 0;\n totalCorrected += p.correctedCount ?? 0;\n }\n\n const accuracy = totalPracticed > 0\n ? Math.round(((totalPracticed - totalMistakes) / totalPracticed) * 100)\n : 0;\n\n return { totalPracticed, totalMistakes, totalCorrected, accuracy };\n}\n\nexport function aggregateChapterMetrics(progress: UserChapterProgress[]): PracticeMetrics {\n let totalPracticed = 0;\n let totalMistakes = 0;\n let totalCorrected = 0;\n\n for (const p of progress) {\n totalPracticed += p.practicedCount ?? 0;\n totalMistakes += p.mistakeCount ?? 0;\n totalCorrected += p.correctedCount ?? 0;\n }\n\n const accuracy = totalPracticed > 0\n ? Math.round(((totalPracticed - totalMistakes) / totalPracticed) * 100)\n : 0;\n\n return { totalPracticed, totalMistakes, totalCorrected, accuracy };\n}\n\nexport function aggregateTopicMetrics(progress: UserTopicProgress[]): PracticeMetrics {\n let totalPracticed = 0;\n let totalMistakes = 0;\n let totalCorrected = 0;\n\n for (const p of progress) {\n totalPracticed += p.practicedCount ?? 0;\n totalMistakes += p.mistakeCount ?? 0;\n totalCorrected += p.correctedCount ?? 0;\n }\n\n const accuracy = totalPracticed > 0\n ? Math.round(((totalPracticed - totalMistakes) / totalPracticed) * 100)\n : 0;\n\n return { totalPracticed, totalMistakes, totalCorrected, accuracy };\n}\n\nexport function calculateAccuracy(practiced: number, mistakes: number): number {\n if (practiced <= 0) return 0;\n return Math.round(((practiced - mistakes) / practiced) * 100);\n}\n\nexport function isLanguageMatch(qLang?: string | null, targetLang?: string): boolean {\n if (!targetLang || targetLang === \"All\") return true;\n if (!qLang) return true;\n const q = qLang.trim().toLowerCase();\n const t = targetLang.trim().toLowerCase();\n if (t === \"bangla\" || t === \"bn\" || t === \"bengali\") {\n return q === \"bangla\" || q === \"bn\" || q === \"bengali\" || q === \"all\";\n }\n if (t === \"english\" || t === \"en\") {\n return q === \"english\" || q === \"en\" || q === \"all\";\n }\n return q === t || q === \"all\";\n}\n\nexport function isDifficultyMatch(qDiff?: string | null, targetDiff?: string): boolean {\n if (!targetDiff || targetDiff === \"All\") return true;\n if (!qDiff) return true;\n return qDiff.trim().toLowerCase() === targetDiff.trim().toLowerCase();\n}\n\nexport function isValidMCQ(q: unknown): boolean {\n if (!q || typeof q !== \"object\") return false;\n const item = q as Record<string, unknown>;\n if (!item.id || item.type === \"WRITTEN\") return false;\n\n const rawOpts = item.options as unknown;\n const opts = Array.isArray(rawOpts)\n ? rawOpts\n : (rawOpts && typeof rawOpts === \"object\" && Array.isArray((rawOpts as { items?: unknown[] }).items))\n ? (rawOpts as { items: unknown[] }).items\n : [];\n\n return opts.length >= 2;\n}\n\n","export function shuffle<T>(array: T[]): T[] {\n const result = [...array];\n let currentIndex = result.length;\n while (currentIndex !== 0) {\n const randomIndex = Math.floor(Math.random() * currentIndex);\n currentIndex--;\n [result[currentIndex], result[randomIndex]] = [result[randomIndex]!, result[currentIndex]!];\n }\n return result;\n}\n\nexport function shuffleInPlace<T>(array: T[]): void {\n let currentIndex = array.length;\n while (currentIndex !== 0) {\n const randomIndex = Math.floor(Math.random() * currentIndex);\n currentIndex--;\n [array[currentIndex], array[randomIndex]] = [array[randomIndex]!, array[currentIndex]!];\n }\n}\n"],"mappings":"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","names":[]}
|