1
0
Fork 0
mirror of https://github.com/jellyfin/jellyfin-web synced 2025-03-30 19:56:21 +00:00

move some files to more reasonable locations

This commit is contained in:
dkanada 2020-04-24 23:37:27 +09:00
parent 3bcadd7605
commit 1843927576
16 changed files with 13 additions and 12 deletions

View file

@ -0,0 +1,11 @@
.headroom {
transition: transform 140ms linear;
}
.headroom--pinned {
transform: none;
}
.headroom--unpinned:not(.headroomDisabled) {
transform: translateY(-100%);
}

View file

@ -0,0 +1,343 @@
/*!
* headroom.js v0.7.0 - Give your page some headroom. Hide your header until you need it
* Copyright (c) 2014 Nick Williams - http://wicky.nillia.ms/headroom.js
* License: MIT
*/
define(['dom', 'layoutManager', 'browser', 'css!./headroom'], function (dom, layoutManager, browser) {
'use strict';
/* exported features */
var requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame;
/**
* Handles debouncing of events via requestAnimationFrame
* @see http://www.html5rocks.com/en/tutorials/speed/animations/
* @param {Function} callback The callback to handle whichever event
*/
function Debouncer(callback) {
this.callback = callback;
this.ticking = false;
}
Debouncer.prototype = {
constructor: Debouncer,
/**
* dispatches the event to the supplied callback
* @private
*/
update: function () {
if (this.callback) {
this.callback();
}
this.ticking = false;
},
/**
* Attach this as the event listeners
*/
handleEvent: function () {
if (!this.ticking) {
requestAnimationFrame(this.rafCallback || (this.rafCallback = this.update.bind(this)));
this.ticking = true;
}
}
};
function onHeadroomClearedExternally() {
this.state = null;
}
/**
* UI enhancement for fixed headers.
* Hides header when scrolling down
* Shows header when scrolling up
* @constructor
* @param {DOMElement} elem the header element
* @param {Object} options options for the widget
*/
function Headroom(elems, options) {
options = Object.assign(Headroom.options, options || {});
this.lastKnownScrollY = 0;
this.elems = elems;
this.scroller = options.scroller;
this.debouncer = onScroll.bind(this);
this.offset = options.offset;
this.initialised = false;
this.initialClass = options.initialClass;
this.unPinnedClass = options.unPinnedClass;
this.pinnedClass = options.pinnedClass;
this.state = 'clear';
this.options = {
offset: 0,
scroller: window,
initialClass: 'headroom',
unPinnedClass: 'headroom--unpinned',
pinnedClass: 'headroom--pinned'
};
this.add = function (elem) {
if (browser.supportsCssAnimation()) {
elem.classList.add(this.initialClass);
elem.addEventListener('clearheadroom', onHeadroomClearedExternally.bind(this));
this.elems.push(elem);
}
};
this.remove = function (elem) {
elem.classList.remove(this.unPinnedClass);
elem.classList.remove(this.initialClass);
elem.classList.remove(this.pinnedClass);
var i = this.elems.indexOf(elem);
if (i !== -1) {
this.elems.splice(i, 1);
}
};
this.pause = function () {
this.paused = true;
};
this.resume = function () {
this.paused = false;
};
/**
* Unattaches events and removes any classes that were added
*/
this.destroy = function () {
this.initialised = false;
for (var i = 0, length = this.elems.length; i < length; i++) {
var classList = this.elems[i].classList;
classList.remove(this.unPinnedClass);
classList.remove(this.initialClass);
classList.remove(this.pinnedClass);
}
var scrollEventName = this.scroller.getScrollEventName ? this.scroller.getScrollEventName() : 'scroll';
dom.removeEventListener(this.scroller, scrollEventName, this.debouncer, {
capture: false,
passive: true
});
};
/**
* Attaches the scroll event
* @private
*/
this.attachEvent = function () {
if (!this.initialised) {
this.lastKnownScrollY = this.getScrollY();
this.initialised = true;
var scrollEventName = this.scroller.getScrollEventName ? this.scroller.getScrollEventName() : 'scroll';
dom.addEventListener(this.scroller, scrollEventName, this.debouncer, {
capture: false,
passive: true
});
this.update();
}
};
/**
* Unpins the header if it's currently pinned
*/
this.clear = function () {
if (this.state === 'clear') {
return;
}
this.state = 'clear';
var unpinnedClass = this.unPinnedClass;
var pinnedClass = this.pinnedClass;
for (var i = 0, length = this.elems.length; i < length; i++) {
var classList = this.elems[i].classList;
classList.remove(unpinnedClass);
//classList.remove(pinnedClass);
}
};
/**
* Unpins the header if it's currently pinned
*/
this.pin = function () {
if (this.state === 'pin') {
return;
}
this.state = 'pin';
var unpinnedClass = this.unPinnedClass;
var pinnedClass = this.pinnedClass;
for (var i = 0, length = this.elems.length; i < length; i++) {
var classList = this.elems[i].classList;
classList.remove(unpinnedClass);
classList.add(pinnedClass);
}
};
/**
* Unpins the header if it's currently pinned
*/
this.unpin = function () {
if (this.state === 'unpin') {
return;
}
this.state = 'unpin';
var unpinnedClass = this.unPinnedClass;
var pinnedClass = this.pinnedClass;
for (var i = 0, length = this.elems.length; i < length; i++) {
var classList = this.elems[i].classList;
classList.add(unpinnedClass);
//classList.remove(pinnedClass);
}
};
/**
* Gets the Y scroll position
* @see https://developer.mozilla.org/en-US/docs/Web/API/Window.scrollY
* @return {Number} pixels the page has scrolled along the Y-axis
*/
this.getScrollY = function () {
var scroller = this.scroller;
if (scroller.getScrollPosition) {
return scroller.getScrollPosition();
}
var pageYOffset = scroller.pageYOffset;
if (pageYOffset !== undefined) {
return pageYOffset;
}
var scrollTop = scroller.scrollTop;
if (scrollTop !== undefined) {
return scrollTop;
}
return (document.documentElement || document.body).scrollTop;
};
/**
* determine if it is appropriate to unpin
* @param {int} currentScrollY the current y scroll position
* @return {bool} true if should unpin, false otherwise
*/
this.shouldUnpin = function (currentScrollY) {
var scrollingDown = currentScrollY > this.lastKnownScrollY;
var pastOffset = currentScrollY >= this.offset;
return scrollingDown && pastOffset;
};
/**
* determine if it is appropriate to pin
* @param {int} currentScrollY the current y scroll position
* @return {bool} true if should pin, false otherwise
*/
this.shouldPin = function (currentScrollY) {
var scrollingUp = currentScrollY < this.lastKnownScrollY;
var pastOffset = currentScrollY <= this.offset;
return scrollingUp || pastOffset;
};
/**
* Handles updating the state of the widget
*/
this.update = function () {
if (this.paused) {
return;
}
var currentScrollY = this.getScrollY();
var lastKnownScrollY = this.lastKnownScrollY;
var isTv = layoutManager.tv;
if (currentScrollY <= (isTv ? 120 : 10)) {
this.clear();
} else if (this.shouldUnpin(currentScrollY)) {
this.unpin();
} else if (this.shouldPin(currentScrollY)) {
var toleranceExceeded = Math.abs(currentScrollY - lastKnownScrollY) >= 14;
if (currentScrollY && isTv) {
this.unpin();
} else if (toleranceExceeded) {
this.clear();
}
} else if (isTv) {
//this.clear();
}
this.lastKnownScrollY = currentScrollY;
};
if (browser.supportsCssAnimation()) {
for (var i = 0, length = this.elems.length; i < length; i++) {
this.elems[i].classList.add(this.initialClass);
this.elems[i].addEventListener('clearheadroom', onHeadroomClearedExternally.bind(this));
}
this.attachEvent();
}
}
function onScroll() {
if (this.paused) {
return;
}
requestAnimationFrame(this.rafCallback || (this.rafCallback = this.update.bind(this)));
}
/**
* Default options
* @type {Object}
*/
Headroom.options = {
offset: 0,
scroller: window,
initialClass: 'headroom',
unPinnedClass: 'headroom--unpinned',
pinnedClass: 'headroom--pinned'
};
return Headroom;
});

