39948-vm/frontend/docs/components-module.md
2026-07-03 16:11:24 +02:00

36 KiB
Raw Blame History

Frontend Components Module

Overview

The Components module contains 194 TypeScript files that provide the React component library for the Tour Builder Platform. Components are organized by domain, function, and factory patterns to maximize reuse.

Location: frontend/src/components/


Architecture Diagram

frontend/src/components/
│
├── Entity Components (13 directories, 52+ files)
│   ├── Users/          # TableUsers, configureUsersCols, CardUsers, ListUsers
│   ├── Projects/       # TableProjects, configureProjectsCols, ...
│   ├── Assets/         # TableAssets, configureAssetsCols, useAssetUploader
│   ├── Roles/          # TableRoles, configureRolesCols, ...
│   └── ... (9 more entity directories)
│
├── Uploaders/
│   └── UploadService.js             # Chunked upload with MIME validation
│
├── Factory Components
│   ├── Factory/createTableComponent.tsx      # Table component generator
│   └── DataGrid/configBuilderFactory.tsx     # Column config generator
│
├── Generic Components
│   ├── Generic/GenericTable.tsx              # Base table with DataGrid
│   └── Generic/GenericFormField.tsx          # Form field wrapper
│
├── Constructor Components (15 files)
│   ├── CanvasElement.tsx                     # Canvas element rendering
│   ├── ElementEditorPanel.tsx                # Element settings sidebar
│   ├── ConstructorControlsPanel.tsx          # Editor controls
│   └── ...
│
├── ElementSettings (23 files)
│   ├── CommonSettingsSection.tsx             # Position, timing
│   ├── NavigationSettingsSection.tsx         # Navigation links
│   ├── StyleSettingsSection.tsx              # CSS properties
│   └── ... (Full + Compact variants, types, hooks)
│
├── UiElements (16 files)
│   ├── UiElementRenderer.tsx                 # Unified element renderer
│   ├── shared/useElementWrapperStyle.ts      # Shared styling hook
│   ├── GalleryCarouselOverlay.tsx            # Gallery/Carousel overlay component
│   └── elements/ (10 per-type components)    # NavigationElement, GalleryElement, etc.
│
├── Offline/PWA (5 files)
│   ├── OfflineStatusIndicator.tsx
│   ├── DownloadProgressPanel.tsx
│   └── ...
│
├── Runtime/ (1 file)
│   └── RuntimeControls.tsx             # Configurable offline/fullscreen/sound controls
│
├── Layout Components
│   ├── NavBar.tsx                            # Top navigation
│   ├── AsideMenu.tsx                         # Sidebar navigation
│   ├── FooterBar.tsx                         # Footer
│   └── SectionMain.tsx                       # Main content wrapper
│
└── Standalone Components (~50 files)
    ├── BaseButton.tsx                        # Button component
    ├── CardBox.tsx                           # Card container
    ├── FormField.tsx                         # Form field wrapper
    ├── RuntimePresentation.tsx               # Tour playback
    ├── RuntimeElement.tsx                    # Runtime element wrapper
    ├── TourFlowManager.tsx                   # Page management
    └── ...

Component Categories

1. Entity Components (13 Directories)

Each entity has a dedicated directory with consistent structure.

Entities:

  • Users/, Roles/, Permissions/
  • Projects/, Project_memberships/
  • Assets/, Asset_variants/
  • Tour_pages/, Project_audio_tracks/
  • Publish_events/, Pwa_caches/
  • Access_logs/, Presigned_url_requests/

Standard Files per Entity:

File Purpose Generator
Table[Entity].tsx Data grid table createTableComponent
configure[Entity]Cols.tsx Column definitions createColumnLoader
Card[Entity].tsx Card display Manual
List[Entity].tsx List display Manual

Example: TableUsers.tsx (23 LOC)

import { createTableComponent } from '../Factory/createTableComponent';
import {
  fetch, update, deleteItem, setRefetch, deleteItemsByIds,
} from '../../stores/users/usersSlice';
import { loadColumns } from './configureUsersCols';
import type { User } from '../../types/entities';

