439 lines
14 KiB
JavaScript
439 lines
14 KiB
JavaScript
// What's That!? - Popup Script
|
|
class PopupController {
|
|
constructor() {
|
|
this.currentTab = 'relationships';
|
|
this.stats = null;
|
|
this.availableChats = [];
|
|
this.selectedChat = null;
|
|
this.init();
|
|
}
|
|
|
|
init() {
|
|
this.setupEventListeners();
|
|
this.loadAvailableChats();
|
|
this.loadStats();
|
|
this.checkWhatsAppStatus();
|
|
}
|
|
|
|
setupEventListeners() {
|
|
// Tab switching
|
|
document.querySelectorAll('.tab-button').forEach(button => {
|
|
button.addEventListener('click', (e) => {
|
|
this.switchTab(e.target.dataset.tab);
|
|
});
|
|
});
|
|
|
|
// Action buttons
|
|
document.getElementById('refreshBtn').addEventListener('click', () => {
|
|
this.loadStats();
|
|
});
|
|
|
|
document.getElementById('clearBtn').addEventListener('click', () => {
|
|
this.clearData();
|
|
});
|
|
|
|
// Chat selector
|
|
document.getElementById('chatSelect').addEventListener('change', (e) => {
|
|
this.selectChat(e.target.value);
|
|
});
|
|
|
|
document.getElementById('refreshChatsBtn').addEventListener('click', () => {
|
|
this.loadAvailableChats();
|
|
});
|
|
}
|
|
|
|
switchTab(tabName) {
|
|
// Update tab buttons
|
|
document.querySelectorAll('.tab-button').forEach(btn => {
|
|
btn.classList.remove('active');
|
|
});
|
|
document.querySelector(`[data-tab="${tabName}"]`).classList.add('active');
|
|
|
|
// Update tab panels
|
|
document.querySelectorAll('.tab-panel').forEach(panel => {
|
|
panel.classList.remove('active');
|
|
});
|
|
document.getElementById(tabName).classList.add('active');
|
|
|
|
this.currentTab = tabName;
|
|
this.renderCurrentTab();
|
|
}
|
|
|
|
async loadStats() {
|
|
try {
|
|
this.showLoading();
|
|
|
|
// Request stats from background script
|
|
const response = await chrome.runtime.sendMessage({ type: 'GET_STATS' });
|
|
|
|
if (response && response.stats) {
|
|
this.stats = response.stats;
|
|
this.updateOverview();
|
|
this.renderCurrentTab();
|
|
this.updateStatus('Data loaded successfully', 'success');
|
|
} else {
|
|
this.updateStatus('No data available', 'warning');
|
|
this.showNoData();
|
|
}
|
|
} catch (error) {
|
|
console.error('Error loading stats:', error);
|
|
this.updateStatus('Error loading data', 'error');
|
|
this.showError();
|
|
}
|
|
}
|
|
|
|
updateOverview() {
|
|
if (!this.stats) return;
|
|
|
|
document.getElementById('totalMessages').textContent = this.stats.totalMessages || 0;
|
|
document.getElementById('totalReactions').textContent = this.stats.totalReactions || 0;
|
|
}
|
|
|
|
renderCurrentTab() {
|
|
if (!this.stats) return;
|
|
|
|
switch (this.currentTab) {
|
|
case 'relationships':
|
|
this.renderRelationships();
|
|
break;
|
|
case 'by-sender':
|
|
this.renderBySender();
|
|
break;
|
|
case 'by-reactor':
|
|
this.renderByReactor();
|
|
break;
|
|
case 'top-reactions':
|
|
this.renderTopReactions();
|
|
break;
|
|
}
|
|
}
|
|
|
|
renderRelationships() {
|
|
const container = document.querySelector('.relationships-content');
|
|
|
|
if (!this.stats.relationships || this.stats.relationships.length === 0) {
|
|
container.innerHTML = '<div class="no-data">No relationship data available</div>';
|
|
return;
|
|
}
|
|
|
|
let html = '';
|
|
|
|
// Show top 20 relationships
|
|
this.stats.relationships.slice(0, 20).forEach((rel, index) => {
|
|
const strengthPercent = (rel.strength * 100).toFixed(1);
|
|
const likelihoodPercent = (rel.likelihood * 100).toFixed(1);
|
|
const focusPercent = (rel.focus * 100).toFixed(1);
|
|
|
|
html += `
|
|
<div class="relationship-card">
|
|
<div class="relationship-header">
|
|
<div class="relationship-pair">
|
|
<span class="reactor-name-rel">${this.escapeHtml(rel.from)}</span>
|
|
<span class="arrow">→</span>
|
|
<span class="sender-name-rel">${this.escapeHtml(rel.to)}</span>
|
|
</div>
|
|
<div class="strength-badge">${strengthPercent}% strength</div>
|
|
</div>
|
|
|
|
<div class="relationship-metrics">
|
|
<div class="metric">
|
|
<div class="metric-label">Reactions</div>
|
|
<div class="metric-value">${rel.reactions}</div>
|
|
<div class="metric-subtext">total</div>
|
|
</div>
|
|
|
|
<div class="metric">
|
|
<div class="metric-label">Likelihood</div>
|
|
<div class="metric-value">${likelihoodPercent}%</div>
|
|
<div class="metric-subtext">${rel.reactions}/${rel.totalMessagesBy} msgs</div>
|
|
</div>
|
|
|
|
<div class="metric">
|
|
<div class="metric-label">Focus</div>
|
|
<div class="metric-value">${focusPercent}%</div>
|
|
<div class="metric-subtext">of their reactions</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="progress-bar">
|
|
<div class="progress-fill" style="width: ${strengthPercent}%"></div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
});
|
|
|
|
container.innerHTML = html;
|
|
}
|
|
|
|
renderBySender() {
|
|
const container = document.getElementById('bySenderResults');
|
|
|
|
if (!this.stats.bySender || Object.keys(this.stats.bySender).length === 0) {
|
|
container.innerHTML = '<div class="no-data">No sender data available</div>';
|
|
return;
|
|
}
|
|
|
|
const html = Object.entries(this.stats.bySender)
|
|
.sort((a, b) => {
|
|
const aTotal = Object.values(a[1]).reduce((sum, count) => sum + count, 0);
|
|
const bTotal = Object.values(b[1]).reduce((sum, count) => sum + count, 0);
|
|
return bTotal - aTotal;
|
|
})
|
|
.map(([sender, reactors]) => {
|
|
const totalReactions = Object.values(reactors).reduce((sum, count) => sum + count, 0);
|
|
const topReactor = Object.entries(reactors)
|
|
.sort((a, b) => b[1] - a[1])[0];
|
|
|
|
return `
|
|
<div class="sender-card">
|
|
<div class="sender-header">
|
|
<span class="sender-name">${this.escapeHtml(sender)}</span>
|
|
<span class="reaction-count">${totalReactions} reactions</span>
|
|
</div>
|
|
<div class="reactors-list">
|
|
${Object.entries(reactors)
|
|
.sort((a, b) => b[1] - a[1])
|
|
.slice(0, 5)
|
|
.map(([reactor, count]) => `
|
|
<div class="reactor-item">
|
|
<span class="reactor-name">${this.escapeHtml(reactor)}</span>
|
|
<span class="reactor-count">${count}</span>
|
|
</div>
|
|
`).join('')}
|
|
</div>
|
|
${topReactor ? `
|
|
<div class="top-reactor">
|
|
Most reactions from: <strong>${this.escapeHtml(topReactor[0])}</strong> (${topReactor[1]})
|
|
</div>
|
|
` : ''}
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
|
|
container.innerHTML = html;
|
|
}
|
|
|
|
renderByReactor() {
|
|
const container = document.getElementById('byReactorResults');
|
|
|
|
if (!this.stats.byReactor || Object.keys(this.stats.byReactor).length === 0) {
|
|
container.innerHTML = '<div class="no-data">No reactor data available</div>';
|
|
return;
|
|
}
|
|
|
|
const html = Object.entries(this.stats.byReactor)
|
|
.sort((a, b) => {
|
|
const aTotal = Object.values(a[1]).reduce((sum, count) => sum + count, 0);
|
|
const bTotal = Object.values(b[1]).reduce((sum, count) => sum + count, 0);
|
|
return bTotal - aTotal;
|
|
})
|
|
.map(([reactor, senders]) => {
|
|
const totalReactions = Object.values(senders).reduce((sum, count) => sum + count, 0);
|
|
const topSender = Object.entries(senders)
|
|
.sort((a, b) => b[1] - a[1])[0];
|
|
|
|
return `
|
|
<div class="reactor-card">
|
|
<div class="reactor-header">
|
|
<span class="reactor-name">${this.escapeHtml(reactor)}</span>
|
|
<span class="reaction-count">${totalReactions} reactions given</span>
|
|
</div>
|
|
<div class="senders-list">
|
|
${Object.entries(senders)
|
|
.sort((a, b) => b[1] - a[1])
|
|
.slice(0, 5)
|
|
.map(([sender, count]) => `
|
|
<div class="sender-item">
|
|
<span class="sender-name">${this.escapeHtml(sender)}</span>
|
|
<span class="sender-count">${count}</span>
|
|
</div>
|
|
`).join('')}
|
|
</div>
|
|
${topSender ? `
|
|
<div class="top-sender">
|
|
Reacts most to: <strong>${this.escapeHtml(topSender[0])}</strong> (${topSender[1]})
|
|
</div>
|
|
` : ''}
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
|
|
container.innerHTML = html;
|
|
}
|
|
|
|
renderTopReactions() {
|
|
const container = document.getElementById('topReactionsResults');
|
|
|
|
if (!this.stats.topReactions || Object.keys(this.stats.topReactions).length === 0) {
|
|
container.innerHTML = '<div class="no-data">No top reactions data available</div>';
|
|
return;
|
|
}
|
|
|
|
const html = Object.entries(this.stats.topReactions)
|
|
.map(([sender, reactors]) => `
|
|
<div class="top-reactions-card">
|
|
<div class="sender-name">${this.escapeHtml(sender)}</div>
|
|
<div class="top-reactors">
|
|
${Object.entries(reactors)
|
|
.map(([reactor, count]) => `
|
|
<div class="top-reactor-item">
|
|
<span class="reactor-name">${this.escapeHtml(reactor)}</span>
|
|
<span class="reaction-count">${count}</span>
|
|
</div>
|
|
`).join('')}
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
|
|
container.innerHTML = html;
|
|
}
|
|
|
|
async clearData() {
|
|
if (confirm('Are you sure you want to clear all reaction data?')) {
|
|
try {
|
|
await chrome.runtime.sendMessage({ type: 'CLEAR_DATA' });
|
|
this.stats = null;
|
|
this.updateOverview();
|
|
this.showNoData();
|
|
this.updateStatus('Data cleared', 'success');
|
|
} catch (error) {
|
|
console.error('Error clearing data:', error);
|
|
this.updateStatus('Error clearing data', 'error');
|
|
}
|
|
}
|
|
}
|
|
|
|
checkWhatsAppStatus() {
|
|
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
|
|
const currentTab = tabs[0];
|
|
if (currentTab && currentTab.url.includes('web.whatsapp.com')) {
|
|
this.updateStatus('WhatsApp Web detected', 'success');
|
|
} else {
|
|
this.updateStatus('Open WhatsApp Web to start tracking', 'warning');
|
|
}
|
|
});
|
|
}
|
|
|
|
updateStatus(message, type) {
|
|
const statusText = document.querySelector('.status-text');
|
|
const statusDot = document.querySelector('.status-dot');
|
|
|
|
statusText.textContent = message;
|
|
statusDot.className = `status-dot ${type}`;
|
|
}
|
|
|
|
showLoading() {
|
|
document.querySelectorAll('.results-container').forEach(container => {
|
|
container.innerHTML = '<div class="loading">Loading data...</div>';
|
|
});
|
|
}
|
|
|
|
showNoData() {
|
|
document.querySelectorAll('.results-container').forEach(container => {
|
|
container.innerHTML = '<div class="no-data">No reaction data available yet. Start chatting on WhatsApp Web!</div>';
|
|
});
|
|
}
|
|
|
|
showError() {
|
|
document.querySelectorAll('.results-container').forEach(container => {
|
|
container.innerHTML = '<div class="error">Error loading data. Please try refreshing.</div>';
|
|
});
|
|
}
|
|
|
|
escapeHtml(text) {
|
|
const div = document.createElement('div');
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
}
|
|
|
|
async loadAvailableChats() {
|
|
try {
|
|
// Request available chats from content script
|
|
const response = await chrome.runtime.sendMessage({ type: 'GET_AVAILABLE_CHATS' });
|
|
|
|
if (response && response.chats) {
|
|
this.availableChats = response.chats;
|
|
this.populateChatSelector();
|
|
} else {
|
|
this.showChatSelectorError('No chats found');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error loading chats:', error);
|
|
this.showChatSelectorError('Error loading chats');
|
|
}
|
|
}
|
|
|
|
populateChatSelector() {
|
|
const chatSelect = document.getElementById('chatSelect');
|
|
chatSelect.innerHTML = '';
|
|
|
|
if (this.availableChats.length === 0) {
|
|
chatSelect.innerHTML = '<option value="">No chats available</option>';
|
|
return;
|
|
}
|
|
|
|
// Add default option
|
|
const defaultOption = document.createElement('option');
|
|
defaultOption.value = '';
|
|
defaultOption.textContent = 'Select a chat/group...';
|
|
chatSelect.appendChild(defaultOption);
|
|
|
|
// Add chat options
|
|
this.availableChats.forEach(chat => {
|
|
const option = document.createElement('option');
|
|
option.value = chat.id;
|
|
option.textContent = chat.name;
|
|
chatSelect.appendChild(option);
|
|
});
|
|
}
|
|
|
|
showChatSelectorError(message) {
|
|
const chatSelect = document.getElementById('chatSelect');
|
|
chatSelect.innerHTML = `<option value="">${message}</option>`;
|
|
}
|
|
|
|
async selectChat(chatId) {
|
|
if (!chatId) {
|
|
this.selectedChat = null;
|
|
this.stats = null;
|
|
this.updateOverview();
|
|
this.showNoData();
|
|
return;
|
|
}
|
|
|
|
this.selectedChat = chatId;
|
|
|
|
try {
|
|
// Request stats for the selected chat
|
|
const response = await chrome.runtime.sendMessage({
|
|
type: 'GET_STATS_FOR_CHAT',
|
|
chatId: chatId
|
|
});
|
|
|
|
if (response && response.stats) {
|
|
this.stats = response.stats;
|
|
this.updateOverview();
|
|
this.renderCurrentTab();
|
|
this.updateStatus(`Data loaded for ${this.getChatName(chatId)}`, 'success');
|
|
} else {
|
|
this.updateStatus('No data available for this chat', 'warning');
|
|
this.showNoData();
|
|
}
|
|
} catch (error) {
|
|
console.error('Error loading chat stats:', error);
|
|
this.updateStatus('Error loading chat data', 'error');
|
|
this.showError();
|
|
}
|
|
}
|
|
|
|
getChatName(chatId) {
|
|
const chat = this.availableChats.find(c => c.id === chatId);
|
|
return chat ? chat.name : 'Unknown Chat';
|
|
}
|
|
}
|
|
|
|
// Initialize popup when DOM is loaded
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
new PopupController();
|
|
});
|