hotel_pi/frontend/README.md
2026-04-06 21:33:52 -04:00

416 lines
8.1 KiB
Markdown

# Frontend Development Guide
## Overview
The Hotel Pi frontend is a fullscreen kiosk application built with **Vite** and **Svelte**. It provides a premium, responsive interface for browsing restaurants, attractions, and launching media apps.
## Architecture
```
src/
├── App.svelte # Root component (routing & state)
├── main.js # Entry point
├── components/ # Reusable UI components
│ ├── IdleScreen.svelte # Welcome/idle display
│ ├── HomeScreen.svelte # Main menu
│ ├── RestaurantsPage.svelte # Restaurant list
│ ├── AttractionsPage.svelte # Attractions list
│ └── Clock.svelte # Time display
├── lib/
│ ├── store.js # Svelte state management
│ ├── api.js # Directus CMS integration
│ ├── websocket.js # WebSocket client
│ └── qrcode.js # QR code generation
└── routes/ # Page components (if using SvelteKit)
index.html # HTML entry point
vite.config.js # Build configuration
tsconfig.json # TypeScript config
package.json # Dependencies
```
## Development
### Setup
```bash
cd frontend
npm install
npm run dev
```
Frontend will run on http://localhost:5173 with hot reload enabled.
### Commands
```bash
npm run dev # Start dev server
npm run build # Build for production
npm run preview # Preview production build
npm run format # Format code with Prettier
npm run lint # Check code style
```
## Core Components
### App.svelte
Main application component handling:
- Screen navigation (idle → home → restaurants/attractions)
- Input handling (keyboard and WebSocket)
- State management
- API data fetching
- Idle timeout logic
**Props:**
- None (root component)
**State:**
- `currentScreen` - Active screen
- `selectedIndex` - Currently selected menu item
- `restaurants`, `attractions` - CMS content
- `wsConnected` - Control service connection status
### IdleScreen.svelte
Fullscreen ambient display shown when idle.
**Features:**
- Animated gradient background
- Current time display (Clock component)
- Welcome message
- Floating ambient elements
- Auto-advance on any input
**Props:**
```javascript
export let welcomeName = 'Guest';
```
### HomeScreen.svelte
Main menu with three options:
1. Watch Plex
2. Restaurants
3. Things to Do
**Features:**
- Grid-based menu layout
- Navigation with arrow keys
- Visual feedback for selected item
- Smooth transitions
### RestaurantsPage.svelte
Carousel of restaurants with details.
**Features:**
- Full-screen restaurant display
- Image gallery
- QR code for website
- Description and cuisine type
- Navigation controls
**Data:**
Pulled from Directus `restaurants` collection:
- name
- description
- cuisine_type
- website_url
- image
### AttractionsPage.svelte
Similar to RestaurantsPage but for attractions.
**Features:**
- Attraction showcase
- Category badges
- Distance information
- Operating hours
- Website QR code
**Data:**
From Directus `attractions` collection:
- name
- description
- category
- distance_km
- website_url
- hours
- image
### Clock.svelte
Real-time clock display.
**Features:**
- Updates every second
- 12-hour format with AM/PM
- Large, readable typeface
- Text shadow for visibility
## Store (State Management)
Located in `src/lib/store.js`:
```javascript
export const currentScreen; // Svelte store
export const selectedIndex; // Current selection
export const restaurants; // From CMS
export const attractions; // From CMS
export const wsConnected; // WebSocket status
export function pushScreen(screen); // Navigate to screen
export function popScreen(); // Go back
export function resetNavigation(); // Reset to idle
```
### Usage
```svelte
<script>
import { currentScreen, selectedIndex, pushScreen } from '$lib/store.js';
</script>
{#if $currentScreen === 'home'}
<HomeScreen />
{/if}
<button on:click={() => pushScreen('restaurants')}>View Restaurants</button>
```
## API Integration
Located in `src/lib/api.js`:
```javascript
export async function fetchRestaurants() // Get all restaurants
export async function fetchAttractions() // Get all attractions
export function getImageUrl(filename) // Construct image URL
```
### Example
```javascript
import { fetchRestaurants } from '$lib/api.js';
const restaurants = await fetchRestaurants();
// Returns:
// [
// {
// id: 'uuid',
// name: 'La Bella Vita',
// description: '...',
// image: { ... }
// }
// ]
```
## WebSocket Communication
Located in `src/lib/websocket.js`:
```javascript
const ws = new WebSocketManager(url);
ws.on('connected', () => {});
ws.on('disconnected', () => {});
ws.on('input', (data) => {});
ws.connect();
ws.send('launch-plex');
```
## Styling
### Design System
- **Colors:**
- Primary: `#667eea` (purple-blue)
- Accent Red: `#e94560`
- Accent Cyan: `#00d4ff`
- Dark BG: `#1a1a2e`
- Card BG: `#0f3460`
- **Typography:**
- Font: Inter (system fonts fallback)
- Sizes: 0.875rem to 4rem
- Weights: 300 (light), 400 (regular), 600 (semibold), 700 (bold)
- **Spacing:**
- Base unit: 1rem
- Gaps: 0.5rem to 3rem
### CSS Features
- CSS Grid for layouts
- Flexbox for components
- CSS animations (preferred over JS)
- Media queries for responsive design
- CSS variables for theming
### Animations
Key animations used:
```css
@keyframes fade-in
@keyframes slide-down
@keyframes float
@keyframes bounce
@keyframes gradient-shift
```
Keep animations smooth and under 600ms for best UX.
## Input Handling
### Keyboard Input
```javascript
'arrowup', 'arrowdown', 'arrowleft', 'arrowright' - Navigate
'enter' - Select
'escape' - Go back
any key - Wake from idle (if on idle screen)
```
### WebSocket Input
Control service sends `input` events:
```json
{
"type": "input",
"payload": {
"type": "up" // "up", "down", "left", "right", "select", "back"
}
}
```
## Performance Optimization
1. **Lazy Loading:** Images load on-demand
2. **CSS Animations:** Prefer over JavaScript transitions
3. **Minimal Dependencies:** Only `qrcode` library
4. **Vite Optimization:**
- Tree-shaking
- Minification in production
- Code splitting
## Building for Production
```bash
npm run build
# Creates dist/ with optimized bundle
```
### Docker Production Build
```dockerfile
FROM node:20-alpine
WORKDIR /app
COPY . .
RUN npm ci && npm run build
CMD ["npm", "run", "preview"]
```
## Troubleshooting
### Hot Reload Not Working
- Clear `.svelte-kit/` and `dist/`
- Restart dev server
- Check Vite config
### Components Not Updating
- Verify store subscriptions use `$` prefix
- Check for reactive declarations (`:`)
- Ensure state updates are triggering changes
### CSS Not Applied
- Check CSS is within `<style>` block
- Verify selector specificity
- Use `:global()` for global styles carefully
### API Connection Failed
- Verify `VITE_API_URL` in `.env`
- Check Directus is running
- Review CORS settings
### WebSocket Disconnects
- Check `VITE_WS_URL` is correct
- Verify control service is running
- Review firewall rules
- Check browser console for connection errors
## Code Quality
### Formatting
```bash
npm run format # Fix formatting with Prettier
```
### Linting
```bash
npm run lint # Check code style
```
### Type Checking
TypeScript configured but optional. Add type annotations for better IDE support:
```svelte
<script>
let count: number = 0;
let items: Array<{id: string; name: string}> = [];
</script>
```
## Component Template
```svelte
<script>
import { onMount } from 'svelte';
let isLoading = false;
onMount(async () => {
// Initialization
});
</script>
<div class="component">
{#if isLoading}
<p>Loading...</p>
{:else}
<p>Content</p>
{/if}
</div>
<style>
.component {
/* Styles */
}
</style>
```
## Browser Support
- Chrome/Chromium 90+
- Firefox 88+
- Safari 14+
Tested primarily on Chromium for Raspberry Pi.
## Resources
- [Svelte Docs](https://svelte.dev)
- [Vite Docs](https://vitejs.dev)
- [Inter Font](https://fonts.google.com/specimen/Inter)