mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-17 11:45:05 +02:00
Restructure/frontend editor (#6404)
## Move editor under `frontend/editor/`
Pure restructure: `frontend/` becomes the workspace, `frontend/editor/`
holds
the PDF editor. 1775 file renames + 40 wiring edits. No logic changes.
### Why
`frontend/` is currently the editor — its `src/`, `public/`,
`src-tauri/`,
config files all sit at the root. Promoting `frontend/` to a
workspace and putting the editor in a sibling folder leaves room for
future
apps to drop in alongside it, sharing one `package.json` /
`node_modules` /
lint config / Storybook.
### What moves
frontend/
├── editor/ ← NEW: everything editor-specific
│ ├── src/ ← was frontend/src/
│ ├── public/ ← was frontend/public/
│ ├── src-tauri/ ← was frontend/src-tauri/
│ ├── index.html, vite.config.ts, vitest.config.ts, playwright.config.ts
│ ├── tsconfig*.json, tailwind.config.js, postcss.config.js
│ ├── scripts/
│ ├── .env, .env.desktop, .env.saas
│ └── DeveloperGuide.md
├── package.json, package-lock.json, node_modules/ ← workspace install
├── eslint.config.mjs, .prettierrc, .prettierignore ← shared tooling
├── .gitignore
└── README.md
### Wiring edits (40 files)
- `.taskfiles/frontend.yml`, `desktop.yml`, `e2e.yml`
- `build.gradle`, `app/core/build.gradle`
- `eslint.config.mjs`, `frontend/package.json`, `.gitignore`,
`.prettierignore`
- `docker/frontend/Dockerfile`
- 8 `.github/workflows/*.yml`, plus `.github/dependabot.yml`,
`.github/config/.files.yaml`, `.github/labeler-config-srvaroa.yml`
- `scripts/translations/**`
- Docs: `AGENTS.md`, `CLAUDE.md`, `ADDING_TOOLS.md`,
`DeveloperGuide.md`,
`WINDOWS_SIGNING.md`, `devGuide/HowToAddNewLanguage.md`,
`frontend/README.md`,
`frontend/editor/DeveloperGuide.md`
Plus 3 renamed + edited: `editor/vite.config.ts` (env path +
node_modules
walk-up), `editor/scripts/setup-env.mts` (renamed from `.ts` for
`import.meta.url`), `editor/scripts/build-provisioner.mjs` (resolve
src-tauri
relative to script).
### Verification
| Check | Result |
|---|---|
| `task frontend:typecheck:all` (6 variants) | exit 0 |
| `task frontend:lint` (eslint + dpdm) | exit 0 |
| `task frontend:format:check` | exit 0 |
| `task frontend:test` | 657 tests pass, 50 files |
| `task frontend:build:{core,proprietary,saas,desktop,prototypes}` | all
green |
| `task desktop:build` | full Tauri pipeline →
`Stirling-PDF_2.11.0_x64_en-US.msi` |
| `playwright test --list --project=stubbed` | 172 tests discovered |
`task desktop:build` exercises the heaviest path — Rust + WiX + MSI
bundle
against the moved `editor/src-tauri/`. If anything in the restructure
was
wrong it wouldn't have built.
### Test plan
- [ ] `frontend-validation.yml` green
- [ ] `e2e-stubbed.yml` green
- [ ] `tauri-build.yml` green on at least one platform
- [ ] `check_toml.yml` runs on a translation-touching PR
---------
Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
48027ee9d6
commit
0a50e765b7
@@ -0,0 +1,26 @@
|
||||
// Utility helpers to open the settings/config modal programmatically
|
||||
// and optionally navigate to a specific section (e.g., 'plan').
|
||||
|
||||
import type { NavKey } from "@app/components/shared/config/types";
|
||||
|
||||
export function openAppSettings(targetKey?: NavKey, notice?: string) {
|
||||
try {
|
||||
const detail: { key?: NavKey; notice?: string } = {};
|
||||
if (targetKey) detail.key = targetKey;
|
||||
if (notice) detail.notice = notice;
|
||||
// Ask the UI to open the App Config modal
|
||||
window.dispatchEvent(new CustomEvent("appConfig:open", { detail }));
|
||||
// If a specific section is requested, navigate there once modal mounts
|
||||
if (targetKey) {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("appConfig:navigate", { detail: { key: targetKey } }),
|
||||
);
|
||||
}
|
||||
} catch (_e) {
|
||||
// no-op on SSR or test environments
|
||||
}
|
||||
}
|
||||
|
||||
export function openPlanSettings(notice?: string) {
|
||||
openAppSettings("plan", notice);
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* Crops an image based on the provided pixel crop area using HTML5 Canvas API.
|
||||
* Returns a PNG blob ready for upload.
|
||||
*/
|
||||
|
||||
export interface Area {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a cropped image blob from the source image and crop area.
|
||||
*
|
||||
* @param imageSrc - Data URL or blob URL of the source image
|
||||
* @param pixelCrop - Pixel coordinates and dimensions of the crop area
|
||||
* @returns Promise that resolves to a PNG Blob of the cropped image
|
||||
*/
|
||||
export async function getCroppedImage(
|
||||
imageSrc: string,
|
||||
pixelCrop: Area,
|
||||
): Promise<Blob> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const image = new Image();
|
||||
|
||||
image.onload = () => {
|
||||
try {
|
||||
// Create canvas with crop dimensions
|
||||
const canvas = document.createElement("canvas");
|
||||
const ctx = canvas.getContext("2d");
|
||||
|
||||
if (!ctx) {
|
||||
reject(new Error("Failed to get canvas context"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Set canvas size to crop dimensions
|
||||
canvas.width = pixelCrop.width;
|
||||
canvas.height = pixelCrop.height;
|
||||
|
||||
// Draw the cropped region
|
||||
// drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh)
|
||||
// sx, sy: source x, y coordinates
|
||||
// sw, sh: source width, height
|
||||
// dx, dy: destination x, y coordinates (0, 0 for top-left)
|
||||
// dw, dh: destination width, height
|
||||
ctx.drawImage(
|
||||
image,
|
||||
pixelCrop.x,
|
||||
pixelCrop.y,
|
||||
pixelCrop.width,
|
||||
pixelCrop.height,
|
||||
0,
|
||||
0,
|
||||
pixelCrop.width,
|
||||
pixelCrop.height,
|
||||
);
|
||||
|
||||
// Convert canvas to PNG blob
|
||||
canvas.toBlob(
|
||||
(blob) => {
|
||||
if (!blob) {
|
||||
reject(new Error("Failed to create blob from canvas"));
|
||||
return;
|
||||
}
|
||||
resolve(blob);
|
||||
},
|
||||
"image/png",
|
||||
1.0, // Maximum quality
|
||||
);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
};
|
||||
|
||||
image.onerror = () => {
|
||||
reject(new Error("Failed to load image"));
|
||||
};
|
||||
|
||||
// Start loading the image
|
||||
image.src = imageSrc;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { URL_TO_TOOL_MAP } from "@app/utils/urlMapping";
|
||||
|
||||
const SUBPATH = import.meta.env.VITE_RUN_SUBPATH.replace(/^\/|\/$/g, ""); // "app" or ""
|
||||
|
||||
/**
|
||||
* Normalize pathname by stripping subpath prefix and trailing slashes
|
||||
*/
|
||||
export function normalizePath(pathname: string): string {
|
||||
// Ensure leading slash, strip subpath prefix if configured
|
||||
let p = pathname.startsWith("/") ? pathname : `/${pathname}`;
|
||||
if (SUBPATH && p.startsWith(`/${SUBPATH}/`)) {
|
||||
p = p.slice(SUBPATH.length + 1); // remove "/app"
|
||||
} else if (SUBPATH && p === `/${SUBPATH}`) {
|
||||
p = "/";
|
||||
}
|
||||
// Strip trailing slash except root
|
||||
if (p.length > 1 && p.endsWith("/")) p = p.slice(0, -1);
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if pathname is an auth route
|
||||
*/
|
||||
export function isAuthRoute(pathname: string): boolean {
|
||||
const p = normalizePath(pathname);
|
||||
return p === "/login" || p === "/signup" || p === "/auth/callback";
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if pathname is home route
|
||||
*/
|
||||
export function isHomeRoute(pathname: string): boolean {
|
||||
return normalizePath(pathname) === "/";
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if pathname is a tool route
|
||||
*/
|
||||
export function isToolRoute(pathname: string): boolean {
|
||||
const p = normalizePath(pathname);
|
||||
// direct match or try without trailing slash variants if your map uses them
|
||||
if (URL_TO_TOOL_MAP[p] !== undefined) return true;
|
||||
// Fallback: try adding/removing trailing slash
|
||||
if (URL_TO_TOOL_MAP[`${p}/`] !== undefined) return true;
|
||||
if (p.endsWith("/") && URL_TO_TOOL_MAP[p.slice(0, -1)] !== undefined)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
Reference in New Issue
Block a user