Created Frontend with Bootstrap #2

Merged
tidoni merged 5 commits from dev into main 2024-03-07 15:36:28 +00:00
26 changed files with 807 additions and 136 deletions

125
app.py
View File

@ -1,5 +1,7 @@
import shutil
import os
import glob
import traceback
from flask import Flask, render_template, request, redirect, jsonify, send_from_directory
from pdf_util.pdf_project_manager import pdf_project_manager
@ -20,14 +22,33 @@ logging.basicConfig(
)
app = Flask(__name__)
app = Flask(__name__, static_folder=os.path.abspath('/app/static'))
app.config['UPLOAD_FOLDER'] = 'uploads'
@app.route('/')
def index():
return render_template('index.html')
return redirect("/split", code=302)
@app.route('/project')
def project():
return render_template('base.html', page='project', settings='set', error=False)
@app.route('/split')
def splitt():
return render_template('base.html', page='split', settings='set', error=False)
@app.route('/merge')
def merge():
return render_template('base.html', page='merge', settings='set', error=False)
@app.route('/ocr')
def ocr():
return render_template('base.html', page='ocr', settings='set', error=False)
@app.route('/split/<path:path>')
@ -40,6 +61,64 @@ def send_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('/move_page/<uuid>/<from_page>/<to_page>')
def move_page(uuid, from_page, to_page):
try:
pdf_project = pdf_project_manager(uuid4=uuid)
logging.debug("int(from_page): ")
logging.debug(int(from_page))
logging.debug("int(to_page): ")
logging.debug(int(to_page))
pdf_project.move_page(from_location=int(from_page), to_location=int(to_page))
response = jsonify({'status': 200, 'message': 'page moved successfully'})
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'])
def split_to_zip():
if 'pdf_1' not in request.files:
@ -106,6 +185,46 @@ def merge_to_pdf():
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__':
app.run(debug=True)

6
static/css/bootstrap.min.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,13 @@
#pdf-dropzone {
min-height: 50px;
max-height: 150px;
height: 150px;
border: 0.1em solid darkgray;
border-radius: 1em;
padding-top:60px;
text-align: center;
}
#pdf-dropzone:hover {
cursor: pointer;
border: 0.2em solid darkslategray;
}

36
static/css/main.css Normal file
View File

@ -0,0 +1,36 @@
.div_float_clear {
clear: both;
}
/* 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;
*/
}
@media (min-width: 992px) {
.nav-item.active {
border-bottom: 1px solid var(--bs-nav-link-color);
}
}

9
static/css/merge.css Normal file
View File

@ -0,0 +1,9 @@
#btn-group-download {
width: 100%;
margin-top: 1em;
display: none;
}
#btn_download_complete {
width: 100%;
}

44
static/css/project.css Normal file
View File

@ -0,0 +1,44 @@
/*
https://css-tricks.com/pure-css-horizontal-scrolling/
*/
.single_page_embed {
height: 90%;
width: 100%;
display: inline-block;
margin: 0 auto;
}
.single_page_div {
display: block;
padding: 1px;
transform: rotate(90deg) translateX(500px); /* translateY(-2px) */
transform-origin: right top;
height: 500px;
width: 500px;
text-align: center;
}
#btn-group-download {
width: 100%;
margin-top: 1em;
display: none;
}
#btn_download_complete, #btn_download_singel_pages {
width: 50%;
}
#overview-wrapper {
width: 100%;
}
#page_overview {
display: block;
top: 0;
left: 0;
width: 500px;
margin: 0;
padding-top: 5px;
overflow-y: auto;
overflow-x: hidden;
transform: rotate(-90deg) translateY(-500px);
transform-origin: right top;
/* border: 0.1em solid darkcyan; */
}

9
static/css/split.css Normal file
View File

@ -0,0 +1,9 @@
#btn-group-download {
width: 100%;
margin-top: 1em;
display: none;
}
#btn_download_singel_pages {
width: 100%;
}

