Simplify Widgets tab: state button instead of checkbox, add drag-free up/down reordering

Drops the checkbox/tooltip/hint-paragraph combo for a single click-to-toggle
Active/Disabled badge (same pattern as EventsTab's Active/Paused). Widget
order is now admin-sortable via up/down arrows and persisted as
widgetOrder, with Sidebar.svelte rendering widgets in that exact sequence
instead of a fixed hardcoded order.
This commit is contained in:
Claude
2026-07-26 19:05:24 +00:00
parent 9ca5bf566d
commit 9e13e95f35
9 changed files with 109 additions and 82 deletions
+7 -4
View File
@@ -62,10 +62,13 @@ export async function registerPublicRoutes(app: FastifyInstance) {
return categories.filter((c) => !c.isPrivate);
});
// Per-widget sidebar visibility — see the admin panel's consolidated "Widgets" tab.
// Each widget keeps polling/config regardless of this; it only gates whether the
// sidebar renders it at all.
app.get('/api/widgets', async () => settingsDb.getSettings().widgets);
// Per-widget enable flags + display order — see the admin panel's consolidated
// "Widgets" tab. Weather/Stocks/PoE2's backend pollers are also gated on these
// flags (see scheduler.ts); Sidebar.svelte renders in exactly this order.
app.get('/api/widgets', async () => {
const { widgets, widgetOrder } = settingsDb.getSettings();
return { ...widgets, order: widgetOrder };
});
// Sidebar widgets — see WeatherTab/StocksTab/BookmarksTab in the admin panel.
app.get('/api/weather', async () => settingsDb.getSettings().weather);
+6
View File
@@ -188,6 +188,7 @@ export function migrate() {
widget_stocks_enabled INTEGER NOT NULL DEFAULT 1,
widget_bookmarks_enabled INTEGER NOT NULL DEFAULT 1,
widget_poe2_enabled INTEGER NOT NULL DEFAULT 1,
widget_order TEXT NOT NULL DEFAULT '["weather","stocks","poe2","bookmarks"]', -- JSON array, admin-sortable via the Widgets tab
weather_location_name TEXT,
weather_latitude REAL,
weather_longitude REAL,
@@ -363,6 +364,11 @@ export function migrate() {
db.exec('ALTER TABLE global_settings ADD COLUMN widget_bookmarks_enabled INTEGER NOT NULL DEFAULT 1');
db.exec('ALTER TABLE global_settings ADD COLUMN widget_poe2_enabled INTEGER NOT NULL DEFAULT 1');
}
if (!hasColumn('global_settings', 'widget_order')) {
db.exec(
`ALTER TABLE global_settings ADD COLUMN widget_order TEXT NOT NULL DEFAULT '["weather","stocks","poe2","bookmarks"]'`
);
}
// Seed a handful of sensible default tickers so the Stocks widget isn't empty on a
// fresh install — the admin can remove/replace any of them via the Stocks tab.
+3
View File
@@ -21,6 +21,7 @@ function rowToSettings(row: any): GlobalSettings {
bookmarks: !!row.widget_bookmarks_enabled,
poe2: !!row.widget_poe2_enabled
},
widgetOrder: JSON.parse(row.widget_order),
retention: {
publishedArticleMaxAgeDays: row.published_article_max_age_days,
rawItemMaxAgeDays: row.raw_item_max_age_days,
@@ -79,6 +80,7 @@ export function updateSettings(patch: Partial<GlobalSettings>): GlobalSettings {
nitter_media_mode=$nitter_media_mode, fxtwitter_base_url=$fxtwitter_base_url, telegram_media_mode=$telegram_media_mode,
widget_weather_enabled=$widget_weather_enabled, widget_stocks_enabled=$widget_stocks_enabled,
widget_bookmarks_enabled=$widget_bookmarks_enabled, widget_poe2_enabled=$widget_poe2_enabled,
widget_order=$widget_order,
published_article_max_age_days=$published_article_max_age_days, raw_item_max_age_days=$raw_item_max_age_days,
storage_cap_enabled=$storage_cap_enabled, storage_cap_value=$storage_cap_value, storage_cap_unit=$storage_cap_unit,
weather_location_name=$weather_location_name, weather_latitude=$weather_latitude, weather_longitude=$weather_longitude,
@@ -104,6 +106,7 @@ export function updateSettings(patch: Partial<GlobalSettings>): GlobalSettings {
$widget_stocks_enabled: merged.widgets.stocks ? 1 : 0,
$widget_bookmarks_enabled: merged.widgets.bookmarks ? 1 : 0,
$widget_poe2_enabled: merged.widgets.poe2 ? 1 : 0,
$widget_order: JSON.stringify(merged.widgetOrder),
$published_article_max_age_days: merged.retention.publishedArticleMaxAgeDays,
$raw_item_max_age_days: merged.retention.rawItemMaxAgeDays,
$storage_cap_enabled: merged.retention.storageCapEnabled ? 1 : 0,
+3 -1
View File
@@ -281,13 +281,15 @@ export interface GlobalSettings {
fxtwitterBaseUrl: string;
/** How Telegram message media (attached photos/videos, channel avatars) is served — see pipeline/publish.ts's resolveTelegramMedia. No "direct" option: Telegram has no public hotlinkable media URL, bytes only come from the authenticated MTProto session. */
telegramMediaMode: 'self-host' | 'proxy';
/** Per-widget sidebar visibility — see admin/settings' consolidated "Widgets" tab. Each widget keeps polling/config regardless (disabling doesn't pause its poller), this only gates whether GET /api/widgets tells the sidebar to render it. */
/** Per-widget enable flags — see admin/settings' consolidated "Widgets" tab. Weather/Stocks/PoE2's backend pollers (scheduler.ts) are gated on these too, not just sidebar visibility; Bookmarks has no poller so its flag only affects the sidebar. */
widgets: {
weather: boolean;
stocks: boolean;
bookmarks: boolean;
poe2: boolean;
};
/** Sidebar widget display order, admin-sortable via the Widgets tab's up/down arrows — mirrored exactly by Sidebar.svelte. */
widgetOrder: ('weather' | 'stocks' | 'bookmarks' | 'poe2')[];
retention: {
publishedArticleMaxAgeDays: number | null;
rawItemMaxAgeDays: number | null;