feat: donation banner, About tab, settings UI improvements
Some checks failed
Lint & Test / test (push) Has been cancelled
Some checks failed
Lint & Test / test (push) Has been cancelled
- Dismissible donation/open-source banner after 3+ sessions (30-day snooze) - New About tab in Settings: version, repo link, license info - Centralize project URLs (REPO_URL, DONATE_URL) in __init__.py, served via /health - Center settings tab bar, reduce tab padding for 6-tab fit - External URL save button: icon button instead of full-width text button - Remove redundant settings footer close button - Footer "Source Code" link replaced with "About" opening settings - i18n keys for en/ru/zh
This commit is contained in:
143
server/src/wled_controller/static/js/features/donation.ts
Normal file
143
server/src/wled_controller/static/js/features/donation.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
* Donation banner — shows a dismissible open-source/donation notice
|
||||
* after the user has had a few sessions with the app.
|
||||
*/
|
||||
|
||||
import { t } from '../core/i18n.ts';
|
||||
import { ICON_HEART, ICON_EXTERNAL_LINK, ICON_X, ICON_GITHUB } from '../core/icons.ts';
|
||||
|
||||
// ─── Config ─────────────────────────────────────────────────
|
||||
|
||||
/** URLs are set from the server /health response via setProjectUrls(). */
|
||||
let _donateUrl = '';
|
||||
let _repoUrl = '';
|
||||
|
||||
/** Minimum number of app opens before showing the banner. */
|
||||
const MIN_SESSIONS = 3;
|
||||
|
||||
/** "Remind me later" snooze duration (30 days). */
|
||||
const SNOOZE_MS = 30 * 24 * 60 * 60 * 1000;
|
||||
|
||||
// ─── localStorage keys ──────────────────────────────────────
|
||||
|
||||
const LS_DISMISSED = 'donation-banner-dismissed';
|
||||
const LS_SNOOZE = 'donation-banner-snooze-until';
|
||||
const LS_SESSIONS = 'donation-banner-sessions';
|
||||
|
||||
// ─── Public API ─────────────────────────────────────────────
|
||||
|
||||
/** Set project URLs from server health response. Call before initDonationBanner. */
|
||||
export function setProjectUrls(repoUrl: string, donateUrl: string): void {
|
||||
_repoUrl = repoUrl || '';
|
||||
_donateUrl = donateUrl || '';
|
||||
}
|
||||
|
||||
/** Call once on app init. Increments session count and shows banner if conditions met. */
|
||||
export function initDonationBanner(): void {
|
||||
// No URLs configured — nothing to show
|
||||
if (!_donateUrl && !_repoUrl) return;
|
||||
|
||||
const sessions = (parseInt(localStorage.getItem(LS_SESSIONS) || '0', 10) || 0) + 1;
|
||||
localStorage.setItem(LS_SESSIONS, String(sessions));
|
||||
|
||||
if (sessions < MIN_SESSIONS) return;
|
||||
if (localStorage.getItem(LS_DISMISSED) === '1') return;
|
||||
|
||||
const snoozeUntil = parseInt(localStorage.getItem(LS_SNOOZE) || '0', 10) || 0;
|
||||
if (snoozeUntil > Date.now()) return;
|
||||
|
||||
_showBanner();
|
||||
}
|
||||
|
||||
/** Dismiss forever. */
|
||||
export function dismissDonation(): void {
|
||||
localStorage.setItem(LS_DISMISSED, '1');
|
||||
_hideBanner();
|
||||
}
|
||||
|
||||
/** Snooze for 30 days. */
|
||||
export function snoozeDonation(): void {
|
||||
localStorage.setItem(LS_SNOOZE, String(Date.now() + SNOOZE_MS));
|
||||
_hideBanner();
|
||||
}
|
||||
|
||||
/** Render the About panel content in settings modal. */
|
||||
export function renderAboutPanel(): void {
|
||||
const container = document.getElementById('about-panel-content');
|
||||
if (!container) return;
|
||||
|
||||
const version = document.getElementById('version-number')?.textContent || '';
|
||||
|
||||
let links = '';
|
||||
|
||||
if (_repoUrl) {
|
||||
links += `<a href="${_repoUrl}" target="_blank" rel="noopener" class="about-link">
|
||||
${ICON_GITHUB}
|
||||
<span>${t('donation.view_source')}</span>
|
||||
${ICON_EXTERNAL_LINK}
|
||||
</a>`;
|
||||
}
|
||||
|
||||
if (_donateUrl) {
|
||||
links += `<a href="${_donateUrl}" target="_blank" rel="noopener" class="about-link about-link-donate">
|
||||
${ICON_HEART}
|
||||
<span>${t('donation.about_donate')}</span>
|
||||
${ICON_EXTERNAL_LINK}
|
||||
</a>`;
|
||||
}
|
||||
|
||||
container.innerHTML = `
|
||||
<div class="about-section">
|
||||
<div class="about-logo">${ICON_HEART}</div>
|
||||
<h3 class="about-title">${t('donation.about_title')}</h3>
|
||||
${version ? `<span class="about-version">${version}</span>` : ''}
|
||||
<p class="about-text">${t('donation.about_opensource')}</p>
|
||||
${links ? `<div class="about-links">${links}</div>` : ''}
|
||||
<p class="about-license">${t('donation.about_license')}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// ─── Internal ───────────────────────────────────────────────
|
||||
|
||||
function _showBanner(): void {
|
||||
const banner = document.getElementById('donation-banner');
|
||||
if (!banner) return;
|
||||
|
||||
let actions = '';
|
||||
|
||||
if (_donateUrl) {
|
||||
actions += `<a href="${_donateUrl}" target="_blank" rel="noopener"
|
||||
class="btn btn-icon donation-banner-action donation-banner-donate"
|
||||
title="${t('donation.support')}">
|
||||
${ICON_HEART}
|
||||
</a>`;
|
||||
}
|
||||
|
||||
if (_repoUrl) {
|
||||
actions += `<a href="${_repoUrl}" target="_blank" rel="noopener"
|
||||
class="btn btn-icon donation-banner-action"
|
||||
title="${t('donation.view_source')}">
|
||||
${ICON_GITHUB}
|
||||
</a>`;
|
||||
}
|
||||
|
||||
actions += `<button class="btn btn-icon donation-banner-action"
|
||||
onclick="snoozeDonation()" title="${t('donation.later')}">
|
||||
${ICON_X}
|
||||
</button>`;
|
||||
|
||||
banner.innerHTML = `
|
||||
<span class="donation-banner-text">
|
||||
${ICON_HEART}
|
||||
${t('donation.message')}
|
||||
</span>
|
||||
${actions}
|
||||
`;
|
||||
banner.style.display = 'flex';
|
||||
}
|
||||
|
||||
function _hideBanner(): void {
|
||||
const banner = document.getElementById('donation-banner');
|
||||
if (banner) banner.style.display = 'none';
|
||||
}
|
||||
@@ -81,6 +81,10 @@ export function switchSettingsTab(tabId: string): void {
|
||||
(window as any).initUpdateSettingsPanel();
|
||||
(window as any).loadUpdateSettings();
|
||||
}
|
||||
// Lazy-render the about panel content
|
||||
if (tabId === 'about' && typeof (window as any).renderAboutPanel === 'function') {
|
||||
(window as any).renderAboutPanel();
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Log Viewer ────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user