mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-08-28 09:11:06 +00:00
343 lines
9.0 KiB
TypeScript
343 lines
9.0 KiB
TypeScript
import browser from "webextension-polyfill";
|
|
import stringToHTML from "@/seqta/utils/stringToHTML";
|
|
import { settingsState } from "@/seqta/utils/listeners/SettingsState";
|
|
import loadingSpinner from "./loading-spinner.html?raw";
|
|
import loadingOverlay from "./loading-overlay.html?raw";
|
|
import { startLoadingCanvas } from "./loadingCanvas";
|
|
import {
|
|
pickLoadingVariant,
|
|
resolveLoadingTheme,
|
|
type LoadingVariant,
|
|
} from "./loadingVariants";
|
|
|
|
let stopCanvas: (() => void) | null = null;
|
|
let unbindThemeGuard: (() => void) | null = null;
|
|
let activeLoading: {
|
|
host: HTMLElement;
|
|
shadow: ShadowRoot;
|
|
variant: LoadingVariant;
|
|
} | null = null;
|
|
|
|
const THEME_GUARD_KEYS = [
|
|
"selectedTheme",
|
|
"selectedColor",
|
|
"DarkMode",
|
|
"adaptiveThemeColour",
|
|
"selectedFont",
|
|
] as const;
|
|
|
|
const LOADING_PALETTE_DARK = {
|
|
line: "rgba(255, 255, 255, 0.08)",
|
|
lineAccent: "rgba(255, 255, 255, 0.15)",
|
|
lineBlue: "rgba(96, 165, 250, 0.42)",
|
|
grid: "rgba(255, 255, 255, 0.028)",
|
|
text: "#f4f4f5",
|
|
version: "rgba(255, 255, 255, 0.35)",
|
|
} as const;
|
|
|
|
const LOADING_PALETTE_LIGHT = {
|
|
line: "rgba(24, 24, 27, 0.1)",
|
|
lineAccent: "rgba(24, 24, 27, 0.18)",
|
|
lineBlue: "rgba(37, 99, 235, 0.5)",
|
|
grid: "rgba(24, 24, 27, 0.055)",
|
|
text: "#18181b",
|
|
version: "rgba(24, 24, 27, 0.42)",
|
|
} as const;
|
|
|
|
function loadingPalette(darkMode: boolean) {
|
|
return darkMode ? LOADING_PALETTE_DARK : LOADING_PALETTE_LIGHT;
|
|
}
|
|
|
|
const loadingStyles = /* css */ `
|
|
:host {
|
|
--bk-line-color: ${LOADING_PALETTE_DARK.line};
|
|
--bk-line-accent: ${LOADING_PALETTE_DARK.lineAccent};
|
|
--bk-line-blue: ${LOADING_PALETTE_DARK.lineBlue};
|
|
--bk-grid-color: ${LOADING_PALETTE_DARK.grid};
|
|
--bk-spin-outer: 1s;
|
|
--bk-spin-inner: 3s;
|
|
--bk-spin-small: 3s;
|
|
--bk-stage-name: bkloading-stage-in;
|
|
--bk-stage-duration: 0.85s;
|
|
--bk-vignette-fill: radial-gradient(ellipse at center, transparent 32%, rgba(0, 0, 0, 0.75) 100%);
|
|
--bk-version-color: ${LOADING_PALETTE_DARK.version};
|
|
color: ${LOADING_PALETTE_DARK.text};
|
|
opacity: 1;
|
|
transition: opacity 0.85s cubic-bezier(0.4, 0, 0.2, 1);
|
|
}
|
|
|
|
:host(.closeLoading) {
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.bkloading__canvas {
|
|
position: absolute;
|
|
inset: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.bkloading__vignette {
|
|
position: absolute;
|
|
inset: 0;
|
|
pointer-events: none;
|
|
background: var(--bk-vignette-fill);
|
|
opacity: 0.88;
|
|
}
|
|
|
|
.bkloading__content {
|
|
position: absolute;
|
|
inset: 0;
|
|
display: grid;
|
|
place-items: center;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.bkloading__logo-stage,
|
|
.bkloading-inline {
|
|
position: relative;
|
|
}
|
|
|
|
.bkloading__logo-stage {
|
|
width: 220px;
|
|
height: 220px;
|
|
animation-name: var(--bk-stage-name);
|
|
animation-duration: var(--bk-stage-duration);
|
|
animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
|
|
animation-fill-mode: both;
|
|
}
|
|
|
|
.bkloading-inline {
|
|
width: 120px;
|
|
height: 120px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.bkloading__logo-stage .svg,
|
|
.bkloading-inline .svg {
|
|
transform-origin: center center;
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
backface-visibility: hidden;
|
|
will-change: transform;
|
|
}
|
|
|
|
.bkloading__logo-stage .logo,
|
|
.bkloading-inline .logo {
|
|
transform: translate3d(-50%, -50%, 0);
|
|
}
|
|
|
|
.bkloading__logo-stage .logo {
|
|
z-index: 2;
|
|
}
|
|
|
|
.bkloading__logo-stage .big-circle {
|
|
margin: -90px;
|
|
animation: bkloading-spin var(--bk-spin-inner) linear infinite;
|
|
}
|
|
|
|
.bkloading__logo-stage .small-circle {
|
|
margin: -67px;
|
|
animation: bkloading-spin var(--bk-spin-small) linear infinite;
|
|
}
|
|
|
|
.bkloading__logo-stage .outer-circle {
|
|
margin: -110px;
|
|
animation: bkloading-spin-reverse var(--bk-spin-outer) linear infinite;
|
|
}
|
|
|
|
.bkloading-inline .big-circle {
|
|
margin: -88px;
|
|
animation: bkloading-spin 3s linear infinite;
|
|
}
|
|
|
|
.bkloading-inline .small-circle {
|
|
margin: -66px;
|
|
animation: bkloading-spin-reverse 3s linear infinite;
|
|
}
|
|
|
|
.bkloading-inline .outer-circle {
|
|
margin: -108px;
|
|
animation: bkloading-spin 4.5s linear infinite;
|
|
}
|
|
|
|
.bkloading__version {
|
|
position: absolute;
|
|
right: 12px;
|
|
bottom: 10px;
|
|
font-size: 0.95rem;
|
|
letter-spacing: 0.02em;
|
|
color: var(--bk-version-color);
|
|
pointer-events: none;
|
|
}
|
|
|
|
@keyframes bkloading-stage-in {
|
|
from {
|
|
opacity: 0;
|
|
transform: translate3d(0, 12px, 0) scale(0.94);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translate3d(0, 0, 0) scale(1);
|
|
}
|
|
}
|
|
|
|
@keyframes bkloading-spin {
|
|
to {
|
|
transform: rotate(360deg);
|
|
}
|
|
}
|
|
|
|
@keyframes bkloading-spin-reverse {
|
|
to {
|
|
transform: rotate(-360deg);
|
|
}
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.bkloading__logo-stage .big-circle,
|
|
.bkloading__logo-stage .small-circle,
|
|
.bkloading__logo-stage .outer-circle,
|
|
.bkloading-inline .big-circle,
|
|
.bkloading-inline .small-circle,
|
|
.bkloading-inline .outer-circle {
|
|
animation: none !important;
|
|
}
|
|
}
|
|
`;
|
|
|
|
function overlayWithSpinner(): string {
|
|
return loadingOverlay.replace(
|
|
"<div class=\"bkloading__logo-stage\"></div>",
|
|
`<div class="bkloading__logo-stage">${loadingSpinner}</div>`,
|
|
);
|
|
}
|
|
|
|
function applyHostShell(host: HTMLElement, darkMode: boolean) {
|
|
const palette = loadingPalette(darkMode);
|
|
const shell = [
|
|
["position", "fixed"],
|
|
["inset", "0"],
|
|
["z-index", "1000000"],
|
|
["display", "block"],
|
|
["overflow", "hidden"],
|
|
["contain", "strict"],
|
|
["visibility", "visible"],
|
|
["pointer-events", "auto"],
|
|
["color", palette.text],
|
|
["--bk-line-color", palette.line],
|
|
["--bk-line-accent", palette.lineAccent],
|
|
["--bk-line-blue", palette.lineBlue],
|
|
["--bk-grid-color", palette.grid],
|
|
["--bk-version-color", palette.version],
|
|
] as const;
|
|
|
|
for (const [prop, value] of shell) {
|
|
host.style.setProperty(prop, value, "important");
|
|
}
|
|
}
|
|
|
|
function applyVariantTheme(
|
|
host: HTMLElement,
|
|
shadow: ShadowRoot,
|
|
variant: LoadingVariant,
|
|
) {
|
|
const darkMode = !!settingsState.DarkMode;
|
|
const theme = resolveLoadingTheme(variant, darkMode);
|
|
host.dataset.variant = variant.id;
|
|
host.dataset.scheme = darkMode ? "dark" : "light";
|
|
applyHostShell(host, darkMode);
|
|
host.style.setProperty("background", theme.background, "important");
|
|
host.style.setProperty("--bk-spin-outer", theme.spinOuter, "important");
|
|
host.style.setProperty("--bk-spin-inner", theme.spinInner, "important");
|
|
host.style.setProperty("--bk-stage-name", theme.stageName, "important");
|
|
host.style.setProperty("--bk-spin-small", theme.spinSmall, "important");
|
|
host.style.setProperty("--bk-stage-duration", theme.stageDuration, "important");
|
|
host.style.setProperty("--bk-vignette-fill", theme.vignetteFill, "important");
|
|
|
|
const vignette = shadow.querySelector(".bkloading__vignette") as HTMLElement | null;
|
|
if (vignette) {
|
|
vignette.style.opacity = String(theme.vignetteOpacity);
|
|
vignette.style.background = theme.vignetteFill;
|
|
}
|
|
}
|
|
|
|
function refreshActiveLoadingTheme() {
|
|
if (!activeLoading || activeLoading.host.classList.contains("closeLoading")) return;
|
|
applyVariantTheme(activeLoading.host, activeLoading.shadow, activeLoading.variant);
|
|
}
|
|
|
|
function bindLoadingThemeGuard() {
|
|
const onThemeChange = () => refreshActiveLoadingTheme();
|
|
for (const key of THEME_GUARD_KEYS) {
|
|
settingsState.register(key, onThemeChange);
|
|
}
|
|
|
|
const observer = new MutationObserver(onThemeChange);
|
|
observer.observe(document.documentElement, {
|
|
attributes: true,
|
|
attributeFilter: ["style", "class", "data-city-state"],
|
|
});
|
|
|
|
return () => {
|
|
for (const key of THEME_GUARD_KEYS) {
|
|
settingsState.unregister(key, onThemeChange);
|
|
}
|
|
observer.disconnect();
|
|
};
|
|
}
|
|
|
|
function startCanvasForRoot(
|
|
host: HTMLElement,
|
|
shadow: ShadowRoot,
|
|
variant: LoadingVariant,
|
|
) {
|
|
stopCanvas?.();
|
|
const canvas = shadow.querySelector(".bkloading__canvas") as HTMLCanvasElement | null;
|
|
stopCanvas = canvas ? startLoadingCanvas(canvas, host, variant) : null;
|
|
}
|
|
|
|
export function AppendLoadingSymbol(givenID: string, position: string) {
|
|
const loadingsymbol = stringToHTML(/* html */ `
|
|
<div id="${givenID}" class="bkloading-inline">
|
|
${loadingSpinner}
|
|
</div>`).firstChild;
|
|
|
|
document.querySelector(position)?.appendChild(loadingsymbol!);
|
|
}
|
|
|
|
export function stopLoadingAnimation() {
|
|
stopCanvas?.();
|
|
stopCanvas = null;
|
|
unbindThemeGuard?.();
|
|
unbindThemeGuard = null;
|
|
activeLoading = null;
|
|
}
|
|
|
|
export default function loading() {
|
|
stopLoadingAnimation();
|
|
|
|
const variant = pickLoadingVariant();
|
|
const host = document.createElement("div");
|
|
host.id = "loading";
|
|
host.className = "bkloading";
|
|
|
|
const shadow = host.attachShadow({ mode: "open" });
|
|
shadow.innerHTML = `
|
|
<style>${loadingStyles}</style>
|
|
${overlayWithSpinner()}
|
|
<div class="bkloading__version">v${browser.runtime.getManifest().version}</div>
|
|
`;
|
|
|
|
document.documentElement.append(host);
|
|
|
|
applyVariantTheme(host, shadow, variant);
|
|
startCanvasForRoot(host, shadow, variant);
|
|
|
|
activeLoading = { host, shadow, variant };
|
|
unbindThemeGuard = bindLoadingThemeGuard();
|
|
}
|