mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2026-02-04 06:37:38 +03:00
feat: Group web settings by category and add missing OPDS credentials
Address code review: settings are now grouped by category (Display, Reader, Controls, System, Calibre) in both the JSON API and the web UI. Adds category field to SettingInfo struct. Also adds opdsUsername and opdsPassword to the Calibre section, which were missing from the web settings.
This commit is contained in:
parent
84fa55789c
commit
434fc9fb7d
@ -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<SettingInfo> 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),
|
||||
};
|
||||
}
|
||||
|
||||
@ -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<std::string> 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<std::string> 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, {}, {}};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -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")};
|
||||
|
||||
@ -965,6 +965,9 @@ void CrossPointWebServer::handleGetSettings() const {
|
||||
JsonObject obj = settingsArray.add<JsonObject>();
|
||||
obj["key"] = setting.key;
|
||||
obj["name"] = setting.name;
|
||||
if (setting.category) {
|
||||
obj["category"] = setting.category;
|
||||
}
|
||||
|
||||
switch (setting.type) {
|
||||
case SettingType::TOGGLE:
|
||||
|
||||
@ -168,13 +168,11 @@
|
||||
<a href="/settings">Settings</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div id="settings-container">
|
||||
<div class="loading">Loading settings...</div>
|
||||
<div class="card"><div class="loading">Loading settings...</div></div>
|
||||
</div>
|
||||
<button id="save-btn" class="save-btn" style="display: none;">Save Settings</button>
|
||||
<div id="status-message" class="status-message"></div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<p style="text-align: center; color: #95a5a6; margin: 0">
|
||||
@ -205,7 +203,23 @@
|
||||
const container = document.getElementById('settings-container');
|
||||
container.innerHTML = '';
|
||||
|
||||
// Group settings by category
|
||||
const categories = new Map();
|
||||
settings.forEach(setting => {
|
||||
const cat = setting.category || 'Other';
|
||||
if (!categories.has(cat)) categories.set(cat, []);
|
||||
categories.get(cat).push(setting);
|
||||
});
|
||||
|
||||
categories.forEach((catSettings, categoryName) => {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'card';
|
||||
|
||||
const heading = document.createElement('h2');
|
||||
heading.textContent = categoryName;
|
||||
card.appendChild(heading);
|
||||
|
||||
catSettings.forEach(setting => {
|
||||
originalSettings[setting.key] = setting.value;
|
||||
currentSettings[setting.key] = setting.value;
|
||||
|
||||
@ -251,14 +265,14 @@
|
||||
<input type="text" id="${setting.key}"
|
||||
value="${setting.value || ''}"
|
||||
maxlength="${setting.maxLength}"
|
||||
placeholder="Enter URL...">
|
||||
placeholder="Enter value...">
|
||||
`;
|
||||
break;
|
||||
}
|
||||
|
||||
row.appendChild(label);
|
||||
row.appendChild(control);
|
||||
container.appendChild(row);
|
||||
card.appendChild(row);
|
||||
|
||||
// Add change listener
|
||||
const input = control.querySelector('input, select');
|
||||
@ -268,6 +282,9 @@
|
||||
}
|
||||
});
|
||||
|
||||
container.appendChild(card);
|
||||
});
|
||||
|
||||
document.getElementById('save-btn').style.display = 'block';
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user