import browser from 'browser'; import appHost from 'apphost'; import loading from 'loading'; import connectionManager from 'connectionManager'; import globalize from 'globalize'; import dom from 'dom'; import 'css!./multiSelect'; /* eslint-disable indent */ let selectedItems = []; let selectedElements = []; let currentSelectionCommandsPanel; function hideSelections() { const selectionCommandsPanel = currentSelectionCommandsPanel; if (selectionCommandsPanel) { selectionCommandsPanel.parentNode.removeChild(selectionCommandsPanel); currentSelectionCommandsPanel = null; selectedItems = []; selectedElements = []; const elems = document.querySelectorAll('.itemSelectionPanel'); for (let i = 0, length = elems.length; i < length; i++) { const 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')) { const chkItemSelect = itemSelectionPanel.querySelector('.chkItemSelect'); if (chkItemSelect) { if (chkItemSelect.classList.contains('checkedInitial')) { chkItemSelect.classList.remove('checkedInitial'); } else { const newValue = !chkItemSelect.checked; chkItemSelect.checked = newValue; updateItemSelection(chkItemSelect, newValue); } } } e.preventDefault(); e.stopPropagation(); return false; } function updateItemSelection(chkItemSelect, selected) { const id = dom.parentWithAttribute(chkItemSelect, 'data-id').getAttribute('data-id'); if (selected) { const current = selectedItems.filter(i => { return i === id; }); if (!current.length) { selectedItems.push(id); selectedElements.push(chkItemSelect); } } else { selectedItems = selectedItems.filter(i => { return i !== id; }); selectedElements = selectedElements.filter(i => { return i !== chkItemSelect; }); } if (selectedItems.length) { const itemSelectionCount = document.querySelector('.itemSelectionCount'); if (itemSelectionCount) { itemSelectionCount.innerHTML = selectedItems.length; } } else { hideSelections(); } } function onSelectionChange(e) { updateItemSelection(this, this.checked); } function showSelection(item, isChecked) { let itemSelectionPanel = item.querySelector('.itemSelectionPanel'); if (!itemSelectionPanel) { itemSelectionPanel = document.createElement('div'); itemSelectionPanel.classList.add('itemSelectionPanel'); const parent = item.querySelector('.cardBox') || item.querySelector('.cardContent'); parent.classList.add('withMultiSelect'); parent.appendChild(itemSelectionPanel); let 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'; } const checkedAttribute = isChecked ? ' checked' : ''; itemSelectionPanel.innerHTML = ``; const chkItemSelect = itemSelectionPanel.querySelector('.chkItemSelect'); chkItemSelect.addEventListener('change', onSelectionChange); } } function showSelectionCommands() { let selectionCommandsPanel = currentSelectionCommandsPanel; if (!selectionCommandsPanel) { selectionCommandsPanel = document.createElement('div'); selectionCommandsPanel.classList.add('selectionCommandsPanel'); document.body.appendChild(selectionCommandsPanel); currentSelectionCommandsPanel = selectionCommandsPanel; let html = ''; html += ''; html += '

'; const moreIcon = 'more_vert'; html += ``; selectionCommandsPanel.innerHTML = html; selectionCommandsPanel.querySelector('.btnCloseSelectionPanel').addEventListener('click', hideSelections); const btnSelectionPanelOptions = selectionCommandsPanel.querySelector('.btnSelectionPanelOptions'); dom.addEventListener(btnSelectionPanelOptions, 'click', showMenuForSelectedItems, { passive: true }); } } function alertText(options) { return new Promise((resolve, reject) => { import('alert').then(({default: alert}) => { alert(options).then(resolve, resolve); }); }); } function deleteItems(apiClient, itemIds) { return new Promise((resolve, reject) => { let msg = globalize.translate('ConfirmDeleteItem'); let title = globalize.translate('HeaderDeleteItem'); if (itemIds.length > 1) { msg = globalize.translate('ConfirmDeleteItems'); title = globalize.translate('HeaderDeleteItems'); } import('confirm').then(({default: confirm}) => { confirm(msg, title).then(() => { const promises = itemIds.map(itemId => { apiClient.deleteItem(itemId); }); Promise.all(promises).then(resolve, () => { alertText(globalize.translate('ErrorDeletingItem')).then(reject, reject); }); }, reject); }); }); } function showMenuForSelectedItems(e) { const apiClient = connectionManager.currentApiClient(); apiClient.getCurrentUser().then(user => { const 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.default.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' }); import('actionsheet').then(({default: actionsheet}) => { actionsheet.show({ items: menuItems, positionTo: e.target, callback: function (id) { const items = selectedItems.slice(0); const serverId = apiClient.serverInfo().Id; switch (id) { case 'addtocollection': import('collectionEditor').then(({default: collectionEditor}) => { new collectionEditor({ items: items, serverId: serverId }); }); hideSelections(); dispatchNeedsRefresh(); break; case 'playlist': import('playlistEditor').then(({default: playlistEditor}) => { new playlistEditor({ 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(itemId => { apiClient.markPlayed(apiClient.getCurrentUserId(), itemId); }); hideSelections(); dispatchNeedsRefresh(); break; case 'markunplayed': items.forEach(itemId => { apiClient.markUnplayed(apiClient.getCurrentUserId(), itemId); }); hideSelections(); dispatchNeedsRefresh(); break; case 'refresh': import('refreshDialog').then(({default: refreshDialog}) => { new refreshDialog({ itemIds: items, serverId: serverId }).show(); }); hideSelections(); dispatchNeedsRefresh(); break; default: break; } } }); }); }); } function dispatchNeedsRefresh() { const elems = []; [].forEach.call(selectedElements, i => { const container = dom.parentWithAttribute(i, 'is', 'emby-itemscontainer'); if (container && !elems.includes(container)) { elems.push(container); } }); for (let i = 0, length = elems.length; i < length; i++) { elems[i].notifyRefreshNeeded(true); } } function combineVersions(apiClient, selection) { if (selection.length < 2) { import('alert').then(({default: alert}) => { alert({ text: globalize.translate('PleaseSelectTwoItems') }); }); return; } loading.show(); apiClient.ajax({ type: 'POST', url: apiClient.getUrl('Videos/MergeVersions', { Ids: selection.join(',') }) }).then(() => { loading.hide(); hideSelections(); dispatchNeedsRefresh(); }); } function showSelections(initialCard) { import('emby-checkbox').then(() => { const cards = document.querySelectorAll('.card'); for (let i = 0, length = cards.length; i < length; i++) { showSelection(cards[i], initialCard === cards[i]); } showSelectionCommands(); updateItemSelection(initialCard, true); }); } function onContainerClick(e) { const target = e.target; if (selectedItems.length) { const card = dom.parentWithClass(target, 'card'); if (card) { const itemSelectionPanel = card.querySelector('.itemSelectionPanel'); if (itemSelectionPanel) { return onItemSelectionPanelClick(e, itemSelectionPanel); } } e.preventDefault(); e.stopPropagation(); return false; } } document.addEventListener('viewbeforehide', hideSelections); export default function (options) { const self = this; const container = options.container; function onTapHold(e) { const 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; } let touchTarget; let touchStartTimeout; let touchStartX; let touchStartY; function onTouchStart(e) { const touch = getTouches(e)[0]; touchTarget = null; touchStartX = 0; touchStartY = 0; if (touch) { touchStartX = touch.clientX; touchStartY = touch.clientY; const element = touch.target; if (element) { const card = dom.parentWithClass(element, 'card'); if (card) { if (touchStartTimeout) { clearTimeout(touchStartTimeout); touchStartTimeout = null; } touchTarget = card; touchStartTimeout = setTimeout(onTouchStartTimerFired, 550); } } } } function onTouchMove(e) { if (touchTarget) { const touch = getTouches(e)[0]; let deltaX; let deltaY; if (touch) { const touchEndX = touch.clientX || 0; const 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; } const 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 = () => { container.removeEventListener('click', onContainerClick); container.removeEventListener('contextmenu', onTapHold); const element = container; dom.removeEventListener(element, 'touchstart', onTouchStart, { passive: true }); dom.removeEventListener(element, 'touchmove', onTouchMove, { passive: true }); dom.removeEventListener(element, 'touchend', onTouchEnd, { passive: true }); dom.removeEventListener(element, 'mousedown', onMouseDown, { passive: true }); dom.removeEventListener(element, 'mouseleave', onMouseOut, { passive: true }); dom.removeEventListener(element, 'mouseup', onMouseOut, { passive: true }); }; } /* eslint-enable indent */