Merge branch 'pipeline' of git@github.com:Frooodle/Stirling-PDF.git into pipeline
This commit is contained in:
3
src/main/resources/static/images/pipeline.svg
Normal file
3
src/main/resources/static/images/pipeline.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-bezier2" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M1 2.5A1.5 1.5 0 0 1 2.5 1h1A1.5 1.5 0 0 1 5 2.5h4.134a1 1 0 1 1 0 1h-2.01c.18.18.34.381.484.605.638.992.892 2.354.892 3.895 0 1.993.257 3.092.713 3.7.356.476.895.721 1.787.784A1.5 1.5 0 0 1 12.5 11h1a1.5 1.5 0 0 1 1.5 1.5v1a1.5 1.5 0 0 1-1.5 1.5h-1a1.5 1.5 0 0 1-1.5-1.5H6.866a1 1 0 1 1 0-1h1.711a2.839 2.839 0 0 1-.165-.2C7.743 11.407 7.5 10.007 7.5 8c0-1.46-.246-2.597-.733-3.355-.39-.605-.952-1-1.767-1.112A1.5 1.5 0 0 1 3.5 5h-1A1.5 1.5 0 0 1 1 3.5v-1zM2.5 2a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1zm10 10a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 790 B |
@@ -218,7 +218,7 @@ const DraggableUtils = {
|
||||
},
|
||||
async getOverlayedPdfDocument() {
|
||||
const pdfBytes = await this.pdfDoc.getData();
|
||||
const pdfDocModified = await PDFLib.PDFDocument.load(pdfBytes);
|
||||
const pdfDocModified = await PDFLib.PDFDocument.load(pdfBytes, { ignoreEncryption: true });
|
||||
this.storePageContents();
|
||||
|
||||
const pagesMap = this.documentsMap.get(this.pdfDoc);
|
||||
|
||||
@@ -152,7 +152,7 @@ class PdfContainer {
|
||||
|
||||
async toPdfLib(objectUrl) {
|
||||
const existingPdfBytes = await fetch(objectUrl).then(res => res.arrayBuffer());
|
||||
const pdfDoc = await PDFLib.PDFDocument.load(existingPdfBytes);
|
||||
const pdfDoc = await PDFLib.PDFDocument.load(existingPdfBytes, { ignoreEncryption: true });
|
||||
return pdfDoc;
|
||||
}
|
||||
|
||||
|
||||
385
src/main/resources/static/js/pipeline.js
Normal file
385
src/main/resources/static/js/pipeline.js
Normal file
@@ -0,0 +1,385 @@
|
||||
document.getElementById('validateButton').addEventListener('click', function(event) {
|
||||
event.preventDefault();
|
||||
validatePipeline();
|
||||
});
|
||||
function validatePipeline() {
|
||||
let pipelineListItems = document.getElementById('pipelineList').children;
|
||||
let isValid = true;
|
||||
let containsAddPassword = false;
|
||||
for (let i = 0; i < pipelineListItems.length - 1; i++) {
|
||||
let currentOperation = pipelineListItems[i].querySelector('.operationName').textContent;
|
||||
let nextOperation = pipelineListItems[i + 1].querySelector('.operationName').textContent;
|
||||
if (currentOperation === '/add-password') {
|
||||
containsAddPassword = true;
|
||||
}
|
||||
console.log(currentOperation);
|
||||
console.log(apiDocs[currentOperation]);
|
||||
let currentOperationDescription = apiDocs[currentOperation]?.post?.description || "";
|
||||
let nextOperationDescription = apiDocs[nextOperation]?.post?.description || "";
|
||||
|
||||
console.log("currentOperationDescription", currentOperationDescription);
|
||||
console.log("nextOperationDescription", nextOperationDescription);
|
||||
|
||||
let currentOperationOutput = currentOperationDescription.match(/Output:([A-Z\/]*)/)?.[1] || "";
|
||||
let nextOperationInput = nextOperationDescription.match(/Input:([A-Z\/]*)/)?.[1] || "";
|
||||
|
||||
console.log("Operation " + currentOperation + " Output: " + currentOperationOutput);
|
||||
console.log("Operation " + nextOperation + " Input: " + nextOperationInput);
|
||||
|
||||
// Splitting in case of multiple possible output/input
|
||||
let currentOperationOutputArr = currentOperationOutput.split('/');
|
||||
let nextOperationInputArr = nextOperationInput.split('/');
|
||||
|
||||
if (currentOperationOutput !== 'ANY' && nextOperationInput !== 'ANY') {
|
||||
let intersection = currentOperationOutputArr.filter(value => nextOperationInputArr.includes(value));
|
||||
console.log(`Intersection: ${intersection}`);
|
||||
|
||||
if (intersection.length === 0) {
|
||||
isValid = false;
|
||||
console.log(`Incompatible operations: The output of operation '${currentOperation}' (${currentOperationOutput}) is not compatible with the input of the following operation '${nextOperation}' (${nextOperationInput}).`);
|
||||
alert(`Incompatible operations: The output of operation '${currentOperation}' (${currentOperationOutput}) is not compatible with the input of the following operation '${nextOperation}' (${nextOperationInput}).`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (containsAddPassword && pipelineListItems[pipelineListItems.length - 1].querySelector('.operationName').textContent !== '/add-password') {
|
||||
alert('The "add-password" operation should be at the end of the operations sequence. Please adjust the operations order.');
|
||||
return false;
|
||||
}
|
||||
if (isValid) {
|
||||
console.log('Pipeline is valid');
|
||||
// Continue with the pipeline operation
|
||||
} else {
|
||||
console.error('Pipeline is not valid');
|
||||
// Stop operation, maybe display an error to the user
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
document.getElementById('submitConfigBtn').addEventListener('click', function() {
|
||||
|
||||
if (validatePipeline() === false) {
|
||||
return;
|
||||
}
|
||||
let selectedOperation = document.getElementById('operationsDropdown').value;
|
||||
let parameters = operationSettings[selectedOperation] || {};
|
||||
|
||||
let pipelineConfig = {
|
||||
"name": "uniquePipelineName",
|
||||
"pipeline": [{
|
||||
"operation": selectedOperation,
|
||||
"parameters": parameters
|
||||
}]
|
||||
};
|
||||
|
||||
let pipelineConfigJson = JSON.stringify(pipelineConfig, null, 2);
|
||||
|
||||
let formData = new FormData();
|
||||
|
||||
let fileInput = document.getElementById('fileInput');
|
||||
let files = fileInput.files;
|
||||
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
console.log("files[i]", files[i].name);
|
||||
formData.append('fileInput', files[i], files[i].name);
|
||||
}
|
||||
|
||||
console.log("pipelineConfigJson", pipelineConfigJson);
|
||||
formData.append('json', pipelineConfigJson);
|
||||
console.log("formData", formData);
|
||||
|
||||
fetch('/handleData', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.blob())
|
||||
.then(blob => {
|
||||
|
||||
let url = window.URL.createObjectURL(blob);
|
||||
let a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = 'outputfile';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error:', error);
|
||||
});
|
||||
});
|
||||
|
||||
let apiDocs = {};
|
||||
|
||||
let operationSettings = {};
|
||||
|
||||
fetch('v3/api-docs')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
apiDocs = data.paths;
|
||||
let operationsDropdown = document.getElementById('operationsDropdown');
|
||||
|
||||
operationsDropdown.innerHTML = '';
|
||||
|
||||
Object.keys(apiDocs).forEach(operation => {
|
||||
if (apiDocs[operation].hasOwnProperty('post')) {
|
||||
let option = document.createElement('option');
|
||||
option.textContent = operation;
|
||||
operationsDropdown.appendChild(option);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById('addOperationBtn').addEventListener('click', function() {
|
||||
let selectedOperation = document.getElementById('operationsDropdown').value;
|
||||
let pipelineList = document.getElementById('pipelineList');
|
||||
|
||||
let listItem = document.createElement('li');
|
||||
listItem.className = "list-group-item";
|
||||
listItem.innerHTML = `
|
||||
<div class="d-flex justify-content-between align-items-center w-100">
|
||||
<div class="operationName">${selectedOperation}</div>
|
||||
<div class="arrows d-flex">
|
||||
<button class="btn btn-secondary move-up btn-margin"><span>↑</span></button>
|
||||
<button class="btn btn-secondary move-down btn-margin"><span>↓</span></button>
|
||||
<button class="btn btn-warning pipelineSettings btn-margin"><span>⚙️</span></button>
|
||||
<button class="btn btn-danger remove"><span>X</span></button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
pipelineList.appendChild(listItem);
|
||||
|
||||
listItem.querySelector('.move-up').addEventListener('click', function(event) {
|
||||
event.preventDefault();
|
||||
if (listItem.previousElementSibling) {
|
||||
pipelineList.insertBefore(listItem, listItem.previousElementSibling);
|
||||
}
|
||||
});
|
||||
|
||||
listItem.querySelector('.move-down').addEventListener('click', function(event) {
|
||||
event.preventDefault();
|
||||
if (listItem.nextElementSibling) {
|
||||
pipelineList.insertBefore(listItem.nextElementSibling, listItem);
|
||||
}
|
||||
});
|
||||
|
||||
listItem.querySelector('.remove').addEventListener('click', function(event) {
|
||||
event.preventDefault();
|
||||
pipelineList.removeChild(listItem);
|
||||
});
|
||||
|
||||
listItem.querySelector('.pipelineSettings').addEventListener('click', function(event) {
|
||||
event.preventDefault();
|
||||
showpipelineSettingsModal(selectedOperation);
|
||||
});
|
||||
|
||||
function showpipelineSettingsModal(operation) {
|
||||
let pipelineSettingsModal = document.getElementById('pipelineSettingsModal');
|
||||
let pipelineSettingsContent = document.getElementById('pipelineSettingsContent');
|
||||
let operationData = apiDocs[operation].post.parameters || [];
|
||||
|
||||
pipelineSettingsContent.innerHTML = '';
|
||||
|
||||
operationData.forEach(parameter => {
|
||||
let parameterDiv = document.createElement('div');
|
||||
parameterDiv.className = "form-group";
|
||||
|
||||
let parameterLabel = document.createElement('label');
|
||||
parameterLabel.textContent = `${parameter.name} (${parameter.schema.type}): `;
|
||||
parameterLabel.title = parameter.description;
|
||||
parameterDiv.appendChild(parameterLabel);
|
||||
|
||||
let parameterInput;
|
||||
switch (parameter.schema.type) {
|
||||
case 'string':
|
||||
case 'number':
|
||||
case 'integer':
|
||||
parameterInput = document.createElement('input');
|
||||
parameterInput.type = parameter.schema.type === 'string' ? 'text' : 'number';
|
||||
parameterInput.className = "form-control";
|
||||
break;
|
||||
case 'boolean':
|
||||
parameterInput = document.createElement('input');
|
||||
parameterInput.type = 'checkbox';
|
||||
break;
|
||||
case 'array':
|
||||
case 'object':
|
||||
parameterInput = document.createElement('textarea');
|
||||
parameterInput.placeholder = `Enter a JSON formatted ${parameter.schema.type}`;
|
||||
parameterInput.className = "form-control";
|
||||
break;
|
||||
case 'enum':
|
||||
parameterInput = document.createElement('select');
|
||||
parameterInput.className = "form-control";
|
||||
parameter.schema.enum.forEach(option => {
|
||||
let optionElement = document.createElement('option');
|
||||
optionElement.value = option;
|
||||
optionElement.text = option;
|
||||
parameterInput.appendChild(optionElement);
|
||||
});
|
||||
break;
|
||||
default:
|
||||
parameterInput = document.createElement('input');
|
||||
parameterInput.type = 'text';
|
||||
parameterInput.className = "form-control";
|
||||
}
|
||||
parameterInput.id = parameter.name;
|
||||
|
||||
if (operationSettings[operation] && operationSettings[operation][parameter.name] !== undefined) {
|
||||
let savedValue = operationSettings[operation][parameter.name];
|
||||
|
||||
switch (parameter.schema.type) {
|
||||
case 'number':
|
||||
case 'integer':
|
||||
parameterInput.value = savedValue.toString();
|
||||
break;
|
||||
case 'boolean':
|
||||
parameterInput.checked = savedValue;
|
||||
break;
|
||||
case 'array':
|
||||
case 'object':
|
||||
parameterInput.value = JSON.stringify(savedValue);
|
||||
break;
|
||||
default:
|
||||
parameterInput.value = savedValue;
|
||||
}
|
||||
}
|
||||
|
||||
parameterDiv.appendChild(parameterInput);
|
||||
|
||||
pipelineSettingsContent.appendChild(parameterDiv);
|
||||
});
|
||||
|
||||
let saveButton = document.createElement('button');
|
||||
saveButton.textContent = "Save Settings";
|
||||
saveButton.className = "btn btn-primary";
|
||||
saveButton.addEventListener('click', function(event) {
|
||||
event.preventDefault();
|
||||
let settings = {};
|
||||
operationData.forEach(parameter => {
|
||||
let value = document.getElementById(parameter.name).value;
|
||||
switch (parameter.schema.type) {
|
||||
case 'number':
|
||||
case 'integer':
|
||||
settings[parameter.name] = Number(value);
|
||||
break;
|
||||
case 'boolean':
|
||||
settings[parameter.name] = document.getElementById(parameter.name).checked;
|
||||
break;
|
||||
case 'array':
|
||||
case 'object':
|
||||
try {
|
||||
settings[parameter.name] = JSON.parse(value);
|
||||
} catch (err) {
|
||||
console.error(`Invalid JSON format for ${parameter.name}`);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
settings[parameter.name] = value;
|
||||
}
|
||||
});
|
||||
operationSettings[operation] = settings;
|
||||
console.log(settings);
|
||||
pipelineSettingsModal.style.display = "none";
|
||||
});
|
||||
pipelineSettingsContent.appendChild(saveButton);
|
||||
|
||||
pipelineSettingsModal.style.display = "block";
|
||||
|
||||
pipelineSettingsModal.getElementsByClassName("close")[0].onclick = function() {
|
||||
pipelineSettingsModal.style.display = "none";
|
||||
}
|
||||
|
||||
window.onclick = function(event) {
|
||||
if (event.target == pipelineSettingsModal) {
|
||||
pipelineSettingsModal.style.display = "none";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('savePipelineBtn').addEventListener('click', function() {
|
||||
if (validatePipeline() === false) {
|
||||
return;
|
||||
}
|
||||
let pipelineList = document.getElementById('pipelineList').children;
|
||||
let pipelineConfig = {
|
||||
"name": "uniquePipelineName",
|
||||
"pipeline": []
|
||||
};
|
||||
|
||||
for (let i = 0; i < pipelineList.length; i++) {
|
||||
let operationName = pipelineList[i].querySelector('.operationName').textContent;
|
||||
let parameters = operationSettings[operationName] || {};
|
||||
|
||||
pipelineConfig.pipeline.push({
|
||||
"operation": operationName,
|
||||
"parameters": parameters
|
||||
});
|
||||
}
|
||||
|
||||
let a = document.createElement('a');
|
||||
a.href = URL.createObjectURL(new Blob([JSON.stringify(pipelineConfig, null, 2)], {
|
||||
type: 'application/json'
|
||||
}));
|
||||
a.download = 'pipelineConfig.json';
|
||||
a.style.display = 'none';
|
||||
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
});
|
||||
|
||||
document.getElementById('uploadPipelineBtn').addEventListener('click', function() {
|
||||
document.getElementById('uploadPipelineInput').click();
|
||||
});
|
||||
|
||||
document.getElementById('uploadPipelineInput').addEventListener('change', function(e) {
|
||||
let reader = new FileReader();
|
||||
reader.onload = function(event) {
|
||||
let pipelineConfig = JSON.parse(event.target.result);
|
||||
let pipelineList = document.getElementById('pipelineList');
|
||||
|
||||
while (pipelineList.firstChild) {
|
||||
pipelineList.removeChild(pipelineList.firstChild);
|
||||
}
|
||||
|
||||
pipelineConfig.pipeline.forEach(operationConfig => {
|
||||
let operationsDropdown = document.getElementById('operationsDropdown');
|
||||
operationsDropdown.value = operationConfig.operation;
|
||||
operationSettings[operationConfig.operation] = operationConfig.parameters;
|
||||
document.getElementById('addOperationBtn').click();
|
||||
|
||||
let lastOperation = pipelineList.lastChild;
|
||||
|
||||
lastOperation.querySelector('.pipelineSettings').click();
|
||||
|
||||
Object.keys(operationConfig.parameters).forEach(parameterName => {
|
||||
let input = document.getElementById(parameterName);
|
||||
if (input) {
|
||||
switch (input.type) {
|
||||
case 'checkbox':
|
||||
input.checked = operationConfig.parameters[parameterName];
|
||||
break;
|
||||
case 'number':
|
||||
input.value = operationConfig.parameters[parameterName].toString();
|
||||
break;
|
||||
case 'text':
|
||||
case 'textarea':
|
||||
default:
|
||||
input.value = JSON.stringify(operationConfig.parameters[parameterName]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelector('#pipelineSettingsModal .btn-primary').click();
|
||||
});
|
||||
};
|
||||
reader.readAsText(e.target.files[0]);
|
||||
});
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user