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

WebOS 4 apparently has a ligature bug that prevents icons with an underscore from working as intended. This replaces them with either the corresponding CSS class or unicode escape code, depending on context. Refactoring is needed in order to be able to use CSS classes everywhere, so in the interest of time, this does the best available currently. Fixes #678
590 lines
19 KiB
JavaScript
590 lines
19 KiB
JavaScript
define(["browser", "appStorage", "apphost", "loading", "connectionManager", "globalize", "appRouter", "dom", "css!./multiselect"], function (browser, appStorage, appHost, loading, connectionManager, globalize, appRouter, dom) {
|
|
"use strict";
|
|
|
|
var selectedItems = [];
|
|
var selectedElements = [];
|
|
var currentSelectionCommandsPanel;
|
|
|
|
function hideSelections() {
|
|
|
|
var selectionCommandsPanel = currentSelectionCommandsPanel;
|
|
if (selectionCommandsPanel) {
|
|
|
|
selectionCommandsPanel.parentNode.removeChild(selectionCommandsPanel);
|
|
currentSelectionCommandsPanel = null;
|
|
|
|
selectedItems = [];
|
|
selectedElements = [];
|
|
var elems = document.querySelectorAll(".itemSelectionPanel");
|
|
for (var i = 0, length = elems.length; i < length; i++) {
|
|
|
|
var parent = elems[i].parentNode;
|
|
parent.removeChild(elems[i]);
|
|
parent.classList.remove("withMultiSelect");
|
|
}
|
|
}
|
|
}
|
|
|
|
function onItemSelectionPanelClick(e, itemSelectionPanel) {
|
|
|
|
// toggle the checkbox, if it wasn't clicked on
|
|
if (!dom.parentWithClass(e.target, "chkItemSelect")) {
|
|
var chkItemSelect = itemSelectionPanel.querySelector(".chkItemSelect");
|
|
|
|
if (chkItemSelect) {
|
|
|
|
if (chkItemSelect.classList.contains("checkedInitial")) {
|
|
chkItemSelect.classList.remove("checkedInitial");
|
|
} else {
|
|
var newValue = !chkItemSelect.checked;
|
|
chkItemSelect.checked = newValue;
|
|
updateItemSelection(chkItemSelect, newValue);
|
|
}
|
|
}
|
|
}
|
|
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
return false;
|
|
}
|
|
|
|
function updateItemSelection(chkItemSelect, selected) {
|
|
|
|
var id = dom.parentWithAttribute(chkItemSelect, "data-id").getAttribute("data-id");
|
|
|
|
if (selected) {
|
|
|
|
var current = selectedItems.filter(function (i) {
|
|
return i === id;
|
|
});
|
|
|
|
if (!current.length) {
|
|
selectedItems.push(id);
|
|
selectedElements.push(chkItemSelect);
|
|
}
|
|
|
|
} else {
|
|
selectedItems = selectedItems.filter(function (i) {
|
|
return i !== id;
|
|
});
|
|
selectedElements = selectedElements.filter(function (i) {
|
|
return i !== chkItemSelect;
|
|
});
|
|
}
|
|
|
|
if (selectedItems.length) {
|
|
var itemSelectionCount = document.querySelector(".itemSelectionCount");
|
|
if (itemSelectionCount) {
|
|
itemSelectionCount.innerHTML = selectedItems.length;
|
|
}
|
|
} else {
|
|
hideSelections();
|
|
}
|
|
}
|
|
|
|
function onSelectionChange(e) {
|
|
updateItemSelection(this, this.checked);
|
|
}
|
|
|
|
function showSelection(item, isChecked) {
|
|
|
|
var itemSelectionPanel = item.querySelector(".itemSelectionPanel");
|
|
|
|
if (!itemSelectionPanel) {
|
|
|
|
itemSelectionPanel = document.createElement("div");
|
|
itemSelectionPanel.classList.add("itemSelectionPanel");
|
|
|
|
var parent = item.querySelector(".cardBox") || item.querySelector(".cardContent");
|
|
parent.classList.add("withMultiSelect");
|
|
parent.appendChild(itemSelectionPanel);
|
|
|
|
var cssClass = "chkItemSelect";
|
|
if (isChecked && !browser.firefox) {
|
|
// In firefox, the initial tap hold doesnt' get treated as a click
|
|
// In other browsers it does, so we need to make sure that initial click is ignored
|
|
cssClass += " checkedInitial";
|
|
}
|
|
var checkedAttribute = isChecked ? " checked" : "";
|
|
itemSelectionPanel.innerHTML = '<label class="checkboxContainer"><input type="checkbox" is="emby-checkbox" data-outlineclass="multiSelectCheckboxOutline" class="' + cssClass + '"' + checkedAttribute + '/><span></span></label>';
|
|
var chkItemSelect = itemSelectionPanel.querySelector(".chkItemSelect");
|
|
chkItemSelect.addEventListener("change", onSelectionChange);
|
|
}
|
|
}
|
|
|
|
function showSelectionCommands() {
|
|
|
|
var selectionCommandsPanel = currentSelectionCommandsPanel;
|
|
|
|
if (!selectionCommandsPanel) {
|
|
|
|
selectionCommandsPanel = document.createElement("div");
|
|
selectionCommandsPanel.classList.add("selectionCommandsPanel");
|
|
|
|
document.body.appendChild(selectionCommandsPanel);
|
|
currentSelectionCommandsPanel = selectionCommandsPanel;
|
|
|
|
var html = "";
|
|
|
|
html += '<button is="paper-icon-button-light" class="btnCloseSelectionPanel autoSize"><i class="material-icons">close</i></button>';
|
|
html += '<h1 class="itemSelectionCount"></h1>';
|
|
|
|
var moreIcon = "";
|
|
html += '<button is="paper-icon-button-light" class="btnSelectionPanelOptions autoSize" style="margin-left:auto;"><i class="material-icons">' + moreIcon + '</i></button>';
|
|
|
|
selectionCommandsPanel.innerHTML = html;
|
|
|
|
selectionCommandsPanel.querySelector(".btnCloseSelectionPanel").addEventListener("click", hideSelections);
|
|
|
|
var btnSelectionPanelOptions = selectionCommandsPanel.querySelector(".btnSelectionPanelOptions");
|
|
|
|
dom.addEventListener(btnSelectionPanelOptions, "click", showMenuForSelectedItems, { passive: true });
|
|
}
|
|
}
|
|
|
|
function alertText(options) {
|
|
|
|
return new Promise(function (resolve, reject) {
|
|
|
|
require(["alert"], function (alert) {
|
|
alert(options).then(resolve, resolve);
|
|
});
|
|
});
|
|
}
|
|
|
|
function deleteItems(apiClient, itemIds) {
|
|
|
|
return new Promise(function (resolve, reject) {
|
|
|
|
var msg = globalize.translate("ConfirmDeleteItem");
|
|
var title = globalize.translate("HeaderDeleteItem");
|
|
|
|
if (itemIds.length > 1) {
|
|
msg = globalize.translate("ConfirmDeleteItems");
|
|
title = globalize.translate("HeaderDeleteItems");
|
|
}
|
|
|
|
require(["confirm"], function (confirm) {
|
|
|
|
confirm(msg, title).then(function () {
|
|
var promises = itemIds.map(function (itemId) {
|
|
apiClient.deleteItem(itemId);
|
|
});
|
|
|
|
Promise.all(promises).then(resolve, function () {
|
|
|
|
alertText(globalize.translate("ErrorDeletingItem")).then(reject, reject);
|
|
});
|
|
}, reject);
|
|
|
|
});
|
|
});
|
|
}
|
|
|
|
function showMenuForSelectedItems(e) {
|
|
|
|
var apiClient = connectionManager.currentApiClient();
|
|
|
|
apiClient.getCurrentUser().then(function (user) {
|
|
|
|
var menuItems = [];
|
|
|
|
menuItems.push({
|
|
name: globalize.translate("AddToCollection"),
|
|
id: "addtocollection",
|
|
icon: "add"
|
|
});
|
|
|
|
menuItems.push({
|
|
name: globalize.translate("AddToPlaylist"),
|
|
id: "playlist",
|
|
icon: "playlist_add"
|
|
});
|
|
|
|
// TODO: Be more dynamic based on what is selected
|
|
if (user.Policy.EnableContentDeletion) {
|
|
menuItems.push({
|
|
name: globalize.translate("Delete"),
|
|
id: "delete",
|
|
icon: "delete"
|
|
});
|
|
}
|
|
|
|
if (user.Policy.EnableContentDownloading && appHost.supports("filedownload")) {
|
|
menuItems.push({
|
|
name: Globalize.translate("ButtonDownload"),
|
|
id: "download",
|
|
icon: "file_download"
|
|
});
|
|
}
|
|
|
|
if (user.Policy.IsAdministrator) {
|
|
menuItems.push({
|
|
name: globalize.translate("GroupVersions"),
|
|
id: "groupvideos",
|
|
icon: "call_merge"
|
|
});
|
|
}
|
|
|
|
menuItems.push({
|
|
name: globalize.translate("MarkPlayed"),
|
|
id: "markplayed",
|
|
icon: "check_box"
|
|
});
|
|
|
|
menuItems.push({
|
|
name: globalize.translate("MarkUnplayed"),
|
|
id: "markunplayed",
|
|
icon: "check_box_outline_blank"
|
|
});
|
|
|
|
menuItems.push({
|
|
name: globalize.translate("RefreshMetadata"),
|
|
id: "refresh",
|
|
icon: "refresh"
|
|
});
|
|
|
|
require(['actionsheet'], function (actionsheet) {
|
|
actionsheet.show({
|
|
items: menuItems,
|
|
positionTo: e.target,
|
|
callback: function (id) {
|
|
var items = selectedItems.slice(0);
|
|
var serverId = apiClient.serverInfo().Id;
|
|
|
|
switch (id) {
|
|
case "addtocollection":
|
|
require(["collectionEditor"], function (collectionEditor) {
|
|
new collectionEditor().show({
|
|
items: items,
|
|
serverId: serverId
|
|
});
|
|
});
|
|
hideSelections();
|
|
dispatchNeedsRefresh();
|
|
break;
|
|
case "playlist":
|
|
require(["playlistEditor"], function (playlistEditor) {
|
|
new playlistEditor().show({
|
|
items: items,
|
|
serverId: serverId
|
|
});
|
|
});
|
|
hideSelections();
|
|
dispatchNeedsRefresh();
|
|
break;
|
|
case "delete":
|
|
deleteItems(apiClient, items).then(dispatchNeedsRefresh);
|
|
hideSelections();
|
|
dispatchNeedsRefresh();
|
|
break;
|
|
case "groupvideos":
|
|
combineVersions(apiClient, items);
|
|
break;
|
|
case "markplayed":
|
|
items.forEach(function (itemId) {
|
|
apiClient.markPlayed(apiClient.getCurrentUserId(), itemId);
|
|
});
|
|
hideSelections();
|
|
dispatchNeedsRefresh();
|
|
break;
|
|
case "markunplayed":
|
|
items.forEach(function (itemId) {
|
|
apiClient.markUnplayed(apiClient.getCurrentUserId(), itemId);
|
|
});
|
|
hideSelections();
|
|
dispatchNeedsRefresh();
|
|
break;
|
|
case "refresh":
|
|
require(["refreshDialog"], function (refreshDialog) {
|
|
new refreshDialog({
|
|
itemIds: items,
|
|
serverId: serverId
|
|
}).show();
|
|
});
|
|
hideSelections();
|
|
dispatchNeedsRefresh();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
|
|
});
|
|
});
|
|
}
|
|
|
|
function dispatchNeedsRefresh() {
|
|
|
|
var elems = [];
|
|
|
|
[].forEach.call(selectedElements, function (i) {
|
|
|
|
var container = dom.parentWithAttribute(i, "is", "emby-itemscontainer");
|
|
|
|
if (container && elems.indexOf(container) === -1) {
|
|
elems.push(container);
|
|
}
|
|
});
|
|
|
|
for (var i = 0, length = elems.length; i < length; i++) {
|
|
elems[i].notifyRefreshNeeded(true);
|
|
}
|
|
}
|
|
|
|
function combineVersions(apiClient, selection) {
|
|
|
|
if (selection.length < 2) {
|
|
|
|
require(["alert"], function (alert) {
|
|
alert({
|
|
text: globalize.translate("PleaseSelectTwoItems")
|
|
});
|
|
});
|
|
return;
|
|
}
|
|
|
|
loading.show();
|
|
|
|
apiClient.ajax({
|
|
|
|
type: "POST",
|
|
url: apiClient.getUrl("Videos/MergeVersions", { Ids: selection.join(",") })
|
|
|
|
}).then(function () {
|
|
|
|
loading.hide();
|
|
hideSelections();
|
|
dispatchNeedsRefresh();
|
|
});
|
|
}
|
|
|
|
function showSelections(initialCard) {
|
|
|
|
require(["emby-checkbox"], function () {
|
|
var cards = document.querySelectorAll(".card");
|
|
for (var i = 0, length = cards.length; i < length; i++) {
|
|
showSelection(cards[i], initialCard === cards[i]);
|
|
}
|
|
|
|
showSelectionCommands();
|
|
updateItemSelection(initialCard, true);
|
|
});
|
|
}
|
|
|
|
function onContainerClick(e) {
|
|
|
|
var target = e.target;
|
|
|
|
if (selectedItems.length) {
|
|
|
|
var card = dom.parentWithClass(target, "card");
|
|
if (card) {
|
|
var itemSelectionPanel = card.querySelector(".itemSelectionPanel");
|
|
if (itemSelectionPanel) {
|
|
return onItemSelectionPanelClick(e, itemSelectionPanel);
|
|
}
|
|
}
|
|
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
document.addEventListener("viewbeforehide", hideSelections);
|
|
|
|
return function (options) {
|
|
|
|
var self = this;
|
|
|
|
var container = options.container;
|
|
|
|
function onTapHold(e) {
|
|
|
|
var card = dom.parentWithClass(e.target, "card");
|
|
|
|
if (card) {
|
|
|
|
showSelections(card);
|
|
}
|
|
|
|
e.preventDefault();
|
|
// It won't have this if it's a hammer event
|
|
if (e.stopPropagation) {
|
|
e.stopPropagation();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function getTouches(e) {
|
|
|
|
return e.changedTouches || e.targetTouches || e.touches;
|
|
}
|
|
|
|
var touchTarget;
|
|
var touchStartTimeout;
|
|
var touchStartX;
|
|
var touchStartY;
|
|
function onTouchStart(e) {
|
|
|
|
var touch = getTouches(e)[0];
|
|
touchTarget = null;
|
|
touchStartX = 0;
|
|
touchStartY = 0;
|
|
|
|
if (touch) {
|
|
touchStartX = touch.clientX;
|
|
touchStartY = touch.clientY;
|
|
var element = touch.target;
|
|
|
|
if (element) {
|
|
var card = dom.parentWithClass(element, "card");
|
|
|
|
if (card) {
|
|
|
|
if (touchStartTimeout) {
|
|
clearTimeout(touchStartTimeout);
|
|
touchStartTimeout = null;
|
|
}
|
|
|
|
touchTarget = card;
|
|
touchStartTimeout = setTimeout(onTouchStartTimerFired, 550);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function onTouchMove(e) {
|
|
|
|
if (touchTarget) {
|
|
var touch = getTouches(e)[0];
|
|
var deltaX;
|
|
var deltaY;
|
|
|
|
if (touch) {
|
|
var touchEndX = touch.clientX || 0;
|
|
var touchEndY = touch.clientY || 0;
|
|
deltaX = Math.abs(touchEndX - (touchStartX || 0));
|
|
deltaY = Math.abs(touchEndY - (touchStartY || 0));
|
|
} else {
|
|
deltaX = 100;
|
|
deltaY = 100;
|
|
}
|
|
if (deltaX >= 5 || deltaY >= 5) {
|
|
onMouseOut(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
function onTouchEnd(e) {
|
|
|
|
onMouseOut(e);
|
|
}
|
|
|
|
function onMouseDown(e) {
|
|
|
|
if (touchStartTimeout) {
|
|
clearTimeout(touchStartTimeout);
|
|
touchStartTimeout = null;
|
|
}
|
|
|
|
touchTarget = e.target;
|
|
touchStartTimeout = setTimeout(onTouchStartTimerFired, 550);
|
|
}
|
|
|
|
function onMouseOut(e) {
|
|
|
|
if (touchStartTimeout) {
|
|
clearTimeout(touchStartTimeout);
|
|
touchStartTimeout = null;
|
|
}
|
|
touchTarget = null;
|
|
}
|
|
|
|
function onTouchStartTimerFired() {
|
|
|
|
if (!touchTarget) {
|
|
return;
|
|
}
|
|
|
|
var card = dom.parentWithClass(touchTarget, "card");
|
|
touchTarget = null;
|
|
|
|
if (card) {
|
|
|
|
showSelections(card);
|
|
}
|
|
}
|
|
|
|
function initTapHold(element) {
|
|
|
|
// mobile safari doesn't allow contextmenu override
|
|
if (browser.touch && !browser.safari) {
|
|
element.addEventListener("contextmenu", onTapHold);
|
|
} else {
|
|
dom.addEventListener(element, "touchstart", onTouchStart, {
|
|
passive: true
|
|
});
|
|
dom.addEventListener(element, "touchmove", onTouchMove, {
|
|
passive: true
|
|
});
|
|
dom.addEventListener(element, "touchend", onTouchEnd, {
|
|
passive: true
|
|
});
|
|
dom.addEventListener(element, "touchcancel", onTouchEnd, {
|
|
passive: true
|
|
});
|
|
dom.addEventListener(element, "mousedown", onMouseDown, {
|
|
passive: true
|
|
});
|
|
dom.addEventListener(element, "mouseleave", onMouseOut, {
|
|
passive: true
|
|
});
|
|
dom.addEventListener(element, "mouseup", onMouseOut, {
|
|
passive: true
|
|
});
|
|
}
|
|
}
|
|
|
|
initTapHold(container);
|
|
|
|
if (options.bindOnClick !== false) {
|
|
container.addEventListener("click", onContainerClick);
|
|
}
|
|
|
|
self.onContainerClick = onContainerClick;
|
|
|
|
self.destroy = function () {
|
|
|
|
container.removeEventListener("click", onContainerClick);
|
|
container.removeEventListener("contextmenu", onTapHold);
|
|
|
|
var element = container;
|
|
|
|
dom.removeEventListener(element, "touchstart", onTouchStart, {
|
|
passive: true
|
|
});
|
|
dom.removeEventListener(element, "touchmove", onTouchMove, {
|
|
passive: true
|
|
});
|
|
dom.removeEventListener(element, "touchend", onTouchEnd, {
|
|
passive: true
|
|
});
|
|
// this fires in safari due to magnifying class
|
|
//dom.removeEventListener(element, "touchcancel", onTouchEnd, {
|
|
// passive: true
|
|
//});
|
|
dom.removeEventListener(element, "mousedown", onMouseDown, {
|
|
passive: true
|
|
});
|
|
dom.removeEventListener(element, "mouseleave", onMouseOut, {
|
|
passive: true
|
|
});
|
|
dom.removeEventListener(element, "mouseup", onMouseOut, {
|
|
passive: true
|
|
});
|
|
};
|
|
};
|
|
});
|