mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-08-26 14:19:24 +00:00
Visual updates
This commit is contained in:
parent
ad47c1dafb
commit
9a55437aad
@ -114,7 +114,6 @@ export default function Workbench() {
|
|||||||
onSplitAll={pageEditorFunctions.handleSplitAll}
|
onSplitAll={pageEditorFunctions.handleSplitAll}
|
||||||
onPageBreak={pageEditorFunctions.handlePageBreak}
|
onPageBreak={pageEditorFunctions.handlePageBreak}
|
||||||
onPageBreakAll={pageEditorFunctions.handlePageBreakAll}
|
onPageBreakAll={pageEditorFunctions.handlePageBreakAll}
|
||||||
onExportSelected={pageEditorFunctions.onExportSelected}
|
|
||||||
onExportAll={pageEditorFunctions.onExportAll}
|
onExportAll={pageEditorFunctions.onExportAll}
|
||||||
exportLoading={pageEditorFunctions.exportLoading}
|
exportLoading={pageEditorFunctions.exportLoading}
|
||||||
selectionMode={pageEditorFunctions.selectionMode}
|
selectionMode={pageEditorFunctions.selectionMode}
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.pageContainer:hover .pageHoverControls {
|
.pageContainer:hover .pageHoverControls {
|
||||||
opacity: 1 !important;
|
opacity: 0.9 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Checkbox container - prevent transform inheritance */
|
/* Checkbox container - prevent transform inheritance */
|
||||||
|
@ -47,6 +47,9 @@ export interface PageEditorProps {
|
|||||||
handleSplitAll: () => void;
|
handleSplitAll: () => void;
|
||||||
handlePageBreak: () => void;
|
handlePageBreak: () => void;
|
||||||
handlePageBreakAll: () => void;
|
handlePageBreakAll: () => void;
|
||||||
|
handleSelectAll: () => void;
|
||||||
|
handleDeselectAll: () => void;
|
||||||
|
handleSetSelectedPages: (pageNumbers: number[]) => void;
|
||||||
showExportPreview: (selectedOnly: boolean) => void;
|
showExportPreview: (selectedOnly: boolean) => void;
|
||||||
onExportSelected: () => void;
|
onExportSelected: () => void;
|
||||||
onExportAll: () => void;
|
onExportAll: () => void;
|
||||||
@ -147,10 +150,20 @@ const PageEditor = ({
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
// Interface functions for parent component
|
// Interface functions for parent component
|
||||||
const displayDocument = editedDocument || mergedPdfDocument;
|
const displayDocument = editedDocument || mergedPdfDocument;
|
||||||
|
|
||||||
|
// Select all pages by default when document initially loads
|
||||||
|
const hasInitializedSelection = useRef(false);
|
||||||
|
useEffect(() => {
|
||||||
|
if (displayDocument && displayDocument.pages.length > 0 && !hasInitializedSelection.current) {
|
||||||
|
const allPageNumbers = Array.from({ length: displayDocument.pages.length }, (_, i) => i + 1);
|
||||||
|
setSelectedPageNumbers(allPageNumbers);
|
||||||
|
setSelectionMode(true);
|
||||||
|
hasInitializedSelection.current = true;
|
||||||
|
}
|
||||||
|
}, [displayDocument, setSelectedPageNumbers, setSelectionMode]);
|
||||||
|
|
||||||
// DOM-first command handlers
|
// DOM-first command handlers
|
||||||
const handleRotatePages = useCallback((pageIds: string[], rotation: number) => {
|
const handleRotatePages = useCallback((pageIds: string[], rotation: number) => {
|
||||||
const bulkRotateCommand = new BulkRotateCommand(pageIds, rotation);
|
const bulkRotateCommand = new BulkRotateCommand(pageIds, rotation);
|
||||||
@ -217,17 +230,15 @@ const PageEditor = ({
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleRotate = useCallback((direction: 'left' | 'right') => {
|
const handleRotate = useCallback((direction: 'left' | 'right') => {
|
||||||
if (!displayDocument) return;
|
if (!displayDocument || selectedPageNumbers.length === 0) return;
|
||||||
const rotation = direction === 'left' ? -90 : 90;
|
const rotation = direction === 'left' ? -90 : 90;
|
||||||
const pagesToRotate = selectionMode && selectedPageNumbers.length > 0
|
const pagesToRotate = selectedPageNumbers.map(pageNum => {
|
||||||
? selectedPageNumbers.map(pageNum => {
|
const page = displayDocument.pages.find(p => p.pageNumber === pageNum);
|
||||||
const page = displayDocument.pages.find(p => p.pageNumber === pageNum);
|
return page?.id || '';
|
||||||
return page?.id || '';
|
}).filter(id => id);
|
||||||
}).filter(id => id)
|
|
||||||
: displayDocument.pages.map(p => p.id);
|
|
||||||
|
|
||||||
handleRotatePages(pagesToRotate, rotation);
|
handleRotatePages(pagesToRotate, rotation);
|
||||||
}, [displayDocument, selectedPageNumbers, selectionMode, handleRotatePages]);
|
}, [displayDocument, selectedPageNumbers, handleRotatePages]);
|
||||||
|
|
||||||
const handleDelete = useCallback(() => {
|
const handleDelete = useCallback(() => {
|
||||||
if (!displayDocument || selectedPageNumbers.length === 0) return;
|
if (!displayDocument || selectedPageNumbers.length === 0) return;
|
||||||
@ -262,6 +273,53 @@ const PageEditor = ({
|
|||||||
const handleSplit = useCallback(() => {
|
const handleSplit = useCallback(() => {
|
||||||
if (!displayDocument || selectedPageNumbers.length === 0) return;
|
if (!displayDocument || selectedPageNumbers.length === 0) return;
|
||||||
|
|
||||||
|
// Convert selected page numbers to split positions (0-based indices)
|
||||||
|
const selectedPositions: number[] = [];
|
||||||
|
selectedPageNumbers.forEach(pageNum => {
|
||||||
|
const pageIndex = displayDocument.pages.findIndex(p => p.pageNumber === pageNum);
|
||||||
|
if (pageIndex !== -1 && pageIndex < displayDocument.pages.length - 1) {
|
||||||
|
// Only allow splits before the last page
|
||||||
|
selectedPositions.push(pageIndex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (selectedPositions.length === 0) return;
|
||||||
|
|
||||||
|
// Smart toggle logic: follow the majority, default to adding splits if equal
|
||||||
|
const existingSplitsCount = selectedPositions.filter(pos => splitPositions.has(pos)).length;
|
||||||
|
const noSplitsCount = selectedPositions.length - existingSplitsCount;
|
||||||
|
|
||||||
|
// Remove splits only if majority already have splits
|
||||||
|
// If equal (50/50), default to adding splits
|
||||||
|
const shouldRemoveSplits = existingSplitsCount > noSplitsCount;
|
||||||
|
|
||||||
|
console.log(`Smart split toggle: ${existingSplitsCount} with splits, ${noSplitsCount} without splits → will ${shouldRemoveSplits ? 'remove' : 'add'} splits`);
|
||||||
|
|
||||||
|
const newSplitPositions = new Set(splitPositions);
|
||||||
|
|
||||||
|
if (shouldRemoveSplits) {
|
||||||
|
// Remove splits from all selected positions
|
||||||
|
selectedPositions.forEach(pos => newSplitPositions.delete(pos));
|
||||||
|
} else {
|
||||||
|
// Add splits to all selected positions
|
||||||
|
selectedPositions.forEach(pos => newSplitPositions.add(pos));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a custom command that sets the final state directly
|
||||||
|
const smartSplitCommand = {
|
||||||
|
execute: () => setSplitPositions(newSplitPositions),
|
||||||
|
undo: () => setSplitPositions(splitPositions),
|
||||||
|
description: shouldRemoveSplits
|
||||||
|
? `Remove ${selectedPositions.length} split(s)`
|
||||||
|
: `Add ${selectedPositions.length - existingSplitsCount} split(s)`
|
||||||
|
};
|
||||||
|
|
||||||
|
undoManagerRef.current.executeCommand(smartSplitCommand);
|
||||||
|
}, [selectedPageNumbers, displayDocument, splitPositions, setSplitPositions]);
|
||||||
|
|
||||||
|
const handleSplitAll = useCallback(() => {
|
||||||
|
if (!displayDocument || selectedPageNumbers.length === 0) return;
|
||||||
|
|
||||||
console.log('Toggle split markers at selected page positions:', selectedPageNumbers);
|
console.log('Toggle split markers at selected page positions:', selectedPageNumbers);
|
||||||
|
|
||||||
// Convert page numbers to positions (0-based indices)
|
// Convert page numbers to positions (0-based indices)
|
||||||
@ -284,17 +342,6 @@ const PageEditor = ({
|
|||||||
}
|
}
|
||||||
}, [selectedPageNumbers, displayDocument, splitPositions]);
|
}, [selectedPageNumbers, displayDocument, splitPositions]);
|
||||||
|
|
||||||
const handleSplitAll = useCallback(() => {
|
|
||||||
if (!displayDocument) return;
|
|
||||||
|
|
||||||
const splitAllCommand = new SplitAllCommand(
|
|
||||||
displayDocument.pages.length,
|
|
||||||
() => splitPositions,
|
|
||||||
setSplitPositions
|
|
||||||
);
|
|
||||||
undoManagerRef.current.executeCommand(splitAllCommand);
|
|
||||||
}, [displayDocument, splitPositions]);
|
|
||||||
|
|
||||||
const handlePageBreak = useCallback(() => {
|
const handlePageBreak = useCallback(() => {
|
||||||
if (!displayDocument || selectedPageNumbers.length === 0) return;
|
if (!displayDocument || selectedPageNumbers.length === 0) return;
|
||||||
|
|
||||||
@ -310,15 +357,18 @@ const PageEditor = ({
|
|||||||
}, [selectedPageNumbers, displayDocument]);
|
}, [selectedPageNumbers, displayDocument]);
|
||||||
|
|
||||||
const handlePageBreakAll = useCallback(() => {
|
const handlePageBreakAll = useCallback(() => {
|
||||||
if (!displayDocument) return;
|
if (!displayDocument || selectedPageNumbers.length === 0) return;
|
||||||
|
|
||||||
const pageBreakAllCommand = new BulkPageBreakCommand(
|
console.log('Insert page breaks after selected pages:', selectedPageNumbers);
|
||||||
|
|
||||||
|
const pageBreakCommand = new PageBreakCommand(
|
||||||
|
selectedPageNumbers,
|
||||||
() => displayDocument,
|
() => displayDocument,
|
||||||
setEditedDocument,
|
setEditedDocument,
|
||||||
setSelectedPageNumbers
|
setSelectedPageNumbers
|
||||||
);
|
);
|
||||||
undoManagerRef.current.executeCommand(pageBreakAllCommand);
|
undoManagerRef.current.executeCommand(pageBreakCommand);
|
||||||
}, [displayDocument]);
|
}, [selectedPageNumbers, displayDocument]);
|
||||||
|
|
||||||
const handleSelectAll = useCallback(() => {
|
const handleSelectAll = useCallback(() => {
|
||||||
if (!displayDocument) return;
|
if (!displayDocument) return;
|
||||||
|
@ -31,8 +31,7 @@ interface PageEditorControlsProps {
|
|||||||
onPageBreak: () => void;
|
onPageBreak: () => void;
|
||||||
onPageBreakAll: () => void;
|
onPageBreakAll: () => void;
|
||||||
|
|
||||||
// Export functions
|
// Export functions (moved to right rail)
|
||||||
onExportSelected: () => void;
|
|
||||||
onExportAll: () => void;
|
onExportAll: () => void;
|
||||||
exportLoading: boolean;
|
exportLoading: boolean;
|
||||||
|
|
||||||
@ -57,7 +56,6 @@ const PageEditorControls = ({
|
|||||||
onSplitAll,
|
onSplitAll,
|
||||||
onPageBreak,
|
onPageBreak,
|
||||||
onPageBreakAll,
|
onPageBreakAll,
|
||||||
onExportSelected,
|
|
||||||
onExportAll,
|
onExportAll,
|
||||||
exportLoading,
|
exportLoading,
|
||||||
selectionMode,
|
selectionMode,
|
||||||
@ -65,32 +63,43 @@ const PageEditorControls = ({
|
|||||||
splitPositions,
|
splitPositions,
|
||||||
totalPages
|
totalPages
|
||||||
}: PageEditorControlsProps) => {
|
}: PageEditorControlsProps) => {
|
||||||
// Calculate split all tooltip text
|
// Calculate split tooltip text using smart toggle logic
|
||||||
const getSplitAllTooltip = () => {
|
const getSplitTooltip = () => {
|
||||||
if (selectionMode) {
|
if (!splitPositions || !totalPages || selectedPages.length === 0) {
|
||||||
return "Split Selected";
|
return "Split Selected";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!splitPositions || !totalPages) {
|
// Convert selected pages to split positions (same logic as handleSplit)
|
||||||
return "Split All";
|
const selectedSplitPositions = selectedPages.map(pageNum => pageNum - 1).filter(pos => pos < totalPages - 1);
|
||||||
|
|
||||||
|
if (selectedSplitPositions.length === 0) {
|
||||||
|
return "Split Selected";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if all possible splits are active
|
// Smart toggle logic: follow the majority, default to adding splits if equal
|
||||||
const allPossibleSplitsCount = totalPages - 1;
|
const existingSplitsCount = selectedSplitPositions.filter(pos => splitPositions.has(pos)).length;
|
||||||
const hasAllSplits = splitPositions.size === allPossibleSplitsCount &&
|
const noSplitsCount = selectedSplitPositions.length - existingSplitsCount;
|
||||||
Array.from({length: allPossibleSplitsCount}, (_, i) => i).every(pos => splitPositions.has(pos));
|
|
||||||
|
|
||||||
return hasAllSplits ? "Remove All Splits" : "Split All";
|
// Remove splits only if majority already have splits
|
||||||
|
// If equal (50/50), default to adding splits
|
||||||
|
const willRemoveSplits = existingSplitsCount > noSplitsCount;
|
||||||
|
|
||||||
|
if (willRemoveSplits) {
|
||||||
|
return existingSplitsCount === selectedSplitPositions.length
|
||||||
|
? "Remove All Selected Splits"
|
||||||
|
: "Remove Selected Splits";
|
||||||
|
} else {
|
||||||
|
return existingSplitsCount === 0
|
||||||
|
? "Split Selected"
|
||||||
|
: "Complete Selected Splits";
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Calculate page break tooltip text
|
// Calculate page break tooltip text
|
||||||
const getPageBreakTooltip = () => {
|
const getPageBreakTooltip = () => {
|
||||||
if (selectionMode) {
|
return selectedPages.length > 0
|
||||||
return selectedPages.length > 0
|
? `Insert ${selectedPages.length} Page Break${selectedPages.length > 1 ? 's' : ''}`
|
||||||
? `Insert ${selectedPages.length} Page Break${selectedPages.length > 1 ? 's' : ''}`
|
: "Insert Page Breaks";
|
||||||
: "Insert Page Breaks";
|
|
||||||
}
|
|
||||||
return "Insert Page Breaks After All Pages";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -145,10 +154,10 @@ const PageEditorControls = ({
|
|||||||
<div style={{ width: 1, height: 28, backgroundColor: 'var(--mantine-color-gray-3)', margin: '0 8px' }} />
|
<div style={{ width: 1, height: 28, backgroundColor: 'var(--mantine-color-gray-3)', margin: '0 8px' }} />
|
||||||
|
|
||||||
{/* Page Operations */}
|
{/* Page Operations */}
|
||||||
<Tooltip label={selectionMode ? "Rotate Selected Left" : "Rotate All Left"}>
|
<Tooltip label="Rotate Selected Left">
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
onClick={() => onRotate('left')}
|
onClick={() => onRotate('left')}
|
||||||
disabled={selectionMode && selectedPages.length === 0}
|
disabled={selectedPages.length === 0}
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
style={{ color: 'var(--mantine-color-dimmed)' }}
|
style={{ color: 'var(--mantine-color-dimmed)' }}
|
||||||
radius="md"
|
radius="md"
|
||||||
@ -157,10 +166,10 @@ const PageEditorControls = ({
|
|||||||
<RotateLeftIcon />
|
<RotateLeftIcon />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip label={selectionMode ? "Rotate Selected Right" : "Rotate All Right"}>
|
<Tooltip label="Rotate Selected Right">
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
onClick={() => onRotate('right')}
|
onClick={() => onRotate('right')}
|
||||||
disabled={selectionMode && selectedPages.length === 0}
|
disabled={selectedPages.length === 0}
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
style={{ color: 'var(--mantine-color-dimmed)' }}
|
style={{ color: 'var(--mantine-color-dimmed)' }}
|
||||||
radius="md"
|
radius="md"
|
||||||
@ -169,23 +178,22 @@ const PageEditorControls = ({
|
|||||||
<RotateRightIcon />
|
<RotateRightIcon />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
{selectionMode && (
|
<Tooltip label="Delete Selected">
|
||||||
<Tooltip label="Delete Selected">
|
|
||||||
<ActionIcon
|
|
||||||
onClick={onDelete}
|
|
||||||
disabled={selectedPages.length === 0}
|
|
||||||
variant={selectedPages.length > 0 ? "light" : "subtle"}
|
|
||||||
radius="md"
|
|
||||||
size="lg"
|
|
||||||
>
|
|
||||||
<DeleteIcon />
|
|
||||||
</ActionIcon>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
<Tooltip label={getSplitAllTooltip()}>
|
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
onClick={selectionMode ? onSplit : onSplitAll}
|
onClick={onDelete}
|
||||||
disabled={selectionMode && selectedPages.length === 0}
|
disabled={selectedPages.length === 0}
|
||||||
|
variant="subtle"
|
||||||
|
style={{ color: 'var(--mantine-color-dimmed)' }}
|
||||||
|
radius="md"
|
||||||
|
size="lg"
|
||||||
|
>
|
||||||
|
<DeleteIcon />
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip label={getSplitTooltip()}>
|
||||||
|
<ActionIcon
|
||||||
|
onClick={onSplit}
|
||||||
|
disabled={selectedPages.length === 0}
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
style={{ color: 'var(--mantine-color-dimmed)' }}
|
style={{ color: 'var(--mantine-color-dimmed)' }}
|
||||||
radius="md"
|
radius="md"
|
||||||
@ -196,8 +204,8 @@ const PageEditorControls = ({
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip label={getPageBreakTooltip()}>
|
<Tooltip label={getPageBreakTooltip()}>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
onClick={selectionMode ? onPageBreak : onPageBreakAll}
|
onClick={onPageBreak}
|
||||||
disabled={selectionMode && selectedPages.length === 0}
|
disabled={selectedPages.length === 0}
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
style={{ color: 'var(--mantine-color-dimmed)' }}
|
style={{ color: 'var(--mantine-color-dimmed)' }}
|
||||||
radius="md"
|
radius="md"
|
||||||
@ -206,21 +214,6 @@ const PageEditorControls = ({
|
|||||||
<InsertPageBreakIcon />
|
<InsertPageBreakIcon />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
{/* Export Controls */}
|
|
||||||
{selectionMode && (
|
|
||||||
<Tooltip label="Export Selected">
|
|
||||||
<ActionIcon
|
|
||||||
onClick={onExportSelected}
|
|
||||||
disabled={exportLoading || selectedPages.length === 0}
|
|
||||||
variant={selectedPages.length > 0 ? "light" : "subtle"}
|
|
||||||
radius="md"
|
|
||||||
size="lg"
|
|
||||||
>
|
|
||||||
<DownloadIcon />
|
|
||||||
</ActionIcon>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -402,8 +402,8 @@ const PageThumbnail: React.FC<PageThumbnailProps> = ({
|
|||||||
bottom: 8,
|
bottom: 8,
|
||||||
left: '50%',
|
left: '50%',
|
||||||
transform: 'translateX(-50%)',
|
transform: 'translateX(-50%)',
|
||||||
backgroundColor: 'var(--mantine-color-default-hover)',
|
backgroundColor: 'var(--bg-toolbar)',
|
||||||
border: '1px solid rgba(128, 128, 128, 0.3)',
|
border: '1px solid var(--border-default)',
|
||||||
padding: '6px 12px',
|
padding: '6px 12px',
|
||||||
borderRadius: 20,
|
borderRadius: 20,
|
||||||
opacity: 0,
|
opacity: 0,
|
||||||
|
@ -295,6 +295,26 @@ export default function RightRail() {
|
|||||||
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Export Selected Pages - page editor only */}
|
||||||
|
{pageControlsMounted && (
|
||||||
|
<Tooltip content={t('rightRail.exportSelected', 'Export Selected Pages')} position="left" offset={12} arrow>
|
||||||
|
<div className={`right-rail-fade ${pageControlsVisible ? 'enter' : 'exit'}`} aria-hidden={!pageControlsVisible}>
|
||||||
|
<div style={{ display: 'inline-flex' }}>
|
||||||
|
<ActionIcon
|
||||||
|
variant="subtle"
|
||||||
|
radius="md"
|
||||||
|
className="right-rail-icon"
|
||||||
|
onClick={() => { pageEditorFunctions?.onExportSelected?.(); }}
|
||||||
|
disabled={!pageControlsVisible || (pageEditorFunctions?.selectedPages?.length || 0) === 0 || pageEditorFunctions?.exportLoading}
|
||||||
|
aria-label={typeof t === 'function' ? t('rightRail.exportSelected', 'Export Selected Pages') : 'Export Selected Pages'}
|
||||||
|
>
|
||||||
|
<LocalIcon icon="download" width="1.5rem" height="1.5rem" />
|
||||||
|
</ActionIcon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Close (File Editor: Close Selected | Page Editor: Close PDF) */}
|
{/* Close (File Editor: Close Selected | Page Editor: Close PDF) */}
|
||||||
<Tooltip content={currentView === 'pageEditor' ? t('rightRail.closePdf', 'Close PDF') : t('rightRail.closeSelected', 'Close Selected Files')} position="left" offset={12} arrow>
|
<Tooltip content={currentView === 'pageEditor' ? t('rightRail.closePdf', 'Close PDF') : t('rightRail.closeSelected', 'Close Selected Files')} position="left" offset={12} arrow>
|
||||||
<div>
|
<div>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user