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
|
||||
* get an ordinary blob-backed <img> here just because it is a cover.
|
||||
*/
|
||||
protectionLevel?: 'basic' | 'standard' | 'enhanced' | 'maximum';
|
||||
useEnhancedProtection?: boolean;
|
||||
useCanvasRendering?: boolean;
|
||||
allowDownloads?: boolean;
|
||||
/**
|
||||
* Compact chip row instead of cover cards. Used by the full-bleed layouts
|
||||
@@ -40,8 +38,6 @@ export const GalleryFolderTiles: React.FC<GalleryFolderTilesProps> = ({
|
||||
onOpen,
|
||||
compact = false,
|
||||
slug,
|
||||
protectionLevel,
|
||||
useCanvasRendering,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -95,10 +91,6 @@ export const GalleryFolderTiles: React.FC<GalleryFolderTilesProps> = ({
|
||||
isGallery
|
||||
slug={slug}
|
||||
// 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">
|
||||
|
||||
@@ -1311,9 +1311,7 @@ export const GalleryView: React.FC<GalleryViewProps> = ({ slug, event, requiresP
|
||||
onOpen={openFolderBySlug}
|
||||
compact={compact}
|
||||
slug={slug}
|
||||
protectionLevel={protectionLevel}
|
||||
useEnhancedProtection={protectionLevel !== 'basic'}
|
||||
useCanvasRendering={useCanvasRendering}
|
||||
allowDownloads={allowDownloads}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -23,9 +23,7 @@ interface HeroHeaderProps {
|
||||
heroLogoPosition?: 'top' | 'center' | 'bottom';
|
||||
dividerStyle?: HeroDividerStyle;
|
||||
allowDownloads?: boolean;
|
||||
protectionLevel?: 'basic' | 'standard' | 'enhanced' | 'maximum';
|
||||
useEnhancedProtection?: boolean;
|
||||
useCanvasRendering?: boolean;
|
||||
onScrollToContent?: () => void;
|
||||
// Hero image anchor position (#162) – keyword or "X% Y%" focal point
|
||||
heroImageAnchor?: string;
|
||||
@@ -43,8 +41,6 @@ export const HeroHeader: React.FC<HeroHeaderProps> = ({
|
||||
heroLogoSize = 'medium',
|
||||
heroLogoPosition = 'top',
|
||||
dividerStyle = 'wave',
|
||||
protectionLevel = 'standard',
|
||||
useCanvasRendering = false,
|
||||
onScrollToContent,
|
||||
heroImageAnchor = 'center'
|
||||
}) => {
|
||||
@@ -142,7 +138,6 @@ export const HeroHeader: React.FC<HeroHeaderProps> = ({
|
||||
style={{ objectPosition: heroImageAnchor }}
|
||||
isGallery={true}
|
||||
slug={slug}
|
||||
useCanvasRendering={useCanvasRendering || protectionLevel === 'maximum'}
|
||||
/>
|
||||
|
||||
{/* Overlay */}
|
||||
|
||||
@@ -196,7 +196,6 @@ export const PhotoGrid: React.FC<PhotoGridProps> = ({
|
||||
allowDownloads={allowDownloads}
|
||||
protectionLevel={protectionLevel}
|
||||
useEnhancedProtection={useEnhancedProtection}
|
||||
useCanvasRendering={useCanvasRendering}
|
||||
slug={slug}
|
||||
feedbackEnabled={feedbackEnabled}
|
||||
/>
|
||||
@@ -214,6 +213,7 @@ export const PhotoGrid: React.FC<PhotoGridProps> = ({
|
||||
allowDownloads={allowDownloads}
|
||||
protectionLevel={protectionLevel}
|
||||
useEnhancedProtection={useEnhancedProtection}
|
||||
useCanvasRendering={useCanvasRendering}
|
||||
disableRightClick={disableRightClick}
|
||||
enableDevtoolsProtection={enableDevtoolsProtection}
|
||||
/>
|
||||
@@ -231,7 +231,6 @@ interface PhotoThumbnailProps {
|
||||
allowDownloads?: boolean;
|
||||
protectionLevel?: 'basic' | 'standard' | 'enhanced' | 'maximum';
|
||||
useEnhancedProtection?: boolean;
|
||||
useCanvasRendering?: boolean;
|
||||
slug: string; // Add slug as required prop
|
||||
feedbackEnabled?: boolean;
|
||||
}
|
||||
@@ -244,7 +243,6 @@ const PhotoThumbnail: React.FC<PhotoThumbnailProps> = ({
|
||||
onDownload,
|
||||
allowDownloads = true,
|
||||
protectionLevel = 'standard',
|
||||
useCanvasRendering = false,
|
||||
slug,
|
||||
feedbackEnabled = false
|
||||
}) => {
|
||||
@@ -268,7 +266,6 @@ const PhotoThumbnail: React.FC<PhotoThumbnailProps> = ({
|
||||
loading="lazy"
|
||||
isGallery={true}
|
||||
slug={slug}
|
||||
useCanvasRendering={useCanvasRendering || protectionLevel === 'maximum'}
|
||||
onProtectionViolation={(violationType) => {
|
||||
// Track analytics
|
||||
if (typeof window !== 'undefined' && (window as any).umami) {
|
||||
|
||||
@@ -361,9 +361,7 @@ export const PhotoGridWithLayouts: React.FC<PhotoGridWithLayoutsProps> = ({
|
||||
heroLogoPosition={heroLogoPosition}
|
||||
dividerStyle={heroDividerStyle}
|
||||
allowDownloads={allowDownloads}
|
||||
protectionLevel={protectionLevel}
|
||||
useEnhancedProtection={useEnhancedProtection}
|
||||
useCanvasRendering={useCanvasRendering}
|
||||
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;
|
||||
protectionLevel?: 'basic' | 'standard' | 'enhanced' | 'maximum';
|
||||
useEnhancedProtection?: boolean;
|
||||
/** Canvas rendering applies to the lightbox only; tiles always render <img>. */
|
||||
useCanvasRendering?: boolean;
|
||||
feedbackEnabled?: boolean;
|
||||
feedbackOptions?: {
|
||||
|
||||
@@ -46,9 +46,7 @@ interface PhotoCardProps {
|
||||
isLiked: boolean;
|
||||
slug: string;
|
||||
allowDownloads?: boolean;
|
||||
protectionLevel?: 'basic' | 'standard' | 'enhanced' | 'maximum';
|
||||
useEnhancedProtection?: boolean;
|
||||
useCanvasRendering?: boolean;
|
||||
feedbackEnabled?: boolean;
|
||||
// #506: track the per-event "allow likes" toggle so the per-photo
|
||||
// Like button respects it. `feedbackEnabled` alone isn't enough —
|
||||
@@ -68,8 +66,6 @@ const PhotoCard: React.FC<PhotoCardProps> = ({
|
||||
isSelectionMode,
|
||||
isLiked,
|
||||
slug,
|
||||
protectionLevel = 'standard',
|
||||
useCanvasRendering = false,
|
||||
feedbackEnabled = false,
|
||||
allowLikes = false,
|
||||
index
|
||||
@@ -122,7 +118,6 @@ const PhotoCard: React.FC<PhotoCardProps> = ({
|
||||
loading="lazy"
|
||||
isGallery={true}
|
||||
slug={slug}
|
||||
useCanvasRendering={useCanvasRendering || protectionLevel === 'maximum'}
|
||||
/>
|
||||
|
||||
{/* Colour label (#1044) — same badge every layout uses. */}
|
||||
@@ -208,9 +203,7 @@ export const GalleryPremiumLayout: React.FC<GalleryPremiumLayoutProps> = ({
|
||||
allowDownloads = true,
|
||||
downloadChoices,
|
||||
onPickResolution,
|
||||
protectionLevel = 'standard',
|
||||
useEnhancedProtection = false,
|
||||
useCanvasRendering = false,
|
||||
feedbackEnabled = false,
|
||||
feedbackOptions,
|
||||
heroPhotoOverride,
|
||||
@@ -629,9 +622,7 @@ export const GalleryPremiumLayout: React.FC<GalleryPremiumLayoutProps> = ({
|
||||
isLiked={likedPhotoIds.has(originalPhoto.id)}
|
||||
slug={slug}
|
||||
allowDownloads={allowDownloads}
|
||||
protectionLevel={protectionLevel}
|
||||
useEnhancedProtection={useEnhancedProtection}
|
||||
useCanvasRendering={useCanvasRendering}
|
||||
feedbackEnabled={feedbackEnabled}
|
||||
allowLikes={!!feedbackOptions?.allowLikes}
|
||||
index={photoIndex}
|
||||
|
||||
@@ -255,9 +255,7 @@ export const GalleryStoryLayout: React.FC<GalleryStoryLayoutProps> = ({
|
||||
photo={heroPhoto}
|
||||
slug={slug}
|
||||
allowDownloads={allowDownloads}
|
||||
protectionLevel={protectionLevel}
|
||||
useEnhancedProtection={useEnhancedProtection}
|
||||
useCanvasRendering={useCanvasRendering}
|
||||
/>
|
||||
|
||||
{/* Main Content - Scenes */}
|
||||
@@ -281,9 +279,7 @@ export const GalleryStoryLayout: React.FC<GalleryStoryLayoutProps> = ({
|
||||
onPhotoClick={handleOpenLightbox}
|
||||
slug={slug}
|
||||
allowDownloads={allowDownloads}
|
||||
protectionLevel={protectionLevel}
|
||||
useEnhancedProtection={useEnhancedProtection}
|
||||
useCanvasRendering={useCanvasRendering}
|
||||
/>
|
||||
) : (
|
||||
<div id={`gallery-${scene.id}`} className="story-gallery-grid">
|
||||
@@ -298,9 +294,7 @@ export const GalleryStoryLayout: React.FC<GalleryStoryLayoutProps> = ({
|
||||
slug={slug}
|
||||
galleryId={`gallery-${scene.id}`}
|
||||
allowDownloads={allowDownloads}
|
||||
protectionLevel={protectionLevel}
|
||||
useEnhancedProtection={useEnhancedProtection}
|
||||
useCanvasRendering={useCanvasRendering}
|
||||
// Mark first photo in each grid as featured
|
||||
featured={index === 0 && scene.photos.length > 4}
|
||||
/>
|
||||
|
||||
@@ -18,9 +18,7 @@ interface GridPhotoProps {
|
||||
animationType?: string;
|
||||
allowDownloads?: boolean;
|
||||
slug?: string;
|
||||
protectionLevel?: 'basic' | 'standard' | 'enhanced' | 'maximum';
|
||||
useEnhancedProtection?: boolean;
|
||||
useCanvasRendering?: boolean;
|
||||
feedbackEnabled?: boolean;
|
||||
feedbackOptions?: {
|
||||
allowLikes?: boolean;
|
||||
@@ -47,8 +45,6 @@ const GridPhoto: React.FC<GridPhotoProps> = ({
|
||||
animationType = 'fade',
|
||||
allowDownloads = true,
|
||||
slug,
|
||||
protectionLevel = 'standard',
|
||||
useCanvasRendering = false,
|
||||
feedbackEnabled = false,
|
||||
feedbackOptions,
|
||||
savedIdentity,
|
||||
@@ -130,7 +126,6 @@ const GridPhoto: React.FC<GridPhotoProps> = ({
|
||||
loading: 'lazy',
|
||||
isGallery: true,
|
||||
slug,
|
||||
useCanvasRendering: useCanvasRendering || protectionLevel === 'maximum',
|
||||
onProtectionViolation: (violationType: string) => {
|
||||
console.warn(`Protection violation on grid photo ${photo.id}: ${violationType}`);
|
||||
},
|
||||
@@ -200,9 +195,7 @@ export const GridGalleryLayout: React.FC<BaseGalleryLayoutProps> = ({
|
||||
isSelectionMode = false,
|
||||
onPhotoSelect,
|
||||
allowDownloads = true,
|
||||
protectionLevel = 'standard',
|
||||
useEnhancedProtection = false,
|
||||
useCanvasRendering = false,
|
||||
feedbackEnabled = false,
|
||||
feedbackOptions,
|
||||
isClient = false,
|
||||
@@ -255,9 +248,7 @@ export const GridGalleryLayout: React.FC<BaseGalleryLayoutProps> = ({
|
||||
animationType={animation}
|
||||
allowDownloads={allowDownloads}
|
||||
slug={slug}
|
||||
protectionLevel={protectionLevel}
|
||||
useEnhancedProtection={useEnhancedProtection}
|
||||
useCanvasRendering={useCanvasRendering}
|
||||
feedbackEnabled={feedbackEnabled}
|
||||
feedbackOptions={feedbackOptions}
|
||||
savedIdentity={savedIdentity}
|
||||
|
||||
@@ -40,9 +40,7 @@ interface JustifiedPhotoProps {
|
||||
animationType?: string;
|
||||
allowDownloads?: boolean;
|
||||
slug?: string;
|
||||
protectionLevel?: 'basic' | 'standard' | 'enhanced' | 'maximum';
|
||||
useEnhancedProtection?: boolean;
|
||||
useCanvasRendering?: boolean;
|
||||
feedbackEnabled?: boolean;
|
||||
feedbackOptions?: {
|
||||
allowLikes?: boolean;
|
||||
@@ -69,8 +67,6 @@ const JustifiedPhoto: React.FC<JustifiedPhotoProps> = ({
|
||||
animationType = 'fade',
|
||||
allowDownloads = true,
|
||||
slug,
|
||||
protectionLevel = 'standard',
|
||||
useCanvasRendering = false,
|
||||
feedbackEnabled = false,
|
||||
feedbackOptions,
|
||||
savedIdentity,
|
||||
@@ -133,7 +129,6 @@ const JustifiedPhoto: React.FC<JustifiedPhotoProps> = ({
|
||||
loading: 'lazy',
|
||||
isGallery: true,
|
||||
slug,
|
||||
useCanvasRendering: useCanvasRendering || protectionLevel === 'maximum',
|
||||
onProtectionViolation: (violationType: string) => {
|
||||
console.warn(`Protection violation on justified photo ${photo.id}: ${violationType}`);
|
||||
},
|
||||
@@ -214,9 +209,7 @@ export const JustifiedGalleryLayout: React.FC<JustifiedGalleryLayoutProps> = ({
|
||||
isSelectionMode = false,
|
||||
onPhotoSelect,
|
||||
allowDownloads = true,
|
||||
protectionLevel = 'standard',
|
||||
useEnhancedProtection = false,
|
||||
useCanvasRendering = false,
|
||||
feedbackEnabled = false,
|
||||
feedbackOptions,
|
||||
// Hero props
|
||||
@@ -399,7 +392,6 @@ export const JustifiedGalleryLayout: React.FC<JustifiedGalleryLayoutProps> = ({
|
||||
className="w-full h-full object-cover"
|
||||
isGallery={true}
|
||||
slug={slug}
|
||||
useCanvasRendering={useCanvasRendering || protectionLevel === 'maximum'}
|
||||
/>
|
||||
|
||||
{/* Overlay */}
|
||||
@@ -523,9 +515,7 @@ export const JustifiedGalleryLayout: React.FC<JustifiedGalleryLayoutProps> = ({
|
||||
animationType={animation}
|
||||
allowDownloads={allowDownloads}
|
||||
slug={slug}
|
||||
protectionLevel={protectionLevel}
|
||||
useEnhancedProtection={useEnhancedProtection}
|
||||
useCanvasRendering={useCanvasRendering}
|
||||
feedbackEnabled={feedbackEnabled}
|
||||
feedbackOptions={feedbackOptions}
|
||||
savedIdentity={savedIdentity}
|
||||
|
||||
@@ -14,9 +14,7 @@ interface StoryCarouselProps {
|
||||
slug: string;
|
||||
id: string;
|
||||
allowDownloads?: boolean;
|
||||
protectionLevel?: 'basic' | 'standard' | 'enhanced' | 'maximum';
|
||||
useEnhancedProtection?: boolean;
|
||||
useCanvasRendering?: boolean;
|
||||
}
|
||||
|
||||
export const StoryCarousel: React.FC<StoryCarouselProps> = ({
|
||||
@@ -27,9 +25,7 @@ export const StoryCarousel: React.FC<StoryCarouselProps> = ({
|
||||
slug,
|
||||
id,
|
||||
allowDownloads = true,
|
||||
protectionLevel = 'standard',
|
||||
useEnhancedProtection = false,
|
||||
useCanvasRendering = false
|
||||
}) => {
|
||||
return (
|
||||
<div id={id} className="story-carousel">
|
||||
@@ -53,9 +49,7 @@ export const StoryCarousel: React.FC<StoryCarouselProps> = ({
|
||||
slug={slug}
|
||||
galleryId={id}
|
||||
allowDownloads={allowDownloads}
|
||||
protectionLevel={protectionLevel}
|
||||
useEnhancedProtection={useEnhancedProtection}
|
||||
useCanvasRendering={useCanvasRendering}
|
||||
/>
|
||||
</div>
|
||||
</SwiperSlide>
|
||||
|
||||
@@ -11,9 +11,7 @@ interface StoryHeroProps {
|
||||
photo?: Photo | null;
|
||||
slug: string;
|
||||
allowDownloads?: boolean;
|
||||
protectionLevel?: 'basic' | 'standard' | 'enhanced' | 'maximum';
|
||||
useEnhancedProtection?: boolean;
|
||||
useCanvasRendering?: boolean;
|
||||
}
|
||||
|
||||
export const StoryHero: React.FC<StoryHeroProps> = ({
|
||||
@@ -22,8 +20,6 @@ export const StoryHero: React.FC<StoryHeroProps> = ({
|
||||
stats,
|
||||
photo,
|
||||
slug,
|
||||
protectionLevel = 'standard',
|
||||
useCanvasRendering = false
|
||||
}) => {
|
||||
const formattedDate = date
|
||||
? new Date(date).toLocaleDateString('en-US', {
|
||||
@@ -49,7 +45,6 @@ export const StoryHero: React.FC<StoryHeroProps> = ({
|
||||
className="w-full h-full object-cover"
|
||||
isGallery={true}
|
||||
slug={slug}
|
||||
useCanvasRendering={useCanvasRendering || protectionLevel === 'maximum'}
|
||||
/>
|
||||
) : (
|
||||
<div className="w-full h-full bg-gray-900" />
|
||||
|
||||
@@ -14,9 +14,7 @@ interface StoryPhotoCardProps {
|
||||
onClick?: () => void;
|
||||
slug: string;
|
||||
allowDownloads?: boolean;
|
||||
protectionLevel?: 'basic' | 'standard' | 'enhanced' | 'maximum';
|
||||
useEnhancedProtection?: boolean;
|
||||
useCanvasRendering?: boolean;
|
||||
featured?: boolean;
|
||||
galleryId: string;
|
||||
}
|
||||
@@ -28,8 +26,6 @@ export const StoryPhotoCard: React.FC<StoryPhotoCardProps> = ({
|
||||
onToggleFavorite,
|
||||
onClick,
|
||||
slug,
|
||||
protectionLevel = 'standard',
|
||||
useCanvasRendering = false,
|
||||
featured = false,
|
||||
galleryId: _galleryId
|
||||
}) => {
|
||||
@@ -103,7 +99,6 @@ export const StoryPhotoCard: React.FC<StoryPhotoCardProps> = ({
|
||||
}`}
|
||||
isGallery={true}
|
||||
slug={slug}
|
||||
useCanvasRendering={useCanvasRendering || protectionLevel === 'maximum'}
|
||||
/>
|
||||
)}
|
||||
</a>
|
||||
|
||||
@@ -184,7 +184,7 @@ export const ImageSecurityTab: React.FC = () => {
|
||||
/>
|
||||
<Image className="w-4 h-4 ml-2 mr-1 text-neutral-500" />
|
||||
<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>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -1938,7 +1938,7 @@
|
||||
"disableRightClick": "Rechtsklick-Menü blockieren",
|
||||
"watermarkDownloads": "Wasserzeichen bei Downloads hinzufügen",
|
||||
"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.",
|
||||
"protectionLevelBasic": "Einfach - Nur Rechtsklick-Sperre",
|
||||
"protectionLevelStandard": "Standard - Tastenkombinationen blockiert",
|
||||
@@ -2378,7 +2378,7 @@
|
||||
"protectionLevel": "Standard-Schutzstufe",
|
||||
"imageQuality": "Standard-Bildqualität",
|
||||
"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",
|
||||
"rateLimitingHelp": "Begrenzen Sie, wie viele Bilder angefordert werden können, um Scraping zu verhindern.",
|
||||
"requestsPerMinute": "Anfragen pro Minute",
|
||||
|
||||
@@ -1434,7 +1434,7 @@
|
||||
"disableRightClick": "Block right-click menu",
|
||||
"watermarkDownloads": "Add watermark to downloads",
|
||||
"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.",
|
||||
"protectionLevelBasic": "Basic - Right-click blocking only",
|
||||
"protectionLevelStandard": "Standard - Keyboard shortcuts blocked",
|
||||
@@ -1941,7 +1941,7 @@
|
||||
"protectionLevel": "Default Protection Level",
|
||||
"imageQuality": "Default Image Quality",
|
||||
"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",
|
||||
"rateLimitingHelp": "Limit how many images can be requested to prevent scraping.",
|
||||
"requestsPerMinute": "Requests per minute",
|
||||
|
||||
@@ -498,7 +498,7 @@
|
||||
"disableRightClick": "Bloquer le clic droit",
|
||||
"watermarkDownloads": "Ajouter un filigrane aux téléchargements",
|
||||
"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.",
|
||||
"protectionLevelBasic": "Basique - Blocage du clic droit uniquement",
|
||||
"protectionLevelStandard": "Standard - Blocage des raccourcis clavier",
|
||||
@@ -912,7 +912,7 @@
|
||||
"protectionLevel": "Niveau de protection par défaut",
|
||||
"imageQuality": "Qualité d'image 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",
|
||||
"rateLimitingHelp": "Limitez le nombre d'images qui peuvent être demandées pour éviter le scraping.",
|
||||
"requestsPerMinute": "Requêtes par minute",
|
||||
|
||||
@@ -498,7 +498,7 @@
|
||||
"disableRightClick": "Blokiraj meni desnega klika",
|
||||
"watermarkDownloads": "Dodaj vodni žig prenosom",
|
||||
"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.",
|
||||
"protectionLevelBasic": "Osnovno - samo blokada desnega klika",
|
||||
"protectionLevelStandard": "Standardno - blokirane bližnjice na tipkovnici",
|
||||
@@ -912,7 +912,7 @@
|
||||
"protectionLevel": "Privzeta raven zaščite",
|
||||
"imageQuality": "Privzeta kakovost slike",
|
||||
"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",
|
||||
"rateLimitingHelp": "Omejite, koliko slik je mogoče zahtevati, da preprečite množično pobiranje.",
|
||||
"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"
|
||||
/>
|
||||
<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>
|
||||
|
||||
<p className="text-xs text-neutral-500 dark:text-neutral-400 mt-2">
|
||||
|
||||
Reference in New Issue
Block a user