Merge pull request #1299 from PicPeak/fix/1297-inert-protection-props
fix(gallery): remove the inert image-protection prop surface from AuthenticatedImage
This commit is contained in:
@@ -11,25 +11,12 @@ import {
|
||||
interface AuthenticatedImageProps extends Omit<React.ImgHTMLAttributes<HTMLImageElement>, 'onLoad'> {
|
||||
src: string;
|
||||
fallbackSrc?: string;
|
||||
useWatermark?: boolean;
|
||||
isGallery?: boolean;
|
||||
protectFromDownload?: boolean;
|
||||
slug?: string;
|
||||
photoId?: number;
|
||||
requiresToken?: boolean;
|
||||
secureUrlTemplate?: string;
|
||||
downloadUrlTemplate?: string;
|
||||
onProtectionViolation?: (violationType: string) => void;
|
||||
watermarkText?: string;
|
||||
overlayProtection?: boolean;
|
||||
fragmentGrid?: boolean;
|
||||
scrambleFragments?: boolean;
|
||||
useCanvasRendering?: boolean;
|
||||
blockKeyboardShortcuts?: boolean;
|
||||
detectPrintScreen?: boolean;
|
||||
detectDevTools?: boolean;
|
||||
protectionLevel?: 'basic' | 'standard' | 'enhanced' | 'maximum';
|
||||
useEnhancedProtection?: boolean;
|
||||
/** Fired when the canvas branch blocks a context-menu attempt. The only
|
||||
* protection callback this component actually implements (#1297). */
|
||||
onProtectionViolation?: (violationType: string) => void;
|
||||
onLoad?: () => void;
|
||||
/**
|
||||
* Priority in the shared fetch queue (#1287). NOT the native `fetchPriority`
|
||||
@@ -43,51 +30,44 @@ interface AuthenticatedImageProps extends Omit<React.ImgHTMLAttributes<HTMLImage
|
||||
queuePriority?: 'high' | 'prefetch' | 'normal';
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches an image with the gallery's bearer token and renders it.
|
||||
*
|
||||
* IMAGE PROTECTION IS NOT IMPLEMENTED HERE (#1297). This component used to
|
||||
* accept the whole protection prop surface — protectFromDownload,
|
||||
* watermarkText, fragmentGrid, blockKeyboardShortcuts, detectPrintScreen,
|
||||
* detectDevTools, protectionLevel and the rest — and discard every one of
|
||||
* them in a `void unusedProps` block. Callers computed them from the event's
|
||||
* protection level and passed them in good faith, so raising that level
|
||||
* produced canvas rendering (via the layouts' own OR on
|
||||
* `protectionLevel === 'maximum'`) and nothing else it implies.
|
||||
*
|
||||
* They are removed rather than implemented, so the interface states what the
|
||||
* component actually does. The implementation those props describe already
|
||||
* exists in `ProtectedImage` — which is exported and currently rendered
|
||||
* nowhere. Wiring that in is a deliberate product decision about what
|
||||
* protection level should mean, not a silent side effect of a cleanup.
|
||||
*
|
||||
* Two props survive because they are real:
|
||||
* useCanvasRendering draws to a canvas instead of an <img>
|
||||
* onProtectionViolation fires from the canvas context-menu handler below
|
||||
*
|
||||
* `useWatermark` was removed too. #1297 did not list it — it sat outside the
|
||||
* `unusedProps` block — but it was equally inert: declared, defaulted, never
|
||||
* read.
|
||||
*/
|
||||
export const AuthenticatedImage: React.FC<AuthenticatedImageProps> = ({
|
||||
src,
|
||||
fallbackSrc,
|
||||
alt,
|
||||
useWatermark = false,
|
||||
isGallery = false,
|
||||
protectFromDownload,
|
||||
slug,
|
||||
photoId,
|
||||
requiresToken,
|
||||
secureUrlTemplate,
|
||||
downloadUrlTemplate,
|
||||
onProtectionViolation,
|
||||
watermarkText,
|
||||
overlayProtection,
|
||||
fragmentGrid,
|
||||
scrambleFragments,
|
||||
useCanvasRendering,
|
||||
blockKeyboardShortcuts,
|
||||
detectPrintScreen,
|
||||
detectDevTools,
|
||||
protectionLevel,
|
||||
useEnhancedProtection,
|
||||
onProtectionViolation,
|
||||
onLoad,
|
||||
queuePriority = 'normal',
|
||||
...props
|
||||
}) => {
|
||||
const unusedProps = {
|
||||
protectFromDownload,
|
||||
photoId,
|
||||
requiresToken,
|
||||
secureUrlTemplate,
|
||||
downloadUrlTemplate,
|
||||
onProtectionViolation,
|
||||
watermarkText,
|
||||
overlayProtection,
|
||||
fragmentGrid,
|
||||
scrambleFragments,
|
||||
blockKeyboardShortcuts,
|
||||
detectPrintScreen,
|
||||
detectDevTools,
|
||||
protectionLevel,
|
||||
useEnhancedProtection
|
||||
};
|
||||
void unusedProps;
|
||||
|
||||
const [imageSrc, setImageSrc] = useState<string>('');
|
||||
const [error, setError] = useState(false);
|
||||
|
||||
@@ -41,9 +41,7 @@ export const GalleryFolderTiles: React.FC<GalleryFolderTilesProps> = ({
|
||||
compact = false,
|
||||
slug,
|
||||
protectionLevel,
|
||||
useEnhancedProtection,
|
||||
useCanvasRendering,
|
||||
allowDownloads = true,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -96,12 +94,6 @@ export const GalleryFolderTiles: React.FC<GalleryFolderTilesProps> = ({
|
||||
className="w-full h-full object-cover group-hover:scale-[1.02] transition-transform"
|
||||
isGallery
|
||||
slug={slug}
|
||||
photoId={coverPhoto.id}
|
||||
requiresToken={coverPhoto.requires_token}
|
||||
secureUrlTemplate={coverPhoto.secure_url_template}
|
||||
protectFromDownload={!allowDownloads || useEnhancedProtection}
|
||||
protectionLevel={protectionLevel}
|
||||
useEnhancedProtection={useEnhancedProtection}
|
||||
// Same rule as every other gallery image path: maximum
|
||||
// protection implies canvas rendering even when the separate
|
||||
// toggle is off (its default), otherwise a cover silently
|
||||
|
||||
@@ -43,9 +43,7 @@ export const HeroHeader: React.FC<HeroHeaderProps> = ({
|
||||
heroLogoSize = 'medium',
|
||||
heroLogoPosition = 'top',
|
||||
dividerStyle = 'wave',
|
||||
allowDownloads = true,
|
||||
protectionLevel = 'standard',
|
||||
useEnhancedProtection = false,
|
||||
useCanvasRendering = false,
|
||||
onScrollToContent,
|
||||
heroImageAnchor = 'center'
|
||||
@@ -144,10 +142,6 @@ export const HeroHeader: React.FC<HeroHeaderProps> = ({
|
||||
style={{ objectPosition: heroImageAnchor }}
|
||||
isGallery={true}
|
||||
slug={slug}
|
||||
photoId={heroPhoto.id}
|
||||
protectFromDownload={!allowDownloads || useEnhancedProtection}
|
||||
protectionLevel={protectionLevel}
|
||||
useEnhancedProtection={useEnhancedProtection}
|
||||
useCanvasRendering={useCanvasRendering || protectionLevel === 'maximum'}
|
||||
/>
|
||||
|
||||
|
||||
@@ -144,9 +144,6 @@ export const PeopleSheet: React.FC<PeopleSheetProps> = ({
|
||||
alt=""
|
||||
isGallery
|
||||
slug={slug}
|
||||
photoId={photo.id}
|
||||
requiresToken={photo.requires_token}
|
||||
secureUrlTemplate={photo.secure_url_template}
|
||||
// Crop to the face, exactly as the strip does. Without
|
||||
// this a group photo shows whoever is centred — often
|
||||
// not the person being labelled, and identical for two
|
||||
|
||||
@@ -86,9 +86,6 @@ const PersonAvatar: React.FC<PersonAvatarProps> = ({
|
||||
alt=""
|
||||
isGallery
|
||||
slug={slug}
|
||||
photoId={photo.id}
|
||||
requiresToken={photo.requires_token}
|
||||
secureUrlTemplate={photo.secure_url_template}
|
||||
style={cropStyle || { width: '100%', height: '100%', objectFit: 'cover' }}
|
||||
/>
|
||||
) : (
|
||||
|
||||
@@ -244,7 +244,6 @@ const PhotoThumbnail: React.FC<PhotoThumbnailProps> = ({
|
||||
onDownload,
|
||||
allowDownloads = true,
|
||||
protectionLevel = 'standard',
|
||||
useEnhancedProtection = false,
|
||||
useCanvasRendering = false,
|
||||
slug,
|
||||
feedbackEnabled = false
|
||||
@@ -269,18 +268,7 @@ const PhotoThumbnail: React.FC<PhotoThumbnailProps> = ({
|
||||
loading="lazy"
|
||||
isGallery={true}
|
||||
slug={slug}
|
||||
photoId={photo.id}
|
||||
requiresToken={photo.requires_token}
|
||||
secureUrlTemplate={photo.secure_url_template}
|
||||
protectFromDownload={!allowDownloads || useEnhancedProtection}
|
||||
protectionLevel={protectionLevel}
|
||||
useEnhancedProtection={useEnhancedProtection}
|
||||
useCanvasRendering={useCanvasRendering || protectionLevel === 'maximum'}
|
||||
fragmentGrid={protectionLevel === 'enhanced' || protectionLevel === 'maximum'}
|
||||
blockKeyboardShortcuts={useEnhancedProtection}
|
||||
detectPrintScreen={useEnhancedProtection}
|
||||
detectDevTools={protectionLevel === 'maximum'}
|
||||
watermarkText={useEnhancedProtection ? 'Protected' : undefined}
|
||||
onProtectionViolation={(violationType) => {
|
||||
// Track analytics
|
||||
if (typeof window !== 'undefined' && (window as any).umami) {
|
||||
|
||||
@@ -1159,9 +1159,6 @@ export const PhotoLightbox: React.FC<PhotoLightboxProps> = ({
|
||||
draggable={false}
|
||||
isGallery={true}
|
||||
slug={slug}
|
||||
photoId={photo.id}
|
||||
requiresToken={photo.requires_token}
|
||||
secureUrlTemplate={photo.secure_url_template}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -1189,21 +1186,9 @@ export const PhotoLightbox: React.FC<PhotoLightboxProps> = ({
|
||||
transition: isDragging ? 'none' : 'transform 0.2s',
|
||||
}}
|
||||
draggable={false}
|
||||
useWatermark={useEnhancedProtection}
|
||||
watermarkText={useEnhancedProtection ? `${photo.filename} - Protected` : undefined}
|
||||
isGallery={true}
|
||||
slug={slug}
|
||||
photoId={photo.id}
|
||||
requiresToken={photo.requires_token}
|
||||
secureUrlTemplate={photo.secure_url_template}
|
||||
protectFromDownload={!allowDownloads || useEnhancedProtection}
|
||||
protectionLevel={protectionLevel}
|
||||
useEnhancedProtection={useEnhancedProtection}
|
||||
useCanvasRendering={useCanvasRendering || protectionLevel === 'maximum'}
|
||||
fragmentGrid={protectionLevel === 'enhanced' || protectionLevel === 'maximum'}
|
||||
blockKeyboardShortcuts={useEnhancedProtection}
|
||||
detectPrintScreen={useEnhancedProtection}
|
||||
detectDevTools={protectionLevel === 'enhanced' || protectionLevel === 'maximum'}
|
||||
onProtectionViolation={(violationType) => {
|
||||
console.warn(`Protection violation in lightbox for photo ${photo.id}: ${violationType}`);
|
||||
|
||||
|
||||
@@ -89,7 +89,6 @@ export const CarouselGalleryLayout: React.FC<BaseGalleryLayoutProps> = ({
|
||||
alt={currentPhoto.filename}
|
||||
className="w-full h-full object-contain"
|
||||
isGallery={true}
|
||||
protectFromDownload={!allowDownloads}
|
||||
/>
|
||||
|
||||
{/* Colour labels for the photo in view (#1189). Bottom-left because it
|
||||
@@ -267,7 +266,6 @@ export const CarouselGalleryLayout: React.FC<BaseGalleryLayoutProps> = ({
|
||||
className="w-full h-full object-cover"
|
||||
loading="lazy"
|
||||
isGallery={true}
|
||||
protectFromDownload={!allowDownloads}
|
||||
/>
|
||||
{/* The strip is the only place this layout shows more than one
|
||||
photo at a time, so it is the only place a label can
|
||||
|
||||
@@ -68,9 +68,7 @@ const PhotoCard: React.FC<PhotoCardProps> = ({
|
||||
isSelectionMode,
|
||||
isLiked,
|
||||
slug,
|
||||
allowDownloads = true,
|
||||
protectionLevel = 'standard',
|
||||
useEnhancedProtection = false,
|
||||
useCanvasRendering = false,
|
||||
feedbackEnabled = false,
|
||||
allowLikes = false,
|
||||
@@ -124,12 +122,6 @@ const PhotoCard: React.FC<PhotoCardProps> = ({
|
||||
loading="lazy"
|
||||
isGallery={true}
|
||||
slug={slug}
|
||||
photoId={photo.id}
|
||||
requiresToken={photo.requires_token}
|
||||
secureUrlTemplate={photo.secure_url_template}
|
||||
protectFromDownload={!allowDownloads || useEnhancedProtection}
|
||||
protectionLevel={protectionLevel}
|
||||
useEnhancedProtection={useEnhancedProtection}
|
||||
useCanvasRendering={useCanvasRendering || protectionLevel === 'maximum'}
|
||||
/>
|
||||
|
||||
|
||||
@@ -48,7 +48,6 @@ const GridPhoto: React.FC<GridPhotoProps> = ({
|
||||
allowDownloads = true,
|
||||
slug,
|
||||
protectionLevel = 'standard',
|
||||
useEnhancedProtection = false,
|
||||
useCanvasRendering = false,
|
||||
feedbackEnabled = false,
|
||||
feedbackOptions,
|
||||
@@ -93,18 +92,7 @@ const GridPhoto: React.FC<GridPhotoProps> = ({
|
||||
loading: 'lazy',
|
||||
isGallery: true,
|
||||
slug,
|
||||
photoId: photo.id,
|
||||
requiresToken: photo.requires_token,
|
||||
secureUrlTemplate: photo.secure_url_template,
|
||||
protectFromDownload: !allowDownloads || useEnhancedProtection,
|
||||
protectionLevel,
|
||||
useEnhancedProtection,
|
||||
useCanvasRendering: useCanvasRendering || protectionLevel === 'maximum',
|
||||
fragmentGrid: protectionLevel === 'enhanced' || protectionLevel === 'maximum',
|
||||
blockKeyboardShortcuts: useEnhancedProtection,
|
||||
detectPrintScreen: useEnhancedProtection,
|
||||
detectDevTools: protectionLevel === 'maximum',
|
||||
watermarkText: useEnhancedProtection ? 'Protected' : undefined,
|
||||
onProtectionViolation: (violationType: string) => {
|
||||
console.warn(`Protection violation on grid photo ${photo.id}: ${violationType}`);
|
||||
},
|
||||
|
||||
@@ -70,7 +70,6 @@ const JustifiedPhoto: React.FC<JustifiedPhotoProps> = ({
|
||||
allowDownloads = true,
|
||||
slug,
|
||||
protectionLevel = 'standard',
|
||||
useEnhancedProtection = false,
|
||||
useCanvasRendering = false,
|
||||
feedbackEnabled = false,
|
||||
feedbackOptions,
|
||||
@@ -134,18 +133,7 @@ const JustifiedPhoto: React.FC<JustifiedPhotoProps> = ({
|
||||
loading: 'lazy',
|
||||
isGallery: true,
|
||||
slug,
|
||||
photoId: photo.id,
|
||||
requiresToken: photo.requires_token,
|
||||
secureUrlTemplate: photo.secure_url_template,
|
||||
protectFromDownload: !allowDownloads || useEnhancedProtection,
|
||||
protectionLevel,
|
||||
useEnhancedProtection,
|
||||
useCanvasRendering: useCanvasRendering || protectionLevel === 'maximum',
|
||||
fragmentGrid: protectionLevel === 'enhanced' || protectionLevel === 'maximum',
|
||||
blockKeyboardShortcuts: useEnhancedProtection,
|
||||
detectPrintScreen: useEnhancedProtection,
|
||||
detectDevTools: protectionLevel === 'maximum',
|
||||
watermarkText: useEnhancedProtection ? 'Protected' : undefined,
|
||||
onProtectionViolation: (violationType: string) => {
|
||||
console.warn(`Protection violation on justified photo ${photo.id}: ${violationType}`);
|
||||
},
|
||||
@@ -411,10 +399,6 @@ export const JustifiedGalleryLayout: React.FC<JustifiedGalleryLayoutProps> = ({
|
||||
className="w-full h-full object-cover"
|
||||
isGallery={true}
|
||||
slug={slug}
|
||||
photoId={heroPhoto.id}
|
||||
protectFromDownload={!allowDownloads || useEnhancedProtection}
|
||||
protectionLevel={protectionLevel}
|
||||
useEnhancedProtection={useEnhancedProtection}
|
||||
useCanvasRendering={useCanvasRendering || protectionLevel === 'maximum'}
|
||||
/>
|
||||
|
||||
|
||||
@@ -120,7 +120,6 @@ const MasonryPhoto: React.FC<MasonryPhotoProps> = ({
|
||||
className: 'w-full h-full object-cover rounded-lg',
|
||||
loading: 'lazy',
|
||||
isGallery: true,
|
||||
protectFromDownload: !allowDownloads,
|
||||
}}
|
||||
overlayBaseClassName="absolute inset-0 bg-black/40 transition-opacity duration-200 rounded-lg flex items-center justify-center gap-2"
|
||||
allowDownloads={allowDownloads}
|
||||
@@ -376,7 +375,6 @@ export const MasonryGalleryLayout: React.FC<BaseGalleryLayoutProps> = ({
|
||||
className: 'w-full h-full object-cover rounded-lg transition-transform duration-300 group-hover:scale-[1.02]',
|
||||
loading: 'lazy',
|
||||
isGallery: true,
|
||||
protectFromDownload: !allowDownloads,
|
||||
}}
|
||||
overlayBaseClassName="absolute inset-0 bg-black/40 transition-opacity duration-200 rounded-lg flex items-center justify-center gap-2"
|
||||
actionVariant="dark"
|
||||
@@ -434,7 +432,6 @@ export const MasonryGalleryLayout: React.FC<BaseGalleryLayoutProps> = ({
|
||||
className: 'w-full h-full object-cover rounded-lg transition-transform duration-300 group-hover:scale-[1.02]',
|
||||
loading: 'lazy',
|
||||
isGallery: true,
|
||||
protectFromDownload: !allowDownloads,
|
||||
}}
|
||||
overlayBaseClassName="absolute inset-0 bg-black/40 transition-opacity duration-200 rounded-lg flex items-center justify-center gap-2"
|
||||
actionVariant="dark"
|
||||
@@ -501,7 +498,6 @@ export const MasonryGalleryLayout: React.FC<BaseGalleryLayoutProps> = ({
|
||||
className: 'w-full h-full object-cover transition-transform duration-300 group-hover:scale-105',
|
||||
loading: 'lazy',
|
||||
isGallery: true,
|
||||
protectFromDownload: !allowDownloads,
|
||||
}}
|
||||
overlayBaseClassName="absolute inset-0 bg-black/40 transition-opacity duration-200 flex items-center justify-center gap-2"
|
||||
actionVariant="dark"
|
||||
|
||||
@@ -83,7 +83,6 @@ const MosaicPhoto: React.FC<MosaicPhotoProps> = ({
|
||||
className: 'w-full h-full object-cover transition-transform duration-300 group-hover:scale-105',
|
||||
loading: 'lazy',
|
||||
isGallery: true,
|
||||
protectFromDownload: !allowDownloads,
|
||||
}}
|
||||
overlayBaseClassName="absolute inset-0 bg-black/40 transition-opacity duration-200 flex items-center justify-center gap-2"
|
||||
allowDownloads={allowDownloads}
|
||||
|
||||
@@ -130,7 +130,6 @@ export const TimelineGalleryLayout: React.FC<BaseGalleryLayoutProps> = ({
|
||||
className: 'w-full h-full object-cover rounded-lg',
|
||||
loading: 'lazy',
|
||||
isGallery: true,
|
||||
protectFromDownload: !allowDownloads,
|
||||
}}
|
||||
overlayBaseClassName="absolute inset-0 bg-black/40 transition-opacity duration-200 rounded-lg flex items-center justify-center gap-2"
|
||||
allowDownloads={allowDownloads}
|
||||
|
||||
@@ -22,9 +22,7 @@ export const StoryHero: React.FC<StoryHeroProps> = ({
|
||||
stats,
|
||||
photo,
|
||||
slug,
|
||||
allowDownloads = true,
|
||||
protectionLevel = 'standard',
|
||||
useEnhancedProtection = false,
|
||||
useCanvasRendering = false
|
||||
}) => {
|
||||
const formattedDate = date
|
||||
@@ -51,12 +49,6 @@ export const StoryHero: React.FC<StoryHeroProps> = ({
|
||||
className="w-full h-full object-cover"
|
||||
isGallery={true}
|
||||
slug={slug}
|
||||
photoId={photo.id}
|
||||
requiresToken={photo.requires_token}
|
||||
secureUrlTemplate={photo.secure_url_template}
|
||||
protectFromDownload={!allowDownloads || useEnhancedProtection}
|
||||
protectionLevel={protectionLevel}
|
||||
useEnhancedProtection={useEnhancedProtection}
|
||||
useCanvasRendering={useCanvasRendering || protectionLevel === 'maximum'}
|
||||
/>
|
||||
) : (
|
||||
|
||||
@@ -28,9 +28,7 @@ export const StoryPhotoCard: React.FC<StoryPhotoCardProps> = ({
|
||||
onToggleFavorite,
|
||||
onClick,
|
||||
slug,
|
||||
allowDownloads = true,
|
||||
protectionLevel = 'standard',
|
||||
useEnhancedProtection = false,
|
||||
useCanvasRendering = false,
|
||||
featured = false,
|
||||
galleryId: _galleryId
|
||||
@@ -105,12 +103,6 @@ export const StoryPhotoCard: React.FC<StoryPhotoCardProps> = ({
|
||||
}`}
|
||||
isGallery={true}
|
||||
slug={slug}
|
||||
photoId={photo.id}
|
||||
requiresToken={photo.requires_token}
|
||||
secureUrlTemplate={photo.secure_url_template}
|
||||
protectFromDownload={!allowDownloads || useEnhancedProtection}
|
||||
protectionLevel={protectionLevel}
|
||||
useEnhancedProtection={useEnhancedProtection}
|
||||
useCanvasRendering={useCanvasRendering || protectionLevel === 'maximum'}
|
||||
/>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user