mirror of
https://github.com/tiennm99/coolify.git
synced 2026-10-16 02:15:32 +00:00
feat(ui): polish domains, storage, env vars and resource nav
Improve project resource UIs: sort domains by DNS failure, stop re-adding www pairs on refresh, lazy-load storage tabs with counts, tighten env-var tables, keep application tabs active across Livewire polls, unify database type labels, and update related CSS/JS and tests.
This commit is contained in:
1 parent
dbc6f5e08c
commit
a7a06aa6c8
75 files changed
+3909
-1173
No files matched your search
@@ -12,3 +12,110 @@ document.addEventListener('livewire:navigated', () => {
|
||||
// Keeping this registration independent from the current route also makes it
|
||||
// available before Alpine processes terminal markup after wire:navigate.
|
||||
document.addEventListener('alpine:init', initializeTerminalComponent);
|
||||
|
||||
/**
|
||||
* Smooth-scroll a settings section into view, then flash its border for 500ms
|
||||
* after the scroll has settled. Starting the flash immediately makes long
|
||||
* jumps (top → bottom) finish scrolling after the animation has already ended.
|
||||
*
|
||||
* @param {string} id
|
||||
*/
|
||||
window.scrollToSettingsSection = function scrollToSettingsSection(id) {
|
||||
const el = document.getElementById(id);
|
||||
if (!el) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof el._sectionHighlightCleanup === 'function') {
|
||||
el._sectionHighlightCleanup();
|
||||
}
|
||||
|
||||
const runHighlight = () => {
|
||||
el.classList.remove('is-section-highlight');
|
||||
// Force reflow so the 500ms highlight can re-run on repeated clicks.
|
||||
void el.offsetWidth;
|
||||
el.classList.add('is-section-highlight');
|
||||
el._sectionHighlightTimer = window.setTimeout(() => {
|
||||
el.classList.remove('is-section-highlight');
|
||||
}, 500);
|
||||
};
|
||||
|
||||
let finished = false;
|
||||
let rafId = 0;
|
||||
let scrollEndHandler = null;
|
||||
|
||||
const cleanup = () => {
|
||||
if (rafId) {
|
||||
window.cancelAnimationFrame(rafId);
|
||||
rafId = 0;
|
||||
}
|
||||
if (scrollEndHandler) {
|
||||
window.removeEventListener('scrollend', scrollEndHandler);
|
||||
scrollEndHandler = null;
|
||||
}
|
||||
if (el._sectionHighlightTimer) {
|
||||
window.clearTimeout(el._sectionHighlightTimer);
|
||||
el._sectionHighlightTimer = null;
|
||||
}
|
||||
};
|
||||
|
||||
const finish = () => {
|
||||
if (finished) {
|
||||
return;
|
||||
}
|
||||
finished = true;
|
||||
cleanup();
|
||||
runHighlight();
|
||||
};
|
||||
|
||||
el._sectionHighlightCleanup = () => {
|
||||
finished = true;
|
||||
cleanup();
|
||||
el.classList.remove('is-section-highlight');
|
||||
el._sectionHighlightCleanup = null;
|
||||
};
|
||||
|
||||
el.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
|
||||
// Prefer the native scrollend event when the browser fires it.
|
||||
scrollEndHandler = () => finish();
|
||||
window.addEventListener('scrollend', scrollEndHandler, { once: true });
|
||||
|
||||
// Fallback: wait until the target's Y position is stable for a few frames
|
||||
// (covers browsers without scrollend, and no-op scrolls when already in view).
|
||||
let lastTop = null;
|
||||
let stableFrames = 0;
|
||||
let frames = 0;
|
||||
const maxFrames = 180; // ~3s safety cap
|
||||
|
||||
const tick = () => {
|
||||
if (finished) {
|
||||
return;
|
||||
}
|
||||
|
||||
frames += 1;
|
||||
const top = el.getBoundingClientRect().top;
|
||||
|
||||
if (lastTop !== null && Math.abs(top - lastTop) < 0.5) {
|
||||
stableFrames += 1;
|
||||
} else {
|
||||
stableFrames = 0;
|
||||
}
|
||||
lastTop = top;
|
||||
|
||||
// Skip the first couple frames so we don't flash before smooth scroll starts.
|
||||
if (frames > 4 && stableFrames >= 4) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
if (frames >= maxFrames) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
rafId = window.requestAnimationFrame(tick);
|
||||
};
|
||||
|
||||
rafId = window.requestAnimationFrame(tick);
|
||||
};
|
||||
Reference in new issue
Block a user