View file

@ -0,0 +1,52 @@
.tmla-mask,
.touch-menu-la {
position: fixed;
top: 0;
bottom: 0;
contain: strict;
}
.touch-menu-la {
background-color: #fff;
will-change: transform;
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-transition: -webkit-transform ease-out 40ms, left ease-out 260ms;
-o-transition: transform ease-out 40ms, left ease-out 260ms;
transition: transform ease-out 40ms, left ease-out 260ms;
z-index: 1099;
}
.touch-menu-la.transition {
-webkit-transition: -webkit-transform ease-out 240ms, left ease-out 260ms;
-o-transition: transform ease-out 240ms, left ease-out 260ms;
transition: transform ease-out 240ms, left ease-out 260ms;
}
.drawer-open {
-webkit-box-shadow: 2px 0 12px rgba(0, 0, 0, 0.4);
box-shadow: 2px 0 12px rgba(0, 0, 0, 0.4);
}
.scrollContainer {
-webkit-box-flex: 1;
-webkit-flex-grow: 1;
flex-grow: 1;
}
.tmla-mask {
left: 0;
right: 0;
opacity: 0;
z-index: 1098;
-webkit-transition: opacity ease-in-out 0.38s, visibility ease-in-out 0.38s;
-o-transition: opacity ease-in-out 0.38s, visibility ease-in-out 0.38s;
transition: opacity ease-in-out 0.38s, visibility ease-in-out 0.38s;
will-change: opacity;
background-color: rgba(0, 0, 0, 0.3);
}
.tmla-mask.backdrop {
opacity: 1;
}

View file

