fixed disabling for elements in presentations

This commit is contained in:
Dmitri 2026-06-25 11:33:40 +02:00
parent 8b29dd2b92
commit e23e8bc4fd
6 changed files with 39 additions and 15 deletions

View File

@ -3,7 +3,7 @@
*/ */
import withSerwistInit from '@serwist/next'; import withSerwistInit from '@serwist/next';
const output = process.env.NODE_ENV === 'production' ? 'export' : 'standalone'; const output = process.env.NEXT_OUTPUT || undefined;
// Configure Serwist for service worker generation // Configure Serwist for service worker generation
const withSerwist = withSerwistInit({ const withSerwist = withSerwistInit({
@ -42,4 +42,4 @@ const nextConfig = {
}, },
}; };
export default withSerwist(nextConfig); export default withSerwist(nextConfig);

View File

@ -3,7 +3,7 @@
"scripts": { "scripts": {
"dev": "cross-env PORT=${FRONT_PORT:-3000} next dev --turbopack", "dev": "cross-env PORT=${FRONT_PORT:-3000} next dev --turbopack",
"build": "next build", "build": "next build",
"start": "next start", "start": "next start -H 0.0.0.0 -p ${FRONT_PORT:-3001}",
"lint": "eslint . --ext .ts,.tsx", "lint": "eslint . --ext .ts,.tsx",
"format": "prettier '{components,pages,src,interfaces,hooks}/**/*.{tsx,ts,js}' --write" "format": "prettier '{components,pages,src,interfaces,hooks}/**/*.{tsx,ts,js}' --write"
}, },

View File

@ -24,6 +24,8 @@ import { isInfoPanelElementType } from '../lib/elementDefaults';
interface RuntimeElementProps { interface RuntimeElementProps {
element: CanvasElement; element: CanvasElement;
onClick: () => void; onClick: () => void;
/** Whether runtime interaction should be ignored for this element */
isDisabled?: boolean;
/** Optional URL resolver for preloaded blob URLs */ /** Optional URL resolver for preloaded blob URLs */
resolveUrl?: (url: string | undefined) => string; resolveUrl?: (url: string | undefined) => string;
/** Gallery card click handler */ /** Gallery card click handler */
@ -45,6 +47,7 @@ const clamp = (value: number, min: number, max: number) =>
const RuntimeElement: React.FC<RuntimeElementProps> = ({ const RuntimeElement: React.FC<RuntimeElementProps> = ({
element, element,
onClick, onClick,
isDisabled = false,
resolveUrl, resolveUrl,
onGalleryCardClick, onGalleryCardClick,
letterboxStyles, letterboxStyles,
@ -69,7 +72,7 @@ const RuntimeElement: React.FC<RuntimeElementProps> = ({
eventHandlers, eventHandlers,
onPersistClick, onPersistClick,
state: effectState, state: effectState,
} = useElementEffects(effectProperties, { } = useElementEffects(isDisabled ? {} : effectProperties, {
resetKey: element.id, // Reset reveal on element change resetKey: element.id, // Reset reveal on element change
forceVisible: isInfoPanelOpen, forceVisible: isInfoPanelOpen,
}); });
@ -77,8 +80,8 @@ const RuntimeElement: React.FC<RuntimeElementProps> = ({
// Audio effects - uses exposed state from useElementEffects // Audio effects - uses exposed state from useElementEffects
// resolveUrl prop resolves to preloaded blob URLs via RuntimePresentation // resolveUrl prop resolves to preloaded blob URLs via RuntimePresentation
useAudioEffects({ useAudioEffects({
hoverAudioUrl: effectProperties.hoverAudioUrl, hoverAudioUrl: isDisabled ? undefined : effectProperties.hoverAudioUrl,
clickAudioUrl: effectProperties.clickAudioUrl, clickAudioUrl: isDisabled ? undefined : effectProperties.clickAudioUrl,
volume: parseFloat(effectProperties.audioVolume || '1'), volume: parseFloat(effectProperties.audioVolume || '1'),
isHovered: effectState.isHovered, isHovered: effectState.isHovered,
isActive: effectState.isActive, isActive: effectState.isActive,
@ -89,12 +92,22 @@ const RuntimeElement: React.FC<RuntimeElementProps> = ({
// Combined click handler // Combined click handler
// Skip toggle for info panel elements (their visibility is tied to panel open state) // Skip toggle for info panel elements (their visibility is tied to panel open state)
const handleClick = () => { const handleClick = () => {
if (isDisabled) {
return;
}
if (!isInfoPanelElementType(element.type)) { if (!isInfoPanelElementType(element.type)) {
onPersistClick(); // Toggle persistence state onPersistClick(); // Toggle persistence state
} }
onClick(); // Original navigation action onClick(); // Original navigation action
}; };
const handleKeyDown = (event: React.KeyboardEvent) => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
handleClick();
}
};
// Build base position style (outer div - handles positioning + animation) // Build base position style (outer div - handles positioning + animation)
let positionStyle: React.CSSProperties = { let positionStyle: React.CSSProperties = {
left: `${xPercent}%`, left: `${xPercent}%`,
@ -135,15 +148,17 @@ const RuntimeElement: React.FC<RuntimeElementProps> = ({
); );
// Check if we need the inner wrapper for effects // Check if we need the inner wrapper for effects
const needsEffectWrapper = hasAnyEffects(effectProperties); const needsEffectWrapper = !isDisabled && hasAnyEffects(effectProperties);
return ( return (
<div <div
className='absolute cursor-pointer' className='absolute cursor-pointer'
style={positionStyle} style={positionStyle}
onClick={handleClick} onClick={handleClick}
tabIndex={0} onKeyDown={handleKeyDown}
{...(!needsEffectWrapper ? eventHandlers : {})} tabIndex={isDisabled ? -1 : 0}
aria-disabled={isDisabled}
{...(!isDisabled && !needsEffectWrapper ? eventHandlers : {})}
> >
{needsEffectWrapper ? ( {needsEffectWrapper ? (
// Inner wrapper handles hover/focus/active effects independently from animation // Inner wrapper handles hover/focus/active effects independently from animation

View File

@ -693,6 +693,13 @@ export default function RuntimePresentation({
const handleElementClick = useCallback( const handleElementClick = useCallback(
(element: CanvasElement) => { (element: CanvasElement) => {
if (isNavigationType(element.type) && element.navDisabled) {
return;
}
if (isInfoPanelElementType(element.type) && element.infoPanelDisabled) {
return;
}
// Handle info panel click // Handle info panel click
if (isInfoPanelElementType(element.type)) { if (isInfoPanelElementType(element.type)) {
setActiveInfoPanel(element); setActiveInfoPanel(element);
@ -1014,6 +1021,12 @@ export default function RuntimePresentation({
<RuntimeElement <RuntimeElement
key={element.id} key={element.id}
element={element} element={element}
isDisabled={
(isNavigationType(element.type) &&
Boolean(element.navDisabled)) ||
(isInfoPanelElementType(element.type) &&
Boolean(element.infoPanelDisabled))
}
onClick={() => handleElementClick(element)} onClick={() => handleElementClick(element)}
resolveUrl={resolveUrlWithBlob} resolveUrl={resolveUrlWithBlob}
onGalleryCardClick={(cardIndex) => onGalleryCardClick={(cardIndex) =>

View File

@ -48,11 +48,7 @@ const InfoPanelElement: React.FC<InfoPanelElementProps> = ({
// Common wrapper props // Common wrapper props
const wrapperProps = { const wrapperProps = {
className, className,
style: { style,
...style,
opacity: isDisabled ? 0.5 : style.opacity,
cursor: isDisabled ? 'not-allowed' : style.cursor,
},
onClick: handleClick, onClick: handleClick,
role: 'button' as const, role: 'button' as const,
tabIndex: isDisabled ? -1 : 0, tabIndex: isDisabled ? -1 : 0,

View File

@ -2,7 +2,7 @@
"name": "app", "name": "app",
"version": "0.0.1", "version": "0.0.1",
"scripts": { "scripts": {
"build:production": "cd ./frontend && yarn install && yarn run build && rm -rf ./node_modules && cd ../backend && yarn install", "build:production": "cd ./frontend && yarn install && yarn run build && cd ../backend && yarn install",
"start:production": "cd ./backend && NODE_ENV=production yarn start" "start:production": "cd ./backend && NODE_ENV=production yarn start"
} }
} }