mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2026-02-04 14:47:37 +03:00
feat: Add KOReader Sync and OPDS Browser settings to web UI
Web settings now mirror the device UI structure: - Display, Reader, Controls, System (same as device categories) - KOReader Sync: username, password, server URL, document matching - OPDS Browser: server URL, username, password Adds dynamic getter/setter support to SettingInfo for settings backed by stores other than CrossPointSettings (KOReaderCredentialStore).
This commit is contained in:
parent
98bd0572e0
commit
76bfe2d35d
@ -2,11 +2,13 @@
|
||||
#include <vector>
|
||||
|
||||
#include "CrossPointSettings.h"
|
||||
#include "KOReaderCredentialStore.h"
|
||||
#include "activities/settings/CategorySettingsActivity.h"
|
||||
|
||||
// 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.
|
||||
// Returns the list of all settings, used by both the device UI and the web API.
|
||||
// Categories match the device UI grouping. Settings with categories that don't
|
||||
// match a device UI category (e.g. "KOReader Sync", "OPDS Browser") are
|
||||
// web-only — they correspond to device sub-screens accessed via Action items.
|
||||
inline std::vector<SettingInfo> getSettingsList() {
|
||||
return {
|
||||
// Display
|
||||
@ -55,12 +57,28 @@ inline std::vector<SettingInfo> getSettingsList() {
|
||||
SettingInfo::Enum("sleepTimeout", "Time to Sleep", "System", &CrossPointSettings::sleepTimeout,
|
||||
{"1 min", "5 min", "10 min", "15 min", "30 min"}),
|
||||
|
||||
// Calibre / OPDS
|
||||
SettingInfo::String("opdsServerUrl", "OPDS Server URL", "Calibre", SETTINGS.opdsServerUrl,
|
||||
// KOReader Sync (device sub-screen accessed via System > KOReader Sync action)
|
||||
SettingInfo::DynamicString(
|
||||
"koUsername", "Username", "KOReader Sync", [] { return KOREADER_STORE.getUsername(); },
|
||||
[](const std::string& v) { KOREADER_STORE.setCredentials(v, KOREADER_STORE.getPassword()); }, 64),
|
||||
SettingInfo::DynamicString(
|
||||
"koPassword", "Password", "KOReader Sync", [] { return KOREADER_STORE.getPassword(); },
|
||||
[](const std::string& v) { KOREADER_STORE.setCredentials(KOREADER_STORE.getUsername(), v); }, 64),
|
||||
SettingInfo::DynamicString(
|
||||
"koServerUrl", "Sync Server URL", "KOReader Sync", [] { return KOREADER_STORE.getServerUrl(); },
|
||||
[](const std::string& v) { KOREADER_STORE.setServerUrl(v); }, 128),
|
||||
SettingInfo::DynamicEnum(
|
||||
"koMatchMethod", "Document Matching", "KOReader Sync",
|
||||
[] { return static_cast<uint8_t>(KOREADER_STORE.getMatchMethod()); },
|
||||
[](uint8_t v) { KOREADER_STORE.setMatchMethod(static_cast<DocumentMatchMethod>(v)); },
|
||||
{"Filename", "Binary"}),
|
||||
|
||||
// OPDS Browser (device sub-screen accessed via System > OPDS Browser action)
|
||||
SettingInfo::String("opdsServerUrl", "Server URL", "OPDS Browser", SETTINGS.opdsServerUrl,
|
||||
sizeof(SETTINGS.opdsServerUrl) - 1),
|
||||
SettingInfo::String("opdsUsername", "OPDS Username", "Calibre", SETTINGS.opdsUsername,
|
||||
SettingInfo::String("opdsUsername", "Username", "OPDS Browser", SETTINGS.opdsUsername,
|
||||
sizeof(SETTINGS.opdsUsername) - 1),
|
||||
SettingInfo::String("opdsPassword", "OPDS Password", "Calibre", SETTINGS.opdsPassword,
|
||||
SettingInfo::String("opdsPassword", "Password", "OPDS Browser", SETTINGS.opdsPassword,
|
||||
sizeof(SETTINGS.opdsPassword) - 1),
|
||||
};
|
||||
}
|
||||
|
||||
@ -30,6 +30,12 @@ struct SettingInfo {
|
||||
};
|
||||
ValueRange valueRange;
|
||||
|
||||
// Dynamic accessors for settings not in CrossPointSettings
|
||||
std::function<uint8_t()> valueGetter;
|
||||
std::function<void(uint8_t)> valueSetter;
|
||||
std::function<std::string()> stringGetter;
|
||||
std::function<void(const std::string&)> stringSetter;
|
||||
|
||||
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, {}, {}};
|
||||
@ -52,6 +58,34 @@ struct SettingInfo {
|
||||
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, {}, {}};
|
||||
}
|
||||
|
||||
static SettingInfo DynamicEnum(const char* key, const char* name, const char* category,
|
||||
std::function<uint8_t()> getter, std::function<void(uint8_t)> setter,
|
||||
std::vector<std::string> values) {
|
||||
SettingInfo info{};
|
||||
info.key = key;
|
||||
info.name = name;
|
||||
info.category = category;
|
||||
info.type = SettingType::ENUM;
|
||||
info.enumValues = std::move(values);
|
||||
info.valueGetter = std::move(getter);
|
||||
info.valueSetter = std::move(setter);
|
||||
return info;
|
||||
}
|
||||
|
||||
static SettingInfo DynamicString(const char* key, const char* name, const char* category,
|
||||
std::function<std::string()> getter,
|
||||
std::function<void(const std::string&)> setter, size_t maxLen) {
|
||||
SettingInfo info{};
|
||||
info.key = key;
|
||||
info.name = name;
|
||||
info.category = category;
|
||||
info.type = SettingType::STRING;
|
||||
info.stringMaxLen = maxLen;
|
||||
info.stringGetter = std::move(getter);
|
||||
info.stringSetter = std::move(setter);
|
||||
return info;
|
||||
}
|
||||
};
|
||||
|
||||
class CategorySettingsActivity final : public ActivityWithSubactivity {
|
||||
|
||||
@ -972,12 +972,12 @@ void CrossPointWebServer::handleGetSettings() const {
|
||||
switch (setting.type) {
|
||||
case SettingType::TOGGLE:
|
||||
obj["type"] = "toggle";
|
||||
obj["value"] = SETTINGS.*(setting.valuePtr) ? 1 : 0;
|
||||
obj["value"] = (setting.valueGetter ? setting.valueGetter() : SETTINGS.*(setting.valuePtr)) ? 1 : 0;
|
||||
break;
|
||||
|
||||
case SettingType::ENUM: {
|
||||
obj["type"] = "enum";
|
||||
obj["value"] = SETTINGS.*(setting.valuePtr);
|
||||
obj["value"] = setting.valueGetter ? setting.valueGetter() : SETTINGS.*(setting.valuePtr);
|
||||
JsonArray opts = obj["options"].to<JsonArray>();
|
||||
for (const auto& opt : setting.enumValues) {
|
||||
opts.add(opt);
|
||||
@ -987,7 +987,7 @@ void CrossPointWebServer::handleGetSettings() const {
|
||||
|
||||
case SettingType::VALUE:
|
||||
obj["type"] = "value";
|
||||
obj["value"] = SETTINGS.*(setting.valuePtr);
|
||||
obj["value"] = setting.valueGetter ? setting.valueGetter() : SETTINGS.*(setting.valuePtr);
|
||||
obj["min"] = setting.valueRange.min;
|
||||
obj["max"] = setting.valueRange.max;
|
||||
obj["step"] = setting.valueRange.step;
|
||||
@ -995,7 +995,11 @@ void CrossPointWebServer::handleGetSettings() const {
|
||||
|
||||
case SettingType::STRING:
|
||||
obj["type"] = "string";
|
||||
obj["value"] = setting.stringPtr;
|
||||
if (setting.stringGetter) {
|
||||
obj["value"] = setting.stringGetter();
|
||||
} else {
|
||||
obj["value"] = setting.stringPtr;
|
||||
}
|
||||
obj["maxLength"] = setting.stringMaxLen;
|
||||
break;
|
||||
|
||||
@ -1043,7 +1047,11 @@ void CrossPointWebServer::handlePostSettings() {
|
||||
switch (setting.type) {
|
||||
case SettingType::TOGGLE: {
|
||||
const int value = doc[setting.key].as<int>();
|
||||
SETTINGS.*(setting.valuePtr) = value ? 1 : 0;
|
||||
if (setting.valueSetter) {
|
||||
setting.valueSetter(value ? 1 : 0);
|
||||
} else {
|
||||
SETTINGS.*(setting.valuePtr) = value ? 1 : 0;
|
||||
}
|
||||
updatedCount++;
|
||||
break;
|
||||
}
|
||||
@ -1051,7 +1059,11 @@ void CrossPointWebServer::handlePostSettings() {
|
||||
case SettingType::ENUM: {
|
||||
const int value = doc[setting.key].as<int>();
|
||||
if (value >= 0 && value < static_cast<int>(setting.enumValues.size())) {
|
||||
SETTINGS.*(setting.valuePtr) = static_cast<uint8_t>(value);
|
||||
if (setting.valueSetter) {
|
||||
setting.valueSetter(static_cast<uint8_t>(value));
|
||||
} else {
|
||||
SETTINGS.*(setting.valuePtr) = static_cast<uint8_t>(value);
|
||||
}
|
||||
updatedCount++;
|
||||
}
|
||||
break;
|
||||
@ -1060,7 +1072,11 @@ void CrossPointWebServer::handlePostSettings() {
|
||||
case SettingType::VALUE: {
|
||||
const int value = doc[setting.key].as<int>();
|
||||
if (value >= setting.valueRange.min && value <= setting.valueRange.max) {
|
||||
SETTINGS.*(setting.valuePtr) = static_cast<uint8_t>(value);
|
||||
if (setting.valueSetter) {
|
||||
setting.valueSetter(static_cast<uint8_t>(value));
|
||||
} else {
|
||||
SETTINGS.*(setting.valuePtr) = static_cast<uint8_t>(value);
|
||||
}
|
||||
updatedCount++;
|
||||
}
|
||||
break;
|
||||
@ -1069,8 +1085,12 @@ void CrossPointWebServer::handlePostSettings() {
|
||||
case SettingType::STRING: {
|
||||
const char* value = doc[setting.key].as<const char*>();
|
||||
if (value != nullptr) {
|
||||
strncpy(setting.stringPtr, value, setting.stringMaxLen);
|
||||
setting.stringPtr[setting.stringMaxLen] = '\0';
|
||||
if (setting.stringSetter) {
|
||||
setting.stringSetter(value);
|
||||
} else {
|
||||
strncpy(setting.stringPtr, value, setting.stringMaxLen);
|
||||
setting.stringPtr[setting.stringMaxLen] = '\0';
|
||||
}
|
||||
updatedCount++;
|
||||
}
|
||||
break;
|
||||
@ -1083,6 +1103,7 @@ void CrossPointWebServer::handlePostSettings() {
|
||||
|
||||
if (updatedCount > 0) {
|
||||
SETTINGS.saveToFile();
|
||||
KOREADER_STORE.saveToFile();
|
||||
Serial.printf("[%lu] [WEB] Updated %d settings and saved to file\n", millis(), updatedCount);
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user