@ -0,0 +1,353 @@
define(["browser", "dom", "css!./navdrawer", "scrollStyles"], function (browser, dom) {
"use strict";
return function (options) {
function getTouches(e) {
return e.changedTouches || e.targetTouches || e.touches;
}
function onMenuTouchStart(e) {
options.target.classList.remove("transition");
var touches = getTouches(e);
var touch = touches[0] || {};
menuTouchStartX = touch.clientX;
menuTouchStartY = touch.clientY;
menuTouchStartTime = new Date().getTime();
}
function setVelocity(deltaX) {
var time = new Date().getTime() - (menuTouchStartTime || 0);
velocity = Math.abs(deltaX) / time;
}
function onMenuTouchMove(e) {
var isOpen = self.visible;
var touches = getTouches(e);
var touch = touches[0] || {};
var endX = touch.clientX || 0;
var endY = touch.clientY || 0;
var deltaX = endX - (menuTouchStartX || 0);
var deltaY = endY - (menuTouchStartY || 0);
setVelocity(deltaX);
if (isOpen && 1 !== dragMode && deltaX > 0) {
dragMode = 2;
}
if (0 === dragMode && (!isOpen || Math.abs(deltaX) >= 10) && Math.abs(deltaY) < 5) {
dragMode = 1;
scrollContainer.addEventListener("scroll", disableEvent);
self.showMask();
} else if (0 === dragMode && Math.abs(deltaY) >= 5) {
dragMode = 2;
}
if (1 === dragMode) {
newPos = currentPos + deltaX;
self.changeMenuPos();
}
}
function onMenuTouchEnd(e) {
options.target.classList.add("transition");
scrollContainer.removeEventListener("scroll", disableEvent);
dragMode = 0;
var touches = getTouches(e);
var touch = touches[0] || {};
var endX = touch.clientX || 0;
var endY = touch.clientY || 0;
var deltaX = endX - (menuTouchStartX || 0);
var deltaY = endY - (menuTouchStartY || 0);
currentPos = deltaX;
self.checkMenuState(deltaX, deltaY);
}
function onEdgeTouchStart(e) {
if (isPeeking) {
onMenuTouchMove(e);
} else {
if (((getTouches(e)[0] || {}).clientX || 0) <= options.handleSize) {
isPeeking = true;
if (e.type === "touchstart") {
dom.removeEventListener(edgeContainer, "touchmove", onEdgeTouchMove, {});
dom.addEventListener(edgeContainer, "touchmove", onEdgeTouchMove, {});
}
onMenuTouchStart(e);
}
}
}
function onEdgeTouchMove(e) {
e.preventDefault();
e.stopPropagation();
onEdgeTouchStart(e);
}
function onEdgeTouchEnd(e) {
if (isPeeking) {
isPeeking = false;
dom.removeEventListener(edgeContainer, "touchmove", onEdgeTouchMove, {});
onMenuTouchEnd(e);
}
}
function disableEvent(e) {
e.preventDefault();
e.stopPropagation();
}
function onBackgroundTouchStart(e) {
var touches = getTouches(e);
var touch = touches[0] || {};
backgroundTouchStartX = touch.clientX;
backgroundTouchStartTime = new Date().getTime();
}
function onBackgroundTouchMove(e) {
var touches = getTouches(e);
var touch = touches[0] || {};
var endX = touch.clientX || 0;
if (endX <= options.width && self.isVisible) {
countStart++;
var deltaX = endX - (backgroundTouchStartX || 0);
if (countStart == 1) {
startPoint = deltaX;
}
if (deltaX < 0 && dragMode !== 2) {
dragMode = 1;
newPos = deltaX - startPoint + options.width;
self.changeMenuPos();
var time = new Date().getTime() - (backgroundTouchStartTime || 0);
velocity = Math.abs(deltaX) / time;
}
}
e.preventDefault();
e.stopPropagation();
}
function onBackgroundTouchEnd(e) {
var touches = getTouches(e);
var touch = touches[0] || {};
var endX = touch.clientX || 0;
var deltaX = endX - (backgroundTouchStartX || 0);
self.checkMenuState(deltaX);
countStart = 0;
}
function onMaskTransitionEnd() {
var classList = mask.classList;
if (!classList.contains("backdrop")) {
classList.add("hide");
}
}
var self;
var defaults;
var mask;
var newPos = 0;
var currentPos = 0;
var startPoint = 0;
var countStart = 0;
var velocity = 0;
options.target.classList.add("transition");
var dragMode = 0;
var scrollContainer = options.target.querySelector(".mainDrawer-scrollContainer");
scrollContainer.classList.add("scrollY");
var TouchMenuLA = function () {
self = this;
defaults = {
width: 260,
handleSize: 10,
disableMask: false,
maxMaskOpacity: 0.5
};
this.isVisible = false;
this.initialize();
};
TouchMenuLA.prototype.initElements = function () {
options.target.classList.add("touch-menu-la");
options.target.style.width = options.width + "px";
options.target.style.left = -options.width + "px";
if (!options.disableMask) {
mask = document.createElement("div");
mask.className = "tmla-mask hide";
document.body.appendChild(mask);
dom.addEventListener(mask, dom.whichTransitionEvent(), onMaskTransitionEnd, {
passive: true
});
}
};
var menuTouchStartX;
var menuTouchStartY;
var menuTouchStartTime;
var edgeContainer = document.querySelector(".mainDrawerHandle");
var isPeeking = false;
TouchMenuLA.prototype.animateToPosition = function (pos) {
requestAnimationFrame(function () {
options.target.style.transform = pos ? "translateX(" + pos + "px)" : "none";
});
};
TouchMenuLA.prototype.changeMenuPos = function () {
if (newPos <= options.width) {
this.animateToPosition(newPos);
}
};
TouchMenuLA.prototype.clickMaskClose = function () {
mask.addEventListener("click", function () {
self.close();
});
};
TouchMenuLA.prototype.checkMenuState = function (deltaX, deltaY) {
if (velocity >= 0.4) {
if (deltaX >= 0 || Math.abs(deltaY || 0) >= 70) {
self.open();
} else {
self.close();
}
} else {
if (newPos >= 100) {
self.open();
} else {
if (newPos) {
self.close();
}
}
}
};
TouchMenuLA.prototype.open = function () {
this.animateToPosition(options.width);
currentPos = options.width;
this.isVisible = true;
options.target.classList.add("drawer-open");
self.showMask();
self.invoke(options.onChange);
};
TouchMenuLA.prototype.close = function () {
this.animateToPosition(0);
currentPos = 0;
self.isVisible = false;
options.target.classList.remove("drawer-open");
self.hideMask();
self.invoke(options.onChange);
};
TouchMenuLA.prototype.toggle = function () {
if (self.isVisible) {
self.close();
} else {
self.open();
}
};
var backgroundTouchStartX;
var backgroundTouchStartTime;
TouchMenuLA.prototype.showMask = function () {
mask.classList.remove("hide");
mask.offsetWidth;
mask.classList.add("backdrop");
};
TouchMenuLA.prototype.hideMask = function () {
mask.classList.add("hide");
mask.classList.remove("backdrop");
};
TouchMenuLA.prototype.invoke = function (fn) {
if (fn) {
fn.apply(self);
}
};
var _edgeSwipeEnabled;
TouchMenuLA.prototype.setEdgeSwipeEnabled = function (enabled) {
if (!options.disableEdgeSwipe) {
if (browser.touch) {
if (enabled) {
if (!_edgeSwipeEnabled) {
_edgeSwipeEnabled = true;
dom.addEventListener(edgeContainer, "touchstart", onEdgeTouchStart, {
passive: true
});
dom.addEventListener(edgeContainer, "touchend", onEdgeTouchEnd, {
passive: true
});
dom.addEventListener(edgeContainer, "touchcancel", onEdgeTouchEnd, {
passive: true
});
}
} else {
if (_edgeSwipeEnabled) {
_edgeSwipeEnabled = false;
dom.removeEventListener(edgeContainer, "touchstart", onEdgeTouchStart, {
passive: true
});
dom.removeEventListener(edgeContainer, "touchend", onEdgeTouchEnd, {
passive: true
});
dom.removeEventListener(edgeContainer, "touchcancel", onEdgeTouchEnd, {
passive: true
});
}
}
}
}
};
TouchMenuLA.prototype.initialize = function () {
options = Object.assign(defaults, options || {});
if (browser.edge) {
options.disableEdgeSwipe = true;
}
self.initElements();
if (browser.touch) {
dom.addEventListener(options.target, "touchstart", onMenuTouchStart, {
passive: true
});
dom.addEventListener(options.target, "touchmove", onMenuTouchMove, {
passive: true
});
dom.addEventListener(options.target, "touchend", onMenuTouchEnd, {
passive: true
});
dom.addEventListener(options.target, "touchcancel", onMenuTouchEnd, {
passive: true
});
dom.addEventListener(mask, "touchstart", onBackgroundTouchStart, {
passive: true
});
dom.addEventListener(mask, "touchmove", onBackgroundTouchMove, {});
dom.addEventListener(mask, "touchend", onBackgroundTouchEnd, {
passive: true
});
dom.addEventListener(mask, "touchcancel", onBackgroundTouchEnd, {
passive: true
});
}
self.clickMaskClose();
};
return new TouchMenuLA();
};
});

