refactor: Making the front end modular using webpack.

This commit is contained in:
sergiotarxz 2021-01-19 01:13:52 +01:00
parent 6e08769a8d
commit 6ed9ab8f24
Signed by: sergiotarxz
GPG Key ID: E5903508B6510AC2
14 changed files with 811 additions and 201 deletions

View File

@ -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"

View File

@ -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 {

File diff suppressed because one or more lines are too long

274
themes/default/public/dist/css/index.css vendored Normal file
View File

@ -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;
}
}

68
themes/default/public/dist/index.html vendored Normal file
View File

@ -0,0 +1,68 @@
<html>
<head>
<script src="js/peertube-dl-web.js"></script>
<link rel="stylesheet" href="css/index.css"></script>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<div class="application-container">
<h2>Peertube-dl Web Application</h2>
<form id="download-form">
<input class="block" type="text" id="download-form-url" placeholder="Introduce the url you want to download."/>
<button class="block" id="download-form-button" >Fetch from api</button>
</form>
</div>
<div id="modal-format-selector">
<div class="video-container-bar">
<a id="close-modal-format-selector">x</a>
</div>
<h2>Example video</h2>
<p>Example description</p>
<div class="format-list">
<h3>Video Formats.</h3>
<div class="video-formats">
</div>
<h3>Audio Formats.</h3>
<div class="audio-formats">
</div>
</div>
</div>
<div id="modal-loading">
<embed src="img/spinner.svg"/>
</div>
<div id="modal-video-container">
<div class="video-container-bar">
<a id="close-and-reset-video-container">x</a>
</div>
<div id="video-container">
<div class="block">
<video id="video" controls="controls"></video>
</div>
<div id="download-video-container" class="block">
<a id="download-video-prepare" class="active">Prepare download</a>
<a id="download-video-loading"><embed src="img/spinner.svg"/></a>
<a id="download-video">Download</a>
</div>
</div>
</div>
<div id="poping-notice">
<div id="poping-notice-content">
<p>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 <a href="https://gitea.sergiotarxz.freemyip.com/sergiotarxz/Peertube-dl">my gitea</a> 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.<p>
<p>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
<a href="https://gitea.sergiotarxz.freemyip.com/sergiotarxz/Peertube-dl/issues">https://gitea.sergiotarxz.freemyip.com/sergiotarxz/Peertube-dl/issues</a>
to help this software improve since I find tracking users a pretty bad way to discover bugs and potential good features.</p>
<p>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.</p>
</div>
<div id="poping-notice-container-bar">
<a id="close-poping-notice" href="#">X</a>
</div>
</div>
</body>
</html>

View File

@ -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"
}
}

View File

@ -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 };

View File

@ -0,0 +1,10 @@
"use strict";
import { Application } from './application.js';
window.addEventListener('load', (event) => {
let application = new Application();
application.init();
});

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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'),
},
};