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

481 lines
14 KiB
JavaScript
Raw Normal View History

2020-08-14 08:46:34 +02:00
import itemShortcuts from '../../components/shortcuts';
import inputManager from '../../scripts/inputManager';
2020-08-16 20:24:45 +02:00
import { playbackManager } from '../../components/playback/playbackmanager';
2020-08-14 08:46:34 +02:00
import imageLoader from '../../components/images/imageLoader';
import layoutManager from '../../components/layoutManager';
import browser from '../../scripts/browser';
import dom from '../../scripts/dom';
import loading from '../../components/loading/loading';
import focusManager from '../../components/focusManager';
import serverNotifications from '../../scripts/serverNotifications';
import Events from '../../utils/events.ts';
2020-09-08 02:43:56 -04:00
import 'webcomponents.js/webcomponents-lite';
import ServerConnections from '../../components/ServerConnections';
2020-11-08 12:37:53 +00:00
import Sortable from 'sortablejs';
2023-04-19 01:56:05 -04:00
const ItemsContainerPrototype = Object.create(HTMLDivElement.prototype);
2023-04-19 01:56:05 -04:00
function onClick(e) {
const itemsContainer = this;
const multiSelect = itemsContainer.multiSelect;
2018-10-23 01:05:09 +03:00
2023-04-19 01:56:05 -04:00
if (multiSelect?.onContainerClick.call(itemsContainer, e) === false) {
return;
}
2023-04-19 01:56:05 -04:00
itemShortcuts.onClick.call(itemsContainer, e);
}
2023-04-19 01:56:05 -04:00
function disableEvent(e) {
e.preventDefault();
e.stopPropagation();
return false;
}
function onContextMenu(e) {
const target = e.target;
const card = dom.parentWithAttribute(target, 'data-id');
// check for serverId, it won't be present on selectserver
2023-07-06 13:39:48 -04:00
if (card?.getAttribute('data-serverid')) {
2023-04-19 01:56:05 -04:00
inputManager.handleCommand('menu', {
sourceElement: card
});
2018-10-23 01:05:09 +03:00
e.preventDefault();
e.stopPropagation();
return false;
2018-10-23 01:05:09 +03:00
}
2023-04-19 01:56:05 -04:00
}
2018-10-23 01:05:09 +03:00
2023-04-19 01:56:05 -04:00
function getShortcutOptions() {
return {
click: false
};
}
2023-04-19 01:56:05 -04:00
ItemsContainerPrototype.enableMultiSelect = function (enabled) {
const current = this.multiSelect;
2023-04-19 01:56:05 -04:00
if (!enabled) {
if (current) {
current.destroy();
this.multiSelect = null;
}
2023-04-19 01:56:05 -04:00
return;
2018-10-23 01:05:09 +03:00
}
2023-04-19 01:56:05 -04:00
if (current) {
return;
2018-10-23 01:05:09 +03:00
}
2023-04-19 01:56:05 -04:00
const self = this;
import('../../components/multiSelect/multiSelect').then(({ default: MultiSelect }) => {
self.multiSelect = new MultiSelect({
container: self,
bindOnClick: false
});
});
};
function onDrop(evt, itemsContainer) {
const el = evt.item;
const newIndex = evt.newIndex;
const itemId = el.getAttribute('data-playlistitemid');
const playlistId = el.getAttribute('data-playlistid');
if (!playlistId) {
const oldIndex = evt.oldIndex;
el.dispatchEvent(new CustomEvent('itemdrop', {
detail: {
oldIndex: oldIndex,
newIndex: newIndex,
playlistItemId: itemId
},
bubbles: true,
cancelable: false
}));
return;
}
2023-04-19 01:56:05 -04:00
const serverId = el.getAttribute('data-serverid');
const apiClient = ServerConnections.getApiClient(serverId);
2023-04-19 01:56:05 -04:00
loading.show();
2023-04-19 01:56:05 -04:00
apiClient.ajax({
url: apiClient.getUrl('Playlists/' + playlistId + '/Items/' + itemId + '/Move/' + newIndex),
type: 'POST'
}).then(function () {
loading.hide();
}, function () {
loading.hide();
itemsContainer.refreshItems();
});
}
2023-04-19 01:56:05 -04:00
ItemsContainerPrototype.enableDragReordering = function (enabled) {
const current = this.sortable;
if (!enabled) {
if (current) {
current.destroy();
this.sortable = null;
}
2023-04-19 01:56:05 -04:00
return;
}
2023-04-19 01:56:05 -04:00
if (current) {
return;
2018-10-23 01:05:09 +03:00
}
2023-04-19 01:56:05 -04:00
const self = this;
self.sortable = new Sortable(self, {
draggable: '.listItem',
handle: '.listViewDragHandle',
2023-04-19 01:56:05 -04:00
// dragging ended
onEnd: function (evt) {
return onDrop(evt, self);
}
2023-04-19 01:56:05 -04:00
});
};
2023-04-19 01:56:05 -04:00
function onUserDataChanged(e, apiClient, userData) {
const itemsContainer = this;
2023-04-19 01:56:05 -04:00
import('../../components/cardbuilder/cardBuilder').then((cardBuilder) => {
cardBuilder.onUserDataChanged(userData, itemsContainer);
});
2023-04-19 01:56:05 -04:00
const eventsToMonitor = getEventsToMonitor(itemsContainer);
2023-04-19 01:56:05 -04:00
// TODO: Check user data change reason?
if (eventsToMonitor.indexOf('markfavorite') !== -1
2022-10-04 17:31:48 -04:00
|| eventsToMonitor.indexOf('markplayed') !== -1
2023-04-19 01:56:05 -04:00
) {
itemsContainer.notifyRefreshNeeded();
2018-10-23 01:05:09 +03:00
}
2023-04-19 01:56:05 -04:00
}
2018-10-23 01:05:09 +03:00
2023-04-19 01:56:05 -04:00
function getEventsToMonitor(itemsContainer) {
const monitor = itemsContainer.getAttribute('data-monitor');
if (monitor) {
return monitor.split(',');
2018-10-23 01:05:09 +03:00
}
2023-04-19 01:56:05 -04:00
return [];
}
2023-04-19 01:56:05 -04:00
function onTimerCreated(e, apiClient, data) {
const itemsContainer = this;
2023-04-19 01:56:05 -04:00
if (getEventsToMonitor(itemsContainer).indexOf('timers') !== -1) {
itemsContainer.notifyRefreshNeeded();
return;
}
2023-04-19 01:56:05 -04:00
const programId = data.ProgramId;
// This could be null, not supported by all tv providers
const newTimerId = data.Id;
import('../../components/cardbuilder/cardBuilder').then((cardBuilder) => {
cardBuilder.onTimerCreated(programId, newTimerId, itemsContainer);
});
}
function onSeriesTimerCreated() {
const itemsContainer = this;
if (getEventsToMonitor(itemsContainer).indexOf('seriestimers') !== -1) {
itemsContainer.notifyRefreshNeeded();
2018-10-23 01:05:09 +03:00
}
2023-04-19 01:56:05 -04:00
}
2018-10-23 01:05:09 +03:00
2023-04-19 01:56:05 -04:00
function onTimerCancelled(e, apiClient, data) {
const itemsContainer = this;
if (getEventsToMonitor(itemsContainer).indexOf('timers') !== -1) {
itemsContainer.notifyRefreshNeeded();
return;
2018-10-23 01:05:09 +03:00
}
2023-04-19 01:56:05 -04:00
import('../../components/cardbuilder/cardBuilder').then((cardBuilder) => {
cardBuilder.onTimerCancelled(data.Id, itemsContainer);
});
}
2023-04-19 01:56:05 -04:00
function onSeriesTimerCancelled(e, apiClient, data) {
const itemsContainer = this;
if (getEventsToMonitor(itemsContainer).indexOf('seriestimers') !== -1) {
itemsContainer.notifyRefreshNeeded();
return;
2018-10-23 01:05:09 +03:00
}
2023-04-19 01:56:05 -04:00
import('../../components/cardbuilder/cardBuilder').then((cardBuilder) => {
cardBuilder.onSeriesTimerCancelled(data.Id, itemsContainer);
});
}
2023-04-19 01:56:05 -04:00
function onLibraryChanged(e, apiClient, data) {
const itemsContainer = this;
const eventsToMonitor = getEventsToMonitor(itemsContainer);
if (eventsToMonitor.indexOf('seriestimers') !== -1 || eventsToMonitor.indexOf('timers') !== -1) {
// yes this is an assumption
return;
2018-10-23 01:05:09 +03:00
}
2023-04-19 01:56:05 -04:00
const itemsAdded = data.ItemsAdded || [];
const itemsRemoved = data.ItemsRemoved || [];
if (!itemsAdded.length && !itemsRemoved.length) {
return;
}
2019-05-23 03:02:18 -07:00
2023-04-19 01:56:05 -04:00
const parentId = itemsContainer.getAttribute('data-parentid');
if (parentId) {
const foldersAddedTo = data.FoldersAddedTo || [];
const foldersRemovedFrom = data.FoldersRemovedFrom || [];
const collectionFolders = data.CollectionFolders || [];
2023-04-19 01:56:05 -04:00
if (foldersAddedTo.indexOf(parentId) === -1 && foldersRemovedFrom.indexOf(parentId) === -1 && collectionFolders.indexOf(parentId) === -1) {
return;
}
2018-10-23 01:05:09 +03:00
}
2023-04-19 01:56:05 -04:00
itemsContainer.notifyRefreshNeeded();
}
2023-04-19 01:56:05 -04:00
function onPlaybackStopped(e, stopInfo) {
const itemsContainer = this;
const state = stopInfo.state;
const eventsToMonitor = getEventsToMonitor(itemsContainer);
if (state.NowPlayingItem && state.NowPlayingItem.MediaType === 'Video') {
if (eventsToMonitor.indexOf('videoplayback') !== -1) {
2022-10-03 14:22:02 -04:00
itemsContainer.notifyRefreshNeeded(true);
return;
}
2023-04-19 01:56:05 -04:00
} else if (state.NowPlayingItem?.MediaType === 'Audio' && eventsToMonitor.indexOf('audioplayback') !== -1) {
itemsContainer.notifyRefreshNeeded(true);
return;
2018-10-23 01:05:09 +03:00
}
2023-04-19 01:56:05 -04:00
}
function addNotificationEvent(instance, name, handler, owner) {
const localHandler = handler.bind(instance);
owner = owner || serverNotifications;
Events.on(owner, name, localHandler);
instance['event_' + name] = localHandler;
}
function removeNotificationEvent(instance, name, owner) {
const handler = instance['event_' + name];
if (handler) {
owner = owner || serverNotifications;
2023-04-19 01:56:05 -04:00
Events.off(owner, name, handler);
instance['event_' + name] = null;
2018-10-23 01:05:09 +03:00
}
2023-04-19 01:56:05 -04:00
}
2018-10-23 01:05:09 +03:00
2023-04-19 01:56:05 -04:00
ItemsContainerPrototype.createdCallback = function () {
this.classList.add('itemsContainer');
};
2023-04-19 01:56:05 -04:00
ItemsContainerPrototype.attachedCallback = function () {
this.addEventListener('click', onClick);
2023-04-19 01:56:05 -04:00
if (browser.touch) {
this.addEventListener('contextmenu', disableEvent);
} else {
if (this.getAttribute('data-contextmenu') !== 'false') {
this.addEventListener('contextmenu', onContextMenu);
}
2023-04-19 01:56:05 -04:00
}
2023-04-19 01:56:05 -04:00
if (layoutManager.desktop || layoutManager.mobile && this.getAttribute('data-multiselect') !== 'false') {
this.enableMultiSelect(true);
}
2023-04-19 01:56:05 -04:00
if (layoutManager.tv) {
this.classList.add('itemsContainer-tv');
}
2023-04-19 01:56:05 -04:00
itemShortcuts.on(this, getShortcutOptions());
2023-04-19 01:56:05 -04:00
addNotificationEvent(this, 'UserDataChanged', onUserDataChanged);
addNotificationEvent(this, 'TimerCreated', onTimerCreated);
addNotificationEvent(this, 'SeriesTimerCreated', onSeriesTimerCreated);
addNotificationEvent(this, 'TimerCancelled', onTimerCancelled);
addNotificationEvent(this, 'SeriesTimerCancelled', onSeriesTimerCancelled);
addNotificationEvent(this, 'LibraryChanged', onLibraryChanged);
addNotificationEvent(this, 'playbackstop', onPlaybackStopped, playbackManager);
2023-04-19 01:56:05 -04:00
if (this.getAttribute('data-dragreorder') === 'true') {
this.enableDragReordering(true);
}
};
ItemsContainerPrototype.detachedCallback = function () {
clearRefreshInterval(this);
this.enableMultiSelect(false);
this.enableDragReordering(false);
this.removeEventListener('click', onClick);
this.removeEventListener('contextmenu', onContextMenu);
this.removeEventListener('contextmenu', disableEvent);
itemShortcuts.off(this, getShortcutOptions());
removeNotificationEvent(this, 'UserDataChanged');
removeNotificationEvent(this, 'TimerCreated');
removeNotificationEvent(this, 'SeriesTimerCreated');
removeNotificationEvent(this, 'TimerCancelled');
removeNotificationEvent(this, 'SeriesTimerCancelled');
removeNotificationEvent(this, 'LibraryChanged');
removeNotificationEvent(this, 'playbackstop', playbackManager);
this.fetchData = null;
this.getItemsHtml = null;
this.parentContainer = null;
};
ItemsContainerPrototype.pause = function () {
clearRefreshInterval(this, true);
this.paused = true;
};
ItemsContainerPrototype.resume = function (options) {
this.paused = false;
const refreshIntervalEndTime = this.refreshIntervalEndTime;
if (refreshIntervalEndTime) {
const remainingMs = refreshIntervalEndTime - new Date().getTime();
if (remainingMs > 0 && !this.needsRefresh) {
resetRefreshInterval(this, remainingMs);
} else {
this.needsRefresh = true;
this.refreshIntervalEndTime = null;
}
2023-04-19 01:56:05 -04:00
}
2023-07-06 13:39:48 -04:00
if (this.needsRefresh || (options?.refresh)) {
2023-04-19 01:56:05 -04:00
return this.refreshItems();
}
2023-04-19 01:56:05 -04:00
return Promise.resolve();
};
2023-04-19 01:56:05 -04:00
ItemsContainerPrototype.refreshItems = function () {
if (!this.fetchData) {
return Promise.resolve();
2023-04-19 01:56:05 -04:00
}
2023-04-19 01:56:05 -04:00
if (this.paused) {
this.needsRefresh = true;
return Promise.resolve();
}
2023-04-19 01:56:05 -04:00
this.needsRefresh = false;
2023-04-19 01:56:05 -04:00
return this.fetchData().then(onDataFetched.bind(this));
};
2023-04-19 01:56:05 -04:00
ItemsContainerPrototype.notifyRefreshNeeded = function (isInForeground) {
if (this.paused) {
this.needsRefresh = true;
return;
}
2023-04-19 01:56:05 -04:00
const timeout = this.refreshTimeout;
if (timeout) {
clearTimeout(timeout);
}
2023-04-19 01:56:05 -04:00
if (isInForeground === true) {
this.refreshItems();
} else {
this.refreshTimeout = setTimeout(this.refreshItems.bind(this), 10000);
}
};
2023-04-19 01:56:05 -04:00
function clearRefreshInterval(itemsContainer, isPausing) {
if (itemsContainer.refreshInterval) {
clearInterval(itemsContainer.refreshInterval);
itemsContainer.refreshInterval = null;
2023-04-19 01:56:05 -04:00
if (!isPausing) {
itemsContainer.refreshIntervalEndTime = null;
}
2018-10-23 01:05:09 +03:00
}
2023-04-19 01:56:05 -04:00
}
2018-10-23 01:05:09 +03:00
2023-04-19 01:56:05 -04:00
function resetRefreshInterval(itemsContainer, intervalMs) {
clearRefreshInterval(itemsContainer);
2023-04-19 01:56:05 -04:00
if (!intervalMs) {
intervalMs = parseInt(itemsContainer.getAttribute('data-refreshinterval') || '0', 10);
}
2023-04-19 01:56:05 -04:00
if (intervalMs) {
itemsContainer.refreshInterval = setInterval(itemsContainer.notifyRefreshNeeded.bind(itemsContainer), intervalMs);
itemsContainer.refreshIntervalEndTime = new Date().getTime() + intervalMs;
2018-10-23 01:05:09 +03:00
}
2023-04-19 01:56:05 -04:00
}
2018-10-23 01:05:09 +03:00
2023-04-19 01:56:05 -04:00
function onDataFetched(result) {
const items = result.Items || result;
2023-04-19 01:56:05 -04:00
const parentContainer = this.parentContainer;
if (parentContainer) {
if (items.length) {
parentContainer.classList.remove('hide');
} else {
parentContainer.classList.add('hide');
}
2023-04-19 01:56:05 -04:00
}
2023-04-19 01:56:05 -04:00
const activeElement = document.activeElement;
let focusId;
let hasActiveElement;
2023-04-19 01:56:05 -04:00
if (this.contains(activeElement)) {
hasActiveElement = true;
focusId = activeElement.getAttribute('data-id');
}
2023-04-19 01:56:05 -04:00
this.innerHTML = this.getItemsHtml(items);
2023-04-19 01:56:05 -04:00
imageLoader.lazyChildren(this);
2023-04-19 01:56:05 -04:00
if (hasActiveElement) {
setFocus(this, focusId);
}
2023-04-19 01:56:05 -04:00
resetRefreshInterval(this);
2023-04-19 01:56:05 -04:00
if (this.afterRefresh) {
this.afterRefresh(result);
2018-10-23 01:05:09 +03:00
}
2023-04-19 01:56:05 -04:00
}
function setFocus(itemsContainer, focusId) {
if (focusId) {
const newElement = itemsContainer.querySelector('[data-id="' + focusId + '"]');
if (newElement) {
try {
focusManager.focus(newElement);
return;
} catch (err) {
console.error(err);
}
2018-10-23 01:05:09 +03:00
}
}
2023-04-19 01:56:05 -04:00
focusManager.autoFocus(itemsContainer);
}
document.registerElement('emby-itemscontainer', {
prototype: ItemsContainerPrototype,
extends: 'div'
});