1764 lines
40 KiB
Markdown
1764 lines
40 KiB
Markdown
# Frontend Interfaces Module
|
||
|
||
## Overview
|
||
|
||
The Interfaces module provides **TypeScript type definitions** for the entire frontend application. It defines entity types, API contracts, Redux state shapes, form configurations, and domain-specific interfaces for the constructor, runtime, and offline systems.
|
||
|
||
**Primary Location:** `frontend/src/types/`
|
||
|
||
**Secondary Locations:**
|
||
- `frontend/src/interfaces/` - Legacy UI types
|
||
- `frontend/src/components/*/types.ts` - Component-specific types
|
||
- `frontend/src/lib/*.ts` - Library types
|
||
|
||
**Total Files:** ~15 type definition files (~2,680+ LOC)
|
||
|
||
---
|
||
|
||
## Architecture
|
||
|
||
```
|
||
frontend/src/
|
||
├── types/ # Primary type definitions
|
||
│ ├── index.ts (14 LOC) # Central re-exports
|
||
│ ├── entities.ts (276 LOC) # Entity interfaces
|
||
│ ├── api.ts (62 LOC) # API types
|
||
│ ├── redux.ts (60 LOC) # Redux state types
|
||
│ ├── forms.ts (76 LOC) # Form configuration
|
||
│ ├── filters.ts (74 LOC) # Filter/table config
|
||
│ ├── permissions.ts (122 LOC) # Permission enum
|
||
│ ├── offline.ts (194 LOC) # PWA/offline types
|
||
│ ├── preload.ts (57 LOC) # Preload types
|
||
│ ├── runtime.ts (80 LOC) # Runtime types
|
||
│ ├── constructor.ts (418 LOC) # Constructor types
|
||
│ └── presentation.ts (107 LOC) # Shared presentation
|
||
│
|
||
├── interfaces/ # Legacy UI types
|
||
│ └── index.ts (122 LOC) # Menu, color, user types
|
||
│
|
||
├── components/
|
||
│ ├── Constructor/types.ts (312 LOC) # Constructor props
|
||
│ ├── ElementSettings/types.ts (293 LOC) # Settings props
|
||
│ └── DataGrid/configBuilderFactory.tsx # Column metadata
|
||
│
|
||
└── lib/
|
||
├── elementStyles.ts (146 LOC) # CSS style interfaces
|
||
└── elementEffects.ts (267 LOC) # Effect interfaces
|
||
```
|
||
|
||
---
|
||
|
||
## Type Categories
|
||
|
||
### Category Overview
|
||
|
||
| Category | Location | Purpose |
|
||
|----------|----------|---------|
|
||
| **Entities** | `types/entities.ts` | Database model interfaces |
|
||
| **API** | `types/api.ts` | Request/response types |
|
||
| **Redux** | `types/redux.ts` | Store state shapes |
|
||
| **Forms** | `types/forms.ts` | Form field config |
|
||
| **Filters** | `types/filters.ts` | Table/filter config |
|
||
| **Permissions** | `types/permissions.ts` | RBAC enum |
|
||
| **Offline/PWA** | `types/offline.ts` | Offline storage types |
|
||
| **Preload** | `types/preload.ts` | Asset preloading |
|
||
| **Runtime** | `types/runtime.ts` | Presentation playback |
|
||
| **Constructor** | `types/constructor.ts` | Visual editor types |
|
||
| **Presentation** | `types/presentation.ts` | Shared presentation |
|
||
| **UI/Menu** | `interfaces/index.ts` | Navigation config |
|
||
| **Styles** | `lib/elementStyles.ts` | CSS properties |
|
||
| **Effects** | `lib/elementEffects.ts` | Animation config |
|
||
|
||
---
|
||
|
||
## Entity Interfaces (`types/entities.ts`)
|
||
|
||
### Base Entity
|
||
|
||
All entities extend the base interface:
|
||
|
||
```typescript
|
||
export interface BaseEntity {
|
||
id: string;
|
||
createdAt?: string;
|
||
updatedAt?: string;
|
||
}
|
||
```
|
||
|
||
### Core Entities
|
||
|
||
#### User
|
||
|
||
```typescript
|
||
export interface User extends BaseEntity {
|
||
firstName?: string;
|
||
lastName?: string;
|
||
phoneNumber?: string;
|
||
email: string;
|
||
disabled?: boolean;
|
||
avatar?: ImageFile[];
|
||
app_role?: Role | null;
|
||
custom_permissions?: PermissionEntity[];
|
||
password?: string;
|
||
}
|
||
```
|
||
|
||
#### Project
|
||
|
||
```typescript
|
||
export interface Project extends BaseEntity {
|
||
name: string;
|
||
slug?: string;
|
||
description?: string;
|
||
logo_url?: string;
|
||
favicon_url?: string;
|
||
og_image_url?: string;
|
||
}
|
||
```
|
||
|
||
#### Asset
|
||
|
||
```typescript
|
||
export interface Asset extends BaseEntity {
|
||
project?: Project | string | null;
|
||
name: string;
|
||
asset_type: 'image' | 'video' | 'audio' | 'file';
|
||
type?: 'general' | 'icon' | 'background_image' | 'audio' |
|
||
'video' | 'transition' | 'logo' | 'favicon' | 'document';
|
||
cdn_url?: string;
|
||
storage_key?: string;
|
||
mime_type?: string;
|
||
size_mb?: number;
|
||
width_px?: number;
|
||
height_px?: number;
|
||
duration_sec?: number;
|
||
checksum?: string;
|
||
is_public?: boolean;
|
||
is_deleted?: boolean;
|
||
deleted_at_time?: string | Date;
|
||
}
|
||
```
|
||
|
||
#### TourPage
|
||
|
||
```typescript
|
||
export interface TourPage extends BaseEntity {
|
||
project?: Project | string | null;
|
||
title?: string;
|
||
slug?: string;
|
||
background_asset?: Asset | string | null;
|
||
audio_asset?: Asset | string | null;
|
||
is_start_page?: boolean;
|
||
sort_order?: number;
|
||
}
|
||
```
|
||
|
||
### Entity Type Map
|
||
|
||
```typescript
|
||
export type EntityName =
|
||
| 'users' | 'roles' | 'permissions' | 'projects'
|
||
| 'project_memberships' | 'assets' | 'asset_variants'
|
||
| 'tour_pages' | 'project_audio_tracks' | 'publish_events'
|
||
| 'pwa_caches' | 'presigned_url_requests' | 'access_logs'
|
||
| 'element_type_defaults';
|
||
|
||
export interface EntityTypeMap {
|
||
users: User;
|
||
roles: Role;
|
||
projects: Project;
|
||
assets: Asset;
|
||
tour_pages: TourPage;
|
||
// ... all 14 entities mapped
|
||
}
|
||
```
|
||
|
||
### All Entity Interfaces
|
||
|
||
| Interface | Fields | Purpose |
|
||
|-----------|--------|---------|
|
||
| `User` | 10 | User accounts |
|
||
| `Role` | 2 | Role definitions |
|
||
| `PermissionEntity` | 1 | Permission records |
|
||
| `Project` | 6 | Tour projects |
|
||
| `Asset` | 14 | Media files |
|
||
| `AssetVariant` | 5 | Image variants |
|
||
| `TourPage` | 7 | Tour pages |
|
||
| `PageElement` | 10 | UI elements |
|
||
| `PageLink` | 4 | Navigation links |
|
||
| `Transition` | 5 | Video transitions |
|
||
| `ProjectAudioTrack` | 14 | Audio tracks |
|
||
| `ProjectMembership` | 8 | Team access |
|
||
| `PublishEvent` | 17 | Publish history |
|
||
| `PwaCache` | 4 | PWA cache records |
|
||
| `PresignedUrlRequest` | 11 | URL requests |
|
||
| `AccessLog` | 9 | Audit logs |
|
||
| `UIElement` | 7 | UI components |
|
||
| `ImageFile` | 5 | Upload metadata |
|
||
|
||
---
|
||
|
||
## API Interfaces (`types/api.ts`)
|
||
|
||
### Response Types
|
||
|
||
```typescript
|
||
// Paginated list response
|
||
export interface PaginatedResponse<T> {
|
||
rows: T[];
|
||
count: number;
|
||
}
|
||
|
||
// API error structure
|
||
export interface ApiError {
|
||
message?: string;
|
||
statusCode?: number;
|
||
errors?: Record<string, { _errors: string[] }>;
|
||
}
|
||
```
|
||
|
||
### Request Types
|
||
|
||
```typescript
|
||
// Fetch parameters
|
||
export interface FetchParams {
|
||
id?: string;
|
||
query?: string;
|
||
limit?: number;
|
||
page?: number;
|
||
}
|
||
|
||
// List query parameters
|
||
export interface ListQueryParams {
|
||
page?: number;
|
||
limit?: number;
|
||
sort?: 'asc' | 'desc';
|
||
field?: string;
|
||
[filterKey: string]: string | number | boolean | undefined;
|
||
}
|
||
|
||
// Sort model (DataGrid)
|
||
export interface SortModel {
|
||
field: string;
|
||
sort: 'asc' | 'desc';
|
||
}
|
||
```
|
||
|
||
### Mutation Types
|
||
|
||
```typescript
|
||
// Create/update payload
|
||
export interface MutationPayload<T> {
|
||
id?: string;
|
||
data: Partial<T>;
|
||
}
|
||
|
||
// Delete by IDs
|
||
export interface DeleteByIdsPayload {
|
||
ids: string[];
|
||
}
|
||
|
||
// Autocomplete item
|
||
export interface AutocompleteItem {
|
||
id: string;
|
||
label: string;
|
||
}
|
||
|
||
// CSV upload response
|
||
export interface CsvUploadResponse {
|
||
imported: number;
|
||
errors?: string[];
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Redux Interfaces (`types/redux.ts`)
|
||
|
||
### Notification State
|
||
|
||
```typescript
|
||
export interface NotificationState {
|
||
showNotification: boolean;
|
||
textNotification: string;
|
||
typeNotification: 'warn' | 'error' | 'success' | 'info' | '';
|
||
}
|
||
```
|
||
|
||
### Entity Slice State
|
||
|
||
```typescript
|
||
// Generic entity state (used by createEntitySlice)
|
||
export interface EntitySliceState<T> {
|
||
data: T[];
|
||
loading: boolean;
|
||
count: number;
|
||
refetch: boolean;
|
||
notify: NotificationState;
|
||
}
|
||
|
||
// Legacy entity slice state (for backward compatibility during migration)
|
||
export interface LegacyEntitySliceState<T> {
|
||
[entityName: string]: T[] | boolean | number | NotificationState;
|
||
loading: boolean;
|
||
count: number;
|
||
refetch: boolean;
|
||
notify: NotificationState;
|
||
}
|
||
|
||
// Slice factory configuration
|
||
export interface EntitySliceConfig {
|
||
name: string;
|
||
endpoint: string;
|
||
singularName?: string; // For notification messages (e.g., "User" instead of "Users")
|
||
}
|
||
```
|
||
|
||
### Core Slice States
|
||
|
||
```typescript
|
||
// Auth state
|
||
export interface AuthState {
|
||
currentUser: User | null;
|
||
token: string | null;
|
||
isFetching: boolean;
|
||
errorMessage: string | null;
|
||
loadingMessage: string | null;
|
||
notify: NotificationState;
|
||
}
|
||
|
||
// Style state
|
||
export interface StyleState {
|
||
darkMode: boolean;
|
||
bgLayoutColor: string;
|
||
focusRingColor: string;
|
||
corners: string;
|
||
cardsColor: string;
|
||
}
|
||
|
||
// Main app state
|
||
export interface MainState {
|
||
isAsideMobileExpanded: boolean;
|
||
isAsideLgActive: boolean;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Form Interfaces (`types/forms.ts`)
|
||
|
||
### Field Types
|
||
|
||
```typescript
|
||
export type FormFieldType =
|
||
| 'text' | 'email' | 'number' | 'password'
|
||
| 'textarea' | 'richtext'
|
||
| 'select' | 'multiselect'
|
||
| 'date' | 'datetime'
|
||
| 'boolean' | 'switch'
|
||
| 'image' | 'file' | 'enum';
|
||
```
|
||
|
||
### Field Configuration
|
||
|
||
```typescript
|
||
export interface FormFieldConfig {
|
||
name: string;
|
||
label: string;
|
||
type: FormFieldType;
|
||
required?: boolean;
|
||
placeholder?: string;
|
||
itemRef?: string; // Entity for select options
|
||
showField?: string; // Display field from options
|
||
options?: EnumOption[]; // Enum select options
|
||
uploadPath?: string; // File upload path
|
||
fileSchema?: FileSchema; // File constraints
|
||
min?: number;
|
||
max?: number;
|
||
maxLength?: number;
|
||
className?: string;
|
||
}
|
||
|
||
export interface EnumOption {
|
||
value: string;
|
||
label: string;
|
||
}
|
||
|
||
export interface FileSchema {
|
||
size?: number; // Max bytes
|
||
formats?: string[]; // Allowed extensions
|
||
}
|
||
```
|
||
|
||
### Form Page Configuration
|
||
|
||
```typescript
|
||
export interface FormPageConfig<TFormData> {
|
||
entityName: string;
|
||
entityTitle: string;
|
||
mode: 'create' | 'edit';
|
||
permission: string;
|
||
initialValues: TFormData;
|
||
fields: FormFieldConfig[];
|
||
listPath?: string;
|
||
}
|
||
|
||
export type FormSubmitHandler<T> = (values: T) => Promise<void>;
|
||
export type FormValues = Record<string, unknown>;
|
||
```
|
||
|
||
---
|
||
|
||
## Filter Interfaces (`types/filters.ts`)
|
||
|
||
### Filter Configuration
|
||
|
||
```typescript
|
||
export type FilterType = 'text' | 'enum' | 'date' | 'number' | 'boolean';
|
||
|
||
export interface Filter {
|
||
label: string;
|
||
title: string; // Field name
|
||
type?: FilterType;
|
||
options?: string[]; // For enum filters
|
||
number?: boolean;
|
||
date?: boolean;
|
||
}
|
||
|
||
export interface FilterItem {
|
||
id: string;
|
||
fields: FilterFields;
|
||
}
|
||
|
||
export interface FilterFields {
|
||
selectedField: string;
|
||
filterValue: string;
|
||
filterValueFrom: string;
|
||
filterValueTo: string;
|
||
}
|
||
|
||
// Filter request format for API
|
||
export type FilterRequest = Record<string, string | string[]>;
|
||
```
|
||
|
||
### Table Configuration
|
||
|
||
```typescript
|
||
export interface TableColumnConfig {
|
||
field: string;
|
||
headerName: string;
|
||
width?: number;
|
||
minWidth?: number;
|
||
maxWidth?: number;
|
||
flex?: number;
|
||
editable?: boolean;
|
||
sortable?: boolean;
|
||
filterable?: boolean;
|
||
type?: 'string' | 'number' | 'date' | 'dateTime' | 'boolean' | 'singleSelect';
|
||
valueOptions?: string[];
|
||
renderType?: 'link' | 'boolean' | 'date' | 'datetime' | 'image' | 'actions' | 'relation';
|
||
relationField?: string;
|
||
}
|
||
|
||
export interface TableConfig {
|
||
entityName: string;
|
||
columns: TableColumnConfig[];
|
||
filters: Filter[];
|
||
defaultSort?: { field: string; sort: 'asc' | 'desc' };
|
||
perPage?: number;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Permission Types (`types/permissions.ts`)
|
||
|
||
### Permission Enum
|
||
|
||
```typescript
|
||
export enum Permission {
|
||
// User permissions
|
||
READ_USERS = 'READ_USERS',
|
||
CREATE_USERS = 'CREATE_USERS',
|
||
UPDATE_USERS = 'UPDATE_USERS',
|
||
DELETE_USERS = 'DELETE_USERS',
|
||
|
||
// Project permissions
|
||
READ_PROJECTS = 'READ_PROJECTS',
|
||
CREATE_PROJECTS = 'CREATE_PROJECTS',
|
||
UPDATE_PROJECTS = 'UPDATE_PROJECTS',
|
||
DELETE_PROJECTS = 'DELETE_PROJECTS',
|
||
|
||
// Asset permissions
|
||
READ_ASSETS = 'READ_ASSETS',
|
||
CREATE_ASSETS = 'CREATE_ASSETS',
|
||
UPDATE_ASSETS = 'UPDATE_ASSETS',
|
||
DELETE_ASSETS = 'DELETE_ASSETS',
|
||
|
||
// ... 15 more entity permission groups
|
||
}
|
||
```
|
||
|
||
### Permission Helpers
|
||
|
||
```typescript
|
||
export type PermissionString = `${Permission}` | string;
|
||
|
||
// Get CRUD permissions for any entity
|
||
export const getEntityPermissions = (entityName: string) => {
|
||
const uppercased = entityName.toUpperCase();
|
||
return {
|
||
read: `READ_${uppercased}` as PermissionString,
|
||
create: `CREATE_${uppercased}` as PermissionString,
|
||
update: `UPDATE_${uppercased}` as PermissionString,
|
||
delete: `DELETE_${uppercased}` as PermissionString,
|
||
};
|
||
};
|
||
```
|
||
|
||
### Permission Categories
|
||
|
||
| Entity | Permissions |
|
||
|--------|-------------|
|
||
| Users | READ, CREATE, UPDATE, DELETE |
|
||
| Roles | READ, CREATE, UPDATE, DELETE |
|
||
| Permissions | READ, CREATE, UPDATE, DELETE |
|
||
| Projects | READ, CREATE, UPDATE, DELETE |
|
||
| Project Memberships | READ, CREATE, UPDATE, DELETE |
|
||
| Assets | READ, CREATE, UPDATE, DELETE |
|
||
| Asset Variants | READ, CREATE, UPDATE, DELETE |
|
||
| Tour Pages | READ, CREATE, UPDATE, DELETE |
|
||
| Page Elements | READ, CREATE, UPDATE, DELETE |
|
||
| Page Links | READ, CREATE, UPDATE, DELETE |
|
||
| Transitions | READ, CREATE, UPDATE, DELETE |
|
||
| Project Audio Tracks | READ, CREATE, UPDATE, DELETE |
|
||
| Publish Events | READ, CREATE, UPDATE, DELETE |
|
||
| PWA Caches | READ, CREATE, UPDATE, DELETE |
|
||
| Presigned URL Requests | READ, CREATE, UPDATE, DELETE |
|
||
| Access Logs | READ, CREATE, UPDATE, DELETE |
|
||
| UI Elements | READ, CREATE, UPDATE, DELETE |
|
||
|
||
**Total: 68 permissions (17 entities × 4 CRUD operations)**
|
||
|
||
---
|
||
|
||
## Offline/PWA Interfaces (`types/offline.ts`)
|
||
|
||
### Asset Types
|
||
|
||
```typescript
|
||
export type AssetVariantType =
|
||
| 'thumbnail' | 'preview' | 'webp'
|
||
| 'mp4_low' | 'mp4_high' | 'original';
|
||
|
||
export type AssetType = 'image' | 'video' | 'audio' | 'transition' | 'other';
|
||
|
||
export type PreloadJobStatus =
|
||
| 'queued' | 'downloading' | 'completed' | 'error' | 'paused';
|
||
```
|
||
|
||
### Preload Job
|
||
|
||
```typescript
|
||
export interface PreloadJob {
|
||
id: string;
|
||
assetId: string;
|
||
url: string;
|
||
filename: string;
|
||
progress: number;
|
||
status: PreloadJobStatus;
|
||
bytesLoaded: number;
|
||
totalBytes: number;
|
||
pageId?: string;
|
||
variantType?: AssetVariantType;
|
||
assetType?: AssetType;
|
||
error?: string;
|
||
addedAt: number;
|
||
startedAt?: number;
|
||
completedAt?: number;
|
||
}
|
||
```
|
||
|
||
### Offline Project
|
||
|
||
```typescript
|
||
export type ProjectOfflineStatus =
|
||
| 'not_downloaded' | 'downloading' | 'downloaded' | 'outdated' | 'error';
|
||
|
||
export interface OfflineProject {
|
||
id: string;
|
||
slug: string;
|
||
name: string;
|
||
status: ProjectOfflineStatus;
|
||
totalAssets: number;
|
||
downloadedAssets: number;
|
||
totalSizeBytes: number;
|
||
downloadedSizeBytes: number;
|
||
lastSyncedAt?: number;
|
||
version?: string;
|
||
}
|
||
```
|
||
|
||
### Storage Types
|
||
|
||
```typescript
|
||
export interface OfflineAsset {
|
||
id: string;
|
||
projectId: string;
|
||
url: string;
|
||
filename: string;
|
||
variantType: AssetVariantType;
|
||
assetType: AssetType;
|
||
mimeType: string;
|
||
sizeBytes: number;
|
||
blob: Blob;
|
||
downloadedAt: number;
|
||
}
|
||
|
||
export interface DownloadQueueItem {
|
||
id: string;
|
||
projectId: string;
|
||
assetId: string;
|
||
url: string;
|
||
filename: string;
|
||
status: PreloadJobStatus;
|
||
priority: number;
|
||
retryCount: number;
|
||
bytesLoaded: number;
|
||
totalBytes: number;
|
||
addedAt: number;
|
||
lastAttemptAt?: number;
|
||
error?: string;
|
||
}
|
||
```
|
||
|
||
### Network/Storage Info
|
||
|
||
```typescript
|
||
export interface NetworkInfo {
|
||
isOnline: boolean;
|
||
effectiveType?: 'slow-2g' | '2g' | '3g' | '4g';
|
||
downlink?: number; // Mbps
|
||
rtt?: number; // ms
|
||
saveData?: boolean;
|
||
}
|
||
|
||
export interface StorageQuotaInfo {
|
||
usage: number;
|
||
quota: number;
|
||
percentUsed: number;
|
||
available: number;
|
||
canStore: (bytes: number) => boolean;
|
||
}
|
||
```
|
||
|
||
### Manifest Types
|
||
|
||
```typescript
|
||
export interface OfflineManifest {
|
||
version: string;
|
||
projectId: string;
|
||
projectSlug: string;
|
||
assets: ManifestAsset[];
|
||
totalSizeBytes: number;
|
||
generatedAt: number;
|
||
}
|
||
|
||
export interface ManifestAsset {
|
||
id: string;
|
||
url: string;
|
||
filename: string;
|
||
variantType: AssetVariantType;
|
||
assetType: AssetType;
|
||
mimeType: string;
|
||
sizeBytes: number;
|
||
pageIds: string[];
|
||
}
|
||
```
|
||
|
||
### Preload State Types
|
||
|
||
```typescript
|
||
// Preload orchestrator state
|
||
export interface PreloadState {
|
||
isActive: boolean;
|
||
currentPageId: string | null;
|
||
queuedAssets: string[];
|
||
loadingAssets: string[];
|
||
completedAssets: string[];
|
||
failedAssets: string[];
|
||
totalProgress: number;
|
||
}
|
||
|
||
// Neighbor graph for preloading
|
||
export interface NeighborGraph {
|
||
// Map from pageId to array of connected pageIds with distance
|
||
connections: Map<string, Array<{ pageId: string; distance: number }>>;
|
||
// Get neighbors within a certain depth
|
||
getNeighbors: (pageId: string, maxDepth: number) => string[];
|
||
// Get all assets for a set of pages
|
||
getAssetsForPages: (pageIds: string[]) => string[];
|
||
}
|
||
```
|
||
|
||
### Event Types
|
||
|
||
```typescript
|
||
export interface PreloadStartEvent {
|
||
jobId: string;
|
||
assetId: string;
|
||
url: string;
|
||
}
|
||
|
||
export interface PreloadProgressEvent {
|
||
jobId: string;
|
||
progress: number;
|
||
bytesLoaded: number;
|
||
totalBytes: number;
|
||
}
|
||
|
||
export interface PreloadCompleteEvent {
|
||
jobId: string;
|
||
assetId: string;
|
||
}
|
||
|
||
export interface PreloadErrorEvent {
|
||
jobId: string;
|
||
assetId: string;
|
||
error: string;
|
||
}
|
||
|
||
export interface ProjectDownloadProgressEvent {
|
||
projectId: string;
|
||
progress: number;
|
||
downloadedAssets: number;
|
||
totalAssets: number;
|
||
downloadedBytes: number;
|
||
totalBytes: number;
|
||
}
|
||
|
||
export interface ProjectDownloadCompleteEvent {
|
||
projectId: string;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Preload Interfaces (`types/preload.ts`)
|
||
|
||
### Page Types
|
||
|
||
```typescript
|
||
export interface PreloadPage {
|
||
id: string;
|
||
background_image_url?: string;
|
||
background_video_url?: string;
|
||
background_audio_url?: string;
|
||
}
|
||
|
||
export interface PreloadPageLink {
|
||
id: string;
|
||
from_pageId?: string;
|
||
to_pageId?: string;
|
||
is_active?: boolean;
|
||
transition?: {
|
||
id: string;
|
||
video_url?: string;
|
||
};
|
||
}
|
||
|
||
export interface PreloadElement {
|
||
id: string;
|
||
pageId?: string;
|
||
element_type?: string;
|
||
content_json?: string;
|
||
}
|
||
```
|
||
|
||
### Asset Info
|
||
|
||
```typescript
|
||
export interface PreloadAssetInfo {
|
||
url: string;
|
||
pageId: string;
|
||
assetType: 'image' | 'video' | 'audio' | 'transition' | 'other';
|
||
priority: number;
|
||
}
|
||
|
||
export interface PreloadNeighborInfo {
|
||
pageId: string;
|
||
distance: number;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Runtime Interfaces (`types/runtime.ts`)
|
||
|
||
### Runtime Types
|
||
|
||
```typescript
|
||
export interface RuntimeProject {
|
||
id: string;
|
||
name?: string;
|
||
slug?: string;
|
||
description?: string;
|
||
}
|
||
|
||
export interface RuntimePage extends PreloadPage {
|
||
slug?: string;
|
||
name?: string;
|
||
sort_order?: number;
|
||
ui_schema_json?: string;
|
||
environment?: 'dev' | 'stage' | 'production';
|
||
}
|
||
|
||
export interface RuntimePageLink extends PreloadPageLink {
|
||
transitionId?: string;
|
||
direction?: string;
|
||
external_url?: string;
|
||
trigger_selector?: string;
|
||
}
|
||
|
||
export interface RuntimeElement extends PreloadElement {
|
||
name?: string;
|
||
sort_order?: number;
|
||
is_visible?: boolean;
|
||
x_percent?: number;
|
||
y_percent?: number;
|
||
width_percent?: number;
|
||
height_percent?: number;
|
||
rotation_deg?: number;
|
||
style_json?: string;
|
||
}
|
||
|
||
export interface RuntimeTransition {
|
||
id: string;
|
||
name?: string;
|
||
slug?: string;
|
||
video_url?: string;
|
||
duration_sec?: number;
|
||
supports_reverse?: boolean;
|
||
}
|
||
```
|
||
|
||
### Transition State
|
||
|
||
```typescript
|
||
export interface TransitionOverlayState {
|
||
targetPageId: string;
|
||
transitionName: string;
|
||
videoUrl: string;
|
||
isReverse: boolean;
|
||
durationSec?: number;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Constructor Interfaces (`types/constructor.ts`)
|
||
|
||
### Element Types
|
||
|
||
```typescript
|
||
export type CanvasElementType =
|
||
| 'navigation_next' // Forward navigation
|
||
| 'navigation_prev' // Back navigation
|
||
| 'spot' // Hotspot
|
||
| 'description' // Text block
|
||
| 'tooltip' // Hover tooltip
|
||
| 'gallery' // Image gallery
|
||
| 'carousel' // Image carousel
|
||
| 'logo' // Logo element
|
||
| 'video_player' // Video player
|
||
| 'audio_player' // Audio player
|
||
| 'popup'; // Popup/modal
|
||
|
||
export type NavigationButtonKind = 'forward' | 'back';
|
||
```
|
||
|
||
### Canvas Element
|
||
|
||
```typescript
|
||
export interface BaseCanvasElement
|
||
extends ElementStyleProperties,
|
||
ElementEffectProperties {
|
||
id: string;
|
||
type: CanvasElementType | string;
|
||
label?: string;
|
||
xPercent?: number;
|
||
yPercent?: number;
|
||
iconUrl?: string;
|
||
appearDelaySec?: number;
|
||
appearDurationSec?: number | null;
|
||
}
|
||
|
||
export interface CanvasElement extends BaseCanvasElement {
|
||
type: CanvasElementType;
|
||
label: string;
|
||
xPercent: number;
|
||
yPercent: number;
|
||
|
||
// Media
|
||
mediaUrl?: string;
|
||
mediaAutoplay?: boolean;
|
||
mediaLoop?: boolean;
|
||
mediaMuted?: boolean;
|
||
backgroundImageUrl?: string;
|
||
videoUrl?: string;
|
||
audioUrl?: string;
|
||
|
||
// Gallery
|
||
galleryCards?: GalleryCard[];
|
||
galleryHeaderImageUrl?: string;
|
||
galleryTitle?: string;
|
||
galleryInfoSpans?: GalleryInfoSpan[];
|
||
galleryColumns?: number;
|
||
galleryTitleFontFamily?: string;
|
||
galleryCardFontFamily?: string;
|
||
|
||
// Carousel
|
||
carouselSlides?: CarouselSlide[];
|
||
carouselCaptionFontFamily?: string;
|
||
carouselPrevIconUrl?: string;
|
||
carouselNextIconUrl?: string;
|
||
|
||
// Tooltip
|
||
tooltipTitle?: string;
|
||
tooltipText?: string;
|
||
tooltipTitleFontFamily?: string;
|
||
tooltipTextFontFamily?: string;
|
||
|
||
// Description
|
||
descriptionTitle?: string;
|
||
descriptionText?: string;
|
||
descriptionTitleFontSize?: string;
|
||
descriptionTextFontSize?: string;
|
||
descriptionTitleFontFamily?: string;
|
||
descriptionTextFontFamily?: string;
|
||
descriptionTitleColor?: string;
|
||
descriptionTextColor?: string;
|
||
descriptionBackgroundColor?: string;
|
||
|
||
// Navigation
|
||
navLabel?: string;
|
||
navType?: NavigationButtonKind;
|
||
navDisabled?: boolean;
|
||
/** @deprecated Use targetPageSlug instead */
|
||
targetPageId?: string;
|
||
targetPageSlug?: string;
|
||
transitionVideoUrl?: string;
|
||
transitionReverseMode?: 'auto_reverse' | 'separate_video';
|
||
reverseVideoUrl?: string;
|
||
transitionDurationSec?: number;
|
||
|
||
// Gallery Carousel Settings
|
||
galleryCarouselPrevIconUrl?: string;
|
||
galleryCarouselNextIconUrl?: string;
|
||
galleryCarouselBackIconUrl?: string;
|
||
galleryCarouselBackLabel?: string;
|
||
galleryCarouselPrevX?: number;
|
||
galleryCarouselPrevY?: number;
|
||
galleryCarouselNextX?: number;
|
||
galleryCarouselNextY?: number;
|
||
galleryCarouselBackX?: number;
|
||
galleryCarouselBackY?: number;
|
||
galleryCarouselPrevWidth?: string;
|
||
galleryCarouselPrevHeight?: string;
|
||
galleryCarouselNextWidth?: string;
|
||
galleryCarouselNextHeight?: string;
|
||
galleryCarouselBackWidth?: string;
|
||
galleryCarouselBackHeight?: string;
|
||
}
|
||
```
|
||
|
||
### Collection Items
|
||
|
||
```typescript
|
||
export interface GalleryCard {
|
||
id: string;
|
||
imageUrl: string;
|
||
title: string;
|
||
description: string;
|
||
}
|
||
|
||
export interface GalleryInfoSpan {
|
||
id: string;
|
||
text: string;
|
||
}
|
||
|
||
export interface CarouselSlide {
|
||
id: string;
|
||
imageUrl: string;
|
||
caption: string;
|
||
}
|
||
```
|
||
|
||
### Constructor Schema
|
||
|
||
```typescript
|
||
export interface ConstructorSchema {
|
||
elements?: CanvasElement[];
|
||
}
|
||
```
|
||
|
||
### Asset Types
|
||
|
||
```typescript
|
||
export interface ConstructorAsset {
|
||
id: string;
|
||
name?: string;
|
||
asset_type?: 'image' | 'video' | 'audio' | 'file';
|
||
type?: 'icon' | 'background_image' | 'audio' | 'video' |
|
||
'transition' | 'logo' | 'favicon' | 'document' | 'general';
|
||
cdn_url?: string | null;
|
||
storage_key?: string | null;
|
||
}
|
||
|
||
export interface AssetOption {
|
||
value: string;
|
||
label: string;
|
||
}
|
||
```
|
||
|
||
### Element Defaults
|
||
|
||
```typescript
|
||
export interface UiElementDefault {
|
||
id: string;
|
||
element_type?: string;
|
||
is_active?: boolean;
|
||
default_settings_json?: string | Record<string, unknown>;
|
||
}
|
||
|
||
export interface NormalizedElementDefault {
|
||
id: string;
|
||
element_type: string;
|
||
name?: string;
|
||
sort_order?: number;
|
||
settings: Partial<CanvasElement>;
|
||
}
|
||
```
|
||
|
||
### Editor Props Interfaces
|
||
|
||
```typescript
|
||
export interface EditorLayoutProps {
|
||
elementEditorRef: React.RefObject<HTMLDivElement | null>;
|
||
position: { x: number; y: number };
|
||
isCollapsed: boolean;
|
||
onToggleCollapse: () => void;
|
||
onDragStart: (event: React.MouseEvent) => void;
|
||
}
|
||
|
||
export interface EditorStateProps {
|
||
title: string;
|
||
activeTab: 'general' | 'css' | 'effects';
|
||
onTabChange: (tab: 'general' | 'css' | 'effects') => void;
|
||
}
|
||
|
||
export interface EditorElementProps {
|
||
selectedElement: CanvasElement | null;
|
||
selectedMenuItem: 'none' | 'background_image' | 'background_video' |
|
||
'background_audio' | 'create_transition';
|
||
onRemoveElement: () => void;
|
||
onUpdateElement: (patch: Partial<CanvasElement>) => void;
|
||
}
|
||
|
||
export interface EditorBackgroundProps {
|
||
backgroundImageUrl: string;
|
||
backgroundVideoUrl: string;
|
||
backgroundAudioUrl: string;
|
||
onBackgroundImageChange: (value: string) => void;
|
||
onBackgroundVideoChange: (value: string) => void;
|
||
onBackgroundAudioChange: (value: string) => void;
|
||
}
|
||
|
||
export interface EditorTransitionProps {
|
||
newTransitionName: string;
|
||
newTransitionVideoUrl: string;
|
||
newTransitionSupportsReverse: boolean;
|
||
isCreatingTransition: boolean;
|
||
onNewTransitionNameChange: (value: string) => void;
|
||
onNewTransitionVideoUrlChange: (value: string) => void;
|
||
onNewTransitionSupportsReverseChange: (value: boolean) => void;
|
||
onCreateTransition: () => void;
|
||
}
|
||
|
||
export interface EditorDurationNotesProps {
|
||
backgroundVideoDurationNote: string;
|
||
backgroundAudioDurationNote: string;
|
||
newTransitionDurationNote: string;
|
||
selectedMediaDurationNote: string;
|
||
selectedTransitionDurationNote: string;
|
||
}
|
||
|
||
export interface EditorAssetOptionsProps {
|
||
backgroundImageAssetOptions: AssetOption[];
|
||
videoAssetOptions: AssetOption[];
|
||
audioAssetOptions: AssetOption[];
|
||
transitionVideoAssetOptions: AssetOption[];
|
||
iconAssetOptions: AssetOption[];
|
||
imageAssetOptions: AssetOption[];
|
||
}
|
||
|
||
export interface EditorNavigationProps {
|
||
allowedNavigationTypes: Array<'navigation_next' | 'navigation_prev'>;
|
||
pages: Array<{
|
||
id: string;
|
||
name?: string;
|
||
slug?: string;
|
||
sort_order?: number;
|
||
}>;
|
||
activePageId: string;
|
||
onPreviewTransition: (direction: 'forward' | 'back') => void;
|
||
normalizeNavigationType: (
|
||
element: CanvasElement,
|
||
nextType: 'navigation_next' | 'navigation_prev',
|
||
) => CanvasElement;
|
||
}
|
||
|
||
export interface EditorCollectionOpsProps {
|
||
galleryCards: {
|
||
add: () => void;
|
||
update: (cardId: string, patch: Partial<GalleryCard>) => void;
|
||
remove: (cardId: string) => void;
|
||
};
|
||
galleryInfoSpans: {
|
||
add: () => void;
|
||
update: (spanId: string, text: string) => void;
|
||
remove: (spanId: string) => void;
|
||
};
|
||
carouselSlides: {
|
||
add: () => void;
|
||
update: (slideId: string, patch: Partial<CarouselSlide>) => void;
|
||
remove: (slideId: string) => void;
|
||
};
|
||
}
|
||
|
||
export interface EditorMediaUtilsProps {
|
||
getDuration: (url: string) => number | undefined;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Presentation Interfaces (`types/presentation.ts`)
|
||
|
||
### State Types
|
||
|
||
```typescript
|
||
export interface TransitionPreviewState {
|
||
targetPageId: string;
|
||
videoUrl: string;
|
||
storageKey: string;
|
||
isReverse: boolean;
|
||
}
|
||
|
||
export interface BackgroundState {
|
||
imageUrl: string;
|
||
videoUrl: string;
|
||
audioUrl: string;
|
||
}
|
||
|
||
export interface NavigationTarget {
|
||
page: RuntimePage;
|
||
pageId: string;
|
||
transitionVideoUrl?: string;
|
||
isBack: boolean;
|
||
}
|
||
```
|
||
|
||
### API Configuration
|
||
|
||
```typescript
|
||
export interface RuntimeApiConfig {
|
||
headers: {
|
||
'X-Runtime-Project-Slug'?: string;
|
||
'X-Runtime-Environment'?: string;
|
||
};
|
||
}
|
||
|
||
export interface PageDataLoaderResult {
|
||
project: RuntimeProject | null;
|
||
pages: RuntimePage[];
|
||
isLoading: boolean;
|
||
error: string;
|
||
reload: (preservePageId?: string) => Promise<void>;
|
||
}
|
||
|
||
// Runtime project (duplicate in presentation.ts)
|
||
export interface RuntimeProject {
|
||
id: string;
|
||
name?: string;
|
||
slug?: string;
|
||
description?: string;
|
||
}
|
||
```
|
||
|
||
### Navigation Types
|
||
|
||
```typescript
|
||
export interface NavigableElement {
|
||
id: string;
|
||
type: string;
|
||
targetPageSlug?: string;
|
||
targetPageId?: string;
|
||
transitionVideoUrl?: string;
|
||
navType?: 'forward' | 'back';
|
||
navDisabled?: boolean;
|
||
}
|
||
```
|
||
|
||
### Transition Types
|
||
|
||
```typescript
|
||
export type TransitionPhase =
|
||
| 'idle'
|
||
| 'preparing'
|
||
| 'playing'
|
||
| 'reversing'
|
||
| 'completed';
|
||
|
||
export interface BackgroundTransitionOptions {
|
||
pendingTransitionComplete: boolean;
|
||
isBackgroundReady: boolean;
|
||
transitionVideoRef: React.RefObject<HTMLVideoElement | null>;
|
||
pageSwitch: {
|
||
clearPreviousBackground: () => void;
|
||
isSwitching: boolean;
|
||
isNewBgReady: boolean;
|
||
previousBgImageUrl: string;
|
||
};
|
||
onCleanup: () => void;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## UI/Menu Interfaces (`interfaces/index.ts`)
|
||
|
||
### Menu Types
|
||
|
||
```typescript
|
||
export type MenuAsideItem = {
|
||
label: string;
|
||
icon?: string;
|
||
href?: string;
|
||
target?: string;
|
||
color?: ColorButtonKey;
|
||
isLogout?: boolean;
|
||
withDevider?: boolean;
|
||
menu?: MenuAsideItem[];
|
||
permissions?: string | string[];
|
||
};
|
||
|
||
export type MenuNavBarItem = {
|
||
label?: string;
|
||
icon?: string;
|
||
href?: string;
|
||
target?: string;
|
||
isDivider?: boolean;
|
||
isLogout?: boolean;
|
||
isDesktopNoLabel?: boolean;
|
||
isToggleLightDark?: boolean;
|
||
isCurrentUser?: boolean;
|
||
menu?: MenuNavBarItem[];
|
||
};
|
||
```
|
||
|
||
### Color Types
|
||
|
||
```typescript
|
||
export type ColorKey =
|
||
| 'white' | 'light' | 'contrast'
|
||
| 'success' | 'danger' | 'warning' | 'info';
|
||
|
||
export type ColorButtonKey =
|
||
| 'white' | 'whiteDark' | 'lightDark' | 'contrast'
|
||
| 'success' | 'danger' | 'warning' | 'info' | 'void';
|
||
|
||
export type BgKey = 'purplePink' | 'pinkRed' | 'violet';
|
||
|
||
export type StyleKey = 'white' | 'basic';
|
||
```
|
||
|
||
---
|
||
|
||
## Style Interfaces (`lib/elementStyles.ts`)
|
||
|
||
### CSS Style Properties
|
||
|
||
```typescript
|
||
export interface ElementStyleProperties {
|
||
width?: string;
|
||
height?: string;
|
||
minWidth?: string;
|
||
maxWidth?: string;
|
||
minHeight?: string;
|
||
maxHeight?: string;
|
||
margin?: string;
|
||
padding?: string;
|
||
gap?: string;
|
||
fontSize?: string;
|
||
lineHeight?: string;
|
||
fontWeight?: string;
|
||
border?: string;
|
||
borderRadius?: string;
|
||
opacity?: string;
|
||
boxShadow?: string;
|
||
display?: string;
|
||
position?: string;
|
||
justifyContent?: string;
|
||
alignItems?: string;
|
||
textAlign?: string;
|
||
zIndex?: string;
|
||
backgroundColor?: string;
|
||
color?: string;
|
||
fontFamily?: string;
|
||
}
|
||
|
||
export const ELEMENT_STYLE_PROPS = [
|
||
'width', 'height', 'minWidth', 'maxWidth', 'minHeight', 'maxHeight',
|
||
'margin', 'padding', 'gap', 'fontSize', 'lineHeight', 'fontWeight',
|
||
'border', 'borderRadius', 'boxShadow', 'display', 'position',
|
||
'justifyContent', 'alignItems', 'textAlign', 'zIndex',
|
||
'backgroundColor', 'color', 'fontFamily',
|
||
] as const;
|
||
```
|
||
|
||
---
|
||
|
||
## Effect Interfaces (`lib/elementEffects.ts`)
|
||
|
||
### Animation Types
|
||
|
||
```typescript
|
||
export type AppearAnimationType =
|
||
| '' | 'fade' | 'slide-up' | 'slide-down'
|
||
| 'slide-left' | 'slide-right' | 'scale';
|
||
```
|
||
|
||
### Effect Properties
|
||
|
||
```typescript
|
||
export interface ElementEffectProperties {
|
||
// Appear animation
|
||
appearAnimation?: AppearAnimationType;
|
||
appearAnimationDuration?: string;
|
||
appearAnimationEasing?: string;
|
||
|
||
// Hover effects
|
||
hoverScale?: string;
|
||
hoverOpacity?: string;
|
||
hoverBackgroundColor?: string;
|
||
hoverColor?: string;
|
||
hoverBoxShadow?: string;
|
||
hoverTransitionDuration?: string;
|
||
|
||
// Focus effects
|
||
focusScale?: string;
|
||
focusOpacity?: string;
|
||
focusOutline?: string;
|
||
focusBoxShadow?: string;
|
||
|
||
// Active/press effects
|
||
activeScale?: string;
|
||
activeOpacity?: string;
|
||
activeBackgroundColor?: string;
|
||
}
|
||
|
||
export const EFFECT_PROPS = [
|
||
'appearAnimation', 'appearAnimationDuration', 'appearAnimationEasing',
|
||
'hoverScale', 'hoverOpacity', 'hoverBackgroundColor', 'hoverColor',
|
||
'hoverBoxShadow', 'hoverTransitionDuration',
|
||
'focusScale', 'focusOpacity', 'focusOutline', 'focusBoxShadow',
|
||
'activeScale', 'activeOpacity', 'activeBackgroundColor',
|
||
] as const;
|
||
```
|
||
|
||
---
|
||
|
||
## Component Types
|
||
|
||
### Constructor Component Types (`components/Constructor/types.ts`)
|
||
|
||
```typescript
|
||
export type ConstructorInteractionMode = 'edit' | 'interact';
|
||
|
||
export type EditorMenuItem =
|
||
| 'none' | 'background_image' | 'background_video'
|
||
| 'background_audio' | 'create_transition';
|
||
|
||
export type ElementEditorTab = 'general' | 'css' | 'effects';
|
||
|
||
export interface Position {
|
||
x: number;
|
||
y: number;
|
||
}
|
||
|
||
export interface CanvasElementProps {
|
||
element: CanvasElement;
|
||
isSelected: boolean;
|
||
isEditMode: boolean;
|
||
isDisabled?: boolean;
|
||
canvasElapsedSec: number;
|
||
preloadedIconUrl: boolean;
|
||
onClick: (element: CanvasElement) => void;
|
||
onMouseDown?: (event: React.MouseEvent, elementId: string) => void;
|
||
}
|
||
|
||
export interface ConstructorCanvasProps {
|
||
canvasRef: React.RefObject<HTMLDivElement>;
|
||
elements: CanvasElement[];
|
||
selectedElementId: string;
|
||
isEditMode: boolean;
|
||
isLoading: boolean;
|
||
canvasElapsedSec: number;
|
||
preloadedIconUrlMap: Record<string, boolean>;
|
||
background: CanvasBackgroundProps;
|
||
transitionPreview: boolean;
|
||
isReverseBuffering: boolean;
|
||
onElementClick: (element: CanvasElement) => void;
|
||
onElementMouseDown: (event: React.MouseEvent, elementId: string) => void;
|
||
onCreatePage: () => void;
|
||
isCreatingPage: boolean;
|
||
}
|
||
```
|
||
|
||
### Element Settings Types (`components/ElementSettings/types.ts`)
|
||
|
||
```typescript
|
||
export type ElementSettingsContext =
|
||
| 'global' // element-type-defaults page
|
||
| 'project' // project-element-defaults page
|
||
| 'constructor'; // constructor page
|
||
|
||
export interface StyleSettingsSectionProps {
|
||
values: ElementStyleProperties;
|
||
onChange: (prop: keyof ElementStyleProperties, value: string) => void;
|
||
}
|
||
|
||
export interface NavigationSettingsSectionProps {
|
||
iconUrl: string;
|
||
navLabel: string;
|
||
navType: 'forward' | 'back';
|
||
navDisabled: boolean;
|
||
targetPageId: string;
|
||
targetPageSlug: string;
|
||
transitionVideoUrl: string;
|
||
transitionReverseMode: 'auto_reverse' | 'separate_video';
|
||
reverseVideoUrl: string;
|
||
onChange: (field: string, value: string | boolean) => void;
|
||
context: ElementSettingsContext;
|
||
iconAssetOptions?: AssetOption[];
|
||
transitionVideoAssetOptions?: AssetOption[];
|
||
pageOptions?: { value: string; label: string }[];
|
||
}
|
||
```
|
||
|
||
### DataGrid Column Types (`components/DataGrid/configBuilderFactory.tsx`)
|
||
|
||
```typescript
|
||
export interface ColumnMetadata {
|
||
field: string;
|
||
headerName: string;
|
||
type?: 'text' | 'boolean' | 'date' | 'datetime' | 'number' |
|
||
'relation' | 'relationMany' | 'singleSelectRelation' |
|
||
'image' | 'actions';
|
||
editable?: boolean;
|
||
sortable?: boolean;
|
||
width?: number;
|
||
flex?: number;
|
||
minWidth?: number;
|
||
entityRef?: string;
|
||
displayField?: string;
|
||
renderCell?: (params: GridRenderCellParams) => React.ReactElement;
|
||
valueFormatter?: (value: unknown) => string;
|
||
}
|
||
|
||
export interface ColumnBuilderConfig {
|
||
entityName: string;
|
||
entityPath?: string;
|
||
columns: ColumnMetadata[];
|
||
updatePermission?: string;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Hook Interfaces
|
||
|
||
### usePageSwitch
|
||
|
||
```typescript
|
||
export interface SwitchablePage {
|
||
id: string;
|
||
background_image_url?: string;
|
||
background_video_url?: string;
|
||
background_audio_url?: string;
|
||
}
|
||
|
||
export interface PreloadCacheProvider {
|
||
getReadyBlobUrl?: (url: string) => string | null;
|
||
getCachedBlobUrl?: (url: string) => Promise<string | null>;
|
||
preloadedUrls?: Set<string>;
|
||
}
|
||
|
||
export interface UsePageSwitchOptions {
|
||
preloadCache?: PreloadCacheProvider;
|
||
}
|
||
|
||
export interface UsePageSwitchResult {
|
||
currentBgImageUrl: string;
|
||
currentBgVideoUrl: string;
|
||
currentBgAudioUrl: string;
|
||
previousBgImageUrl: string;
|
||
isSwitching: boolean;
|
||
isNewBgReady: boolean;
|
||
// ... additional methods
|
||
}
|
||
```
|
||
|
||
### useTransitionPlayback
|
||
|
||
```typescript
|
||
export type ReverseMode = 'none' | 'reverse' | 'separate';
|
||
|
||
export interface TransitionConfig {
|
||
videoUrl: string;
|
||
storageKey?: string;
|
||
reverseMode: ReverseMode;
|
||
reverseVideoUrl?: string;
|
||
durationSec?: number;
|
||
targetPageId?: string;
|
||
displayName?: string;
|
||
}
|
||
|
||
export interface UseTransitionPlaybackOptions {
|
||
videoRef: RefObject<HTMLVideoElement | null>;
|
||
transition: TransitionConfig | null;
|
||
onComplete: (targetPageId?: string) => void;
|
||
onError?: (reason: string) => void;
|
||
timeouts?: { ... };
|
||
features?: { ... };
|
||
preload?: { ... };
|
||
}
|
||
|
||
export type PlaybackPhase =
|
||
| 'idle' | 'preparing' | 'playing'
|
||
| 'reversing' | 'finishing' | 'completed';
|
||
|
||
export interface UseTransitionPlaybackResult {
|
||
phase: PlaybackPhase;
|
||
isBuffering: boolean;
|
||
isReversing: boolean;
|
||
cancel: () => void;
|
||
forceComplete: () => void;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Type Utilities
|
||
|
||
### Type Guards
|
||
|
||
```typescript
|
||
// Canvas element type guard
|
||
export function isCanvasElementType(value: string): value is CanvasElementType {
|
||
return CANVAS_ELEMENT_TYPES.includes(value as CanvasElementType);
|
||
}
|
||
```
|
||
|
||
### Normalization Functions
|
||
|
||
```typescript
|
||
// Normalize element default from API
|
||
export function normalizeElementDefault(
|
||
row: Record<string, unknown>,
|
||
): NormalizedElementDefault | null;
|
||
|
||
// Build defaults map
|
||
export function buildElementDefaultsMap(
|
||
defaults: NormalizedElementDefault[],
|
||
): Partial<Record<CanvasElementType, Partial<CanvasElement>>>;
|
||
```
|
||
|
||
### Value Helpers (ElementSettings)
|
||
|
||
```typescript
|
||
export const clampPercent = (value: string): number;
|
||
export const parseNullableNumber = (value: string): number | null;
|
||
export const extractNumericValue = (value: unknown): string;
|
||
export const normalizeNumberString = (value: string): string;
|
||
export const toOptionalTrimmed = (value: string): string | undefined;
|
||
export const toUnitValue = (value: string, unit: 'vw' | 'vh' | 'px'): string | undefined;
|
||
```
|
||
|
||
---
|
||
|
||
## Import Patterns
|
||
|
||
### Central Index Exports
|
||
|
||
The central `types/index.ts` re-exports all core type modules:
|
||
|
||
```typescript
|
||
export * from './entities';
|
||
export * from './api';
|
||
export * from './redux';
|
||
export * from './forms';
|
||
export * from './filters';
|
||
export * from './permissions';
|
||
export * from './offline';
|
||
export * from './preload';
|
||
export * from './runtime';
|
||
export * from './constructor';
|
||
// Note: presentation.ts is NOT exported from index
|
||
```
|
||
|
||
### Central Import
|
||
|
||
```typescript
|
||
// Import all types from central index
|
||
import {
|
||
User,
|
||
Project,
|
||
Asset,
|
||
PaginatedResponse,
|
||
Permission,
|
||
PreloadJob,
|
||
CanvasElement,
|
||
} from '@/types';
|
||
```
|
||
|
||
### Domain-Specific Import
|
||
|
||
```typescript
|
||
// Import from specific modules
|
||
import { CanvasElement, CanvasElementType } from '@/types/constructor';
|
||
import { PreloadJob, OfflineProject } from '@/types/offline';
|
||
import { RuntimePage, RuntimeTransition } from '@/types/runtime';
|
||
```
|
||
|
||
### Component Types Import
|
||
|
||
```typescript
|
||
// Import component-specific types
|
||
import type {
|
||
ConstructorCanvasProps,
|
||
ElementEditorPanelProps,
|
||
} from '@/components/Constructor/types';
|
||
```
|
||
|
||
---
|
||
|
||
## File Inventory
|
||
|
||
| File | LOC | Interfaces | Types | Enums |
|
||
|------|-----|------------|-------|-------|
|
||
| `types/entities.ts` | 276 | 18 | 2 | 0 |
|
||
| `types/api.ts` | 62 | 9 | 0 | 0 |
|
||
| `types/redux.ts` | 60 | 7 | 0 | 0 |
|
||
| `types/forms.ts` | 76 | 5 | 2 | 0 |
|
||
| `types/filters.ts` | 74 | 6 | 2 | 0 |
|
||
| `types/permissions.ts` | 122 | 0 | 1 | 1 |
|
||
| `types/offline.ts` | 194 | 16 | 3 | 0 |
|
||
| `types/preload.ts` | 57 | 5 | 0 | 0 |
|
||
| `types/runtime.ts` | 80 | 7 | 0 | 0 |
|
||
| `types/constructor.ts` | 418 | 25 | 2 | 0 |
|
||
| `types/presentation.ts` | 107 | 9 | 1 | 0 |
|
||
| `types/index.ts` | 14 | 0 | 0 | 0 |
|
||
| `interfaces/index.ts` | 122 | 4 | 10 | 0 |
|
||
| `components/Constructor/types.ts` | 312 | 20 | 3 | 0 |
|
||
| `components/ElementSettings/types.ts` | 293 | 12 | 1 | 0 |
|
||
| `lib/elementStyles.ts` | 146 | 1 | 1 | 0 |
|
||
| `lib/elementEffects.ts` | 267 | 1 | 2 | 0 |
|
||
| **Total** | **~2,680** | **~145** | **~30** | **1** |
|
||
|
||
---
|
||
|
||
## Best Practices
|
||
|
||
### 1. Use Central Exports
|
||
|
||
```typescript
|
||
// Good - import from central index
|
||
import { User, Project, Asset } from '@/types';
|
||
|
||
// Avoid - importing from individual files for common types
|
||
import { User } from '@/types/entities';
|
||
import { Project } from '@/types/entities';
|
||
```
|
||
|
||
### 2. Extend Base Entity
|
||
|
||
```typescript
|
||
// Good - extend BaseEntity
|
||
export interface NewEntity extends BaseEntity {
|
||
customField: string;
|
||
}
|
||
|
||
// Avoid - duplicating id/createdAt/updatedAt
|
||
export interface NewEntity {
|
||
id: string;
|
||
createdAt: string;
|
||
// ...
|
||
}
|
||
```
|
||
|
||
### 3. Use Const Assertions for Enums
|
||
|
||
```typescript
|
||
// Good - const assertion for type narrowing
|
||
export const CANVAS_ELEMENT_TYPES = [...] as const;
|
||
export type CanvasElementType = (typeof CANVAS_ELEMENT_TYPES)[number];
|
||
|
||
// Works well with type guards
|
||
function isCanvasElementType(value: string): value is CanvasElementType {
|
||
return CANVAS_ELEMENT_TYPES.includes(value as CanvasElementType);
|
||
}
|
||
```
|
||
|
||
### 4. Component Props Interfaces
|
||
|
||
```typescript
|
||
// Good - clear interface name with Props suffix
|
||
export interface CanvasElementProps {
|
||
element: CanvasElement;
|
||
isSelected: boolean;
|
||
onClick: (element: CanvasElement) => void;
|
||
}
|
||
|
||
// Use in component
|
||
function CanvasElementComponent({ element, isSelected, onClick }: CanvasElementProps) {
|
||
// ...
|
||
}
|
||
```
|
||
|
||
### 5. Generic Types for Reusability
|
||
|
||
```typescript
|
||
// Good - generic interfaces
|
||
export interface EntitySliceState<T> {
|
||
data: T[];
|
||
loading: boolean;
|
||
count: number;
|
||
}
|
||
|
||
// Usage
|
||
type UsersState = EntitySliceState<User>;
|
||
type ProjectsState = EntitySliceState<Project>;
|
||
```
|
||
|
||
---
|
||
|
||
## Related Documentation
|
||
|
||
- [types-module.md](./types-module.md) - Older types documentation
|
||
- [stores-module.md](./stores-module.md) - Redux state types usage
|
||
- [factories-module.md](./factories-module.md) - Generic type usage in factories
|
||
- [hooks-module.md](./hooks-module.md) - Hook interface definitions
|
||
- [components-module.md](./components-module.md) - Component props types
|