// ==UserScript==
// @name Auto Refresh Interface for Hotsauce
// @namespace http://tampermonkey.net/
// @version 2.5.1
// @description Auto refresh for HotSOS. Has improved UI and features with error suppression
// @match https://na4.m-tech.com/service-optimization/operations/service-orders/PendingOrders
// @grant none
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// Global variables
let refreshInterval;
let currentIntervalValue;
let isEnabled = true; // Auto-refresh is enabled by default
let statusText;
let toggleCircle;
let toggleContainer = null;
let toggle;
let intervalInput;
let lastRefreshAttempt = 0;
let isDragging = false;
let xOffset = 0;
let yOffset = 0;
let initialX, initialY;
let isUIOperation = false; // Flag to prevent unwanted updates
const MIN_REFRESH_INTERVAL = 1000; // 1 second minimum between refreshes
// Settings management
const Settings = {
load() {
const defaultSettings = {
interval: 30,
position: { x: 0, y: 0 },
lastUsedPreset: '30s',
isEnabled: true // Added isEnabled to default settings
};
try {
return { ...defaultSettings, ...JSON.parse(localStorage.getItem('autoRefreshSettings') || '{}') };
} catch (e) {
console.log('Error loading settings, using defaults');
return defaultSettings;
}
},
save(settings) {
localStorage.setItem('autoRefreshSettings', JSON.stringify(settings));
}
};
// Preset configurations
const PRESETS = [
{ label: '15s', value: 15 },
{ label: '30s', value: 30 },
{ label: '1m', value: 60 },
{ label: '5m', value: 300 }
];
// Enhanced page ready check
function checkPageReady() {
const refreshButton = findRefreshButton();
const isLoading = document.querySelector('.loading-screen, .spinner, .mat-progress-spinner');
if (isLoading) {
console.log('Loading screen detected, waiting...');
setTimeout(() => findAndClickRefreshButton(), 500);
return false;
}
return refreshButton !== null;
}
function findRefreshButton() {
try {
const selectors = [
'button.mat-icon-button.mat-accent:has(soe-icon[icon="refresh-dot"])',
'button.mat-icon-button.mat-accent:has(soe-icon[icon="refresh"])',
'button.mat-icon-button:has(soe-icon[icon="refresh"])',
'button.mat-icon-button:has(soe-icon[icon="refresh-dot"])',
'button[soe-data-cy="refresh"]'
];
for (const selector of selectors) {
try {
const button = document.querySelector(selector);
if (button) return button;
} catch (e) {
// Silently continue to next selector
}
}
// Fallback to searching for any button with refresh-related attributes
try {
const allButtons = document.querySelectorAll('button');
return Array.from(allButtons).find(button => {
try {
const buttonText = button.textContent.toLowerCase();
const hasRefreshIcon = button.querySelector('soe-icon[icon*="refresh"]');
const hasRefreshClass = button.className.toLowerCase().includes('refresh');
return hasRefreshIcon || hasRefreshClass || buttonText.includes('refresh');
} catch (e) {
return false;
}
});
} catch (e) {
// Return null if all attempts fail
return null;
}
} catch (e) {
// Catch and suppress any errors
console.log('Error in findRefreshButton (suppressed)');
return null;
}
}
function updateStatus(state = 'normal') {
if (!statusText || isUIOperation) return; // Skip updating if this is a UI operation
const now = new Date().toLocaleTimeString();
const stateColors = {
success: '#059669',
error: '#DC2626',
normal: '#666'
};
// Always update with timestamp for any refresh attempt
statusText.textContent = `Last check: ${now}`;
statusText.style.color = isEnabled ? stateColors[state] : stateColors.normal;
if (state === 'error') {
setTimeout(() => {
if (isEnabled) updateStatus('normal');
}, 3000);
}
}
function findAndClickRefreshButton(force = false) {
const now = Date.now();
if (!force && now - lastRefreshAttempt < MIN_REFRESH_INTERVAL) {
console.log('Skipping refresh - too soon since last attempt');
return false;
}
const refreshButton = findRefreshButton();
if (refreshButton) {
console.log('Refresh button found and clicked');
refreshButton.click();
updateStatus('success'); // Always show success state when refresh button is clicked
lastRefreshAttempt = now;
return true;
}
if (force) {
console.log('Forced refresh attempt');
setTimeout(() => findAndClickRefreshButton(true), 500);
}
console.log('Refresh button not found');
updateStatus('error');
return false;
}
function setupRefreshButtonListener() {
const refreshButton = findRefreshButton();
if (refreshButton && !refreshButton.dataset.listenerAdded) {
refreshButton.addEventListener('click', () => {
// Set lastRefreshAttempt to ensure we show timestamp instead of guidance
lastRefreshAttempt = Date.now();
// Always show success status with timestamp for manual refreshes
updateStatus('success');
if (isEnabled && refreshInterval) {
clearInterval(refreshInterval);
refreshInterval = setInterval(findAndClickRefreshButton, currentIntervalValue * 1000);
}
});
refreshButton.dataset.listenerAdded = 'true';
}
}
function updateInterval(newInterval, intervalInput) {
if (refreshInterval) {
clearInterval(refreshInterval);
refreshInterval = null;
}
intervalInput.value = newInterval.toString();
currentIntervalValue = newInterval;
if (isEnabled) {
findAndClickRefreshButton();
refreshInterval = setInterval(findAndClickRefreshButton, newInterval * 1000);
console.log('Interval updated to', newInterval, 'seconds');
}
const settings = Settings.load();
settings.interval = newInterval;
Settings.save(settings);
}
function updatePresetButtonStyles(presetContainer, currentValue) {
const numericValue = parseInt(currentValue);
presetContainer.querySelectorAll('button').forEach(button => {
const preset = PRESETS.find(p => p.label === button.textContent);
const isMatch = preset && preset.value === numericValue;
button.style.background = isMatch ? '#0066cc' : '#f0f0f0';
button.style.color = isMatch ? 'white' : '#333';
button.style.opacity = isEnabled ? '1' : '0.7';
});
}
function handleIntervalUpdate(newInterval, intervalInput, presetContainer) {
isEnabled = true;
toggle.style.backgroundColor = '#0066cc';
toggleCircle.style.transform = 'translateX(14px)';
updateInterval(newInterval, intervalInput);
updatePresetButtonStyles(presetContainer, newInterval);
updateInputStyle(true);
// Save enabled state
const settings = Settings.load();
settings.isEnabled = true;
Settings.save(settings);
}
function updateInputStyle(enabled) {
if (!intervalInput) return;
intervalInput.style.opacity = enabled ? '1' : '0.7';
intervalInput.style.color = enabled ? '#000' : '#666';
}
// Simple function to ensure the container is always in view
function checkContainerVisibility() {
if (!toggleContainer || isDragging) return;
const rect = toggleContainer.getBoundingClientRect();
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
// Check if container is completely out of view
if (rect.left > viewportWidth || rect.right < 0 || rect.top > viewportHeight || rect.bottom < 0) {
console.log('Container off screen, resetting position');
xOffset = 20;
yOffset = 20;
toggleContainer.style.transform = `translate(${xOffset}px, ${yOffset}px)`;
// Save the new position
const settings = Settings.load();
settings.position = { x: xOffset, y: yOffset };
Settings.save(settings);
}
}
function updateRefreshButtonStatus() {
// Only update initial status if no refresh has happened yet
if (lastRefreshAttempt > 0) return;
const refreshButton = findRefreshButton();
if (refreshButton) {
statusText.textContent = "Ready to refresh";
statusText.style.color = "#059669"; // Success green
} else {
statusText.textContent = "Navigate to pending orders page";
statusText.style.color = "#DC2626"; // Error red
}
}
function createUI() {
// Load settings
const settings = Settings.load();
currentIntervalValue = settings.interval;
// Load saved enabled state
isEnabled = settings.hasOwnProperty('isEnabled') ? settings.isEnabled : true;
// Load saved position
if (settings.position) {
xOffset = settings.position.x;
yOffset = settings.position.y;
}
// Create main container
toggleContainer = document.createElement('div');
toggleContainer.style.cssText = `
position: fixed;
top: 140px;
right: 100px;
z-index: 10000;
background: white;
padding: 10px 14px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
font-family: system-ui, -apple-system, sans-serif;
font-size: 11px;
min-width: 150px;
width: 180px;
touch-action: none;
`;
// Add a global style to remove focus outlines for all elements in our container
const style = document.createElement('style');
style.textContent = `
#auto-refresh-container button:focus,
#auto-refresh-container input:focus,
#auto-refresh-container div:focus {
outline: none !important;
}
`;
document.head.appendChild(style);
// Add an ID for our style targeting
toggleContainer.id = 'auto-refresh-container';
// Apply saved transform immediately
toggleContainer.style.transform = `translate(${xOffset}px, ${yOffset}px)`;
// Create header
const headerDiv = document.createElement('div');
headerDiv.style.cssText = `
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
padding-bottom: 4px;
border-bottom: 1px solid #eee;
cursor: move;
touch-action: none;
`;
const titleSpan = document.createElement('span');
titleSpan.textContent = 'Auto Refresh';
titleSpan.style.cssText = 'font-weight: bold; cursor: move;';
const minimizeButton = document.createElement('button');
minimizeButton.innerHTML = '−';
minimizeButton.style.cssText = `
background: none;
border: none;
cursor: pointer;
padding: 4px 8px;
color: #666;
font-size: 14px;
touch-action: manipulation;
outline: none; /* Remove focus outline */
`;
// Create content container
const contentDiv = document.createElement('div');
contentDiv.style.cssText = 'display: flex; flex-direction: column; gap: 8px;';
// Create preset buttons
const presetContainer = document.createElement('div');
presetContainer.style.cssText = 'display: flex; gap: 4px; margin-bottom: 6px;';
PRESETS.forEach(preset => {
const button = document.createElement('button');
button.textContent = preset.label;
button.style.cssText = `
padding: 4px 8px;
background: #f0f0f0;
color: #333;
border: none;
border-radius: 3px;
font-size: 10px;
cursor: pointer;
flex: 1;
touch-action: manipulation;
opacity: 0.7;
outline: none; /* Remove focus outline */
`;
presetContainer.appendChild(button);
const handlePresetClick = (e) => {
e.preventDefault();
const newInterval = preset.value;
handleIntervalUpdate(newInterval, intervalInput, presetContainer);
// Update settings
const currentSettings = Settings.load();
currentSettings.interval = newInterval;
currentSettings.lastUsedPreset = preset.label;
currentSettings.isEnabled = true; // Ensure enabled state is saved
Settings.save(currentSettings);
};
button.addEventListener('click', handlePresetClick);
button.addEventListener('touchend', handlePresetClick);
});
// Create interval input section
const intervalContainer = document.createElement('div');
intervalContainer.style.cssText = 'display: flex; align-items: center; gap: 8px;';
const intervalLabel = document.createElement('label');
intervalLabel.textContent = 'Interval (sec):';
intervalLabel.style.cssText = 'font-size: 11px; color: #333;';
intervalInput = document.createElement('input');
intervalInput.type = 'number';
intervalInput.min = '5';
intervalInput.max = '3600';
intervalInput.value = settings.interval.toString();
intervalInput.style.cssText = `
width: 45px;
padding: 2px 4px;
border: 1px solid #ccc;
border-radius: 3px;
font-size: 11px;
touch-action: manipulation;
opacity: 0.7;
color: #666;
outline: none; /* Remove focus outline */
`;
// Create button container
const buttonContainer = document.createElement('div');
buttonContainer.style.cssText = 'display: flex; gap: 2px; margin-left: 2px;';
const setIntervalButton = document.createElement('button');
setIntervalButton.textContent = 'Set';
setIntervalButton.style.cssText = `
padding: 4px 8px;
background: #0066cc;
color: white;
border: none;
border-radius: 3px;
font-size: 10px;
cursor: pointer;
touch-action: manipulation;
outline: none; /* Remove focus outline */
`;
const resetDefaultButton = document.createElement('button');
resetDefaultButton.textContent = 'Def';
resetDefaultButton.style.cssText = `
padding: 4px 8px;
background: #666;
color: white;
border: none;
border-radius: 3px;
font-size: 10px;
cursor: pointer;
touch-action: manipulation;
outline: none; /* Remove focus outline */
`;
// Create toggle switch
const toggleSwitch = document.createElement('div');
toggleSwitch.style.cssText = 'display: flex; align-items: center; gap: 8px;';
toggle = document.createElement('div');
toggle.style.cssText = `
width: 32px;
height: 18px;
background: ${isEnabled ? '#0066cc' : '#ccc'};
border-radius: 9px;
position: relative;
transition: background-color 0.2s;
cursor: pointer;
touch-action: manipulation;
outline: none; /* Remove focus outline */
`;
toggleCircle = document.createElement('div');
toggleCircle.style.cssText = `
width: 14px;
height: 14px;
background: white;
border-radius: 50%;
position: absolute;
top: 2px;
left: 2px;
transition: transform 0.2s;
transform: ${isEnabled ? 'translateX(14px)' : 'translateX(0)'};
`;
const toggleLabel = document.createElement('span');
toggleLabel.textContent = 'Enable Auto-refresh';
toggleLabel.style.cssText = 'color: #333; font-size: 11px;';
// Create status text with better initial guidance
statusText = document.createElement('div');
statusText.style.cssText = 'font-size: 10px; color: #666;';
statusText.textContent = 'Navigate to pending orders page';
// Assemble UI
toggle.appendChild(toggleCircle);
toggleSwitch.appendChild(toggle);
toggleSwitch.appendChild(toggleLabel);
headerDiv.appendChild(titleSpan);
headerDiv.appendChild(minimizeButton);
intervalContainer.appendChild(intervalLabel);
intervalContainer.appendChild(intervalInput);
buttonContainer.appendChild(setIntervalButton);
buttonContainer.appendChild(resetDefaultButton);
intervalContainer.appendChild(buttonContainer);
contentDiv.appendChild(presetContainer);
contentDiv.appendChild(intervalContainer);
contentDiv.appendChild(toggleSwitch);
contentDiv.appendChild(statusText);
toggleContainer.appendChild(headerDiv);
toggleContainer.appendChild(contentDiv);
document.body.appendChild(toggleContainer);
// Setup dragging functionality with improved error handling
function dragStart(e) {
try {
if (e.target === minimizeButton) return;
// Prevent default only when needed to avoid conflicts
if (e.type === 'touchstart' && e.target.closest('.auto-refresh-container')) {
e.preventDefault();
}
// Get exact current position for smooth dragging
const transform = toggleContainer.style.transform;
const match = transform.match(/translate\((-?\d+)px, *(-?\d+)px\)/);
if (match) {
xOffset = parseInt(match[1]);
yOffset = parseInt(match[2]);
}
const event = e.type === 'touchstart' ? e.touches[0] : e;
initialX = event.clientX - xOffset;
initialY = event.clientY - yOffset;
if (e.target === headerDiv || e.target === titleSpan || e.target === toggleContainer) {
isDragging = true;
}
} catch (err) {
console.log('Error in drag start:', err);
isDragging = false;
}
}
function drag(e) {
try {
if (isDragging) {
e.preventDefault();
const event = e.type === 'touchmove' ? e.touches[0] : e;
xOffset = event.clientX - initialX;
yOffset = event.clientY - initialY;
toggleContainer.style.transform = `translate(${xOffset}px, ${yOffset}px)`;
}
} catch (err) {
console.log('Error in drag:', err);
isDragging = false;
}
}
function dragEnd() {
try {
if (isDragging) {
isDragging = false;
// Save position
const settings = Settings.load();
settings.position = { x: xOffset, y: yOffset };
Settings.save(settings);
}
} catch (err) {
console.log('Error in drag end:', err);
isDragging = false;
}
}
// Add drag events with improved error handling
toggleContainer.addEventListener('mousedown', dragStart);
document.addEventListener('mousemove', drag);
document.addEventListener('mouseup', dragEnd);
// Special handling for touch events to work better on tablets/touch devices
toggleContainer.addEventListener('touchstart', dragStart, { passive: false });
document.addEventListener('touchmove', drag, { passive: false });
document.addEventListener('touchend', dragEnd, { passive: true });
document.addEventListener('touchcancel', () => {
if (isDragging) {
isDragging = false;
console.log('Touch cancelled, reset drag state');
}
});
// Minimize/Maximize functionality
let isMinimized = false;
minimizeButton.addEventListener('click', (e) => {
// Prevent status updates during UI operations
isUIOperation = true;
e.stopPropagation(); // Prevent event bubbling
isMinimized = !isMinimized;
contentDiv.style.display = isMinimized ? 'none' : 'flex';
minimizeButton.innerHTML = isMinimized ? '+' : '−';
toggleContainer.style.padding = isMinimized ? '10px 14px 2px' : '10px 14px';
// Reset flag after operation is complete
setTimeout(() => {
isUIOperation = false;
}, 100);
});
// Set interval button handlers
setIntervalButton.addEventListener('click', (e) => {
e.preventDefault();
const newInterval = Math.max(5, Math.min(3600, parseInt(intervalInput.value) || 30));
handleIntervalUpdate(newInterval, intervalInput, presetContainer);
});
resetDefaultButton.addEventListener('click', (e) => {
e.preventDefault();
handleIntervalUpdate(30, intervalInput, presetContainer);
});
// Toggle switch functionality
toggle.addEventListener('click', function() {
isEnabled = !isEnabled;
if (isEnabled) {
toggle.style.backgroundColor = '#0066cc';
toggleCircle.style.transform = 'translateX(14px)';
const currentValue = parseInt(intervalInput.value) || 30;
updateInterval(currentValue, intervalInput);
updatePresetButtonStyles(presetContainer, currentValue);
updateInputStyle(true);
// Update status when enabling
updateRefreshButtonStatus();
} else {
toggle.style.backgroundColor = '#ccc';
toggleCircle.style.transform = 'translateX(0)';
if (refreshInterval) {
clearInterval(refreshInterval);
refreshInterval = null;
}
statusText.style.color = '#666';
statusText.textContent = 'Auto-refresh disabled';
updatePresetButtonStyles(presetContainer, intervalInput.value);
updateInputStyle(false);
}
// Save the enabled state
const settings = Settings.load();
settings.isEnabled = isEnabled;
Settings.save(settings);
});
// Set up mutation observer for dynamic refresh button
const observer = new MutationObserver(() => {
setupRefreshButtonListener();
// Only update status text if no refresh has happened yet
if (!lastRefreshAttempt) {
updateRefreshButtonStatus();
}
});
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['class']
});
// Initial setup
setupRefreshButtonListener();
updateRefreshButtonStatus(); // Check initial status
// Set initial interval from settings and update presets
intervalInput.value = settings.interval.toString();
updatePresetButtonStyles(presetContainer, settings.interval);
updateInputStyle(isEnabled);
}
// Initialization function - create UI immediately without waiting for page to be ready
function initScript() {
console.log('Starting initialization...');
// Create UI immediately
createUI();
// Force position check after a short delay
setTimeout(checkContainerVisibility, 500);
// Set up observers and listeners
const observer = new MutationObserver((mutations) => {
const loadingScreenChange = mutations.some(mutation =>
Array.from(mutation.addedNodes).some(node =>
node.nodeType === 1 && (
node.classList?.contains('loading-screen') ||
node.classList?.contains('spinner') ||
node.classList?.contains('mat-progress-spinner')
)
)
);
if (loadingScreenChange) {
console.log('Loading screen change detected');
setTimeout(() => findAndClickRefreshButton(true), 500);
}
});
observer.observe(document.body, {
subtree: true,
childList: true,
attributes: true,
attributeFilter: ['class', 'style']
});
// Add event listener for window resize
window.addEventListener('resize', function() {
if (!isDragging) {
checkContainerVisibility();
}
});
// Also check visibility after orientation change
window.addEventListener('orientationchange', function() {
setTimeout(function() {
checkContainerVisibility();
}, 300);
});
// Initialize refresh functionality if page is ready
if (checkPageReady()) {
setupRefreshButtonListener();
console.log('Page ready, refresh functionality initialized');
// Only show guidance if no refresh has happened yet
if (lastRefreshAttempt === 0) {
updateRefreshButtonStatus();
}
// Start auto-refresh if enabled by default
if (isEnabled) {
if (refreshInterval) clearInterval(refreshInterval);
refreshInterval = setInterval(findAndClickRefreshButton, currentIntervalValue * 1000);
// Initial refresh
setTimeout(() => findAndClickRefreshButton(), 1000);
}
} else {
console.log('Page not ready, will initialize refresh functionality when ready');
// Update status to indicate page navigation needed
if (statusText) {
statusText.textContent = 'Navigate to pending orders page';
statusText.style.color = '#DC2626'; // Error red
}
// Set up periodic check for refresh button
const readyCheckInterval = setInterval(() => {
if (checkPageReady()) {
setupRefreshButtonListener();
console.log('Page now ready, refresh functionality initialized');
updateRefreshButtonStatus(); // Update status when page becomes ready
// Start auto-refresh if enabled by default
if (isEnabled) {
if (refreshInterval) clearInterval(refreshInterval);
refreshInterval = setInterval(findAndClickRefreshButton, currentIntervalValue * 1000);
// Initial refresh
setTimeout(() => findAndClickRefreshButton(), 1000);
}
clearInterval(readyCheckInterval);
}
}, 1000);
}
}
function suppressErrors() {
// Store original console error method
const originalConsoleError = console.error;
// Override console.error to filter out specific errors
console.error = function(...args) {
// Check if this is a DOM-related error we want to suppress
const errorText = args.join(' ');
if (errorText.includes('elementFinder') ||
errorText.includes('Cannot read properties') ||
errorText.includes('is not defined') ||
errorText.includes('is null')) {
// Silently ignore these errors
return;
}
// Pass other errors through to the original method
originalConsoleError.apply(console, args);
};
// Add global error handler
window.addEventListener('error', function(event) {
// Prevent the error from showing in console
event.preventDefault();
return true;
}, true);
// Add CSS to hide elementFinder displays - improved selectors
const style = document.createElement('style');
style.textContent = `
/* Target the elementFinder display - more comprehensive selectors */
div:not(#auto-refresh-container):has(span:contains("elementFinder")),
div:not(#auto-refresh-container) > span:contains("elementFinder"),
div:not(#auto-refresh-container):contains("elementFinder"),
div:not(#auto-refresh-container):contains("/html/body/"),
div:not(#auto-refresh-container):contains("soe-timespan-block"),
body > div:last-of-type:not(#auto-refresh-container) {
display: none !important;
visibility: hidden !important;
opacity: 0 !important;
height: 0 !important;
pointer-events: none !important;
position: absolute !important;
z-index: -9999 !important;
}
`;
document.head.appendChild(style);
// Function to actively remove elementFinder displays
function removeElementFinderDisplays() {
try {
const allDivs = document.querySelectorAll('div:not(#auto-refresh-container)');
for (const div of allDivs) {
if (div.id === 'auto-refresh-container') continue;
// Check for elementFinder text content - simplified to catch all variations
const text = div.textContent || '';
if (text.toLowerCase().includes('elementfinder') ||
text.includes('/html/body/') ||
text.includes('soe-timespan-block')) {
// Hide it first
div.style.display = 'none';
div.style.visibility = 'hidden';
div.style.opacity = '0';
div.style.height = '0';
// Try to remove it
try {
if (div.parentNode) {
div.parentNode.removeChild(div);
}
} catch (e) {
// If we can't remove it, at least it's hidden
}
}
}
// Special check for elementFinder at bottom of page
const bodyChildren = document.body.children;
const lastChild = bodyChildren[bodyChildren.length - 1];
if (lastChild && lastChild.id !== 'auto-refresh-container') {
const text = lastChild.textContent || '';
if (text.includes('elementFinder') || text.includes('/html/body/')) {
lastChild.style.display = 'none';
try {
document.body.removeChild(lastChild);
} catch (e) {
// Just hide it if we can't remove it
}
}
}
} catch (e) {
// Silent fail
}
}
// Run removal immediately
removeElementFinderDisplays();
// Set up interval to constantly check for error displays
setInterval(removeElementFinderDisplays, 200);
// Set up mutation observer to detect when new error displays are added
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.addedNodes.length) {
removeElementFinderDisplays();
break;
}
}
});
// Start observing the document body for changes
observer.observe(document.body, {
childList: true,
subtree: true
});
}
// Apply error suppression
suppressErrors();
// Start the script when the window loads
window.addEventListener('load', function() {
console.log('Window loaded, running script...');
initScript();
});
})();