1
0
mirror of https://github.com/Stirling-Tools/Stirling-PDF.git synced 2024-06-03 06:10:11 +02:00

GenericFields

This commit is contained in:
Felix Kaspar 2024-02-25 20:55:48 +01:00
parent 20f027bb5a
commit 13bfa0b0d0
13 changed files with 173 additions and 28 deletions

View File

@ -5,6 +5,8 @@
<link rel="icon" type="image/svg+xml" href="/stirling-pdf-logo.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Tauri + React + TS</title>
<script src="browserfs.min.js"></script>
</head>
<body>

View File

@ -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<HTMLFormElement>;
}
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 (
<div>Description: {(schemaDescription?.flags as any)?.description}</div>
<div>
<h3>{label}</h3>
{description}
<hr />
<form onSubmit={(e) => { onSubmit(e); e.preventDefault(); }}>
{
values ? Object.keys(values).map((key, i) => {
return (<GenericField key={key} fieldName={key} joiDefinition={values[key]} />)
}) : undefined
}
<input type="submit" value="Submit" />
</form>
</div>
);
}

View File

@ -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 (
<Fragment>
<label htmlFor={fieldName}>{fieldName}:</label>
<input type="number" list={fieldName} name={fieldName}/>
<datalist id={fieldName}>
{joiDefinition.allow.map((e: string) => {
return (<option key={e} value={e}/>)
})}
</datalist>
<br/>
</Fragment>
);
}
else {
// TODO: Implement unrestrained text input
return (<pre>{JSON.stringify(joiDefinition, null, 2)}</pre>)
}
break;
case "string":
var validValues = joiDefinition.allow;
if(validValues) { // Restrained text input
return (
<Fragment>
<label htmlFor={fieldName}>{fieldName}:</label>
<input type="text" list={fieldName} name={fieldName}/>
<datalist id={fieldName}>
{joiDefinition.allow.map((e: string) => {
return (<option key={e} value={e}/>)
})}
</datalist>
<br/>
</Fragment>
);
}
else {
// TODO: Implement unrestrained text input
return (<pre>{JSON.stringify(joiDefinition, null, 2)}</pre>)
}
break;
default:
return (<div>Field "{fieldName}": <br /> requested type "{joiDefinition.type}" not found</div>)
}
}

View File

@ -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<Joi.Description>();
const operators = ["impose"]; // TODO: Make this dynamic
const operators = listOperatorNames();
const activeOperator = useRef<typeof Operator>();
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<string, string> {
const result: Record<string, string> = {};
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() {
<select id="pdfOptions" onChange={selectionChanged}>
<option value="none">none</option>
{ operators.map((operator, i) => {
return (<option value={operator}>{operator}</option>)
return (<option key={operator} value={operator}>{operator}</option>)
}) }
</select>
<div id="values">
<BuildFields schemaDescription={schemaDescription}></BuildFields>
<BuildFields schemaDescription={schemaDescription} onSubmit={handleSubmit}></BuildFields>
</div>
<br />
<button id="processButton">Process process file with current settings</button>
<p>
<Link to="/">Go back home...</Link>
</p>

View File

@ -18,7 +18,7 @@
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
"noFallthroughCasesInSwitch": true,
},
"include": [
"src",

View File

@ -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

View File

@ -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

View File

@ -10,7 +10,7 @@
<script src="/dep/pdf.min.js"></script>
<script src="/dep/jsQR.js"></script>
<script src="/wasm/browserfs.min.js"></script>
<script src="/browserfs.min.js"></script>
<script src="/wasm/opencv/opencv_3_4_custom_O3.js"></script>
<script src="index.js" type="module"></script>

View File

@ -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;

View File

@ -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;

View File

@ -1,6 +1,6 @@
export interface Action {
values: any;
type: string;
type: "wait" | "done" | "impose" | string;
actions?: Action[];
}

View File

@ -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<any[]> {
async run(input: PdfFile[] | any[], progressCallback: (progress: Progress) => void): Promise<PdfFile[] | any[]> {
return [];
}
}

View File

@ -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();