const TableUsers = createTableComponent<User>({
  entityName: 'users',
  sliceSelector: (state) => state.users,
  fetchAction: fetch,
  updateAction: update,
  deleteAction: deleteItem,
  deleteByIdsAction: deleteItemsByIds,
  setRefetchAction: setRefetch,
  loadColumnsFunction: loadColumns,
});

export default TableUsers;

Example: configureUsersCols.tsx

import { createColumnLoader, ColumnMetadata } from '../DataGrid/configBuilderFactory';

const USERS_COLUMNS: ColumnMetadata[] = [
  { field: 'firstName', headerName: 'First Name', type: 'text', editable: true },
  { field: 'lastName', headerName: 'Last Name', type: 'text', editable: true },
  { field: 'email', headerName: 'E-Mail', type: 'text', editable: true },
  { field: 'disabled', headerName: 'Disabled', type: 'boolean', editable: true },
  {
    field: 'avatar',
    headerName: 'Avatar',
    type: 'image',
    renderCell: (params) => <ImageField image={params?.row?.avatar} />,
  },
  {
    field: 'app_role',
    headerName: 'App Role',
    type: 'singleSelectRelation',
    entityRef: 'roles',
    editable: true,
  },
  { field: 'actions', headerName: '', type: 'actions' },
];

export const loadColumns = createColumnLoader({
  entityName: 'users',
  columns: USERS_COLUMNS,
});

2. Factory Components

createTableComponent (Factory/createTableComponent.tsx)

Purpose: Generates entity table components from configuration.

Input Configuration:

interface TableComponentConfig<T extends BaseEntity> {
  entityName: string;
  sliceSelector: (state: RootState) => EntitySliceState<T>;
  fetchAction: AsyncThunk<...>;
  updateAction: AsyncThunk<...>;
  deleteAction: AsyncThunk<...>;
  deleteByIdsAction: AsyncThunk<...>;
  setRefetchAction: (refetch: boolean) => Action;
  loadColumnsFunction: (onDelete, entityName, user) => Promise<GridColDef[]>;
}

Output: React component wrapping GenericTable.

Usage Pattern:

const TableAssets = createTableComponent<Asset>({
  entityName: 'assets',
  sliceSelector: (state) => state.assets,
  fetchAction: fetch,
  updateAction: update,
  deleteAction: deleteItem,
  deleteByIdsAction: deleteItemsByIds,
  setRefetchAction: setRefetch,
  loadColumnsFunction: loadColumns,
});

configBuilderFactory (DataGrid/configBuilderFactory.tsx)

Purpose: Generates MUI X DataGrid column configurations from metadata.

Column Types Supported:

Type Description MUI Type
text Plain text string
boolean Checkbox boolean
date Date only dateTime
datetime Date and time dateTime
number Numeric number
relation Single relation singleSelect
relationMany Multiple relations singleSelect + DataGridMultiSelect
singleSelectRelation Dropdown relation singleSelect
image Image thumbnail custom renderCell
actions Row actions actions

Features:

  • Auto-fetches relation options via /[entity]/autocomplete?limit=50 to match the backend autocomplete limit
  • Formats singleSelectRelation values from either loaded { id, label } options or nested relation objects, so list tables display names instead of UUIDs
  • AsyncPaginate relation fields (SelectField, SelectFieldMany, RoleSelect) use page size 50 for the same backend limit
  • Permission-aware editable fields
  • Custom formatters via dataFormatter
  • Action column with view/edit/delete popover

3. Generic Components

GenericTable (Generic/GenericTable.tsx)

Purpose: Reusable data grid with full CRUD functionality.

Features:

  • MUI X DataGrid v7 integration
  • Server-side pagination, sorting, filtering
  • Row editing (inline)
  • Bulk selection and delete
  • Toast notifications
  • Filter panel with field selection

Props:

