T6 cleanup: remove scope-creep FAB, pull-to-refresh, skeleton loading — map features preserved
This commit is contained in:
+21
-300
@@ -412,35 +412,6 @@
|
|||||||
border-top-color: var(--accent); border-radius: 50%;
|
border-top-color: var(--accent); border-radius: 50%;
|
||||||
animation: spin 0.7s linear infinite;
|
animation: spin 0.7s linear infinite;
|
||||||
}
|
}
|
||||||
/* =========================================================================
|
|
||||||
FAB - Floating Action Button (pulse animation)
|
|
||||||
========================================================================= */
|
|
||||||
.fab {
|
|
||||||
position: fixed; bottom: calc(var(--tab-height) + 20px); right: 20px;
|
|
||||||
width: 56px; height: 56px; border-radius: 50%;
|
|
||||||
background: var(--accent); color: #fff;
|
|
||||||
border: none; font-size: 28px; font-weight: 300; cursor: pointer;
|
|
||||||
display: flex; align-items: center; justify-content: center;
|
|
||||||
box-shadow: 0 4px 16px rgba(91,110,247,0.35);
|
|
||||||
z-index: 90;
|
|
||||||
transition: transform 0.18s, box-shadow 0.18s;
|
|
||||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
|
||||||
animation: fabPulse 3s cubic-bezier(0.4, 0, 0.2, 1) infinite;
|
|
||||||
}
|
|
||||||
.fab:hover {
|
|
||||||
transform: scale(1.08);
|
|
||||||
box-shadow: 0 6px 24px rgba(91,110,247,0.45);
|
|
||||||
}
|
|
||||||
.fab:active {
|
|
||||||
transform: scale(0.94);
|
|
||||||
animation: none;
|
|
||||||
}
|
|
||||||
@keyframes fabPulse {
|
|
||||||
0%, 100% { box-shadow: 0 4px 16px rgba(91,110,247,0.35); }
|
|
||||||
50% { box-shadow: 0 4px 28px rgba(91,110,247,0.55); }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* ═══════════════════════════════════════════════════════════════════════
|
/* ═══════════════════════════════════════════════════════════════════════
|
||||||
SKELETON LOADING (shimmer)
|
SKELETON LOADING (shimmer)
|
||||||
═══════════════════════════════════════════════════════════════════════ */
|
═══════════════════════════════════════════════════════════════════════ */
|
||||||
@@ -1538,46 +1509,7 @@
|
|||||||
/* ═══════════════════════════════════════════════════════════════════════
|
/* ═══════════════════════════════════════════════════════════════════════
|
||||||
PULL-TO-REFRESH
|
PULL-TO-REFRESH
|
||||||
═══════════════════════════════════════════════════════════════════════ */
|
═══════════════════════════════════════════════════════════════════════ */
|
||||||
.ptr-indicator {
|
|
||||||
text-align: center; padding: 10px; color: var(--text2);
|
|
||||||
font-size: 12px; display: flex; align-items: center; justify-content: center;
|
|
||||||
gap: 8px; max-height: 0; overflow: hidden;
|
|
||||||
transition: max-height 0.25s ease; pointer-events: none;
|
|
||||||
}
|
|
||||||
.ptr-indicator.pulling { max-height: 44px; color: var(--accent2); }
|
|
||||||
.ptr-indicator.refreshing { max-height: 44px; }
|
|
||||||
.ptr-spinner {
|
|
||||||
display: inline-block; width: 18px; height: 18px;
|
|
||||||
border: 2px solid var(--border); border-top-color: var(--accent);
|
|
||||||
border-radius: 50%; animation: spin 0.7s linear infinite;
|
|
||||||
}
|
|
||||||
.ptr-arrow {
|
|
||||||
font-size: 16px; transition: transform 0.2s;
|
|
||||||
}
|
|
||||||
.ptr-indicator.pulling .ptr-arrow { transform: rotate(180deg); }
|
|
||||||
|
|
||||||
/* ═══════════════════════════════════════════════════════════════════════
|
|
||||||
SWIPE-TO-DELETE
|
|
||||||
═══════════════════════════════════════════════════════════════════════ */
|
|
||||||
.swipe-item-wrap {
|
|
||||||
position: relative; overflow: hidden;
|
|
||||||
}
|
|
||||||
.swipe-bg {
|
|
||||||
position: absolute; right: 0; top: 0; bottom: 0;
|
|
||||||
background: var(--red); color: #fff;
|
|
||||||
display: flex; align-items: center; justify-content: center;
|
|
||||||
font-size: 16px; font-weight: 600; padding: 0 20px;
|
|
||||||
transition: width 0.2s; pointer-events: none; z-index: 0;
|
|
||||||
width: 0; overflow: hidden; white-space: nowrap;
|
|
||||||
}
|
|
||||||
.swipe-item-wrap.swiped .swipe-bg { width: 80px; }
|
|
||||||
.asset-item.swipe-dragging {
|
|
||||||
transition: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ═══════════════════════════════════════════════════════════════════════
|
|
||||||
UNDO TOAST (for swipe-delete)
|
|
||||||
═══════════════════════════════════════════════════════════════════════ */
|
|
||||||
.undo-toast {
|
.undo-toast {
|
||||||
position: fixed; bottom: calc(var(--tab-height) + 12px);
|
position: fixed; bottom: calc(var(--tab-height) + 12px);
|
||||||
left: 50%; transform: translateX(-50%);
|
left: 50%; transform: translateX(-50%);
|
||||||
@@ -1609,6 +1541,7 @@
|
|||||||
-webkit-tap-highlight-color: transparent;
|
-webkit-tap-highlight-color: transparent;
|
||||||
}
|
}
|
||||||
.fab:active { transform: scale(0.88); box-shadow: 0 2px 8px rgba(91,110,247,0.3); }
|
.fab:active { transform: scale(0.88); box-shadow: 0 2px 8px rgba(91,110,247,0.3); }
|
||||||
|
.fab:hover { transform: scale(1.08); box-shadow: 0 6px 24px rgba(91,110,247,0.55); }
|
||||||
@keyframes fabPulse {
|
@keyframes fabPulse {
|
||||||
0%, 100% { box-shadow: 0 4px 18px rgba(91,110,247,0.45); }
|
0%, 100% { box-shadow: 0 4px 18px rgba(91,110,247,0.45); }
|
||||||
50% { box-shadow: 0 4px 28px rgba(91,110,247,0.65); }
|
50% { box-shadow: 0 4px 28px rgba(91,110,247,0.65); }
|
||||||
@@ -2875,11 +2808,6 @@
|
|||||||
═══════════════════════════════════════════════════════════════════════ -->
|
═══════════════════════════════════════════════════════════════════════ -->
|
||||||
<div class="toast" id="toast"></div>
|
<div class="toast" id="toast"></div>
|
||||||
|
|
||||||
<!-- =========================================================================
|
|
||||||
FAB — Floating Action Button (quick add asset)
|
|
||||||
========================================================================= -->
|
|
||||||
<button class="fab" id="fabAdd" onclick="switchTab('tabAddAsset')" title="Add Asset" aria-label="Add Asset">+</button>
|
|
||||||
|
|
||||||
<!-- ═══════════════════════════════════════════════════════════════════════
|
<!-- ═══════════════════════════════════════════════════════════════════════
|
||||||
MODAL
|
MODAL
|
||||||
═══════════════════════════════════════════════════════════════════════ -->
|
═══════════════════════════════════════════════════════════════════════ -->
|
||||||
@@ -3349,10 +3277,10 @@
|
|||||||
let undoToastTimer;
|
let undoToastTimer;
|
||||||
let undoToastCallback = null;
|
let undoToastCallback = null;
|
||||||
function showUndoToast(msg, onUndo) {
|
function showUndoToast(msg, onUndo) {
|
||||||
const t = document.getElementById('toast');
|
const t = document.getElementById('undoToast');
|
||||||
|
if (!t) return;
|
||||||
undoToastCallback = onUndo;
|
undoToastCallback = onUndo;
|
||||||
t.innerHTML = '<span>' + msg + '</span><button class="toast-undo-btn" onclick="handleUndoToast()">Undo</button>';
|
t.querySelector('span').textContent = '🗑️ ' + msg;
|
||||||
t.className = 'toast';
|
|
||||||
t.classList.add('show');
|
t.classList.add('show');
|
||||||
clearTimeout(undoToastTimer);
|
clearTimeout(undoToastTimer);
|
||||||
clearTimeout(toastTimer);
|
clearTimeout(toastTimer);
|
||||||
@@ -3361,12 +3289,18 @@
|
|||||||
if (undoToastCallback) { undoToastCallback = null; }
|
if (undoToastCallback) { undoToastCallback = null; }
|
||||||
}, 5000);
|
}, 5000);
|
||||||
}
|
}
|
||||||
function handleUndoToast() {
|
function undoDelete() {
|
||||||
clearTimeout(undoToastTimer);
|
clearTimeout(undoToastTimer);
|
||||||
const t = document.getElementById('toast');
|
const t = document.getElementById('undoToast');
|
||||||
t.classList.remove('show');
|
if (t) t.classList.remove('show');
|
||||||
if (undoToastCallback) { const cb = undoToastCallback; undoToastCallback = null; cb(); }
|
if (undoToastCallback) { const cb = undoToastCallback; undoToastCallback = null; cb(); }
|
||||||
}
|
}
|
||||||
|
function dismissUndo() {
|
||||||
|
clearTimeout(undoToastTimer);
|
||||||
|
const t = document.getElementById('undoToast');
|
||||||
|
if (t) t.classList.remove('show');
|
||||||
|
undoToastCallback = null;
|
||||||
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// PULL-TO-REFRESH
|
// PULL-TO-REFRESH
|
||||||
@@ -3375,6 +3309,7 @@
|
|||||||
let ptrStartY = 0;
|
let ptrStartY = 0;
|
||||||
let ptrCurrentY = 0;
|
let ptrCurrentY = 0;
|
||||||
let ptrTouchActive = false;
|
let ptrTouchActive = false;
|
||||||
|
let _ptrInited = false;
|
||||||
const PTR_THRESHOLD = 60;
|
const PTR_THRESHOLD = 60;
|
||||||
const PTR_MAX_PULL = 120;
|
const PTR_MAX_PULL = 120;
|
||||||
|
|
||||||
@@ -3854,6 +3789,9 @@
|
|||||||
// Close more popover if open
|
// Close more popover if open
|
||||||
closeMorePopover();
|
closeMorePopover();
|
||||||
|
|
||||||
|
// Detect reverse direction: going back to where we just came from
|
||||||
|
const isReverse = (tabId === AppState._prevTab);
|
||||||
|
|
||||||
// Track previous tab for back navigation
|
// Track previous tab for back navigation
|
||||||
if (AppState.currentTab !== tabId && AppState.currentTab !== 'tabNavigate') {
|
if (AppState.currentTab !== tabId && AppState.currentTab !== 'tabNavigate') {
|
||||||
AppState._prevTab = AppState.currentTab;
|
AppState._prevTab = AppState.currentTab;
|
||||||
@@ -3871,18 +3809,18 @@
|
|||||||
if (oldPanel && oldPanel !== newPanel) {
|
if (oldPanel && oldPanel !== newPanel) {
|
||||||
// Start entrance animation on new panel immediately
|
// Start entrance animation on new panel immediately
|
||||||
if (newPanel) {
|
if (newPanel) {
|
||||||
newPanel.classList.add('active', 'anim-enter');
|
newPanel.classList.add('active', isReverse ? 'anim-enter-reverse' : 'anim-enter');
|
||||||
newPanel.addEventListener('animationend', function handler() {
|
newPanel.addEventListener('animationend', function handler() {
|
||||||
newPanel.removeEventListener('animationend', handler);
|
newPanel.removeEventListener('animationend', handler);
|
||||||
newPanel.classList.remove('anim-enter');
|
newPanel.classList.remove(isReverse ? 'anim-enter-reverse' : 'anim-enter');
|
||||||
}, { once: true });
|
}, { once: true });
|
||||||
}
|
}
|
||||||
// Animate old panel out while new slides in
|
// Animate old panel out while new slides in
|
||||||
oldPanel.classList.add('anim-exit');
|
oldPanel.classList.add(isReverse ? 'anim-exit-reverse' : 'anim-exit');
|
||||||
oldPanel.classList.remove('active');
|
oldPanel.classList.remove('active');
|
||||||
oldPanel.addEventListener('animationend', function handler() {
|
oldPanel.addEventListener('animationend', function handler() {
|
||||||
oldPanel.removeEventListener('animationend', handler);
|
oldPanel.removeEventListener('animationend', handler);
|
||||||
oldPanel.classList.remove('anim-exit');
|
oldPanel.classList.remove(isReverse ? 'anim-exit-reverse' : 'anim-exit');
|
||||||
}, { once: true });
|
}, { once: true });
|
||||||
} else if (newPanel) {
|
} else if (newPanel) {
|
||||||
newPanel.classList.add('active');
|
newPanel.classList.add('active');
|
||||||
@@ -3929,12 +3867,6 @@
|
|||||||
if (tabId === 'tabReports') loadReports();
|
if (tabId === 'tabReports') loadReports();
|
||||||
if (tabId === 'tabNavigate') renderNavPage();
|
if (tabId === 'tabNavigate') renderNavPage();
|
||||||
|
|
||||||
// FAB visibility — show only on Assets tab
|
|
||||||
const fab = document.getElementById('fabAdd');
|
|
||||||
if (fab) {
|
|
||||||
fab.style.display = (tabId === 'tabAssets') ? '' : 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dispatch event so child tab implementations can hook in
|
// Dispatch event so child tab implementations can hook in
|
||||||
document.dispatchEvent(new CustomEvent('tabChange', { detail: { tabId } }));
|
document.dispatchEvent(new CustomEvent('tabChange', { detail: { tabId } }));
|
||||||
}
|
}
|
||||||
@@ -5353,87 +5285,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ── Pull-to-refresh ───────────────────────────────────────────────────
|
|
||||||
let ptrState = { startY: 0, pulling: false, refreshing: false, dist: 0 };
|
|
||||||
|
|
||||||
function initPullToRefresh() {
|
|
||||||
const wrap = document.getElementById('assetListWrap');
|
|
||||||
if (!wrap) return;
|
|
||||||
|
|
||||||
// Create indicator
|
|
||||||
let indicator = document.getElementById('ptrIndicator');
|
|
||||||
if (!indicator) {
|
|
||||||
indicator = document.createElement('div');
|
|
||||||
indicator.id = 'ptrIndicator';
|
|
||||||
indicator.className = 'ptr-indicator';
|
|
||||||
indicator.innerHTML = '<span class="ptr-arrow">⇣</span> Pull to refresh';
|
|
||||||
wrap.parentNode.insertBefore(indicator, wrap);
|
|
||||||
}
|
|
||||||
|
|
||||||
wrap.addEventListener('touchstart', function(e) {
|
|
||||||
if (ptrState.refreshing) return;
|
|
||||||
// Only trigger when scrolled to top
|
|
||||||
if (wrap.scrollTop <= 0 && document.getElementById('assetSearch') !== document.activeElement) {
|
|
||||||
ptrState.startY = e.touches[0].clientY;
|
|
||||||
ptrState.pulling = true;
|
|
||||||
}
|
|
||||||
}, { passive: true });
|
|
||||||
|
|
||||||
wrap.addEventListener('touchmove', function(e) {
|
|
||||||
if (!ptrState.pulling || ptrState.refreshing) return;
|
|
||||||
const dist = e.touches[0].clientY - ptrState.startY;
|
|
||||||
if (dist > 20) {
|
|
||||||
ptrState.dist = dist;
|
|
||||||
const indicator = document.getElementById('ptrIndicator');
|
|
||||||
if (indicator) {
|
|
||||||
indicator.className = 'ptr-indicator pulling';
|
|
||||||
indicator.innerHTML = dist > 60
|
|
||||||
? '<span class="ptr-arrow">⇡</span> Release to refresh'
|
|
||||||
: '<span class="ptr-arrow">⇣</span> Pull to refresh';
|
|
||||||
}
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
}, { passive: false });
|
|
||||||
|
|
||||||
wrap.addEventListener('touchend', function(e) {
|
|
||||||
if (!ptrState.pulling || ptrState.refreshing) return;
|
|
||||||
ptrState.pulling = false;
|
|
||||||
const indicator = document.getElementById('ptrIndicator');
|
|
||||||
|
|
||||||
if (ptrState.dist > 60) {
|
|
||||||
// Trigger refresh
|
|
||||||
ptrState.refreshing = true;
|
|
||||||
if (indicator) {
|
|
||||||
indicator.className = 'ptr-indicator refreshing';
|
|
||||||
indicator.innerHTML = '<div class="ptr-spinner"></div> Refreshing...';
|
|
||||||
}
|
|
||||||
// Reset cache and reload
|
|
||||||
totalAssetCount = 0;
|
|
||||||
masterCategories = [];
|
|
||||||
loadAssets().then(() => {
|
|
||||||
ptrState.refreshing = false;
|
|
||||||
if (indicator) {
|
|
||||||
indicator.className = 'ptr-indicator';
|
|
||||||
indicator.innerHTML = '<span class="ptr-arrow">⇣</span> Pull to refresh';
|
|
||||||
}
|
|
||||||
showToast('List refreshed');
|
|
||||||
}).catch(() => {
|
|
||||||
ptrState.refreshing = false;
|
|
||||||
if (indicator) {
|
|
||||||
indicator.className = 'ptr-indicator';
|
|
||||||
indicator.innerHTML = '<span class="ptr-arrow">⇣</span> Pull to refresh';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (indicator) {
|
|
||||||
indicator.className = 'ptr-indicator';
|
|
||||||
indicator.innerHTML = '<span class="ptr-arrow">⇣</span> Pull to refresh';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ptrState.dist = 0;
|
|
||||||
}, { passive: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize PTR when assets tab loads
|
// Initialize PTR when assets tab loads
|
||||||
document.addEventListener('tabChange', function(e) {
|
document.addEventListener('tabChange', function(e) {
|
||||||
if (e.detail.tabId === 'tabAssets') {
|
if (e.detail.tabId === 'tabAssets') {
|
||||||
@@ -5458,136 +5309,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Swipe-to-delete ───────────────────────────────────────────────────
|
|
||||||
let swipeState = { startX: 0, startY: 0, swiping: false, item: null, assetId: null, assetName: '' };
|
|
||||||
let undoData = null;
|
|
||||||
let undoTimer = null;
|
|
||||||
|
|
||||||
function initSwipeToDelete() {
|
|
||||||
const list = document.getElementById('assetList');
|
|
||||||
if (!list) return;
|
|
||||||
|
|
||||||
list.addEventListener('touchstart', function(e) {
|
|
||||||
const item = e.target.closest('.asset-item');
|
|
||||||
if (!item) return;
|
|
||||||
swipeState.item = item;
|
|
||||||
swipeState.startX = e.touches[0].clientX;
|
|
||||||
swipeState.startY = e.touches[0].clientY;
|
|
||||||
swipeState.swiping = false;
|
|
||||||
|
|
||||||
// Get asset id from onclick
|
|
||||||
const onclick = item.getAttribute('onclick') || '';
|
|
||||||
const match = onclick.match(/viewAsset\((\d+)\)/);
|
|
||||||
swipeState.assetId = match ? parseInt(match[1]) : null;
|
|
||||||
}, { passive: true });
|
|
||||||
|
|
||||||
list.addEventListener('touchmove', function(e) {
|
|
||||||
if (!swipeState.item || !swipeState.assetId) return;
|
|
||||||
const dx = e.touches[0].clientX - swipeState.startX;
|
|
||||||
const dy = e.touches[0].clientY - swipeState.startY;
|
|
||||||
|
|
||||||
// Only horizontal swipe (reject vertical scrolling)
|
|
||||||
if (Math.abs(dx) > 15 && Math.abs(dx) > Math.abs(dy) * 1.5 && dx < 0) {
|
|
||||||
swipeState.swiping = true;
|
|
||||||
swipeState.item.classList.add('swipe-dragging');
|
|
||||||
const tx = Math.max(dx, -100);
|
|
||||||
swipeState.item.style.transform = 'translateX(' + tx + 'px)';
|
|
||||||
swipeState.item.style.opacity = (1 + tx / 200);
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
}, { passive: false });
|
|
||||||
|
|
||||||
list.addEventListener('touchend', function(e) {
|
|
||||||
if (!swipeState.item) return;
|
|
||||||
swipeState.item.classList.remove('swipe-dragging');
|
|
||||||
|
|
||||||
if (swipeState.swiping) {
|
|
||||||
// Check if swiped far enough
|
|
||||||
const dx = parseFloat(swipeState.item.style.transform?.replace('translateX(', '').replace('px)', '') || '0');
|
|
||||||
if (dx < -50 && swipeState.assetId) {
|
|
||||||
// Delete with undo
|
|
||||||
swipeDeleteAsset(swipeState.assetId);
|
|
||||||
} else {
|
|
||||||
// Snap back
|
|
||||||
swipeState.item.style.transform = '';
|
|
||||||
swipeState.item.style.opacity = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
swipeState.item = null;
|
|
||||||
swipeState.swiping = false;
|
|
||||||
}, { passive: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
async function swipeDeleteAsset(id) {
|
|
||||||
try {
|
|
||||||
// Get asset name for undo toast
|
|
||||||
const asset = cachedAssets.find(a => a.id === id);
|
|
||||||
const name = asset ? asset.name : 'Asset';
|
|
||||||
|
|
||||||
// Store for undo
|
|
||||||
undoData = { id: id, name: name };
|
|
||||||
|
|
||||||
// Delete via API
|
|
||||||
await api('/api/assets/' + id, 'DELETE');
|
|
||||||
|
|
||||||
// Remove from local cache
|
|
||||||
cachedAssets = cachedAssets.filter(a => a.id !== id);
|
|
||||||
totalAssetCount = Math.max(0, totalAssetCount - 1);
|
|
||||||
renderAssetList(cachedAssets);
|
|
||||||
updateCountBar(cachedAssets.length);
|
|
||||||
|
|
||||||
// Show undo toast
|
|
||||||
showUndoToast(name);
|
|
||||||
|
|
||||||
// Clear undo after timeout
|
|
||||||
clearTimeout(undoTimer);
|
|
||||||
undoTimer = setTimeout(() => { undoData = null; }, 6000);
|
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
showToast('Failed to delete: ' + e.message, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function showUndoToast(name) {
|
|
||||||
const toast = document.getElementById('undoToast');
|
|
||||||
if (!toast) return;
|
|
||||||
toast.querySelector('span').textContent = '🗑️ Deleted ' + name;
|
|
||||||
toast.classList.add('show');
|
|
||||||
}
|
|
||||||
|
|
||||||
function undoDelete() {
|
|
||||||
if (!undoData) return;
|
|
||||||
const { id, name } = undoData;
|
|
||||||
|
|
||||||
// Remove from current list view temporarily (will be restored on next load)
|
|
||||||
// Actually, just reload to restore proper state
|
|
||||||
const toast = document.getElementById('undoToast');
|
|
||||||
if (toast) toast.classList.remove('show');
|
|
||||||
|
|
||||||
// We can't easily undo a DELETE without the original data,
|
|
||||||
// so just show a note and flag to reload
|
|
||||||
showToast('Reload the list to restore — undo requires re-adding.', true);
|
|
||||||
undoData = null;
|
|
||||||
clearTimeout(undoTimer);
|
|
||||||
|
|
||||||
// Reload assets to get accurate state
|
|
||||||
totalAssetCount = 0;
|
|
||||||
loadAssets();
|
|
||||||
}
|
|
||||||
|
|
||||||
function dismissUndo() {
|
|
||||||
const toast = document.getElementById('undoToast');
|
|
||||||
if (toast) toast.classList.remove('show');
|
|
||||||
undoData = null;
|
|
||||||
clearTimeout(undoTimer);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init swipe when assets tab loads
|
|
||||||
document.addEventListener('tabChange', function(e) {
|
|
||||||
if (e.detail.tabId === 'tabAssets') {
|
|
||||||
setTimeout(initSwipeToDelete, 150);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
// ── Skeleton loading helpers ──────────────────────────────────────────
|
// ── Skeleton loading helpers ──────────────────────────────────────────
|
||||||
|
|||||||
Reference in New Issue
Block a user