@@ -27,6 +27,7 @@ import {
2727 ViewModule ,
2828 Star ,
2929 StarBorder ,
30+ Close ,
3031 Warning ,
3132 WaterDamage ,
3233 WaterDrop ,
@@ -89,8 +90,10 @@ import {
8990 WidgetGauge ,
9091 WidgetUniversal ,
9192 type WidgetUniversalSettings ,
93+ WidgetPresence ,
94+ type WidgetPresenceSettings ,
9295 WidgetGate ,
93- PluginWidget ,
96+ WidgetPlugin ,
9497 type WidgetWeatherSettings ,
9598 type WidgetIframeSettings ,
9699 type WidgetWindSettings ,
@@ -111,7 +114,7 @@ import type {
111114 DevicesDetectorState ,
112115 CustomWidgetPlugin ,
113116} from '@iobroker/dm-widgets' ;
114- import type { WidgetGeneric } from './Widgets/Generic' ;
117+ import { WidgetGeneric } from './Widgets/Generic' ;
115118
116119interface CategoryProps {
117120 category : CategoryInfo ;
@@ -223,10 +226,14 @@ type OrderedItem = {
223226 data : CategoryInfo | WidgetInfo | CustomWidgetBase ;
224227} ;
225228
226- function getGridColumn ( item : OrderedItem , widgetSettings : Record < string , WidgetSettingsBase > ) : string | undefined {
229+ function getGridColumn ( item : OrderedItem , widgetSettings : Record < string , WidgetSettingsBase > , editing ?: boolean ) : string | undefined {
227230 if ( item . type === 'category' ) {
228231 return '1 / -1' ;
229232 }
233+ if ( item . type === 'custom' && ( item . data as CustomWidgetBase ) . type === 'newline' ) {
234+ // In edit mode: normal 1x1 grid item (draggable); play mode: full-row span for line break
235+ return editing ? undefined : '1 / -1' ;
236+ }
230237 let size : '1x1' | '2x0.5' | '2x1' ;
231238 if ( item . type === 'widget' ) {
232239 size = widgetSettings [ item . id ] ?. size || '1x1' ;
@@ -482,6 +489,7 @@ function SortableGrid(props: {
482489
483490 const orderedItems = liveOrder . map ( id => itemMap . get ( id ) ) . filter ( Boolean ) as OrderedItem [ ] ;
484491 const activeItem = activeId ? itemMap . get ( activeId ) : null ;
492+ const draggingNewline = activeItem ?. type === 'custom' && ( activeItem . data as CustomWidgetBase ) . type === 'newline' ;
485493
486494 const renderContent = ( item : OrderedItem , isDropTarget ?: boolean ) : React . ReactNode => {
487495 if ( item . type === 'category' ) {
@@ -536,7 +544,7 @@ function SortableGrid(props: {
536544 }
537545
538546 const parentCatId = category . props . category . parent ? String ( category . props . category . parent ) : null ;
539- const showParentDrop = ! ! activeId && ! ! onMoveWidgetToCategory && ! ! parentCatId ;
547+ const showParentDrop = ! ! activeId && ! draggingNewline && ! ! onMoveWidgetToCategory && ! ! parentCatId ;
540548
541549 return (
542550 < DndContext
@@ -587,14 +595,15 @@ function SortableGrid(props: {
587595 >
588596 { orderedItems . map ( item => {
589597 const isCw = item . type === 'custom' ;
598+ const isNewline = isCw && ( item . data as CustomWidgetBase ) . type === 'newline' ;
590599 const fav =
591- item . type === 'category'
600+ item . type === 'category' || isNewline
592601 ? undefined
593602 : isCw
594603 ? ( item . data as CustomWidgetBase ) . favorite
595604 : widgetSettings [ item . id ] ?. favorite ;
596605 const toggleFav =
597- item . type === 'category'
606+ item . type === 'category' || isNewline
598607 ? undefined
599608 : isCw
600609 ? category . props . onToggleCustomWidgetFavorite
@@ -603,7 +612,7 @@ function SortableGrid(props: {
603612 < SortableItem
604613 key = { item . id }
605614 id = { item . id }
606- gridColumn = { getGridColumn ( item , widgetSettings ) }
615+ gridColumn = { getGridColumn ( item , widgetSettings , true ) }
607616 isDragging = { item . id === activeId }
608617 favorite = { fav }
609618 onToggleFavorite = { toggleFav }
@@ -686,14 +695,15 @@ function GroupSortableGrid(props: {
686695 >
687696 { items . map ( item => {
688697 const isCw = item . type === 'custom' ;
698+ const isNewline = isCw && ( item . data as CustomWidgetBase ) . type === 'newline' ;
689699 const fav =
690- item . type === 'category'
700+ item . type === 'category' || isNewline
691701 ? undefined
692702 : isCw
693703 ? ( item . data as CustomWidgetBase ) . favorite
694704 : widgetSettings [ item . id ] ?. favorite ;
695705 const toggleFav =
696- item . type === 'category'
706+ item . type === 'category' || isNewline
697707 ? undefined
698708 : isCw
699709 ? category . props . onToggleCustomWidgetFavorite
@@ -702,7 +712,7 @@ function GroupSortableGrid(props: {
702712 < SortableItem
703713 key = { item . id }
704714 id = { item . id }
705- gridColumn = { getGridColumn ( item , widgetSettings ) }
715+ gridColumn = { getGridColumn ( item , widgetSettings , true ) }
706716 isDragging = { item . id === activeId }
707717 favorite = { fav }
708718 onToggleFavorite = { toggleFav }
@@ -1979,9 +1989,20 @@ export default class Category extends Component<CategoryProps, CategoryState> {
19791989 ) ;
19801990 break ;
19811991 }
1992+ case 'presence' :
1993+ content = (
1994+ < WidgetPresence
1995+ key = { def . id }
1996+ settings = { def as WidgetPresenceSettings }
1997+ stateContext = { this . props . stateContext }
1998+ onOpenSettings = { settingsCb }
1999+ onRemove = { removeCb }
2000+ />
2001+ ) ;
2002+ break ;
19822003 case 'plugin' :
19832004 content = (
1984- < PluginWidget
2005+ < WidgetPlugin
19852006 key = { def . id }
19862007 id = { def . id }
19872008 stateContext = { this . props . stateContext }
@@ -1994,6 +2015,60 @@ export default class Category extends Component<CategoryProps, CategoryState> {
19942015 />
19952016 ) ;
19962017 break ;
2018+ case 'newline' :
2019+ content = removeCb ? (
2020+ < Box
2021+ key = { def . id }
2022+ sx = { theme => WidgetGeneric . getStyleCompact ( theme ) }
2023+ >
2024+ < Box
2025+ sx = { theme => ( {
2026+ display : 'flex' ,
2027+ flexDirection : 'column' ,
2028+ alignItems : 'center' ,
2029+ justifyContent : 'center' ,
2030+ boxSizing : 'border-box' ,
2031+ width : '100%' ,
2032+ aspectRatio : '1' ,
2033+ border : '2px dashed' ,
2034+ borderColor :
2035+ theme . palette . mode === 'dark'
2036+ ? 'rgba(255,255,255,0.2)'
2037+ : 'rgba(0,0,0,0.15)' ,
2038+ borderRadius : 'inherit' ,
2039+ gap : 0.5 ,
2040+ } ) }
2041+ >
2042+ < Typography sx = { { fontSize : 'max(36px, 20cqi)' , lineHeight : 1 , opacity : 0.4 } } >
2043+ ⏎
2044+ </ Typography >
2045+ < Typography
2046+ variant = "caption"
2047+ sx = { { opacity : 0.3 , userSelect : 'none' , fontSize : 'max(0.6rem, 3cqi)' } }
2048+ >
2049+ { I18n . t ( 'wm_New line' ) }
2050+ </ Typography >
2051+ </ Box >
2052+ < IconButton
2053+ size = "small"
2054+ onClick = { ( ) => removeCb ( ) }
2055+ sx = { {
2056+ position : 'absolute' ,
2057+ top : 6 ,
2058+ right : 6 ,
2059+ p : '3px' ,
2060+ opacity : 0 ,
2061+ '&:hover' : { opacity : 1 } ,
2062+ '.MuiBox-root:hover > &' : { opacity : 0.7 } ,
2063+ } }
2064+ >
2065+ < Close sx = { { fontSize : 16 } } />
2066+ </ IconButton >
2067+ </ Box >
2068+ ) : (
2069+ < Box key = { def . id } />
2070+ ) ;
2071+ break ;
19972072 default :
19982073 content = null ;
19992074 }
0 commit comments