interface GenericTableProps<T extends BaseEntity> {
  entityName: string;
  sliceSelector: (state: RootState) => EntitySliceState<T>;
  fetchAction: AsyncThunk<...>;
  updateAction: AsyncThunk<...>;
  deleteAction: AsyncThunk<...>;
  deleteByIdsAction?: AsyncThunk<...>;
  setRefetchAction: (refetch: boolean) => Action;
  loadColumnsFunction: (...) => Promise<GridColDef[]>;
  filters: Filter[];
  filterItems: FilterItem[];
  setFilterItems: (items: FilterItem[]) => void;
  extraQuery?: string;
}

Data Flow:

1. Component mounts → loadColumnsFunction loads columns
2. fetchAction dispatched with pagination/sort/filter query
3. Data stored in Redux slice under entityName key
4. DataGrid renders rows with server-side pagination
5. Edit saves via updateAction
6. Delete via deleteAction or deleteByIdsAction (bulk)

4. Constructor Components (15 files)

Components for the visual tour builder interface.

Component Purpose LOC
CanvasElement.tsx Renders element on canvas with positioning 62
ElementEditorPanel.tsx Settings sidebar with tabs (General/CSS/Effects) 592
ConstructorToolbar.tsx Floating main constructor toolbar with mode, page, element, save, stage, exit, and collapse actions ~490
ConstructorControlsPanel.tsx Legacy/secondary controls panel for constructor actions ~200
ConstructorMenu.tsx Left menu with element types ~150
BackgroundSettingsEditor.tsx Image/video/audio background selection ~100
CreateTransitionForm.tsx Transition video creation form ~80
ElementEditorHeader.tsx Draggable header for editor panel ~50
PageSelector.tsx Tour page dropdown selector with ordinal labels and optional toolbar sizing class ~60
InteractionModeToggle.tsx Edit vs Interact mode toggle with compact toolbar layout ~40
MenuActionButton.tsx Reusable menu button ~30
AssetSelectCompact.tsx Asset dropdown selector ~50
CanvasBackground.tsx Background rendering (image/video) ~80
TransitionPreviewOverlay.tsx Video transition preview ~100
index.ts Barrel exports ~20
types.ts Constructor type definitions ~50

ConstructorToolbar.tsx action layout:

  • The left block contains the Edit/Interact mode toggle.
  • Page actions groups page selector, page reorder, new page, duplicate page, delete page, and background controls. All page-level controls use consistent 40px control height and explicit vertical dividers.
  • Elements actions groups the Add Elements dropdown and element Copy/Paste buttons. Copy is enabled only when an element is selected; Paste is enabled only when the constructor-local element clipboard has content.
  • Save, Stage, Exit, and Collapse are kept in the final action group. Save and Stage use fixed compact widths and reserve the timestamp subtitle row even when no timestamp is available, so the action group remains vertically aligned as save status changes.
  • Section labels are centered above their controls to distinguish page-level copy/duplicate from element copy/paste.

Page management controls:

  • PageSelector.tsx displays page labels with order prefixes so users can see the current sequence directly in the dropdown.
  • Page move buttons call the constructor page reorder handler with up or down; the handler persists the full ordered ID list via POST /api/tour_pages/reorder.
  • Duplicate page saves the current dev page first, then calls POST /api/tour_pages/:id/duplicate; the backend appends the new independent page as the last page and regenerates page element IDs.
  • Delete page opens the constructor confirmation modal and then calls the same DELETE /api/tour_pages/:id endpoint used by Pages & Transitions.
  • Buttons are disabled while their matching async action is saving, when the user lacks the required permission, and at first/last page boundaries for reorder.

Element clipboard controls:

  • Element Copy/Paste lives in the main toolbar, not in the element editor panel, so users can copy an element, switch pages, and paste it into another page.
  • The clipboard is session-local to the active constructor page instance and is managed by useConstructorElements.
  • Paste appends a deep clone to the active page's inline ui_schema_json.elements[] data. It preserves all settings, including position, dimensions, links, navigation targets, transition fields, CSS styles, and effects, while regenerating element and nested item IDs.

