diff --git a/peertube-dl-web.conf b/peertube-dl-web.conf index 71f81e1..3c317df 100644 --- a/peertube-dl-web.conf +++ b/peertube-dl-web.conf @@ -5,5 +5,6 @@ clients => 3, proxy => 1, pid_file => $ENV{PIDFILE} || '/var/run/peertube-dl-web.pid', - } + }, + theme => 'new_look_default', }; diff --git a/themes/new_look_default/public/dist/css/index.css b/themes/new_look_default/public/dist/css/index.css new file mode 100644 index 0000000..40ef2fe --- /dev/null +++ b/themes/new_look_default/public/dist/css/index.css @@ -0,0 +1,306 @@ +body { + height: 98vh; + + display: flex; + flex-flow: column; + align-items: center; + justify-content: center; + + background-color: #111827; +} + +.application-container { + display: flex; + flex-flow: column; + align-items: center; + justify-content: center; + + border-radius: 0.3rem; + + background-color: #1f2937; + + padding: 1.5rem 3rem 1.5rem 3rem; +} + +.application-container form { + display: flex; + flex-flow: column; + justify-content: center; + + width: 100%; + padding: 1rem; +} + +.application-container h2 { + color: #ffffff; + font-weight: 400; + font-size: 1.6rem; +} + +.application-container input { + padding: 1rem; + + color: #ffffff; + background-color: #374151; + + border-radius: 0.2rem; + border: 1px solid transparent; +} + +.application-container button { + margin-top: 1rem; + + color: #ffffff; + font-weight: bold; + + background-color: #059669; + + border-radius: 0.2rem; + border: 1px solid transparent; + + padding: 0.5rem 1rem 0.5rem 1rem; +} + +.application-container button:hover,.application-container button:focus { + background-color: #059; +} + +.application-container button, +.application-container input { + font-size: 0.9rem; +} + +.application-container.active { + display: flex; +} + +#poping-notice-content a { + text-decoration: none; + color: #10b981; +} + +#poping-notice-content a:hover,#poping-notice-content a:focus { + color: #0ae; +} + +#poping-notice-container-bar { + display: flex; + justify-content: center; +} + +#close-poping-notice { + background-color: #059669; + padding: 0.5rem 5rem 0.5rem 5rem; + border-radius: 0.3rem; + text-decoration: none; + font-weight: bolder; + color: white; +} + +#close-poping-notice:hover,#close-poping-notice:focus { + background-color: #059; +} + +.modal { + position: absolute; + display: none; + color: white; +} + +#poping-notice { + padding: 3rem; + border-radius: 0.3rem; + background-color: #374151; + color: white; + overflow-y: scroll; + width: 94%; +} + +#modal-video-container,#modal-format-selector { + height: 100%; + width: 100%; + + flex-flow: column; + align-items: center; + + background-color: #111827; +} + +.video-container-bar { + width: 95%; + height: 2rem; + padding: 1rem; + display: flex; + justify-content: end; +} + +.video-container-bar a { + padding: 1rem; + border-radius: 0.3rem; + + display: flex; + align-items: center; + justify-content: center; + + background-color: #dc2626; +} + +.video-container-bar a:hover,.video-container-bar a:focus { + color: white; + background-color: grey; +} + + +#modal-video-container > #block { + display: flex; + flex-flow: column; + align-items: center; + justify-content: center; +} + +#download-video-container { + padding: 2rem; + + display: flex; + align-items: center; + justify-content: center; +} + +.button-download { + display: none; + border-radius: 0.3rem; + background-color: #059669; + text-decoration: none; + color: white; + user-select: none; + cursor: pointer; + height: 30px; + padding: 0 2rem; +} + +.button-download:hover,.button-download:focus { + background-color: #059; +} + +.button-download embed { + height: 100%; +} + +#download-video-loading.active { + height: 2rem; +} + +#download-video-container .button-download.active { + display: flex; + align-items: center; +} + +#modal-loading { + display: none; +} + +.modal { + --tw-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), + 0 4px 6px -2px rgba(0, 0, 0, 0.05); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), + var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); + overflow-y: scroll; +} + +#poping-notice.active { + display: block; +} + +.modal.active { + display: flex; +} + +#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%; +} + +#video { + width: 100%; +} + +#video-container { + height: 100%; + display: flex; + flex-direction: column; + justify-content: center; +} + +.format-list > div { + display: grid; + grid-column-gap: 10px; + grid-row-gap: 10px; + grid-template-areas: "a"; + grid-auto-columns: 99%; + margin: 1rem; +} + +.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; + border-radius: 0.5rem; + background-color: #059669; + overflow-wrap: anywhere; +} + +.format-list > div > a:focus,.format-list > div > a:hover { + background-color: #059; +} + +.format-list > div > a:after { + padding-bottom: 100%; + display: block; + content: ""; +} + +.scale { + height: 1em; + width; 1em; +} + +@media (min-width: 600px) { + .format-list > div { + grid-template-areas: "a a"; + grid-auto-columns: 49.75%; + } +} + +@media (min-width: 805px) { + .format-list > div { + grid-template-areas: "a a a"; + grid-auto-columns: 33%; + } +} + +@media (min-width: 812px) { + #poping-notice { + width: 40%; + } +} diff --git a/themes/new_look_default/public/dist/img/audio_muted.svg b/themes/new_look_default/public/dist/img/audio_muted.svg new file mode 100644 index 0000000..119d23f --- /dev/null +++ b/themes/new_look_default/public/dist/img/audio_muted.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/themes/new_look_default/public/dist/img/peertube-dl-logo-fullsize.png b/themes/new_look_default/public/dist/img/peertube-dl-logo-fullsize.png new file mode 100644 index 0000000..e52a812 Binary files /dev/null and b/themes/new_look_default/public/dist/img/peertube-dl-logo-fullsize.png differ diff --git a/themes/new_look_default/public/dist/img/peertube-dl-logo.png b/themes/new_look_default/public/dist/img/peertube-dl-logo.png new file mode 100644 index 0000000..f884590 Binary files /dev/null and b/themes/new_look_default/public/dist/img/peertube-dl-logo.png differ diff --git a/themes/new_look_default/public/dist/index.html b/themes/new_look_default/public/dist/index.html new file mode 100644 index 0000000..70834ed --- /dev/null +++ b/themes/new_look_default/public/dist/index.html @@ -0,0 +1,76 @@ + + + + + + + Peertube-dl Web + + + + + + + + +
+

Peertube-dl Web Application

+
+ + +
+
+ + + + + + diff --git a/themes/new_look_default/public/package.json b/themes/new_look_default/public/package.json new file mode 100644 index 0000000..3edd723 --- /dev/null +++ b/themes/new_look_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/new_look_default/public/src/application.js b/themes/new_look_default/public/src/application.js new file mode 100644 index 0000000..2509e11 --- /dev/null +++ b/themes/new_look_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/new_look_default/public/src/index.js b/themes/new_look_default/public/src/index.js new file mode 100644 index 0000000..6ea3341 --- /dev/null +++ b/themes/new_look_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/new_look_default/public/src/view/download_form.js b/themes/new_look_default/public/src/view/download_form.js new file mode 100644 index 0000000..74f1e55 --- /dev/null +++ b/themes/new_look_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/new_look_default/public/src/view/format_selector.js b/themes/new_look_default/public/src/view/format_selector.js new file mode 100644 index 0000000..fb51107 --- /dev/null +++ b/themes/new_look_default/public/src/view/format_selector.js @@ -0,0 +1,120 @@ +"use strict"; + +class FormatSelector { + constructor() { + this.query_selector = '#modal-format-selector'; + this.addEventListeners(); + } + + appendFormat(container, object, is_video, callback) { + let a = document.createElement('a'); + let br = function() { return document.createElement('br') }; + let inner_text = []; + let muted_video = false; + if ( is_video ) { + if ( object.audioSampleRate === undefined ) { + muted_video = true; + } + inner_text.push('Id: ' + object.id); + inner_text.push(br()); + inner_text.push('Format: ' + object.mimeType); + inner_text.push(br()); + inner_text.push('QualityLabel: ' + object.qualityLabel + "p"); + inner_text.push(br()); + inner_text.push('Bitrate: ' + object.bitrate); + inner_text.push(br()); + inner_text.push( ( muted_video) ? + "No audio. " : + 'AudioSampleRate: ' + object.audioSampleRate + ); + } else { + inner_text.push('Id: ' + object.id + "\n"); + inner_text.push(br()); + inner_text.push('Format: ' + object.mimeType); + inner_text.push(br()); + inner_text.push('AudioSampleRate: ' + object.audioSampleRate); + inner_text.push(br()); + inner_text.push('Bitrate: ' + object.bitrate); + } + + a.addEventListener( 'click', (event) => { + callback(object.id); + }); + if (muted_video) { + let img_muted_video = document.createElement('img'); + img_muted_video.classList.add('mute_img'); + img_muted_video.classList.add('scale'); + img_muted_video.src = 'img/audio_muted.svg'; + inner_text.push(img_muted_video); + } + + for (let text of inner_text) { + if (typeof text === "string" + || text instanceof String) { + text = document.createTextNode(text); + a.appendChild(text); + } else if ( text instanceof Node) { + a.appendChild(text); + } else { + throw ('Text is not a instance of Node nor a String'); + } + } + + 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/new_look_default/public/src/view/loading_modal.js b/themes/new_look_default/public/src/view/loading_modal.js new file mode 100644 index 0000000..10ae7bc --- /dev/null +++ b/themes/new_look_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/new_look_default/public/src/view/poping_notice.js b/themes/new_look_default/public/src/view/poping_notice.js new file mode 100644 index 0000000..69156af --- /dev/null +++ b/themes/new_look_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/new_look_default/public/src/view/video_container.js b/themes/new_look_default/public/src/view/video_container.js new file mode 100644 index 0000000..d29f4b5 --- /dev/null +++ b/themes/new_look_default/public/src/view/video_container.js @@ -0,0 +1,113 @@ +"use strict"; + +class VideoContainer { + constructor() { + this.query_selector = '#modal-video-container'; + this.addEventListeners(); + } + + setVisible(option) { + if (option) { + this.element.classList.add('active'); + } else { + this.video.pause(); + 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/new_look_default/public/webpack.config.js b/themes/new_look_default/public/webpack.config.js new file mode 100644 index 0000000..bc82144 --- /dev/null +++ b/themes/new_look_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'), + }, +};