Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

custom title page for pdf export #205

Merged
merged 5 commits into from
Mar 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
logbook.pdf
*.pdf
map.png
.logbook.json

Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## [Unreleased]

- New: Add support for a custom title page for PDF A4/A5 exports.
- Update: Update openlayers lib from 7.3.0 to 9.0.0. No UI changes.
- Update: Update golang from 1.20.3 to 1.21.8. No UI changes.
- Fix: Finally fixed the unit tests. No UI changes.
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ You also can easily export all flight records into EASA style pdf format, print

## [Unreleased]

- New: Add support for a custom title page for PDF A4/A5 exports.
- Update: Update openlayers lib from 7.3.0 to 9.0.0. No UI changes.
- Update: Update golang from 1.20.3 to 1.21.8. No UI changes.
- Fix: Finally fixed the unit tests. No UI changes.
Expand Down
8 changes: 7 additions & 1 deletion cmd/web/handlers_export.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,20 @@ func (app *application) HandlerExportLogbook(w http.ResponseWriter, r *http.Requ
Signature: settings.SignatureText,
SignatureImage: settings.SignatureImage,
}
var customTitleUUID string

if format == exportA4 {
logbook.Export = settings.ExportA4
customTitleUUID = settings.ExportA4.CustomTitle

} else if format == exportA5 {
logbook.Export = settings.ExportA5
customTitleUUID = settings.ExportA5.CustomTitle
}

att, _ := app.db.GetAttachmentByID(customTitleUUID)
logbook.Export.CustomTitleBlob = att.Document

