mirror of
https://github.com/jellyfin/jellyfin-web.git
synced 2024-11-18 03:18:19 -07:00
453 lines
13 KiB
JavaScript
453 lines
13 KiB
JavaScript
define(['browser'], function (browser) {
|
|
'use strict';
|
|
|
|
var mainAnimatedPages = document.querySelector('.mainAnimatedPages');
|
|
var allPages = [];
|
|
var currentUrls = [];
|
|
var pageContainerCount = 3;
|
|
var selectedPageIndex = -1;
|
|
|
|
function enableAnimation() {
|
|
|
|
if (browser.tv) {
|
|
return false;
|
|
}
|
|
if (browser.safari) {
|
|
// Right now they don't look good. Haven't figured out why yet.
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
function loadView(options) {
|
|
|
|
if (options.cancel) {
|
|
return;
|
|
}
|
|
|
|
cancelActiveAnimations();
|
|
|
|
var selected = selectedPageIndex;
|
|
var previousAnimatable = selected == -1 ? null : allPages[selected];
|
|
var pageIndex = selected + 1;
|
|
|
|
if (pageIndex >= pageContainerCount) {
|
|
pageIndex = 0;
|
|
}
|
|
|
|
var newViewInfo = normalizeNewView(options);
|
|
var newView = newViewInfo.elem;
|
|
|
|
var dependencies = typeof (newView) == 'string' ? null : newView.getAttribute('data-require');
|
|
dependencies = dependencies ? dependencies.split(',') : [];
|
|
|
|
var isPluginpage = options.url.toLowerCase().indexOf('/configurationpage?') != -1;
|
|
|
|
if (isPluginpage) {
|
|
dependencies.push('jqmpopup');
|
|
dependencies.push('legacy/dashboard');
|
|
dependencies.push('legacy/selectmenu');
|
|
dependencies.push('jqmlistview');
|
|
dependencies.push('fnchecked');
|
|
}
|
|
|
|
if (isPluginpage || (newView.classList && newView.classList.contains('type-interior'))) {
|
|
dependencies.push('scripts/notifications');
|
|
dependencies.push('css!css/notifications.css');
|
|
dependencies.push('dashboardcss');
|
|
}
|
|
|
|
return new Promise(function (resolve, reject) {
|
|
|
|
require(dependencies, function () {
|
|
|
|
var currentPage = allPages[pageIndex];
|
|
|
|
if (currentPage) {
|
|
triggerDestroy(currentPage);
|
|
}
|
|
|
|
var view = newView;
|
|
|
|
if (typeof (view) == 'string') {
|
|
view = document.createElement('div');
|
|
view.innerHTML = newView;
|
|
}
|
|
|
|
view.classList.add('mainAnimatedPage');
|
|
|
|
if (currentPage) {
|
|
if (newViewInfo.hasScript && window.$) {
|
|
// TODO: figure this out without jQuery
|
|
view = $(view).appendTo(mainAnimatedPages)[0];
|
|
mainAnimatedPages.removeChild(currentPage);
|
|
} else {
|
|
mainAnimatedPages.replaceChild(view, currentPage);
|
|
}
|
|
} else {
|
|
if (newViewInfo.hasScript && window.$) {
|
|
// TODO: figure this out without jQuery
|
|
view = $(view).appendTo(mainAnimatedPages)[0];
|
|
} else {
|
|
mainAnimatedPages.appendChild(view);
|
|
}
|
|
}
|
|
|
|
if (typeof (newView) != 'string') {
|
|
enhanceNewView(dependencies, view);
|
|
}
|
|
|
|
if (options.type) {
|
|
view.setAttribute('data-type', options.type);
|
|
}
|
|
|
|
var animatable = view;
|
|
allPages[pageIndex] = view;
|
|
|
|
if (onBeforeChange) {
|
|
onBeforeChange(view, false, options);
|
|
}
|
|
|
|
beforeAnimate(allPages, pageIndex, selected);
|
|
// animate here
|
|
animate(animatable, previousAnimatable, options.transition, options.isBack).then(function () {
|
|
|
|
selectedPageIndex = pageIndex;
|
|
currentUrls[pageIndex] = options.url;
|
|
if (!options.cancel && previousAnimatable) {
|
|
afterAnimate(allPages, pageIndex);
|
|
}
|
|
|
|
// Temporary hack
|
|
// If a view renders UI in viewbeforeshow the lazy image loader will think the images aren't visible and won't load images
|
|
// The views need to be updated to start loading data in beforeshow, but not render until show
|
|
document.dispatchEvent(new CustomEvent('scroll', {}));
|
|
|
|
if (window.$) {
|
|
$.mobile = $.mobile || {};
|
|
$.mobile.activePage = view;
|
|
}
|
|
|
|
resolve(view);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
function enhanceNewView(dependencies, newView) {
|
|
|
|
var hasJqm = false;
|
|
|
|
for (var i = 0, length = dependencies.length; i < length; i++) {
|
|
if (dependencies[i].indexOf('jqm') == 0) {
|
|
hasJqm = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (hasJqm && window.$) {
|
|
$(newView).trigger('create');
|
|
}
|
|
}
|
|
|
|
function replaceAll(str, find, replace) {
|
|
|
|
return str.split(find).join(replace);
|
|
}
|
|
|
|
function parseHtml(html, hasScript) {
|
|
|
|
if (hasScript) {
|
|
html = replaceAll(html, '<!--<script', '<script');
|
|
html = replaceAll(html, '</script>-->', '</script>');
|
|
}
|
|
|
|
var wrapper = document.createElement('div');
|
|
wrapper.innerHTML = html;
|
|
return wrapper.querySelector('div[data-role="page"]');
|
|
}
|
|
|
|
function normalizeNewView(options) {
|
|
|
|
if (options.view.indexOf('data-role="page"') == -1) {
|
|
return options.view;
|
|
}
|
|
|
|
var hasScript = options.view.indexOf('<script') != -1;
|
|
var elem = parseHtml(options.view, hasScript);
|
|
|
|
if (hasScript) {
|
|
hasScript = elem.querySelector('script') != null;
|
|
}
|
|
|
|
return {
|
|
elem: elem,
|
|
hasScript: hasScript
|
|
};
|
|
}
|
|
|
|
function beforeAnimate(allPages, newPageIndex, oldPageIndex) {
|
|
for (var i = 0, length = allPages.length; i < length; i++) {
|
|
if (newPageIndex == i || oldPageIndex == i) {
|
|
//allPages[i].classList.remove('hide');
|
|
} else {
|
|
allPages[i].classList.add('hide');
|
|
}
|
|
}
|
|
}
|
|
|
|
function afterAnimate(allPages, newPageIndex) {
|
|
for (var i = 0, length = allPages.length; i < length; i++) {
|
|
if (newPageIndex == i) {
|
|
//allPages[i].classList.remove('hide');
|
|
} else {
|
|
allPages[i].classList.add('hide');
|
|
}
|
|
}
|
|
}
|
|
|
|
function animate(newAnimatedPage, oldAnimatedPage, transition, isBack) {
|
|
|
|
transition = transition || 'fade';
|
|
|
|
if (enableAnimation() && oldAnimatedPage && newAnimatedPage.animate) {
|
|
if (transition == 'slide') {
|
|
return slideLeft(newAnimatedPage, oldAnimatedPage, transition, isBack);
|
|
} else if (transition == 'slidedown') {
|
|
return slideDown(newAnimatedPage, oldAnimatedPage, transition, isBack);
|
|
} else if (transition == 'fade') {
|
|
return fade(newAnimatedPage, oldAnimatedPage, transition, isBack);
|
|
}
|
|
}
|
|
|
|
return Promise.resolve();
|
|
}
|
|
|
|
function slideLeft(newAnimatedPage, oldAnimatedPage, transition, isBack) {
|
|
|
|
return new Promise(function (resolve, reject) {
|
|
|
|
// Do not use fill: both or the ability to swipe horizontally may be affected on Chrome 50
|
|
var timings = {
|
|
duration: 450,
|
|
iterations: 1,
|
|
easing: 'ease-out'
|
|
}
|
|
|
|
if (!browser.chrome) {
|
|
timings.fill = 'both';
|
|
}
|
|
|
|
var animations = [];
|
|
|
|
if (oldAnimatedPage) {
|
|
var destination = isBack ? '100%' : '-100%';
|
|
|
|
animations.push(oldAnimatedPage.animate([
|
|
|
|
{ transform: 'none', offset: 0 },
|
|
{ transform: 'translate3d(' + destination + ', 0, 0)', offset: 1 }
|
|
|
|
], timings));
|
|
}
|
|
|
|
var start = isBack ? '-100%' : '100%';
|
|
|
|
animations.push(newAnimatedPage.animate([
|
|
|
|
{ transform: 'translate3d(' + start + ', 0, 0)', offset: 0 },
|
|
{ transform: 'none', offset: 1 }
|
|
|
|
], timings));
|
|
|
|
currentAnimations = animations;
|
|
|
|
animations[animations.length - 1].onfinish = resolve;
|
|
});
|
|
}
|
|
|
|
function slideDown(newAnimatedPage, oldAnimatedPage, transition, isBack) {
|
|
|
|
return new Promise(function (resolve, reject) {
|
|
|
|
// Do not use fill: both or the ability to swipe horizontally may be affected on Chrome 50
|
|
var timings = {
|
|
duration: 450,
|
|
iterations: 1,
|
|
easing: 'ease-out'
|
|
}
|
|
|
|
if (!browser.chrome) {
|
|
timings.fill = 'both';
|
|
}
|
|
|
|
var animations = [];
|
|
|
|
if (oldAnimatedPage) {
|
|
var destination = isBack ? '100%' : '-100%';
|
|
|
|
animations.push(oldAnimatedPage.animate([
|
|
|
|
{ transform: 'none', offset: 0 },
|
|
{ transform: 'translate3d(' + destination + ', 0, 0)', offset: 1 }
|
|
|
|
], timings));
|
|
}
|
|
|
|
var start = isBack ? '100%' : '-100%';
|
|
|
|
animations.push(newAnimatedPage.animate([
|
|
|
|
{ transform: 'translate3d(0, ' + start + ', 0)', offset: 0 },
|
|
{ transform: 'none', offset: 1 }
|
|
|
|
], timings));
|
|
|
|
currentAnimations = animations;
|
|
|
|
animations[animations.length - 1].onfinish = resolve;
|
|
});
|
|
}
|
|
|
|
function fade(newAnimatedPage, oldAnimatedPage, transition, isBack) {
|
|
|
|
return new Promise(function (resolve, reject) {
|
|
|
|
// Do not use fill: both or the ability to swipe horizontally may be affected on Chrome 50
|
|
var timings = {
|
|
duration: 160,
|
|
iterations: 1,
|
|
easing: 'ease-out'
|
|
}
|
|
|
|
if (!browser.chrome) {
|
|
timings.fill = 'both';
|
|
}
|
|
|
|
var animations = [];
|
|
|
|
if (oldAnimatedPage) {
|
|
animations.push(oldAnimatedPage.animate([
|
|
|
|
{ opacity: 1, offset: 0 },
|
|
{ opacity: 0, offset: 1 }
|
|
|
|
], timings));
|
|
}
|
|
|
|
animations.push(newAnimatedPage.animate([
|
|
|
|
{ opacity: 0, offset: 0 },
|
|
{ opacity: 1, offset: 1 }
|
|
|
|
], timings));
|
|
|
|
currentAnimations = animations;
|
|
|
|
animations[animations.length - 1].onfinish = resolve;
|
|
});
|
|
}
|
|
|
|
var currentAnimations = [];
|
|
function cancelActiveAnimations() {
|
|
|
|
var animations = currentAnimations;
|
|
for (var i = 0, length = animations.length; i < length; i++) {
|
|
cancelAnimation(animations[i]);
|
|
}
|
|
}
|
|
|
|
function cancelAnimation(animation) {
|
|
|
|
try {
|
|
animation.cancel();
|
|
} catch (err) {
|
|
console.log('Error canceling animation: ' + err);
|
|
}
|
|
}
|
|
|
|
var onBeforeChange;
|
|
function setOnBeforeChange(fn) {
|
|
onBeforeChange = fn;
|
|
}
|
|
|
|
function tryRestoreView(options) {
|
|
|
|
var url = options.url;
|
|
var index = currentUrls.indexOf(url);
|
|
|
|
if (index != -1) {
|
|
|
|
var animatable = allPages[index];
|
|
var view = animatable;
|
|
|
|
if (view) {
|
|
|
|
if (options.cancel) {
|
|
return;
|
|
}
|
|
|
|
cancelActiveAnimations();
|
|
|
|
var selected = selectedPageIndex;
|
|
var previousAnimatable = selected == -1 ? null : allPages[selected];
|
|
|
|
if (onBeforeChange) {
|
|
onBeforeChange(view, true, options);
|
|
}
|
|
|
|
beforeAnimate(allPages, index, selected);
|
|
|
|
animatable.classList.remove('hide');
|
|
|
|
return animate(animatable, previousAnimatable, options.transition, options.isBack).then(function () {
|
|
|
|
selectedPageIndex = index;
|
|
if (!options.cancel && previousAnimatable) {
|
|
afterAnimate(allPages, index);
|
|
}
|
|
|
|
// Temporary hack
|
|
// If a view renders UI in viewbeforeshow the lazy image loader will think the images aren't visible and won't load images
|
|
// The views need to be updated to start loading data in beforeshow, but not render until show
|
|
document.dispatchEvent(new CustomEvent('scroll', {}));
|
|
|
|
if (window.$) {
|
|
$.mobile = $.mobile || {};
|
|
$.mobile.activePage = view;
|
|
}
|
|
return view;
|
|
});
|
|
}
|
|
}
|
|
|
|
return Promise.reject();
|
|
}
|
|
|
|
function triggerDestroy(view) {
|
|
view.dispatchEvent(new CustomEvent("viewdestroy", {}));
|
|
}
|
|
|
|
function reset() {
|
|
|
|
allPages = [];
|
|
currentUrls = [];
|
|
mainAnimatedPages.innerHTML = '';
|
|
selectedPageIndex = -1;
|
|
}
|
|
|
|
if (enableAnimation() && !document.documentElement.animate) {
|
|
require(['webAnimations']);
|
|
}
|
|
|
|
reset();
|
|
mainAnimatedPages.classList.remove('hide');
|
|
|
|
return {
|
|
loadView: loadView,
|
|
tryRestoreView: tryRestoreView,
|
|
reset: reset,
|
|
setOnBeforeChange: setOnBeforeChange
|
|
};
|
|
}); |