Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions frontend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ VITE_API_URL=http://localhost:8005
VITE_API_TIMEOUT=10000
# PluginStudio Dev Mode - Controls visibility of development UI elements
VITE_PLUGIN_STUDIO_DEV_MODE=false
# Show editing controls - Controls visibility of module editing controls
VITE_SHOW_EDITING_CONTROLS=false


# Development Only - Temporary Auto Login (Remove in production)
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/components/DynamicPageRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -300,9 +300,11 @@ export const DynamicPageRenderer: React.FC<DynamicPageRendererProps> = ({

// Determine render mode based on current path
const renderMode = useMemo((): RenderMode => {
if (location.pathname.startsWith('/plugin-studio') || location.pathname.startsWith('/pages/')) {
// Only Plugin Studio routes should use STUDIO mode
if (location.pathname.startsWith('/plugin-studio')) {
return RenderMode.STUDIO;
}
// All other routes, including /pages/, should use PUBLISHED mode
return RenderMode.PUBLISHED;
}, [location.pathname]);

Expand Down
5 changes: 5 additions & 0 deletions frontend/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ const envSchema = z.object({
.string()
.transform((val) => val === "true")
.default("false"),
VITE_SHOW_EDITING_CONTROLS: z
.string()
.transform((val) => val === "true")
.default("false"),
VITE_DEV_AUTO_LOGIN: z
.string()
.transform((val) => val === "true")
Expand Down Expand Up @@ -74,6 +78,7 @@ export const config = {
},
devMode: {
pluginStudio: env.VITE_PLUGIN_STUDIO_DEV_MODE || false,
showEditingControls: env.VITE_SHOW_EDITING_CONTROLS || false,
},
} as const;

Expand Down
66 changes: 66 additions & 0 deletions frontend/src/contexts/ControlVisibilityContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import React, { createContext, useContext, ReactNode, useMemo } from 'react';
import { useLocation } from 'react-router-dom';
import { RenderMode } from '../features/unified-dynamic-page-renderer/types';
import { usePluginStudioDevMode } from '../hooks/usePluginStudioDevMode';

interface ControlVisibilityContextType {
showControls: boolean;
isPluginStudio: boolean;
renderMode: RenderMode;
canEdit: boolean;
controlsEnabled: boolean;
}

const ControlVisibilityContext = createContext<ControlVisibilityContextType | undefined>(undefined);

interface ControlVisibilityProviderProps {
children: ReactNode;
renderMode?: RenderMode;
}

export const ControlVisibilityProvider: React.FC<ControlVisibilityProviderProps> = ({
children,
renderMode: propRenderMode
}) => {
const location = useLocation();
const { isPluginStudioDevMode } = usePluginStudioDevMode();

const contextValue = useMemo(() => {
// Determine if we're in Plugin Studio based on route
const isPluginStudio = location.pathname.startsWith('/plugin-studio');

// Determine render mode - prioritize prop, then derive from context
const renderMode = propRenderMode || (isPluginStudio ? RenderMode.STUDIO : RenderMode.PUBLISHED);

// Controls should only be shown in Plugin Studio with dev mode enabled
const controlsEnabled = isPluginStudioDevMode && isPluginStudio;

// Additional check for studio mode
const canEdit = renderMode === RenderMode.STUDIO && controlsEnabled;

// Final decision on showing controls
const showControls = canEdit && controlsEnabled;

return {
showControls,
isPluginStudio,
renderMode,
canEdit,
controlsEnabled,
};
}, [location.pathname, isPluginStudioDevMode, propRenderMode]);

return (
<ControlVisibilityContext.Provider value={contextValue}>
{children}
</ControlVisibilityContext.Provider>
);
};

export const useControlVisibilityContext = () => {
const context = useContext(ControlVisibilityContext);
if (context === undefined) {
throw new Error('useControlVisibilityContext must be used within a ControlVisibilityProvider');
}
return context;
};
10 changes: 5 additions & 5 deletions frontend/src/contexts/PluginStudioDevModeContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ export const PluginStudioDevModeProvider: React.FC<PluginStudioDevModeProviderPr
const isPluginStudioDevMode = config.devMode.pluginStudio;

const features = {
unifiedIndicator: isPluginStudioDevMode,
rendererSwitch: isPluginStudioDevMode,
debugPanels: isPluginStudioDevMode,
moduleDebugInfo: isPluginStudioDevMode,
unifiedIndicator: false, // Disable debug indicator
rendererSwitch: false, // Disable unified renderer switch
debugPanels: false, // Disable debug panels
moduleDebugInfo: false, // Disable module debug info
studioToolbar: isPluginStudioDevMode,
performanceMetrics: isPluginStudioDevMode,
performanceMetrics: false, // Disable performance metrics display
};

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import { Box, IconButton } from '@mui/material';
import { Settings, Delete, DragIndicator } from '@mui/icons-material';
import { useShowControls } from '../../../hooks/useControlVisibility';

export interface GridItemControlsProps {
isSelected: boolean;
Expand All @@ -21,6 +22,13 @@ export const GridItemControls: React.FC<GridItemControlsProps> = ({
onRemove,
onSelect
}) => {
const showControls = useShowControls();

// Early return if controls should not be shown
if (!showControls) {
return null;
}

// Use onRemove if provided, otherwise use onDelete
const handleDelete = onRemove || onDelete;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { useBreakpoint } from '../hooks/useBreakpoint';
import { GridItemControls } from '../../plugin-studio/components/canvas/GridItemControls';
import { useUnifiedLayoutState } from '../hooks/useUnifiedLayoutState';
import { LayoutChangeOrigin } from '../utils/layoutChangeManager';
import { useControlVisibility } from '../../../hooks/useControlVisibility';

const ResponsiveGridLayout = WidthProvider(Responsive);

Expand Down Expand Up @@ -107,6 +108,48 @@ export const LayoutEngine: React.FC<LayoutEngineProps> = React.memo(({

const { currentBreakpoint } = useBreakpoint();

// Control visibility based on context
const { showControls } = useControlVisibility(mode);

// Failsafe: Programmatically remove resize handles when controls should be hidden
useEffect(() => {
if (!showControls) {
const removeResizeHandles = () => {
const resizeHandles = document.querySelectorAll('.react-resizable-handle');
resizeHandles.forEach(handle => {
(handle as HTMLElement).style.display = 'none';
(handle as HTMLElement).style.visibility = 'hidden';
(handle as HTMLElement).style.pointerEvents = 'none';
});
};

// Remove immediately
removeResizeHandles();

// Also remove after a short delay to catch any dynamically added handles
const timeoutId = setTimeout(removeResizeHandles, 100);

// Set up a mutation observer to catch any new resize handles
const observer = new MutationObserver(() => {
if (!showControls) {
removeResizeHandles();
}
});

observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['class']
});

return () => {
clearTimeout(timeoutId);
observer.disconnect();
};
}
}, [showControls]);

// Track operation IDs for proper state management
const currentOperationId = useRef<string | null>(null);
const pageIdRef = useRef<string | undefined>(pageId);
Expand Down Expand Up @@ -512,7 +555,7 @@ export const LayoutEngine: React.FC<LayoutEngineProps> = React.memo(({
// Instead of returning null, try to render with the layout item data directly
// This allows the LegacyModuleAdapter to handle the module loading
const isSelected = selectedItem === item.i;
const isStudioMode = mode === RenderMode.STUDIO;
const isStudioMode = showControls; // Use control visibility instead of just mode check

// Try to extract pluginId from moduleId if item.pluginId is 'unknown'
let fallbackPluginId = item.pluginId;
Expand Down Expand Up @@ -559,7 +602,7 @@ export const LayoutEngine: React.FC<LayoutEngineProps> = React.memo(({
style={{ position: 'relative' }}
>
{/* Use the legacy GridItemControls component for consistent behavior */}
{isStudioMode && (
{showControls && (
<GridItemControls
isSelected={isSelected}
onConfig={() => onItemConfig?.(item.i)}
Expand All @@ -585,7 +628,7 @@ export const LayoutEngine: React.FC<LayoutEngineProps> = React.memo(({
}

const isSelected = selectedItem === item.i;
const isStudioMode = mode === RenderMode.STUDIO;
const isStudioMode = showControls; // Use control visibility instead of just mode check

return (
<div
Expand All @@ -596,7 +639,7 @@ export const LayoutEngine: React.FC<LayoutEngineProps> = React.memo(({
style={{ position: 'relative' }}
>
{/* Use the legacy GridItemControls component for consistent behavior */}
{isStudioMode && (
{showControls && (
<GridItemControls
isSelected={isSelected}
onConfig={() => onItemConfig?.(item.i)}
Expand Down Expand Up @@ -679,8 +722,9 @@ export const LayoutEngine: React.FC<LayoutEngineProps> = React.memo(({
onDragStop: handleDragStop,
onResizeStart: handleResizeStart,
onResizeStop: handleResizeStop,
isDraggable: mode === RenderMode.STUDIO,
isResizable: mode === RenderMode.STUDIO,
isDraggable: showControls, // Use control visibility instead of mode
isResizable: showControls, // Use control visibility instead of mode
resizeHandles: showControls ? ['se' as const] : [], // Only show resize handles when controls are visible
draggableHandle: '.react-grid-dragHandleExample',
compactType: 'vertical' as const,
useCSSTransforms: true,
Expand All @@ -690,7 +734,7 @@ export const LayoutEngine: React.FC<LayoutEngineProps> = React.memo(({
transformScale: 1,
...defaultGridConfig,
};
}, [currentLayouts, mode, handleLayoutChange, handleDragStart, handleDragStop, handleResizeStart, handleResizeStop]);
}, [currentLayouts, mode, showControls, handleLayoutChange, handleDragStart, handleDragStop, handleResizeStart, handleResizeStop]);

// Memoize the rendered grid items with minimal stable dependencies
const gridItems = useMemo(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useState, useEffect, useCallback } from 'react';
import { RenderMode, PageData } from '../types';
import { StudioFeatures, PublishedFeatures, PreviewFeatures } from '../types/layout';
import { usePluginStudioDevMode } from '../../../hooks/usePluginStudioDevMode';
import { useControlVisibility } from '../../../hooks/useControlVisibility';

export interface ModeControllerProps {
mode: RenderMode;
Expand Down Expand Up @@ -67,11 +68,12 @@ export const ModeController: React.FC<ModeControllerProps> = ({
onModeTransition,
}) => {
const { isPluginStudioDevMode } = usePluginStudioDevMode();
const { showControls } = useControlVisibility(mode);
const [isTransitioning, setIsTransitioning] = useState(false);
const [activeFeatures, setActiveFeatures] = useState<Record<string, boolean>>({});

// Don't render the ModeController if Plugin Studio dev mode is disabled
if (!isPluginStudioDevMode) {
// Don't render the ModeController if controls should not be shown
if (!showControls) {
return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Box, IconButton, Tooltip } from '@mui/material';
import DeleteIcon from '@mui/icons-material/Delete';
import SettingsIcon from '@mui/icons-material/Settings';
import DragIndicatorIcon from '@mui/icons-material/DragIndicator';
import { useShowControls } from '../../../hooks/useControlVisibility';

interface UnifiedModuleControlsProps {
moduleId: string;
Expand All @@ -28,6 +29,13 @@ export const UnifiedModuleControls: React.FC<UnifiedModuleControlsProps> = ({
onDelete,
onSelect
}) => {
const showControls = useShowControls();

// Early return if controls should not be shown
if (!showControls) {
return null;
}

return (
<>
{/* Move handle - top right, always visible on hover */}
Expand Down
Loading