View file

@ -0,0 +1,132 @@
define(["events", "playbackManager", "pluginManager", "inputManager", "connectionManager", "userSettings"], function (events, playbackManager, pluginManager, inputManager, connectionManager, userSettings) {
"use strict";
function getMinIdleTime() {
// Returns the minimum amount of idle time required before the screen saver can be displayed
//time units used Millisecond
return 180000;
}
var lastFunctionalEvent = 0;
function getFunctionalEventIdleTime() {
return new Date().getTime() - lastFunctionalEvent;
}
events.on(playbackManager, "playbackstop", function (e, stopInfo) {
var state = stopInfo.state;
if (state.NowPlayingItem && state.NowPlayingItem.MediaType == "Video") {
lastFunctionalEvent = new Date().getTime();
}
});
function getScreensaverPlugin(isLoggedIn) {
var option;
try {
option = userSettings.get("screensaver", false);
} catch (err) {
option = isLoggedIn ? "backdropscreensaver" : "logoscreensaver";
}
var plugins = pluginManager.ofType("screensaver");
for (var i = 0, length = plugins.length; i < length; i++) {
var plugin = plugins[i];
if (plugin.id === option) {
return plugin;
}
}
return null;
}
function ScreenSaverManager() {
var self = this;
var activeScreenSaver;
function showScreenSaver(screensaver) {
if (activeScreenSaver) {
throw new Error("An existing screensaver is already active.");
}
console.debug("Showing screensaver " + screensaver.name);
screensaver.show();
activeScreenSaver = screensaver;
if (screensaver.hideOnClick !== false) {
window.addEventListener("click", hide, true);
}
if (screensaver.hideOnMouse !== false) {
window.addEventListener("mousemove", hide, true);
}
if (screensaver.hideOnKey !== false) {
window.addEventListener("keydown", hide, true);
}
}
function hide() {
if (activeScreenSaver) {
console.debug("Hiding screensaver");
activeScreenSaver.hide();
activeScreenSaver = null;
}
window.removeEventListener("click", hide, true);
window.removeEventListener("mousemove", hide, true);
window.removeEventListener("keydown", hide, true);
}
self.isShowing = function () {
return activeScreenSaver != null;
};
self.show = function () {
var isLoggedIn;
var apiClient = connectionManager.currentApiClient();
if (apiClient && apiClient.isLoggedIn()) {
isLoggedIn = true;
}
var screensaver = getScreensaverPlugin(isLoggedIn);
if (screensaver) {
showScreenSaver(screensaver);
}
};
self.hide = function () {
hide();
};
function onInterval() {
if (self.isShowing()) {
return;
}
if (inputManager.idleTime() < getMinIdleTime()) {
return;
}
if (getFunctionalEventIdleTime < getMinIdleTime()) {
return;
}
if (playbackManager.isPlayingVideo()) {
return;
}
self.show();
}
setInterval(onInterval, 10000);
}
return new ScreenSaverManager();
});

932
src/libraries/scroller.js Normal file
View file

