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

Merge pull request #5455 from thornbill/playlist-access-checks-3

Add playlist access checks
This commit is contained in:
Joshua M. Boniface 2024-05-06 09:08:02 -04:00 committed by GitHub
commit 19c41cf636
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 105 additions and 48 deletions

View file

@ -280,11 +280,11 @@ export function getCommands(options) {
}); });
} }
if (item.PlaylistItemId && options.playlistId) { if (item.PlaylistItemId && options.playlistId && options.canEditPlaylist) {
commands.push({ commands.push({
name: globalize.translate('RemoveFromPlaylist'), name: globalize.translate('RemoveFromPlaylist'),
id: 'removefromplaylist', id: 'removefromplaylist',
icon: 'remove' icon: 'playlist_remove'
}); });
} }
@ -292,7 +292,7 @@ export function getCommands(options) {
commands.push({ commands.push({
name: globalize.translate('RemoveFromCollection'), name: globalize.translate('RemoveFromCollection'),
id: 'removefromcollection', id: 'removefromcollection',
icon: 'remove' icon: 'playlist_remove'
}); });
} }
@ -696,6 +696,6 @@ export function show(options) {
} }
export default { export default {
getCommands: getCommands, getCommands,
show: show show
}; };

View file

@ -170,8 +170,9 @@ function populatePlaylists(editorOptions: PlaylistEditorOptions, panel: DialogEl
...playlist, ...playlist,
permissions permissions
})) }))
.catch((err) => { .catch(err => {
console.warn('[PlaylistEditor] Failed to fetch playlist permissions', 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; return playlist;
}); });
@ -231,7 +232,7 @@ function getEditorHtml(items: string[]) {
html += ` html += `
<div class="checkboxContainer checkboxContainer-withDescription"> <div class="checkboxContainer checkboxContainer-withDescription">
<label> <label>
<input type="checkbox" is="emby-checkbox" id="chkPlaylistPublic" /> <input type="checkbox" is="emby-checkbox" id="chkPlaylistPublic" checked />
<span>${globalize.translate('PlaylistPublic')}</span> <span>${globalize.translate('PlaylistPublic')}</span>
</label> </label>
<div class="fieldDescription checkboxFieldDescription"> <div class="fieldDescription checkboxFieldDescription">

View file

@ -2,6 +2,7 @@
* Module shortcuts. * Module shortcuts.
* @module components/shortcuts * @module components/shortcuts
*/ */
import { getPlaylistsApi } from '@jellyfin/sdk/lib/utils/api/playlists-api';
import { playbackManager } from './playback/playbackmanager'; import { playbackManager } from './playback/playbackmanager';
import inputManager from '../scripts/inputManager'; import inputManager from '../scripts/inputManager';
@ -12,6 +13,7 @@ import recordingHelper from './recordingcreator/recordinghelper';
import ServerConnections from './ServerConnections'; import ServerConnections from './ServerConnections';
import toast from './toast/toast'; import toast from './toast/toast';
import * as userSettings from '../scripts/settings/userSettings'; import * as userSettings from '../scripts/settings/userSettings';
import { toApi } from 'utils/jellyfin-apiclient/compat';
function playAllFromHere(card, serverId, queue) { function playAllFromHere(card, serverId, queue) {
const parent = card.parentNode; const parent = card.parentNode;
@ -100,7 +102,7 @@ function notifyRefreshNeeded(childElement, itemsContainer) {
} }
} }
function showContextMenu(card, options) { function showContextMenu(card, options = {}) {
getItem(card).then(item => { getItem(card).then(item => {
const playlistId = card.getAttribute('data-playlistid'); const playlistId = card.getAttribute('data-playlistid');
const collectionId = card.getAttribute('data-collectionid'); const collectionId = card.getAttribute('data-collectionid');
@ -110,18 +112,48 @@ function showContextMenu(card, options) {
item.PlaylistItemId = elem ? elem.getAttribute('data-playlistitemid') : null; item.PlaylistItemId = elem ? elem.getAttribute('data-playlistitemid') : null;
} }
import('./itemContextMenu').then((itemContextMenu) => { const apiClient = ServerConnections.getApiClient(item.ServerId);
ServerConnections.getApiClient(item.ServerId).getCurrentUser().then(user => { const api = toApi(apiClient);
itemContextMenu.show(Object.assign({
item: item, 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, play: true,
queue: true, queue: true,
playAllFromHere: item.Type === 'Season' || !item.IsFolder, playAllFromHere: item.Type === 'Season' || !item.IsFolder,
queueAllFromHere: !item.IsFolder, queueAllFromHere: !item.IsFolder,
playlistId: playlistId, playlistId,
collectionId: collectionId, canEditPlaylist: !!playlistPerms.CanEdit,
user: user collectionId,
}, options || {})) user,
...options
});
})
.then(result => { .then(result => {
if (result.command === 'playallfromhere' || result.command === 'queueallfromhere') { if (result.command === 'playallfromhere' || result.command === 'queueallfromhere') {
executeAction(card, options.positionTo, result.command); executeAction(card, options.positionTo, result.command);
@ -131,8 +163,6 @@ function showContextMenu(card, options) {
}) })
.catch(() => { /* no-op */ }); .catch(() => { /* no-op */ });
}); });
});
});
} }
function getItemInfoFromCard(card) { function getItemInfoFromCard(card) {
@ -406,8 +436,8 @@ export function getShortcutAttributesHtml(item, serverId) {
} }
export default { export default {
on: on, on,
off: off, off,
onClick: onClick, onClick,
getShortcutAttributesHtml: getShortcutAttributesHtml getShortcutAttributesHtml
}; };

View file

@ -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 () { return function () {
const query = { const query = {
Fields: 'PrimaryImageAspectRatio', Fields: 'PrimaryImageAspectRatio',
EnableImageTypes: 'Primary,Backdrop,Banner,Thumb', 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 function (items) {
return listView.getListViewHtml({ return listView.getListViewHtml({
items: items, items,
showIndex: false, showIndex: false,
showRemoveFromPlaylist: true,
playFromHere: true, playFromHere: true,
action: 'playallfromhere', action: 'playallfromhere',
smallIcon: true, smallIcon: true,
dragHandle: true, dragHandle: isEditable,
playlistId: itemId 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'); const elem = page.querySelector('#childrenContent .itemsContainer');
elem.classList.add('vertical-list'); elem.classList.add('vertical-list');
elem.classList.remove('vertical-wrap'); elem.classList.remove('vertical-wrap');
elem.enableDragReordering(true); elem.enableDragReordering(isEditable);
elem.fetchData = getFetchPlaylistItemsFn(item.Id); elem.fetchData = getFetchPlaylistItemsFn(apiClient, item.Id);
elem.getItemsHtml = getItemsHtmlFn(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) { function render(page, item) {
if (!page.playlistInit) { if (!page.playlistInit) {
page.playlistInit = true; 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 = { const PlaylistViewer = {