1328 lines
40 KiB
JavaScript
1328 lines
40 KiB
JavaScript
/**
|
|
* ========== SENA SDK - MERGED COMPLETE VERSION ==========
|
|
* Combines all features from root, update_1, and update_2
|
|
* - Advanced game type support (G1-G9, G26)
|
|
* - PostMessage handling with late arrival support
|
|
* - Multi-question and single-question modes
|
|
* - G4, G5, G9 specific implementations
|
|
* - Matching, WordSearch, GroupSort, Crossword, Puzzle support
|
|
*/
|
|
|
|
/**
|
|
* ========== GAME TYPES CONFIG ==========
|
|
*/
|
|
var SENA_GAME_TYPES = {
|
|
1: { name: 'Quiz', description: 'Trắc nghiệm' },
|
|
2: { name: 'Sort', description: 'Sắp xếp' },
|
|
3: { name: 'Match', description: 'Nối cặp', sdk: 'tdv_sdk' },
|
|
4: { name: 'Fill', description: 'Điền từ' },
|
|
5: { name: 'Custom', description: 'Tùy chỉnh' },
|
|
6: { name: 'OddOneOut', description: 'Chọn khác loại' },
|
|
7: { name: 'WordSearch', description: 'Tìm từ ẩn', sdk: 'tdv_wordsearch' },
|
|
8: { name: 'GroupSort', description: 'Phân loại nhóm', sdk: 'tdv_groupsort' },
|
|
9: { name: 'Crossword', description: 'Ô chữ', sdk: 'tdv_crossword' },
|
|
26: { name: 'Puzzle', description: 'Ghép hình', sdk: 'tdv_puzzle' }
|
|
};
|
|
|
|
// Helper: Get valid game type range
|
|
function getSenaGameTypeRange() {
|
|
var types = Object.keys(SENA_GAME_TYPES).map(Number);
|
|
return { min: Math.min.apply(null, types), max: Math.max.apply(null, types) };
|
|
}
|
|
|
|
// ========== STUBS FOR LAZY LOADING ==========
|
|
window.tdv_sdk = window.tdv_sdk || {
|
|
isStub: true,
|
|
init: function (mode) {
|
|
console.log('⚠️ tdv_sdk.init() called on Stub. Mode saved:', mode);
|
|
this._pendingMode = mode;
|
|
},
|
|
prepareIDs: function () { },
|
|
getItemCount: function () { return 0; },
|
|
loadFromPostMessage: function () { console.warn('Stub loadFromPostMessage called'); },
|
|
isCorrect: function () { return 0; },
|
|
getCorrectCount: function () { return 0; },
|
|
getTimeSpent: function () { return 0; }
|
|
};
|
|
|
|
// Helper: Regex for game code
|
|
function getSenaGameCodeRegex(strict) {
|
|
var range = getSenaGameTypeRange();
|
|
var typePattern = '[' + range.min + '-' + range.max + ']';
|
|
var qoPattern = strict ? '[0-2]' : '[0-9]';
|
|
return new RegExp('^G(' + typePattern + ')([2-9])(' + qoPattern + ')(' + qoPattern + ')(?:S([0-1]))?(?:T(\\d+))?$');
|
|
}
|
|
|
|
/**
|
|
* Sena SDK Constructor
|
|
*/
|
|
function SenaSDK(gid = 'G2510S1T30') {
|
|
// Core data
|
|
this.data = null;
|
|
this.correctAnswer = null;
|
|
this.gameCode = gid;
|
|
this.timeLimit = 0;
|
|
this.shuffle = true;
|
|
this.uuid = Date.now() + '_' + Math.floor(Math.random() * 100000);
|
|
// Time tracking
|
|
this.startTime = 0;
|
|
this.endTime = 0;
|
|
|
|
// TTS (Web Speech API)
|
|
this.speechSynthesis = window.speechSynthesis;
|
|
this.currentUtterance = null;
|
|
this.voiceSettings = {
|
|
lang: 'en-US',
|
|
rate: 1.0,
|
|
pitch: 1.0,
|
|
volume: 1.0
|
|
};
|
|
|
|
// Multi-question support
|
|
this.list = [];
|
|
this.currentQuestion = null;
|
|
this.level = 0;
|
|
this.totalQuestions = 0;
|
|
this.userResults = [];
|
|
this.gameID = null;
|
|
this.userId = null;
|
|
this.postMessageDataLoaded = false;
|
|
|
|
// Game type flags
|
|
this.isMatchingGame = false;
|
|
this.isOddOneOutGame = false;
|
|
this.isWordSearchGame = false;
|
|
this.isGroupSortGame = false;
|
|
|
|
// PostMessage tracking
|
|
this._postMessageListenerRegistered = false;
|
|
this._waitingForPostMessage = false;
|
|
this._postMessageTimeout = null;
|
|
this._loadCallback = null;
|
|
|
|
// Interaction tracking
|
|
this._gameStartedByUser = false;
|
|
this._dataLoadedFromServer = false;
|
|
|
|
// G5 specific
|
|
this.masterList = [];
|
|
this.currentLevel = 0;
|
|
this.totalLevels = 1;
|
|
this.timePerCard = 0;
|
|
|
|
// Mode: 'live' | 'preview' | 'dev'
|
|
this.mode = 'preview';
|
|
}
|
|
|
|
/**
|
|
* Shuffle array using Fisher-Yates algorithm
|
|
*/
|
|
SenaSDK.prototype.shuffleArray = function (array) {
|
|
for (let i = array.length - 1; i > 0; i--) {
|
|
const j = Math.floor(Math.random() * (i + 1));
|
|
[array[i], array[j]] = [array[j], array[i]];
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Load data from postMessage (SERVER_PUSH_DATA)
|
|
*/
|
|
SenaSDK.prototype.loadFromPostMessage = function (inputJson, callback) {
|
|
let self = this;
|
|
|
|
if (!inputJson) {
|
|
console.error('🎮 Sena SDK: No data in postMessage');
|
|
if (callback) callback(false);
|
|
return false;
|
|
}
|
|
|
|
console.log('📦 Sena SDK: Loading from PostMessage:', inputJson);
|
|
|
|
// Update gameCode if provided
|
|
if (inputJson.gameCode) {
|
|
self.gameCode = inputJson.gameCode;
|
|
}
|
|
|
|
let gameCode = self.gameCode;
|
|
let gameCategory = gameCode.charAt(1);
|
|
|
|
console.log('🎮 Sena SDK: GameCode:', gameCode, '| Category:', gameCategory);
|
|
|
|
// ========== FORWARD TO SPECIALIZED SDKs ==========
|
|
|
|
// Matching (G3xxx)
|
|
if (gameCategory === '3') {
|
|
console.log('🎯 Sena SDK: Detected MATCHING GAME (G3xxx), forwarding to tdv_sdk...');
|
|
if (window.tdv_sdk && typeof window.tdv_sdk.loadFromPostMessage === 'function') {
|
|
self.isMatchingGame = true;
|
|
return window.tdv_sdk.loadFromPostMessage(inputJson, callback);
|
|
} else {
|
|
console.error('❌ tdv_sdk not loaded!');
|
|
if (callback) callback(false);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// WordSearch (G7xxx)
|
|
if (gameCategory === '7') {
|
|
console.log('🎯 Sena SDK: Detected WORDSEARCH GAME (G7xxx), forwarding to tdv_wordsearch...');
|
|
if (window.tdv_wordsearch && typeof window.tdv_wordsearch.loadFromPostMessage === 'function') {
|
|
self.isWordSearchGame = true;
|
|
return window.tdv_wordsearch.loadFromPostMessage(inputJson, callback);
|
|
} else {
|
|
console.error('❌ tdv_wordsearch not loaded!');
|
|
if (callback) callback(false);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// GroupSort (G8xxx)
|
|
if (gameCategory === '8') {
|
|
console.log('🎯 Sena SDK: Detected GROUPSORT GAME (G8xxx), forwarding to tdv_groupsort...');
|
|
if (window.tdv_groupsort && typeof window.tdv_groupsort.loadFromPostMessage === 'function') {
|
|
self.isGroupSortGame = true;
|
|
return window.tdv_groupsort.loadFromPostMessage(inputJson, callback);
|
|
} else {
|
|
console.error('❌ tdv_groupsort not loaded!');
|
|
if (callback) callback(false);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Crossword (G9xxx)
|
|
if (gameCategory === '9') {
|
|
console.log('🎯 Sena SDK: Detected CROSSWORD GAME (G9xxx), forwarding to tdv_crossword...');
|
|
if (window.tdv_crossword && typeof window.tdv_crossword.loadFromPostMessage === 'function') {
|
|
return window.tdv_crossword.loadFromPostMessage(inputJson, callback);
|
|
} else {
|
|
console.error('❌ tdv_crossword not loaded!');
|
|
if (callback) callback(false);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Puzzle (G26xxx)
|
|
if (gameCategory === '2' && gameCode.charAt(2) === '6') {
|
|
console.log('🎯 Sena SDK: Detected PUZZLE GAME (G26xxx), forwarding to tdv_puzzle...');
|
|
if (window.tdv_puzzle && typeof window.tdv_puzzle.loadFromPostMessage === 'function') {
|
|
return window.tdv_puzzle.loadFromPostMessage(inputJson, callback);
|
|
} else {
|
|
console.error('❌ tdv_puzzle not loaded!');
|
|
if (callback) callback(false);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// ========== BASIC GAMES (Quiz, Sort, OddOneOut, Fill, Custom) ==========
|
|
let items = inputJson.list || [inputJson];
|
|
|
|
// Cancel timeout if waiting
|
|
if (self._postMessageTimeout) {
|
|
clearTimeout(self._postMessageTimeout);
|
|
self._postMessageTimeout = null;
|
|
}
|
|
self._waitingForPostMessage = false;
|
|
|
|
// Parse game code
|
|
self._parseGameCode();
|
|
|
|
if (items.length === 1) {
|
|
// Single question
|
|
let item = items[0];
|
|
self.data = {
|
|
question: item.question || item.data?.question || '',
|
|
request: item.request || item.data?.request || '',
|
|
options: item.options || item.data?.options || [],
|
|
image: item.image || item.data?.image || '',
|
|
audio: item.audio || item.data?.audio || '',
|
|
hint: item.hint || item.data?.hint || null,
|
|
reason: item.reason || item.data?.reason || ''
|
|
};
|
|
self.correctAnswer = item.answer;
|
|
self.gameID = item.id || null;
|
|
|
|
// G5 specific: Initialize master list
|
|
if (self.gameType === 5 && self.data?.options) {
|
|
self.masterList = [...self.data.options];
|
|
self.totalLevels = Math.ceil(self.masterList.length / self.itemCount);
|
|
self.currentLevel = 0;
|
|
self.loadLevelG5(1);
|
|
}
|
|
|
|
// G4 specific: Process data
|
|
if (self.gameType === 4) {
|
|
self._processG4Data();
|
|
}
|
|
} else {
|
|
// Multi-question
|
|
self.list = items.map((item, idx) => ({
|
|
id: item.id || idx,
|
|
question: item.question || '',
|
|
request: item.request || '',
|
|
options: item.options || [],
|
|
answer: item.answer,
|
|
image: item.image || '',
|
|
audio: item.audio || '',
|
|
reason: item.reason || ''
|
|
}));
|
|
self.totalQuestions = items.length;
|
|
self.level = 0;
|
|
self._loadCurrentQuestionToData();
|
|
}
|
|
|
|
self.postMessageDataLoaded = true;
|
|
console.log('✅ Sena SDK: Data processed -', items.length, 'item(s)');
|
|
|
|
if (self._loadCallback) {
|
|
self._loadCallback(true);
|
|
self._loadCallback = null;
|
|
}
|
|
|
|
if (callback) callback(true);
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* Parse game code to get settings
|
|
*/
|
|
SenaSDK.prototype._parseGameCode = function () {
|
|
let self = this;
|
|
const gameCode = self.gameCode || 'G2510S1T30';
|
|
const regex = getSenaGameCodeRegex(true);
|
|
let match = gameCode.match(regex);
|
|
|
|
if (match) {
|
|
self.gameType = parseInt(match[1], 10);
|
|
self.itemCount = parseInt(match[2], 10);
|
|
self.questionType = parseInt(match[3], 10);
|
|
self.optionType = parseInt(match[4], 10);
|
|
const shuffleFlag = match[5] !== undefined ? match[5] : '1';
|
|
const timeStr = match[6] !== undefined ? match[6] : '0';
|
|
self.shuffle = (shuffleFlag === '1');
|
|
|
|
// G5 uses T for time per card
|
|
if (self.gameType === 5) {
|
|
self.timePerCard = parseInt(timeStr, 10);
|
|
self.timeLimit = 0;
|
|
} else {
|
|
self.timeLimit = parseInt(timeStr, 10);
|
|
self.timePerCard = 0;
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Load current question to this.data
|
|
*/
|
|
SenaSDK.prototype._loadCurrentQuestionToData = function () {
|
|
let self = this;
|
|
|
|
if (self.list.length > 0 && self.level < self.list.length) {
|
|
self.currentQuestion = self.list[self.level];
|
|
self.data = {
|
|
question: self.currentQuestion.question || '',
|
|
request: self.currentQuestion.request || '',
|
|
options: self.currentQuestion.options || [],
|
|
image: self.currentQuestion.image || '',
|
|
audio: self.currentQuestion.audio || '',
|
|
reason: self.currentQuestion.reason || ''
|
|
};
|
|
self.correctAnswer = self.currentQuestion.answer;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Move to next question
|
|
*/
|
|
SenaSDK.prototype.nextQuestion = function () {
|
|
let self = this;
|
|
|
|
if (self.level < self.totalQuestions - 1) {
|
|
self.level++;
|
|
self._loadCurrentQuestionToData();
|
|
console.log('🎮 Sena SDK: Next Question:', self.level + 1, '/', self.totalQuestions);
|
|
return true;
|
|
}
|
|
|
|
console.log('🎮 Sena SDK: No more questions');
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* Get current question number (1-indexed)
|
|
*/
|
|
SenaSDK.prototype.getCurrentNumber = function () {
|
|
return this.level + 1;
|
|
};
|
|
|
|
/**
|
|
* Get total questions
|
|
*/
|
|
SenaSDK.prototype.getTotalQuestions = function () {
|
|
return this.totalQuestions || 1;
|
|
};
|
|
|
|
// PostMessage timeout setting
|
|
SenaSDK.prototype.POSTMESSAGE_TIMEOUT_MS = 5000; // 5 seconds
|
|
|
|
/**
|
|
* Load data (with mode support)
|
|
*/
|
|
SenaSDK.prototype.load = function (callback, template = 'G2510S1T30') {
|
|
let self = this;
|
|
|
|
// Auto-register postMessage listener
|
|
if (!self._postMessageListenerRegistered) {
|
|
self.registerPostMessageListener();
|
|
self._postMessageListenerRegistered = true;
|
|
}
|
|
|
|
// Already loaded from postMessage
|
|
if (self.postMessageDataLoaded && self.list.length > 0) {
|
|
console.log('🎮 Sena SDK: Data already loaded from postMessage');
|
|
if (callback) callback(true);
|
|
return;
|
|
}
|
|
|
|
// Already have data
|
|
if (self.data && self.data.options) {
|
|
console.log('🎮 Sena SDK: Data already available');
|
|
if (callback) callback(true);
|
|
return;
|
|
}
|
|
|
|
// Get URL parameters
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
const LID = urlParams.get('LID');
|
|
if (LID) {
|
|
self.gameCode = LID;
|
|
}
|
|
|
|
const urlMode = urlParams.get('mode');
|
|
if (urlMode && ['live', 'preview', 'dev'].includes(urlMode.toLowerCase())) {
|
|
self.mode = urlMode.toLowerCase();
|
|
}
|
|
|
|
console.log('🎮 Sena SDK: Mode =', self.mode.toUpperCase(), '| GameCode =', self.gameCode);
|
|
|
|
self._loadCallback = callback;
|
|
|
|
// Mode-based loading
|
|
switch (self.mode) {
|
|
case 'live':
|
|
self._waitingForPostMessage = true;
|
|
console.log('⏳ Sena SDK: [LIVE MODE] Waiting for server data (no timeout)...');
|
|
self._sendGameReady();
|
|
break;
|
|
|
|
case 'dev':
|
|
console.log('🔧 Sena SDK: [DEV MODE] Loading sample data immediately...');
|
|
self._waitingForPostMessage = false;
|
|
self._loadFromServer(callback, template);
|
|
break;
|
|
|
|
case 'preview':
|
|
default:
|
|
self._waitingForPostMessage = true;
|
|
console.log('⏳ Sena SDK: [PREVIEW MODE] Waiting for postMessage (5s timeout)...');
|
|
|
|
self._postMessageTimeout = setTimeout(function () {
|
|
if (self._waitingForPostMessage && !self.postMessageDataLoaded) {
|
|
console.warn('⚠️ Sena SDK: No postMessage received, fallback to sample data...');
|
|
self._loadFromServer(self._loadCallback, template);
|
|
}
|
|
}, self.POSTMESSAGE_TIMEOUT_MS);
|
|
break;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Send GAME_READY message
|
|
*/
|
|
SenaSDK.prototype._sendGameReady = function () {
|
|
let self = this;
|
|
window.parent.postMessage({
|
|
type: "GAME_READY",
|
|
uuid: self.uuid,
|
|
payload: {
|
|
game_id: self.gameID || self.gameCode,
|
|
game_code: self.gameCode,
|
|
mode: self.mode
|
|
}
|
|
}, "*");
|
|
console.log('📤 Sena SDK: Sent GAME_READY to parent');
|
|
console.log('🔑 UUID:', self.uuid);
|
|
};
|
|
|
|
/**
|
|
* Load from server (fallback)
|
|
*/
|
|
SenaSDK.prototype._loadFromServer = function (callback, template = 'G2510S1T30') {
|
|
let self = this;
|
|
self._waitingForPostMessage = false;
|
|
|
|
let url = `https://senaai.tech/sample/${self.gameCode}.json`;
|
|
console.log('📡 Sena SDK: Fetching sample from:', url);
|
|
|
|
fetch(url)
|
|
.then(response => {
|
|
if (!response.ok) throw new Error("HTTP " + response.status);
|
|
return response.json();
|
|
})
|
|
.then(jsonData => {
|
|
console.log('✅ Sena SDK: Fetched data success');
|
|
|
|
self.loadFromPostMessage(jsonData);
|
|
self._dataLoadedFromServer = true;
|
|
})
|
|
.catch(error => {
|
|
console.error('❌ Sena SDK: Error loading data:', error);
|
|
if (self.mode === 'dev') {
|
|
self._loadHardcodedFallback(callback);
|
|
} else {
|
|
if (callback) callback(false);
|
|
}
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Hardcoded fallback (for dev mode when fetch fails)
|
|
*/
|
|
SenaSDK.prototype._loadHardcodedFallback = function (callback) {
|
|
let self = this;
|
|
console.warn('⚠️ Sena SDK: Using HARDCODED fallback data');
|
|
|
|
let fallbackJson = {
|
|
gameCode: self.gameCode,
|
|
data: {
|
|
question: "What is 2 + 2?",
|
|
options: ["3", "4", "5", "6"]
|
|
},
|
|
answer: "4"
|
|
};
|
|
|
|
self.loadFromPostMessage(fallbackJson, callback);
|
|
};
|
|
|
|
/**
|
|
* Validate game code
|
|
*/
|
|
SenaSDK.prototype.validateGameCode = function (code) {
|
|
code = code || this.gameCode;
|
|
const regex = getSenaGameCodeRegex(true);
|
|
const match = code.match(regex);
|
|
const range = getSenaGameTypeRange();
|
|
|
|
if (!match) {
|
|
return { valid: false, error: 'Invalid game code format', code: code };
|
|
}
|
|
|
|
return {
|
|
valid: true,
|
|
code: code,
|
|
type: parseInt(match[1], 10),
|
|
count: parseInt(match[2], 10),
|
|
qType: parseInt(match[3], 10),
|
|
oType: parseInt(match[4], 10),
|
|
shuffle: match[5] !== '0',
|
|
time: match[6] ? parseInt(match[6], 10) : 0,
|
|
description: this.getGameCodeDescription(match)
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Get game code description
|
|
*/
|
|
SenaSDK.prototype.getGameCodeDescription = function (match) {
|
|
if (!match) return '';
|
|
|
|
const gameType = SENA_GAME_TYPES[match[1]]?.name || 'Unknown';
|
|
const contentTypes = ['Text', 'Image', 'Audio'];
|
|
const qType = contentTypes[parseInt(match[3])] || 'Unknown';
|
|
const oType = contentTypes[parseInt(match[4])] || 'Unknown';
|
|
|
|
return `${gameType}: ${match[2]} items, ${qType} → ${oType}`;
|
|
};
|
|
|
|
/**
|
|
* Generate developer guide
|
|
*/
|
|
SenaSDK.prototype.guide = function () {
|
|
let self = this;
|
|
const gameCode = self.gameCode || 'G2510S1T30';
|
|
|
|
return `╔════════════════════════════════════════════════════════════════════════════╗
|
|
║ SENA SDK - DEVELOPER GUIDE: ${gameCode.padEnd(37)}║
|
|
╚════════════════════════════════════════════════════════════════════════════╝
|
|
|
|
📚 MERGED VERSION
|
|
This SDK combines all game types (G1-G9, G26) with full postMessage support.
|
|
|
|
🎮 SUPPORTED GAME TYPES:
|
|
${Object.entries(SENA_GAME_TYPES).map(([key, val]) => ` G${key}: ${val.name} - ${val.description}`).join('\n')}
|
|
|
|
📖 For detailed documentation, visit: https://senaai.tech/docs
|
|
`;
|
|
};
|
|
|
|
// ========== GETTERS ==========
|
|
|
|
SenaSDK.prototype.getQuestionValue = function () {
|
|
var q = String(this.data?.question || '').trim();
|
|
if (q.toLowerCase().startsWith('http')) return '';
|
|
return q;
|
|
};
|
|
|
|
SenaSDK.prototype.getQuestionType = function () {
|
|
let self = this;
|
|
const gameCode = self.gameCode || 'G2510S1T30';
|
|
const regex = getSenaGameCodeRegex(true);
|
|
const match = gameCode.match(regex);
|
|
if (match) {
|
|
const qIdx = match[3];
|
|
const types = { '0': 'text', '1': 'image', '2': 'audio' };
|
|
return types[qIdx] || 'text';
|
|
}
|
|
return 'text';
|
|
};
|
|
|
|
SenaSDK.prototype.getQuestionImage = function () {
|
|
return String(this.data?.question || '').trim();
|
|
};
|
|
|
|
SenaSDK.prototype.getRequestValue = function () {
|
|
return String(this.data?.request || '').trim();
|
|
};
|
|
|
|
SenaSDK.prototype.getRequestType = function () {
|
|
return this.getQuestionType();
|
|
};
|
|
|
|
SenaSDK.prototype.getOptionsCount = function () {
|
|
return this.data?.options?.length || 0;
|
|
};
|
|
|
|
SenaSDK.prototype.getOptionsType = function () {
|
|
let self = this;
|
|
const gameCode = self.gameCode || 'G2510S1T30';
|
|
const regex = getSenaGameCodeRegex(true);
|
|
const match = gameCode.match(regex);
|
|
if (match) {
|
|
const oIdx = match[4];
|
|
const types = { '0': 'text', '1': 'image', '2': 'audio' };
|
|
return types[oIdx] || 'text';
|
|
}
|
|
return 'text';
|
|
};
|
|
|
|
SenaSDK.prototype.getOptionsValue = function (index) {
|
|
return this.data?.options?.[index];
|
|
};
|
|
|
|
SenaSDK.prototype.getHintType = function () {
|
|
return this.data?.hint?.type || '';
|
|
};
|
|
|
|
SenaSDK.prototype.getHintCount = function () {
|
|
const hintValue = this.getHintValue();
|
|
if (hintValue === null) return 0;
|
|
if (Array.isArray(hintValue)) return hintValue.length;
|
|
return 1;
|
|
};
|
|
|
|
SenaSDK.prototype.getHintValue = function (index) {
|
|
if (this.data?.hint?.value !== undefined) {
|
|
const hintValue = this.data.hint.value;
|
|
const hintType = this.getHintType();
|
|
|
|
if (hintType === 'display' && Array.isArray(hintValue)) {
|
|
if (index !== undefined && index >= 0 && index < hintValue.length) {
|
|
return hintValue[index];
|
|
}
|
|
return hintValue;
|
|
}
|
|
|
|
return hintValue;
|
|
}
|
|
return null;
|
|
};
|
|
|
|
SenaSDK.prototype.getReason = function () {
|
|
return this.data?.reason || this.currentQuestion?.reason || '';
|
|
};
|
|
|
|
SenaSDK.prototype.hasReason = function () {
|
|
let reason = this.getReason();
|
|
return (reason && reason.length > 0) ? 1 : 0;
|
|
};
|
|
|
|
// ========== G9 MEMORY FUNCTIONS ==========
|
|
|
|
SenaSDK.prototype.getTargetName = function () {
|
|
if (this.correctAnswer) {
|
|
if (typeof this.correctAnswer === 'object' && this.correctAnswer.text) {
|
|
return this.correctAnswer.text;
|
|
}
|
|
return String(this.correctAnswer);
|
|
}
|
|
return '';
|
|
};
|
|
|
|
SenaSDK.prototype.getCardName = function (index) {
|
|
if (this.data?.options?.[index]) {
|
|
let opt = this.data.options[index];
|
|
return opt.name || opt.text || '';
|
|
}
|
|
return '';
|
|
};
|
|
|
|
SenaSDK.prototype.getCardImage = function (index) {
|
|
return this.data?.options?.[index]?.image || '';
|
|
};
|
|
|
|
SenaSDK.prototype.getCardAudio = function (index) {
|
|
return this.data?.options?.[index]?.audio || '';
|
|
};
|
|
|
|
SenaSDK.prototype.getCardID = function (index) {
|
|
return this.data?.options?.[index]?.id || '';
|
|
};
|
|
|
|
SenaSDK.prototype.getCardType = function (index) {
|
|
if (this.data?.options?.[index]) {
|
|
return this.data.options[index].type || 'text';
|
|
}
|
|
if (this.masterList?.[index]) {
|
|
return this.masterList[index].type || 'text';
|
|
}
|
|
return 'text';
|
|
};
|
|
|
|
// ========== G5 VOICE RUSH FUNCTIONS ==========
|
|
|
|
SenaSDK.prototype.loadLevelG5 = function (levelIndex) {
|
|
let self = this;
|
|
if (self.gameType !== 5 || !self.masterList) return false;
|
|
|
|
self.currentLevel = levelIndex;
|
|
let count = self.itemCount;
|
|
window.Sena_TotalLevels = Math.ceil(self.masterList.length / count);
|
|
|
|
let startIndex = (levelIndex - 1) * count;
|
|
let endIndex = Math.min(startIndex + count, self.masterList.length);
|
|
let levelOptions = self.masterList.slice(startIndex, endIndex);
|
|
|
|
self.data.options = levelOptions;
|
|
|
|
console.log(`🎮 Sena SDK: Loaded G5 Level ${levelIndex} with ${levelOptions.length} cards`);
|
|
return true;
|
|
};
|
|
|
|
SenaSDK.prototype.getTotalLevels = function () {
|
|
return this.totalLevels || 1;
|
|
};
|
|
|
|
SenaSDK.prototype.getTimePerCard = function () {
|
|
if (this.timePerCard === undefined) {
|
|
this._parseGameCode();
|
|
}
|
|
return this.timePerCard > 0 ? this.timePerCard : 5;
|
|
};
|
|
|
|
// ========== G4 MEMORY CARD FUNCTIONS ==========
|
|
|
|
SenaSDK.prototype._processG4Data = function () {
|
|
let self = this;
|
|
if (!self.data.options) self.data.options = [];
|
|
|
|
// Count occurrences
|
|
let counts = {};
|
|
self.data.options.forEach(item => {
|
|
if (item.type !== 'blank' && item.name) {
|
|
counts[item.name] = (counts[item.name] || 0) + 1;
|
|
}
|
|
});
|
|
|
|
// Remove orphans (items that appear less than 2 times)
|
|
self.data.options.forEach(item => {
|
|
if (item.type !== 'blank' && item.name && counts[item.name] < 2) {
|
|
console.log('🎮 Sena SDK: Orphan card detected:', item.name);
|
|
item.type = 'blank';
|
|
item.name = 'blank';
|
|
item.image = '';
|
|
item.id = 'blank_sanitized';
|
|
}
|
|
});
|
|
|
|
// Fill with blank cards to make 9 total
|
|
while (self.data.options.length < 9) {
|
|
self.data.options.push({
|
|
id: 'blank_' + self.data.options.length,
|
|
type: 'blank',
|
|
name: 'blank',
|
|
value: -1,
|
|
image: ''
|
|
});
|
|
}
|
|
|
|
// Shuffle
|
|
if (self.shuffle) {
|
|
self.shuffleArray(self.data.options);
|
|
}
|
|
};
|
|
|
|
SenaSDK.prototype.checkPair = function (idx1, idx2, callback) {
|
|
let self = this;
|
|
|
|
let card1 = self.data?.options?.[idx1];
|
|
let card2 = self.data?.options?.[idx2];
|
|
|
|
if (!card1 || !card2) {
|
|
if (callback) callback(false);
|
|
return;
|
|
}
|
|
|
|
let isMatch = false;
|
|
|
|
if (card1.type !== 'blank' && card2.type !== 'blank') {
|
|
if (card1.id !== card2.id) {
|
|
if (card1.name && card2.name && card1.name.toLowerCase() === card2.name.toLowerCase()) {
|
|
isMatch = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
console.log(`🎮 Sena SDK: Check Pair [${idx1}] vs [${idx2}] -> ${isMatch ? 'MATCH' : 'WRONG'}`);
|
|
|
|
if (callback) callback(isMatch);
|
|
};
|
|
|
|
// ========== GAME ACTIONS ==========
|
|
|
|
SenaSDK.prototype.start = function () {
|
|
let self = this;
|
|
|
|
// Forward to specialized SDKs
|
|
if (self.isMatchingGame && window.tdv_sdk?.start) {
|
|
console.log('🎮 Sena SDK: Forwarding start() to tdv_sdk');
|
|
window.tdv_sdk.start();
|
|
return;
|
|
}
|
|
|
|
// Standard games
|
|
self.curIndex = 0;
|
|
if (self.shuffle && self.data?.options) {
|
|
self.shuffleArray(self.data.options);
|
|
}
|
|
self.startTime = Date.now();
|
|
};
|
|
|
|
SenaSDK.prototype.markUserInteraction = function () {
|
|
if (!this._gameStartedByUser) {
|
|
this._gameStartedByUser = true;
|
|
console.log('🎮 Sena SDK: User interaction detected');
|
|
|
|
if (this.isMatchingGame && window.tdv_sdk) {
|
|
window.tdv_sdk._gameStartedByUser = true;
|
|
}
|
|
}
|
|
};
|
|
|
|
SenaSDK.prototype.canReloadData = function () {
|
|
return !this._gameStartedByUser;
|
|
};
|
|
|
|
SenaSDK.prototype.end = function (answer, callback) {
|
|
let self = this;
|
|
const category = self.gameCode ? self.gameCode.charAt(1) : '';
|
|
|
|
// ========== FORWARD TO SPECIALIZED SDKs ==========
|
|
|
|
// Crossword (G9)
|
|
if (category === '9' && window.tdv_crossword) {
|
|
let isCorrect = (window.tdv_crossword.placedCount >= window.tdv_crossword.totalCells);
|
|
let duration = window.tdv_crossword.getTimeSpent?.() || 0;
|
|
|
|
console.log(`🎮 Sena SDK: Crossword Result: ${isCorrect ? 'WIN' : 'LOSE'}`);
|
|
|
|
if (callback) callback(isCorrect);
|
|
return { isCorrect, duration };
|
|
}
|
|
|
|
// Matching/GroupSort (G3/G8)
|
|
if ((category === '8' || category === '3' || self.isMatchingGame) && window.tdv_sdk) {
|
|
console.log('🎮 Sena SDK: Forwarding end() to tdv_sdk');
|
|
|
|
let isCorrect = false;
|
|
let duration = 0;
|
|
|
|
if (String(answer || '').includes(':') && category === '8') {
|
|
isCorrect = self.validateCheck(answer);
|
|
} else {
|
|
isCorrect = window.tdv_sdk.isCorrect?.() === 1;
|
|
}
|
|
|
|
duration = window.tdv_sdk.getTimeSpent?.() || 0;
|
|
|
|
if (callback) callback(isCorrect);
|
|
return { isCorrect, duration };
|
|
}
|
|
|
|
// ========== STANDARD GAMES (Quiz/Sort/Fill) ==========
|
|
|
|
self.endTime = Date.now();
|
|
const duration = (self.endTime - self.startTime) / 1000;
|
|
|
|
const answerStr = String(answer || '');
|
|
const userAnswers = answerStr.includes('|')
|
|
? answerStr.split('|').map(a => a.trim().toLowerCase()).filter(a => a)
|
|
: [answerStr.trim().toLowerCase()].filter(a => a);
|
|
|
|
let correctAnswers = [];
|
|
if (self.correctAnswer) {
|
|
if (Array.isArray(self.correctAnswer)) {
|
|
correctAnswers = self.correctAnswer.map(a => {
|
|
if (typeof a === 'string') return a.toLowerCase();
|
|
if (a.text) return a.text.toLowerCase();
|
|
return '';
|
|
});
|
|
} else if (typeof self.correctAnswer === 'string') {
|
|
correctAnswers = self.correctAnswer.includes('|')
|
|
? self.correctAnswer.split('|').map(a => a.trim().toLowerCase())
|
|
: [self.correctAnswer.toLowerCase()];
|
|
} else if (self.correctAnswer.text) {
|
|
correctAnswers = [self.correctAnswer.text.toLowerCase()];
|
|
}
|
|
}
|
|
|
|
// Compare answers
|
|
const isStrictOrder = self.gameType === 2; // Sort game
|
|
const finalUser = isStrictOrder ? [...userAnswers] : [...userAnswers].sort();
|
|
const finalCorrect = isStrictOrder ? [...correctAnswers] : [...correctAnswers].sort();
|
|
|
|
let isCorrect = false;
|
|
|
|
const getFileName = (url) => {
|
|
if (!url.startsWith('http')) return url;
|
|
return url.split('/').pop().split('?')[0];
|
|
};
|
|
|
|
if (finalUser.length === finalCorrect.length) {
|
|
isCorrect = finalUser.every((uVal, index) => {
|
|
let cVal = finalCorrect[index];
|
|
if (uVal === cVal) return true;
|
|
if (uVal.startsWith('http') || cVal.startsWith('http')) {
|
|
return getFileName(uVal) === getFileName(cVal);
|
|
}
|
|
return false;
|
|
});
|
|
}
|
|
|
|
// Check time limit
|
|
if (self.timeLimit > 0 && duration > self.timeLimit) {
|
|
isCorrect = false;
|
|
console.log('🎮 Sena SDK: Time limit exceeded');
|
|
}
|
|
|
|
const result = {
|
|
isCorrect: isCorrect,
|
|
duration: duration,
|
|
correctAnswer: correctAnswers.join(' | '),
|
|
userAnswer: userAnswers.join(' | ')
|
|
};
|
|
|
|
console.log(`🎮 Sena SDK: Result: ${isCorrect ? 'CORRECT' : 'INCORRECT'} (${duration}s)`);
|
|
|
|
window.parent.postMessage(
|
|
{
|
|
type: "GAME_RESULT",
|
|
uuid: self.uuid,
|
|
payload: {
|
|
game_id: self.gameID || self.gameCode,
|
|
result: isCorrect ? "CORRECT" : "INCORRECT",
|
|
time_spent: duration
|
|
}
|
|
},
|
|
"*"
|
|
);
|
|
|
|
console.log("📤 Sena SDK: GAME_RESULT sent successfully");
|
|
|
|
if (callback) callback(result.isCorrect);
|
|
return result;
|
|
};
|
|
|
|
SenaSDK.prototype.validateCheck = function (answer) {
|
|
let self = this;
|
|
const answerStr = String(answer || '');
|
|
const userAnswers = answerStr.includes('|')
|
|
? answerStr.split('|').map(a => a.trim().toLowerCase())
|
|
: [answerStr.trim().toLowerCase()];
|
|
|
|
let correctAnswers = [];
|
|
if (self.correctAnswer) {
|
|
if (Array.isArray(self.correctAnswer)) {
|
|
correctAnswers = self.correctAnswer.map(a => {
|
|
if (typeof a === 'string') return a.toLowerCase();
|
|
if (a.hasOwnProperty('left') && a.hasOwnProperty('right')) {
|
|
return (String(a.left) + ':' + String(a.right)).toLowerCase();
|
|
}
|
|
return '';
|
|
});
|
|
}
|
|
}
|
|
|
|
// Partial check for GroupSort
|
|
if (userAnswers.length === 1 && correctAnswers.length > 1) {
|
|
return correctAnswers.includes(userAnswers[0]);
|
|
}
|
|
|
|
// Full check
|
|
if (userAnswers.length === correctAnswers.length) {
|
|
return userAnswers.every((ans, index) => ans === correctAnswers[index]);
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
// ========== AUDIO ==========
|
|
|
|
SenaSDK.prototype.playVoice = function (type) {
|
|
let self = this;
|
|
let textToSpeak = '';
|
|
|
|
if (type.startsWith('option')) {
|
|
const optionIndex = parseInt(type.slice(6)) - 1;
|
|
textToSpeak = self.getOptionsValue(optionIndex);
|
|
} else if (type === 'question') {
|
|
textToSpeak = self.getQuestionValue();
|
|
} else if (type === 'request') {
|
|
textToSpeak = self.getRequestValue();
|
|
} else if (type === 'hint') {
|
|
const hintValue = self.getHintValue();
|
|
if (typeof hintValue === 'string') {
|
|
textToSpeak = hintValue;
|
|
}
|
|
}
|
|
|
|
if (textToSpeak === '') return;
|
|
|
|
if (window['audioPlayer']) {
|
|
window['audioPlayer'].pause();
|
|
window['audioPlayer'].src = textToSpeak;
|
|
window['audioPlayer'].play();
|
|
} else {
|
|
window['audioPlayer'] = new Audio(textToSpeak);
|
|
window['audioPlayer'].play();
|
|
}
|
|
};
|
|
|
|
// ========== POSTMESSAGE ==========
|
|
|
|
SenaSDK.prototype.registerPostMessageListener = function () {
|
|
let self = this;
|
|
|
|
window.addEventListener("message", function (event) {
|
|
if (!event.data || !event.data.type) return;
|
|
|
|
switch (event.data.type) {
|
|
case "SERVER_PUSH_DATA":
|
|
console.log('📥 Sena SDK: Received SERVER_PUSH_DATA');
|
|
|
|
if (self._postMessageTimeout) {
|
|
clearTimeout(self._postMessageTimeout);
|
|
self._postMessageTimeout = null;
|
|
}
|
|
self._waitingForPostMessage = false;
|
|
|
|
// Nếu trước đó load bằng server rồi
|
|
if (self._dataLoadedFromServer && !self._gameStartedByUser) {
|
|
console.log('🔄 PostMessage late, reloading data');
|
|
self.postMessageDataLoaded = false;
|
|
self._dataLoadedFromServer = false;
|
|
}
|
|
|
|
if (self._gameStartedByUser) {
|
|
console.log('⚠️ Sena SDK: PostMessage ignored (user already playing)');
|
|
return;
|
|
}
|
|
|
|
self.loadFromPostMessage(event.data.jsonData);
|
|
|
|
if (self._loadCallback) {
|
|
let cb = self._loadCallback;
|
|
self._loadCallback = null;
|
|
cb(true);
|
|
}
|
|
break;
|
|
|
|
case "SYNC_TIME":
|
|
if (event.data.end_time_iso) {
|
|
let endTimeDate = new Date(event.data.end_time_iso);
|
|
let now = new Date();
|
|
self.timeLimit = Math.max(0, Math.floor((endTimeDate - now) / 1000));
|
|
console.log('🎮 Sena SDK: Time synced:', self.timeLimit, 's');
|
|
}
|
|
break;
|
|
|
|
case "SDK_DATA_READY":
|
|
console.log('📥 Sena SDK: Received SDK_DATA_READY');
|
|
self._handleSdkDataReady(event.data.payload);
|
|
break;
|
|
|
|
case "SDK_PUSH_DATA":
|
|
console.log('📥 Sena SDK: Received SDK_PUSH_DATA');
|
|
if (event.data.payload?.items) {
|
|
self._handleSdkPushData(event.data.payload.items);
|
|
}
|
|
break;
|
|
|
|
case "SEQUENCE_SYNC": // Đổi ở đây
|
|
console.log("📥 Sena SDK: Received SEQUENCE_SYNC", event.data);
|
|
if (event.data.uuid === self.uuid) {
|
|
console.log("🔄 Sena SDK: Own message echoed back, processing...");
|
|
}
|
|
|
|
if (typeof self.onCustomMessage === "function") {
|
|
self.onCustomMessage(event.data.data, event.data.uuid);
|
|
}
|
|
break;
|
|
|
|
case "SDK_ERROR":
|
|
console.error('❌ Sena SDK: Received SDK_ERROR', event.data.payload);
|
|
break;
|
|
}
|
|
});
|
|
|
|
console.log('🎮 Sena SDK: PostMessage listener registered');
|
|
};
|
|
|
|
/**
|
|
* Send custom message to parent window with UUID
|
|
* @param {Object} data - Custom JSON data to send
|
|
*/
|
|
SenaSDK.prototype.sendMessageToParent = function (data) {
|
|
let self = this;
|
|
|
|
if (!data || typeof data !== 'object') {
|
|
console.error('❌ Sena SDK: sendMessageToParent requires data object');
|
|
return false;
|
|
}
|
|
|
|
let payload = {
|
|
type: "Sequence_Sync",
|
|
uuid: self.uuid,
|
|
data: data,
|
|
timestamp: Date.now()
|
|
};
|
|
|
|
window.parent.postMessage(payload, "*");
|
|
|
|
console.log('📤 Sena SDK: Sent Sequence_Sync to parent');
|
|
console.log('🔑 UUID:', self.uuid);
|
|
console.log('📦 Data:', data);
|
|
|
|
return true;
|
|
};
|
|
|
|
SenaSDK.prototype._handleSdkDataReady = function (payload) {
|
|
let self = this;
|
|
|
|
if (!payload?.items) {
|
|
console.error('🎮 Sena SDK: SDK_DATA_READY missing items');
|
|
return;
|
|
}
|
|
|
|
if (self._postMessageTimeout) {
|
|
clearTimeout(self._postMessageTimeout);
|
|
self._postMessageTimeout = null;
|
|
}
|
|
self._waitingForPostMessage = false;
|
|
|
|
let items = payload.items;
|
|
|
|
if (items.length === 1) {
|
|
let item = items[0];
|
|
self.data = {
|
|
question: item.question || '',
|
|
request: item.question || '',
|
|
options: item.options?.map(o => o.text || o.audio || o) || []
|
|
};
|
|
self.gameID = item.id;
|
|
} else {
|
|
self.list = items.map((item, idx) => ({
|
|
id: item.id || idx,
|
|
question: item.question || '',
|
|
options: item.options?.map(o => o.text || o) || [],
|
|
answer: null
|
|
}));
|
|
self.totalQuestions = items.length;
|
|
self.level = payload.completed_count || 0;
|
|
self._loadCurrentQuestionToData();
|
|
}
|
|
|
|
self.postMessageDataLoaded = true;
|
|
console.log('✅ Sena SDK: SDK_DATA_READY processed');
|
|
|
|
if (self._loadCallback) {
|
|
self._loadCallback(true);
|
|
self._loadCallback = null;
|
|
}
|
|
};
|
|
|
|
SenaSDK.prototype._handleSdkPushData = function (items) {
|
|
let self = this;
|
|
|
|
if (!items || items.length === 0) {
|
|
console.error('🎮 Sena SDK: SDK_PUSH_DATA missing items');
|
|
return;
|
|
}
|
|
|
|
if (self._postMessageTimeout) {
|
|
clearTimeout(self._postMessageTimeout);
|
|
self._postMessageTimeout = null;
|
|
}
|
|
self._waitingForPostMessage = false;
|
|
|
|
if (items.length === 1) {
|
|
let item = items[0];
|
|
self.data = {
|
|
question: item.question || '',
|
|
options: item.options || []
|
|
};
|
|
self.correctAnswer = item.answer;
|
|
self.gameID = item.id;
|
|
} else {
|
|
self.list = items.map((item, idx) => ({
|
|
id: item.id || idx,
|
|
question: item.question || '',
|
|
options: item.options || [],
|
|
answer: item.answer
|
|
}));
|
|
self.totalQuestions = items.length;
|
|
self.level = 0;
|
|
self._loadCurrentQuestionToData();
|
|
}
|
|
|
|
self.postMessageDataLoaded = true;
|
|
console.log('✅ Sena SDK: SDK_PUSH_DATA processed');
|
|
|
|
if (self._loadCallback) {
|
|
self._loadCallback(true);
|
|
self._loadCallback = null;
|
|
}
|
|
};
|
|
|
|
SenaSDK.prototype.getTimeSpent = function () {
|
|
if (!this.startTime) return 0;
|
|
return Math.floor((Date.now() - this.startTime) / 1000);
|
|
};
|
|
|
|
// ========== MULTI-QUESTION SUPPORT ==========
|
|
|
|
SenaSDK.prototype.play = function (selectedText, isTimeout) {
|
|
let self = this;
|
|
|
|
if (!self.currentQuestion && self.data) {
|
|
let result = { isCorrect: false, result: 0 };
|
|
self.end(selectedText, function (isCorrect) {
|
|
result.isCorrect = isCorrect;
|
|
result.result = isCorrect ? 1 : 0;
|
|
});
|
|
return result.result;
|
|
}
|
|
|
|
if (!self.currentQuestion) return 0;
|
|
|
|
let alreadyAnswered = self.userResults.find(r => r.id === self.currentQuestion.id);
|
|
if (alreadyAnswered) return alreadyAnswered.result;
|
|
|
|
let isCorrect = false;
|
|
let userChoice = null;
|
|
|
|
if (isTimeout === true || String(isTimeout) === 'true') {
|
|
isCorrect = false;
|
|
userChoice = null;
|
|
} else {
|
|
userChoice = String(selectedText).trim();
|
|
let correctAnswer = String(self.currentQuestion.answer).trim();
|
|
isCorrect = (userChoice.toLowerCase() === correctAnswer.toLowerCase());
|
|
}
|
|
|
|
let resultValue = isCorrect ? 1 : 0;
|
|
|
|
self.userResults.push({ id: self.currentQuestion.id, result: resultValue });
|
|
|
|
console.log('🎮 Sena SDK: Answer Result:', resultValue);
|
|
|
|
return resultValue;
|
|
};
|
|
|
|
SenaSDK.prototype.submitResults = function () {
|
|
let self = this;
|
|
|
|
let uniqueResults = [];
|
|
let seenIds = {};
|
|
for (let i = self.userResults.length - 1; i >= 0; i--) {
|
|
if (!seenIds[self.userResults[i].id]) {
|
|
uniqueResults.unshift(self.userResults[i]);
|
|
seenIds[self.userResults[i].id] = true;
|
|
}
|
|
}
|
|
|
|
let correctCount = uniqueResults.filter(r => r.result === 1).length;
|
|
let totalScore = self.totalQuestions > 0
|
|
? Math.round((correctCount / self.totalQuestions) * 100) / 10
|
|
: 0;
|
|
let timeSpent = Math.floor((Date.now() - self.startTime) / 1000);
|
|
|
|
let finalData = {
|
|
game_id: self.gameID,
|
|
score: totalScore,
|
|
time_spent: timeSpent,
|
|
correct_count: correctCount,
|
|
total_questions: self.totalQuestions,
|
|
details: uniqueResults
|
|
};
|
|
|
|
console.log('🎮 Sena SDK: Final Score:', totalScore);
|
|
|
|
window.parent.postMessage({
|
|
type: "FINAL_RESULT",
|
|
uuid: self.uuid,
|
|
data: finalData
|
|
}, "*");
|
|
|
|
return finalData;
|
|
};
|
|
|
|
// ========== HELPER ==========
|
|
|
|
SenaSDK.prototype.helper = {};
|
|
|
|
SenaSDK.prototype.helper.CalcObjectPositions = function (n, objectWidth, margin, maxWidth) {
|
|
let self = this;
|
|
self.positions = [];
|
|
const totalWidth = n * objectWidth + (n - 1) * margin;
|
|
const startX = (maxWidth - totalWidth) / 2;
|
|
|
|
for (let i = 0; i < n; i++) {
|
|
self.positions.push(startX + i * (objectWidth + margin) + objectWidth / 2);
|
|
}
|
|
};
|
|
|
|
SenaSDK.prototype.helper.getPosXbyIndex = function (index) {
|
|
if (index < 0 || index >= this.positions.length) return null;
|
|
return this.positions[index];
|
|
};
|
|
|
|
// ========== EXPORT ==========
|
|
|
|
if (typeof module !== 'undefined' && module.exports) {
|
|
module.exports = SenaSDK;
|
|
} else if (typeof define === 'function' && define.amd) {
|
|
define([], function () { return SenaSDK; });
|
|
} else {
|
|
window.SenaSDK = SenaSDK;
|
|
}
|