1010 v-for =" status in sortedRegisteredStatus"
1111 :id =" 'status-' + status"
1212 :key =" status" >
13- <div :ref =" 'status-' + status" />
13+ <div :ref =" (element) => setStatusElement( status, element) " />
1414 </li >
1515 </ul >
1616
1717 <Draggable
1818 v-model =" layout "
19- class="panels"
2019 v-bind =" { swapThreshold: 0.30 , delay: 500 , delayOnTouchOnly: true , touchStartThreshold: 3 } "
20+ class="panels"
21+ :itemKey =" (element ) => element "
2122 handle=".panel--header "
2223 @end =" saveLayout " >
23- <template v-for =" panelId in layout " >
24- <div
25- v-if =" isApiWidgetV2(panels[panelId].id)"
26- :key =" `${panels[panelId].id}-v2`"
27- class =" panel" >
28- <div class =" panel--header" >
29- <h2 >
30- <img v-if =" apiWidgets[panels[panelId].id].icon_url" :src =" apiWidgets[panels[panelId].id].icon_url" alt =" " >
31- <span v-else :class =" apiWidgets[panels[panelId].id].icon_class" aria-hidden =" true" />
32- {{ apiWidgets[panels[panelId].id].title }}
33- </h2 >
34- </div >
35- <div class =" panel--content" >
36- <ApiDashboardWidget
37- :widget =" apiWidgets [panels [panelId ].id ]"
38- :data =" apiWidgetItems [panels [panelId ].id ]"
39- :loading =" loadingItems " />
40- </div >
41- </div >
42- <div v-else :key =" panels[panelId].id" class =" panel" >
43- <div class =" panel--header" >
44- <h2 >
45- <span :class =" panels[panelId].iconClass" aria-hidden =" true" />
46- {{ panels[panelId].title }}
47- </h2 >
48- </div >
49- <div class =" panel--content" :class =" { loading: !panels[panelId].mounted }" >
50- <div :ref =" panels[panelId].id" :data-id =" panels[panelId].id" />
51- </div >
24+ <template #item =" { element: panelId } " >
25+ <div :key =" panelId" class =" panel" >
26+ <template v-if =" isApiWidgetV2 (panels [panelId ].id )" >
27+ <div class =" panel--header" >
28+ <h2 >
29+ <img v-if =" apiWidgets[panels[panelId].id].icon_url" :src =" apiWidgets[panels[panelId].id].icon_url" alt =" " >
30+ <span v-else :class =" apiWidgets[panels[panelId].id].icon_class" aria-hidden =" true" />
31+ {{ apiWidgets[panels[panelId].id].title }}
32+ </h2 >
33+ </div >
34+ <div class =" panel--content" >
35+ <ApiDashboardWidget
36+ :widget =" apiWidgets [panels [panelId ].id ]"
37+ :data =" apiWidgetItems [panels [panelId ].id ]"
38+ :loading =" loadingItems " />
39+ </div >
40+ </template >
41+ <template v-else >
42+ <div class =" panel--header" >
43+ <h2 >
44+ <span :class =" panels[panelId].iconClass" aria-hidden =" true" />
45+ {{ panels[panelId].title }}
46+ </h2 >
47+ </div >
48+ <div class =" panel--content" :class =" { loading: !panels[panelId].mounted }" >
49+ <div :ref =" panels[panelId].id" :data-id =" panels[panelId].id" />
50+ </div >
51+ </template >
5252 </div >
5353 </template >
5454 </Draggable >
6262 </NcButton >
6363 </div >
6464
65- <NcModal v-if =" modal " size="large" @close =" closeModal " >
65+ <NcModal
66+ v-if =" modal "
67+ size="large"
68+ :closeOnClickOutside =" true "
69+ @close =" closeModal " >
6670 <div class =" modal__content" >
6771 <h2 >{{ t('dashboard', 'Edit widgets') }}</h2 >
6872 <ol class =" panels" >
8185 </li >
8286 </ol >
8387 <Draggable
84- v-model = " layout "
88+ v-bind = " { swapThreshold: 0.30 , delay: 500 , delayOnTouchOnly: true , touchStartThreshold: 3 } "
8589 class="panels"
8690 tag="ol"
87- v-bind = " { swapThreshold: 0.30 , delay: 500 , delayOnTouchOnly: true , touchStartThreshold: 3 } "
91+ : itemKey = " ( panel ) => panel . id "
8892 handle=".draggable "
89- @end =" saveLayout " >
90- <li v-for =" panel in sortedPanels" :key =" panel.id" :class =" 'panel-' + panel.id" >
91- <input
92- :id =" 'panel-checkbox-' + panel.id"
93- type =" checkbox"
94- class =" checkbox"
95- :checked =" isActive(panel)"
96- @input =" updateCheckbox(panel, $event.target.checked)" >
97- <label :for =" 'panel-checkbox-' + panel.id" :class =" { draggable: isActive(panel) }" >
98- <img v-if =" panel.iconUrl" alt =" " :src =" panel.iconUrl" >
99- <span v-else :class =" panel.iconClass" aria-hidden =" true" />
100- {{ panel.title }}
101- </label >
102- </li >
93+ :modelValue =" modalPanelList "
94+ @update :modelValue =" onModalPanelListUpdate "
95+ @start =" onModalDragStart "
96+ @end =" onModalDragEnd " >
97+ <template #item =" { element: panel } " >
98+ <li :key =" panel.id" :class =" 'panel-' + panel.id" >
99+ <input
100+ :id =" 'panel-checkbox-' + panel.id"
101+ type =" checkbox"
102+ class =" checkbox"
103+ :checked =" isActive(panel)"
104+ @input =" updateCheckbox(panel, $event.target.checked)" >
105+ <label :for =" 'panel-checkbox-' + panel.id" :class =" { draggable: isActive(panel) }" >
106+ <img v-if =" panel.iconUrl" alt =" " :src =" panel.iconUrl" >
107+ <span v-else :class =" panel.iconClass" aria-hidden =" true" />
108+ {{ panel.title }}
109+ </label >
110+ </li >
111+ </template >
103112 </Draggable >
104-
105113 <a v-if =" isAdmin && appStoreEnabled" :href =" appStoreUrl" class =" button" >{{ t('dashboard', 'Get more widgets from the App Store') }}</a >
106114
107115 <div v-if =" statuses.weather && isStatusActive('weather')" >
@@ -125,7 +133,6 @@ import { getCurrentUser } from '@nextcloud/auth'
125133import axios from ' @nextcloud/axios'
126134import { loadState } from ' @nextcloud/initial-state'
127135import { generateOcsUrl , generateUrl } from ' @nextcloud/router'
128- import Vue from ' vue'
129136import Draggable from ' vuedraggable'
130137import NcButton from ' @nextcloud/vue/components/NcButton'
131138import NcModal from ' @nextcloud/vue/components/NcModal'
@@ -176,15 +183,17 @@ export default {
176183 timer: new Date (),
177184 registeredStatus: [],
178185 callbacks: {},
179- callbacksStatus: {},
180186 allCallbacksStatus: {},
187+ statusElements: {},
181188 statusInfo,
182189 enabledStatuses: loadState (' dashboard' , ' statuses' ),
183190 panels,
184191 firstRun,
185192 displayName: getCurrentUser ()? .displayName ,
186193 uid: getCurrentUser ()? .uid ,
187194 layout: loadState (' dashboard' , ' layout' ).filter ((panelId ) => panels[panelId]),
195+ modalPanelList: [],
196+ isModalDragging: false ,
188197 modal: false ,
189198 appStoreUrl: generateUrl (' /settings/apps/dashboard' ),
190199 appStoreEnabled: loadState (' dashboard' , ' appStoreEnabled' , true ),
@@ -263,39 +272,19 @@ export default {
263272 return Object .keys (this .allCallbacksStatus ).slice ().sort (this .sortStatuses )
264273 },
265274
266- sortedPanels () {
267- return Object .values (this .panels ).sort ((a , b ) => {
268- const indexA = this .layout .indexOf (a .id )
269- const indexB = this .layout .indexOf (b .id )
270- if (indexA === - 1 || indexB === - 1 ) {
271- return indexB - indexA || a .id - b .id
272- }
273- return indexA - indexB || a .id - b .id
274- })
275- },
276-
277275 sortedRegisteredStatus () {
278276 return this .registeredStatus .slice ().sort (this .sortStatuses )
279277 },
280278 },
281279
282280 watch: {
283281 callbacks () {
284- this .rerenderPanels ()
282+ this .$nextTick (() => this . rerenderPanels () )
285283 },
286284
287- callbacksStatus () {
288- for (const app in this .callbacksStatus ) {
289- const element = this .$refs [' status-' + app]
290- if (this .statuses [app] && this .statuses [app].mounted ) {
291- continue
292- }
293- if (element) {
294- this .callbacksStatus [app](element[0 ])
295- Vue .set (this .statuses , app, { mounted: true })
296- } else {
297- logger .error (' Failed to register panel in the frontend as no backend data was provided for ' + app)
298- }
285+ layout () {
286+ if (this .modal && ! this .isModalDragging ) {
287+ this .modalPanelList = this .getSortedPanelObjects ()
299288 }
300289 },
301290 },
@@ -320,6 +309,8 @@ export default {
320309 }, widget .reload_interval * 1000 )
321310 }
322311 }
312+
313+ this .$nextTick (() => this .rerenderPanels ())
323314 },
324315
325316 mounted () {
@@ -335,7 +326,7 @@ export default {
335326 }
336327 },
337328
338- destroyed () {
329+ unmounted () {
339330 window .removeEventListener (' scroll' , this .handleScroll )
340331 },
341332
@@ -347,18 +338,40 @@ export default {
347338 * @param {Function} callback The callback function to register a panel which gets the DOM element passed as parameter
348339 */
349340 register (app , callback ) {
350- Vue . set ( this .callbacks , app, callback)
341+ this .callbacks [ app] = callback
351342 },
352343
353344 registerStatus (app , callback ) {
354345 // always save callbacks in case user enables the status later
355- Vue . set ( this .allCallbacksStatus , app, callback)
346+ this .allCallbacksStatus [ app] = callback
356347 // register only if status is enabled or missing from config
357348 if (this .isStatusActive (app)) {
358- this .registeredStatus .push (app)
359- this .$nextTick (() => {
360- Vue .set (this .callbacksStatus , app, callback)
361- })
349+ if (! this .registeredStatus .includes (app)) {
350+ this .registeredStatus .push (app)
351+ }
352+ this .$nextTick (() => this .mountStatus (app))
353+ }
354+ },
355+
356+ setStatusElement (app , element ) {
357+ if (element) {
358+ this .statusElements [app] = element
359+ this .mountStatus (app)
360+ } else {
361+ delete this .statusElements [app]
362+ }
363+ },
364+
365+ mountStatus (app ) {
366+ if (this .statuses [app]? .mounted ) {
367+ return
368+ }
369+
370+ const element = this .statusElements [app]
371+ const callback = this .allCallbacksStatus [app]
372+ if (element && callback) {
373+ callback (element)
374+ this .statuses [app] = { mounted: true }
362375 }
363376 },
364377
@@ -377,10 +390,12 @@ export default {
377390 continue
378391 }
379392 if (element) {
380- this .callbacks [app](element[0 ], {
393+ // In Vue 3, refs in v-for are arrays
394+ const el = Array .isArray (element) ? element[0 ] : element
395+ this .callbacks [app](el, {
381396 widget: this .panels [app],
382397 })
383- Vue . set ( this .panels [app], ' mounted' , true )
398+ this .panels [app]. mounted = true
384399 } else {
385400 logger .error (' Failed to register panel in the frontend as no backend data was provided for ' + app)
386401 }
@@ -402,12 +417,40 @@ export default {
402417 showModal () {
403418 this .modal = true
404419 this .firstRun = false
420+ this .modalPanelList = this .getSortedPanelObjects ()
405421 },
406422
407423 closeModal () {
408424 this .modal = false
409425 },
410426
427+ getSortedPanelObjects () {
428+ return Object .values (this .panels ).sort ((a , b ) => {
429+ const indexA = this .layout .indexOf (a .id )
430+ const indexB = this .layout .indexOf (b .id )
431+ if (indexA === - 1 || indexB === - 1 ) {
432+ return indexB - indexA || a .id - b .id
433+ }
434+ return indexA - indexB || a .id - b .id
435+ })
436+ },
437+
438+ onModalPanelListUpdate (newList ) {
439+ this .modalPanelList = newList
440+ },
441+
442+ onModalDragStart () {
443+ this .isModalDragging = true
444+ },
445+
446+ onModalDragEnd () {
447+ this .isModalDragging = false
448+ this .layout = this .modalPanelList
449+ .filter ((panel ) => this .layout .includes (panel .id ))
450+ .map ((panel ) => panel .id )
451+ this .saveLayout ()
452+ },
453+
411454 updateCheckbox (panel , currentValue ) {
412455 const index = this .layout .indexOf (panel .id )
413456 if (! currentValue && index > - 1 ) {
@@ -418,7 +461,7 @@ export default {
418461 this .fetchApiWidgetItems ([panel .id ], true )
419462 }
420463 }
421- Vue . set ( this .panels [panel .id ], ' mounted' , false )
464+ this .panels [panel .id ]. mounted = false
422465 this .saveLayout ()
423466 this .$nextTick (() => this .rerenderPanels ())
424467 },
@@ -457,10 +500,7 @@ export default {
457500 const j = this .registeredStatus .findIndex ((s ) => s === app)
458501 if (j !== - 1 ) {
459502 this .registeredStatus .splice (j, 1 )
460- Vue .set (this .statuses , app, { mounted: false })
461- this .$nextTick (() => {
462- Vue .delete (this .callbacksStatus , app)
463- })
503+ this .statuses [app] = { mounted: false }
464504 }
465505 this .saveStatuses ()
466506 },
@@ -803,4 +843,8 @@ html, body {
803843 /* Scrollbar sits on the background image — use plain-text color for contrast */
804844 scrollbar- color: var (-- color- background- plain- text) transparent;
805845}
846+
847+ #app- content- vue {
848+ width: 100 % ;
849+ }
806850< / style>
0 commit comments