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:
commit
19c41cf636
4 changed files with 105 additions and 48 deletions
|
@ -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
|
||||||
};
|
};
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue