usegamigameapi 1.0.13 → 1.0.14
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/README.md +8 -8
- package/package.json +6 -4
- package/dist/index.d.mts +0 -23
- package/dist/index.d.ts +0 -23
- package/dist/index.js +0 -179
- package/dist/index.js.map +0 -1
- package/dist/index.mjs +0 -152
- package/dist/index.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -48,8 +48,8 @@ The `useGameAPI` hook accepts an object with the following optional callbacks:
|
|
|
48
48
|
|
|
49
49
|
| Parameter | Type | Description |
|
|
50
50
|
|-----------|------|-------------|
|
|
51
|
-
| `onAnswerCorrect` | `(params: { currentQuestionIndex: number }) => void` | Callback fired when the user submits a correct answer |
|
|
52
|
-
| `onAnswerIncorrect` | `() => void` | Callback fired when the user submits an incorrect answer |
|
|
51
|
+
| `onAnswerCorrect` | `(params: { currentQuestionIndex: number; correctAnswerId?: number; incorrectAnswerId?: number }) => void` | Callback fired when the user submits a correct answer |
|
|
52
|
+
| `onAnswerIncorrect` | `(params: { currentQuestionIndex: number; correctAnswerId?: number; incorrectAnswerId?: number }) => void` | Callback fired when the user submits an incorrect answer |
|
|
53
53
|
|
|
54
54
|
### Return Values
|
|
55
55
|
|
|
@@ -60,7 +60,7 @@ The hook returns an object with the following properties:
|
|
|
60
60
|
| Property | Type | Description |
|
|
61
61
|
|----------|------|-------------|
|
|
62
62
|
| `quiz` | `any` | The current question data received from the parent window (contains text, audio URL, answers array, etc.) |
|
|
63
|
-
| `currentResult` | `any` | The result object after submitting an answer (contains `isCorrect`, `isLastQuestion`, and explanation) |
|
|
63
|
+
| `currentResult` | `any` | The result object after submitting an answer (contains `isCorrect`, `isLastQuestion`, `correctAnswerId`, `incorrectAnswerId`, and explanation) |
|
|
64
64
|
| `answers` | `(boolean \| null)[]` | Array of answer history where each index corresponds to a question (true = correct, false = incorrect, null = unanswered) |
|
|
65
65
|
| `correctCount` | `number` | Total count of correctly answered questions |
|
|
66
66
|
| `currentQuestionIndex` | `number` | Zero-based index of the current question |
|
|
@@ -106,13 +106,13 @@ function GameQuiz() {
|
|
|
106
106
|
handleContinue,
|
|
107
107
|
finish
|
|
108
108
|
} = useGameAPI({
|
|
109
|
-
onAnswerCorrect: ({ currentQuestionIndex }) => {
|
|
109
|
+
onAnswerCorrect: ({ currentQuestionIndex, correctAnswerId }) => {
|
|
110
110
|
// Show success animation or confetti
|
|
111
|
-
console.log(`Question ${currentQuestionIndex + 1} answered correctly
|
|
111
|
+
console.log(`Question ${currentQuestionIndex + 1} answered correctly!`, { correctAnswerId });
|
|
112
112
|
},
|
|
113
|
-
onAnswerIncorrect: () => {
|
|
113
|
+
onAnswerIncorrect: ({ currentQuestionIndex, correctAnswerId, incorrectAnswerId }) => {
|
|
114
114
|
// Show error feedback
|
|
115
|
-
console.log('Incorrect answer,
|
|
115
|
+
console.log('Incorrect answer', { correctAnswerId, incorrectAnswerId });
|
|
116
116
|
}
|
|
117
117
|
});
|
|
118
118
|
|
|
@@ -244,7 +244,7 @@ This hook uses a **bridge pattern** to facilitate communication between a parent
|
|
|
244
244
|
1. **Initialization**: When the hook mounts, it sends `SHOW_LO5` to request the first question
|
|
245
245
|
2. **Question Received**: Parent responds with `RETURN_LO5` containing question data
|
|
246
246
|
3. **Answer Submission**: User selects an answer and calls `updateAnswer()`, which sends `UPDATE_ANSWER` with the answer ID
|
|
247
|
-
4. **Result Received**: Parent responds with `RETURN_UPDATE_ANSWER` containing `isCorrect`, `isLastQuestion`, and result data
|
|
247
|
+
4. **Result Received**: Parent responds with `RETURN_UPDATE_ANSWER` containing `isCorrect`, `isLastQuestion`, `correctAnswerId`, `incorrectAnswerId`, and result data
|
|
248
248
|
5. **Continue**: User calls `handleContinue()` which either requests the next question (`SHOW_LO5`) or marks the quiz as completed
|
|
249
249
|
6. **Finish**: When complete, `finish()` sends `FINISH` to signal completion (typically for iframe resizing)
|
|
250
250
|
|
package/package.json
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "usegamigameapi",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.14",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"module": "./dist/index.mjs",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
7
|
-
"files": [
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
8
10
|
"scripts": {
|
|
9
|
-
"build": "tsup",
|
|
11
|
+
"build": "tsup",
|
|
10
12
|
"publish": "npm run build && npm publish --access public"
|
|
11
13
|
},
|
|
12
14
|
"peerDependencies": {
|
|
@@ -19,4 +21,4 @@
|
|
|
19
21
|
"tsup": "^8.5.1",
|
|
20
22
|
"typescript": "^5.9.3"
|
|
21
23
|
}
|
|
22
|
-
}
|
|
24
|
+
}
|
package/dist/index.d.mts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
declare function useGameAPI({ onAnswerCorrect, onAnswerIncorrect }: any): {
|
|
2
|
-
quiz: any;
|
|
3
|
-
currentResult: any;
|
|
4
|
-
answers: (boolean | null)[];
|
|
5
|
-
correctCount: number;
|
|
6
|
-
currentQuestionIndex: number;
|
|
7
|
-
selectedAnswer: {
|
|
8
|
-
id: number;
|
|
9
|
-
content: string;
|
|
10
|
-
} | null;
|
|
11
|
-
isSubmitting: boolean;
|
|
12
|
-
hasSubmitted: boolean;
|
|
13
|
-
isCompleted: boolean;
|
|
14
|
-
handleAnswerSelect: (answer: {
|
|
15
|
-
id: number;
|
|
16
|
-
content: string;
|
|
17
|
-
}) => void;
|
|
18
|
-
updateAnswer: () => Promise<void>;
|
|
19
|
-
handleContinue: () => void;
|
|
20
|
-
finish: () => void;
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export { useGameAPI };
|
package/dist/index.d.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
declare function useGameAPI({ onAnswerCorrect, onAnswerIncorrect }: any): {
|
|
2
|
-
quiz: any;
|
|
3
|
-
currentResult: any;
|
|
4
|
-
answers: (boolean | null)[];
|
|
5
|
-
correctCount: number;
|
|
6
|
-
currentQuestionIndex: number;
|
|
7
|
-
selectedAnswer: {
|
|
8
|
-
id: number;
|
|
9
|
-
content: string;
|
|
10
|
-
} | null;
|
|
11
|
-
isSubmitting: boolean;
|
|
12
|
-
hasSubmitted: boolean;
|
|
13
|
-
isCompleted: boolean;
|
|
14
|
-
handleAnswerSelect: (answer: {
|
|
15
|
-
id: number;
|
|
16
|
-
content: string;
|
|
17
|
-
}) => void;
|
|
18
|
-
updateAnswer: () => Promise<void>;
|
|
19
|
-
handleContinue: () => void;
|
|
20
|
-
finish: () => void;
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export { useGameAPI };
|
package/dist/index.js
DELETED
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
|
|
20
|
-
// src/index.ts
|
|
21
|
-
var index_exports = {};
|
|
22
|
-
__export(index_exports, {
|
|
23
|
-
useGameAPI: () => useGameAPI
|
|
24
|
-
});
|
|
25
|
-
module.exports = __toCommonJS(index_exports);
|
|
26
|
-
|
|
27
|
-
// src/useGameAPI.ts
|
|
28
|
-
var import_react = require("react");
|
|
29
|
-
|
|
30
|
-
// src/bridge.ts
|
|
31
|
-
var ACTIONS = {
|
|
32
|
-
SHOW_LO5: "SHOW_LO5",
|
|
33
|
-
// Sub -> Main: Web con đã sẵn sàng, yêu cầu web cha gửi câu hỏi
|
|
34
|
-
RETURN_LO5: "RETURN_LO5",
|
|
35
|
-
// Main -> Sub: Web cha gửi nội dung câu hỏi
|
|
36
|
-
UPDATE_ANSWER: "UPDATE_ANSWER",
|
|
37
|
-
// Sub -> Main: Gửi đáp án user chọn cho web cha
|
|
38
|
-
RETURN_UPDATE_ANSWER: "RETURN_UPDATE_ANSWER",
|
|
39
|
-
// Main -> Sub: Web cha gửi lại kết quả đúng/sai cho web con
|
|
40
|
-
FINISH: "FINISH"
|
|
41
|
-
// Sub -> Main: Yêu cầu resize iframe
|
|
42
|
-
};
|
|
43
|
-
var QuestionBridge = class {
|
|
44
|
-
constructor() {
|
|
45
|
-
this.handlers = {};
|
|
46
|
-
window.addEventListener("message", this.handleMessage.bind(this));
|
|
47
|
-
}
|
|
48
|
-
// Hàm nhận message
|
|
49
|
-
handleMessage(event) {
|
|
50
|
-
const { type, payload } = event.data;
|
|
51
|
-
if (this.handlers[type]) {
|
|
52
|
-
this.handlers[type](payload);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
// Đăng ký hàm xử lý cho từng loại sự kiện (để UI gọi)
|
|
56
|
-
// stw -> lovable
|
|
57
|
-
on(type, callback) {
|
|
58
|
-
this.handlers[type] = callback;
|
|
59
|
-
}
|
|
60
|
-
// lovable -> stw
|
|
61
|
-
// Gửi message lên Main Web
|
|
62
|
-
send(type, payload = {}) {
|
|
63
|
-
if (window.parent) {
|
|
64
|
-
window.parent.postMessage({ type, payload }, "*");
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
};
|
|
68
|
-
var bridge = new QuestionBridge();
|
|
69
|
-
|
|
70
|
-
// src/useGameAPI.ts
|
|
71
|
-
console.log("useGameAPI-V1.0.13");
|
|
72
|
-
function useGameAPI({ onAnswerCorrect, onAnswerIncorrect }) {
|
|
73
|
-
const [currentQuestionIndex, setCurrentQuestionIndex] = (0, import_react.useState)(0);
|
|
74
|
-
const currentQuestionIndexRef = (0, import_react.useRef)(0);
|
|
75
|
-
const [selectedAnswer, setSelectedAnswer] = (0, import_react.useState)(null);
|
|
76
|
-
const [answers, setAnswers] = (0, import_react.useState)([]);
|
|
77
|
-
const [quiz, setQuiz] = (0, import_react.useState)(null);
|
|
78
|
-
const [currentResult, setCurrentResult] = (0, import_react.useState)(null);
|
|
79
|
-
const [correctCount, setCorrectCount] = (0, import_react.useState)(0);
|
|
80
|
-
const [isSubmitting, setIsSubmitting] = (0, import_react.useState)(false);
|
|
81
|
-
const [hasSubmitted, setHasSubmitted] = (0, import_react.useState)(false);
|
|
82
|
-
const [isCompleted, setIsCompleted] = (0, import_react.useState)(false);
|
|
83
|
-
const isLastQuestionRef = (0, import_react.useRef)(false);
|
|
84
|
-
const finish = (0, import_react.useCallback)(() => {
|
|
85
|
-
bridge.send(ACTIONS.FINISH);
|
|
86
|
-
}, []);
|
|
87
|
-
const updateAnswer = (0, import_react.useCallback)(async () => {
|
|
88
|
-
if (!selectedAnswer || isSubmitting) {
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
bridge.send(ACTIONS.UPDATE_ANSWER, selectedAnswer.id);
|
|
92
|
-
setIsSubmitting(true);
|
|
93
|
-
setHasSubmitted(true);
|
|
94
|
-
return Promise.resolve();
|
|
95
|
-
}, [selectedAnswer, isSubmitting]);
|
|
96
|
-
const handleContinue = (0, import_react.useCallback)(() => {
|
|
97
|
-
if (!hasSubmitted) return;
|
|
98
|
-
if (isLastQuestionRef.current) {
|
|
99
|
-
setIsCompleted(true);
|
|
100
|
-
} else {
|
|
101
|
-
bridge.send(ACTIONS.SHOW_LO5);
|
|
102
|
-
const nextIndex = currentQuestionIndexRef.current + 1;
|
|
103
|
-
currentQuestionIndexRef.current = nextIndex;
|
|
104
|
-
setCurrentQuestionIndex(nextIndex);
|
|
105
|
-
setSelectedAnswer(null);
|
|
106
|
-
setIsSubmitting(false);
|
|
107
|
-
setHasSubmitted(false);
|
|
108
|
-
setCurrentResult(null);
|
|
109
|
-
}
|
|
110
|
-
}, [hasSubmitted]);
|
|
111
|
-
const handleAnswerSelect = (0, import_react.useCallback)(
|
|
112
|
-
(answer) => {
|
|
113
|
-
if (!isSubmitting && !hasSubmitted) {
|
|
114
|
-
setSelectedAnswer(answer);
|
|
115
|
-
}
|
|
116
|
-
},
|
|
117
|
-
[isSubmitting, hasSubmitted]
|
|
118
|
-
);
|
|
119
|
-
(0, import_react.useEffect)(() => {
|
|
120
|
-
bridge.send(ACTIONS.SHOW_LO5);
|
|
121
|
-
bridge.on(ACTIONS.RETURN_LO5, (_data) => {
|
|
122
|
-
setQuiz(_data);
|
|
123
|
-
});
|
|
124
|
-
bridge.on(ACTIONS.RETURN_UPDATE_ANSWER, (_data) => {
|
|
125
|
-
const isCorrect = (_data == null ? void 0 : _data.isCorrect) === true;
|
|
126
|
-
const isLast = (_data == null ? void 0 : _data.isLastQuestion) === true;
|
|
127
|
-
isLastQuestionRef.current = isLast;
|
|
128
|
-
setAnswers((prevAnswers) => {
|
|
129
|
-
const next = [...prevAnswers];
|
|
130
|
-
next[currentQuestionIndexRef.current] = isCorrect;
|
|
131
|
-
return next;
|
|
132
|
-
});
|
|
133
|
-
if (isCorrect) {
|
|
134
|
-
setCorrectCount((prev) => prev + 1);
|
|
135
|
-
console.log({ currentQuestionIndex: currentQuestionIndexRef.current });
|
|
136
|
-
onAnswerCorrect == null ? void 0 : onAnswerCorrect({ currentQuestionIndex: currentQuestionIndexRef.current });
|
|
137
|
-
} else {
|
|
138
|
-
onAnswerIncorrect == null ? void 0 : onAnswerIncorrect();
|
|
139
|
-
}
|
|
140
|
-
setIsSubmitting(false);
|
|
141
|
-
setCurrentResult(_data);
|
|
142
|
-
});
|
|
143
|
-
}, []);
|
|
144
|
-
return {
|
|
145
|
-
// State Data
|
|
146
|
-
quiz,
|
|
147
|
-
// Dữ liệu câu hỏi hiện tại (để render UI)
|
|
148
|
-
currentResult,
|
|
149
|
-
// Kết quả vừa trả lời (để hiện giải thích đúng/sai)
|
|
150
|
-
answers,
|
|
151
|
-
// Mảng lịch sử kết quả (để vẽ progress bar)
|
|
152
|
-
correctCount,
|
|
153
|
-
// Số câu đúng
|
|
154
|
-
currentQuestionIndex,
|
|
155
|
-
// Index hiện tại (để UI biết đang ở câu mấy)
|
|
156
|
-
// State Status
|
|
157
|
-
selectedAnswer,
|
|
158
|
-
// Đáp án đang được chọn
|
|
159
|
-
isSubmitting,
|
|
160
|
-
// Trạng thái đang submmit
|
|
161
|
-
hasSubmitted,
|
|
162
|
-
isCompleted,
|
|
163
|
-
// Trạng thái khi hoàn thành câu hỏi cuối
|
|
164
|
-
// Methods
|
|
165
|
-
handleAnswerSelect,
|
|
166
|
-
// Hàm gán đáp án user đã chọn
|
|
167
|
-
updateAnswer,
|
|
168
|
-
// Hàm gửi đáp án user chọn lên web cha
|
|
169
|
-
handleContinue,
|
|
170
|
-
// Hàm chuyển qua câu hỏi tiếp theo hoặc kết thúc
|
|
171
|
-
finish
|
|
172
|
-
// Hàm kết thúc
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
176
|
-
0 && (module.exports = {
|
|
177
|
-
useGameAPI
|
|
178
|
-
});
|
|
179
|
-
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/useGameAPI.ts","../src/bridge.ts"],"sourcesContent":["export * from \"./useGameAPI\";","import { useState, useCallback, useRef, useEffect } from \"react\";\n// Lưu ý: Đảm bảo đường dẫn import bridge đúng với cấu trúc dự án của bạn\nimport { bridge, ACTIONS } from \"./bridge\";\n\nconsole.log('useGameAPI-V1.0.13');\n\nexport function useGameAPI({ onAnswerCorrect, onAnswerIncorrect }: any) {\n // ================= STATE =================\n const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0); // State để UI re-render\n const currentQuestionIndexRef = useRef(0); // Ref để logic chạy đúng trong closure\n\n const [selectedAnswer, setSelectedAnswer] = useState<{ id: number; content: string } | null>(null);\n const [answers, setAnswers] = useState<(boolean | null)[]>([]); // Lưu lịch sử đúng/sai\n\n // Game Status\n const [quiz, setQuiz] = useState<any>(null); // Data câu hỏi hiện tại từ Main Web\n const [currentResult, setCurrentResult] = useState<any>(null); // Kết quả trả về sau khi submit\n const [correctCount, setCorrectCount] = useState(0);\n\n // Flow Flags\n const [isSubmitting, setIsSubmitting] = useState(false); // Đang chờ kết quả\n const [hasSubmitted, setHasSubmitted] = useState(false); // Đã có kết quả, chờ bấm Continue\n const [isCompleted, setIsCompleted] = useState(false); // Game hoàn tất\n\n // Biến cờ tạm lưu trạng thái câu cuối cùng từ server trả về\n const isLastQuestionRef = useRef(false);\n\n // ================= ACTIONS =================\n\n const finish = useCallback(() => {\n bridge.send(ACTIONS.FINISH);\n }, []);\n\n // 1. Gửi đáp án lên Main Web\n const updateAnswer = useCallback(async () => {\n if (!selectedAnswer || isSubmitting) {\n return;\n }\n\n bridge.send(ACTIONS.UPDATE_ANSWER, selectedAnswer.id);\n setIsSubmitting(true);\n setHasSubmitted(true);\n\n // Trả về promise giả để UI có thể await nếu cần (ví dụ trigger confetti ngay lập tức)\n return Promise.resolve();\n }, [selectedAnswer, isSubmitting]);\n\n // 2. Chuyển câu hỏi hoặc Kết thúc\n const handleContinue = useCallback(() => {\n if (!hasSubmitted) return;\n\n // Check cờ isLastQuestion được set từ lúc nhận kết quả\n if (isLastQuestionRef.current) {\n setIsCompleted(true);\n } else {\n // Yêu cầu câu hỏi tiếp theo\n bridge.send(ACTIONS.SHOW_LO5);\n\n const nextIndex = currentQuestionIndexRef.current + 1;\n currentQuestionIndexRef.current = nextIndex;\n setCurrentQuestionIndex(nextIndex); // Sync state for UI\n\n // Reset state cho câu mới\n setSelectedAnswer(null);\n setIsSubmitting(false);\n setHasSubmitted(false);\n setCurrentResult(null);\n }\n }, [hasSubmitted]);\n\n // 3. UI Handler: Chọn đáp án\n const handleAnswerSelect = useCallback(\n (answer: { id: number; content: string }) => {\n if (!isSubmitting && !hasSubmitted) {\n setSelectedAnswer(answer);\n }\n },\n [isSubmitting, hasSubmitted],\n );\n\n // ================= LISTENERS =================\n useEffect(() => {\n // 1. Start Game: Yêu cầu câu hỏi đầu tiên\n bridge.send(ACTIONS.SHOW_LO5);\n\n // 2. Nhận câu hỏi mới\n bridge.on(ACTIONS.RETURN_LO5, (_data) => {\n // _data ở đây là nội dung câu hỏi (text, audio url, answers array...)\n setQuiz(_data);\n });\n\n // 3. Nhận kết quả trả lời\n bridge.on(ACTIONS.RETURN_UPDATE_ANSWER, (_data) => {\n const isCorrect = _data?.isCorrect === true;\n const isLast = _data?.isLastQuestion === true;\n\n // Lưu cờ báo hiệu câu cuối\n isLastQuestionRef.current = isLast;\n\n // Cập nhật lịch sử trả lời\n setAnswers((prevAnswers) => {\n const next = [...prevAnswers];\n // Đảm bảo array đủ dài (phòng trường hợp nhảy cóc - dù khó xảy ra)\n next[currentQuestionIndexRef.current] = isCorrect;\n return next;\n });\n\n // Xử lý đếm đúng\n if (isCorrect) {\n setCorrectCount((prev) => prev + 1);\n console.log({ currentQuestionIndex: currentQuestionIndexRef.current });\n onAnswerCorrect?.({ currentQuestionIndex: currentQuestionIndexRef.current });\n } else {\n onAnswerIncorrect?.();\n }\n\n // Update UI Status\n setIsSubmitting(false);\n setCurrentResult(_data);\n });\n }, []); // Run once on mount\n\n return {\n // State Data\n quiz, // Dữ liệu câu hỏi hiện tại (để render UI)\n currentResult, // Kết quả vừa trả lời (để hiện giải thích đúng/sai)\n answers, // Mảng lịch sử kết quả (để vẽ progress bar)\n correctCount, // Số câu đúng\n currentQuestionIndex, // Index hiện tại (để UI biết đang ở câu mấy)\n\n // State Status\n selectedAnswer, // Đáp án đang được chọn\n isSubmitting, // Trạng thái đang submmit\n hasSubmitted,\n isCompleted, // Trạng thái khi hoàn thành câu hỏi cuối\n\n // Methods\n handleAnswerSelect, // Hàm gán đáp án user đã chọn\n updateAnswer, // Hàm gửi đáp án user chọn lên web cha\n handleContinue, // Hàm chuyển qua câu hỏi tiếp theo hoặc kết thúc\n finish, // Hàm kết thúc\n };\n}\n","// src/bridge.ts\n\n// Định nghĩa các loại action\nexport const ACTIONS = {\n SHOW_LO5: \"SHOW_LO5\", // Sub -> Main: Web con đã sẵn sàng, yêu cầu web cha gửi câu hỏi\n RETURN_LO5: \"RETURN_LO5\", // Main -> Sub: Web cha gửi nội dung câu hỏi\n UPDATE_ANSWER: \"UPDATE_ANSWER\", // Sub -> Main: Gửi đáp án user chọn cho web cha\n RETURN_UPDATE_ANSWER: \"RETURN_UPDATE_ANSWER\", // Main -> Sub: Web cha gửi lại kết quả đúng/sai cho web con\n FINISH: \"FINISH\", // Sub -> Main: Yêu cầu resize iframe\n};\n\nclass QuestionBridge {\n private handlers: Record<string, (payload: any) => void> = {};\n\n constructor() {\n // Lắng nghe message từ Main Web\n window.addEventListener(\"message\", this.handleMessage.bind(this));\n }\n\n // Hàm nhận message\n private handleMessage(event: MessageEvent) {\n const { type, payload } = event.data;\n if (this.handlers[type]) {\n this.handlers[type](payload);\n }\n }\n\n // Đăng ký hàm xử lý cho từng loại sự kiện (để UI gọi)\n // stw -> lovable\n on(type: string, callback: (payload: any) => void) {\n this.handlers[type] = callback;\n }\n\n // lovable -> stw\n // Gửi message lên Main Web\n send(type: string, payload: any = {}) {\n if (window.parent) {\n window.parent.postMessage({ type, payload }, \"*\"); // Thay '*' bằng domain Main Web khi prod\n }\n }\n}\n\n// Singleton instance để dùng toàn app\nexport const bridge = new QuestionBridge();\n"],"mappings":"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","names":[]}
|
package/dist/index.mjs
DELETED
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
// src/useGameAPI.ts
|
|
2
|
-
import { useState, useCallback, useRef, useEffect } from "react";
|
|
3
|
-
|
|
4
|
-
// src/bridge.ts
|
|
5
|
-
var ACTIONS = {
|
|
6
|
-
SHOW_LO5: "SHOW_LO5",
|
|
7
|
-
// Sub -> Main: Web con đã sẵn sàng, yêu cầu web cha gửi câu hỏi
|
|
8
|
-
RETURN_LO5: "RETURN_LO5",
|
|
9
|
-
// Main -> Sub: Web cha gửi nội dung câu hỏi
|
|
10
|
-
UPDATE_ANSWER: "UPDATE_ANSWER",
|
|
11
|
-
// Sub -> Main: Gửi đáp án user chọn cho web cha
|
|
12
|
-
RETURN_UPDATE_ANSWER: "RETURN_UPDATE_ANSWER",
|
|
13
|
-
// Main -> Sub: Web cha gửi lại kết quả đúng/sai cho web con
|
|
14
|
-
FINISH: "FINISH"
|
|
15
|
-
// Sub -> Main: Yêu cầu resize iframe
|
|
16
|
-
};
|
|
17
|
-
var QuestionBridge = class {
|
|
18
|
-
constructor() {
|
|
19
|
-
this.handlers = {};
|
|
20
|
-
window.addEventListener("message", this.handleMessage.bind(this));
|
|
21
|
-
}
|
|
22
|
-
// Hàm nhận message
|
|
23
|
-
handleMessage(event) {
|
|
24
|
-
const { type, payload } = event.data;
|
|
25
|
-
if (this.handlers[type]) {
|
|
26
|
-
this.handlers[type](payload);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
// Đăng ký hàm xử lý cho từng loại sự kiện (để UI gọi)
|
|
30
|
-
// stw -> lovable
|
|
31
|
-
on(type, callback) {
|
|
32
|
-
this.handlers[type] = callback;
|
|
33
|
-
}
|
|
34
|
-
// lovable -> stw
|
|
35
|
-
// Gửi message lên Main Web
|
|
36
|
-
send(type, payload = {}) {
|
|
37
|
-
if (window.parent) {
|
|
38
|
-
window.parent.postMessage({ type, payload }, "*");
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
var bridge = new QuestionBridge();
|
|
43
|
-
|
|
44
|
-
// src/useGameAPI.ts
|
|
45
|
-
console.log("useGameAPI-V1.0.13");
|
|
46
|
-
function useGameAPI({ onAnswerCorrect, onAnswerIncorrect }) {
|
|
47
|
-
const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0);
|
|
48
|
-
const currentQuestionIndexRef = useRef(0);
|
|
49
|
-
const [selectedAnswer, setSelectedAnswer] = useState(null);
|
|
50
|
-
const [answers, setAnswers] = useState([]);
|
|
51
|
-
const [quiz, setQuiz] = useState(null);
|
|
52
|
-
const [currentResult, setCurrentResult] = useState(null);
|
|
53
|
-
const [correctCount, setCorrectCount] = useState(0);
|
|
54
|
-
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
55
|
-
const [hasSubmitted, setHasSubmitted] = useState(false);
|
|
56
|
-
const [isCompleted, setIsCompleted] = useState(false);
|
|
57
|
-
const isLastQuestionRef = useRef(false);
|
|
58
|
-
const finish = useCallback(() => {
|
|
59
|
-
bridge.send(ACTIONS.FINISH);
|
|
60
|
-
}, []);
|
|
61
|
-
const updateAnswer = useCallback(async () => {
|
|
62
|
-
if (!selectedAnswer || isSubmitting) {
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
bridge.send(ACTIONS.UPDATE_ANSWER, selectedAnswer.id);
|
|
66
|
-
setIsSubmitting(true);
|
|
67
|
-
setHasSubmitted(true);
|
|
68
|
-
return Promise.resolve();
|
|
69
|
-
}, [selectedAnswer, isSubmitting]);
|
|
70
|
-
const handleContinue = useCallback(() => {
|
|
71
|
-
if (!hasSubmitted) return;
|
|
72
|
-
if (isLastQuestionRef.current) {
|
|
73
|
-
setIsCompleted(true);
|
|
74
|
-
} else {
|
|
75
|
-
bridge.send(ACTIONS.SHOW_LO5);
|
|
76
|
-
const nextIndex = currentQuestionIndexRef.current + 1;
|
|
77
|
-
currentQuestionIndexRef.current = nextIndex;
|
|
78
|
-
setCurrentQuestionIndex(nextIndex);
|
|
79
|
-
setSelectedAnswer(null);
|
|
80
|
-
setIsSubmitting(false);
|
|
81
|
-
setHasSubmitted(false);
|
|
82
|
-
setCurrentResult(null);
|
|
83
|
-
}
|
|
84
|
-
}, [hasSubmitted]);
|
|
85
|
-
const handleAnswerSelect = useCallback(
|
|
86
|
-
(answer) => {
|
|
87
|
-
if (!isSubmitting && !hasSubmitted) {
|
|
88
|
-
setSelectedAnswer(answer);
|
|
89
|
-
}
|
|
90
|
-
},
|
|
91
|
-
[isSubmitting, hasSubmitted]
|
|
92
|
-
);
|
|
93
|
-
useEffect(() => {
|
|
94
|
-
bridge.send(ACTIONS.SHOW_LO5);
|
|
95
|
-
bridge.on(ACTIONS.RETURN_LO5, (_data) => {
|
|
96
|
-
setQuiz(_data);
|
|
97
|
-
});
|
|
98
|
-
bridge.on(ACTIONS.RETURN_UPDATE_ANSWER, (_data) => {
|
|
99
|
-
const isCorrect = (_data == null ? void 0 : _data.isCorrect) === true;
|
|
100
|
-
const isLast = (_data == null ? void 0 : _data.isLastQuestion) === true;
|
|
101
|
-
isLastQuestionRef.current = isLast;
|
|
102
|
-
setAnswers((prevAnswers) => {
|
|
103
|
-
const next = [...prevAnswers];
|
|
104
|
-
next[currentQuestionIndexRef.current] = isCorrect;
|
|
105
|
-
return next;
|
|
106
|
-
});
|
|
107
|
-
if (isCorrect) {
|
|
108
|
-
setCorrectCount((prev) => prev + 1);
|
|
109
|
-
console.log({ currentQuestionIndex: currentQuestionIndexRef.current });
|
|
110
|
-
onAnswerCorrect == null ? void 0 : onAnswerCorrect({ currentQuestionIndex: currentQuestionIndexRef.current });
|
|
111
|
-
} else {
|
|
112
|
-
onAnswerIncorrect == null ? void 0 : onAnswerIncorrect();
|
|
113
|
-
}
|
|
114
|
-
setIsSubmitting(false);
|
|
115
|
-
setCurrentResult(_data);
|
|
116
|
-
});
|
|
117
|
-
}, []);
|
|
118
|
-
return {
|
|
119
|
-
// State Data
|
|
120
|
-
quiz,
|
|
121
|
-
// Dữ liệu câu hỏi hiện tại (để render UI)
|
|
122
|
-
currentResult,
|
|
123
|
-
// Kết quả vừa trả lời (để hiện giải thích đúng/sai)
|
|
124
|
-
answers,
|
|
125
|
-
// Mảng lịch sử kết quả (để vẽ progress bar)
|
|
126
|
-
correctCount,
|
|
127
|
-
// Số câu đúng
|
|
128
|
-
currentQuestionIndex,
|
|
129
|
-
// Index hiện tại (để UI biết đang ở câu mấy)
|
|
130
|
-
// State Status
|
|
131
|
-
selectedAnswer,
|
|
132
|
-
// Đáp án đang được chọn
|
|
133
|
-
isSubmitting,
|
|
134
|
-
// Trạng thái đang submmit
|
|
135
|
-
hasSubmitted,
|
|
136
|
-
isCompleted,
|
|
137
|
-
// Trạng thái khi hoàn thành câu hỏi cuối
|
|
138
|
-
// Methods
|
|
139
|
-
handleAnswerSelect,
|
|
140
|
-
// Hàm gán đáp án user đã chọn
|
|
141
|
-
updateAnswer,
|
|
142
|
-
// Hàm gửi đáp án user chọn lên web cha
|
|
143
|
-
handleContinue,
|
|
144
|
-
// Hàm chuyển qua câu hỏi tiếp theo hoặc kết thúc
|
|
145
|
-
finish
|
|
146
|
-
// Hàm kết thúc
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
export {
|
|
150
|
-
useGameAPI
|
|
151
|
-
};
|
|
152
|
-
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/useGameAPI.ts","../src/bridge.ts"],"sourcesContent":["import { useState, useCallback, useRef, useEffect } from \"react\";\n// Lưu ý: Đảm bảo đường dẫn import bridge đúng với cấu trúc dự án của bạn\nimport { bridge, ACTIONS } from \"./bridge\";\n\nconsole.log('useGameAPI-V1.0.13');\n\nexport function useGameAPI({ onAnswerCorrect, onAnswerIncorrect }: any) {\n // ================= STATE =================\n const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0); // State để UI re-render\n const currentQuestionIndexRef = useRef(0); // Ref để logic chạy đúng trong closure\n\n const [selectedAnswer, setSelectedAnswer] = useState<{ id: number; content: string } | null>(null);\n const [answers, setAnswers] = useState<(boolean | null)[]>([]); // Lưu lịch sử đúng/sai\n\n // Game Status\n const [quiz, setQuiz] = useState<any>(null); // Data câu hỏi hiện tại từ Main Web\n const [currentResult, setCurrentResult] = useState<any>(null); // Kết quả trả về sau khi submit\n const [correctCount, setCorrectCount] = useState(0);\n\n // Flow Flags\n const [isSubmitting, setIsSubmitting] = useState(false); // Đang chờ kết quả\n const [hasSubmitted, setHasSubmitted] = useState(false); // Đã có kết quả, chờ bấm Continue\n const [isCompleted, setIsCompleted] = useState(false); // Game hoàn tất\n\n // Biến cờ tạm lưu trạng thái câu cuối cùng từ server trả về\n const isLastQuestionRef = useRef(false);\n\n // ================= ACTIONS =================\n\n const finish = useCallback(() => {\n bridge.send(ACTIONS.FINISH);\n }, []);\n\n // 1. Gửi đáp án lên Main Web\n const updateAnswer = useCallback(async () => {\n if (!selectedAnswer || isSubmitting) {\n return;\n }\n\n bridge.send(ACTIONS.UPDATE_ANSWER, selectedAnswer.id);\n setIsSubmitting(true);\n setHasSubmitted(true);\n\n // Trả về promise giả để UI có thể await nếu cần (ví dụ trigger confetti ngay lập tức)\n return Promise.resolve();\n }, [selectedAnswer, isSubmitting]);\n\n // 2. Chuyển câu hỏi hoặc Kết thúc\n const handleContinue = useCallback(() => {\n if (!hasSubmitted) return;\n\n // Check cờ isLastQuestion được set từ lúc nhận kết quả\n if (isLastQuestionRef.current) {\n setIsCompleted(true);\n } else {\n // Yêu cầu câu hỏi tiếp theo\n bridge.send(ACTIONS.SHOW_LO5);\n\n const nextIndex = currentQuestionIndexRef.current + 1;\n currentQuestionIndexRef.current = nextIndex;\n setCurrentQuestionIndex(nextIndex); // Sync state for UI\n\n // Reset state cho câu mới\n setSelectedAnswer(null);\n setIsSubmitting(false);\n setHasSubmitted(false);\n setCurrentResult(null);\n }\n }, [hasSubmitted]);\n\n // 3. UI Handler: Chọn đáp án\n const handleAnswerSelect = useCallback(\n (answer: { id: number; content: string }) => {\n if (!isSubmitting && !hasSubmitted) {\n setSelectedAnswer(answer);\n }\n },\n [isSubmitting, hasSubmitted],\n );\n\n // ================= LISTENERS =================\n useEffect(() => {\n // 1. Start Game: Yêu cầu câu hỏi đầu tiên\n bridge.send(ACTIONS.SHOW_LO5);\n\n // 2. Nhận câu hỏi mới\n bridge.on(ACTIONS.RETURN_LO5, (_data) => {\n // _data ở đây là nội dung câu hỏi (text, audio url, answers array...)\n setQuiz(_data);\n });\n\n // 3. Nhận kết quả trả lời\n bridge.on(ACTIONS.RETURN_UPDATE_ANSWER, (_data) => {\n const isCorrect = _data?.isCorrect === true;\n const isLast = _data?.isLastQuestion === true;\n\n // Lưu cờ báo hiệu câu cuối\n isLastQuestionRef.current = isLast;\n\n // Cập nhật lịch sử trả lời\n setAnswers((prevAnswers) => {\n const next = [...prevAnswers];\n // Đảm bảo array đủ dài (phòng trường hợp nhảy cóc - dù khó xảy ra)\n next[currentQuestionIndexRef.current] = isCorrect;\n return next;\n });\n\n // Xử lý đếm đúng\n if (isCorrect) {\n setCorrectCount((prev) => prev + 1);\n console.log({ currentQuestionIndex: currentQuestionIndexRef.current });\n onAnswerCorrect?.({ currentQuestionIndex: currentQuestionIndexRef.current });\n } else {\n onAnswerIncorrect?.();\n }\n\n // Update UI Status\n setIsSubmitting(false);\n setCurrentResult(_data);\n });\n }, []); // Run once on mount\n\n return {\n // State Data\n quiz, // Dữ liệu câu hỏi hiện tại (để render UI)\n currentResult, // Kết quả vừa trả lời (để hiện giải thích đúng/sai)\n answers, // Mảng lịch sử kết quả (để vẽ progress bar)\n correctCount, // Số câu đúng\n currentQuestionIndex, // Index hiện tại (để UI biết đang ở câu mấy)\n\n // State Status\n selectedAnswer, // Đáp án đang được chọn\n isSubmitting, // Trạng thái đang submmit\n hasSubmitted,\n isCompleted, // Trạng thái khi hoàn thành câu hỏi cuối\n\n // Methods\n handleAnswerSelect, // Hàm gán đáp án user đã chọn\n updateAnswer, // Hàm gửi đáp án user chọn lên web cha\n handleContinue, // Hàm chuyển qua câu hỏi tiếp theo hoặc kết thúc\n finish, // Hàm kết thúc\n };\n}\n","// src/bridge.ts\n\n// Định nghĩa các loại action\nexport const ACTIONS = {\n SHOW_LO5: \"SHOW_LO5\", // Sub -> Main: Web con đã sẵn sàng, yêu cầu web cha gửi câu hỏi\n RETURN_LO5: \"RETURN_LO5\", // Main -> Sub: Web cha gửi nội dung câu hỏi\n UPDATE_ANSWER: \"UPDATE_ANSWER\", // Sub -> Main: Gửi đáp án user chọn cho web cha\n RETURN_UPDATE_ANSWER: \"RETURN_UPDATE_ANSWER\", // Main -> Sub: Web cha gửi lại kết quả đúng/sai cho web con\n FINISH: \"FINISH\", // Sub -> Main: Yêu cầu resize iframe\n};\n\nclass QuestionBridge {\n private handlers: Record<string, (payload: any) => void> = {};\n\n constructor() {\n // Lắng nghe message từ Main Web\n window.addEventListener(\"message\", this.handleMessage.bind(this));\n }\n\n // Hàm nhận message\n private handleMessage(event: MessageEvent) {\n const { type, payload } = event.data;\n if (this.handlers[type]) {\n this.handlers[type](payload);\n }\n }\n\n // Đăng ký hàm xử lý cho từng loại sự kiện (để UI gọi)\n // stw -> lovable\n on(type: string, callback: (payload: any) => void) {\n this.handlers[type] = callback;\n }\n\n // lovable -> stw\n // Gửi message lên Main Web\n send(type: string, payload: any = {}) {\n if (window.parent) {\n window.parent.postMessage({ type, payload }, \"*\"); // Thay '*' bằng domain Main Web khi prod\n }\n }\n}\n\n// Singleton instance để dùng toàn app\nexport const bridge = new QuestionBridge();\n"],"mappings":";AAAA,SAAS,UAAU,aAAa,QAAQ,iBAAiB;;;ACGlD,IAAM,UAAU;AAAA,EACrB,UAAU;AAAA;AAAA,EACV,YAAY;AAAA;AAAA,EACZ,eAAe;AAAA;AAAA,EACf,sBAAsB;AAAA;AAAA,EACtB,QAAQ;AAAA;AACV;AAEA,IAAM,iBAAN,MAAqB;AAAA,EAGnB,cAAc;AAFd,SAAQ,WAAmD,CAAC;AAI1D,WAAO,iBAAiB,WAAW,KAAK,cAAc,KAAK,IAAI,CAAC;AAAA,EAClE;AAAA;AAAA,EAGQ,cAAc,OAAqB;AACzC,UAAM,EAAE,MAAM,QAAQ,IAAI,MAAM;AAChC,QAAI,KAAK,SAAS,IAAI,GAAG;AACvB,WAAK,SAAS,IAAI,EAAE,OAAO;AAAA,IAC7B;AAAA,EACF;AAAA;AAAA;AAAA,EAIA,GAAG,MAAc,UAAkC;AACjD,SAAK,SAAS,IAAI,IAAI;AAAA,EACxB;AAAA;AAAA;AAAA,EAIA,KAAK,MAAc,UAAe,CAAC,GAAG;AACpC,QAAI,OAAO,QAAQ;AACjB,aAAO,OAAO,YAAY,EAAE,MAAM,QAAQ,GAAG,GAAG;AAAA,IAClD;AAAA,EACF;AACF;AAGO,IAAM,SAAS,IAAI,eAAe;;;ADvCzC,QAAQ,IAAI,oBAAoB;AAEzB,SAAS,WAAW,EAAE,iBAAiB,kBAAkB,GAAQ;AAEtE,QAAM,CAAC,sBAAsB,uBAAuB,IAAI,SAAS,CAAC;AAClE,QAAM,0BAA0B,OAAO,CAAC;AAExC,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,SAAiD,IAAI;AACjG,QAAM,CAAC,SAAS,UAAU,IAAI,SAA6B,CAAC,CAAC;AAG7D,QAAM,CAAC,MAAM,OAAO,IAAI,SAAc,IAAI;AAC1C,QAAM,CAAC,eAAe,gBAAgB,IAAI,SAAc,IAAI;AAC5D,QAAM,CAAC,cAAc,eAAe,IAAI,SAAS,CAAC;AAGlD,QAAM,CAAC,cAAc,eAAe,IAAI,SAAS,KAAK;AACtD,QAAM,CAAC,cAAc,eAAe,IAAI,SAAS,KAAK;AACtD,QAAM,CAAC,aAAa,cAAc,IAAI,SAAS,KAAK;AAGpD,QAAM,oBAAoB,OAAO,KAAK;AAItC,QAAM,SAAS,YAAY,MAAM;AAC/B,WAAO,KAAK,QAAQ,MAAM;AAAA,EAC5B,GAAG,CAAC,CAAC;AAGL,QAAM,eAAe,YAAY,YAAY;AAC3C,QAAI,CAAC,kBAAkB,cAAc;AACnC;AAAA,IACF;AAEA,WAAO,KAAK,QAAQ,eAAe,eAAe,EAAE;AACpD,oBAAgB,IAAI;AACpB,oBAAgB,IAAI;AAGpB,WAAO,QAAQ,QAAQ;AAAA,EACzB,GAAG,CAAC,gBAAgB,YAAY,CAAC;AAGjC,QAAM,iBAAiB,YAAY,MAAM;AACvC,QAAI,CAAC,aAAc;AAGnB,QAAI,kBAAkB,SAAS;AAC7B,qBAAe,IAAI;AAAA,IACrB,OAAO;AAEL,aAAO,KAAK,QAAQ,QAAQ;AAE5B,YAAM,YAAY,wBAAwB,UAAU;AACpD,8BAAwB,UAAU;AAClC,8BAAwB,SAAS;AAGjC,wBAAkB,IAAI;AACtB,sBAAgB,KAAK;AACrB,sBAAgB,KAAK;AACrB,uBAAiB,IAAI;AAAA,IACvB;AAAA,EACF,GAAG,CAAC,YAAY,CAAC;AAGjB,QAAM,qBAAqB;AAAA,IACzB,CAAC,WAA4C;AAC3C,UAAI,CAAC,gBAAgB,CAAC,cAAc;AAClC,0BAAkB,MAAM;AAAA,MAC1B;AAAA,IACF;AAAA,IACA,CAAC,cAAc,YAAY;AAAA,EAC7B;AAGA,YAAU,MAAM;AAEd,WAAO,KAAK,QAAQ,QAAQ;AAG5B,WAAO,GAAG,QAAQ,YAAY,CAAC,UAAU;AAEvC,cAAQ,KAAK;AAAA,IACf,CAAC;AAGD,WAAO,GAAG,QAAQ,sBAAsB,CAAC,UAAU;AACjD,YAAM,aAAY,+BAAO,eAAc;AACvC,YAAM,UAAS,+BAAO,oBAAmB;AAGzC,wBAAkB,UAAU;AAG5B,iBAAW,CAAC,gBAAgB;AAC1B,cAAM,OAAO,CAAC,GAAG,WAAW;AAE5B,aAAK,wBAAwB,OAAO,IAAI;AACxC,eAAO;AAAA,MACT,CAAC;AAGD,UAAI,WAAW;AACb,wBAAgB,CAAC,SAAS,OAAO,CAAC;AAClC,gBAAQ,IAAI,EAAE,sBAAsB,wBAAwB,QAAQ,CAAC;AACrE,2DAAkB,EAAE,sBAAsB,wBAAwB,QAAQ;AAAA,MAC5E,OAAO;AACL;AAAA,MACF;AAGA,sBAAgB,KAAK;AACrB,uBAAiB,KAAK;AAAA,IACxB,CAAC;AAAA,EACH,GAAG,CAAC,CAAC;AAEL,SAAO;AAAA;AAAA,IAEL;AAAA;AAAA,IACA;AAAA;AAAA,IACA;AAAA;AAAA,IACA;AAAA;AAAA,IACA;AAAA;AAAA;AAAA,IAGA;AAAA;AAAA,IACA;AAAA;AAAA,IACA;AAAA,IACA;AAAA;AAAA;AAAA,IAGA;AAAA;AAAA,IACA;AAAA;AAAA,IACA;AAAA;AAAA,IACA;AAAA;AAAA,EACF;AACF;","names":[]}
|