View File

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

BIN
static/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 MiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,53 +0,0 @@
function downloadURI(uri, name) {
var link = document.createElement("a");
link.download = name;
link.href = uri;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
delete link;
}
function uploadPDF_split() {
const fileInput = document.getElementById('split_pdfFile');
const file = fileInput.files[0];
const formData = new FormData();
formData.append('pdf', file);
const backendURL = '/split_to_zip';
fetch(backendURL, {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
console.debug("data from Backend: ", data)
downloadURI(data["url"], data["name"]);
})
.catch(error => console.error(error));
}
function uploadPDF_merge() {
const fileInput_1 = document.getElementById('merge_pdfFile_1');
const file_1 = fileInput_1.files[0];
const fileInput_2 = document.getElementById('merge_pdfFile_2');
const file_2 = fileInput_2.files[0];
const formData = new FormData();
formData.append('pdf_1', file_1);
formData.append('pdf_2', file_2);
const backendURL = '/merge_to_pdf';
fetch(backendURL, {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
console.debug("data from Backend: ", data)
downloadURI(data["url"], data["name"]);
})
.catch(error => console.error(error));
}

7
static/js/bootstrap.bundle.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

84
static/js/merge.js Normal file
View File

@ -0,0 +1,84 @@
/* eslint-disable new-cap */
/* eslint-disable camelcase */
/* eslint-disable no-console */
let uuid;
let pdf_uploaded = false;
import('./pdf_api_wrapper.js');
// eslint-disable-next-line no-undef, no-unused-vars
const vueInstance = new Vue({
el: '#app',
data() {
return {
pdf_file: null,
project_uuid: null,
};
},
mounted() {
window.addEventListener('load', () => this.side_loaded());
},
methods: {
move_page(from_page, to_page) {
console.debug('trying to move page: ', from_page, to_page);
fetch(`/move_page/${this.project_uuid}/${from_page}/${to_page}`, {
method: 'GET',
})
.then((response) => response.json())
.then((data) => {
console.debug('data from Backend: ', data);
if (data.status === 200) {
this.display_pages();
console.info('Page was moved');
} else {
console.error('Page could not be moved');
}
})
.catch((error) => console.error(error));
},
side_loaded() {
document.getElementById('btn_download_complete').addEventListener('click', () => new pdf_api_wrapper(`${uuid}`).download_pdf());
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.
// eslint-disable-next-line no-undef
const dropzone = new Dropzone('div#pdf-dropzone', {
url: '/add_pdf_to_project/',
paramName: 'pdf',
});
dropzone.on('addedfile', (file) => {
console.log('A file has been added');
// eslint-disable-next-line no-param-reassign
file.previewElement.innerHTML = '';
console.debug('Suppress file.previewElement');
console.debug('PDF Project UUID: ', this.project_uuid);
});
dropzone.on('sending', (file, xhr, formData) => {
formData.append('uuid', uuid);
});
// eslint-disable-next-line no-unused-vars
dropzone.on('complete', (file, xhr, formData) => {
pdf_uploaded = true;
document.getElementById('btn-group-download').style.display = 'flex';
});
},
},
});

View File

