Compare commits

...

5 Commits

Author SHA1 Message Date
CaptainFrito
5f1e4b785d
Merge af7c0fef9d into d403044f76 2026-02-03 22:29:51 +07:00
Aaron Cunliffe
d403044f76
fix: Increase network SSID display length (#670)
Some checks are pending
CI / build (push) Waiting to run
## Rationale 

I have 2 wifi access points with almost identical names, just one has
`_EXT` at the end of it. With the current display limit of 13 characters
before adding ellipsis, I can't tell which is which.

Before device screenshot with masked SSIDs:
<img
src="https://github.com/user-attachments/assets/3c5cbbaa-b2f6-412f-b5a8-6278963bd0f2"
width="300">


## Summary

Adjusted displayed length from 13 characters to 30 in the Wifi selection
screen - I've left some space for potential proportional font changes in
the future

After image with masked SSIDs:
<img
src="https://github.com/user-attachments/assets/c5f0712b-bbd3-4eec-9820-4693fae90c9f"
width="300">

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**< NO >**_
2026-02-03 18:24:23 +03:00
CaptainFrito
af7c0fef9d Lyra as default theme 2026-02-03 19:47:16 +07:00
CaptainFrito
e5ac4354a4 Removed useless bools in components 2026-02-03 19:19:39 +07:00
Aaron Cunliffe
f67c544e16
fix: webserver folder creation regex change (#653)
Some checks failed
CI / build (push) Has been cancelled
## Summary

Resolves #562 

Implements regex change to support valid characters discussed by
@daveallie in issue
[here](https://github.com/crosspoint-reader/crosspoint-reader/issues/562#issuecomment-3830809156).

Also rejects `.` and `..` as folder names which are invalid in FAT32 and
exFAT filesystems

## Additional Context
- Unsure on the wording for the alert, it feels overly explicit, but
that might be a good thing. Happy to change.

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**< PARTIALLY >**_
2026-02-02 21:27:02 +11:00
13 changed files with 40 additions and 38 deletions

View File

@ -153,7 +153,7 @@ Click **File Manager** to access file management features.
1. Click the **+ Add** button in the top-right corner 1. Click the **+ Add** button in the top-right corner
2. Select **New Folder** from the dropdown menu 2. Select **New Folder** from the dropdown menu
3. Enter a folder name (letters, numbers, underscores, and hyphens only) 3. Enter a folder name (must not contain characters \" * : < > ? / \\ | and must not be . or ..)
4. Click **Create Folder** 4. Click **Create Folder**
This is useful for organizing your ebooks by genre, author, or series. This is useful for organizing your ebooks by genre, author, or series.

@ -1 +1 @@
Subproject commit bd4e6707503ab9c97d13ee0d8f8c69e9ff03cd12 Subproject commit c8ce3949b3368329c290ca1858e65dc3416fc591

View File

@ -141,7 +141,7 @@ class CrossPointSettings {
// Long-press chapter skip on side buttons // Long-press chapter skip on side buttons
uint8_t longPressChapterSkip = 1; uint8_t longPressChapterSkip = 1;
// UI Theme // UI Theme
uint8_t uiTheme = CLASSIC; uint8_t uiTheme = LYRA;
~CrossPointSettings() = default; ~CrossPointSettings() = default;

View File

@ -277,7 +277,7 @@ void HomeActivity::render() {
pageHeight - (metrics.headerHeight + metrics.homeTopPadding + metrics.verticalSpacing * 2 + pageHeight - (metrics.headerHeight + metrics.homeTopPadding + metrics.verticalSpacing * 2 +
metrics.buttonHintsHeight)}, metrics.buttonHintsHeight)},
static_cast<int>(menuItems.size()), selectorIndex - recentBooks.size(), static_cast<int>(menuItems.size()), selectorIndex - recentBooks.size(),
[&menuItems](int index) { return std::string(menuItems[index]); }, false, nullptr); [&menuItems](int index) { return std::string(menuItems[index]); }, nullptr);
const auto labels = mappedInput.mapLabels("", "Select", "Up", "Down"); const auto labels = mappedInput.mapLabels("", "Select", "Up", "Down");
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);

View File

@ -201,7 +201,7 @@ void MyLibraryActivity::render() const {
} else { } else {
GUI.drawList( GUI.drawList(
renderer, Rect{0, contentTop, pageWidth, contentHeight}, files.size(), selectorIndex, renderer, Rect{0, contentTop, pageWidth, contentHeight}, files.size(), selectorIndex,
[this](int index) { return files[index]; }, false, nullptr, false, nullptr, false, nullptr); [this](int index) { return files[index]; }, nullptr, nullptr, nullptr);
} }
// Help text // Help text

