Compare commits
104 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b7c70c83b4 | ||
|
|
74079512d0 | ||
|
|
f3697a18e3 | ||
|
|
734c871666 | ||
|
|
5dc73ab773 | ||
|
|
152daf60fb | ||
|
|
1a7e148a78 | ||
|
|
227f46456b | ||
|
|
c2bcda925a | ||
|
|
e806ee8015 | ||
|
|
51e35ee0ee | ||
|
|
f7f6d40eee | ||
|
|
d449ccf624 | ||
|
|
40ed0f68db | ||
|
|
4056d87335 | ||
|
|
335a879e81 | ||
|
|
a928e7a917 | ||
|
|
a884268d97 | ||
|
|
28862d70ca | ||
|
|
187b7b3e78 | ||
|
|
fc3e2adc82 | ||
|
|
c19bc8d07a | ||
|
|
09a3d83dc5 | ||
|
|
9d540d6aa2 | ||
|
|
331360098f | ||
|
|
96d6f56e85 | ||
|
|
527688db90 | ||
|
|
ecb12e66b6 | ||
|
|
fae524f8da | ||
|
|
dbadfe50e6 | ||
|
|
edd4e0c39a | ||
|
|
771e66100f | ||
|
|
d0dbb7e708 | ||
|
|
3eca56f8c6 | ||
|
|
a484a804ad | ||
|
|
6f4bb8242b | ||
|
|
2420e59cd8 | ||
|
|
df10eacf92 | ||
|
|
b1bb8e0f02 | ||
|
|
efd4bdc493 | ||
|
|
e72f3d5525 | ||
|
|
f587797ddb | ||
|
|
c723a5c77d | ||
|
|
a91dd0e502 | ||
|
|
534a7776cf | ||
|
|
9cb499c93f | ||
|
|
52fae8e41b | ||
|
|
113f87aa3e | ||
|
|
6eed4a3238 | ||
|
|
2fc152e96f | ||
|
|
13bfa0b0d0 | ||
|
|
20f027bb5a | ||
|
|
644e0ceae9 | ||
|
|
244fb36195 | ||
|
|
aadc5b5cda | ||
|
|
fe12a2722c | ||
|
|
da32769463 | ||
|
|
e7b4c93481 | ||
|
|
829127c3f8 | ||
|
|
50ab159abe | ||
|
|
fbc921a077 | ||
|
|
9a721f8658 | ||
|
|
81861ac4c4 | ||
|
|
bcb8ff5843 | ||
|
|
c07e247a2a | ||
|
|
9c1588d150 | ||
|
|
5fd505d4f4 | ||
|
|
836922b856 | ||
|
|
8a5711cd86 | ||
|
|
768ab4def1 | ||
|
|
0bdda72caa | ||
|
|
0411a36b56 | ||
|
|
b2fdb7ec15 | ||
|
|
f6e67e04da | ||
|
|
f87fb32913 | ||
|
|
31ae0110ed | ||
|
|
51ca8ac7fc | ||
|
|
8d9c24b09b | ||
|
|
0df64d5e7d | ||
|
|
a0cf84d16c | ||
|
|
c9c03b206c | ||
|
|
762fa850f1 | ||
|
|
45d5f2c533 | ||
|
|
abc0f8cb8a | ||
|
|
3e10972efa | ||
|
|
efd8b48a3f | ||
|
|
993d44b9b8 | ||
|
|
ba2588ea24 | ||
|
|
09aa3a8bc9 | ||
|
|
26dcc4843e | ||
|
|
58c08821d2 | ||
|
|
c45e6c43f2 | ||
|
|
b9b7f0d5ea | ||
|
|
3fb8c6c6eb | ||
|
|
1d89a4c081 | ||
|
|
162a2baa44 | ||
|
|
498f287d57 | ||
|
|
90f0ee0bc5 | ||
|
|
6d81fa1a9e | ||
|
|
a02169048d | ||
|
|
a5060f0fd3 | ||
|
|
42904788bf | ||
|
|
d73a61ae3d | ||
|
|
7df48d43d6 |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -4,4 +4,7 @@ node_modules/
|
||||
dist/
|
||||
android/
|
||||
ios/
|
||||
releases/
|
||||
releases/
|
||||
.vscode/
|
||||
.env.local
|
||||
/server-node/jobs
|
||||
@@ -2,25 +2,34 @@
|
||||
|
||||
This file should introduce you with the concepts and tools used in this project.
|
||||
|
||||
## Basic Setup
|
||||
|
||||
- Install/Update **Node (v22.2.0, [nvm](https://github.com/coreybutler/nvm-windows))** & NPM(>10.2.1)
|
||||
- To install all dependecies `npm run update-all-dependencies` (in [root](/))
|
||||
- To test your current setup and boot a complete install of spdf v2 run `npm run dev-all` (in [root](/))
|
||||
|
||||
## Nomenclature
|
||||
|
||||
- API - Probably refers to the “normal“ API of spdf v2 without workflows unless otherwise noted.
|
||||
- Workflow - Either the express-endpoint for running workflows or the user defined workflow-json
|
||||
- Action - A sub-element of a workflow describing what operation should run on the inputted file / output of the last action.
|
||||
- Operation - The actual function that will run on the pdf, including parameters.
|
||||
- Operator - The actual code/implementation of the Operation (e.g. impose.ts) OR The parent class of every Operator.
|
||||
- Validator - A function that makes sure things are as they should be. Every Operator must have one.
|
||||
- Decorator - Explanations and Human Readable names of fields, these will be displayed in the frontend and used to provide better errors for the (workflow-)API
|
||||
|
||||
## Folder structure
|
||||
|
||||
- client-tauri - The frontend - Can be built to web and to a desktop app (with extra functions) using tauri.
|
||||
- server-node - The backend - Provides extra functionality for the web client.
|
||||
- shared-operatons - Components (e.g. Operators) that are shared between frontend and backend.
|
||||
|
||||
## Adding a PDF Operator
|
||||
|
||||
An Operator is either shared by the server and the client or it might have different implementations based on if its executed by the client, desktop-backend or web-backend. The current structure allows us to define where the Operator can be run.
|
||||
|
||||
## PDF Library Docs
|
||||
- [pdf-lib](https://pdf-lib.js.org) - js
|
||||
- [mozilla's pdfjs-dist/pdf.js](https://www.npmjs.com/package/pdfjs-dist) - js
|
||||
- [pdfcpu](https://pdfcpu.io) - go-wasm
|
||||
- [opencv-wasm](https://www.npmjs.com/package/opencv-wasm) - ?-wasm
|
||||
- [pdfjs](https://www.npmjs.com/package/pdfjs-dist) - js
|
||||
|
||||
## Adding a PDF Operation
|
||||
StirlingPDF aims to support as many types of operations as possible, including some that cannot be executed in the client. Because of this, we have decided to move some of the shared functionality into it's own node module so that it can be shared by both client and server.
|
||||
|
||||
### Adding a shared (server + client) operation
|
||||
1. Add the code for the operation to a new file in the [functions folder](/shared-operations/functions/).
|
||||
|
||||
> **NOTE:** many of the functions in these files use **dependency injection** (see impose for an example).
|
||||
>
|
||||
> **Explanation:** Because some libraries need to be imported in different ways. We import the library as needed in the ```pdf-operations.js``` files, then pass the required library objects into the operation function as a parameter.
|
||||
|
||||
2. Now that we have the function code, we need to tell the other modules that it exists. Edit the [server operations](/server-node/src/pdf-operations.js) and the [client operations](/client-ionic/src/utils/pdf-operations.ts) files to add your new operation! (Try to follow existing patterns where possible, keep the added operations in alphabetical order in the files).
|
||||
|
||||
3. If you added a wrapper function to the [client operations](/client-ionic/src/utils/pdf-operations.ts) file, you will also need to add the TypeScript declarations to the [declaration](/client-ionic/declarations/shared-operations.d.ts) file. See the other module declarations for examples.
|
||||
|
||||
### Adding a server only operation
|
||||
> WIP
|
||||
- [opencv-wasm](https://www.npmjs.com/package/opencv-wasm) - c++-wasm
|
||||
202
README.md
202
README.md
@@ -2,6 +2,9 @@
|
||||
|
||||
This is the development repository for the new StirlingPDF backend. With the power of JS, WASM & GO this will provide almost all functionality SPDF can do currently directly on the client. For automation purposes this will still provide an API to automate your workflows.
|
||||
|
||||

|
||||
This image is here to reflect current progress and will be updated accordingly.
|
||||
|
||||
## Try the new API!
|
||||
|
||||
[](https://documenter.getpostman.com/view/30633786/2s9YRB1Wto)
|
||||
@@ -21,13 +24,13 @@ To create your own, you have to understand a few key features first. You can als
|
||||
"outputOptions": {
|
||||
"zip": false
|
||||
},
|
||||
"operations": [
|
||||
"actions": [
|
||||
{
|
||||
"type": "extract",
|
||||
"values": {
|
||||
"pageIndexes": [0, 2]
|
||||
},
|
||||
"operations": []
|
||||
"actions": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -42,20 +45,20 @@ You can also nest workflows like this:
|
||||
"outputOptions": {
|
||||
"zip": false
|
||||
},
|
||||
"operations": [
|
||||
"actions": [
|
||||
{
|
||||
"type": "extract",
|
||||
"values": {
|
||||
"pageIndexes": [0, 2]
|
||||
},
|
||||
"operations": [
|
||||
"actions": [
|
||||
{
|
||||
"type": "impose",
|
||||
"values": {
|
||||
"nup": 2, // 2 pages of the input document will be put on one page of the output document.
|
||||
"format": "A4L" // A4L -> The page size of the Ouput will be an A4 in Landscape. You can also use other paper formats and "P" for portrait output.
|
||||
},
|
||||
"operations": []
|
||||
"actions": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -77,147 +80,80 @@ If you are interested in learning about this, take a look at the Example workflo
|
||||
|
||||
### Rewrite Roadmap
|
||||
|
||||
* [x] Client side PDF-Manipulation
|
||||
* [x] Workflows
|
||||
* [X] Client side PDF-Manipulation
|
||||
* [X] Workflows
|
||||
* [X] passportjs backend (auth)
|
||||
* [ ] Auth in frontend
|
||||
* [ ] Feature equivalent with S-PDF v1
|
||||
* [ ] Stateful UI
|
||||
* [ ] Node based editing of Workflows
|
||||
* [ ] Propper auth using passportjs
|
||||
|
||||
### Functions
|
||||
|
||||
Current functions of spdf and their progress in this repo.
|
||||
|
||||
#### Page Operations
|
||||
| Status | Feature | Description |
|
||||
| ------ | ------------------------ | ----------- |
|
||||
| 🚧A | Merge | |
|
||||
| 🚧A | Split | |
|
||||
| 🚧A | Organize | |
|
||||
| 🚧S | Rotate | |
|
||||
| 🚧A | Remove Pages | |
|
||||
| 🚧A | Multi-Page Layout | |
|
||||
| ❌ | Adjust page size/scale | |
|
||||
| 🚧A | Auto Split Pages | |
|
||||
| ❌ | Adjust Colours/Contrast | |
|
||||
| ❌ | Crop | |
|
||||
| 🚧A | Extract Pages | |
|
||||
| ❌ | PDF to Single large Page | |
|
||||
#### PDF Functions
|
||||
|
||||
| Status | Feature | Description |
|
||||
| ------ | -------------------------------------------------- | ----------- |
|
||||
| ✔️ | arrange | |
|
||||
| ✔️ | extract | |
|
||||
| ✔️ | impose | |
|
||||
| ✔️ | merge | |
|
||||
| ✔️ | remove blank | |
|
||||
| ✔️ | remove | |
|
||||
| ✔️ | rotate pages | |
|
||||
| ✔️ | scale content | |
|
||||
| ✔️ | scale pages | |
|
||||
| ✔️ | split by preset | |
|
||||
| ✔️ | split by index | |
|
||||
| ✔️ | update metadata | |
|
||||
| ✔️ | pdf to single large page | |
|
||||
| 🚧 | remove annotations | |
|
||||
| 🚧 | flatten | |
|
||||
| 🚧 | overlay pdfs | |
|
||||
| 🚧 | compress | |
|
||||
| 🚧 | change permissions | |
|
||||
| 🚧 | pdf to pdf/a | |
|
||||
| 🚧 | add page numbers | |
|
||||
| 🚧 | add image | |
|
||||
| 🚧 | add watermark | |
|
||||
| 🚧 | auto rename | |
|
||||
| 🚧 | add stamp | |
|
||||
| ❌ | repair | |
|
||||
| ❌ | sign with cert | |
|
||||
| ❌ | ocr | |
|
||||
| ❌ | auto split by size/count (+split by preset) | |
|
||||
| ❌ | split pdfs by sections/chapters (+split by preset) | |
|
||||
| ❌ | adjust colors/contrast | |
|
||||
| ❌ | adjust colors/contrast | |
|
||||
| ❌ | sanitize | |
|
||||
| ❌ | sign | |
|
||||
| ❌ | basic text editing | |
|
||||
| ❌ | auto redact | |
|
||||
|
||||
#### Generic Filetype (Filetypes are not supported by workflows yet. Coming Soon™)
|
||||
|
||||
#### Convert
|
||||
| Status | Feature | Description |
|
||||
| ------ | ------------------- | ----------- |
|
||||
| ❌ | Image to PDF | |
|
||||
| 🚧S | Convert file to PDF | |
|
||||
| ❌ | URL to PDF | |
|
||||
| ❌ | HTML to PDF | |
|
||||
| ❌ | Markdown to PDF | |
|
||||
| ❌ | PDF to Image | |
|
||||
| ❌ | PDF to Word | |
|
||||
| ❌ | PDF to Presentation | |
|
||||
| ❌ | PDF to Text/RTF | |
|
||||
| ❌ | PDF to HTML | |
|
||||
| ❌ | PDF to PDF/A | |
|
||||
| 🚧 | image to pdf | |
|
||||
| 🚧 | pdf to image | |
|
||||
| 🚧 | extract images | |
|
||||
| 🚧 | show javascript | |
|
||||
| ❌ | convert file to pdf | |
|
||||
| ❌ | pdf to word | |
|
||||
| ❌ | pdf to presentation | |
|
||||
| ❌ | pdf to rtf | |
|
||||
| ❌ | pdf to html | |
|
||||
| ❌ | pdf to xml | |
|
||||
| ❌ | url/website to pdf | |
|
||||
| ❌ | markdown to pdf | |
|
||||
| ❌ | pdf to csv | |
|
||||
| ❌ | get all info | |
|
||||
| ❌ | compare | |
|
||||
|
||||
#### Security
|
||||
| Status | Feature | Description |
|
||||
| ------ | --------------------- | ----------- |
|
||||
| ❌ | Add Password | |
|
||||
| ❌ | Remove Password | |
|
||||
| ❌ | Change Permissions | |
|
||||
| ❌ | Add Watermark | |
|
||||
| ❌ | Sign with Certificate | |
|
||||
| ❌ | Sanitize | |
|
||||
| ❌ | Auto Redact | |
|
||||
|
||||
#### Miscellaneous
|
||||
| Status | Feature | Description |
|
||||
| ------ | --------------------------- | ----------- |
|
||||
| ❌ | OCR | |
|
||||
| ❌ | Add image | |
|
||||
| ❌ | Compress | |
|
||||
| ❌ | Extract Images | |
|
||||
| 🚧S | Change Metadata | |
|
||||
| 🚧A | Detect/Split Scanned photos | |
|
||||
| ❌ | Sign | |
|
||||
| ❌ | Flatten | |
|
||||
| ❌ | Repair | |
|
||||
| 🚧A | Remove Blank Pages | |
|
||||
| ❌ | Compare/Diff | |
|
||||
| ❌ | Add Page Numbers | |
|
||||
| ❌ | Auto Rename | |
|
||||
| ❌ | Get info | |
|
||||
| ❌ | Show JS | |
|
||||
|
||||
|
||||
|
||||
|
||||
✔️: Done, 🚧: Started Developement, ❌: Planned Feature
|
||||
A: Available in the internal API, S: Available on the node server, C: Available in the client
|
||||
✔️: Done, 🚧: Possible with current Libraries, ❌: Planned Feature
|
||||
|
||||
## Contribute
|
||||
|
||||
For initial instructions look at [CONTRIBUTE.md](./CONTRIBUTE.md)
|
||||
|
||||
|
||||
|
||||
/*
|
||||
///// CONVERT 2 pdf
|
||||
file2pdf
|
||||
url2pdf
|
||||
html2pdf
|
||||
md2pdf
|
||||
image2pdf
|
||||
|
||||
///// CONVERT from pdf
|
||||
pdf2image
|
||||
flatten
|
||||
pdf2pdf/a
|
||||
pdf2word
|
||||
pdf2presentation
|
||||
pdf2rtf
|
||||
pdf2html
|
||||
pdf2xml
|
||||
|
||||
///// SINGLE
|
||||
merge
|
||||
rotate
|
||||
crop
|
||||
pageNumbers
|
||||
colours/contrast
|
||||
addPassword
|
||||
removePassword
|
||||
compress
|
||||
changeMetadata
|
||||
change Permissions
|
||||
OCR
|
||||
sanitise
|
||||
repair
|
||||
compare
|
||||
extract images
|
||||
signWith certificate
|
||||
impose
|
||||
adjust page size/scale
|
||||
auto rename
|
||||
getAllInfo
|
||||
showJS
|
||||
redact
|
||||
pdf2singleLargePage
|
||||
|
||||
///// SPLITTING
|
||||
split
|
||||
auto split
|
||||
detect/split scanned
|
||||
|
||||
///// REARRANGE
|
||||
- organise pages (remove/re-arrange)
|
||||
- removePages
|
||||
- removeBlank
|
||||
- extractPages
|
||||
|
||||
///// ADD OBJECTS
|
||||
add image
|
||||
add watermark
|
||||
sign
|
||||
*/
|
||||
For initial instructions look at [CONTRIBUTE.md](./CONTRIBUTE.md)
|
||||
@@ -1,12 +0,0 @@
|
||||
import { CapacitorConfig } from '@capacitor/cli';
|
||||
|
||||
const config: CapacitorConfig = {
|
||||
appId: 'com.stirlingtools.pdf',
|
||||
appName: 'StirlingPDF',
|
||||
webDir: 'dist',
|
||||
server: {
|
||||
androidScheme: 'https'
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
||||
@@ -1,10 +0,0 @@
|
||||
import { defineConfig } from "cypress";
|
||||
|
||||
export default defineConfig({
|
||||
e2e: {
|
||||
baseUrl: "http://localhost:5173",
|
||||
setupNodeEvents(on, config) {
|
||||
// implement node event listeners here
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1,6 +0,0 @@
|
||||
describe('My First Test', () => {
|
||||
it('Visits the app root url', () => {
|
||||
cy.visit('/')
|
||||
cy.contains('#container', 'Ready to create an app?')
|
||||
})
|
||||
})
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"name": "Using fixtures to represent data",
|
||||
"email": "hello@cypress.io",
|
||||
"body": "Fixtures are a great way to mock data for responses to routes"
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/// <reference types="cypress" />
|
||||
// ***********************************************
|
||||
// This example commands.ts shows you how to
|
||||
// create various custom commands and overwrite
|
||||
// existing commands.
|
||||
//
|
||||
// For more comprehensive examples of custom
|
||||
// commands please read more here:
|
||||
// https://on.cypress.io/custom-commands
|
||||
// ***********************************************
|
||||
//
|
||||
//
|
||||
// -- This is a parent command --
|
||||
// Cypress.Commands.add('login', (email, password) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a child command --
|
||||
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a dual command --
|
||||
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
|
||||
//
|
||||
// declare global {
|
||||
// namespace Cypress {
|
||||
// interface Chainable {
|
||||
// login(email: string, password: string): Chainable<void>
|
||||
// drag(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
|
||||
// dismiss(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
|
||||
// visit(originalFn: CommandOriginalFn, url: string, options: Partial<VisitOptions>): Chainable<Element>
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
@@ -1,20 +0,0 @@
|
||||
// ***********************************************************
|
||||
// This example support/e2e.ts is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import './commands'
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
||||
@@ -1,33 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Ionic App</title>
|
||||
|
||||
<base href="/" />
|
||||
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
|
||||
/>
|
||||
<meta name="format-detection" content="telephone=no" />
|
||||
<meta name="msapplication-tap-highlight" content="no" />
|
||||
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
|
||||
<link rel="shortcut icon" type="image/png" href="/favicon.png" />
|
||||
|
||||
<!-- add to homescreen for ios -->
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-title" content="Ionic App" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
|
||||
|
||||
<!-- TODO: I have no idea if this will build: -->
|
||||
<script src="/src/utils/browserfs.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"name": "StirlingPDF",
|
||||
"integrations": {
|
||||
"capacitor": {}
|
||||
},
|
||||
"type": "react-vite"
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"name": "Blank Starter",
|
||||
"baseref": "main",
|
||||
"tarignore": [
|
||||
"node_modules",
|
||||
"package-lock.json",
|
||||
"www"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "npm run build && npm run test.unit -- --watch=false && npm i --no-save concurrently && ./node_modules/.bin/concurrently \"npm run dev\" \"npm run test.e2e\" --kill-others --success first"
|
||||
}
|
||||
}
|
||||
6316
client-ionic/package-lock.json
generated
6316
client-ionic/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,58 +0,0 @@
|
||||
{
|
||||
"name": "@stirling-pdf/client-ionic",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "ionic serve",
|
||||
"dev-android": "ionic build && ionic cap run android -l --external --open",
|
||||
"linux_build-release-android": "ionic cap build android --no-open --prod && cd android && ./gradlew assembleDebug && mkdir -p ../releases && mv app/build/outputs/apk/debug/app-debug.apk ../releases/StirlingPDF.apk && echo \"Done packaging APK!!!\"",
|
||||
"linux_build-release-pwa": "ionic build && mkdir -p releases && zip -r releases/pwa.zip dist/ && echo \"Done packaging PWA!!!\"",
|
||||
"build-vite": "tsc && vite build",
|
||||
"preview": "vite preview",
|
||||
"test.e2e": "cypress run",
|
||||
"test.unit": "vitest",
|
||||
"lint": "eslint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@capacitor/android": "5.5.1",
|
||||
"@capacitor/app": "5.0.6",
|
||||
"@capacitor/core": "5.5.1",
|
||||
"@capacitor/haptics": "5.0.6",
|
||||
"@capacitor/ios": "^5.5.1",
|
||||
"@capacitor/keyboard": "5.0.6",
|
||||
"@capacitor/status-bar": "5.0.6",
|
||||
"@capawesome/capacitor-file-picker": "^5.1.1",
|
||||
"@ionic/react": "^7.0.0",
|
||||
"@ionic/react-router": "^7.0.0",
|
||||
"@stirling-pdf/shared-operations": "*",
|
||||
"@types/react-router": "^5.1.20",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"downloadjs": "^1.4.7",
|
||||
"ionicons": "^7.0.0",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-router": "^5.3.4",
|
||||
"react-router-dom": "^5.3.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@capacitor/cli": "5.5.1",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@testing-library/user-event": "^14.4.3",
|
||||
"@types/downloadjs": "^1.4.5",
|
||||
"@types/react": "^18.0.27",
|
||||
"@types/react-dom": "^18.0.10",
|
||||
"@vitejs/plugin-legacy": "^4.0.2",
|
||||
"@vitejs/plugin-react": "^4.0.1",
|
||||
"cypress": "^13.3.2",
|
||||
"eslint": "^8.35.0",
|
||||
"eslint-plugin-react": "^7.32.2",
|
||||
"jsdom": "^22.1.0",
|
||||
"typescript": "^5.1.6",
|
||||
"vite": "4.5.0",
|
||||
"vite-plugin-pwa": "^0.16.6",
|
||||
"vitest": "^0.32.2"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 930 B |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"short_name": "StirlingPDF",
|
||||
"name": "StirlingPDF",
|
||||
"icons": [
|
||||
{
|
||||
"src": "assets/icon/favicon.png",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "assets/icon/icon.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512",
|
||||
"purpose": "maskable"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#ffffff",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import App from './App';
|
||||
|
||||
test('renders without crashing', () => {
|
||||
const { baseElement } = render(<App />);
|
||||
expect(baseElement).toBeDefined();
|
||||
});
|
||||
@@ -1,42 +0,0 @@
|
||||
import { Redirect, Route } from 'react-router-dom';
|
||||
import { IonApp, IonRouterOutlet, setupIonicReact } from '@ionic/react';
|
||||
import { IonReactRouter } from '@ionic/react-router';
|
||||
import Home from './pages/Home';
|
||||
|
||||
/* Core CSS required for Ionic components to work properly */
|
||||
import '@ionic/react/css/core.css';
|
||||
|
||||
/* Basic CSS for apps built with Ionic */
|
||||
import '@ionic/react/css/normalize.css';
|
||||
import '@ionic/react/css/structure.css';
|
||||
import '@ionic/react/css/typography.css';
|
||||
|
||||
/* Optional CSS utils that can be commented out */
|
||||
import '@ionic/react/css/padding.css';
|
||||
import '@ionic/react/css/float-elements.css';
|
||||
import '@ionic/react/css/text-alignment.css';
|
||||
import '@ionic/react/css/text-transformation.css';
|
||||
import '@ionic/react/css/flex-utils.css';
|
||||
import '@ionic/react/css/display.css';
|
||||
|
||||
/* Theme variables */
|
||||
import './theme/variables.css';
|
||||
|
||||
setupIonicReact();
|
||||
|
||||
const App: React.FC = () => (
|
||||
<IonApp>
|
||||
<IonReactRouter>
|
||||
<IonRouterOutlet>
|
||||
<Route exact path="/home">
|
||||
<Home />
|
||||
</Route>
|
||||
<Route exact path="/">
|
||||
<Redirect to="/home" />
|
||||
</Route>
|
||||
</IonRouterOutlet>
|
||||
</IonReactRouter>
|
||||
</IonApp>
|
||||
);
|
||||
|
||||
export default App;
|
||||
@@ -1,24 +0,0 @@
|
||||
#container {
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
#container strong {
|
||||
font-size: 20px;
|
||||
line-height: 26px;
|
||||
}
|
||||
|
||||
#container p {
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
color: #8c8c8c;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#container a {
|
||||
text-decoration: none;
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import './ExploreContainer.css';
|
||||
|
||||
interface ContainerProps { }
|
||||
|
||||
const ExploreContainer: React.FC<ContainerProps> = () => {
|
||||
return (
|
||||
<div id="container">
|
||||
<strong>Ready to create an app?</strong>
|
||||
<p>Start with Ionic <a target="_blank" rel="noopener noreferrer" href="https://ionicframework.com/docs/components">UI Components</a></p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ExploreContainer;
|
||||
@@ -1,11 +0,0 @@
|
||||
import React from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import App from './App';
|
||||
|
||||
const container = document.getElementById('root');
|
||||
const root = createRoot(container!);
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
||||
@@ -1,48 +0,0 @@
|
||||
import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar, IonButton } from '@ionic/react';
|
||||
import './Home.css';
|
||||
import { rotatePages } from '../utils/pdf-operations.js';
|
||||
|
||||
import { FilePicker } from '@capawesome/capacitor-file-picker';
|
||||
|
||||
import download from 'downloadjs';
|
||||
|
||||
console.log(rotatePages);
|
||||
async function rotate90() {
|
||||
console.log("Test rotate 90 with Button Click");
|
||||
|
||||
const pickedFiles = await FilePicker.pickFiles({
|
||||
types: ['application/pdf'],
|
||||
multiple: false,
|
||||
});
|
||||
const file = pickedFiles.files[0];
|
||||
|
||||
const buffer = await file.blob?.arrayBuffer();
|
||||
if (!buffer) return;
|
||||
|
||||
const rotated = await rotatePages(buffer, 90)
|
||||
|
||||
download(rotated, "Rotated.pdf", "application/pdf");
|
||||
}
|
||||
|
||||
|
||||
const Home: React.FC = () => {
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Blank</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent fullscreen>
|
||||
<IonHeader collapse="condense">
|
||||
<IonToolbar>
|
||||
<IonTitle size="large">Blank</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonButton onClick={rotate90}>Rotate 90</IonButton>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default Home;
|
||||
@@ -1,14 +0,0 @@
|
||||
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
||||
// allows you to do things like:
|
||||
// expect(element).toHaveTextContent(/react/i)
|
||||
// learn more: https://github.com/testing-library/jest-dom
|
||||
import '@testing-library/jest-dom/extend-expect';
|
||||
|
||||
// Mock matchmedia
|
||||
window.matchMedia = window.matchMedia || function() {
|
||||
return {
|
||||
matches: false,
|
||||
addListener: function() {},
|
||||
removeListener: function() {}
|
||||
};
|
||||
};
|
||||
@@ -1,242 +0,0 @@
|
||||
/* Ionic Variables and Theming. For more info, please see:
|
||||
http://ionicframework.com/docs/theming/ */
|
||||
|
||||
/** Ionic CSS Variables **/
|
||||
:root {
|
||||
/** primary **/
|
||||
--ion-color-primary: #3880ff;
|
||||
--ion-color-primary-rgb: 56, 128, 255;
|
||||
--ion-color-primary-contrast: #ffffff;
|
||||
--ion-color-primary-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-primary-shade: #3171e0;
|
||||
--ion-color-primary-tint: #4c8dff;
|
||||
|
||||
/** secondary **/
|
||||
--ion-color-secondary: #3dc2ff;
|
||||
--ion-color-secondary-rgb: 61, 194, 255;
|
||||
--ion-color-secondary-contrast: #ffffff;
|
||||
--ion-color-secondary-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-secondary-shade: #36abe0;
|
||||
--ion-color-secondary-tint: #50c8ff;
|
||||
|
||||
/** tertiary **/
|
||||
--ion-color-tertiary: #5260ff;
|
||||
--ion-color-tertiary-rgb: 82, 96, 255;
|
||||
--ion-color-tertiary-contrast: #ffffff;
|
||||
--ion-color-tertiary-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-tertiary-shade: #4854e0;
|
||||
--ion-color-tertiary-tint: #6370ff;
|
||||
|
||||
/** success **/
|
||||
--ion-color-success: #2dd36f;
|
||||
--ion-color-success-rgb: 45, 211, 111;
|
||||
--ion-color-success-contrast: #ffffff;
|
||||
--ion-color-success-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-success-shade: #28ba62;
|
||||
--ion-color-success-tint: #42d77d;
|
||||
|
||||
/** warning **/
|
||||
--ion-color-warning: #ffc409;
|
||||
--ion-color-warning-rgb: 255, 196, 9;
|
||||
--ion-color-warning-contrast: #000000;
|
||||
--ion-color-warning-contrast-rgb: 0, 0, 0;
|
||||
--ion-color-warning-shade: #e0ac08;
|
||||
--ion-color-warning-tint: #ffca22;
|
||||
|
||||
/** danger **/
|
||||
--ion-color-danger: #eb445a;
|
||||
--ion-color-danger-rgb: 235, 68, 90;
|
||||
--ion-color-danger-contrast: #ffffff;
|
||||
--ion-color-danger-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-danger-shade: #cf3c4f;
|
||||
--ion-color-danger-tint: #ed576b;
|
||||
|
||||
/** dark **/
|
||||
--ion-color-dark: #222428;
|
||||
--ion-color-dark-rgb: 34, 36, 40;
|
||||
--ion-color-dark-contrast: #ffffff;
|
||||
--ion-color-dark-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-dark-shade: #1e2023;
|
||||
--ion-color-dark-tint: #383a3e;
|
||||
|
||||
/** medium **/
|
||||
--ion-color-medium: #92949c;
|
||||
--ion-color-medium-rgb: 146, 148, 156;
|
||||
--ion-color-medium-contrast: #ffffff;
|
||||
--ion-color-medium-contrast-rgb: 255, 255, 255;
|
||||
--ion-color-medium-shade: #808289;
|
||||
--ion-color-medium-tint: #9d9fa6;
|
||||
|
||||
/** light **/
|
||||
--ion-color-light: #f4f5f8;
|
||||
--ion-color-light-rgb: 244, 245, 248;
|
||||
--ion-color-light-contrast: #000000;
|
||||
--ion-color-light-contrast-rgb: 0, 0, 0;
|
||||
--ion-color-light-shade: #d7d8da;
|
||||
--ion-color-light-tint: #f5f6f9;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
/*
|
||||
* Dark Colors
|
||||
* -------------------------------------------
|
||||
*/
|
||||
|
||||
body {
|
||||
--ion-color-primary: #428cff;
|
||||
--ion-color-primary-rgb: 66,140,255;
|
||||
--ion-color-primary-contrast: #ffffff;
|
||||
--ion-color-primary-contrast-rgb: 255,255,255;
|
||||
--ion-color-primary-shade: #3a7be0;
|
||||
--ion-color-primary-tint: #5598ff;
|
||||
|
||||
--ion-color-secondary: #50c8ff;
|
||||
--ion-color-secondary-rgb: 80,200,255;
|
||||
--ion-color-secondary-contrast: #ffffff;
|
||||
--ion-color-secondary-contrast-rgb: 255,255,255;
|
||||
--ion-color-secondary-shade: #46b0e0;
|
||||
--ion-color-secondary-tint: #62ceff;
|
||||
|
||||
--ion-color-tertiary: #6a64ff;
|
||||
--ion-color-tertiary-rgb: 106,100,255;
|
||||
--ion-color-tertiary-contrast: #ffffff;
|
||||
--ion-color-tertiary-contrast-rgb: 255,255,255;
|
||||
--ion-color-tertiary-shade: #5d58e0;
|
||||
--ion-color-tertiary-tint: #7974ff;
|
||||
|
||||
--ion-color-success: #2fdf75;
|
||||
--ion-color-success-rgb: 47,223,117;
|
||||
--ion-color-success-contrast: #000000;
|
||||
--ion-color-success-contrast-rgb: 0,0,0;
|
||||
--ion-color-success-shade: #29c467;
|
||||
--ion-color-success-tint: #44e283;
|
||||
|
||||
--ion-color-warning: #ffd534;
|
||||
--ion-color-warning-rgb: 255,213,52;
|
||||
--ion-color-warning-contrast: #000000;
|
||||
--ion-color-warning-contrast-rgb: 0,0,0;
|
||||
--ion-color-warning-shade: #e0bb2e;
|
||||
--ion-color-warning-tint: #ffd948;
|
||||
|
||||
--ion-color-danger: #ff4961;
|
||||
--ion-color-danger-rgb: 255,73,97;
|
||||
--ion-color-danger-contrast: #ffffff;
|
||||
--ion-color-danger-contrast-rgb: 255,255,255;
|
||||
--ion-color-danger-shade: #e04055;
|
||||
--ion-color-danger-tint: #ff5b71;
|
||||
|
||||
--ion-color-dark: #f4f5f8;
|
||||
--ion-color-dark-rgb: 244,245,248;
|
||||
--ion-color-dark-contrast: #000000;
|
||||
--ion-color-dark-contrast-rgb: 0,0,0;
|
||||
--ion-color-dark-shade: #d7d8da;
|
||||
--ion-color-dark-tint: #f5f6f9;
|
||||
|
||||
--ion-color-medium: #989aa2;
|
||||
--ion-color-medium-rgb: 152,154,162;
|
||||
--ion-color-medium-contrast: #000000;
|
||||
--ion-color-medium-contrast-rgb: 0,0,0;
|
||||
--ion-color-medium-shade: #86888f;
|
||||
--ion-color-medium-tint: #a2a4ab;
|
||||
|
||||
--ion-color-light: #222428;
|
||||
--ion-color-light-rgb: 34,36,40;
|
||||
--ion-color-light-contrast: #ffffff;
|
||||
--ion-color-light-contrast-rgb: 255,255,255;
|
||||
--ion-color-light-shade: #1e2023;
|
||||
--ion-color-light-tint: #383a3e;
|
||||
}
|
||||
|
||||
/*
|
||||
* iOS Dark Theme
|
||||
* -------------------------------------------
|
||||
*/
|
||||
|
||||
.ios body {
|
||||
--ion-background-color: #000000;
|
||||
--ion-background-color-rgb: 0,0,0;
|
||||
|
||||
--ion-text-color: #ffffff;
|
||||
--ion-text-color-rgb: 255,255,255;
|
||||
|
||||
--ion-color-step-50: #0d0d0d;
|
||||
--ion-color-step-100: #1a1a1a;
|
||||
--ion-color-step-150: #262626;
|
||||
--ion-color-step-200: #333333;
|
||||
--ion-color-step-250: #404040;
|
||||
--ion-color-step-300: #4d4d4d;
|
||||
--ion-color-step-350: #595959;
|
||||
--ion-color-step-400: #666666;
|
||||
--ion-color-step-450: #737373;
|
||||
--ion-color-step-500: #808080;
|
||||
--ion-color-step-550: #8c8c8c;
|
||||
--ion-color-step-600: #999999;
|
||||
--ion-color-step-650: #a6a6a6;
|
||||
--ion-color-step-700: #b3b3b3;
|
||||
--ion-color-step-750: #bfbfbf;
|
||||
--ion-color-step-800: #cccccc;
|
||||
--ion-color-step-850: #d9d9d9;
|
||||
--ion-color-step-900: #e6e6e6;
|
||||
--ion-color-step-950: #f2f2f2;
|
||||
|
||||
--ion-item-background: #000000;
|
||||
|
||||
--ion-card-background: #1c1c1d;
|
||||
}
|
||||
|
||||
.ios ion-modal {
|
||||
--ion-background-color: var(--ion-color-step-100);
|
||||
--ion-toolbar-background: var(--ion-color-step-150);
|
||||
--ion-toolbar-border-color: var(--ion-color-step-250);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Material Design Dark Theme
|
||||
* -------------------------------------------
|
||||
*/
|
||||
|
||||
.md body {
|
||||
--ion-background-color: #121212;
|
||||
--ion-background-color-rgb: 18,18,18;
|
||||
|
||||
--ion-text-color: #ffffff;
|
||||
--ion-text-color-rgb: 255,255,255;
|
||||
|
||||
--ion-border-color: #222222;
|
||||
|
||||
--ion-color-step-50: #1e1e1e;
|
||||
--ion-color-step-100: #2a2a2a;
|
||||
--ion-color-step-150: #363636;
|
||||
--ion-color-step-200: #414141;
|
||||
--ion-color-step-250: #4d4d4d;
|
||||
--ion-color-step-300: #595959;
|
||||
--ion-color-step-350: #656565;
|
||||
--ion-color-step-400: #717171;
|
||||
--ion-color-step-450: #7d7d7d;
|
||||
--ion-color-step-500: #898989;
|
||||
--ion-color-step-550: #949494;
|
||||
--ion-color-step-600: #a0a0a0;
|
||||
--ion-color-step-650: #acacac;
|
||||
--ion-color-step-700: #b8b8b8;
|
||||
--ion-color-step-750: #c4c4c4;
|
||||
--ion-color-step-800: #d0d0d0;
|
||||
--ion-color-step-850: #dbdbdb;
|
||||
--ion-color-step-900: #e7e7e7;
|
||||
--ion-color-step-950: #f3f3f3;
|
||||
|
||||
--ion-item-background: #1e1e1e;
|
||||
|
||||
--ion-toolbar-background: #1f1f1f;
|
||||
|
||||
--ion-tab-bar-background: #1f1f1f;
|
||||
|
||||
--ion-card-background: #1e1e1e;
|
||||
}
|
||||
}
|
||||
|
||||
html {
|
||||
/* For more information on dynamic font scaling, visit the documentation:
|
||||
https://ionicframework.com/docs/layout/dynamic-font-scaling */
|
||||
--ion-dynamic-font: var(--ion-default-dynamic-font);
|
||||
}
|
||||
@@ -1,167 +0,0 @@
|
||||
//download.js v4.2, by dandavis; 2008-2016. [MIT] see http://danml.com/download.html for tests/usage
|
||||
// v1 landed a FF+Chrome compat way of downloading strings to local un-named files, upgraded to use a hidden frame and optional mime
|
||||
// v2 added named files via a[download], msSaveBlob, IE (10+) support, and window.URL support for larger+faster saves than dataURLs
|
||||
// v3 added dataURL and Blob Input, bind-toggle arity, and legacy dataURL fallback was improved with force-download mime and base64 support. 3.1 improved safari handling.
|
||||
// v4 adds AMD/UMD, commonJS, and plain browser support
|
||||
// v4.1 adds url download capability via solo URL argument (same domain/CORS only)
|
||||
// v4.2 adds semantic variable names, long (over 2MB) dataURL support, and hidden by default temp anchors
|
||||
// https://github.com/rndme/download
|
||||
|
||||
(function (root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define([], factory);
|
||||
} else if (typeof exports === 'object') {
|
||||
// Node. Does not work with strict CommonJS, but
|
||||
// only CommonJS-like environments that support module.exports,
|
||||
// like Node.
|
||||
module.exports = factory();
|
||||
} else {
|
||||
// Browser globals (root is window)
|
||||
root.download = factory();
|
||||
}
|
||||
}(this, function () {
|
||||
|
||||
return function download(data, strFileName, strMimeType) {
|
||||
|
||||
var self = window, // this script is only for browsers anyway...
|
||||
defaultMime = "application/octet-stream", // this default mime also triggers iframe downloads
|
||||
mimeType = strMimeType || defaultMime,
|
||||
payload = data,
|
||||
url = !strFileName && !strMimeType && payload,
|
||||
anchor = document.createElement("a"),
|
||||
toString = function(a){return String(a);},
|
||||
myBlob = (self.Blob || self.MozBlob || self.WebKitBlob || toString),
|
||||
fileName = strFileName || "download",
|
||||
blob,
|
||||
reader;
|
||||
myBlob= myBlob.call ? myBlob.bind(self) : Blob ;
|
||||
|
||||
if(String(this)==="true"){ //reverse arguments, allowing download.bind(true, "text/xml", "export.xml") to act as a callback
|
||||
payload=[payload, mimeType];
|
||||
mimeType=payload[0];
|
||||
payload=payload[1];
|
||||
}
|
||||
|
||||
|
||||
if(url && url.length< 2048){ // if no filename and no mime, assume a url was passed as the only argument
|
||||
fileName = url.split("/").pop().split("?")[0];
|
||||
anchor.href = url; // assign href prop to temp anchor
|
||||
if(anchor.href.indexOf(url) !== -1){ // if the browser determines that it's a potentially valid url path:
|
||||
var ajax=new XMLHttpRequest();
|
||||
ajax.open( "GET", url, true);
|
||||
ajax.responseType = 'blob';
|
||||
ajax.onload= function(e){
|
||||
download(e.target.response, fileName, defaultMime);
|
||||
};
|
||||
setTimeout(function(){ ajax.send();}, 0); // allows setting custom ajax headers using the return:
|
||||
return ajax;
|
||||
} // end if valid url?
|
||||
} // end if url?
|
||||
|
||||
|
||||
//go ahead and download dataURLs right away
|
||||
if(/^data:([\w+-]+\/[\w+.-]+)?[,;]/.test(payload)){
|
||||
|
||||
if(payload.length > (1024*1024*1.999) && myBlob !== toString ){
|
||||
payload=dataUrlToBlob(payload);
|
||||
mimeType=payload.type || defaultMime;
|
||||
}else{
|
||||
return navigator.msSaveBlob ? // IE10 can't do a[download], only Blobs:
|
||||
navigator.msSaveBlob(dataUrlToBlob(payload), fileName) :
|
||||
saver(payload) ; // everyone else can save dataURLs un-processed
|
||||
}
|
||||
|
||||
}else{//not data url, is it a string with special needs?
|
||||
if(/([\x80-\xff])/.test(payload)){
|
||||
var i=0, tempUiArr= new Uint8Array(payload.length), mx=tempUiArr.length;
|
||||
for(i;i<mx;++i) tempUiArr[i]= payload.charCodeAt(i);
|
||||
payload=new myBlob([tempUiArr], {type: mimeType});
|
||||
}
|
||||
}
|
||||
blob = payload instanceof myBlob ?
|
||||
payload :
|
||||
new myBlob([payload], {type: mimeType}) ;
|
||||
|
||||
|
||||
function dataUrlToBlob(strUrl) {
|
||||
var parts= strUrl.split(/[:;,]/),
|
||||
type= parts[1],
|
||||
decoder= parts[2] == "base64" ? atob : decodeURIComponent,
|
||||
binData= decoder( parts.pop() ),
|
||||
mx= binData.length,
|
||||
i= 0,
|
||||
uiArr= new Uint8Array(mx);
|
||||
|
||||
for(i;i<mx;++i) uiArr[i]= binData.charCodeAt(i);
|
||||
|
||||
return new myBlob([uiArr], {type: type});
|
||||
}
|
||||
|
||||
function saver(url, winMode){
|
||||
|
||||
if ('download' in anchor) { //html5 A[download]
|
||||
anchor.href = url;
|
||||
anchor.setAttribute("download", fileName);
|
||||
anchor.className = "download-js-link";
|
||||
anchor.innerHTML = "downloading...";
|
||||
anchor.style.display = "none";
|
||||
document.body.appendChild(anchor);
|
||||
setTimeout(function() {
|
||||
anchor.click();
|
||||
document.body.removeChild(anchor);
|
||||
if(winMode===true){setTimeout(function(){ self.URL.revokeObjectURL(anchor.href);}, 250 );}
|
||||
}, 66);
|
||||
return true;
|
||||
}
|
||||
|
||||
// handle non-a[download] safari as best we can:
|
||||
if(/(Version)\/(\d+)\.(\d+)(?:\.(\d+))?.*Safari\//.test(navigator.userAgent)) {
|
||||
if(/^data:/.test(url)) url="data:"+url.replace(/^data:([\w\/\-\+]+)/, defaultMime);
|
||||
if(!window.open(url)){ // popup blocked, offer direct download:
|
||||
if(confirm("Displaying New Document\n\nUse Save As... to download, then click back to return to this page.")){ location.href=url; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//do iframe dataURL download (old ch+FF):
|
||||
var f = document.createElement("iframe");
|
||||
document.body.appendChild(f);
|
||||
|
||||
if(!winMode && /^data:/.test(url)){ // force a mime that will download:
|
||||
url="data:"+url.replace(/^data:([\w\/\-\+]+)/, defaultMime);
|
||||
}
|
||||
f.src=url;
|
||||
setTimeout(function(){ document.body.removeChild(f); }, 333);
|
||||
|
||||
}//end saver
|
||||
|
||||
|
||||
|
||||
|
||||
if (navigator.msSaveBlob) { // IE10+ : (has Blob, but not a[download] or URL)
|
||||
return navigator.msSaveBlob(blob, fileName);
|
||||
}
|
||||
|
||||
if(self.URL){ // simple fast and modern way using Blob and URL:
|
||||
saver(self.URL.createObjectURL(blob), true);
|
||||
}else{
|
||||
// handle non-Blob()+non-URL browsers:
|
||||
if(typeof blob === "string" || blob.constructor===toString ){
|
||||
try{
|
||||
return saver( "data:" + mimeType + ";base64," + self.btoa(blob) );
|
||||
}catch(y){
|
||||
return saver( "data:" + mimeType + "," + encodeURIComponent(blob) );
|
||||
}
|
||||
}
|
||||
|
||||
// Blob but not URL support:
|
||||
reader=new FileReader();
|
||||
reader.onload=function(e){
|
||||
saver(this.result);
|
||||
};
|
||||
reader.readAsDataURL(blob);
|
||||
}
|
||||
return true;
|
||||
}; /* end download() */
|
||||
}));
|
||||
@@ -1,38 +0,0 @@
|
||||
|
||||
// Import injected libraries here!
|
||||
|
||||
import { Metadata, editMetadata as dependantEditMetadata} from "@stirling-pdf/shared-operations/functions/editMetadata";
|
||||
import { extractPages as dependantExtractPages } from "@stirling-pdf/shared-operations/functions/extractPages";
|
||||
import { mergePDFs as dependantMergePDFs } from '@stirling-pdf/shared-operations/functions/mergePDFs';
|
||||
import { rotatePages as dependantRotatePages } from '@stirling-pdf/shared-operations/functions/rotatePages';
|
||||
import { scaleContent as dependantScaleContent} from '@stirling-pdf/shared-operations/functions/scaleContent';
|
||||
import { scalePage as dependantScalePage } from '@stirling-pdf/shared-operations/functions/scalePage';
|
||||
import { splitPDF as dependantSplitPDF } from '@stirling-pdf/shared-operations/functions/splitPDF';
|
||||
|
||||
export async function editMetadata(snapshot: string | Uint8Array | ArrayBuffer, metadata: Metadata) {
|
||||
return dependantEditMetadata(snapshot, metadata);
|
||||
}
|
||||
|
||||
export async function extractPages(snapshot: string | Uint8Array | ArrayBuffer, pageIndexes: number[]) {
|
||||
return dependantExtractPages(snapshot, pageIndexes);
|
||||
}
|
||||
|
||||
export async function mergePDFs(snapshots: (string | Uint8Array | ArrayBuffer)[]) {
|
||||
return dependantMergePDFs(snapshots);
|
||||
}
|
||||
|
||||
export async function rotatePages(snapshot: string | Uint8Array | ArrayBuffer, rotation: number) {
|
||||
return dependantRotatePages(snapshot, rotation);
|
||||
}
|
||||
|
||||
export async function scaleContent(snapshot: string | Uint8Array | ArrayBuffer, scaleFactor: number) {
|
||||
return dependantScaleContent(snapshot, scaleFactor);
|
||||
}
|
||||
|
||||
export async function scalePage(snapshot: string | Uint8Array | ArrayBuffer, pageSize: { width: number; height: number; }) {
|
||||
return dependantScalePage(snapshot, pageSize);
|
||||
}
|
||||
|
||||
export async function splitPDF(snapshot: string | Uint8Array | ArrayBuffer, splitAfterPageArray: number[]) {
|
||||
return dependantSplitPDF(snapshot, splitAfterPageArray);
|
||||
}
|
||||
1
client-ionic/src/vite-env.d.ts
vendored
1
client-ionic/src/vite-env.d.ts
vendored
@@ -1 +0,0 @@
|
||||
/// <reference types="vite/client" />
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
||||
"allowJs": false,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": false,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": [
|
||||
"src",
|
||||
"declarations/*.d.ts"
|
||||
],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import legacy from '@vitejs/plugin-legacy'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import { defineConfig } from 'vite'
|
||||
import { VitePWA } from 'vite-plugin-pwa'; // https://ionicframework.com/docs/react/pwa
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
react(),
|
||||
VitePWA({ registerType: 'autoUpdate' }),
|
||||
legacy()
|
||||
],
|
||||
test: {
|
||||
globals: true,
|
||||
environment: 'jsdom',
|
||||
setupFiles: './src/setupTests.ts',
|
||||
}
|
||||
})
|
||||
2
client-tauri/.env
Normal file
2
client-tauri/.env
Normal file
@@ -0,0 +1,2 @@
|
||||
VITE_USE_AUTH=False
|
||||
VITE_BACKEND=""
|
||||
15
client-tauri/Dockerfile
Normal file
15
client-tauri/Dockerfile
Normal file
@@ -0,0 +1,15 @@
|
||||
# Use an existing image as a base
|
||||
FROM node:22.2.0-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm ci --legacy-peer-deps
|
||||
|
||||
RUN npm i -g serve
|
||||
|
||||
COPY ./dist ./dist
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD [ "serve", "-s", "dist", "-p", "3000" ]
|
||||
13
client-tauri/Makefile
Normal file
13
client-tauri/Makefile
Normal file
@@ -0,0 +1,13 @@
|
||||
vite-build:
|
||||
npx tsc
|
||||
npx vite build
|
||||
|
||||
vite-dockerize:
|
||||
make vite-build
|
||||
bash -c "cp ../package-lock.json ./"
|
||||
docker build . -t stirling-pdf:latest
|
||||
bash -c "rm ./package-lock.json"
|
||||
docker image prune
|
||||
|
||||
tauri-build:
|
||||
npx tauri build
|
||||
@@ -1,7 +1,11 @@
|
||||
# Tauri + React + Typescript
|
||||
# Stirling-PDF frontend
|
||||
|
||||
This template should help get you started developing with Tauri, React and Typescript in Vite.
|
||||
Tauri + Vite + React + Typescript
|
||||
|
||||
## Recommended IDE Setup
|
||||
## Development
|
||||
|
||||
- [VS Code](https://code.visualstudio.com/) + [Tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode) + [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer)
|
||||
Use the package scripts to start developement
|
||||
|
||||
## Production
|
||||
|
||||
Use the make file to build for the correct platform
|
||||
@@ -4,37 +4,40 @@
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"preview": "vite preview",
|
||||
"tauri": "tauri"
|
||||
"vite:dev": "vite",
|
||||
"vite:preview-prod": "make vite-build && vite preview",
|
||||
"tauri:build-debug": "npx tauri build --debug",
|
||||
"tauri:dev": "tauri dev"
|
||||
},
|
||||
"dependencies": {
|
||||
"@stirling-pdf/shared-operations": "^0.0.0",
|
||||
"@tauri-apps/api": "^1.5.1",
|
||||
"@types/semver": "^7.5.8",
|
||||
"archiver": "^6.0.1",
|
||||
"bootstrap": "^5.3.2",
|
||||
"i18next": "^23.6.0",
|
||||
"i18next-browser-languagedetector": "^7.1.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
"pdfjs-dist": "^4.0.189",
|
||||
"react": "^18.2.0",
|
||||
"react-bootstrap": "^2.9.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-i18next": "^13.3.1",
|
||||
"react-i18next": "^15.0.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": "^5.4.2",
|
||||
"vite-plugin-node-polyfills": "^0.22.0",
|
||||
"vite-plugin-top-level-await": "^1.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "^1.5.0",
|
||||
"@types/archiver": "^5.3.4",
|
||||
"@types/archiver": "^6.0.2",
|
||||
"@types/react": "^18.2.15",
|
||||
"@types/react-dom": "^18.2.7",
|
||||
"@types/react-router-bootstrap": "^0.26.5",
|
||||
"@vitejs/plugin-react": "^4.0.3",
|
||||
"@vitejs/plugin-react": "^4.3.1",
|
||||
"typescript": "^5.0.2",
|
||||
"vite": "^4.4.4"
|
||||
"vite-plugin-compile-time": "^0.2.1",
|
||||
"vite-plugin-dynamic-import": "^1.5.0"
|
||||
}
|
||||
}
|
||||
|
||||
1162
client-tauri/src-tauri/Cargo.lock
generated
1162
client-tauri/src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "stirling_pdf"
|
||||
version = "0.0.0"
|
||||
description = "A Tauri App"
|
||||
authors = ["you"]
|
||||
name = "StirlingPDF"
|
||||
version = "2.0.0" # needed for dev build?
|
||||
description = "Selfhosted PDF processing"
|
||||
authors = ["LaserKaspar, SaudF"]
|
||||
license = ""
|
||||
repository = ""
|
||||
edition = "2021"
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"build": {
|
||||
"beforeDevCommand": "npm run dev",
|
||||
"beforeBuildCommand": "npm run build",
|
||||
"beforeDevCommand": "npm run vite:dev",
|
||||
"beforeBuildCommand": "make vite-build",
|
||||
"devPath": "http://localhost:1420",
|
||||
"distDir": "../dist",
|
||||
"withGlobalTauri": false
|
||||
},
|
||||
"package": {
|
||||
"productName": "StirlingPDF",
|
||||
"version": "0.0.0"
|
||||
"version": "2.0.0"
|
||||
},
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
|
||||
@@ -1,72 +1,82 @@
|
||||
import { Suspense } from 'react';
|
||||
import { Fragment, Suspense } from "react";
|
||||
|
||||
import { Routes, Route, Outlet } from "react-router-dom";
|
||||
import { Routes, Route, Outlet, Navigate } from "react-router-dom";
|
||||
import Home from "./pages/Home";
|
||||
import About from "./pages/About";
|
||||
import Dashboard from "./pages/Dashboard";
|
||||
import ToPdf from "./pages/convert/ToPdf"
|
||||
import Operators from "./pages/Operators";
|
||||
import NoMatch from "./pages/NoMatch";
|
||||
import NavBar from "./components/NavBar";
|
||||
|
||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||
import { Container } from "react-bootstrap";
|
||||
|
||||
import i18n from "i18next";
|
||||
import { useTranslation, initReactI18next } from "react-i18next";
|
||||
import LanguageDetector from 'i18next-browser-languagedetector';
|
||||
import ar from './locales/ar.json';
|
||||
import en from './locales/en.json';
|
||||
import LanguageDetector from "i18next-browser-languagedetector";
|
||||
|
||||
import './general.css'
|
||||
import i18next from "i18next";
|
||||
import resourcesToBackend from "i18next-resources-to-backend";
|
||||
import { listOperatorNames } from "@stirling-pdf/shared-operations/src/workflow/operatorAccessor";
|
||||
import AuthenticatedRoute from "./components/AuthenticatedRoute";
|
||||
import Login from "./pages/Auth/Login";
|
||||
import Logout from "./pages/Auth/Logout";
|
||||
import Register from "./pages/Auth/Register";
|
||||
|
||||
i18n
|
||||
.use(LanguageDetector)
|
||||
.use(initReactI18next) // passes i18n down to react-i18next
|
||||
.init({
|
||||
i18next.use(LanguageDetector).use(initReactI18next).use(resourcesToBackend((language: string, namespace: string) => import(`@stirling-pdf/shared-operations/public/locales/${namespace}/${language}.json`).catch((e) => console.warn("some component tried to render with an unsupported language, falling back to en", e))))
|
||||
.init({
|
||||
debug: false,
|
||||
ns: ["common"], // Preload this namespace, no need to add the others, they will load once their module is loaded
|
||||
defaultNS: "common",
|
||||
fallbackLng: "en",
|
||||
resources: { ar,en },
|
||||
});
|
||||
interpolation: {
|
||||
escapeValue: false,
|
||||
},
|
||||
initImmediate: false // Makes loading blocking but sync
|
||||
}); // TODO: use i18next.config.ts instead
|
||||
|
||||
export default function App() {
|
||||
|
||||
return (
|
||||
<Suspense fallback="loading">
|
||||
{/* Routes nest inside one another. Nested route paths build upon
|
||||
return (
|
||||
<Suspense fallback={<Loading/>}>
|
||||
{/* Routes nest inside one another. Nested route paths build upon
|
||||
parent route paths, and nested route elements render inside
|
||||
parent route elements. See the note about <Outlet> below. */}
|
||||
<Routes>
|
||||
<Route path="/" element={<Layout />}>
|
||||
<Route index element={<Home />} />
|
||||
<Route path="about" element={<About />} />
|
||||
<Route path="dashboard" element={<Dashboard />} />
|
||||
<Route path="to-pdf" element={<ToPdf />} />
|
||||
<Routes>
|
||||
<Route path="/auth" element={<Layout />}>
|
||||
<Route index element={<Navigate to="/auth/login" />}/>
|
||||
<Route path="login" element={<Login />}></Route>
|
||||
<Route path="logout" element={<Logout />}></Route>
|
||||
<Route path="register" element={<Register />}></Route>
|
||||
<Route path="*" element={<NoMatch />} />
|
||||
</Route>
|
||||
|
||||
{/* Using path="*"" means "match anything", so this route
|
||||
acts like a catch-all for URLs that we don't have explicit
|
||||
routes for. */}
|
||||
<Route path="*" element={<NoMatch />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
</Suspense>
|
||||
);
|
||||
<Route element={<AuthenticatedRoute />}>
|
||||
<Route path="/" element={<Layout />}>
|
||||
<Route index element={<Home />} />
|
||||
<Route path="*" element={<NoMatch />} />
|
||||
</Route>
|
||||
|
||||
<Route path="/operators" element={<Layout />}>
|
||||
<Route index element={<NoMatch />} />
|
||||
{listOperatorNames().map((name) => {
|
||||
return <Route key={name} path={name} element={<Operators/>} />;
|
||||
})}
|
||||
<Route path="*" element={<NoMatch />} />
|
||||
</Route>
|
||||
</Route>
|
||||
</Routes>
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
function Loading() {
|
||||
return "Loading";
|
||||
}
|
||||
|
||||
function Layout() {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div lang-direction={t('language.direction')}>
|
||||
<NavBar/>
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div lang-direction={t("language.direction")}>
|
||||
<NavBar/>
|
||||
|
||||
{/* An <Outlet> renders whatever child route is currently active,
|
||||
{/* An <Outlet> renders whatever child route is currently active,
|
||||
so you can think about this <Outlet> as a placeholder for
|
||||
the child routes we defined above. */}
|
||||
<Container fluid="sm" className="">
|
||||
<div className="row justify-content-center">
|
||||
<div className="col-md-6">
|
||||
<Outlet/>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
110
client-tauri/src/assets/favicon.svg
Normal file
110
client-tauri/src/assets/favicon.svg
Normal file
@@ -0,0 +1,110 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
version="1.1"
|
||||
id="Layer_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 512 512"
|
||||
style="enable-background:new 0 0 512 512;"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="favicon.svg"
|
||||
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
|
||||
inkscape:export-filename="favicon.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs173">
|
||||
|
||||
|
||||
<linearGradient
|
||||
id="XMLID_5_"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="304.496"
|
||||
y1="422.9102"
|
||||
x2="316.036"
|
||||
y2="326.2626">
|
||||
<stop
|
||||
offset="0"
|
||||
style="stop-color:#DCF1F3"
|
||||
id="stop156" />
|
||||
<stop
|
||||
offset="1"
|
||||
style="stop-color:#C2C2C9"
|
||||
id="stop158" />
|
||||
</linearGradient>
|
||||
|
||||
</defs><sodipodi:namedview
|
||||
id="namedview171"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.4142136"
|
||||
inkscape:cx="219.91021"
|
||||
inkscape:cy="232.63813"
|
||||
inkscape:window-width="3840"
|
||||
inkscape:window-height="2054"
|
||||
inkscape:window-x="2869"
|
||||
inkscape:window-y="-11"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="XMLID_4_" />
|
||||
<style
|
||||
type="text/css"
|
||||
id="style150">
|
||||
.st0{fill:#FFFFFF;}
|
||||
.st1{fill:#C02223;}
|
||||
.st2{fill:#882425;}
|
||||
.st3{fill:url(#XMLID_5_);}
|
||||
.st4{fill:url(#XMLID_7_);}
|
||||
</style>
|
||||
|
||||
<g
|
||||
id="XMLID_4_">
|
||||
<path
|
||||
id="XMLID_131_"
|
||||
class="st1"
|
||||
d="M 347.01402,14.355825 98.978019,69.02261 C 73.825483,74.547445 55.942464,96.792175 55.942464,122.52628 v 315.06096 c 0,22.39012 16.719895,41.14548 38.819234,43.76251 L 224.8861,498.36042 339.48636,384.26465 455.76603,265.15425 453.73057,84.870162 C 453.43979,62.916214 433.08513,46.632491 411.71274,51.284984 l -28.78729,6.251786 0.14539,-13.666697 C 383.36162,24.678542 365.62399,10.284894 347.01402,14.355825 Z"
|
||||
sodipodi:nodetypes="ccssccccccccc"
|
||||
style="stroke-width:1.45391" /><path
|
||||
id="XMLID_117_"
|
||||
class="st2"
|
||||
d="m 383.21622,57.53677 v 285.8375 L 456.05681,265.00885 454.02135,78.763767 C 453.87595,59.863016 436.28372,45.905539 417.81914,49.97647 Z"
|
||||
style="stroke-width:1.45391" /><polygon
|
||||
id="XMLID_18_"
|
||||
class="st3"
|
||||
points="234.7,422.6 368.5,387.7 393.5,262.2 "
|
||||
style="fill:url(#XMLID_5_)"
|
||||
transform="matrix(1.4556308,0,0,1.4548265,-116.73161,-116.45231)" />
|
||||
<linearGradient
|
||||
id="XMLID_7_"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="223.0838"
|
||||
y1="372.7559"
|
||||
x2="241.4174"
|
||||
y2="114.557"
|
||||
gradientTransform="matrix(1.4539039,0,0,1.4539039,-116.19976,-116.20474)">
|
||||
<stop
|
||||
offset="0"
|
||||
style="stop-color:#DCF1F3"
|
||||
id="stop163" />
|
||||
<stop
|
||||
offset="1"
|
||||
style="stop-color:#C2C2C9"
|
||||
id="stop165" />
|
||||
</linearGradient>
|
||||
<path
|
||||
id="XMLID_6_"
|
||||
class="st4"
|
||||
d="m 282.89686,214.84917 c 0,0 -22.24473,-28.93269 -38.67384,-36.78377 -10.46811,-4.94327 -26.02489,-6.83335 -38.23768,-0.72695 -18.02841,9.0142 -19.91848,34.31213 -3.34397,44.34406 3.92553,2.47165 9.15959,4.50711 15.99294,6.10641 36.63838,8.43264 97.12077,25.87949 89.70587,96.10304 0,0 -4.21633,65.86185 -73.56753,73.42215 -12.2128,1.30851 -24.57098,0.43617 -36.493,-2.32625 -16.42911,-3.63476 -45.50719,-11.04967 -59.75545,-19.91849 l -2.61703,-75.16682 h 6.97875 c 0,0 13.81208,33.43978 53.06749,49.57812 7.26952,2.90781 15.26599,4.07093 22.97168,2.90781 9.74116,-1.45391 21.22699,-6.68796 25.87949,-22.53551 0,0 7.85108,-23.11707 -32.85823,-35.76604 -32.56744,-10.17733 -63.24481,-20.64543 -75.89378,-54.95757 -5.961,-16.28371 -6.97874,-34.31212 -2.90781,-51.61358 5.37944,-22.53551 20.79082,-54.23062 64.40794,-67.89732 0,0 57.28381,-15.55677 96.53922,5.52484 l -1.74468,89.70587 z"
|
||||
style="fill:url(#XMLID_7_);stroke-width:1.45391" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.0 KiB |
22
client-tauri/src/components/AuthenticatedRoute.tsx
Normal file
22
client-tauri/src/components/AuthenticatedRoute.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { ReactNode } from "react";
|
||||
import { Navigate, Outlet } from "react-router-dom";
|
||||
|
||||
interface AuthenticatedRouteProps {
|
||||
isAuthenticated: boolean;
|
||||
children?: ReactNode; // Accepting children
|
||||
}
|
||||
|
||||
|
||||
function isAuthenticated() {
|
||||
if (import.meta.env.VITE_USE_AUTH == "True") {
|
||||
// TODO: if user is set in localstorage and is valid (either by time or by checking online) return true
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function AuthenticatedRoute({}: {}): JSX.Element {
|
||||
return isAuthenticated() ? <Outlet /> : <Navigate to="/auth/login" />;
|
||||
};
|
||||
|
||||
export default AuthenticatedRoute;
|
||||
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,138 +0,0 @@
|
||||
#search-icon i {
|
||||
font-size: 24px; /* Adjust this to your desired size */
|
||||
transition: color 0.3s;
|
||||
}
|
||||
|
||||
#search-icon:hover i {
|
||||
color: #666; /* Adjust this to your hover color */
|
||||
}
|
||||
|
||||
#navbarSearch {
|
||||
transition: all 0.3s;
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
|
||||
}
|
||||
|
||||
#navbarSearch.show {
|
||||
max-height: 300px; /* Adjust this to your desired max height */
|
||||
}
|
||||
|
||||
.search-input {
|
||||
transition: border 0.3s, box-shadow 0.3s;
|
||||
|
||||
}
|
||||
|
||||
.search-input:focus {
|
||||
border-color: #666; /* Adjust this to your focus color */
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); /* Adjust this to your desired shadow */
|
||||
}
|
||||
|
||||
#searchResults {
|
||||
max-width: 300px; /* Adjust to your preferred width */
|
||||
transition: height 0.3s ease; /* Smooth height transition */
|
||||
}
|
||||
|
||||
/* Set a fixed height and styling for each search result item */
|
||||
.search-results a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px; /* space between icon and text */
|
||||
height: 40px; /* Adjust based on your design */
|
||||
overflow: hidden; /* Prevent content from overflowing */
|
||||
white-space: nowrap; /* Prevent text from wrapping to next line */
|
||||
text-overflow: ellipsis; /* Truncate text if it's too long */
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#navbarSearch {
|
||||
top: 100%;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
#searchForm {
|
||||
width: 200px; /* Adjust this value as needed */
|
||||
}
|
||||
|
||||
/* Style the search results to match the navbar */
|
||||
#searchResults {
|
||||
max-height: 200px; /* Adjust this value as needed */
|
||||
overflow-y: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#searchResults .dropdown-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
height: 50px; /* Fixed height */
|
||||
overflow: hidden; /* Hide overflow */
|
||||
}
|
||||
|
||||
#searchResults .icon {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
#searchResults .icon-text {
|
||||
display: inline;
|
||||
overflow: hidden; /* Hide overflow */
|
||||
text-overflow: ellipsis; /* Add ellipsis for long text */
|
||||
}
|
||||
|
||||
|
||||
|
||||
.main-icon {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
vertical-align: middle;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.nav-icon svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
vertical-align: middle;
|
||||
transform: translateY(-2px);
|
||||
margin-left: 4px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
/*
|
||||
.icon+.icon {
|
||||
margin-left: -4px;
|
||||
}
|
||||
*/
|
||||
.nav-icon span {
|
||||
margin-left: 4px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.nav-item-separator {
|
||||
position: relative;
|
||||
margin: 0 4px; /* Adjust the margin as needed */
|
||||
}
|
||||
|
||||
.nav-item-separator::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 10%; /* Adjust the top and bottom margins as needed */
|
||||
bottom: 10%;
|
||||
width: 1px;
|
||||
background-color: #ccc; /* Adjust the color as needed */
|
||||
}
|
||||
|
||||
.navbar-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
19
client-tauri/src/components/NavBar.module.css
Normal file
19
client-tauri/src/components/NavBar.module.css
Normal file
@@ -0,0 +1,19 @@
|
||||
.navbar_brand {
|
||||
color: var(--md-sys-color-on-surface) !important;
|
||||
}
|
||||
|
||||
.main_icon {
|
||||
width: 36px;
|
||||
height: 42px;
|
||||
vertical-align: middle;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.icon_text {
|
||||
margin-left: 8px;
|
||||
margin-right: 4px;
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
vertical-align: middle;
|
||||
}
|
||||
@@ -1,173 +1,15 @@
|
||||
|
||||
import { AiOutlineMergeCells, AiOutlineSplitCells } from "react-icons/ai";
|
||||
import { BiCrop, BiSprayCan } from "react-icons/bi";
|
||||
import {
|
||||
BsTools, BsSortNumericDown, BsArrowClockwise, BsFileEarmarkX, BsLayoutSplit, BsPalette, BsArrowUpSquare, Bs1Square, BsFileEarmarkPdf,
|
||||
BsArrowLeftRight, BsFileEarmarkImage, BsFileEarmark, BsFiletypeHtml, BsLink, BsFiletypeMd, BsFileEarmarkWord, BsFiletypePpt, BsFiletypeTxt,
|
||||
BsFiletypeXml, BsLock, BsUnlock, BsShieldLock, BsDroplet, BsAward, BsEraserFill, BsCardList, BsClipboardData, BsFile, BsFileEarmarkRichtext,
|
||||
BsFileZip, BsFiletypeJs, BsFonts, BsImages, BsInfoCircle, BsSearch, BsShieldCheck, BsVectorPen, BsWrench, BsArrowsCollapse, BsGrid, Bs123,
|
||||
BsArrowsFullscreen
|
||||
} from "react-icons/bs";
|
||||
import { MdOutlineScanner, MdOutlineBalance } from "react-icons/md";
|
||||
import { IconType } from "react-icons";
|
||||
|
||||
import Container from 'react-bootstrap/Container';
|
||||
import Nav from 'react-bootstrap/Nav';
|
||||
import Navbar from 'react-bootstrap/Navbar';
|
||||
import NavDropdown from 'react-bootstrap/NavDropdown';
|
||||
import { LinkContainer } from 'react-router-bootstrap';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import LanguagePicker from "./toolbar/LanguagePicker";
|
||||
import Logo from '../../public/stirling-pdf-logo.svg'
|
||||
import './NavBar.css';
|
||||
|
||||
interface NavInfoItem {
|
||||
displayText: string;
|
||||
icon: any;
|
||||
dest: string;
|
||||
tooltip?: string;
|
||||
}
|
||||
interface NavInfoSublist {
|
||||
displayText: string;
|
||||
icon: IconType;
|
||||
sublist: Array<NavInfoItem>;
|
||||
}
|
||||
|
||||
function convertToNavLink(item: NavInfoItem, index: number) {
|
||||
return <LinkContainer key={"nav-link-"+index} to={item.dest}><Nav.Link className="nav-icon" title={item.tooltip}><item.icon/><span>{item.displayText}</span></Nav.Link></LinkContainer>;
|
||||
}
|
||||
function convertToNavDropdownItem(item: NavInfoItem | null, index: number) {
|
||||
if (item == null)
|
||||
return <NavDropdown.Divider key={"nav-dropdown-divider-"+index}/>;
|
||||
|
||||
return (
|
||||
<LinkContainer to={item.dest} key={"nav-dropdown-item-"+index}>
|
||||
<NavDropdown.Item className="nav-icon" title={item.tooltip}>
|
||||
<item.icon/>
|
||||
<span>{item.displayText}</span>
|
||||
</NavDropdown.Item>
|
||||
</LinkContainer>
|
||||
);
|
||||
}
|
||||
function convertToNavDropdown(sublist: NavInfoSublist, index: number) {
|
||||
var myTitle = <>
|
||||
<span className="nav-icon">
|
||||
<sublist.icon/>
|
||||
<span>{sublist.displayText}</span>
|
||||
</span>
|
||||
</>;
|
||||
|
||||
return (
|
||||
<NavDropdown title={myTitle} id="basic-nav-dropdown" key={"nav-dropdown-"+index}>
|
||||
{sublist.sublist.map((item, i) => convertToNavDropdownItem(item, i))}
|
||||
</NavDropdown>
|
||||
);
|
||||
}
|
||||
import StirlingLogo from "../assets/favicon.svg";
|
||||
import NavBarStyles from "./NavBar.module.css";
|
||||
|
||||
function NavBar() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const navInfo = [
|
||||
{displayText: t('multiTool.title'), icon: BsTools, dest: "/home", tooltip: t('home.multiTool.desc')},
|
||||
{displayText: t('navbar.pageOps'), icon: BsFileEarmarkPdf, sublist: [
|
||||
{ displayText: t('home.merge.title'), icon: AiOutlineMergeCells, dest: "/dashboard", tooltip: t('home.merge.desc') },
|
||||
{ displayText: t('home.split.title'), icon: AiOutlineSplitCells, dest: "/about", tooltip: t('home.split.desc') },
|
||||
{ displayText: t('home.pdfOrganiser.title'), icon: BsSortNumericDown, dest: "/nothing-here", tooltip: t('home.pdfOrganiser.desc') },
|
||||
{ displayText: t('home.rotate.title'), icon: BsArrowClockwise, dest: "/nothing-here", tooltip: t('home.rotate.desc') },
|
||||
{ displayText: t('home.removePages.title'), icon: BsFileEarmarkX, dest: "/nothing-here", tooltip: t('home.removePages.desc') },
|
||||
{ displayText: t('home.pageLayout.title'), icon: BsGrid, dest: "/nothing-here", tooltip: t('home.pageLayout.desc') },
|
||||
{ displayText: t('home.scalePages.title'), icon: BsArrowsFullscreen, dest: "/nothing-here", tooltip: t('home.scalePages.desc') },
|
||||
{ displayText: t('home.autoSplitPDF.title'), icon: BsLayoutSplit, dest: "/nothing-here", tooltip: t('home.autoSplitPDF.desc') },
|
||||
{ displayText: t('home.adjust-contrast.title'), icon: BsPalette, dest: "/nothing-here", tooltip: t('home.adjust-contrast.desc') },
|
||||
{ displayText: t('home.crop.title'), icon: BiCrop, dest: "/nothing-here", tooltip: t('home.crop.desc') },
|
||||
{ displayText: t('home.extractPage.title'), icon: BsArrowUpSquare, dest: "/nothing-here", tooltip: t('home.extractPage.desc') },
|
||||
{ displayText: t('home.PdfToSinglePage.title'), icon: Bs1Square, dest: "/nothing-here", tooltip: t('home.PdfToSinglePage.desc') },
|
||||
]},
|
||||
{displayText: t('navbar.convert'), icon: BsArrowLeftRight, sublist: [
|
||||
{ displayText: t('home.imageToPdf.title'), icon: BsFileEarmarkImage, dest: "/dashboard", tooltip: t('home.imageToPdf.desc') },
|
||||
{ displayText: t('home.fileToPDF.title'), icon: BsFileEarmark, dest: "/to-pdf", tooltip: t('home.fileToPDF.desc') },
|
||||
{ displayText: t('home.HTMLToPDF.title'), icon: BsFiletypeHtml, dest: "/nothing-here", tooltip: t('home.HTMLToPDF.desc') },
|
||||
{ displayText: t('home.URLToPDF.title'), icon: BsLink, dest: "/nothing-here", tooltip: t('home.URLToPDF.desc') },
|
||||
{ displayText: t('home.MarkdownToPDF.title'), icon: BsFiletypeMd, dest: "/nothing-here", tooltip: t('home.MarkdownToPDF.desc') },
|
||||
null,
|
||||
{ displayText: t('home.pdfToImage.title'), icon: BsFileEarmarkImage, dest: "/nothing-here", tooltip: t('home.pdfToImage.desc') },
|
||||
{ displayText: t('home.PDFToWord.title'), icon: BsFileEarmarkWord, dest: "/nothing-here", tooltip: t('home.PDFToWord.desc') },
|
||||
{ displayText: t('home.PDFToPresentation.title'), icon: BsFiletypePpt, dest: "/nothing-here", tooltip: t('home.PDFToPresentation.desc') },
|
||||
{ displayText: t('home.PDFToText.title'), icon: BsFiletypeTxt, dest: "/nothing-here", tooltip: t('home.PDFToText.desc') },
|
||||
{ displayText: t('home.PDFToHTML.title'), icon: BsFiletypeHtml, dest: "/nothing-here", tooltip: t('home.PDFToHTML.desc') },
|
||||
{ displayText: t('home.PDFToXML.title'), icon: BsFiletypeXml, dest: "/nothing-here", tooltip: t('home.PDFToXML.desc') },
|
||||
{ displayText: t('home.pdfToPDFA.title'), icon: BsFileEarmarkPdf, dest: "/nothing-here", tooltip: t('home.pdfToPDFA.desc') },
|
||||
]},
|
||||
{displayText: t('navbar.security'), icon: BsShieldCheck, sublist: [
|
||||
{ displayText: t('home.addPassword.title'), icon: BsLock, dest: "/dashboard", tooltip: t('home.addPassword.desc') },
|
||||
{ displayText: t('home.removePassword.title'), icon: BsUnlock, dest: "/nothing-here", tooltip: t('home.removePassword.desc') },
|
||||
{ displayText: t('home.permissions.title'), icon: BsShieldLock, dest: "/nothing-here", tooltip: t('home.permissions.desc') },
|
||||
{ displayText: t('home.watermark.title'), icon: BsDroplet, dest: "/nothing-here", tooltip: t('home.watermark.desc') },
|
||||
{ displayText: t('home.certSign.title'), icon: BsAward, dest: "/nothing-here", tooltip: t('home.certSign.desc') },
|
||||
{ displayText: t('home.sanitizePdf.title'), icon: BiSprayCan, dest: "/nothing-here", tooltip: t('home.sanitizePdf.desc') },
|
||||
{ displayText: t('home.autoRedact.title'), icon: BsEraserFill, dest: "/nothing-here", tooltip: t('home.autoRedact.desc') },
|
||||
]},
|
||||
{displayText: t('navbar.other'), icon: BsCardList, sublist: [
|
||||
{ displayText: t('home.ocr.title'), icon: BsSearch, dest: "/dashboard", tooltip: t('home.ocr.desc') },
|
||||
{ displayText: t('home.addImage.title'), icon: BsFileEarmarkRichtext, dest: "/nothing-here", tooltip: t('home.addImage.desc') },
|
||||
{ displayText: t('home.compressPdfs.title'), icon: BsFileZip, dest: "/nothing-here", tooltip: t('home.compressPdfs.desc') },
|
||||
{ displayText: t('home.extractImages.title'), icon: BsImages, dest: "/nothing-here", tooltip: t('home.extractImages.desc') },
|
||||
{ displayText: t('home.changeMetadata.title'), icon: BsClipboardData, dest: "/nothing-here", tooltip: t('home.changeMetadata.desc') },
|
||||
{ displayText: t('home.ScannerImageSplit.title'), icon: MdOutlineScanner, dest: "/nothing-here", tooltip: t('home.ScannerImageSplit.desc') },
|
||||
{ displayText: t('home.sign.title'), icon: BsVectorPen, dest: "/nothing-here", tooltip: t('home.sign.desc') },
|
||||
{ displayText: t('home.flatten.title'), icon: BsArrowsCollapse, dest: "/nothing-here", tooltip: t('home.flatten.desc') },
|
||||
{ displayText: t('home.repair.title'), icon: BsWrench, dest: "/nothing-here", tooltip: t('home.repair.desc') },
|
||||
{ displayText: t('home.removeBlanks.title'), icon: BsFile, dest: "/nothing-here", tooltip: t('home.removeBlanks.desc') },
|
||||
{ displayText: t('home.compare.title'), icon: MdOutlineBalance, dest: "/nothing-here", tooltip: t('home.compare.desc') },
|
||||
{ displayText: t('home.add-page-numbers.title'), icon: Bs123, dest: "/nothing-here", tooltip: t('home.add-page-numbers.desc') },
|
||||
{ displayText: t('home.auto-rename.title'), icon: BsFonts, dest: "/nothing-here", tooltip: t('home.auto-rename.desc') },
|
||||
{ displayText: t('home.getPdfInfo.title'), icon: BsInfoCircle, dest: "/nothing-here", tooltip: t('home.getPdfInfo.desc') },
|
||||
{ displayText: t('home.showJS.title'), icon: BsFiletypeJs, dest: "/nothing-here", tooltip: t('home.showJS.desc') },
|
||||
]},
|
||||
] as Array<NavInfoItem | NavInfoSublist>;
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<Navbar expand="lg" className="bg-light">
|
||||
<Container>
|
||||
<LinkContainer to="/home">
|
||||
<Navbar.Brand className="nav-icon">
|
||||
<img src={Logo} alt="Image" className="main-icon" />
|
||||
<span className="icon-text">Stirling PDF</span>
|
||||
</Navbar.Brand>
|
||||
</LinkContainer>
|
||||
<Navbar.Toggle aria-controls="basic-navbar-nav"/>
|
||||
<Navbar.Collapse id="basic-navbar-nav">
|
||||
|
||||
<Nav>
|
||||
{navInfo.map((ni, idx) => {
|
||||
var element;
|
||||
if ('dest' in ni) {
|
||||
element = convertToNavLink(ni, idx);
|
||||
} else {
|
||||
element = convertToNavDropdown(ni, idx);
|
||||
}
|
||||
const out: JSX.Element[] = [];
|
||||
if (idx >= 1 ) {
|
||||
out.push( <div className="nav-item nav-item-separator" key={"nav-item-separator-"+idx}></div> );
|
||||
}
|
||||
out.push(element);
|
||||
return out;
|
||||
})}
|
||||
</Nav>
|
||||
|
||||
<div className="flex-fill-remaining-space"></div>
|
||||
|
||||
<Nav>
|
||||
<LanguagePicker />
|
||||
</Nav>
|
||||
|
||||
</Navbar.Collapse>
|
||||
</Container>
|
||||
</Navbar>
|
||||
);
|
||||
return (
|
||||
<nav>
|
||||
<a className={NavBarStyles.navbar_brand} href="/">
|
||||
<img className={NavBarStyles.main_icon} src={StirlingLogo} alt="icon"/>
|
||||
<span className={NavBarStyles.icon_text}>Stirling PDF</span>
|
||||
</a>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
||||
export default NavBar;
|
||||
22
client-tauri/src/components/OperatorCard.module.css
Normal file
22
client-tauri/src/components/OperatorCard.module.css
Normal file
@@ -0,0 +1,22 @@
|
||||
.operator_card {
|
||||
border: 1px solid var(--md-sys-color-surface-5);
|
||||
border-radius: 1.75rem;
|
||||
padding: 1.25rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
background: var(--md-sys-color-surface-5);
|
||||
transition: transform 0.3s, border 0.3s;
|
||||
transform-origin: center center;
|
||||
outline: 0px solid transparent;
|
||||
}
|
||||
|
||||
.operator_card h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.operator_card:hover {
|
||||
cursor: pointer;
|
||||
transform: scale(1.08);
|
||||
box-shadow: var(--md-sys-elevation-2);
|
||||
}
|
||||
34
client-tauri/src/components/OperatorCard.tsx
Normal file
34
client-tauri/src/components/OperatorCard.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import { useEffect, 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;
|
||||
}
|
||||
|
||||
export function OperatorCard({ operatorInternalName }: OperatorCardProps) {
|
||||
const [schema, setSchema] = useState<any>(undefined); // TODO: Type as joi type
|
||||
const [materialSymbolName, setMaterialSymbolName] = useState<MaterialSymbolProps["icon"]>("error");
|
||||
|
||||
useEffect(() => {
|
||||
getSchemaByName(operatorInternalName).then(schema => {
|
||||
if(schema) {
|
||||
setSchema(schema.schema);
|
||||
setMaterialSymbolName(schema.materialSymbolName || "error");
|
||||
}
|
||||
});
|
||||
}, [operatorInternalName]);
|
||||
|
||||
return (
|
||||
<a key={operatorInternalName} href={"/operators/" + operatorInternalName}>
|
||||
<div className={styles.operator_card}>
|
||||
<h3><MaterialSymbol icon={materialSymbolName} size={30} fill grade={-25} color='black'></MaterialSymbol> { schema?.describe().flags.label }</h3>
|
||||
{ schema?.describe().flags.description }
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
143
client-tauri/src/components/fields/GenericField.tsx
Normal file
143
client-tauri/src/components/fields/GenericField.tsx
Normal file
@@ -0,0 +1,143 @@
|
||||
import Joi from "@stirling-tools/joi";
|
||||
import { Fragment } from "react";
|
||||
|
||||
interface GenericFieldProps {
|
||||
fieldName: string,
|
||||
joiDefinition: Joi.Description
|
||||
}
|
||||
|
||||
interface Flags {
|
||||
label: string,
|
||||
description: string,
|
||||
}
|
||||
|
||||
export function GenericField({ fieldName, joiDefinition }: GenericFieldProps) {
|
||||
const flags = joiDefinition.flags as Flags;
|
||||
|
||||
switch (joiDefinition.type) {
|
||||
case "number":
|
||||
var validValues = joiDefinition.allow;
|
||||
if(validValues) { // Restrained number input
|
||||
return (
|
||||
<Fragment>
|
||||
<label htmlFor={fieldName}>{flags.label}:</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 { // Unrestrained number input
|
||||
// TODO: Check if integer or not.
|
||||
return (
|
||||
<Fragment>
|
||||
<label htmlFor={fieldName}>{flags.label}:</label>
|
||||
<input type="number" list={fieldName} name={fieldName}/>
|
||||
<br/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
break;
|
||||
case "string":
|
||||
if(joiDefinition.allow) { // Restrained text input
|
||||
return (
|
||||
<Fragment>
|
||||
<label htmlFor={fieldName}>{flags.label}:</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 {
|
||||
return (
|
||||
<Fragment>
|
||||
<label htmlFor={fieldName}>{flags.label}:</label>
|
||||
<input type="text" list={fieldName} name={fieldName}/>
|
||||
<br/>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
break;
|
||||
case "comma_array":
|
||||
if(joiDefinition.items.length == 1) {
|
||||
const item: Joi.Description = joiDefinition.items[0];
|
||||
|
||||
if(item.type == "number") {
|
||||
const props: any = {};
|
||||
|
||||
item.rules.forEach((rule: { args: any, name: string}) => {
|
||||
|
||||
switch (rule.name) {
|
||||
case "integer":
|
||||
if(props.pattern) {
|
||||
return (<div>props.pattern was already set, this is not implemented.</div>);
|
||||
}
|
||||
props.pattern = `(\\d+)(,\\s*\\d+)*`;
|
||||
break;
|
||||
case "min":
|
||||
// TODO: Could validate this in frontend first.
|
||||
break;
|
||||
case "max":
|
||||
// TODO: Could validate this in frontend first.
|
||||
break;
|
||||
default:
|
||||
return (<div>comma_array, item rule {rule.name} is not implemented.</div>);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<label htmlFor={fieldName}>{flags.label}:</label>
|
||||
<input type="text" pattern={props.pattern} list={fieldName} name={fieldName}/>
|
||||
<br/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
else {
|
||||
return (<div>comma_array, other types than numbers are not implemented yet.</div>);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// TODO: Implement multiple items if necessary
|
||||
return (<div>comma_array, joi items are empty or bigger than one, this is not implemented</div>);
|
||||
}
|
||||
break;
|
||||
case "alternatives": // TODO: Better support this. It is currently used by ScaleContent (working) and SplitByPreset (incompatible, but with that operator it isn't even considered a field so we need a different schema for that)
|
||||
return (
|
||||
<Fragment>
|
||||
<label htmlFor={fieldName}>{flags.label}:</label>
|
||||
<input type="text" list={fieldName} name={fieldName}/>
|
||||
<br/>
|
||||
</Fragment>
|
||||
);
|
||||
case "boolean":
|
||||
return (
|
||||
<Fragment>
|
||||
<label htmlFor={fieldName}>{flags.label}:</label>
|
||||
<input type="checkbox" list={fieldName} name={fieldName}/>
|
||||
<br/>
|
||||
</Fragment>
|
||||
);
|
||||
case "date":
|
||||
return (
|
||||
<Fragment>
|
||||
<label htmlFor={fieldName}>{flags.label}:</label>
|
||||
<input type="date" list={fieldName} name={fieldName}/>
|
||||
<br/>
|
||||
</Fragment>
|
||||
);
|
||||
default:
|
||||
console.log(joiDefinition);
|
||||
return (<div>GenericField.tsx: <br/> "{fieldName}": requested type "{joiDefinition.type}" not found. Check console for further info.</div>)
|
||||
|
||||
}
|
||||
}
|
||||
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);
|
||||
@@ -1,29 +1,30 @@
|
||||
|
||||
import NavDropdown from 'react-bootstrap/NavDropdown';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { BsGlobe2 } from 'react-icons/bs';
|
||||
// import NavDropdown from "react-bootstrap/NavDropdown";
|
||||
// import { useTranslation } from "react-i18next";
|
||||
// import { BsGlobe2 } from "react-icons/bs";
|
||||
|
||||
function generateSublist() {
|
||||
const { i18n } = useTranslation();
|
||||
const out: JSX.Element[] = [];
|
||||
const languages = i18n.options.resources;
|
||||
for (var key in languages) {
|
||||
const lang: any = languages[key].translation;
|
||||
const staticKey = key;
|
||||
out.push((
|
||||
<NavDropdown.Item key={"language-"+key} className="nav-icon" onClick={()=>i18n.changeLanguage(staticKey)}>
|
||||
<span>{lang.language?.flag}</span>
|
||||
<span>{lang.language?.name}</span>
|
||||
</NavDropdown.Item>
|
||||
));
|
||||
}
|
||||
return <>{out}</>;
|
||||
}
|
||||
// function generateSublist() {
|
||||
// const { i18n } = useTranslation();
|
||||
// const out: JSX.Element[] = [];
|
||||
// const languages = i18n.options.resources;
|
||||
|
||||
// for (const key in languages) {
|
||||
// const lang: any = languages[key].translation;
|
||||
// const staticKey = key;
|
||||
// out.push((
|
||||
// <NavDropdown.Item key={"language-"+key} className="nav-icon" onClick={()=>i18n.changeLanguage(staticKey)}>
|
||||
// <span>{lang.language?.flag}</span>
|
||||
// <span>{lang.language?.name}</span>
|
||||
// </NavDropdown.Item>
|
||||
// ));
|
||||
// }
|
||||
// return <>{out}</>;
|
||||
// }
|
||||
|
||||
export default function LanguagePicker() {
|
||||
return (
|
||||
<NavDropdown id="languages-dropdown" title={<><span className="nav-icon"><BsGlobe2/></span></>}>
|
||||
{generateSublist()}
|
||||
</NavDropdown>
|
||||
);
|
||||
}
|
||||
// export default function LanguagePicker() {
|
||||
// return (
|
||||
// <NavDropdown id="languages-dropdown" title={<><span className="nav-icon"><BsGlobe2/></span></>}>
|
||||
// {generateSublist()}
|
||||
// </NavDropdown>
|
||||
// );
|
||||
// }
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
|
||||
declare module '@stirling-pdf/shared-operations/wasm/pdfcpu/pdfcpu-wrapper-browser.js' {
|
||||
export async function oneToOne(wasmArray: any, snapshot: any): Promise<Uint8Array>;
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
|
||||
div[lang-direction=ltr] * {
|
||||
direction: ltr;
|
||||
}
|
||||
div[lang-direction=rtl] * {
|
||||
direction: rtl;
|
||||
text-align: right;
|
||||
}
|
||||
.ignore-rtl {
|
||||
direction: ltr !important;
|
||||
text-align: left !important;
|
||||
}
|
||||
|
||||
.flex-fill-remaining-space {
|
||||
flex-grow: 10000000;
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
/*:root {
|
||||
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
font-weight: 400;
|
||||
|
||||
color: #0f0f0f;
|
||||
background-color: #f6f6f6;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
.container {
|
||||
margin: 0;
|
||||
padding-top: 10vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 6em;
|
||||
padding: 1.5em;
|
||||
will-change: filter;
|
||||
transition: 0.75s;
|
||||
}
|
||||
|
||||
.logo.tauri:hover {
|
||||
filter: drop-shadow(0 0 2em #24c8db);
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
input,
|
||||
button {
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.6em 1.2em;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
color: #0f0f0f;
|
||||
background-color: #ffffff;
|
||||
transition: border-color 0.25s;
|
||||
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
button {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
border-color: #396cd8;
|
||||
}
|
||||
button:active {
|
||||
border-color: #396cd8;
|
||||
background-color: #e8e8e8;
|
||||
}
|
||||
|
||||
input,
|
||||
button {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#greet-input {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
color: #f6f6f6;
|
||||
background-color: #2f2f2f;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #24c8db;
|
||||
}
|
||||
|
||||
input,
|
||||
button {
|
||||
color: #ffffff;
|
||||
background-color: #0f0f0f98;
|
||||
}
|
||||
button:active {
|
||||
background-color: #0f0f0f69;
|
||||
}
|
||||
}
|
||||
*/
|
||||
@@ -1,853 +0,0 @@
|
||||
{
|
||||
"translation": {
|
||||
"language": {
|
||||
"name":"العربية",
|
||||
"flag": "🇸🇦",
|
||||
"direction": "rtl"
|
||||
},
|
||||
"pdfPrompt": "اختر PDF",
|
||||
"multiPdfPrompt": "اختر ملفات PDF (2+)",
|
||||
"multiPdfDropPrompt": "حدد (أو اسحب وأفلت) جميع ملفات PDF التي تحتاجها",
|
||||
"imgPrompt": "اختر صورة",
|
||||
"genericSubmit": "إرسال",
|
||||
"processTimeWarning": "تحذير: يمكن أن تستغرق هذه العملية ما يصل إلى دقيقة حسب حجم الملف",
|
||||
"pageOrderPrompt": "ترتيب الصفحات (أدخل قائمة بأرقام الصفحات مفصولة بفواصل):",
|
||||
"goToPage": "اذهب",
|
||||
"true": "\u0635\u062D\u064A\u062D",
|
||||
"false": "\u062E\u0637\u0623",
|
||||
"unknown": "\u063A\u064A\u0631 \u0645\u0639\u0631\u0648\u0641",
|
||||
"save": "\u062D\u0641\u0638",
|
||||
"close": "\u0625\u063A\u0644\u0627\u0642",
|
||||
"filesSelected": "الملفات المحددة",
|
||||
"noFavourites": "لم تتم إضافة أي مفضلات",
|
||||
"bored": "الانتظار بالملل؟",
|
||||
"alphabet": "\u0627\u0644\u0623\u0628\u062C\u062F\u064A\u0629",
|
||||
"downloadPdf": "تنزيل PDF",
|
||||
"text": "نص",
|
||||
"font": "الخط",
|
||||
"selectFillter": "- حدد -",
|
||||
"pageNum": "رقم الصفحة",
|
||||
"sizes": {
|
||||
"small": "Small",
|
||||
"medium": "Medium",
|
||||
"large": "Large",
|
||||
"x-large": "X-Large"
|
||||
},
|
||||
"error": {
|
||||
"pdfPassword": "The PDF Document is passworded and either the password was not provided or was incorrect"
|
||||
},
|
||||
"delete": "Delete",
|
||||
"username": "Username",
|
||||
"password": "Password",
|
||||
"welcome": "Welcome",
|
||||
"property": "Property",
|
||||
"black": "Black",
|
||||
"white": "White",
|
||||
"red": "Red",
|
||||
"green": "Green",
|
||||
"blue": "Blue",
|
||||
"custom": "Custom...",
|
||||
"changedCredsMessage": "Credentials changed!",
|
||||
"notAuthenticatedMessage": "User not authenticated.",
|
||||
"userNotFoundMessage": "User not found.",
|
||||
"incorrectPasswordMessage": "Current password is incorrect.",
|
||||
"usernameExistsMessage": "New Username already exists.",
|
||||
"navbar": {
|
||||
"convert": "تحويل",
|
||||
"security": "الأمان",
|
||||
"other": "أخرى",
|
||||
"darkmode": "الوضع الداكن",
|
||||
"pageOps": "عمليات الصفحة",
|
||||
"settings": "\u0625\u0639\u062F\u0627\u062F\u0627\u062A"
|
||||
},
|
||||
"settings": {
|
||||
"title": "\u0627\u0644\u0625\u0639\u062F\u0627\u062F\u0627\u062A",
|
||||
"update": "\u0627\u0644\u062A\u062D\u062F\u064A\u062B \u0645\u062A\u0627\u062D",
|
||||
"appVersion": "\u0625\u0635\u062F\u0627\u0631 \u0627\u0644\u062A\u0637\u0628\u064A\u0642:",
|
||||
"downloadOption": {
|
||||
"1": "\u0641\u062A\u062D \u0641\u064A \u0646\u0641\u0633 \u0627\u0644\u0646\u0627\u0641\u0630\u0629",
|
||||
"2": "\u0641\u062A\u062D \u0641\u064A \u0646\u0627\u0641\u0630\u0629 \u062C\u062F\u064A\u062F\u0629",
|
||||
"3": "\u062A\u0646\u0632\u064A\u0644 \u0627\u0644\u0645\u0644\u0641",
|
||||
"title": "\u062A\u062D\u062F\u064A\u062F \u062E\u064A\u0627\u0631 \u0627\u0644\u062A\u0646\u0632\u064A\u0644 (\u0644\u0644\u062A\u0646\u0632\u064A\u0644\u0627\u062A \u0630\u0627\u062A \u0627\u0644\u0645\u0644\u0641 \u0627\u0644\u0648\u0627\u062D\u062F \u063A\u064A\u0631 \u0627\u0644\u0645\u0636\u063A\u0648\u0637):"
|
||||
},
|
||||
"zipThreshold": "\u0645\u0644\u0641\u0627\u062A \u0645\u0636\u063A\u0648\u0637\u0629 \u0639\u0646\u062F \u062A\u062C\u0627\u0648\u0632 \u0639\u062F\u062F \u0627\u0644\u0645\u0644\u0641\u0627\u062A \u0627\u0644\u062A\u064A \u062A\u0645 \u062A\u0646\u0632\u064A\u0644\u0647\u0627",
|
||||
"signOut": "Sign Out",
|
||||
"accountSettings": "Account Settings"
|
||||
},
|
||||
"changeCreds": {
|
||||
"title": "Change Credentials",
|
||||
"header": "Update Your Account Details",
|
||||
"changeUserAndPassword": "You are using default login credentials. Please enter a new password (and username if wanted)",
|
||||
"newUsername": "New Username",
|
||||
"oldPassword": "Current Password",
|
||||
"newPassword": "New Password",
|
||||
"confirmNewPassword": "Confirm New Password",
|
||||
"submit": "Submit Changes"
|
||||
},
|
||||
"account": {
|
||||
"title": "Account Settings",
|
||||
"accountSettings": "Account Settings",
|
||||
"adminSettings": "Admin Settings - View and Add Users",
|
||||
"userControlSettings": "User Control Settings",
|
||||
"changeUsername": "Change Username",
|
||||
"password": "Confirmation Password",
|
||||
"oldPassword": "Old password",
|
||||
"newPassword": "New Password",
|
||||
"changePassword": "Change Password",
|
||||
"confirmNewPassword": "Confirm New Password",
|
||||
"signOut": "Sign Out",
|
||||
"yourApiKey": "Your API Key",
|
||||
"syncTitle": "Sync browser settings with Account",
|
||||
"settingsCompare": "Settings Comparison:",
|
||||
"property": "Property",
|
||||
"webBrowserSettings": "Web Browser Setting",
|
||||
"syncToBrowser": "Sync Account -> Browser",
|
||||
"syncToAccount": "Sync Account <- Browser"
|
||||
},
|
||||
"adminUserSettings": {
|
||||
"title": "User Control Settings",
|
||||
"header": "Admin User Control Settings",
|
||||
"admin": "Admin",
|
||||
"user": "User",
|
||||
"addUser": "Add New User",
|
||||
"roles": "Roles",
|
||||
"role": "Role",
|
||||
"actions": "Actions",
|
||||
"apiUser": "Limited API User",
|
||||
"webOnlyUser": "Web Only User",
|
||||
"forceChange": "Force user to change username/password on login",
|
||||
"submit": "Save User"
|
||||
},
|
||||
"home": {
|
||||
"desc": "متجرك الشامل المستضاف محليًا لجميع احتياجات PDF الخاصة بك.",
|
||||
"searchBar": "Search for features...",
|
||||
"viewPdf": {
|
||||
"title": "View PDF",
|
||||
"desc": "View, annotate, add text or images"
|
||||
},
|
||||
"multiTool": {
|
||||
"title": "أداة متعددة PDF",
|
||||
"desc": "دمج الصفحات وتدويرها وإعادة ترتيبها وإزالتها"
|
||||
},
|
||||
"merge": {
|
||||
"title": "دمج ملفات",
|
||||
"desc": "دمج ملفات PDF متعددة في ملف واحد بسهولة."
|
||||
},
|
||||
"split": {
|
||||
"title": "انقسام ملفات",
|
||||
"desc": "تقسيم ملفات PDF إلى مستندات متعددة"
|
||||
},
|
||||
"rotate": {
|
||||
"title": "تدوير ملفات",
|
||||
"desc": "قم بتدوير ملفات PDF الخاصة بك بسهولة."
|
||||
},
|
||||
"imageToPdf": {
|
||||
"title": "صورة إلى PDF",
|
||||
"desc": "تحويل الصور (PNG ، JPEG ، GIF) إلى PDF."
|
||||
},
|
||||
"pdfToImage": {
|
||||
"title": "تحويل PDF إلى صورة",
|
||||
"desc": "تحويل ملف PDF إلى صورة. (PNG ، JPEG ، GIF)"
|
||||
},
|
||||
"pdfOrganiser": {
|
||||
"title": "منظم",
|
||||
"desc": "إزالة / إعادة ترتيب الصفحات بأي ترتيب"
|
||||
},
|
||||
"addImage": {
|
||||
"title": "إضافة صورة إلى ملف PDF",
|
||||
"desc": "إضافة صورة إلى موقع معين في PDF (العمل قيد التقدم)"
|
||||
},
|
||||
"watermark": {
|
||||
"title": "إضافة علامة مائية",
|
||||
"desc": "أضف علامة مائية مخصصة إلى مستند PDF الخاص بك."
|
||||
},
|
||||
"permissions": {
|
||||
"title": "تغيير الأذونات",
|
||||
"desc": "قم بتغيير أذونات مستند PDF الخاص بك"
|
||||
},
|
||||
"removePages": {
|
||||
"title": "إزالة الصفحات",
|
||||
"desc": "حذف الصفحات غير المرغوب فيها من مستند PDF الخاص بك."
|
||||
},
|
||||
"addPassword": {
|
||||
"title": "إضافة كلمة مرور",
|
||||
"desc": "تشفير مستند PDF الخاص بك بكلمة مرور."
|
||||
},
|
||||
"removePassword": {
|
||||
"title": "إزالة كلمة المرور",
|
||||
"desc": "إزالة الحماية بكلمة مرور من مستند PDF الخاص بك."
|
||||
},
|
||||
"compressPdfs": {
|
||||
"title": "ضغط ملفات",
|
||||
"desc": "ضغط ملفات PDF لتقليل حجم الملف."
|
||||
},
|
||||
"changeMetadata": {
|
||||
"title": "\u062A\u063A\u064A\u064A\u0631 \u0627\u0644\u0628\u064A\u0627\u0646\u0627\u062A \u0627\u0644\u0648\u0635\u0641\u064A\u0629",
|
||||
"desc": "\u062A\u063A\u064A\u064A\u0631 / \u0625\u0632\u0627\u0644\u0629 / \u0625\u0636\u0627\u0641\u0629 \u0628\u064A\u0627\u0646\u0627\u062A \u0623\u0648\u0644\u064A\u0629 \u0645\u0646 \u0645\u0633\u062A\u0646\u062F PDF"
|
||||
},
|
||||
"fileToPDF": {
|
||||
"title": "\u062A\u062D\u0648\u064A\u0644 \u0627\u0644\u0645\u0644\u0641 \u0625\u0644\u0649 PDF",
|
||||
"desc": "\u062A\u062D\u0648\u064A\u0644 \u0623\u064A \u0645\u0644\u0641 \u062A\u0642\u0631\u064A\u0628\u0627 \u0625\u0644\u0649 PDF (DOCX \u0648PNG \u0648XLS \u0648PPT \u0648TXT \u0648\u0627\u0644\u0645\u0632\u064A\u062F)"
|
||||
},
|
||||
"ocr": {
|
||||
"title": "\u062A\u0634\u063A\u064A\u0644 OCR \u0639\u0644\u0649 PDF \u0648 / \u0623\u0648 \u0645\u0633\u062D \u0636\u0648\u0626\u064A",
|
||||
"desc": "\u064A\u0642\u0648\u0645 \u0628\u0631\u0646\u0627\u0645\u062C \u0627\u0644\u062A\u0646\u0638\u064A\u0641 \u0628\u0645\u0633\u062D \u0648\u0627\u0643\u062A\u0634\u0627\u0641 \u0627\u0644\u0646\u0635 \u0645\u0646 \u0627\u0644\u0635\u0648\u0631 \u062F\u0627\u062E\u0644 \u0645\u0644\u0641 PDF \u0648\u064A\u0639\u064A\u062F \u0625\u0636\u0627\u0641\u062A\u0647 \u0643\u0646\u0635"
|
||||
},
|
||||
"extractImages": {
|
||||
"title": "\u0627\u0633\u062A\u062E\u0631\u0627\u062C \u0627\u0644\u0635\u0648\u0631",
|
||||
"desc": "\u064A\u0633\u062A\u062E\u0631\u062C \u062C\u0645\u064A\u0639 \u0627\u0644\u0635\u0648\u0631 \u0645\u0646 \u0645\u0644\u0641 PDF \u0648\u064A\u062D\u0641\u0638\u0647\u0627 \u0641\u064A \u0627\u0644\u0631\u0645\u0632 \u0627\u0644\u0628\u0631\u064A\u062F\u064A"
|
||||
},
|
||||
"pdfToPDFA": {
|
||||
"title": "\u062A\u062D\u0648\u064A\u0644 \u0645\u0644\u0641\u0627\u062A PDF \u0625\u0644\u0649 PDF / A",
|
||||
"desc": "\u062A\u062D\u0648\u064A\u0644 PDF \u0625\u0644\u0649 PDF / A \u0644\u0644\u062A\u062E\u0632\u064A\u0646 \u0637\u0648\u064A\u0644 \u0627\u0644\u0645\u062F\u0649"
|
||||
},
|
||||
"PDFToWord": {
|
||||
"title": "تحويل PDF إلى Word",
|
||||
"desc": "تحويل PDF إلى تنسيقات Word (DOC و DOCX و ODT)"
|
||||
},
|
||||
"PDFToPresentation": {
|
||||
"title": "PDF للعرض التقديمي",
|
||||
"desc": "تحويل PDF إلى تنسيقات عرض تقديمي (PPT و PPTX و ODP)"
|
||||
},
|
||||
"PDFToText": {
|
||||
"title": "تحويل PDF إلى نص / RTF",
|
||||
"desc": "تحويل PDF إلى تنسيق نص أو RTF"
|
||||
},
|
||||
"PDFToHTML": {
|
||||
"title": "تحويل PDF إلى HTML",
|
||||
"desc": "تحويل PDF إلى تنسيق HTML"
|
||||
},
|
||||
"PDFToXML": {
|
||||
"title": "تحويل PDF إلى XML",
|
||||
"desc": "تحويل PDF إلى تنسيق XML"
|
||||
},
|
||||
"ScannerImageSplit": {
|
||||
"title": "كشف / انقسام الصور الممسوحة ضوئيًا",
|
||||
"desc": "تقسيم عدة صور من داخل صورة / ملف PDF"
|
||||
},
|
||||
"sign": {
|
||||
"title": "تسجيل الدخول",
|
||||
"desc": "إضافة التوقيع إلى PDF عن طريق الرسم أو النص أو الصورة"
|
||||
},
|
||||
"flatten": {
|
||||
"title": "تسطيح",
|
||||
"desc": "قم بإزالة كافة العناصر والنماذج التفاعلية من ملف PDF"
|
||||
},
|
||||
"repair": {
|
||||
"title": "إصلاح",
|
||||
"desc": "يحاول إصلاح ملف PDF تالف / معطل"
|
||||
},
|
||||
"removeBlanks": {
|
||||
"title": "إزالة الصفحات الفارغة",
|
||||
"desc": "يكتشف ويزيل الصفحات الفارغة من المستند"
|
||||
},
|
||||
"compare": {
|
||||
"title": "قارن",
|
||||
"desc": "يقارن ويظهر الاختلافات بين 2 من مستندات PDF"
|
||||
},
|
||||
"certSign": {
|
||||
"title": "Sign with Certificate",
|
||||
"desc": "Signs a PDF with a Certificate/Key (PEM/P12)"
|
||||
},
|
||||
"pageLayout": {
|
||||
"title": "Multi-Page Layout",
|
||||
"desc": "Merge multiple pages of a PDF document into a single page"
|
||||
},
|
||||
"scalePages": {
|
||||
"title": "Adjust page size/scale",
|
||||
"desc": "Change the size/scale of page and/or its contents."
|
||||
},
|
||||
"pipeline": {
|
||||
"title": "Pipeline (Advanced)",
|
||||
"desc": "Run multiple actions on PDFs by defining pipeline scripts"
|
||||
},
|
||||
"add-page-numbers": {
|
||||
"title": "Add Page Numbers",
|
||||
"desc": "Add Page numbers throughout a document in a set location"
|
||||
},
|
||||
"auto-rename": {
|
||||
"title": "Auto Rename PDF File",
|
||||
"desc": "Auto renames a PDF file based on its detected header"
|
||||
},
|
||||
"adjust-contrast": {
|
||||
"title": "Adjust Colors/Contrast",
|
||||
"desc": "Adjust Contrast, Saturation and Brightness of a PDF"
|
||||
},
|
||||
"crop": {
|
||||
"title": "Crop PDF",
|
||||
"desc": "Crop a PDF to reduce its size (maintains text!)"
|
||||
},
|
||||
"autoSplitPDF": {
|
||||
"title": "Auto Split Pages",
|
||||
"desc": "Auto Split Scanned PDF with physical scanned page splitter QR Code"
|
||||
},
|
||||
"sanitizePdf": {
|
||||
"title": "Sanitize",
|
||||
"desc": "Remove scripts and other elements from PDF files"
|
||||
},
|
||||
"URLToPDF": {
|
||||
"title": "URL/Website To PDF",
|
||||
"desc": "Converts any http(s)URL to PDF"
|
||||
},
|
||||
"HTMLToPDF": {
|
||||
"title": "HTML to PDF",
|
||||
"desc": "Converts any HTML file or zip to PDF"
|
||||
},
|
||||
"MarkdownToPDF": {
|
||||
"title": "Markdown to PDF",
|
||||
"desc": "Converts any Markdown file to PDF"
|
||||
},
|
||||
"getPdfInfo": {
|
||||
"title": "Get ALL Info on PDF",
|
||||
"desc": "Grabs any and all information possible on PDFs"
|
||||
},
|
||||
"extractPage": {
|
||||
"title": "Extract page(s)",
|
||||
"desc": "Extracts select pages from PDF"
|
||||
},
|
||||
"PdfToSinglePage": {
|
||||
"title": "PDF to Single Large Page",
|
||||
"desc": "Merges all PDF pages into one large single page"
|
||||
},
|
||||
"showJS": {
|
||||
"title": "Show Javascript",
|
||||
"desc": "Searches and displays any JS injected into a PDF"
|
||||
},
|
||||
"autoRedact": {
|
||||
"title": "Auto Redact",
|
||||
"desc": "Auto Redacts(Blacks out) text in a PDF based on input text"
|
||||
}
|
||||
},
|
||||
"viewPdf": {
|
||||
"tags": "view,read,annotate,text,image",
|
||||
"title": "View PDF",
|
||||
"header": "View PDF"
|
||||
},
|
||||
"multiTool": {
|
||||
"tags": "Multi Tool,Multi operation,UI,click drag,front end,client side",
|
||||
"title": "أداة متعددة PDF",
|
||||
"header": "أداة متعددة PDF"
|
||||
},
|
||||
"merge": {
|
||||
"tags": "merge,Page operations,Back end,server side",
|
||||
"title": "دمج",
|
||||
"header": "دمج ملفات PDF متعددة (2+)",
|
||||
"sortByName": "Sort by name",
|
||||
"sortByDate": "Sort by date",
|
||||
"submit": "دمج"
|
||||
},
|
||||
"split": {
|
||||
"tags": "Page operations,divide,Multi Page,cut,server side",
|
||||
"title": "انقسام PDF",
|
||||
"header": "تقسيم PDF",
|
||||
"desc": {
|
||||
"1": "الأرقام التي تحددها هي رقم الصفحة التي تريد تقسيمها",
|
||||
"2": "على هذا النحو ، سيؤدي تحديد 1،3،7-8 إلى تقسيم مستند من 10 صفحات إلى 6 PDFS منفصلة مع:",
|
||||
"3": "المستند رقم 1: الصفحة 1",
|
||||
"4": "المستند رقم 2: الصفحتان 2 و 3",
|
||||
"5": "المستند رقم 3: الصفحة 4 و 5 و 6",
|
||||
"6": "المستند رقم 4: الصفحة 7",
|
||||
"7": "المستند رقم 5: الصفحة 8",
|
||||
"8": "المستند رقم 6: الصفحتان 9 و 10"
|
||||
},
|
||||
"splitPages": "أدخل الصفحات المراد تقسيمها:",
|
||||
"submit": "Split"
|
||||
},
|
||||
"rotate": {
|
||||
"tags": "server side",
|
||||
"title": "تدوير PDF",
|
||||
"header": "تدوير PDF",
|
||||
"selectAngle": "حدد زاوية الدوران (بمضاعفات 90 درجة):",
|
||||
"submit": "استدارة"
|
||||
},
|
||||
"imageToPdf": {
|
||||
"tags": "conversion,img,jpg,picture,photo"
|
||||
},
|
||||
"pdfToImage": {
|
||||
"tags": "conversion,img,jpg,picture,photo",
|
||||
"title": "تحويل PDF إلى صورة",
|
||||
"header": "تحويل PDF إلى صورة",
|
||||
"selectText": "تنسيق الصورة",
|
||||
"singleOrMultiple": "\u0646\u0648\u0639 \u0646\u062A\u064A\u062C\u0629 \u0627\u0644\u0635\u0648\u0631\u0629",
|
||||
"single": "\u0635\u0648\u0631\u0629 \u0648\u0627\u062D\u062F\u0629 \u0643\u0628\u064A\u0631\u0629",
|
||||
"multi": "\u0635\u0648\u0631 \u0645\u062A\u0639\u062F\u062F\u0629",
|
||||
"colorType": "\u0646\u0648\u0639 \u0627\u0644\u0644\u0648\u0646",
|
||||
"color": "\u0627\u0644\u0644\u0648\u0646",
|
||||
"grey": "\u062A\u062F\u0631\u062C \u0627\u0644\u0631\u0645\u0627\u062F\u064A",
|
||||
"blackwhite": "\u0623\u0628\u064A\u0636 \u0648\u0623\u0633\u0648\u062F (\u0642\u062F \u064A\u0641\u0642\u062F \u0627\u0644\u0628\u064A\u0627\u0646\u0627\u062A!)",
|
||||
"submit": "تحول"
|
||||
},
|
||||
"pdfOrganiser": {
|
||||
"tags": "duplex,even,odd,sort,move",
|
||||
"title": "منظم الصفحة",
|
||||
"header": "منظم صفحات PDF",
|
||||
"submit": "إعادة ترتيب الصفحات"
|
||||
},
|
||||
"addImage": {
|
||||
"tags": "img,jpg,picture,photo",
|
||||
"title": "إضافة صورة",
|
||||
"header": "إضافة صورة إلى PDF",
|
||||
"everyPage": "كل صفحة؟",
|
||||
"upload": "إضافة صورة",
|
||||
"submit": "إضافة صورة"
|
||||
},
|
||||
"watermark": {
|
||||
"tags": "Text,repeating,label,own,copyright,trademark,img,jpg,picture,photo",
|
||||
"title": "إضافة علامة مائية",
|
||||
"header": "إضافة علامة مائية",
|
||||
"selectText": {
|
||||
"1": "حدد PDF لإضافة العلامة المائية إلى:",
|
||||
"2": "نص العلامة المائية:",
|
||||
"3": "حجم الخط:",
|
||||
"4": "دوران (0-360):",
|
||||
"5": "widthSpacer (مسافة بين كل علامة مائية أفقيًا):",
|
||||
"6": "heightSpacer (مسافة بين كل علامة مائية عموديًا):",
|
||||
"7": "\u0627\u0644\u062A\u0639\u062A\u064A\u0645 (0\u066A - 100\u066A):",
|
||||
"8": "Watermark Type:",
|
||||
"9": "Watermark Image:"
|
||||
},
|
||||
"submit": "إضافة علامة مائية"
|
||||
},
|
||||
"permissions": {
|
||||
"tags": "read,write,edit,print",
|
||||
"title": "تغيير الأذونات",
|
||||
"header": "تغيير الأذونات",
|
||||
"warning": "تحذير من أن تكون هذه الأذونات غير قابلة للتغيير ، يوصى بتعيينها بكلمة مرور عبر صفحة إضافة كلمة المرور",
|
||||
"selectText": {
|
||||
"1": "حدد ملف PDF لتغيير الأذونات",
|
||||
"2": "أذونات لتعيينها",
|
||||
"3": "منع تجميع المستند",
|
||||
"4": "منع استخراج المحتوى",
|
||||
"5": "منع الاستخراج للوصول",
|
||||
"6": "منع ملء النموذج",
|
||||
"7": "منع التعديل",
|
||||
"8": "منع تعديل التعليق التوضيحي",
|
||||
"9": "منع الطباعة",
|
||||
"10": "منع طباعة التنسيقات المختلفة"
|
||||
},
|
||||
"submit": "تغيير"
|
||||
},
|
||||
"removePages": {
|
||||
"tags": "Remove pages,delete pages"
|
||||
},
|
||||
"addPassword": {
|
||||
"tags": "secure,security",
|
||||
"title": "إضافة كلمة مرور",
|
||||
"header": "إضافة كلمة مرور (تشفير)",
|
||||
"selectText": {
|
||||
"1": "حدد ملف PDF للتشفير",
|
||||
"2": "كلمة المرور",
|
||||
"3": "طول مفتاح التشفير",
|
||||
"4": "القيم الأعلى تكون أقوى ، لكن القيم الأقل لها توافق أفضل.",
|
||||
"5": "أذونات للتعيين",
|
||||
"6": "منع تجميع المستند",
|
||||
"7": "منع استخراج المحتوى",
|
||||
"8": "منع الاستخراج للوصول",
|
||||
"9": "منع ملء النموذج",
|
||||
"10": "منع التعديل",
|
||||
"11": "منع تعديل التعليقات التوضيحية",
|
||||
"12": "منع الطباعة",
|
||||
"13": "منع طباعة تنسيقات مختلفة",
|
||||
"14": "Owner Password",
|
||||
"15": "Restricts what can be done with the document once it is opened (Not supported by all readers)",
|
||||
"16": "Restricts the opening of the document itself"
|
||||
},
|
||||
"submit": "تشفير"
|
||||
},
|
||||
"removePassword": {
|
||||
"tags": "secure,Decrypt,security,unpassword,delete password",
|
||||
"title": "إزالة كلمة المرور",
|
||||
"header": "إزالة كلمة المرور (فك التشفير)",
|
||||
"selectText": {
|
||||
"1": "حدد PDF لفك التشفير",
|
||||
"2": "كلمة المرور"
|
||||
},
|
||||
"submit": "إزالة"
|
||||
},
|
||||
"compressPdfs": {
|
||||
"tags": "squish,small,tiny"
|
||||
},
|
||||
"fileToPDF": {
|
||||
"tags": "transformation,format,document,picture,slide,text,conversion,office,docs,word,excel,powerpoint",
|
||||
"title": "\u0645\u0644\u0641 \u0625\u0644\u0649 PDF",
|
||||
"header": "\u062A\u062D\u0648\u064A\u0644 \u0623\u064A \u0645\u0644\u0641 \u0625\u0644\u0649 PDF",
|
||||
"credit": "\u062A\u0633\u062A\u062E\u062F\u0645 \u0647\u0630\u0647 \u0627\u0644\u062E\u062F\u0645\u0629 \u0644\u064A\u0628\u0631 \u0623\u0648\u0641\u064A\u0633 \u0648\u0623\u0648\u0646\u0648\u0643\u0648\u0646\u0641 \u0644\u062A\u062D\u0648\u064A\u0644 \u0627\u0644\u0645\u0644\u0641\u0627\u062A.",
|
||||
"supportedFileTypes": "\u064A\u062C\u0628 \u0623\u0646 \u062A\u062A\u0636\u0645\u0646 \u0623\u0646\u0648\u0627\u0639 \u0627\u0644\u0645\u0644\u0641\u0627\u062A \u0627\u0644\u0645\u062F\u0639\u0648\u0645\u0629 \u0645\u0627 \u064A\u0644\u064A \u0648\u0644\u0643\u0646 \u0644\u0644\u062D\u0635\u0648\u0644 \u0639\u0644\u0649 \u0642\u0627\u0626\u0645\u0629 \u0645\u062D\u062F\u062B\u0629 \u0643\u0627\u0645\u0644\u0629 \u0628\u0627\u0644\u062A\u0646\u0633\u064A\u0642\u0627\u062A \u0627\u0644\u0645\u062F\u0639\u0648\u0645\u0629 \u060C \u064A\u0631\u062C\u0649 \u0627\u0644\u0631\u062C\u0648\u0639 \u0625\u0644\u0649 \u0648\u062B\u0627\u0626\u0642 LibreOffice",
|
||||
"submit": "\u062A\u062D\u0648\u064A\u0644 \u0625\u0644\u0649 PDF"
|
||||
},
|
||||
"ocr": {
|
||||
"tags": "recognition,text,image,scan,read,identify,detection,editable",
|
||||
"title": "\u0627\u0644\u062A\u0639\u0631\u0641 \u0627\u0644\u0636\u0648\u0626\u064A \u0639\u0644\u0649 \u0627\u0644\u062D\u0631\u0648\u0641 / \u062A\u0646\u0638\u064A\u0641 \u0627\u0644\u0645\u0633\u062D \u0627\u0644\u0636\u0648\u0626\u064A",
|
||||
"header": "\u0645\u0633\u062D \u0627\u0644\u0645\u0633\u062D \u0627\u0644\u0636\u0648\u0626\u064A / \u0627\u0644\u062A\u0639\u0631\u0641 \u0627\u0644\u0636\u0648\u0626\u064A \u0639\u0644\u0649 \u0627\u0644\u062D\u0631\u0648\u0641 (\u0627\u0644\u062A\u0639\u0631\u0641 \u0627\u0644\u0636\u0648\u0626\u064A \u0639\u0644\u0649 \u0627\u0644\u062D\u0631\u0648\u0641)",
|
||||
"selectText": {
|
||||
"1": "\u062D\u062F\u062F \u0627\u0644\u0644\u063A\u0627\u062A \u0627\u0644\u062A\u064A \u0633\u064A\u062A\u0645 \u0627\u0643\u062A\u0634\u0627\u0641\u0647\u0627 \u062F\u0627\u062E\u0644 \u0645\u0644\u0641 PDF (\u0627\u0644\u0644\u063A\u0627\u062A \u0627\u0644\u0645\u062F\u0631\u062C\u0629 \u0647\u064A \u062A\u0644\u0643 \u0627\u0644\u062A\u064A \u062A\u0645 \u0627\u0643\u062A\u0634\u0627\u0641\u0647\u0627 \u062D\u0627\u0644\u064A\u064B\u0627):",
|
||||
"2": "\u0625\u0646\u062A\u0627\u062C \u0645\u0644\u0641 \u0646\u0635\u064A \u064A\u062D\u062A\u0648\u064A \u0639\u0644\u0649 \u0646\u0635 OCR \u0628\u062C\u0627\u0646\u0628 \u0645\u0644\u0641 PDF \u0627\u0644\u0630\u064A \u062A\u0645 \u0625\u0639\u062F\u0627\u062F\u0647 \u0628\u0648\u0627\u0633\u0637\u0629 OCR",
|
||||
"3": "\u062A\u0645 \u0645\u0633\u062D \u0627\u0644\u0635\u0641\u062D\u0627\u062A \u0627\u0644\u0635\u062D\u064A\u062D\u0629 \u0636\u0648\u0626\u064A\u064B\u0627 \u0628\u0632\u0627\u0648\u064A\u0629 \u0645\u0646\u062D\u0631\u0641\u0629 \u0639\u0646 \u0637\u0631\u064A\u0642 \u062A\u062F\u0648\u064A\u0631\u0647\u0627 \u0645\u0631\u0629 \u0623\u062E\u0631\u0649 \u0641\u064A \u0645\u0643\u0627\u0646\u0647\u0627",
|
||||
"4": "\u0635\u0641\u062D\u0629 \u0646\u0638\u064A\u0641\u0629 \u0644\u0630\u0644\u0643 \u0645\u0646 \u063A\u064A\u0631 \u0627\u0644\u0645\u062D\u062A\u0645\u0644 \u0623\u0646 \u064A\u062C\u062F OCR \u0646\u0635\u064B\u0627 \u0641\u064A \u0636\u0648\u0636\u0627\u0621 \u0627\u0644\u062E\u0644\u0641\u064A\u0629. (\u0644\u0627 \u064A\u0648\u062C\u062F \u062A\u063A\u064A\u064A\u0631 \u0641\u064A \u0627\u0644\u0625\u062E\u0631\u0627\u062C)",
|
||||
"5": "\u0635\u0641\u062D\u0629 \u0646\u0638\u064A\u0641\u0629 \u060C \u0644\u0630\u0644\u0643 \u0645\u0646 \u063A\u064A\u0631 \u0627\u0644\u0645\u062D\u062A\u0645\u0644 \u0623\u0646 \u064A\u062C\u062F OCR \u0646\u0635\u064B\u0627 \u0641\u064A \u0636\u0648\u0636\u0627\u0621 \u0627\u0644\u062E\u0644\u0641\u064A\u0629 \u060C \u0648\u064A\u062D\u0627\u0641\u0638 \u0639\u0644\u0649 \u0627\u0644\u062A\u0646\u0638\u064A\u0641 \u0641\u064A \u0627\u0644\u0625\u062E\u0631\u0627\u062C.",
|
||||
"6": "\u064A\u062A\u062C\u0627\u0647\u0644 \u0627\u0644\u0635\u0641\u062D\u0627\u062A \u0627\u0644\u062A\u064A \u062A\u062D\u062A\u0648\u064A \u0639\u0644\u0649 \u0646\u0635 \u062A\u0641\u0627\u0639\u0644\u064A \u060C \u0641\u0642\u0637 \u0635\u0641\u062D\u0627\u062A OCRs \u0627\u0644\u062A\u064A \u0647\u064A \u0635\u0648\u0631",
|
||||
"7": "\u0641\u0631\u0636 \u0627\u0644\u062A\u0639\u0631\u0641 \u0627\u0644\u0636\u0648\u0626\u064A \u0639\u0644\u0649 \u0627\u0644\u062D\u0631\u0648\u0641 \u060C \u0633\u064A\u0624\u062F\u064A \u0627\u0644\u062A\u0639\u0631\u0641 \u0627\u0644\u0636\u0648\u0626\u064A \u0639\u0644\u0649 \u0627\u0644\u062D\u0631\u0648\u0641 \u0639\u0644\u0649 \u0643\u0644 \u0635\u0641\u062D\u0629 \u0625\u0644\u0649 \u0625\u0632\u0627\u0644\u0629 \u062C\u0645\u064A\u0639 \u0639\u0646\u0627\u0635\u0631 \u0627\u0644\u0646\u0635 \u0627\u0644\u0623\u0635\u0644\u064A",
|
||||
"8": "\u0639\u0627\u062F\u064A (\u062E\u0637\u0623 \u0625\u0630\u0627 \u0643\u0627\u0646 PDF \u064A\u062D\u062A\u0648\u064A \u0639\u0644\u0649 \u0646\u0635)",
|
||||
"9": "\u0625\u0639\u062F\u0627\u062F\u0627\u062A \u0625\u0636\u0627\u0641\u064A\u0629",
|
||||
"10": "\u0648\u0636\u0639 \u0627\u0644\u062A\u0639\u0631\u0641 \u0627\u0644\u0636\u0648\u0626\u064A \u0639\u0644\u0649 \u0627\u0644\u062D\u0631\u0648\u0641",
|
||||
"11": "إزالة الصور بعد التعرف الضوئي على الحروف (يزيل كل الصور ، يكون مفيدًا فقط إذا كان جزءًا من خطوة التحويل)",
|
||||
"12": "نوع العرض (متقدم)"
|
||||
},
|
||||
"help": "\u064A\u0631\u062C\u0649 \u0642\u0631\u0627\u0621\u0629 \u0647\u0630\u0647 \u0627\u0644\u0648\u062B\u0627\u0626\u0642 \u062D\u0648\u0644 \u0643\u064A\u0641\u064A\u0629 \u0627\u0633\u062A\u062E\u062F\u0627\u0645 \u0647\u0630\u0627 \u0644\u0644\u063A\u0627\u062A \u0623\u062E\u0631\u0649 \u0648 / \u0623\u0648 \u0627\u0644\u0627\u0633\u062A\u062E\u062F\u0627\u0645 \u0644\u064A\u0633 \u0641\u064A \u0639\u0627\u0645\u0644 \u0627\u0644\u0625\u0631\u0633\u0627\u0621",
|
||||
"credit": "\u062A\u0633\u062A\u062E\u062F\u0645 \u0647\u0630\u0647 \u0627\u0644\u062E\u062F\u0645\u0629 OCRmyPDF \u0648 Tesseract \u0644 OCR.",
|
||||
"submit": "\u0645\u0639\u0627\u0644\u062C\u0629 PDF \u0628\u0627\u0633\u062A\u062E\u062F\u0627\u0645 OCR"
|
||||
},
|
||||
"extractImages": {
|
||||
"tags": "picture,photo,save,archive,zip,capture,grab",
|
||||
"title": "\u0627\u0633\u062A\u062E\u0631\u0627\u062C \u0627\u0644\u0635\u0648\u0631",
|
||||
"header": "\u0627\u0633\u062A\u062E\u0631\u0627\u062C \u0627\u0644\u0635\u0648\u0631",
|
||||
"selectText": "\u062D\u062F\u062F \u062A\u0646\u0633\u064A\u0642 \u0627\u0644\u0635\u0648\u0631\u0629 \u0644\u062A\u062D\u0648\u064A\u0644 \u0627\u0644\u0635\u0648\u0631 \u0627\u0644\u0645\u0633\u062A\u062E\u0631\u062C\u0629 \u0625\u0644\u0649",
|
||||
"submit": "\u0627\u0633\u062A\u062E\u0631\u0627\u062C"
|
||||
},
|
||||
"pdfToPDFA": {
|
||||
"tags": "archive,long-term,standard,conversion,storage,preservation",
|
||||
"title": "PDF \u0625\u0644\u0649 PDF / A",
|
||||
"header": "PDF \u0625\u0644\u0649 PDF / A",
|
||||
"credit": "\u062A\u0633\u062A\u062E\u062F\u0645 \u0647\u0630\u0647 \u0627\u0644\u062E\u062F\u0645\u0629 OCRmyPDF \u0644\u062A\u062D\u0648\u064A\u0644 PDF / A.",
|
||||
"submit": "\u062A\u062D\u0648\u064A\u0644"
|
||||
},
|
||||
"PDFToWord": {
|
||||
"tags": "doc,docx,odt,word,transformation,format,conversion,office,microsoft,docfile",
|
||||
"title": "تحويل PDF إلى Word",
|
||||
"header": "تحويل PDF إلى Word",
|
||||
"selectText": {
|
||||
"1": "تنسيق ملف الإخراج"
|
||||
},
|
||||
"credit": "تستخدم هذه الخدمة LibreOffice لتحويل الملفات.",
|
||||
"submit": "تحويل"
|
||||
},
|
||||
"PDFToPresentation": {
|
||||
"tags": "slides,show,office,microsoft",
|
||||
"title": "PDF للعرض التقديمي",
|
||||
"header": "PDF للعرض التقديمي",
|
||||
"selectText": {
|
||||
"1": "تنسيق ملف الإخراج"
|
||||
},
|
||||
"credit": "تستخدم هذه الخدمة LibreOffice لتحويل الملف.",
|
||||
"submit": "تحويل"
|
||||
},
|
||||
"PDFToText": {
|
||||
"tags": "richformat,richtextformat,rich text format",
|
||||
"title": "تحويل PDF إلى نص / RTF",
|
||||
"header": "تحويل PDF إلى نص / RTF",
|
||||
"selectText": {
|
||||
"1": "تنسيق ملف الإخراج"
|
||||
},
|
||||
"credit": "تستخدم هذه الخدمة LibreOffice لتحويل الملفات.",
|
||||
"submit": "تحويل"
|
||||
},
|
||||
"PDFToHTML": {
|
||||
"tags": "web content,browser friendly",
|
||||
"title": "PDF إلى HTML",
|
||||
"header": "PDF إلى HTML",
|
||||
"credit": "تستخدم هذه الخدمة LibreOffice لتحويل الملفات.",
|
||||
"submit": "تحويل"
|
||||
},
|
||||
"PDFToXML": {
|
||||
"tags": "data-extraction,structured-content,interop,transformation,convert",
|
||||
"title": "تحويل PDF إلى XML",
|
||||
"header": "تحويل PDF إلى XML",
|
||||
"credit": "تستخدم هذه الخدمة LibreOffice لتحويل الملفات.",
|
||||
"submit": "تحويل"
|
||||
},
|
||||
"ScannerImageSplit": {
|
||||
"tags": "separate,auto-detect,scans,multi-photo,organize",
|
||||
"selectText": {
|
||||
"1": "عتبة الزاوية:",
|
||||
"2": "تعيين الحد الأدنى للزاوية المطلقة المطلوبة لتدوير الصورة (افتراضي: 10).",
|
||||
"3": "التسامح:",
|
||||
"4": "يحدد نطاق تباين اللون حول لون الخلفية المقدر (الافتراضي: 30).",
|
||||
"5": "أدنى مساحة:",
|
||||
"6": "تعيين الحد الأدنى لمنطقة الصورة (الافتراضي: 10000).",
|
||||
"7": "الحد الأدنى لمنطقة المحيط:",
|
||||
"8": "تعيين الحد الأدنى لمنطقة المحيط للصورة",
|
||||
"9": "حجم الحدود:",
|
||||
"10": "يضبط حجم الحدود المضافة والمزالة لمنع الحدود البيضاء في الإخراج (الافتراضي: 1)."
|
||||
}
|
||||
},
|
||||
"sign": {
|
||||
"tags": "authorize,initials,drawn-signature,text-sign,image-signature",
|
||||
"title": "تسجيل الدخول",
|
||||
"header": "توقيع ملفات PDF",
|
||||
"upload": "تحميل الصورة",
|
||||
"draw": "رسم التوقيع",
|
||||
"text": "Text Input",
|
||||
"clear": "واضح",
|
||||
"add": "إضافة"
|
||||
},
|
||||
"flatten": {
|
||||
"tags": "static,deactivate,non-interactive,streamline",
|
||||
"title": "تسطيح",
|
||||
"header": "تسوية ملفات PDF",
|
||||
"submit": "تسطيح"
|
||||
},
|
||||
"repair": {
|
||||
"tags": "fix,restore,correction,recover",
|
||||
"title": "إصلاح",
|
||||
"header": "إصلاح ملفات PDF",
|
||||
"submit": "الإصلاح"
|
||||
},
|
||||
"removeBlanks": {
|
||||
"tags": "cleanup,streamline,non-content,organize",
|
||||
"title": "إزالة الفراغات",
|
||||
"header": "إزالة الصفحات الفارغة",
|
||||
"threshold": "العتبة:",
|
||||
"thresholdDesc": "الحد الفاصل لتحديد مدى بياض البكسل الأبيض",
|
||||
"whitePercent": "نسبة الأبيض (٪):",
|
||||
"whitePercentDesc": "النسبة المئوية للصفحة التي يجب أن تكون بيضاء لتتم إزالتها",
|
||||
"submit": "إزالة الفراغات"
|
||||
},
|
||||
"compare": {
|
||||
"tags": "differentiate,contrast,changes,analysis",
|
||||
"title": "يقارن",
|
||||
"header": "قارن ملفات PDF",
|
||||
"document": {
|
||||
"1": "المستند 1",
|
||||
"2": "المستند 2"
|
||||
},
|
||||
"submit": "يقارن"
|
||||
},
|
||||
"certSign": {
|
||||
"tags": "authenticate,PEM,P12,official,encrypt",
|
||||
"title": "توقيع الشهادة",
|
||||
"header": "قم بتوقيع ملف PDF بشهادتك (العمل قيد التقدم)",
|
||||
"selectPDF": "حدد ملف PDF للتوقيع:",
|
||||
"selectKey": "حدد ملف المفتاح الخاص (تنسيق PKCS ",
|
||||
"selectCert": "حدد ملف الشهادة الخاص بك (تنسيق X.509 ، يمكن أن يكون .pem أو .der):",
|
||||
"selectP12": "حدد ملف تخزين المفاتيح PKCS ",
|
||||
"certType": "نوع الشهادة",
|
||||
"password": "أدخل ملف تخزين المفاتيح أو كلمة المرور الخاصة (إن وجدت):",
|
||||
"showSig": "إظهار التوقيع",
|
||||
"reason": "السبب",
|
||||
"location": "الموقع",
|
||||
"name": "الاسم",
|
||||
"submit": "تسجيل PDF"
|
||||
},
|
||||
"pageLayout": {
|
||||
"tags": "merge,composite,single-view,organize",
|
||||
"title": "Multi Page Layout",
|
||||
"header": "Multi Page Layout",
|
||||
"pagesPerSheet": "Pages per sheet:",
|
||||
"addBorder": "Add Borders",
|
||||
"submit": "Submit"
|
||||
},
|
||||
"scalePages": {
|
||||
"tags": "resize,modify,dimension,adapt",
|
||||
"title": "Adjust page-scale",
|
||||
"header": "Adjust page-scale",
|
||||
"pageSize": "Size of a page of the document.",
|
||||
"scaleFactor": "Zoom level (crop) of a page.",
|
||||
"submit": "Submit"
|
||||
},
|
||||
"pipeline": {
|
||||
"tags": "automate,sequence,scripted,batch-process",
|
||||
"title": "Pipeline"
|
||||
},
|
||||
"add-page-numbers": {
|
||||
"tags": "paginate,label,organize,index"
|
||||
},
|
||||
"auto-rename": {
|
||||
"tags": "auto-detect,header-based,organize,relabel",
|
||||
"title": "Auto Rename",
|
||||
"header": "Auto Rename PDF",
|
||||
"submit": "Auto Rename"
|
||||
},
|
||||
"adjust-contrast": {
|
||||
"tags": "color-correction,tune,modify,enhance"
|
||||
},
|
||||
"crop": {
|
||||
"tags": "trim,shrink,edit,shape",
|
||||
"title": "Crop",
|
||||
"header": "Crop Image",
|
||||
"submit": "Submit"
|
||||
},
|
||||
"autoSplitPDF": {
|
||||
"tags": "QR-based,separate,scan-segment,organize",
|
||||
"title": "Auto Split PDF",
|
||||
"header": "Auto Split PDF",
|
||||
"description": "Print, Insert, Scan, upload, and let us auto-separate your documents. No manual work sorting needed.",
|
||||
"selectText": {
|
||||
"1": "Print out some divider sheets from below (Black and white is fine).",
|
||||
"2": "Scan all your documents at once by inserting the divider sheet between them.",
|
||||
"3": "Upload the single large scanned PDF file and let Stirling PDF handle the rest.",
|
||||
"4": "Divider pages are automatically detected and removed, guaranteeing a neat final document."
|
||||
},
|
||||
"formPrompt": "Submit PDF containing Stirling-PDF Page dividers:",
|
||||
"duplexMode": "Duplex Mode (Front and back scanning)",
|
||||
"dividerDownload1": "Download 'Auto Splitter Divider (minimal).pdf'",
|
||||
"dividerDownload2": "Download 'Auto Splitter Divider (with instructions).pdf'",
|
||||
"submit": "Submit"
|
||||
},
|
||||
"sanitizePdf": {
|
||||
"tags": "clean,secure,safe,remove-threats"
|
||||
},
|
||||
"URLToPDF": {
|
||||
"tags": "web-capture,save-page,web-to-doc,archive",
|
||||
"title": "URL To PDF",
|
||||
"header": "URL To PDF",
|
||||
"submit": "Convert",
|
||||
"credit": "Uses WeasyPrint"
|
||||
},
|
||||
"HTMLToPDF": {
|
||||
"tags": "markup,web-content,transformation,convert",
|
||||
"title": "HTML To PDF",
|
||||
"header": "HTML To PDF",
|
||||
"help": "Accepts HTML files and ZIPs containing html/css/images etc required",
|
||||
"submit": "Convert",
|
||||
"credit": "Uses WeasyPrint"
|
||||
},
|
||||
"MarkdownToPDF": {
|
||||
"tags": "markup,web-content,transformation,convert",
|
||||
"title": "Markdown To PDF",
|
||||
"header": "Markdown To PDF",
|
||||
"submit": "Convert",
|
||||
"help": "Work in progress",
|
||||
"credit": "Uses WeasyPrint"
|
||||
},
|
||||
"getPdfInfo": {
|
||||
"tags": "infomation,data,stats,statistics",
|
||||
"title": "Get Info on PDF",
|
||||
"header": "Get Info on PDF",
|
||||
"submit": "Get Info",
|
||||
"downloadJson": "Download JSON"
|
||||
},
|
||||
"extractPage": {
|
||||
"tags": "extract"
|
||||
},
|
||||
"PdfToSinglePage": {
|
||||
"tags": "single page"
|
||||
},
|
||||
"showJS": {
|
||||
"tags": "JS",
|
||||
"title": "Show Javascript",
|
||||
"header": "Show Javascript",
|
||||
"downloadJS": "Download Javascript",
|
||||
"submit": "Show"
|
||||
},
|
||||
"login": {
|
||||
"title": "Sign in",
|
||||
"signin": "Sign in",
|
||||
"rememberme": "Remember me",
|
||||
"invalid": "Invalid username or password.",
|
||||
"locked": "Your account has been locked.",
|
||||
"signinTitle": "Please sign in"
|
||||
},
|
||||
"autoRedact": {
|
||||
"title": "Auto Redact",
|
||||
"header": "Auto Redact",
|
||||
"colorLabel": "Colour",
|
||||
"textsToRedactLabel": "Text to Redact (line-separated)",
|
||||
"textsToRedactPlaceholder": "e.g. \\nConfidential \\nTop-Secret",
|
||||
"useRegexLabel": "Use Regex",
|
||||
"wholeWordSearchLabel": "Whole Word Search",
|
||||
"customPaddingLabel": "Custom Extra Padding",
|
||||
"convertPDFToImageLabel": "Convert PDF to PDF-Image (Used to remove text behind the box)",
|
||||
"submitButton": "Submit"
|
||||
},
|
||||
"pdfToSinglePage": {
|
||||
"title": "PDF To Single Page",
|
||||
"header": "PDF To Single Page",
|
||||
"submit": "Convert To Single Page"
|
||||
},
|
||||
"pageExtracter": {
|
||||
"title": "Extract Pages",
|
||||
"header": "Extract Pages",
|
||||
"submit": "Extract"
|
||||
},
|
||||
"sanitizePDF": {
|
||||
"title": "Sanitize PDF",
|
||||
"header": "Sanitize a PDF file",
|
||||
"selectText": {
|
||||
"1": "Remove JavaScript actions",
|
||||
"2": "Remove embedded files",
|
||||
"3": "Remove metadata",
|
||||
"4": "Remove links",
|
||||
"5": "Remove fonts"
|
||||
},
|
||||
"submit": "Sanitize PDF"
|
||||
},
|
||||
"addPageNumbers": {
|
||||
"title": "Add Page Numbers",
|
||||
"header": "Add Page Numbers",
|
||||
"selectText": {
|
||||
"1": "Select PDF file:",
|
||||
"2": "Margin Size",
|
||||
"3": "Position",
|
||||
"4": "Starting Number",
|
||||
"5": "Pages to Number",
|
||||
"6": "Custom Text"
|
||||
},
|
||||
"customTextDesc": "Custom Text",
|
||||
"numberPagesDesc": "Which pages to number, default 'all', also accepts 1-5 or 2,5,9 etc",
|
||||
"customNumberDesc": "Defaults to {n}, also accepts 'Page {n} of {total}', 'Text-{n}', '{filename}-{n}",
|
||||
"submit": "Add Page Numbers"
|
||||
},
|
||||
"adjustContrast": {
|
||||
"title": "Adjust Contrast",
|
||||
"header": "Adjust Contrast",
|
||||
"contrast": "Contrast:",
|
||||
"brightness": "Brightness:",
|
||||
"saturation": "Saturation:",
|
||||
"download": "Download"
|
||||
},
|
||||
"compress": {
|
||||
"title": "ضغط",
|
||||
"header": "ضغط ملف PDF",
|
||||
"credit": "تستخدم هذه الخدمة OCRmyPDF لضغط / تحسين PDF.",
|
||||
"selectText": {
|
||||
"1": "الوضع اليدوي - من 1 إلى 4",
|
||||
"2": "مستوى التحسين:",
|
||||
"3": "4 (رهيب للصور النصية)",
|
||||
"4": "الوضع التلقائي - يضبط الجودة تلقائيًا للحصول على ملف PDF بالحجم المحدد",
|
||||
"5": "حجم PDF المتوقع (على سبيل المثال 25 ميجا بايت ، 10.8 ميجا بايت ، 25 كيلو بايت)"
|
||||
},
|
||||
"submit": "ضغطضغط"
|
||||
},
|
||||
"pageRemover": {
|
||||
"title": "مزيل الصفحة",
|
||||
"header": "مزيل صفحة PDF",
|
||||
"pagesToDelete": "الصفحات المراد حذفها (أدخل قائمة بأرقام الصفحات مفصولة بفواصل):",
|
||||
"submit": "حذف الصفحات"
|
||||
},
|
||||
"imageToPDF": {
|
||||
"title": "صورة إلى PDF",
|
||||
"header": "صورة إلى PDF",
|
||||
"submit": "تحول",
|
||||
"selectLabel": "Image Fit Options",
|
||||
"fillPage": "Fill Page",
|
||||
"fitDocumentToImage": "Fit Page to Image",
|
||||
"maintainAspectRatio": "Maintain Aspect Ratios",
|
||||
"selectText": {
|
||||
"2": "\u062F\u0648\u0631\u0627\u0646 PDF \u062A\u0644\u0642\u0627\u0626\u064A\u064B\u0627",
|
||||
"3": "\u0627\u0644\u0645\u0646\u0637\u0642 \u0627\u0644\u0645\u062A\u0639\u062F\u062F \u0644\u0644\u0645\u0644\u0641\u0627\u062A (\u0645\u0641\u0639\u0651\u0644 \u0641\u0642\u0637 \u0625\u0630\u0627 \u0643\u0646\u062A \u062A\u0639\u0645\u0644 \u0645\u0639 \u0635\u0648\u0631 \u0645\u062A\u0639\u062F\u062F\u0629)",
|
||||
"4": "\u062F\u0645\u062C \u0641\u064A \u0645\u0644\u0641 PDF \u0648\u0627\u062D\u062F",
|
||||
"5": "\u062A\u062D\u0648\u064A\u0644 \u0625\u0644\u0649 \u0645\u0644\u0641\u0627\u062A PDF \u0645\u0646\u0641\u0635\u0644\u0629"
|
||||
}
|
||||
},
|
||||
"changeMetadata": {
|
||||
"title": "\u0627\u0644\u0639\u0646\u0648\u0627\u0646:",
|
||||
"header": "\u062A\u063A\u064A\u064A\u0631 \u0627\u0644\u0628\u064A\u0627\u0646\u0627\u062A \u0627\u0644\u0648\u0635\u0641\u064A\u0629",
|
||||
"selectText": {
|
||||
"1": "\u064A\u0631\u062C\u0649 \u062A\u0639\u062F\u064A\u0644 \u0627\u0644\u0645\u062A\u063A\u064A\u0631\u0627\u062A \u0627\u0644\u062A\u064A \u062A\u0631\u063A\u0628 \u0641\u064A \u062A\u063A\u064A\u064A\u0631\u0647\u0627",
|
||||
"2": "\u062D\u0630\u0641 \u0643\u0644 \u0627\u0644\u0628\u064A\u0627\u0646\u0627\u062A \u0627\u0644\u0623\u0648\u0644\u064A\u0629",
|
||||
"3": "\u0625\u0638\u0647\u0627\u0631 \u0627\u0644\u0628\u064A\u0627\u0646\u0627\u062A \u0627\u0644\u0623\u0648\u0644\u064A\u0629 \u0627\u0644\u0645\u062E\u0635\u0635\u0629:",
|
||||
"4": "\u0628\u064A\u0627\u0646\u0627\u062A \u0648\u0635\u0641\u064A\u0629 \u0623\u062E\u0631\u0649:",
|
||||
"5": "\u0625\u0636\u0627\u0641\u0629 \u0625\u062F\u062E\u0627\u0644 \u0628\u064A\u0627\u0646\u0627\u062A \u0623\u0648\u0644\u064A\u0629 \u0645\u062E\u0635\u0635"
|
||||
},
|
||||
"author": "\u0627\u0644\u0645\u0624\u0644\u0641:",
|
||||
"creationDate": "\u062A\u0627\u0631\u064A\u062E \u0627\u0644\u0625\u0646\u0634\u0627\u0621 (yyyy / MM / dd HH: mm: ss):",
|
||||
"creator": "\u0627\u0644\u0645\u0646\u0634\u0626:",
|
||||
"keywords": "\u0627\u0644\u0643\u0644\u0645\u0627\u062A \u0627\u0644\u0631\u0626\u064A\u0633\u064A\u0629:",
|
||||
"modDate": "\u062A\u0627\u0631\u064A\u062E \u0627\u0644\u062A\u0639\u062F\u064A\u0644 (yyyy / MM / dd HH: mm: ss):",
|
||||
"producer": "\u0627\u0644\u0645\u0646\u062A\u062C:",
|
||||
"subject": "\u0627\u0644\u0645\u0648\u0636\u0648\u0639:",
|
||||
"trapped": "\u0645\u062D\u0627\u0635\u0631:",
|
||||
"submit": "\u062A\u063A\u064A\u064A\u0631"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,854 +0,0 @@
|
||||
{
|
||||
"translation": {
|
||||
"language": {
|
||||
"name":"English (UK)",
|
||||
"flag": "🇬🇧",
|
||||
"direction": "ltr"
|
||||
},
|
||||
"pdfPrompt": "Select PDF(s)",
|
||||
"multiPdfPrompt": "Select PDFs (2+)",
|
||||
"multiPdfDropPrompt": "Select (or drag & drop) all PDFs you require",
|
||||
"imgPrompt": "Select Image(s)",
|
||||
"genericSubmit": "Submit",
|
||||
"processTimeWarning": "Warning: This process can take up to a minute depending on file-size",
|
||||
"pageOrderPrompt": "Custom Page Order (Enter a comma-separated list of page numbers or Functions like 2n+1) :",
|
||||
"goToPage": "Go",
|
||||
"true": "True",
|
||||
"false": "False",
|
||||
"unknown": "Unknown",
|
||||
"save": "Save",
|
||||
"close": "Close",
|
||||
"filesSelected": "files selected",
|
||||
"noFavourites": "No favourites added",
|
||||
"bored": "Bored Waiting?",
|
||||
"alphabet": "Alphabet",
|
||||
"downloadPdf": "Download PDF",
|
||||
"text": "Text",
|
||||
"font": "Font",
|
||||
"selectFillter": "-- Select --",
|
||||
"pageNum": "Page Number ",
|
||||
"sizes": {
|
||||
"small": "Small",
|
||||
"medium": "Medium",
|
||||
"large": "Large",
|
||||
"x-large": "X-Large"
|
||||
},
|
||||
"error": {
|
||||
"pdfPassword": "The PDF Document is passworded and either the password was not provided or was incorrect"
|
||||
},
|
||||
"delete": "Delete",
|
||||
"username": "Username",
|
||||
"password": "Password",
|
||||
"welcome": "Welcome",
|
||||
"property": "Property",
|
||||
"black": "Black",
|
||||
"white": "White",
|
||||
"red": "Red",
|
||||
"green": "Green",
|
||||
"blue": "Blue",
|
||||
"custom": "Custom...",
|
||||
"changedCredsMessage": "Credentials changed!",
|
||||
"notAuthenticatedMessage": "User not authenticated.",
|
||||
"userNotFoundMessage": "User not found.",
|
||||
"incorrectPasswordMessage": "Current password is incorrect.",
|
||||
"usernameExistsMessage": "New Username already exists.",
|
||||
"navbar": {
|
||||
"convert": "Convert",
|
||||
"security": "Security",
|
||||
"other": "Miscellaneous",
|
||||
"darkmode": "Dark Mode",
|
||||
"pageOps": "Page Operations",
|
||||
"settings": "Settings"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Settings",
|
||||
"update": "Update available",
|
||||
"appVersion": "App Version:",
|
||||
"downloadOption": {
|
||||
"1": "Open in same window",
|
||||
"2": "Open in new window",
|
||||
"3": "Download file",
|
||||
"title": "Choose download option (For single file non zip downloads):"
|
||||
},
|
||||
"zipThreshold": "Zip files when the number of downloaded files exceeds",
|
||||
"signOut": "Sign Out",
|
||||
"accountSettings": "Account Settings"
|
||||
},
|
||||
"changeCreds": {
|
||||
"title": "Change Credentials",
|
||||
"header": "Update Your Account Details",
|
||||
"changeUserAndPassword": "You are using default login credentials. Please enter a new password (and username if wanted)",
|
||||
"newUsername": "New Username",
|
||||
"oldPassword": "Current Password",
|
||||
"newPassword": "New Password",
|
||||
"confirmNewPassword": "Confirm New Password",
|
||||
"submit": "Submit Changes"
|
||||
},
|
||||
"account": {
|
||||
"title": "Account Settings",
|
||||
"accountSettings": "Account Settings",
|
||||
"adminSettings": "Admin Settings - View and Add Users",
|
||||
"userControlSettings": "User Control Settings",
|
||||
"changeUsername": "Change Username",
|
||||
"password": "Confirmation Password",
|
||||
"oldPassword": "Old password",
|
||||
"newPassword": "New Password",
|
||||
"changePassword": "Change Password",
|
||||
"confirmNewPassword": "Confirm New Password",
|
||||
"signOut": "Sign Out",
|
||||
"yourApiKey": "Your API Key",
|
||||
"syncTitle": "Sync browser settings with Account",
|
||||
"settingsCompare": "Settings Comparison:",
|
||||
"property": "Property",
|
||||
"webBrowserSettings": "Web Browser Setting",
|
||||
"syncToBrowser": "Sync Account -> Browser",
|
||||
"syncToAccount": "Sync Account <- Browser"
|
||||
},
|
||||
"adminUserSettings": {
|
||||
"title": "User Control Settings",
|
||||
"header": "Admin User Control Settings",
|
||||
"admin": "Admin",
|
||||
"user": "User",
|
||||
"addUser": "Add New User",
|
||||
"roles": "Roles",
|
||||
"role": "Role",
|
||||
"actions": "Actions",
|
||||
"apiUser": "Limited API User",
|
||||
"webOnlyUser": "Web Only User",
|
||||
"forceChange": "Force user to change username/password on login",
|
||||
"submit": "Save User"
|
||||
},
|
||||
"home": {
|
||||
"desc": "Your locally hosted one-stop-shop for all your PDF needs.",
|
||||
"searchBar": "Search for features...",
|
||||
"viewPdf": {
|
||||
"title": "View PDF",
|
||||
"desc": "View, annotate, add text or images"
|
||||
},
|
||||
"multiTool": {
|
||||
"title": "PDF Multi Tool",
|
||||
"desc": "Merge, Rotate, Rearrange, and Remove pages"
|
||||
},
|
||||
"merge": {
|
||||
"title": "Merge",
|
||||
"desc": "Easily merge multiple PDFs into one."
|
||||
},
|
||||
"split": {
|
||||
"title": "Split",
|
||||
"desc": "Split PDFs into multiple documents"
|
||||
},
|
||||
"rotate": {
|
||||
"title": "Rotate",
|
||||
"desc": "Easily rotate your PDFs."
|
||||
},
|
||||
"imageToPdf": {
|
||||
"title": "Image to PDF",
|
||||
"desc": "Convert a image (PNG, JPEG, GIF) to PDF."
|
||||
},
|
||||
"pdfToImage": {
|
||||
"title": "PDF to Image",
|
||||
"desc": "Convert a PDF to a image. (PNG, JPEG, GIF)"
|
||||
},
|
||||
"pdfOrganiser": {
|
||||
"title": "Organise",
|
||||
"desc": "Remove/Rearrange pages in any order"
|
||||
},
|
||||
"addImage": {
|
||||
"title": "Add image",
|
||||
"desc": "Adds a image onto a set location on the PDF"
|
||||
},
|
||||
"watermark": {
|
||||
"title": "Add Watermark",
|
||||
"desc": "Add a custom watermark to your PDF document."
|
||||
},
|
||||
"permissions": {
|
||||
"title": "Change Permissions",
|
||||
"desc": "Change the permissions of your PDF document"
|
||||
},
|
||||
"removePages": {
|
||||
"title": "Remove",
|
||||
"desc": "Delete unwanted pages from your PDF document."
|
||||
},
|
||||
"addPassword": {
|
||||
"title": "Add Password",
|
||||
"desc": "Encrypt your PDF document with a password."
|
||||
},
|
||||
"removePassword": {
|
||||
"title": "Remove Password",
|
||||
"desc": "Remove password protection from your PDF document."
|
||||
},
|
||||
"compressPdfs": {
|
||||
"title": "Compress",
|
||||
"desc": "Compress PDFs to reduce their file size."
|
||||
},
|
||||
"changeMetadata": {
|
||||
"title": "Change Metadata",
|
||||
"desc": "Change/Remove/Add metadata from a PDF document"
|
||||
},
|
||||
"fileToPDF": {
|
||||
"title": "Convert file to PDF",
|
||||
"desc": "Convert nearly any file to PDF (DOCX, PNG, XLS, PPT, TXT and more)"
|
||||
},
|
||||
"ocr": {
|
||||
"title": "OCR / Cleanup scans",
|
||||
"desc": "Cleanup scans and detects text from images within a PDF and re-adds it as text."
|
||||
},
|
||||
"extractImages": {
|
||||
"title": "Extract Images",
|
||||
"desc": "Extracts all images from a PDF and saves them to zip"
|
||||
},
|
||||
"pdfToPDFA": {
|
||||
"title": "PDF to PDF/A",
|
||||
"desc": "Convert PDF to PDF/A for long-term storage"
|
||||
},
|
||||
"PDFToWord": {
|
||||
"title": "PDF to Word",
|
||||
"desc": "Convert PDF to Word formats (DOC, DOCX and ODT)"
|
||||
},
|
||||
"PDFToPresentation": {
|
||||
"title": "PDF to Presentation",
|
||||
"desc": "Convert PDF to Presentation formats (PPT, PPTX and ODP)"
|
||||
},
|
||||
"PDFToText": {
|
||||
"title": "PDF to RTF (Text)",
|
||||
"desc": "Convert PDF to Text or RTF format"
|
||||
},
|
||||
"PDFToHTML": {
|
||||
"title": "PDF to HTML",
|
||||
"desc": "Convert PDF to HTML format"
|
||||
},
|
||||
"PDFToXML": {
|
||||
"title": "PDF to XML",
|
||||
"desc": "Convert PDF to XML format"
|
||||
},
|
||||
"ScannerImageSplit": {
|
||||
"title": "Detect/Split Scanned photos",
|
||||
"desc": "Splits multiple photos from within a photo/PDF"
|
||||
},
|
||||
"sign": {
|
||||
"title": "Sign",
|
||||
"desc": "Adds signature to PDF by drawing, text or image"
|
||||
},
|
||||
"flatten": {
|
||||
"title": "Flatten",
|
||||
"desc": "Remove all interactive elements and forms from a PDF"
|
||||
},
|
||||
"repair": {
|
||||
"title": "Repair",
|
||||
"desc": "Tries to repair a corrupt/broken PDF"
|
||||
},
|
||||
"removeBlanks": {
|
||||
"title": "Remove Blank pages",
|
||||
"desc": "Detects and removes blank pages from a document"
|
||||
},
|
||||
"compare": {
|
||||
"title": "Compare",
|
||||
"desc": "Compares and shows the differences between 2 PDF Documents"
|
||||
},
|
||||
"certSign": {
|
||||
"title": "Sign with Certificate",
|
||||
"desc": "Signs a PDF with a Certificate/Key (PEM/P12)"
|
||||
},
|
||||
"pageLayout": {
|
||||
"title": "Multi-Page Layout",
|
||||
"desc": "Merge multiple pages of a PDF document into a single page"
|
||||
},
|
||||
"scalePages": {
|
||||
"title": "Adjust page size/scale",
|
||||
"desc": "Change the size/scale of a page and/or its contents."
|
||||
},
|
||||
"pipeline": {
|
||||
"title": "Pipeline (Advanced)",
|
||||
"desc": "Run multiple actions on PDFs by defining pipeline scripts"
|
||||
},
|
||||
"add-page-numbers": {
|
||||
"title": "Add Page Numbers",
|
||||
"desc": "Add Page numbers throughout a document in a set location"
|
||||
},
|
||||
"auto-rename": {
|
||||
"title": "Auto Rename PDF File",
|
||||
"desc": "Auto renames a PDF file based on its detected header "
|
||||
},
|
||||
"adjust-contrast": {
|
||||
"title": "Adjust Colors/Contrast",
|
||||
"desc": "Adjust Contrast, Saturation and Brightness of a PDF"
|
||||
},
|
||||
"crop": {
|
||||
"title": "Crop PDF",
|
||||
"desc": "Crop a PDF to reduce its size (maintains text!)"
|
||||
},
|
||||
"autoSplitPDF": {
|
||||
"title": "Auto Split Pages",
|
||||
"desc": "Auto Split Scanned PDF with physical scanned page splitter QR Code"
|
||||
},
|
||||
"sanitizePdf": {
|
||||
"title": "Sanitize",
|
||||
"desc": "Remove scripts and other elements from PDF files"
|
||||
},
|
||||
"URLToPDF": {
|
||||
"title": "URL/Website To PDF",
|
||||
"desc": "Converts any http(s)URL to PDF"
|
||||
},
|
||||
"HTMLToPDF": {
|
||||
"title": "HTML to PDF",
|
||||
"desc": "Converts any HTML file or zip to PDF"
|
||||
},
|
||||
"MarkdownToPDF": {
|
||||
"title": "Markdown to PDF",
|
||||
"desc": "Converts any Markdown file to PDF"
|
||||
},
|
||||
"getPdfInfo": {
|
||||
"title": "Get ALL Info on PDF",
|
||||
"desc": "Grabs any and all information possible on PDFs"
|
||||
},
|
||||
"extractPage": {
|
||||
"title": "Extract page(s)",
|
||||
"desc": "Extracts select pages from PDF"
|
||||
},
|
||||
"PdfToSinglePage": {
|
||||
"title": "PDF to Single Large Page",
|
||||
"desc": "Merges all PDF pages into one large single page"
|
||||
},
|
||||
"showJS": {
|
||||
"title": "Show Javascript",
|
||||
"desc": "Searches and displays any JS injected into a PDF"
|
||||
},
|
||||
"autoRedact": {
|
||||
"title": "Auto Redact",
|
||||
"desc": "Auto Redacts(Blacks out) text in a PDF based on input text"
|
||||
}
|
||||
},
|
||||
"viewPdf": {
|
||||
"tags": "view,read,annotate,text,image",
|
||||
"title": "View PDF",
|
||||
"header": "View PDF"
|
||||
},
|
||||
"multiTool": {
|
||||
"tags": "Multi Tool,Multi operation,UI,click drag,front end,client side,interactive,intractable,move",
|
||||
"title": "PDF Multi Tool",
|
||||
"header": "PDF Multi Tool"
|
||||
},
|
||||
"merge": {
|
||||
"tags": "merge,Page operations,Back end,server side",
|
||||
"title": "Merge",
|
||||
"header": "Merge multiple PDFs (2+)",
|
||||
"sortByName": "Sort by name",
|
||||
"sortByDate": "Sort by date",
|
||||
"submit": "Merge"
|
||||
},
|
||||
"split": {
|
||||
"tags": "Page operations,divide,Multi Page,cut,server side ",
|
||||
"title": "Split PDF",
|
||||
"header": "Split PDF",
|
||||
"desc": {
|
||||
"1": "The numbers you select are the page number you wish to do a split on",
|
||||
"2": "As such selecting 1,3,7-8 would split a 10 page document into 6 separate PDFS with:",
|
||||
"3": "Document ",
|
||||
"4": "Document ",
|
||||
"5": "Document ",
|
||||
"6": "Document ",
|
||||
"7": "Document ",
|
||||
"8": "Document "
|
||||
},
|
||||
"splitPages": "Enter pages to split on:",
|
||||
"submit": "Split"
|
||||
},
|
||||
"rotate": {
|
||||
"tags": "server side",
|
||||
"title": "Rotate PDF",
|
||||
"header": "Rotate PDF",
|
||||
"selectAngle": "Select rotation angle (in multiples of 90 degrees):",
|
||||
"submit": "Rotate"
|
||||
},
|
||||
"imageToPdf": {
|
||||
"tags": "conversion,img,jpg,picture,photo"
|
||||
},
|
||||
"pdfToImage": {
|
||||
"tags": "conversion,img,jpg,picture,photo",
|
||||
"title": "PDF to Image",
|
||||
"header": "PDF to Image",
|
||||
"selectText": "Image Format",
|
||||
"singleOrMultiple": "Page to Image result type",
|
||||
"single": "Single Big Image Combing all pages",
|
||||
"multi": "Multiple Images, one image per page",
|
||||
"colorType": "Colour type",
|
||||
"color": "Colour",
|
||||
"grey": "Greyscale",
|
||||
"blackwhite": "Black and White (May lose data!)",
|
||||
"submit": "Convert"
|
||||
},
|
||||
"pdfOrganiser": {
|
||||
"tags": "duplex,even,odd,sort,move",
|
||||
"title": "Page Organiser",
|
||||
"header": "PDF Page Organiser",
|
||||
"submit": "Rearrange Pages"
|
||||
},
|
||||
"addImage": {
|
||||
"tags": "img,jpg,picture,photo",
|
||||
"title": "Add Image",
|
||||
"header": "Add image to PDF",
|
||||
"everyPage": "Every Page?",
|
||||
"upload": "Add image",
|
||||
"submit": "Add image"
|
||||
},
|
||||
"watermark": {
|
||||
"tags": "Text,repeating,label,own,copyright,trademark,img,jpg,picture,photo",
|
||||
"title": "Add Watermark",
|
||||
"header": "Add Watermark",
|
||||
"selectText": {
|
||||
"1": "Select PDF to add watermark to:",
|
||||
"2": "Watermark Text:",
|
||||
"3": "Font Size:",
|
||||
"4": "Rotation (0-360):",
|
||||
"5": "widthSpacer (Space between each watermark horizontally):",
|
||||
"6": "heightSpacer (Space between each watermark vertically):",
|
||||
"7": "Opacity (0% - 100%):",
|
||||
"8": "Watermark Type:",
|
||||
"9": "Watermark Image:"
|
||||
},
|
||||
"submit": "Add Watermark"
|
||||
},
|
||||
"permissions": {
|
||||
"tags": "read,write,edit,print",
|
||||
"title": "Change Permissions",
|
||||
"header": "Change Permissions",
|
||||
"warning": "Warning to have these permissions be unchangeable it is recommended to set them with a password via the add-password page",
|
||||
"selectText": {
|
||||
"1": "Select PDF to change permissions",
|
||||
"2": "Permissions to set",
|
||||
"3": "Prevent assembly of document",
|
||||
"4": "Prevent content extraction",
|
||||
"5": "Prevent extraction for accessibility",
|
||||
"6": "Prevent filling in form",
|
||||
"7": "Prevent modification",
|
||||
"8": "Prevent annotation modification",
|
||||
"9": "Prevent printing",
|
||||
"10": "Prevent printing different formats"
|
||||
},
|
||||
"submit": "Change"
|
||||
},
|
||||
"removePages": {
|
||||
"tags": "Remove pages,delete pages"
|
||||
},
|
||||
"addPassword": {
|
||||
"tags": "secure,security",
|
||||
"title": "Add Password",
|
||||
"header": "Add password (Encrypt)",
|
||||
"selectText": {
|
||||
"1": "Select PDF to encrypt",
|
||||
"2": "User Password",
|
||||
"3": "Encryption Key Length",
|
||||
"4": "Higher values are stronger, but lower values have better compatibility.",
|
||||
"5": "Permissions to set (Recommended to be used along with Owner password)",
|
||||
"6": "Prevent assembly of document",
|
||||
"7": "Prevent content extraction",
|
||||
"8": "Prevent extraction for accessibility",
|
||||
"9": "Prevent filling in form",
|
||||
"10": "Prevent modification",
|
||||
"11": "Prevent annotation modification",
|
||||
"12": "Prevent printing",
|
||||
"13": "Prevent printing different formats",
|
||||
"14": "Owner Password",
|
||||
"15": "Restricts what can be done with the document once it is opened (Not supported by all readers)",
|
||||
"16": "Restricts the opening of the document itself"
|
||||
},
|
||||
"submit": "Encrypt"
|
||||
},
|
||||
"removePassword": {
|
||||
"tags": "secure,Decrypt,security,unpassword,delete password",
|
||||
"title": "Remove password",
|
||||
"header": "Remove password (Decrypt)",
|
||||
"selectText": {
|
||||
"1": "Select PDF to Decrypt",
|
||||
"2": "Password"
|
||||
},
|
||||
"submit": "Remove"
|
||||
},
|
||||
"compressPdfs": {
|
||||
"tags": "squish,small,tiny"
|
||||
},
|
||||
"fileToPDF": {
|
||||
"tags": "transformation,format,document,picture,slide,text,conversion,office,docs,word,excel,powerpoint",
|
||||
"title": "File to PDF",
|
||||
"header": "Convert any file to PDF",
|
||||
"credit": "This service uses LibreOffice and Unoconv for file conversion.",
|
||||
"supportedFileTypes": "Supported file types should include the below however for a full updated list of supported formats, please refer to the LibreOffice documentation",
|
||||
"submit": "Convert to PDF"
|
||||
},
|
||||
"ocr": {
|
||||
"tags": "recognition,text,image,scan,read,identify,detection,editable",
|
||||
"title": "OCR / Scan Cleanup",
|
||||
"header": "Cleanup Scans / OCR (Optical Character Recognition)",
|
||||
"selectText": {
|
||||
"1": "Select languages that are to be detected within the PDF (Ones listed are the ones currently detected):",
|
||||
"2": "Produce text file containing OCR text alongside the OCR'ed PDF",
|
||||
"3": "Correct pages were scanned at a skewed angle by rotating them back into place",
|
||||
"4": "Clean page so its less likely that OCR will find text in background noise. (No output change)",
|
||||
"5": "Clean page so its less likely that OCR will find text in background noise, maintains cleanup in output.",
|
||||
"6": "Ignores pages that have interactive text on them, only OCRs pages that are images",
|
||||
"7": "Force OCR, will OCR Every page removing all original text elements",
|
||||
"8": "Normal (Will error if PDF contains text)",
|
||||
"9": "Additional Settings",
|
||||
"10": "OCR Mode",
|
||||
"11": "Remove images after OCR (Removes ALL images, only useful if part of conversion step)",
|
||||
"12": "Render Type (Advanced)"
|
||||
},
|
||||
"help": "Please read this documentation on how to use this for other languages and/or use not in docker",
|
||||
"credit": "This service uses OCRmyPDF and Tesseract for OCR.",
|
||||
"submit": "Process PDF with OCR"
|
||||
},
|
||||
"extractImages": {
|
||||
"tags": "picture,photo,save,archive,zip,capture,grab",
|
||||
"title": "Extract Images",
|
||||
"header": "Extract Images",
|
||||
"selectText": "Select image format to convert extracted images to",
|
||||
"submit": "Extract"
|
||||
},
|
||||
"pdfToPDFA": {
|
||||
"tags": "archive,long-term,standard,conversion,storage,preservation",
|
||||
"title": "PDF To PDF/A",
|
||||
"header": "PDF To PDF/A",
|
||||
"credit": "This service uses OCRmyPDF for PDF/A conversion",
|
||||
"submit": "Convert"
|
||||
},
|
||||
"PDFToWord": {
|
||||
"tags": "doc,docx,odt,word,transformation,format,conversion,office,microsoft,docfile",
|
||||
"title": "PDF to Word",
|
||||
"header": "PDF to Word",
|
||||
"selectText": {
|
||||
"1": "Output file format"
|
||||
},
|
||||
"credit": "This service uses LibreOffice for file conversion.",
|
||||
"submit": "Convert"
|
||||
},
|
||||
"PDFToPresentation": {
|
||||
"tags": "slides,show,office,microsoft",
|
||||
"title": "PDF to Presentation",
|
||||
"header": "PDF to Presentation",
|
||||
"selectText": {
|
||||
"1": "Output file format"
|
||||
},
|
||||
"credit": "This service uses LibreOffice for file conversion.",
|
||||
"submit": "Convert"
|
||||
},
|
||||
"PDFToText": {
|
||||
"tags": "richformat,richtextformat,rich text format",
|
||||
"title": "PDF to RTF (Text)",
|
||||
"header": "PDF to RTF (Text)",
|
||||
"selectText": {
|
||||
"1": "Output file format"
|
||||
},
|
||||
"credit": "This service uses LibreOffice for file conversion.",
|
||||
"submit": "Convert"
|
||||
},
|
||||
"PDFToHTML": {
|
||||
"tags": "web content,browser friendly",
|
||||
"title": "PDF to HTML",
|
||||
"header": "PDF to HTML",
|
||||
"credit": "This service uses LibreOffice for file conversion.",
|
||||
"submit": "Convert"
|
||||
},
|
||||
"PDFToXML": {
|
||||
"tags": "data-extraction,structured-content,interop,transformation,convert",
|
||||
"title": "PDF to XML",
|
||||
"header": "PDF to XML",
|
||||
"credit": "This service uses LibreOffice for file conversion.",
|
||||
"submit": "Convert"
|
||||
},
|
||||
"ScannerImageSplit": {
|
||||
"tags": "separate,auto-detect,scans,multi-photo,organize",
|
||||
"selectText": {
|
||||
"1": "Angle Threshold:",
|
||||
"2": "Sets the minimum absolute angle required for the image to be rotated (default: 10).",
|
||||
"3": "Tolerance:",
|
||||
"4": "Determines the range of color variation around the estimated background color (default: 30).",
|
||||
"5": "Minimum Area:",
|
||||
"6": "Sets the minimum area threshold for a photo (default: 10000).",
|
||||
"7": "Minimum Contour Area:",
|
||||
"8": "Sets the minimum contour area threshold for a photo",
|
||||
"9": "Border Size:",
|
||||
"10": "Sets the size of the border added and removed to prevent white borders in the output (default: 1)."
|
||||
}
|
||||
},
|
||||
"sign": {
|
||||
"tags": "authorize,initials,drawn-signature,text-sign,image-signature",
|
||||
"title": "Sign",
|
||||
"header": "Sign PDFs",
|
||||
"upload": "Upload Image",
|
||||
"draw": "Draw Signature",
|
||||
"text": "Text Input",
|
||||
"clear": "Clear",
|
||||
"add": "Add"
|
||||
},
|
||||
"flatten": {
|
||||
"tags": "static,deactivate,non-interactive,streamline",
|
||||
"title": "Flatten",
|
||||
"header": "Flatten PDFs",
|
||||
"submit": "Flatten"
|
||||
},
|
||||
"repair": {
|
||||
"tags": "fix,restore,correction,recover",
|
||||
"title": "Repair",
|
||||
"header": "Repair PDFs",
|
||||
"submit": "Repair"
|
||||
},
|
||||
"removeBlanks": {
|
||||
"tags": "cleanup,streamline,non-content,organize",
|
||||
"title": "Remove Blanks",
|
||||
"header": "Remove Blank Pages",
|
||||
"threshold": "Pixel Whiteness Threshold:",
|
||||
"thresholdDesc": "Threshold for determining how white a white pixel must be to be classed as 'White'. 0 ",
|
||||
"whitePercent": "White Percent (%):",
|
||||
"whitePercentDesc": "Percent of page that must be 'white' pixels to be removed",
|
||||
"submit": "Remove Blanks"
|
||||
},
|
||||
"compare": {
|
||||
"tags": "differentiate,contrast,changes,analysis",
|
||||
"title": "Compare",
|
||||
"header": "Compare PDFs",
|
||||
"document": {
|
||||
"1": "Document 1",
|
||||
"2": "Document 2"
|
||||
},
|
||||
"submit": "Compare"
|
||||
},
|
||||
"certSign": {
|
||||
"tags": "authenticate,PEM,P12,official,encrypt",
|
||||
"title": "Certificate Signing",
|
||||
"header": "Sign a PDF with your certificate (Work in progress)",
|
||||
"selectPDF": "Select a PDF File for Signing: ",
|
||||
"selectKey": "Select Your Private Key File (PKCS",
|
||||
"selectCert": "Select Your Certificate File (X.509 format, could be .pem or .der): ",
|
||||
"selectP12": "Select Your PKCS",
|
||||
"certType": "Certificate Type",
|
||||
"password": "Enter Your Keystore or Private Key Password (If Any): ",
|
||||
"showSig": "Show Signature",
|
||||
"reason": "Reason",
|
||||
"location": "Location",
|
||||
"name": "Name ",
|
||||
"submit": "Sign PDF"
|
||||
},
|
||||
"pageLayout": {
|
||||
"tags": "merge,composite,single-view,organize",
|
||||
"title": "Multi Page Layout",
|
||||
"header": "Multi Page Layout",
|
||||
"pagesPerSheet": "Pages per sheet:",
|
||||
"addBorder": "Add Borders",
|
||||
"submit": "Submit"
|
||||
},
|
||||
"scalePages": {
|
||||
"tags": "resize,modify,dimension,adapt",
|
||||
"title": "Adjust page-scale",
|
||||
"header": "Adjust page-scale",
|
||||
"pageSize": "Size of a page of the document.",
|
||||
"scaleFactor": "Zoom level (crop) of a page.",
|
||||
"submit": "Submit"
|
||||
},
|
||||
"pipeline": {
|
||||
"tags": "automate,sequence,scripted,batch-process",
|
||||
"title": "Pipeline"
|
||||
},
|
||||
"add-page-numbers": {
|
||||
"tags": "paginate,label,organize,index"
|
||||
},
|
||||
"auto-rename": {
|
||||
"tags": "auto-detect,header-based,organize,relabel",
|
||||
"title": "Auto Rename",
|
||||
"header": "Auto Rename PDF",
|
||||
"submit": "Auto Rename"
|
||||
},
|
||||
"adjust-contrast": {
|
||||
"tags": "color-correction,tune,modify,enhance"
|
||||
},
|
||||
"crop": {
|
||||
"tags": "trim,shrink,edit,shape",
|
||||
"title": "Crop",
|
||||
"header": "Crop Image",
|
||||
"submit": "Submit"
|
||||
},
|
||||
"autoSplitPDF": {
|
||||
"tags": "QR-based,separate,scan-segment,organize",
|
||||
"title": "Auto Split PDF",
|
||||
"header": "Auto Split PDF",
|
||||
"description": "Print, Insert, Scan, upload, and let us auto-separate your documents. No manual work sorting needed.",
|
||||
"selectText": {
|
||||
"1": "Print out some divider sheets from below (Black and white is fine).",
|
||||
"2": "Scan all your documents at once by inserting the divider sheet between them.",
|
||||
"3": "Upload the single large scanned PDF file and let Stirling PDF handle the rest.",
|
||||
"4": "Divider pages are automatically detected and removed, guaranteeing a neat final document."
|
||||
},
|
||||
"formPrompt": "Submit PDF containing Stirling-PDF Page dividers:",
|
||||
"duplexMode": "Duplex Mode (Front and back scanning)",
|
||||
"dividerDownload1": "Download 'Auto Splitter Divider (minimal).pdf'",
|
||||
"dividerDownload2": "Download 'Auto Splitter Divider (with instructions).pdf'",
|
||||
"submit": "Submit"
|
||||
},
|
||||
"sanitizePdf": {
|
||||
"tags": "clean,secure,safe,remove-threats"
|
||||
},
|
||||
"URLToPDF": {
|
||||
"tags": "web-capture,save-page,web-to-doc,archive",
|
||||
"title": "URL To PDF",
|
||||
"header": "URL To PDF",
|
||||
"submit": "Convert",
|
||||
"credit": "Uses WeasyPrint"
|
||||
},
|
||||
"HTMLToPDF": {
|
||||
"tags": "markup,web-content,transformation,convert",
|
||||
"title": "HTML To PDF",
|
||||
"header": "HTML To PDF",
|
||||
"help": "Accepts HTML files and ZIPs containing html/css/images etc required",
|
||||
"submit": "Convert",
|
||||
"credit": "Uses WeasyPrint"
|
||||
},
|
||||
"MarkdownToPDF": {
|
||||
"tags": "markup,web-content,transformation,convert",
|
||||
"title": "Markdown To PDF",
|
||||
"header": "Markdown To PDF",
|
||||
"submit": "Convert",
|
||||
"help": "Work in progress",
|
||||
"credit": "Uses WeasyPrint"
|
||||
},
|
||||
"getPdfInfo": {
|
||||
"tags": "infomation,data,stats,statistics",
|
||||
"title": "Get Info on PDF",
|
||||
"header": "Get Info on PDF",
|
||||
"submit": "Get Info",
|
||||
"downloadJson": "Download JSON"
|
||||
},
|
||||
"extractPage": {
|
||||
"tags": "extract"
|
||||
},
|
||||
"PdfToSinglePage": {
|
||||
"tags": "single page"
|
||||
},
|
||||
"showJS": {
|
||||
"tags": "Redact,Hide,black out,black,marker,hidden",
|
||||
"title": "Show Javascript",
|
||||
"header": "Show Javascript",
|
||||
"downloadJS": "Download Javascript",
|
||||
"submit": "Show"
|
||||
},
|
||||
"login": {
|
||||
"title": "Sign in",
|
||||
"signin": "Sign in",
|
||||
"rememberme": "Remember me",
|
||||
"invalid": "Invalid username or password.",
|
||||
"locked": "Your account has been locked.",
|
||||
"signinTitle": "Please sign in"
|
||||
},
|
||||
"autoRedact": {
|
||||
"title": "Auto Redact",
|
||||
"header": "Auto Redact",
|
||||
"colorLabel": "Colour",
|
||||
"textsToRedactLabel": "Text to Redact (line-separated)",
|
||||
"textsToRedactPlaceholder": "e.g. \\nConfidential \\nTop-Secret",
|
||||
"useRegexLabel": "Use Regex",
|
||||
"wholeWordSearchLabel": "Whole Word Search",
|
||||
"customPaddingLabel": "Custom Extra Padding",
|
||||
"convertPDFToImageLabel": "Convert PDF to PDF-Image (Used to remove text behind the box)",
|
||||
"submitButton": "Submit"
|
||||
},
|
||||
"pdfToSinglePage": {
|
||||
"title": "PDF To Single Page",
|
||||
"header": "PDF To Single Page",
|
||||
"submit": "Convert To Single Page"
|
||||
},
|
||||
"pageExtracter": {
|
||||
"title": "Extract Pages",
|
||||
"header": "Extract Pages",
|
||||
"submit": "Extract"
|
||||
},
|
||||
"sanitizePDF": {
|
||||
"title": "Sanitize PDF",
|
||||
"header": "Sanitize a PDF file",
|
||||
"selectText": {
|
||||
"1": "Remove JavaScript actions",
|
||||
"2": "Remove embedded files",
|
||||
"3": "Remove metadata",
|
||||
"4": "Remove links",
|
||||
"5": "Remove fonts"
|
||||
},
|
||||
"submit": "Sanitize PDF"
|
||||
},
|
||||
"addPageNumbers": {
|
||||
"title": "Add Page Numbers",
|
||||
"header": "Add Page Numbers",
|
||||
"selectText": {
|
||||
"1": "Select PDF file:",
|
||||
"2": "Margin Size",
|
||||
"3": "Position",
|
||||
"4": "Starting Number",
|
||||
"5": "Pages to Number",
|
||||
"6": "Custom Text"
|
||||
},
|
||||
"customTextDesc": "Custom Text",
|
||||
"numberPagesDesc": "Which pages to number, default 'all', also accepts 1-5 or 2,5,9 etc",
|
||||
"customNumberDesc": "Defaults to {n}, also accepts 'Page {n} of {total}', 'Text-{n}', '{filename}-{n}",
|
||||
"submit": "Add Page Numbers"
|
||||
},
|
||||
"adjustContrast": {
|
||||
"title": "Adjust Contrast",
|
||||
"header": "Adjust Contrast",
|
||||
"contrast": "Contrast:",
|
||||
"brightness": "Brightness:",
|
||||
"saturation": "Saturation:",
|
||||
"download": "Download"
|
||||
},
|
||||
"compress": {
|
||||
"title": "Compress",
|
||||
"header": "Compress PDF",
|
||||
"credit": "This service uses Ghostscript for PDF Compress/Optimisation.",
|
||||
"selectText": {
|
||||
"1": "Manual Mode - From 1 to 4",
|
||||
"2": "Optimization level:",
|
||||
"3": "4 (Terrible for text images)",
|
||||
"4": "Auto mode - Auto adjusts quality to get PDF to exact size",
|
||||
"5": "Expected PDF Size (e.g. 25MB, 10.8MB, 25KB) "
|
||||
},
|
||||
"submit": "Compress"
|
||||
},
|
||||
"pageRemover": {
|
||||
"title": "Page Remover",
|
||||
"header": "PDF Page remover",
|
||||
"pagesToDelete": "Pages to delete (Enter a comma-separated list of page numbers) :",
|
||||
"submit": "Delete Pages"
|
||||
},
|
||||
"imageToPDF": {
|
||||
"title": "Image to PDF",
|
||||
"header": "Image to PDF",
|
||||
"submit": "Convert",
|
||||
"selectLabel": "Image Fit Options",
|
||||
"fillPage": "Fill Page",
|
||||
"fitDocumentToImage": "Fit Page to Image",
|
||||
"maintainAspectRatio": "Maintain Aspect Ratios",
|
||||
"selectText": {
|
||||
"2": "Auto rotate PDF",
|
||||
"3": "Multi file logic (Only enabled if working with multiple images)",
|
||||
"4": "Merge into single PDF",
|
||||
"5": "Convert to separate PDFs"
|
||||
}
|
||||
},
|
||||
"changeMetadata": {
|
||||
"title": "Title:",
|
||||
"header": "Change Metadata",
|
||||
"selectText": {
|
||||
"1": "Please edit the variables you wish to change",
|
||||
"2": "Delete all metadata",
|
||||
"3": "Show Custom Metadata:",
|
||||
"4": "Other Metadata:",
|
||||
"5": "Add Custom Metadata Entry"
|
||||
},
|
||||
"author": "Author:",
|
||||
"creationDate": "Creation Date (yyyy/MM/dd HH:mm:ss):",
|
||||
"creator": "Creator:",
|
||||
"keywords": "Keywords:",
|
||||
"modDate": "Modification Date (yyyy/MM/dd HH:mm:ss):",
|
||||
"producer": "Producer:",
|
||||
"subject": "Subject:",
|
||||
"trapped": "Trapped:",
|
||||
"submit": "Change"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,13 @@ import ReactDOM from "react-dom/client";
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
|
||||
import App from "./App";
|
||||
import "./index.css";
|
||||
import "./root.css";
|
||||
import 'react-material-symbols/rounded';
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<React.StrictMode>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
</React.StrictMode>
|
||||
<React.StrictMode>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
</React.StrictMode>
|
||||
);
|
||||
@@ -1,10 +0,0 @@
|
||||
|
||||
function About() {
|
||||
return (
|
||||
<div>
|
||||
<h2>About</h2>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default About;
|
||||
13
client-tauri/src/pages/Auth/Login.tsx
Normal file
13
client-tauri/src/pages/Auth/Login.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
// TODO: Store user info in localstorage. Session cookie will be set automatically
|
||||
|
||||
// TODO: Check if user login is enabled on this server
|
||||
|
||||
import styles from './Auth.module.css';
|
||||
|
||||
function Login() {
|
||||
return (
|
||||
"Login Test Page"
|
||||
);
|
||||
}
|
||||
|
||||
export default Login;
|
||||
13
client-tauri/src/pages/Auth/Logout.tsx
Normal file
13
client-tauri/src/pages/Auth/Logout.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
// TODO: Delete user info in localstorage. Send request to logout endpoint -> session cookie will be removed automatically.
|
||||
|
||||
// TODO: Check if user login is enabled on this server
|
||||
|
||||
import styles from './Auth.module.css';
|
||||
|
||||
function Logout() {
|
||||
return (
|
||||
"Logout Test Page"
|
||||
);
|
||||
}
|
||||
|
||||
export default Logout;
|
||||
13
client-tauri/src/pages/Auth/Register.tsx
Normal file
13
client-tauri/src/pages/Auth/Register.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
// TODO: Register user and login in the same request.
|
||||
|
||||
// TODO: Check if user registration & login is enabled on this server
|
||||
|
||||
import styles from './Auth.module.css';
|
||||
|
||||
function Register() {
|
||||
return (
|
||||
"Register Test Page"
|
||||
);
|
||||
}
|
||||
|
||||
export default Register;
|
||||
@@ -1,49 +0,0 @@
|
||||
|
||||
function Dashboard() {
|
||||
return (
|
||||
<div>
|
||||
<h2>Dashboard</h2>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis aliquet felis in ornare molestie. Quisque et dolor gravida, vulputate libero ultricies, suscipit diam. Pellentesque semper eget purus et rutrum. Duis fringilla elementum tellus, ut egestas nisi ultrices sed. Fusce id elit ipsum. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nulla facilisi. Duis varius, orci vel tempor cursus, elit tellus interdum sem, at vulputate lorem ex et dolor. Vestibulum purus mauris, consequat viverra gravida eget, fermentum a lacus. Phasellus eu varius dolor. Etiam a vulputate sapien. Etiam pulvinar, neque eu elementum imperdiet, nibh ex lobortis magna, ut varius lectus ante tristique massa.
|
||||
|
||||
Nullam quis porttitor sapien. Suspendisse dictum enim vitae tristique aliquet. Nunc imperdiet pellentesque quam, sit amet luctus dui dignissim non. Vivamus eleifend sagittis mauris, at imperdiet nisl. Morbi rutrum magna ut tortor euismod efficitur. Pellentesque quis tortor consectetur, lobortis turpis eget, tincidunt turpis. Nulla consectetur massa ex. Donec lectus purus, interdum sit amet sapien eu, maximus dapibus diam. Suspendisse potenti. In lacinia augue massa, et vulputate eros convallis id. Suspendisse bibendum sagittis posuere. Integer ullamcorper odio eget risus venenatis, non mollis arcu lacinia. Vestibulum feugiat arcu elit, eu varius enim fermentum vitae.
|
||||
|
||||
Morbi rutrum metus magna, ac tempor enim posuere sit amet. Vivamus laoreet, ligula a maximus mattis, eros justo ultrices libero, eget congue enim mi vel massa. Nunc finibus tempor lacus, ac condimentum neque vehicula sit amet. Maecenas vestibulum, eros ut fringilla interdum, nisi metus vestibulum libero, efficitur lacinia massa risus et orci. Nulla orci magna, efficitur a iaculis nec, consequat eu lorem. Maecenas faucibus, diam ut vehicula tincidunt, nulla ipsum dictum magna, quis lobortis felis lacus ac felis. Suspendisse a luctus nunc. Aliquam eget nisi non libero gravida gravida. Etiam massa metus, posuere vel dui eu, malesuada aliquam purus. Maecenas sed sagittis sapien. Integer vel posuere nunc, sit amet venenatis mi.
|
||||
|
||||
Donec vitae ipsum ut velit bibendum ultricies et ut est. Maecenas ac felis commodo, hendrerit sapien ut, molestie sapien. Sed rhoncus dui ut porta volutpat. Fusce in arcu id leo dignissim dignissim. Aenean pharetra ullamcorper tristique. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris convallis ultrices ante pellentesque pellentesque. Proin sed efficitur neque, ut sollicitudin neque. Sed sollicitudin dui diam, vitae fringilla eros bibendum vitae. Duis in augue nec enim pharetra lacinia nec quis nisi. Suspendisse pellentesque et quam a volutpat. Integer consectetur, tellus non feugiat sollicitudin, nibh nisl fringilla felis, at imperdiet turpis orci ut ex. Nam malesuada diam turpis. Phasellus blandit sodales suscipit. Sed condimentum placerat mi blandit ultrices. Integer egestas eleifend blandit.
|
||||
|
||||
Morbi massa sem, efficitur dapibus sapien quis, mollis auctor lorem. Maecenas eget fringilla sem, vitae scelerisque tellus. Nulla orci ante, consequat interdum ornare sit amet, finibus sed lorem. Nunc vulputate ante placerat, porttitor dui sit amet, elementum libero. Maecenas hendrerit, neque et iaculis tristique, arcu felis porta libero, et luctus est enim a arcu. In blandit magna turpis, lobortis accumsan sem pellentesque a. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Ut dui urna, finibus eu tincidunt a, dignissim a metus.
|
||||
|
||||
Ut condimentum eros eu hendrerit aliquam. Vestibulum faucibus, ipsum et posuere tempor, ante sem cursus ante, non facilisis libero arcu ac nisi. Sed porttitor non mi nec tristique. Donec et ligula tincidunt, congue felis et, vehicula ipsum. Integer eget nunc in lectus volutpat ultrices. Praesent malesuada velit vel enim egestas, eu auctor mauris maximus. Proin pretium odio lacus, ac fermentum erat fermentum id.
|
||||
|
||||
Integer congue odio sit amet efficitur scelerisque. Nunc elit arcu, pulvinar vitae aliquam id, aliquet eget tortor. Donec ullamcorper condimentum libero, vel iaculis elit. Maecenas vestibulum fermentum tellus, auctor vestibulum leo volutpat et. Quisque ac mauris tristique, placerat augue et, cursus nunc. Sed nec lacinia lacus. Nam congue quam non nisl fermentum, nec lobortis nulla lobortis. Donec id neque a nibh tempus interdum in eget metus.
|
||||
|
||||
Sed sed ligula sapien. Phasellus non tempor mauris, ac tempor velit. Suspendisse potenti. Nullam tempus enim purus, ac hendrerit odio lobortis vitae. Curabitur sit amet facilisis nisi. In tristique porttitor sem et sollicitudin. Etiam ut tortor hendrerit tortor blandit porttitor. In pretium ex nec arcu scelerisque, id cursus tortor ultrices. Suspendisse potenti.
|
||||
|
||||
Ut dictum velit felis, vitae efficitur arcu varius lobortis. Etiam aliquet quam vel elementum pulvinar. Morbi ultricies nulla sit amet neque mattis, eget viverra elit laoreet. Vivamus elementum eros ipsum, vitae laoreet lacus accumsan at. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed vel turpis in arcu condimentum porta vel et eros. Maecenas luctus euismod mauris ac tincidunt. Nulla rutrum efficitur sollicitudin. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Donec et venenatis lorem. Sed malesuada tincidunt consequat. Phasellus vitae ex magna. Quisque ac hendrerit tellus, sed convallis velit. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Ut imperdiet tempor dui, et sodales leo malesuada eget. Curabitur at finibus neque, vel efficitur ante.
|
||||
|
||||
Suspendisse a pellentesque arcu. Mauris tempus posuere dui, vitae consectetur neque tristique ut. Duis imperdiet pulvinar lacus, ac porttitor diam. Praesent congue justo ut ex tincidunt, non pellentesque justo hendrerit. Etiam volutpat neque ut neque lacinia, quis dapibus tortor pulvinar. Etiam vestibulum tincidunt augue id lacinia. Nullam et fermentum ligula.
|
||||
|
||||
Donec aliquet egestas elementum. Proin volutpat massa eu libero cursus, ut tincidunt enim convallis. Fusce ut magna nec odio feugiat auctor. Donec id libero eget quam finibus vestibulum et nec lectus. Duis et mattis arcu. Nam sit amet dolor eget est consequat euismod sed egestas enim. Fusce scelerisque ligula ut imperdiet mollis.
|
||||
|
||||
Sed fermentum tellus feugiat, pretium tellus et, ullamcorper libero. Curabitur et justo quis odio efficitur euismod. Vivamus elementum sagittis odio euismod lobortis. Curabitur nec nunc eget ipsum malesuada gravida. Maecenas dignissim mauris sapien, quis malesuada ante condimentum in. In suscipit blandit turpis vitae congue. Aenean dignissim tempus finibus.
|
||||
|
||||
Donec lacinia sapien risus, quis efficitur sapien vestibulum eget. Ut vehicula est in scelerisque pretium. Maecenas feugiat tempor urna a lobortis. Duis ac libero quis massa sodales rutrum. Suspendisse fermentum pellentesque nibh, pulvinar rutrum purus tempus consectetur. Donec non elementum diam. Aliquam et justo vitae quam tincidunt molestie. Mauris nec arcu erat. Mauris a justo ipsum. Maecenas consectetur nunc nec lobortis lacinia. Integer quis enim risus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Aliquam consequat, sem quis gravida semper, mauris est pretium nunc, a malesuada erat dolor eget metus. Ut pretium consectetur consequat. Quisque non mollis purus, in facilisis eros.
|
||||
|
||||
Sed aliquam nunc id lorem gravida, non sagittis ligula ultrices. Ut vel risus congue, aliquet lacus eget, ullamcorper arcu. Morbi ullamcorper scelerisque risus eu molestie. Curabitur non dapibus risus. In in libero massa. Etiam sed mi ultrices, lacinia justo nec, gravida est. Sed vehicula sollicitudin ullamcorper. Suspendisse potenti. Etiam non metus malesuada, dictum risus venenatis, fringilla nunc.
|
||||
|
||||
Ut vel ante volutpat justo luctus sollicitudin. Quisque sit amet sagittis velit, quis ultrices elit. Nam euismod, enim vel suscipit pharetra, nisl quam porta nisi, ac mollis eros ante ut lectus. Ut sagittis ex eget diam efficitur commodo. Suspendisse mattis eros ligula, eu facilisis ipsum cursus ac. Nullam imperdiet id diam vel ultrices. Nullam nunc purus, aliquet ac ornare at, posuere id lacus. Sed ex leo, sollicitudin ac velit id, pulvinar lacinia nunc. Nullam molestie faucibus lectus, sed molestie sem vehicula et. Sed justo eros, ullamcorper eu hendrerit ac, vehicula at est. Morbi ut sem iaculis, ultricies lacus eu, tincidunt metus.
|
||||
|
||||
Nullam hendrerit consectetur pulvinar. Praesent id orci feugiat, congue libero sit amet, ultricies neque. Suspendisse magna elit, mattis in mauris eu, porta sollicitudin enim. Cras sagittis lacinia nunc, sed aliquet augue condimentum quis. Proin eu dui at libero pharetra finibus et ac sem. Aliquam euismod, enim eget elementum mattis, lorem tellus maximus nibh, vel malesuada felis nulla nec mi. Aliquam a neque ut ex dapibus accumsan. Donec vitae bibendum mi. Donec blandit ante at dui condimentum, et congue leo luctus. Sed sapien est, accumsan et tellus in, tincidunt vehicula sapien. Curabitur iaculis pharetra urna ac molestie. Proin in enim orci. Vestibulum scelerisque, risus ut tincidunt mollis, dolor mi pretium dui, in fermentum est sapien et lacus. Fusce tincidunt orci nulla, sed gravida massa facilisis sit amet. Proin eget finibus diam. Nunc vitae diam interdum, rhoncus nulla eu, pulvinar ex.
|
||||
|
||||
Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Vivamus ultrices pharetra magna eget malesuada. Ut vel mauris nibh. Integer maximus purus vitae ante elementum congue. Nam fringilla consectetur condimentum. Nunc pulvinar lobortis sem, sit amet egestas sem vulputate in. Curabitur eleifend mi vitae ipsum aliquam congue. Nullam lobortis aliquet euismod. Praesent augue lacus, dapibus sit amet venenatis sed, hendrerit dictum felis. Donec tristique, lectus nec cursus tincidunt, sem justo condimentum ligula, vel feugiat felis leo ut tortor. Donec sed est ac tortor pharetra pulvinar. Pellentesque scelerisque augue quis commodo congue. Etiam eget tempus sapien. Nullam ex ligula, venenatis sed felis vel, scelerisque fermentum nibh. In felis quam, vulputate ac dignissim ut, scelerisque et quam.
|
||||
|
||||
Proin faucibus efficitur sollicitudin. Curabitur pharetra lectus ut metus molestie, eget rutrum velit laoreet. Duis sit amet tellus sem. In pretium egestas massa eu pharetra. Vestibulum suscipit, nibh sit amet tincidunt feugiat, quam mauris scelerisque lorem, quis commodo ex libero in tellus. Donec tellus erat, tempor id fringilla sed, vehicula ac nibh. Curabitur nisi lacus, maximus id lorem vitae, faucibus faucibus massa. Nulla dictum molestie dolor, finibus commodo est lobortis id. Ut nec dapibus purus. Curabitur quis ligula tincidunt, hendrerit risus eu, tristique ipsum. Morbi neque est, pharetra ut pharetra non, semper id justo. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Pellentesque tempor erat elit, non viverra tortor malesuada eget. Aliquam et efficitur lorem, ac euismod dui.
|
||||
|
||||
Aenean tincidunt scelerisque ante non vestibulum. Curabitur eleifend ipsum sem, elementum ornare enim ornare eu. In molestie sodales mattis. Morbi ac posuere lorem. Aliquam in nisi ac ipsum euismod bibendum eget id urna. Quisque suscipit lectus non magna varius venenatis sed sit amet lectus. Nam leo nisl, imperdiet at lorem fringilla, lacinia bibendum lacus. Nunc auctor mauris at orci condimentum venenatis at non augue. Donec iaculis aliquam risus. Suspendisse vel massa leo.
|
||||
|
||||
Mauris sed est turpis. Nullam ut magna eu elit vehicula tempus. Sed mollis ultrices eleifend. Curabitur metus felis, sodales a turpis accumsan, ultricies feugiat arcu. Donec sit amet dui commodo, lacinia sem facilisis, lobortis urna. Donec cursus arcu ex, ac imperdiet lorem rutrum at. Curabitur faucibus erat in dolor placerat, vel blandit ligula eleifend. Morbi blandit nisl ut arcu semper consequat. Nulla malesuada convallis lectus a egestas. Sed volutpat metus vitae libero pulvinar, ut pretium magna malesuada.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Dashboard;
|
||||
@@ -1,9 +1,31 @@
|
||||
import { listOperatorNames } from "@stirling-pdf/shared-operations/src/workflow/operatorAccessor";
|
||||
import { OperatorCard } from "../components/OperatorCard";
|
||||
|
||||
|
||||
import styles from './Home.module.css';
|
||||
|
||||
function Home() {
|
||||
const operators = listOperatorNames();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Home</h2>
|
||||
</div>
|
||||
<div>
|
||||
<h1>
|
||||
Stirling PDF
|
||||
</h1>
|
||||
<h2>
|
||||
Your locally hosted one-stop-shop for all your PDF needs
|
||||
</h2>
|
||||
|
||||
{/**TODO: Search bar */}
|
||||
|
||||
<div className={styles.operator_container}>
|
||||
{
|
||||
operators.map((operator) => {
|
||||
return (<OperatorCard key={operator} operatorInternalName={operator}></OperatorCard>)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { Fragment } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
function NoMatch() {
|
||||
return (
|
||||
<div>
|
||||
<h2>Nothing to see here!</h2>
|
||||
<p>
|
||||
<Link to="/">Go to the home page 3</Link>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<Fragment>
|
||||
<h2>The Page you are trying to access does not exist.</h2>
|
||||
<p>
|
||||
<Link to="/">Go back home...</Link>
|
||||
</p>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
export default NoMatch;
|
||||
98
client-tauri/src/pages/Operators.tsx
Normal file
98
client-tauri/src/pages/Operators.tsx
Normal file
@@ -0,0 +1,98 @@
|
||||
import { Fragment, useEffect, useRef } from "react";
|
||||
|
||||
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 Operators() {
|
||||
const [schema, setSchema] = useState<any>(undefined); // TODO: Type as joi type
|
||||
|
||||
const location = useLocation();
|
||||
|
||||
const operatorInternalName = location.pathname.split("/")[2]; // /operators/<operatorInternalName>
|
||||
|
||||
useEffect(() => {
|
||||
getSchemaByName(operatorInternalName).then(schema => {
|
||||
if(schema) {
|
||||
setSchema(schema.schema);
|
||||
}
|
||||
});
|
||||
}, [location]);
|
||||
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
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 = inputRef.current?.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) {
|
||||
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 validationResults = schema.validate({input: inputs, values: action.values});
|
||||
|
||||
if(validationResults.error) {
|
||||
console.error({error: "Validation failed", details: validationResults.error.message}, validationResults.error.stack);
|
||||
}
|
||||
else {
|
||||
action.values = validationResults.value.values;
|
||||
const Operator = (await getOperatorByName(operatorInternalName))!;
|
||||
|
||||
const operation = new Operator(action);
|
||||
operation.run(validationResults.value.input, (progress) => {
|
||||
console.log("OperationProgress: " + progress.operationProgress, "CurFileProgress: " + progress.curFileProgress);
|
||||
}).then(async pdfFiles => {
|
||||
console.log("Result", pdfFiles);
|
||||
|
||||
for await (const pdfFile of (pdfFiles as PdfFile[])) {
|
||||
var blob = new Blob([await pdfFile.uint8Array], {type: "application/pdf"});
|
||||
var objectUrl = URL.createObjectURL(blob);
|
||||
window.open(objectUrl);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export default Operators;
|
||||
@@ -1,16 +0,0 @@
|
||||
|
||||
import { isLibreOfficeInstalled } from "../../utils/libre-office-utils";
|
||||
|
||||
const hasLibreOffice = await isLibreOfficeInstalled();
|
||||
console.log(hasLibreOffice)
|
||||
|
||||
function About() {
|
||||
return (
|
||||
<div>
|
||||
<h2>Convert to PDF</h2>
|
||||
{"hasLibreOffice: "+hasLibreOffice}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default About;
|
||||
5
client-tauri/src/pages/home.module.css
Normal file
5
client-tauri/src/pages/home.module.css
Normal file
@@ -0,0 +1,5 @@
|
||||
.operator_container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(15rem, 3fr));
|
||||
gap: 30px 30px;
|
||||
}
|
||||
151
client-tauri/src/root.css
Normal file
151
client-tauri/src/root.css
Normal file
@@ -0,0 +1,151 @@
|
||||
:root {
|
||||
--md-sys-color-primary: rgb(0 96 170);
|
||||
--md-sys-color-surface-tint: rgb(0 96 170);
|
||||
--md-sys-color-on-primary: rgb(255 255 255);
|
||||
--md-sys-color-primary-container: rgb(80 163 255);
|
||||
--md-sys-color-on-primary-container: rgb(0 20 43);
|
||||
--md-sys-color-secondary: rgb(65 96 136);
|
||||
--md-sys-color-on-secondary: rgb(255 255 255);
|
||||
--md-sys-color-secondary-container: rgb(188 215 255);
|
||||
--md-sys-color-on-secondary-container: rgb(32 65 103);
|
||||
--md-sys-color-tertiary: rgb(88 90 138);
|
||||
--md-sys-color-on-tertiary: rgb(255 255 255);
|
||||
--md-sys-color-tertiary-container: rgb(151 153 205);
|
||||
--md-sys-color-on-tertiary-container: rgb(7 9 55);
|
||||
--md-sys-color-error: rgb(186 26 26);
|
||||
--md-sys-color-on-error: rgb(255 255 255);
|
||||
--md-sys-color-error-container: rgb(255 218 214);
|
||||
--md-sys-color-on-error-container: rgb(65 0 2);
|
||||
--md-sys-color-background: rgb(248 249 255);
|
||||
--md-sys-color-on-background: rgb(24 28 34);
|
||||
--md-sys-color-surface: rgb(248 249 255);
|
||||
--md-sys-color-on-surface: rgb(24 28 34);
|
||||
--md-sys-color-surface-variant: rgb(220 227 241);
|
||||
--md-sys-color-on-surface-variant: rgb(64 71 83);
|
||||
--md-sys-color-outline: rgb(112 119 132);
|
||||
--md-sys-color-outline-variant: rgb(192 199 213);
|
||||
--md-sys-color-shadow: rgb(0 0 0);
|
||||
--md-sys-color-scrim: rgb(0 0 0);
|
||||
--md-sys-color-inverse-surface: rgb(45 49 55);
|
||||
--md-sys-color-inverse-on-surface: rgb(238 241 250);
|
||||
--md-sys-color-inverse-primary: rgb(162 201 255);
|
||||
--md-sys-color-primary-fixed: rgb(211 228 255);
|
||||
--md-sys-color-on-primary-fixed: rgb(0 28 56);
|
||||
--md-sys-color-primary-fixed-dim: rgb(162 201 255);
|
||||
--md-sys-color-on-primary-fixed-variant: rgb(0 72 130);
|
||||
--md-sys-color-secondary-fixed: rgb(211 228 255);
|
||||
--md-sys-color-on-secondary-fixed: rgb(0 28 56);
|
||||
--md-sys-color-secondary-fixed-dim: rgb(169 201 246);
|
||||
--md-sys-color-on-secondary-fixed-variant: rgb(40 72 111);
|
||||
--md-sys-color-tertiary-fixed: rgb(225 224 255);
|
||||
--md-sys-color-on-tertiary-fixed: rgb(20 22 66);
|
||||
--md-sys-color-tertiary-fixed-dim: rgb(193 194 248);
|
||||
--md-sys-color-on-tertiary-fixed-variant: rgb(64 67 112);
|
||||
--md-sys-color-surface-dim: rgb(215 218 227);
|
||||
--md-sys-color-surface-bright: rgb(248 249 255);
|
||||
--md-sys-color-surface-container-lowest: rgb(255 255 255);
|
||||
--md-sys-color-surface-container-low: rgb(241 243 253);
|
||||
--md-sys-color-surface-container: rgb(235 238 247);
|
||||
--md-sys-color-surface-container-high: rgb(229 232 241);
|
||||
--md-sys-color-surface-container-highest: rgb(223 226 235);
|
||||
--md-nav-section-color-opacity: 1;
|
||||
--md-nav-on-section-color-opacity: 1;
|
||||
--md-nav-section-color-sign: rgba(25, 101, 212, var(--md-nav-section-color-opacity));
|
||||
--md-nav-on-section-color-sign: rgba(255, 251, 254, var(--md-nav-on-section-color-opacity));
|
||||
--md-nav-section-color-organize: rgba(120, 130, 255, var(--md-nav-section-color-opacity));
|
||||
--md-nav-on-section-color-organize: rgba(255, 251, 254, var(--md-nav-on-section-color-opacity));
|
||||
--md-nav-section-color-convert: rgba(25, 177, 212, var(--md-nav-section-color-opacity));
|
||||
--md-nav-on-section-color-convert: rgba(255, 251, 254, var(--md-nav-on-section-color-opacity));
|
||||
--md-nav-section-color-security: rgba(255, 120, 146, var(--md-nav-section-color-opacity));
|
||||
--md-nav-on-section-color-security: rgba(255, 251, 254, var(--md-nav-on-section-color-opacity));
|
||||
--md-nav-section-color-other: rgba(72, 189, 84, var(--md-nav-section-color-opacity));
|
||||
--md-nav-on-section-color-other: rgba(255, 251, 254, var(--md-nav-on-section-color-opacity));
|
||||
--md-nav-section-color-advance: rgba(245, 84, 84, var(--md-nav-section-color-opacity));
|
||||
--md-nav-on-section-color-advance: rgba(255, 251, 254, var(--md-nav-on-section-color-opacity));
|
||||
--md-nav-section-color-image: rgba(212, 172, 25, var(--md-nav-section-color-opacity));
|
||||
--md-nav-on-section-color-image: rgba(255, 251, 254, var(--md-nav-on-section-color-opacity));
|
||||
--md-nav-section-color-word: rgba(61, 153, 245, var(--md-nav-section-color-opacity));
|
||||
--md-nav-on-section-color-word: rgba(255, 251, 254, var(--md-nav-on-section-color-opacity));
|
||||
--md-nav-section-color-ppt: rgba(255, 128, 0, var(--md-nav-section-color-opacity));
|
||||
--md-nav-on-section-color-ppt: rgba(255, 251, 254, var(--md-nav-on-section-color-opacity));
|
||||
|
||||
--bs-blue: #0d6efd;
|
||||
--bs-indigo: #6610f2;
|
||||
--bs-purple: #6f42c1;
|
||||
--bs-pink: #d63384;
|
||||
--bs-red: #dc3545;
|
||||
--bs-orange: #fd7e14;
|
||||
--bs-yellow: #ffc107;
|
||||
--bs-green: #198754;
|
||||
--bs-teal: #20c997;
|
||||
--bs-cyan: #0dcaf0;
|
||||
--bs-white: #fff;
|
||||
--bs-gray: #6c757d;
|
||||
--bs-gray-dark: #343a40;
|
||||
--bs-primary: #0d6efd;
|
||||
--bs-secondary: #6c757d;
|
||||
--bs-success: #198754;
|
||||
--bs-info: #0dcaf0;
|
||||
--bs-warning: #ffc107;
|
||||
--bs-danger: #dc3545;
|
||||
--bs-light: #f8f9fa;
|
||||
--bs-dark: #212529;
|
||||
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
|
||||
}
|
||||
|
||||
:where(html, .light-theme, .dark-theme), .tokens, :host {
|
||||
--md-sys-color-surface-1:
|
||||
color-mix(in srgb, var(--md-sys-color-primary) 13%, rgba(0, 0, 0, 0.05) 5%);
|
||||
--md-sys-color-surface-2:
|
||||
color-mix(in srgb, var(--md-sys-color-primary) 13%, rgba(0, 0, 0, 0.08) 5%);
|
||||
--md-sys-color-surface-3:
|
||||
color-mix(in srgb, var(--md-sys-color-primary) 13%, rgba(0, 0, 0, 0.11) 5%);
|
||||
--md-sys-color-surface-4:
|
||||
color-mix(in srgb, var(--md-sys-color-primary) 13%, rgba(0, 0, 0, 0.12) 5%);
|
||||
--md-sys-color-surface-5:
|
||||
color-mix(in srgb, var(--md-sys-color-primary) 13%, rgba(0, 0, 0, 0.14) 5%);
|
||||
--md-sys-icon-fill-0: 'FILL' 0, 'wght' 500;
|
||||
--md-sys-icon-fill-1: 'FILL' 1, 'wght' 500;
|
||||
--md-sys-state-hover-opacity:
|
||||
color-mix(in srgb, var(--md-sys-color-primary), rgba(0, 0, 0, 0) 80%);
|
||||
--md-sys-color-shadow: #000000;
|
||||
--md-elevation-shadow-color-rgb: 0, 0, 0;
|
||||
--md-elevation-shadow-color: var(--md-elevation-shadow-color-rgb);
|
||||
--md-sys-elevation-0: 0px 0px 0px 0px rgb(var(--md-elevation-shadow-color), 0.2), 0px 0px 0px 0px rgb(var(--md-elevation-shadow-color), 0.14), 0px 0px 0px 0px rgb(var(--md-elevation-shadow-color), 0.12);
|
||||
--md-sys-elevation-1: 0px 3px 1px -2px rgb(var(--md-elevation-shadow-color), 0.2), 0px 2px 2px 0px rgb(var(--md-elevation-shadow-color), 0.14), 0px 1px 5px 0px rgb(var(--md-elevation-shadow-color), 0.12);
|
||||
--md-sys-elevation-2: 0px 2px 4px -1px rgb(var(--md-elevation-shadow-color), 0.2), 0px 4px 5px 0px rgb(var(--md-elevation-shadow-color), 0.14), 0px 1px 10px 0px rgb(var(--md-elevation-shadow-color), 0.12);
|
||||
--md-sys-elevation-3: 0px 5px 5px -3px rgb(var(--md-elevation-shadow-color), 0.2), 0px 8px 10px 1px rgb(var(--md-elevation-shadow-color), 0.14), 0px 3px 14px 2px rgb(var(--md-elevation-shadow-color), 0.12);
|
||||
--md-sys-elevation-4: 0px 5px 5px -3px rgb(0, 0, 0 / 0.2), 0px 8px 10px 1px rgb(var(--md-elevation-shadow-color), 0.14), 0px 3px 14px 2px rgb(var(--md-elevation-shadow-color), 0.12);
|
||||
--md-sys-elevation-5: 0px 8px 10px -6px rgb(var(--md-elevation-shadow-color), 0.2), 0px 16px 24px 2px rgb(var(--md-elevation-shadow-color), 0.14), 0px 6px 30px 5px rgb(var(--md-elevation-shadow-color), 0.12);
|
||||
}
|
||||
|
||||
body, select, textarea {
|
||||
background-color: var(--md-sys-color-surface);
|
||||
color: var(--md-sys-color-on-surface);
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 10px auto;
|
||||
padding: 0 20px;
|
||||
font-family: var(--bs-font-sans-serif);
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
color: #212529;
|
||||
background-color: #fff;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
|
||||
max-width: 90%;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.material-symbols {
|
||||
vertical-align: top;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
|
||||
export function appendToFilename(inputPath: string, toAppend: string) {
|
||||
const parts = inputPath.split('.');
|
||||
const parts = inputPath.split(".");
|
||||
if (parts.length > 1) {
|
||||
parts[parts.length-2] = parts[parts.length-2] + toAppend;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
import { readBinaryFile, writeBinaryFile, removeDir, BaseDirectory } from '@tauri-apps/api/fs';
|
||||
import { PdfFile,RepresentationType } from '@stirling-pdf/shared-operations/src/wrappers/PdfFile'
|
||||
import { runShell } from './tauri-wrapper';
|
||||
import { readBinaryFile, writeBinaryFile, removeDir, BaseDirectory } from "@tauri-apps/api/fs";
|
||||
import { PdfFile,RepresentationType } from "@stirling-pdf/shared-operations/src/wrappers/PdfFile";
|
||||
import { runShell, isTauriAvailable } from "./tauri-wrapper";
|
||||
|
||||
export async function fileToPdf(byteArray: Uint8Array, filename: string): Promise<PdfFile> {
|
||||
const randUuid = crypto.randomUUID();
|
||||
@@ -18,9 +18,9 @@ export async function fileToPdf(byteArray: Uint8Array, filename: string): Promis
|
||||
}
|
||||
console.debug(`${stream}, ${randUuid}: ${message}`);
|
||||
});
|
||||
const lastMessage = messageList[messageList.length-1]
|
||||
const lastMessage = messageList[messageList.length-1];
|
||||
const outputFilePath = lastMessage.split(" -> ")[1].split(".pdf")[0]+".pdf";
|
||||
const outputFilePathSplit = outputFilePath.toString().split("[\\/]")
|
||||
const outputFilePathSplit = outputFilePath.toString().split("[\\/]");
|
||||
const outputFileName = outputFilePathSplit[outputFilePathSplit.length-1];
|
||||
const outputBytes = await readBinaryFile(outputFilePath);
|
||||
|
||||
@@ -30,6 +30,8 @@ export async function fileToPdf(byteArray: Uint8Array, filename: string): Promis
|
||||
}
|
||||
|
||||
export async function isLibreOfficeInstalled() {
|
||||
if (!isTauriAvailable()) return false;
|
||||
|
||||
const messageList: string[] = [];
|
||||
try {
|
||||
await runShell("libreoffice-version", ["--version"], (message, stream) => {
|
||||
@@ -40,7 +42,7 @@ export async function isLibreOfficeInstalled() {
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
console.log("messageList", messageList)
|
||||
console.log("messageList", messageList);
|
||||
const result = messageList[0].match("LibreOffice ([0-9]+\.){4}.*");
|
||||
return result ? true : false;
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
|
||||
import SharedOperations, { OperationsType } from '@stirling-pdf/shared-operations/src'
|
||||
import { ImposeParamsType } from '@stirling-pdf/shared-operations/src/functions/impose'
|
||||
import { PdfFile } from "@stirling-pdf/shared-operations/src/wrappers/PdfFile"
|
||||
|
||||
// Import injected libraries here!
|
||||
import * as pdfcpuWrapper from "@stirling-pdf/shared-operations/wasm/pdfcpu/pdfcpu-wrapper-browser.js";
|
||||
|
||||
async function impose(params: ImposeParamsType): Promise<PdfFile> {
|
||||
return SharedOperations.impose(params, pdfcpuWrapper);
|
||||
}
|
||||
|
||||
const toExport: OperationsType = {
|
||||
...SharedOperations,
|
||||
impose,
|
||||
}
|
||||
export default toExport;
|
||||
@@ -1,9 +1,9 @@
|
||||
|
||||
import { open, save } from '@tauri-apps/api/dialog';
|
||||
import { readBinaryFile, writeBinaryFile } from '@tauri-apps/api/fs';
|
||||
import { Command } from '@tauri-apps/api/shell'
|
||||
import { open, save } from "@tauri-apps/api/dialog";
|
||||
import { readBinaryFile, writeBinaryFile } from "@tauri-apps/api/fs";
|
||||
import { Command } from "@tauri-apps/api/shell";
|
||||
|
||||
export type TauriBrowserFile = {
|
||||
export interface TauriBrowserFile {
|
||||
name: string,
|
||||
relativePath?: string,
|
||||
data: Uint8Array,
|
||||
@@ -29,13 +29,13 @@ export function isTauriAvailable() {
|
||||
}
|
||||
|
||||
// [*] = Not available in browser
|
||||
type SelectFilesDialogOptions = {
|
||||
interface SelectFilesDialogOptions {
|
||||
defaultPath?: string, // [*] the default path to open the dialog on
|
||||
directory?: boolean, // should the dialog be a directory dialog
|
||||
filters?: Array<{ // list of file type filters
|
||||
filters?: { // list of file type filters
|
||||
name: string, // category name eg. 'Images'
|
||||
extensions: string[] // list of extensions eg ['png', 'jpeg', 'jpg']
|
||||
}>,
|
||||
}[],
|
||||
multiple?: boolean, // allow multiple selections
|
||||
recursive?: boolean, // [*] If directory is true, indicates that it will be read recursively later. Defines whether subdirectories will be allowed on the scope or not.
|
||||
title?: string // [*] the title of the dialog
|
||||
@@ -43,7 +43,7 @@ type SelectFilesDialogOptions = {
|
||||
export function openFiles(options: SelectFilesDialogOptions): Promise<TauriBrowserFile[] | null> {
|
||||
return new Promise(async (resolve) => {
|
||||
if (isTauriAvailable()) {
|
||||
var selected = await open(options);
|
||||
let selected = await open(options);
|
||||
if (!selected) {
|
||||
resolve(null);
|
||||
return;
|
||||
@@ -65,15 +65,15 @@ export function openFiles(options: SelectFilesDialogOptions): Promise<TauriBrows
|
||||
resolve(files);
|
||||
return;
|
||||
} else {
|
||||
var input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
const input = document.createElement("input");
|
||||
input.type = "file";
|
||||
if (options.directory) input.setAttribute("webkitdirectory", "");
|
||||
if (options.filters) input.setAttribute("accept", options.filters.flatMap(f => f.extensions).map(ext => "."+ext).join(", "));
|
||||
if (options.multiple) input.setAttribute("multiple", "");
|
||||
|
||||
input.onchange = async () => {
|
||||
if (input.files && input.files.length) {
|
||||
console.log("input.files", input.files)
|
||||
console.log("input.files", input.files);
|
||||
const files: TauriBrowserFile[] = [];
|
||||
for (const f of input.files) {
|
||||
const contents = new Uint8Array(await f.arrayBuffer());
|
||||
@@ -91,8 +91,8 @@ export function openFiles(options: SelectFilesDialogOptions): Promise<TauriBrows
|
||||
|
||||
// detect the user clicking cancel
|
||||
document.body.onfocus = () => {
|
||||
setTimeout(()=>resolve(null), 200); // the timeout is needed because 'document.body.onfocus' is called before 'input.onchange'
|
||||
}
|
||||
setTimeout(()=>{ resolve(null) }, 200); // the timeout is needed because 'document.body.onfocus' is called before 'input.onchange'
|
||||
};
|
||||
|
||||
input.click();
|
||||
}
|
||||
@@ -100,40 +100,40 @@ export function openFiles(options: SelectFilesDialogOptions): Promise<TauriBrows
|
||||
}
|
||||
|
||||
// [*] = Not available in browser
|
||||
type DownloadFilesDialogOptions = {
|
||||
interface DownloadFilesDialogOptions {
|
||||
defaultPath?: string, // the default path to open the dialog on
|
||||
filters?: Array<{ // [*] list of file type filters
|
||||
filters?: { // [*] list of file type filters
|
||||
name: string, // category name eg. 'Images'
|
||||
extensions: string[] // list of extensions eg ['png', 'jpeg', 'jpg']
|
||||
}>,
|
||||
}[],
|
||||
title?: string // [*] the title of the dialog
|
||||
}
|
||||
export async function downloadFile(fileData: Uint8Array, options: DownloadFilesDialogOptions): Promise<undefined> {
|
||||
if (isTauriAvailable()) {
|
||||
const pathToSave = await save(options);
|
||||
console.log("pathToSave", pathToSave)
|
||||
console.log("pathToSave", pathToSave);
|
||||
if (pathToSave) {
|
||||
await writeBinaryFile(pathToSave, fileData);
|
||||
}
|
||||
} else {
|
||||
const pdfBlob = new Blob([fileData], { type: 'application/pdf' });
|
||||
const pdfBlob = new Blob([fileData], { type: "application/pdf" });
|
||||
const url = URL.createObjectURL(pdfBlob);
|
||||
const downloadOption = localStorage.getItem('downloadOption');
|
||||
const downloadOption = localStorage.getItem("downloadOption");
|
||||
|
||||
// ensure filename is not a path
|
||||
const separator = options.defaultPath?.includes("\\") ? "\\" : "/";
|
||||
const filename = options.defaultPath?.split(separator).pop();
|
||||
const filenameToUse = filename ? filename : 'edited.pdf';
|
||||
const filenameToUse = filename ? filename : "edited.pdf";
|
||||
|
||||
if (downloadOption === 'sameWindow') {
|
||||
if (downloadOption === "sameWindow") {
|
||||
// Open the file in the same window
|
||||
window.location.href = url;
|
||||
} else if (downloadOption === 'newWindow') {
|
||||
} else if (downloadOption === "newWindow") {
|
||||
// Open the file in a new window
|
||||
window.open(url, '_blank');
|
||||
window.open(url, "_blank");
|
||||
} else {
|
||||
// Download the file
|
||||
const downloadLink = document.createElement('a');
|
||||
const downloadLink = document.createElement("a");
|
||||
downloadLink.href = url;
|
||||
downloadLink.download = filenameToUse;
|
||||
downloadLink.click();
|
||||
@@ -152,18 +152,18 @@ export function runShell(commandName: string, args: string[], callback: (message
|
||||
return new Promise(async (resolve, reject) => {
|
||||
|
||||
const comm = new Command(commandName, args);
|
||||
comm.on('close', data => {
|
||||
comm.on("close", data => {
|
||||
if (data.code === 0) {
|
||||
resolve();
|
||||
} else {
|
||||
reject(new Error(`Command failed with exit code ${data.code} and signal ${data.signal}`));
|
||||
}
|
||||
});
|
||||
comm.on('error', error => callback(error, "error"));
|
||||
comm.stdout.on('data', line => callback(line, "stdout"));
|
||||
comm.stderr.on('data', line => callback(line, "stderr"));
|
||||
comm.on("error", error => { callback(error, "error") });
|
||||
comm.stdout.on("data", line => { callback(line, "stdout") });
|
||||
comm.stderr.on("data", line => { callback(line, "stderr") });
|
||||
|
||||
const child = await comm.spawn();
|
||||
console.debug(`Started child process with pid: ${child.pid}`)
|
||||
console.debug(`Started child process with pid: ${child.pid}`);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -18,11 +18,12 @@
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
},
|
||||
"include": [
|
||||
"src",
|
||||
"declarations/*.d.ts"
|
||||
"declarations/*.d.ts",
|
||||
"../shared-operations/declarations/*.d.ts"
|
||||
],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
|
||||
@@ -1,29 +1,52 @@
|
||||
import { defineConfig } from "vite";
|
||||
import { nodePolyfills } from 'vite-plugin-node-polyfills'
|
||||
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/
|
||||
export default defineConfig(async () => ({
|
||||
plugins: [
|
||||
react(),
|
||||
topLevelAwait({
|
||||
// The export name of top-level await promise for each chunk module
|
||||
promiseExportName: "__tla",
|
||||
// The function to generate import names of top-level await promise in each chunk module
|
||||
promiseImportName: i => `__tla_${i}`
|
||||
}),
|
||||
],
|
||||
plugins: [
|
||||
// Thanks: https://stackoverflow.com/questions/74417822/how-can-i-use-buffer-process-in-vite-app
|
||||
nodePolyfills({
|
||||
include: [],
|
||||
|
||||
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
|
||||
//
|
||||
// 1. prevent vite from obscuring rust errors
|
||||
clearScreen: false,
|
||||
// 2. tauri expects a fixed port, fail if that port is not available
|
||||
server: {
|
||||
port: 1420,
|
||||
strictPort: true,
|
||||
},
|
||||
// 3. to make use of `TAURI_DEBUG` and other env variables
|
||||
// https://tauri.app/v1/api/config#buildconfig.beforedevcommand
|
||||
envPrefix: ["VITE_"],
|
||||
globals: {
|
||||
Buffer: true, // can also be 'build', 'dev', or false
|
||||
global: false,
|
||||
process: true,
|
||||
},
|
||||
// Whether to polyfill `node:` protocol imports.
|
||||
protocolImports: false,
|
||||
}),
|
||||
react(),
|
||||
topLevelAwait({
|
||||
// The export name of top-level await promise for each chunk module
|
||||
promiseExportName: "__tla",
|
||||
// The function to generate import names of top-level await promise in each chunk module
|
||||
promiseImportName: i => `__tla_${i}`
|
||||
}),
|
||||
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
|
||||
clearScreen: false,
|
||||
// 2. tauri expects a fixed port, fail if that port is not available
|
||||
server: {
|
||||
port: 1420,
|
||||
strictPort: true,
|
||||
},
|
||||
// 3. to make use of `TAURI_DEBUG` and other env variables
|
||||
// https://tauri.app/v1/api/config#buildconfig.beforedevcommand
|
||||
envPrefix: ["VITE_"],
|
||||
base: '/', // relative paths sadly don't work with react router dom sub-dirs for some reason...
|
||||
}));
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import express from 'express';
|
||||
const app = express();
|
||||
const PORT = 80;
|
||||
|
||||
// Server Frontend TODO: Make this typescript compatible
|
||||
app.use(express.static('../client-vanilla/public'));
|
||||
app.use(express.static('../shared-operations'));
|
||||
|
||||
// serve
|
||||
app.listen(PORT, function (err) {
|
||||
if (err) console.log(err);
|
||||
console.log(`http://localhost:${PORT}`);
|
||||
});
|
||||
@@ -1,167 +0,0 @@
|
||||
//download.js v4.2, by dandavis; 2008-2016. [MIT] see http://danml.com/download.html for tests/usage
|
||||
// v1 landed a FF+Chrome compat way of downloading strings to local un-named files, upgraded to use a hidden frame and optional mime
|
||||
// v2 added named files via a[download], msSaveBlob, IE (10+) support, and window.URL support for larger+faster saves than dataURLs
|
||||
// v3 added dataURL and Blob Input, bind-toggle arity, and legacy dataURL fallback was improved with force-download mime and base64 support. 3.1 improved safari handling.
|
||||
// v4 adds AMD/UMD, commonJS, and plain browser support
|
||||
// v4.1 adds url download capability via solo URL argument (same domain/CORS only)
|
||||
// v4.2 adds semantic variable names, long (over 2MB) dataURL support, and hidden by default temp anchors
|
||||
// https://github.com/rndme/download
|
||||
|
||||
(function (root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define([], factory);
|
||||
} else if (typeof exports === 'object') {
|
||||
// Node. Does not work with strict CommonJS, but
|
||||
// only CommonJS-like environments that support module.exports,
|
||||
// like Node.
|
||||
module.exports = factory();
|
||||
} else {
|
||||
// Browser globals (root is window)
|
||||
root.download = factory();
|
||||
}
|
||||
}(this, function () {
|
||||
|
||||
return function download(data, strFileName, strMimeType) {
|
||||
|
||||
var self = window, // this script is only for browsers anyway...
|
||||
defaultMime = "application/octet-stream", // this default mime also triggers iframe downloads
|
||||
mimeType = strMimeType || defaultMime,
|
||||
payload = data,
|
||||
url = !strFileName && !strMimeType && payload,
|
||||
anchor = document.createElement("a"),
|
||||
toString = function(a){return String(a);},
|
||||
myBlob = (self.Blob || self.MozBlob || self.WebKitBlob || toString),
|
||||
fileName = strFileName || "download",
|
||||
blob,
|
||||
reader;
|
||||
myBlob= myBlob.call ? myBlob.bind(self) : Blob ;
|
||||
|
||||
if(String(this)==="true"){ //reverse arguments, allowing download.bind(true, "text/xml", "export.xml") to act as a callback
|
||||
payload=[payload, mimeType];
|
||||
mimeType=payload[0];
|
||||
payload=payload[1];
|
||||
}
|
||||
|
||||
|
||||
if(url && url.length< 2048){ // if no filename and no mime, assume a url was passed as the only argument
|
||||
fileName = url.split("/").pop().split("?")[0];
|
||||
anchor.href = url; // assign href prop to temp anchor
|
||||
if(anchor.href.indexOf(url) !== -1){ // if the browser determines that it's a potentially valid url path:
|
||||
var ajax=new XMLHttpRequest();
|
||||
ajax.open( "GET", url, true);
|
||||
ajax.responseType = 'blob';
|
||||
ajax.onload= function(e){
|
||||
download(e.target.response, fileName, defaultMime);
|
||||
};
|
||||
setTimeout(function(){ ajax.send();}, 0); // allows setting custom ajax headers using the return:
|
||||
return ajax;
|
||||
} // end if valid url?
|
||||
} // end if url?
|
||||
|
||||
|
||||
//go ahead and download dataURLs right away
|
||||
if(/^data:([\w+-]+\/[\w+.-]+)?[,;]/.test(payload)){
|
||||
|
||||
if(payload.length > (1024*1024*1.999) && myBlob !== toString ){
|
||||
payload=dataUrlToBlob(payload);
|
||||
mimeType=payload.type || defaultMime;
|
||||
}else{
|
||||
return navigator.msSaveBlob ? // IE10 can't do a[download], only Blobs:
|
||||
navigator.msSaveBlob(dataUrlToBlob(payload), fileName) :
|
||||
saver(payload) ; // everyone else can save dataURLs un-processed
|
||||
}
|
||||
|
||||
}else{//not data url, is it a string with special needs?
|
||||
if(/([\x80-\xff])/.test(payload)){
|
||||
var i=0, tempUiArr= new Uint8Array(payload.length), mx=tempUiArr.length;
|
||||
for(i;i<mx;++i) tempUiArr[i]= payload.charCodeAt(i);
|
||||
payload=new myBlob([tempUiArr], {type: mimeType});
|
||||
}
|
||||
}
|
||||
blob = payload instanceof myBlob ?
|
||||
payload :
|
||||
new myBlob([payload], {type: mimeType}) ;
|
||||
|
||||
|
||||
function dataUrlToBlob(strUrl) {
|
||||
var parts= strUrl.split(/[:;,]/),
|
||||
type= parts[1],
|
||||
decoder= parts[2] == "base64" ? atob : decodeURIComponent,
|
||||
binData= decoder( parts.pop() ),
|
||||
mx= binData.length,
|
||||
i= 0,
|
||||
uiArr= new Uint8Array(mx);
|
||||
|
||||
for(i;i<mx;++i) uiArr[i]= binData.charCodeAt(i);
|
||||
|
||||
return new myBlob([uiArr], {type: type});
|
||||
}
|
||||
|
||||
function saver(url, winMode){
|
||||
|
||||
if ('download' in anchor) { //html5 A[download]
|
||||
anchor.href = url;
|
||||
anchor.setAttribute("download", fileName);
|
||||
anchor.className = "download-js-link";
|
||||
anchor.innerHTML = "downloading...";
|
||||
anchor.style.display = "none";
|
||||
document.body.appendChild(anchor);
|
||||
setTimeout(function() {
|
||||
anchor.click();
|
||||
document.body.removeChild(anchor);
|
||||
if(winMode===true){setTimeout(function(){ self.URL.revokeObjectURL(anchor.href);}, 250 );}
|
||||
}, 66);
|
||||
return true;
|
||||
}
|
||||
|
||||
// handle non-a[download] safari as best we can:
|
||||
if(/(Version)\/(\d+)\.(\d+)(?:\.(\d+))?.*Safari\//.test(navigator.userAgent)) {
|
||||
if(/^data:/.test(url)) url="data:"+url.replace(/^data:([\w\/\-\+]+)/, defaultMime);
|
||||
if(!window.open(url)){ // popup blocked, offer direct download:
|
||||
if(confirm("Displaying New Document\n\nUse Save As... to download, then click back to return to this page.")){ location.href=url; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//do iframe dataURL download (old ch+FF):
|
||||
var f = document.createElement("iframe");
|
||||
document.body.appendChild(f);
|
||||
|
||||
if(!winMode && /^data:/.test(url)){ // force a mime that will download:
|
||||
url="data:"+url.replace(/^data:([\w\/\-\+]+)/, defaultMime);
|
||||
}
|
||||
f.src=url;
|
||||
setTimeout(function(){ document.body.removeChild(f); }, 333);
|
||||
|
||||
}//end saver
|
||||
|
||||
|
||||
|
||||
|
||||
if (navigator.msSaveBlob) { // IE10+ : (has Blob, but not a[download] or URL)
|
||||
return navigator.msSaveBlob(blob, fileName);
|
||||
}
|
||||
|
||||
if(self.URL){ // simple fast and modern way using Blob and URL:
|
||||
saver(self.URL.createObjectURL(blob), true);
|
||||
}else{
|
||||
// handle non-Blob()+non-URL browsers:
|
||||
if(typeof blob === "string" || blob.constructor===toString ){
|
||||
try{
|
||||
return saver( "data:" + mimeType + ";base64," + self.btoa(blob) );
|
||||
}catch(y){
|
||||
return saver( "data:" + mimeType + "," + encodeURIComponent(blob) );
|
||||
}
|
||||
}
|
||||
|
||||
// Blob but not URL support:
|
||||
reader=new FileReader();
|
||||
reader.onload=function(e){
|
||||
saver(this.result);
|
||||
};
|
||||
reader.readAsDataURL(blob);
|
||||
}
|
||||
return true;
|
||||
}; /* end download() */
|
||||
}));
|
||||
File diff suppressed because it is too large
Load Diff
16
client-vanilla/public/dep/pdf-lib.min.js
vendored
16
client-vanilla/public/dep/pdf-lib.min.js
vendored
File diff suppressed because one or more lines are too long
9995
client-vanilla/public/dep/pdf.min.js
vendored
9995
client-vanilla/public/dep/pdf.min.js
vendored
File diff suppressed because it is too large
Load Diff
22
client-vanilla/public/dep/pdf.worker.min.js
vendored
22
client-vanilla/public/dep/pdf.worker.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -1,165 +0,0 @@
|
||||
// JSON Representation of this Node Tree:
|
||||
// https://discord.com/channels/1068636748814483718/1099390571493195898/1118192754103693483
|
||||
// https://cdn.discordapp.com/attachments/1099390571493195898/1118192753759764520/image.png?ex=6537dba7&is=652566a7&hm=dc46820ef7c34bc37424794966c5f66f93ba0e15a740742c364d47d31ea119a9&
|
||||
export const discordWorkflow = {
|
||||
outputOptions: {
|
||||
zip: false
|
||||
},
|
||||
operations: [
|
||||
{
|
||||
type: "extract",
|
||||
values: { "index": "1" },
|
||||
operations: [
|
||||
{
|
||||
type: "removeObjects",
|
||||
values: { "objectNames": "photo, josh" },
|
||||
operations: [
|
||||
{
|
||||
type: "wait",
|
||||
values: { "id": 1 }
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: "extract",
|
||||
values: { "index": "2-5" },
|
||||
operations: [
|
||||
{
|
||||
type: "fillField",
|
||||
values: { "objectName": "name", "inputValue": "Josh" },
|
||||
operations: [
|
||||
{
|
||||
type: "wait",
|
||||
values: { "id": 1 }
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: "done", // This gets called when the other merge-ops with the same id finish.
|
||||
values: { "id": 1 },
|
||||
operations: [
|
||||
{
|
||||
type: "merge",
|
||||
values: {},
|
||||
operations: []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: "extractImages",
|
||||
values: {},
|
||||
operations: []
|
||||
},
|
||||
{
|
||||
type: "merge",
|
||||
values: {},
|
||||
operations: [
|
||||
{
|
||||
type: "transform",
|
||||
values: { "scale": "2x", "rotation": "90deg" },
|
||||
operations: []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// This will merge all input files into one giant document
|
||||
export const mergeOnly = {
|
||||
outputOptions: {
|
||||
zip: false
|
||||
},
|
||||
operations: [
|
||||
{
|
||||
type: "merge",
|
||||
values: {},
|
||||
operations: []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// Extract Pages and store them in a new document
|
||||
export const extractOnly = {
|
||||
outputOptions: {
|
||||
zip: false
|
||||
},
|
||||
operations: [
|
||||
{
|
||||
type: "extract",
|
||||
values: { "pageIndexes": [0, 2] },
|
||||
operations: []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// Split a document up into multiple documents
|
||||
export const splitOnly = {
|
||||
outputOptions: {
|
||||
zip: false
|
||||
},
|
||||
operations: [
|
||||
{
|
||||
type: "split",
|
||||
values: { "splitAfterPageArray": [2, 10] },
|
||||
operations: []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export const rotateOnly = {
|
||||
outputOptions: {
|
||||
zip: false
|
||||
},
|
||||
operations: [
|
||||
{
|
||||
type: "rotate",
|
||||
values: { "rotation": -90 },
|
||||
operations: []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export const imposeOnly = {
|
||||
outputOptions: {
|
||||
zip: false
|
||||
},
|
||||
operations: [
|
||||
{
|
||||
type: "impose",
|
||||
values: { "nup": 2, "format": "A4L" },
|
||||
operations: []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export const removeBlankPagesOnly = {
|
||||
outputOptions: {
|
||||
zip: false
|
||||
},
|
||||
operations: [
|
||||
{
|
||||
type: "removeBlankPages",
|
||||
values: { "whiteThreashold": 10 },
|
||||
operations: []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export const splitOnQR = {
|
||||
outputOptions: {
|
||||
zip: false
|
||||
},
|
||||
operations: [
|
||||
{
|
||||
type: "splitOn",
|
||||
values: {
|
||||
type: "QR_CODE"
|
||||
},
|
||||
operations: []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
// PDFLib gets imported via index.html script-tag
|
||||
// PDFJS as pdfjsLib via index.html script-tag
|
||||
// jsQR via index.html script-tag
|
||||
|
||||
import * as pdfcpuWraopper from "./wasm/pdfcpu/pdfcpu-wrapper-browser.js";
|
||||
|
||||
const OpenCV = { cv: cv } // OPENCV gets importet as cv via index.html script-tag
|
||||
|
||||
import { extractPages as dependantExtractPages } from "./functions/extractPages.js";
|
||||
import { impose as dependantImpose } from './functions/impose.js';
|
||||
import { mergePDFs as dependantMergePDFs } from './functions/mergePDFs.js';
|
||||
import { rotatePages as dependantRotatePages } from './functions/rotatePages.js';
|
||||
import { scaleContent as dependantScaleContent} from './functions/scaleContent.js';
|
||||
import { scalePage as dependantScalePage } from './functions/scalePage.js';
|
||||
import { splitPDF as dependantSplitPDF } from './functions/splitPDF.js';
|
||||
import { editMetadata as dependantEditMetadata} from "./functions/editMetadata.js";
|
||||
import { sortPagesWithPreset as dependantSortPagesWithPreset} from "./functions/organizePages.js";
|
||||
import { removeBlankPages as dependantRemoveBlankPages} from "./functions/removeBlankPages.js";
|
||||
import { splitOn as dependantSplitOn } from "./functions/splitOn.js";
|
||||
|
||||
// TODO: Dynamic loading & undloading of libraries.
|
||||
|
||||
export async function extractPages(snapshot, pageIndexes) {
|
||||
return dependantExtractPages(snapshot, pageIndexes, PDFLib);
|
||||
}
|
||||
|
||||
export async function impose(snapshot, nup, format) {
|
||||
return dependantImpose(snapshot, nup, format, pdfcpuWraopper);
|
||||
}
|
||||
|
||||
export async function mergePDFs(snapshots) {
|
||||
return dependantMergePDFs(snapshots, PDFLib);
|
||||
}
|
||||
|
||||
export async function rotatePages(snapshot, rotation) {
|
||||
return dependantRotatePages(snapshot, rotation, PDFLib);
|
||||
}
|
||||
|
||||
export async function scaleContent(snapshot, scaleFactor) {
|
||||
return dependantScaleContent(snapshot, scaleFactor, PDFLib);
|
||||
}
|
||||
|
||||
export async function scalePage(snapshot, pageSize) {
|
||||
return dependantScalePage(snapshot, pageSize, PDFLib);
|
||||
}
|
||||
|
||||
export async function splitPDF(snapshot, splitAfterPageArray) {
|
||||
return dependantSplitPDF(snapshot, splitAfterPageArray, PDFLib);
|
||||
}
|
||||
|
||||
export async function editMetadata(snapshot, metadata) {
|
||||
return dependantEditMetadata(snapshot, metadata, PDFLib);
|
||||
}
|
||||
|
||||
export async function sortPagesWithPreset(snapshot, operation) {
|
||||
return dependantSortPagesWithPreset(snapshot, operation, PDFLib);
|
||||
}
|
||||
|
||||
export async function removeBlankPages(snapshot, whiteThreashold) {
|
||||
return dependantRemoveBlankPages(snapshot, whiteThreashold, pdfjsLib, OpenCV, PDFLib);
|
||||
}
|
||||
|
||||
export async function splitOn(snapshot, type, whiteThreashold) {
|
||||
return dependantSplitOn(snapshot, type, whiteThreashold, pdfjsLib, OpenCV, PDFLib, jsQR);
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Document</title>
|
||||
|
||||
<script src="/dep/pdf-lib.min.js"></script>
|
||||
<script src="/dep/downloadjs_1.4.7.js"></script>
|
||||
<script src="/dep/pdf.min.js"></script>
|
||||
<script src="/dep/jsQR.js"></script>
|
||||
|
||||
<script src="/wasm/browserfs.min.js"></script>
|
||||
<script src="/wasm/opencv/opencv_3_4_custom_O3.js"></script>
|
||||
|
||||
<script src="index.js" type="module"></script>
|
||||
</head>
|
||||
<body>
|
||||
<input type="file" id="pdfFile" accept=".pdf" multiple>
|
||||
<br>
|
||||
|
||||
<br>
|
||||
<textarea name="workflow" id="workflow" cols="120" rows="20"></textarea>
|
||||
<br>
|
||||
<select id="pdfOptions">
|
||||
</select>
|
||||
<button id="loadButton">Load</button>
|
||||
<br>
|
||||
|
||||
<br>
|
||||
<button id="doneButton">Done</button>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,65 +0,0 @@
|
||||
import { scaleContent } from "./functions/scaleContent.js";
|
||||
import { scalePage, PageSize } from "./functions/scalePage.js";
|
||||
import * as exampleWorkflows from "./exampleWorkflows.js";
|
||||
import { traverseOperations } from "./traverseOperations.js";
|
||||
import * as Functions from "./functions.js";
|
||||
|
||||
(async () => {
|
||||
const workflowField = document.getElementById("workflow");
|
||||
|
||||
const dropdown = document.getElementById("pdfOptions");
|
||||
// Clear existing options (if any)
|
||||
dropdown.innerHTML = '';
|
||||
|
||||
// Iterate over the keys of the object and create an option for each key
|
||||
for (const key in exampleWorkflows) {
|
||||
const option = document.createElement('option');
|
||||
option.value = key;
|
||||
option.text = key;
|
||||
dropdown.appendChild(option);
|
||||
}
|
||||
|
||||
const loadButton = document.getElementById("loadButton");
|
||||
loadButton.addEventListener("click", (e) => {
|
||||
workflowField.value = JSON.stringify(exampleWorkflows[dropdown.value], null, 2);
|
||||
});
|
||||
loadButton.click();
|
||||
|
||||
const pdfFileInput = document.getElementById('pdfFile');
|
||||
const doneButton = document.getElementById("doneButton");
|
||||
|
||||
doneButton.addEventListener('click', async (e) => {
|
||||
console.log("Starting...");
|
||||
|
||||
const files = Array.from(pdfFileInput.files);
|
||||
const inputs = await Promise.all(files.map(async file => {
|
||||
return {
|
||||
originalFileName: file.name.replace(/\.[^/.]+$/, ""),
|
||||
fileName: file.name.replace(/\.[^/.]+$/, ""),
|
||||
buffer: new Uint8Array(await file.arrayBuffer())
|
||||
}
|
||||
}));
|
||||
console.log(inputs);
|
||||
|
||||
const workflow = JSON.parse(workflowField.value);
|
||||
console.log(workflow);
|
||||
const traverse = traverseOperations(workflow.operations, inputs, Functions);
|
||||
|
||||
let pdfResults;
|
||||
let iteration;
|
||||
while (true) {
|
||||
iteration = await traverse.next();
|
||||
if (iteration.done) {
|
||||
pdfResults = iteration.value;
|
||||
console.log(`data: processing done\n\n`);
|
||||
break;
|
||||
}
|
||||
console.log(`data: ${iteration.value}\n\n`);
|
||||
}
|
||||
|
||||
// TODO: Zip if wanted
|
||||
pdfResults.forEach(result => {
|
||||
download(result.buffer, result.fileName, "application/pdf");
|
||||
});
|
||||
});
|
||||
})();
|
||||
14193
package-lock.json
generated
14193
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
17
package.json
17
package.json
@@ -2,19 +2,24 @@
|
||||
"name": "stirling-pdf",
|
||||
"private": "true",
|
||||
"workspaces": [
|
||||
"client-ionic",
|
||||
"client-tauri",
|
||||
"server-node",
|
||||
"shared-operations"
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "concurrently --names \"node,ionic\" -c \"red.bold,cyan.bold\" --kill-others \"npm run -w server-node dev\" \"npm run -w client-ionic dev\""
|
||||
"dev-all": "concurrently --names \"node,tauri\" -c \"red.bold,cyan.bold\" --kill-others \"npm run -w server-node dev\" \"npm run -w client-tauri dev\"",
|
||||
"update-all-dependencies": "npm i --workspace=stirling-pdf --workspace=server-node --workspace=client-tauri --workspace=shared-operation",
|
||||
"update-backend-dependencies": "npm i --workspace=server-node --workspace=shared-operation",
|
||||
"update-frontend-dependencies": "npm i --workspace=client-tauri --workspace=shared-operation"
|
||||
},
|
||||
"devDependencies": {
|
||||
"concurrently": "^8.2.2"
|
||||
"@typescript-eslint/eslint-plugin": "^6.17.0",
|
||||
"@typescript-eslint/parser": "^6.17.0",
|
||||
"concurrently": "^8.2.2",
|
||||
"eslint": "^8.56.0"
|
||||
},
|
||||
"engines" : {
|
||||
"npm" : ">=7.24.2"
|
||||
"engines": {
|
||||
"npm": ">=7.24.2"
|
||||
},
|
||||
"engineStrict" : true
|
||||
"engineStrict": true
|
||||
}
|
||||
|
||||
7
server-node/.env
Normal file
7
server-node/.env
Normal file
@@ -0,0 +1,7 @@
|
||||
VITE_JOBS_ENABLED=True
|
||||
VITE_JOBS_DIR="./jobs"
|
||||
|
||||
VITE_AUTH_ENABLED=True
|
||||
VITE_AUTH_SESSION_SECRET="default-secret"
|
||||
|
||||
VITE_SEQUELIZE_LOGGING=False
|
||||
19
server-node/Dockerfile
Normal file
19
server-node/Dockerfile
Normal file
@@ -0,0 +1,19 @@
|
||||
# Use an existing image as a base
|
||||
FROM node:22.2.0-alpine
|
||||
|
||||
RUN apk add --no-cache git
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV production
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm ci --legacy-peer-deps
|
||||
|
||||
COPY ./dist ./dist
|
||||
|
||||
RUN apk del git
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
CMD [ "node", "./dist/index.js" ]
|
||||
10
server-node/Makefile
Normal file
10
server-node/Makefile
Normal file
@@ -0,0 +1,10 @@
|
||||
vite-build:
|
||||
npx tsc
|
||||
npx vite build
|
||||
|
||||
vite-dockerize:
|
||||
make vite-build
|
||||
bash -c "cp ../package-lock.json ./"
|
||||
docker build . -t stirling-pdf-backend:latest
|
||||
bash -c "rm ./package-lock.json"
|
||||
docker image prune
|
||||
7
server-node/declarations/ExpressUser.d.ts
vendored
Normal file
7
server-node/declarations/ExpressUser.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
type UserModel = import("../src/auth/user/user-model").User;
|
||||
|
||||
declare namespace Express {
|
||||
interface User extends UserModel {
|
||||
|
||||
}
|
||||
}
|
||||
9
server-node/declarations/ProcessEnv.d.ts
vendored
Normal file
9
server-node/declarations/ProcessEnv.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
declare namespace NodeJS {
|
||||
export interface ProcessEnv {
|
||||
JOBS_ENABLED: "True" | "False",
|
||||
JOBS_DIR: string,
|
||||
AUTH_ENABLED: "True" | "False",
|
||||
AUTH_SESSION_SECRET: string,
|
||||
SEQUELIZE_LOGGING: "True" | "False"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"watch": ["src", "../shared-operations/src"],
|
||||
"ext": "ts,json",
|
||||
"ignore": ["src/**/*.spec.ts"],
|
||||
"exec": "node --trace-warnings --experimental-specifier-resolution=node --loader ts-node/esm ./src/index.ts"
|
||||
}
|
||||
@@ -1,34 +1,70 @@
|
||||
{
|
||||
"name": "@stirling-pdf/server-node",
|
||||
"version": "0.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "npx tsc",
|
||||
"start": "node dist/index.js",
|
||||
"dev": "nodemon"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@stirling-pdf/shared-operations": "*",
|
||||
"@wasmer/wasmfs": "^0.12.0",
|
||||
"archiver": "^6.0.1",
|
||||
"express": "^4.18.2",
|
||||
"express-fileupload": "^1.4.2",
|
||||
"joi": "^17.11.0",
|
||||
"jsqr": "^1.4.0",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"nodemon": "^3.0.1",
|
||||
"pdf-lib": "^1.17.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/multer": "^1.4.10",
|
||||
"ts-node-dev": "^2.0.0",
|
||||
"typescript": "^5.2.2"
|
||||
},
|
||||
"exports": "./dist/index.js",
|
||||
"type": "module"
|
||||
"name": "@stirling-pdf/server-node",
|
||||
"version": "0.0.0",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"exports": "./dist/index.js",
|
||||
"imports": {
|
||||
"#pdfcpu": "@stirling-pdf/shared-operations/src/wasm/pdfcpu/pdfcpu-wrapper.server.js"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"canvas": "^2.11.2"
|
||||
},
|
||||
"scripts": {
|
||||
"vite:dev": "vite",
|
||||
"vite:preview-prod": "make vite-build && node ./dist/index.js"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@rollup/plugin-alias": "^5.1.0",
|
||||
"@rollup/plugin-commonjs": "^25.0.7",
|
||||
"@rollup/plugin-json": "^6.1.0",
|
||||
"@stirling-tools/joi": "github:stirling-tools/joi",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/multer": "^1.4.10",
|
||||
"@wasmer/wasmfs": "^0.12.0",
|
||||
"archiver": "^6.0.1",
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.18.2",
|
||||
"express-fileupload": "^1.4.2",
|
||||
"express-session": "^1.18.0",
|
||||
"i18next": "^23.14.0",
|
||||
"i18next-resources-to-backend": "^1.2.1",
|
||||
"joi": "^17.11.0",
|
||||
"jsqr": "^1.4.0",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"passport": "^0.7.0",
|
||||
"passport-headerapikey": "^1.2.2",
|
||||
"passport-local": "^1.0.0",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"pdfjs-dist": "^4.5.136",
|
||||
"react-material-symbols": "^4.4.0",
|
||||
"rollup-plugin-copy": "^3.5.0",
|
||||
"rollup-plugin-dynamic-import-variables": "^1.1.0",
|
||||
"sequelize": "^6.37.3",
|
||||
"sqlite3": "^5.1.7",
|
||||
"toml": "^3.0.0",
|
||||
"tsconfig-paths": "^4.2.0",
|
||||
"vite": "^5.4.2",
|
||||
"vite-plugin-compile-time": "^0.2.1",
|
||||
"vite-plugin-dynamic-import": "^1.5.0",
|
||||
"vite-plugin-node-polyfills": "^0.22.0",
|
||||
"vite-plugin-top-level-await": "^1.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-dynamic-import-vars": "^2.1.2",
|
||||
"@rollup/plugin-run": "^3.0.2",
|
||||
"@rollup/plugin-typescript": "^11.1.6",
|
||||
"@types/archiver": "^6.0.2",
|
||||
"@types/express-session": "^1.18.0",
|
||||
"@types/passport-local": "^1.0.38",
|
||||
"copyfiles": "^2.4.1",
|
||||
"pkgroll": "^2.0.1",
|
||||
"rimraf": "^5.0.5",
|
||||
"ts-node-dev": "^2.0.0",
|
||||
"typescript": "^5.5.4",
|
||||
"vite-plugin-node": "^3.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
38
server-node/src/auth/apikey/apikey-controller.ts
Normal file
38
server-node/src/auth/apikey/apikey-controller.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { Error as SequelizeError } from "sequelize";
|
||||
import { User } from "../user/user-model";
|
||||
import { APIKey } from "./apikey-model";
|
||||
|
||||
export function findOne(params: {apikey?: string}, cb: (err: Error | null, apikey?: APIKey | undefined, info?: Object | undefined) => void): undefined {
|
||||
const query: any = params;
|
||||
|
||||
for (let key in query) {
|
||||
if (query[key] === undefined) {
|
||||
delete query[key];
|
||||
}
|
||||
}
|
||||
|
||||
if(Object.keys(query).length == 0) {
|
||||
cb(new Error("You need to provide at least one argument."), undefined)
|
||||
}
|
||||
|
||||
APIKey.findOne({
|
||||
where: query,
|
||||
include: APIKey.associations.User
|
||||
}).then(apikey => {
|
||||
if(apikey)
|
||||
cb(null, apikey);
|
||||
else
|
||||
cb(null, undefined, { message: "The requested apikey was not found."});
|
||||
}).catch(e =>
|
||||
cb(e, undefined)
|
||||
);
|
||||
}
|
||||
|
||||
export async function createAPIKey(user: User | undefined): Promise<APIKey | undefined> {
|
||||
if(!user) throw new Error("User was undefined");
|
||||
|
||||
const apikey = crypto.randomUUID(); // TODO: Is this secure enough?
|
||||
const apikeyEntry = await user.createAPIKey({ apikey: apikey });
|
||||
|
||||
return apikeyEntry;
|
||||
}
|
||||
17
server-node/src/auth/apikey/apikey-model.ts
Normal file
17
server-node/src/auth/apikey/apikey-model.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { User } from '../user/user-model';
|
||||
import {
|
||||
Model, InferAttributes, InferCreationAttributes, CreationOptional, NonAttribute, ForeignKey,
|
||||
} from 'sequelize';
|
||||
|
||||
export class APIKey extends Model<InferAttributes<APIKey>, InferCreationAttributes<APIKey>> {
|
||||
declare id: CreationOptional<number>;
|
||||
declare apikey: string;
|
||||
|
||||
declare UserId: ForeignKey<User['id']>;
|
||||
|
||||
// `User` is an eagerly-loaded association.
|
||||
declare User?: NonAttribute<User>;
|
||||
|
||||
declare createdAt: CreationOptional<Date>;
|
||||
declare updatedAt: CreationOptional<Date>;
|
||||
}
|
||||
22
server-node/src/auth/auth-controller.ts
Normal file
22
server-node/src/auth/auth-controller.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import passport from "passport";
|
||||
import session from "express-session";
|
||||
import { initialize } from "./passport-config";
|
||||
import auth from "../routes/auth/auth-controller";
|
||||
import { Express } from "express";
|
||||
|
||||
export function connect(app: Express) {
|
||||
app.use(session({
|
||||
secret: import.meta.env.VITE_SESSION_SECRET || "default-secret",
|
||||
resave: false,
|
||||
saveUninitialized: false
|
||||
}));
|
||||
|
||||
app.use(passport.initialize());
|
||||
app.use(passport.authenticate(['headerapikey', 'session'], {
|
||||
session: false, // Only set a session on the login request.
|
||||
}));
|
||||
|
||||
initialize(passport);
|
||||
|
||||
app.use("/auth", auth);
|
||||
}
|
||||
15
server-node/src/auth/authenticationMiddleware.ts
Normal file
15
server-node/src/auth/authenticationMiddleware.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
|
||||
export function isAuthorized(req: Request, res: Response, next: NextFunction) {
|
||||
if(import.meta.env.VITE_AUTH_ENABLED === "False" || req.user) {
|
||||
return next();
|
||||
}
|
||||
return res.status(403).json({"Error": "Authentication failed."});
|
||||
}
|
||||
|
||||
export function whenAuthIsEnabled(req: Request, res: Response, next: NextFunction) {
|
||||
if(import.meta.env.VITE_AUTH_ENABLED === "True") {
|
||||
return next();
|
||||
}
|
||||
return res.status(403).json({"Error": "Authentication is not enabled."});
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user