diff --git a/build.gradle b/build.gradle
index 5f44fcd5d..404639a4f 100644
--- a/build.gradle
+++ b/build.gradle
@@ -67,7 +67,7 @@ springBoot {
allprojects {
group = 'stirling.software'
- version = '2.3.1'
+ version = '2.4.0'
configurations.configureEach {
exclude group: 'commons-logging', module: 'commons-logging'
diff --git a/frontend/public/locales/en-GB/translation.toml b/frontend/public/locales/en-GB/translation.toml
index 3d0b4518c..9e818ca3f 100644
--- a/frontend/public/locales/en-GB/translation.toml
+++ b/frontend/public/locales/en-GB/translation.toml
@@ -306,6 +306,7 @@ selectOperation = "Select Operation"
addOperationButton = "Add operation"
pipelineHeader = "Pipeline:"
saveButton = "Download"
+saveForFolderScanning = "Save for Folder Scanning"
validateButton = "Validate"
[enterpriseEdition]
@@ -5465,6 +5466,7 @@ desc = "Build multi-step workflows by chaining together PDF actions. Ideal for r
invalidStep = "Invalid step"
reviewTitle = "Automation Results"
copyToSaved = "Copy to Saved"
+exportForFolderScanning = "Export for Folder Scanning"
[automate.files]
placeholder = "Select files to process with this automation"
@@ -5486,6 +5488,7 @@ createTitle = "Create Automation"
editTitle = "Edit Automation"
intro = "Automations run tools sequentially. To get started, add tools in the order you want them to run."
save = "Save Automation"
+exportForFolderScanning = "Export for Folder Scanning"
[automate.creation.name]
label = "Automation Name"
diff --git a/frontend/src-tauri/tauri.conf.json b/frontend/src-tauri/tauri.conf.json
index c30f8c957..f22d2d44f 100644
--- a/frontend/src-tauri/tauri.conf.json
+++ b/frontend/src-tauri/tauri.conf.json
@@ -1,7 +1,7 @@
{
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
"productName": "Stirling-PDF",
- "version": "2.3.1",
+ "version": "2.4.0",
"identifier": "stirling.pdf.dev",
"build": {
"frontendDist": "../dist",
diff --git a/frontend/src/core/components/tools/automate/AutomationCreation.tsx b/frontend/src/core/components/tools/automate/AutomationCreation.tsx
index 836c14fed..e71eb73e4 100644
--- a/frontend/src/core/components/tools/automate/AutomationCreation.tsx
+++ b/frontend/src/core/components/tools/automate/AutomationCreation.tsx
@@ -12,12 +12,14 @@ import {
} from '@mantine/core';
import { Z_INDEX_AUTOMATE_MODAL } from '@app/styles/zIndex';
import CheckIcon from '@mui/icons-material/Check';
+import DownloadIcon from '@mui/icons-material/Download';
import { ToolRegistry } from '@app/data/toolsTaxonomy';
import ToolConfigurationModal from '@app/components/tools/automate/ToolConfigurationModal';
import ToolList from '@app/components/tools/automate/ToolList';
import IconSelector from '@app/components/tools/automate/IconSelector';
import { AutomationConfig, AutomationMode, AutomationTool } from '@app/types/automation';
import { useAutomationForm } from '@app/hooks/tools/automate/useAutomationForm';
+import { downloadFolderScanningConfig } from '@app/utils/automationConverter';
interface AutomationCreationProps {
@@ -194,15 +196,42 @@ export default function AutomationCreation({ mode, existingAutomation, onBack, o
- {/* Save Button */}
- }
- onClick={saveAutomation}
- disabled={!canSaveAutomation()}
- fullWidth
- >
- {t('automate.creation.save', 'Save Automation')}
-
+ {/* Action Buttons */}
+
+ }
+ onClick={saveAutomation}
+ disabled={!canSaveAutomation()}
+ fullWidth
+ >
+ {t('automate.creation.save', 'Save Automation')}
+
+
+ }
+ onClick={() => {
+ // Create a temporary automation config from current state
+ const tempAutomation: AutomationConfig = {
+ id: existingAutomation?.id || 'temp',
+ name: automationName.trim(),
+ description: automationDescription.trim(),
+ icon: automationIcon,
+ operations: selectedTools.map(tool => ({
+ operation: tool.operation,
+ parameters: tool.parameters || {}
+ })),
+ createdAt: existingAutomation?.createdAt || new Date().toISOString(),
+ updatedAt: new Date().toISOString()
+ };
+ downloadFolderScanningConfig(tempAutomation);
+ }}
+ disabled={!canSaveAutomation()}
+ variant="light"
+ fullWidth
+ >
+ {t('automate.creation.exportForFolderScanning', 'Export for Folder Scanning')}
+
+
{/* Tool Configuration Modal */}
diff --git a/frontend/src/core/components/tools/automate/AutomationEntry.tsx b/frontend/src/core/components/tools/automate/AutomationEntry.tsx
index 7871033b9..9f40a07ae 100644
--- a/frontend/src/core/components/tools/automate/AutomationEntry.tsx
+++ b/frontend/src/core/components/tools/automate/AutomationEntry.tsx
@@ -5,6 +5,7 @@ import MoreVertIcon from '@mui/icons-material/MoreVert';
import EditIcon from '@mui/icons-material/Edit';
import DeleteIcon from '@mui/icons-material/Delete';
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
+import DownloadIcon from '@mui/icons-material/Download';
import { Tooltip } from '@app/components/shared/Tooltip';
import { ToolIcon } from '@app/components/shared/ToolIcon';
import { ToolRegistry } from '@app/data/toolsTaxonomy';
@@ -31,6 +32,8 @@ interface AutomationEntryProps {
onDelete?: () => void;
/** Copy handler (for suggested automations) */
onCopy?: () => void;
+ /** Export handler (for folder scanning) */
+ onExport?: () => void;
/** Tool registry to resolve operation names */
toolRegistry?: Partial;
}
@@ -46,6 +49,7 @@ export default function AutomationEntry({
onEdit,
onDelete,
onCopy,
+ onExport,
toolRegistry
}: AutomationEntryProps) {
const { t } = useTranslation();
@@ -225,6 +229,17 @@ export default function AutomationEntry({
{t('edit', 'Edit')}
)}
+ {onExport && (
+ }
+ onClick={(e) => {
+ e.stopPropagation();
+ onExport();
+ }}
+ >
+ {t('automate.exportForFolderScanning', 'Export for Folder Scanning')}
+
+ )}
{onDelete && (
}
diff --git a/frontend/src/core/components/tools/automate/AutomationSelection.tsx b/frontend/src/core/components/tools/automate/AutomationSelection.tsx
index 1b53ff900..73dda0164 100644
--- a/frontend/src/core/components/tools/automate/AutomationSelection.tsx
+++ b/frontend/src/core/components/tools/automate/AutomationSelection.tsx
@@ -7,6 +7,7 @@ import { useSuggestedAutomations } from "@app/hooks/tools/automate/useSuggestedA
import { AutomationConfig, SuggestedAutomation } from "@app/types/automation";
import { iconMap } from '@app/components/tools/automate/iconMap';
import { ToolRegistry } from '@app/data/toolsTaxonomy';
+import { downloadFolderScanningConfig } from '@app/utils/automationConverter';
interface AutomationSelectionProps {
savedAutomations: AutomationConfig[];
@@ -58,6 +59,7 @@ export default function AutomationSelection({
onClick={() => onRun(automation)}
showMenu={true}
onEdit={() => onEdit(automation)}
+ onExport={() => downloadFolderScanningConfig(automation)}
onDelete={() => onDelete(automation)}
toolRegistry={toolRegistry}
/>
diff --git a/frontend/src/core/testing/serverExperienceSimulations.ts b/frontend/src/core/testing/serverExperienceSimulations.ts
index 57fe7b199..121c14fd1 100644
--- a/frontend/src/core/testing/serverExperienceSimulations.ts
+++ b/frontend/src/core/testing/serverExperienceSimulations.ts
@@ -38,7 +38,7 @@ const FREE_LICENSE_INFO: LicenseInfo = {
const BASE_NO_LOGIN_CONFIG: AppConfig = {
enableAnalytics: true,
- appVersion: '2.3.1',
+ appVersion: '2.4.0',
serverCertificateEnabled: false,
enableAlphaFunctionality: false,
serverPort: 8080,
diff --git a/frontend/src/core/utils/automationConverter.ts b/frontend/src/core/utils/automationConverter.ts
new file mode 100644
index 000000000..1744f52f3
--- /dev/null
+++ b/frontend/src/core/utils/automationConverter.ts
@@ -0,0 +1,68 @@
+/**
+ * Utility functions for converting between automation formats
+ */
+
+import { AutomationConfig } from '@app/types/automation';
+
+/**
+ * Pipeline configuration format used by folder scanning
+ */
+interface FolderScanningPipeline {
+ name: string;
+ pipeline: Array<{
+ operation: string;
+ parameters: Record;
+ }>;
+ _examples: {
+ outputDir: string;
+ outputFileName: string;
+ };
+ outputDir: string;
+ outputFileName: string;
+}
+
+/**
+ * Converts an AutomationConfig to a folder scanning pipeline configuration
+ * @param automation The automation configuration to convert
+ * @returns Folder scanning pipeline configuration
+ */
+export function convertToFolderScanningConfig(automation: AutomationConfig): FolderScanningPipeline {
+ return {
+ name: automation.name,
+ pipeline: automation.operations.map(op => ({
+ operation: op.operation,
+ parameters: {
+ ...op.parameters,
+ fileInput: "automated"
+ }
+ })),
+ _examples: {
+ outputDir: "{outputFolder}/{folderName}",
+ outputFileName: "{filename}-{pipelineName}-{date}-{time}"
+ },
+ outputDir: "{outputFolder}",
+ outputFileName: "{filename}"
+ };
+}
+
+/**
+ * Downloads a folder scanning configuration as a JSON file
+ * @param automation The automation configuration to export
+ */
+export function downloadFolderScanningConfig(automation: AutomationConfig): void {
+ const config = convertToFolderScanningConfig(automation);
+ const json = JSON.stringify(config, null, 2);
+ const blob = new Blob([json], { type: 'application/json' });
+ const url = URL.createObjectURL(blob);
+
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = `${automation.name}.json`;
+ a.style.display = 'none';
+
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+
+ URL.revokeObjectURL(url);
+}
diff --git a/frontend/src/proprietary/testing/serverExperienceSimulations.ts b/frontend/src/proprietary/testing/serverExperienceSimulations.ts
index a4add6ab7..1c3b5a6ad 100644
--- a/frontend/src/proprietary/testing/serverExperienceSimulations.ts
+++ b/frontend/src/proprietary/testing/serverExperienceSimulations.ts
@@ -48,7 +48,7 @@ const FREE_LICENSE_INFO: LicenseInfo = {
const BASE_NO_LOGIN_CONFIG: AppConfig = {
enableAnalytics: true,
- appVersion: '2.3.1',
+ appVersion: '2.4.0',
serverCertificateEnabled: false,
enableAlphaFunctionality: false,
enableDesktopInstallSlide: true,