diff --git a/src/SettingsList.h b/src/SettingsList.h index 5b23d548..ba52e571 100644 --- a/src/SettingsList.h +++ b/src/SettingsList.h @@ -6,52 +6,61 @@ // Returns the flat list of all settings for the web API. // This is used by CrossPointWebServer to expose settings over HTTP. +// Categories match the device UI grouping for consistency. inline std::vector getSettingsList() { return { // Display - SettingInfo::Enum("sleepScreen", "Sleep Screen", &CrossPointSettings::sleepScreen, + SettingInfo::Enum("sleepScreen", "Sleep Screen", "Display", &CrossPointSettings::sleepScreen, {"Dark", "Light", "Custom", "Cover", "None"}), - SettingInfo::Enum("sleepScreenCoverMode", "Sleep Screen Cover Mode", &CrossPointSettings::sleepScreenCoverMode, - {"Fit", "Crop"}), - SettingInfo::Enum("sleepScreenCoverFilter", "Sleep Screen Cover Filter", + SettingInfo::Enum("sleepScreenCoverMode", "Sleep Screen Cover Mode", "Display", + &CrossPointSettings::sleepScreenCoverMode, {"Fit", "Crop"}), + SettingInfo::Enum("sleepScreenCoverFilter", "Sleep Screen Cover Filter", "Display", &CrossPointSettings::sleepScreenCoverFilter, {"None", "Contrast", "Inverted"}), - SettingInfo::Enum("statusBar", "Status Bar", &CrossPointSettings::statusBar, + SettingInfo::Enum("statusBar", "Status Bar", "Display", &CrossPointSettings::statusBar, {"None", "No Progress", "Full w/ Percentage", "Full w/ Progress Bar", "Progress Bar"}), - SettingInfo::Enum("hideBatteryPercentage", "Hide Battery %", &CrossPointSettings::hideBatteryPercentage, - {"Never", "In Reader", "Always"}), - SettingInfo::Enum("refreshFrequency", "Refresh Frequency", &CrossPointSettings::refreshFrequency, + SettingInfo::Enum("hideBatteryPercentage", "Hide Battery %", "Display", + &CrossPointSettings::hideBatteryPercentage, {"Never", "In Reader", "Always"}), + SettingInfo::Enum("refreshFrequency", "Refresh Frequency", "Display", &CrossPointSettings::refreshFrequency, {"1 page", "5 pages", "10 pages", "15 pages", "30 pages"}), // Reader - SettingInfo::Enum("fontFamily", "Font Family", &CrossPointSettings::fontFamily, + SettingInfo::Enum("fontFamily", "Font Family", "Reader", &CrossPointSettings::fontFamily, {"Bookerly", "Noto Sans", "Open Dyslexic"}), - SettingInfo::Enum("fontSize", "Font Size", &CrossPointSettings::fontSize, + SettingInfo::Enum("fontSize", "Font Size", "Reader", &CrossPointSettings::fontSize, {"Small", "Medium", "Large", "X Large"}), - SettingInfo::Enum("lineSpacing", "Line Spacing", &CrossPointSettings::lineSpacing, {"Tight", "Normal", "Wide"}), - SettingInfo::Value("screenMargin", "Screen Margin", &CrossPointSettings::screenMargin, {5, 40, 5}), - SettingInfo::Enum("paragraphAlignment", "Paragraph Alignment", &CrossPointSettings::paragraphAlignment, - {"Justify", "Left", "Center", "Right"}), - SettingInfo::Toggle("hyphenationEnabled", "Hyphenation", &CrossPointSettings::hyphenationEnabled), - SettingInfo::Enum("orientation", "Reading Orientation", &CrossPointSettings::orientation, + SettingInfo::Enum("lineSpacing", "Line Spacing", "Reader", &CrossPointSettings::lineSpacing, + {"Tight", "Normal", "Wide"}), + SettingInfo::Value("screenMargin", "Screen Margin", "Reader", &CrossPointSettings::screenMargin, {5, 40, 5}), + SettingInfo::Enum("paragraphAlignment", "Paragraph Alignment", "Reader", + &CrossPointSettings::paragraphAlignment, {"Justify", "Left", "Center", "Right"}), + SettingInfo::Toggle("hyphenationEnabled", "Hyphenation", "Reader", &CrossPointSettings::hyphenationEnabled), + SettingInfo::Enum("orientation", "Reading Orientation", "Reader", &CrossPointSettings::orientation, {"Portrait", "Landscape CW", "Inverted", "Landscape CCW"}), - SettingInfo::Toggle("extraParagraphSpacing", "Extra Paragraph Spacing", + SettingInfo::Toggle("extraParagraphSpacing", "Extra Paragraph Spacing", "Reader", &CrossPointSettings::extraParagraphSpacing), - SettingInfo::Toggle("textAntiAliasing", "Text Anti-Aliasing", &CrossPointSettings::textAntiAliasing), + SettingInfo::Toggle("textAntiAliasing", "Text Anti-Aliasing", "Reader", &CrossPointSettings::textAntiAliasing), // Controls SettingInfo::Enum( - "frontButtonLayout", "Front Button Layout", &CrossPointSettings::frontButtonLayout, + "frontButtonLayout", "Front Button Layout", "Controls", &CrossPointSettings::frontButtonLayout, {"Bck, Cnfrm, Lft, Rght", "Lft, Rght, Bck, Cnfrm", "Lft, Bck, Cnfrm, Rght", "Bck, Cnfrm, Rght, Lft"}), - SettingInfo::Enum("sideButtonLayout", "Side Button Layout (reader)", &CrossPointSettings::sideButtonLayout, - {"Prev, Next", "Next, Prev"}), - SettingInfo::Toggle("longPressChapterSkip", "Long-press Chapter Skip", &CrossPointSettings::longPressChapterSkip), - SettingInfo::Enum("shortPwrBtn", "Short Power Button Click", &CrossPointSettings::shortPwrBtn, + SettingInfo::Enum("sideButtonLayout", "Side Button Layout (reader)", "Controls", + &CrossPointSettings::sideButtonLayout, {"Prev, Next", "Next, Prev"}), + SettingInfo::Toggle("longPressChapterSkip", "Long-press Chapter Skip", "Controls", + &CrossPointSettings::longPressChapterSkip), + SettingInfo::Enum("shortPwrBtn", "Short Power Button Click", "Controls", &CrossPointSettings::shortPwrBtn, {"Ignore", "Sleep", "Page Turn"}), // System - SettingInfo::Enum("sleepTimeout", "Time to Sleep", &CrossPointSettings::sleepTimeout, + SettingInfo::Enum("sleepTimeout", "Time to Sleep", "System", &CrossPointSettings::sleepTimeout, {"1 min", "5 min", "10 min", "15 min", "30 min"}), - SettingInfo::String("opdsServerUrl", "OPDS Server URL", SETTINGS.opdsServerUrl, + + // Calibre / OPDS + SettingInfo::String("opdsServerUrl", "OPDS Server URL", "Calibre", SETTINGS.opdsServerUrl, sizeof(SETTINGS.opdsServerUrl) - 1), + SettingInfo::String("opdsUsername", "OPDS Username", "Calibre", SETTINGS.opdsUsername, + sizeof(SETTINGS.opdsUsername) - 1), + SettingInfo::String("opdsPassword", "OPDS Password", "Calibre", SETTINGS.opdsPassword, + sizeof(SETTINGS.opdsPassword) - 1), }; } diff --git a/src/activities/settings/CategorySettingsActivity.h b/src/activities/settings/CategorySettingsActivity.h index 664b164b..c706c890 100644 --- a/src/activities/settings/CategorySettingsActivity.h +++ b/src/activities/settings/CategorySettingsActivity.h @@ -16,6 +16,7 @@ enum class SettingType { TOGGLE, ENUM, ACTION, VALUE, STRING }; struct SettingInfo { const char* key; // JSON key for web API (nullptr for ACTION types) const char* name; // Display name of the setting + const char* category; // Category for grouping in web UI (nullptr = uncategorized) SettingType type; uint8_t CrossPointSettings::* valuePtr; // Pointer to member in CrossPointSettings (for TOGGLE/ENUM/VALUE) char* stringPtr; // Pointer to char array (for STRING type) @@ -29,26 +30,27 @@ struct SettingInfo { }; ValueRange valueRange; - static SettingInfo Toggle(const char* key, const char* name, uint8_t CrossPointSettings::* ptr) { - return {key, name, SettingType::TOGGLE, ptr, nullptr, 0, {}, {}}; + static SettingInfo Toggle(const char* key, const char* name, const char* category, + uint8_t CrossPointSettings::* ptr) { + return {key, name, category, SettingType::TOGGLE, ptr, nullptr, 0, {}, {}}; } - static SettingInfo Enum(const char* key, const char* name, uint8_t CrossPointSettings::* ptr, - std::vector values) { - return {key, name, SettingType::ENUM, ptr, nullptr, 0, std::move(values), {}}; + static SettingInfo Enum(const char* key, const char* name, const char* category, + uint8_t CrossPointSettings::* ptr, std::vector values) { + return {key, name, category, SettingType::ENUM, ptr, nullptr, 0, std::move(values), {}}; } static SettingInfo Action(const char* name) { - return {nullptr, name, SettingType::ACTION, nullptr, nullptr, 0, {}, {}}; + return {nullptr, name, nullptr, SettingType::ACTION, nullptr, nullptr, 0, {}, {}}; } - static SettingInfo Value(const char* key, const char* name, uint8_t CrossPointSettings::* ptr, - const ValueRange valueRange) { - return {key, name, SettingType::VALUE, ptr, nullptr, 0, {}, valueRange}; + static SettingInfo Value(const char* key, const char* name, const char* category, + uint8_t CrossPointSettings::* ptr, const ValueRange valueRange) { + return {key, name, category, SettingType::VALUE, ptr, nullptr, 0, {}, valueRange}; } - static SettingInfo String(const char* key, const char* name, char* ptr, size_t maxLen) { - return {key, name, SettingType::STRING, nullptr, ptr, maxLen, {}, {}}; + static SettingInfo String(const char* key, const char* name, const char* category, char* ptr, size_t maxLen) { + return {key, name, category, SettingType::STRING, nullptr, ptr, maxLen, {}, {}}; } }; diff --git a/src/activities/settings/SettingsActivity.cpp b/src/activities/settings/SettingsActivity.cpp index da146c83..8ea68a8a 100644 --- a/src/activities/settings/SettingsActivity.cpp +++ b/src/activities/settings/SettingsActivity.cpp @@ -14,49 +14,52 @@ namespace { constexpr int displaySettingsCount = 6; const SettingInfo displaySettings[displaySettingsCount] = { // Should match with SLEEP_SCREEN_MODE - SettingInfo::Enum("sleepScreen", "Sleep Screen", &CrossPointSettings::sleepScreen, + SettingInfo::Enum("sleepScreen", "Sleep Screen", "Display", &CrossPointSettings::sleepScreen, {"Dark", "Light", "Custom", "Cover", "None"}), - SettingInfo::Enum("sleepScreenCoverMode", "Sleep Screen Cover Mode", &CrossPointSettings::sleepScreenCoverMode, - {"Fit", "Crop"}), - SettingInfo::Enum("sleepScreenCoverFilter", "Sleep Screen Cover Filter", + SettingInfo::Enum("sleepScreenCoverMode", "Sleep Screen Cover Mode", "Display", + &CrossPointSettings::sleepScreenCoverMode, {"Fit", "Crop"}), + SettingInfo::Enum("sleepScreenCoverFilter", "Sleep Screen Cover Filter", "Display", &CrossPointSettings::sleepScreenCoverFilter, {"None", "Contrast", "Inverted"}), - SettingInfo::Enum("statusBar", "Status Bar", &CrossPointSettings::statusBar, + SettingInfo::Enum("statusBar", "Status Bar", "Display", &CrossPointSettings::statusBar, {"None", "No Progress", "Full w/ Percentage", "Full w/ Progress Bar", "Progress Bar"}), - SettingInfo::Enum("hideBatteryPercentage", "Hide Battery %", &CrossPointSettings::hideBatteryPercentage, - {"Never", "In Reader", "Always"}), - SettingInfo::Enum("refreshFrequency", "Refresh Frequency", &CrossPointSettings::refreshFrequency, + SettingInfo::Enum("hideBatteryPercentage", "Hide Battery %", "Display", + &CrossPointSettings::hideBatteryPercentage, {"Never", "In Reader", "Always"}), + SettingInfo::Enum("refreshFrequency", "Refresh Frequency", "Display", &CrossPointSettings::refreshFrequency, {"1 page", "5 pages", "10 pages", "15 pages", "30 pages"})}; constexpr int readerSettingsCount = 9; const SettingInfo readerSettings[readerSettingsCount] = { - SettingInfo::Enum("fontFamily", "Font Family", &CrossPointSettings::fontFamily, + SettingInfo::Enum("fontFamily", "Font Family", "Reader", &CrossPointSettings::fontFamily, {"Bookerly", "Noto Sans", "Open Dyslexic"}), - SettingInfo::Enum("fontSize", "Font Size", &CrossPointSettings::fontSize, {"Small", "Medium", "Large", "X Large"}), - SettingInfo::Enum("lineSpacing", "Line Spacing", &CrossPointSettings::lineSpacing, {"Tight", "Normal", "Wide"}), - SettingInfo::Value("screenMargin", "Screen Margin", &CrossPointSettings::screenMargin, {5, 40, 5}), - SettingInfo::Enum("paragraphAlignment", "Paragraph Alignment", &CrossPointSettings::paragraphAlignment, - {"Justify", "Left", "Center", "Right"}), - SettingInfo::Toggle("hyphenationEnabled", "Hyphenation", &CrossPointSettings::hyphenationEnabled), - SettingInfo::Enum("orientation", "Reading Orientation", &CrossPointSettings::orientation, + SettingInfo::Enum("fontSize", "Font Size", "Reader", &CrossPointSettings::fontSize, + {"Small", "Medium", "Large", "X Large"}), + SettingInfo::Enum("lineSpacing", "Line Spacing", "Reader", &CrossPointSettings::lineSpacing, + {"Tight", "Normal", "Wide"}), + SettingInfo::Value("screenMargin", "Screen Margin", "Reader", &CrossPointSettings::screenMargin, {5, 40, 5}), + SettingInfo::Enum("paragraphAlignment", "Paragraph Alignment", "Reader", + &CrossPointSettings::paragraphAlignment, {"Justify", "Left", "Center", "Right"}), + SettingInfo::Toggle("hyphenationEnabled", "Hyphenation", "Reader", &CrossPointSettings::hyphenationEnabled), + SettingInfo::Enum("orientation", "Reading Orientation", "Reader", &CrossPointSettings::orientation, {"Portrait", "Landscape CW", "Inverted", "Landscape CCW"}), - SettingInfo::Toggle("extraParagraphSpacing", "Extra Paragraph Spacing", + SettingInfo::Toggle("extraParagraphSpacing", "Extra Paragraph Spacing", "Reader", &CrossPointSettings::extraParagraphSpacing), - SettingInfo::Toggle("textAntiAliasing", "Text Anti-Aliasing", &CrossPointSettings::textAntiAliasing)}; + SettingInfo::Toggle("textAntiAliasing", "Text Anti-Aliasing", "Reader", &CrossPointSettings::textAntiAliasing)}; constexpr int controlsSettingsCount = 4; const SettingInfo controlsSettings[controlsSettingsCount] = { SettingInfo::Enum( - "frontButtonLayout", "Front Button Layout", &CrossPointSettings::frontButtonLayout, + "frontButtonLayout", "Front Button Layout", "Controls", &CrossPointSettings::frontButtonLayout, {"Bck, Cnfrm, Lft, Rght", "Lft, Rght, Bck, Cnfrm", "Lft, Bck, Cnfrm, Rght", "Bck, Cnfrm, Rght, Lft"}), - SettingInfo::Enum("sideButtonLayout", "Side Button Layout (reader)", &CrossPointSettings::sideButtonLayout, - {"Prev, Next", "Next, Prev"}), - SettingInfo::Toggle("longPressChapterSkip", "Long-press Chapter Skip", &CrossPointSettings::longPressChapterSkip), - SettingInfo::Enum("shortPwrBtn", "Short Power Button Click", &CrossPointSettings::shortPwrBtn, + SettingInfo::Enum("sideButtonLayout", "Side Button Layout (reader)", "Controls", + &CrossPointSettings::sideButtonLayout, {"Prev, Next", "Next, Prev"}), + SettingInfo::Toggle("longPressChapterSkip", "Long-press Chapter Skip", "Controls", + &CrossPointSettings::longPressChapterSkip), + SettingInfo::Enum("shortPwrBtn", "Short Power Button Click", "Controls", &CrossPointSettings::shortPwrBtn, {"Ignore", "Sleep", "Page Turn"})}; constexpr int systemSettingsCount = 5; const SettingInfo systemSettings[systemSettingsCount] = { - SettingInfo::Enum("sleepTimeout", "Time to Sleep", &CrossPointSettings::sleepTimeout, + SettingInfo::Enum("sleepTimeout", "Time to Sleep", "System", &CrossPointSettings::sleepTimeout, {"1 min", "5 min", "10 min", "15 min", "30 min"}), SettingInfo::Action("KOReader Sync"), SettingInfo::Action("OPDS Browser"), SettingInfo::Action("Clear Cache"), SettingInfo::Action("Check for updates")}; diff --git a/src/network/CrossPointWebServer.cpp b/src/network/CrossPointWebServer.cpp index 389e6843..bb0609d6 100644 --- a/src/network/CrossPointWebServer.cpp +++ b/src/network/CrossPointWebServer.cpp @@ -965,6 +965,9 @@ void CrossPointWebServer::handleGetSettings() const { JsonObject obj = settingsArray.add(); obj["key"] = setting.key; obj["name"] = setting.name; + if (setting.category) { + obj["category"] = setting.category; + } switch (setting.type) { case SettingType::TOGGLE: diff --git a/src/network/html/SettingsPage.html b/src/network/html/SettingsPage.html index 76934309..955d25ce 100644 --- a/src/network/html/SettingsPage.html +++ b/src/network/html/SettingsPage.html @@ -168,13 +168,11 @@ Settings -
-
-
Loading settings...
-
- -
+
+
Loading settings...
+ +