CanvasElement.tsx Architecture:

interface CanvasElementProps {
  element: CanvasElementType;
  isSelected: boolean;
  isEditMode: boolean;
  isDisabled?: boolean;
  onClick: () => void;
  onMouseDown?: (event: React.MouseEvent) => void;
  resolveUrl?: (url: string | undefined) => string;
}

const CanvasElement: React.FC<CanvasElementProps> = ({
  element, isSelected, isEditMode, isDisabled, onClick, onMouseDown, resolveUrl,
}) => {
  return (
    <button
      type='button'
      data-constructor-element-id={element.id}
      className='absolute'
      style={{
        left: `${element.xPercent}%`,
        top: `${element.yPercent}%`,
        transform: 'translate(-50%, -50%)',
        background: 'transparent',
        border: 'none',
        padding: 0,
        margin: 0,
      }}
      onMouseDown={isEditMode ? onMouseDown : undefined}
      onClick={onClick}
    >
      <UiElementRenderer
        element={element}
        resolveUrl={resolveUrl}
        isSelected={isSelected}
        isEditMode={isEditMode}
        isDisabled={isDisabled}
      />
    </button>
  );
};

ElementEditorPanel Tabs:

Tab Section Properties
General CommonSettingsSection label, position, timing
General NavigationSettingsSection type, target, transition
General TooltipSettingsSection title, text, icon
General DescriptionSettingsSection title, text, fonts, colors
General MediaSettingsSection url, autoplay, loop, muted
General GallerySettingsSection cards (image, title, link)
General CarouselSettingsSection slides, navigation icons
CSS StyleSettingsSection width, height, margin, padding, etc.
Effects EffectsSettingsSection appear animation, hover (incl. hover reveal), focus, active

5. ElementSettings Components (23 files)

Settings panels for element configuration in the constructor.

Full-Size Variants:

  • CommonSettingsSection.tsx
  • NavigationSettingsSection.tsx
  • TooltipSettingsSection.tsx
  • DescriptionSettingsSection.tsx
  • MediaSettingsSection.tsx
  • GallerySettingsSection.tsx
  • CarouselSettingsSection.tsx
  • InfoPanelSettingsSection.tsx
  • StyleSettingsSection.tsx
  • EffectsSettingsSection.tsx
  • ElementSettingsTabs.tsx

Compact Variants (for ElementEditorPanel):

  • CommonSettingsSectionCompact.tsx
  • NavigationSettingsSectionCompact.tsx
  • TooltipSettingsSectionCompact.tsx
  • DescriptionSettingsSectionCompact.tsx
  • MediaSettingsSectionCompact.tsx
  • GallerySettingsSectionCompact.tsx
  • GalleryCarouselSettingsSectionCompact.tsx (shared gallery/carousel settings)
  • CarouselSettingsSectionCompact.tsx
  • InfoPanelSettingsSectionCompact.tsx
  • StyleSettingsSectionCompact.tsx
  • EffectsSettingsSectionCompact.tsx

Info Panel Settings:

  • The constructor General tab renders InfoPanelSettingsSectionCompact.
  • Open by default writes infoPanelOpenByDefault to the element JSON.
  • Global and project element-default detail pages render the full InfoPanelSettingsSection, so Info Panel default state can be configured at platform, project, and instance scope.
  • Disabled Info Panels (infoPanelDisabled) do not open by click or by default state.

Supporting Files:

  • index.ts - Barrel exports
  • types.ts - Type definitions and unit normalization helpers
  • useElementSettingsForm.ts - Form state management hook

Unit Normalization Helpers (types.ts):

The types.ts file provides helper functions for CSS value handling:

// Extract numeric value from CSS value with unit
extractNumericValue('24vw')     // '24'
extractNumericValue('100px')   // '100'

