mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Merge pull request #3453 from dmitrylyzo/mediainfo-copy
Add copy buttons to Media Info
This commit is contained in:
commit
a96dff3c52
6 changed files with 107 additions and 30 deletions
|
@ -992,6 +992,10 @@ div.itemDetailGalleryLink.defaultCardBackground {
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mediaInfoContent .btnCopy .material-icons {
|
||||||
|
font-size: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
.mediaInfoStream {
|
.mediaInfoStream {
|
||||||
margin: 0 3em 0 0;
|
margin: 0 3em 0 0;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
@ -1000,6 +1004,10 @@ div.itemDetailGalleryLink.defaultCardBackground {
|
||||||
|
|
||||||
.mediaInfoStreamType {
|
.mediaInfoStreamType {
|
||||||
display: block;
|
display: block;
|
||||||
|
margin: 0.622em 0; /* copy button height compensation */
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-tv .mediaInfoStreamType {
|
||||||
margin: 1em 0;
|
margin: 1em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import browser from '../scripts/browser';
|
import browser from '../scripts/browser';
|
||||||
|
import { copy } from '../scripts/clipboard';
|
||||||
import globalize from '../scripts/globalize';
|
import globalize from '../scripts/globalize';
|
||||||
import actionsheet from './actionSheet/actionSheet';
|
import actionsheet from './actionSheet/actionSheet';
|
||||||
import { appHost } from './apphost';
|
import { appHost } from './apphost';
|
||||||
|
@ -366,32 +367,11 @@ import toast from './toast/toast';
|
||||||
break;
|
break;
|
||||||
case 'copy-stream': {
|
case 'copy-stream': {
|
||||||
const downloadHref = apiClient.getItemDownloadUrl(itemId);
|
const downloadHref = apiClient.getItemDownloadUrl(itemId);
|
||||||
const textAreaCopy = function () {
|
copy(downloadHref).then(() => {
|
||||||
const textArea = document.createElement('textarea');
|
toast(globalize.translate('CopyStreamURLSuccess'));
|
||||||
textArea.value = downloadHref;
|
}).catch(() => {
|
||||||
document.body.appendChild(textArea);
|
prompt(globalize.translate('CopyStreamURL'), downloadHref);
|
||||||
textArea.focus();
|
});
|
||||||
textArea.select();
|
|
||||||
|
|
||||||
if (document.execCommand('copy')) {
|
|
||||||
toast(globalize.translate('CopyStreamURLSuccess'));
|
|
||||||
} else {
|
|
||||||
prompt(globalize.translate('CopyStreamURL'), downloadHref);
|
|
||||||
}
|
|
||||||
document.body.removeChild(textArea);
|
|
||||||
};
|
|
||||||
|
|
||||||
/* eslint-disable-next-line compat/compat */
|
|
||||||
if (navigator.clipboard === undefined) {
|
|
||||||
textAreaCopy();
|
|
||||||
} else {
|
|
||||||
/* eslint-disable-next-line compat/compat */
|
|
||||||
navigator.clipboard.writeText(downloadHref).then(function () {
|
|
||||||
toast(globalize.translate('CopyStreamURLSuccess'));
|
|
||||||
}).catch(function () {
|
|
||||||
textAreaCopy();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
getResolveFunction(resolve, id)();
|
getResolveFunction(resolve, id)();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,9 @@
|
||||||
|
|
||||||
import dialogHelper from '../dialogHelper/dialogHelper';
|
import dialogHelper from '../dialogHelper/dialogHelper';
|
||||||
import layoutManager from '../layoutManager';
|
import layoutManager from '../layoutManager';
|
||||||
|
import toast from '../toast/toast';
|
||||||
|
import { copy } from '../../scripts/clipboard';
|
||||||
|
import dom from '../../scripts/dom';
|
||||||
import globalize from '../../scripts/globalize';
|
import globalize from '../../scripts/globalize';
|
||||||
import loading from '../loading/loading';
|
import loading from '../loading/loading';
|
||||||
import '../../elements/emby-select/emby-select';
|
import '../../elements/emby-select/emby-select';
|
||||||
|
@ -19,6 +22,12 @@ import '../../assets/css/flexstyles.scss';
|
||||||
import ServerConnections from '../ServerConnections';
|
import ServerConnections from '../ServerConnections';
|
||||||
import template from './itemMediaInfo.template.html';
|
import template from './itemMediaInfo.template.html';
|
||||||
|
|
||||||
|
// Do not add extra spaces between tags - they will be copied into the result
|
||||||
|
const copyButtonHtml = layoutManager.tv ? '' :
|
||||||
|
`<button is="paper-icon-button-light" class="btnCopy" title="${globalize.translate('Copy')}" aria-label="${globalize.translate('Copy')}"
|
||||||
|
><span class="material-icons content_copy" aria-hidden="true"></span></button>`;
|
||||||
|
const attributeDelimiterHtml = layoutManager.tv ? '' : '<span class="hide">: </span>';
|
||||||
|
|
||||||
function setMediaInfo(user, page, item) {
|
function setMediaInfo(user, page, item) {
|
||||||
let html = item.MediaSources.map(version => {
|
let html = item.MediaSources.map(version => {
|
||||||
return getMediaSourceHtml(user, item, version);
|
return getMediaSourceHtml(user, item, version);
|
||||||
|
@ -28,12 +37,25 @@ import template from './itemMediaInfo.template.html';
|
||||||
}
|
}
|
||||||
const mediaInfoContent = page.querySelector('#mediaInfoContent');
|
const mediaInfoContent = page.querySelector('#mediaInfoContent');
|
||||||
mediaInfoContent.innerHTML = html;
|
mediaInfoContent.innerHTML = html;
|
||||||
|
|
||||||
|
for (const btn of mediaInfoContent.querySelectorAll('.btnCopy')) {
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
const infoBlock = dom.parentWithClass(btn, 'mediaInfoStream') || dom.parentWithClass(btn, 'mediaInfoSource') || mediaInfoContent;
|
||||||
|
|
||||||
|
copy(infoBlock.textContent).then(() => {
|
||||||
|
toast(globalize.translate('Copied'));
|
||||||
|
}).catch(() => {
|
||||||
|
console.error('Could not copy text');
|
||||||
|
toast(globalize.translate('CopyFailed'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMediaSourceHtml(user, item, version) {
|
function getMediaSourceHtml(user, item, version) {
|
||||||
let html = '';
|
let html = '<div class="mediaInfoSource">';
|
||||||
if (version.Name) {
|
if (version.Name) {
|
||||||
html += `<div><h2 class="mediaInfoStreamType">${version.Name}</h2></div>`;
|
html += `<div><h2 class="mediaInfoStreamType">${version.Name}${copyButtonHtml}</h2></div>\n`;
|
||||||
}
|
}
|
||||||
if (version.Container) {
|
if (version.Container) {
|
||||||
html += `${createAttribute(globalize.translate('MediaInfoContainer'), version.Container)}<br/>`;
|
html += `${createAttribute(globalize.translate('MediaInfoContainer'), version.Container)}<br/>`;
|
||||||
|
@ -69,7 +91,7 @@ import template from './itemMediaInfo.template.html';
|
||||||
}
|
}
|
||||||
|
|
||||||
const displayType = globalize.translate(translateString);
|
const displayType = globalize.translate(translateString);
|
||||||
html += `<h2 class="mediaInfoStreamType">${displayType}</h2>`;
|
html += `\n<h2 class="mediaInfoStreamType">${displayType}${copyButtonHtml}</h2>\n`;
|
||||||
const attributes = [];
|
const attributes = [];
|
||||||
if (stream.DisplayTitle) {
|
if (stream.DisplayTitle) {
|
||||||
attributes.push(createAttribute(globalize.translate('MediaInfoTitle'), stream.DisplayTitle));
|
attributes.push(createAttribute(globalize.translate('MediaInfoTitle'), stream.DisplayTitle));
|
||||||
|
@ -154,11 +176,12 @@ import template from './itemMediaInfo.template.html';
|
||||||
html += attributes.join('<br/>');
|
html += attributes.join('<br/>');
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
}
|
}
|
||||||
|
html += '</div>';
|
||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createAttribute(label, value) {
|
function createAttribute(label, value) {
|
||||||
return `<span class="mediaInfoLabel">${label}</span><span class="mediaInfoAttribute">${value}</span>`;
|
return `<span class="mediaInfoLabel">${label}</span>${attributeDelimiterHtml}<span class="mediaInfoAttribute">${value}</span>\n`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadMediaInfo(itemId, serverId) {
|
function loadMediaInfo(itemId, serverId) {
|
||||||
|
|
60
src/scripts/clipboard.js
Normal file
60
src/scripts/clipboard.js
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
import browser from './browser';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copies text to the clipboard using the textarea.
|
||||||
|
* @param {string} text - Text to copy.
|
||||||
|
* @returns {Promise<void>} Promise.
|
||||||
|
*/
|
||||||
|
function textAreaCopy(text) {
|
||||||
|
const textArea = document.createElement('textarea');
|
||||||
|
textArea.value = text;
|
||||||
|
document.body.appendChild(textArea);
|
||||||
|
textArea.focus();
|
||||||
|
|
||||||
|
// iOS 13.4 supports Clipboard.writeText (https://stackoverflow.com/a/61868028)
|
||||||
|
if (browser.iOS && browser.iOSVersion < 13.4) {
|
||||||
|
// https://stackoverflow.com/a/46858939
|
||||||
|
|
||||||
|
const range = document.createRange();
|
||||||
|
range.selectNodeContents(textArea);
|
||||||
|
const selection = window.getSelection();
|
||||||
|
selection.removeAllRanges();
|
||||||
|
selection.addRange(range);
|
||||||
|
textArea.setSelectionRange(0, 999999);
|
||||||
|
} else {
|
||||||
|
textArea.select();
|
||||||
|
}
|
||||||
|
|
||||||
|
let ret;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (document.execCommand('copy')) {
|
||||||
|
ret = Promise.resolve();
|
||||||
|
} else {
|
||||||
|
ret = Promise.reject();
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
ret = Promise.reject();
|
||||||
|
}
|
||||||
|
|
||||||
|
document.body.removeChild(textArea);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copies text to the clipboard.
|
||||||
|
* @param {string} text - Text to copy.
|
||||||
|
* @returns {Promise<void>} Promise.
|
||||||
|
*/
|
||||||
|
export function copy(text) {
|
||||||
|
/* eslint-disable-next-line compat/compat */
|
||||||
|
if (navigator.clipboard === undefined) {
|
||||||
|
return textAreaCopy(text);
|
||||||
|
} else {
|
||||||
|
/* eslint-disable-next-line compat/compat */
|
||||||
|
return navigator.clipboard.writeText(text).catch(() => {
|
||||||
|
return textAreaCopy(text);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -148,6 +148,9 @@
|
||||||
"Console": "Console",
|
"Console": "Console",
|
||||||
"ContinueWatching": "Continue watching",
|
"ContinueWatching": "Continue watching",
|
||||||
"Continuing": "Continuing",
|
"Continuing": "Continuing",
|
||||||
|
"Copied": "Copied",
|
||||||
|
"Copy": "Copy",
|
||||||
|
"CopyFailed": "Could not copy",
|
||||||
"CopyStreamURL": "Copy Stream URL",
|
"CopyStreamURL": "Copy Stream URL",
|
||||||
"CopyStreamURLSuccess": "URL copied successfully.",
|
"CopyStreamURLSuccess": "URL copied successfully.",
|
||||||
"CriticRating": "Critics rating",
|
"CriticRating": "Critics rating",
|
||||||
|
|
|
@ -121,6 +121,9 @@
|
||||||
"Connect": "Соединиться",
|
"Connect": "Соединиться",
|
||||||
"ContinueWatching": "Продолжение просмотра",
|
"ContinueWatching": "Продолжение просмотра",
|
||||||
"Continuing": "Продолжаемое",
|
"Continuing": "Продолжаемое",
|
||||||
|
"Copied": "Скопировано",
|
||||||
|
"Copy": "Копировать",
|
||||||
|
"CopyFailed": "Не удалось скопировать",
|
||||||
"CriticRating": "Оценка критиков",
|
"CriticRating": "Оценка критиков",
|
||||||
"CustomDlnaProfilesHelp": "Создайте настраиваемый профиль, назначаемый для нового устройства или переопределите системный профиль.",
|
"CustomDlnaProfilesHelp": "Создайте настраиваемый профиль, назначаемый для нового устройства или переопределите системный профиль.",
|
||||||
"DateAdded": "Дата добавления",
|
"DateAdded": "Дата добавления",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue