Merge pull request #1322 from PicPeak/fix/canvas-lightbox-only
fix(gallery): keep canvas rendering in the lightbox, render tiles as <img>
This commit is contained in:
@@ -22,9 +22,7 @@ interface GalleryFolderTilesProps {
|
|||||||
* so a gallery configured for canvas rendering or maximum protection must not
|
* so a gallery configured for canvas rendering or maximum protection must not
|
||||||
* get an ordinary blob-backed <img> here just because it is a cover.
|
* get an ordinary blob-backed <img> here just because it is a cover.
|
||||||
*/
|
*/
|
||||||
protectionLevel?: 'basic' | 'standard' | 'enhanced' | 'maximum';
|
|
||||||
useEnhancedProtection?: boolean;
|
useEnhancedProtection?: boolean;
|
||||||
useCanvasRendering?: boolean;
|
|
||||||
allowDownloads?: boolean;
|
allowDownloads?: boolean;
|
||||||
/**
|
/**
|
||||||
* Compact chip row instead of cover cards. Used by the full-bleed layouts
|
* Compact chip row instead of cover cards. Used by the full-bleed layouts
|
||||||
@@ -40,8 +38,6 @@ export const GalleryFolderTiles: React.FC<GalleryFolderTilesProps> = ({
|
|||||||
onOpen,
|
onOpen,
|
||||||
compact = false,
|
compact = false,
|
||||||
slug,
|
slug,
|
||||||
protectionLevel,
|
|
||||||
useCanvasRendering,
|
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@@ -95,10 +91,6 @@ export const GalleryFolderTiles: React.FC<GalleryFolderTilesProps> = ({
|
|||||||
isGallery
|
isGallery
|
||||||
slug={slug}
|
slug={slug}
|
||||||
// Same rule as every other gallery image path: maximum
|
// 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
|
|
||||||
// falls back to a blob-backed <img>.
|
|
||||||
useCanvasRendering={useCanvasRendering || protectionLevel === 'maximum'}
|
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="w-full h-full flex items-center justify-center">
|
<div className="w-full h-full flex items-center justify-center">
|
||||||
|
|||||||
@@ -1311,9 +1311,7 @@ export const GalleryView: React.FC<GalleryViewProps> = ({ slug, event, requiresP
|
|||||||
onOpen={openFolderBySlug}
|
onOpen={openFolderBySlug}
|
||||||
compact={compact}
|
compact={compact}
|
||||||
slug={slug}
|
slug={slug}
|
||||||
protectionLevel={protectionLevel}
|
|
||||||
useEnhancedProtection={protectionLevel !== 'basic'}
|
useEnhancedProtection={protectionLevel !== 'basic'}
|
||||||
useCanvasRendering={useCanvasRendering}
|
|
||||||
allowDownloads={allowDownloads}
|
allowDownloads={allowDownloads}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -23,9 +23,7 @@ interface HeroHeaderProps {
|
|||||||
heroLogoPosition?: 'top' | 'center' | 'bottom';
|
heroLogoPosition?: 'top' | 'center' | 'bottom';
|
||||||
dividerStyle?: HeroDividerStyle;
|
dividerStyle?: HeroDividerStyle;
|
||||||
allowDownloads?: boolean;
|
allowDownloads?: boolean;
|
||||||
protectionLevel?: 'basic' | 'standard' | 'enhanced' | 'maximum';
|
|
||||||
useEnhancedProtection?: boolean;
|
useEnhancedProtection?: boolean;
|
||||||
useCanvasRendering?: boolean;
|
|
||||||
onScrollToContent?: () => void;
|
onScrollToContent?: () => void;
|
||||||
// Hero image anchor position (#162) – keyword or "X% Y%" focal point
|
// Hero image anchor position (#162) – keyword or "X% Y%" focal point
|
||||||
heroImageAnchor?: string;
|
heroImageAnchor?: string;
|
||||||
@@ -43,8 +41,6 @@ export const HeroHeader: React.FC<HeroHeaderProps> = ({
|
|||||||
heroLogoSize = 'medium',
|
heroLogoSize = 'medium',
|
||||||
heroLogoPosition = 'top',
|
heroLogoPosition = 'top',
|
||||||
dividerStyle = 'wave',
|
dividerStyle = 'wave',
|
||||||
protectionLevel = 'standard',
|
|
||||||
useCanvasRendering = false,
|
|
||||||
onScrollToContent,
|
onScrollToContent,
|
||||||
heroImageAnchor = 'center'
|
heroImageAnchor = 'center'
|
||||||
}) => {
|
}) => {
|
||||||
@@ -142,7 +138,6 @@ export const HeroHeader: React.FC<HeroHeaderProps> = ({
|
|||||||
style={{ objectPosition: heroImageAnchor }}
|
style={{ objectPosition: heroImageAnchor }}
|
||||||
isGallery={true}
|
isGallery={true}
|
||||||
slug={slug}
|
slug={slug}
|
||||||
useCanvasRendering={useCanvasRendering || protectionLevel === 'maximum'}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Overlay */}
|
{/* Overlay */}
|
||||||
|
|||||||
@@ -196,7 +196,6 @@ export const PhotoGrid: React.FC<PhotoGridProps> = ({
|
|||||||
allowDownloads={allowDownloads}
|
allowDownloads={allowDownloads}
|
||||||
protectionLevel={protectionLevel}
|
protectionLevel={protectionLevel}
|
||||||
useEnhancedProtection={useEnhancedProtection}
|
useEnhancedProtection={useEnhancedProtection}
|
||||||
useCanvasRendering={useCanvasRendering}
|
|
||||||
slug={slug}
|
slug={slug}
|
||||||
feedbackEnabled={feedbackEnabled}
|
feedbackEnabled={feedbackEnabled}
|
||||||
/>
|
/>
|
||||||
@@ -214,6 +213,7 @@ export const PhotoGrid: React.FC<PhotoGridProps> = ({
|
|||||||
allowDownloads={allowDownloads}
|
allowDownloads={allowDownloads}
|
||||||
protectionLevel={protectionLevel}
|
protectionLevel={protectionLevel}
|
||||||
useEnhancedProtection={useEnhancedProtection}
|
useEnhancedProtection={useEnhancedProtection}
|
||||||
|
useCanvasRendering={useCanvasRendering}
|
||||||
disableRightClick={disableRightClick}
|
disableRightClick={disableRightClick}
|
||||||
enableDevtoolsProtection={enableDevtoolsProtection}
|
enableDevtoolsProtection={enableDevtoolsProtection}
|
||||||
/>
|
/>
|
||||||
@@ -231,7 +231,6 @@ interface PhotoThumbnailProps {
|
|||||||
allowDownloads?: boolean;
|
allowDownloads?: boolean;
|
||||||
protectionLevel?: 'basic' | 'standard' | 'enhanced' | 'maximum';
|
protectionLevel?: 'basic' | 'standard' | 'enhanced' | 'maximum';
|
||||||
useEnhancedProtection?: boolean;
|
useEnhancedProtection?: boolean;
|
||||||
useCanvasRendering?: boolean;
|
|
||||||
slug: string; // Add slug as required prop
|
slug: string; // Add slug as required prop
|
||||||
feedbackEnabled?: boolean;
|
feedbackEnabled?: boolean;
|
||||||
}
|
}
|
||||||
@@ -244,7 +243,6 @@ const PhotoThumbnail: React.FC<PhotoThumbnailProps> = ({
|
|||||||
onDownload,
|
onDownload,
|
||||||
allowDownloads = true,
|
allowDownloads = true,
|
||||||
protectionLevel = 'standard',
|
protectionLevel = 'standard',
|
||||||
useCanvasRendering = false,
|
|
||||||
slug,
|
slug,
|
||||||
feedbackEnabled = false
|
feedbackEnabled = false
|
||||||
}) => {
|
}) => {
|
||||||
@@ -268,7 +266,6 @@ const PhotoThumbnail: React.FC<PhotoThumbnailProps> = ({
|
|||||||
loading="lazy"
|
loading="lazy"
|
||||||
isGallery={true}
|
isGallery={true}
|
||||||
slug={slug}
|
slug={slug}
|
||||||
useCanvasRendering={useCanvasRendering || protectionLevel === 'maximum'}
|
|
||||||
onProtectionViolation={(violationType) => {
|
onProtectionViolation={(violationType) => {
|
||||||
// Track analytics
|
// Track analytics
|
||||||
if (typeof window !== 'undefined' && (window as any).umami) {
|
if (typeof window !== 'undefined' && (window as any).umami) {
|
||||||
|
|||||||
@@ -361,9 +361,7 @@ export const PhotoGridWithLayouts: React.FC<PhotoGridWithLayoutsProps> = ({
|
|||||||
heroLogoPosition={heroLogoPosition}
|
heroLogoPosition={heroLogoPosition}
|
||||||
dividerStyle={heroDividerStyle}
|
dividerStyle={heroDividerStyle}
|
||||||
allowDownloads={allowDownloads}
|
allowDownloads={allowDownloads}
|
||||||
protectionLevel={protectionLevel}
|
|
||||||
useEnhancedProtection={useEnhancedProtection}
|
useEnhancedProtection={useEnhancedProtection}
|
||||||
useCanvasRendering={useCanvasRendering}
|
|
||||||
heroImageAnchor={heroImageAnchor}
|
heroImageAnchor={heroImageAnchor}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
/**
|
||||||
|
* Canvas rendering is a lightbox concern, not a tile concern.
|
||||||
|
*
|
||||||
|
* A canvas pins a backing store of naturalWidth × naturalHeight × 4 bytes
|
||||||
|
* that the browser is not allowed to evict, and iOS Safari has a hard
|
||||||
|
* budget for canvas memory that fails silently when exceeded — blank
|
||||||
|
* tiles, no error. A gallery is hundreds of tiles and one lightbox image,
|
||||||
|
* so the tiles render <img> whatever the protection level says, and the
|
||||||
|
* lightbox keeps the per-event toggle and the `maximum` implication.
|
||||||
|
*
|
||||||
|
* Source-level pin: nothing under components/gallery except PhotoLightbox
|
||||||
|
* may hand `useCanvasRendering` to AuthenticatedImage.
|
||||||
|
*/
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
|
||||||
|
const root = path.resolve(__dirname, '..');
|
||||||
|
function walk(dir: string): string[] {
|
||||||
|
return fs.readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
|
||||||
|
const file = path.join(dir, entry.name);
|
||||||
|
if (entry.isDirectory()) return entry.name === '__tests__' ? [] : walk(file);
|
||||||
|
return file.endsWith('.tsx') ? [file] : [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('canvas rendering stays in the lightbox', () => {
|
||||||
|
const files = walk(root);
|
||||||
|
const lightbox = path.join(root, 'PhotoLightbox.tsx');
|
||||||
|
|
||||||
|
/** The JSX props of every <AuthenticatedImage> in a file, plus every
|
||||||
|
* `imageProps={{ ... }}` object a layout hands to PhotoCard to spread in. */
|
||||||
|
const imageProps = (source: string) => [
|
||||||
|
...source.split('<AuthenticatedImage').slice(1).map((chunk) => chunk.split('/>')[0]),
|
||||||
|
...source.split('imageProps={{').slice(1).map((chunk) => chunk.split('}}')[0]),
|
||||||
|
];
|
||||||
|
|
||||||
|
it('only PhotoLightbox passes useCanvasRendering to AuthenticatedImage', () => {
|
||||||
|
const offenders = files.filter((file) => file !== lightbox
|
||||||
|
&& imageProps(fs.readFileSync(file, 'utf8')).some((props) => props.includes('useCanvasRendering')));
|
||||||
|
expect(offenders.map((f) => path.relative(root, f))).toEqual([]);
|
||||||
|
// The pin has teeth: the lightbox itself is caught by the same probe.
|
||||||
|
expect(imageProps(fs.readFileSync(lightbox, 'utf8')).some((props) => props.includes('useCanvasRendering'))).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('only PhotoLightbox turns canvas on for protection level maximum', () => {
|
||||||
|
const offenders = files.filter((file) => file !== lightbox
|
||||||
|
&& /useCanvasRendering[^\n]*protectionLevel === 'maximum'/.test(fs.readFileSync(file, 'utf8')));
|
||||||
|
expect(offenders.map((f) => path.relative(root, f))).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('the lightbox still does both', () => {
|
||||||
|
const source = fs.readFileSync(lightbox, 'utf8');
|
||||||
|
expect(source).toMatch(/useCanvasRendering=\{useCanvasRendering \|\| protectionLevel === 'maximum'\}/);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -47,6 +47,7 @@ export interface BaseGalleryLayoutProps {
|
|||||||
onPickResolution?: (photoIds: number[]) => void;
|
onPickResolution?: (photoIds: number[]) => void;
|
||||||
protectionLevel?: 'basic' | 'standard' | 'enhanced' | 'maximum';
|
protectionLevel?: 'basic' | 'standard' | 'enhanced' | 'maximum';
|
||||||
useEnhancedProtection?: boolean;
|
useEnhancedProtection?: boolean;
|
||||||
|
/** Canvas rendering applies to the lightbox only; tiles always render <img>. */
|
||||||
useCanvasRendering?: boolean;
|
useCanvasRendering?: boolean;
|
||||||
feedbackEnabled?: boolean;
|
feedbackEnabled?: boolean;
|
||||||
feedbackOptions?: {
|
feedbackOptions?: {
|
||||||
|
|||||||
@@ -46,9 +46,7 @@ interface PhotoCardProps {
|
|||||||
isLiked: boolean;
|
isLiked: boolean;
|
||||||
slug: string;
|
slug: string;
|
||||||
allowDownloads?: boolean;
|
allowDownloads?: boolean;
|
||||||
protectionLevel?: 'basic' | 'standard' | 'enhanced' | 'maximum';
|
|
||||||
useEnhancedProtection?: boolean;
|
useEnhancedProtection?: boolean;
|
||||||
useCanvasRendering?: boolean;
|
|
||||||
feedbackEnabled?: boolean;
|
feedbackEnabled?: boolean;
|
||||||
// #506: track the per-event "allow likes" toggle so the per-photo
|
// #506: track the per-event "allow likes" toggle so the per-photo
|
||||||
// Like button respects it. `feedbackEnabled` alone isn't enough —
|
// Like button respects it. `feedbackEnabled` alone isn't enough —
|
||||||
@@ -68,8 +66,6 @@ const PhotoCard: React.FC<PhotoCardProps> = ({
|
|||||||
isSelectionMode,
|
isSelectionMode,
|
||||||
isLiked,
|
isLiked,
|
||||||
slug,
|
slug,
|
||||||
protectionLevel = 'standard',
|
|
||||||
useCanvasRendering = false,
|
|
||||||
feedbackEnabled = false,
|
feedbackEnabled = false,
|
||||||
allowLikes = false,
|
allowLikes = false,
|
||||||
index
|
index
|
||||||
@@ -122,7 +118,6 @@ const PhotoCard: React.FC<PhotoCardProps> = ({
|
|||||||
loading="lazy"
|
loading="lazy"
|
||||||
isGallery={true}
|
isGallery={true}
|
||||||
slug={slug}
|
slug={slug}
|
||||||
useCanvasRendering={useCanvasRendering || protectionLevel === 'maximum'}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Colour label (#1044) — same badge every layout uses. */}
|
{/* Colour label (#1044) — same badge every layout uses. */}
|
||||||
@@ -208,9 +203,7 @@ export const GalleryPremiumLayout: React.FC<GalleryPremiumLayoutProps> = ({
|
|||||||
allowDownloads = true,
|
allowDownloads = true,
|
||||||
downloadChoices,
|
downloadChoices,
|
||||||
onPickResolution,
|
onPickResolution,
|
||||||
protectionLevel = 'standard',
|
|
||||||
useEnhancedProtection = false,
|
useEnhancedProtection = false,
|
||||||
useCanvasRendering = false,
|
|
||||||
feedbackEnabled = false,
|
feedbackEnabled = false,
|
||||||
feedbackOptions,
|
feedbackOptions,
|
||||||
heroPhotoOverride,
|
heroPhotoOverride,
|
||||||
@@ -629,9 +622,7 @@ export const GalleryPremiumLayout: React.FC<GalleryPremiumLayoutProps> = ({
|
|||||||
isLiked={likedPhotoIds.has(originalPhoto.id)}
|
isLiked={likedPhotoIds.has(originalPhoto.id)}
|
||||||
slug={slug}
|
slug={slug}
|
||||||
allowDownloads={allowDownloads}
|
allowDownloads={allowDownloads}
|
||||||
protectionLevel={protectionLevel}
|
|
||||||
useEnhancedProtection={useEnhancedProtection}
|
useEnhancedProtection={useEnhancedProtection}
|
||||||
useCanvasRendering={useCanvasRendering}
|
|
||||||
feedbackEnabled={feedbackEnabled}
|
feedbackEnabled={feedbackEnabled}
|
||||||
allowLikes={!!feedbackOptions?.allowLikes}
|
allowLikes={!!feedbackOptions?.allowLikes}
|
||||||
index={photoIndex}
|
index={photoIndex}
|
||||||
|
|||||||
@@ -255,9 +255,7 @@ export const GalleryStoryLayout: React.FC<GalleryStoryLayoutProps> = ({
|
|||||||
photo={heroPhoto}
|
photo={heroPhoto}
|
||||||
slug={slug}
|
slug={slug}
|
||||||
allowDownloads={allowDownloads}
|
allowDownloads={allowDownloads}
|
||||||
protectionLevel={protectionLevel}
|
|
||||||
useEnhancedProtection={useEnhancedProtection}
|
useEnhancedProtection={useEnhancedProtection}
|
||||||
useCanvasRendering={useCanvasRendering}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Main Content - Scenes */}
|
{/* Main Content - Scenes */}
|
||||||
@@ -281,9 +279,7 @@ export const GalleryStoryLayout: React.FC<GalleryStoryLayoutProps> = ({
|
|||||||
onPhotoClick={handleOpenLightbox}
|
onPhotoClick={handleOpenLightbox}
|
||||||
slug={slug}
|
slug={slug}
|
||||||
allowDownloads={allowDownloads}
|
allowDownloads={allowDownloads}
|
||||||
protectionLevel={protectionLevel}
|
|
||||||
useEnhancedProtection={useEnhancedProtection}
|
useEnhancedProtection={useEnhancedProtection}
|
||||||
useCanvasRendering={useCanvasRendering}
|
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div id={`gallery-${scene.id}`} className="story-gallery-grid">
|
<div id={`gallery-${scene.id}`} className="story-gallery-grid">
|
||||||
@@ -298,9 +294,7 @@ export const GalleryStoryLayout: React.FC<GalleryStoryLayoutProps> = ({
|
|||||||
slug={slug}
|
slug={slug}
|
||||||
galleryId={`gallery-${scene.id}`}
|
galleryId={`gallery-${scene.id}`}
|
||||||
allowDownloads={allowDownloads}
|
allowDownloads={allowDownloads}
|
||||||
protectionLevel={protectionLevel}
|
|
||||||
useEnhancedProtection={useEnhancedProtection}
|
useEnhancedProtection={useEnhancedProtection}
|
||||||
useCanvasRendering={useCanvasRendering}
|
|
||||||
// Mark first photo in each grid as featured
|
// Mark first photo in each grid as featured
|
||||||
featured={index === 0 && scene.photos.length > 4}
|
featured={index === 0 && scene.photos.length > 4}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -18,9 +18,7 @@ interface GridPhotoProps {
|
|||||||
animationType?: string;
|
animationType?: string;
|
||||||
allowDownloads?: boolean;
|
allowDownloads?: boolean;
|
||||||
slug?: string;
|
slug?: string;
|
||||||
protectionLevel?: 'basic' | 'standard' | 'enhanced' | 'maximum';
|
|
||||||
useEnhancedProtection?: boolean;
|
useEnhancedProtection?: boolean;
|
||||||
useCanvasRendering?: boolean;
|
|
||||||
feedbackEnabled?: boolean;
|
feedbackEnabled?: boolean;
|
||||||
feedbackOptions?: {
|
feedbackOptions?: {
|
||||||
allowLikes?: boolean;
|
allowLikes?: boolean;
|
||||||
@@ -47,8 +45,6 @@ const GridPhoto: React.FC<GridPhotoProps> = ({
|
|||||||
animationType = 'fade',
|
animationType = 'fade',
|
||||||
allowDownloads = true,
|
allowDownloads = true,
|
||||||
slug,
|
slug,
|
||||||
protectionLevel = 'standard',
|
|
||||||
useCanvasRendering = false,
|
|
||||||
feedbackEnabled = false,
|
feedbackEnabled = false,
|
||||||
feedbackOptions,
|
feedbackOptions,
|
||||||
savedIdentity,
|
savedIdentity,
|
||||||
@@ -130,7 +126,6 @@ const GridPhoto: React.FC<GridPhotoProps> = ({
|
|||||||
loading: 'lazy',
|
loading: 'lazy',
|
||||||
isGallery: true,
|
isGallery: true,
|
||||||
slug,
|
slug,
|
||||||
useCanvasRendering: useCanvasRendering || protectionLevel === 'maximum',
|
|
||||||
onProtectionViolation: (violationType: string) => {
|
onProtectionViolation: (violationType: string) => {
|
||||||
console.warn(`Protection violation on grid photo ${photo.id}: ${violationType}`);
|
console.warn(`Protection violation on grid photo ${photo.id}: ${violationType}`);
|
||||||
},
|
},
|
||||||
@@ -200,9 +195,7 @@ export const GridGalleryLayout: React.FC<BaseGalleryLayoutProps> = ({
|
|||||||
isSelectionMode = false,
|
isSelectionMode = false,
|
||||||
onPhotoSelect,
|
onPhotoSelect,
|
||||||
allowDownloads = true,
|
allowDownloads = true,
|
||||||
protectionLevel = 'standard',
|
|
||||||
useEnhancedProtection = false,
|
useEnhancedProtection = false,
|
||||||
useCanvasRendering = false,
|
|
||||||
feedbackEnabled = false,
|
feedbackEnabled = false,
|
||||||
feedbackOptions,
|
feedbackOptions,
|
||||||
isClient = false,
|
isClient = false,
|
||||||
@@ -255,9 +248,7 @@ export const GridGalleryLayout: React.FC<BaseGalleryLayoutProps> = ({
|
|||||||
animationType={animation}
|
animationType={animation}
|
||||||
allowDownloads={allowDownloads}
|
allowDownloads={allowDownloads}
|
||||||
slug={slug}
|
slug={slug}
|
||||||
protectionLevel={protectionLevel}
|
|
||||||
useEnhancedProtection={useEnhancedProtection}
|
useEnhancedProtection={useEnhancedProtection}
|
||||||
useCanvasRendering={useCanvasRendering}
|
|
||||||
feedbackEnabled={feedbackEnabled}
|
feedbackEnabled={feedbackEnabled}
|
||||||
feedbackOptions={feedbackOptions}
|
feedbackOptions={feedbackOptions}
|
||||||
savedIdentity={savedIdentity}
|
savedIdentity={savedIdentity}
|
||||||
|
|||||||
@@ -40,9 +40,7 @@ interface JustifiedPhotoProps {
|
|||||||
animationType?: string;
|
animationType?: string;
|
||||||
allowDownloads?: boolean;
|
allowDownloads?: boolean;
|
||||||
slug?: string;
|
slug?: string;
|
||||||
protectionLevel?: 'basic' | 'standard' | 'enhanced' | 'maximum';
|
|
||||||
useEnhancedProtection?: boolean;
|
useEnhancedProtection?: boolean;
|
||||||
useCanvasRendering?: boolean;
|
|
||||||
feedbackEnabled?: boolean;
|
feedbackEnabled?: boolean;
|
||||||
feedbackOptions?: {
|
feedbackOptions?: {
|
||||||
allowLikes?: boolean;
|
allowLikes?: boolean;
|
||||||
@@ -69,8 +67,6 @@ const JustifiedPhoto: React.FC<JustifiedPhotoProps> = ({
|
|||||||
animationType = 'fade',
|
animationType = 'fade',
|
||||||
allowDownloads = true,
|
allowDownloads = true,
|
||||||
slug,
|
slug,
|
||||||
protectionLevel = 'standard',
|
|
||||||
useCanvasRendering = false,
|
|
||||||
feedbackEnabled = false,
|
feedbackEnabled = false,
|
||||||
feedbackOptions,
|
feedbackOptions,
|
||||||
savedIdentity,
|
savedIdentity,
|
||||||
@@ -133,7 +129,6 @@ const JustifiedPhoto: React.FC<JustifiedPhotoProps> = ({
|
|||||||
loading: 'lazy',
|
loading: 'lazy',
|
||||||
isGallery: true,
|
isGallery: true,
|
||||||
slug,
|
slug,
|
||||||
useCanvasRendering: useCanvasRendering || protectionLevel === 'maximum',
|
|
||||||
onProtectionViolation: (violationType: string) => {
|
onProtectionViolation: (violationType: string) => {
|
||||||
console.warn(`Protection violation on justified photo ${photo.id}: ${violationType}`);
|
console.warn(`Protection violation on justified photo ${photo.id}: ${violationType}`);
|
||||||
},
|
},
|
||||||
@@ -214,9 +209,7 @@ export const JustifiedGalleryLayout: React.FC<JustifiedGalleryLayoutProps> = ({
|
|||||||
isSelectionMode = false,
|
isSelectionMode = false,
|
||||||
onPhotoSelect,
|
onPhotoSelect,
|
||||||
allowDownloads = true,
|
allowDownloads = true,
|
||||||
protectionLevel = 'standard',
|
|
||||||
useEnhancedProtection = false,
|
useEnhancedProtection = false,
|
||||||
useCanvasRendering = false,
|
|
||||||
feedbackEnabled = false,
|
feedbackEnabled = false,
|
||||||
feedbackOptions,
|
feedbackOptions,
|
||||||
// Hero props
|
// Hero props
|
||||||
@@ -399,7 +392,6 @@ export const JustifiedGalleryLayout: React.FC<JustifiedGalleryLayoutProps> = ({
|
|||||||
className="w-full h-full object-cover"
|
className="w-full h-full object-cover"
|
||||||
isGallery={true}
|
isGallery={true}
|
||||||
slug={slug}
|
slug={slug}
|
||||||
useCanvasRendering={useCanvasRendering || protectionLevel === 'maximum'}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Overlay */}
|
{/* Overlay */}
|
||||||
@@ -523,9 +515,7 @@ export const JustifiedGalleryLayout: React.FC<JustifiedGalleryLayoutProps> = ({
|
|||||||
animationType={animation}
|
animationType={animation}
|
||||||
allowDownloads={allowDownloads}
|
allowDownloads={allowDownloads}
|
||||||
slug={slug}
|
slug={slug}
|
||||||
protectionLevel={protectionLevel}
|
|
||||||
useEnhancedProtection={useEnhancedProtection}
|
useEnhancedProtection={useEnhancedProtection}
|
||||||
useCanvasRendering={useCanvasRendering}
|
|
||||||
feedbackEnabled={feedbackEnabled}
|
feedbackEnabled={feedbackEnabled}
|
||||||
feedbackOptions={feedbackOptions}
|
feedbackOptions={feedbackOptions}
|
||||||
savedIdentity={savedIdentity}
|
savedIdentity={savedIdentity}
|
||||||
|
|||||||
@@ -14,9 +14,7 @@ interface StoryCarouselProps {
|
|||||||
slug: string;
|
slug: string;
|
||||||
id: string;
|
id: string;
|
||||||
allowDownloads?: boolean;
|
allowDownloads?: boolean;
|
||||||
protectionLevel?: 'basic' | 'standard' | 'enhanced' | 'maximum';
|
|
||||||
useEnhancedProtection?: boolean;
|
useEnhancedProtection?: boolean;
|
||||||
useCanvasRendering?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const StoryCarousel: React.FC<StoryCarouselProps> = ({
|
export const StoryCarousel: React.FC<StoryCarouselProps> = ({
|
||||||
@@ -27,9 +25,7 @@ export const StoryCarousel: React.FC<StoryCarouselProps> = ({
|
|||||||
slug,
|
slug,
|
||||||
id,
|
id,
|
||||||
allowDownloads = true,
|
allowDownloads = true,
|
||||||
protectionLevel = 'standard',
|
|
||||||
useEnhancedProtection = false,
|
useEnhancedProtection = false,
|
||||||
useCanvasRendering = false
|
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div id={id} className="story-carousel">
|
<div id={id} className="story-carousel">
|
||||||
@@ -53,9 +49,7 @@ export const StoryCarousel: React.FC<StoryCarouselProps> = ({
|
|||||||
slug={slug}
|
slug={slug}
|
||||||
galleryId={id}
|
galleryId={id}
|
||||||
allowDownloads={allowDownloads}
|
allowDownloads={allowDownloads}
|
||||||
protectionLevel={protectionLevel}
|
|
||||||
useEnhancedProtection={useEnhancedProtection}
|
useEnhancedProtection={useEnhancedProtection}
|
||||||
useCanvasRendering={useCanvasRendering}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</SwiperSlide>
|
</SwiperSlide>
|
||||||
|
|||||||
@@ -11,9 +11,7 @@ interface StoryHeroProps {
|
|||||||
photo?: Photo | null;
|
photo?: Photo | null;
|
||||||
slug: string;
|
slug: string;
|
||||||
allowDownloads?: boolean;
|
allowDownloads?: boolean;
|
||||||
protectionLevel?: 'basic' | 'standard' | 'enhanced' | 'maximum';
|
|
||||||
useEnhancedProtection?: boolean;
|
useEnhancedProtection?: boolean;
|
||||||
useCanvasRendering?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const StoryHero: React.FC<StoryHeroProps> = ({
|
export const StoryHero: React.FC<StoryHeroProps> = ({
|
||||||
@@ -22,8 +20,6 @@ export const StoryHero: React.FC<StoryHeroProps> = ({
|
|||||||
stats,
|
stats,
|
||||||
photo,
|
photo,
|
||||||
slug,
|
slug,
|
||||||
protectionLevel = 'standard',
|
|
||||||
useCanvasRendering = false
|
|
||||||
}) => {
|
}) => {
|
||||||
const formattedDate = date
|
const formattedDate = date
|
||||||
? new Date(date).toLocaleDateString('en-US', {
|
? new Date(date).toLocaleDateString('en-US', {
|
||||||
@@ -49,7 +45,6 @@ export const StoryHero: React.FC<StoryHeroProps> = ({
|
|||||||
className="w-full h-full object-cover"
|
className="w-full h-full object-cover"
|
||||||
isGallery={true}
|
isGallery={true}
|
||||||
slug={slug}
|
slug={slug}
|
||||||
useCanvasRendering={useCanvasRendering || protectionLevel === 'maximum'}
|
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="w-full h-full bg-gray-900" />
|
<div className="w-full h-full bg-gray-900" />
|
||||||
|
|||||||
@@ -14,9 +14,7 @@ interface StoryPhotoCardProps {
|
|||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
slug: string;
|
slug: string;
|
||||||
allowDownloads?: boolean;
|
allowDownloads?: boolean;
|
||||||
protectionLevel?: 'basic' | 'standard' | 'enhanced' | 'maximum';
|
|
||||||
useEnhancedProtection?: boolean;
|
useEnhancedProtection?: boolean;
|
||||||
useCanvasRendering?: boolean;
|
|
||||||
featured?: boolean;
|
featured?: boolean;
|
||||||
galleryId: string;
|
galleryId: string;
|
||||||
}
|
}
|
||||||
@@ -28,8 +26,6 @@ export const StoryPhotoCard: React.FC<StoryPhotoCardProps> = ({
|
|||||||
onToggleFavorite,
|
onToggleFavorite,
|
||||||
onClick,
|
onClick,
|
||||||
slug,
|
slug,
|
||||||
protectionLevel = 'standard',
|
|
||||||
useCanvasRendering = false,
|
|
||||||
featured = false,
|
featured = false,
|
||||||
galleryId: _galleryId
|
galleryId: _galleryId
|
||||||
}) => {
|
}) => {
|
||||||
@@ -103,7 +99,6 @@ export const StoryPhotoCard: React.FC<StoryPhotoCardProps> = ({
|
|||||||
}`}
|
}`}
|
||||||
isGallery={true}
|
isGallery={true}
|
||||||
slug={slug}
|
slug={slug}
|
||||||
useCanvasRendering={useCanvasRendering || protectionLevel === 'maximum'}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ export const ImageSecurityTab: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
<Image className="w-4 h-4 ml-2 mr-1 text-neutral-500" />
|
<Image className="w-4 h-4 ml-2 mr-1 text-neutral-500" />
|
||||||
<span className="text-sm text-neutral-700 dark:text-neutral-300">
|
<span className="text-sm text-neutral-700 dark:text-neutral-300">
|
||||||
{t('settings.imageSecurity.enableCanvas', 'Enable canvas rendering by default (advanced protection)')}
|
{t('settings.imageSecurity.enableCanvas', 'Enable canvas rendering in the lightbox by default (advanced protection)')}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1938,7 +1938,7 @@
|
|||||||
"disableRightClick": "Rechtsklick-Menü blockieren",
|
"disableRightClick": "Rechtsklick-Menü blockieren",
|
||||||
"watermarkDownloads": "Wasserzeichen bei Downloads hinzufügen",
|
"watermarkDownloads": "Wasserzeichen bei Downloads hinzufügen",
|
||||||
"enableDevtoolsProtection": "Entwicklertools erkennen",
|
"enableDevtoolsProtection": "Entwicklertools erkennen",
|
||||||
"useCanvasRendering": "Canvas-Rendering (erweiterter Schutz)",
|
"useCanvasRendering": "Canvas-Rendering in der Lightbox (erweiterter Schutz)",
|
||||||
"protectionInfo": "Schutzfunktionen helfen, unerlaubte Downloads zu verhindern, können jedoch nicht alle Methoden blockieren.",
|
"protectionInfo": "Schutzfunktionen helfen, unerlaubte Downloads zu verhindern, können jedoch nicht alle Methoden blockieren.",
|
||||||
"protectionLevelBasic": "Einfach - Nur Rechtsklick-Sperre",
|
"protectionLevelBasic": "Einfach - Nur Rechtsklick-Sperre",
|
||||||
"protectionLevelStandard": "Standard - Tastenkombinationen blockiert",
|
"protectionLevelStandard": "Standard - Tastenkombinationen blockiert",
|
||||||
@@ -2378,7 +2378,7 @@
|
|||||||
"protectionLevel": "Standard-Schutzstufe",
|
"protectionLevel": "Standard-Schutzstufe",
|
||||||
"imageQuality": "Standard-Bildqualität",
|
"imageQuality": "Standard-Bildqualität",
|
||||||
"enableDevtools": "DevTools-Erkennung standardmäßig aktivieren",
|
"enableDevtools": "DevTools-Erkennung standardmäßig aktivieren",
|
||||||
"enableCanvas": "Canvas-Rendering standardmäßig aktivieren (erweiterter Schutz)",
|
"enableCanvas": "Canvas-Rendering in der Lightbox standardmäßig aktivieren (erweiterter Schutz)",
|
||||||
"rateLimiting": "Ratenbegrenzung",
|
"rateLimiting": "Ratenbegrenzung",
|
||||||
"rateLimitingHelp": "Begrenzen Sie, wie viele Bilder angefordert werden können, um Scraping zu verhindern.",
|
"rateLimitingHelp": "Begrenzen Sie, wie viele Bilder angefordert werden können, um Scraping zu verhindern.",
|
||||||
"requestsPerMinute": "Anfragen pro Minute",
|
"requestsPerMinute": "Anfragen pro Minute",
|
||||||
|
|||||||
@@ -1434,7 +1434,7 @@
|
|||||||
"disableRightClick": "Block right-click menu",
|
"disableRightClick": "Block right-click menu",
|
||||||
"watermarkDownloads": "Add watermark to downloads",
|
"watermarkDownloads": "Add watermark to downloads",
|
||||||
"enableDevtoolsProtection": "Detect developer tools",
|
"enableDevtoolsProtection": "Detect developer tools",
|
||||||
"useCanvasRendering": "Canvas rendering (advanced protection)",
|
"useCanvasRendering": "Canvas rendering in the lightbox (advanced protection)",
|
||||||
"protectionInfo": "Protection features help prevent unauthorized downloads but cannot block all methods.",
|
"protectionInfo": "Protection features help prevent unauthorized downloads but cannot block all methods.",
|
||||||
"protectionLevelBasic": "Basic - Right-click blocking only",
|
"protectionLevelBasic": "Basic - Right-click blocking only",
|
||||||
"protectionLevelStandard": "Standard - Keyboard shortcuts blocked",
|
"protectionLevelStandard": "Standard - Keyboard shortcuts blocked",
|
||||||
@@ -1941,7 +1941,7 @@
|
|||||||
"protectionLevel": "Default Protection Level",
|
"protectionLevel": "Default Protection Level",
|
||||||
"imageQuality": "Default Image Quality",
|
"imageQuality": "Default Image Quality",
|
||||||
"enableDevtools": "Enable DevTools detection by default",
|
"enableDevtools": "Enable DevTools detection by default",
|
||||||
"enableCanvas": "Enable canvas rendering by default (advanced protection)",
|
"enableCanvas": "Enable canvas rendering in the lightbox by default (advanced protection)",
|
||||||
"rateLimiting": "Rate Limiting",
|
"rateLimiting": "Rate Limiting",
|
||||||
"rateLimitingHelp": "Limit how many images can be requested to prevent scraping.",
|
"rateLimitingHelp": "Limit how many images can be requested to prevent scraping.",
|
||||||
"requestsPerMinute": "Requests per minute",
|
"requestsPerMinute": "Requests per minute",
|
||||||
|
|||||||
@@ -498,7 +498,7 @@
|
|||||||
"disableRightClick": "Bloquer le clic droit",
|
"disableRightClick": "Bloquer le clic droit",
|
||||||
"watermarkDownloads": "Ajouter un filigrane aux téléchargements",
|
"watermarkDownloads": "Ajouter un filigrane aux téléchargements",
|
||||||
"enableDevtoolsProtection": "Détecter les outils de développement",
|
"enableDevtoolsProtection": "Détecter les outils de développement",
|
||||||
"useCanvasRendering": "Rendu canvas (protection avancée)",
|
"useCanvasRendering": "Rendu canvas dans la visionneuse (protection avancée)",
|
||||||
"protectionInfo": "Les fonctionnalités de protection aident à prévenir les téléchargements non autorisés mais ne peuvent pas bloquer toutes les méthodes.",
|
"protectionInfo": "Les fonctionnalités de protection aident à prévenir les téléchargements non autorisés mais ne peuvent pas bloquer toutes les méthodes.",
|
||||||
"protectionLevelBasic": "Basique - Blocage du clic droit uniquement",
|
"protectionLevelBasic": "Basique - Blocage du clic droit uniquement",
|
||||||
"protectionLevelStandard": "Standard - Blocage des raccourcis clavier",
|
"protectionLevelStandard": "Standard - Blocage des raccourcis clavier",
|
||||||
@@ -912,7 +912,7 @@
|
|||||||
"protectionLevel": "Niveau de protection par défaut",
|
"protectionLevel": "Niveau de protection par défaut",
|
||||||
"imageQuality": "Qualité d'image par défaut",
|
"imageQuality": "Qualité d'image par défaut",
|
||||||
"enableDevtools": "Activer la détection des outils DevTools par défaut",
|
"enableDevtools": "Activer la détection des outils DevTools par défaut",
|
||||||
"enableCanvas": "Activer le rendu canvas par défaut (protection avancée)",
|
"enableCanvas": "Activer le rendu canvas dans la visionneuse par défaut (protection avancée)",
|
||||||
"rateLimiting": "Limitation de débit",
|
"rateLimiting": "Limitation de débit",
|
||||||
"rateLimitingHelp": "Limitez le nombre d'images qui peuvent être demandées pour éviter le scraping.",
|
"rateLimitingHelp": "Limitez le nombre d'images qui peuvent être demandées pour éviter le scraping.",
|
||||||
"requestsPerMinute": "Requêtes par minute",
|
"requestsPerMinute": "Requêtes par minute",
|
||||||
|
|||||||
@@ -498,7 +498,7 @@
|
|||||||
"disableRightClick": "Blokiraj meni desnega klika",
|
"disableRightClick": "Blokiraj meni desnega klika",
|
||||||
"watermarkDownloads": "Dodaj vodni žig prenosom",
|
"watermarkDownloads": "Dodaj vodni žig prenosom",
|
||||||
"enableDevtoolsProtection": "Zaznaj orodja za razvijalce",
|
"enableDevtoolsProtection": "Zaznaj orodja za razvijalce",
|
||||||
"useCanvasRendering": "Izris prek canvas (napredna zaščita)",
|
"useCanvasRendering": "Izris prek canvas v pregledovalniku (napredna zaščita)",
|
||||||
"protectionInfo": "Zaščitne funkcije pomagajo preprečiti nepooblaščene prenose, vendar ne morejo blokirati vseh metod.",
|
"protectionInfo": "Zaščitne funkcije pomagajo preprečiti nepooblaščene prenose, vendar ne morejo blokirati vseh metod.",
|
||||||
"protectionLevelBasic": "Osnovno - samo blokada desnega klika",
|
"protectionLevelBasic": "Osnovno - samo blokada desnega klika",
|
||||||
"protectionLevelStandard": "Standardno - blokirane bližnjice na tipkovnici",
|
"protectionLevelStandard": "Standardno - blokirane bližnjice na tipkovnici",
|
||||||
@@ -912,7 +912,7 @@
|
|||||||
"protectionLevel": "Privzeta raven zaščite",
|
"protectionLevel": "Privzeta raven zaščite",
|
||||||
"imageQuality": "Privzeta kakovost slike",
|
"imageQuality": "Privzeta kakovost slike",
|
||||||
"enableDevtools": "Privzeto omogoči zaznavanje DevTools",
|
"enableDevtools": "Privzeto omogoči zaznavanje DevTools",
|
||||||
"enableCanvas": "Privzeto omogoči izris prek canvas (napredna zaščita)",
|
"enableCanvas": "Privzeto omogoči izris prek canvas v pregledovalniku (napredna zaščita)",
|
||||||
"rateLimiting": "Omejevanje hitrosti",
|
"rateLimiting": "Omejevanje hitrosti",
|
||||||
"rateLimitingHelp": "Omejite, koliko slik je mogoče zahtevati, da preprečite množično pobiranje.",
|
"rateLimitingHelp": "Omejite, koliko slik je mogoče zahtevati, da preprečite množično pobiranje.",
|
||||||
"requestsPerMinute": "Zahtev na minuto",
|
"requestsPerMinute": "Zahtev na minuto",
|
||||||
|
|||||||
@@ -669,7 +669,7 @@ export const EventInformationCard: React.FC<EventInformationCardProps> = ({
|
|||||||
className="w-4 h-4 text-accent border-neutral-300 dark:border-neutral-600 rounded focus:ring-primary-500"
|
className="w-4 h-4 text-accent border-neutral-300 dark:border-neutral-600 rounded focus:ring-primary-500"
|
||||||
/>
|
/>
|
||||||
<Image className="w-4 h-4 ml-2 mr-1 text-neutral-500 dark:text-neutral-400" />
|
<Image className="w-4 h-4 ml-2 mr-1 text-neutral-500 dark:text-neutral-400" />
|
||||||
<span className="text-sm text-neutral-700 dark:text-neutral-300">{t('events.useCanvasRendering', 'Canvas rendering (advanced protection)')}</span>
|
<span className="text-sm text-neutral-700 dark:text-neutral-300">{t('events.useCanvasRendering', 'Canvas rendering in the lightbox (advanced protection)')}</span>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<p className="text-xs text-neutral-500 dark:text-neutral-400 mt-2">
|
<p className="text-xs text-neutral-500 dark:text-neutral-400 mt-2">
|
||||||
|
|||||||
Reference in New Issue
Block a user