diff --git a/Makefile.PL b/Makefile.PL index 965b185..f39cee4 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -21,7 +21,8 @@ package MY { my $return = $self->SUPER::top_targets(@_); $return = [ split /\n/, $return ]; for my $i ( keys @$return ) { - $return->[$i] .= ' install_config' if $return->[$i] =~ /^all :/; + $return->[$i] .= ' install_config build_frontend install_frontend' + if $return->[$i] =~ /^all :/; } return join "\n", @$return; } @@ -42,6 +43,12 @@ THEME = ' . ( $config->{theme} // 'default' ) . "\n"; sub postamble { return "\n" + . "install_frontend : build_frontend\n" + . "\tif [ ! -e lib/Peertube/DL/public/ ];" + . " then mkdir -pv lib/Peertube/DL/public/;" . " fi\n" + . "\tcp -rfv themes/\${THEME}/public/dist/* lib/Peertube/DL/public/\n" + . "build_frontend :\n" + . "\tcd themes/\${THEME}/public && yarn build\n" . "install_config :\n" . "\tinstall peertube-dl-web.conf bin/peertube-dl-web.conf\n" . "src: src/Makefile\n" diff --git a/lib/Peertube/DL/public/css/index.css b/lib/Peertube/DL/public/css/index.css index fa83ef8..d36581e 100644 --- a/lib/Peertube/DL/public/css/index.css +++ b/lib/Peertube/DL/public/css/index.css @@ -29,6 +29,10 @@ a:hover,a:focus { border: black 1px solid; } +#modal-video-container.active { + display: block; +} + .video-container-bar { display: flex; justify-content: right; @@ -245,6 +249,7 @@ h2 { text-decoration: none; color: black; background: #eee; + overflow-wrap: anywhere; } .format-list > div > a:hover { diff --git a/lib/Peertube/DL/public/js/peertube-dl-web.js b/lib/Peertube/DL/public/js/peertube-dl-web.js index a66965a..f035de2 100644 --- a/lib/Peertube/DL/public/js/peertube-dl-web.js +++ b/lib/Peertube/DL/public/js/peertube-dl-web.js @@ -1,200 +1,2 @@ -"use strict"; - -let downloadFormButton; -let modalVideoContainer; -let closeAndResetVideoContainer; -let downloadFormUrl; -let downloadVideo; -let downloadVideoPrepare; -let downloadVideoLoading; -let downloadForm; -let video; -let modalLoading; -let filename; -let url_video; -let popingNotice; -let popingNoticeContent; -let closePopingNotice; -let titleModalFormatSelector; -let descriptionModalFormatSelector; -let modalFormatSelector; -let videoFormats; -let audioFormats; -let closeModalFormatSelector; - -function downloadFormButtonHandler(event) { - event.preventDefault(); - modalLoading.classList.add('active'); - askForURL(downloadFormUrl.value); -} -function askForURL(url, format) { - getRealURL(url, format).then( (response) => { - if ( response.options !== undefined && response.options.list_formats !== undefined && response.options.list_formats ) { - titleModalFormatSelector.innerText = response.title; - descriptionModalFormatSelector.innerText = response.description; - videoFormats.innerHTML = ''; - audioFormats.innerHTML = ''; - for ( let x of response.formats.audio_formats) { - let a = document.createElement('a'); - a.innerText = 'Id: ' + x.id + "\n" - + 'Format: ' + x.mimeType + "\n" - + 'AudioSampleRate: ' + x.audioSampleRate + "\n" - + 'Bitrate: ' + x.bitrate + ".\n"; - a.addEventListener( 'click', (event) => { - modalLoading.classList.add('active'); - askForURL(url, x.id ); - }); - audioFormats.appendChild(a); - modalLoading.classList.remove('active'); - modalFormatSelector.classList.add('active'); - } - for ( let x of response.formats.video_formats ) { - let a = document.createElement('a'); - a.innerText = 'Id: ' + x.id + "\n" - + 'Format: ' + x.mimeType + "\n" - + 'QualityLabel: ' + x.qualityLabel + "p\n" - + 'Bitrate: ' + x.bitrate + "\n" - + ( - ( x.audioSampleRate !== undefined ) ? - 'AudioSampleRate: ' + x.audioSampleRate + ".\n" : - "No audio." - ); - a.addEventListener( 'click', (event) => { - modalLoading.classList.add('active'); - askForURL(url, x.id ); - }); - videoFormats.appendChild(a); - modalLoading.classList.remove('active'); - modalFormatSelector.classList.add('active'); - } - } else { - video.src = response.url; - filename = response.filename; - url_video = response.url; - video.addEventListener('canplay', (event) => { - modalLoading.classList.remove('active'); - modalVideoContainer.style.display = 'block'; - }); - } - return true; - }).catch ( (error) => { - console.log(error); - modalLoading.classList.remove('active'); - popingNoticeContent.innerHTML = ''; - let p = document.createElement('p'); - p.appendChild(document.createTextNode('The url ')); - let input_url = document.createElement('a'); - input_url.href = url; - input_url.innerText = url; - p.appendChild(input_url) - p.appendChild(document.createTextNode(' is not supported, if you think this is an error, report it ')); - let issues_url = 'https://gitea.sergiotarxz.freemyip.com/sergiotarxz/Peertube-dl/issues'; - let issues_url_element = document.createElement('a'); - issues_url_element.href = issues_url; - issues_url_element.innerText = 'here'; - p.appendChild(issues_url_element); - p.appendChild(document.createTextNode('.')); - popingNoticeContent.appendChild(p); - popingNotice.classList.add('active'); - return false; - }); -} - -function downloadVideoPrepareHandler(event) { - downloadVideoPrepare.classList.remove('active'); - downloadVideoLoading.classList.add('active'); - generateBlobVideo(url_video).then( blob => { - downloadVideo.href = URL.createObjectURL(blob); - downloadVideo.download = filename; - downloadVideoLoading.classList.remove('active'); - downloadVideo.classList.add('active'); - }); -} - -function downloadFormHandler(event) { - downloadFormButtonHandler(event); -} - -async function generateBlobVideo(url) { - const blob = await fetch(url, { - mode: 'nocors', - }) - .then(res => res.blob()) - .catch( err => generateBlobVideoByProxy(url) ); - return blob; -} - -async function generateBlobVideoByProxy(url) { - const blob = await fetch( '/proxy_to_get', { - method: 'POST', - mode: 'cors', - cache: 'no-cache', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({url: url}), - } - ).then(res => res.blob()); - return blob; - } - -async function getRealURL(url, format) { - let request = { url: url }; - if (format !== undefined) - request.format = format; - const response = await fetch('/api', { - method: 'POST', - mode: 'cors', - cache: 'no-cache', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(request), - }); - return response.json(); -} - -function closeAndResetVideoContainerHandler(event) { - event.preventDefault(); - modalVideoContainer.style.display = 'none'; - downloadVideo.classList.remove('active'); - downloadVideoLoading.classList.remove('active'); - downloadVideoPrepare.classList.add('active'); -} - -window.addEventListener('load', (event) => { - downloadFormButton = document.querySelector('#download-form-button'); - downloadForm = document.querySelector('#download-form'); - modalVideoContainer = document.querySelector('#modal-video-container'); - video = document.querySelector('#video'); - downloadFormUrl = document.querySelector('#download-form-url'); - downloadVideo = document.querySelector('#download-video'); - downloadVideoLoading = document.querySelector('#download-video-loading'); - downloadVideoPrepare = document.querySelector('#download-video-prepare'); - closeAndResetVideoContainer = document.querySelector('#close-and-reset-video-container'); - modalLoading = document.querySelector('#modal-loading'); - popingNotice = document.querySelector('#poping-notice'); - popingNoticeContent = document.querySelector('#poping-notice-content'); - closePopingNotice = document.querySelector('#close-poping-notice'); - titleModalFormatSelector = document.querySelector('#modal-format-selector h2'); - descriptionModalFormatSelector = document.querySelector('#modal-format-selector p'); - modalFormatSelector = document.querySelector('#modal-format-selector'); - videoFormats = document.querySelector('#modal-format-selector .video-formats'); - audioFormats = document.querySelector('#modal-format-selector .audio-formats'); - closeModalFormatSelector = document.querySelector('#close-modal-format-selector'); - - downloadFormButton.addEventListener('click', downloadFormButtonHandler); - downloadVideoPrepare.addEventListener('click', downloadVideoPrepareHandler); - downloadForm.addEventListener('submit', downloadFormHandler); - closeAndResetVideoContainer.addEventListener('click', closeAndResetVideoContainerHandler); - closeModalFormatSelector.addEventListener('click', (event) => { - modalFormatSelector.classList.remove('active'); - }); - closePopingNotice.addEventListener('click', (event) => { - popingNotice.classList.remove('active'); - }); - - popingNotice.classList.add('active'); -}); - - +(()=>{"use strict";class e{constructor(){this.query_selector="#poping-notice",this.closePopingNotice.addEventListener("click",(e=>{this.setVisible(!1)}))}setVisible(e){e?this.element.classList.add("active"):this.element.classList.remove("active")}setMessage(e){if(!e instanceof Array)throw"Message is not instance of Array.";let t=document.createElement("p");for(let o of e)if("string"==typeof o||o instanceof String)o=document.createTextNode(o),t.appendChild(o);else{if(!(o instanceof Node))throw"Node is not a instance of Node nor a String";t.appendChild(o)}this.popingNoticeContent.innerHTML="",this.popingNoticeContent.appendChild(t)}get querySelector(){return this.query_selector}get element(){return document.querySelector(this.querySelector)}get popingNoticeContent(){return this.element.querySelector("#poping-notice-content")}get closePopingNotice(){return this.element.querySelector("#close-poping-notice")}}class t{constructor(e){this.query_selector="#download-form",this.callback=t=>{t.preventDefault(),e(this.downloadFormUrl.value)},this.addEventListeners()}addEventListeners(){this.downloadFormButton.addEventListener("click",this.callback),this.element.addEventListener("submit",this.callback)}get downloadFormButton(){return this.element.querySelector("#download-form-button")}get downloadFormUrl(){return this.element.querySelector("#download-form-url")}get querySelector(){return this.query_selector}get element(){return document.querySelector(this.querySelector)}}class o{constructor(){this.query_selector="#modal-loading"}setVisible(e){e?this.element.classList.add("active"):this.element.classList.remove("active")}get element(){return document.querySelector(this.querySelector)}get querySelector(){return this.query_selector}}class i{constructor(){this.query_selector="#modal-video-container",this.addEventListeners()}setVisible(e){e?this.element.classList.add("active"):(this.element.classList.remove("active"),this.downloadVideoPrepare.classList.add("active"),this.downloadVideoLoading.classList.remove("active"),this.downloadVideo.classList.remove("active"))}addEventListeners(){this.downloadVideoPrepare.addEventListener("click",this.downloadPrepareHandler.bind(this)),this.closeAndResetVideoContainer.addEventListener("click",(e=>{this.setVisible(!1)}))}downloadPrepareHandler(e){this.downloadVideoPrepare.classList.remove("active"),this.downloadVideoLoading.classList.add("active"),this.generateBlobVideo(this.URLVideo).then((e=>{this.downloadVideo.href=URL.createObjectURL(e),this.downloadVideo.download=this.filename,this.downloadVideoLoading.classList.remove("active"),this.downloadVideo.classList.add("active")}))}async generateBlobVideo(e){return await fetch(e,{mode:"cors"}).then((e=>e.blob())).catch((t=>this.generateBlobVideoByProxy(e)))}async generateBlobVideoByProxy(e){return await fetch("/proxy_to_get",{method:"POST",mode:"cors",cache:"no-cache",headers:{"Content-Type":"application/json"},body:JSON.stringify({url:e})}).then((e=>e.blob()))}onCanPlay(e){video.addEventListener("canplay",(t=>{e()}))}setURLVideo(e){this.url_video=e,video.src=e}setFilename(e){this.filename=e}get closeAndResetVideoContainer(){return this.element.querySelector("#close-and-reset-video-container")}get downloadVideo(){return this.element.querySelector("#download-video")}get URLVideo(){return this.url_video}get closeAndResetVideoContainer(){return this.element.querySelector("#close-and-reset-video-container")}get downloadVideoLoading(){return this.element.querySelector("#download-video-loading")}get downloadVideoPrepare(){return this.element.querySelector("#download-video-prepare")}get video(){return this.element.querySelector("#video")}get element(){return document.querySelector(this.querySelector)}get querySelector(){return this.query_selector}}class r{constructor(){this.query_selector="#modal-format-selector",this.addEventListeners()}appendFormat(e,t,o,i){let r=document.createElement("a");r.innerText=o?"Id: "+t.id+"\nFormat: "+t.mimeType+"\nQualityLabel: "+t.qualityLabel+"p\nBitrate: "+t.bitrate+"\n"+(void 0!==t.audioSampleRate?"AudioSampleRate: "+t.audioSampleRate+".\n":"No audio."):"Id: "+t.id+"\nFormat: "+t.mimeType+"\nAudioSampleRate: "+t.audioSampleRate+"\nBitrate: "+t.bitrate+".\n",r.addEventListener("click",(e=>{i(t.id)})),e.appendChild(r)}prepareFormatSelector(e,t,o,i,r){this.titleFormatSelector.innerText=e,this.descriptionFormatSelector.innerText=t,this.videoFormats.innerHTML="",this.audioFormats.innerHTML="";for(let e of o)this.appendFormat(this.audioFormats,e,!1,r);for(let e of i)this.appendFormat(this.videoFormats,e,!0,r)}setVisible(e){e?this.element.classList.add("active"):this.element.classList.remove("active")}addEventListeners(){this.closeFormatSelector.addEventListener("click",(e=>{this.setVisible(!1)}))}get videoFormats(){return this.element.querySelector(".video-formats")}get audioFormats(){return this.element.querySelector(".audio-formats")}get titleFormatSelector(){return this.element.querySelector("h2")}get descriptionFormatSelector(){return this.element.querySelector("p")}get closeFormatSelector(){return this.element.querySelector("#close-modal-format-selector")}get element(){return document.querySelector(this.querySelector)}get querySelector(){return this.query_selector}}class s{constructor(){this.poping_notice=new e,this.download_form=new t(this.onDownloadFormGot.bind(this)),this.loading_modal=new o,this.video_container=new i,this.format_selector=new r}init(){this.popingNotice.setVisible(!0)}onDownloadFormGot(e){this.dispatchURL(e)}dispatchURL(e,t){let o;this.loadingModal.setVisible(!0),this.queryAPI(e,t).then((t=>{if(void 0!==t.options&&void 0!==t.options.list_formats&&t.options.list_formats){if(void 0===t.formats||void 0===t.formats.audio_formats||void 0===t.formats.video_formats)throw"Format object is not valid.";this.formatSelector.prepareFormatSelector(t.title,t.description,t.formats.audio_formats,t.formats.video_formats,(t=>{this.loadingModal.setVisible(!0),this.dispatchURL(e,t)})),this.formatSelector.setVisible(!0),this.loadingModal.setVisible(!1)}else this.videoContainer.onCanPlay(this.onCanPlayVideoContainer.bind(this)),this.videoContainer.setURLVideo(t.url),this.videoContainer.setFilename(t.filename)})).catch((t=>{o=t.toString(),this.loadingModal.setVisible(!1);let i=document.createElement("a");i.href=e,i.innerText=e;let r=document.createElement("a");r.href="https://gitea.sergiotarxz.freemyip.com/sergiotarxz/Peertube-dl/issues",r.innerText="here",this.popingNotice.setMessage(["The url ",i," is not supported, the error was: ",o," if you think this is an error, report it ",r,"."]),this.popingNotice.setVisible(!0)}))}onCanPlayVideoContainer(){this.videoContainer.setVisible(!0),this.loadingModal.setVisible(!1)}async queryAPI(e,t){let o={url:e};return void 0!==t&&(o.format=t),(await fetch("/api",{method:"POST",mode:"cors",cache:"no-cache",headers:{"Content-Type":"application/json"},body:JSON.stringify(o)})).json()}get formatSelector(){return this.format_selector}get videoContainer(){return this.video_container}get downloadForm(){return this.download_form}get popingNotice(){return this.poping_notice}get loadingModal(){return this.loading_modal}}window.addEventListener("load",(e=>{(new s).init()}))})(); +//# sourceMappingURL=peertube-dl-web.js.map \ No newline at end of file diff --git a/themes/default/public/dist/css/index.css b/themes/default/public/dist/css/index.css new file mode 100644 index 0000000..d36581e --- /dev/null +++ b/themes/default/public/dist/css/index.css @@ -0,0 +1,274 @@ +body { + height: 99.9%; + margin: 0; + padding: 0; +} + +a { + color: blue; +} + +a:hover,a:focus { + text-decoration: underline; +} + +#video { + width: 100%; + margin: 3px; +} + +#modal-video-container { + display: none; + background: white; + position: fixed; + top: 50%; + left: 50%; + height: 100%; + width: 100%; + transform: translate(-50%, -50%); + border: black 1px solid; +} + +#modal-video-container.active { + display: block; +} + +.video-container-bar { + display: flex; + justify-content: right; +} + +#close-and-reset-video-container { + margin-top: 0.25rem; + margin-right: 0.25rem; + border: 1px solid black; +} + +#close-and-reset-video-container:hover:#close-and-reset-video-container:focus { + background: black; + color: white; +} + +#download-video-container { + margin: 10px; + justify-content: center; +} + +#download-video-container a { + display: none; + padding-left: 5px; + padding-right: 5px; + padding-top: 5px; + padding-bottom: 5px; + width: 100%; + background: #0a0; + border-radius: 5px; + font-size: 30px; + color: white; + height: 30px; +} + +#download-video-container a.active { + display: block; +} + +#download-video-container a embed { + height: 30px; +} + +#video-container { + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + height: 100%; +} + +.block { + display: block; +} + +.application-container { + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + text-align: center; + height: 100%; + background: #000; + color: white; +} + +#download-form { + display: flex; + justify-content: center; + flex-direction: column; +} + +#download-form-button { + margin-top: 5px; + height: 50px; + font-size: 1.5rem; + background: #fff; + color: black; + border: none; +} + +h2 { + font-size: 2rem; +} + +#modal-loading { + display: none; + position: fixed; + top: 50%; + left: 50%; + height: 100%; + width: 100%; + transform: translate(-50%, -50%); + border: black 1px solid; + justify-content: right; + align-items: center; +} + +#modal-loading.active { + display: flex; +} + +#modal-loading embed { + width: 10%; +} + +#poping-notice { + display: none; + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + border: black 1px solid; + width: 91%; + background: white; + padding: 10px; + border-radius: 15px; + max-height: 95%; + overflow-y: scroll; +} + +#poping-notice.active { + display: block; +} + +#poping-notice-container-bar { + display: flex; + justify-content: center; + font-size: 5rem; +} + +#close-poping-notice { + width: 150px; + height: 150px; + align-items: center; + display: flex; + text-align: center; + justify-content: center; + border-radius: 50%; + background: #0f0; + color: black; + text-decoration: none; +} + +#close-poping-notice:hover,#close-poping-notice:focus { + background: black; + color: white; +} + +#modal-format-selector { + display: none; + background: white; + position: fixed; + top: 50%; + left: 50%; + height: 100%; + width: 100%; + transform: translate(-50%, -50%); + border: black 1px solid; + flex-direction: column; + overflow-y: scroll; +} + +#modal-format-selector.active { + display: flex; +} + +#modal-format-selector > h2 { + text-align: center; +} + +#modal-format-selector > p { + margin-left: 2rem; +} + +#modal-format-selector .format-list { + box-sizing: border-box; + background: #fff; + margin: 2rem; +} + +#close-modal-format-selector { + margin-top: 0.50rem; + margin-right: 0.50rem; + border: 1px solid black; + background: grey; + color: white; + width: 25px; + height: 25px; + text-align: center; + font-size: 20px; + font-weight: bold; +} + +#close-modal-format-selector:hover,#close-modal-format-selector:focus { + background: black; +} + +.format-list > div { + width: 100%; + display: grid; + grid-auto-columns: 50%; + grid-template-areas: "a a"; +} + +.format-list > div > a { + border: 1px solid black; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.5rem; + padding-right: 5%; + padding-left: 5%; + text-decoration: none; + color: black; + background: #eee; + overflow-wrap: anywhere; +} + +.format-list > div > a:hover { + background: black; + color: white; +} + +.format-list > div > a:after { + padding-bottom: 100%; + display: block; + content: ""; +} + +div.video-formats a { + background: #f00; +} + +@media (min-width: 668px) { + #poping-notice { + width: 629px; + } +} diff --git a/themes/default/public/dist/index.html b/themes/default/public/dist/index.html new file mode 100644 index 0000000..4eac01f --- /dev/null +++ b/themes/default/public/dist/index.html @@ -0,0 +1,68 @@ + + + + + + + +
+