@ -0,0 +1,932 @@
define(['browser', 'layoutManager', 'dom', 'focusManager', 'ResizeObserver', 'scrollStyles'], function (browser, layoutManager, dom, focusManager, ResizeObserver) {
'use strict';
/**
* Return type of the value.
*
* @param {Mixed} value
*
* @return {String}
*/
function type(value) {
if (value == null) {
return String(value);
}
if (typeof value === 'object' || typeof value === 'function') {
return Object.prototype.toString.call(value).match(/\s([a-z]+)/i)[1].toLowerCase() || 'object';
}
return typeof value;
}
/**
* Disables an event it was triggered on and unbinds itself.
*
* @param {Event} event
*
* @return {Void}
*/
function disableOneEvent(event) {
/*jshint validthis:true */
event.preventDefault();
event.stopPropagation();
this.removeEventListener(event.type, disableOneEvent);
}
/**
* Make sure that number is within the limits.
*
* @param {Number} number
* @param {Number} min
* @param {Number} max
*
* @return {Number}
*/
function within(number, min, max) {
return number < min ? min : number > max ? max : number;
}
// Other global values
var dragMouseEvents = ['mousemove', 'mouseup'];
var dragTouchEvents = ['touchmove', 'touchend'];
var wheelEvent = (document.implementation.hasFeature('Event.wheel', '3.0') ? 'wheel' : 'mousewheel');
var interactiveElements = ['INPUT', 'SELECT', 'TEXTAREA'];
var tmpArray = [];
var time;
// Math shorthands
var abs = Math.abs;
var sqrt = Math.sqrt;
var pow = Math.pow;
var round = Math.round;
var max = Math.max;
var min = Math.min;
var scrollerFactory = function (frame, options) {
// Extend options
var o = Object.assign({}, {
slidee: null, // Selector, DOM element, or jQuery object with DOM element representing SLIDEE.
horizontal: false, // Switch to horizontal mode.
// Scrolling
mouseWheel: true,
scrollBy: 0, // Pixels or items to move per one mouse scroll. 0 to disable scrolling
// Dragging
dragSource: null, // Selector or DOM element for catching dragging events. Default is FRAME.
mouseDragging: 1, // Enable navigation by dragging the SLIDEE with mouse cursor.
touchDragging: 1, // Enable navigation by dragging the SLIDEE with touch events.
dragThreshold: 3, // Distance in pixels before Sly recognizes dragging.
intervactive: null, // Selector for special interactive elements.
// Mixed options
speed: 0 // Animations speed in milliseconds. 0 to disable animations.
}, options);
var isSmoothScrollSupported = 'scrollBehavior' in document.documentElement.style;
// native scroll is a must with touch input
// also use native scroll when scrolling vertically in desktop mode - excluding horizontal because the mouse wheel support is choppy at the moment
// in cases with firefox, if the smooth scroll api is supported then use that because their implementation is very good
if (options.allowNativeScroll === false) {
options.enableNativeScroll = false;
} else if (isSmoothScrollSupported && ((browser.firefox && !layoutManager.tv) || options.allowNativeSmoothScroll)) {
// native smooth scroll
options.enableNativeScroll = true;
} else if (options.requireAnimation && (browser.animate || browser.supportsCssAnimation())) {
// transform is the only way to guarantee animation
options.enableNativeScroll = false;
} else if (!layoutManager.tv || !browser.animate) {
options.enableNativeScroll = true;
}
// Need this for the magic wheel. With the animated scroll the magic wheel will run off of the screen
if (browser.web0s) {
options.enableNativeScroll = true;
}
// Private variables
var self = this;
self.options = o;
// Frame
var slideeElement = o.slidee ? o.slidee : sibling(frame.firstChild)[0];
self._pos = {
start: 0,
center: 0,
end: 0,
cur: 0,
dest: 0
};
var transform = !options.enableNativeScroll;
// Miscellaneous
var scrollSource = frame;
var dragSourceElement = o.dragSource ? o.dragSource : frame;
var dragging = {
released: 1
};
var scrolling = {
last: 0,
delta: 0,
resetTime: 200
};
// Expose properties
self.initialized = 0;
self.slidee = slideeElement;
self.options = o;
self.dragging = dragging;
var nativeScrollElement = frame;
function sibling(n, elem) {
var matched = [];
for (; n; n = n.nextSibling) {
if (n.nodeType === 1 && n !== elem) {
matched.push(n);
}
}
return matched;
}
var requiresReflow = true;
var frameSize = 0;
var slideeSize = 0;
function ensureSizeInfo() {
if (requiresReflow) {
requiresReflow = false;
// Reset global variables
frameSize = o.horizontal ? (frame).offsetWidth : (frame).offsetHeight;
slideeSize = o.scrollWidth || Math.max(slideeElement[o.horizontal ? 'offsetWidth' : 'offsetHeight'], slideeElement[o.horizontal ? 'scrollWidth' : 'scrollHeight']);
// Set position limits & relativess
self._pos.end = max(slideeSize - frameSize, 0);
}
}
/**
* Loading function.
*
* Populate arrays, set sizes, bind events, ...
*
* @param {Boolean} [isInit] Whether load is called from within self.init().
* @return {Void}
*/
function load(isInit) {
requiresReflow = true;
if (!isInit) {
ensureSizeInfo();
// Fix possible overflowing
var pos = self._pos;
self.slideTo(within(pos.dest, pos.start, pos.end));
}
}
function initFrameResizeObserver() {
var observerOptions = {};
self.frameResizeObserver = new ResizeObserver(onResize, observerOptions);
self.frameResizeObserver.observe(frame);
}
self.reload = function () {
load();
};
self.getScrollEventName = function () {
return transform ? 'scrollanimate' : 'scroll';
};
self.getScrollSlider = function () {
return slideeElement;
};
self.getScrollFrame = function () {
return frame;
};
function nativeScrollTo(container, pos, immediate) {
if (container.scroll) {
if (o.horizontal) {
container.scroll({
left: pos,
behavior: immediate ? 'instant' : 'smooth'
});
} else {
container.scroll({
top: pos,
behavior: immediate ? 'instant' : 'smooth'
});
}
} else if (!immediate && container.scrollTo) {
if (o.horizontal) {
container.scrollTo(Math.round(pos), 0);
} else {
container.scrollTo(0, Math.round(pos));
}
} else {
if (o.horizontal) {
container.scrollLeft = Math.round(pos);
} else {
container.scrollTop = Math.round(pos);
}
}
}
var lastAnimate;
/**
* Animate to a position.
*
* @param {Int} newPos New position.
* @param {Bool} immediate Reposition immediately without an animation.
*
* @return {Void}
*/
self.slideTo = function (newPos, immediate, fullItemPos) {
ensureSizeInfo();
var pos = self._pos;
newPos = within(newPos, pos.start, pos.end);
if (!transform) {
nativeScrollTo(nativeScrollElement, newPos, immediate);
return;
}
// Update the animation object
var from = pos.cur;
immediate = immediate || dragging.init || !o.speed;
var now = new Date().getTime();
if (o.autoImmediate) {
if (!immediate && (now - (lastAnimate || 0)) <= 50) {
immediate = true;
}
}
if (!immediate && o.skipSlideToWhenVisible && fullItemPos && fullItemPos.isVisible) {
return;
}
// Start animation rendering
// NOTE the dependency was modified here to fix a scrollbutton issue
pos.dest = newPos;
renderAnimateWithTransform(from, newPos, immediate);
lastAnimate = now;
};
function setStyleProperty(elem, name, value, speed, resetTransition) {
var style = elem.style;
if (resetTransition || browser.edge) {
style.transition = 'none';
void elem.offsetWidth;
}
style.transition = 'transform ' + speed + 'ms ease-out';
style[name] = value;
}
function dispatchScrollEventIfNeeded() {
if (o.dispatchScrollEvent) {
frame.dispatchEvent(new CustomEvent(self.getScrollEventName(), {
bubbles: true,
cancelable: false
}));
}
}
function renderAnimateWithTransform(fromPosition, toPosition, immediate) {
var speed = o.speed;
if (immediate) {
speed = o.immediateSpeed || 50;
}
if (o.horizontal) {
setStyleProperty(slideeElement, 'transform', 'translateX(' + (-round(toPosition)) + 'px)', speed);
} else {
setStyleProperty(slideeElement, 'transform', 'translateY(' + (-round(toPosition)) + 'px)', speed);
}
self._pos.cur = toPosition;
dispatchScrollEventIfNeeded();
}
function getBoundingClientRect(elem) {
// Support: BlackBerry 5, iOS 3 (original iPhone)
// If we don't have gBCR, just use 0,0 rather than error
if (elem.getBoundingClientRect) {
return elem.getBoundingClientRect();
} else {
return { top: 0, left: 0 };
}
}
/**
* Returns the position object.
*
* @param {Mixed} item
*
* @return {Object}
*/
self.getPos = function (item) {
var scrollElement = transform ? slideeElement : nativeScrollElement;
var slideeOffset = getBoundingClientRect(scrollElement);
var itemOffset = getBoundingClientRect(item);
var slideeStartPos = o.horizontal ? slideeOffset.left : slideeOffset.top;
var slideeEndPos = o.horizontal ? slideeOffset.right : slideeOffset.bottom;
var offset = o.horizontal ? itemOffset.left - slideeOffset.left : itemOffset.top - slideeOffset.top;
var size = o.horizontal ? itemOffset.width : itemOffset.height;
if (!size && size !== 0) {
size = item[o.horizontal ? 'offsetWidth' : 'offsetHeight'];
}
var centerOffset = o.centerOffset || 0;
if (!transform) {
centerOffset = 0;
if (o.horizontal) {
offset += nativeScrollElement.scrollLeft;
} else {
offset += nativeScrollElement.scrollTop;
}
}
ensureSizeInfo();
var currentStart = self._pos.cur;
var currentEnd = currentStart + frameSize;
console.debug('offset:' + offset + ' currentStart:' + currentStart + ' currentEnd:' + currentEnd);
var isVisible = offset >= currentStart && (offset + size) <= currentEnd;
return {
start: offset,
center: offset + centerOffset - (frameSize / 2) + (size / 2),
end: offset - frameSize + size,
size: size,
isVisible: isVisible
};
};
self.getCenterPosition = function (item) {
ensureSizeInfo();
var pos = self.getPos(item);
return within(pos.center, pos.start, pos.end);
};
function dragInitSlidee(event) {
var isTouch = event.type === 'touchstart';
// Ignore when already in progress, or interactive element in non-touch navivagion
if (dragging.init || !isTouch && isInteractive(event.target)) {
return;
}
// SLIDEE dragging conditions
if (!(isTouch ? o.touchDragging : o.mouseDragging && event.which < 2)) {
return;
}
if (!isTouch) {
// prevents native image dragging in Firefox
event.preventDefault();
}
// Reset dragging object
dragging.released = 0;
// Properties used in dragHandler
dragging.init = 0;
dragging.source = event.target;
dragging.touch = isTouch;
var pointer = isTouch ? event.touches[0] : event;
dragging.initX = pointer.pageX;
dragging.initY = pointer.pageY;
dragging.initPos = self._pos.cur;
dragging.start = +new Date();
dragging.time = 0;
dragging.path = 0;
dragging.delta = 0;
dragging.locked = 0;
dragging.pathToLock = isTouch ? 30 : 10;
// Bind dragging events
if (transform) {
if (isTouch) {
dragTouchEvents.forEach(function (eventName) {
dom.addEventListener(document, eventName, dragHandler, {
passive: true
});
});
} else {
dragMouseEvents.forEach(function (eventName) {
dom.addEventListener(document, eventName, dragHandler, {
passive: true
});
});
}
}
}
/**
* Handler for dragging scrollbar handle or SLIDEE.
*
* @param {Event} event
*
* @return {Void}
*/
function dragHandler(event) {
dragging.released = event.type === 'mouseup' || event.type === 'touchend';
var pointer = dragging.touch ? event[dragging.released ? 'changedTouches' : 'touches'][0] : event;
dragging.pathX = pointer.pageX - dragging.initX;
dragging.pathY = pointer.pageY - dragging.initY;
dragging.path = sqrt(pow(dragging.pathX, 2) + pow(dragging.pathY, 2));
dragging.delta = o.horizontal ? dragging.pathX : dragging.pathY;
if (!dragging.released && dragging.path < 1) {
return;
}
// We haven't decided whether this is a drag or not...
if (!dragging.init) {
// If the drag path was very short, maybe it's not a drag?
if (dragging.path < o.dragThreshold) {
// If the pointer was released, the path will not become longer and it's
// definitely not a drag. If not released yet, decide on next iteration
return dragging.released ? dragEnd() : undefined;
} else {
// If dragging path is sufficiently long we can confidently start a drag
// if drag is in different direction than scroll, ignore it
if (o.horizontal ? abs(dragging.pathX) > abs(dragging.pathY) : abs(dragging.pathX) < abs(dragging.pathY)) {
dragging.init = 1;
} else {
return dragEnd();
}
}
}
//event.preventDefault();
// Disable click on a source element, as it is unwelcome when dragging
if (!dragging.locked && dragging.path > dragging.pathToLock) {
dragging.locked = 1;
dragging.source.addEventListener('click', disableOneEvent);
}
// Cancel dragging on release
if (dragging.released) {
dragEnd();
}
self.slideTo(round(dragging.initPos - dragging.delta));
}
/**
* Stops dragging and cleans up after it.
*
* @return {Void}
*/
function dragEnd() {
dragging.released = true;
dragTouchEvents.forEach(function (eventName) {
dom.removeEventListener(document, eventName, dragHandler, {
passive: true
});
});
dragMouseEvents.forEach(function (eventName) {
dom.removeEventListener(document, eventName, dragHandler, {
passive: true
});
});
// Make sure that disableOneEvent is not active in next tick.
setTimeout(function () {
dragging.source.removeEventListener('click', disableOneEvent);
});
dragging.init = 0;
}
/**
* Check whether element is interactive.
*
* @return {Boolean}
*/
function isInteractive(element) {
while (element) {
if (interactiveElements.indexOf(element.tagName) !== -1) {
return true;
}
element = element.parentNode;
}
return false;
}
/**
* Mouse wheel delta normalization.
*
* @param {Event} event
*
* @return {Int}
*/
function normalizeWheelDelta(event) {
// JELLYFIN MOD: Only use deltaX for horizontal scroll and remove IE8 support
scrolling.curDelta = o.horizontal ? event.deltaX : event.deltaY;
// END JELLYFIN MOD
if (transform) {
scrolling.curDelta /= event.deltaMode === 1 ? 3 : 100;
}
return scrolling.curDelta;
}
/**
* Mouse scrolling handler.
*
* @param {Event} event
*
* @return {Void}
*/
function scrollHandler(event) {
ensureSizeInfo();
var pos = self._pos;
// Ignore if there is no scrolling to be done
if (!o.scrollBy || pos.start === pos.end) {
return;
}
var delta = normalizeWheelDelta(event);
if (transform) {
// Trap scrolling only when necessary and/or requested
if (delta > 0 && pos.dest < pos.end || delta < 0 && pos.dest > pos.start) {
//stopDefault(event, 1);
}
self.slideBy(o.scrollBy * delta);
} else {
if (isSmoothScrollSupported) {
delta *= 12;
}
if (o.horizontal) {
nativeScrollElement.scrollLeft += delta;
} else {
nativeScrollElement.scrollTop += delta;
}
}
}
/**
* Destroys instance and everything it created.
*
* @return {Void}
*/
self.destroy = function () {
if (self.frameResizeObserver) {
self.frameResizeObserver.disconnect();
self.frameResizeObserver = null;
}
// Reset native FRAME element scroll
dom.removeEventListener(frame, 'scroll', resetScroll, {
passive: true
});
dom.removeEventListener(scrollSource, wheelEvent, scrollHandler, {
passive: true
});
dom.removeEventListener(dragSourceElement, 'touchstart', dragInitSlidee, {
passive: true
});
dom.removeEventListener(frame, 'click', onFrameClick, {
passive: true,
capture: true
});
dom.removeEventListener(dragSourceElement, 'mousedown', dragInitSlidee, {
//passive: true
});
// Reset initialized status and return the instance
self.initialized = 0;
return self;
};
var contentRect = {};
function onResize(entries) {
var entry = entries[0];
if (entry) {
var newRect = entry.contentRect;
// handle element being hidden
if (newRect.width === 0 || newRect.height === 0) {
return;
}
if (newRect.width !== contentRect.width || newRect.height !== contentRect.height) {
contentRect = newRect;
load(false);
}
}
}
function resetScroll() {
if (o.horizontal) {
this.scrollLeft = 0;
} else {
this.scrollTop = 0;
}
}
function onFrameClick(e) {
if (e.which === 1) {
var focusableParent = focusManager.focusableParent(e.target);
if (focusableParent && focusableParent !== document.activeElement) {
focusableParent.focus();
}
}
}
self.getScrollPosition = function () {
if (transform) {
return self._pos.cur;
}
if (o.horizontal) {
return nativeScrollElement.scrollLeft;
} else {
return nativeScrollElement.scrollTop;
}
};
self.getScrollSize = function () {
if (transform) {
return slideeSize;
}
if (o.horizontal) {
return nativeScrollElement.scrollWidth;
} else {
return nativeScrollElement.scrollHeight;
}
};
/**
* Initialize.
*
* @return {Object}
*/
self.init = function () {
if (self.initialized) {
return;
}
if (!transform) {
if (o.horizontal) {
if (layoutManager.desktop && !o.hideScrollbar) {
nativeScrollElement.classList.add('scrollX');
} else {
nativeScrollElement.classList.add('scrollX');
nativeScrollElement.classList.add('hiddenScrollX');
if (layoutManager.tv && o.allowNativeSmoothScroll !== false) {
nativeScrollElement.classList.add('smoothScrollX');
}
}
if (o.forceHideScrollbars) {
nativeScrollElement.classList.add('hiddenScrollX-forced');
}
} else {
if (layoutManager.desktop && !o.hideScrollbar) {
nativeScrollElement.classList.add('scrollY');
} else {
nativeScrollElement.classList.add('scrollY');
nativeScrollElement.classList.add('hiddenScrollY');
if (layoutManager.tv && o.allowNativeSmoothScroll !== false) {
nativeScrollElement.classList.add('smoothScrollY');
}
}
if (o.forceHideScrollbars) {
nativeScrollElement.classList.add('hiddenScrollY-forced');
}
}
} else {
frame.style.overflow = 'hidden';
slideeElement.style['will-change'] = 'transform';
slideeElement.style.transition = 'transform ' + o.speed + 'ms ease-out';
if (o.horizontal) {
slideeElement.classList.add('animatedScrollX');
} else {
slideeElement.classList.add('animatedScrollY');
}
}
if (transform || layoutManager.tv) {
// This can prevent others from being able to listen to mouse events
dom.addEventListener(dragSourceElement, 'mousedown', dragInitSlidee, {
//passive: true
});
}
initFrameResizeObserver();
if (transform) {
dom.addEventListener(dragSourceElement, 'touchstart', dragInitSlidee, {
passive: true
});
if (!o.horizontal) {
dom.addEventListener(frame, 'scroll', resetScroll, {
passive: true
});
}
if (o.mouseWheel) {
// Scrolling navigation
dom.addEventListener(scrollSource, wheelEvent, scrollHandler, {
passive: true
});
}
} else if (o.horizontal) {
// Don't bind to mouse events with vertical scroll since the mouse wheel can handle this natively
if (o.mouseWheel) {
// Scrolling navigation
dom.addEventListener(scrollSource, wheelEvent, scrollHandler, {
passive: true
});
}
}
dom.addEventListener(frame, 'click', onFrameClick, {
passive: true,
capture: true
});
// Mark instance as initialized
self.initialized = 1;
// Load
load(true);
// Return instance
return self;
};
};
/**
* Slide SLIDEE by amount of pixels.
*
* @param {Int} delta Pixels/Items. Positive means forward, negative means backward.
* @param {Bool} immediate Reposition immediately without an animation.
*
* @return {Void}
*/
scrollerFactory.prototype.slideBy = function (delta, immediate) {
if (!delta) {
return;
}
this.slideTo(this._pos.dest + delta, immediate);
};
/**
* Core method for handling `toLocation` methods.
*
* @param {String} location
* @param {Mixed} item
* @param {Bool} immediate
*
* @return {Void}
*/
scrollerFactory.prototype.to = function (location, item, immediate) {
// Optional arguments logic
if (type(item) === 'boolean') {
immediate = item;
item = undefined;
}
if (item === undefined) {
this.slideTo(this._pos[location], immediate);
} else {
//if (!transform) {
// item.scrollIntoView();
// return;
//}
var itemPos = this.getPos(item);
if (itemPos) {
this.slideTo(itemPos[location], immediate, itemPos);
}
}
};
/**
* Animate element or the whole SLIDEE to the start of the frame.
*
* @param {Mixed} item Item DOM element, or index starting at 0. Omitting will animate SLIDEE.
* @param {Bool} immediate Reposition immediately without an animation.
*
* @return {Void}
*/
scrollerFactory.prototype.toStart = function (item, immediate) {
this.to('start', item, immediate);
};
/**
* Animate element or the whole SLIDEE to the end of the frame.
*
* @param {Mixed} item Item DOM element, or index starting at 0. Omitting will animate SLIDEE.
* @param {Bool} immediate Reposition immediately without an animation.
*
* @return {Void}
*/
scrollerFactory.prototype.toEnd = function (item, immediate) {
this.to('end', item, immediate);
};
/**
* Animate element or the whole SLIDEE to the center of the frame.
*
* @param {Mixed} item Item DOM element, or index starting at 0. Omitting will animate SLIDEE.
* @param {Bool} immediate Reposition immediately without an animation.
*
* @return {Void}
*/
scrollerFactory.prototype.toCenter = function (item, immediate) {
this.to('center', item, immediate);
};
scrollerFactory.create = function (frame, options) {
var instance = new scrollerFactory(frame, options);
return Promise.resolve(instance);
};
return scrollerFactory;
});