// Convert value to CSS value with unit (robust normalization)
toUnitValue('24', 'vw')        // '24vw'
toUnitValue('24vw', 'vw')      // '24vw' (preserved)
toUnitValue('0', 'px')         // '0'    (no unit for zero)
toUnitValue('calc(100%)', 'px') // 'calc(100%)' (CSS functions preserved)
toUnitValue('10px 20px', 'px')  // '10px 20px' (complex values preserved)

// Normalize number string (removes invalid chars)
normalizeNumberString('24.5')   // '24.5'
normalizeNumberString('abc')    // ''

// Convert to optional trimmed value
toOptionalTrimmed('')          // undefined
toOptionalTrimmed('  value  ') // 'value'

Edge Cases Handled by toUnitValue:

Input Output Reason
"24" "24px" Plain number → add unit
"24px" "24px" Has unit → preserve
"0" "0" Zero → no unit needed
"-10" "-10px" Negative → add unit
".5" "0.5rem" Decimal → add unit
"10px 20px" "10px 20px" Complex → preserve
"calc(100% - 20px)" preserved CSS function → preserve
"var(--spacing)" preserved CSS variable → preserve

Index Re-exports:

// ElementSettings/index.ts
export { ElementSettingsTabsCompact } from './ElementSettingsTabs';
export { StyleSettingsSectionCompact } from './StyleSettingsSectionCompact';
export { EffectsSettingsSectionCompact } from './EffectsSettingsSectionCompact';
export { extractNumericValue, toUnitValue, toOptionalTrimmed } from './types';
// ... etc

6. UiElements Components (16 files)

Unified element rendering for WYSIWYG consistency between Constructor and Runtime.

Directory Structure:

UiElements/
├── UiElementRenderer.tsx           # Main entry point
├── GalleryCarouselOverlay.tsx      # Fullscreen overlay for gallery/carousel and Info Panel media
├── shared/
│   └── useElementWrapperStyle.ts   # Shared styling hook
├── elements/
│   ├── NavigationElement.tsx       # Forward/back buttons
│   ├── GalleryElement.tsx          # Image grid
│   ├── TooltipElement.tsx          # Tooltip popup
│   ├── DescriptionElement.tsx      # Styled text block
│   ├── CarouselElement.tsx         # Image slideshow
│   ├── LogoElement.tsx             # Logo display
│   ├── SpotElement.tsx             # Hotspot indicator
│   ├── VideoPlayerElement.tsx      # Video with controls
│   ├── AudioPlayerElement.tsx      # Audio with controls
│   └── PopupElement.tsx            # Popup trigger
├── ElementPreview.tsx              # Palette preview (separate system)
├── defaults.ts                     # Default values
└── types.ts                        # Type definitions

UiElementRenderer.tsx

Purpose: Unified entry point for rendering UI elements with consistent styling. Used by both CanvasElement (constructor) and RuntimeElement (presentation).

Architecture:

┌─────────────────────────────────────────────────────────────┐
│  UiElementRenderer - unified styling + content               │
│    ├── useElementWrapperStyle (shared hook)                  │
│    └── Per-type components (10 files)                        │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│  CONSTRUCTOR: CanvasElement (position, selection, drag)      │
│    └── UiElementRenderer                                     │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│  RUNTIME: RuntimeElement (position, effects)                 │
│    └── UiElementRenderer                                     │
└─────────────────────────────────────────────────────────────┘

Props:

interface UiElementRendererProps {
  element: CanvasElement;
  resolveUrl?: (url: string | undefined) => string;
  // Constructor-specific props (optional)
  isSelected?: boolean;
  isEditMode?: boolean;
  isDisabled?: boolean;
}

useElementWrapperStyle Hook

Purpose: Single source of truth for element wrapper styling. Ensures constructor and runtime render elements identically.

interface UseElementWrapperStyleOptions {
  element: CanvasElement;
  isSelected?: boolean;   // Constructor only
  isEditMode?: boolean;   // Constructor only
  isDisabled?: boolean;   // Constructor only
}

interface ElementWrapperStyle {
  className: string;      // Tailwind classes
  style: CSSProperties;   // Inline styles from buildElementStyle
}

