From da080c22d3f4ae20f641a18f6d50378c7b0d9364 Mon Sep 17 00:00:00 2001 From: maziggy Date: Thu, 9 Apr 2026 09:52:56 +0200 Subject: [PATCH] Improve settings menu layout - 2 --- frontend/src/components/Card.tsx | 11 +- frontend/src/pages/SettingsPage.tsx | 288 +++++++++++++--- ...{index-BKDWdPbe.css => index-Caj-77TJ.css} | 2 +- .../{index-BUj9NFXp.js => index-OM4EoHsE.js} | 326 +++++++++--------- static/index.html | 4 +- 5 files changed, 406 insertions(+), 225 deletions(-) rename static/assets/{index-BKDWdPbe.css => index-Caj-77TJ.css} (61%) rename static/assets/{index-BUj9NFXp.js => index-OM4EoHsE.js} (75%) diff --git a/frontend/src/components/Card.tsx b/frontend/src/components/Card.tsx index 4aa8ee67f..d2a50ca85 100644 --- a/frontend/src/components/Card.tsx +++ b/frontend/src/components/Card.tsx @@ -14,10 +14,15 @@ interface CardProps extends HTMLAttributes { className?: string; onClick?: (e: MouseEvent) => void; onContextMenu?: (e: MouseEvent) => void; +} + +interface CardSectionProps { + children: ReactNode; + className?: string; dense?: boolean; } -export function Card({ children, className = '', onClick, onContextMenu, dense: _dense, ...rest }: CardProps) { +export function Card({ children, className = '', onClick, onContextMenu, ...rest }: CardProps) { return (
(null); const [editingTemplate, setEditingTemplate] = useState(null); + const [templateFilter, setTemplateFilter] = useState(''); + const [settingsSearch, setSettingsSearch] = useState(''); const [showLogViewer, setShowLogViewer] = useState(false); const [defaultView, setDefaultViewState] = useState(getDefaultView()); @@ -964,29 +966,156 @@ export function SettingsPage() { ); } + // Cross-tab settings search index. Keep labels short — matched against user's query. + // anchor is the DOM id attached to the target card (also wired to scrollIntoView). + const searchIndex: Array<{ + label: string; + tab: TabType; + subTab?: UsersSubTab; + keywords: string; + anchor: string; + }> = [ + // General + { label: t('settings.general'), tab: 'general', keywords: 'language date time format printer model printers cards', anchor: 'card-general' }, + { label: t('settings.appearance'), tab: 'general', keywords: 'theme dark light mode colors', anchor: 'card-appearance' }, + { label: t('settings.archiveSettings'), tab: 'general', keywords: 'archive auto save thumbnails captures', anchor: 'card-archive' }, + { label: t('settings.camera'), tab: 'general', keywords: 'camera external video stream', anchor: 'card-camera' }, + { label: t('settings.costTracking'), tab: 'general', keywords: 'currency filament cost energy kwh price', anchor: 'card-cost' }, + { label: t('settings.fileManager'), tab: 'general', keywords: 'file manager archive mode disk warning storage', anchor: 'card-filemanager' }, + { label: t('settings.updates'), tab: 'general', keywords: 'updates version firmware beta check', anchor: 'card-updates' }, + { label: t('settings.dataManagement'), tab: 'general', keywords: 'data reset clear logs notifications preferences', anchor: 'card-data' }, + // Smart Plugs + { label: t('settings.smartPlugs'), tab: 'plugs', keywords: 'smart plug energy power automation tapo kasa tplink shelly', anchor: 'card-plugs' }, + // Notifications + { label: t('settings.providers'), tab: 'notifications', keywords: 'telegram discord email notification providers webhook', anchor: 'card-providers' }, + { label: t('settings.messageTemplates'), tab: 'notifications', keywords: 'message templates notification text edit', anchor: 'card-templates' }, + // Queue / Workflow + { label: t('settings.defaultPrintOptions', 'Default Print Options'), tab: 'queue', keywords: 'print bed leveling flow calibration vibration first layer timelapse', anchor: 'card-print-options' }, + { label: t('settings.staggeredStart', 'Staggered Start'), tab: 'queue', keywords: 'staggered batch delay start queue group', anchor: 'card-staggered' }, + { label: t('settings.plateClear', 'Plate-Clear Confirmation'), tab: 'queue', keywords: 'plate clear confirm auto queue', anchor: 'card-plate' }, + { label: t('settings.gcodeInjection', 'G-code Injection'), tab: 'queue', keywords: 'gcode injection start end autoprint farmloop swapmod autoclear printflow', anchor: 'card-gcode' }, + { label: t('settings.queueDrying'), tab: 'queue', keywords: 'drying presets temperature time humidity ams', anchor: 'card-drying' }, + // Filament + { label: t('settings.filamentChecks'), tab: 'filament', keywords: 'filament check warning runout remaining', anchor: 'card-filamentchecks' }, + { label: t('settings.printModal'), tab: 'filament', keywords: 'print modal custom mapping', anchor: 'card-printmodal' }, + { label: t('settings.amsDisplayThresholds'), tab: 'filament', keywords: 'ams humidity temperature threshold history retention', anchor: 'card-amsthresholds' }, + // Network + { label: t('settings.externalUrl'), tab: 'network', keywords: 'external url reverse proxy public notification link', anchor: 'card-externalurl' }, + { label: t('settings.ftpRetry'), tab: 'network', keywords: 'ftp retry upload retries backoff', anchor: 'card-ftpretry' }, + { label: t('settings.homeAssistant'), tab: 'network', keywords: 'home assistant ha hass mqtt integration', anchor: 'card-ha' }, + { label: t('settings.mqttPublishing'), tab: 'network', keywords: 'mqtt publish broker topic', anchor: 'card-mqtt' }, + { label: t('settings.prometheusMetrics'), tab: 'network', keywords: 'prometheus metrics grafana monitoring bearer token', anchor: 'card-prometheus' }, + // API Keys + { label: t('settings.createNewApiKey'), tab: 'apikeys', keywords: 'api key create permission scope', anchor: 'card-createapi' }, + { label: t('settings.webhookEndpoints'), tab: 'apikeys', keywords: 'webhook endpoint post http', anchor: 'card-webhooks' }, + { label: t('settings.apiBrowser'), tab: 'apikeys', keywords: 'api browser endpoint documentation test', anchor: 'card-apibrowser' }, + // Virtual Printer + { label: t('settings.tabs.virtualPrinter'), tab: 'virtual-printer', keywords: 'virtual printer proxy archive slicer bambustudio orcaslicer ip bind', anchor: 'card-vp' }, + // Users / Auth + { label: t('settings.currentUser'), tab: 'users', subTab: 'users', keywords: 'current user profile password change', anchor: 'card-currentuser' }, + { label: t('settings.users'), tab: 'users', subTab: 'users', keywords: 'users accounts list', anchor: 'card-users' }, + { label: t('settings.groups'), tab: 'users', subTab: 'users', keywords: 'groups roles permissions administrators operators viewers', anchor: 'card-groups' }, + { label: t('settings.email.smtpSettings', 'SMTP Configuration'), tab: 'users', subTab: 'email', keywords: 'smtp email send server port password auth starttls ssl', anchor: 'card-smtp' }, + { label: t('settings.ldap.title', 'LDAP Authentication'), tab: 'users', subTab: 'ldap', keywords: 'ldap active directory ad authentication bind dn search base group mapping', anchor: 'card-ldap' }, + // Backup + { label: t('settings.tabs.backup'), tab: 'backup', keywords: 'backup github restore download cloud sync profiles archives', anchor: 'card-backup' }, + ]; + + const searchQuery = settingsSearch.trim().toLowerCase(); + const searchResults = searchQuery + ? searchIndex.filter( + e => + e.label.toLowerCase().includes(searchQuery) || + e.keywords.toLowerCase().includes(searchQuery) + ).slice(0, 8) + : []; + + const jumpToSetting = (entry: typeof searchIndex[number]) => { + handleTabChange(entry.tab); + if (entry.subTab) { + setUsersSubTab(entry.subTab); + } + setSettingsSearch(''); + // Scroll to the card after the tab has rendered + setTimeout(() => { + const el = document.getElementById(entry.anchor); + if (el) { + el.scrollIntoView({ behavior: 'smooth', block: 'start' }); + el.classList.add('ring-2', 'ring-bambu-green'); + setTimeout(() => el.classList.remove('ring-2', 'ring-bambu-green'), 1500); + } + }, 50); + }; + return (
-
-

{t('settings.title')}

-

{t('settings.configureBambuddy')}

+
+
+

{t('settings.title')}

+

{t('settings.configureBambuddy')}

+
+ {/* Cross-tab search */} +
+ + setSettingsSearch(e.target.value)} + placeholder={t('settings.searchPlaceholder', 'Search settings…')} + className="w-full pl-9 pr-8 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white text-sm focus:outline-none focus:border-bambu-green" + /> + {settingsSearch && ( + + )} + {searchResults.length > 0 && ( +
+ {searchResults.map((entry) => ( + + ))} +
+ )} + {searchQuery && searchResults.length === 0 && ( +
+

{t('settings.noSearchResults', 'No matching settings.')}

+
+ )} +
- {/* Tab Navigation */} -
+ {/* Tab Navigation + content: horizontal tabs on mobile, vertical rail on lg+ */} +
+
+ +
{activeTab === 'general' && (
{/* Left Column - General Settings */}
- +

{t('settings.general')}

@@ -1270,7 +1400,7 @@ export function SettingsPage() {
- +

@@ -1385,7 +1515,7 @@ export function SettingsPage() { - +

{t('settings.archiveSettings')}

@@ -1460,7 +1590,7 @@ export function SettingsPage() { {/* Second Column - Camera, Cost, AMS & Spoolman */}
{/* Camera Settings */} - +