mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-08-26 06:09:23 +00:00
Fix split display on zoom
This commit is contained in:
parent
5025e06289
commit
d8f34769aa
@ -8,10 +8,6 @@ import java.time.LocalDateTime;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
=======
|
|
||||||
>>>>>>> origin
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
|
||||||
import org.springframework.scheduling.annotation.Scheduled;
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@ -33,10 +29,6 @@ public class KeyPairCleanupService {
|
|||||||
private final KeyPersistenceService keyPersistenceService;
|
private final KeyPersistenceService keyPersistenceService;
|
||||||
private final ApplicationProperties.Security.Jwt jwtProperties;
|
private final ApplicationProperties.Security.Jwt jwtProperties;
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
@Autowired
|
|
||||||
=======
|
|
||||||
>>>>>>> origin
|
|
||||||
public KeyPairCleanupService(
|
public KeyPairCleanupService(
|
||||||
KeyPersistenceService keyPersistenceService,
|
KeyPersistenceService keyPersistenceService,
|
||||||
ApplicationProperties applicationProperties) {
|
ApplicationProperties applicationProperties) {
|
||||||
|
@ -1,17 +1,11 @@
|
|||||||
package stirling.software.proprietary.security.service;
|
package stirling.software.proprietary.security.service;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
<<<<<<< HEAD
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
=======
|
|
||||||
import java.nio.file.DirectoryStream;
|
import java.nio.file.DirectoryStream;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.nio.file.StandardCopyOption;
|
import java.nio.file.StandardCopyOption;
|
||||||
>>>>>>> origin
|
|
||||||
import java.security.KeyFactory;
|
import java.security.KeyFactory;
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.security.KeyPairGenerator;
|
import java.security.KeyPairGenerator;
|
||||||
@ -28,10 +22,6 @@ import java.util.List;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
=======
|
|
||||||
>>>>>>> origin
|
|
||||||
import org.springframework.cache.Cache;
|
import org.springframework.cache.Cache;
|
||||||
import org.springframework.cache.CacheManager;
|
import org.springframework.cache.CacheManager;
|
||||||
import org.springframework.cache.annotation.CacheEvict;
|
import org.springframework.cache.annotation.CacheEvict;
|
||||||
@ -54,24 +44,10 @@ public class KeyPersistenceService implements KeyPersistenceServiceInterface {
|
|||||||
public static final String KEY_SUFFIX = ".key";
|
public static final String KEY_SUFFIX = ".key";
|
||||||
|
|
||||||
private final ApplicationProperties.Security.Jwt jwtProperties;
|
private final ApplicationProperties.Security.Jwt jwtProperties;
|
||||||
<<<<<<< HEAD
|
|
||||||
private final CacheManager cacheManager;
|
|
||||||
=======
|
|
||||||
>>>>>>> origin
|
|
||||||
private final Cache verifyingKeyCache;
|
private final Cache verifyingKeyCache;
|
||||||
|
|
||||||
private volatile JwtVerificationKey activeKey;
|
private volatile JwtVerificationKey activeKey;
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
@Autowired
|
|
||||||
public KeyPersistenceService(
|
|
||||||
ApplicationProperties applicationProperties, CacheManager cacheManager) {
|
|
||||||
this.jwtProperties = applicationProperties.getSecurity().getJwt();
|
|
||||||
this.cacheManager = cacheManager;
|
|
||||||
this.verifyingKeyCache = cacheManager.getCache("verifyingKeys");
|
|
||||||
}
|
|
||||||
|
|
||||||
=======
|
|
||||||
public KeyPersistenceService(
|
public KeyPersistenceService(
|
||||||
ApplicationProperties applicationProperties, CacheManager cacheManager) {
|
ApplicationProperties applicationProperties, CacheManager cacheManager) {
|
||||||
this.jwtProperties = applicationProperties.getSecurity().getJwt();
|
this.jwtProperties = applicationProperties.getSecurity().getJwt();
|
||||||
@ -106,7 +82,6 @@ public class KeyPersistenceService implements KeyPersistenceServiceInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
>>>>>>> origin
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void initializeKeystore() {
|
public void initializeKeystore() {
|
||||||
if (!isKeystoreEnabled()) {
|
if (!isKeystoreEnabled()) {
|
||||||
@ -114,10 +89,7 @@ public class KeyPersistenceService implements KeyPersistenceServiceInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
<<<<<<< HEAD
|
|
||||||
=======
|
|
||||||
moveKeysToBackup();
|
moveKeysToBackup();
|
||||||
>>>>>>> origin
|
|
||||||
ensurePrivateKeyDirectoryExists();
|
ensurePrivateKeyDirectoryExists();
|
||||||
loadKeyPair();
|
loadKeyPair();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@ -214,11 +186,7 @@ public class KeyPersistenceService implements KeyPersistenceServiceInterface {
|
|||||||
nativeCache.asMap().size());
|
nativeCache.asMap().size());
|
||||||
|
|
||||||
return nativeCache.asMap().values().stream()
|
return nativeCache.asMap().values().stream()
|
||||||
<<<<<<< HEAD
|
|
||||||
.filter(value -> value instanceof JwtVerificationKey)
|
|
||||||
=======
|
|
||||||
.filter(JwtVerificationKey.class::isInstance)
|
.filter(JwtVerificationKey.class::isInstance)
|
||||||
>>>>>>> origin
|
|
||||||
.map(value -> (JwtVerificationKey) value)
|
.map(value -> (JwtVerificationKey) value)
|
||||||
.filter(
|
.filter(
|
||||||
key -> {
|
key -> {
|
||||||
@ -292,10 +260,7 @@ public class KeyPersistenceService implements KeyPersistenceServiceInterface {
|
|||||||
return Base64.getEncoder().encodeToString(publicKey.getEncoded());
|
return Base64.getEncoder().encodeToString(publicKey.getEncoded());
|
||||||
}
|
}
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
=======
|
|
||||||
@Override
|
@Override
|
||||||
>>>>>>> origin
|
|
||||||
public PublicKey decodePublicKey(String encodedKey)
|
public PublicKey decodePublicKey(String encodedKey)
|
||||||
throws NoSuchAlgorithmException, InvalidKeySpecException {
|
throws NoSuchAlgorithmException, InvalidKeySpecException {
|
||||||
byte[] keyBytes = Base64.getDecoder().decode(encodedKey);
|
byte[] keyBytes = Base64.getDecoder().decode(encodedKey);
|
||||||
|
@ -3,6 +3,7 @@ import { Box } from '@mantine/core';
|
|||||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||||
import { dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
|
import { dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
|
||||||
import styles from './PageEditor.module.css';
|
import styles from './PageEditor.module.css';
|
||||||
|
import { GRID_CONSTANTS } from './constants';
|
||||||
|
|
||||||
interface DragDropItem {
|
interface DragDropItem {
|
||||||
id: string;
|
id: string;
|
||||||
@ -33,10 +34,7 @@ const DragDropGrid = <T extends DragDropItem>({
|
|||||||
|
|
||||||
// Responsive grid configuration
|
// Responsive grid configuration
|
||||||
const [itemsPerRow, setItemsPerRow] = useState(4);
|
const [itemsPerRow, setItemsPerRow] = useState(4);
|
||||||
const ITEM_WIDTH = 320; // 20rem (page width)
|
const OVERSCAN = items.length > 1000 ? GRID_CONSTANTS.OVERSCAN_LARGE : GRID_CONSTANTS.OVERSCAN_SMALL;
|
||||||
const ITEM_GAP = 24; // 1.5rem gap between items
|
|
||||||
const ITEM_HEIGHT = 340; // 20rem + gap
|
|
||||||
const OVERSCAN = items.length > 1000 ? 8 : 4; // More overscan for large documents
|
|
||||||
|
|
||||||
// Calculate items per row based on container width
|
// Calculate items per row based on container width
|
||||||
const calculateItemsPerRow = useCallback(() => {
|
const calculateItemsPerRow = useCallback(() => {
|
||||||
@ -45,6 +43,11 @@ const DragDropGrid = <T extends DragDropItem>({
|
|||||||
const containerWidth = containerRef.current.offsetWidth;
|
const containerWidth = containerRef.current.offsetWidth;
|
||||||
if (containerWidth === 0) return 4; // Container not measured yet
|
if (containerWidth === 0) return 4; // Container not measured yet
|
||||||
|
|
||||||
|
// Convert rem to pixels for calculation
|
||||||
|
const remToPx = parseFloat(getComputedStyle(document.documentElement).fontSize);
|
||||||
|
const ITEM_WIDTH = parseFloat(GRID_CONSTANTS.ITEM_WIDTH) * remToPx;
|
||||||
|
const ITEM_GAP = parseFloat(GRID_CONSTANTS.ITEM_GAP) * remToPx;
|
||||||
|
|
||||||
// Calculate how many items fit: (width - gap) / (itemWidth + gap)
|
// Calculate how many items fit: (width - gap) / (itemWidth + gap)
|
||||||
const availableWidth = containerWidth - ITEM_GAP; // Account for first gap
|
const availableWidth = containerWidth - ITEM_GAP; // Account for first gap
|
||||||
const itemWithGap = ITEM_WIDTH + ITEM_GAP;
|
const itemWithGap = ITEM_WIDTH + ITEM_GAP;
|
||||||
@ -82,7 +85,10 @@ const DragDropGrid = <T extends DragDropItem>({
|
|||||||
const rowVirtualizer = useVirtualizer({
|
const rowVirtualizer = useVirtualizer({
|
||||||
count: Math.ceil(items.length / itemsPerRow),
|
count: Math.ceil(items.length / itemsPerRow),
|
||||||
getScrollElement: () => containerRef.current?.closest('[data-scrolling-container]') as Element,
|
getScrollElement: () => containerRef.current?.closest('[data-scrolling-container]') as Element,
|
||||||
estimateSize: () => ITEM_HEIGHT,
|
estimateSize: () => {
|
||||||
|
const remToPx = parseFloat(getComputedStyle(document.documentElement).fontSize);
|
||||||
|
return parseFloat(GRID_CONSTANTS.ITEM_HEIGHT) * remToPx;
|
||||||
|
},
|
||||||
overscan: OVERSCAN,
|
overscan: OVERSCAN,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -124,7 +130,7 @@ const DragDropGrid = <T extends DragDropItem>({
|
|||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
gap: '1.5rem',
|
gap: GRID_CONSTANTS.ITEM_GAP,
|
||||||
justifyContent: 'flex-start',
|
justifyContent: 'flex-start',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
@ -31,6 +31,7 @@ import {
|
|||||||
BulkPageBreakCommand,
|
BulkPageBreakCommand,
|
||||||
UndoManager
|
UndoManager
|
||||||
} from './commands/pageCommands';
|
} from './commands/pageCommands';
|
||||||
|
import { GRID_CONSTANTS } from './constants';
|
||||||
import { usePageDocument } from './hooks/usePageDocument';
|
import { usePageDocument } from './hooks/usePageDocument';
|
||||||
import { usePageEditorState } from './hooks/usePageEditorState';
|
import { usePageEditorState } from './hooks/usePageEditorState';
|
||||||
|
|
||||||
@ -103,6 +104,9 @@ const PageEditor = ({
|
|||||||
|
|
||||||
// Grid container ref for positioning split indicators
|
// Grid container ref for positioning split indicators
|
||||||
const gridContainerRef = useRef<HTMLDivElement>(null);
|
const gridContainerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
// State to trigger re-renders when container size changes
|
||||||
|
const [containerDimensions, setContainerDimensions] = useState({ width: 0, height: 0 });
|
||||||
|
|
||||||
// Undo/Redo state
|
// Undo/Redo state
|
||||||
const [canUndo, setCanUndo] = useState(false);
|
const [canUndo, setCanUndo] = useState(false);
|
||||||
@ -121,6 +125,28 @@ const PageEditor = ({
|
|||||||
updateUndoRedoState();
|
updateUndoRedoState();
|
||||||
}, [updateUndoRedoState]);
|
}, [updateUndoRedoState]);
|
||||||
|
|
||||||
|
// Watch for container size changes to update split line positions
|
||||||
|
useEffect(() => {
|
||||||
|
const container = gridContainerRef.current;
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
const resizeObserver = new ResizeObserver((entries) => {
|
||||||
|
const entry = entries[0];
|
||||||
|
if (entry) {
|
||||||
|
setContainerDimensions({
|
||||||
|
width: entry.contentRect.width,
|
||||||
|
height: entry.contentRect.height
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
resizeObserver.observe(container);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
resizeObserver.disconnect();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
// Interface functions for parent component
|
// Interface functions for parent component
|
||||||
const displayDocument = editedDocument || mergedPdfDocument;
|
const displayDocument = editedDocument || mergedPdfDocument;
|
||||||
@ -576,18 +602,25 @@ const PageEditor = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{Array.from(splitPositions).map((position) => {
|
{Array.from(splitPositions).map((position) => {
|
||||||
// Calculate the split line position based on grid layout
|
// Match DragDropGrid's layout calculations exactly
|
||||||
const ITEM_WIDTH = 320; // 20rem
|
const containerWidth = containerDimensions.width;
|
||||||
const ITEM_HEIGHT = 340; // 20rem + gap
|
const remToPx = parseFloat(getComputedStyle(document.documentElement).fontSize);
|
||||||
const ITEM_GAP = 24; // 1.5rem
|
const ITEM_WIDTH = parseFloat(GRID_CONSTANTS.ITEM_WIDTH) * remToPx;
|
||||||
const ITEMS_PER_ROW = 4; // Default, could be dynamic
|
const ITEM_HEIGHT = parseFloat(GRID_CONSTANTS.ITEM_HEIGHT) * remToPx;
|
||||||
|
const ITEM_GAP = parseFloat(GRID_CONSTANTS.ITEM_GAP) * remToPx;
|
||||||
const row = Math.floor(position / ITEMS_PER_ROW);
|
|
||||||
const col = position % ITEMS_PER_ROW;
|
// Calculate items per row using DragDropGrid's logic
|
||||||
|
const availableWidth = containerWidth - ITEM_GAP; // Account for first gap
|
||||||
// Position after the current item
|
const itemWithGap = ITEM_WIDTH + ITEM_GAP;
|
||||||
const leftPosition = (col + 1) * (ITEM_WIDTH + ITEM_GAP) - ITEM_GAP / 2;
|
const itemsPerRow = Math.max(1, Math.floor(availableWidth / itemWithGap));
|
||||||
const topPosition = row * ITEM_HEIGHT + 100; // Offset for header controls
|
|
||||||
|
// Calculate position within the grid (same as DragDropGrid)
|
||||||
|
const row = Math.floor(position / itemsPerRow);
|
||||||
|
const col = position % itemsPerRow;
|
||||||
|
|
||||||
|
// Position split line between pages (after the current page)
|
||||||
|
const leftPosition = col * itemWithGap + ITEM_WIDTH + (ITEM_GAP / 2);
|
||||||
|
const topPosition = row * ITEM_HEIGHT;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -597,7 +630,7 @@ const PageEditor = ({
|
|||||||
left: leftPosition,
|
left: leftPosition,
|
||||||
top: topPosition,
|
top: topPosition,
|
||||||
width: '1px',
|
width: '1px',
|
||||||
height: '20rem', // Match item height
|
height: GRID_CONSTANTS.ITEM_WIDTH, // Match item height
|
||||||
borderLeft: '1px dashed #3b82f6'
|
borderLeft: '1px dashed #3b82f6'
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
8
frontend/src/components/pageEditor/constants.ts
Normal file
8
frontend/src/components/pageEditor/constants.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
// Shared constants for PageEditor grid layout
|
||||||
|
export const GRID_CONSTANTS = {
|
||||||
|
ITEM_WIDTH: '20rem', // page width
|
||||||
|
ITEM_HEIGHT: '21.5rem', // 20rem + 1.5rem gap
|
||||||
|
ITEM_GAP: '1.5rem', // gap between items
|
||||||
|
OVERSCAN_SMALL: 4, // Overscan for normal documents
|
||||||
|
OVERSCAN_LARGE: 8, // Overscan for large documents (>1000 pages)
|
||||||
|
} as const;
|
Loading…
x
Reference in New Issue
Block a user