mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Merge pull request #5392 from thornbill/playlist-editor-ts
This commit is contained in:
commit
7158be9aaf
5 changed files with 324 additions and 488 deletions
1
src/apiclient.d.ts
vendored
1
src/apiclient.d.ts
vendored
|
@ -76,6 +76,7 @@ declare module 'jellyfin-apiclient' {
|
||||||
accessToken(): string;
|
accessToken(): string;
|
||||||
addMediaPath(virtualFolderName: string, mediaPath: string, networkSharePath: string, refreshLibrary?: boolean): Promise<void>;
|
addMediaPath(virtualFolderName: string, mediaPath: string, networkSharePath: string, refreshLibrary?: boolean): Promise<void>;
|
||||||
addVirtualFolder(name: string, type?: string, refreshLibrary?: boolean, libraryOptions?: any): Promise<void>;
|
addVirtualFolder(name: string, type?: string, refreshLibrary?: boolean, libraryOptions?: any): Promise<void>;
|
||||||
|
ajax(request: any): Promise<any>;
|
||||||
appName(): string;
|
appName(): string;
|
||||||
appVersion(): string;
|
appVersion(): string;
|
||||||
authenticateUserByName(name: string, password: string): Promise<AuthenticationResult>;
|
authenticateUserByName(name: string, password: string): Promise<AuthenticationResult>;
|
||||||
|
|
|
@ -1,283 +0,0 @@
|
||||||
import escapeHtml from 'escape-html';
|
|
||||||
import dom from '../../scripts/dom';
|
|
||||||
import dialogHelper from '../dialogHelper/dialogHelper';
|
|
||||||
import loading from '../loading/loading';
|
|
||||||
import layoutManager from '../layoutManager';
|
|
||||||
import { playbackManager } from '../playback/playbackmanager';
|
|
||||||
import { pluginManager } from '../pluginManager';
|
|
||||||
import * as userSettings from '../../scripts/settings/userSettings';
|
|
||||||
import { appRouter } from '../router/appRouter';
|
|
||||||
import globalize from '../../scripts/globalize';
|
|
||||||
import { PluginType } from '../../types/plugin.ts';
|
|
||||||
|
|
||||||
import '../../elements/emby-button/emby-button';
|
|
||||||
import '../../elements/emby-input/emby-input';
|
|
||||||
import '../../elements/emby-button/paper-icon-button-light';
|
|
||||||
import '../../elements/emby-select/emby-select';
|
|
||||||
import 'material-design-icons-iconfont';
|
|
||||||
import '../formdialog.scss';
|
|
||||||
import ServerConnections from '../ServerConnections';
|
|
||||||
|
|
||||||
let currentServerId;
|
|
||||||
|
|
||||||
function onSubmit(e) {
|
|
||||||
const panel = dom.parentWithClass(this, 'dialog');
|
|
||||||
|
|
||||||
const playlistId = panel.querySelector('#selectPlaylistToAddTo').value;
|
|
||||||
const apiClient = ServerConnections.getApiClient(currentServerId);
|
|
||||||
|
|
||||||
if (playlistId) {
|
|
||||||
userSettings.set('playlisteditor-lastplaylistid', playlistId);
|
|
||||||
addToPlaylist(apiClient, panel, playlistId);
|
|
||||||
} else {
|
|
||||||
createPlaylist(apiClient, panel);
|
|
||||||
}
|
|
||||||
|
|
||||||
e.preventDefault();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createPlaylist(apiClient, dlg) {
|
|
||||||
loading.show();
|
|
||||||
|
|
||||||
const url = apiClient.getUrl('Playlists', {
|
|
||||||
Name: dlg.querySelector('#txtNewPlaylistName').value,
|
|
||||||
Ids: dlg.querySelector('.fldSelectedItemIds').value || '',
|
|
||||||
userId: apiClient.getCurrentUserId()
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
apiClient.ajax({
|
|
||||||
type: 'POST',
|
|
||||||
url: url,
|
|
||||||
dataType: 'json',
|
|
||||||
contentType: 'application/json'
|
|
||||||
}).then(result => {
|
|
||||||
loading.hide();
|
|
||||||
|
|
||||||
const id = result.Id;
|
|
||||||
dlg.submitted = true;
|
|
||||||
dialogHelper.close(dlg);
|
|
||||||
redirectToPlaylist(apiClient, id);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function redirectToPlaylist(apiClient, id) {
|
|
||||||
appRouter.showItem(id, apiClient.serverId());
|
|
||||||
}
|
|
||||||
|
|
||||||
function addToPlaylist(apiClient, dlg, id) {
|
|
||||||
const itemIds = dlg.querySelector('.fldSelectedItemIds').value || '';
|
|
||||||
|
|
||||||
if (id === 'queue') {
|
|
||||||
playbackManager.queue({
|
|
||||||
serverId: apiClient.serverId(),
|
|
||||||
ids: itemIds.split(',')
|
|
||||||
});
|
|
||||||
dlg.submitted = true;
|
|
||||||
dialogHelper.close(dlg);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
loading.show();
|
|
||||||
|
|
||||||
const url = apiClient.getUrl(`Playlists/${id}/Items`, {
|
|
||||||
Ids: itemIds,
|
|
||||||
userId: apiClient.getCurrentUserId()
|
|
||||||
});
|
|
||||||
|
|
||||||
apiClient.ajax({
|
|
||||||
type: 'POST',
|
|
||||||
url: url
|
|
||||||
|
|
||||||
}).then(() => {
|
|
||||||
loading.hide();
|
|
||||||
|
|
||||||
dlg.submitted = true;
|
|
||||||
dialogHelper.close(dlg);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function triggerChange(select) {
|
|
||||||
select.dispatchEvent(new CustomEvent('change', {}));
|
|
||||||
}
|
|
||||||
|
|
||||||
function populatePlaylists(editorOptions, panel) {
|
|
||||||
const select = panel.querySelector('#selectPlaylistToAddTo');
|
|
||||||
|
|
||||||
loading.hide();
|
|
||||||
|
|
||||||
panel.querySelector('.newPlaylistInfo').classList.add('hide');
|
|
||||||
|
|
||||||
const options = {
|
|
||||||
Recursive: true,
|
|
||||||
IncludeItemTypes: 'Playlist',
|
|
||||||
SortBy: 'SortName',
|
|
||||||
EnableTotalRecordCount: false
|
|
||||||
};
|
|
||||||
|
|
||||||
const apiClient = ServerConnections.getApiClient(currentServerId);
|
|
||||||
const SyncPlay = pluginManager.firstOfType(PluginType.SyncPlay)?.instance;
|
|
||||||
|
|
||||||
apiClient.getItems(apiClient.getCurrentUserId(), options).then(result => {
|
|
||||||
let html = '';
|
|
||||||
|
|
||||||
if ((editorOptions.enableAddToPlayQueue !== false && playbackManager.isPlaying()) || SyncPlay?.Manager.isSyncPlayEnabled()) {
|
|
||||||
html += `<option value="queue">${globalize.translate('AddToPlayQueue')}</option>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
html += `<option value="">${globalize.translate('OptionNew')}</option>`;
|
|
||||||
|
|
||||||
html += result.Items.map(i => {
|
|
||||||
return `<option value="${i.Id}">${escapeHtml(i.Name)}</option>`;
|
|
||||||
});
|
|
||||||
|
|
||||||
select.innerHTML = html;
|
|
||||||
|
|
||||||
let defaultValue = editorOptions.defaultValue;
|
|
||||||
if (!defaultValue) {
|
|
||||||
defaultValue = userSettings.get('playlisteditor-lastplaylistid') || '';
|
|
||||||
}
|
|
||||||
select.value = defaultValue === 'new' ? '' : defaultValue;
|
|
||||||
|
|
||||||
// If the value is empty set it again, in case we tried to set a lastplaylistid that is no longer valid
|
|
||||||
if (!select.value) {
|
|
||||||
select.value = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
triggerChange(select);
|
|
||||||
|
|
||||||
loading.hide();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getEditorHtml(items) {
|
|
||||||
let html = '';
|
|
||||||
|
|
||||||
html += '<div class="formDialogContent smoothScrollY" style="padding-top:2em;">';
|
|
||||||
html += '<div class="dialogContentInner dialog-content-centered">';
|
|
||||||
html += '<form style="margin:auto;">';
|
|
||||||
|
|
||||||
html += '<div class="fldSelectPlaylist selectContainer">';
|
|
||||||
let autoFocus = items.length ? ' autofocus' : '';
|
|
||||||
html += `<select is="emby-select" id="selectPlaylistToAddTo" label="${globalize.translate('LabelPlaylist')}"${autoFocus}></select>`;
|
|
||||||
html += '</div>';
|
|
||||||
|
|
||||||
html += '<div class="newPlaylistInfo">';
|
|
||||||
|
|
||||||
html += '<div class="inputContainer">';
|
|
||||||
autoFocus = items.length ? '' : ' autofocus';
|
|
||||||
html += `<input is="emby-input" type="text" id="txtNewPlaylistName" required="required" label="${globalize.translate('LabelName')}"${autoFocus} />`;
|
|
||||||
html += '</div>';
|
|
||||||
|
|
||||||
// newPlaylistInfo
|
|
||||||
html += '</div>';
|
|
||||||
|
|
||||||
html += '<div class="formDialogFooter">';
|
|
||||||
html += `<button is="emby-button" type="submit" class="raised btnSubmit block formDialogFooterItem button-submit">${globalize.translate('Add')}</button>`;
|
|
||||||
html += '</div>';
|
|
||||||
|
|
||||||
html += '<input type="hidden" class="fldSelectedItemIds" />';
|
|
||||||
|
|
||||||
html += '</form>';
|
|
||||||
html += '</div>';
|
|
||||||
html += '</div>';
|
|
||||||
|
|
||||||
return html;
|
|
||||||
}
|
|
||||||
|
|
||||||
function initEditor(content, options, items) {
|
|
||||||
content.querySelector('#selectPlaylistToAddTo').addEventListener('change', function () {
|
|
||||||
if (this.value) {
|
|
||||||
content.querySelector('.newPlaylistInfo').classList.add('hide');
|
|
||||||
content.querySelector('#txtNewPlaylistName').removeAttribute('required');
|
|
||||||
} else {
|
|
||||||
content.querySelector('.newPlaylistInfo').classList.remove('hide');
|
|
||||||
content.querySelector('#txtNewPlaylistName').setAttribute('required', 'required');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
content.querySelector('form').addEventListener('submit', onSubmit);
|
|
||||||
|
|
||||||
content.querySelector('.fldSelectedItemIds', content).value = items.join(',');
|
|
||||||
|
|
||||||
if (items.length) {
|
|
||||||
content.querySelector('.fldSelectPlaylist').classList.remove('hide');
|
|
||||||
populatePlaylists(options, content);
|
|
||||||
} else {
|
|
||||||
content.querySelector('.fldSelectPlaylist').classList.add('hide');
|
|
||||||
|
|
||||||
const selectPlaylistToAddTo = content.querySelector('#selectPlaylistToAddTo');
|
|
||||||
selectPlaylistToAddTo.innerHTML = '';
|
|
||||||
selectPlaylistToAddTo.value = '';
|
|
||||||
triggerChange(selectPlaylistToAddTo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function centerFocus(elem, horiz, on) {
|
|
||||||
import('../../scripts/scrollHelper').then((scrollHelper) => {
|
|
||||||
const fn = on ? 'on' : 'off';
|
|
||||||
scrollHelper.centerFocus[fn](elem, horiz);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export class PlaylistEditor {
|
|
||||||
show(options) {
|
|
||||||
const items = options.items || {};
|
|
||||||
currentServerId = options.serverId;
|
|
||||||
|
|
||||||
const dialogOptions = {
|
|
||||||
removeOnClose: true,
|
|
||||||
scrollY: false
|
|
||||||
};
|
|
||||||
|
|
||||||
if (layoutManager.tv) {
|
|
||||||
dialogOptions.size = 'fullscreen';
|
|
||||||
} else {
|
|
||||||
dialogOptions.size = 'small';
|
|
||||||
}
|
|
||||||
|
|
||||||
const dlg = dialogHelper.createDialog(dialogOptions);
|
|
||||||
|
|
||||||
dlg.classList.add('formDialog');
|
|
||||||
|
|
||||||
let html = '';
|
|
||||||
const title = globalize.translate('HeaderAddToPlaylist');
|
|
||||||
|
|
||||||
html += '<div class="formDialogHeader">';
|
|
||||||
html += `<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1" title="${globalize.translate('ButtonBack')}"><span class="material-icons arrow_back" aria-hidden="true"></span></button>`;
|
|
||||||
html += '<h3 class="formDialogHeaderTitle">';
|
|
||||||
html += title;
|
|
||||||
html += '</h3>';
|
|
||||||
|
|
||||||
html += '</div>';
|
|
||||||
|
|
||||||
html += getEditorHtml(items);
|
|
||||||
|
|
||||||
dlg.innerHTML = html;
|
|
||||||
|
|
||||||
initEditor(dlg, options, items);
|
|
||||||
|
|
||||||
dlg.querySelector('.btnCancel').addEventListener('click', () => {
|
|
||||||
dialogHelper.close(dlg);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (layoutManager.tv) {
|
|
||||||
centerFocus(dlg.querySelector('.formDialogContent'), false, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return dialogHelper.open(dlg).then(() => {
|
|
||||||
if (layoutManager.tv) {
|
|
||||||
centerFocus(dlg.querySelector('.formDialogContent'), false, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dlg.submitted) {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.reject();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PlaylistEditor;
|
|
321
src/components/playlisteditor/playlisteditor.ts
Normal file
321
src/components/playlisteditor/playlisteditor.ts
Normal file
|
@ -0,0 +1,321 @@
|
||||||
|
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind';
|
||||||
|
import { ItemSortBy } from '@jellyfin/sdk/lib/generated-client/models/item-sort-by';
|
||||||
|
import { getItemsApi } from '@jellyfin/sdk/lib/utils/api/items-api';
|
||||||
|
import { getPlaylistsApi } from '@jellyfin/sdk/lib/utils/api/playlists-api';
|
||||||
|
import escapeHtml from 'escape-html';
|
||||||
|
|
||||||
|
import dom from 'scripts/dom';
|
||||||
|
import globalize from 'scripts/globalize';
|
||||||
|
import { currentSettings as userSettings } from 'scripts/settings/userSettings';
|
||||||
|
import { PluginType } from 'types/plugin';
|
||||||
|
import { toApi } from 'utils/jellyfin-apiclient/compat';
|
||||||
|
|
||||||
|
import dialogHelper from '../dialogHelper/dialogHelper';
|
||||||
|
import loading from '../loading/loading';
|
||||||
|
import layoutManager from '../layoutManager';
|
||||||
|
import { playbackManager } from '../playback/playbackmanager';
|
||||||
|
import { pluginManager } from '../pluginManager';
|
||||||
|
import { appRouter } from '../router/appRouter';
|
||||||
|
import ServerConnections from '../ServerConnections';
|
||||||
|
|
||||||
|
import 'elements/emby-button/emby-button';
|
||||||
|
import 'elements/emby-input/emby-input';
|
||||||
|
import 'elements/emby-button/paper-icon-button-light';
|
||||||
|
import 'elements/emby-select/emby-select';
|
||||||
|
|
||||||
|
import 'material-design-icons-iconfont';
|
||||||
|
import '../formdialog.scss';
|
||||||
|
|
||||||
|
interface DialogElement extends HTMLDivElement {
|
||||||
|
submitted?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PlaylistEditorOptions {
|
||||||
|
items: string[],
|
||||||
|
serverId: string,
|
||||||
|
enableAddToPlayQueue?: boolean,
|
||||||
|
defaultValue?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentServerId: string;
|
||||||
|
|
||||||
|
function onSubmit(this: HTMLElement, e: Event) {
|
||||||
|
const panel = dom.parentWithClass(this, 'dialog') as DialogElement | null;
|
||||||
|
|
||||||
|
if (panel) {
|
||||||
|
const playlistId = panel.querySelector<HTMLSelectElement>('#selectPlaylistToAddTo')?.value;
|
||||||
|
|
||||||
|
loading.show();
|
||||||
|
|
||||||
|
if (playlistId) {
|
||||||
|
userSettings.set('playlisteditor-lastplaylistid', playlistId);
|
||||||
|
addToPlaylist(panel, playlistId)
|
||||||
|
.catch(err => {
|
||||||
|
console.error('[PlaylistEditor] Failed to add to playlist %s', playlistId, err);
|
||||||
|
})
|
||||||
|
.finally(loading.hide);
|
||||||
|
} else {
|
||||||
|
createPlaylist(panel)
|
||||||
|
.catch(err => {
|
||||||
|
console.error('[PlaylistEditor] Failed to create playlist', err);
|
||||||
|
})
|
||||||
|
.finally(loading.hide);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error('[PlaylistEditor] Dialog element is missing!');
|
||||||
|
}
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createPlaylist(dlg: DialogElement) {
|
||||||
|
const apiClient = ServerConnections.getApiClient(currentServerId);
|
||||||
|
const api = toApi(apiClient);
|
||||||
|
|
||||||
|
const itemIds = dlg.querySelector<HTMLInputElement>('.fldSelectedItemIds')?.value || '';
|
||||||
|
|
||||||
|
return getPlaylistsApi(api)
|
||||||
|
.createPlaylist({
|
||||||
|
name: dlg.querySelector<HTMLInputElement>('#txtNewPlaylistName')?.value,
|
||||||
|
ids: itemIds.split(','),
|
||||||
|
userId: apiClient.getCurrentUserId()
|
||||||
|
})
|
||||||
|
.then(result => {
|
||||||
|
dlg.submitted = true;
|
||||||
|
dialogHelper.close(dlg);
|
||||||
|
|
||||||
|
redirectToPlaylist(result.data.Id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function redirectToPlaylist(id: string | undefined) {
|
||||||
|
appRouter.showItem(id, currentServerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addToPlaylist(dlg: DialogElement, id: string) {
|
||||||
|
const apiClient = ServerConnections.getApiClient(currentServerId);
|
||||||
|
const api = toApi(apiClient);
|
||||||
|
const itemIds = dlg.querySelector<HTMLInputElement>('.fldSelectedItemIds')?.value || '';
|
||||||
|
|
||||||
|
if (id === 'queue') {
|
||||||
|
playbackManager.queue({
|
||||||
|
serverId: currentServerId,
|
||||||
|
ids: itemIds.split(',')
|
||||||
|
});
|
||||||
|
dlg.submitted = true;
|
||||||
|
dialogHelper.close(dlg);
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
return getPlaylistsApi(api)
|
||||||
|
.addItemToPlaylist({
|
||||||
|
playlistId: id,
|
||||||
|
ids: itemIds.split(','),
|
||||||
|
userId: apiClient.getCurrentUserId()
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
dlg.submitted = true;
|
||||||
|
dialogHelper.close(dlg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function triggerChange(select: HTMLSelectElement) {
|
||||||
|
select.dispatchEvent(new CustomEvent('change', {}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function populatePlaylists(editorOptions: PlaylistEditorOptions, panel: DialogElement) {
|
||||||
|
const select = panel.querySelector<HTMLSelectElement>('#selectPlaylistToAddTo');
|
||||||
|
|
||||||
|
if (!select) {
|
||||||
|
return Promise.reject(new Error('Playlist <select> element is missing'));
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.show();
|
||||||
|
|
||||||
|
panel.querySelector('.newPlaylistInfo')?.classList.add('hide');
|
||||||
|
|
||||||
|
const apiClient = ServerConnections.getApiClient(currentServerId);
|
||||||
|
const api = toApi(apiClient);
|
||||||
|
const SyncPlay = pluginManager.firstOfType(PluginType.SyncPlay)?.instance;
|
||||||
|
|
||||||
|
return getItemsApi(api)
|
||||||
|
.getItems({
|
||||||
|
userId: apiClient.getCurrentUserId(),
|
||||||
|
includeItemTypes: [ BaseItemKind.Playlist ],
|
||||||
|
sortBy: [ ItemSortBy.SortName ],
|
||||||
|
recursive: true
|
||||||
|
})
|
||||||
|
.then(({ data }) => {
|
||||||
|
let html = '';
|
||||||
|
|
||||||
|
if ((editorOptions.enableAddToPlayQueue !== false && playbackManager.isPlaying()) || SyncPlay?.Manager.isSyncPlayEnabled()) {
|
||||||
|
html += `<option value="queue">${globalize.translate('AddToPlayQueue')}</option>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
html += `<option value="">${globalize.translate('OptionNew')}</option>`;
|
||||||
|
|
||||||
|
html += data.Items?.map(i => {
|
||||||
|
return `<option value="${i.Id}">${escapeHtml(i.Name)}</option>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
select.innerHTML = html;
|
||||||
|
|
||||||
|
let defaultValue = editorOptions.defaultValue;
|
||||||
|
if (!defaultValue) {
|
||||||
|
defaultValue = userSettings.get('playlisteditor-lastplaylistid') || '';
|
||||||
|
}
|
||||||
|
select.value = defaultValue === 'new' ? '' : defaultValue;
|
||||||
|
|
||||||
|
// If the value is empty set it again, in case we tried to set a lastplaylistid that is no longer valid
|
||||||
|
if (!select.value) {
|
||||||
|
select.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
triggerChange(select);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEditorHtml(items: string[]) {
|
||||||
|
let html = '';
|
||||||
|
|
||||||
|
html += '<div class="formDialogContent smoothScrollY" style="padding-top:2em;">';
|
||||||
|
html += '<div class="dialogContentInner dialog-content-centered">';
|
||||||
|
html += '<form style="margin:auto;">';
|
||||||
|
|
||||||
|
html += '<div class="fldSelectPlaylist selectContainer">';
|
||||||
|
let autoFocus = items.length ? ' autofocus' : '';
|
||||||
|
html += `<select is="emby-select" id="selectPlaylistToAddTo" label="${globalize.translate('LabelPlaylist')}"${autoFocus}></select>`;
|
||||||
|
html += '</div>';
|
||||||
|
|
||||||
|
html += '<div class="newPlaylistInfo">';
|
||||||
|
|
||||||
|
html += '<div class="inputContainer">';
|
||||||
|
autoFocus = items.length ? '' : ' autofocus';
|
||||||
|
html += `<input is="emby-input" type="text" id="txtNewPlaylistName" required="required" label="${globalize.translate('LabelName')}"${autoFocus} />`;
|
||||||
|
html += '</div>';
|
||||||
|
|
||||||
|
// newPlaylistInfo
|
||||||
|
html += '</div>';
|
||||||
|
|
||||||
|
html += '<div class="formDialogFooter">';
|
||||||
|
html += `<button is="emby-button" type="submit" class="raised btnSubmit block formDialogFooterItem button-submit">${globalize.translate('Add')}</button>`;
|
||||||
|
html += '</div>';
|
||||||
|
|
||||||
|
html += '<input type="hidden" class="fldSelectedItemIds" />';
|
||||||
|
|
||||||
|
html += '</form>';
|
||||||
|
html += '</div>';
|
||||||
|
html += '</div>';
|
||||||
|
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function initEditor(content: DialogElement, options: PlaylistEditorOptions, items: string[]) {
|
||||||
|
content.querySelector('#selectPlaylistToAddTo')?.addEventListener('change', function(this: HTMLSelectElement) {
|
||||||
|
if (this.value) {
|
||||||
|
content.querySelector('.newPlaylistInfo')?.classList.add('hide');
|
||||||
|
content.querySelector('#txtNewPlaylistName')?.removeAttribute('required');
|
||||||
|
} else {
|
||||||
|
content.querySelector('.newPlaylistInfo')?.classList.remove('hide');
|
||||||
|
content.querySelector('#txtNewPlaylistName')?.setAttribute('required', 'required');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
content.querySelector('form')?.addEventListener('submit', onSubmit);
|
||||||
|
|
||||||
|
const selectedItemsInput = content.querySelector<HTMLInputElement>('.fldSelectedItemIds');
|
||||||
|
if (selectedItemsInput) {
|
||||||
|
selectedItemsInput.value = items.join(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (items.length) {
|
||||||
|
content.querySelector('.fldSelectPlaylist')?.classList.remove('hide');
|
||||||
|
populatePlaylists(options, content)
|
||||||
|
.catch(err => {
|
||||||
|
console.error('[PlaylistEditor] failed to populate playlists', err);
|
||||||
|
})
|
||||||
|
.finally(loading.hide);
|
||||||
|
} else {
|
||||||
|
content.querySelector('.fldSelectPlaylist')?.classList.add('hide');
|
||||||
|
|
||||||
|
const selectPlaylistToAddTo = content.querySelector<HTMLSelectElement>('#selectPlaylistToAddTo');
|
||||||
|
if (selectPlaylistToAddTo) {
|
||||||
|
selectPlaylistToAddTo.innerHTML = '';
|
||||||
|
selectPlaylistToAddTo.value = '';
|
||||||
|
triggerChange(selectPlaylistToAddTo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function centerFocus(elem: HTMLDivElement | null, horiz: boolean, on: boolean) {
|
||||||
|
if (!elem) {
|
||||||
|
console.error('[PlaylistEditor] cannot focus null element');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
import('../../scripts/scrollHelper')
|
||||||
|
.then((scrollHelper) => {
|
||||||
|
const fn = on ? 'on' : 'off';
|
||||||
|
scrollHelper.centerFocus[fn](elem, horiz);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error('[PlaylistEditor] failed to load scroll helper', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PlaylistEditor {
|
||||||
|
show(options: PlaylistEditorOptions) {
|
||||||
|
const items = options.items || [];
|
||||||
|
currentServerId = options.serverId;
|
||||||
|
|
||||||
|
const dialogOptions = {
|
||||||
|
removeOnClose: true,
|
||||||
|
scrollY: false,
|
||||||
|
size: layoutManager.tv ? 'fullscreen' : 'small'
|
||||||
|
};
|
||||||
|
|
||||||
|
const dlg: DialogElement = dialogHelper.createDialog(dialogOptions);
|
||||||
|
|
||||||
|
dlg.classList.add('formDialog');
|
||||||
|
|
||||||
|
let html = '';
|
||||||
|
const title = globalize.translate('HeaderAddToPlaylist');
|
||||||
|
|
||||||
|
html += '<div class="formDialogHeader">';
|
||||||
|
html += `<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1" title="${globalize.translate('ButtonBack')}"><span class="material-icons arrow_back" aria-hidden="true"></span></button>`;
|
||||||
|
html += '<h3 class="formDialogHeaderTitle">';
|
||||||
|
html += title;
|
||||||
|
html += '</h3>';
|
||||||
|
|
||||||
|
html += '</div>';
|
||||||
|
|
||||||
|
html += getEditorHtml(items);
|
||||||
|
|
||||||
|
dlg.innerHTML = html;
|
||||||
|
|
||||||
|
initEditor(dlg, options, items);
|
||||||
|
|
||||||
|
dlg.querySelector('.btnCancel')?.addEventListener('click', () => {
|
||||||
|
dialogHelper.close(dlg);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (layoutManager.tv) {
|
||||||
|
centerFocus(dlg.querySelector('.formDialogContent'), false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dialogHelper.open(dlg).then(() => {
|
||||||
|
if (layoutManager.tv) {
|
||||||
|
centerFocus(dlg.querySelector('.formDialogContent'), false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dlg.submitted) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(new Error());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PlaylistEditor;
|
|
@ -1,203 +0,0 @@
|
||||||
import loading from '../components/loading/loading';
|
|
||||||
import listView from '../components/listview/listview';
|
|
||||||
import cardBuilder from '../components/cardbuilder/cardBuilder';
|
|
||||||
import libraryMenu from './libraryMenu';
|
|
||||||
import libraryBrowser from './libraryBrowser';
|
|
||||||
import imageLoader from '../components/images/imageLoader';
|
|
||||||
import * as userSettings from './settings/userSettings';
|
|
||||||
import '../elements/emby-itemscontainer/emby-itemscontainer';
|
|
||||||
import Dashboard from '../utils/dashboard';
|
|
||||||
|
|
||||||
export default function (view) {
|
|
||||||
function getPageData() {
|
|
||||||
const key = getSavedQueryKey();
|
|
||||||
let pageData = data[key];
|
|
||||||
|
|
||||||
if (!pageData) {
|
|
||||||
pageData = data[key] = {
|
|
||||||
query: {
|
|
||||||
SortBy: 'SortName',
|
|
||||||
SortOrder: 'Ascending',
|
|
||||||
IncludeItemTypes: 'Playlist',
|
|
||||||
Recursive: true,
|
|
||||||
Fields: 'PrimaryImageAspectRatio,SortName,CumulativeRunTimeTicks,CanDelete',
|
|
||||||
StartIndex: 0
|
|
||||||
},
|
|
||||||
view: userSettings.getSavedView(key) || 'Poster'
|
|
||||||
};
|
|
||||||
|
|
||||||
if (userSettings.libraryPageSize() > 0) {
|
|
||||||
pageData.query['Limit'] = userSettings.libraryPageSize();
|
|
||||||
}
|
|
||||||
|
|
||||||
pageData.query.ParentId = libraryMenu.getTopParentId();
|
|
||||||
userSettings.loadQuerySettings(key, pageData.query);
|
|
||||||
}
|
|
||||||
|
|
||||||
return pageData;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getQuery() {
|
|
||||||
return getPageData().query;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSavedQueryKey() {
|
|
||||||
return `${libraryMenu.getTopParentId()}-playlists`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function showLoadingMessage() {
|
|
||||||
loading.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideLoadingMessage() {
|
|
||||||
loading.hide();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onViewStyleChange() {
|
|
||||||
const viewStyle = getPageData().view;
|
|
||||||
const itemsContainer = view.querySelector('.itemsContainer');
|
|
||||||
|
|
||||||
if (viewStyle == 'List') {
|
|
||||||
itemsContainer.classList.add('vertical-list');
|
|
||||||
itemsContainer.classList.remove('vertical-wrap');
|
|
||||||
} else {
|
|
||||||
itemsContainer.classList.remove('vertical-list');
|
|
||||||
itemsContainer.classList.add('vertical-wrap');
|
|
||||||
}
|
|
||||||
|
|
||||||
itemsContainer.innerHTML = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
function reloadItems() {
|
|
||||||
showLoadingMessage();
|
|
||||||
const query = getQuery();
|
|
||||||
const promise1 = ApiClient.getItems(Dashboard.getCurrentUserId(), query);
|
|
||||||
// TODO: promise2 is unused, check if necessary.
|
|
||||||
const promise2 = Dashboard.getCurrentUser();
|
|
||||||
Promise.all([promise1, promise2]).then(function (responses) {
|
|
||||||
const result = responses[0];
|
|
||||||
// TODO: Is the scroll necessary?
|
|
||||||
window.scrollTo(0, 0);
|
|
||||||
let html = '';
|
|
||||||
const viewStyle = getPageData().view;
|
|
||||||
view.querySelector('.listTopPaging').innerHTML = libraryBrowser.getQueryPagingHtml({
|
|
||||||
startIndex: query.StartIndex,
|
|
||||||
limit: query.Limit,
|
|
||||||
totalRecordCount: result.TotalRecordCount,
|
|
||||||
viewButton: false,
|
|
||||||
showLimit: false,
|
|
||||||
updatePageSizeSetting: false,
|
|
||||||
addLayoutButton: true,
|
|
||||||
layouts: 'List,Poster,PosterCard,Thumb,ThumbCard',
|
|
||||||
currentLayout: viewStyle
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result.TotalRecordCount) {
|
|
||||||
if (viewStyle == 'List') {
|
|
||||||
html = listView.getListViewHtml({
|
|
||||||
items: result.Items,
|
|
||||||
sortBy: query.SortBy
|
|
||||||
});
|
|
||||||
} else if (viewStyle == 'PosterCard') {
|
|
||||||
html = cardBuilder.getCardsHtml({
|
|
||||||
items: result.Items,
|
|
||||||
shape: 'square',
|
|
||||||
coverImage: true,
|
|
||||||
showTitle: true,
|
|
||||||
cardLayout: true
|
|
||||||
});
|
|
||||||
} else if (viewStyle == 'Thumb') {
|
|
||||||
html = cardBuilder.getCardsHtml({
|
|
||||||
items: result.Items,
|
|
||||||
shape: 'backdrop',
|
|
||||||
showTitle: true,
|
|
||||||
centerText: true,
|
|
||||||
preferThumb: true,
|
|
||||||
overlayPlayButton: true
|
|
||||||
});
|
|
||||||
} else if (viewStyle == 'ThumbCard') {
|
|
||||||
html = cardBuilder.getCardsHtml({
|
|
||||||
items: result.Items,
|
|
||||||
shape: 'backdrop',
|
|
||||||
showTitle: true,
|
|
||||||
preferThumb: true,
|
|
||||||
cardLayout: true
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
html = cardBuilder.getCardsHtml({
|
|
||||||
items: result.Items,
|
|
||||||
shape: 'square',
|
|
||||||
showTitle: true,
|
|
||||||
coverImage: true,
|
|
||||||
centerText: true,
|
|
||||||
overlayPlayButton: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
view.querySelector('.noItemsMessage').classList.add('hide');
|
|
||||||
} else {
|
|
||||||
view.querySelector('.noItemsMessage').classList.remove('hide');
|
|
||||||
}
|
|
||||||
|
|
||||||
const elem = view.querySelector('.itemsContainer');
|
|
||||||
elem.innerHTML = html;
|
|
||||||
imageLoader.lazyChildren(elem);
|
|
||||||
const btnNextPage = view.querySelector('.btnNextPage');
|
|
||||||
|
|
||||||
if (btnNextPage) {
|
|
||||||
btnNextPage.addEventListener('click', function () {
|
|
||||||
if (userSettings.libraryPageSize() > 0) {
|
|
||||||
query.StartIndex += query.Limit;
|
|
||||||
}
|
|
||||||
reloadItems();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const btnPreviousPage = view.querySelector('.btnPreviousPage');
|
|
||||||
|
|
||||||
if (btnPreviousPage) {
|
|
||||||
btnPreviousPage.addEventListener('click', function () {
|
|
||||||
if (userSettings.libraryPageSize() > 0) {
|
|
||||||
query.StartIndex = Math.max(0, query.StartIndex - query.Limit);
|
|
||||||
}
|
|
||||||
reloadItems();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const btnChangeLayout = view.querySelector('.btnChangeLayout');
|
|
||||||
|
|
||||||
if (btnChangeLayout) {
|
|
||||||
btnChangeLayout.addEventListener('layoutchange', function (e) {
|
|
||||||
const layout = e.detail.viewStyle;
|
|
||||||
getPageData().view = layout;
|
|
||||||
userSettings.saveViewSetting(getSavedQueryKey(), layout);
|
|
||||||
onViewStyleChange();
|
|
||||||
reloadItems();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
userSettings.saveQuerySettings(getSavedQueryKey(), query);
|
|
||||||
hideLoadingMessage();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = {};
|
|
||||||
view.addEventListener('viewbeforeshow', function () {
|
|
||||||
reloadItems();
|
|
||||||
});
|
|
||||||
view.querySelector('.btnNewPlaylist').addEventListener('click', function () {
|
|
||||||
import('../components/playlisteditor/playlisteditor').then(({ default: PlaylistEditor }) => {
|
|
||||||
const serverId = ApiClient.serverInfo().Id;
|
|
||||||
const playlistEditor = new PlaylistEditor();
|
|
||||||
playlistEditor.show({
|
|
||||||
items: [],
|
|
||||||
serverId: serverId
|
|
||||||
}).catch(() => {
|
|
||||||
// Dialog closed
|
|
||||||
});
|
|
||||||
}).catch(err => {
|
|
||||||
console.error('[btnNewPlaylist] failed to load playlist editor', err);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
onViewStyleChange();
|
|
||||||
}
|
|
||||||
|
|
|
@ -68,7 +68,7 @@ export class UserSettings {
|
||||||
* Set value of setting.
|
* Set value of setting.
|
||||||
* @param {string} name - Name of setting.
|
* @param {string} name - Name of setting.
|
||||||
* @param {mixed} value - Value of setting.
|
* @param {mixed} value - Value of setting.
|
||||||
* @param {boolean} enableOnServer - Flag to save preferences on server.
|
* @param {boolean} [enableOnServer] - Flag to save preferences on server.
|
||||||
*/
|
*/
|
||||||
set(name, value, enableOnServer) {
|
set(name, value, enableOnServer) {
|
||||||
const userId = this.currentUserId;
|
const userId = this.currentUserId;
|
||||||
|
@ -90,7 +90,7 @@ export class UserSettings {
|
||||||
/**
|
/**
|
||||||
* Get value of setting.
|
* Get value of setting.
|
||||||
* @param {string} name - Name of setting.
|
* @param {string} name - Name of setting.
|
||||||
* @param {boolean} enableOnServer - Flag to return preferences from server (cached).
|
* @param {boolean} [enableOnServer] - Flag to return preferences from server (cached).
|
||||||
* @return {string} Value of setting.
|
* @return {string} Value of setting.
|
||||||
*/
|
*/
|
||||||
get(name, enableOnServer) {
|
get(name, enableOnServer) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue