213 lines
6.3 KiB
JavaScript
213 lines
6.3 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;
|
|
})();
|