mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-06-06 03:34:40 +00:00
feat: add smooth animation to notifications opening like settings
This commit is contained in:
@@ -1653,6 +1653,13 @@ html.transparencyEffects
|
|||||||
box-shadow: 0px 10px 15px -3px rgba(0, 0, 0, 0.4);
|
box-shadow: 0px 10px 15px -3px rgba(0, 0, 0, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Smoothed by attachNotificationsPanelAnimation (matches #ExtensionPopup spring) */
|
||||||
|
.bsplus-notifications-panel {
|
||||||
|
transform-origin: top right;
|
||||||
|
will-change: opacity, transform;
|
||||||
|
filter: drop-shadow(0px 0px 20px rgba(0, 0, 0, 0.35));
|
||||||
|
}
|
||||||
|
|
||||||
#menu li.active {
|
#menu li.active {
|
||||||
color: #ffffff !important;
|
color: #ffffff !important;
|
||||||
background: rgba(0, 0, 0, 0.35);
|
background: rgba(0, 0, 0, 0.35);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { isSeqtaEngageExperience } from "@/seqta/utils/isSeqtaEngage";
|
|||||||
import { loadEngageHomePage } from "@/seqta/utils/Loaders/LoadEngageHomePage";
|
import { loadEngageHomePage } from "@/seqta/utils/Loaders/LoadEngageHomePage";
|
||||||
import { loadHomePage } from "@/seqta/utils/Loaders/LoadHomePage";
|
import { loadHomePage } from "@/seqta/utils/Loaders/LoadHomePage";
|
||||||
import { SendNewsPage } from "@/seqta/utils/SendNewsPage";
|
import { SendNewsPage } from "@/seqta/utils/SendNewsPage";
|
||||||
|
import { attachNotificationsPanelAnimation } from "@/seqta/utils/attachNotificationsPanelAnimation";
|
||||||
import { setupSettingsButton } from "@/seqta/utils/setupSettingsButton";
|
import { setupSettingsButton } from "@/seqta/utils/setupSettingsButton";
|
||||||
import { waitForElm } from "@/seqta/utils/waitForElm";
|
import { waitForElm } from "@/seqta/utils/waitForElm";
|
||||||
|
|
||||||
@@ -89,6 +90,7 @@ export async function AddBetterSEQTAElements() {
|
|||||||
addExtensionSettings();
|
addExtensionSettings();
|
||||||
await createSettingsButton();
|
await createSettingsButton();
|
||||||
setupSettingsButton();
|
setupSettingsButton();
|
||||||
|
attachNotificationsPanelAnimation();
|
||||||
}
|
}
|
||||||
|
|
||||||
function createHomeButton(fragment: DocumentFragment, _: HTMLElement) {
|
function createHomeButton(fragment: DocumentFragment, _: HTMLElement) {
|
||||||
@@ -423,10 +425,12 @@ async function setupEngageSettingsButton() {
|
|||||||
await addDarkLightToggle(parent);
|
await addDarkLightToggle(parent);
|
||||||
await createSettingsButton(parent);
|
await createSettingsButton(parent);
|
||||||
setupSettingsButton();
|
setupSettingsButton();
|
||||||
|
attachNotificationsPanelAnimation();
|
||||||
} catch {
|
} catch {
|
||||||
await addDarkLightToggle();
|
await addDarkLightToggle();
|
||||||
await createSettingsButton();
|
await createSettingsButton();
|
||||||
setupSettingsButton();
|
setupSettingsButton();
|
||||||
|
attachNotificationsPanelAnimation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,128 @@
|
|||||||
|
import { animate } from "motion";
|
||||||
|
import { settingsState } from "@/seqta/utils/listeners/SettingsState";
|
||||||
|
import { waitForElm } from "@/seqta/utils/waitForElm";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the SEQTA notifications dropdown panel (the list container next to the bell).
|
||||||
|
*/
|
||||||
|
function findNotificationPanel(): HTMLElement | null {
|
||||||
|
const wrapper = document.querySelector(".connectedNotificationsWrapper");
|
||||||
|
if (!wrapper) return null;
|
||||||
|
|
||||||
|
const flat = wrapper.querySelector<HTMLElement>(":scope > div > button + div");
|
||||||
|
if (flat) return flat;
|
||||||
|
|
||||||
|
const notifBlock = wrapper.querySelector("[class*='notifications__notifications___']");
|
||||||
|
if (notifBlock?.nextElementSibling instanceof HTMLElement) {
|
||||||
|
return notifBlock.nextElementSibling;
|
||||||
|
}
|
||||||
|
|
||||||
|
const list = wrapper.querySelector<HTMLElement>("[class*='notifications__list___']");
|
||||||
|
if (list) return list;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPanelVisible(el: HTMLElement): boolean {
|
||||||
|
return (
|
||||||
|
el.getClientRects().length > 0 && getComputedStyle(el).visibility !== "hidden"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let lastVisible = false;
|
||||||
|
/** Invalidates in-flight open animations when the panel closes or reopens. */
|
||||||
|
let motionGeneration = 0;
|
||||||
|
|
||||||
|
function runOpenAnimation(panel: HTMLElement) {
|
||||||
|
const myGen = ++motionGeneration;
|
||||||
|
panel.classList.add("bsplus-notifications-panel");
|
||||||
|
|
||||||
|
if (!settingsState.animations) {
|
||||||
|
panel.style.opacity = "1";
|
||||||
|
panel.style.transform = "scale(1)";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
panel.style.opacity = "0";
|
||||||
|
panel.style.transform = "scale(0)";
|
||||||
|
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
if (myGen !== motionGeneration) return;
|
||||||
|
animate(0, 1, {
|
||||||
|
onUpdate: (progress) => {
|
||||||
|
panel.style.opacity = String(progress);
|
||||||
|
panel.style.transform = `scale(${progress})`;
|
||||||
|
},
|
||||||
|
type: "spring",
|
||||||
|
stiffness: 280,
|
||||||
|
damping: 20,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearPanelMotionStyles(panel: HTMLElement) {
|
||||||
|
motionGeneration++;
|
||||||
|
panel.style.opacity = "";
|
||||||
|
panel.style.transform = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spring open / fade close for the native SEQTA notifications dropdown, matching ExtensionPopup.
|
||||||
|
*/
|
||||||
|
export function attachNotificationsPanelAnimation() {
|
||||||
|
void setupNotificationsPanelAnimation();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setupNotificationsPanelAnimation() {
|
||||||
|
try {
|
||||||
|
await waitForElm(".connectedNotificationsWrapper", true, 100, 60);
|
||||||
|
} catch {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const wrapper = document.querySelector(".connectedNotificationsWrapper");
|
||||||
|
if (!wrapper) return;
|
||||||
|
|
||||||
|
const sync = () => {
|
||||||
|
const panel = findNotificationPanel();
|
||||||
|
// When SEQTA removes the dropdown from the DOM on close, we must reset
|
||||||
|
// lastVisible — otherwise the next open still looks "already visible" and skips animation.
|
||||||
|
if (!panel) {
|
||||||
|
if (lastVisible) {
|
||||||
|
lastVisible = false;
|
||||||
|
motionGeneration++;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const visible = isPanelVisible(panel);
|
||||||
|
if (visible === lastVisible) return;
|
||||||
|
|
||||||
|
if (visible) {
|
||||||
|
runOpenAnimation(panel);
|
||||||
|
} else {
|
||||||
|
clearPanelMotionStyles(panel);
|
||||||
|
}
|
||||||
|
lastVisible = visible;
|
||||||
|
};
|
||||||
|
|
||||||
|
const observer = new MutationObserver(() => {
|
||||||
|
sync();
|
||||||
|
});
|
||||||
|
observer.observe(wrapper, {
|
||||||
|
subtree: true,
|
||||||
|
childList: true,
|
||||||
|
attributes: true,
|
||||||
|
attributeFilter: ["style", "class"],
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener(
|
||||||
|
"click",
|
||||||
|
() => {
|
||||||
|
requestAnimationFrame(() => requestAnimationFrame(sync));
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
sync();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user