@@ -75,10 +75,14 @@ import TabIconModal from './TabIconModal';
7575import DeleteConfirmationDialog from './DeleteConfirmationDialog' ;
7676import AdminFormSection from './AdminFormSection' ;
7777import VersionInfoCard from './VersionInfoCard' ;
78+ import LoadingBackdrop from './LoadingBackdrop' ;
79+ import RefreshIntervalSelect from './RefreshIntervalSelect' ;
80+ import ScreensaverIntervalSlider from './ScreensaverIntervalSlider' ;
7881import GoogleAccountConnection from './GoogleAccountConnection' ;
7982import ClamValueModal from './ClamValueModal' ;
8083import SoundPicker from './SoundPicker' ;
8184import useIsMobile from '../hooks/useIsMobile.js' ;
85+ import { syncWidgetAssignments } from '../utils/assignmentSync.js' ;
8286import { stackableTableSx } from '../utils/responsiveTable.js' ;
8387import {
8488 INTERFACE_COLORS_STORAGE_KEY ,
@@ -627,24 +631,7 @@ const AdminPanel = ({ setWidgetSettings, onPluginsChanged, onTabsChanged }) => {
627631 const currentResponse = await axios . get ( `${ API_DEVICE_URL } /widget-assignments` ) ;
628632 const currentAssignments = Array . isArray ( currentResponse . data ) ? currentResponse . data : [ ] ;
629633
630- for ( const [ widgetName , desiredTabNumbers ] of Object . entries ( widgetAssignments ) ) {
631- const existing = currentAssignments . filter ( a => a . widget_name === widgetName ) ;
632- const existingTabNumbers = existing . map ( a => a . tab_number ) ;
633-
634- const toRemove = existing . filter ( a => ! desiredTabNumbers . includes ( a . tab_number ) ) ;
635- const toAdd = desiredTabNumbers . filter ( number => ! existingTabNumbers . includes ( number ) ) ;
636-
637- for ( const assignment of toRemove ) {
638- await axios . delete ( `${ API_DEVICE_URL } /widget-assignments/${ assignment . id } ` ) ;
639- }
640-
641- for ( const tabNumber of toAdd ) {
642- await axios . post ( `${ API_DEVICE_URL } /widget-assignments` , {
643- widget_name : widgetName ,
644- tabNumber : tabNumber ,
645- } ) ;
646- }
647- }
634+ await syncWidgetAssignments ( API_DEVICE_URL , widgetAssignments , currentAssignments ) ;
648635
649636 if ( onTabsChanged ) {
650637 await onTabsChanged ( ) ;
@@ -676,24 +663,7 @@ const AdminPanel = ({ setWidgetSettings, onPluginsChanged, onTabsChanged }) => {
676663 const currentResponse = await axios . get ( `${ API_DEVICE_URL } /widget-assignments` ) ;
677664 const currentAssignments = Array . isArray ( currentResponse . data ) ? currentResponse . data : [ ] ;
678665
679- for ( const [ pluginWidgetName , desiredTabNumbers ] of Object . entries ( pluginAssignments ) ) {
680- const existing = currentAssignments . filter ( a => a . widget_name === pluginWidgetName ) ;
681- const existingTabNumbers = existing . map ( a => a . tab_number ) ;
682-
683- const toRemove = existing . filter ( a => ! desiredTabNumbers . includes ( a . tab_number ) ) ;
684- const toAdd = desiredTabNumbers . filter ( number => ! existingTabNumbers . includes ( number ) ) ;
685-
686- for ( const assignment of toRemove ) {
687- await axios . delete ( `${ API_DEVICE_URL } /widget-assignments/${ assignment . id } ` ) ;
688- }
689-
690- for ( const tabNumber of toAdd ) {
691- await axios . post ( `${ API_DEVICE_URL } /widget-assignments` , {
692- widget_name : pluginWidgetName ,
693- tabNumber : tabNumber ,
694- } ) ;
695- }
696- }
666+ await syncWidgetAssignments ( API_DEVICE_URL , pluginAssignments , currentAssignments ) ;
697667
698668 if ( onTabsChanged ) {
699669 await onTabsChanged ( ) ;
@@ -1806,26 +1776,12 @@ const AdminPanel = ({ setWidgetSettings, onPluginsChanged, onTabsChanged }) => {
18061776 </ Grid >
18071777
18081778 < Grid size = { { xs : 12 , sm : 6 } } >
1809- < FormControl fullWidth size = "small" >
1810- < InputLabel id = { `${ widget } -refresh-label` } >
1811- < Box sx = { { display : 'flex' , alignItems : 'center' , gap : 1 } } >
1812- < Timer fontSize = "small" />
1813- Auto-Refresh Interval
1814- </ Box >
1815- </ InputLabel >
1816- < Select
1817- labelId = { `${ widget } -refresh-label` }
1818- value = { config . refreshInterval || 0 }
1819- onChange = { ( e ) => handleRefreshIntervalChange ( widget , e . target . value ) }
1820- label = "Auto-Refresh Interval"
1821- >
1822- { refreshIntervalOptions . map ( ( option ) => (
1823- < MenuItem key = { option . value } value = { option . value } >
1824- { option . label }
1825- </ MenuItem >
1826- ) ) }
1827- </ Select >
1828- </ FormControl >
1779+ < RefreshIntervalSelect
1780+ labelId = { `${ widget } -refresh-label` }
1781+ value = { config . refreshInterval }
1782+ onChange = { ( value ) => handleRefreshIntervalChange ( widget , value ) }
1783+ options = { refreshIntervalOptions }
1784+ />
18291785 </ Grid >
18301786 </ Grid >
18311787
@@ -1883,26 +1839,12 @@ const AdminPanel = ({ setWidgetSettings, onPluginsChanged, onTabsChanged }) => {
18831839 </ Grid >
18841840
18851841 < Grid size = { { xs : 12 , sm : 6 } } >
1886- < FormControl fullWidth size = "small" >
1887- < InputLabel id = "weather-refresh-label" >
1888- < Box sx = { { display : 'flex' , alignItems : 'center' , gap : 1 } } >
1889- < Timer fontSize = "small" />
1890- Auto-Refresh Interval
1891- </ Box >
1892- </ InputLabel >
1893- < Select
1894- labelId = "weather-refresh-label"
1895- value = { widgetSettings . weather ?. refreshInterval || 0 }
1896- onChange = { ( e ) => handleRefreshIntervalChange ( 'weather' , e . target . value ) }
1897- label = "Auto-Refresh Interval"
1898- >
1899- { refreshIntervalOptions . map ( ( option ) => (
1900- < MenuItem key = { option . value } value = { option . value } >
1901- { option . label }
1902- </ MenuItem >
1903- ) ) }
1904- </ Select >
1905- </ FormControl >
1842+ < RefreshIntervalSelect
1843+ labelId = "weather-refresh-label"
1844+ value = { widgetSettings . weather ?. refreshInterval }
1845+ onChange = { ( value ) => handleRefreshIntervalChange ( 'weather' , value ) }
1846+ options = { refreshIntervalOptions }
1847+ />
19061848 </ Grid >
19071849 </ Grid >
19081850
@@ -2088,31 +2030,17 @@ const AdminPanel = ({ setWidgetSettings, onPluginsChanged, onTabsChanged }) => {
20882030 </ Grid >
20892031
20902032 < Grid size = { { xs : 12 , sm : 6 } } >
2091- < FormControl fullWidth size = "small" >
2092- < InputLabel id = { `plugin-${ plugin . filename } -refresh-label` } >
2093- < Box sx = { { display : 'flex' , alignItems : 'center' , gap : 1 } } >
2094- < Timer fontSize = "small" />
2095- Auto-Refresh Interval
2096- </ Box >
2097- </ InputLabel >
2098- < Select
2099- labelId = { `plugin-${ plugin . filename } -refresh-label` }
2100- value = { pSettings . refreshInterval || 0 }
2101- onChange = { ( e ) => {
2102- setPluginSettings ( prev => ( {
2103- ...prev ,
2104- [ plugin . filename ] : { ...prev [ plugin . filename ] , refreshInterval : e . target . value }
2105- } ) ) ;
2106- } }
2107- label = "Auto-Refresh Interval"
2108- >
2109- { refreshIntervalOptions . map ( ( option ) => (
2110- < MenuItem key = { option . value } value = { option . value } >
2111- { option . label }
2112- </ MenuItem >
2113- ) ) }
2114- </ Select >
2115- </ FormControl >
2033+ < RefreshIntervalSelect
2034+ labelId = { `plugin-${ plugin . filename } -refresh-label` }
2035+ value = { pSettings . refreshInterval }
2036+ onChange = { ( value ) => {
2037+ setPluginSettings ( prev => ( {
2038+ ...prev ,
2039+ [ plugin . filename ] : { ...prev [ plugin . filename ] , refreshInterval : value }
2040+ } ) ) ;
2041+ } }
2042+ options = { refreshIntervalOptions }
2043+ />
21162044 </ Grid >
21172045 </ Grid >
21182046
@@ -2524,45 +2452,35 @@ const AdminPanel = ({ setWidgetSettings, onPluginsChanged, onTabsChanged }) => {
25242452 />
25252453
25262454 { screensaverSettings . mode === 'photos' && (
2527- < >
2528- < Typography variant = "subtitle2" sx = { { mb : 1 , fontWeight : 'bold' } } >
2529- Photo Slideshow Interval: { screensaverSettings . slideshowInterval } second{ screensaverSettings . slideshowInterval !== 1 ? 's' : '' }
2530- </ Typography >
2531- < Slider
2532- value = { screensaverSettings . slideshowInterval }
2533- onChange = { ( e , value ) => setScreensaverSettings ( prev => ( { ...prev , slideshowInterval : value } ) ) }
2534- min = { 3 }
2535- max = { 60 }
2536- marks = { [
2537- { value : 3 , label : '3s' } ,
2538- { value : 10 , label : '10s' } ,
2539- { value : 30 , label : '30s' } ,
2540- { value : 60 , label : '60s' }
2541- ] }
2542- sx = { { mb : 4 } }
2543- />
2544- </ >
2455+ < ScreensaverIntervalSlider
2456+ label = "Photo Slideshow Interval"
2457+ value = { screensaverSettings . slideshowInterval }
2458+ onChange = { ( value ) => setScreensaverSettings ( prev => ( { ...prev , slideshowInterval : value } ) ) }
2459+ min = { 3 }
2460+ max = { 60 }
2461+ marks = { [
2462+ { value : 3 , label : '3s' } ,
2463+ { value : 10 , label : '10s' } ,
2464+ { value : 30 , label : '30s' } ,
2465+ { value : 60 , label : '60s' }
2466+ ] }
2467+ />
25452468 ) }
25462469
25472470 { screensaverSettings . mode === 'tabs' && (
2548- < >
2549- < Typography variant = "subtitle2" sx = { { mb : 1 , fontWeight : 'bold' } } >
2550- Tab Cycle Interval: { screensaverSettings . slideshowInterval } second{ screensaverSettings . slideshowInterval !== 1 ? 's' : '' }
2551- </ Typography >
2552- < Slider
2553- value = { screensaverSettings . slideshowInterval }
2554- onChange = { ( e , value ) => setScreensaverSettings ( prev => ( { ...prev , slideshowInterval : value } ) ) }
2555- min = { 5 }
2556- max = { 120 }
2557- marks = { [
2558- { value : 5 , label : '5s' } ,
2559- { value : 30 , label : '30s' } ,
2560- { value : 60 , label : '60s' } ,
2561- { value : 120 , label : '2m' }
2562- ] }
2563- sx = { { mb : 4 } }
2564- />
2565- </ >
2471+ < ScreensaverIntervalSlider
2472+ label = "Tab Cycle Interval"
2473+ value = { screensaverSettings . slideshowInterval }
2474+ onChange = { ( value ) => setScreensaverSettings ( prev => ( { ...prev , slideshowInterval : value } ) ) }
2475+ min = { 5 }
2476+ max = { 120 }
2477+ marks = { [
2478+ { value : 5 , label : '5s' } ,
2479+ { value : 30 , label : '30s' } ,
2480+ { value : 60 , label : '60s' } ,
2481+ { value : 120 , label : '2m' }
2482+ ] }
2483+ />
25662484 ) }
25672485
25682486 < Button
@@ -3480,87 +3398,7 @@ const AdminPanel = ({ setWidgetSettings, onPluginsChanged, onTabsChanged }) => {
34803398 </ Dialog >
34813399
34823400 { /* Loading Indicator */ }
3483- < Backdrop
3484- sx = { {
3485- color : '#fff' ,
3486- zIndex : ( theme ) => theme . zIndex . drawer + 1 ,
3487- backdropFilter : 'blur(10px)' ,
3488- backgroundColor : 'rgba(0, 0, 0, 0.3)' ,
3489- } }
3490- open = { isLoading }
3491- >
3492- < Box
3493- sx = { {
3494- display : 'flex' ,
3495- flexDirection : 'column' ,
3496- alignItems : 'center' ,
3497- gap : 3 ,
3498- p : 4 ,
3499- borderRadius : 3 ,
3500- background : 'rgba(255, 255, 255, 0.1)' ,
3501- backdropFilter : 'blur(20px)' ,
3502- border : '1px solid rgba(255, 255, 255, 0.2)' ,
3503- boxShadow : '0 8px 32px rgba(0, 0, 0, 0.3)' ,
3504- } }
3505- >
3506- < Box
3507- sx = { {
3508- position : 'relative' ,
3509- width : 80 ,
3510- height : 80 ,
3511- display : 'flex' ,
3512- alignItems : 'center' ,
3513- justifyContent : 'center' ,
3514- } }
3515- >
3516- { [ 0 , 1 , 2 ] . map ( ( index ) => (
3517- < Box
3518- key = { index }
3519- sx = { {
3520- position : 'absolute' ,
3521- fontSize : '2rem' ,
3522- animation : `clamBounce 1.5s ease-in-out ${ index * 0.2 } s infinite` ,
3523- '@keyframes clamBounce' : {
3524- '0%, 80%, 100%' : {
3525- transform : 'scale(0.8) translateY(0)' ,
3526- opacity : 0.6 ,
3527- } ,
3528- '40%' : {
3529- transform : 'scale(1.2) translateY(-20px)' ,
3530- opacity : 1 ,
3531- } ,
3532- } ,
3533- } }
3534- >
3535- 🥟
3536- </ Box >
3537- ) ) }
3538- </ Box >
3539-
3540- < Typography
3541- variant = "h6"
3542- sx = { {
3543- color : 'white' ,
3544- fontWeight : 'bold' ,
3545- textAlign : 'center' ,
3546- textShadow : '0 2px 4px rgba(0, 0, 0, 0.5)' ,
3547- } }
3548- >
3549- Processing...
3550- </ Typography >
3551-
3552- < CircularProgress
3553- size = { 40 }
3554- thickness = { 2 }
3555- sx = { {
3556- color : 'rgba(255, 255, 255, 0.7)' ,
3557- '& .MuiCircularProgress-circle' : {
3558- strokeLinecap : 'round' ,
3559- } ,
3560- } }
3561- />
3562- </ Box >
3563- </ Backdrop >
3401+ < LoadingBackdrop open = { isLoading } />
35643402
35653403 { /* PIN Modal */ }
35663404 < PinModal
0 commit comments