233 lines
6.0 KiB
JavaScript
233 lines
6.0 KiB
JavaScript
// Copyright (c) 2019 Florian Klampfer <https://qwtel.com/>
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import { fromEvent, merge, timer, zip } from "rxjs";
|
|
import {
|
|
tap,
|
|
exhaustMap,
|
|
map,
|
|
mapTo,
|
|
mergeMap,
|
|
pairwise,
|
|
share,
|
|
startWith,
|
|
switchMap,
|
|
takeUntil,
|
|
} from "rxjs/operators";
|
|
|
|
import { animate, empty, importTemplate, webComponentsReady } from "./common";
|
|
import { CrossFader } from "./cross-fader";
|
|
import { setupFLIP } from "./flip";
|
|
|
|
(async () => {
|
|
await Promise.all([
|
|
...("fetch" in window
|
|
? []
|
|
: [import(/* webpackChunkName: "fetch" */ "./polyfills/fetch")]),
|
|
...("customElements" in window
|
|
? []
|
|
: [
|
|
import(
|
|
/* webpackChunkName: "webcomponents" */ "./polyfills/webcomponents"
|
|
),
|
|
]),
|
|
...("animate" in Element.prototype
|
|
? []
|
|
: [import(/* webpackChunkName: "webanimations" */ "web-animations-js")]),
|
|
...("IntersectionObserver" in window
|
|
? []
|
|
: [
|
|
import(
|
|
/* webpackChunkName: "intersection-observer" */ "intersection-observer"
|
|
),
|
|
]),
|
|
]);
|
|
|
|
await webComponentsReady;
|
|
|
|
const NAVBAR_SEL = "#_navbar > .content > .nav-btn-bar";
|
|
|
|
const CROSS_FADE_DURATION = 2000;
|
|
|
|
const FADE_OUT = [{ opacity: 1 }, { opacity: 0 }];
|
|
const FADE_IN = [
|
|
{ opacity: 0, transform: "translateY(-3rem)" },
|
|
{ opacity: 1, transform: "translateY(0)" },
|
|
];
|
|
|
|
function setupAnimationMain(pushStateEl) {
|
|
pushStateEl?.parentNode?.insertBefore(
|
|
importTemplate("_animation-template"),
|
|
pushStateEl,
|
|
);
|
|
return pushStateEl?.previousElementSibling;
|
|
}
|
|
|
|
function setupLoading(navbarEl) {
|
|
navbarEl?.appendChild(importTemplate("_loading-template"));
|
|
return navbarEl?.lastElementChild;
|
|
}
|
|
|
|
function setupErrorPage(main, url) {
|
|
const { pathname } = url;
|
|
const error = importTemplate("_error-template");
|
|
const anchor = error?.querySelector(".this-link");
|
|
if (anchor) {
|
|
anchor.href = pathname;
|
|
anchor.textContent = pathname;
|
|
}
|
|
main?.appendChild(error);
|
|
return main?.lastElementChild;
|
|
}
|
|
|
|
function getFlipType(el) {
|
|
if (el?.classList.contains("flip-title")) return "title";
|
|
if (el?.classList.contains("flip-project")) return "project";
|
|
return el?.getAttribute?.("data-flip");
|
|
}
|
|
|
|
const pushStateEl = document.querySelector("hy-push-state");
|
|
if (!pushStateEl) return;
|
|
|
|
const duration = Number(pushStateEl.getAttribute("duration")) || 350;
|
|
const settings = {
|
|
duration: duration,
|
|
easing: "ease",
|
|
};
|
|
|
|
const animateFadeOut = ({ main }) =>
|
|
animate(main, FADE_OUT, { ...settings, fill: "forwards" }).pipe(
|
|
mapTo({ main }),
|
|
);
|
|
|
|
const animateFadeIn = ({ replaceEls: [main], flipType }) =>
|
|
animate(main, FADE_IN, settings).pipe(mapTo({ main, flipType }));
|
|
|
|
const drawerEl = document.querySelector("hy-drawer");
|
|
const navbarEl = document.querySelector(NAVBAR_SEL);
|
|
|
|
const animationMain = setupAnimationMain(pushStateEl);
|
|
const loadingEl = setupLoading(navbarEl);
|
|
|
|
const fromEventX = (eventName, mapFn) =>
|
|
fromEvent(pushStateEl, eventName).pipe(
|
|
map(({ detail }) => detail),
|
|
mapFn ? map(mapFn) : (_) => _,
|
|
share(),
|
|
);
|
|
|
|
const start$ = fromEventX("hy-push-state-start", (detail) =>
|
|
Object.assign(detail, { flipType: getFlipType(detail.anchor) }),
|
|
);
|
|
const ready$ = fromEventX("hy-push-state-ready");
|
|
const after$ = fromEventX("hy-push-state-after");
|
|
const progress$ = fromEventX("hy-push-state-progress");
|
|
const error$ = fromEventX("hy-push-state-networkerror");
|
|
|
|
const fadeOut$ = start$.pipe(
|
|
map((context) =>
|
|
Object.assign(context, { main: document.getElementById("_main") }),
|
|
),
|
|
tap(({ main }) => {
|
|
main.style.pointerEvents = "none";
|
|
}),
|
|
window._noDrawer && drawerEl?.classList.contains("cover")
|
|
? tap(() => {
|
|
drawerEl.classList.remove("cover");
|
|
drawerEl.parentNode?.appendChild(drawerEl);
|
|
})
|
|
: (_) => _,
|
|
exhaustMap(animateFadeOut),
|
|
tap(({ main }) => empty.call(main)),
|
|
share(),
|
|
);
|
|
|
|
if (loadingEl) {
|
|
progress$.subscribe(() => {
|
|
loadingEl.style.display = "flex";
|
|
});
|
|
ready$.subscribe(() => {
|
|
loadingEl.style.display = "none";
|
|
});
|
|
}
|
|
|
|
const fadeIn$ = after$.pipe(
|
|
switchMap((context) => {
|
|
const p = animateFadeIn(context).toPromise();
|
|
context.transitionUntil(p);
|
|
return p;
|
|
}),
|
|
share(),
|
|
);
|
|
|
|
const flip$ = setupFLIP(start$, ready$, merge(fadeIn$, error$), {
|
|
animationMain,
|
|
settings: settings,
|
|
}).pipe(share());
|
|
|
|
start$
|
|
.pipe(
|
|
switchMap((context) => {
|
|
const promise = zip(timer(duration), fadeOut$, flip$).toPromise();
|
|
context.transitionUntil(promise);
|
|
return promise;
|
|
}),
|
|
)
|
|
.subscribe();
|
|
|
|
// FIXME: Keeping permanent subscription? turn into hot observable?
|
|
fadeOut$.subscribe();
|
|
flip$.subscribe();
|
|
|
|
const sidebarBg = document.querySelector(".sidebar-bg");
|
|
if (sidebarBg) {
|
|
const crossFader = new CrossFader(CROSS_FADE_DURATION);
|
|
after$
|
|
.pipe(
|
|
switchMap(({ document }) =>
|
|
zip(crossFader.fetchImage(document), fadeIn$).pipe(
|
|
map(([x]) => x),
|
|
takeUntil(start$),
|
|
),
|
|
),
|
|
startWith([sidebarBg]),
|
|
pairwise(),
|
|
mergeMap(([prev, curr]) => crossFader.fade(prev, curr)),
|
|
)
|
|
.subscribe();
|
|
}
|
|
|
|
error$
|
|
.pipe(
|
|
switchMap(({ url }) => {
|
|
if (loadingEl) loadingEl.style.display = "none";
|
|
|
|
const main = document.getElementById("_main");
|
|
if (main) main.style.pointerEvents = "";
|
|
empty.call(animationMain?.querySelector(".page"));
|
|
empty.call(main);
|
|
|
|
setupErrorPage(main, url);
|
|
|
|
return animate(main, FADE_IN, { ...settings, fill: "forwards" });
|
|
}),
|
|
)
|
|
.subscribe();
|
|
|
|
import(/* webpackMode: "eager" */ "@hydecorp/push-state");
|
|
|
|
window._pushState = pushStateEl;
|
|
})();
|