260 lines
44 KiB
Go
260 lines
44 KiB
Go
// Code generated by templ - DO NOT EDIT.
|
||
|
||
// templ: version: v0.3.977
|
||
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 Devices(user User, devices []DeviceData, pendingRegistrations []PendingRegistrationData) 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, "<!doctype html><html lang=\"en\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><title>Device Management - Bookhoard</title><script src=\"/static/htmx.min.js\"></script><script src=\"/static/toast.js\"></script><link href=\"/static/style.css\" rel=\"stylesheet\"><script src=\"/static/theme.js\"></script></head><body class=\"theme-{ user.Theme }\">")
|
||
if templ_7745c5c3_Err != nil {
|
||
return templ_7745c5c3_Err
|
||
}
|
||
templ_7745c5c3_Err = Header(user, "/devices").Render(ctx, templ_7745c5c3_Buffer)
|
||
if templ_7745c5c3_Err != nil {
|
||
return templ_7745c5c3_Err
|
||
}
|
||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<div class=\"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8\"><!-- Header Section --><div class=\"mb-8\"><div class=\"flex justify-between items-center\"><div><h1 class=\"text-3xl font-bold\" style=\"color: var(--text-primary)\">Device Management</h1><p style=\"color: var(--text-secondary)\">Manage your reading devices and sync settings</p></div><button onclick=\"showAddDeviceModal()\" class=\"btn-primary px-4 py-2 rounded-lg\">➕ Add New Device</button></div></div><!-- Devices Grid --><div id=\"devices-container\" class=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6\"><!-- Empty state -->")
|
||
if templ_7745c5c3_Err != nil {
|
||
return templ_7745c5c3_Err
|
||
}
|
||
if len(devices) == 0 {
|
||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "<div id=\"empty-state\" class=\"text-center py-16 col-span-full\" style=\"color: var(--text-secondary)\"><div class=\"text-6xl mb-4\">📱</div><h3 class=\"text-xl font-semibold mb-2\" style=\"color: var(--text-primary)\">No Devices Yet</h3><p class=\"mb-4\">Add your reading devices to enable cross-device sync</p><button onclick=\"showAddDeviceModal()\" class=\"btn-primary px-4 py-2 rounded-lg\">Add Your First Device</button></div>")
|
||
if templ_7745c5c3_Err != nil {
|
||
return templ_7745c5c3_Err
|
||
}
|
||
}
|
||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<!-- Devices Grid -->")
|
||
if templ_7745c5c3_Err != nil {
|
||
return templ_7745c5c3_Err
|
||
}
|
||
if len(devices) > 0 {
|
||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<div id=\"devices-grid\" class=\"grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6\">")
|
||
if templ_7745c5c3_Err != nil {
|
||
return templ_7745c5c3_Err
|
||
}
|
||
for _, device := range devices {
|
||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<div class=\"card p-6 rounded-lg border\" style=\"background-color: var(--bg-secondary); border-color: var(--border);\"><div class=\"flex items-start justify-between mb-4\"><div class=\"text-4xl\">")
|
||
if templ_7745c5c3_Err != nil {
|
||
return templ_7745c5c3_Err
|
||
}
|
||
if device.DeviceType == "koreader" {
|
||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "📖")
|
||
if templ_7745c5c3_Err != nil {
|
||
return templ_7745c5c3_Err
|
||
}
|
||
} else if device.DeviceType == "kobo" {
|
||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "📚")
|
||
if templ_7745c5c3_Err != nil {
|
||
return templ_7745c5c3_Err
|
||
}
|
||
} else if device.DeviceType == "web" {
|
||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "🌐")
|
||
if templ_7745c5c3_Err != nil {
|
||
return templ_7745c5c3_Err
|
||
}
|
||
} else {
|
||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "📱")
|
||
if templ_7745c5c3_Err != nil {
|
||
return templ_7745c5c3_Err
|
||
}
|
||
}
|
||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</div><div class=\"flex space-x-2\"><button onclick=\"showShelfMappings('{ device.ID }')\" class=\"p-2 hover:opacity-80 rounded\" style=\"color: var(--text-secondary); background-color: var(--bg-primary);\">📚</button> <button onclick=\"showDeviceSettings('{ device.ID }')\" class=\"p-2 hover:opacity-80 rounded\" style=\"color: var(--text-secondary); background-color: var(--bg-primary);\">⚙️</button></div></div><h3 class=\"text-lg font-semibold mb-1\" 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(device.DeviceName)
|
||
if templ_7745c5c3_Err != nil {
|
||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `devices.templ`, Line: 69, Col: 102}
|
||
}
|
||
_, 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, 12, "</h3><p class=\"text-sm mb-4\" style=\"color: var(--text-secondary)\">")
|
||
if templ_7745c5c3_Err != nil {
|
||
return templ_7745c5c3_Err
|
||
}
|
||
var templ_7745c5c3_Var3 string
|
||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(device.DeviceType)
|
||
if templ_7745c5c3_Err != nil {
|
||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `devices.templ`, Line: 70, Col: 89}
|
||
}
|
||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
||
if templ_7745c5c3_Err != nil {
|
||
return templ_7745c5c3_Err
|
||
}
|
||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</p><div class=\"space-y-2 text-sm\"><div class=\"flex justify-between\"><span style=\"color: var(--text-secondary)\">Sync Status</span> ")
|
||
if templ_7745c5c3_Err != nil {
|
||
return templ_7745c5c3_Err
|
||
}
|
||
if device.SyncEnabled {
|
||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<span style=\"color: var(--accent)\">✓ Enabled</span>")
|
||
if templ_7745c5c3_Err != nil {
|
||
return templ_7745c5c3_Err
|
||
}
|
||
} else {
|
||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<span style=\"color: var(--text-secondary)\">✗ Disabled</span>")
|
||
if templ_7745c5c3_Err != nil {
|
||
return templ_7745c5c3_Err
|
||
}
|
||
}
|
||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</div><div class=\"flex justify-between\"><span style=\"color: var(--text-secondary)\">Last Sync</span> ")
|
||
if templ_7745c5c3_Err != nil {
|
||
return templ_7745c5c3_Err
|
||
}
|
||
if device.LastSync != "" {
|
||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<span style=\"color: var(--text-primary)\">")
|
||
if templ_7745c5c3_Err != nil {
|
||
return templ_7745c5c3_Err
|
||
}
|
||
var templ_7745c5c3_Var4 string
|
||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(device.LastSync)
|
||
if templ_7745c5c3_Err != nil {
|
||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `devices.templ`, Line: 83, Col: 70}
|
||
}
|
||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
||
if templ_7745c5c3_Err != nil {
|
||
return templ_7745c5c3_Err
|
||
}
|
||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</span>")
|
||
if templ_7745c5c3_Err != nil {
|
||
return templ_7745c5c3_Err
|
||
}
|
||
} else {
|
||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<span style=\"color: var(--text-primary)\">Never</span>")
|
||
if templ_7745c5c3_Err != nil {
|
||
return templ_7745c5c3_Err
|
||
}
|
||
}
|
||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "</div><div class=\"flex justify-between\"><span style=\"color: var(--text-secondary)\">Last Seen</span> ")
|
||
if templ_7745c5c3_Err != nil {
|
||
return templ_7745c5c3_Err
|
||
}
|
||
if device.LastSeen != "" {
|
||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "<span style=\"color: var(--text-primary)\">")
|
||
if templ_7745c5c3_Err != nil {
|
||
return templ_7745c5c3_Err
|
||
}
|
||
var templ_7745c5c3_Var5 string
|
||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(device.LastSeen)
|
||
if templ_7745c5c3_Err != nil {
|
||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `devices.templ`, Line: 91, Col: 70}
|
||
}
|
||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||
if templ_7745c5c3_Err != nil {
|
||
return templ_7745c5c3_Err
|
||
}
|
||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "</span>")
|
||
if templ_7745c5c3_Err != nil {
|
||
return templ_7745c5c3_Err
|
||
}
|
||
} else {
|
||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "<span style=\"color: var(--text-primary)\">Never</span>")
|
||
if templ_7745c5c3_Err != nil {
|
||
return templ_7745c5c3_Err
|
||
}
|
||
}
|
||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "</div></div></div>")
|
||
if templ_7745c5c3_Err != nil {
|
||
return templ_7745c5c3_Err
|
||
}
|
||
}
|
||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "</div>")
|
||
if templ_7745c5c3_Err != nil {
|
||
return templ_7745c5c3_Err
|
||
}
|
||
}
|
||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "</div><!-- Pending Registrations -->")
|
||
if templ_7745c5c3_Err != nil {
|
||
return templ_7745c5c3_Err
|
||
}
|
||
if len(pendingRegistrations) > 0 {
|
||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "<div id=\"pending-section\" class=\"mt-12\"><h2 class=\"text-2xl font-bold mb-4\" style=\"color: var(--text-primary)\">Pending Device Registrations</h2><div class=\"space-y-4\">")
|
||
if templ_7745c5c3_Err != nil {
|
||
return templ_7745c5c3_Err
|
||
}
|
||
for _, reg := range pendingRegistrations {
|
||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "<div class=\"card p-4 rounded-lg border flex items-center justify-between\" style=\"background-color: var(--bg-secondary); border-color: var(--border);\"><div><h3 class=\"font-semibold\" style=\"color: var(--text-primary)\">")
|
||
if templ_7745c5c3_Err != nil {
|
||
return templ_7745c5c3_Err
|
||
}
|
||
var templ_7745c5c3_Var6 string
|
||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(reg.DeviceName)
|
||
if templ_7745c5c3_Err != nil {
|
||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `devices.templ`, Line: 110, Col: 87}
|
||
}
|
||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||
if templ_7745c5c3_Err != nil {
|
||
return templ_7745c5c3_Err
|
||
}
|
||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "</h3><p class=\"text-sm\" style=\"color: var(--text-secondary)\">")
|
||
if templ_7745c5c3_Err != nil {
|
||
return templ_7745c5c3_Err
|
||
}
|
||
var templ_7745c5c3_Var7 string
|
||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(reg.DeviceType)
|
||
if templ_7745c5c3_Err != nil {
|
||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `devices.templ`, Line: 112, Col: 27}
|
||
}
|
||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
||
if templ_7745c5c3_Err != nil {
|
||
return templ_7745c5c3_Err
|
||
}
|
||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, " - Expires in ")
|
||
if templ_7745c5c3_Err != nil {
|
||
return templ_7745c5c3_Err
|
||
}
|
||
var templ_7745c5c3_Var8 string
|
||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(reg.ExpiresAt)
|
||
if templ_7745c5c3_Err != nil {
|
||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `devices.templ`, Line: 112, Col: 58}
|
||
}
|
||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
||
if templ_7745c5c3_Err != nil {
|
||
return templ_7745c5c3_Err
|
||
}
|
||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "</p></div><div class=\"flex space-x-2\"><button onclick=\"approveDevice('{ reg.RegistrationID }')\" class=\"btn-primary px-4 py-2 rounded-lg text-sm\">Approve</button> <button onclick=\"rejectDevice('{ reg.RegistrationID }')\" class=\"btn-danger px-4 py-2 rounded-lg text-sm\">Reject</button></div></div>")
|
||
if templ_7745c5c3_Err != nil {
|
||
return templ_7745c5c3_Err
|
||
}
|
||
}
|
||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "</div></div>")
|
||
if templ_7745c5c3_Err != nil {
|
||
return templ_7745c5c3_Err
|
||
}
|
||
}
|
||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "<!-- Sync Queue Section --><div id=\"sync-queue-section\" class=\"mt-12 hidden\"><div class=\"flex justify-between items-center mb-4\"><h2 class=\"text-2xl font-bold\" style=\"color: var(--text-primary)\">Sync Queue</h2><button onclick=\"clearSyncQueue()\" class=\"btn-secondary px-4 py-2 rounded-lg\">Clear Queue</button></div><div id=\"sync-queue\" class=\"space-y-3\"></div></div></div><!-- Add Device Modal --><div id=\"add-device-modal\" class=\"hidden fixed inset-0 z-50 flex items-center justify-center\" style=\"background-color: rgba(0, 0, 0, 0.7);\"><div class=\"card rounded-lg p-6 w-full max-w-md mx-4\" style=\"background-color: var(--bg-secondary); border-color: var(--border);\"><div class=\"flex justify-between items-center mb-6\"><h2 class=\"text-xl font-bold\" style=\"color: var(--text-primary)\">Add New Device</h2><button onclick=\"hideAddDeviceModal()\" class=\"p-2 hover:opacity-80 rounded\" style=\"color: var(--text-primary)\">✕</button></div><form id=\"add-device-form\" onsubmit=\"handleAddDevice(event)\"><div class=\"mb-4\"><label class=\"block text-sm font-medium mb-2\" style=\"color: var(--text-secondary)\">Device Name</label> <input type=\"text\" id=\"device-name\" required class=\"w-full px-4 py-2 border rounded-lg\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);\" placeholder=\"My Kindle Paperwhite\"></div><div class=\"mb-4\"><label class=\"block text-sm font-medium mb-2\" style=\"color: var(--text-secondary)\">Device Type</label> <select id=\"device-type\" required class=\"w-full px-4 py-2 border rounded-lg\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);\"><option value=\"\">Select device type...</option> <option value=\"koreader\">KOReader (Kindle, Kobo, PocketBook)</option> <option value=\"kobo\">Kobo E-Reader</option> <option value=\"web\">Web Browser</option> <option value=\"mobile\">Mobile App</option></select></div><div class=\"mb-6\"><label class=\"block text-sm font-medium mb-2\" style=\"color: var(--text-secondary)\">Device Identifier</label> <input type=\"text\" id=\"device-identifier\" required class=\"w-full px-4 py-2 border rounded-lg\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);\" placeholder=\"Hardware ID or Serial Number\"><p class=\"text-xs mt-1\" style=\"color: var(--text-secondary)\">Enter your device's unique identifier</p></div><div class=\"flex justify-end space-x-3\"><button type=\"button\" onclick=\"hideAddDeviceModal()\" class=\"btn-secondary px-4 py-2 rounded-lg\">Cancel</button> <button type=\"submit\" class=\"btn-primary px-4 py-2 rounded-lg\">Register Device</button></div></form></div></div><!-- Device Settings Modal --><div id=\"device-settings-modal\" class=\"hidden fixed inset-0 z-50 flex items-center justify-center\" style=\"background-color: rgba(0, 0, 0, 0.7);\"><div class=\"card rounded-lg p-6 w-full max-w-md mx-4\" style=\"background-color: var(--bg-secondary); border-color: var(--border);\"><div class=\"flex justify-between items-center mb-6\"><h2 class=\"text-xl font-bold\" style=\"color: var(--text-primary)\">Device Settings</h2><button onclick=\"hideDeviceSettingsModal()\" class=\"p-2 hover:opacity-80 rounded\" style=\"color: var(--text-primary)\">✕</button></div><form id=\"device-settings-form\" onsubmit=\"handleSaveDeviceSettings(event)\"><input type=\"hidden\" id=\"settings-device-id\"> <input type=\"hidden\" id=\"settings-device-type\"><div class=\"mb-4\"><label class=\"block text-sm font-medium mb-2\" style=\"color: var(--text-secondary)\">Device Name</label> <input type=\"text\" id=\"settings-device-name\" required class=\"w-full px-4 py-2 border rounded-lg\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);\"></div><div class=\"mb-4\"><label class=\"flex items-center space-x-3 cursor-pointer\"><input type=\"checkbox\" id=\"settings-sync-enabled\" checked class=\"w-5 h-5 rounded\"> <span style=\"color: var(--text-primary)\">Enable Sync</span></label><p class=\"text-xs mt-1 ml-8\" style=\"color: var(--text-secondary)\">Allow this device to sync reading progress</p></div><div class=\"mb-4\"><label class=\"flex items-center space-x-3 cursor-pointer\"><input type=\"checkbox\" id=\"settings-auto-sync\" checked class=\"w-5 h-5 rounded\"> <span style=\"color: var(--text-primary)\">Auto Sync</span></label><p class=\"text-xs mt-1 ml-8\" style=\"color: var(--text-secondary)\">Automatically sync changes</p></div><div class=\"mb-6\"><label class=\"block text-sm font-medium mb-2\" style=\"color: var(--text-secondary)\">Sync Frequency</label> <select id=\"settings-sync-frequency\" class=\"w-full px-4 py-2 border rounded-lg\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);\"><option value=\"1\">Every 1 minute</option> <option value=\"5\" selected>Every 5 minutes</option> <option value=\"15\">Every 15 minutes</option> <option value=\"30\">Every 30 minutes</option> <option value=\"60\">Every hour</option></select></div><div class=\"border-t pt-6 mb-6\" style=\"border-color: var(--border);\"><h3 class=\"text-lg font-semibold mb-4\" style=\"color: var(--text-primary)\">Collection View Settings</h3><p class=\"text-sm mb-4\" style=\"color: var(--text-secondary)\">Configure how collections are displayed on this device</p><div class=\"mb-4\"><label class=\"block text-sm font-medium mb-2\" style=\"color: var(--text-secondary)\">Default View Mode</label> <select id=\"settings-view-mode\" class=\"w-full px-4 py-2 border rounded-lg\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);\"><option value=\"grid\">Grid View</option> <option value=\"list\">List View</option> <option value=\"compact\">Compact View</option></select></div><div class=\"mb-4\"><label class=\"block text-sm font-medium mb-2\" style=\"color: var(--text-secondary)\">Sort Collections By</label> <select id=\"settings-sort-order\" class=\"w-full px-4 py-2 border rounded-lg\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);\"><option value=\"name\">Collection Name</option> <option value=\"created\">Date Created</option> <option value=\"book_count\">Book Count</option> <option value=\"recent\">Recently Added</option></select></div><div class=\"mb-4\"><label class=\"block text-sm font-medium mb-2\" style=\"color: var(--text-secondary)\">Items Per Page</label> <select id=\"settings-items-per-page\" class=\"w-full px-4 py-2 border rounded-lg\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);\"><option value=\"12\">12 items</option> <option value=\"24\" selected>24 items</option> <option value=\"48\">48 items</option> <option value=\"96\">96 items</option></select></div><div class=\"mb-4\"><label class=\"flex items-center space-x-3 cursor-pointer\"><input type=\"checkbox\" id=\"settings-show-covers\" checked class=\"w-5 h-5 rounded\"> <span style=\"color: var(--text-primary)\">Show Cover Images</span></label><p class=\"text-xs mt-1 ml-8\" style=\"color: var(--text-secondary)\">Display book covers in collection views</p></div><div class=\"mb-4\"><label class=\"flex items-center space-x-3 cursor-pointer\"><input type=\"checkbox\" id=\"settings-show-progress\" class=\"w-5 h-5 rounded\"> <span style=\"color: var(--text-primary)\">Show Reading Progress</span></label><p class=\"text-xs mt-1 ml-8\" style=\"color: var(--text-secondary)\">Display progress indicators for books</p></div></div><div class=\"flex justify-end space-x-3\"><button type=\"button\" onclick=\"hideDeviceSettingsModal()\" class=\"btn-secondary px-4 py-2 rounded-lg\">Cancel</button> <button type=\"button\" onclick=\"handleRevokeDevice()\" class=\"btn-danger px-4 py-2 rounded-lg\">Revoke Device</button> <button type=\"submit\" class=\"btn-primary px-4 py-2 rounded-lg\">Save Settings</button></div></form></div></div><!-- Device Shelf Mappings Modal --><div id=\"shelf-mappings-modal\" class=\"hidden fixed inset-0 z-50 flex items-center justify-center\" style=\"background-color: rgba(0, 0, 0, 0.7);\"><div class=\"card rounded-lg p-6 w-full max-w-2xl mx-4 max-h-[90vh] overflow-y-auto\" style=\"background-color: var(--bg-secondary); border-color: var(--border);\"><div class=\"flex justify-between items-center mb-6\"><h2 class=\"text-xl font-bold\" style=\"color: var(--text-primary)\">Collection to Shelf Mappings</h2><button onclick=\"hideShelfMappingsModal()\" class=\"p-2 hover:opacity-80 rounded\" style=\"color: var(--text-primary)\">✕</button></div><input type=\"hidden\" id=\"mappings-device-id\"><div id=\"shelf-mappings-container\" class=\"space-y-4 mb-6\"><p style=\"color: var(--text-secondary)\">Loading mappings...</p></div><div class=\"flex justify-end space-x-3\"><button type=\"button\" onclick=\"hideShelfMappingsModal()\" class=\"btn-secondary px-4 py-2 rounded-lg\">Close</button> <button type=\"button\" onclick=\"showAddMappingModal()\" class=\"btn-primary px-4 py-2 rounded-lg\">➕ Add Mapping</button></div></div></div><!-- Add/Edit Shelf Mapping Modal --><div id=\"add-mapping-modal\" class=\"hidden fixed inset-0 z-50 flex items-center justify-center\" style=\"background-color: rgba(0, 0, 0, 0.7);\"><div class=\"card rounded-lg p-6 w-full max-w-md mx-4\" style=\"background-color: var(--bg-secondary); border-color: var(--border);\"><div class=\"flex justify-between items-center mb-6\"><h2 class=\"text-xl font-bold\" style=\"color: var(--text-primary)\">Add Collection Mapping</h2><button onclick=\"hideAddMappingModal()\" class=\"p-2 hover:opacity-80 rounded\" style=\"color: var(--text-primary)\">✕</button></div><form id=\"mapping-form\" onsubmit=\"handleSaveMapping(event)\"><input type=\"hidden\" id=\"mapping-device-id\"> <input type=\"hidden\" id=\"mapping-id\"><div class=\"mb-4\"><label class=\"block text-sm font-medium mb-2\" style=\"color: var(--text-secondary)\">Collection</label> <select id=\"mapping-collection\" required class=\"w-full px-4 py-2 border rounded-lg\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);\"><option value=\"\">Select collection...</option></select></div><div class=\"mb-4\"><label class=\"block text-sm font-medium mb-2\" style=\"color: var(--text-secondary)\">Device Shelf Name</label> <input type=\"text\" id=\"mapping-shelf-name\" required class=\"w-full px-4 py-2 border rounded-lg\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);\" placeholder=\"e.g., Sci-Fi\"><p class=\"text-xs mt-1\" style=\"color: var(--text-secondary)\">Name of the shelf on this device</p></div><div class=\"mb-6\"><label class=\"block text-sm font-medium mb-2\" style=\"color: var(--text-secondary)\">Sync Direction</label> <select id=\"mapping-sync-direction\" required class=\"w-full px-4 py-2 border rounded-lg\" style=\"background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);\"><option value=\"bidirectional\">Bidirectional (sync both ways)</option> <option value=\"book_to_device\">Bookhoard → Device</option> <option value=\"device_to_book\">Device → Bookhoard</option> <option value=\"none\">No sync</option></select></div><div class=\"flex justify-end space-x-3\"><button type=\"button\" onclick=\"hideAddMappingModal()\" class=\"btn-secondary px-4 py-2 rounded-lg\">Cancel</button> <button type=\"submit\" class=\"btn-primary px-4 py-2 rounded-lg\">Save Mapping</button></div></form></div></div><script>\n function getDeviceIcon(typeName) {\n const deviceIcons = {\n 'koreader': '📖',\n 'kobo': '📚',\n 'web': '🌐',\n 'mobile': '📱'\n };\n return deviceIcons[typeName] || '📱';\n }\n\n function showAddDeviceModal() {\n document.getElementById('add-device-modal').classList.remove('hidden');\n }\n\n function hideAddDeviceModal() {\n document.getElementById('add-device-modal').classList.add('hidden');\n document.getElementById('add-device-form').reset();\n }\n\n function handleAddDevice(event) {\n event.preventDefault();\n\n const data = {\n device_name: document.getElementById('device-name').value,\n device_type: document.getElementById('device-type').value,\n device_identifier: document.getElementById('device-identifier').value\n };\n\n fetch('/api/devices/register', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': 'Bearer ' + localStorage.getItem('token')\n },\n body: JSON.stringify(data)\n })\n .then(response => response.json())\n .then(result => {\n showToast('Device registered! Check your device for sync instructions.', 'success');\n hideAddDeviceModal();\n loadDevices();\n })\n .catch(error => {\n showToast('Failed to register device', 'error');\n });\n }\n\n function showDeviceSettings(deviceId) {\n const device = devices.find(d => d.id === deviceId);\n if (!device) return;\n\n document.getElementById('settings-device-id').value = deviceId;\n document.getElementById('settings-device-type').value = device.device_type;\n document.getElementById('settings-device-name').value = device.device_name;\n document.getElementById('settings-sync-enabled').checked = device.sync_enabled;\n document.getElementById('settings-auto-sync').checked = device.auto_sync;\n document.getElementById('settings-sync-frequency').value = device.sync_frequency_minutes;\n\n const deviceType = device.device_type;\n \n fetch('/api/collections', {\n headers: { 'Authorization': 'Bearer ' + localStorage.getItem('token') }\n })\n .then(response => response.json())\n .then(result => {\n if (result.collections && result.collections.length > 0) {\n const firstCollection = result.collections[0];\n const viewSettings = firstCollection.view_settings || {};\n const deviceSettings = viewSettings[deviceType] || {};\n \n document.getElementById('settings-view-mode').value = deviceSettings.view_mode || 'grid';\n document.getElementById('settings-sort-order').value = deviceSettings.sort_order || 'name';\n document.getElementById('settings-items-per-page').value = deviceSettings.items_per_page || 24;\n document.getElementById('settings-show-covers').checked = deviceSettings.show_covers !== false;\n document.getElementById('settings-show-progress').checked = deviceSettings.show_progress || false;\n }\n })\n .catch(error => {\n console.error('Failed to load view settings:', error);\n });\n\n document.getElementById('device-settings-modal').classList.remove('hidden');\n }\n\n function showShelfMappings(deviceId) {\n document.getElementById('mappings-device-id').value = deviceId;\n loadShelfMappings(deviceId);\n document.getElementById('shelf-mappings-modal').classList.remove('hidden');\n }\n\n function hideShelfMappingsModal() {\n document.getElementById('shelf-mappings-modal').classList.add('hidden');\n }\n\n function loadShelfMappings(deviceId) {\n const container = document.getElementById('shelf-mappings-container');\n\n fetch(`/api/devices/${deviceId}/collections`, {\n headers: { 'Authorization': 'Bearer ' + localStorage.getItem('token') }\n })\n .then(response => response.json())\n .then(result => {\n if (result.mappings && result.mappings.length > 0) {\n container.innerHTML = result.mappings.map(mapping => `\n <div class=\"card p-4 rounded-lg border\" style=\"background-color: var(--bg-primary); border-color: var(--border);\">\n <div class=\"flex justify-between items-center\">\n <div>\n <h4 class=\"font-semibold\" style=\"color: var(--text-primary)\">${mapping.collection_name}</h4>\n <p class=\"text-sm\" style=\"color: var(--text-secondary)\">\n → Device Shelf: <strong>${mapping.device_shelf_name}</strong>\n </p>\n <p class=\"text-xs\" style=\"color: var(--text-secondary)\">\n Sync: ${mapping.sync_direction}\n </p>\n </div>\n <div class=\"flex space-x-2\">\n <button onclick=\"editMapping('${mapping.id}', '${mapping.collection_id}', '${mapping.device_shelf_name}', '${mapping.sync_direction}')\"\n class=\"p-2 hover:opacity-80 rounded\" style=\"color: var(--text-secondary); background-color: var(--bg-secondary);\">\n ✏️\n </button>\n <button onclick=\"deleteMapping('${mapping.id}')\"\n class=\"p-2 hover:opacity-80 rounded\" style=\"color: var(--text-secondary); background-color: var(--bg-secondary);\">\n 🗑️\n </button>\n </div>\n </div>\n </div>\n `).join('');\n } else {\n container.innerHTML = '<p style=\"color: var(--text-secondary)\">No shelf mappings configured. Click \"Add Mapping\" to create one.</p>';\n }\n })\n .catch(error => {\n container.innerHTML = '<p style=\"color: var(--error)\">Failed to load mappings</p>';\n });\n }\n\n function showAddMappingModal() {\n const deviceId = document.getElementById('mappings-device-id').value;\n document.getElementById('mapping-device-id').value = deviceId;\n document.getElementById('mapping-id').value = '';\n document.getElementById('mapping-collection').value = '';\n document.getElementById('mapping-shelf-name').value = '';\n document.getElementById('mapping-sync-direction').value = 'bidirectional';\n\n loadCollections();\n document.getElementById('add-mapping-modal').classList.remove('hidden');\n }\n\n function hideAddMappingModal() {\n document.getElementById('add-mapping-modal').classList.add('hidden');\n document.getElementById('mapping-form').reset();\n }\n\n function loadCollections() {\n const select = document.getElementById('mapping-collection');\n\n fetch('/api/collections', {\n headers: { 'Authorization': 'Bearer ' + localStorage.getItem('token') }\n })\n .then(response => response.json())\n .then(result => {\n if (result.collections) {\n select.innerHTML = '<option value=\"\">Select collection...</option>' +\n result.collections.map(col => `<option value=\"${col.id}\">${col.name}</option>`).join('');\n }\n })\n .catch(error => {\n console.error('Failed to load collections', error);\n });\n }\n\n function editMapping(mappingId, collectionId, shelfName, syncDirection) {\n document.getElementById('mapping-id').value = mappingId;\n document.getElementById('mapping-collection').value = collectionId;\n document.getElementById('mapping-shelf-name').value = shelfName;\n document.getElementById('mapping-sync-direction').value = syncDirection;\n\n document.getElementById('add-mapping-modal').classList.remove('hidden');\n }\n\n function handleSaveMapping(event) {\n event.preventDefault();\n\n const deviceId = document.getElementById('mapping-device-id').value;\n const mappingId = document.getElementById('mapping-id').value;\n const isUpdate = mappingId !== '';\n\n const data = {\n collection_id: document.getElementById('mapping-collection').value,\n device_shelf_name: document.getElementById('mapping-shelf-name').value,\n sync_direction: document.getElementById('mapping-sync-direction').value\n };\n\n const url = isUpdate ? `/api/devices/${deviceId}/collections/${mappingId}` : `/api/devices/${deviceId}/collections`;\n const method = isUpdate ? 'PUT' : 'POST';\n\n fetch(url, {\n method: method,\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': 'Bearer ' + localStorage.getItem('token')\n },\n body: JSON.stringify(data)\n })\n .then(response => response.json())\n .then(result => {\n showToast(isUpdate ? 'Mapping updated' : 'Mapping created', 'success');\n hideAddMappingModal();\n loadShelfMappings(deviceId);\n })\n .catch(error => {\n showToast('Failed to save mapping', 'error');\n });\n }\n\n function deleteMapping(mappingId) {\n if (!confirm('Are you sure you want to delete this mapping?')) return;\n\n const deviceId = document.getElementById('mappings-device-id').value;\n\n fetch(`/api/devices/${deviceId}/collections/${mappingId}`, {\n method: 'DELETE',\n headers: { 'Authorization': 'Bearer ' + localStorage.getItem('token') }\n })\n .then(response => {\n if (response.ok) {\n showToast('Mapping deleted', 'success');\n loadShelfMappings(deviceId);\n } else {\n showToast('Failed to delete mapping', 'error');\n }\n })\n .catch(error => {\n showToast('Failed to delete mapping', 'error');\n });\n }\n\n function hideDeviceSettingsModal() {\n document.getElementById('device-settings-modal').classList.add('hidden');\n }\n\n function handleSaveDeviceSettings(event) {\n event.preventDefault();\n\n const deviceId = document.getElementById('settings-device-id').value;\n const deviceType = document.getElementById('settings-device-type')?.value || '';\n \n const deviceData = {\n device_name: document.getElementById('settings-device-name').value,\n sync_enabled: document.getElementById('settings-sync-enabled').checked,\n auto_sync: document.getElementById('settings-auto-sync').checked,\n sync_frequency_minutes: parseInt(document.getElementById('settings-sync-frequency').value)\n };\n\n const viewSettings = {\n view_mode: document.getElementById('settings-view-mode').value,\n sort_order: document.getElementById('settings-sort-order').value,\n items_per_page: parseInt(document.getElementById('settings-items-per-page').value),\n show_covers: document.getElementById('settings-show-covers').checked,\n show_progress: document.getElementById('settings-show-progress').checked\n };\n\n fetch(`/api/devices/${deviceId}`, {\n method: 'PUT',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': 'Bearer ' + localStorage.getItem('token')\n },\n body: JSON.stringify(deviceData)\n })\n .then(response => response.json())\n .then(result => {\n return fetch('/api/collections', {\n method: 'GET',\n headers: { 'Authorization': 'Bearer ' + localStorage.getItem('token') }\n });\n })\n .then(response => response.json())\n .then(result => {\n const updatePromises = result.collections.map(collection => {\n const currentSettings = collection.view_settings || {};\n currentSettings[deviceType] = viewSettings;\n \n return fetch(`/api/collections/${collection.id}`, {\n method: 'PUT',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': 'Bearer ' + localStorage.getItem('token')\n },\n body: JSON.stringify({\n ...collection,\n view_settings: currentSettings\n })\n });\n });\n \n return Promise.all(updatePromises);\n })\n .then(() => {\n showToast('Device settings saved', 'success');\n hideDeviceSettingsModal();\n location.reload();\n })\n .catch(error => {\n showToast('Failed to save settings', 'error');\n });\n }\n\n function handleRevokeDevice() {\n const deviceId = document.getElementById('settings-device-id').value;\n if (!confirm('Are you sure you want to revoke this device? It will no longer be able to sync.')) return;\n\n fetch(`/api/devices/${deviceId}`, {\n method: 'DELETE',\n headers: { 'Authorization': 'Bearer ' + localStorage.getItem('token') }\n })\n .then(response => {\n if (response.ok) {\n showToast('Device revoked successfully', 'success');\n hideDeviceSettingsModal();\n location.reload();\n } else {\n showToast('Failed to revoke device', 'error');\n }\n })\n .catch(error => {\n showToast('Failed to revoke device', 'error');\n });\n }\n\n function approveDevice(registrationId) {\n fetch(`/api/devices/approve/${registrationId}`, {\n method: 'GET',\n headers: { 'Authorization': 'Bearer ' + localStorage.getItem('token') }\n })\n .then(response => response.json())\n .then(result => {\n showToast('Device approved successfully', 'success');\n location.reload();\n })\n .catch(error => {\n showToast('Failed to approve device', 'error');\n });\n }\n\n function rejectDevice(registrationId) {\n fetch(`/api/devices/reject/${registrationId}`, {\n method: 'POST',\n headers: { 'Authorization': 'Bearer ' + localStorage.getItem('token') }\n })\n .then(response => response.json())\n .then(result => {\n showToast('Device registration rejected', 'info');\n location.reload();\n })\n .catch(error => {\n showToast('Failed to reject device', 'error');\n });\n }\n\n function clearSyncQueue() {\n if (!confirm('Are you sure you want to clear all sync queue items?')) return;\n\n fetch('/api/queue/clear', {\n method: 'DELETE',\n headers: { 'Authorization': 'Bearer ' + localStorage.getItem('token') }\n })\n .then(response => {\n if (response.ok) {\n showToast('Sync queue cleared', 'success');\n } else {\n showToast('Failed to clear queue', 'error');\n }\n });\n }\n\n function logout() {\n localStorage.removeItem('token');\n window.location.href = '/login';\n }\n\n // Load devices on page load\n loadDevices();\n </script></body></html>")
|
||
if templ_7745c5c3_Err != nil {
|
||
return templ_7745c5c3_Err
|
||
}
|
||
return nil
|
||
})
|
||
}
|
||
|
||
var _ = templruntime.GeneratedTemplate
|