diff --git a/client-tauri/index.html b/client-tauri/index.html
index 92c0a3c96..2d851d5b9 100644
--- a/client-tauri/index.html
+++ b/client-tauri/index.html
@@ -5,6 +5,8 @@
Tauri + React + TS
+
+
diff --git a/client-tauri/src/components/fields/BuildFields.tsx b/client-tauri/src/components/fields/BuildFields.tsx
index 6e259dc8c..e07bc8840 100644
--- a/client-tauri/src/components/fields/BuildFields.tsx
+++ b/client-tauri/src/components/fields/BuildFields.tsx
@@ -1,15 +1,31 @@
import Joi from "@stirling-tools/joi";
-
+import { GenericField } from "./GenericField";
+import React from "react";
interface BuildFieldsProps {
/** The text to display inside the button */
schemaDescription: Joi.Description | undefined;
+ onSubmit: React.FormEventHandler;
}
-
-export function BuildFields({ schemaDescription }: BuildFieldsProps) {
+export function BuildFields({ schemaDescription, onSubmit }: BuildFieldsProps) {
console.log("Render Build Fields", schemaDescription);
+ const label = (schemaDescription?.flags as any)?.label
+ const description = (schemaDescription?.flags as any)?.description;
+ const values = (schemaDescription?.keys as any)?.values.keys as { [key: string]: Joi.Description};
return (
- Description: {(schemaDescription?.flags as any)?.description}
+
+
{label}
+ {description}
+
+
+
);
}
\ No newline at end of file
diff --git a/client-tauri/src/components/fields/GenericField.tsx b/client-tauri/src/components/fields/GenericField.tsx
new file mode 100644
index 000000000..24c861c0d
--- /dev/null
+++ b/client-tauri/src/components/fields/GenericField.tsx
@@ -0,0 +1,57 @@
+import Joi from "@stirling-tools/joi";
+import { Fragment } from "react";
+
+interface GenericFieldProps {
+ fieldName: string
+ joiDefinition: Joi.Description;
+}
+
+export function GenericField({ fieldName, joiDefinition }: GenericFieldProps) {
+ switch (joiDefinition.type) {
+ case "number":
+ var validValues = joiDefinition.allow;
+ if(validValues) { // Restrained text input
+ return (
+
+
+
+
+
+
+ );
+ }
+ else {
+ // TODO: Implement unrestrained text input
+ return ({JSON.stringify(joiDefinition, null, 2)}
)
+ }
+ break;
+ case "string":
+ var validValues = joiDefinition.allow;
+ if(validValues) { // Restrained text input
+ return (
+
+
+
+
+
+
+ );
+ }
+ else {
+ // TODO: Implement unrestrained text input
+ return ({JSON.stringify(joiDefinition, null, 2)}
)
+ }
+ break;
+
+ default:
+ return (Field "{fieldName}":
requested type "{joiDefinition.type}" not found
)
+ }
+}
\ No newline at end of file
diff --git a/client-tauri/src/pages/Dynamic.tsx b/client-tauri/src/pages/Dynamic.tsx
index bca30de1c..2ca5d19e2 100644
--- a/client-tauri/src/pages/Dynamic.tsx
+++ b/client-tauri/src/pages/Dynamic.tsx
@@ -1,16 +1,20 @@
import { Link } from "react-router-dom";
-import { BaseSyntheticEvent, createContext, useState } from "react";
+import { BaseSyntheticEvent, createContext, useRef, useState } from "react";
import { Operator } from "@stirling-pdf/shared-operations/src/functions";
import i18next from "i18next";
import Joi from "@stirling-tools/joi";
import { BuildFields } from "../components/fields/BuildFields";
+import { listOperatorNames } from "@stirling-pdf/shared-operations/src/workflow/operatorAccessor";
+import { PdfFile, RepresentationType } from "@stirling-pdf/shared-operations/src/wrappers/PdfFile";
+import { Action } from "@stirling-pdf/shared-operations/declarations/Action";
+import { JoiPDFFileSchema } from "@stirling-pdf/shared-operations/src/wrappers/PdfFileJoi";
function Dynamic() {
const [schemaDescription, setSchemaDescription] = useState();
-
- const operators = ["impose"]; // TODO: Make this dynamic
+ const operators = listOperatorNames();
+ const activeOperator = useRef();
function selectionChanged(s: BaseSyntheticEvent) {
const selectedValue = s.target.value;
@@ -19,7 +23,7 @@ function Dynamic() {
return;
}
- i18next.loadNamespaces("impose", (err, t) => {
+ i18next.loadNamespaces(selectedValue, (err, t) => {
if (err) throw err;
const LoadingModule = import(`@stirling-pdf/shared-operations/src/functions/${selectedValue}`) as Promise<{ [key: string]: typeof Operator }>;
@@ -27,12 +31,77 @@ function Dynamic() {
const Operator = Module[capitalizeFirstLetter(selectedValue)];
const description = Operator.schema.describe();
- setSchemaDescription(description); // This will update children
- console.log(description);
+ activeOperator.current = Operator;
+ // This will update children
+ setSchemaDescription(description);
});
});
}
+ function formDataToObject(formData: FormData): Record {
+ const result: Record = {};
+
+ formData.forEach((value, key) => {
+ result[key] = value.toString();
+ });
+
+ return result;
+ }
+
+ async function handleSubmit(e: BaseSyntheticEvent) {
+ if(!activeOperator.current) {
+ throw new Error("Please select an Operator in the Dropdown");
+ }
+
+ const formData = new FormData(e.target);
+
+ const action: Action = {type: activeOperator.current.constructor.name, values: formDataToObject(formData)};
+
+ // Validate PDF File
+
+ // Createing the pdffile before validation because joi cant handle it for some reason and I can't fix the underlying issue / I want to make progress, wasted like 3 hours on this already. TODO: The casting should be done in JoiPDFFileSchema.ts if done correctly...
+ const files = (document.getElementById("pdfFile") as HTMLInputElement).files;
+ const inputs: PdfFile[] = [];
+
+ if(files) {
+ const filesArray: File[] = Array.from(files as any);
+ for (let i = 0; i < files.length; i++) {
+ const file = filesArray[i];
+ if(file) {
+ console.log(new Uint8Array(await file.arrayBuffer()));
+ inputs.push(new PdfFile(
+ file.name.replace(/\.[^/.]+$/, ""), // Strip Extension
+ new Uint8Array(await file.arrayBuffer()),
+ RepresentationType.Uint8Array
+ ));
+ }
+ else
+ throw new Error("This should not happen. Contact maintainers.");
+ }
+ }
+
+ const pdfValidationResults = await JoiPDFFileSchema.validate(inputs);
+ if(pdfValidationResults.error) {
+ console.log({error: "PDF validation failed", details: pdfValidationResults.error.message});
+ }
+ const pdfFiles: PdfFile[] = pdfValidationResults.value;
+
+ // Validate Action Values
+ const actionValidationResults = activeOperator.current.schema.validate({input: pdfFiles, values: action.values});
+
+ if(actionValidationResults.error) {
+ console.log({error: "Value validation failed", details: actionValidationResults.error.message});
+ return;
+ }
+
+ action.values = pdfValidationResults.value.values;
+ const operation = new activeOperator.current(action);
+
+ operation.run(pdfValidationResults.value, (progress) => {}).then(pdfFiles => {
+ console.log("Done");
+ });
+ };
+
function capitalizeFirstLetter(string: String) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
@@ -46,17 +115,14 @@ function Dynamic() {
-
+
-
-
-
Go back home...
diff --git a/client-tauri/tsconfig.json b/client-tauri/tsconfig.json
index 63432ac5b..ce8a07e6d 100644
--- a/client-tauri/tsconfig.json
+++ b/client-tauri/tsconfig.json
@@ -18,7 +18,7 @@
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
- "noFallthroughCasesInSwitch": true
+ "noFallthroughCasesInSwitch": true,
},
"include": [
"src",
diff --git a/client-tauri/vite.config.ts b/client-tauri/vite.config.ts
index 596922d5c..c37ca4b1f 100644
--- a/client-tauri/vite.config.ts
+++ b/client-tauri/vite.config.ts
@@ -3,6 +3,7 @@ import react from "@vitejs/plugin-react";
import topLevelAwait from "vite-plugin-top-level-await";
import dynamicImport from 'vite-plugin-dynamic-import'
import compileTime from "vite-plugin-compile-time"
+import { fileURLToPath, URL } from 'node:url'
// https://vitejs.dev/config/
@@ -18,7 +19,11 @@ export default defineConfig(async () => ({
compileTime(),
dynamicImport(),
],
-
+ resolve: {
+ alias: {
+ '#pdfcpu': fileURLToPath(new URL("../shared-operations/src/wasm/pdfcpu/pdfcpu-wrapper.client", import.meta.url))
+ }
+ },
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
//
// 1. prevent vite from obscuring rust errors
diff --git a/client-vanilla/index.js b/client-vanilla/index.js
index 2b3e24615..f6b7988bf 100644
--- a/client-vanilla/index.js
+++ b/client-vanilla/index.js
@@ -3,7 +3,7 @@ const app = express();
const PORT = 80;
// Server Frontend TODO: Make this typescript compatible
-app.use(express.static('../client-vanilla/public'));
+app.use(express.static('./public'));
app.use(express.static('../shared-operations'));
// serve
diff --git a/client-vanilla/public/index.html b/client-vanilla/public/index.html
index 9bf370580..8233891bc 100644
--- a/client-vanilla/public/index.html
+++ b/client-vanilla/public/index.html
@@ -10,7 +10,7 @@
-
+
diff --git a/server-node/src/routes/api/dynamic-operations-controller.ts b/server-node/src/routes/api/dynamic-operations-controller.ts
index b38d031d4..cb92a3211 100644
--- a/server-node/src/routes/api/dynamic-operations-controller.ts
+++ b/server-node/src/routes/api/dynamic-operations-controller.ts
@@ -24,7 +24,7 @@ async function handleEndpoint(req: Request, res: Response) {
return;
}
- const validationResults = JoiPDFFileSchema.validate(req.files);
+ const validationResults = await JoiPDFFileSchema.validateAsync(req.files);
if(validationResults.error) {
res.status(400).json({error: "PDF validation failed", details: validationResults.error.message});
return;
diff --git a/server-node/src/routes/api/workflow-controller.ts b/server-node/src/routes/api/workflow-controller.ts
index 765339582..8f1b5b164 100644
--- a/server-node/src/routes/api/workflow-controller.ts
+++ b/server-node/src/routes/api/workflow-controller.ts
@@ -43,7 +43,7 @@ router.post("/:workflowUuid?", [
}
}
- const validationResults = JoiPDFFileSchema.validate(req.files);
+ const validationResults = await JoiPDFFileSchema.validateAsync(req.files);
if(validationResults.error) {
res.status(400).json({error: "PDF validation failed", details: validationResults.error.message});
return;
diff --git a/shared-operations/declarations/Action.d.ts b/shared-operations/declarations/Action.d.ts
index de5bd74c5..17e694ea9 100644
--- a/shared-operations/declarations/Action.d.ts
+++ b/shared-operations/declarations/Action.d.ts
@@ -1,6 +1,6 @@
export interface Action {
values: any;
- type: string;
+ type: "wait" | "done" | "impose" | string;
actions?: Action[];
}
diff --git a/shared-operations/src/functions/index.ts b/shared-operations/src/functions/index.ts
index 49985e134..f82e383bc 100644
--- a/shared-operations/src/functions/index.ts
+++ b/shared-operations/src/functions/index.ts
@@ -1,3 +1,4 @@
+import { PdfFile } from "wrappers/PdfFile";
import { Action } from "../../declarations/Action";
import Joi from "@stirling-tools/joi";
@@ -33,7 +34,7 @@ export class Operator {
this.actionValues = action.values;
}
- async run(input: any[], progressCallback: (progress: Progress) => void): Promise {
+ async run(input: PdfFile[] | any[], progressCallback: (progress: Progress) => void): Promise {
return [];
}
}
diff --git a/shared-operations/src/wasm/pdfcpu/pdfcpu-wrapper.client.js b/shared-operations/src/wasm/pdfcpu/pdfcpu-wrapper.client.js
index 73cffdf4b..cbc1627fd 100644
--- a/shared-operations/src/wasm/pdfcpu/pdfcpu-wrapper.client.js
+++ b/shared-operations/src/wasm/pdfcpu/pdfcpu-wrapper.client.js
@@ -1,4 +1,5 @@
// imports browserfs via index.html script-tag
+import wasmUrl from '../../../public/wasm/pdfcpu/pdfcpu.wasm?url'
let wasmLocation = "/wasm/pdfcpu/";
@@ -29,15 +30,12 @@ function configureFs() {
}
function loadWasm() {
- const script = document.createElement("script");
- script.src = wasmLocation + "/wasm_exec.js";
- script.async = true;
- document.body.appendChild(script);
+ import("./wasm_exec.js");
}
const runWasm = async (param) => {
if (window.cachedWasmResponse === undefined) {
- const response = await fetch(wasmLocation + "/pdfcpu.wasm");
+ const response = await fetch(wasmUrl);
const buffer = await response.arrayBuffer();
window.cachedWasmResponse = buffer;
window.go = new Go();