err = logbook.ExportPDF(format, flightRecords, w)

} else if format == exportCSV {
Expand All @@ -89,7 +96,6 @@ func (app *application) HandlerExportLogbook(w http.ResponseWriter, r *http.Requ

} else {
err = errors.New("unknown export format")

}

if err != nil {
Expand Down
41 changes: 39 additions & 2 deletions cmd/web/templates/export-a4.partials.gohtml
Original file line number Diff line number Diff line change
Expand Up @@ -375,8 +375,10 @@
</div>
<div class="col-md-6">
<div class="row mb-2">
<div class="col">
<label for="time_field_format_a4" class="col-sm-8 col-form-label">Time fields autoformat</label>
<div class="col-sm-8">
<label for="time_field_format_a4" class="col-form-label">Time fields autoformat</label>
</div>
<div class="col-sm-4 text-end">
<div class="btn-group" role="group" id="time_field_format_a4" name="time_field_format_a4">
<input type="radio" class="btn-check" name="time_field_format_radio_a4" id="time_field_format_radio1_a4" autocomplete="off" {{if eq $settings.ExportA4.TimeFieldsAutoFormat 0}}checked{{end}}>
<label class="btn btn-sm btn-outline-secondary" for="time_field_format_radio1_a4">None</label>
Expand All @@ -389,6 +391,14 @@
</div>
</div>
</div>
<div class="row mb-2">
<div class="col-sm-8">
<label for="custom_title_a4" class="col-sm-5 col-form-label">Custom A4 title page</label>
</div>
<div class="col-sm-4 text-end">
<button type="button" class="btn btn-sm btn-secondary" onclick="wlbExport.showCustomTitleModal('A4');"><i class="bi bi-file-pdf"></i> Manage...</button>
</div>
</div>
</div>
</div>

Expand All @@ -397,4 +407,31 @@
<button class="btn btn-sm btn-outline-secondary" type="button" onclick="wlbExport.saveExportA4()" id="save_a4"><i class="bi bi-save-fill"></i> Save</button>
<button class="btn btn-sm btn-outline-secondary" type="button" onclick="wlbCommon.runExport('{{$api.ExportFormatA4}}')" id="export_a4"><i class="bi bi-file-pdf-fill"></i> Export PDF A4</button>


<!-- Modal -->
<div class="modal fade" id="custom_title_a4_modal" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="custom_title_a4_modal" aria-hidden="true">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="custom_title_a4_modal">Custom title page</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="col">
<div class="input-group">
<input type="file" accept=".pdf" class="form-control form-control-sm" id="custom_title_a4" name="custom_title_a4" />

<button type="button" class="btn btn-sm btn-secondary" onclick="wlbExport.deleteCustomTitle('A4');"><i class="bi bi-file-x"></i> Delete</button>
</div>
</div>
<br>
<div id="custom_title_a4_document"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-sm btn-outline-secondary" data-bs-dismiss="modal" onclick="wlbExport.saveExportA4()"><i class="bi bi-save-fill"></i> Save</button>
<button type="button" class="btn btn-sm btn-outline-secondary" data-bs-dismiss="modal"><i class="bi bi-arrow-counterclockwise"></i> OK</button>
</div>
</div>
</div>
</div>
{{end}}
40 changes: 38 additions & 2 deletions cmd/web/templates/export-a5.partials.gohtml
Original file line number Diff line number Diff line change
Expand Up @@ -383,8 +383,10 @@
</div>
<div class="col-md-6">
<div class="row mb-2">
<div class="col">
<label for="time_field_format_a5" class="col-sm-8 col-form-label">Time fields autoformat</label>
<div class="col-sm-8">
<label for="time_field_format_a5" class="col-form-label">Time fields autoformat</label>
</div>
<div class="col-sm-4 text-end">
<div class="btn-group" role="group" id="time_field_format_a5" name="time_field_format_a5">
<input type="radio" class="btn-check" name="time_field_format_radio_a5" id="time_field_format_radio1_a5" autocomplete="off" {{if eq $settings.ExportA5.TimeFieldsAutoFormat 0}}checked{{end}}>
<label class="btn btn-sm btn-outline-secondary" for="time_field_format_radio1_a5">None</label>
Expand All @@ -397,6 +399,14 @@
</div>
</div>
</div>
<div class="row mb-2">
<div class="col-sm-8">
<label for="custom_title_a5" class="col-sm-5 col-form-label">Custom A5 title page</label>
</div>
<div class="col-sm-4 text-end">
<button type="button" class="btn btn-sm btn-secondary" onclick="wlbExport.showCustomTitleModal('A5');"><i class="bi bi-file-pdf"></i> Manage...</button>
</div>
</div>
</div>
</div>

Expand All @@ -405,4 +415,30 @@
<button class="btn btn-sm btn-outline-secondary" type="button" onclick="wlbExport.saveExportA5()" id="save_a5"><i class="bi bi-save-fill"></i> Save</button>
<button class="btn btn-sm btn-outline-secondary" type="button" onclick="wlbCommon.runExport('{{$api.ExportFormatA5}}')" id="export_a5"><i class="bi bi-file-pdf-fill"></i> Export PDF A5</button>

<!-- Modal -->
<div class="modal fade" id="custom_title_a5_modal" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="custom_title_a5_modal" aria-hidden="true">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="custom_title_a5_modal">Custom title page</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="col">
<div class="input-group">
<input type="file" accept=".pdf" class="form-control form-control-sm" id="custom_title_a5" name="custom_title_a5" />

<button type="button" class="btn btn-sm btn-secondary" onclick="wlbExport.deleteCustomTitle('A5');"><i class="bi bi-file-x"></i> Delete</button>
</div>
</div>
<br>
<div id="custom_title_a5_document"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-sm btn-outline-secondary" data-bs-dismiss="modal" onclick="wlbExport.saveExportA5()"><i class="bi bi-save-fill"></i> Save</button>
<button type="button" class="btn btn-sm btn-outline-secondary" data-bs-dismiss="modal"><i class="bi bi-arrow-counterclockwise"></i> OK</button>
</div>
</div>
</div>
</div>
{{end}}
156 changes: 123 additions & 33 deletions cmd/web/templates/export-js.partials.gohtml
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,102 @@
// WebLogbook Export Namespace
wlbExport = function () {

function saveExportA4() {
const formatMap = {
"A4": "custom_title_a4",
"A5": "custom_title_a5"
};

async function getCustomTitle(format) {
const attachments_url = "{{index .API "LogbookUUIDAttachments"}}";

if (!formatMap[format]) {return;}

const url = attachments_url.replace("{uuid}", formatMap[format]);
const data = await wlbCommon.getJSON(url);
if (data.length > 0) {
return data[0].uuid;
} else {
return ""
}
}

async function deleteCustomTitle(format) {
// drop current saved file from attachments
const attachments_url = "{{index .API "LogbookUUIDAttachments"}}".replace("{uuid}", formatMap[format]);
const data = await wlbCommon.getJSON(attachments_url);

if (data.length > 0) {
for (let i = 0; i < data.length; i++) {
const payload = {uuid: data[i].uuid}
const requestOptions = {method: "post", body: JSON.stringify(payload)};
await fetch("{{index .API "LogbookAttachmentsDelete"}}", requestOptions)
}
document.getElementById(`${formatMap[format]}_document`).innerHTML = "";
}
}

async function saveCustomTitle(format) {
const custom_title_element = document.getElementById(formatMap[format]);
const file = custom_title_element.files[0];

if (file) {
await deleteCustomTitle(format);

// upload new file as attachment
let payload = new FormData();
payload.append("document", file);
payload.append("record_id", formatMap[format]);

const requestOptions = {method: "post", body: payload};
await fetch("{{index .API "LogbookAttachmentsUpload"}}", requestOptions)

// update uuid in settings
const attachments_url = "{{index .API "LogbookUUIDAttachments"}}".replace("{uuid}", formatMap[format]);
const data = await wlbCommon.getJSON(attachments_url);
if (data.length > 0) {
return data[0].uuid;
} else {
return ""
}
} else {
const uuid = await getCustomTitle(format);
return uuid;
}
}

function showCustomTitleModal(format) {
const modal = new bootstrap.Modal(document.getElementById(`${formatMap[format]}_modal`), {});

getCustomTitle(format).then(uuid => {
if (uuid !== "") {
const data = "{{$api.LogbookAttachmentsDownload}}"+uuid;
fetch(data).then(response => response.blob()).then(blob => {
const reader = new FileReader();
reader.onload = function() {
const text = `<object data="${reader.result}" width="100%" height="700px">
<p>Your browser does not support preview of the document</p>
</object>`;

document.getElementById(`${formatMap[format]}_document`).innerHTML = text;
};
reader.readAsDataURL(blob);
});
}
});

modal.show();
}

async function saveExportA4() {
let time_fields_auto_format = 0;
if (document.getElementById("time_field_format_radio2_a4").checked) {
time_fields_auto_format = 1;
} else if (document.getElementById("time_field_format_radio3_a4").checked) {
time_fields_auto_format = 2;
}

let custom_title_a4 = await saveCustomTitle("A4");

let payload = {
export_a4: {
logbook_rows: parseInt(document.getElementById("logbook_rows_a4").value),
Expand All @@ -26,6 +114,7 @@ wlbExport = function () {
include_signature: document.getElementById("include_signature_a4").checked,
is_extended: document.getElementById("is_extended_a4").checked,
time_fields_auto_format: time_fields_auto_format,
custom_title: custom_title_a4,
columns: {
col1: parseFloat(document.getElementById("col1_a4").value),
col2: parseFloat(document.getElementById("col2_a4").value),
Expand Down Expand Up @@ -88,36 +177,36 @@ wlbExport = function () {
};

const requestOptions = {
method: 'post',
method: "post",
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
"Accept": "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
};

fetch("{{$api.Export}}/{{$api.ExportFormatA4}}", requestOptions)
.then(response => response.json())
.then(function(data) {
if (data.ok) {
wlbCommon.showInfoMessage(data.message);
if (typeof data.redirect_url !== 'undefined') {
location.href = data.redirect_url;
}
} else {
wlbCommon.showErrorMessage(data.message);
}
});
const response = await fetch("{{$api.Export}}/{{$api.ExportFormatA4}}", requestOptions);
const data = await response.json();
if (data.ok) {
wlbCommon.showInfoMessage(data.message);
if (typeof data.redirect_url !== "undefined") {
location.href = data.redirect_url;
}
} else {
wlbCommon.showErrorMessage(data.message);
}
}

function saveExportA5() {
async function saveExportA5() {
let time_fields_auto_format = 0;
if (document.getElementById("time_field_format_radio2_a5").checked) {
time_fields_auto_format = 1;
} else if (document.getElementById("time_field_format_radio3_a5").checked) {
time_fields_auto_format = 2;
}

let custom_title_a5 = await saveCustomTitle("A5");

let payload = {
export_a5: {
logbook_rows: parseInt(document.getElementById("logbook_rows_a5").value),
Expand All @@ -132,6 +221,7 @@ wlbExport = function () {
include_signature: document.getElementById("include_signature_a5").checked,
is_extended: document.getElementById("is_extended_a5").checked,
time_fields_auto_format: time_fields_auto_format,
custom_title: custom_title_a5,
columns: {
col1: parseFloat(document.getElementById("col1_a5").value),
col2: parseFloat(document.getElementById("col2_a5").value),
Expand Down Expand Up @@ -194,26 +284,24 @@ wlbExport = function () {
};

const requestOptions = {
method: 'post',
method: "post",
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
"Accept": "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
};

fetch("{{$api.Export}}/{{$api.ExportFormatA5}}", requestOptions)
.then(response => response.json())
.then(function(data) {
if (data.ok) {
wlbCommon.showInfoMessage(data.message);
if (typeof data.redirect_url !== 'undefined') {
location.href = data.redirect_url;
}
} else {
wlbCommon.showErrorMessage(data.message);
}
});
const response = await fetch("{{$api.Export}}/{{$api.ExportFormatA5}}", requestOptions);
const data = await response.json();
if (data.ok) {
wlbCommon.showInfoMessage(data.message);
if (typeof data.redirect_url !== "undefined") {
location.href = data.redirect_url;
}
} else {
wlbCommon.showErrorMessage(data.message);
}
}

function saveExportXLS() {
Expand Down Expand Up @@ -317,7 +405,9 @@ wlbExport = function () {
saveExportCSV:saveExportCSV,
saveExportXLS:saveExportXLS,
restoreDefaults:restoreDefaults,
showTab:showTab
showTab:showTab,
deleteCustomTitle:deleteCustomTitle,
showCustomTitleModal:showCustomTitleModal
}
}();

Expand Down
Loading
Loading