@ -0,0 +1,41 @@
/* eslint-disable camelcase */
/* eslint-disable no-console */
// eslint-disable-next-line no-unused-vars
class pdf_api_wrapper {
constructor(project_uuid) {
this.uuid = project_uuid;
}
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() {
console.debug('download_pdf triggered');
this.downloadURI(`/projects/${this.uuid}/complete.pdf`, 'complete_project.pdf');
}
download_split_pdf() {
fetch(`/get_single_pages_archive/${this.uuid}/`, {
method: 'GET',
})
.then((response) => response.json())
.then((data) => {
console.debug('data from Backend: ', data);
if (data.status === 200) {
this.downloadURI(`/projects/${this.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));
}
}

156
static/js/project.js Normal file
View File

@ -0,0 +1,156 @@
/* eslint-disable new-cap */
/* eslint-disable camelcase */
/* eslint-disable no-console */
let uuid;
let pdf_uploaded = false;
import('./pdf_api_wrapper.js');
// eslint-disable-next-line no-undef, no-unused-vars
const vueInstance = new Vue({
el: '#app',
data() {
return {
pdf_file: null,
project_uuid: null,
};
},
mounted() {
window.addEventListener('load', () => this.side_loaded());
window.addEventListener('resize', () => this.scale_page_view());
},
methods: {
move_page(from_page, to_page) {
console.debug('trying to move page: ', from_page, to_page);
fetch(`/move_page/${this.project_uuid}/${from_page}/${to_page}`, {
method: 'GET',
})
.then((response) => response.json())
.then((data) => {
console.debug('data from Backend: ', data);
if (data.status === 200) {
this.display_pages();
console.info('Page was moved');
} else {
console.error('Page could not be moved');
}
})
.catch((error) => console.error(error));
},
scale_page_view() {
const overview_wrapper_width = document.getElementById('overview-wrapper').offsetWidth;
// console.debug('width: ', overview_wrapper_width)
const page_overview = document.getElementById('page_overview');
const page_overview_height = `${overview_wrapper_width}px`;
page_overview.style.height = page_overview_height;
let page_overview_margin_bottom = '';
if (!pdf_uploaded) {
page_overview_margin_bottom = `${-1 * (overview_wrapper_width)}px`;
page_overview.style.width = '0px';
} else {
page_overview_margin_bottom = `${-1 * (overview_wrapper_width - 500)}px`;
page_overview.style.width = '500px';
}
console.debug('page_overview_margin_bottom: ', page_overview_margin_bottom);
page_overview.style['margin-bottom'] = page_overview_margin_bottom;
},
display_pages() {
const 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) {
document.getElementById('page_overview').style.width = '500px'; // Set initial width...
this.scale_page_view();
// eslint-disable-next-line no-restricted-syntax
for (const page in data.pages) {
if (data.pages[page] !== undefined) {
console.debug(page);
const node = document.createElement('embed');
node.type = 'application/pdf';
node.src = data.pages[page];
node.classList.add('single_page_embed');
const div_page = document.createElement('div');
div_page.classList.add('single_page_div');
div_page.appendChild(node);
div_page.innerHTML += '<div class="btn-group pdf-interact-buttons" role="group" aria-label="page interact buttons" style="display: flex;">';
div_page.innerHTML += `<button type="button" class="btn btn-outline-secondary" onclick="vueInstance.move_page(${Number(page) + 1}, ${Number(page)})"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-box-arrow-left" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M6 12.5a.5.5 0 0 0 .5.5h8a.5.5 0 0 0 .5-.5v-9a.5.5 0 0 0-.5-.5h-8a.5.5 0 0 0-.5.5v2a.5.5 0 0 1-1 0v-2A1.5 1.5 0 0 1 6.5 2h8A1.5 1.5 0 0 1 16 3.5v9a1.5 1.5 0 0 1-1.5 1.5h-8A1.5 1.5 0 0 1 5 12.5v-2a.5.5 0 0 1 1 0z"/><path fill-rule="evenodd" d="M.146 8.354a.5.5 0 0 1 0-.708l3-3a.5.5 0 1 1 .708.708L1.707 7.5H10.5a.5.5 0 0 1 0 1H1.707l2.147 2.146a.5.5 0 0 1-.708.708z"/></svg></button>`;
div_page.innerHTML += '<button hidden type="button" class="btn btn-outline-secondary"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16"><path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0z"/><path d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4zM2.5 3h11V2h-11z"/></svg></button>';
div_page.innerHTML += `<button type="button" class="btn btn-outline-secondary" onclick="vueInstance.move_page(${Number(page) + 1}, ${Number(page) + 2})"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-box-arrow-right" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M10 12.5a.5.5 0 0 1-.5.5h-8a.5.5 0 0 1-.5-.5v-9a.5.5 0 0 1 .5-.5h8a.5.5 0 0 1 .5.5v2a.5.5 0 0 0 1 0v-2A1.5 1.5 0 0 0 9.5 2h-8A1.5 1.5 0 0 0 0 3.5v9A1.5 1.5 0 0 0 1.5 14h8a1.5 1.5 0 0 0 1.5-1.5v-2a.5.5 0 0 0-1 0z"></path><path fill-rule="evenodd" d="M15.854 8.354a.5.5 0 0 0 0-.708l-3-3a.5.5 0 0 0-.708.708L14.293 7.5H5.5a.5.5 0 0 0 0 1h8.793l-2.147 2.146a.5.5 0 0 0 .708.708z"></path></svg></button>`;
div_page.innerHTML += '</div>';
// node.display = 'inline-block'
div_overview.appendChild(div_page);
}
}
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);
},
side_loaded() {
document.getElementById('btn_download_complete').addEventListener('click', () => new pdf_api_wrapper(`${uuid}`).download_pdf());
document.getElementById('btn_download_singel_pages').addEventListener('click', () => new pdf_api_wrapper(`${uuid}`).download_split_pdf());
this.scale_page_view();
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.
// eslint-disable-next-line no-undef
const dropzone = new Dropzone('div#pdf-dropzone', {
url: '/add_pdf_to_project/',
paramName: 'pdf',
});
dropzone.on('addedfile', (file) => {
console.log('A file has been added');
// eslint-disable-next-line no-param-reassign
file.previewElement.innerHTML = '';
console.debug('Suppress file.previewElement');
console.debug('PDF Project UUID: ', this.project_uuid);
});
dropzone.on('sending', (file, xhr, formData) => {
formData.append('uuid', uuid);
});
// eslint-disable-next-line no-unused-vars
dropzone.on('complete', (file, xhr, formData) => {
pdf_uploaded = true;
document.getElementById('btn-group-download').style.display = 'flex';
this.display_pages();
});
},
},
});

84
static/js/split.js Normal file
View File

@ -0,0 +1,84 @@
/* eslint-disable new-cap */
/* eslint-disable camelcase */
/* eslint-disable no-console */
let uuid;
let pdf_uploaded = false;
import('./pdf_api_wrapper.js');
// eslint-disable-next-line no-undef, no-unused-vars
const vueInstance = new Vue({
el: '#app',
data() {
return {
pdf_file: null,
project_uuid: null,
};
},
mounted() {
window.addEventListener('load', () => this.side_loaded());
},
methods: {
move_page(from_page, to_page) {
console.debug('trying to move page: ', from_page, to_page);
fetch(`/move_page/${this.project_uuid}/${from_page}/${to_page}`, {
method: 'GET',
})
.then((response) => response.json())
.then((data) => {
console.debug('data from Backend: ', data);
if (data.status === 200) {
this.display_pages();
console.info('Page was moved');
} else {
console.error('Page could not be moved');
}
})
.catch((error) => console.error(error));
},
side_loaded() {
document.getElementById('btn_download_singel_pages').addEventListener('click', () => new pdf_api_wrapper(`${uuid}`).download_split_pdf());
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.
// eslint-disable-next-line no-undef
const dropzone = new Dropzone('div#pdf-dropzone', {
url: '/add_pdf_to_project/',
paramName: 'pdf',
});
dropzone.on('addedfile', (file) => {
console.log('A file has been added');
// eslint-disable-next-line no-param-reassign
file.previewElement.innerHTML = '';
console.debug('Suppress file.previewElement');
console.debug('PDF Project UUID: ', this.project_uuid);
});
dropzone.on('sending', (file, xhr, formData) => {
formData.append('uuid', uuid);
});
// eslint-disable-next-line no-unused-vars
dropzone.on('complete', (file, xhr, formData) => {
pdf_uploaded = true;
document.getElementById('btn-group-download').style.display = 'flex';
});
},
},
});

71
templates/base.html Normal file
View File

@ -0,0 +1,71 @@
<!DOCTYPE html>
<html lang="de" data-bs-theme="light">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="{{ url_for('static', filename='img/filetype-pdf.svg') }}" type="image/x-icon">
<link href="{{ url_for('static', filename='css/bootstrap.min.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='css/main.css') }}" rel="stylesheet">
<title>PDF-Web-Toolkit{% if page == 'project' %} | Project{% endif %}{% if page == 'split' %} | Split{% endif %}{% if page == 'merge' %} | Merge{% endif %}{% if page == 'ocr' %} | OCR{% endif %}</title>
</head>
<div class="col-lg-8 mx-auto py-md-5">
<header class="">
<nav class="navbar navbar-expand-lg border-bottom">
<div class="container">
<a class="navbar-brand" href="/">
<picture>
<source srcset="{{ url_for('static', filename='img/filetype-pdf.svg') }}" media="(prefers-color-scheme: dark)">
<img src="{{ url_for('static', filename='img/filetype-pdf.svg') }}" alt="PDF-Web-Toolkit" height="40">
</picture>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarContent" aria-controls="navbarContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarContent">
<ul class="navbar-nav mr-auto flex-fill">
<li class="nav-item {%if page == 'split'%}active{%endif%}"><a class="nav-link" href="/split">Simple Split</a></li>
<li class="nav-item {%if page == 'merge'%}active{%endif%}"><a class="nav-link" href="/merge">Simple Merge</a></li>
<li class="nav-item {%if page == 'ocr'%}active{%endif%}" hidden><a class="nav-link" href="/ocr">Simple OCR</a></li>
<li class="nav-item {%if page == 'project'%}active{%endif%}"><a class="nav-link" href="/project">Project</a></li>
</ul>
</div>
</div>
</nav>
</header>
<main class="py-3">
<div class="container py-3">
{% if page == 'project' %}
{% include 'partials/project.html' %}
{% endif %}
{% if page == 'split' %}
{% include 'partials/split.html' %}
{% endif %}
{% if page == 'merge' %}
{% include 'partials/merge.html' %}
{% endif %}
{% if page == 'ocr' %}
{% include 'partials/ocr.html' %}
{% endif %}
</div>
</main>
<!-- https://getbootstrap.com/docs/5.3/examples/footers/# -->
<footer class="py-3 my-4 border-top">
<div class="container py-3">
<p class="text-center text-body-secondary text-muted">Developed with ♥ by <a href="https://github.com/tidoni/pdf-web-toolkit" target="_blank">Tidoni</a> (Niklas Müller)</p>
</div>
</footer>
<script src="{{ url_for('static', filename='js/bootstrap.bundle.min.js') }}"></script>
</body>
</html>

View File

@ -1,80 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PDF Tools</title>
</head>
<body>
<div id="app">
<h1>PDF Splitter</h1>
<input type="file" @change="handleFileSelect($event, 'split_pdfFile')" accept=".pdf"><br>
<button @click="uploadPDF('split_pdfFile')">Split PDF into individual pages</button>
<h1>PDF Merger</h1>
<input type="file" @change="handleFileSelect($event, 'merge_pdfFile_1')" accept=".pdf"><br>
<input type="file" @change="handleFileSelect($event, 'merge_pdfFile_2')" accept=".pdf"><br>
<button @click="uploadPDF('merge_pdfFile_1', 'merge_pdfFile_2')">Merge the two selected PDFs</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
<script>
new Vue({
el: '#app',
data: {
split_pdfFile: null,
merge_pdfFile_1: null,
merge_pdfFile_2: null
},
methods: {
handleFileSelect(event, inputId) {
const fileInput = event.target;
this[inputId] = fileInput.files[0];
console.debug("fileInput", fileInput);
console.debug("this[inputId]", this[inputId]);
},
uploadPDF(fileInputId1, fileInputId2) {
const file1 = this[fileInputId1];
const file2 = fileInputId2 ? this[fileInputId2] : null;
console.debug("file1", file1)
console.debug("file2", file2)
const formData = new FormData();
formData.append('pdf_1', file1);
if (file2) {
formData.append('pdf_2', file2);
}
console.debug("formData", formData)
const backendURL = file2 ? '/merge_to_pdf' : '/split_to_zip';
fetch(backendURL, {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
console.debug("data from Backend: ", data);
this.downloadURI(data["url"], data["name"]);
})
.catch(error => console.error(error));
},
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;
}
}
});
</script>
</body>
</html>

View File

@ -0,0 +1,11 @@
<!-- 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" />
<!-- <script src="{{ url_for('static', filename='js/dropzone_functions.js') }}"></script> -->
<link rel="stylesheet" href="{{ url_for('static', filename='css/dropzone_styles.css') }}">
<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>

View File

@ -0,0 +1,16 @@
<div class="page_description_div" id="merge_description_div">
<p class="page_description_p" id="merge_description_p">
Upload PDFs that you want to merge.
</p>
</div>
{% include 'partials/dropzone.html' %}
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
<script src="{{ url_for('static', filename='js/pdf_api_wrapper.js') }}"></script>
<script src="{{ url_for('static', filename='js/merge.js') }}"></script>
<link href="{{ url_for('static', filename='css/merge.css') }}" rel="stylesheet">
<div id="btn-group-download" class="btn-group" role="group" aria-label="Download outlined buttons">
<button type="button" class="btn btn-outline-dark" id="btn_download_complete">Download merged PDFs</button>
</div>

View File

@ -0,0 +1,3 @@
{% include 'partials/dropzone.html' %}
From ocr.html

View File

@ -0,0 +1,30 @@
<div class="page_description_div" id="project_description_div">
<p class="page_description_p" id="project_description_p">
Here you can create a Project out of multiple PDFs, move pages around and download the adapted File.
</p>
</div>
{% include 'partials/dropzone.html' %}
<div class="clearfix"></div>
<div style="height: 2em;"></div>
<div id="overview-wrapper">
<div id="page_overview">
<!-- Filled by dynamically loaded content -->
<!--
<div class="single_page_div">PDF_1</div>
<div class="single_page_div">PDF_2</div>
<div class="single_page_div">PDF_3</div>
-->
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
<script src="{{ url_for('static', filename='js/pdf_api_wrapper.js') }}"></script>
<script src="{{ url_for('static', filename='js/project.js') }}"></script>
<link href="{{ url_for('static', filename='css/project.css') }}" rel="stylesheet">
<div id="btn-group-download" class="btn-group" role="group" aria-label="Download outlined buttons">
<button type="button" class="btn btn-outline-dark" id="btn_download_complete">Download whole Project</button>
<button type="button" class="btn btn-outline-dark" id="btn_download_singel_pages">Download Project as single Pages</button>
</div>

View File

@ -0,0 +1,16 @@
<div class="page_description_div" id="splitt_description_div">
<p class="page_description_p" id="splitt_description_p">
Upload a PDF that you want to be splitt into seperate pages.
</p>
</div>
{% include 'partials/dropzone.html' %}
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
<script src="{{ url_for('static', filename='js/pdf_api_wrapper.js') }}"></script>
<script src="{{ url_for('static', filename='js/split.js') }}"></script>
<link href="{{ url_for('static', filename='css/split.css') }}" rel="stylesheet">
<div id="btn-group-download" class="btn-group" role="group" aria-label="Download outlined buttons">
<button type="button" class="btn btn-outline-dark" id="btn_download_singel_pages">Download Project as single Pages</button>
</div>