8.1 KiB
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
cd frontend
npm install
npm run dev
Frontend will run on http://localhost:5173 with hot reload enabled.
Commands
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 screenselectedIndex- Currently selected menu itemrestaurants,attractions- CMS contentwsConnected- 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:
export let welcomeName = 'Guest';
HomeScreen.svelte
Main menu with three options:
- Watch Plex
- Restaurants
- 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:
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
<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:
export async function fetchRestaurants() // Get all restaurants
export async function fetchAttractions() // Get all attractions
export function getImageUrl(filename) // Construct image URL
Example
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:
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
- Primary:
-
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:
@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
'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:
{
"type": "input",
"payload": {
"type": "up" // "up", "down", "left", "right", "select", "back"
}
}
Performance Optimization
- Lazy Loading: Images load on-demand
- CSS Animations: Prefer over JavaScript transitions
- Minimal Dependencies: Only
qrcodelibrary - Vite Optimization:
- Tree-shaking
- Minification in production
- Code splitting
Building for Production
npm run build
# Creates dist/ with optimized bundle
Docker Production Build
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/anddist/ - 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_URLin.env - Check Directus is running
- Review CORS settings
WebSocket Disconnects
- Check
VITE_WS_URLis correct - Verify control service is running
- Review firewall rules
- Check browser console for connection errors
Code Quality
Formatting
npm run format # Fix formatting with Prettier
Linting
npm run lint # Check code style
Type Checking
TypeScript configured but optional. Add type annotations for better IDE support:
<script>
let count: number = 0;
let items: Array<{id: string; name: string}> = [];
</script>
Component Template
<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.