diff --git a/frontend/src/components/shared/ButtonSelector.tsx b/frontend/src/components/shared/ButtonSelector.tsx index bc95134d6..a45936f6a 100644 --- a/frontend/src/components/shared/ButtonSelector.tsx +++ b/frontend/src/components/shared/ButtonSelector.tsx @@ -1,4 +1,5 @@ import { Button, Group, Stack, Text } from "@mantine/core"; +import FitText from "./FitText"; export interface ButtonOption { value: T; @@ -13,6 +14,8 @@ interface ButtonSelectorProps { label?: string; disabled?: boolean; fullWidth?: boolean; + buttonClassName?: string; + textClassName?: string; } const ButtonSelector = ({ @@ -22,6 +25,8 @@ const ButtonSelector = ({ label = undefined, disabled = false, fullWidth = true, + buttonClassName, + textClassName, }: ButtonSelectorProps) => { return ( @@ -41,6 +46,7 @@ const ButtonSelector = ({ color={value === option.value ? 'var(--color-primary-500)' : 'var(--text-muted)'} onClick={() => onChange(option.value)} disabled={disabled || option.disabled} + className={buttonClassName} style={{ flex: fullWidth ? 1 : undefined, height: 'auto', @@ -48,7 +54,13 @@ const ButtonSelector = ({ fontSize: 'var(--mantine-font-size-sm)' }} > - {option.label} + ))} diff --git a/frontend/src/components/tools/addStamp/StampPreview.module.css b/frontend/src/components/tools/addStamp/StampPreview.module.css index f7e5c5c3c..779d7bf1d 100644 --- a/frontend/src/components/tools/addStamp/StampPreview.module.css +++ b/frontend/src/components/tools/addStamp/StampPreview.module.css @@ -127,7 +127,9 @@ /* Information text container */ .informationContainer { background-color: var(--information-text-bg); - padding: 8px; + padding: 2px; + padding-left: 8px; + padding-right: 8px; border-radius: 10px; margin-top: 8px; margin-bottom: 8px; @@ -138,8 +140,8 @@ } .informationText { - font-size: 0.875rem; - font-weight: 500; + font-size: 0.75rem; + font-weight: 400; color: var(--information-text-color); } diff --git a/frontend/src/components/tools/addStamp/StampPreview.tsx b/frontend/src/components/tools/addStamp/StampPreview.tsx index 7d94ac20f..62eed20b4 100644 --- a/frontend/src/components/tools/addStamp/StampPreview.tsx +++ b/frontend/src/components/tools/addStamp/StampPreview.tsx @@ -110,6 +110,48 @@ export default function StampPreview({ parameters, onParameterChange, file, show ) ), [containerSize, parameters, imageMeta, pageSize, showQuickGrid, hoverTile, pageThumbnail]); + // Keep center fixed when scaling via slider (or any fontSize changes) + const prevDimsRef = useRef<{ fontSize: number; widthPx: number; heightPx: number; leftPx: number; bottomPx: number } | null>(null); + useEffect(() => { + const itemStyle = style.item as any; + if (!itemStyle || containerSize.width <= 0 || containerSize.height <= 0) return; + + const parse = (v: any) => parseFloat(String(v).replace('px', '')) || 0; + const leftPx = parse(itemStyle.left); + const bottomPx = parse(itemStyle.bottom); + const widthPx = parse(itemStyle.width); + const heightPx = parse(itemStyle.height); + + const prev = prevDimsRef.current; + const hasOverrides = parameters.overrideX >= 0 && parameters.overrideY >= 0; + const canAdjust = hasOverrides && !showQuickGrid; + if ( + prev && + canAdjust && + parameters.fontSize !== prev.fontSize && + prev.widthPx > 0 && + prev.heightPx > 0 && + widthPx > 0 && + heightPx > 0 + ) { + const centerX = prev.leftPx + prev.widthPx / 2; + const centerY = prev.bottomPx + prev.heightPx / 2; + const newLeftPx = centerX - widthPx / 2; + const newBottomPx = centerY - heightPx / 2; + + const widthPts = pageSize?.widthPts ?? 595.28; + const heightPts = pageSize?.heightPts ?? 841.89; + const scaleX = containerSize.width / widthPts; + const scaleY = containerSize.height / heightPts; + const newLeftPts = Math.max(0, Math.min(containerSize.width, newLeftPx)) / scaleX; + const newBottomPts = Math.max(0, Math.min(containerSize.height, newBottomPx)) / scaleY; + onParameterChange('overrideX', newLeftPts as any); + onParameterChange('overrideY', newBottomPts as any); + } + + prevDimsRef.current = { fontSize: parameters.fontSize, widthPx, heightPx, leftPx, bottomPx }; + }, [parameters.fontSize, style.item, containerSize, pageSize, showQuickGrid, parameters.overrideX, parameters.overrideY, onParameterChange]); + // Drag/resize/rotate interactions const draggingRef = useRef<{ type: 'move' | 'resize' | 'rotate'; startX: number; startY: number; initLeft: number; initBottom: number; initHeight: number; centerX: number; centerY: number } | null>(null); diff --git a/frontend/src/components/tools/addStamp/StampPreviewUtils.ts b/frontend/src/components/tools/addStamp/StampPreviewUtils.ts index 0cd6b8d76..17a4f1bfe 100644 --- a/frontend/src/components/tools/addStamp/StampPreviewUtils.ts +++ b/frontend/src/components/tools/addStamp/StampPreviewUtils.ts @@ -192,7 +192,7 @@ export function computeStampPreviewStyle( height: `${heightPx}px`, opacity: displayOpacity, transform: `rotate(${-parameters.rotation}deg)`, - transformOrigin: 'left bottom', + transformOrigin: 'center center', color: parameters.customColor, display: 'flex', flexDirection: 'column', diff --git a/frontend/src/components/tools/addStamp/useAddStampOperation.ts b/frontend/src/components/tools/addStamp/useAddStampOperation.ts index 1ff6b62c6..fe67e9925 100644 --- a/frontend/src/components/tools/addStamp/useAddStampOperation.ts +++ b/frontend/src/components/tools/addStamp/useAddStampOperation.ts @@ -9,9 +9,7 @@ export const buildAddStampFormData = (parameters: AddStampParameters, file: File formData.append('pageNumbers', parameters.pageNumbers); formData.append('customMargin', 'medium'); formData.append('position', String(parameters.position)); - const effectiveFontSize = parameters.stampType === 'text' - ? parameters.fontSize * 2.2 // upscale to match the preview size - : parameters.fontSize; + const effectiveFontSize = parameters.fontSize; formData.append('fontSize', String(effectiveFontSize)); formData.append('rotation', String(parameters.rotation)); formData.append('opacity', String(parameters.opacity / 100)); diff --git a/frontend/src/tools/AddStamp.tsx b/frontend/src/tools/AddStamp.tsx index 61645abc9..704b447cf 100644 --- a/frontend/src/tools/AddStamp.tsx +++ b/frontend/src/tools/AddStamp.tsx @@ -12,6 +12,7 @@ import LocalIcon from "../components/shared/LocalIcon"; import styles from "../components/tools/addStamp/StampPreview.module.css"; import { Tooltip } from "../components/shared/Tooltip"; import FitText from "../components/shared/FitText"; +import ButtonSelector from "../components/shared/ButtonSelector"; const AddStamp = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => { const { t } = useTranslation(); @@ -99,24 +100,17 @@ const AddStamp = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => {
{t('AddStampRequest.stampType', 'Stamp Type')} - - - - + params.updateParameter('stampType', v)} + options={[ + { value: 'text', label: t('watermark.type.1', 'Text') }, + { value: 'image', label: t('watermark.type.2', 'Image') }, + ]} + disabled={endpointLoading} + buttonClassName={styles.modeToggleButton} + textClassName={styles.modeToggleButtonText} + />
{params.parameters.stampType === 'text' && ( @@ -208,45 +202,31 @@ const AddStamp = ({ onPreviewFile, onComplete, onError }: BaseToolProps) => { {/* Mode toggle: Quick grid vs Custom drag - only show for image stamps */} {params.parameters.stampType === 'image' && ( - - - - + { + const isQuick = v === 'quick'; + setQuickPositionModeSelected(isQuick); + setCustomPositionModeSelected(!isQuick); + }} + options={[ + { value: 'quick', label: t('quickPosition', 'Quick Position') }, + { value: 'custom', label: t('customPosition', 'Custom Position') }, + ]} + disabled={endpointLoading} + buttonClassName={styles.modeToggleButton} + textClassName={styles.modeToggleButtonText} + /> )} {params.parameters.stampType === 'image' && customPositionModeSelected && (
- {t('customPosition', 'Drag the stamp to the desired location in the preview window.')} + {t('AddStampRequest.customPosition', 'Drag the stamp to the desired location in the preview window.')} +
+ )} + {params.parameters.stampType === 'image' && !customPositionModeSelected && ( +
+ {t('AddStampRequest.quickPosition', 'Select a position on the page to place the stamp.')}
)}