# 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)** ```typescript 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({ entityName: 'users', sliceSelector: (state) => state.users, fetchAction: fetch, updateAction: update, deleteAction: deleteItem, deleteByIdsAction: deleteItemsByIds, setRefetchAction: setRefetch, loadColumnsFunction: loadColumns, }); export default TableUsers; ``` **Example: configureUsersCols.tsx** ```typescript 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) => , }, { 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:** ```typescript interface TableComponentConfig { entityName: string; sliceSelector: (state: RootState) => EntitySliceState; fetchAction: AsyncThunk<...>; updateAction: AsyncThunk<...>; deleteAction: AsyncThunk<...>; deleteByIdsAction: AsyncThunk<...>; setRefetchAction: (refetch: boolean) => Action; loadColumnsFunction: (onDelete, entityName, user) => Promise; } ``` **Output:** React component wrapping `GenericTable`. **Usage Pattern:** ```typescript const TableAssets = createTableComponent({ 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:** ```typescript interface GenericTableProps { entityName: string; sliceSelector: (state: RootState) => EntitySliceState; fetchAction: AsyncThunk<...>; updateAction: AsyncThunk<...>; deleteAction: AsyncThunk<...>; deleteByIdsAction?: AsyncThunk<...>; setRefetchAction: (refetch: boolean) => Action; loadColumnsFunction: (...) => Promise; 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:** ```typescript 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 = ({ element, isSelected, isEditMode, isDisabled, onClick, onMouseDown, resolveUrl, }) => { return ( ); }; ``` **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: ```typescript // 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:** ```typescript // 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:** ```typescript 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. ```typescript 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` | `