diff --git a/src/components/subtitleeditor/subtitleeditor.js b/src/components/subtitleeditor/subtitleeditor.js index cd1e5ca8e2..980a5ef735 100644 --- a/src/components/subtitleeditor/subtitleeditor.js +++ b/src/components/subtitleeditor/subtitleeditor.js @@ -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) { diff --git a/src/components/subtitleeditor/subtitleeditor.template.html b/src/components/subtitleeditor/subtitleeditor.template.html index 7471972f59..b01942c995 100644 --- a/src/components/subtitleeditor/subtitleeditor.template.html +++ b/src/components/subtitleeditor/subtitleeditor.template.html @@ -18,6 +18,7 @@ + diff --git a/src/components/subtitleuploader/style.css b/src/components/subtitleuploader/style.css new file mode 100644 index 0000000000..c9d5eb980d --- /dev/null +++ b/src/components/subtitleuploader/style.css @@ -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; +} diff --git a/src/components/subtitleuploader/subtitleuploader.js b/src/components/subtitleuploader/subtitleuploader.js new file mode 100644 index 0000000000..1c9b8cfb24 --- /dev/null +++ b/src/components/subtitleuploader/subtitleuploader.js @@ -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 = 'subtitles' + escape(theFile.name) + ''; + + 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 +}; diff --git a/src/components/subtitleuploader/subtitleuploader.template.html b/src/components/subtitleuploader/subtitleuploader.template.html new file mode 100644 index 0000000000..3f4944de57 --- /dev/null +++ b/src/components/subtitleuploader/subtitleuploader.template.html @@ -0,0 +1,45 @@ +
+ +

+ ${HeaderUploadSubtitle} +

+
+ +
+
+ +
+ +
+

${HeaderAddUpdateSubtitle}

+ + +
+
+
+
${LabelDropSubtitleHere}
+ + +
+
+
+
+ +
+
+ +
+ +
+
+
+
+
diff --git a/src/strings/en-us.json b/src/strings/en-us.json index 58a7fbda00..f700c65030 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -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:", diff --git a/src/strings/fr.json b/src/strings/fr.json index 9f22d52e14..51641e01ba 100644 --- a/src/strings/fr.json +++ b/src/strings/fr.json @@ -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 :",