Per-Type Element Components

Each element type has a dedicated component that handles both wrapper styling and content:

Component Element Type Rendering
NavigationElement navigation_next/prev Icon image or text label
TooltipElement tooltip Icon or title/text popup
DescriptionElement description Icon or styled title/text block
VideoPlayerElement video_player <video> with controls
AudioPlayerElement audio_player <audio> with controls
GalleryElement gallery 3-column image grid
CarouselElement carousel Slides with dots and nav icons
LogoElement logo Icon image or text
SpotElement spot Icon or animated pulse circle
PopupElement popup Text label

Per-Type Component Interface:

interface ElementComponentProps {
  element: CanvasElement;
  resolveUrl?: (url: string | undefined) => string;
  className: string;      // From useElementWrapperStyle
  style: CSSProperties;   // From useElementWrapperStyle
}

7. Offline/PWA Components (4 files)

Component Purpose
OfflineStatusIndicator.tsx Shows online/offline status
DownloadProgressPanel.tsx Shows download progress for PWA caching
OfflineToggle.tsx Enable/disable offline mode
StorageUsageDisplay.tsx Shows storage quota usage

8. Uploaders (1 file)

UploadService.js

Purpose: File upload service with chunked uploads and MIME type validation.

Location: frontend/src/components/Uploaders/UploadService.js

Key Features:

  • Single-file upload via upload()
  • Chunked upload via uploadChunked() for large files
  • MIME type validation with extension fallback
  • Progress callbacks and abort support

MIME Type Validation:

const VALID_MIME_TYPES = {
  image: {
    prefixes: ['image/'],
    extensions: ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'bmp', 'ico'],
  },
  video: {
    prefixes: ['video/'],
    extensions: ['mp4', 'webm', 'mov', 'avi', 'mkv', 'm4v', 'ogv'],
  },
  audio: {
    prefixes: ['audio/'],
    extensions: ['mp3', 'wav', 'ogg', 'aac', 'm4a', 'flac', 'weba'],
  },
};

Validation Schema:

Property Type Description
assetType string Asset type: 'image', 'video', or 'audio'
image boolean Legacy: Must be an image
video boolean Legacy: Must be a video
audio boolean Legacy: Must be audio
size number Max file size in bytes
formats string[] Allowed file extensions

Usage:

// Chunked upload with validation
const result = await FileUploader.uploadChunked(
  'assets/project-id',
  file,
  { assetType: 'image' },  // Validation schema
  {
    chunkSize: 5 * 1024 * 1024,
    maxRetries: 3,
    signal: abortController.signal,
    onProgress: (percent) => console.log(`${percent}%`),
    onStatus: (status) => console.log(status),
  }
);

See Asset Upload & Variants for complete upload flow documentation.


9. Layout Components

NavBar.tsx

Purpose: Top navigation bar with responsive menu.

Features:

  • Fixed position with scroll shadow
  • Mobile menu toggle
  • Integrates with NavBarMenuList for menu items
  • Theme-aware background
type Props = {
  menu: MenuNavBarItem[];
  className: string;
  children: ReactNode;
};

export default function NavBar({ menu, className, children }: Props) {
  const [isMenuNavBarActive, setIsMenuNavBarActive] = useState(false);
  const [isScrolled, setIsScrolled] = useState(false);
  // ...
}

AsideMenu.tsx

Purpose: Sidebar navigation with mobile responsiveness.

type Props = {
  menu: MenuAsideItem[];
  isAsideMobileExpanded: boolean;
  isAsideLgActive: boolean;
  onAsideLgClose: () => void;
};

SectionMain.tsx

Purpose: Main content wrapper with consistent padding.

FooterBar.tsx

Purpose: Footer with copyright.


10. Standalone Components (~50 files)

Key standalone components used throughout the application.

BaseButton.tsx

Purpose: Universal button component with variants.

