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';
const output = process.env.NODE_ENV === 'production' ? 'export' : 'standalone';
const output = process.env.NEXT_OUTPUT || undefined;
// Configure Serwist for service worker generation
const withSerwist = withSerwistInit({
@ -42,4 +42,4 @@ const nextConfig = {
},
};
export default withSerwist(nextConfig);
export default withSerwist(nextConfig);

View File

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

View File

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

View File

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

View File

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

View File

@ -2,7 +2,7 @@
"name": "app",
"version": "0.0.1",
"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"
}
}