mirror of
https://github.com/jellyfin/jellyfin-web.git
synced 2024-11-16 02:18:16 -07:00
Merge pull request #1212 from redSpoutnik/feature-manually-add-subtitle
Feature: manually add subtitle
This commit is contained in:
commit
b3cdb019c7
@ -352,6 +352,28 @@ function centerFocus(elem, horiz, on) {
|
||||
});
|
||||
}
|
||||
|
||||
function onOpenUploadMenu(e) {
|
||||
const dialog = dom.parentWithClass(e.target, 'subtitleEditorDialog');
|
||||
const selectLanguage = dialog.querySelector('#selectLanguage');
|
||||
const apiClient = ServerConnections.getApiClient(currentItem.ServerId);
|
||||
|
||||
import('../subtitleuploader/subtitleuploader').then(({default: subtitleUploader}) => {
|
||||
subtitleUploader.show({
|
||||
languages: {
|
||||
list: selectLanguage.innerHTML,
|
||||
value: selectLanguage.value
|
||||
},
|
||||
itemId: currentItem.Id,
|
||||
serverId: currentItem.ServerId
|
||||
}).then(function (hasChanged) {
|
||||
if (hasChanged) {
|
||||
hasChanges = true;
|
||||
reload(dialog, apiClient, currentItem.Id);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function showEditorInternal(itemId, serverId, template) {
|
||||
hasChanges = false;
|
||||
|
||||
@ -379,6 +401,8 @@ function showEditorInternal(itemId, serverId, template) {
|
||||
|
||||
dlg.querySelector('.subtitleSearchForm').addEventListener('submit', onSearchSubmit);
|
||||
|
||||
dlg.querySelector('.btnOpenUploadMenu').addEventListener('click', onOpenUploadMenu);
|
||||
|
||||
const btnSubmit = dlg.querySelector('.btnSubmit');
|
||||
|
||||
if (layoutManager.tv) {
|
||||
|
@ -18,6 +18,7 @@
|
||||
<select is="emby-select" id="selectLanguage" required="required" label="${LabelLanguage}" autofocus></select>
|
||||
</div>
|
||||
<button type="submit" is="paper-icon-button-light" title="${Search}" class="btnSearchSubtitles flex-shrink-zero emby-select-iconbutton"><span class="material-icons search"></span></button>
|
||||
<button type="button" is="paper-icon-button-light" title="${Upload}" class="btnOpenUploadMenu flex-shrink-zero emby-select-iconbutton"><span class="material-icons add"></span></button>
|
||||
</div>
|
||||
<button is="emby-button" type="submit" class="raised btnSubmit block button-submit">${Search}</button>
|
||||
</form>
|
||||
|
11
src/components/subtitleuploader/style.css
Normal file
11
src/components/subtitleuploader/style.css
Normal file
@ -0,0 +1,11 @@
|
||||
.subtitleEditor-dropZone {
|
||||
border: 0.2em dashed currentcolor;
|
||||
border-radius: 0.25em;
|
||||
|
||||
text-align: center;
|
||||
position: relative;
|
||||
height: 12em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
172
src/components/subtitleuploader/subtitleuploader.js
Normal file
172
src/components/subtitleuploader/subtitleuploader.js
Normal file
@ -0,0 +1,172 @@
|
||||
import dialogHelper from '../../components/dialogHelper/dialogHelper';
|
||||
import ServerConnections from '../ServerConnections';
|
||||
import dom from '../../scripts/dom';
|
||||
import loading from '../../components/loading/loading';
|
||||
import scrollHelper from '../../libraries/scroller';
|
||||
import layoutManager from '../layoutManager';
|
||||
import globalize from '../../scripts/globalize';
|
||||
import template from './subtitleuploader.template.html';
|
||||
import toast from '../toast/toast';
|
||||
import '../../elements/emby-button/emby-button';
|
||||
import '../../elements/emby-select/emby-select';
|
||||
import '../formdialog.css';
|
||||
import './style.css';
|
||||
|
||||
let currentItemId;
|
||||
let currentServerId;
|
||||
let currentFile;
|
||||
let hasChanges = false;
|
||||
|
||||
function onFileReaderError(evt) {
|
||||
loading.hide();
|
||||
|
||||
const error = evt.target.error;
|
||||
if (error.code !== error.ABORT_ERR) {
|
||||
toast(globalize.translate('MessageFileReadError'));
|
||||
}
|
||||
}
|
||||
|
||||
function isValidSubtitleFile(file) {
|
||||
return file && ['.sub', '.srt', '.vtt', '.ass', '.ssa']
|
||||
.some(function(ext) {
|
||||
return file.name.endsWith(ext);
|
||||
});
|
||||
}
|
||||
|
||||
function setFiles(page, files) {
|
||||
const file = files[0];
|
||||
|
||||
if (!isValidSubtitleFile(file)) {
|
||||
page.querySelector('#subtitleOutput').innerHTML = '';
|
||||
page.querySelector('#fldUpload').classList.add('hide');
|
||||
page.querySelector('#labelDropSubtitle').classList.remove('hide');
|
||||
currentFile = null;
|
||||
return;
|
||||
}
|
||||
|
||||
currentFile = file;
|
||||
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onerror = onFileReaderError;
|
||||
reader.onloadstart = function () {
|
||||
page.querySelector('#fldUpload').classList.add('hide');
|
||||
};
|
||||
reader.onabort = function () {
|
||||
loading.hide();
|
||||
console.debug('File read cancelled');
|
||||
};
|
||||
|
||||
// Closure to capture the file information.
|
||||
reader.onload = (function (theFile) {
|
||||
return function () {
|
||||
// Render file.
|
||||
const html = '<a><i class="material-icons" style="transform: translateY(25%);">subtitles</i><span>' + escape(theFile.name) + '</span><a/>';
|
||||
|
||||
page.querySelector('#subtitleOutput').innerHTML = html;
|
||||
page.querySelector('#fldUpload').classList.remove('hide');
|
||||
page.querySelector('#labelDropSubtitle').classList.add('hide');
|
||||
};
|
||||
})(file);
|
||||
|
||||
// Read in the subtitle file as a data URL.
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
|
||||
function onSubmit(e) {
|
||||
const file = currentFile;
|
||||
|
||||
if (!isValidSubtitleFile(file)) {
|
||||
toast(globalize.translate('MessageSubtitleFileTypeAllowed'));
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
loading.show();
|
||||
|
||||
const dlg = dom.parentWithClass(this, 'dialog');
|
||||
const language = dlg.querySelector('#selectLanguage').value;
|
||||
const isForced = dlg.querySelector('#chkIsForced').checked;
|
||||
|
||||
ServerConnections.getApiClient(currentServerId).uploadItemSubtitle(currentItemId, language, isForced, file).then(function () {
|
||||
dlg.querySelector('#uploadSubtitle').value = '';
|
||||
loading.hide();
|
||||
hasChanges = true;
|
||||
dialogHelper.close(dlg);
|
||||
});
|
||||
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
function initEditor(page) {
|
||||
page.querySelector('.uploadSubtitleForm').addEventListener('submit', onSubmit);
|
||||
page.querySelector('#uploadSubtitle').addEventListener('change', function () {
|
||||
setFiles(page, this.files);
|
||||
});
|
||||
page.querySelector('.btnBrowse').addEventListener('click', function () {
|
||||
page.querySelector('#uploadSubtitle').click();
|
||||
});
|
||||
}
|
||||
|
||||
function showEditor(options, resolve, reject) {
|
||||
options = options || {};
|
||||
currentItemId = options.itemId;
|
||||
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');
|
||||
dlg.classList.add('subtitleUploaderDialog');
|
||||
|
||||
dlg.innerHTML = globalize.translateHtml(template, 'core');
|
||||
|
||||
if (layoutManager.tv) {
|
||||
scrollHelper.centerFocus.on(dlg, false);
|
||||
}
|
||||
|
||||
// Has to be assigned a z-index after the call to .open()
|
||||
dlg.addEventListener('close', function () {
|
||||
if (layoutManager.tv) {
|
||||
scrollHelper.centerFocus.off(dlg, false);
|
||||
}
|
||||
loading.hide();
|
||||
resolve(hasChanges);
|
||||
});
|
||||
|
||||
dialogHelper.open(dlg);
|
||||
|
||||
initEditor(dlg);
|
||||
|
||||
const selectLanguage = dlg.querySelector('#selectLanguage');
|
||||
|
||||
if (options.languages) {
|
||||
selectLanguage.innerHTML = options.languages.list || null;
|
||||
selectLanguage.value = options.languages.value || null;
|
||||
}
|
||||
|
||||
dlg.querySelector('.btnCancel').addEventListener('click', function () {
|
||||
dialogHelper.close(dlg);
|
||||
});
|
||||
}
|
||||
|
||||
export function show(options) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
hasChanges = false;
|
||||
showEditor(options, resolve, reject);
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
show: show
|
||||
};
|
@ -0,0 +1,45 @@
|
||||
<div class="formDialogHeader">
|
||||
<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1"><em class="material-icons arrow_back"></em></button>
|
||||
<h3 class="formDialogHeaderTitle">
|
||||
${HeaderUploadSubtitle}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div class="formDialogContent">
|
||||
<div class="dialogContentInner">
|
||||
|
||||
<form class="uploadSubtitleForm">
|
||||
|
||||
<div class="flex align-items-center" style="margin:1.5em 0;">
|
||||
<h2 style="margin:0;">${HeaderAddUpdateSubtitle}</h2>
|
||||
|
||||
<button is="emby-button" type="button" class="raised raised-mini btnBrowse" style="margin-left:1.5em;">
|
||||
<em class="material-icons">folder</em>
|
||||
<span>${Browse}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<div class="subtitleEditor-dropZone fieldDescription">
|
||||
<div id="labelDropSubtitle">${LabelDropSubtitleHere}</div>
|
||||
<output id="subtitleOutput" class="flex align-items-center justify-content-center" style="position: absolute;top:0;left:0;right:0;bottom:0;width:100%;"></output>
|
||||
<input type="file" accept=".sub,.srt,.vtt,.ass,.ssa" id="uploadSubtitle" name="uploadSubtitle" style="position: absolute;top:0;left:0;right:0;bottom:0;width:100%;opacity:0;"/>
|
||||
</div>
|
||||
<div id="fldUpload" class="hide">
|
||||
<br />
|
||||
<div class="checkboxContainer">
|
||||
<label>
|
||||
<input type="checkbox" is="emby-checkbox" id="chkIsForced" />
|
||||
<span>${LabelIsForced}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="selectContainer flex-grow">
|
||||
<select is="emby-select" id="selectLanguage" required="required" label="${LabelLanguage}" autofocus></select>
|
||||
</div>
|
||||
<button is="emby-button" type="submit" class="raised button-submit block">
|
||||
<span>${Upload}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
@ -264,6 +264,8 @@
|
||||
"HeaderAddToCollection": "Add to Collection",
|
||||
"HeaderAddToPlaylist": "Add to Playlist",
|
||||
"HeaderAddUpdateImage": "Add/Update Image",
|
||||
"HeaderAddUpdateSubtitle": "Add/Update Subtitle",
|
||||
"HeaderAddUser": "Add User",
|
||||
"HeaderAdditionalParts": "Additional Parts",
|
||||
"HeaderAdmin": "Admin",
|
||||
"HeaderAlbumArtists": "Album Artists",
|
||||
@ -433,6 +435,7 @@
|
||||
"HeaderTypeText": "Enter Text",
|
||||
"HeaderUpcomingOnTV": "Upcoming On TV",
|
||||
"HeaderUploadImage": "Upload Image",
|
||||
"HeaderUploadSubtitle": "Upload Subtitle",
|
||||
"HeaderUser": "User",
|
||||
"HeaderUsers": "Users",
|
||||
"HeaderVideoQuality": "Video Quality",
|
||||
@ -546,6 +549,7 @@
|
||||
"LabelDropImageHere": "Drop image here, or click to browse.",
|
||||
"LabelDroppedFrames": "Dropped frames:",
|
||||
"LabelDropShadow": "Drop shadow:",
|
||||
"LabelDropSubtitleHere": "Drop subtitle here, or click to browse.",
|
||||
"LabelDynamicExternalId": "{0} Id:",
|
||||
"LabelEasyPinCode": "Easy pin code:",
|
||||
"LabelEmbedAlbumArtDidl": "Embed album art in Didl",
|
||||
@ -607,6 +611,7 @@
|
||||
"LabelInNetworkSignInWithEasyPassword": "Enable in-network sign in with my easy pin code",
|
||||
"LabelInNetworkSignInWithEasyPasswordHelp": "Use the easy pin code to sign in to clients within your local network. Your regular password will only be needed away from home. If the pin code is left blank, you won't need a password within your home network.",
|
||||
"LabelInternetQuality": "Internet quality:",
|
||||
"LabelIsForced": "Forced",
|
||||
"LabelKeepUpTo": "Keep up to:",
|
||||
"LabelKnownProxies": "Known proxies:",
|
||||
"LabelKidsCategories": "Children's categories:",
|
||||
|
@ -223,6 +223,8 @@
|
||||
"HeaderAddToCollection": "Ajouter à la collection",
|
||||
"HeaderAddToPlaylist": "Ajouter à la liste de lecture",
|
||||
"HeaderAddUpdateImage": "Ajouter/Mettre à jour une image",
|
||||
"HeaderAddUpdateSubtitle": "Ajouter/Mettre à jour des sous-titres",
|
||||
"HeaderAddUser": "Ajouter un utilisateur",
|
||||
"HeaderAdditionalParts": "Parties additionelles",
|
||||
"HeaderAdmin": "Administrateur",
|
||||
"HeaderAlbumArtists": "Artistes",
|
||||
@ -381,6 +383,7 @@
|
||||
"HeaderTypeText": "Entrer texte",
|
||||
"HeaderUpcomingOnTV": "Prochainement à la TV",
|
||||
"HeaderUploadImage": "Envoyer une image",
|
||||
"HeaderUploadSubtitle": "Envoyer des sous-titres",
|
||||
"HeaderUser": "Utilisateur",
|
||||
"HeaderUsers": "Utilisateurs",
|
||||
"HeaderVideoQuality": "Qualité vidéo",
|
||||
@ -478,6 +481,7 @@
|
||||
"LabelDownloadLanguages": "Téléchargement des langues :",
|
||||
"LabelDropImageHere": "Faites glisser l'image ici, ou cliquez pour parcourir vos fichiers.",
|
||||
"LabelDropShadow": "Ombre portée :",
|
||||
"LabelDropSubtitleHere": "Faites glisser les sous-tires ici, ou cliquez pour parcourir vos fichiers.",
|
||||
"LabelDynamicExternalId": "ID {0} :",
|
||||
"LabelEasyPinCode": "Code Easy PIN :",
|
||||
"LabelEmbedAlbumArtDidl": "Intégrer les images d'album dans le DIDL",
|
||||
@ -532,6 +536,7 @@
|
||||
"LabelInNetworkSignInWithEasyPassword": "Activer l'authentification simplifiée dans les réseaux domestiques avec mon code Easy PIN",
|
||||
"LabelInNetworkSignInWithEasyPasswordHelp": "Utilisez votre code Easy PIN pour vous connecter aux applications depuis l'intérieur de votre réseau local. Votre mot de passe habituel ne sera requis que depuis l'extérieur. Si le code PIN n'est pas défini, vous n'aurez pas besoin de mot de passe depuis l'intérieur de votre réseau local.",
|
||||
"LabelInternetQuality": "Qualité d'internet :",
|
||||
"LabelIsForced": "Forcés",
|
||||
"LabelKeepUpTo": "Garder jusqu'à :",
|
||||
"LabelKidsCategories": "Catégories jeunesse :",
|
||||
"LabelKodiMetadataDateFormat": "Format de la date de sortie :",
|
||||
|
Loading…
Reference in New Issue
Block a user