@@ -205,67 +203,86 @@ const container = document.getElementById('settings-container'); container.innerHTML = ''; + // Group settings by category + const categories = new Map(); settings.forEach(setting => { - originalSettings[setting.key] = setting.value; - currentSettings[setting.key] = setting.value; + const cat = setting.category || 'Other'; + if (!categories.has(cat)) categories.set(cat, []); + categories.get(cat).push(setting); + }); - const row = document.createElement('div'); - row.className = 'setting-row'; + categories.forEach((catSettings, categoryName) => { + const card = document.createElement('div'); + card.className = 'card'; - const label = document.createElement('span'); - label.className = 'setting-label'; - label.textContent = setting.name; + const heading = document.createElement('h2'); + heading.textContent = categoryName; + card.appendChild(heading); - const control = document.createElement('div'); - control.className = 'setting-control'; + catSettings.forEach(setting => { + originalSettings[setting.key] = setting.value; + currentSettings[setting.key] = setting.value; - switch (setting.type) { - case 'toggle': - control.innerHTML = ` - - `; - break; + const row = document.createElement('div'); + row.className = 'setting-row'; - case 'enum': - let options = setting.options.map((opt, idx) => - `` - ).join(''); - control.innerHTML = ``; - break; + const label = document.createElement('span'); + label.className = 'setting-label'; + label.textContent = setting.name; - case 'value': - control.innerHTML = ` - - `; - break; + const control = document.createElement('div'); + control.className = 'setting-control'; - case 'string': - control.innerHTML = ` - - `; - break; - } + switch (setting.type) { + case 'toggle': + control.innerHTML = ` + + `; + break; - row.appendChild(label); - row.appendChild(control); - container.appendChild(row); + case 'enum': + let options = setting.options.map((opt, idx) => + `` + ).join(''); + control.innerHTML = ``; + break; - // Add change listener - const input = control.querySelector('input, select'); - if (input) { - input.addEventListener('change', () => updateCurrentValue(setting)); - input.addEventListener('input', () => updateCurrentValue(setting)); - } + case 'value': + control.innerHTML = ` + + `; + break; + + case 'string': + control.innerHTML = ` + + `; + break; + } + + row.appendChild(label); + row.appendChild(control); + card.appendChild(row); + + // Add change listener + const input = control.querySelector('input, select'); + if (input) { + input.addEventListener('change', () => updateCurrentValue(setting)); + input.addEventListener('input', () => updateCurrentValue(setting)); + } + }); + + container.appendChild(card); }); document.getElementById('save-btn').style.display = 'block';