mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2025-11-16 01:21:16 +01:00
Improvement/V2/generic_obscure_component_wrapper (#4794)
Created PrivateContent Component to be used as wrapper. This way all tools that need to obscure contents can update this wrapper. --------- Co-authored-by: Connor Yoh <connor@stirlingpdf.com> Co-authored-by: James Brunton <jbrunton96@gmail.com>
This commit is contained in:
parent
45389340ed
commit
f4543d26cd
@ -3,6 +3,7 @@ import { Paper, Button, Modal, Stack, Text, Popover, ColorPicker as MantineColor
|
|||||||
import { ColorSwatchButton } from '@app/components/annotation/shared/ColorPicker';
|
import { ColorSwatchButton } from '@app/components/annotation/shared/ColorPicker';
|
||||||
import PenSizeSelector from '@app/components/tools/sign/PenSizeSelector';
|
import PenSizeSelector from '@app/components/tools/sign/PenSizeSelector';
|
||||||
import SignaturePad from 'signature_pad';
|
import SignaturePad from 'signature_pad';
|
||||||
|
import { PrivateContent } from '@app/components/shared/PrivateContent';
|
||||||
|
|
||||||
interface DrawingCanvasProps {
|
interface DrawingCanvasProps {
|
||||||
selectedColor: string;
|
selectedColor: string;
|
||||||
@ -177,19 +178,21 @@ export const DrawingCanvas: React.FC<DrawingCanvasProps> = ({
|
|||||||
<Paper withBorder p="md">
|
<Paper withBorder p="md">
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
<Text fw={500}>Draw your signature</Text>
|
<Text fw={500}>Draw your signature</Text>
|
||||||
<canvas
|
<PrivateContent>
|
||||||
ref={previewCanvasRef}
|
<canvas
|
||||||
width={width}
|
ref={previewCanvasRef}
|
||||||
height={height}
|
width={width}
|
||||||
style={{
|
height={height}
|
||||||
border: '1px solid #ccc',
|
style={{
|
||||||
borderRadius: '4px',
|
border: '1px solid #ccc',
|
||||||
cursor: disabled ? 'default' : 'pointer',
|
borderRadius: '4px',
|
||||||
backgroundColor: '#ffffff',
|
cursor: disabled ? 'default' : 'pointer',
|
||||||
width: '100%',
|
backgroundColor: '#ffffff',
|
||||||
}}
|
width: '100%',
|
||||||
onClick={disabled ? undefined : openModal}
|
}}
|
||||||
/>
|
onClick={disabled ? undefined : openModal}
|
||||||
|
/>
|
||||||
|
</PrivateContent>
|
||||||
<Text size="sm" c="dimmed" ta="center">
|
<Text size="sm" c="dimmed" ta="center">
|
||||||
Click to open drawing canvas
|
Click to open drawing canvas
|
||||||
</Text>
|
</Text>
|
||||||
@ -246,23 +249,25 @@ export const DrawingCanvas: React.FC<DrawingCanvasProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<canvas
|
<PrivateContent>
|
||||||
ref={(el) => {
|
<canvas
|
||||||
modalCanvasRef.current = el;
|
ref={(el) => {
|
||||||
if (el) initPad(el);
|
modalCanvasRef.current = el;
|
||||||
}}
|
if (el) initPad(el);
|
||||||
style={{
|
}}
|
||||||
border: '1px solid #ccc',
|
style={{
|
||||||
borderRadius: '4px',
|
border: '1px solid #ccc',
|
||||||
display: 'block',
|
borderRadius: '4px',
|
||||||
touchAction: 'none',
|
display: 'block',
|
||||||
backgroundColor: 'white',
|
touchAction: 'none',
|
||||||
width: '100%',
|
backgroundColor: 'white',
|
||||||
maxWidth: '800px',
|
width: '100%',
|
||||||
height: '400px',
|
maxWidth: '800px',
|
||||||
cursor: 'crosshair',
|
height: '400px',
|
||||||
}}
|
cursor: 'crosshair',
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
</PrivateContent>
|
||||||
|
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
<Button variant="subtle" color="red" onClick={clear}>
|
<Button variant="subtle" color="red" onClick={clear}>
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { FileInput, Text, Stack } from '@mantine/core';
|
import { FileInput, Text, Stack } from '@mantine/core';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { PrivateContent } from '@app/components/shared/PrivateContent';
|
||||||
|
|
||||||
interface ImageUploaderProps {
|
interface ImageUploaderProps {
|
||||||
onImageChange: (file: File | null) => void;
|
onImageChange: (file: File | null) => void;
|
||||||
@ -40,13 +41,15 @@ export const ImageUploader: React.FC<ImageUploaderProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
<FileInput
|
<PrivateContent>
|
||||||
label={label || t('sign.image.label', 'Upload signature image')}
|
<FileInput
|
||||||
placeholder={placeholder || t('sign.image.placeholder', 'Select image file')}
|
label={label || t('sign.image.label', 'Upload signature image')}
|
||||||
accept="image/*"
|
placeholder={placeholder || t('sign.image.placeholder', 'Select image file')}
|
||||||
onChange={handleImageChange}
|
accept="image/*"
|
||||||
disabled={disabled}
|
onChange={handleImageChange}
|
||||||
/>
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
</PrivateContent>
|
||||||
<Text size="sm" c="dimmed">
|
<Text size="sm" c="dimmed">
|
||||||
{hint || t('sign.image.hint', 'Upload an image of your signature')}
|
{hint || t('sign.image.hint', 'Upload an image of your signature')}
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
@ -21,6 +21,7 @@ import { FileId } from '@app/types/file';
|
|||||||
import { formatFileSize } from '@app/utils/fileUtils';
|
import { formatFileSize } from '@app/utils/fileUtils';
|
||||||
import ToolChain from '@app/components/shared/ToolChain';
|
import ToolChain from '@app/components/shared/ToolChain';
|
||||||
import HoverActionMenu, { HoverAction } from '@app/components/shared/HoverActionMenu';
|
import HoverActionMenu, { HoverAction } from '@app/components/shared/HoverActionMenu';
|
||||||
|
import { PrivateContent } from '@app/components/shared/PrivateContent';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -328,8 +329,8 @@ const FileEditorThumbnail = ({
|
|||||||
marginTop: '0.5rem',
|
marginTop: '0.5rem',
|
||||||
marginBottom: '0.5rem',
|
marginBottom: '0.5rem',
|
||||||
}}>
|
}}>
|
||||||
<Text size="lg" fw={700} className={`${styles.title} ph-no-capture `} lineClamp={2}>
|
<Text size="lg" fw={700} className={styles.title} lineClamp={2}>
|
||||||
{file.name}
|
<PrivateContent>{file.name}</PrivateContent>
|
||||||
</Text>
|
</Text>
|
||||||
<Text
|
<Text
|
||||||
size="sm"
|
size="sm"
|
||||||
@ -353,20 +354,20 @@ const FileEditorThumbnail = ({
|
|||||||
>
|
>
|
||||||
<div className={styles.previewPaper}>
|
<div className={styles.previewPaper}>
|
||||||
{file.thumbnailUrl && (
|
{file.thumbnailUrl && (
|
||||||
<img
|
<PrivateContent>
|
||||||
className="ph-no-capture"
|
<img
|
||||||
src={file.thumbnailUrl}
|
src={file.thumbnailUrl}
|
||||||
alt={file.name}
|
alt={file.name}
|
||||||
draggable={false}
|
draggable={false}
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
decoding="async"
|
decoding="async"
|
||||||
onError={(e) => {
|
onError={(e) => {
|
||||||
const img = e.currentTarget;
|
const img = e.currentTarget;
|
||||||
img.style.display = 'none';
|
img.style.display = 'none';
|
||||||
img.parentElement?.setAttribute('data-thumb-missing', 'true');
|
img.parentElement?.setAttribute('data-thumb-missing', 'true');
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
maxWidth: '80%',
|
maxWidth: '80%',
|
||||||
maxHeight: '80%',
|
maxHeight: '80%',
|
||||||
objectFit: 'contain',
|
objectFit: 'contain',
|
||||||
borderRadius: 0,
|
borderRadius: 0,
|
||||||
@ -378,6 +379,7 @@ const FileEditorThumbnail = ({
|
|||||||
alignSelf: 'start'
|
alignSelf: 'start'
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</PrivateContent>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import ChevronRightIcon from '@mui/icons-material/ChevronRight';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { getFileSize } from '@app/utils/fileUtils';
|
import { getFileSize } from '@app/utils/fileUtils';
|
||||||
import { StirlingFileStub } from '@app/types/fileContext';
|
import { StirlingFileStub } from '@app/types/fileContext';
|
||||||
|
import { PrivateContent } from '@app/components/shared/PrivateContent';
|
||||||
|
|
||||||
interface CompactFileDetailsProps {
|
interface CompactFileDetailsProps {
|
||||||
currentFile: StirlingFileStub | null;
|
currentFile: StirlingFileStub | null;
|
||||||
@ -41,18 +42,19 @@ const CompactFileDetails: React.FC<CompactFileDetailsProps> = ({
|
|||||||
{/* Small preview */}
|
{/* Small preview */}
|
||||||
<Box style={{ width: '7.5rem', height: '9.375rem', flexShrink: 0, position: 'relative', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
<Box style={{ width: '7.5rem', height: '9.375rem', flexShrink: 0, position: 'relative', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||||
{currentFile && thumbnail ? (
|
{currentFile && thumbnail ? (
|
||||||
<img
|
<PrivateContent>
|
||||||
className='ph-no-capture'
|
<img
|
||||||
src={thumbnail}
|
src={thumbnail}
|
||||||
alt={currentFile.name}
|
alt={currentFile.name}
|
||||||
style={{
|
style={{
|
||||||
maxWidth: '100%',
|
maxWidth: '100%',
|
||||||
maxHeight: '100%',
|
maxHeight: '100%',
|
||||||
objectFit: 'contain',
|
objectFit: 'contain',
|
||||||
borderRadius: '0.25rem',
|
borderRadius: '0.25rem',
|
||||||
boxShadow: '0 2px 4px rgba(0, 0, 0, 0.1)'
|
boxShadow: '0 2px 4px rgba(0, 0, 0, 0.1)'
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</PrivateContent>
|
||||||
) : currentFile ? (
|
) : currentFile ? (
|
||||||
<Center style={{
|
<Center style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
@ -67,8 +69,8 @@ const CompactFileDetails: React.FC<CompactFileDetailsProps> = ({
|
|||||||
|
|
||||||
{/* File info */}
|
{/* File info */}
|
||||||
<Box style={{ flex: 1, minWidth: 0 }}>
|
<Box style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Text className='ph-no-capture' size="sm" fw={500} truncate>
|
<Text size="sm" fw={500} truncate>
|
||||||
{currentFile ? currentFile.name : 'No file selected'}
|
<PrivateContent>{currentFile ? currentFile.name : 'No file selected'}</PrivateContent>
|
||||||
</Text>
|
</Text>
|
||||||
<Text size="xs" c="dimmed">
|
<Text size="xs" c="dimmed">
|
||||||
{currentFile ? getFileSize(currentFile) : ''}
|
{currentFile ? getFileSize(currentFile) : ''}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { detectFileExtension, getFileSize } from '@app/utils/fileUtils';
|
import { detectFileExtension, getFileSize } from '@app/utils/fileUtils';
|
||||||
import { StirlingFileStub } from '@app/types/fileContext';
|
import { StirlingFileStub } from '@app/types/fileContext';
|
||||||
import ToolChain from '@app/components/shared/ToolChain';
|
import ToolChain from '@app/components/shared/ToolChain';
|
||||||
|
import { PrivateContent } from '@app/components/shared/PrivateContent';
|
||||||
|
|
||||||
interface FileInfoCardProps {
|
interface FileInfoCardProps {
|
||||||
currentFile: StirlingFileStub | null;
|
currentFile: StirlingFileStub | null;
|
||||||
@ -26,7 +27,9 @@ const FileInfoCard: React.FC<FileInfoCardProps> = ({
|
|||||||
<ScrollArea style={{ flex: 1 }} p="md">
|
<ScrollArea style={{ flex: 1 }} p="md">
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
<Group justify="space-between" py="xs">
|
<Group justify="space-between" py="xs">
|
||||||
<Text className='ph-no-capture' size="sm" c="dimmed">{t('fileManager.fileName', 'Name')}</Text>
|
<Text size="sm" c="dimmed">
|
||||||
|
<PrivateContent>{t('fileManager.fileName', 'Name')}</PrivateContent>
|
||||||
|
</Text>
|
||||||
<Text size="sm" fw={500} style={{ maxWidth: '60%', textAlign: 'right' }} truncate>
|
<Text size="sm" fw={500} style={{ maxWidth: '60%', textAlign: 'right' }} truncate>
|
||||||
{currentFile ? currentFile.name : ''}
|
{currentFile ? currentFile.name : ''}
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import { useFileManagerContext } from '@app/contexts/FileManagerContext';
|
|||||||
import { zipFileService } from '@app/services/zipFileService';
|
import { zipFileService } from '@app/services/zipFileService';
|
||||||
import ToolChain from '@app/components/shared/ToolChain';
|
import ToolChain from '@app/components/shared/ToolChain';
|
||||||
import { Z_INDEX_OVER_FILE_MANAGER_MODAL } from '@app/styles/zIndex';
|
import { Z_INDEX_OVER_FILE_MANAGER_MODAL } from '@app/styles/zIndex';
|
||||||
|
import { PrivateContent } from '@app/components/shared/PrivateContent';
|
||||||
|
|
||||||
interface FileListItemProps {
|
interface FileListItemProps {
|
||||||
file: StirlingFileStub;
|
file: StirlingFileStub;
|
||||||
@ -99,7 +100,9 @@ const FileListItem: React.FC<FileListItemProps> = ({
|
|||||||
|
|
||||||
<Box style={{ flex: 1, minWidth: 0 }}>
|
<Box style={{ flex: 1, minWidth: 0 }}>
|
||||||
<Group gap="xs" align="center">
|
<Group gap="xs" align="center">
|
||||||
<Text size="sm" fw={500} className='ph-no-capture' truncate style={{ flex: 1 }}>{file.name}</Text>
|
<Text size="sm" fw={500} truncate style={{ flex: 1 }}>
|
||||||
|
<PrivateContent>{file.name}</PrivateContent>
|
||||||
|
</Text>
|
||||||
<Badge size="xs" variant="light" color={"blue"}>
|
<Badge size="xs" variant="light" color={"blue"}>
|
||||||
v{currentVersion}
|
v{currentVersion}
|
||||||
</Badge>
|
</Badge>
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import { draggable, dropTargetForElements } from '@atlaskit/pragmatic-drag-and-d
|
|||||||
import styles from '@app/components/pageEditor/PageEditor.module.css';
|
import styles from '@app/components/pageEditor/PageEditor.module.css';
|
||||||
import { useFileContext } from '@app/contexts/FileContext';
|
import { useFileContext } from '@app/contexts/FileContext';
|
||||||
import { FileId } from '@app/types/file';
|
import { FileId } from '@app/types/file';
|
||||||
|
import { PrivateContent } from '@app/components/shared/PrivateContent';
|
||||||
|
|
||||||
interface FileItem {
|
interface FileItem {
|
||||||
id: FileId;
|
id: FileId;
|
||||||
@ -316,29 +317,30 @@ const FileThumbnail = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{file.thumbnail && (
|
{file.thumbnail && (
|
||||||
<img
|
<PrivateContent>
|
||||||
className="ph-no-capture"
|
<img
|
||||||
src={file.thumbnail}
|
src={file.thumbnail}
|
||||||
alt={file.name}
|
alt={file.name}
|
||||||
draggable={false}
|
draggable={false}
|
||||||
onError={(e) => {
|
onError={(e) => {
|
||||||
// Hide broken image if blob URL was revoked
|
// Hide broken image if blob URL was revoked
|
||||||
const img = e.target as HTMLImageElement;
|
const img = e.target as HTMLImageElement;
|
||||||
img.style.display = 'none';
|
img.style.display = 'none';
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
maxWidth: '80%',
|
||||||
|
maxHeight: '80%',
|
||||||
|
objectFit: 'contain',
|
||||||
|
borderRadius: 0,
|
||||||
|
background: '#ffffff',
|
||||||
|
border: '1px solid var(--border-default)',
|
||||||
|
display: 'block',
|
||||||
|
marginLeft: 'auto',
|
||||||
|
marginRight: 'auto',
|
||||||
|
alignSelf: 'start'
|
||||||
}}
|
}}
|
||||||
style={{
|
/>
|
||||||
maxWidth: '80%',
|
</PrivateContent>
|
||||||
maxHeight: '80%',
|
|
||||||
objectFit: 'contain',
|
|
||||||
borderRadius: 0,
|
|
||||||
background: '#ffffff',
|
|
||||||
border: '1px solid var(--border-default)',
|
|
||||||
display: 'block',
|
|
||||||
marginLeft: 'auto',
|
|
||||||
marginRight: 'auto',
|
|
||||||
alignSelf: 'start'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import { useThumbnailGeneration } from '@app/hooks/useThumbnailGeneration';
|
|||||||
import { useFilesModalContext } from '@app/contexts/FilesModalContext';
|
import { useFilesModalContext } from '@app/contexts/FilesModalContext';
|
||||||
import styles from '@app/components/pageEditor/PageEditor.module.css';
|
import styles from '@app/components/pageEditor/PageEditor.module.css';
|
||||||
import HoverActionMenu, { HoverAction } from '@app/components/shared/HoverActionMenu';
|
import HoverActionMenu, { HoverAction } from '@app/components/shared/HoverActionMenu';
|
||||||
|
import { PrivateContent } from '@app/components/shared/PrivateContent';
|
||||||
|
|
||||||
|
|
||||||
interface PageThumbnailProps {
|
interface PageThumbnailProps {
|
||||||
@ -442,21 +443,22 @@ const PageThumbnail: React.FC<PageThumbnailProps> = ({
|
|||||||
}}></div>
|
}}></div>
|
||||||
</div>
|
</div>
|
||||||
) : thumbnailUrl ? (
|
) : thumbnailUrl ? (
|
||||||
<img
|
<PrivateContent>
|
||||||
className="ph-no-capture"
|
<img
|
||||||
src={thumbnailUrl}
|
src={thumbnailUrl}
|
||||||
alt={`Page ${page.pageNumber}`}
|
alt={`Page ${page.pageNumber}`}
|
||||||
draggable={false}
|
draggable={false}
|
||||||
data-original-rotation={page.rotation}
|
data-original-rotation={page.rotation}
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
objectFit: 'contain',
|
objectFit: 'contain',
|
||||||
borderRadius: 2,
|
borderRadius: 2,
|
||||||
transform: `rotate(${page.rotation}deg)`,
|
transform: `rotate(${page.rotation}deg)`,
|
||||||
transition: 'transform 0.3s ease-in-out'
|
transition: 'transform 0.3s ease-in-out'
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</PrivateContent>
|
||||||
) : (
|
) : (
|
||||||
<div style={{ textAlign: 'center' }}>
|
<div style={{ textAlign: 'center' }}>
|
||||||
<Text size="lg" c="dimmed">📄</Text>
|
<Text size="lg" c="dimmed">📄</Text>
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { Menu, Loader, Group, Text } from '@mantine/core';
|
|||||||
import VisibilityIcon from '@mui/icons-material/Visibility';
|
import VisibilityIcon from '@mui/icons-material/Visibility';
|
||||||
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
|
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
|
||||||
import FitText from '@app/components/shared/FitText';
|
import FitText from '@app/components/shared/FitText';
|
||||||
|
import { PrivateContent } from '@app/components/shared/PrivateContent';
|
||||||
|
|
||||||
interface FileDropdownMenuProps {
|
interface FileDropdownMenuProps {
|
||||||
displayName: string;
|
displayName: string;
|
||||||
@ -31,7 +32,9 @@ export const FileDropdownMenu: React.FC<FileDropdownMenuProps> = ({
|
|||||||
) : (
|
) : (
|
||||||
<VisibilityIcon fontSize="small" />
|
<VisibilityIcon fontSize="small" />
|
||||||
)}
|
)}
|
||||||
<FitText text={displayName} fontSize={14} minimumFontScale={0.6} className="ph-no-capture" />
|
<PrivateContent>
|
||||||
|
<FitText text={displayName} fontSize={14} minimumFontScale={0.6} />
|
||||||
|
</PrivateContent>
|
||||||
<KeyboardArrowDownIcon fontSize="small" />
|
<KeyboardArrowDownIcon fontSize="small" />
|
||||||
</div>
|
</div>
|
||||||
</Menu.Target>
|
</Menu.Target>
|
||||||
@ -61,7 +64,9 @@ export const FileDropdownMenu: React.FC<FileDropdownMenuProps> = ({
|
|||||||
>
|
>
|
||||||
<Group gap="xs" style={{ width: '100%', justifyContent: 'space-between' }}>
|
<Group gap="xs" style={{ width: '100%', justifyContent: 'space-between' }}>
|
||||||
<div style={{ flex: 1, textAlign: 'left', minWidth: 0 }}>
|
<div style={{ flex: 1, textAlign: 'left', minWidth: 0 }}>
|
||||||
<FitText text={itemName} fontSize={14} minimumFontScale={0.7} className="ph-no-capture" />
|
<PrivateContent>
|
||||||
|
<FitText text={itemName} fontSize={14} minimumFontScale={0.7} />
|
||||||
|
</PrivateContent>
|
||||||
</div>
|
</div>
|
||||||
{file.versionNumber && file.versionNumber > 1 && (
|
{file.versionNumber && file.versionNumber > 1 && (
|
||||||
<Text size="xs" c="dimmed">
|
<Text size="xs" c="dimmed">
|
||||||
|
|||||||
40
frontend/src/core/components/shared/PrivateContent.tsx
Normal file
40
frontend/src/core/components/shared/PrivateContent.tsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface PrivateContentProps extends React.HTMLAttributes<HTMLSpanElement> {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper component for content that should not be captured by analytics tools.
|
||||||
|
* Currently applies the 'ph-no-capture' className to prevent PostHog capture.
|
||||||
|
*
|
||||||
|
* Uses `display: contents` to be layout-invisible - the wrapper exists in the DOM
|
||||||
|
* for analytics filtering, but doesn't affect layout, flexbox, grid, or styling.
|
||||||
|
*
|
||||||
|
* Use this component to wrap any content containing sensitive or private information
|
||||||
|
* that should be excluded from analytics tracking.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* <PrivateContent>
|
||||||
|
* <Text>Sensitive filename.pdf</Text>
|
||||||
|
* </PrivateContent>
|
||||||
|
*
|
||||||
|
* <PrivateContent>
|
||||||
|
* <img src={thumbnail} alt="preview" />
|
||||||
|
* </PrivateContent>
|
||||||
|
*/
|
||||||
|
export const PrivateContent: React.FC<PrivateContentProps> = ({
|
||||||
|
children,
|
||||||
|
className = '',
|
||||||
|
style,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
const combinedClassName = `ph-no-capture${className ? ` ${className}` : ''}`;
|
||||||
|
const combinedStyle = { display: 'contents' as const, ...style };
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className={combinedClassName} style={combinedStyle} {...props}>
|
||||||
|
{children}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -9,6 +9,7 @@ import PictureAsPdfIcon from "@mui/icons-material/PictureAsPdf";
|
|||||||
import { WorkbenchType, isValidWorkbench } from '@app/types/workbench';
|
import { WorkbenchType, isValidWorkbench } from '@app/types/workbench';
|
||||||
import type { CustomWorkbenchViewInstance } from '@app/contexts/ToolWorkflowContext';
|
import type { CustomWorkbenchViewInstance } from '@app/contexts/ToolWorkflowContext';
|
||||||
import { FileDropdownMenu } from '@app/components/shared/FileDropdownMenu';
|
import { FileDropdownMenu } from '@app/components/shared/FileDropdownMenu';
|
||||||
|
import { PrivateContent } from '@app/components/shared/PrivateContent';
|
||||||
|
|
||||||
|
|
||||||
const viewOptionStyle: React.CSSProperties = {
|
const viewOptionStyle: React.CSSProperties = {
|
||||||
@ -54,7 +55,7 @@ const createViewOptions = (
|
|||||||
) : (
|
) : (
|
||||||
<VisibilityIcon fontSize="small" />
|
<VisibilityIcon fontSize="small" />
|
||||||
)}
|
)}
|
||||||
<span className="ph-no-capture">{displayName}</span>
|
<PrivateContent>{displayName}</PrivateContent>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
value: "viewer",
|
value: "viewer",
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import React from 'react';
|
|||||||
import { Box, Center, Image } from '@mantine/core';
|
import { Box, Center, Image } from '@mantine/core';
|
||||||
import PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf';
|
import PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf';
|
||||||
import { StirlingFileStub } from '@app/types/fileContext';
|
import { StirlingFileStub } from '@app/types/fileContext';
|
||||||
|
import { PrivateContent } from '@app/components/shared/PrivateContent';
|
||||||
|
|
||||||
export interface DocumentThumbnailProps {
|
export interface DocumentThumbnailProps {
|
||||||
file: File | StirlingFileStub | null;
|
file: File | StirlingFileStub | null;
|
||||||
@ -35,13 +36,14 @@ const DocumentThumbnail: React.FC<DocumentThumbnailProps> = ({
|
|||||||
if (thumbnail) {
|
if (thumbnail) {
|
||||||
return (
|
return (
|
||||||
<Box style={containerStyle} onClick={onClick}>
|
<Box style={containerStyle} onClick={onClick}>
|
||||||
<Image
|
<PrivateContent>
|
||||||
className='ph-no-capture'
|
<Image
|
||||||
src={thumbnail}
|
src={thumbnail}
|
||||||
alt={`Preview of ${file.name}`}
|
alt={`Preview of ${file.name}`}
|
||||||
fit="contain"
|
fit="contain"
|
||||||
style={{ maxWidth: '100%', maxHeight: '100%' }}
|
style={{ maxWidth: '100%', maxHeight: '100%' }}
|
||||||
/>
|
/>
|
||||||
|
</PrivateContent>
|
||||||
{children}
|
{children}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
@ -50,13 +52,14 @@ const DocumentThumbnail: React.FC<DocumentThumbnailProps> = ({
|
|||||||
return (
|
return (
|
||||||
<Box style={containerStyle} onClick={onClick}>
|
<Box style={containerStyle} onClick={onClick}>
|
||||||
<Center style={{ width: '100%', height: '100%', backgroundColor: 'var(--mantine-color-gray-1)', borderRadius: '0.25rem' }}>
|
<Center style={{ width: '100%', height: '100%', backgroundColor: 'var(--mantine-color-gray-1)', borderRadius: '0.25rem' }}>
|
||||||
<PictureAsPdfIcon
|
<PrivateContent>
|
||||||
className='ph-no-capture'
|
<PictureAsPdfIcon
|
||||||
style={{
|
style={{
|
||||||
fontSize: '2rem',
|
fontSize: '2rem',
|
||||||
color: 'var(--mantine-color-gray-6)'
|
color: 'var(--mantine-color-gray-6)'
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</PrivateContent>
|
||||||
</Center>
|
</Center>
|
||||||
{children}
|
{children}
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { AddPageNumbersParameters } from '@app/components/tools/addPageNumbers/u
|
|||||||
import { pdfWorkerManager } from '@app/services/pdfWorkerManager';
|
import { pdfWorkerManager } from '@app/services/pdfWorkerManager';
|
||||||
import { useThumbnailGeneration } from '@app/hooks/useThumbnailGeneration';
|
import { useThumbnailGeneration } from '@app/hooks/useThumbnailGeneration';
|
||||||
import styles from '@app/components/tools/addPageNumbers/PageNumberPreview.module.css';
|
import styles from '@app/components/tools/addPageNumbers/PageNumberPreview.module.css';
|
||||||
|
import { PrivateContent } from '@app/components/shared/PrivateContent';
|
||||||
|
|
||||||
// Simple utilities for page numbers (adapted from stamp)
|
// Simple utilities for page numbers (adapted from stamp)
|
||||||
const A4_ASPECT_RATIO = 0.707;
|
const A4_ASPECT_RATIO = 0.707;
|
||||||
@ -197,12 +198,14 @@ export default function PageNumberPreview({ parameters, onParameterChange, file,
|
|||||||
style={containerStyle}
|
style={containerStyle}
|
||||||
>
|
>
|
||||||
{pageThumbnail && (
|
{pageThumbnail && (
|
||||||
<img
|
<PrivateContent>
|
||||||
src={pageThumbnail}
|
<img
|
||||||
alt="page preview"
|
src={pageThumbnail}
|
||||||
className={`${styles.pageThumbnail} ph-no-capture`}
|
alt="page preview"
|
||||||
draggable={false}
|
className={styles.pageThumbnail}
|
||||||
/>
|
draggable={false}
|
||||||
|
/>
|
||||||
|
</PrivateContent>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Quick position overlay grid - EXACT copy from stamp */}
|
{/* Quick position overlay grid - EXACT copy from stamp */}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { useFilesModalContext } from "@app/contexts/FilesModalContext";
|
|||||||
import { useAllFiles } from "@app/contexts/FileContext";
|
import { useAllFiles } from "@app/contexts/FileContext";
|
||||||
import { useFileManager } from "@app/hooks/useFileManager";
|
import { useFileManager } from "@app/hooks/useFileManager";
|
||||||
import { StirlingFile } from "@app/types/fileContext";
|
import { StirlingFile } from "@app/types/fileContext";
|
||||||
|
import { PrivateContent } from "@app/components/shared/PrivateContent"
|
||||||
|
|
||||||
export interface FileStatusIndicatorProps {
|
export interface FileStatusIndicatorProps {
|
||||||
selectedFiles?: StirlingFile[];
|
selectedFiles?: StirlingFile[];
|
||||||
@ -134,7 +135,9 @@ const FileStatusIndicator = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Text size="sm" c="dimmed" style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}>
|
<Text size="sm" c="dimmed" style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}>
|
||||||
✓ {selectedFiles.length === 1 ? t("fileSelected", "Selected: {{filename}}", { filename: selectedFiles[0]?.name }) : t("filesSelected", "{{count}} files selected", { count: selectedFiles.length })}
|
✓ {selectedFiles.length === 1
|
||||||
|
? <PrivateContent>{t("fileSelected", "Selected: {{filename}}", { filename: selectedFiles[0]?.name }) }</PrivateContent>
|
||||||
|
: t("filesSelected", "{{count}} files selected", { count: selectedFiles.length })}
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { Box, Text, Loader, Stack, Center, Flex } from '@mantine/core';
|
|||||||
import FilePreview from '@app/components/shared/FilePreview';
|
import FilePreview from '@app/components/shared/FilePreview';
|
||||||
import FileMetadata from '@app/components/tools/shared/FileMetadata';
|
import FileMetadata from '@app/components/tools/shared/FileMetadata';
|
||||||
import NavigationControls from '@app/components/tools/shared/NavigationControls';
|
import NavigationControls from '@app/components/tools/shared/NavigationControls';
|
||||||
|
import { PrivateContent } from '@app/components/shared/PrivateContent';
|
||||||
|
|
||||||
export interface ReviewFile {
|
export interface ReviewFile {
|
||||||
file: File;
|
file: File;
|
||||||
@ -62,7 +63,6 @@ const ResultsPreview = ({
|
|||||||
{/* File name at the top */}
|
{/* File name at the top */}
|
||||||
<Box mb="sm" style={{ minHeight: '3rem', display: 'flex', alignItems: 'flex-start' }}>
|
<Box mb="sm" style={{ minHeight: '3rem', display: 'flex', alignItems: 'flex-start' }}>
|
||||||
<Text
|
<Text
|
||||||
className='ph-no-capture'
|
|
||||||
size="sm"
|
size="sm"
|
||||||
fw={500}
|
fw={500}
|
||||||
style={{
|
style={{
|
||||||
@ -71,7 +71,7 @@ const ResultsPreview = ({
|
|||||||
}}
|
}}
|
||||||
title={currentFile.file.name}
|
title={currentFile.file.name}
|
||||||
>
|
>
|
||||||
{currentFile.file.name}
|
<PrivateContent>{currentFile.file.name}</PrivateContent>
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import React, { useEffect, useMemo, useState } from 'react';
|
|||||||
import { createPluginRegistration } from '@embedpdf/core';
|
import { createPluginRegistration } from '@embedpdf/core';
|
||||||
import { EmbedPDF } from '@embedpdf/core/react';
|
import { EmbedPDF } from '@embedpdf/core/react';
|
||||||
import { usePdfiumEngine } from '@embedpdf/engines/react';
|
import { usePdfiumEngine } from '@embedpdf/engines/react';
|
||||||
|
import { PrivateContent } from '@app/components/shared/PrivateContent';
|
||||||
|
|
||||||
// Import the essential plugins
|
// Import the essential plugins
|
||||||
import { Viewport, ViewportPluginPackage } from '@embedpdf/plugin-viewport/react';
|
import { Viewport, ViewportPluginPackage } from '@embedpdf/plugin-viewport/react';
|
||||||
@ -184,18 +185,17 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, onSignatur
|
|||||||
|
|
||||||
// Wrap your UI with the <EmbedPDF> provider
|
// Wrap your UI with the <EmbedPDF> provider
|
||||||
return (
|
return (
|
||||||
<div
|
<PrivateContent>
|
||||||
className='ph-no-capture'
|
<div
|
||||||
|
style={{
|
||||||
style={{
|
height: '100%',
|
||||||
height: '100%',
|
width: '100%',
|
||||||
width: '100%',
|
position: 'relative',
|
||||||
position: 'relative',
|
overflow: 'hidden',
|
||||||
overflow: 'hidden',
|
flex: 1,
|
||||||
flex: 1,
|
minHeight: 0,
|
||||||
minHeight: 0,
|
minWidth: 0,
|
||||||
minWidth: 0,
|
}}>
|
||||||
}}>
|
|
||||||
<EmbedPDF
|
<EmbedPDF
|
||||||
engine={engine}
|
engine={engine}
|
||||||
plugins={plugins}
|
plugins={plugins}
|
||||||
@ -338,6 +338,7 @@ export function LocalEmbedPDF({ file, url, enableAnnotations = false, onSignatur
|
|||||||
</Viewport>
|
</Viewport>
|
||||||
</GlobalPointerProvider>
|
</GlobalPointerProvider>
|
||||||
</EmbedPDF>
|
</EmbedPDF>
|
||||||
</div>
|
</div>
|
||||||
|
</PrivateContent>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { Box, ScrollArea } from '@mantine/core';
|
import { Box, ScrollArea } from '@mantine/core';
|
||||||
import { useViewer } from '@app/contexts/ViewerContext';
|
import { useViewer } from '@app/contexts/ViewerContext';
|
||||||
|
import { PrivateContent } from '@app/components/shared/PrivateContent';
|
||||||
|
|
||||||
interface ThumbnailSidebarProps {
|
interface ThumbnailSidebarProps {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
@ -145,18 +146,19 @@ export function ThumbnailSidebar({ visible, onToggle: _onToggle, activeFileIndex
|
|||||||
>
|
>
|
||||||
{/* Thumbnail Image */}
|
{/* Thumbnail Image */}
|
||||||
{thumbnails[pageIndex] && thumbnails[pageIndex] !== 'error' ? (
|
{thumbnails[pageIndex] && thumbnails[pageIndex] !== 'error' ? (
|
||||||
<img
|
<PrivateContent>
|
||||||
className='ph-no-capture'
|
<img
|
||||||
src={thumbnails[pageIndex]}
|
src={thumbnails[pageIndex]}
|
||||||
alt={`Page ${pageIndex + 1} thumbnail`}
|
alt={`Page ${pageIndex + 1} thumbnail`}
|
||||||
style={{
|
style={{
|
||||||
maxWidth: '100%',
|
maxWidth: '100%',
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
borderRadius: '4px',
|
borderRadius: '4px',
|
||||||
boxShadow: '0 2px 4px rgba(0, 0, 0, 0.1)',
|
boxShadow: '0 2px 4px rgba(0, 0, 0, 0.1)',
|
||||||
border: '1px solid var(--border-subtle)'
|
border: '1px solid var(--border-subtle)'
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</PrivateContent>
|
||||||
) : thumbnails[pageIndex] === 'error' ? (
|
) : thumbnails[pageIndex] === 'error' ? (
|
||||||
<div style={{
|
<div style={{
|
||||||
width: '11.5rem',
|
width: '11.5rem',
|
||||||
|
|||||||
@ -1,9 +0,0 @@
|
|||||||
import { defineConfig } from 'vitest/config';
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
test: {
|
|
||||||
environment: 'node',
|
|
||||||
testTimeout: 5000,
|
|
||||||
include: ['src/utils/convertUtils.test.ts']
|
|
||||||
},
|
|
||||||
});
|
|
||||||
Loading…
Reference in New Issue
Block a user