Files
bookhoard/templates/header_templ.go
T
john-okeefe bc58103c62 feat(ui): persist library selection across page navigation
Save the selected library to localStorage when the user changes the
dropdown on the bookshelf page, and restore it on every page load via
a new restoreLibrarySelection() call in the header Alpine component.

This ensures that when a user navigates between dashboard, bookshelf,
collections, etc., their last-chosen library filter is automatically
re-applied rather than resetting to the default.

Changes:
- web/src/bookshelf.ts: listen for change events on #library-select
  and persist the value to localStorage
- web/src/header.ts: add restoreLibrarySelection() which checks
  localStorage and sets the matching dropdown option on page load
- templates/header.templ: call restoreLibrarySelection() in x-init
- templates/header_templ.go: regenerated from templ source
2026-04-26 21:21:40 -04:00

79 lines
14 KiB
Go

// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.1001
package templates
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func Header(user User, currentPath string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<nav x-data=\"header\" x-init=\"initializeSearch(); initializeTheme(); loadWoodPaneling(); updateWoodPanelingIndicators(); restoreLibrarySelection()\" class=\"border-b header-nav\" style=\"border-color: var(--border); background-color: var(--bg-secondary);\"><div x-data=\"{}\" x-init=\"console.log('Alpine is working!', $el)\"></div><div class=\"w-full px-4 sm:px-6 lg:px-8\"><div class=\"flex justify-between items-center h-16\"><!-- Left: App Title & Navigation --><div class=\"flex items-center space-x-6\"><a href=\"/dashboard\" class=\"text-xl font-bold hover:opacity-80 transition-opacity\" style=\"color: var(--text-primary); text-decoration: none;\">📚 Bookhoard</a><div class=\"hidden md:flex items-center space-x-4\"><a href=\"/dashboard\" class=\"text-sm hover:opacity-80 transition-opacity\" style=\"color: var(--text-secondary); text-decoration: none;\">Library</a> <a href=\"/bookshelf\" class=\"text-sm hover:opacity-80 transition-opacity\" style=\"color: var(--text-secondary); text-decoration: none;\">All Books</a> <a href=\"/collections\" class=\"text-sm hover:opacity-80 transition-opacity\" style=\"color: var(--text-secondary); text-decoration: none;\">Collections</a> <a href=\"/progress\" class=\"text-sm hover:opacity-80 transition-opacity\" style=\"color: var(--text-secondary); text-decoration: none;\">Progress</a> <a href=\"/devices\" class=\"text-sm hover:opacity-80 transition-opacity\" style=\"color: var(--text-secondary); text-decoration: none;\">Devices</a></div></div><!-- Center: Search Box --><div class=\"flex-1 max-w-2xl mx-8\"><div class=\"relative\"><input type=\"text\" id=\"header-search\" placeholder=\"Search your library...\" class=\"w-full px-4 py-2 pl-10 border rounded-lg\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);\" autocomplete=\"off\"> <svg class=\"absolute left-3 top-2.5 h-5 w-5\" style=\"color: var(--text-secondary)\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z\"></path></svg></div></div><!-- Right: Theme Switcher & User Menu --><div x-data=\"{ themeDropdownOpen: false, userMenuOpen: false }\" class=\"flex items-center space-x-4\"><!-- Theme Switcher --><div class=\"relative\"><button @click=\"themeDropdownOpen = !themeDropdownOpen\" type=\"button\" class=\"p-2 rounded-lg hover:bg-gray-700 transition-colors\" style=\"background-color: var(--bg-primary);\"><svg class=\"h-6 w-6\" style=\"color: var(--text-primary)\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01\"></path></svg></button><div x-show=\"themeDropdownOpen\" @click.outside=\"themeDropdownOpen = false\" x-transition:enter=\"transition ease-out duration-200\" x-transition:enter-start=\"opacity-0 scale-95\" x-transition:enter-end=\"opacity-100 scale-100\" x-transition:leave=\"transition ease-in duration-150\" x-transition:leave-start=\"opacity-100 scale-100\" x-transition:leave-end=\"opacity-0 scale-95\" class=\"absolute right-0 mt-2 w-64 rounded-lg shadow-lg z-50\" style=\"background-color: var(--bg-secondary); border: 1px solid var(--border); display: none;\"><div class=\"p-4\"><h3 class=\"text-sm font-semibold mb-3\" style=\"color: var(--text-primary)\">Select Theme</h3><div class=\"space-y-2\"><button @click=\"changeTheme('tokyo-night'); themeDropdownOpen = false\" type=\"button\" class=\"w-full text-left px-3 py-2 rounded hover:opacity-80 transition-opacity\" style=\"color: var(--text-primary); background-color: var(--bg-primary);\"><span class=\"inline-block w-4 h-4 rounded mr-2\" style=\"background-color: #7aa2f7;\"></span> Tokyo Night</button> <button @click=\"changeTheme('dracula'); themeDropdownOpen = false\" type=\"button\" class=\"w-full text-left px-3 py-2 rounded hover:opacity-80 transition-opacity\" style=\"color: var(--text-primary); background-color: var(--bg-primary);\"><span class=\"inline-block w-4 h-4 rounded mr-2\" style=\"background-color: #bd93f9;\"></span> Dracula</button> <button @click=\"changeTheme('nord'); themeDropdownOpen = false\" type=\"button\" class=\"w-full text-left px-3 py-2 rounded hover:opacity-80 transition-opacity\" style=\"color: var(--text-primary); background-color: var(--bg-primary);\"><span class=\"inline-block w-4 h-4 rounded mr-2\" style=\"background-color: #88c0d0;\"></span> Nord</button> <button @click=\"changeTheme('solarized-dark'); themeDropdownOpen = false\" type=\"button\" class=\"w-full text-left px-3 py-2 rounded hover:opacity-80 transition-opacity\" style=\"color: var(--text-primary); background-color: var(--bg-primary);\"><span class=\"inline-block w-4 h-4 rounded mr-2\" style=\"background-color: #2aa198;\"></span> Solarized Dark</button> <button @click=\"changeTheme('monokai'); themeDropdownOpen = false\" type=\"button\" class=\"w-full text-left px-3 py-2 rounded hover:opacity-80 transition-opacity\" style=\"color: var(--text-primary); background-color: var(--bg-primary);\"><span class=\"inline-block w-4 h-4 rounded mr-2\" style=\"background-color: #a6e22e;\"></span> Monokai</button> <button @click=\"changeTheme('one-dark-pro'); themeDropdownOpen = false\" type=\"button\" class=\"w-full text-left px-3 py-2 rounded hover:opacity-80 transition-opacity\" style=\"color: var(--text-primary); background-color: var(--bg-primary);\"><span class=\"inline-block w-4 h-4 rounded mr-2\" style=\"background-color: #61dafb;\"></span> One Dark Pro</button> <button @click=\"changeTheme('material-dark'); themeDropdownOpen = false\" type=\"button\" class=\"w-full text-left px-3 py-2 rounded hover:opacity-80 transition-opacity\" style=\"color: var(--text-primary); background-color: var(--bg-primary);\"><span class=\"inline-block w-4 h-4 rounded mr-2\" style=\"background-color: #80cbc4;\"></span> Material Dark</button><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 @click=\"changeWoodPaneling('none'); themeDropdownOpen = false\" type=\"button\" 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 @click=\"changeWoodPaneling('wood-light'); themeDropdownOpen = false\" type=\"button\" 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 @click=\"changeWoodPaneling('wood-dark'); themeDropdownOpen = false\" type=\"button\" 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 @click=\"changeWoodPaneling('wood-mahogany'); themeDropdownOpen = false\" type=\"button\" 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></div></div></div></div><!-- User Icon with Dropdown --><div class=\"relative\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if user.ID != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<!-- LOGGED IN: Show user menu with logout --> <button @click=\"userMenuOpen = !userMenuOpen\" type=\"button\" class=\"flex items-center space-x-2 p-2 rounded-lg hover:bg-gray-700 transition-colors\" style=\"background-color: var(--bg-primary);\"><svg class=\"h-6 w-6\" style=\"color: var(--text-primary)\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z\"></path></svg> <span class=\"text-sm hidden sm:block\" style=\"color: var(--text-primary)\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(user.Username)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/header.templ`, Line: 211, Col: 96}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</span></button><div x-show=\"userMenuOpen\" @click.outside=\"userMenuOpen = false\" x-transition class=\"absolute right-0 mt-2 w-48 rounded-lg shadow-lg z-50\" style=\"background-color: var(--bg-secondary); border: 1px solid var(--border); display: none;\"><div class=\"py-1\"><a href=\"/profile\" class=\"block px-4 py-2 text-sm hover:opacity-80\" style=\"color: var(--text-primary); background-color: var(--bg-secondary); text-decoration: none;\">Profile</a> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if user.Role == "admin" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<a href=\"/admin\" class=\"block px-4 py-2 text-sm hover:opacity-80\" style=\"color: var(--text-primary); background-color: var(--bg-secondary); text-decoration: none;\">Admin Panel</a>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<div class=\"border-t my-1\" style=\"border-color: var(--border);\"></div><button @click=\"logout(); userMenuOpen = false\" type=\"button\" class=\"block w-full text-left px-4 py-2 text-sm hover:opacity-80\" style=\"color: var(--text-primary); background-color: var(--bg-secondary);\">Logout</button></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<!-- LOGGED OUT: Show login form --> <button @click=\"userMenuOpen = !userMenuOpen\" type=\"button\" class=\"flex items-center space-x-2 p-2 rounded-lg hover:bg-gray-700 transition-colors\" style=\"background-color: var(--bg-primary);\"><svg class=\"h-6 w-6\" style=\"color: var(--text-primary)\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z\"></path></svg> <span class=\"text-sm hidden sm:block\" style=\"color: var(--text-primary)\">Login</span></button><div x-show=\"userMenuOpen\" @click.outside=\"userMenuOpen = false\" x-transition class=\"absolute right-0 mt-2 w-64 rounded-lg shadow-lg z-50 p-4\" style=\"background-color: var(--bg-secondary); border: 1px solid var(--border); display: none;\"><form hx-post=\"/api/auth/login\" hx-target=\"#login-result\" hx-swap=\"innerHTML\" class=\"space-y-3\"><input type=\"hidden\" name=\"redirect\" value=\"{ currentPath }\"><div><label class=\"block text-sm mb-1\" style=\"color: var(--text-secondary)\">Email or Username</label> <input type=\"text\" name=\"login\" class=\"w-full px-3 py-2 border rounded text-sm\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);\" required></div><div><label class=\"block text-sm mb-1\" style=\"color: var(--text-secondary)\">Password</label> <input type=\"password\" name=\"password\" class=\"w-full px-3 py-2 border rounded text-sm\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);\" required></div><button type=\"submit\" class=\"w-full py-2 rounded text-sm\" style=\"background-color: var(--accent); color: white;\">Sign In</button></form><div id=\"login-result\"></div><div class=\"border-t my-2\" style=\"border-color: var(--border);\"></div><a href=\"/register\" class=\"block text-center text-sm hover:opacity-80\" style=\"color: var(--text-secondary); text-decoration: none;\">Don't have an account? Sign Up</a></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</div></div></div></div></nav><script type=\"module\" src=\"/static/main.js\"></script>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate