1022 lines
30 KiB
Markdown
1022 lines
30 KiB
Markdown
# Frontend Config Module
|
||
|
||
## Overview
|
||
|
||
The Config module provides **centralized configuration** for the frontend application across multiple layers: build tooling, runtime settings, navigation menus, offline/PWA functionality, and asset preloading.
|
||
|
||
**Location:**
|
||
- Root configs: `frontend/*.config.*`
|
||
- Runtime configs: `frontend/src/config/`, `frontend/src/config.ts`, `frontend/src/menuAside.ts`, `frontend/src/menuNavBar.ts`
|
||
|
||
**Total Files:** 12 config-related files
|
||
|
||
---
|
||
|
||
## Architecture
|
||
|
||
```
|
||
frontend/
|
||
├── next.config.mjs (44 LOC) # Next.js configuration
|
||
├── tailwind.config.js (114 LOC) # Tailwind CSS theming
|
||
├── postcss.config.js (8 LOC) # PostCSS plugins
|
||
├── prettier.config.js (12 LOC) # Code formatting
|
||
├── tsconfig.json (30 LOC) # TypeScript compiler
|
||
├── .eslintrc.cjs (50 LOC) # ESLint rules
|
||
├── .eslintignore (3 LOC) # ESLint exclusions
|
||
├── package.json (84 LOC) # NPM scripts & deps
|
||
│
|
||
└── src/
|
||
├── config.ts (20 LOC) # Runtime app config
|
||
├── menuAside.ts (42 LOC) # Sidebar navigation
|
||
├── menuNavBar.ts (51 LOC) # Top navbar menus
|
||
├── interfaces/index.ts (123 LOC) # Menu type definitions
|
||
│
|
||
└── config/
|
||
├── offline.config.ts (52 LOC) # PWA/offline settings
|
||
└── preload.config.ts (94 LOC) # Asset preloading settings
|
||
```
|
||
|
||
---
|
||
|
||
## Root Configuration Files
|
||
|
||
### 1. Next.js Configuration (`next.config.mjs`)
|
||
|
||
**Purpose:** Configure Next.js build and runtime behavior.
|
||
|
||
**Lines:** 44 LOC
|
||
|
||
```javascript
|
||
import withSerwistInit from '@serwist/next';
|
||
import path from 'node:path';
|
||
import { fileURLToPath } from 'node:url';
|
||
|
||
const output = process.env.NEXT_OUTPUT || undefined;
|
||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||
const isDevelopment = process.env.NODE_ENV === 'development';
|
||
|
||
const withSerwist = withSerwistInit({
|
||
swSrc: 'src/sw.ts',
|
||
swDest: 'public/sw.js',
|
||
disable: isDevelopment,
|
||
maximumFileSizeToCacheInBytes: 5 * 1024 * 1024,
|
||
});
|
||
|
||
const nextConfig = {
|
||
trailingSlash: true,
|
||
distDir: isDevelopment ? '.next' : 'build',
|
||
outputFileTracingRoot: __dirname,
|
||
output,
|
||
basePath: '',
|
||
devIndicators: {
|
||
position: 'bottom-left',
|
||
},
|
||
typescript: {
|
||
ignoreBuildErrors: false,
|
||
},
|
||
eslint: {
|
||
ignoreDuringBuilds: false,
|
||
},
|
||
images: {
|
||
unoptimized: true,
|
||
remotePatterns: [
|
||
{ protocol: 'https', hostname: '**' },
|
||
],
|
||
localPatterns: [
|
||
{ pathname: '/api/**' },
|
||
],
|
||
},
|
||
};
|
||
|
||
export default withSerwist(nextConfig);
|
||
```
|
||
|
||
#### Configuration Options
|
||
|
||
| Setting | Value | Purpose |
|
||
|---------|-------|---------|
|
||
| `trailingSlash` | `true` | URLs end with `/` for consistent routing |
|
||
| `distDir` | `.next` in development, `build` in production | Keeps Turbopack dev artifacts separate from production `next build` output |
|
||
| `output` | `undefined` by default, env override supported | Default VM build runs as a Next.js server |
|
||
| `images.unoptimized` | `true` | Disable image optimization (custom asset handling) |
|
||
| `typescript.ignoreBuildErrors` | `false` | Fail build on TS errors |
|
||
| `eslint.ignoreDuringBuilds` | `false` | Fail build on lint errors |
|
||
|
||
#### Serwist (PWA) Integration
|
||
|
||
```javascript
|
||
withSerwistInit({
|
||
swSrc: 'src/sw.ts', // Service worker source
|
||
swDest: 'public/sw.js', // Compiled output
|
||
disable: isDevelopment, // Dev: no SW
|
||
})
|
||
```
|
||
|
||
`public/sw.js` is generated by the build and is ignored by git. The source of
|
||
truth is `src/sw.ts`. `next-env.d.ts` is also generated by Next.js and ignored
|
||
to avoid local diffs when build/type generation updates its route type
|
||
reference.
|
||
|
||
---
|
||
|
||
### 2. Tailwind CSS Configuration (`tailwind.config.js`)
|
||
|
||
**Purpose:** Custom theme, colors, animations, and plugins.
|
||
|
||
**Lines:** 114 LOC
|
||
|
||
#### Content Scanning
|
||
```javascript
|
||
content: [
|
||
'./src/**/*.{js,ts,jsx,tsx}',
|
||
],
|
||
```
|
||
|
||
#### Dark Mode
|
||
```javascript
|
||
darkMode: 'class', // Toggle via class (not media query)
|
||
```
|
||
|
||
#### Custom Theme Extensions
|
||
|
||
```javascript
|
||
theme: {
|
||
extend: {
|
||
// Custom fonts
|
||
fontFamily: {
|
||
'instrument': ['"Instrument Sans Variable"', 'sans-serif'],
|
||
'instrument-condensed': ['"Instrument Sans Variable"', 'sans-serif'],
|
||
},
|
||
|
||
// Z-index for overlays
|
||
zIndex: { '-1': '-1' },
|
||
|
||
// Flex utilities
|
||
flexGrow: { 5: '5' },
|
||
|
||
// Max heights for menus/modals
|
||
maxHeight: {
|
||
'screen-menu': 'calc(100vh - 3.5rem)',
|
||
modal: 'calc(100vh - 160px)',
|
||
},
|
||
|
||
// Transition properties
|
||
transitionProperty: {
|
||
position: 'right, left, top, bottom, margin, padding',
|
||
textColor: 'color',
|
||
},
|
||
|
||
// Animations
|
||
keyframes: {
|
||
'fade-out': { from: { opacity: 1 }, to: { opacity: 0 } },
|
||
'fade-in': { from: { opacity: 0 }, to: { opacity: 1 } },
|
||
},
|
||
animation: {
|
||
'fade-out': 'fade-out 250ms ease-in-out',
|
||
'fade-in': 'fade-in 250ms ease-in-out',
|
||
},
|
||
|
||
// Custom color palette
|
||
colors: {
|
||
dark: {
|
||
900: '#131618',
|
||
800: '#21242A',
|
||
700: '#2C2F36',
|
||
600: '#9CA3AF',
|
||
500: '#CBD5E1',
|
||
},
|
||
green: { text: '#45B26B' },
|
||
'pavitra': {
|
||
'blue': '#0162FD',
|
||
'green': '#00B448',
|
||
'orange': '#FFAA00',
|
||
'red': '#F20041',
|
||
900: '#14142A',
|
||
800: '#4E4B66',
|
||
700: '#6E7191',
|
||
600: '#A0A3BD',
|
||
500: '#D9DBE9',
|
||
400: '#EFF0F6',
|
||
300: '#F7F7FC',
|
||
},
|
||
},
|
||
|
||
// Extended border radius
|
||
borderRadius: {
|
||
'3xl': '2rem',
|
||
},
|
||
},
|
||
}
|
||
```
|
||
|
||
#### Custom Plugins
|
||
|
||
```javascript
|
||
plugins: [
|
||
require('@tailwindcss/forms'), // Form styling
|
||
require('@tailwindcss/typography'), // Prose content
|
||
|
||
// Custom scrollbar utility
|
||
plugin(function ({ matchUtilities, theme }) {
|
||
matchUtilities({
|
||
'aside-scrollbars': (value) => ({
|
||
scrollbarWidth: 'thin',
|
||
scrollbarColor: `${theme(`colors.${color}.${thumb}`)} ${theme(`colors.${color}.${track}`)}`,
|
||
'&::-webkit-scrollbar': { width: '8px', height: '8px' },
|
||
'&::-webkit-scrollbar-track': { backgroundColor: theme(`colors.${color}.${track}`) },
|
||
'&::-webkit-scrollbar-thumb': { borderRadius: '0.25rem', backgroundColor: theme(`colors.${color}.${thumb}`) },
|
||
}),
|
||
}, { values: theme('asideScrollbars') });
|
||
}),
|
||
]
|
||
```
|
||
|
||
---
|
||
|
||
### 3. PostCSS Configuration (`postcss.config.js`)
|
||
|
||
**Purpose:** CSS processing pipeline.
|
||
|
||
**Lines:** 8 LOC
|
||
|
||
```javascript
|
||
module.exports = {
|
||
plugins: {
|
||
'postcss-import': {}, // @import resolution
|
||
'tailwindcss/nesting': {}, // CSS nesting support
|
||
tailwindcss: {}, // Tailwind processing
|
||
autoprefixer: {}, // Vendor prefixes
|
||
},
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 4. Prettier Configuration (`prettier.config.js`)
|
||
|
||
**Purpose:** Code formatting rules.
|
||
|
||
**Lines:** 12 LOC
|
||
|
||
```javascript
|
||
module.exports = {
|
||
semi: false, // No semicolons
|
||
singleQuote: true, // Single quotes
|
||
printWidth: 100, // Line width
|
||
trailingComma: 'es5', // ES5-compatible trailing commas
|
||
arrowParens: 'always', // Always wrap arrow params: (x) => x
|
||
tabWidth: 2, // 2-space indentation
|
||
useTabs: false, // Spaces, not tabs
|
||
quoteProps: 'as-needed', // Quote object keys only when needed
|
||
jsxSingleQuote: false, // Double quotes in JSX
|
||
bracketSpacing: true, // Spaces in object literals
|
||
bracketSameLine: false, // JSX closing bracket on new line
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 5. TypeScript Configuration (`tsconfig.json`)
|
||
|
||
**Purpose:** TypeScript compiler options.
|
||
|
||
**Lines:** 30 LOC
|
||
|
||
```json
|
||
{
|
||
"compilerOptions": {
|
||
"target": "es5",
|
||
"lib": ["dom", "dom.iterable", "esnext"],
|
||
"allowJs": true,
|
||
"skipLibCheck": true,
|
||
"strict": false,
|
||
"forceConsistentCasingInFileNames": true,
|
||
"noEmit": true,
|
||
"esModuleInterop": true,
|
||
"module": "esnext",
|
||
"moduleResolution": "node",
|
||
"resolveJsonModule": true,
|
||
"isolatedModules": true,
|
||
"jsx": "preserve",
|
||
"incremental": true
|
||
},
|
||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||
"exclude": ["node_modules"]
|
||
}
|
||
```
|
||
|
||
#### Key Settings
|
||
|
||
| Setting | Value | Purpose |
|
||
|---------|-------|---------|
|
||
| `strict` | `false` | Relaxed type checking (legacy) |
|
||
| `noEmit` | `true` | Next.js handles compilation |
|
||
| `incremental` | `true` | Faster rebuilds |
|
||
| `isolatedModules` | `true` | Required for Next.js |
|
||
|
||
---
|
||
|
||
### 6. ESLint Configuration (`.eslintrc.cjs`)
|
||
|
||
**Purpose:** Code linting rules.
|
||
|
||
**Lines:** 50 LOC
|
||
|
||
```javascript
|
||
module.exports = {
|
||
extends: [
|
||
'next/core-web-vitals',
|
||
'plugin:@typescript-eslint/recommended',
|
||
'eslint-config-prettier',
|
||
],
|
||
plugins: ['import'],
|
||
settings: {
|
||
'import/resolver': {
|
||
typescript: { project: ['./tsconfig.json'] },
|
||
},
|
||
},
|
||
rules: {
|
||
'react/no-children-prop': 'off',
|
||
'@typescript-eslint/no-explicit-any': 'off',
|
||
'@typescript-eslint/no-unused-vars': 'off',
|
||
'@typescript-eslint/ban-types': 'off',
|
||
'react-hooks/exhaustive-deps': 'off',
|
||
'import/named': 'error',
|
||
'import/no-duplicates': 'error',
|
||
'import/no-unresolved': 'error',
|
||
'no-console': 'error', // Use logger instead
|
||
},
|
||
overrides: [
|
||
// Allow console in logger utility
|
||
{ files: ['src/lib/logger.ts'], rules: { 'no-console': 'off' } },
|
||
// Allow console in service worker
|
||
{ files: ['src/sw.ts'], rules: { 'no-console': 'off' } },
|
||
// Allow console in API routes
|
||
{ files: ['src/pages/api/**/*.ts'], rules: { 'no-console': 'off' } },
|
||
],
|
||
};
|
||
```
|
||
|
||
#### Key Rules
|
||
|
||
| Rule | Setting | Purpose |
|
||
|------|---------|---------|
|
||
| `no-console` | `error` | Force use of `logger` utility |
|
||
| `import/no-unresolved` | `error` | Catch missing imports |
|
||
| `import/no-duplicates` | `error` | No duplicate imports |
|
||
|
||
---
|
||
|
||
## Source Configuration Files
|
||
|
||
### 1. Runtime App Config (`src/config.ts`)
|
||
|
||
**Purpose:** Application-wide runtime settings.
|
||
|
||
**Lines:** 20 LOC
|
||
|
||
```typescript
|
||
export const hostApi =
|
||
process.env.NODE_ENV === 'development' && !process.env.NEXT_PUBLIC_BACK_API
|
||
? 'http://localhost'
|
||
: '';
|
||
|
||
export const portApi =
|
||
process.env.NODE_ENV === 'development' && !process.env.NEXT_PUBLIC_BACK_API
|
||
? 3000
|
||
: '';
|
||
|
||
export const baseURLApi = `${hostApi}${portApi ? `:${portApi}` : ``}/api`;
|
||
|
||
export const localStorageDarkModeKey = 'darkMode';
|
||
|
||
export const localStorageStyleKey = 'style';
|
||
|
||
export const containerMaxW = 'xl:max-w-full xl:mx-auto 2xl:mx-20';
|
||
|
||
export const appTitle = 'Shimahara Visual';
|
||
|
||
export const getPageTitle = (currentPageTitle: string) =>
|
||
`${currentPageTitle} — ${appTitle}`;
|
||
```
|
||
|
||
#### Exports
|
||
|
||
| Export | Type | Purpose | Used By |
|
||
|--------|------|---------|---------|
|
||
| `baseURLApi` | `string` | API base URL | `_app.tsx`, `UploadService.js` |
|
||
| `localStorageDarkModeKey` | `string` | Dark mode storage key | `styleSlice.ts` |
|
||
| `localStorageStyleKey` | `string` | Style storage key | `styleSlice.ts` |
|
||
| `containerMaxW` | `string` | Container max-width classes | `NavBar.tsx`, `SectionMain.tsx`, `FooterBar.tsx` |
|
||
| `appTitle` | `string` | Application name | Various pages |
|
||
| `getPageTitle` | `function` | Generate page title | All pages (30+ files) |
|
||
|
||
---
|
||
|
||
### 2. Sidebar Menu Config (`src/menuAside.ts`)
|
||
|
||
**Purpose:** Sidebar navigation items.
|
||
|
||
**Lines:** 42 LOC
|
||
|
||
```typescript
|
||
import * as icon from '@mdi/js';
|
||
import { MenuAsideItem } from './interfaces';
|
||
|
||
const menuAside: MenuAsideItem[] = [
|
||
{
|
||
href: '/dashboard',
|
||
icon: icon.mdiViewDashboardOutline,
|
||
label: 'Dashboard',
|
||
},
|
||
{
|
||
href: '/projects/projects-list',
|
||
label: 'Projects',
|
||
icon: icon.mdiFolder,
|
||
permissions: 'READ_PROJECTS',
|
||
},
|
||
{
|
||
href: '/element-type-defaults',
|
||
label: 'Element Defaults',
|
||
icon: icon.mdiPaletteSwatch,
|
||
permissions: 'READ_PAGE_ELEMENTS',
|
||
},
|
||
{
|
||
href: '/users/users-list',
|
||
label: 'Users',
|
||
icon: icon.mdiAccountGroup,
|
||
permissions: 'READ_USERS',
|
||
},
|
||
{
|
||
href: '/profile',
|
||
label: 'Profile',
|
||
icon: icon.mdiAccountCircle,
|
||
},
|
||
{
|
||
href: swaggerDocsUrl,
|
||
target: '_blank',
|
||
label: 'Swagger',
|
||
icon: icon.mdiFileCode,
|
||
permissions: 'READ_API_DOCS',
|
||
},
|
||
];
|
||
|
||
export default menuAside;
|
||
```
|
||
|
||
#### Menu Item Structure
|
||
|
||
```typescript
|
||
type MenuAsideItem = {
|
||
label: string; // Display text
|
||
icon?: string; // MDI icon path
|
||
href?: string; // Navigation URL
|
||
target?: string; // Link target (_blank, etc.)
|
||
color?: ColorButtonKey; // Button color
|
||
isLogout?: boolean; // Logout action
|
||
withDevider?: boolean; // Show divider
|
||
menu?: MenuAsideItem[]; // Sub-menu items
|
||
permissions?: string | string[]; // Required permission(s)
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
### 3. Navbar Menu Config (`src/menuNavBar.ts`)
|
||
|
||
**Purpose:** Top navigation bar items.
|
||
|
||
**Lines:** 51 LOC
|
||
|
||
```typescript
|
||
import { mdiAccount, mdiLogout, mdiThemeLightDark } from '@mdi/js';
|
||
import { MenuNavBarItem } from './interfaces';
|
||
|
||
const menuNavBar: MenuNavBarItem[] = [
|
||
{
|
||
isCurrentUser: true,
|
||
menu: [
|
||
{ icon: mdiAccount, label: 'My Profile', href: '/profile' },
|
||
{ isDivider: true },
|
||
{ icon: mdiLogout, label: 'Log Out', isLogout: true },
|
||
],
|
||
},
|
||
{
|
||
icon: mdiThemeLightDark,
|
||
label: 'Light/Dark',
|
||
isDesktopNoLabel: true,
|
||
isToggleLightDark: true,
|
||
},
|
||
{
|
||
icon: mdiLogout,
|
||
label: 'Log out',
|
||
isDesktopNoLabel: true,
|
||
isLogout: true,
|
||
},
|
||
];
|
||
|
||
export const webPagesNavBar = [];
|
||
|
||
export default menuNavBar;
|
||
```
|
||
|
||
#### Menu Item Structure
|
||
|
||
```typescript
|
||
type MenuNavBarItem = {
|
||
label?: string; // Display text
|
||
icon?: string; // MDI icon path
|
||
href?: string; // Navigation URL
|
||
target?: string; // Link target
|
||
isDivider?: boolean; // Render as divider
|
||
isLogout?: boolean; // Logout action
|
||
isDesktopNoLabel?: boolean; // Icon-only on desktop
|
||
isToggleLightDark?: boolean; // Dark mode toggle
|
||
isCurrentUser?: boolean; // User dropdown
|
||
menu?: MenuNavBarItem[]; // Sub-menu items
|
||
};
|
||
```
|
||
|
||
---
|
||
|
||
### 4. Offline Config (`src/config/offline.config.ts`)
|
||
|
||
**Purpose:** PWA offline mode and IndexedDB settings.
|
||
|
||
**Lines:** 52 LOC
|
||
|
||
```typescript
|
||
export const OFFLINE_CONFIG = {
|
||
// IndexedDB
|
||
dbName: 'TourBuilderOffline',
|
||
dbVersion: 1,
|
||
|
||
// Cache names (for Cache API)
|
||
cacheNames: {
|
||
static: 'tour-builder-static-v1',
|
||
dynamic: 'tour-builder-dynamic-v1',
|
||
assets: 'tour-builder-assets-v1',
|
||
},
|
||
|
||
// Events (EventEmitter event names)
|
||
events: {
|
||
preloadStart: 'asset-preload-start',
|
||
preloadProgress: 'asset-preload-progress',
|
||
preloadComplete: 'asset-preload-complete',
|
||
preloadError: 'asset-preload-error',
|
||
projectDownloadProgress: 'project-download-progress',
|
||
projectDownloadComplete: 'project-download-complete',
|
||
queueUpdate: 'queue-update',
|
||
},
|
||
|
||
// Service worker settings
|
||
serviceWorker: {
|
||
scope: '/',
|
||
updateInterval: 60 * 60 * 1000, // 1 hour
|
||
},
|
||
|
||
// Storage settings
|
||
storage: {
|
||
cacheApiMaxSize: 5 * 1024 * 1024, // 5MB - use Cache API
|
||
indexedDbMinSize: 5 * 1024 * 1024, // 5MB+ - use IndexedDB
|
||
},
|
||
|
||
// Retry settings
|
||
retry: {
|
||
maxRetries: 3,
|
||
backoffMs: 1000,
|
||
maxBackoffMs: 30000,
|
||
},
|
||
} as const;
|
||
|
||
export type OfflineConfig = typeof OFFLINE_CONFIG;
|
||
```
|
||
|
||
#### Usage
|
||
|
||
```typescript
|
||
import { OFFLINE_CONFIG } from '../config/offline.config';
|
||
|
||
// Service worker cache name
|
||
const cache = await caches.open(OFFLINE_CONFIG.cacheNames.assets);
|
||
|
||
// Storage size threshold
|
||
if (fileSize < OFFLINE_CONFIG.storage.cacheApiMaxSize) {
|
||
// Store in Cache API
|
||
} else {
|
||
// Store in IndexedDB
|
||
}
|
||
```
|
||
|
||
#### Consumers
|
||
|
||
| File | Usage |
|
||
|------|-------|
|
||
| `sw.ts` | Cache names, storage settings |
|
||
| `StorageManager.ts` | Storage thresholds |
|
||
| `DownloadManager.ts` | Retry settings |
|
||
| `DownloadEventBus.ts` | Event names |
|
||
| `usePreloadOrchestrator.ts` | Event names |
|
||
| `usePreloadProgress.ts` | Event names |
|
||
| `useOfflineMode.ts` | Cache names |
|
||
| `DownloadContext.tsx` | Events, retries |
|
||
| `offlineDb/schema.ts` | DB name, version |
|
||
|
||
---
|
||
|
||
### 5. Preload Config (`src/config/preload.config.ts`)
|
||
|
||
**Purpose:** Asset preloading priorities and queue settings.
|
||
|
||
**Lines:** 94 LOC
|
||
|
||
```typescript
|
||
export const PRELOAD_CONFIG = {
|
||
// Queue settings
|
||
maxConcurrentDownloads: 3,
|
||
maxRetries: 3,
|
||
retryDelayMs: 1000,
|
||
|
||
// Size thresholds
|
||
largeFileThreshold: 5 * 1024 * 1024, // 5MB -> use IndexedDB
|
||
videoChunkSize: 5 * 1024 * 1024, // 5MB chunks
|
||
initialVideoBufferSeconds: 5,
|
||
|
||
// Priority weights (higher = load first)
|
||
priority: {
|
||
currentPage: 1000,
|
||
neighborBase: 500,
|
||
assetType: {
|
||
transition: 150, // Highest - needed on navigation click
|
||
image: 100, // Backgrounds load during transition
|
||
audio: 50,
|
||
video: 30,
|
||
} as Record<string, number>,
|
||
variant: {
|
||
thumbnail: 50,
|
||
preview: 40,
|
||
webp: 35,
|
||
mp4_low: 20,
|
||
mp4_high: 10,
|
||
original: 5,
|
||
} as Record<string, number>,
|
||
linkCountMultiplier: 10,
|
||
maxLinkBonus: 50,
|
||
},
|
||
|
||
// Storage
|
||
storage: {
|
||
warningPercent: 80,
|
||
criticalPercent: 95,
|
||
minFreeBuffer: 50 * 1024 * 1024, // 50MB
|
||
},
|
||
|
||
// Auto-cleanup timeouts
|
||
autoRemove: {
|
||
completedMs: 3000,
|
||
errorMs: 10000,
|
||
},
|
||
|
||
// Neighbor graph traversal
|
||
neighborGraph: {
|
||
maxDepth: 1, // Only preload immediate neighbors
|
||
constructorMaxDepth: 1,
|
||
},
|
||
|
||
// Asset URL field names in element content_json
|
||
assetFields: {
|
||
all: [
|
||
'iconUrl', 'imageUrl', 'mediaUrl', 'videoUrl', 'audioUrl',
|
||
'transitionVideoUrl', 'backgroundImageUrl', 'reverseVideoUrl',
|
||
'carouselPrevIconUrl', 'carouselNextIconUrl', 'src', 'url',
|
||
'poster', 'thumbnail',
|
||
] as const,
|
||
images: [
|
||
'iconUrl', 'imageUrl', 'backgroundImageUrl',
|
||
'carouselPrevIconUrl', 'carouselNextIconUrl', 'src',
|
||
] as const,
|
||
nested: ['galleryCards', 'carouselSlides'] as const,
|
||
nestedUrlFields: ['imageUrl', 'videoUrl'] as const,
|
||
},
|
||
} as const;
|
||
|
||
export type PreloadConfig = typeof PRELOAD_CONFIG;
|
||
```
|
||
|
||
#### Priority System
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ Priority Calculation │
|
||
│ │
|
||
│ Base Priority: │
|
||
│ currentPage: 1000 (loading current page assets) │
|
||
│ neighborBase: 500 (adjacent pages) │
|
||
│ │
|
||
│ Asset Type Bonus: │
|
||
│ transition: +150 (needed immediately) │
|
||
│ image: +100 (backgrounds) │
|
||
│ audio: +50 (background audio) │
|
||
│ video: +30 (content videos) │
|
||
│ │
|
||
│ Variant Bonus: │
|
||
│ thumbnail: +50 (quick previews) │
|
||
│ preview: +40 (medium quality) │
|
||
│ webp: +35 (optimized images) │
|
||
│ mp4_low: +20 (mobile video) │
|
||
│ mp4_high: +10 (HD video) │
|
||
│ original: +5 (full quality) │
|
||
│ │
|
||
│ Link Bonus: │
|
||
│ linkCountMultiplier × numLinks (max: maxLinkBonus) │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
#### Consumers
|
||
|
||
| File | Usage |
|
||
|------|-------|
|
||
| `usePreloadOrchestrator.ts` | Queue, priorities, fields |
|
||
| `useNeighborGraph.ts` | Graph traversal depth |
|
||
| `extractPageLinks.ts` | Asset field names |
|
||
| `imagePreDecode.ts` | Image fields |
|
||
| `StorageManager.ts` | Size thresholds |
|
||
| `DownloadManager.ts` | Queue settings |
|
||
| `useStorageQuota.ts` | Storage limits |
|
||
| `usePreloadProgress.ts` | Auto-cleanup |
|
||
| `DownloadContext.tsx` | Queue settings |
|
||
|
||
---
|
||
|
||
## Environment Variables
|
||
|
||
### Build-Time Variables
|
||
|
||
| Variable | Used In | Purpose |
|
||
|----------|---------|---------|
|
||
| `NODE_ENV` | `next.config.mjs`, `config.ts` | Build/runtime mode |
|
||
|
||
### Runtime Variables (NEXT_PUBLIC_*)
|
||
|
||
| Variable | Default | Purpose |
|
||
|----------|---------|---------|
|
||
| `NEXT_PUBLIC_BACK_API` | - | Backend API URL |
|
||
| `NEXT_PUBLIC_LOG_LEVEL` | `'info'` | Logger verbosity |
|
||
|
||
### Usage in Code
|
||
|
||
```typescript
|
||
// API URL detection
|
||
export const apiBaseURL = process.env.NEXT_PUBLIC_BACK_API || baseURLApi;
|
||
axios.defaults.baseURL = apiBaseURL;
|
||
|
||
// Logger level
|
||
export const frontendLogLevel = process.env.NEXT_PUBLIC_LOG_LEVEL || 'info';
|
||
|
||
// Development mode
|
||
export const isDevelopment = process.env.NODE_ENV === 'development';
|
||
export const isProduction = process.env.NODE_ENV === 'production';
|
||
```
|
||
|
||
Feature modules, hooks, components, and app initialization should import these
|
||
exports from `frontend/src/config.ts` instead of reading `process.env` directly.
|
||
|
||
---
|
||
|
||
## Service Worker Configuration
|
||
|
||
The service worker (`src/sw.ts`) uses `OFFLINE_CONFIG` for caching:
|
||
|
||
```typescript
|
||
import { OFFLINE_CONFIG } from './config/offline.config';
|
||
|
||
// Cache strategies
|
||
const serwist = new Serwist({
|
||
runtimeCaching: [
|
||
// Static assets - Cache First
|
||
{
|
||
matcher: ({ request }) => request.destination === 'image',
|
||
handler: new CacheFirst({
|
||
cacheName: OFFLINE_CONFIG.cacheNames.assets,
|
||
}),
|
||
},
|
||
// API requests - Network First
|
||
{
|
||
matcher: ({ url }) => url.pathname.startsWith('/api/'),
|
||
handler: new NetworkFirst({
|
||
cacheName: 'api-cache',
|
||
networkTimeoutSeconds: 10,
|
||
}),
|
||
},
|
||
],
|
||
});
|
||
```
|
||
|
||
#### Cacheable Extensions
|
||
|
||
```typescript
|
||
const CACHEABLE_EXTENSIONS = [
|
||
// Images
|
||
'.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg', '.ico',
|
||
// Video
|
||
'.mp4', '.webm', '.mov',
|
||
// Audio
|
||
'.mp3', '.wav', '.ogg', '.m4a',
|
||
// Fonts
|
||
'.woff', '.woff2', '.ttf', '.eot',
|
||
// Code
|
||
'.css', '.js',
|
||
];
|
||
```
|
||
|
||
---
|
||
|
||
## Logger Configuration
|
||
|
||
The logger (`src/lib/logger.ts`) provides structured logging:
|
||
|
||
```typescript
|
||
interface LoggerConfig {
|
||
level: LogLevel; // 'debug' | 'info' | 'warn' | 'error'
|
||
isDevelopment: boolean; // Colored vs JSON output
|
||
service: string; // Service name for log aggregation
|
||
}
|
||
|
||
// Configuration from environment
|
||
this.config = {
|
||
level: toLogLevel(frontendLogLevel),
|
||
isDevelopment,
|
||
service: 'tour-builder-frontend',
|
||
};
|
||
```
|
||
|
||
#### Usage
|
||
|
||
```typescript
|
||
import { logger } from '@/lib/logger';
|
||
|
||
logger.info('User logged in', { userId: '123' });
|
||
logger.error('Failed to fetch', error);
|
||
```
|
||
|
||
---
|
||
|
||
## Configuration Dependencies
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ Configuration Flow │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ next.config.mjs (Build Config) │
|
||
│ ┌─────────────────────────────────────────────────────────┐│
|
||
│ │ - Enables Serwist (PWA) ││
|
||
│ │ - Sets output mode (export/standalone) ││
|
||
│ │ - Configures TypeScript/ESLint checking ││
|
||
│ └─────────────────────────────────────────────────────────┘│
|
||
└─────────────────────────────────────────────────────────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ config.ts (Runtime Config) │
|
||
│ ┌─────────────────────────────────────────────────────────┐│
|
||
│ │ - API URLs (baseURLApi, apiBaseURL) ││
|
||
│ │ - Runtime flags (isDevelopment, isProduction) ││
|
||
│ │ - Public log level ││
|
||
│ │ - App title, page titles ││
|
||
│ │ - Storage keys ││
|
||
│ │ - External service keys (TinyMCE) ││
|
||
│ └─────────────────────────────────────────────────────────┘│
|
||
└─────────────────────────────────────────────────────────────┘
|
||
│
|
||
┌──────────────┼──────────────┐
|
||
▼ ▼ ▼
|
||
┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐
|
||
│ offline.config.ts │ │ preload.config.ts │ │ menuAside/NavBar.ts │
|
||
│ ┌────────────────┐ │ │ ┌────────────────┐ │ │ ┌────────────────┐ │
|
||
│ │ Cache names │ │ │ │ Queue settings │ │ │ │ Navigation │ │
|
||
│ │ Event names │ │ │ │ Priorities │ │ │ │ items │ │
|
||
│ │ Storage limits │ │ │ │ Asset fields │ │ │ │ Permissions │ │
|
||
│ │ Retry config │ │ │ │ Graph depth │ │ │ │ Icons │ │
|
||
│ └────────────────┘ │ │ └────────────────┘ │ │ └────────────────┘ │
|
||
└─────────────────────┘ └─────────────────────┘ └─────────────────────┘
|
||
│ │ │
|
||
▼ ▼ ▼
|
||
┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐
|
||
│ Service Worker │ │ Preload Hooks │ │ Layout Components │
|
||
│ StorageManager │ │ DownloadManager │ │ AsideMenu │
|
||
│ DownloadEventBus │ │ NeighborGraph │ │ NavBar │
|
||
└─────────────────────┘ └─────────────────────┘ └─────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## Config Inventory
|
||
|
||
| File | LOC | Purpose | Consumers |
|
||
|------|-----|---------|-----------|
|
||
| `next.config.mjs` | 44 | Build configuration | Next.js build |
|
||
| `tailwind.config.js` | 114 | CSS theming | All components |
|
||
| `postcss.config.js` | 8 | CSS processing | Build pipeline |
|
||
| `prettier.config.js` | 12 | Code formatting | Development |
|
||
| `tsconfig.json` | 30 | TypeScript compiler | Build, IDE |
|
||
| `.eslintrc.cjs` | 50 | Linting rules | Build, IDE |
|
||
| `config.ts` | 24 | Runtime settings and public env access | 30+ files |
|
||
| `menuAside.ts` | 42 | Sidebar navigation | `AsideMenuLayer.tsx` |
|
||
| `menuNavBar.ts` | 51 | Navbar navigation | `NavBar.tsx` |
|
||
| `offline.config.ts` | 52 | PWA/offline settings | 9 files |
|
||
| `preload.config.ts` | 94 | Preloading settings | 10 files |
|
||
| **Total** | **517** | | |
|
||
|
||
---
|
||
|
||
## Best Practices
|
||
|
||
### 1. Use Typed Constants
|
||
|
||
```typescript
|
||
// Good - const assertion for type safety
|
||
export const OFFLINE_CONFIG = {
|
||
dbName: 'TourBuilderOffline',
|
||
// ...
|
||
} as const;
|
||
|
||
export type OfflineConfig = typeof OFFLINE_CONFIG;
|
||
```
|
||
|
||
### 2. Centralize Environment Access
|
||
|
||
```typescript
|
||
// Good - single source of truth
|
||
// config.ts
|
||
export const apiBaseURL = process.env.NEXT_PUBLIC_BACK_API || baseURLApi;
|
||
|
||
// Consumer
|
||
import { apiBaseURL } from '../config';
|
||
```
|
||
|
||
### 3. Group Related Settings
|
||
|
||
```typescript
|
||
// Good - logical grouping
|
||
export const PRELOAD_CONFIG = {
|
||
queue: { maxConcurrent: 3, maxRetries: 3 },
|
||
priority: { currentPage: 1000, neighborBase: 500 },
|
||
storage: { warningPercent: 80, criticalPercent: 95 },
|
||
};
|
||
```
|
||
|
||
### 4. Document Magic Numbers
|
||
|
||
```typescript
|
||
// Good - explain values
|
||
storage: {
|
||
cacheApiMaxSize: 5 * 1024 * 1024, // 5MB - files below go to Cache API
|
||
minFreeBuffer: 50 * 1024 * 1024, // 50MB - minimum free space
|
||
},
|
||
```
|
||
|
||
---
|
||
|
||
## Related Documentation
|
||
|
||
- [lib-module.md](./lib-module.md) - Offline storage using OFFLINE_CONFIG
|
||
- [hooks-module.md](./hooks-module.md) - Preload hooks using PRELOAD_CONFIG
|
||
- [layouts-module.md](./layouts-module.md) - Menu rendering
|
||
- [stores-module.md](./stores-module.md) - styleSlice using storage keys
|
||
|
||
---
|
||
|
||
## Adding New Configuration
|
||
|
||
### Step 1: Create Config File
|
||
|
||
```typescript
|
||
// src/config/feature.config.ts
|
||
export const FEATURE_CONFIG = {
|
||
enabled: true,
|
||
settings: {
|
||
maxItems: 100,
|
||
timeout: 5000,
|
||
},
|
||
} as const;
|
||
|
||
export type FeatureConfig = typeof FEATURE_CONFIG;
|
||
```
|
||
|
||
### Step 2: Import Where Needed
|
||
|
||
```typescript
|
||
import { FEATURE_CONFIG } from '../config/feature.config';
|
||
|
||
if (FEATURE_CONFIG.enabled) {
|
||
// Use settings
|
||
}
|
||
```
|
||
|
||
### Step 3: Document in This File
|
||
|
||
Add entry to Config Inventory table and explain usage.
|