diff --git a/client-tauri/package.json b/client-tauri/package.json index 333928d91..ab55d1f59 100644 --- a/client-tauri/package.json +++ b/client-tauri/package.json @@ -23,6 +23,7 @@ "react-dom": "^18.2.0", "react-i18next": "^13.3.1", "react-icons": "^4.11.0", + "react-material-symbols": "^4.4.0", "react-router-bootstrap": "^0.26.2", "react-router-dom": "^6.18.0", "vite-plugin-node-polyfills": "^0.21.0", diff --git a/client-tauri/src/components/BuildForm.module.css b/client-tauri/src/components/BuildForm.module.css new file mode 100644 index 000000000..b94e9071e --- /dev/null +++ b/client-tauri/src/components/BuildForm.module.css @@ -0,0 +1,22 @@ +.fields { + margin: 20px 0; +} + +.fields input, select { + width: 100%; + padding: 12px 20px; + margin: 8px 0; + display: inline-block; + border: 1px solid #ccc; + border-radius: 4px; + box-sizing: border-box; +} + +.submit { + background-color: var(--md-sys-color-primary);; + border: none; + color: white; + padding: 16px 32px; + text-decoration: none; + cursor: pointer; +} \ No newline at end of file diff --git a/client-tauri/src/components/BuildForm.tsx b/client-tauri/src/components/BuildForm.tsx new file mode 100644 index 000000000..14a1d380e --- /dev/null +++ b/client-tauri/src/components/BuildForm.tsx @@ -0,0 +1,28 @@ +import Joi from "@stirling-tools/joi"; +import { GenericField } from "./fields/GenericField"; +import React from "react"; + +import styles from "./BuildForm.module.css"; + +interface BuildFormProps { + /** The text to display inside the button */ + schemaDescription: Joi.Description | undefined; + onSubmit: React.FormEventHandler; +} + +export function BuildForm({ schemaDescription, onSubmit }: BuildFormProps) { + console.log("Render Build Fields", schemaDescription); + const values = (schemaDescription?.keys as any)?.values.keys as { [key: string]: Joi.Description}; + return ( +
{ onSubmit(e); e.preventDefault(); }}> +
+ { + values ? Object.keys(values).map((key) => { + return () + }) : undefined + } +
+ +
+ ); +} \ No newline at end of file diff --git a/client-tauri/src/components/OperatorCard.tsx b/client-tauri/src/components/OperatorCard.tsx index 9ed425142..f2e53c453 100644 --- a/client-tauri/src/components/OperatorCard.tsx +++ b/client-tauri/src/components/OperatorCard.tsx @@ -1,8 +1,10 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { getSchemaByName } from "@stirling-pdf/shared-operations/src/workflow/operatorAccessor"; import styles from './OperatorCard.module.css'; +import { MaterialSymbol, MaterialSymbolProps } from 'react-material-symbols'; + interface OperatorCardProps { /** The text to display inside the button */ operatorInternalName: string; @@ -10,22 +12,21 @@ interface OperatorCardProps { export function OperatorCard({ operatorInternalName }: OperatorCardProps) { const [schema, setSchema] = useState(undefined); // TODO: Type as joi type + const [materialSymbolName, setMaterialSymbolName] = useState("error"); useEffect(() => { getSchemaByName(operatorInternalName).then(schema => { if(schema) { setSchema(schema.schema); + setMaterialSymbolName(schema.materialSymbolName || "error"); } }); }, [operatorInternalName]); return ( -
- -
-

{ schema?.describe().flags.label }

+

{ schema?.describe().flags.label }

{ schema?.describe().flags.description }
diff --git a/client-tauri/src/components/fields/BuildFields.tsx b/client-tauri/src/components/fields/BuildFields.tsx deleted file mode 100644 index bb4b5a0f2..000000000 --- a/client-tauri/src/components/fields/BuildFields.tsx +++ /dev/null @@ -1,24 +0,0 @@ -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, onSubmit }: BuildFieldsProps) { - console.log("Render Build Fields", schemaDescription); - const values = (schemaDescription?.keys as any)?.values.keys as { [key: string]: Joi.Description}; - return ( -
{ onSubmit(e); e.preventDefault(); }}> - { - values ? Object.keys(values).map((key) => { - return () - }) : undefined - } - - - ); -} \ No newline at end of file diff --git a/client-tauri/src/components/fields/InputField.module.css b/client-tauri/src/components/fields/InputField.module.css new file mode 100644 index 000000000..20a24c5e6 --- /dev/null +++ b/client-tauri/src/components/fields/InputField.module.css @@ -0,0 +1,10 @@ +.custom_file_upload input[type="file"] { + display: none; +} + +.custom_file_upload { + border: 1px solid #ccc; + display: inline-block; + padding: 6px 12px; + cursor: pointer; +} \ No newline at end of file diff --git a/client-tauri/src/components/fields/InputField.tsx b/client-tauri/src/components/fields/InputField.tsx new file mode 100644 index 000000000..8b3793a12 --- /dev/null +++ b/client-tauri/src/components/fields/InputField.tsx @@ -0,0 +1,29 @@ +import { forwardRef, ForwardedRef, FormEvent } from 'react'; + +import styles from "./InputField.module.css"; + +function InputField(_props: {}, inputRef: ForwardedRef) { + function onChange(e: FormEvent) { + const files = (e.target as HTMLInputElement).files; + 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(file.name); + } + else + throw new Error("This should not happen. Contact maintainers."); + } + } + } + + return ( + + ) +} + +export default forwardRef(InputField); \ No newline at end of file diff --git a/client-tauri/src/main.tsx b/client-tauri/src/main.tsx index 174d885e7..c9c6f8b1e 100644 --- a/client-tauri/src/main.tsx +++ b/client-tauri/src/main.tsx @@ -4,6 +4,7 @@ import { BrowserRouter } from "react-router-dom"; import App from "./App"; import "./root.css"; +import 'react-material-symbols/rounded'; ReactDOM.createRoot(document.getElementById("root")!).render( diff --git a/client-tauri/src/pages/Operators.tsx b/client-tauri/src/pages/Operators.tsx index ac931b0c3..a96b8b4f7 100644 --- a/client-tauri/src/pages/Operators.tsx +++ b/client-tauri/src/pages/Operators.tsx @@ -1,16 +1,15 @@ -import { Link } from "react-router-dom"; -import { Fragment, useEffect } from "react"; +import { Fragment, useEffect, useRef } from "react"; -import { BaseSyntheticEvent, useRef, useState } from "react"; -import { Operator, OperatorSchema } from "@stirling-pdf/shared-operations/src/functions"; -import Joi from "@stirling-tools/joi"; -import { BuildFields } from "../components/fields/BuildFields"; +import { BaseSyntheticEvent, useState } from "react"; +import { BuildForm } from "../components/BuildForm"; import { getOperatorByName, getSchemaByName } 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 { useLocation } from 'react-router-dom' +import InputField from "../components/fields/InputField"; + function Dynamic() { const [schema, setSchema] = useState(undefined); // TODO: Type as joi type @@ -26,32 +25,19 @@ function Dynamic() { } }); }, [location]); - - - return ( - -

{ schema?.describe().flags.label }

- { schema?.describe().flags.description } - -
- -
- -
- -
-
- ); + + const inputRef = useRef(); async function handleSubmit(e: BaseSyntheticEvent) { const formData = new FormData(e.target); const values = Object.fromEntries(formData.entries()); let action: Action = {type: operatorInternalName, values: values}; + // 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 files = inputRef.current?.files; const inputs: PdfFile[] = []; if(files) { @@ -93,6 +79,19 @@ function Dynamic() { }); } }; + + return ( + +

{ schema?.describe().flags.label }

+

{ schema?.describe().flags.description }

+ + + +
+ +
+
+ ); } diff --git a/client-tauri/src/root.css b/client-tauri/src/root.css index 0cd8ef080..ef5fda835 100644 --- a/client-tauri/src/root.css +++ b/client-tauri/src/root.css @@ -144,4 +144,8 @@ body { a { text-decoration: inherit; color: inherit; +} + +.material-symbols { + vertical-align: top; } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 2df69d8b8..2f482f3d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,6 +35,7 @@ "react-dom": "^18.2.0", "react-i18next": "^13.3.1", "react-icons": "^4.11.0", + "react-material-symbols": "^4.4.0", "react-router-bootstrap": "^0.26.2", "react-router-dom": "^6.18.0", "vite-plugin-node-polyfills": "^0.21.0", @@ -8659,6 +8660,18 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/react-material-symbols": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/react-material-symbols/-/react-material-symbols-4.4.0.tgz", + "integrity": "sha512-KKLocA1Fqdgk70Z78PeY4M8yvqIpnvCmHuniXbF52fPD+CNoPrR5DlECq7UVOgvgTpcOQzY0qgMdpvxuw3Ynvg==", + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + } + }, "node_modules/react-refresh": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", @@ -11827,7 +11840,8 @@ "memfs": "^4.9.2", "next-i18next": "^15.1.1", "pdf-lib": "^1.17.1", - "pdfjs-dist": "^4.2.67" + "pdfjs-dist": "^4.2.67", + "react-material-symbols": "^4.4.0" }, "devDependencies": { "@types/multer": "^1.4.11" diff --git a/shared-operations/package.json b/shared-operations/package.json index bb6787f18..a55af2477 100644 --- a/shared-operations/package.json +++ b/shared-operations/package.json @@ -16,7 +16,8 @@ "memfs": "^4.9.2", "next-i18next": "^15.1.1", "pdf-lib": "^1.17.1", - "pdfjs-dist": "^4.2.67" + "pdfjs-dist": "^4.2.67", + "react-material-symbols": "^4.4.0" }, "devDependencies": { "@types/multer": "^1.4.11" diff --git a/shared-operations/src/functions/arrangePages.schema.ts b/shared-operations/src/functions/arrangePages.schema.ts index 050bdadfa..be6b92b7f 100644 --- a/shared-operations/src/functions/arrangePages.schema.ts +++ b/shared-operations/src/functions/arrangePages.schema.ts @@ -22,5 +22,6 @@ export default new OperatorSchema( .label(i18next.t("values.arrangementConfig.friendlyName", { ns: "arrangePages" })).description(i18next.t("values.arrangementConfig.description", { ns: "arrangePages" })) .example("REVERSE_ORDER").example("DUPLEX_SORT").example("BOOKLET_SORT").required() }), - JoiPDFFileSchema.label(i18next.t("outputs.pdffile.name")).description(i18next.t("outputs.pdffile.description")) + JoiPDFFileSchema.label(i18next.t("outputs.pdffile.name")).description(i18next.t("outputs.pdffile.description")), + "list" ); \ No newline at end of file diff --git a/shared-operations/src/functions/index.ts b/shared-operations/src/functions/index.ts index 9ce528a07..8f2228829 100644 --- a/shared-operations/src/functions/index.ts +++ b/shared-operations/src/functions/index.ts @@ -1,6 +1,8 @@ import { PdfFile } from "../wrappers/PdfFile"; import { Action } from "../../declarations/Action"; -import Joi from "@stirling-tools/joi"; +import Joi, { StringRegexOptions } from "@stirling-tools/joi"; +import { MaterialSymbolProps } from "react-material-symbols"; + export interface ValidationResult { valid: boolean, @@ -32,13 +34,15 @@ export class Operator { export class OperatorSchema { schema: Joi.ObjectSchema; + materialSymbolName: MaterialSymbolProps["icon"] | undefined; - constructor(label: string, description: string, inputSchema: Joi.Schema, valueSchema: Joi.Schema, outputSchema: Joi.Schema) { + constructor(label: string, description: string, inputSchema: Joi.Schema, valueSchema: Joi.Schema, outputSchema: Joi.Schema, materialSymbolName?: MaterialSymbolProps["icon"] | undefined) { this.schema = Joi.object({ input: inputSchema, values: valueSchema.required(), output: outputSchema }).label(label).description(description); + this.materialSymbolName = materialSymbolName; } }