docs: add theme fix and admin sidebar implementation plans
- Add THEME_FIX_PLAN.md: comprehensive plan for theme system consistency - Add ADMIN_SIDEBAR_PLAN.md: reusable admin sidebar component plan - Add WOOD_PANELING_PLAN.md: wood paneling and full-width layout plan
This commit is contained in:
@@ -0,0 +1,318 @@
|
||||
# Admin Panel Sidebar Reusable Component Plan
|
||||
|
||||
## Problem Statement
|
||||
|
||||
The admin panel sidebar is currently implemented with code duplication and inconsistencies:
|
||||
|
||||
### Current Issues
|
||||
|
||||
1. **Code Duplication**: Sidebar markup is duplicated across templates:
|
||||
- `admin.templ` (lines 17-32)
|
||||
- `admin_library.templ` (lines 14-29)
|
||||
|
||||
2. **Inconsistent Navigation Links**:
|
||||
- `admin.templ`: Dashboard → **"User Administration"** → Library Management
|
||||
- `admin_library.templ`: Dashboard → **"Profile Settings"** → Library Management
|
||||
- "Profile Settings" link points to `/admin/profile` (doesn't exist)
|
||||
- "User Administration" link points to `/admin/users` (correct)
|
||||
|
||||
3. **Missing Sidebar**: `admin_users.templ` has **no sidebar at all** - only uses main `@Header` component
|
||||
|
||||
4. **Inconsistent Layouts**:
|
||||
- `admin.templ` & `admin_library.templ`: Full sidebar layout with `<aside>` + `<main>` in flex container
|
||||
- `admin_users.templ`: Regular layout without sidebar, just `@Header` + `<main>`
|
||||
|
||||
### Root Cause
|
||||
|
||||
Each admin template implements its own sidebar inline instead of using a shared component, leading to:
|
||||
- Maintenance burden (changes require updating multiple files)
|
||||
- Inconsistency (different links, different styling)
|
||||
- Missing features (admin_users has no sidebar)
|
||||
|
||||
---
|
||||
|
||||
## Solution Strategy
|
||||
|
||||
Create a reusable `AdminSidebar` component that:
|
||||
- Can be included in all admin pages with a single line
|
||||
- Accepts `user` and `currentPath` parameters
|
||||
- Automatically highlights the active page
|
||||
- Provides consistent, maintainable navigation
|
||||
|
||||
---
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### Phase 1: Create AdminSidebar Component
|
||||
|
||||
**New file**: `templates/admin_sidebar.templ`
|
||||
|
||||
```templ
|
||||
package templates
|
||||
|
||||
templ AdminSidebar(user User, currentPath string) {
|
||||
<aside class="w-64 border-r" style="background-color: var(--bg-secondary); border-color: var(--border)">
|
||||
<div class="p-6">
|
||||
<h2 class="text-lg font-semibold mb-6" style="color: var(--text-primary)">Admin Panel</h2>
|
||||
<nav class="space-y-2">
|
||||
<a href="/admin"
|
||||
class={"block px-4 py-2 rounded-lg " +
|
||||
("bg-accent text-bg-primary" if currentPath == "/admin" else "hover:opacity-80")}
|
||||
style="color: var(--text-primary)">
|
||||
🏠 Dashboard
|
||||
</a>
|
||||
<a href="/admin/users"
|
||||
class={"block px-4 py-2 rounded-lg " +
|
||||
("bg-accent text-bg-primary" if currentPath == "/admin/users" else "hover:opacity-80")}
|
||||
style="color: var(--text-primary)">
|
||||
👤 User Administration
|
||||
</a>
|
||||
<a href="/admin/library"
|
||||
class={"block px-4 py-2 rounded-lg " +
|
||||
("bg-accent text-bg-primary" if currentPath == "/admin/library" else "hover:opacity-80")}
|
||||
style="color: var(--text-primary)">
|
||||
📚 Library Management
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
</aside>
|
||||
}
|
||||
```
|
||||
|
||||
**Key Features:**
|
||||
- Single source of truth for admin navigation
|
||||
- Dynamic active state highlighting based on `currentPath`
|
||||
- Consistent link labels and destinations
|
||||
- Icon + text format for clarity
|
||||
- No code duplication
|
||||
|
||||
---
|
||||
|
||||
### Phase 2: Update admin.templ
|
||||
|
||||
**File**: `templates/admin.templ`
|
||||
|
||||
**Replace sidebar section (lines 17-32) with component call:**
|
||||
|
||||
**Before:**
|
||||
```templ
|
||||
<aside class="w-64 border-r" style="background-color: var(--bg-secondary); border-color: var(--border)">
|
||||
<div class="p-6">
|
||||
<h2 class="text-lg font-semibold mb-6" style="color: var(--text-primary)">Admin Panel</h2>
|
||||
<nav class="space-y-2">
|
||||
<a href="/admin" class="block px-4 py-2 rounded-lg bg-accent text-bg-primary" style="color: var(--text-primary)">
|
||||
🏠 Dashboard
|
||||
</a>
|
||||
<a href="/admin/users" class="block px-4 py-2 rounded-lg hover:opacity-80" style="color: var(--text-primary)">
|
||||
👤 User Administration
|
||||
</a>
|
||||
<a href="/admin/library" class="block px-4 py-2 rounded-lg hover:opacity-80" style="color: var(--text-primary)">
|
||||
📚 Library Management
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
</aside>
|
||||
```
|
||||
|
||||
**After:**
|
||||
```templ
|
||||
@AdminSidebar(user, "/admin")
|
||||
```
|
||||
|
||||
**Also update theme on line 13:**
|
||||
```templ
|
||||
<body class="theme-{ user.Theme }">
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Phase 3: Update admin_library.templ
|
||||
|
||||
**File**: `templates/admin_library.templ`
|
||||
|
||||
**Replace sidebar section (lines 14-29) with component call:**
|
||||
|
||||
**Before:**
|
||||
```templ
|
||||
<aside class="w-64 border-r" style="background-color: var(--bg-secondary); border-color: var(--border)">
|
||||
<div class="p-6">
|
||||
<h2 class="text-lg font-semibold mb-6" style="color: var(--text-primary)">Admin Panel</h2>
|
||||
<nav class="space-y-2">
|
||||
<a href="/admin" class="block px-4 py-2 rounded-lg hover:opacity-80" style="color: var(--text-primary)">
|
||||
🏠 Dashboard
|
||||
</a>
|
||||
<a href="/admin/profile" class="block px-4 py-2 rounded-lg hover:opacity-80" style="color: var(--text-primary)">
|
||||
👤 Profile Settings
|
||||
</a>
|
||||
<a href="/admin/library" class="block px-4 py-2 rounded-lg bg-accent text-bg-primary" style="color: var(--text-primary)">
|
||||
📚 Library Management
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
</aside>
|
||||
```
|
||||
|
||||
**After:**
|
||||
```templ
|
||||
@AdminSidebar(user, "/admin/library")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Phase 4: Add Sidebar to admin_users.templ
|
||||
|
||||
**File**: `templates/admin_users.templ`
|
||||
|
||||
**Current state**: Page has no sidebar, just `@Header` and `<main>`
|
||||
|
||||
**Add sidebar layout wrapper:**
|
||||
|
||||
**Before:**
|
||||
```templ
|
||||
<body class="theme-{ currentUser.Theme }">
|
||||
@Header(currentUser, "/admin/users")
|
||||
|
||||
<!-- Modal Container -->
|
||||
<div id="modal-container"></div>
|
||||
|
||||
<main class="max-w-7xl mx-auto px-4 py-8">
|
||||
```
|
||||
|
||||
**After:**
|
||||
```templ
|
||||
<body class="theme-{ currentUser.Theme }">
|
||||
@Header(currentUser, "/admin/users")
|
||||
|
||||
<!-- Modal Container -->
|
||||
<div id="modal-container"></div>
|
||||
|
||||
<div class="flex min-h-screen" style="background-color: var(--bg-primary)">
|
||||
@AdminSidebar(currentUser, "/admin/users")
|
||||
|
||||
<main class="flex-1 p-8">
|
||||
<div class="max-w-7xl">
|
||||
```
|
||||
|
||||
**Also need to close the new div wrapper at the end of the file (before closing `</body>`):**
|
||||
```templ
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Phase 5: Fix Layout Inconsistency in admin_users.templ
|
||||
|
||||
**File**: `templates/admin_users.templ`
|
||||
|
||||
**Remove max-width and padding from main element (now handled by sidebar layout):**
|
||||
|
||||
Current line 21:
|
||||
```templ
|
||||
<main class="max-w-7xl mx-auto px-4 py-8">
|
||||
```
|
||||
|
||||
Change to:
|
||||
```templ
|
||||
<main class="flex-1 p-8">
|
||||
<div class="max-w-7xl">
|
||||
```
|
||||
|
||||
**This wraps the content in a max-width container to match other admin pages.**
|
||||
|
||||
---
|
||||
|
||||
## Navigation Links Decision
|
||||
|
||||
**DECISION: Use exact links from `admin.templ`**
|
||||
|
||||
The AdminSidebar component will use the same 3 navigation links as `admin.templ`:
|
||||
|
||||
1. 🏠 **Dashboard** → `/admin`
|
||||
2. 👤 **User Administration** → `/admin/users`
|
||||
3. 📚 **Library Management** → `/admin/library`
|
||||
|
||||
**Rationale:**
|
||||
- These links already exist and work correctly
|
||||
- Consistent with current admin.templ implementation
|
||||
- "Profile Settings" link in admin_library.templ pointed to non-existent `/admin/profile`
|
||||
- Clean, functional navigation without broken links
|
||||
|
||||
---
|
||||
|
||||
## Benefits of This Approach
|
||||
|
||||
1. **Maintainability**: Update sidebar in one place, all pages benefit
|
||||
2. **Consistency**: All admin pages have identical navigation
|
||||
3. **Active States**: Automatic highlighting of current page
|
||||
4. **DRY Principle**: No code duplication
|
||||
5. **Scalability**: Easy to add new admin pages - just include component
|
||||
6. **User Experience**: Clear visual indication of current location
|
||||
|
||||
---
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
After implementation:
|
||||
|
||||
- [ ] Visit `/admin` → Dashboard link highlighted
|
||||
- [ ] Visit `/admin/users` → User Administration link highlighted
|
||||
- [ ] Visit `/admin/library` → Library Management link highlighted
|
||||
- [ ] Click each link → navigates to correct page
|
||||
- [ ] All three pages have consistent sidebar appearance
|
||||
- [ ] admin_users page now has sidebar (previously missing)
|
||||
- [ ] No broken links (removed /admin/profile)
|
||||
- [ ] Hover states work on all links
|
||||
- [ ] Active state has different styling (bg-accent)
|
||||
- [ ] Layout consistent across all admin pages
|
||||
- [ ] Templates compile successfully
|
||||
|
||||
---
|
||||
|
||||
## Files Summary
|
||||
|
||||
### New Files Created
|
||||
- `templates/admin_sidebar.templ` - Reusable admin sidebar component
|
||||
|
||||
### Files Modified
|
||||
- `templates/admin.templ` - Replace inline sidebar with component, fix theme
|
||||
- `templates/admin_library.templ` - Replace inline sidebar with component
|
||||
- `templates/admin_users.templ` - Add sidebar (previously missing), fix layout wrapper
|
||||
|
||||
---
|
||||
|
||||
## Migration Notes
|
||||
|
||||
### Breaking Changes
|
||||
- None - purely refactoring, same functionality
|
||||
|
||||
### Backward Compatibility
|
||||
- Full - all URLs and functionality remain the same
|
||||
|
||||
### Database Dependencies
|
||||
- None
|
||||
|
||||
### Performance
|
||||
- No impact - same HTML rendered, just defined once instead of duplicated
|
||||
|
||||
---
|
||||
|
||||
## Rollback Plan
|
||||
|
||||
If issues arise:
|
||||
1. Revert individual template changes
|
||||
2. Delete `admin_sidebar.templ`
|
||||
3. Each admin page returns to its previous standalone state
|
||||
4. No data loss (purely visual component refactoring)
|
||||
|
||||
---
|
||||
|
||||
## Future Improvements
|
||||
|
||||
1. **Collapsible sidebar**: Add toggle to hide/show sidebar
|
||||
2. **Badge notifications**: Show counts (e.g., "3 pending users")
|
||||
3. **Dropdown menus**: Group related admin functions
|
||||
4. **Permission-based links**: Hide/show links based on user role
|
||||
5. **Breadcrumbs**: Add breadcrumb navigation for admin section
|
||||
6. **Additional admin pages**: Easy to extend - just include `@AdminSidebar` with new path
|
||||
+8
-15
@@ -50,20 +50,23 @@ When users change themes via the header dropdown, new pages do not consistently
|
||||
#### Phase 1: Update Authenticated Pages
|
||||
|
||||
**Files to modify:**
|
||||
- `templates/admin_library.templ`
|
||||
- `templates/admin_users.templ`
|
||||
- `templates/bookshelf.templ`
|
||||
- `templates/admin_library.templ` → `theme-{ user.Theme }`
|
||||
- `templates/admin_users.templ` → `theme-{ currentUser.Theme }` (note: parameter is `currentUser`, not `user`)
|
||||
- `templates/bookshelf.templ` → `theme-{ user.Theme }`
|
||||
|
||||
**Change:**
|
||||
```templ
|
||||
<!-- Before -->
|
||||
<body class="theme-tokyo-night">
|
||||
|
||||
<!-- After -->
|
||||
<!-- After (admin_library.templ, bookshelf.templ) -->
|
||||
<body class="theme-{ user.Theme }">
|
||||
|
||||
<!-- After (admin_users.templ) -->
|
||||
<body class="theme-{ currentUser.Theme }">
|
||||
```
|
||||
|
||||
**Requires:** Ensure `user` parameter is available in these templates (it should be already).
|
||||
**Requires:** Verify the correct parameter name in each template (`user` vs `currentUser`).
|
||||
|
||||
#### Phase 2: Update Public Pages with Default Theme
|
||||
|
||||
@@ -72,16 +75,6 @@ When users change themes via the header dropdown, new pages do not consistently
|
||||
- `templates/login.templ`
|
||||
- `templates/register.templ`
|
||||
|
||||
**For `index.templ`:**
|
||||
- Check if user object available (if logged in)
|
||||
- If logged in: use `user.Theme`
|
||||
- If not logged in: use default theme with localStorage check
|
||||
|
||||
```templ
|
||||
<body class="theme-{ index.User.Theme }">
|
||||
```
|
||||
|
||||
**For `login.templ` and `register.templ`:**
|
||||
- Use default theme since no user context
|
||||
- Consider reading localStorage via inline JavaScript (progressive enhancement)
|
||||
|
||||
|
||||
@@ -0,0 +1,685 @@
|
||||
# Wood Paneling & Full-Width Layout Implementation
|
||||
|
||||
## Problem Statement
|
||||
|
||||
The current "wood themes" were implemented as color themes with CSS gradients, but they should be wood paneling textures applied to the dashboard bookshelf background only. Additionally, the app uses constrained width containers (`max-w-7xl`) which limit screen real estate on large monitors, unlike modern bookshelf apps like Audiobookshelf.
|
||||
|
||||
## Root Cause Analysis
|
||||
|
||||
### Current Implementation Issues
|
||||
|
||||
1. **Wood themes are misimplemented:**
|
||||
- Wood themes (`wood-light`, `wood-dark`, `wood-mahogany`) are in the theme dropdown alongside color themes
|
||||
- Applied via CSS gradients in `header.ts` `changeThemeTo()`
|
||||
- Gradients applied to entire `<body>` instead of just bookshelf background
|
||||
- Not integrated with `theme.ts` `applyTheme()` function
|
||||
|
||||
2. **Constrained layout:**
|
||||
- Header uses `max-w-7xl mx-auto` container
|
||||
- Dashboard content uses `max-w-7xl mx-auto` container
|
||||
- Other pages use `max-w-7xl mx-auto` containers
|
||||
- Wastes horizontal space on large monitors
|
||||
|
||||
3. **No active indicators:**
|
||||
- Theme dropdown doesn't show which theme/wood option is currently active
|
||||
- Users can't tell what's selected without remembering
|
||||
|
||||
## Solution Strategy
|
||||
|
||||
### Approach: Separate Concerns
|
||||
|
||||
**Principle:** Wood paneling is a visual treatment for the bookshelf, not a color theme. It should be:
|
||||
- Separate from color themes
|
||||
- Applied only to `#collections-container` on dashboard
|
||||
- Stored in localStorage (not database)
|
||||
- User-selectable from theme dropdown (but sectioned separately)
|
||||
|
||||
**Layout principle:** Full-width layout for modern app feel, better space utilization.
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### Phase 0: Download Wood Textures
|
||||
|
||||
**Source URLs:**
|
||||
|
||||
1. **Light Wood:** https://opengameart.org/content/light-wood-1024x1024
|
||||
- Author: qubodup
|
||||
- License: CC0 (no attribution required)
|
||||
- File: qubodup-light_wood.png (1.9 MB)
|
||||
- Resolution: 1024x1024
|
||||
|
||||
2. **Dark & Mahogany Wood:** https://opengameart.org/content/5-wood-textures
|
||||
- Author: Luke.RUSTLTD
|
||||
- License: CC0 (no attribution required)
|
||||
- File: wood_0.zip (6.5 MB) - contains 5 textures
|
||||
- Select 2 best: darkest and medium/reddish
|
||||
- Resolution: Likely 512x512 or 1024x1024
|
||||
|
||||
**Download Steps:**
|
||||
|
||||
```bash
|
||||
# Create textures directory
|
||||
mkdir -p web/static/textures
|
||||
|
||||
# Download light wood
|
||||
cd web/static/textures
|
||||
curl -O https://opengameart.org/sites/default/files/qubodup-light_wood.png
|
||||
mv qubodup-light_wood.png wood-light.png
|
||||
|
||||
# Download texture pack (dark & mahogany)
|
||||
cd /tmp
|
||||
curl -O https://opengameart.org/sites/default/files/wood_0.zip
|
||||
unzip wood_0.zip
|
||||
|
||||
# Select and copy best 2 textures (you'll need to review and choose)
|
||||
# Copy selected ones to web/static/textures/wood-dark.png
|
||||
# Copy selected ones to web/static/textures/wood-mahogany.png
|
||||
```
|
||||
|
||||
**Manual Steps:**
|
||||
1. Extract `wood_0.zip`
|
||||
2. Review the 5 textures
|
||||
3. Select darkest → rename to `wood-dark.png`
|
||||
4. Select medium/reddish → rename to `wood-mahogany.png`
|
||||
5. Move both to `web/static/textures/`
|
||||
|
||||
**Optional Optimization:**
|
||||
```bash
|
||||
# Use ImageOptim (Mac) or FileOptimizer (Windows/Linux)
|
||||
# Or use online: TinyPNG.com
|
||||
# Goal: Keep each under 500 KB for fast loading
|
||||
```
|
||||
|
||||
**Final Files:**
|
||||
- `web/static/textures/wood-light.png`
|
||||
- `web/static/textures/wood-dark.png`
|
||||
- `web/static/textures/wood-mahogany.png`
|
||||
|
||||
---
|
||||
|
||||
### Phase 1: Remove Wood Themes from Theme System
|
||||
|
||||
**Goal:** Remove wood themes as color themes from the dropdown
|
||||
|
||||
#### Files to Modify:
|
||||
|
||||
**1. `templates/header.templ`**
|
||||
- Remove wood theme buttons (lines 86-99)
|
||||
- This section will be replaced with wood paneling section in Phase 3
|
||||
|
||||
**2. `tailwind.config.ts`**
|
||||
- Remove `theme-wood-light`, `theme-wood-dark`, `theme-wood-mahogany` from safelist (lines 21-23)
|
||||
|
||||
**Before:**
|
||||
```typescript
|
||||
safelist: [
|
||||
'theme-wood-light',
|
||||
'theme-wood-dark',
|
||||
'theme-wood-mahogany',
|
||||
// ... other themes
|
||||
]
|
||||
```
|
||||
|
||||
**After:**
|
||||
```typescript
|
||||
safelist: [
|
||||
// Remove wood theme entries
|
||||
// Keep only color themes
|
||||
]
|
||||
```
|
||||
|
||||
**3. `web/src/header.ts`**
|
||||
- Remove wood theme handling from `changeThemeTo()` function (lines 31-51)
|
||||
- Keep only regular theme handling
|
||||
|
||||
**Before:**
|
||||
```typescript
|
||||
const changeThemeTo = (theme: string): void => {
|
||||
if (theme.startsWith('wood-')) {
|
||||
// Apply wood background theme
|
||||
document.body.className = `theme-${theme}`;
|
||||
|
||||
// Set wood background style
|
||||
let woodGradient = '';
|
||||
switch (theme) {
|
||||
case 'wood-light':
|
||||
woodGradient = 'linear-gradient(135deg, #deb887 0%, #d2a679 50%, #c9975b 100%)';
|
||||
break;
|
||||
case 'wood-dark':
|
||||
woodGradient = 'linear-gradient(135deg, #8b7355 0%, #6b5344 50%, #5a4636 100%)';
|
||||
break;
|
||||
case 'wood-mahogany':
|
||||
woodGradient = 'linear-gradient(135deg, #a0522d 0%, #8b4513 50%, #7a3c10 100%)';
|
||||
break;
|
||||
}
|
||||
|
||||
document.body.style.background = woodGradient;
|
||||
document.body.style.backgroundSize = 'cover';
|
||||
document.body.style.backgroundAttachment = 'fixed';
|
||||
} else {
|
||||
// Apply regular theme
|
||||
document.body.className = `theme-${theme}`;
|
||||
document.body.style.background = '';
|
||||
document.body.style.backgroundSize = '';
|
||||
document.body.style.backgroundAttachment = '';
|
||||
}
|
||||
// ... rest of function
|
||||
};
|
||||
```
|
||||
|
||||
**After:**
|
||||
```typescript
|
||||
const changeThemeTo = (theme: string): void => {
|
||||
// Apply regular theme only
|
||||
document.body.className = `theme-${theme}`;
|
||||
document.body.style.background = '';
|
||||
document.body.style.backgroundSize = '';
|
||||
document.body.style.backgroundAttachment = '';
|
||||
|
||||
// Save to localStorage
|
||||
localStorage.setItem('theme', theme);
|
||||
|
||||
// Save to server if logged in
|
||||
const token = localStorage.getItem('token');
|
||||
if (token) {
|
||||
fetch('/api/auth/theme', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify({ theme })
|
||||
}).catch(err => console.log('Theme save failed', err));
|
||||
}
|
||||
|
||||
// Close dropdown
|
||||
const dropdown = document.getElementById('theme-dropdown');
|
||||
if (dropdown) {
|
||||
dropdown.classList.add('hidden');
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Phase 2: Create Wood Paneling System
|
||||
|
||||
**Goal:** Create separate wood paneling preference system (localStorage only)
|
||||
|
||||
#### New File: `web/src/woodPaneling.ts`
|
||||
|
||||
```typescript
|
||||
// Wood paneling management functionality
|
||||
|
||||
type WoodPanelingType = 'none' | 'wood-light' | 'wood-dark' | 'wood-mahogany';
|
||||
|
||||
const WOOD_STORAGE_KEY = 'wood-paneling';
|
||||
|
||||
// Apply wood paneling to collections container
|
||||
const applyWoodPaneling = (paneling: WoodPanelingType): void => {
|
||||
const container = document.getElementById('collections-container');
|
||||
if (!container) return;
|
||||
|
||||
// Remove all wood background classes
|
||||
container.classList.remove('bg-wood-light', 'bg-wood-dark', 'bg-wood-mahogany');
|
||||
|
||||
if (paneling !== 'none') {
|
||||
// Add selected wood background class
|
||||
container.classList.add(`bg-${paneling}`);
|
||||
}
|
||||
|
||||
// Save to localStorage
|
||||
localStorage.setItem(WOOD_STORAGE_KEY, paneling);
|
||||
};
|
||||
|
||||
// Load wood paneling from localStorage on page load
|
||||
const loadWoodPaneling = (): void => {
|
||||
const stored = localStorage.getItem(WOOD_STORAGE_KEY) as WoodPanelingType | null;
|
||||
if (stored) {
|
||||
applyWoodPaneling(stored);
|
||||
} else {
|
||||
// Default to none
|
||||
applyWoodPaneling('none');
|
||||
}
|
||||
};
|
||||
|
||||
// Change wood paneling (called from theme dropdown)
|
||||
const changeWoodPaneling = (paneling: WoodPanelingType): void => {
|
||||
applyWoodPaneling(paneling);
|
||||
|
||||
// Update active indicators
|
||||
updateWoodPanelingIndicators();
|
||||
|
||||
// Close dropdown
|
||||
const dropdown = document.getElementById('theme-dropdown');
|
||||
if (dropdown) {
|
||||
dropdown.classList.add('hidden');
|
||||
}
|
||||
};
|
||||
|
||||
// Update visual indicators for wood paneling buttons
|
||||
const updateWoodPanelingIndicators = (): void => {
|
||||
const currentWood = localStorage.getItem(WOOD_STORAGE_KEY) || 'none';
|
||||
|
||||
// Update wood paneling buttons
|
||||
document.querySelectorAll('.wood-paneling-btn').forEach(btn => {
|
||||
const wood = btn.getAttribute('data-wood');
|
||||
if (wood === currentWood) {
|
||||
// Active state - darker background
|
||||
btn.style.backgroundColor = 'var(--bg-primary)';
|
||||
} else {
|
||||
// Inactive state
|
||||
btn.style.backgroundColor = 'var(--bg-secondary)';
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Make functions available globally
|
||||
(window as any).changeWoodPaneling = changeWoodPaneling;
|
||||
(window as any).loadWoodPaneling = loadWoodPaneling;
|
||||
(window as any).updateWoodPanelingIndicators = updateWoodPanelingIndicators;
|
||||
|
||||
// Auto-initialize when DOM is ready
|
||||
if (typeof document !== 'undefined') {
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
loadWoodPaneling();
|
||||
updateWoodPanelingIndicators();
|
||||
});
|
||||
} else {
|
||||
loadWoodPaneling();
|
||||
updateWoodPanelingIndicators();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Build:** Add to build system (will be compiled to `web/static/woodPaneling.js`)
|
||||
|
||||
---
|
||||
|
||||
### Phase 3: Update Header with Wood Paneling Section
|
||||
|
||||
**Goal:** Replace wood theme buttons with wood paneling section
|
||||
|
||||
#### File: `templates/header.templ`
|
||||
|
||||
**Replace wood theme section (lines 86-99) with:**
|
||||
|
||||
```templ
|
||||
<div class="border-t pt-2 mt-2" style="border-color: var(--border);">
|
||||
<p class="text-xs mb-2" style="color: var(--text-secondary)">Bookshelf Background</p>
|
||||
<button onclick="changeWoodPaneling('none')"
|
||||
class="wood-paneling-btn w-full text-left px-3 py-2 rounded hover:opacity-80 transition-opacity"
|
||||
style="color: var(--text-primary);"
|
||||
data-wood="none">
|
||||
None
|
||||
</button>
|
||||
<button onclick="changeWoodPaneling('wood-light')"
|
||||
class="wood-paneling-btn w-full text-left px-3 py-2 rounded hover:opacity-80 transition-opacity"
|
||||
style="color: var(--text-primary);"
|
||||
data-wood="wood-light">
|
||||
<span class="inline-block w-4 h-4 rounded mr-2"
|
||||
style="background: url('/static/textures/wood-light.png'); background-size: cover;"></span>
|
||||
Wood Light
|
||||
</button>
|
||||
<button onclick="changeWoodPaneling('wood-dark')"
|
||||
class="wood-paneling-btn w-full text-left px-3 py-2 rounded hover:opacity-80 transition-opacity"
|
||||
style="color: var(--text-primary);"
|
||||
data-wood="wood-dark">
|
||||
<span class="inline-block w-4 h-4 rounded mr-2"
|
||||
style="background: url('/static/textures/wood-dark.png'); background-size: cover;"></span>
|
||||
Wood Dark
|
||||
</button>
|
||||
<button onclick="changeWoodPaneling('wood-mahogany')"
|
||||
class="wood-paneling-btn w-full text-left px-3 py-2 rounded hover:opacity-80 transition-opacity"
|
||||
style="color: var(--text-primary);"
|
||||
data-wood="wood-mahogany">
|
||||
<span class="inline-block w-4 h-4 rounded mr-2"
|
||||
style="background: url('/static/textures/wood-mahogany.png'); background-size: cover;"></span>
|
||||
Wood Mahogany
|
||||
</button>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Add script includes at bottom of header (before closing script tag):**
|
||||
|
||||
```templ
|
||||
<script src="/static/woodPaneling.js"></script>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Phase 4: Add Active Indicators for Themes
|
||||
|
||||
**Goal:** Show which theme and wood paneling option is active
|
||||
|
||||
#### New File: `web/src/themeDropdown.ts`
|
||||
|
||||
```typescript
|
||||
// Theme dropdown active indicator management
|
||||
|
||||
// Update visual indicators for theme buttons
|
||||
const updateThemeIndicators = (): void => {
|
||||
const currentTheme = localStorage.getItem('theme') || 'tokyo-night';
|
||||
|
||||
// Update theme buttons (all buttons with changeThemeTo onclick)
|
||||
document.querySelectorAll('[onclick^="changeThemeTo"]').forEach(btn => {
|
||||
const onclick = btn.getAttribute('onclick') || '';
|
||||
const match = onclick.match(/changeThemeTo\('(.+?)'\)/);
|
||||
if (match) {
|
||||
const theme = match[1];
|
||||
if (theme === currentTheme) {
|
||||
// Active state - darker background
|
||||
btn.style.backgroundColor = 'var(--bg-primary)';
|
||||
} else {
|
||||
// Inactive state
|
||||
btn.style.backgroundColor = 'var(--bg-secondary)';
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Make function available globally
|
||||
(window as any).updateThemeIndicators = updateThemeIndicators;
|
||||
|
||||
// Update on dropdown toggle
|
||||
const originalToggleThemeDropdown = (window as any).toggleThemeDropdown;
|
||||
if (originalToggleThemeDropdown) {
|
||||
(window as any).toggleThemeDropdown = () => {
|
||||
originalToggleThemeDropdown();
|
||||
updateThemeIndicators();
|
||||
(window as any).updateWoodPanelingIndicators?.();
|
||||
};
|
||||
}
|
||||
|
||||
// Update after theme changes
|
||||
const originalChangeThemeTo = (window as any).changeThemeTo;
|
||||
if (originalChangeThemeTo) {
|
||||
(window as any).changeThemeTo = (...args: unknown[]) => {
|
||||
originalChangeThemeTo(...args);
|
||||
updateThemeIndicators();
|
||||
};
|
||||
}
|
||||
|
||||
// Auto-initialize when DOM is ready
|
||||
if (typeof document !== 'undefined') {
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', updateThemeIndicators);
|
||||
} else {
|
||||
updateThemeIndicators();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Add script include to `templates/header.templ`:**
|
||||
|
||||
```templ
|
||||
<script src="/static/themeDropdown.js"></script>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Phase 5: Update Tailwind Config for Wood Backgrounds
|
||||
|
||||
**Goal:** Add Tailwind utility classes for wood textures
|
||||
|
||||
#### File: `tailwind.config.ts`
|
||||
|
||||
**Add to `theme.extend`:**
|
||||
|
||||
```typescript
|
||||
export default {
|
||||
// ... existing config
|
||||
theme: {
|
||||
extend: {
|
||||
// ... existing extensions
|
||||
backgroundImage: {
|
||||
'wood-light': "url('/static/textures/wood-light.png')",
|
||||
'wood-dark': "url('/static/textures/wood-dark.png')",
|
||||
'wood-mahogany': "url('/static/textures/wood-mahogany.png')",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
**Regenerate CSS:**
|
||||
```bash
|
||||
npm run build:css
|
||||
# or whatever the build command is
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Phase 6: Apply Full-Width Layout Globally
|
||||
|
||||
**Goal:** Remove `max-w-7xl` containers from header and all pages
|
||||
|
||||
#### Files to Modify:
|
||||
|
||||
**1. `templates/header.templ`**
|
||||
|
||||
**Line 5 - Remove `max-w-7xl`:**
|
||||
|
||||
```templ
|
||||
<!-- Before -->
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
|
||||
<!-- After -->
|
||||
<div class="w-full px-4 sm:px-6 lg:px-8">
|
||||
```
|
||||
|
||||
**2. `templates/dashboard.templ`**
|
||||
|
||||
**Line 27 - Library selector:**
|
||||
|
||||
```templ
|
||||
<!-- Before -->
|
||||
<div class="max-w-7xl mx-auto px-4 py-3 flex items-center justify-between">
|
||||
|
||||
<!-- After -->
|
||||
<div class="w-full px-4 py-3 flex items-center justify-between">
|
||||
```
|
||||
|
||||
**Line 67 - Collections container:**
|
||||
|
||||
```templ
|
||||
<!-- Before -->
|
||||
<main id="collections-container" class="max-w-7xl mx-auto px-4 py-8">
|
||||
|
||||
<!-- After -->
|
||||
<main id="collections-container" class="w-full px-4 py-8">
|
||||
```
|
||||
|
||||
**3. Other pages (remove `max-w-7xl mx-auto` from all content containers):**
|
||||
|
||||
- `templates/collections.templ`
|
||||
- `templates/progress.templ`
|
||||
- `templates/queue.templ`
|
||||
- `templates/devices.templ`
|
||||
- `templates/analytics.templ`
|
||||
- `templates/conflicts.templ`
|
||||
- `templates/unlinked_books.templ`
|
||||
- `templates/bookshelf.templ`
|
||||
- `templates/profile.templ`
|
||||
- `templates/admin.templ` (see note below)
|
||||
- `templates/admin_library.templ` (see note below)
|
||||
- `templates/admin_users.templ` (see note below)
|
||||
- `templates/docs.templ`
|
||||
|
||||
**Pattern:** Replace `class="max-w-7xl mx-auto px-4 ..."` with `class="w-full px-4 ..."`
|
||||
|
||||
**Note:** Keep padding (`px-4`, `py-8`, etc.) for readability.
|
||||
|
||||
**Special note for ALL admin templates (admin.templ, admin_library.templ, admin_users.templ):**
|
||||
These files were already modified by ADMIN_SIDEBAR_PLAN.md to add sidebar layout wrappers.
|
||||
The current structure for all three is:
|
||||
```templ
|
||||
<div class="flex min-h-screen" style="background-color: var(--bg-primary)">
|
||||
@AdminSidebar(user, currentPath)
|
||||
<main class="flex-1 p-8">
|
||||
<div class="max-w-7xl">
|
||||
(or <div class="max-w-6xl"> in some cases)
|
||||
```
|
||||
|
||||
To apply full-width layout, change the inner div in all three:
|
||||
```templ
|
||||
<!-- Before -->
|
||||
<div class="max-w-7xl">
|
||||
(or <div class="max-w-6xl">)
|
||||
|
||||
<!-- After -->
|
||||
<div class="w-full">
|
||||
```
|
||||
|
||||
**Do NOT** modify the `main` element or the flex wrapper in any admin template - these are part of the admin sidebar layout.
|
||||
|
||||
---
|
||||
|
||||
### Phase 7: Add Inline Script for Dashboard Initialization
|
||||
|
||||
**Goal:** Ensure wood paneling is applied immediately on dashboard load
|
||||
|
||||
#### File: `templates/dashboard.templ`
|
||||
|
||||
**Add inline script after existing scripts (around line 20):**
|
||||
|
||||
```templ
|
||||
<script>
|
||||
// Apply wood paneling immediately (before DOM fully loads)
|
||||
(function() {
|
||||
const woodPaneling = localStorage.getItem('wood-paneling') || 'none';
|
||||
if (woodPaneling !== 'none') {
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const container = document.getElementById('collections-container');
|
||||
if (container) {
|
||||
container.classList.add('bg-' + woodPaneling);
|
||||
}
|
||||
});
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
```
|
||||
|
||||
**This ensures:**
|
||||
- Wood paneling applied before external woodPaneling.js loads
|
||||
- No flash of wrong background
|
||||
- Progressive enhancement: works even if JS fails
|
||||
|
||||
---
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
After implementation:
|
||||
|
||||
### Wood Paneling
|
||||
- [ ] Wood paneling options appear in theme dropdown under "Bookshelf Background" section
|
||||
- [ ] "None" option removes wood texture
|
||||
- [ ] Selecting "Wood Light" applies light wood texture to dashboard background
|
||||
- [ ] Selecting "Wood Dark" applies dark wood texture to dashboard background
|
||||
- [ ] Selecting "Wood Mahogany" applies mahogany texture to dashboard background
|
||||
- [ ] Texture covers only `#collections-container` area (not header or sidebar)
|
||||
- [ ] Texture looks seamless when repeated
|
||||
- [ ] Wood preference persists across page refreshes (localStorage)
|
||||
- [ ] Active wood option has darker background highlight
|
||||
- [ ] Switching between wood options updates immediately
|
||||
|
||||
### Full-Width Layout
|
||||
- [ ] Header uses full width on all pages
|
||||
- [ ] Dashboard uses full width
|
||||
- [ ] Collections page uses full width
|
||||
- [ ] Bookshelf page uses full width
|
||||
- [ ] All other pages use full width
|
||||
- [ ] Content still has appropriate padding for readability
|
||||
- [ ] Layout responsive on mobile devices
|
||||
|
||||
### Theme System
|
||||
- [ ] Wood themes removed from theme dropdown
|
||||
- [ ] Color themes still work correctly
|
||||
- [ ] Active color theme has darker background highlight
|
||||
- [ ] Theme switching still saves to server
|
||||
- [ ] No wood theme options in color theme section
|
||||
|
||||
### Performance
|
||||
- [ ] Wood textures load quickly (no visible delay on reasonable connection)
|
||||
- [ ] No flash of wrong background on page load
|
||||
- [ ] Texture files are optimized (< 500 KB each)
|
||||
|
||||
---
|
||||
|
||||
## Files Summary
|
||||
|
||||
### New Files Created
|
||||
- `web/src/woodPaneling.ts` - Wood paneling management
|
||||
- `web/src/themeDropdown.ts` - Active indicator management
|
||||
- `web/static/textures/wood-light.png` - Light wood texture
|
||||
- `web/static/textures/wood-dark.png` - Dark wood texture
|
||||
- `web/static/textures/wood-mahogany.png` - Mahogany texture
|
||||
|
||||
### Files Modified
|
||||
- `templates/header.templ` - Remove wood themes, add wood paneling section, remove max-width
|
||||
- `tailwind.config.ts` - Remove theme-wood-* from safelist, add wood background images
|
||||
- `web/src/header.ts` - Remove wood theme logic from `changeThemeTo()`
|
||||
- `templates/dashboard.templ` - Remove max-width, add inline initialization script
|
||||
- `templates/*.templ` - Remove `max-w-7xl` from all page templates
|
||||
|
||||
---
|
||||
|
||||
## Migration Notes
|
||||
|
||||
### Breaking Changes
|
||||
- Wood themes removed from theme system
|
||||
- Users with wood theme selected will see tokyo-night (or their last color theme)
|
||||
- Wood paneling preference starts as "none" (users must opt-in)
|
||||
|
||||
### Backward Compatibility
|
||||
- Color themes unaffected
|
||||
- localStorage theme preference still works
|
||||
- Server-side theme sync still works
|
||||
|
||||
### Database Dependencies
|
||||
- None (wood paneling is localStorage only)
|
||||
|
||||
### Performance
|
||||
- Wood textures only loaded when selected
|
||||
- "None" option = no texture load (fastest)
|
||||
- Texture size optimized to ~200-500 KB each
|
||||
|
||||
### Progressive Enhancement
|
||||
- Pages work without wood paneling (default "none")
|
||||
- Wood paneling degrades gracefully if JS fails
|
||||
- Color themes work independently of wood paneling
|
||||
|
||||
---
|
||||
|
||||
## Rollback Plan
|
||||
|
||||
If issues arise:
|
||||
1. Remove wood paneling section from header
|
||||
2. Restore `max-w-7xl` containers in templates
|
||||
3. Delete wood texture files
|
||||
4. Revert header.ts to include wood theme logic
|
||||
5. Wood paneling preference in localStorage will be ignored (harmless)
|
||||
|
||||
---
|
||||
|
||||
## Future Improvements
|
||||
|
||||
1. **Per-page wood paneling:** Allow wood on bookshelf page, not just dashboard
|
||||
2. **Texture variety:** Add more wood options (oak, pine, walnut)
|
||||
3. **Texture intensity:** Add opacity slider for subtler effect
|
||||
4. **Custom textures:** Allow users to upload their own backgrounds
|
||||
5. **Server-side sync:** Store wood paneling preference in database for cross-device sync
|
||||
6. **Preview mode:** Show texture preview before applying
|
||||
7. **High-DPI textures:** Provide 2x versions for retina displays
|
||||
|
||||
---
|
||||
|
||||
## Attribution
|
||||
|
||||
All wood textures are CC0 licensed (no attribution required):
|
||||
|
||||
- **Light wood:** qubodup - https://opengameart.org/content/light-wood-1024x1024
|
||||
- **Dark/Mahogany wood:** Luke.RUSTLTD - https://opengameart.org/content/5-wood-textures
|
||||
|
||||
**Optional:** Add to README.md or about page for documentation purposes.
|
||||
Reference in New Issue
Block a user