created dynamic frontend
This commit is contained in:
parent
93b7f68b54
commit
2535006f02
89
app.py
89
app.py
|
@ -1,5 +1,7 @@
|
||||||
import shutil
|
import shutil
|
||||||
import os
|
import os
|
||||||
|
import glob
|
||||||
|
import traceback
|
||||||
from flask import Flask, render_template, request, redirect, jsonify, send_from_directory
|
from flask import Flask, render_template, request, redirect, jsonify, send_from_directory
|
||||||
from pdf_util.pdf_project_manager import pdf_project_manager
|
from pdf_util.pdf_project_manager import pdf_project_manager
|
||||||
|
|
||||||
|
@ -21,7 +23,6 @@ logging.basicConfig(
|
||||||
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
app.config['UPLOAD_FOLDER'] = 'uploads'
|
app.config['UPLOAD_FOLDER'] = 'uploads'
|
||||||
|
|
||||||
|
|
||||||
|
@ -30,6 +31,11 @@ def index():
|
||||||
return render_template('index.html')
|
return render_template('index.html')
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/app/')
|
||||||
|
def pdf_app():
|
||||||
|
return render_template('app.html')
|
||||||
|
|
||||||
|
|
||||||
@app.route('/split/<path:path>')
|
@app.route('/split/<path:path>')
|
||||||
def send_report(path):
|
def send_report(path):
|
||||||
return send_from_directory('split', path)
|
return send_from_directory('split', path)
|
||||||
|
@ -40,6 +46,47 @@ def send_merge(path):
|
||||||
return send_from_directory('merge', path)
|
return send_from_directory('merge', path)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/projects/<path:path>')
|
||||||
|
def get_project(path):
|
||||||
|
return send_from_directory('projects', path)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/get_single_pages_archive/<uuid>/')
|
||||||
|
def get_single_pages_archive(uuid):
|
||||||
|
try:
|
||||||
|
shutil.make_archive('pdf_splitted', 'zip', "/app/projects/" + uuid + '/splitted')
|
||||||
|
os.rename('/app/pdf_splitted.zip', '/app/projects/' + uuid + '/pdf_splitted.zip')
|
||||||
|
response = jsonify({'status': 200, 'url': '/projects/' + uuid + '/pdf_splitted.zip'})
|
||||||
|
except Exception as e:
|
||||||
|
logging.debug("There was an error: " + str(e))
|
||||||
|
logging.debug("Stacktrace: " + str(traceback.format_exc()))
|
||||||
|
response = jsonify({"status": 500, "error_message": e})
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/get_single_pages_info/<uuid>/')
|
||||||
|
def get_single_pages_info(uuid):
|
||||||
|
try:
|
||||||
|
pages = []
|
||||||
|
|
||||||
|
page_list = glob.glob("/app/projects/" + uuid + "/splitted/*.pdf")
|
||||||
|
logging.debug("page_list: ")
|
||||||
|
logging.debug(page_list)
|
||||||
|
|
||||||
|
page_list.sort()
|
||||||
|
logging.debug("sorted_page_list: ")
|
||||||
|
logging.debug(page_list)
|
||||||
|
|
||||||
|
for file in page_list:
|
||||||
|
pages.append(file[4:]) # Cut of /app
|
||||||
|
response = jsonify({'status': 200, 'pages': pages})
|
||||||
|
except Exception as e:
|
||||||
|
logging.debug("There was an error: " + str(e))
|
||||||
|
logging.debug("Stacktrace: " + str(traceback.format_exc()))
|
||||||
|
response = jsonify({"status": 500, "error_message": e})
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
@app.route('/split_to_zip', methods=['POST'])
|
@app.route('/split_to_zip', methods=['POST'])
|
||||||
def split_to_zip():
|
def split_to_zip():
|
||||||
if 'pdf_1' not in request.files:
|
if 'pdf_1' not in request.files:
|
||||||
|
@ -106,6 +153,46 @@ def merge_to_pdf():
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/init_project/', methods=['GET'])
|
||||||
|
def init_project():
|
||||||
|
try:
|
||||||
|
pdf_project = pdf_project_manager()
|
||||||
|
response = jsonify({"status": 200, "project_uuid": pdf_project.uuid})
|
||||||
|
except Exception as e:
|
||||||
|
logging.debug("There was an error: " + str(e))
|
||||||
|
logging.debug("Stacktrace: " + str(traceback.format_exc()))
|
||||||
|
response = jsonify({"status": 500, "project_uuid": ''})
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/add_pdf_to_project/', methods=['POST'])
|
||||||
|
def add_pdf_to_project():
|
||||||
|
try:
|
||||||
|
if 'pdf' not in request.files:
|
||||||
|
logging.debug(request)
|
||||||
|
return redirect(request.url)
|
||||||
|
else:
|
||||||
|
pdf_file = request.files['pdf']
|
||||||
|
filename_1 = os.path.join(os.path.dirname(os.path.realpath(__file__)), app.config['UPLOAD_FOLDER'], pdf_file.filename)
|
||||||
|
pdf_file.save(filename_1)
|
||||||
|
|
||||||
|
pdf_file = request.files['pdf']
|
||||||
|
uuid = request.form['uuid']
|
||||||
|
logging.debug(pdf_file)
|
||||||
|
logging.debug(uuid)
|
||||||
|
|
||||||
|
pdf_project = pdf_project_manager(uuid4=uuid)
|
||||||
|
pdf_project.add_pdf(filename_1)
|
||||||
|
|
||||||
|
response = jsonify({"status": 200, "message": 'PDF added'})
|
||||||
|
except Exception as e:
|
||||||
|
logging.debug("There was an error: " + str(e))
|
||||||
|
logging.debug("Stacktrace: " + str(traceback.format_exc()))
|
||||||
|
response = jsonify({"status": 500, "error_message": e})
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
||||||
app.run(debug=True)
|
app.run(debug=True)
|
||||||
|
|
|
@ -0,0 +1,141 @@
|
||||||
|
div {
|
||||||
|
/* border: 2px solid yellowgreen; */
|
||||||
|
}
|
||||||
|
|
||||||
|
#app {
|
||||||
|
overflow: hidden;
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#div_content {
|
||||||
|
padding-left: 2em;
|
||||||
|
padding-right: 2em;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#div_website_title {
|
||||||
|
margin-top: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#div_project_title {
|
||||||
|
/* border: 2px solid darkorchid; */
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#div_dropzone {
|
||||||
|
margin-top: 0.5em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.div_float_clear {
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 26pt;
|
||||||
|
color: greenyellow;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.button_export {
|
||||||
|
width: 50%;
|
||||||
|
margin-left: 0%;
|
||||||
|
margin-right: 0%;
|
||||||
|
margin-top: 1em;
|
||||||
|
margin-bottom: 2em;
|
||||||
|
border-width: 0px;
|
||||||
|
padding: 1em;
|
||||||
|
background-image: linear-gradient(to right, #d1d1d1, #dddddd);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button_export:hover {
|
||||||
|
background-image: linear-gradient(to right, #c1c1c1, #cccccc);
|
||||||
|
}
|
||||||
|
|
||||||
|
#button_download_individual_pages {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
#button_download_complete_pdf {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#pdf-dropzone {
|
||||||
|
min-height: 50px;
|
||||||
|
max-height: 150px;
|
||||||
|
height: 150px;
|
||||||
|
border: 1px solid black;
|
||||||
|
padding-top:60px
|
||||||
|
}
|
||||||
|
|
||||||
|
#pdf-dropzone:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#preview-template {
|
||||||
|
border: 2px solid yellowgreen;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
#page_overview {
|
||||||
|
border: 2px solid yellowgreen;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#control-div {
|
||||||
|
width: 40%;
|
||||||
|
float: left;
|
||||||
|
/* padding: 1em; */
|
||||||
|
overflow: hidden; /* Makes the container actually "contain" the floated divs */
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#history-div {
|
||||||
|
width: 60%;
|
||||||
|
float: left;
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#comment-label, #comment-input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* The snackbar - position it at the bottom and in the middle of the screen */
|
||||||
|
#snackbar {
|
||||||
|
visibility: hidden; /* Hidden by default. Visible on click */
|
||||||
|
min-width: 800px; /* Set a default minimum width */
|
||||||
|
margin-left: -400px; /* Divide value of min-width by 2 */
|
||||||
|
background-color: #333; /* Black background color */
|
||||||
|
color: #fff; /* White text color */
|
||||||
|
text-align: center; /* Centered text */
|
||||||
|
border-radius: 2px; /* Rounded borders */
|
||||||
|
padding: 16px; /* Padding */
|
||||||
|
position: fixed; /* Sit on top of the screen */
|
||||||
|
z-index: 1; /* Add a z-index if needed */
|
||||||
|
left: 50%; /* Center the snackbar */
|
||||||
|
bottom: 30px; /* 30px from the bottom */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Show the snackbar when clicking on a button (class added with JavaScript) */
|
||||||
|
#snackbar.show {
|
||||||
|
visibility: visible; /* Show the snackbar */
|
||||||
|
/* Add animation: Take 0.5 seconds to fade in and out the snackbar.
|
||||||
|
However, delay the fade out process for 2.5 seconds */
|
||||||
|
/*
|
||||||
|
-webkit-animation: fadein 0.5s, fadeout 0.5s 2.5s;
|
||||||
|
animation: fadein 0.5s, fadeout 0.5s 2.5s;
|
||||||
|
*/
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
.form__group {
|
||||||
|
position: relative;
|
||||||
|
padding: 15px 0 0;
|
||||||
|
margin-top: 10px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form__field {
|
||||||
|
font-family: inherit;
|
||||||
|
width: 100%;
|
||||||
|
border: 0;
|
||||||
|
border-bottom: 2px solid #9b9b9b;
|
||||||
|
outline: 0;
|
||||||
|
font-size: 1em;
|
||||||
|
color: #333;
|
||||||
|
padding: 7px 0;
|
||||||
|
background: transparent;
|
||||||
|
transition: border-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form__field::placeholder {
|
||||||
|
color: #666666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form__field:focus::placeholder {
|
||||||
|
color: #666666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form__field:focus {
|
||||||
|
padding-bottom: 6px;
|
||||||
|
border-width: 3px;
|
||||||
|
border-image: linear-gradient(to right, #477493, #0f719b);
|
||||||
|
border-image-slice: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form__field_bad_input {
|
||||||
|
padding-bottom: 6px;
|
||||||
|
font-weight: 700;
|
||||||
|
border-width: 3px;
|
||||||
|
border-image: linear-gradient(to right, #f91d1d, #9f4a4a);
|
||||||
|
border-image-slice: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* reset input */
|
||||||
|
.form__field:required, .form__field:invalid {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
|
@ -0,0 +1,164 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="shortcut icon" href="/static/icons/filetype-pdf.svg" type="image/x-icon">
|
||||||
|
<link rel="stylesheet" href="/static/css/styles.css">
|
||||||
|
<link rel="stylesheet" href="/static/css/styles_forms.css">
|
||||||
|
|
||||||
|
<!-- Dropzone -->
|
||||||
|
<script src="https://unpkg.com/dropzone@5/dist/min/dropzone.min.js"></script>
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/dropzone@5/dist/min/dropzone.min.css" type="text/css" />
|
||||||
|
|
||||||
|
<!-- Bootstrap -->
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css">
|
||||||
|
|
||||||
|
<title>PDF Web Toolkit</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div id="app">
|
||||||
|
<div id="div_content">
|
||||||
|
<div id="div_website_title">
|
||||||
|
<h1>PDF Web Toolkit</h1>
|
||||||
|
</div>
|
||||||
|
<div id="div_project_title">
|
||||||
|
<div class="form__group field">
|
||||||
|
<input id="project_title" class="form__field" type="text" @change="handleProjectTitleChange($event, 'project_title')" placeholder="Project Title" onfocus="document.getElementById('project_title').classList.remove('form__field_bad_input');"><br>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="div_float_clear"></div>
|
||||||
|
<div id="div_dropzone">
|
||||||
|
<div class="my-dropzone" id="pdf-dropzone">Drop pdf here or click to upload...</div>
|
||||||
|
<div class="dz-preview dz-file-preview well" id="preview-template"></div>
|
||||||
|
</div>
|
||||||
|
<div class="div_float_clear"></div>
|
||||||
|
<div id="page_overview">
|
||||||
|
<!-- Filled by dynamically loaded content -->
|
||||||
|
</div>
|
||||||
|
<div id="div_select_buttons">
|
||||||
|
<button class="button_export" id="button_download_complete_pdf" @click="download_pdf()">Download complete PDF</button>
|
||||||
|
<button class="button_export" id="button_download_individual_pages" @click="download_split_pdf()">Split PDF into individual pages</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
var uuid;
|
||||||
|
|
||||||
|
new Vue({
|
||||||
|
el: '#app',
|
||||||
|
data: {
|
||||||
|
pdf_file: null,
|
||||||
|
project_uuid: null
|
||||||
|
},
|
||||||
|
mounted(){
|
||||||
|
window.addEventListener("load", () => this.side_loaded());
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
downloadURI(uri, name) {
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.download = name;
|
||||||
|
link.href = uri;
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
delete link;
|
||||||
|
},
|
||||||
|
download_pdf() {
|
||||||
|
this.downloadURI("/projects/" + this.project_uuid + "/complete.pdf", "complete_project.pdf");
|
||||||
|
},
|
||||||
|
download_split_pdf() {
|
||||||
|
fetch('/get_single_pages_archive/' + this.project_uuid + '/', {
|
||||||
|
method: 'GET'
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
console.debug("data from Backend: ", data);
|
||||||
|
if (data["status"] == 200) {
|
||||||
|
this.downloadURI("/projects/" + this.project_uuid + "/pdf_splitted.zip", "splitted_pdfs.zip");
|
||||||
|
console.info("Archive with single Pages-PDFs created");
|
||||||
|
} else {
|
||||||
|
console.error("Project could not be created");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => console.error(error));
|
||||||
|
},
|
||||||
|
side_loaded() {
|
||||||
|
fetch('/init_project/', {
|
||||||
|
method: 'GET'
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
console.debug("data from Backend: ", data);
|
||||||
|
if (data["status"] == 200) {
|
||||||
|
this.project_uuid = data["project_uuid"];
|
||||||
|
uuid = data["project_uuid"];
|
||||||
|
} else {
|
||||||
|
this.project_uuid = null;
|
||||||
|
console.error("Project could not be created");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => console.error(error));
|
||||||
|
|
||||||
|
// Dropzone has been added as a global variable.
|
||||||
|
const dropzone = new Dropzone("div#pdf-dropzone", {
|
||||||
|
url: "/add_pdf_to_project/",
|
||||||
|
paramName: "pdf"
|
||||||
|
});
|
||||||
|
|
||||||
|
dropzone.on("addedfile", file => {
|
||||||
|
// console.debug("on addedfile this...", this)
|
||||||
|
console.log("A file has been added");
|
||||||
|
file.previewElement.innerHTML = "";
|
||||||
|
console.debug("Suppress file.previewElement");
|
||||||
|
console.debug("PDF Project UUID: ", this.project_uuid);
|
||||||
|
});
|
||||||
|
|
||||||
|
dropzone.on("sending", function(file, xhr, formData) {
|
||||||
|
// console.debug("on sending this...", this)
|
||||||
|
// formData.append("uuid", this.project_uuid);
|
||||||
|
formData.append("uuid", uuid);
|
||||||
|
});
|
||||||
|
|
||||||
|
dropzone.on("complete", function(file, xhr, formData) {
|
||||||
|
div_overview = document.getElementById("page_overview");
|
||||||
|
div_overview.innerHTML = ""
|
||||||
|
console.debug("div_overview", div_overview);
|
||||||
|
|
||||||
|
fetch('/get_single_pages_info/' + uuid + '/', {
|
||||||
|
method: 'GET'
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
console.debug("data from Backend: ", data);
|
||||||
|
if (data["status"] == 200) {
|
||||||
|
for (page in data["pages"]) {
|
||||||
|
console.debug(page);
|
||||||
|
// <embed src="" width="500" height="375" type="application/pdf">
|
||||||
|
node = document.createElement("embed");
|
||||||
|
node.width = "300";
|
||||||
|
node.height = "300";
|
||||||
|
node.type = "application/pdf";
|
||||||
|
node.src = data["pages"][page]
|
||||||
|
div_overview.appendChild(node)
|
||||||
|
}
|
||||||
|
console.debug(data["status"]);
|
||||||
|
} else {
|
||||||
|
console.debug(data["status"]);
|
||||||
|
console.error("Project could not be created");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => console.error(error));
|
||||||
|
console.debug("div_overview", div_overview);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -3,7 +3,9 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="shortcut icon" href="/static/icons/filetype-pdf.svg" type="image/x-icon">
|
||||||
<title>PDF Tools</title>
|
<title>PDF Tools</title>
|
||||||
|
<meta http-equiv="Refresh" content="0; url='/app/'" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
@ -53,13 +55,13 @@ new Vue({
|
||||||
const backendURL = file2 ? '/merge_to_pdf' : '/split_to_zip';
|
const backendURL = file2 ? '/merge_to_pdf' : '/split_to_zip';
|
||||||
|
|
||||||
fetch(backendURL, {
|
fetch(backendURL, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData
|
body: formData
|
||||||
})
|
})
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
console.debug("data from Backend: ", data);
|
console.debug("data from Backend: ", data);
|
||||||
this.downloadURI(data["url"], data["name"]);
|
this.downloadURI(data["url"], data["name"]);
|
||||||
})
|
})
|
||||||
.catch(error => console.error(error));
|
.catch(error => console.error(error));
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue