// ==UserScript==
// @name Torn City Reviver's Tool
// @namespace http://tampermonkey.net/
// @version 2.1.1
// @description A tool to make it easier to revive people while in the Hospital page: Checks if users are available for revive in Torn City while in the hospital page, displays their names with revive buttons, and updates on page change, with collapsible box feature, status filter (Active, Idle, Offline), sorting options, pagination and manual refresh. Now with resizable box!
// @author LilyWaterbug [2608747]
// @match https://www.torn.com/hospitalview.php*
// @grant none
// @license MIT
// ==/UserScript==
(function() {
'use strict';
const style = document.createElement('style');
style.textContent = `
/* Ocultar apariencia nativa */
.torn-filter-checkbox {
appearance: none;
-webkit-appearance: none;
width: 14px;
height: 14px;
border: 2px solid currentColor;
border-radius: 3px;
margin-right: 6px;
cursor: pointer;
transition: background-color 0.2s, transform 0.1s, box-shadow 0.2s;
/* 💥 Sombra por defecto */
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
/* Cuando está activado, colorear totalmente */
.torn-filter-checkbox:checked {
background-color: currentColor;
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.2);
}
/* Pequeña animación al hacer click */
.torn-filter-checkbox:active {
transform: scale(0.9);
}
.torn-filter-label {
display: flex;
align-items: center;
font-size: 13px;
font-weight: bold;
cursor: pointer;
}
`;
document.head.appendChild(style);
// Create a collapsible box to display available users
const resultBox = document.createElement('div');
resultBox.classList.add('torn-reviver-box');
// Default initial position on left
const defaultLeft = '10px';
const defaultTop = '10px';
const defaultWidth = '210px';
const defaultHeight = '';
resultBox.style.position = 'fixed';
resultBox.style.left = defaultLeft;
resultBox.style.top = defaultTop;
resultBox.style.width = '210px';
resultBox.style.maxHeight = '80vh';
resultBox.style.overflowY = 'auto';
resultBox.style.padding = '10px';
resultBox.style.backgroundColor = '#f4f4f4';
resultBox.style.border = '1px solid #ccc';
resultBox.style.zIndex = '1000';
resultBox.style.fontSize = '14px';
resultBox.style.borderRadius = '8px';
resultBox.style.boxShadow = '0 2px 5px rgba(0,0,0,0.2)';
resultBox.style.resize = 'both';
resultBox.style.overflow = 'auto';
// Title bar container
const titleBar = document.createElement('div');
titleBar.style.display = 'flex';
titleBar.style.justifyContent = 'space-between';
titleBar.style.alignItems = 'center';
titleBar.style.cursor = 'grab';
titleBar.style.marginBottom = '8px';
// Title text and counter
const titleText = document.createElement('strong');
titleText.innerHTML = 'Available to revive (<span id="user-count">0</span>):';
titleBar.appendChild(titleText);
// Controls in title: reset and collapse
const titleControls = document.createElement('div');
titleControls.style.display = 'flex';
titleControls.style.alignItems = 'center';
// Reset button
const resetBtn = document.createElement('button');
resetBtn.textContent = '⟳';
resetBtn.title = 'Reset position';
resetBtn.style.marginRight = '4px';
resetBtn.style.cursor = 'pointer';
resetBtn.addEventListener('click', () => {
resultBox.style.left = defaultLeft;
resultBox.style.top = defaultTop;
resultBox.style.width = defaultWidth;
resultBox.style.height = defaultHeight;
localStorage.removeItem('tornReviver_posX');
localStorage.removeItem('tornReviver_posY');
localStorage.removeItem('tornReviver_width');
localStorage.removeItem('tornReviver_height');
// Asegurarnos de que el contenido esté visible
content.style.display = 'block';
// Volver el indicador a ▼
collapseIndicator.textContent = '▼';
// Quitar el flag de collapsed del localStorage
localStorage.removeItem('tornReviver_collapsed');
// (Opcional) Restaurar altura al valor por defecto
resultBox.style.height = defaultHeight;
});
titleControls.appendChild(resetBtn);
// Add visual indicator for resize in the bottom-right corner
const resizeIndicator = document.createElement('div');
resizeIndicator.style.position = 'absolute';
resizeIndicator.style.right = '2px';
resizeIndicator.style.bottom = '2px';
resizeIndicator.style.width = '10px';
resizeIndicator.style.height = '10px';
resizeIndicator.style.borderRight = '2px solid #888';
resizeIndicator.style.borderBottom = '2px solid #888';
resizeIndicator.style.pointerEvents = 'none'; // Let events pass through
resultBox.appendChild(resizeIndicator);
// Collapsible title with user counter
const title = document.createElement('div');
// Collapse indicator
const collapseIndicator = document.createElement('span');
collapseIndicator.textContent = '▼';
collapseIndicator.style.cursor = 'pointer';
collapseIndicator.style.fontSize = '12px';
titleControls.appendChild(collapseIndicator);
titleBar.appendChild(titleControls);
resultBox.appendChild(titleBar);
title.addEventListener('click', (e) => {
// Only toggle content if not dragging
if (!isDragging) {
content.style.display = content.style.display === 'none' ? 'block' : 'none';
collapseIndicator.textContent = content.style.display === 'none' ? '▶' : '▼';
// Save preference
localStorage.setItem('tornReviver_collapsed', content.style.display === 'none');
}
});
resultBox.appendChild(title);
// Sort menu with improved style
const sortMenu = document.createElement('select');
sortMenu.innerHTML = `
<option value="status">By Status (Active, Idle, Offline)</option>
<option value="status-reverse">By Status (Offline, Idle, Active)</option>
<option value="time">By Time (Longest to Shortest)</option>
<option value="time-reverse">By Time (Shortest to Longest)</option>
`;
sortMenu.style.marginBottom = '10px';
sortMenu.style.width = '100%';
sortMenu.style.padding = '5px';
sortMenu.style.borderRadius = '4px';
sortMenu.style.border = '1px solid #ccc';
sortMenu.style.backgroundColor = '#f9f9f9';
sortMenu.addEventListener('change', () => {
localStorage.setItem('tornReviver_sortBy', sortMenu.value);
updateAvailableUsers();
});
resultBox.appendChild(sortMenu);
// Status filter checkboxes
const filterContainer = document.createElement('div');
filterContainer.style.display = 'flex';
filterContainer.style.justifyContent = 'space-between';
filterContainer.style.marginBottom = '10px';
filterContainer.style.padding = '5px';
filterContainer.style.backgroundColor = '#f0f0f0';
filterContainer.style.borderRadius = '4px';
const statuses = ['Active', 'Idle', 'Offline'];
const colors = {
'Active': '#28a745',
'Idle': '#ffc107',
'Offline': '#6c757d'
};
statuses.forEach(status => {
const filterOption = document.createElement('label');
filterOption.htmlFor = `filter-${status.toLowerCase()}`;
filterOption.classList.add('torn-filter-label');
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.id = `filter-${status.toLowerCase()}`;
checkbox.checked = true;
checkbox.classList.add('torn-filter-checkbox');
checkbox.style.color = colors[status];
checkbox.addEventListener('change', updateAvailableUsers);
// Pintar el label con el color de status
filterOption.style.color = colors[status];
// Texto del status
const textNode = document.createTextNode(status);
filterOption.appendChild(checkbox);
filterOption.appendChild(textNode);
filterContainer.appendChild(filterOption);
});
resultBox.appendChild(filterContainer);
const factionFilterContainer = document.createElement('div');
factionFilterContainer.style.marginBottom = '10px';
factionFilterContainer.style.padding = '5px';
factionFilterContainer.style.backgroundColor = '#f0f0f0';
factionFilterContainer.style.borderRadius = '4px';
factionFilterContainer.innerHTML = `
<div style="display: flex; justify-content: space-between; margin-bottom: 5px;">
<label class="torn-filter-label" style="color: #666;">
<input type="checkbox" id="filter-has-faction" class="torn-filter-checkbox" checked style="color: #666;">
Faction
</label>
<label class="torn-filter-label" style="color: #666;">
<input type="checkbox" id="filter-no-faction" class="torn-filter-checkbox" checked style="color: #666;">
Factionless
</label>
</div>
<div>
<input type="text" id="faction-id-filter" placeholder="🔍 Faction ID" style="width: 100%; padding: 3px; border-radius: 3px; border: 1px solid #ccc;">
</div>
`;
resultBox.appendChild(factionFilterContainer);
// Pagination controls and reload page button
const controlsContainer = document.createElement('div');
controlsContainer.style.display = 'flex';
controlsContainer.style.justifyContent = 'space-between';
controlsContainer.style.marginBottom = '10px';
const prevPageButton = document.createElement('button');
prevPageButton.textContent = '⬅️';
prevPageButton.style.cursor = 'pointer';
prevPageButton.style.padding = '5px';
prevPageButton.style.borderRadius = '4px';
prevPageButton.style.border = '1px solid #ccc';
prevPageButton.style.backgroundColor = '#f9f9f9';
prevPageButton.style.boxShadow = '0 2px 5px rgba(0,0,0,0.05)';
prevPageButton.addEventListener('click', () => navigatePage(-1));
controlsContainer.appendChild(prevPageButton);
const reloadButton = document.createElement('button');
reloadButton.textContent = '🔁';
reloadButton.style.cursor = 'pointer';
reloadButton.style.padding = '5px';
reloadButton.style.borderRadius = '4px';
reloadButton.style.border = '1px solid #ccc';
reloadButton.style.backgroundColor = '#f9f9f9';
reloadButton.title = 'Reload page';
reloadButton.style.boxShadow = '0 2px 5px rgba(0,0,0,0.05)';
reloadButton.addEventListener('click', () => location.reload());
controlsContainer.appendChild(reloadButton);
const nextPageButton = document.createElement('button');
nextPageButton.textContent = '➡️';
nextPageButton.style.cursor = 'pointer';
nextPageButton.style.padding = '5px';
nextPageButton.style.borderRadius = '4px';
nextPageButton.style.border = '1px solid #ccc';
nextPageButton.style.backgroundColor = '#f9f9f9';
nextPageButton.style.boxShadow = '0 2px 5px rgba(0,0,0,0.05)';
nextPageButton.addEventListener('click', () => navigatePage(1));
controlsContainer.appendChild(nextPageButton);
resultBox.appendChild(controlsContainer);
// Stats bar for quick info
const statsBar = document.createElement('div');
statsBar.style.fontSize = '12px';
statsBar.style.marginBottom = '8px';
statsBar.style.padding = '5px';
statsBar.style.borderRadius = '4px';
statsBar.style.backgroundColor = '#e9e9e9';
statsBar.style.display = 'flex';
statsBar.style.justifyContent = 'space-between';
statsBar.innerHTML = `
<span id="active-count" style="color:${colors['Active']}">Active: 0</span>
<span id="idle-count" style="color:${colors['Idle']}">Idle: 0</span>
<span id="offline-count" style="color:${colors['Offline']}">Offline: 0</span>
`;
resultBox.appendChild(statsBar);
const factionStatsBar = document.createElement('div');
factionStatsBar.style.fontSize = '12px';
factionStatsBar.style.marginBottom = '8px';
factionStatsBar.style.padding = '5px';
factionStatsBar.style.borderRadius = '4px';
factionStatsBar.style.backgroundColor = '#e9e9e9';
factionStatsBar.style.display = 'flex';
factionStatsBar.style.justifyContent = 'space-between';
factionStatsBar.innerHTML = `
<span id="faction-count" style="color:#3A5998">With Faction: 0</span>
<span id="no-faction-count" style="color:#666">Factionless: 0</span>
<span id="matching-faction-count" style="color:#28a745"></span>
`;
resultBox.appendChild(factionStatsBar);
// Content that collapses
const content = document.createElement('div');
content.style.display = localStorage.getItem('tornReviver_collapsed') === 'true' ? 'none' : 'block';
if (content.style.display === 'none') {
collapseIndicator.textContent = '▶';
// collapse height
resultBox.style.height = titleBar.offsetHeight + 'px';
}
resultBox.appendChild(content);
// Collapse toggle only on icon click
let prevHeight = '';
collapseIndicator.addEventListener('click', (e) => {
e.stopPropagation();
const isCollapsed = content.style.display === 'none';
if (isCollapsed) {
// Expand
content.style.display = 'block';
resultBox.style.height = prevHeight || '';
collapseIndicator.textContent = '▼';
localStorage.setItem('tornReviver_collapsed', false);
} else {
// Collapse
prevHeight = resultBox.style.height;
content.style.display = 'none';
resultBox.style.height = titleBar.offsetHeight + 'px';
collapseIndicator.textContent = '▶';
localStorage.setItem('tornReviver_collapsed', true);
}
});
document.body.appendChild(resultBox);
const hasFactionFilter = document.getElementById('filter-has-faction');
const noFactionFilter = document.getElementById('filter-no-faction');
const factionIdFilter = document.getElementById('faction-id-filter');
if (hasFactionFilter) hasFactionFilter .addEventListener('change', updateAvailableUsers);
if (noFactionFilter) noFactionFilter .addEventListener('change', updateAvailableUsers);
if (factionIdFilter) factionIdFilter .addEventListener('input', updateAvailableUsers);
// Function to clear result box
function clearResultBox() {
while (content.firstChild) {
content.removeChild(content.firstChild);
}
}
// Function to update the user counter
function updateUserCount(counts) {
document.getElementById('user-count').textContent = counts.total;
document.getElementById('active-count').textContent = `Active: ${counts.active}`;
document.getElementById('idle-count').textContent = `Idle: ${counts.idle}`;
document.getElementById('offline-count').textContent = `Offline: ${counts.offline}`;
// Actualizar contadores de facción
document.getElementById('faction-count').textContent = `With Faction: ${counts.hasFaction || 0}`;
document.getElementById('no-faction-count').textContent = `Factionless: ${counts.noFaction || 0}`;
// Si hay un filtro de facción específico activo, mostrar cuántos coinciden
const factionFilter = document.getElementById('faction-id-filter')?.value;
if (factionFilter && counts.matchingFaction !== undefined) {
document.getElementById('matching-faction-count').textContent =
`Match "${factionFilter.substring(0, 8)}${factionFilter.length > 8 ? '...' : ''}": ${counts.matchingFaction}`;
document.getElementById('matching-faction-count').style.display = 'inline';
} else {
document.getElementById('matching-faction-count').style.display = 'none';
}
}
// Function to get user status
function getUserStatus(userElement) {
const iconTray = userElement.querySelector('#iconTray li');
if (iconTray && iconTray.title.includes("Online")) {
return 'Active';
} else if (iconTray && iconTray.title.includes("Idle")) {
return 'Idle';
} else {
return 'Offline';
}
}
// Function to get user wait time
function getUserTime(userElement) {
const timeText = userElement.querySelector('.time')?.innerText || "0m";
const timeParts = timeText.match(/(\d+)([hm])/);
if (timeParts) {
const value = parseInt(timeParts[1]);
return timeParts[2] === 'h' ? value * 60 : value;
}
return 0;
}
// Function to add names with links, revive button and status to result box
function addToResultBox(userData) {
const userContainer = document.createElement('div');
userContainer.className = `user-row status-${userData.status.toLowerCase()}`;
userContainer.style.display = 'grid';
userContainer.style.gridTemplateColumns = '1fr auto';
userContainer.style.alignItems = 'center';
userContainer.style.gap = '8px';
userContainer.style.marginBottom = '5px';
userContainer.style.padding = '5px';
userContainer.style.borderRadius = '4px';
userContainer.style.backgroundColor = '#fff';
userContainer.style.boxShadow = '0 2px 5px rgba(0,0,0,0.2)';
userContainer.style.border = `0px solid ${colors[userData.status]}`;
const userInfo = document.createElement('div');
const link = document.createElement('a');
link.href = `https://www.torn.com/profiles.php?XID=${userData.userId}`;
link.textContent = `ID: ${userData.userId}`;
link.target = '_blank';
link.style.color = '#000';
link.style.textDecoration = 'none';
link.style.fontWeight = 'bold';
const statusIndicator = document.createElement('span');
statusIndicator.textContent = ` (${userData.status})`;
statusIndicator.style.color = colors[userData.status];
const timeIndicator = document.createElement('div');
timeIndicator.textContent = `⏱️ ${userData.time < 60 ? userData.time + 'm' : Math.floor(userData.time/60) + 'h ' + (userData.time % 60) + 'm'}`;
timeIndicator.style.fontSize = '11px';
timeIndicator.style.color = '#666';
userInfo.appendChild(link);
userInfo.appendChild(statusIndicator);
userInfo.appendChild(timeIndicator);
// Añadir indicador de facción si existe
if (userData.faction.hasFaction) {
const factionIndicator = document.createElement('div');
let factionText = 'Facción: ';
if (userData.faction.factionName) {
factionText += userData.faction.factionName;
} else if (userData.faction.factionId) {
factionText += `ID: ${userData.faction.factionId}`;
} else {
factionText += "Sí";
}
factionIndicator.textContent = factionText;
factionIndicator.style.fontSize = '11px';
factionIndicator.style.color = '#666';
userInfo.appendChild(factionIndicator);
}
const reviveButton = document.createElement('button');
reviveButton.style.cursor = 'pointer';
reviveButton.style.backgroundColor = '#FF6347';
reviveButton.style.border = 'none';
reviveButton.style.borderRadius = '50%';
reviveButton.style.width = '24px';
reviveButton.style.height = '24px';
reviveButton.style.display = 'flex';
reviveButton.style.alignItems = 'center';
reviveButton.style.justifyContent = 'center';
reviveButton.innerHTML = '<span style="color:white;font-weight:bold;">+</span>';
reviveButton.title = 'Click to revive';
reviveButton.addEventListener('click', function() {
userData.reviveLinkElement.querySelector('.revive-icon').click();
reviveButton.disabled = true;
reviveButton.style.backgroundColor = '#ccc';
reviveButton.innerHTML = '<span style="color:white;font-weight:bold;">✓</span>';
setTimeout(() => {
userData.reviveLinkElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
// Remove this user from the list or mark as revived after a delay
setTimeout(() => {
userContainer.style.opacity = '0.5';
userContainer.style.border = '1px dashed #ccc';
}, 1000);
}, 500);
});
userContainer.appendChild(userInfo);
userContainer.appendChild(reviveButton);
content.appendChild(userContainer);
}
// Function to navigate between hospital pages
let currentPage = 0;
function navigatePage(direction) {
currentPage += direction;
if (currentPage < 0) currentPage = 0; // Don't go back beyond first page
window.location.href = `https://www.torn.com/hospitalview.php#start=${currentPage * 50}`;
setTimeout(updateAvailableUsers, 500); // Delay to reload list after page change
}
// Función para obtener información de facción
function getUserFaction(userElement) {
const factionLink = userElement.querySelector('a.user.faction');
if (!factionLink) return { hasFaction: false };
// Verificar si tiene una facción (diferente de solo la clase "user faction")
const hasFaction = factionLink.classList.length > 2 || factionLink.getAttribute('href');
// Obtener el ID de facción si está disponible
let factionId = '';
let factionName = '';
if (hasFaction) {
const href = factionLink.getAttribute('href');
if (href) {
const factionIdMatch = href.match(/ID=(\d+)/);
if (factionIdMatch && factionIdMatch[1]) {
factionId = factionIdMatch[1];
}
}
// Intentar obtener el nombre de la facción desde el título de la imagen
const factionImg = factionLink.querySelector('img');
if (factionImg && factionImg.title && factionImg.title.trim() !== '') {
factionName = factionImg.title;
}
}
return {
hasFaction,
factionId,
factionName,
factionElement: factionLink
};
}
// Function to process and update available users list
function updateAvailableUsers() {
clearResultBox();
// Get active filters
const activeFilters = {};
statuses.forEach(status => {
activeFilters[status] = document.getElementById(`filter-${status.toLowerCase()}`).checked;
});
const userContainers = [...document.querySelectorAll('.user-info-list-wrap li')];
let usersData = userContainers.map(user => {
const reviveLink = user.querySelector('a.revive');
if (reviveLink && !reviveLink.classList.contains('reviveNotAvailable')) {
const href = reviveLink.getAttribute('href');
const userIdMatch = href.match(/ID=(\d+)/);
if (userIdMatch && userIdMatch[1]) {
return {
userId: userIdMatch[1],
reviveLinkElement: reviveLink,
status: getUserStatus(user),
time: getUserTime(user),
faction: getUserFaction(user) // Añade esta línea
};
}
}
return null;
}).filter(user => user !== null);
// Apply status filters
usersData = usersData.filter(user => activeFilters[user.status]);
// Apply faction filters
const showHasFaction = document.getElementById('filter-has-faction').checked;
const showNoFaction = document.getElementById('filter-no-faction').checked;
const factionFilter = document.getElementById('faction-id-filter').value.toLowerCase();
usersData = usersData.filter(user => {
// Filtro básico de facción
if (user.faction.hasFaction && !showHasFaction) return false;
if (!user.faction.hasFaction && !showNoFaction) return false;
// Filtro por ID o nombre de facción si se ha especificado
if (factionFilter && user.faction.hasFaction) {
const matchesId = user.faction.factionId && user.faction.factionId.includes(factionFilter);
const matchesName = user.faction.factionName &&
user.faction.factionName.toLowerCase().includes(factionFilter);
if (!matchesId && !matchesName) return false;
}
return true;
});
// Apply sorting
const sortBy = sortMenu.value;
if (sortBy === 'status') {
usersData.sort((a, b) => {
const statusOrder = { 'Active': 1, 'Idle': 2, 'Offline': 3 };
return statusOrder[a.status] - statusOrder[b.status];
});
} else if (sortBy === 'status-reverse') {
usersData.sort((a, b) => {
const statusOrder = { 'Active': 3, 'Idle': 2, 'Offline': 1 };
return statusOrder[a.status] - statusOrder[b.status];
});
} else if (sortBy === 'time') {
usersData.sort((a, b) => b.time - a.time);
} else if (sortBy === 'time-reverse') {
usersData.sort((a, b) => a.time - b.time);
}
// Count users by status
const counts = {
active: usersData.filter(u => u.status === 'Active').length,
idle: usersData.filter(u => u.status === 'Idle').length,
offline: usersData.filter(u => u.status === 'Offline').length,
total: usersData.length,
hasFaction: usersData.filter(u => u.faction.hasFaction).length,
noFaction: usersData.filter(u => !u.faction.hasFaction).length
};
updateUserCount(counts);
// Add users to the display
usersData.forEach(userData => {
addToResultBox(userData);
});
if (usersData.length === 0) {
const noUserMessage = document.createElement('div');
noUserMessage.textContent = "No users available to revive with current filters.";
noUserMessage.style.color = 'gray';
noUserMessage.style.textAlign = 'center';
noUserMessage.style.fontSize = '12px';
noUserMessage.style.padding = '10px';
content.appendChild(noUserMessage);
}
}
// Load saved preferences
function loadSavedPreferences() {
// Load sort preference
const savedSort = localStorage.getItem('tornReviver_sortBy');
if (savedSort) {
sortMenu.value = savedSort;
}
// Load filter preferences
statuses.forEach(status => {
const savedFilter = localStorage.getItem(`tornReviver_filter_${status.toLowerCase()}`);
if (savedFilter !== null) {
document.getElementById(`filter-${status.toLowerCase()}`).checked = savedFilter === 'true';
}
});
// Save filter changes
statuses.forEach(status => {
document.getElementById(`filter-${status.toLowerCase()}`).addEventListener('change', function() {
localStorage.setItem(`tornReviver_filter_${status.toLowerCase()}`, this.checked);
});
});
// Load saved size
const savedWidth = localStorage.getItem('tornReviver_width');
const savedHeight = localStorage.getItem('tornReviver_height');
if (savedWidth) resultBox.style.width = savedWidth;
if (savedHeight) resultBox.style.height = savedHeight;
const savedHasFaction = localStorage.getItem('tornReviver_filter_hasFaction');
const savedNoFaction = localStorage.getItem('tornReviver_filter_noFaction');
if (savedHasFaction !== null) {
document.getElementById('filter-has-faction').checked = savedHasFaction === 'true';
}
if (savedNoFaction !== null) {
document.getElementById('filter-no-faction').checked = savedNoFaction === 'true';
}
const savedFactionFilter = localStorage.getItem('tornReviver_factionFilter');
if (savedFactionFilter) {
document.getElementById('faction-id-filter').value = savedFactionFilter;
}
// Save faction filter changes
document.getElementById('filter-has-faction').addEventListener('change', function() {
localStorage.setItem('tornReviver_filter_hasFaction', this.checked);
});
document.getElementById('filter-no-faction').addEventListener('change', function() {
localStorage.setItem('tornReviver_filter_noFaction', this.checked);
});
document.getElementById('faction-id-filter').addEventListener('input', function() {
localStorage.setItem('tornReviver_factionFilter', this.value);
});
}
// Make the box draggable
let isDragging = false;
let dragOffsetX = 0;
let dragOffsetY = 0;
titleBar.addEventListener('mousedown', (e) => {
isDragging = true;
dragOffsetX = e.clientX - resultBox.getBoundingClientRect().left;
dragOffsetY = e.clientY - resultBox.getBoundingClientRect().top;
titleBar.style.cursor = 'grabbing';
e.preventDefault();
});
document.addEventListener('mousemove', (e) => {
if (!isDragging) return;
let newLeft = e.clientX - dragOffsetX;
let newTop = e.clientY - dragOffsetY;
const maxX = window.innerWidth - resultBox.offsetWidth;
const maxY = window.innerHeight - resultBox.offsetHeight;
newLeft = Math.min(Math.max(0, newLeft), maxX);
newTop = Math.min(Math.max(0, newTop), maxY);
resultBox.style.left = newLeft + 'px';
resultBox.style.top = newTop + 'px';
});
document.addEventListener('mouseup', () => {
if (!isDragging) return;
isDragging = false;
titleBar.style.cursor = 'grab';
localStorage.setItem('tornReviver_posX', resultBox.style.left);
localStorage.setItem('tornReviver_posY', resultBox.style.top);
});
// Save resized dimensions
window.addEventListener('resize', function() {
if (resultBox.style.width && resultBox.style.height) {
localStorage.setItem('tornReviver_width', resultBox.style.width);
localStorage.setItem('tornReviver_height', resultBox.style.height);
}
});
// Specific handler for the result box being resized
const resizeObserver = new ResizeObserver(entries => {
for (let entry of entries) {
if (entry.target.style.width && entry.target.style.height) {
localStorage.setItem('tornReviver_width', entry.target.style.width);
localStorage.setItem('tornReviver_height', entry.target.style.height);
}
}
});
resizeObserver.observe(resultBox);
// Restore saved position
const savedX = localStorage.getItem('tornReviver_posX');
const savedY = localStorage.getItem('tornReviver_posY');
if (savedX && savedY) {
resultBox.style.left = savedX;
resultBox.style.top = savedY;
}
// Watch for URL changes to update users
let lastUrl = window.location.href;
new MutationObserver(() => {
const currentUrl = window.location.href;
if (currentUrl !== lastUrl) {
lastUrl = currentUrl;
setTimeout(updateAvailableUsers, 500);
}
}).observe(document, { subtree: true, childList: true });
// Watch for hospital list changes
const hospitalList = document.querySelector('.user-info-list-wrap');
if (hospitalList) {
new MutationObserver(updateAvailableUsers).observe(hospitalList, { childList: true, subtree: true });
}
// Initialize
loadSavedPreferences();
window.addEventListener('load', updateAvailableUsers);
// First update after a small delay to ensure DOM is ready
setTimeout(updateAvailableUsers, 500);
})();