diff --git a/src/components/itemContextMenu.js b/src/components/itemContextMenu.js index fd7d82b42e..263a557164 100644 --- a/src/components/itemContextMenu.js +++ b/src/components/itemContextMenu.js @@ -280,11 +280,11 @@ export function getCommands(options) { }); } - if (item.PlaylistItemId && options.playlistId) { + if (item.PlaylistItemId && options.playlistId && options.canEditPlaylist) { commands.push({ name: globalize.translate('RemoveFromPlaylist'), id: 'removefromplaylist', - icon: 'remove' + icon: 'playlist_remove' }); } @@ -292,7 +292,7 @@ export function getCommands(options) { commands.push({ name: globalize.translate('RemoveFromCollection'), id: 'removefromcollection', - icon: 'remove' + icon: 'playlist_remove' }); } @@ -696,6 +696,6 @@ export function show(options) { } export default { - getCommands: getCommands, - show: show + getCommands, + show }; diff --git a/src/components/playlisteditor/playlisteditor.ts b/src/components/playlisteditor/playlisteditor.ts index 5d15a39095..46100472c2 100644 --- a/src/components/playlisteditor/playlisteditor.ts +++ b/src/components/playlisteditor/playlisteditor.ts @@ -170,8 +170,9 @@ function populatePlaylists(editorOptions: PlaylistEditorOptions, panel: DialogEl ...playlist, permissions })) - .catch((err) => { - console.warn('[PlaylistEditor] Failed to fetch playlist permissions', err); + .catch(err => { + // If a user doesn't have access, then the request will 404 and throw + console.info('[PlaylistEditor] Failed to fetch playlist permissions', err); return playlist; }); @@ -231,7 +232,7 @@ function getEditorHtml(items: string[]) { html += `
diff --git a/src/components/shortcuts.js b/src/components/shortcuts.js index cdf51b4661..91f96f9d9b 100644 --- a/src/components/shortcuts.js +++ b/src/components/shortcuts.js @@ -2,6 +2,7 @@ * Module shortcuts. * @module components/shortcuts */ +import { getPlaylistsApi } from '@jellyfin/sdk/lib/utils/api/playlists-api'; import { playbackManager } from './playback/playbackmanager'; import inputManager from '../scripts/inputManager'; @@ -12,6 +13,7 @@ import recordingHelper from './recordingcreator/recordinghelper'; import ServerConnections from './ServerConnections'; import toast from './toast/toast'; import * as userSettings from '../scripts/settings/userSettings'; +import { toApi } from 'utils/jellyfin-apiclient/compat'; function playAllFromHere(card, serverId, queue) { const parent = card.parentNode; @@ -100,7 +102,7 @@ function notifyRefreshNeeded(childElement, itemsContainer) { } } -function showContextMenu(card, options) { +function showContextMenu(card, options = {}) { getItem(card).then(item => { const playlistId = card.getAttribute('data-playlistid'); const collectionId = card.getAttribute('data-collectionid'); @@ -110,28 +112,56 @@ function showContextMenu(card, options) { item.PlaylistItemId = elem ? elem.getAttribute('data-playlistitemid') : null; } - import('./itemContextMenu').then((itemContextMenu) => { - ServerConnections.getApiClient(item.ServerId).getCurrentUser().then(user => { - itemContextMenu.show(Object.assign({ - item: item, + const apiClient = ServerConnections.getApiClient(item.ServerId); + const api = toApi(apiClient); + + Promise.all([ + // Import the item menu component + import('./itemContextMenu'), + // Fetch the current user + apiClient.getCurrentUser(), + // Fetch playlist perms if item is a child of a playlist + playlistId ? + getPlaylistsApi(api) + .getPlaylistUser({ + playlistId, + userId: apiClient.getCurrentUserId() + }) + .then(({ data }) => data) + .catch(err => { + // If a user doesn't have access, then the request will 404 and throw + console.info('[Shortcuts] Failed to fetch playlist permissions', err); + return { CanEdit: false }; + }) : + // Not a playlist item + Promise.resolve({ CanEdit: false }) + ]) + .then(([ + itemContextMenu, + user, + playlistPerms + ]) => { + return itemContextMenu.show({ + item, play: true, queue: true, playAllFromHere: item.Type === 'Season' || !item.IsFolder, queueAllFromHere: !item.IsFolder, - playlistId: playlistId, - collectionId: collectionId, - user: user - }, options || {})) - .then(result => { - if (result.command === 'playallfromhere' || result.command === 'queueallfromhere') { - executeAction(card, options.positionTo, result.command); - } else if (result.updated || result.deleted) { - notifyRefreshNeeded(card, options.itemsContainer); - } - }) - .catch(() => { /* no-op */ }); - }); - }); + playlistId, + canEditPlaylist: !!playlistPerms.CanEdit, + collectionId, + user, + ...options + }); + }) + .then(result => { + if (result.command === 'playallfromhere' || result.command === 'queueallfromhere') { + executeAction(card, options.positionTo, result.command); + } else if (result.updated || result.deleted) { + notifyRefreshNeeded(card, options.itemsContainer); + } + }) + .catch(() => { /* no-op */ }); }); } @@ -406,8 +436,8 @@ export function getShortcutAttributesHtml(item, serverId) { } export default { - on: on, - off: off, - onClick: onClick, - getShortcutAttributesHtml: getShortcutAttributesHtml + on, + off, + onClick, + getShortcutAttributesHtml }; diff --git a/src/scripts/playlistViewer.js b/src/scripts/playlistViewer.js index 16844ee8c8..512a15281b 100644 --- a/src/scripts/playlistViewer.js +++ b/src/scripts/playlistViewer.js @@ -1,48 +1,74 @@ -import listView from '../components/listview/listview'; +import { getPlaylistsApi } from '@jellyfin/sdk/lib/utils/api/playlists-api'; -function getFetchPlaylistItemsFn(itemId) { +import ServerConnections from 'components/ServerConnections'; +import listView from 'components/listview/listview'; +import { toApi } from 'utils/jellyfin-apiclient/compat'; + +function getFetchPlaylistItemsFn(apiClient, itemId) { return function () { const query = { Fields: 'PrimaryImageAspectRatio', EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', - UserId: ApiClient.getCurrentUserId() + UserId: apiClient.getCurrentUserId() }; - return ApiClient.getJSON(ApiClient.getUrl(`Playlists/${itemId}/Items`, query)); + return apiClient.getJSON(apiClient.getUrl(`Playlists/${itemId}/Items`, query)); }; } -function getItemsHtmlFn(itemId) { +function getItemsHtmlFn(playlistId, isEditable = false) { return function (items) { return listView.getListViewHtml({ - items: items, + items, showIndex: false, - showRemoveFromPlaylist: true, playFromHere: true, action: 'playallfromhere', smallIcon: true, - dragHandle: true, - playlistId: itemId + dragHandle: isEditable, + playlistId }); }; } -function init(page, item) { +async function init(page, item) { + const apiClient = ServerConnections.getApiClient(item.ServerId); + const api = toApi(apiClient); + + let isEditable = false; + const { data } = await getPlaylistsApi(api) + .getPlaylistUser({ + playlistId: item.Id, + userId: apiClient.getCurrentUserId() + }) + .catch(err => { + // If a user doesn't have access, then the request will 404 and throw + console.info('[PlaylistViewer] Failed to fetch playlist permissions', err); + return { data: {} }; + }); + isEditable = !!data.CanEdit; + const elem = page.querySelector('#childrenContent .itemsContainer'); elem.classList.add('vertical-list'); elem.classList.remove('vertical-wrap'); - elem.enableDragReordering(true); - elem.fetchData = getFetchPlaylistItemsFn(item.Id); - elem.getItemsHtml = getItemsHtmlFn(item.Id); + elem.enableDragReordering(isEditable); + elem.fetchData = getFetchPlaylistItemsFn(apiClient, item.Id); + elem.getItemsHtml = getItemsHtmlFn(item.Id, isEditable); +} + +function refresh(page) { + page.querySelector('#childrenContent').classList.add('verticalSection-extrabottompadding'); + page.querySelector('#childrenContent .itemsContainer').refreshItems(); } function render(page, item) { if (!page.playlistInit) { page.playlistInit = true; - init(page, item); + init(page, item) + .finally(() => { + refresh(page); + }); + } else { + refresh(page); } - - page.querySelector('#childrenContent').classList.add('verticalSection-extrabottompadding'); - page.querySelector('#childrenContent .itemsContainer').refreshItems(); } const PlaylistViewer = {