feat(rotation): implement rotation utilities and enhance angle input handling

This commit is contained in:
alam00000 committed 2026-05-09 14:21:07 +05:30
1 parent bef12f1d23
commit 7a6cd61286
4 files changed
+170 -51

No files matched your search

+42 -51
View File
@@ -9,6 +9,14 @@ import {
import { loadPdfWithPasswordPrompt } from '../utils/password-prompt.js';
import * as pdfjsLib from 'pdfjs-dist';
import { loadPdfDocument } from '../utils/load-pdf-document.js';
import {
ROTATION_MIN,
ROTATION_MAX,
ROTATION_STEP,
clampRotation,
roundToStep,
parseAngleInput,
} from '../utils/rotation-utils.js';
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
'pdfjs-dist/build/pdf.worker.min.mjs',
@@ -106,67 +114,51 @@ function createPageWrapper(
const controls = document.createElement('div');
controls.className = 'flex items-center justify-center gap-1 p-2 bg-gray-800';
const angleInput = document.createElement('input');
angleInput.type = 'number';
angleInput.step = ROTATION_STEP.toString();
angleInput.min = ROTATION_MIN.toString();
angleInput.max = ROTATION_MAX.toString();
angleInput.id = `page-angle-${pageIndex}`;
angleInput.value = pageState.rotations[pageIndex]?.toString() || '0';
angleInput.className =
'w-16 h-8 text-center bg-gray-700 border border-gray-600 text-white rounded text-xs';
const commitAngle = (normalize: boolean) => {
const angle = parseAngleInput(angleInput.value);
if (normalize) angleInput.value = angle.toString();
pageState.rotations[pageIndex] = angle;
canvasWrapper.style.transform = `rotate(${-angle}deg)`;
};
angleInput.addEventListener('input', () => commitAngle(false));
angleInput.addEventListener('change', () => commitAngle(true));
const decrementBtn = document.createElement('button');
decrementBtn.className =
'w-8 h-8 flex items-center justify-center bg-gray-700 hover:bg-gray-600 text-white rounded border border-gray-600 text-sm';
decrementBtn.textContent = '-';
decrementBtn.onclick = function (e) {
e.stopPropagation();
const input = document.getElementById(
`page-angle-${pageIndex}`
) as HTMLInputElement;
const current = parseFloat(input.value) || 0;
input.value = (Math.round((current - 1) * 10) / 10).toString();
const current = parseFloat(angleInput.value) || 0;
angleInput.value = clampRotation(roundToStep(current - 1)).toString();
commitAngle(true);
};
const angleInput = document.createElement('input');
angleInput.type = 'number';
angleInput.step = '0.1';
angleInput.id = `page-angle-${pageIndex}`;
angleInput.value = pageState.rotations[pageIndex]?.toString() || '0';
angleInput.className =
'w-16 h-8 text-center bg-gray-700 border border-gray-600 text-white rounded text-xs';
const incrementBtn = document.createElement('button');
incrementBtn.className =
'w-8 h-8 flex items-center justify-center bg-gray-700 hover:bg-gray-600 text-white rounded border border-gray-600 text-sm';
incrementBtn.textContent = '+';
incrementBtn.onclick = function (e) {
e.stopPropagation();
const input = document.getElementById(
`page-angle-${pageIndex}`
) as HTMLInputElement;
const current = parseFloat(input.value) || 0;
input.value = (Math.round((current + 1) * 10) / 10).toString();
const current = parseFloat(angleInput.value) || 0;
angleInput.value = clampRotation(roundToStep(current + 1)).toString();
commitAngle(true);
};
const applyBtn = document.createElement('button');
applyBtn.className =
'w-8 h-8 flex items-center justify-center bg-gray-700 hover:bg-gray-600 text-white rounded border border-gray-600';
applyBtn.innerHTML = '<i data-lucide="rotate-cw" class="w-4 h-4"></i>';
applyBtn.onclick = function (e) {
e.stopPropagation();
const input = document.getElementById(
`page-angle-${pageIndex}`
) as HTMLInputElement;
const parsed = parseFloat(input.value);
const angle = Number.isFinite(parsed) ? Math.round(parsed * 10) / 10 : 0;
input.value = angle.toString();
pageState.rotations[pageIndex] = angle;
const wrapper = container.querySelector(
'.thumbnail-wrapper'
) as HTMLElement;
if (wrapper) wrapper.style.transform = `rotate(${-angle}deg)`;
};
controls.append(decrementBtn, angleInput, incrementBtn, applyBtn);
controls.append(decrementBtn, angleInput, incrementBtn);
container.appendChild(controls);
// Re-create icons for the new element
setTimeout(function () {
createIcons({ icons });
}, 0);
return container;
}
@@ -281,10 +273,6 @@ async function applyRotations() {
const currentRotation = originalPage.getRotation().angle;
const totalRotation = currentRotation + rotation;
console.log(
`Page ${i}: rotation=${rotation}, currentRotation=${currentRotation}, totalRotation=${totalRotation}, applying=${-totalRotation}`
);
if (totalRotation % 90 === 0) {
const [copiedPage] = await newPdfDoc.copyPages(pageState.pdfDoc, [i]);
copiedPage.setRotation(degrees(totalRotation));
@@ -377,21 +365,24 @@ document.addEventListener('DOMContentLoaded', function () {
if (batchDecrement && batchAngleInput) {
batchDecrement.addEventListener('click', function () {
const current = parseFloat(batchAngleInput.value) || 0;
batchAngleInput.value = (Math.round((current - 1) * 10) / 10).toString();
batchAngleInput.value = clampRotation(
roundToStep(current - 1)
).toString();
});
}
if (batchIncrement && batchAngleInput) {
batchIncrement.addEventListener('click', function () {
const current = parseFloat(batchAngleInput.value) || 0;
batchAngleInput.value = (Math.round((current + 1) * 10) / 10).toString();
batchAngleInput.value = clampRotation(
roundToStep(current + 1)
).toString();
});
}
if (batchApply && batchAngleInput) {
batchApply.addEventListener('click', function () {
const parsed = parseFloat(batchAngleInput.value);
const angle = Number.isFinite(parsed) ? Math.round(parsed * 10) / 10 : 0;
const angle = parseAngleInput(batchAngleInput.value);
batchAngleInput.value = angle.toString();
for (let i = 0; i < pageState.rotations.length; i++) {
pageState.rotations[i] = angle;
+17
View File
@@ -0,0 +1,17 @@
export const ROTATION_MIN = -360;
export const ROTATION_MAX = 360;
export const ROTATION_STEP = 0.1;
export function clampRotation(value: number): number {
return Math.max(ROTATION_MIN, Math.min(ROTATION_MAX, value));
}
export function roundToStep(value: number): number {
return Math.round(value * 10) / 10;
}
export function parseAngleInput(raw: string, fallback = 0): number {
const parsed = parseFloat(raw);
if (!Number.isFinite(parsed)) return fallback;
return clampRotation(roundToStep(parsed));
}