<div id="app-dashboard">
<h2>{{ greeting.icon }} {{ greeting.text }}</h2>
- <Container class="panels"
- orientation="horizontal"
- drag-handle-selector=".panel--header"
- @drop="onDrop">
- <Draggable v-for="panelId in layout" :key="panels[panelId].id" class="panel">
+ <Draggable class="panels" v-model="layout" @end="saveLayout">
+ <div v-for="panelId in layout" :key="panels[panelId].id" class="panel">
<div class="panel--header">
- <a :href="panels[panelId].url">
- <h3 :class="panels[panelId].iconClass">
- {{ panels[panelId].title }}
- </h3>
- </a>
+ <h3 :class="panels[panelId].iconClass">
+ {{ panels[panelId].title }}
+ </h3>
</div>
<div class="panel--content">
<div :ref="panels[panelId].id" :data-id="panels[panelId].id" />
</div>
- </Draggable>
- </Container>
+ </div>
+ </Draggable>
<a class="edit-panels icon-add" @click="showModal">{{ t('dashboard', 'Edit panels') }}</a>
<Modal v-if="modal" @close="closeModal">
<div class="modal__content">
class="checkbox"
:checked="isActive(panel)"
@input="updateCheckbox(panel, $event.target.checked)">
- <label :for="'panel-checkbox-' + panel.id">
+ <label :for="'panel-checkbox-' + panel.id" :class="panel.iconClass">
{{ panel.title }}
</label>
</li>
<li key="appstore">
- <a href="generateUrl('/apps/settings')" class="button">{{ t('dashboard', 'Get more panels from the app store') }}</a>
+ <a :href="appStoreUrl" class="button">{{ t('dashboard', 'Get more panels from the app store') }}</a>
</li>
</transition-group>
</div>
import { loadState } from '@nextcloud/initial-state'
import { getCurrentUser } from '@nextcloud/auth'
import { Modal } from '@nextcloud/vue'
-import { Container, Draggable } from 'vue-smooth-dnd'
+import Draggable from 'vuedraggable'
import axios from '@nextcloud/axios'
import { generateUrl } from '@nextcloud/router'
const panels = loadState('dashboard', 'panels')
-const applyDrag = (arr, dragResult) => {
- const { removedIndex, addedIndex, payload } = dragResult
- if (removedIndex === null && addedIndex === null) return arr
-
- const result = [...arr]
- let itemToAdd = payload
-
- if (removedIndex !== null) {
- itemToAdd = result.splice(removedIndex, 1)[0]
- }
-
- if (addedIndex !== null) {
- result.splice(addedIndex, 0, itemToAdd)
- }
-
- return result
-}
-
export default {
name: 'App',
components: {
Modal,
- Container,
Draggable,
},
data() {
name: getCurrentUser()?.displayName,
layout: loadState('dashboard', 'layout').filter((panelId) => panels[panelId]),
modal: false,
+ appStoreUrl: generateUrl('/settings/apps'),
}
},
computed: {
layout: this.layout.join(','),
})
},
- onDrop(dropResult) {
- this.layout = applyDrag(this.layout, dropResult)
- this.saveLayout()
- },
showModal() {
this.modal = true
},
}
.panels {
- width: 100%;
+ width: auto;
+ margin: auto;
+ max-width: 1500px;
display: flex;
justify-content: center;
flex-direction: row;
border-radius: var(--border-radius-large);
border: 2px solid var(--color-border);
+ &.sortable-ghost {
+ opacity: 0.1;
+ }
+
& > .panel--header {
position: sticky;
display: flex;
backdrop-filter: blur(4px);
cursor: grab;
+ &, ::v-deep * {
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ }
+
&:active {
cursor: grabbing;
}
& > .panel--content {
margin: 0 16px 16px 16px;
+ height: 420px;
+ overflow: auto;
}
}
padding: 10px;
display: block;
list-style-type: none;
+ background-size: 16px;
+ background-position: left center;
+ padding-left: 26px;
}
}
"is-fullwidth-code-point": "^2.0.0"
}
},
- "smooth-dnd": {
- "version": "0.12.1",
- "resolved": "https://registry.npmjs.org/smooth-dnd/-/smooth-dnd-0.12.1.tgz",
- "integrity": "sha512-Dndj/MOG7VP83mvzfGCLGzV2HuK1lWachMtWl/Iuk6zV7noDycIBnflwaPuDzoaapEl3Pc4+ybJArkkx9sxPZg=="
- },
"snap.js": {
"version": "2.0.9",
"resolved": "https://registry.npmjs.org/snap.js/-/snap.js-2.0.9.tgz",
}
}
},
+ "sortablejs": {
+ "version": "1.10.2",
+ "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.10.2.tgz",
+ "integrity": "sha512-YkPGufevysvfwn5rfdlGyrGjt7/CRHwvRPogD/lC+TnvcN29jDpCifKP+rBqf+LRldfXSTh+0CGLcSg0VIxq3A=="
+ },
"source-list-map": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.3.4.tgz",
"integrity": "sha512-SdKRBeoXUjaZ9R/8AyxsdTqkOfMcI5tWxPZOUX5Ie1BTL5rPSZ0O++pbiZCeYeythiZIdLEfkDiQPKIaWk5hDg=="
},
- "vue-smooth-dnd": {
- "version": "0.8.1",
- "resolved": "https://registry.npmjs.org/vue-smooth-dnd/-/vue-smooth-dnd-0.8.1.tgz",
- "integrity": "sha512-eZVVPTwz4A1cs0+CjXx/ihV+gAl3QBoWQnU6+23Gp59t0WBU99z7ducBQ4FvjBamqOlg8SDOE5eFHQedxwB4Wg==",
- "requires": {
- "smooth-dnd": "0.12.1"
- }
- },
"vue-style-loader": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.2.tgz",
"date-format-parse": "^0.2.5"
}
},
+ "vuedraggable": {
+ "version": "2.24.0",
+ "resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-2.24.0.tgz",
+ "integrity": "sha512-IlslPpc+iZ2zPNSJbydFZIDrE+don5u+Nc/bjT2YaF+Azidc+wxxJKfKT0NwE68AKk0syb0YbZneAcnynqREZQ==",
+ "requires": {
+ "sortablejs": "^1.10.1"
+ }
+ },
"vuex": {
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/vuex/-/vuex-3.5.1.tgz",