1080 lines
36 KiB
Markdown
1080 lines
36 KiB
Markdown
# 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<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**
|
||
```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) => <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:**
|
||
```typescript
|
||
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:**
|
||
```typescript
|
||
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:**
|
||
```typescript
|
||
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:**
|
||
```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<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:
|
||
|
||
```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` | `<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:**
|
||
```typescript
|
||
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:**
|
||
|
||
```javascript
|
||
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:**
|
||
```javascript
|
||
// 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](../../documentation/asset-upload-variants.md) 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
|
||
|
||
```typescript
|
||
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.
|
||
|
||
```typescript
|
||
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.
|
||
|
||
```typescript
|
||
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:**
|
||
```typescript
|
||
// 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.
|
||
|
||
```typescript
|
||
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.
|
||
|
||
```typescript
|
||
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:
|
||
|
||
```typescript
|
||
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:
|
||
|
||
```typescript
|
||
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:
|
||
|
||
```typescript
|
||
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`:
|
||
|
||
```typescript
|
||
import { buildElementStyle } from '../../lib/elementStyles';
|
||
|
||
const style = buildElementStyle(element);
|
||
// Returns: { width, height, backgroundColor, color, fontSize, ... }
|
||
```
|
||
|
||
---
|
||
|
||
## Best Practices
|
||
|
||
### 1. Use Factories for Entity Components
|
||
|
||
```typescript
|
||
// Good - 23 LOC
|
||
const TableUsers = createTableComponent<User>({ ... });
|
||
|
||
// Avoid - 400+ LOC of boilerplate
|
||
```
|
||
|
||
### 2. Theme-Aware Styling
|
||
|
||
```typescript
|
||
// Good - reads from Redux
|
||
const corners = useAppSelector((state) => state.style.corners);
|
||
|
||
// Avoid - hardcoded
|
||
className="rounded-lg"
|
||
```
|
||
|
||
### 3. URL Resolution for Assets
|
||
|
||
```typescript
|
||
// Good - supports preloaded blob URLs
|
||
<img src={resolve(element.iconUrl)} />
|
||
|
||
// Avoid - only works online
|
||
<img src={element.iconUrl} />
|
||
```
|
||
|
||
### 4. Compact Variants for Embedded Use
|
||
|
||
```typescript
|
||
// Good - use compact in panels
|
||
<CommonSettingsSectionCompact {...props} />
|
||
|
||
// Avoid - full variant in compact space
|
||
<CommonSettingsSection {...props} />
|
||
```
|
||
|
||
---
|
||
|
||
## Related Documentation
|
||
|
||
- [Frontend Architecture](./frontend-architecture.md) - Overall frontend structure
|
||
- [Pages Module](./pages-module.md) - Page components
|
||
- [Hooks Reference](./hooks-reference.md) - Custom hooks
|
||
- [Constructor Page Editor](./constructor-page-editor.md) - Visual builder
|
||
- [Runtime Presentation](./runtime-presentation.md) - Tour playback
|