feat(workflow): add altenate merge tool and translate workflow node and engine errors across locales

This commit is contained in:
alam00000
2026-04-30 13:16:48 +05:30
parent 0040317e48
commit ef3dab5aeb
74 changed files with 2552 additions and 1077 deletions
+23 -48
View File
@@ -5,11 +5,9 @@ import { createIcons, icons } from 'lucide';
import Sortable from 'sortablejs';
import { isCpdfAvailable } from '../utils/cpdf-helper.js';
import { makeUniqueFileKey } from '../utils/deduplicate-filename.js';
import {
showWasmRequiredDialog,
WasmProvider,
} from '../utils/wasm-provider.js';
import { showWasmRequiredDialog } from '../utils/wasm-provider.js';
import { batchDecryptIfNeeded } from '../utils/password-prompt.js';
import { interleavePdfs } from '../utils/alternate-merge.js';
const pageState: AlternateMergeState = {
files: [],
@@ -17,10 +15,6 @@ const pageState: AlternateMergeState = {
pdfDocs: new Map(),
};
const alternateMergeWorker = new Worker(
import.meta.env.BASE_URL + 'workers/alternate-merge.worker.js'
);
function resetState() {
pageState.files = [];
pageState.pdfBytes.clear();
@@ -180,47 +174,28 @@ async function mixPages() {
return;
}
const message = {
command: 'interleave',
files: filesToMerge,
cpdfUrl: WasmProvider.getUrl('cpdf')! + 'coherentpdf.browser.min.js',
};
alternateMergeWorker.postMessage(
message,
filesToMerge.map(function (f) {
return f.data;
})
);
alternateMergeWorker.onmessage = function (e: MessageEvent) {
hideLoader();
if (e.data.status === 'success') {
const blob = new Blob([e.data.pdfBytes], { type: 'application/pdf' });
downloadFile(blob, 'alternated-mixed.pdf');
showAlert(
'Success',
'PDFs have been mixed successfully!',
'success',
function () {
resetState();
}
);
} else {
console.error('Worker interleave error:', e.data.message);
showAlert('Error', e.data.message || 'Failed to interleave PDFs.');
}
};
alternateMergeWorker.onerror = function (e) {
hideLoader();
console.error('Worker error:', e);
showAlert('Error', 'An unexpected error occurred in the merge worker.');
};
} catch (e) {
console.error('Alternate Merge error:', e);
showAlert('Error', 'An error occurred while mixing the PDFs.');
const mergedBytes = await interleavePdfs(filesToMerge);
hideLoader();
const blob = new Blob([new Uint8Array(mergedBytes)], {
type: 'application/pdf',
});
downloadFile(blob, 'alternated-mixed.pdf');
showAlert(
'Success',
'PDFs have been mixed successfully!',
'success',
function () {
resetState();
}
);
} catch (e) {
hideLoader();
console.error('Alternate Merge error:', e);
const message =
e instanceof Error
? e.message
: 'An error occurred while mixing the PDFs.';
showAlert('Error', message);
}
}
+21 -26
View File
@@ -10,6 +10,8 @@ import {
import { isToolDisabled } from '../utils/disabled-tools.js';
import type { BaseWorkflowNode } from '../workflow/nodes/base-node';
import type { WorkflowEditor } from '../workflow/editor';
import { translateCategory, translateNodeLabel } from '../workflow/i18n';
import type { NodeCategory } from '../workflow/types';
import {
PDFInputNode,
EncryptedPDFError,
@@ -426,31 +428,22 @@ function buildToolbox() {
if (!container) return;
const categorized = getNodesByCategory();
const categoryOrder: Array<{ key: string; label: string; color: string }> = [
{ key: 'Input', label: 'Input', color: 'text-blue-400' },
{
key: 'Edit & Annotate',
label: 'Edit & Annotate',
color: 'text-indigo-300',
},
{
key: 'Organize & Manage',
label: 'Organize & Manage',
color: 'text-violet-300',
},
{
key: 'Optimize & Repair',
label: 'Optimize & Repair',
color: 'text-amber-300',
},
{ key: 'Secure PDF', label: 'Secure PDF', color: 'text-rose-300' },
{ key: 'Output', label: 'Output', color: 'text-teal-300' },
const categoryOrder: Array<{
key: NodeCategory;
color: string;
}> = [
{ key: 'Input', color: 'text-blue-400' },
{ key: 'Edit & Annotate', color: 'text-indigo-300' },
{ key: 'Organize & Manage', color: 'text-violet-300' },
{ key: 'Optimize & Repair', color: 'text-amber-300' },
{ key: 'Secure PDF', color: 'text-rose-300' },
{ key: 'Output', color: 'text-teal-300' },
];
for (const cat of categoryOrder) {
const entries = (
categorized[cat.key as keyof typeof categorized] ?? []
).filter((entry) => !entry.toolPageId || !isToolDisabled(entry.toolPageId));
const entries = (categorized[cat.key] ?? []).filter(
(entry) => !entry.toolPageId || !isToolDisabled(entry.toolPageId)
);
if (entries.length === 0) continue;
const section = document.createElement('div');
@@ -461,7 +454,7 @@ function buildToolbox() {
header.type = 'button';
const headerLabel = document.createElement('span');
headerLabel.textContent = cat.label;
headerLabel.textContent = translateCategory(cat.key);
header.appendChild(headerLabel);
const chevronWrap = document.createElement('span');
@@ -485,17 +478,19 @@ function buildToolbox() {
const item = document.createElement('button');
item.className =
'toolbox-node-item w-full text-left px-2 py-1.5 rounded-md text-gray-300 hover:bg-gray-700 hover:text-white transition-colors text-xs flex items-center gap-2';
item.dataset.label = entry.label;
item.dataset.type = Object.keys(nodeRegistry).find(
const nodeType = Object.keys(nodeRegistry).find(
(k) => nodeRegistry[k] === entry
)!;
const translatedLabel = translateNodeLabel(nodeType, entry);
item.dataset.label = translatedLabel;
item.dataset.type = nodeType;
const iconEl = document.createElement('i');
iconEl.className = `ph ${entry.icon} text-sm flex-shrink-0`;
item.appendChild(iconEl);
const labelEl = document.createElement('span');
labelEl.textContent = entry.label;
labelEl.textContent = translatedLabel;
item.appendChild(labelEl);
item.addEventListener('click', () => {