type Props = {
  label?: string;
  /** Optional subtitle displayed below the label in smaller text */
  subtitle?: string;
  icon?: string;
  iconSize?: string | number;
  href?: string;
  target?: string;
  type?: string;
  color?: ColorButtonKey;
  className?: string;
  small?: boolean;
  outline?: boolean;
  active?: boolean;
  disabled?: boolean;
  roundedFull?: boolean;
  onClick?: (e: React.MouseEvent) => void;
};

Features:

  • Link or button rendering
  • Icon support with BaseIcon
  • Color variants via getButtonColor
  • Theme-aware corners from Redux
  • Subtitle support for secondary text (e.g., timestamps)

Subtitle Usage:

// Show publish timestamp below button label
<BaseButton
  label="Publish to Production"
  subtitle={lastPublishedAt ? `Last: ${relativeTimestamp(lastPublishedAt)}` : undefined}
  color="success"
  onClick={handlePublish}
/>
// Renders:
// ┌──────────────────────────┐
// │  Publish to Production   │
// │    Last: 5 min ago       │  ← smaller, 80% opacity
// └──────────────────────────┘

CardBox.tsx

Purpose: Card container with consistent styling.

type Props = {
  rounded?: string;
  flex?: string;
  className?: string;
  hasComponentLayout?: boolean;
  hasTable?: boolean;
  isHoverable?: boolean;
  isModal?: boolean;
  children?: ReactNode;
  footer?: ReactNode;
};

FormField.tsx

Purpose: Form field wrapper with label and help text.

Features:

  • Multi-column grid support (2-3 children)
  • Icon prefix support
  • Theme-aware styling

RuntimePresentation.tsx (18.5KB)

Purpose: Full-screen tour playback component.

Hooks Used:

  • usePageDataLoader - Load page data
  • usePreloadOrchestrator - Asset preloading
  • usePageSwitch - Page navigation
  • useTransitionPlayback - Video transitions
  • useBackgroundTransition - Background changes

Features:

  • Fullscreen mode
  • Keyboard navigation (ArrowLeft/Right, Escape)
  • Touch gestures
  • Transition videos
  • Element effects (appear animation, hover)

RuntimeElement.tsx

Purpose: Element wrapper for runtime with interactive effects. Delegates styling and content to UiElementRenderer.

interface RuntimeElementProps {
  element: any;
  onClick: () => void;
  resolveUrl?: (url: string | undefined) => string;
}

const RuntimeElement: React.FC<RuntimeElementProps> = ({
  element,
  onClick,
  resolveUrl,
}) => {
  // Extract effect properties for hover/focus/active states
  const { effectStyle, eventHandlers } = useElementEffects(effectProperties);

  // Build position style with effects merged
  let positionStyle: React.CSSProperties = {
    left: `${xPercent}%`,
    top: `${yPercent}%`,
    transform: `translate(-50%, -50%)${rotation ? ` rotate(${rotation}deg)` : ''}`,
  };

  // Merge effect styles and animations...

  return (
    <div
      className='absolute cursor-pointer'
      style={positionStyle}
      onClick={onClick}
      tabIndex={0}
      {...eventHandlers}
    >
      <UiElementRenderer element={element} resolveUrl={resolveUrl} />
    </div>
  );
};

TourFlowManager.tsx (17KB)

Purpose: Page and transition management interface.

Features:

  • Page list with thumbnails
  • Drag-and-drop reordering
  • Transition configuration
  • Page CRUD operations
  • Sort order management

Other Notable Components

Component Purpose
BaseIcon.tsx MDI icon wrapper
ImageField.tsx Image display with fallback
Search.tsx Global search input
SectionTitleLineWithButton.tsx Section header with action
DataGridMultiSelect.tsx Multi-select for DataGrid
ListActionsPopover.tsx Row actions dropdown
Divider.tsx Horizontal divider
NotificationBar.tsx Toast notification wrapper
OverlayLayer.tsx Modal overlay background
PillTag.tsx Tag/badge component
UserAvatar.tsx User avatar display

Component Patterns

1. Factory Pattern

