Add visual workflow builder, fix critical bugs, and add Arabic i18n support

This commit is contained in:
alam00000
2026-02-08 17:05:40 +05:30
parent 36ebb3b429
commit 5d8b83e105
118 changed files with 14151 additions and 2357 deletions

521
src/pages/pdf-workflow.html Normal file
View File

@@ -0,0 +1,521 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PDF Workflow Builder - Visual Pipeline | BentoPDF</title>
<meta
name="title"
content="PDF Workflow Builder - Visual Pipeline | BentoPDF"
/>
<meta
name="description"
content="Build custom PDF processing pipelines visually. Drag, connect, and execute PDF tools in sequence. Free, secure, runs entirely in your browser."
/>
<meta
name="keywords"
content="pdf workflow, pdf automation, pdf pipeline, visual pdf editor, node editor"
/>
<meta name="author" content="BentoPDF" />
<meta
name="robots"
content="index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1"
/>
<link rel="canonical" href="https://www.bentopdf.com/pdf-workflow.html" />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://www.bentopdf.com/pdf-workflow" />
<meta
property="og:title"
content="PDF Workflow Builder - Visual Pipeline | BentoPDF"
/>
<meta
property="og:description"
content="Build custom PDF processing pipelines visually. Drag, connect, and execute PDF tools in sequence."
/>
<meta
property="og:image"
content="https://www.bentopdf.com/images/og-pdf-workflow.png"
/>
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:site_name" content="BentoPDF" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:url" content="https://www.bentopdf.com/pdf-workflow" />
<meta name="twitter:title" content="PDF Workflow Builder" />
<meta
name="twitter:description"
content="Build custom PDF processing pipelines visually."
/>
<meta
name="twitter:image"
content="https://www.bentopdf.com/images/twitter-pdf-workflow.png"
/>
<meta name="twitter:site" content="@BentoPDF" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-title" content="PDF Workflow" />
<link href="/src/css/styles.css" rel="stylesheet" />
<link rel="manifest" href="/site.webmanifest" />
<link rel="icon" type="image/svg+xml" href="/images/favicon.svg" />
<link
rel="icon"
type="image/png"
sizes="192x192"
href="/images/favicon-192x192.png"
/>
<link
rel="icon"
type="image/png"
sizes="512x512"
href="/images/favicon-512x512.png"
/>
<link
rel="apple-touch-icon"
sizes="180x180"
href="/images/apple-touch-icon.png"
/>
<link rel="icon" href="/favicon.ico" sizes="32x32" />
<style>
#rete-container {
background-image: radial-gradient(circle, #374151 1px, transparent 1px);
background-size: 20px 20px;
cursor: default;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
#toolbox-sidebar {
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
#rete-container.is-panning {
cursor: grabbing;
}
#rete-container [data-testid='node'] {
cursor: pointer;
}
#rete-container [data-testid='node']:active {
cursor: grabbing;
}
.wf-node {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
width: 280px;
}
.wf-card {
background: #1f2937;
border: 2px solid var(--cat-color, #6b7280);
border-radius: 12px;
width: 100%;
overflow: hidden;
transition: box-shadow 0.2s;
}
.wf-selected .wf-card {
box-shadow: 0 0 0 3px
color-mix(in srgb, var(--cat-color) 40%, transparent);
}
.wf-header {
padding: 8px 14px 4px;
display: flex;
align-items: center;
justify-content: space-between;
}
.wf-cat {
font-size: 11px;
font-weight: 600;
color: var(--cat-color, #9ca3af);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.wf-divider {
height: 1px;
background: #374151;
margin: 0 14px;
}
.wf-body {
padding: 10px 14px 12px;
display: flex;
align-items: flex-start;
gap: 10px;
}
.wf-icon {
width: 18px;
height: 18px;
color: var(--cat-color, #9ca3af);
flex-shrink: 0;
margin-top: 1px;
}
.wf-info {
flex: 1;
min-width: 0;
}
.wf-title {
font-size: 13px;
font-weight: 600;
color: #f3f4f6;
line-height: 1.3;
}
.wf-desc {
font-size: 11px;
color: #9ca3af;
margin-top: 2px;
line-height: 1.3;
}
.wf-tag {
font-size: 10px;
padding: 2px 8px;
border-radius: 4px;
background: color-mix(in srgb, var(--cat-color) 15%, transparent);
color: var(--cat-color, #9ca3af);
font-weight: 500;
white-space: nowrap;
flex-shrink: 0;
align-self: center;
}
.wf-sockets {
display: flex;
justify-content: center;
gap: 8px;
position: relative;
z-index: 1;
}
.wf-sockets-top {
margin-bottom: -7px;
}
.wf-sockets-bottom {
margin-top: -7px;
}
.wf-socket-slot {
display: flex;
align-items: center;
justify-content: center;
}
.wf-socket {
width: 14px;
height: 14px;
border-radius: 50%;
background: var(--socket-color, #6366f1);
border: 2px solid #1f2937;
box-shadow: 0 0 0 1px var(--socket-color, #6366f1);
cursor: crosshair;
transition: transform 0.15s;
}
.wf-socket:hover {
transform: scale(1.3);
}
.connection .main-path {
stroke: #6366f1;
stroke-width: 2px;
fill: none;
}
@keyframes wf-bar-slide {
0% {
background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
}
@keyframes wf-dot-pulse {
0%,
100% {
opacity: 1;
transform: scale(1);
}
50% {
opacity: 0.4;
transform: scale(0.75);
}
}
.wf-bar-slide {
animation: wf-bar-slide 1.5s ease-in-out infinite;
}
.wf-dot-pulse {
animation: wf-dot-pulse 1.2s ease-in-out infinite;
}
</style>
</head>
<body class="antialiased bg-gray-900 h-screen flex flex-col overflow-hidden">
{{> navbar }}
<!-- Main Workflow Layout -->
<div id="workflow-app" class="flex flex-1 min-h-0">
<!-- LEFT SIDEBAR: Node Toolbox -->
<aside
id="toolbox-sidebar"
class="w-60 bg-gray-800 border-r border-gray-700 flex flex-col overflow-y-auto flex-shrink-0"
>
<div class="p-3 border-b border-gray-700">
<h2 class="text-white font-bold text-sm mb-2">Nodes</h2>
<input
id="node-search"
type="text"
placeholder="Search nodes..."
class="w-full bg-gray-900 border border-gray-600 text-white rounded-md px-2 py-1.5 text-xs focus:border-indigo-500 focus:outline-none"
/>
</div>
<div id="toolbox-categories" class="flex-1 p-2 space-y-2 text-sm"></div>
</aside>
<!-- CENTER: Canvas + Toolbar -->
<main class="flex-1 flex flex-col min-w-0">
<!-- Top Toolbar -->
<div
id="workflow-toolbar"
class="h-11 bg-gray-800 border-b border-gray-700 flex items-center px-4 gap-2 flex-shrink-0"
>
<button
id="run-btn"
class="bg-indigo-600 hover:bg-indigo-700 text-white font-semibold px-4 py-1.5 rounded-lg flex items-center gap-1.5 text-sm transition-colors"
>
<i class="ph ph-play text-base"></i> Run
</button>
<button
id="clear-btn"
class="bg-gray-700 hover:bg-gray-600 text-white px-3 py-1.5 rounded-lg text-sm transition-colors"
>
Clear
</button>
<div class="flex-1"></div>
<button
id="save-btn"
class="bg-gray-700 hover:bg-gray-600 text-white px-3 py-1.5 rounded-lg text-sm flex items-center gap-1 transition-colors"
>
<i class="ph ph-floppy-disk text-sm"></i> Save
</button>
<button
id="load-btn"
class="bg-gray-700 hover:bg-gray-600 text-white px-3 py-1.5 rounded-lg text-sm flex items-center gap-1 transition-colors"
>
<i class="ph ph-folder-open text-sm"></i> Load
</button>
<button
id="export-btn"
class="bg-gray-700 hover:bg-gray-600 text-white px-3 py-1.5 rounded-lg text-sm transition-colors"
>
Export
</button>
<button
id="import-btn"
class="bg-gray-700 hover:bg-gray-600 text-white px-3 py-1.5 rounded-lg text-sm transition-colors"
>
Import
</button>
</div>
<!-- Rete.js Canvas -->
<div id="rete-container" class="flex-1 relative"></div>
<!-- Bottom Status Bar -->
<div
id="status-bar"
class="h-7 bg-gray-800 border-t border-gray-700 flex items-center px-4 text-xs text-gray-400"
>
<span id="status-text">Ready</span>
<div class="flex-1"></div>
<span id="node-count">0 nodes</span>
</div>
</main>
<!-- RIGHT SIDEBAR: Selected Node Settings -->
<aside
id="settings-sidebar"
class="w-64 bg-gray-800 border-l border-gray-700 flex flex-col overflow-y-auto flex-shrink-0 hidden"
>
<div
class="p-3 border-b border-gray-700 flex items-center justify-between"
>
<h2 id="settings-title" class="text-white font-bold text-sm">
Settings
</h2>
<button
id="close-settings"
class="text-gray-400 hover:text-white transition-colors"
>
<i class="ph ph-x text-base"></i>
</button>
</div>
<div id="settings-content" class="flex-1 p-3 space-y-3"></div>
</aside>
</div>
<!-- Loader Modal -->
<div
id="loader-modal"
class="hidden fixed inset-0 bg-black/40 backdrop-blur-sm flex items-center justify-center z-50"
>
<div
class="bg-gray-800/95 p-8 rounded-2xl flex flex-col items-center gap-4 border border-gray-600/50 shadow-2xl"
>
<div class="solid-spinner"></div>
<p id="loader-text" class="text-white text-lg font-medium">
Processing...
</p>
</div>
</div>
<!-- Save Template Modal -->
<div
id="save-template-modal"
class="fixed inset-0 bg-black/40 backdrop-blur-sm flex items-center justify-center z-50 hidden"
>
<div
class="bg-gray-800/95 rounded-2xl shadow-2xl p-6 max-w-sm w-full mx-4 border border-gray-600/50"
>
<div class="flex items-center gap-3 mb-5">
<div
class="w-9 h-9 rounded-xl bg-indigo-500/10 flex items-center justify-center"
>
<i class="ph ph-floppy-disk text-lg text-indigo-400"></i>
</div>
<h3 class="text-base font-semibold text-white">Save Template</h3>
</div>
<label class="block text-xs font-medium text-gray-400 mb-1.5"
>Template Name</label
>
<input
id="save-template-name"
type="text"
placeholder="e.g. Invoice Workflow"
class="w-full bg-gray-900/80 border border-gray-600/60 text-white rounded-lg px-3 py-2.5 text-sm focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500/30 focus:outline-none placeholder-gray-500 mb-1"
/>
<p
id="save-template-error"
class="text-red-400 text-xs mb-4 hidden"
></p>
<div class="flex gap-2 mt-4">
<button
id="save-template-cancel"
class="flex-1 bg-gray-700/80 hover:bg-gray-600 text-gray-300 font-medium py-2.5 px-4 rounded-lg transition-colors text-sm"
>
Cancel
</button>
<button
id="save-template-confirm"
class="flex-1 bg-indigo-600 hover:bg-indigo-500 text-white font-medium py-2.5 px-4 rounded-lg transition-colors text-sm"
>
Save
</button>
</div>
</div>
</div>
<!-- Load Template Modal -->
<div
id="load-template-modal"
class="fixed inset-0 bg-black/40 backdrop-blur-sm flex items-center justify-center z-50 hidden"
>
<div
class="bg-gray-800/95 rounded-2xl shadow-2xl p-6 max-w-md w-full mx-4 border border-gray-600/50"
>
<div class="flex items-center gap-3 mb-5">
<div
class="w-9 h-9 rounded-xl bg-indigo-500/10 flex items-center justify-center"
>
<i class="ph ph-folder-open text-lg text-indigo-400"></i>
</div>
<h3 class="text-base font-semibold text-white">Load Template</h3>
</div>
<div
id="load-template-list"
class="space-y-1.5 max-h-64 overflow-y-auto mb-4"
></div>
<p
id="load-template-empty"
class="text-gray-500 text-sm text-center py-6 hidden"
>
No saved templates yet.
</p>
<button
id="load-template-cancel"
class="w-full bg-gray-700/80 hover:bg-gray-600 text-gray-300 font-medium py-2.5 px-4 rounded-lg transition-colors text-sm mt-2"
>
Cancel
</button>
</div>
</div>
<!-- Alert Modal -->
<div
id="alert-modal"
class="fixed inset-0 bg-black/40 backdrop-blur-sm flex items-center justify-center z-50 hidden"
>
<div
class="bg-gray-800/95 rounded-2xl shadow-2xl p-6 max-w-sm w-full mx-4 border border-gray-600/50"
>
<h3 id="alert-title" class="text-lg font-semibold text-white mb-2">
Alert
</h3>
<p id="alert-message" class="text-gray-300 text-sm mb-6"></p>
<button
id="alert-ok"
class="w-full bg-indigo-600 hover:bg-indigo-500 text-white font-medium py-2.5 px-4 rounded-lg transition-colors text-sm"
>
OK
</button>
</div>
</div>
<script type="module">
import '@phosphor-icons/web/regular';
</script>
<script type="module" src="/src/version.ts"></script>
<script type="module" src="/src/js/logic/pdf-workflow-page.ts"></script>
<script type="module" src="/src/js/mobileMenu.ts"></script>
<script type="module" src="/src/js/main.ts"></script>
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "SoftwareApplication",
"name": "PDF Workflow Builder - BentoPDF",
"applicationCategory": "PDF Tool",
"operatingSystem": "Any - Web Browser",
"offers": {
"@type": "Offer",
"price": "0",
"priceCurrency": "USD"
}
}
</script>
</body>
</html>