View File

@ -137,8 +137,8 @@ void RecentBooksActivity::render() const {
} else { } else {
GUI.drawList( GUI.drawList(
renderer, Rect{0, contentTop, pageWidth, contentHeight}, recentBooks.size(), selectorIndex, renderer, Rect{0, contentTop, pageWidth, contentHeight}, recentBooks.size(), selectorIndex,
[this](int index) { return recentBooks[index].title; }, true, [this](int index) { return recentBooks[index].title; }, [this](int index) { return recentBooks[index].author; },
[this](int index) { return recentBooks[index].author; }, false, nullptr, false, nullptr); nullptr, nullptr);
} }
// Help text // Help text

View File

@ -547,8 +547,8 @@ void WifiSelectionActivity::renderNetworkList() const {
// Draw network name (truncate if too long) // Draw network name (truncate if too long)
std::string displayName = network.ssid; std::string displayName = network.ssid;
if (displayName.length() > 16) { if (displayName.length() > 33) {
displayName.replace(13, displayName.length() - 13, "..."); displayName.replace(30, displayName.length() - 30, "...");
} }
renderer.drawText(UI_10_FONT_ID, 20, networkY, displayName.c_str()); renderer.drawText(UI_10_FONT_ID, 20, networkY, displayName.c_str());

View File

@ -277,7 +277,7 @@ void SettingsActivity::render() const {
pageHeight - (metrics.topPadding + metrics.headerHeight + metrics.tabBarHeight + metrics.buttonHintsHeight + pageHeight - (metrics.topPadding + metrics.headerHeight + metrics.tabBarHeight + metrics.buttonHintsHeight +
metrics.verticalSpacing * 2)}, metrics.verticalSpacing * 2)},
settingsCount, selectedSettingIndex - 1, [this](int index) { return std::string(settingsList[index].name); }, settingsCount, selectedSettingIndex - 1, [this](int index) { return std::string(settingsList[index].name); },
false, nullptr, false, nullptr, true, nullptr, nullptr,
[this](int i) { [this](int i) {
const auto& setting = settingsList[i]; const auto& setting = settingsList[i];
std::string valueText = ""; std::string valueText = "";

View File

@ -158,11 +158,12 @@ void BaseTheme::drawSideButtonHints(const GfxRenderer& renderer, const char* top
} }
void BaseTheme::drawList(const GfxRenderer& renderer, Rect rect, int itemCount, int selectedIndex, void BaseTheme::drawList(const GfxRenderer& renderer, Rect rect, int itemCount, int selectedIndex,
const std::function<std::string(int index)>& rowTitle, bool hasSubtitle, const std::function<std::string(int index)>& rowTitle,
const std::function<std::string(int index)>& rowSubtitle, bool hasIcon, const std::function<std::string(int index)>& rowSubtitle,
const std::function<std::string(int index)>& rowIcon, bool hasValue, const std::function<std::string(int index)>& rowIcon,
const std::function<std::string(int index)>& rowValue) const { const std::function<std::string(int index)>& rowValue) const {
int rowHeight = hasSubtitle ? BaseMetrics::values.listWithSubtitleRowHeight : BaseMetrics::values.listRowHeight; int rowHeight =
(rowSubtitle != nullptr) ? BaseMetrics::values.listWithSubtitleRowHeight : BaseMetrics::values.listRowHeight;
int pageItems = rect.height / rowHeight; int pageItems = rect.height / rowHeight;
const int totalPages = (itemCount + pageItems - 1) / pageItems; const int totalPages = (itemCount + pageItems - 1) / pageItems;
@ -200,15 +201,15 @@ void BaseTheme::drawList(const GfxRenderer& renderer, Rect rect, int itemCount,
const auto pageStartIndex = selectedIndex / pageItems * pageItems; const auto pageStartIndex = selectedIndex / pageItems * pageItems;
for (int i = pageStartIndex; i < itemCount && i < pageStartIndex + pageItems; i++) { for (int i = pageStartIndex; i < itemCount && i < pageStartIndex + pageItems; i++) {
const int itemY = rect.y + (i % pageItems) * rowHeight; const int itemY = rect.y + (i % pageItems) * rowHeight;
int textWidth = contentWidth - BaseMetrics::values.contentSidePadding * 2 - (hasValue ? 60 : 0); int textWidth = contentWidth - BaseMetrics::values.contentSidePadding * 2 - (rowValue != nullptr ? 60 : 0);
// Draw name // Draw name
auto itemName = rowTitle(i); auto itemName = rowTitle(i);
auto font = hasSubtitle ? UI_12_FONT_ID : UI_10_FONT_ID; auto font = (rowSubtitle != nullptr) ? UI_12_FONT_ID : UI_10_FONT_ID;
auto item = renderer.truncatedText(font, itemName.c_str(), textWidth); auto item = renderer.truncatedText(font, itemName.c_str(), textWidth);
renderer.drawText(font, rect.x + BaseMetrics::values.contentSidePadding, itemY, item.c_str(), i != selectedIndex); renderer.drawText(font, rect.x + BaseMetrics::values.contentSidePadding, itemY, item.c_str(), i != selectedIndex);
if (hasSubtitle) { if (rowSubtitle != nullptr) {
// Draw subtitle // Draw subtitle
std::string subtitleText = rowSubtitle(i); std::string subtitleText = rowSubtitle(i);
auto subtitle = renderer.truncatedText(UI_10_FONT_ID, subtitleText.c_str(), textWidth); auto subtitle = renderer.truncatedText(UI_10_FONT_ID, subtitleText.c_str(), textWidth);
@ -216,7 +217,7 @@ void BaseTheme::drawList(const GfxRenderer& renderer, Rect rect, int itemCount,
i != selectedIndex); i != selectedIndex);
} }
if (hasValue) { if (rowValue != nullptr) {
// Draw value // Draw value
std::string valueText = rowValue(i); std::string valueText = rowValue(i);
const auto valueTextWidth = renderer.getTextWidth(UI_10_FONT_ID, valueText.c_str()); const auto valueTextWidth = renderer.getTextWidth(UI_10_FONT_ID, valueText.c_str());
@ -570,7 +571,7 @@ void BaseTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, const std:
} }
void BaseTheme::drawButtonMenu(GfxRenderer& renderer, Rect rect, int buttonCount, int selectedIndex, void BaseTheme::drawButtonMenu(GfxRenderer& renderer, Rect rect, int buttonCount, int selectedIndex,
const std::function<std::string(int index)>& buttonLabel, bool hasIcon, const std::function<std::string(int index)>& buttonLabel,
const std::function<std::string(int index)>& rowIcon) const { const std::function<std::string(int index)>& rowIcon) const {
for (int i = 0; i < buttonCount; ++i) { for (int i = 0; i < buttonCount; ++i) {
const int tileY = BaseMetrics::values.verticalSpacing + rect.y + const int tileY = BaseMetrics::values.verticalSpacing + rect.y +

View File

@ -98,9 +98,9 @@ class BaseTheme {
const char* btn4) const; const char* btn4) const;
virtual void drawSideButtonHints(const GfxRenderer& renderer, const char* topBtn, const char* bottomBtn) const; virtual void drawSideButtonHints(const GfxRenderer& renderer, const char* topBtn, const char* bottomBtn) const;
virtual void drawList(const GfxRenderer& renderer, Rect rect, int itemCount, int selectedIndex, virtual void drawList(const GfxRenderer& renderer, Rect rect, int itemCount, int selectedIndex,
const std::function<std::string(int index)>& rowTitle, bool hasSubtitle, const std::function<std::string(int index)>& rowTitle,
const std::function<std::string(int index)>& rowSubtitle, bool hasIcon, const std::function<std::string(int index)>& rowSubtitle,
const std::function<std::string(int index)>& rowIcon, bool hasValue, const std::function<std::string(int index)>& rowIcon,
const std::function<std::string(int index)>& rowValue) const; const std::function<std::string(int index)>& rowValue) const;
virtual void drawHeader(const GfxRenderer& renderer, Rect rect, const char* title) const; virtual void drawHeader(const GfxRenderer& renderer, Rect rect, const char* title) const;
@ -110,7 +110,7 @@ class BaseTheme {
const int selectorIndex, bool& coverRendered, bool& coverBufferStored, const int selectorIndex, bool& coverRendered, bool& coverBufferStored,
bool& bufferRestored, std::function<bool()> storeCoverBuffer) const; bool& bufferRestored, std::function<bool()> storeCoverBuffer) const;
virtual void drawButtonMenu(GfxRenderer& renderer, Rect rect, int buttonCount, int selectedIndex, virtual void drawButtonMenu(GfxRenderer& renderer, Rect rect, int buttonCount, int selectedIndex,
const std::function<std::string(int index)>& buttonLabel, bool hasIcon, const std::function<std::string(int index)>& buttonLabel,
const std::function<std::string(int index)>& rowIcon) const; const std::function<std::string(int index)>& rowIcon) const;
virtual Rect drawPopup(const GfxRenderer& renderer, const char* message) const; virtual Rect drawPopup(const GfxRenderer& renderer, const char* message) const;
virtual void fillPopupProgress(const GfxRenderer& renderer, const Rect& layout, const int progress) const; virtual void fillPopupProgress(const GfxRenderer& renderer, const Rect& layout, const int progress) const;

View File

@ -115,11 +115,12 @@ void LyraTheme::drawTabBar(const GfxRenderer& renderer, Rect rect, const std::ve
} }
void LyraTheme::drawList(const GfxRenderer& renderer, Rect rect, int itemCount, int selectedIndex, void LyraTheme::drawList(const GfxRenderer& renderer, Rect rect, int itemCount, int selectedIndex,
const std::function<std::string(int index)>& rowTitle, bool hasSubtitle, const std::function<std::string(int index)>& rowTitle,
const std::function<std::string(int index)>& rowSubtitle, bool hasIcon, const std::function<std::string(int index)>& rowSubtitle,
const std::function<std::string(int index)>& rowIcon, bool hasValue, const std::function<std::string(int index)>& rowIcon,
const std::function<std::string(int index)>& rowValue) const { const std::function<std::string(int index)>& rowValue) const {
int rowHeight = hasSubtitle ? LyraMetrics::values.listWithSubtitleRowHeight : LyraMetrics::values.listRowHeight; int rowHeight =
(rowSubtitle != nullptr) ? LyraMetrics::values.listWithSubtitleRowHeight : LyraMetrics::values.listRowHeight;
int pageItems = rect.height / rowHeight; int pageItems = rect.height / rowHeight;
const int totalPages = (itemCount + pageItems - 1) / pageItems; const int totalPages = (itemCount + pageItems - 1) / pageItems;
@ -153,13 +154,13 @@ void LyraTheme::drawList(const GfxRenderer& renderer, Rect rect, int itemCount,
// Draw name // Draw name
int textWidth = contentWidth - LyraMetrics::values.contentSidePadding * 2 - hPaddingInSelection * 2 - int textWidth = contentWidth - LyraMetrics::values.contentSidePadding * 2 - hPaddingInSelection * 2 -
(hasValue ? 60 : 0); // TODO truncate according to value width? (rowValue != nullptr ? 60 : 0); // TODO truncate according to value width?
auto itemName = rowTitle(i); auto itemName = rowTitle(i);
auto item = renderer.truncatedText(UI_10_FONT_ID, itemName.c_str(), textWidth); auto item = renderer.truncatedText(UI_10_FONT_ID, itemName.c_str(), textWidth);
renderer.drawText(UI_10_FONT_ID, rect.x + LyraMetrics::values.contentSidePadding + hPaddingInSelection * 2, renderer.drawText(UI_10_FONT_ID, rect.x + LyraMetrics::values.contentSidePadding + hPaddingInSelection * 2,
itemY + 6, item.c_str(), true); itemY + 6, item.c_str(), true);
if (hasSubtitle) { if (rowSubtitle != nullptr) {
// Draw subtitle // Draw subtitle
std::string subtitleText = rowSubtitle(i); std::string subtitleText = rowSubtitle(i);
auto subtitle = renderer.truncatedText(SMALL_FONT_ID, subtitleText.c_str(), textWidth); auto subtitle = renderer.truncatedText(SMALL_FONT_ID, subtitleText.c_str(), textWidth);
@ -167,7 +168,7 @@ void LyraTheme::drawList(const GfxRenderer& renderer, Rect rect, int itemCount,
itemY + 30, subtitle.c_str(), true); itemY + 30, subtitle.c_str(), true);
} }
if (hasValue) { if (rowValue != nullptr) {
// Draw value // Draw value
std::string valueText = rowValue(i); std::string valueText = rowValue(i);
if (!valueText.empty()) { if (!valueText.empty()) {
@ -329,7 +330,7 @@ void LyraTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, const std:
} }
void LyraTheme::drawButtonMenu(GfxRenderer& renderer, Rect rect, int buttonCount, int selectedIndex, void LyraTheme::drawButtonMenu(GfxRenderer& renderer, Rect rect, int buttonCount, int selectedIndex,
const std::function<std::string(int index)>& buttonLabel, bool hasIcon, const std::function<std::string(int index)>& buttonLabel,
const std::function<std::string(int index)>& rowIcon) const { const std::function<std::string(int index)>& rowIcon) const {
for (int i = 0; i < buttonCount; ++i) { for (int i = 0; i < buttonCount; ++i) {
int tileWidth = (rect.width - LyraMetrics::values.contentSidePadding * 2 - LyraMetrics::values.menuSpacing) / 2; int tileWidth = (rect.width - LyraMetrics::values.contentSidePadding * 2 - LyraMetrics::values.menuSpacing) / 2;

View File

@ -41,15 +41,15 @@ class LyraTheme : public BaseTheme {
void drawTabBar(const GfxRenderer& renderer, Rect rect, const std::vector<TabInfo>& tabs, void drawTabBar(const GfxRenderer& renderer, Rect rect, const std::vector<TabInfo>& tabs,
bool selected) const override; bool selected) const override;
void drawList(const GfxRenderer& renderer, Rect rect, int itemCount, int selectedIndex, void drawList(const GfxRenderer& renderer, Rect rect, int itemCount, int selectedIndex,
const std::function<std::string(int index)>& rowTitle, bool hasSubtitle, const std::function<std::string(int index)>& rowTitle,
const std::function<std::string(int index)>& rowSubtitle, bool hasIcon, const std::function<std::string(int index)>& rowSubtitle,
const std::function<std::string(int index)>& rowIcon, bool hasValue, const std::function<std::string(int index)>& rowIcon,
const std::function<std::string(int index)>& rowValue) const override; const std::function<std::string(int index)>& rowValue) const override;
void drawButtonHints(GfxRenderer& renderer, const char* btn1, const char* btn2, const char* btn3, void drawButtonHints(GfxRenderer& renderer, const char* btn1, const char* btn2, const char* btn3,
const char* btn4) const override; const char* btn4) const override;
void drawSideButtonHints(const GfxRenderer& renderer, const char* topBtn, const char* bottomBtn) const override; void drawSideButtonHints(const GfxRenderer& renderer, const char* topBtn, const char* bottomBtn) const override;
void drawButtonMenu(GfxRenderer& renderer, Rect rect, int buttonCount, int selectedIndex, void drawButtonMenu(GfxRenderer& renderer, Rect rect, int buttonCount, int selectedIndex,
const std::function<std::string(int index)>& buttonLabel, bool hasIcon, const std::function<std::string(int index)>& buttonLabel,
const std::function<std::string(int index)>& rowIcon) const override; const std::function<std::string(int index)>& rowIcon) const override;
void drawRecentBookCover(GfxRenderer& renderer, Rect rect, const std::vector<RecentBook>& recentBooks, void drawRecentBookCover(GfxRenderer& renderer, Rect rect, const std::vector<RecentBook>& recentBooks,
const int selectorIndex, bool& coverRendered, bool& coverBufferStored, bool& bufferRestored, const int selectorIndex, bool& coverRendered, bool& coverBufferStored, bool& bufferRestored,

View File

@ -1146,10 +1146,10 @@ function retryAllFailedUploads() {
return; return;
} }
// Validate folder name (no special characters except underscore and hyphen) // Validate folder name
const validName = /^[a-zA-Z0-9_\-]+$/.test(folderName); const validName = /^(?!\.{1,2}$)[^"*:<>?\/\\|]+$/.test(folderName);
if (!validName) { if (!validName) {
alert('Folder name can only contain letters, numbers, underscores, and hyphens.'); alert('Folder name cannot contain \" * : < > ? / \\ | and must not be . or ..');
return; return;
} }