Fix split display on zoom

This commit is contained in:
Reece Browne 2025-08-25 18:44:34 +01:00
parent 5025e06289
commit d8f34769aa
5 changed files with 66 additions and 62 deletions

View File

@ -8,10 +8,6 @@ import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.TimeUnit;
<<<<<<< HEAD
import org.springframework.beans.factory.annotation.Autowired;
=======
>>>>>>> origin
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
@ -33,10 +29,6 @@ public class KeyPairCleanupService {
private final KeyPersistenceService keyPersistenceService;
private final ApplicationProperties.Security.Jwt jwtProperties;
<<<<<<< HEAD
@Autowired
=======
>>>>>>> origin
public KeyPairCleanupService(
KeyPersistenceService keyPersistenceService,
ApplicationProperties applicationProperties) {

View File

@ -1,17 +1,11 @@
package stirling.software.proprietary.security.service;
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.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
>>>>>>> origin
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
@ -28,10 +22,6 @@ import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
<<<<<<< HEAD
import org.springframework.beans.factory.annotation.Autowired;
=======
>>>>>>> origin
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CacheEvict;
@ -54,24 +44,10 @@ public class KeyPersistenceService implements KeyPersistenceServiceInterface {
public static final String KEY_SUFFIX = ".key";
private final ApplicationProperties.Security.Jwt jwtProperties;
<<<<<<< HEAD
private final CacheManager cacheManager;
=======
>>>>>>> origin
private final Cache verifyingKeyCache;
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(
ApplicationProperties applicationProperties, CacheManager cacheManager) {
this.jwtProperties = applicationProperties.getSecurity().getJwt();
@ -106,7 +82,6 @@ public class KeyPersistenceService implements KeyPersistenceServiceInterface {
}
}
>>>>>>> origin
@PostConstruct
public void initializeKeystore() {
if (!isKeystoreEnabled()) {
@ -114,10 +89,7 @@ public class KeyPersistenceService implements KeyPersistenceServiceInterface {
}
try {
<<<<<<< HEAD
=======
moveKeysToBackup();
>>>>>>> origin
ensurePrivateKeyDirectoryExists();
loadKeyPair();
} catch (Exception e) {
@ -214,11 +186,7 @@ public class KeyPersistenceService implements KeyPersistenceServiceInterface {
nativeCache.asMap().size());
return nativeCache.asMap().values().stream()
<<<<<<< HEAD
.filter(value -> value instanceof JwtVerificationKey)
=======
.filter(JwtVerificationKey.class::isInstance)
>>>>>>> origin
.map(value -> (JwtVerificationKey) value)
.filter(
key -> {
@ -292,10 +260,7 @@ public class KeyPersistenceService implements KeyPersistenceServiceInterface {
return Base64.getEncoder().encodeToString(publicKey.getEncoded());
}
<<<<<<< HEAD
=======
@Override
>>>>>>> origin
public PublicKey decodePublicKey(String encodedKey)
throws NoSuchAlgorithmException, InvalidKeySpecException {
byte[] keyBytes = Base64.getDecoder().decode(encodedKey);

View File

@ -3,6 +3,7 @@ import { Box } from '@mantine/core';
import { useVirtualizer } from '@tanstack/react-virtual';
import { dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
import styles from './PageEditor.module.css';
import { GRID_CONSTANTS } from './constants';
interface DragDropItem {
id: string;
@ -33,10 +34,7 @@ const DragDropGrid = <T extends DragDropItem>({
// Responsive grid configuration
const [itemsPerRow, setItemsPerRow] = useState(4);
const ITEM_WIDTH = 320; // 20rem (page width)
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
const OVERSCAN = items.length > 1000 ? GRID_CONSTANTS.OVERSCAN_LARGE : GRID_CONSTANTS.OVERSCAN_SMALL;
// Calculate items per row based on container width
const calculateItemsPerRow = useCallback(() => {
@ -45,6 +43,11 @@ const DragDropGrid = <T extends DragDropItem>({
const containerWidth = containerRef.current.offsetWidth;
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)
const availableWidth = containerWidth - ITEM_GAP; // Account for first gap
const itemWithGap = ITEM_WIDTH + ITEM_GAP;
@ -82,7 +85,10 @@ const DragDropGrid = <T extends DragDropItem>({
const rowVirtualizer = useVirtualizer({
count: Math.ceil(items.length / itemsPerRow),
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,
});
@ -124,7 +130,7 @@ const DragDropGrid = <T extends DragDropItem>({
<div
style={{
display: 'flex',
gap: '1.5rem',
gap: GRID_CONSTANTS.ITEM_GAP,
justifyContent: 'flex-start',
height: '100%',
alignItems: 'center',

View File

@ -31,6 +31,7 @@ import {
BulkPageBreakCommand,
UndoManager
} from './commands/pageCommands';
import { GRID_CONSTANTS } from './constants';
import { usePageDocument } from './hooks/usePageDocument';
import { usePageEditorState } from './hooks/usePageEditorState';
@ -104,6 +105,9 @@ const PageEditor = ({
// Grid container ref for positioning split indicators
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
const [canUndo, setCanUndo] = useState(false);
const [canRedo, setCanRedo] = useState(false);
@ -121,6 +125,28 @@ const PageEditor = ({
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
const displayDocument = editedDocument || mergedPdfDocument;
@ -576,18 +602,25 @@ const PageEditor = ({
}}
>
{Array.from(splitPositions).map((position) => {
// Calculate the split line position based on grid layout
const ITEM_WIDTH = 320; // 20rem
const ITEM_HEIGHT = 340; // 20rem + gap
const ITEM_GAP = 24; // 1.5rem
const ITEMS_PER_ROW = 4; // Default, could be dynamic
// Match DragDropGrid's layout calculations exactly
const containerWidth = containerDimensions.width;
const remToPx = parseFloat(getComputedStyle(document.documentElement).fontSize);
const ITEM_WIDTH = parseFloat(GRID_CONSTANTS.ITEM_WIDTH) * remToPx;
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
const itemWithGap = ITEM_WIDTH + ITEM_GAP;
const itemsPerRow = Math.max(1, Math.floor(availableWidth / itemWithGap));
// Position after the current item
const leftPosition = (col + 1) * (ITEM_WIDTH + ITEM_GAP) - ITEM_GAP / 2;
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 (
<div
@ -597,7 +630,7 @@ const PageEditor = ({
left: leftPosition,
top: topPosition,
width: '1px',
height: '20rem', // Match item height
height: GRID_CONSTANTS.ITEM_WIDTH, // Match item height
borderLeft: '1px dashed #3b82f6'
}}
/>

View 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;