Tables: 13 entity tables generated from configuration. Columns: 13 column configs generated from metadata.

Benefit: ~1500 LOC reduced to ~300 LOC configuration.

2. Theme-Aware Styling

Components read theme values from Redux:

const corners = useAppSelector((state) => state.style.corners);
const cardsStyle = useAppSelector((state) => state.style.cardsStyle);
const iconsColor = useAppSelector((state) => state.style.iconsColor);
const bgColor = useAppSelector((state) => state.style.bgLayoutColor);
const focusRing = useAppSelector((state) => state.style.focusRingColor);

3. Composition Pattern

Complex components compose smaller ones:

RuntimePresentation
├── RuntimeElement (×N)
│   └── UiElementRenderer
│       └── Per-type Element (NavigationElement, GalleryElement, etc.)
├── CanvasBackground
└── TransitionVideoOverlay

Constructor Page
├── CanvasElement (×N)
│   └── UiElementRenderer
│       └── Per-type Element (NavigationElement, GalleryElement, etc.)
├── CanvasBackground
└── ElementEditorPanel

4. Compact Variants

Settings sections have two variants:

  • Full: Standalone use with labels
  • Compact: Embedded use with compact layout

5. URL Resolution Pattern

Components accept optional URL resolver for blob URLs:

interface Props {
  resolveUrl?: (url: string | undefined) => string;
}

// Usage
const resolve = resolveUrl ?? resolveAssetPlaybackUrl;
const src = resolve(element.iconUrl);

File Count Summary

Category Files LOC (est.)
Entity Components 52 ~1000
Factory Components 2 ~420
Generic Components 2 ~500
Constructor 15 ~1600
ElementSettings 23 ~2200
UiElements 16 ~750
Offline 5 ~350
Layout 10 ~500
Standalone ~50 ~4000
Total ~175 ~11,300

Component Dependencies

┌─────────────────────────────────────────────────────┐
│                     Pages                           │
│   (dashboard.tsx, assets-list.tsx, constructor.tsx) │
└─────────────────────────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────┐
│              Entity Components                      │
│   (TableUsers, TableAssets, TableProjects, ...)     │
│                                                     │
│   Uses: createTableComponent + configBuilderFactory │
└─────────────────────────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────┐
│              GenericTable                           │
│   MUI X DataGrid + filters + pagination + editing   │
│                                                     │
│   Uses: CardBox, BaseButton, Formik                 │
└─────────────────────────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────┐
│              Base Components                        │
│   BaseButton, CardBox, FormField, BaseIcon          │
│                                                     │
│   Uses: Redux style selectors, Tailwind CSS         │
└─────────────────────────────────────────────────────┘

Styling System

Tailwind CSS Classes

Components use Tailwind utilities with theme values:

const componentClass = [
  `flex dark:border-dark-700 dark:bg-dark-900`,
  corners !== 'rounded-full' ? corners : 'rounded-3xl',
  cardsStyle,
];

Dark Mode

All components support dark mode via:

  • dark: prefix classes
  • darkMode state from Redux
  • Applied at layout level

Dynamic Styling

Constructor elements use inline styles from buildElementStyle:

import { buildElementStyle } from '../../lib/elementStyles';

const style = buildElementStyle(element);
// Returns: { width, height, backgroundColor, color, fontSize, ... }

Best Practices

1. Use Factories for Entity Components

// Good - 23 LOC
const TableUsers = createTableComponent<User>({ ... });

// Avoid - 400+ LOC of boilerplate

2. Theme-Aware Styling

// Good - reads from Redux
const corners = useAppSelector((state) => state.style.corners);

// Avoid - hardcoded
className="rounded-lg"

3. URL Resolution for Assets

// Good - supports preloaded blob URLs
<img src={resolve(element.iconUrl)} />

// Avoid - only works online
<img src={element.iconUrl} />

4. Compact Variants for Embedded Use

// Good - use compact in panels
<CommonSettingsSectionCompact {...props} />

// Avoid - full variant in compact space
<CommonSettingsSection {...props} />