mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-06-22 23:45:02 +00:00
Styled operator pages
This commit is contained in:
parent
51e35ee0ee
commit
e806ee8015
@ -23,6 +23,7 @@
|
|||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-i18next": "^13.3.1",
|
"react-i18next": "^13.3.1",
|
||||||
"react-icons": "^4.11.0",
|
"react-icons": "^4.11.0",
|
||||||
|
"react-material-symbols": "^4.4.0",
|
||||||
"react-router-bootstrap": "^0.26.2",
|
"react-router-bootstrap": "^0.26.2",
|
||||||
"react-router-dom": "^6.18.0",
|
"react-router-dom": "^6.18.0",
|
||||||
"vite-plugin-node-polyfills": "^0.21.0",
|
"vite-plugin-node-polyfills": "^0.21.0",
|
||||||
|
22
client-tauri/src/components/BuildForm.module.css
Normal file
22
client-tauri/src/components/BuildForm.module.css
Normal file
@ -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;
|
||||||
|
}
|
28
client-tauri/src/components/BuildForm.tsx
Normal file
28
client-tauri/src/components/BuildForm.tsx
Normal file
@ -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<HTMLFormElement>;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<form onSubmit={(e) => { onSubmit(e); e.preventDefault(); }}>
|
||||||
|
<div className={styles.fields}>
|
||||||
|
{
|
||||||
|
values ? Object.keys(values).map((key) => {
|
||||||
|
return (<GenericField key={key} fieldName={key} joiDefinition={values[key]} />)
|
||||||
|
}) : undefined
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<input className={styles.submit} type="submit" value="Submit" />
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
@ -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 { getSchemaByName } from "@stirling-pdf/shared-operations/src/workflow/operatorAccessor";
|
||||||
|
|
||||||
import styles from './OperatorCard.module.css';
|
import styles from './OperatorCard.module.css';
|
||||||
|
import { MaterialSymbol, MaterialSymbolProps } from 'react-material-symbols';
|
||||||
|
|
||||||
interface OperatorCardProps {
|
interface OperatorCardProps {
|
||||||
/** The text to display inside the button */
|
/** The text to display inside the button */
|
||||||
operatorInternalName: string;
|
operatorInternalName: string;
|
||||||
@ -10,22 +12,21 @@ interface OperatorCardProps {
|
|||||||
|
|
||||||
export function OperatorCard({ operatorInternalName }: OperatorCardProps) {
|
export function OperatorCard({ operatorInternalName }: OperatorCardProps) {
|
||||||
const [schema, setSchema] = useState<any>(undefined); // TODO: Type as joi type
|
const [schema, setSchema] = useState<any>(undefined); // TODO: Type as joi type
|
||||||
|
const [materialSymbolName, setMaterialSymbolName] = useState<MaterialSymbolProps["icon"]>("error");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getSchemaByName(operatorInternalName).then(schema => {
|
getSchemaByName(operatorInternalName).then(schema => {
|
||||||
if(schema) {
|
if(schema) {
|
||||||
setSchema(schema.schema);
|
setSchema(schema.schema);
|
||||||
|
setMaterialSymbolName(schema.materialSymbolName || "error");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [operatorInternalName]);
|
}, [operatorInternalName]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<a key={operatorInternalName} href={"/operators/" + operatorInternalName}>
|
<a key={operatorInternalName} href={"/operators/" + operatorInternalName}>
|
||||||
<div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div className={styles.operator_card}>
|
<div className={styles.operator_card}>
|
||||||
<h3>{ schema?.describe().flags.label }</h3>
|
<h3><MaterialSymbol icon={materialSymbolName} size={30} fill grade={-25} color='black'></MaterialSymbol> { schema?.describe().flags.label }</h3>
|
||||||
{ schema?.describe().flags.description }
|
{ schema?.describe().flags.description }
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
@ -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<HTMLFormElement>;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (
|
|
||||||
<form onSubmit={(e) => { onSubmit(e); e.preventDefault(); }}>
|
|
||||||
{
|
|
||||||
values ? Object.keys(values).map((key) => {
|
|
||||||
return (<GenericField key={key} fieldName={key} joiDefinition={values[key]} />)
|
|
||||||
}) : undefined
|
|
||||||
}
|
|
||||||
<input type="submit" value="Submit" />
|
|
||||||
</form>
|
|
||||||
);
|
|
||||||
}
|
|
10
client-tauri/src/components/fields/InputField.module.css
Normal file
10
client-tauri/src/components/fields/InputField.module.css
Normal file
@ -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;
|
||||||
|
}
|
29
client-tauri/src/components/fields/InputField.tsx
Normal file
29
client-tauri/src/components/fields/InputField.tsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { forwardRef, ForwardedRef, FormEvent } from 'react';
|
||||||
|
|
||||||
|
import styles from "./InputField.module.css";
|
||||||
|
|
||||||
|
function InputField(_props: {}, inputRef: ForwardedRef<HTMLInputElement>) {
|
||||||
|
function onChange(e: FormEvent<HTMLInputElement>) {
|
||||||
|
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 (
|
||||||
|
<label className={styles.custom_file_upload}>
|
||||||
|
<input onChange={onChange} type="file" id="pdfFile" accept=".pdf" multiple ref={inputRef}/>
|
||||||
|
Upload your PDF(s)!
|
||||||
|
</label>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default forwardRef(InputField);
|
@ -4,6 +4,7 @@ import { BrowserRouter } from "react-router-dom";
|
|||||||
|
|
||||||
import App from "./App";
|
import App from "./App";
|
||||||
import "./root.css";
|
import "./root.css";
|
||||||
|
import 'react-material-symbols/rounded';
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
|
@ -1,16 +1,15 @@
|
|||||||
import { Link } from "react-router-dom";
|
import { Fragment, useEffect, useRef } from "react";
|
||||||
import { Fragment, useEffect } from "react";
|
|
||||||
|
|
||||||
import { BaseSyntheticEvent, useRef, useState } from "react";
|
import { BaseSyntheticEvent, useState } from "react";
|
||||||
import { Operator, OperatorSchema } from "@stirling-pdf/shared-operations/src/functions";
|
import { BuildForm } from "../components/BuildForm";
|
||||||
import Joi from "@stirling-tools/joi";
|
|
||||||
import { BuildFields } from "../components/fields/BuildFields";
|
|
||||||
import { getOperatorByName, getSchemaByName } from "@stirling-pdf/shared-operations/src/workflow/operatorAccessor";
|
import { getOperatorByName, getSchemaByName } from "@stirling-pdf/shared-operations/src/workflow/operatorAccessor";
|
||||||
import { PdfFile, RepresentationType } from "@stirling-pdf/shared-operations/src/wrappers/PdfFile";
|
import { PdfFile, RepresentationType } from "@stirling-pdf/shared-operations/src/wrappers/PdfFile";
|
||||||
import { Action } from "@stirling-pdf/shared-operations/declarations/Action";
|
import { Action } from "@stirling-pdf/shared-operations/declarations/Action";
|
||||||
|
|
||||||
import { useLocation } from 'react-router-dom'
|
import { useLocation } from 'react-router-dom'
|
||||||
|
|
||||||
|
import InputField from "../components/fields/InputField";
|
||||||
|
|
||||||
|
|
||||||
function Dynamic() {
|
function Dynamic() {
|
||||||
const [schema, setSchema] = useState<any>(undefined); // TODO: Type as joi type
|
const [schema, setSchema] = useState<any>(undefined); // TODO: Type as joi type
|
||||||
@ -26,32 +25,19 @@ function Dynamic() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [location]);
|
}, [location]);
|
||||||
|
|
||||||
|
const inputRef = useRef<HTMLInputElement>();
|
||||||
return (
|
|
||||||
<Fragment>
|
|
||||||
<h3>{ schema?.describe().flags.label }</h3>
|
|
||||||
{ schema?.describe().flags.description }
|
|
||||||
|
|
||||||
<br />
|
|
||||||
<input type="file" id="pdfFile" accept=".pdf" multiple />
|
|
||||||
<br />
|
|
||||||
|
|
||||||
<div id="values">
|
|
||||||
<BuildFields schemaDescription={schema?.describe()} onSubmit={handleSubmit}></BuildFields>
|
|
||||||
</div>
|
|
||||||
</Fragment>
|
|
||||||
);
|
|
||||||
|
|
||||||
async function handleSubmit(e: BaseSyntheticEvent) {
|
async function handleSubmit(e: BaseSyntheticEvent) {
|
||||||
const formData = new FormData(e.target);
|
const formData = new FormData(e.target);
|
||||||
const values = Object.fromEntries(formData.entries());
|
const values = Object.fromEntries(formData.entries());
|
||||||
let action: Action = {type: operatorInternalName, values: values};
|
let action: Action = {type: operatorInternalName, values: values};
|
||||||
|
|
||||||
|
|
||||||
// Validate PDF File
|
// 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...
|
// 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[] = [];
|
const inputs: PdfFile[] = [];
|
||||||
|
|
||||||
if(files) {
|
if(files) {
|
||||||
@ -93,6 +79,19 @@ function Dynamic() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<h1>{ schema?.describe().flags.label }</h1>
|
||||||
|
<h2>{ schema?.describe().flags.description }</h2>
|
||||||
|
|
||||||
|
<InputField ref={inputRef} />
|
||||||
|
|
||||||
|
<div id="values">
|
||||||
|
<BuildForm schemaDescription={schema?.describe()} onSubmit={handleSubmit}></BuildForm>
|
||||||
|
</div>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -144,4 +144,8 @@ body {
|
|||||||
a {
|
a {
|
||||||
text-decoration: inherit;
|
text-decoration: inherit;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.material-symbols {
|
||||||
|
vertical-align: top;
|
||||||
}
|
}
|
16
package-lock.json
generated
16
package-lock.json
generated
@ -35,6 +35,7 @@
|
|||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-i18next": "^13.3.1",
|
"react-i18next": "^13.3.1",
|
||||||
"react-icons": "^4.11.0",
|
"react-icons": "^4.11.0",
|
||||||
|
"react-material-symbols": "^4.4.0",
|
||||||
"react-router-bootstrap": "^0.26.2",
|
"react-router-bootstrap": "^0.26.2",
|
||||||
"react-router-dom": "^6.18.0",
|
"react-router-dom": "^6.18.0",
|
||||||
"vite-plugin-node-polyfills": "^0.21.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",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
"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": {
|
"node_modules/react-refresh": {
|
||||||
"version": "0.14.0",
|
"version": "0.14.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz",
|
||||||
@ -11827,7 +11840,8 @@
|
|||||||
"memfs": "^4.9.2",
|
"memfs": "^4.9.2",
|
||||||
"next-i18next": "^15.1.1",
|
"next-i18next": "^15.1.1",
|
||||||
"pdf-lib": "^1.17.1",
|
"pdf-lib": "^1.17.1",
|
||||||
"pdfjs-dist": "^4.2.67"
|
"pdfjs-dist": "^4.2.67",
|
||||||
|
"react-material-symbols": "^4.4.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/multer": "^1.4.11"
|
"@types/multer": "^1.4.11"
|
||||||
|
@ -16,7 +16,8 @@
|
|||||||
"memfs": "^4.9.2",
|
"memfs": "^4.9.2",
|
||||||
"next-i18next": "^15.1.1",
|
"next-i18next": "^15.1.1",
|
||||||
"pdf-lib": "^1.17.1",
|
"pdf-lib": "^1.17.1",
|
||||||
"pdfjs-dist": "^4.2.67"
|
"pdfjs-dist": "^4.2.67",
|
||||||
|
"react-material-symbols": "^4.4.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/multer": "^1.4.11"
|
"@types/multer": "^1.4.11"
|
||||||
|
@ -22,5 +22,6 @@ export default new OperatorSchema(
|
|||||||
.label(i18next.t("values.arrangementConfig.friendlyName", { ns: "arrangePages" })).description(i18next.t("values.arrangementConfig.description", { ns: "arrangePages" }))
|
.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()
|
.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"
|
||||||
);
|
);
|
@ -1,6 +1,8 @@
|
|||||||
import { PdfFile } from "../wrappers/PdfFile";
|
import { PdfFile } from "../wrappers/PdfFile";
|
||||||
import { Action } from "../../declarations/Action";
|
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 {
|
export interface ValidationResult {
|
||||||
valid: boolean,
|
valid: boolean,
|
||||||
@ -32,13 +34,15 @@ export class Operator {
|
|||||||
|
|
||||||
export class OperatorSchema {
|
export class OperatorSchema {
|
||||||
schema: Joi.ObjectSchema<any>;
|
schema: Joi.ObjectSchema<any>;
|
||||||
|
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({
|
this.schema = Joi.object({
|
||||||
input: inputSchema,
|
input: inputSchema,
|
||||||
values: valueSchema.required(),
|
values: valueSchema.required(),
|
||||||
output: outputSchema
|
output: outputSchema
|
||||||
}).label(label).description(description);
|
}).label(label).description(description);
|
||||||
|
this.materialSymbolName = materialSymbolName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user