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 = {