Peertube-dl Web Application

+
+ + +
+
+ + + +
+
+

This webpage is free as in freedom software, it is offered to you with the hope it will be useful, but without any warranty, + you can find the source code at my gitea with docs to setup your own + webpage like this, this software is licensed under the AGPLv3 license which means you MUST convey the source code in a human readable form + if you distribute this software or use it as an service to users of service or distributees.

+ +

I hope that if you find a non supported url which should be supported, a bug, or a feature you would like this webpage to have you file an issue in + https://gitea.sergiotarxz.freemyip.com/sergiotarxz/Peertube-dl/issues + to help this software improve since I find tracking users a pretty bad way to discover bugs and potential good features.

+ +

This webpage may load third party resources depending on the url you give to it which may put cookies in your browser, you are + encouraged to frecuently delete your browser cookies to avoid those third parties tracking you on internet, Firefox offers you an + option to delete cookies as soon as you close the browser which may be a good idea to enable in the orwellian internet of today.

+
+
+ X +
+
+ + diff --git a/themes/default/public/package.json b/themes/default/public/package.json new file mode 100644 index 0000000..3edd723 --- /dev/null +++ b/themes/default/public/package.json @@ -0,0 +1,13 @@ +{ + "name": "default-theme", + "version": "1.0.0", + "private": true, + "license": "MIT", + "scripts": { + "build": "webpack" + }, + "dependencies": { + "webpack": "^5.15.0", + "webpack-cli": "^4.3.1" + } +} diff --git a/themes/default/public/src/application.js b/themes/default/public/src/application.js new file mode 100644 index 0000000..2509e11 --- /dev/null +++ b/themes/default/public/src/application.js @@ -0,0 +1,106 @@ +"use strict"; + +import { PopingNotice } from './view/poping_notice.js'; +import { DownloadForm } from './view/download_form.js'; +import { LoadingModal } from './view/loading_modal.js'; +import { VideoContainer } from './view/video_container.js'; +import { FormatSelector } from './view/format_selector.js'; + +class Application { + constructor() { + this.poping_notice = new PopingNotice(); + this.download_form = new DownloadForm(this.onDownloadFormGot.bind(this)); + this.loading_modal = new LoadingModal(); + this.video_container = new VideoContainer(); + this.format_selector = new FormatSelector(); + } + + init() { + this.popingNotice.setVisible(true); + } + + onDownloadFormGot(url) { + this.dispatchURL(url); + } + + dispatchURL(url, format) { + this.loadingModal.setVisible(true); + let error_str; + let success = this.queryAPI(url, format).then( (response) => { + if ( response.options !== undefined && response.options.list_formats !== undefined && response.options.list_formats ) { + if ( response.formats === undefined + || response.formats.audio_formats === undefined + || response.formats.video_formats === undefined ) { + throw 'Format object is not valid.'; + } + this.formatSelector.prepareFormatSelector( + response.title, response.description, + response.formats.audio_formats, response.formats.video_formats, + ( id ) => { + this.loadingModal.setVisible(true); + this.dispatchURL(url, id); + }); + this.formatSelector.setVisible(true); + this.loadingModal.setVisible(false); + } else { + this.videoContainer.onCanPlay(this.onCanPlayVideoContainer.bind(this)); + this.videoContainer.setURLVideo(response.url); + this.videoContainer.setFilename(response.filename); + } + }).catch( (error) => { + error_str = error.toString(); + this.loadingModal.setVisible(false); + let input_url = document.createElement('a'); + input_url.href = url; + input_url.innerText = url; + let issues_url = document.createElement('a'); + issues_url.href = 'https://gitea.sergiotarxz.freemyip.com/sergiotarxz/Peertube-dl/issues'; + issues_url.innerText = 'here'; + this.popingNotice.setMessage( [ 'The url ', input_url, ' is not supported, the error was: ', error_str , ' if you think this is an error, report it ', issues_url, '.' ]); + this.popingNotice.setVisible(true); +}); + } + + onCanPlayVideoContainer() { + this.videoContainer.setVisible(true); + this.loadingModal.setVisible(false); + } + + async queryAPI(url, format) { + let request = { url: url }; + if (format !== undefined) + request.format = format; + const response = await fetch('/api', { + method: 'POST', + mode: 'cors', + cache: 'no-cache', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(request), + }); + return response.json(); + } + + get formatSelector() { + return this.format_selector; + } + + get videoContainer() { + return this.video_container; + } + + get downloadForm() { + return this.download_form; + } + + get popingNotice() { + return this.poping_notice; + } + + get loadingModal() { + return this.loading_modal; + } +} + +export { Application }; diff --git a/themes/default/public/src/index.js b/themes/default/public/src/index.js new file mode 100644 index 0000000..6ea3341 --- /dev/null +++ b/themes/default/public/src/index.js @@ -0,0 +1,10 @@ +"use strict"; + +import { Application } from './application.js'; + +window.addEventListener('load', (event) => { + let application = new Application(); + application.init(); +}); + + diff --git a/themes/default/public/src/view/download_form.js b/themes/default/public/src/view/download_form.js new file mode 100644 index 0000000..74f1e55 --- /dev/null +++ b/themes/default/public/src/view/download_form.js @@ -0,0 +1,35 @@ +"use strict"; + +class DownloadForm { + constructor(callback) { + this.query_selector = '#download-form'; + this.callback = (event) => { + event.preventDefault(); + callback(this.downloadFormUrl.value); + }; + this.addEventListeners(); + } + + addEventListeners() { + this.downloadFormButton.addEventListener('click', this.callback); + this.element.addEventListener('submit', this.callback); + } + + get downloadFormButton() { + return this.element.querySelector('#download-form-button'); + } + + get downloadFormUrl() { + return this.element.querySelector('#download-form-url'); + } + + get querySelector() { + return this.query_selector; + } + + get element() { + return document.querySelector(this.querySelector); + } +} + +export { DownloadForm }; diff --git a/themes/default/public/src/view/format_selector.js b/themes/default/public/src/view/format_selector.js new file mode 100644 index 0000000..c9e9665 --- /dev/null +++ b/themes/default/public/src/view/format_selector.js @@ -0,0 +1,87 @@ +"use strict"; + +class FormatSelector { + constructor() { + this.query_selector = '#modal-format-selector'; + this.addEventListeners(); + } + + appendFormat(container, object, is_video, callback) { + let a = document.createElement('a'); + if ( is_video ) { + a.innerText = 'Id: ' + object.id + "\n" + + 'Format: ' + object.mimeType + "\n" + + 'QualityLabel: ' + object.qualityLabel + "p\n" + + 'Bitrate: ' + object.bitrate + "\n" + + ( + ( object.audioSampleRate !== undefined ) ? + 'AudioSampleRate: ' + object.audioSampleRate + ".\n" : + "No audio." + ); + } else { + a.innerText = 'Id: ' + object.id + "\n" + + 'Format: ' + object.mimeType + "\n" + + 'AudioSampleRate: ' + object.audioSampleRate + "\n" + + 'Bitrate: ' + object.bitrate + ".\n"; + } + a.addEventListener( 'click', (event) => { + callback(object.id); + }); + container.appendChild(a); + } + + prepareFormatSelector(title, description, audio_formats, video_formats, callback) { + this.titleFormatSelector.innerText = title; + this.descriptionFormatSelector.innerText = description; + this.videoFormats.innerHTML = ''; + this.audioFormats.innerHTML = ''; + for ( let x of audio_formats) { + this.appendFormat(this.audioFormats, x, false, callback); + } + for ( let x of video_formats ) { + this.appendFormat(this.videoFormats, x, true, callback); + } + } + + setVisible(option) { + if (option) { + this.element.classList.add('active'); + } else { + this.element.classList.remove('active'); + } + } + + addEventListeners() { + this.closeFormatSelector.addEventListener('click', (event) => { this.setVisible(false); }); + } + + get videoFormats() { + return this.element.querySelector('.video-formats'); + } + + get audioFormats() { + return this.element.querySelector('.audio-formats'); + } + + get titleFormatSelector() { + return this.element.querySelector('h2'); + } + + get descriptionFormatSelector() { + return this.element.querySelector('p'); + } + + get closeFormatSelector() { + return this.element.querySelector('#close-modal-format-selector'); + } + + get element() { + return document.querySelector(this.querySelector); + } + + get querySelector() { + return this.query_selector; + } +} + +export { FormatSelector }; diff --git a/themes/default/public/src/view/loading_modal.js b/themes/default/public/src/view/loading_modal.js new file mode 100644 index 0000000..10ae7bc --- /dev/null +++ b/themes/default/public/src/view/loading_modal.js @@ -0,0 +1,25 @@ +"use strict"; + +class LoadingModal { + constructor() { + this.query_selector = '#modal-loading'; + } + + setVisible(option) { + if (option) { + this.element.classList.add('active'); + } else { + this.element.classList.remove('active'); + } + } + + get element() { + return document.querySelector(this.querySelector); + } + + get querySelector() { + return this.query_selector; + } +} + +export { LoadingModal }; diff --git a/themes/default/public/src/view/poping_notice.js b/themes/default/public/src/view/poping_notice.js new file mode 100644 index 0000000..69156af --- /dev/null +++ b/themes/default/public/src/view/poping_notice.js @@ -0,0 +1,56 @@ +"use strict"; + +class PopingNotice { + constructor() { + this.query_selector = '#poping-notice'; + this.closePopingNotice.addEventListener('click', (event) => { + this.setVisible(false); + }); + } + + setVisible(option) { + if (option) { + this.element.classList.add('active'); + } else { + this.element.classList.remove('active'); + } + } + + setMessage(message) { + if (!message instanceof Array) + throw 'Message is not instance of Array.'; + let p = document.createElement('p'); + for (let node of message) { + if (typeof node === "string" + || node instanceof String) { + node = document.createTextNode(node); + p.appendChild(node); + } else if ( node instanceof Node) { + p.appendChild(node); + } else { + throw ('Node is not a instance of Node nor a String'); + } + } + this.popingNoticeContent.innerHTML = ''; + this.popingNoticeContent.appendChild(p); + + } + + get querySelector() { + return this.query_selector; + } + + get element() { + return document.querySelector(this.querySelector); + } + + get popingNoticeContent() { + return this.element.querySelector('#poping-notice-content'); + } + + get closePopingNotice() { + return this.element.querySelector('#close-poping-notice'); + } +} + +export { PopingNotice }; diff --git a/themes/default/public/src/view/video_container.js b/themes/default/public/src/view/video_container.js new file mode 100644 index 0000000..99caaa6 --- /dev/null +++ b/themes/default/public/src/view/video_container.js @@ -0,0 +1,112 @@ +"use strict"; + +class VideoContainer { + constructor() { + this.query_selector = '#modal-video-container'; + this.addEventListeners(); + } + + setVisible(option) { + if (option) { + this.element.classList.add('active'); + } else { + this.element.classList.remove('active'); + this.downloadVideoPrepare.classList.add('active'); + this.downloadVideoLoading.classList.remove('active'); + this.downloadVideo.classList.remove('active'); + } + } + + addEventListeners() { + this.downloadVideoPrepare.addEventListener('click', this.downloadPrepareHandler.bind(this)); + this.closeAndResetVideoContainer.addEventListener('click', (event) => { + this.setVisible(false); + }); + } + + downloadPrepareHandler(event) { + this.downloadVideoPrepare.classList.remove('active'); + this.downloadVideoLoading.classList.add('active'); + this.generateBlobVideo(this.URLVideo).then( blob => { + this.downloadVideo.href = URL.createObjectURL(blob); + this.downloadVideo.download = this.filename; + this.downloadVideoLoading.classList.remove('active'); + this.downloadVideo.classList.add('active'); + }); + + } + + async generateBlobVideo(url) { + const blob = await fetch(url, { mode: 'cors', }) + .then(res => res.blob()) + .catch( err => this.generateBlobVideoByProxy(url) ); + return blob; + } + + async generateBlobVideoByProxy(url) { + const blob = await fetch( '/proxy_to_get', { + method: 'POST', + mode: 'cors', + cache: 'no-cache', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({url: url}), + } + ).then(res => res.blob()); + return blob; + } + + onCanPlay(callback) { + video.addEventListener('canplay', (event) => { + callback(); + }); + } + + setURLVideo(url) { + this.url_video = url; + video.src = url; + } + + setFilename(filename) { + this.filename = filename; + } + + get closeAndResetVideoContainer() { + return this.element.querySelector('#close-and-reset-video-container'); + } + + get downloadVideo() { + return this.element.querySelector('#download-video'); + } + + get URLVideo() { + return this.url_video; + } + + get closeAndResetVideoContainer() { + return this.element.querySelector('#close-and-reset-video-container'); + } + + get downloadVideoLoading() { + return this.element.querySelector('#download-video-loading'); + } + + get downloadVideoPrepare() { + return this.element.querySelector('#download-video-prepare'); + } + + get video() { + return this.element.querySelector('#video'); + } + + get element() { + return document.querySelector(this.querySelector); + } + + get querySelector() { + return this.query_selector; + } +} + +export { VideoContainer }; diff --git a/themes/default/public/webpack.config.js b/themes/default/public/webpack.config.js new file mode 100644 index 0000000..bc82144 --- /dev/null +++ b/themes/default/public/webpack.config.js @@ -0,0 +1,10 @@ +const path = require('path'); + +module.exports = { + entry: './src/index.js', + devtool: 'source-map', + output: { + filename: 'peertube-dl-web.js', + path: path.resolve(__dirname, 'dist/js'), + }, +};