fixed auth redirection bug
This commit is contained in:
@@ -160,3 +160,104 @@ html.theme-light {
|
|||||||
animation:s7 1s infinite;
|
animation:s7 1s infinite;
|
||||||
}
|
}
|
||||||
@keyframes s7 {to{transform: rotate(.5turn)}}
|
@keyframes s7 {to{transform: rotate(.5turn)}}
|
||||||
|
|
||||||
|
/* table responsiveness */
|
||||||
|
/* Custom table styles */
|
||||||
|
.cell-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell-table th,
|
||||||
|
.cell-table td {
|
||||||
|
padding: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile styles */
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
.cell-table thead {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell-table tbody {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell-carousel {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell-carousel__container {
|
||||||
|
display: flex;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
min-height: 300px; /* Adjust based on your content */
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell-carousel__slide {
|
||||||
|
flex: 0 0 100%;
|
||||||
|
padding: 1rem;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell-card {
|
||||||
|
border: 1px solid #dbdbdb;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 1rem;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell-card__item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
border-bottom: 1px solid #dbdbdb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell-card__item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell-card__label {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Navigation buttons */
|
||||||
|
.cell-carousel__nav {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* .cell-carousel__btn {
|
||||||
|
background: #3273dc;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
margin: 0 0.5rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
} */
|
||||||
|
|
||||||
|
.cell-carousel__indicators {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell-carousel__dot {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #dbdbdb;
|
||||||
|
margin: 0 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell-carousel__dot--active {
|
||||||
|
background: #3273dc;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -407,20 +407,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<table class="table is-fullwidth" id="bandTable">
|
<table class="cell-table" id="bandTable">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th>EARFCN</th>
|
<th>EARFCN</th>
|
||||||
<th>Bandwidth</th>
|
<th>Bandwidth</th>
|
||||||
<th>Physical ID</th>
|
<th>Physical ID</th>
|
||||||
<th class="is-hidden-mobile">RSRP</th>
|
<th>RSRP</th>
|
||||||
<th class="is-hidden-mobile">RSRQ</th>
|
<th>RSRQ</th>
|
||||||
<th class="is-hidden-mobile">SINR</th>
|
<th>SINR</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<!-- Automatically Generated Here -->
|
<!-- Rows will be added dynamically -->
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,50 +1,15 @@
|
|||||||
class AuthManager {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
constructor() {
|
const SESSION_DURATION = 30 * 60 * 1000; // 30 minutes in milliseconds
|
||||||
this.protectedPages = new Set([
|
|
||||||
'/home.html',
|
function generateAuthToken(length = 32) {
|
||||||
'/advance-settings.html',
|
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||||
'/bandlock.html',
|
|
||||||
'/cell-locking.html',
|
|
||||||
'/cell-scanner.html',
|
|
||||||
'/cell-settings.html',
|
|
||||||
'/cell-sms.html',
|
|
||||||
'/about.html'
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Session timeout in milliseconds (e.g., 30 minutes)
|
|
||||||
this.sessionTimeout = 30 * 60 * 1000;
|
|
||||||
|
|
||||||
this.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
// Initially hide the body to prevent content flashing
|
|
||||||
document.body.style.display = 'none';
|
|
||||||
|
|
||||||
// Check authentication state
|
|
||||||
this.checkAuthState();
|
|
||||||
|
|
||||||
// Set up event listeners
|
|
||||||
this.setupEventListeners();
|
|
||||||
|
|
||||||
// Show body after auth check
|
|
||||||
document.body.style.display = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
generateAuthToken(length = 32) {
|
|
||||||
const charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
|
||||||
return Array.from(crypto.getRandomValues(new Uint8Array(length)))
|
return Array.from(crypto.getRandomValues(new Uint8Array(length)))
|
||||||
.map(x => charset[x % charset.length])
|
.map(x => charset[x % charset.length])
|
||||||
.join('');
|
.join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
isProtectedPage(path) {
|
function getSessionData() {
|
||||||
return this.protectedPages.has(path) ||
|
const sessionStr = localStorage.getItem("session");
|
||||||
Array.from(this.protectedPages).some(page => path.includes(page));
|
|
||||||
}
|
|
||||||
|
|
||||||
getSessionData() {
|
|
||||||
const sessionStr = localStorage.getItem('session');
|
|
||||||
if (!sessionStr) return null;
|
if (!sessionStr) return null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -53,153 +18,133 @@ class AuthManager {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setSessionData(token) {
|
function setSessionData(token) {
|
||||||
const session = {
|
const session = {
|
||||||
token,
|
token,
|
||||||
lastActivity: Date.now(),
|
lastActivity: Date.now(),
|
||||||
expiresAt: Date.now() + this.sessionTimeout
|
expiresAt: Date.now() + SESSION_DURATION
|
||||||
};
|
};
|
||||||
localStorage.setItem('session', JSON.stringify(session));
|
localStorage.setItem("session", JSON.stringify(session));
|
||||||
}
|
}
|
||||||
|
|
||||||
isSessionValid() {
|
function isSessionValid() {
|
||||||
const session = this.getSessionData();
|
const session = getSessionData();
|
||||||
if (!session) return false;
|
if (!session) return false;
|
||||||
|
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
|
||||||
// Check if session has expired
|
// Check if session has expired
|
||||||
if (now > session.expiresAt) {
|
if (now > session.expiresAt) {
|
||||||
this.logout();
|
logout();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update last activity and extend session if needed
|
// Extend session if it's been more than 5 minutes since last activity
|
||||||
if (now - session.lastActivity > 5 * 60 * 1000) { // Update every 5 minutes
|
if (now - session.lastActivity > 5 * 60 * 1000) {
|
||||||
this.setSessionData(session.token);
|
setSessionData(session.token);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
checkAuthState() {
|
function logout() {
|
||||||
const currentPath = window.location.pathname;
|
localStorage.removeItem("session");
|
||||||
const isAuthenticated = this.isSessionValid();
|
window.location.href = "index.html";
|
||||||
|
|
||||||
// Redirect logic
|
|
||||||
if (!isAuthenticated && this.isProtectedPage(currentPath)) {
|
|
||||||
window.location.href = '/index.html';
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isAuthenticated && currentPath.includes('index.html')) {
|
|
||||||
window.location.href = '/home.html';
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async login(username, password) {
|
// Initially hide the body to prevent content from flashing
|
||||||
try {
|
document.body.style.display = "none";
|
||||||
const formData = new URLSearchParams();
|
|
||||||
formData.append('username', username);
|
|
||||||
formData.append('password', encodeURIComponent(password));
|
|
||||||
|
|
||||||
const response = await fetch('/cgi-bin/auth.sh', {
|
// Define which pages should be protected
|
||||||
method: 'POST',
|
const protectedPages = [
|
||||||
body: formData,
|
"/home.html",
|
||||||
headers: {
|
"/advance-settings.html",
|
||||||
'Content-Type': 'application/x-www-form-urlencoded'
|
"/bandlock.html",
|
||||||
}
|
"/cell-locking.html",
|
||||||
});
|
"/cell-scanner.html",
|
||||||
|
"/cell-settings.html",
|
||||||
|
"/cell-sms.html",
|
||||||
|
"/about.html",
|
||||||
|
];
|
||||||
|
|
||||||
const result = await response.json();
|
const currentPage = window.location.pathname;
|
||||||
|
|
||||||
if (result.state === 'success') {
|
// Authentication check
|
||||||
const token = this.generateAuthToken();
|
const isAuthenticated = isSessionValid();
|
||||||
this.setSessionData(token);
|
|
||||||
window.location.href = '/home.html';
|
// Redirect logic
|
||||||
return true;
|
if (!isAuthenticated && protectedPages.includes(currentPage)) {
|
||||||
}
|
window.location.href = "index.html";
|
||||||
|
return;
|
||||||
return false;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Login error:', error);
|
|
||||||
throw new Error('An error occurred during login');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logout() {
|
if (isAuthenticated && currentPage.includes("index.html")) {
|
||||||
localStorage.removeItem('session');
|
window.location.href = "home.html";
|
||||||
window.location.href = '/index.html';
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Show the page if authentication check is complete
|
||||||
|
document.body.style.display = "";
|
||||||
|
|
||||||
setupEventListeners() {
|
// Login form logic
|
||||||
// Handle login form
|
const loginForm = document.getElementById("loginForm");
|
||||||
const loginForm = document.getElementById('loginForm');
|
if (loginForm) {
|
||||||
if (loginForm) {
|
loginForm.addEventListener("submit", async (e) => {
|
||||||
loginForm.addEventListener('submit', async (e) => {
|
e.preventDefault();
|
||||||
e.preventDefault();
|
|
||||||
const username = document.getElementById('username').value;
|
|
||||||
const password = document.getElementById('password').value;
|
|
||||||
const errorElement = document.getElementById('error');
|
|
||||||
|
|
||||||
try {
|
const username = document.getElementById("username").value;
|
||||||
const success = await this.login(username, password);
|
const password = document.getElementById("password").value;
|
||||||
if (!success) {
|
const errorElement = document.getElementById("error");
|
||||||
errorElement.textContent = 'Invalid username or password';
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
errorElement.textContent = error.message;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle component loading
|
try {
|
||||||
window.addEventListener('componentLoaded', (event) => {
|
const formData = new URLSearchParams();
|
||||||
if (event.detail.componentId === 'nav-placeholder') {
|
formData.append("username", username);
|
||||||
this.setupNavbarHandlers();
|
formData.append("password", encodeURIComponent(password));
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Set up periodic session check
|
const response = await fetch("/cgi-bin/auth.sh", {
|
||||||
setInterval(() => {
|
method: "POST",
|
||||||
if (this.isProtectedPage(window.location.pathname)) {
|
body: formData,
|
||||||
this.isSessionValid();
|
headers: {
|
||||||
}
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
}, 60000); // Check every minute
|
},
|
||||||
}
|
|
||||||
|
|
||||||
setupNavbarHandlers() {
|
|
||||||
// Handle logout button
|
|
||||||
const logoutButton = document.getElementById('logoutButton');
|
|
||||||
if (logoutButton) {
|
|
||||||
logoutButton.addEventListener('click', (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
this.logout();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle home navigation
|
|
||||||
const homeLinks = document.querySelectorAll('.navbar-item');
|
|
||||||
homeLinks.forEach(link => {
|
|
||||||
if (link.textContent.trim() === 'Home') {
|
|
||||||
link.addEventListener('click', (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
if (this.isSessionValid()) {
|
|
||||||
window.location.href = '/home.html';
|
|
||||||
} else {
|
|
||||||
window.location.href = '/index.html';
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.state === "success") {
|
||||||
|
const newToken = generateAuthToken();
|
||||||
|
setSessionData(newToken);
|
||||||
|
window.location.href = "home.html";
|
||||||
|
} else {
|
||||||
|
errorElement.textContent = "Invalid username or password";
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
errorElement.textContent = "An error occurred. Please try again later.";
|
||||||
|
console.error("Login error:", error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize auth manager when DOM is loaded
|
// Event listeners
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
const logoutButton = document.getElementById("logoutButton");
|
||||||
window.authManager = new AuthManager();
|
if (logoutButton) {
|
||||||
|
logoutButton.addEventListener("click", logout);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.querySelectorAll(".navbar-item").forEach((el) => {
|
||||||
|
if (el.textContent.includes("Home")) {
|
||||||
|
el.addEventListener("click", (e) => {
|
||||||
|
if (isSessionValid()) {
|
||||||
|
e.preventDefault();
|
||||||
|
window.location.href = "home.html";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Periodic session check
|
||||||
|
if (protectedPages.includes(currentPage)) {
|
||||||
|
setInterval(isSessionValid, 60000); // Check every minute
|
||||||
|
}
|
||||||
});
|
});
|
||||||
@@ -97,7 +97,11 @@ function handleRefreshClick() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Promise.all([fetchATCommandData(), fetchConnectionStatus(), fetchTrafficStats]).finally(() => {
|
Promise.all([
|
||||||
|
fetchATCommandData(),
|
||||||
|
fetchConnectionStatus(),
|
||||||
|
fetchTrafficStats,
|
||||||
|
]).finally(() => {
|
||||||
if (refreshButton) {
|
if (refreshButton) {
|
||||||
refreshButton.disabled = false;
|
refreshButton.disabled = false;
|
||||||
const icon = refreshButton.querySelector("i");
|
const icon = refreshButton.querySelector("i");
|
||||||
@@ -184,7 +188,10 @@ function startPeriodicRefresh(refreshRate = DEFAULT_REFRESH_RATE) {
|
|||||||
|
|
||||||
// Start new intervals
|
// Start new intervals
|
||||||
atCommandInterval = setInterval(fetchATCommandData, refreshRate);
|
atCommandInterval = setInterval(fetchATCommandData, refreshRate);
|
||||||
trafficStatsInterval = setInterval(fetchTrafficStats, TRAFFIC_STATS_REFRESH_RATE);
|
trafficStatsInterval = setInterval(
|
||||||
|
fetchTrafficStats,
|
||||||
|
TRAFFIC_STATS_REFRESH_RATE
|
||||||
|
);
|
||||||
connectionStatusInterval = setInterval(
|
connectionStatusInterval = setInterval(
|
||||||
fetchConnectionStatus,
|
fetchConnectionStatus,
|
||||||
refreshRate * CONNECTION_CHECK_MULTIPLIER
|
refreshRate * CONNECTION_CHECK_MULTIPLIER
|
||||||
@@ -620,22 +627,56 @@ function createBandTableRow(bandData, networkType, servingCellJSON) {
|
|||||||
?.replace("LTE BAND ", "B")
|
?.replace("LTE BAND ", "B")
|
||||||
.replace("NR5G BAND ", "N");
|
.replace("NR5G BAND ", "N");
|
||||||
|
|
||||||
// Create row HTML
|
// Create both desktop and mobile versions of the content
|
||||||
row.innerHTML = `
|
const desktopContent = `
|
||||||
<td>${formattedBandNumber || "N/A"}</td>
|
<td>${formattedBandNumber || "N/A"}</td>
|
||||||
<td>${earfcn || "N/A"}</td>
|
<td>${earfcn || "N/A"}</td>
|
||||||
<td>${bandwidth || "N/A"}</td>
|
<td>${bandwidth || "N/A"}</td>
|
||||||
<td>${pci || "N/A"}</td>
|
<td>${pci || "N/A"}</td>
|
||||||
<td class="is-hidden-mobile">
|
<td>${rsrp ? createSignalTag(rsrp, "RSRP") : "N/A"}</td>
|
||||||
${rsrp ? createSignalTag(rsrp, "RSRP") : "N/A"}
|
<td>${rsrq ? createSignalTag(rsrq, "RSRQ") : "N/A"}</td>
|
||||||
</td>
|
<td>${sinr ? createSignalTag(sinr, "SINR") : "N/A"}</td>
|
||||||
<td class="is-hidden-mobile">
|
|
||||||
${rsrq ? createSignalTag(rsrq, "RSRQ") : "N/A"}
|
|
||||||
</td>
|
|
||||||
<td class="is-hidden-mobile">
|
|
||||||
${sinr ? createSignalTag(sinr, "SINR") : "N/A"}
|
|
||||||
</td>
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const mobileContent = `
|
||||||
|
<div class="cell-carousel__slide">
|
||||||
|
<div class="cell-card">
|
||||||
|
<div class="cell-card__item">
|
||||||
|
<span class="cell-card__label">Name</span>
|
||||||
|
<span>${formattedBandNumber || "N/A"}</span>
|
||||||
|
</div>
|
||||||
|
<div class="cell-card__item">
|
||||||
|
<span class="cell-card__label">EARFCN</span>
|
||||||
|
<span>${earfcn || "N/A"}</span>
|
||||||
|
</div>
|
||||||
|
<div class="cell-card__item">
|
||||||
|
<span class="cell-card__label">Bandwidth</span>
|
||||||
|
<span>${bandwidth || "N/A"}</span>
|
||||||
|
</div>
|
||||||
|
<div class="cell-card__item">
|
||||||
|
<span class="cell-card__label">Physical ID</span>
|
||||||
|
<span>${pci || "N/A"}</span>
|
||||||
|
</div>
|
||||||
|
<div class="cell-card__item">
|
||||||
|
<span class="cell-card__label">RSRP</span>
|
||||||
|
<span>${rsrp ? createSignalTag(rsrp, "RSRP") : "N/A"}</span>
|
||||||
|
</div>
|
||||||
|
<div class="cell-card__item">
|
||||||
|
<span class="cell-card__label">RSRQ</span>
|
||||||
|
<span>${rsrq ? createSignalTag(rsrq, "RSRQ") : "N/A"}</span>
|
||||||
|
</div>
|
||||||
|
<div class="cell-card__item">
|
||||||
|
<span class="cell-card__label">SINR</span>
|
||||||
|
<span>${sinr ? createSignalTag(sinr, "SINR") : "N/A"}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Store both versions in data attributes
|
||||||
|
row.setAttribute("data-desktop", desktopContent);
|
||||||
|
row.setAttribute("data-mobile", mobileContent);
|
||||||
|
row.innerHTML = desktopContent;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error parsing band data:", error);
|
console.error("Error parsing band data:", error);
|
||||||
row.innerHTML = '<td colspan="7">Error parsing band data</td>';
|
row.innerHTML = '<td colspan="7">Error parsing band data</td>';
|
||||||
@@ -930,7 +971,6 @@ async function fetchTrafficStats() {
|
|||||||
// Update the DOM
|
// Update the DOM
|
||||||
setText("download", downloadFormatted);
|
setText("download", downloadFormatted);
|
||||||
setText("upload", uploadFormatted);
|
setText("upload", uploadFormatted);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("There was a problem with the fetch operation:", error);
|
console.error("There was a problem with the fetch operation:", error);
|
||||||
}
|
}
|
||||||
@@ -1045,3 +1085,118 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
setupRefreshControls();
|
setupRefreshControls();
|
||||||
setupEventListeners();
|
setupEventListeners();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Mobile carousel functions
|
||||||
|
let touchStartX = 0;
|
||||||
|
let touchEndX = 0;
|
||||||
|
|
||||||
|
function initMobileCarousel() {
|
||||||
|
const table = document.getElementById("bandTable");
|
||||||
|
const tbody = table.querySelector("tbody");
|
||||||
|
const rows = tbody.querySelectorAll("tr");
|
||||||
|
|
||||||
|
if (window.innerWidth <= 768) {
|
||||||
|
// Create carousel structure
|
||||||
|
const carouselWrapper = document.createElement("div");
|
||||||
|
carouselWrapper.className = "cell-carousel";
|
||||||
|
|
||||||
|
const carouselContainer = document.createElement("div");
|
||||||
|
carouselContainer.className = "cell-carousel__container";
|
||||||
|
|
||||||
|
// Move content to carousel
|
||||||
|
rows.forEach((row) => {
|
||||||
|
carouselContainer.insertAdjacentHTML(
|
||||||
|
"beforeend",
|
||||||
|
row.getAttribute("data-mobile")
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
carouselWrapper.appendChild(carouselContainer);
|
||||||
|
|
||||||
|
// Add only indicators
|
||||||
|
const indicatorsHTML = `
|
||||||
|
<div class="cell-carousel__indicators">
|
||||||
|
${Array.from(
|
||||||
|
{ length: rows.length },
|
||||||
|
(_, i) =>
|
||||||
|
`<span class="cell-carousel__dot ${
|
||||||
|
i === 0 ? "cell-carousel__dot--active" : ""
|
||||||
|
}"
|
||||||
|
onclick="goToSlide(${i})"></span>`
|
||||||
|
).join("")}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Replace table with carousel
|
||||||
|
table.style.display = "none";
|
||||||
|
table.parentNode.insertBefore(carouselWrapper, table);
|
||||||
|
carouselWrapper.insertAdjacentHTML("beforeend", indicatorsHTML);
|
||||||
|
|
||||||
|
// Add touch event listeners
|
||||||
|
carouselContainer.addEventListener("touchstart", handleTouchStart, false);
|
||||||
|
carouselContainer.addEventListener("touchmove", handleTouchMove, false);
|
||||||
|
carouselContainer.addEventListener("touchend", handleTouchEnd, false);
|
||||||
|
} else {
|
||||||
|
// Restore desktop view if necessary
|
||||||
|
const carousel = document.querySelector(".cell-carousel");
|
||||||
|
if (carousel) {
|
||||||
|
carousel.remove();
|
||||||
|
table.style.display = "";
|
||||||
|
}
|
||||||
|
rows.forEach((row) => {
|
||||||
|
row.innerHTML = row.getAttribute("data-desktop");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTouchStart(event) {
|
||||||
|
touchStartX = event.touches[0].clientX;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTouchMove(event) {
|
||||||
|
event.preventDefault(); // Prevent scrolling while swiping
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTouchEnd(event) {
|
||||||
|
touchEndX = event.changedTouches[0].clientX;
|
||||||
|
handleSwipe();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSwipe() {
|
||||||
|
const swipeThreshold = 50; // Minimum distance for a swipe
|
||||||
|
const container = document.querySelector(".cell-carousel__container");
|
||||||
|
const slides = container.querySelectorAll(".cell-carousel__slide");
|
||||||
|
|
||||||
|
const diffX = touchStartX - touchEndX;
|
||||||
|
|
||||||
|
if (Math.abs(diffX) > swipeThreshold) {
|
||||||
|
if (diffX > 0 && currentSlide < slides.length - 1) {
|
||||||
|
// Swipe left, go to next slide
|
||||||
|
currentSlide++;
|
||||||
|
} else if (diffX < 0 && currentSlide > 0) {
|
||||||
|
// Swipe right, go to previous slide
|
||||||
|
currentSlide--;
|
||||||
|
}
|
||||||
|
updateCarousel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function goToSlide(index) {
|
||||||
|
currentSlide = index;
|
||||||
|
updateCarousel();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCarousel() {
|
||||||
|
const container = document.querySelector(".cell-carousel__container");
|
||||||
|
container.style.transform = `translateX(-${currentSlide * 100}%)`;
|
||||||
|
|
||||||
|
// Update indicators
|
||||||
|
const dots = document.querySelectorAll(".cell-carousel__dot");
|
||||||
|
dots.forEach((dot, index) => {
|
||||||
|
dot.classList.toggle("cell-carousel__dot--active", index === currentSlide);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize carousel on load and resize
|
||||||
|
window.addEventListener("load", initMobileCarousel);
|
||||||
|
window.addEventListener("resize", initMobileCarousel);
|
||||||
|
|||||||
Reference in New Issue
Block a user