Page editor updates

This commit is contained in:
Reece 2025-06-16 19:57:50 +01:00
parent 7fc850b138
commit e7995a0256

View File

@ -20,6 +20,8 @@ import ConstructionIcon from "@mui/icons-material/Construction";
import EventListIcon from "@mui/icons-material/EventList"; import EventListIcon from "@mui/icons-material/EventList";
import DeselectIcon from "@mui/icons-material/Deselect"; import DeselectIcon from "@mui/icons-material/Deselect";
import SelectAllIcon from "@mui/icons-material/SelectAll"; import SelectAllIcon from "@mui/icons-material/SelectAll";
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
import ArrowForwardIcon from "@mui/icons-material/ArrowForward";
import { usePDFProcessor } from "../hooks/usePDFProcessor"; import { usePDFProcessor } from "../hooks/usePDFProcessor";
import { PDFDocument, PDFPage } from "../types/pageEditor"; import { PDFDocument, PDFPage } from "../types/pageEditor";
import { fileStorage } from "../services/fileStorage"; import { fileStorage } from "../services/fileStorage";
@ -62,6 +64,7 @@ const PageEditor: React.FC<PageEditorProps> = ({
const [exportLoading, setExportLoading] = useState(false); const [exportLoading, setExportLoading] = useState(false);
const [showExportModal, setShowExportModal] = useState(false); const [showExportModal, setShowExportModal] = useState(false);
const [exportPreview, setExportPreview] = useState<{pageCount: number; splitCount: number; estimatedSize: string} | null>(null); const [exportPreview, setExportPreview] = useState<{pageCount: number; splitCount: number; estimatedSize: string} | null>(null);
const [movingPage, setMovingPage] = useState<string | null>(null);
const fileInputRef = useRef<() => void>(null); const fileInputRef = useRef<() => void>(null);
// Undo/Redo system // Undo/Redo system
@ -345,12 +348,7 @@ const PageEditor: React.FC<PageEditorProps> = ({
<Box pos="relative" h="100vh" style={{ overflow: 'auto' }}> <Box pos="relative" h="100vh" style={{ overflow: 'auto' }}>
<LoadingOverlay visible={loading || pdfLoading} /> <LoadingOverlay visible={loading || pdfLoading} />
<Box p="xl"> <Container size="lg" p="xl" h="100%" style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
{error && (
<Alert color="red" mb="md" onClose={() => setError(null)}>
{error}
</Alert>
)}
<Dropzone <Dropzone
onDrop={(files) => files[0] && handleFileUpload(files[0])} onDrop={(files) => files[0] && handleFileUpload(files[0])}
@ -371,7 +369,7 @@ const PageEditor: React.FC<PageEditorProps> = ({
</Stack> </Stack>
</Center> </Center>
</Dropzone> </Dropzone>
</Box> </Container>
</Box> </Box>
); );
} }
@ -392,6 +390,14 @@ const PageEditor: React.FC<PageEditorProps> = ({
.page-container:hover { .page-container:hover {
transform: scale(1.02); transform: scale(1.02);
} }
.page-move-animation {
transition: all 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
.page-moving {
z-index: 10;
transform: scale(1.05);
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
}
@keyframes pulse { @keyframes pulse {
0%, 100% { 0%, 100% {
opacity: 1; opacity: 1;
@ -489,30 +495,15 @@ const PageEditor: React.FC<PageEditorProps> = ({
{page.splitBefore && index > 0 && ( {page.splitBefore && index > 0 && (
<div <div
style={{ style={{
width: '4px', width: '2px',
height: '15rem', height: '20rem',
border: '2px dashed #3b82f6', borderLeft: '2px dashed #3b82f6',
backgroundColor: 'transparent', backgroundColor: 'transparent',
borderRadius: '2px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
marginLeft: '-0.75rem', marginLeft: '-0.75rem',
marginRight: '-0.75rem', marginRight: '-0.75rem',
position: 'relative',
flexShrink: 0 flexShrink: 0
}} }}
>
<ContentCutIcon
style={{
fontSize: 18,
color: '#3b82f6',
backgroundColor: 'white',
borderRadius: '50%',
padding: '3px'
}}
/> />
</div>
)} )}
<div <div
data-page-id={page.id} data-page-id={page.id}
@ -520,18 +511,20 @@ const PageEditor: React.FC<PageEditorProps> = ({
!rounded-lg !rounded-lg
cursor-grab cursor-grab
select-none select-none
w-[15rem] w-[20rem]
h-[15rem] h-[20rem]
flex items-center justify-center flex items-center justify-center
flex-shrink-0 flex-shrink-0
shadow-sm shadow-sm
hover:shadow-md hover:shadow-md
transition-all transition-all
relative relative
page-move-animation
${selectedPages.includes(page.id) ${selectedPages.includes(page.id)
? 'ring-2 ring-blue-500 bg-blue-50' ? 'ring-2 ring-blue-500 bg-blue-50'
: 'bg-white hover:bg-gray-50'} : 'bg-white hover:bg-gray-50'}
${draggedPage === page.id ? 'opacity-50 scale-95' : ''} ${draggedPage === page.id ? 'opacity-50 scale-95' : ''}
${movingPage === page.id ? 'page-moving' : ''}
`} `}
style={{ style={{
transform: (() => { transform: (() => {
@ -606,6 +599,62 @@ const PageEditor: React.FC<PageEditorProps> = ({
whiteSpace: 'nowrap' whiteSpace: 'nowrap'
}} }}
> >
<Tooltip label="Move Left">
<ActionIcon
size="md"
variant="subtle"
c="white"
disabled={index === 0}
onClick={(e) => {
e.stopPropagation();
if (index > 0 && !movingPage) {
setMovingPage(page.id);
setTimeout(() => {
const command = new ReorderPageCommand(
pdfDocument,
setPdfDocument,
page.id,
index - 1
);
executeCommand(command);
setTimeout(() => setMovingPage(null), 100);
setStatus(`Moved page ${page.pageNumber} left`);
}, 50);
}
}}
>
<ArrowBackIcon style={{ fontSize: 20 }} />
</ActionIcon>
</Tooltip>
<Tooltip label="Move Right">
<ActionIcon
size="md"
variant="subtle"
c="white"
disabled={index === pdfDocument.pages.length - 1}
onClick={(e) => {
e.stopPropagation();
if (index < pdfDocument.pages.length - 1 && !movingPage) {
setMovingPage(page.id);
setTimeout(() => {
const command = new ReorderPageCommand(
pdfDocument,
setPdfDocument,
page.id,
index + 1
);
executeCommand(command);
setTimeout(() => setMovingPage(null), 100);
setStatus(`Moved page ${page.pageNumber} right`);
}, 50);
}
}}
>
<ArrowForwardIcon style={{ fontSize: 20 }} />
</ActionIcon>
</Tooltip>
<Tooltip label="Rotate Left"> <Tooltip label="Rotate Left">
<ActionIcon <ActionIcon
size="md" size="md"
@ -668,6 +717,7 @@ const PageEditor: React.FC<PageEditorProps> = ({
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>
{index > 0 && (
<Tooltip label="Split Here"> <Tooltip label="Split Here">
<ActionIcon <ActionIcon
size="md" size="md"
@ -687,6 +737,7 @@ const PageEditor: React.FC<PageEditorProps> = ({
<ContentCutIcon style={{ fontSize: 20 }} /> <ContentCutIcon style={{ fontSize: 20 }} />
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>
)}
<Tooltip label="Select Page"> <Tooltip label="Select Page">
<Checkbox <Checkbox
@ -716,31 +767,24 @@ const PageEditor: React.FC<PageEditorProps> = ({
))} ))}
{/* Landing zone at the end */} {/* Landing zone at the end */}
<div className="w-[20rem] h-[20rem] flex items-center justify-center flex-shrink-0">
<div <div
data-drop-zone="end" data-drop-zone="end"
className={`cursor-pointer select-none w-[15rem] h-[15rem] flex items-center justify-center flex-shrink-0 shadow-sm hover:shadow-md transition-all relative ${dropTarget === 'end' ? 'ring-2 ring-green-500 bg-green-50' : 'bg-white hover:bg-blue-50 border-2 border-dashed border-gray-300 hover:border-blue-400'}`}
style={{ style={{
width: '15rem', borderRadius: '12px'
height: '15rem',
border: '2px dashed #9ca3af',
borderRadius: '8px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0,
backgroundColor: dropTarget === 'end' ? '#ecfdf5' : 'transparent',
borderColor: dropTarget === 'end' ? '#10b981' : '#9ca3af',
transition: 'all 0.2s ease-in-out'
}} }}
onDragOver={handleDragOver} onDragOver={handleDragOver}
onDragEnter={handleEndZoneDragEnter} onDragEnter={handleEndZoneDragEnter}
onDragLeave={handleDragLeave} onDragLeave={handleDragLeave}
onDrop={(e) => handleDrop(e, 'end')} onDrop={(e) => handleDrop(e, 'end')}
> >
<Text c="dimmed" size="sm" ta="center"> <Text c="dimmed" size="sm" ta="center" fw={500}>
Drop here to<br />move to end Drop here to<br />move to end
</Text> </Text>
</div> </div>
</div> </div>
</div>
<Group justify="space-between" mt="md"> <Group justify="space-between" mt="md">
<Button <Button