View file

@ -0,0 +1,41 @@
define(['dom'], function (dom) {
'use strict';
/**
* Copyright 2012, Digital Fusion
* Licensed under the MIT license.
* http://teamdf.com/jquery-plugins/license/
*
* @author Sam Sehnert
* @desc A small plugin that checks whether elements are within
* the user visible viewport of a web browser.
* only accounts for vertical position, not horizontal.
*/
function visibleInViewport(elem, partial, thresholdX, thresholdY) {
thresholdX = thresholdX || 0;
thresholdY = thresholdY || 0;
if (!elem.getBoundingClientRect) {
return true;
}
var windowSize = dom.getWindowSize();
var vpWidth = windowSize.innerWidth;
var vpHeight = windowSize.innerHeight;
// Use this native browser method, if available.
var rec = elem.getBoundingClientRect();
var tViz = rec.top >= 0 && rec.top < vpHeight + thresholdY;
var bViz = rec.bottom > 0 && rec.bottom <= vpHeight + thresholdY;
var lViz = rec.left >= 0 && rec.left < vpWidth + thresholdX;
var rViz = rec.right > 0 && rec.right <= vpWidth + thresholdX;
var vVisible = partial ? tViz || bViz : tViz && bViz;
var hVisible = partial ? lViz || rViz : lViz && rViz;
return vVisible && hVisible;
}
return visibleInViewport;
});