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} +
+
{ onSubmit(e); e.preventDefault(); }}> + { + values ? Object.keys(values).map((key, i) => { + return () + }) : undefined + } + + +
); } \ 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 ( + + + + + {joiDefinition.allow.map((e: string) => { + 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 ( + + + + + {joiDefinition.allow.map((e: string) => { + 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();