summaryrefslogtreecommitdiffstats
path: root/apps/dashboard
diff options
context:
space:
mode:
authorJulius Härtl <jus@bitgrid.net>2020-06-15 08:18:50 +0200
committerJulius Härtl <jus@bitgrid.net>2020-08-05 17:01:27 +0200
commit429049c809226f3750647a19a4cb48e0d3d4ea75 (patch)
tree3a66ed71df4d417dacfb888ce831e5db7d98267d /apps/dashboard
parent55473dd2eb042078b7fc5aef37e7b7fb614554fa (diff)
downloadnextcloud-server-429049c809226f3750647a19a4cb48e0d3d4ea75.tar.gz
nextcloud-server-429049c809226f3750647a19a4cb48e0d3d4ea75.zip
Allow userdefined order and start with drag and drop resorting
Signed-off-by: Julius Härtl <jus@bitgrid.net>
Diffstat (limited to 'apps/dashboard')
-rw-r--r--apps/dashboard/appinfo/routes.php1
-rw-r--r--apps/dashboard/lib/Controller/DashboardController.php28
-rw-r--r--apps/dashboard/src/App.vue196
-rw-r--r--apps/dashboard/src/main.js2
4 files changed, 199 insertions, 28 deletions
diff --git a/apps/dashboard/appinfo/routes.php b/apps/dashboard/appinfo/routes.php
index 34792a9d47d..4edca1a3ec5 100644
--- a/apps/dashboard/appinfo/routes.php
+++ b/apps/dashboard/appinfo/routes.php
@@ -27,5 +27,6 @@ declare(strict_types=1);
return [
'routes' => [
['name' => 'dashboard#index', 'url' => '/', 'verb' => 'GET'],
+ ['name' => 'dashboard#updateLayout', 'url' => '/layout', 'verb' => 'POST'],
]
];
diff --git a/apps/dashboard/lib/Controller/DashboardController.php b/apps/dashboard/lib/Controller/DashboardController.php
index 687fbace380..e796ae67ccd 100644
--- a/apps/dashboard/lib/Controller/DashboardController.php
+++ b/apps/dashboard/lib/Controller/DashboardController.php
@@ -26,13 +26,16 @@ declare(strict_types=1);
namespace OCA\Dashboard\Controller;
+use OCA\Dashboard\AppInfo\Application;
use OCA\Viewer\Event\LoadViewer;
use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\Dashboard\IManager;
use OCP\Dashboard\IPanel;
use OCP\Dashboard\RegisterPanelEvent;
use OCP\EventDispatcher\IEventDispatcher;
+use OCP\IConfig;
use OCP\IInitialStateService;
use OCP\IRequest;
@@ -44,19 +47,27 @@ class DashboardController extends Controller {
private $eventDispatcher;
/** @var IManager */
private $dashboardManager;
+ /** @var IConfig */
+ private $config;
+ /** @var string */
+ private $userId;
public function __construct(
string $appName,
IRequest $request,
IInitialStateService $initialStateService,
IEventDispatcher $eventDispatcher,
- IManager $dashboardManager
+ IManager $dashboardManager,
+ IConfig $config,
+ $userId
) {
parent::__construct($appName, $request);
$this->inititalStateService = $initialStateService;
$this->eventDispatcher = $eventDispatcher;
$this->dashboardManager = $dashboardManager;
+ $this->config = $config;
+ $this->userId = $userId;
}
/**
@@ -67,7 +78,7 @@ class DashboardController extends Controller {
public function index(): TemplateResponse {
$this->eventDispatcher->dispatchTyped(new RegisterPanelEvent($this->dashboardManager));
- $dashboardManager = $this->dashboardManager;
+ $userLayout = explode(',', $this->config->getUserValue($this->userId, 'dashboard', 'layout', 'calendar,recommendations,spreed,mail'));
$panels = array_map(function (IPanel $panel) {
return [
'id' => $panel->getId(),
@@ -75,8 +86,9 @@ class DashboardController extends Controller {
'iconClass' => $panel->getIconClass(),
'url' => $panel->getUrl()
];
- }, $dashboardManager->getPanels());
+ }, $this->dashboardManager->getPanels());
$this->inititalStateService->provideInitialState('dashboard', 'panels', $panels);
+ $this->inititalStateService->provideInitialState('dashboard', 'layout', $userLayout);
if (class_exists(LoadViewer::class)) {
$this->eventDispatcher->dispatchTyped(new LoadViewer());
@@ -84,4 +96,14 @@ class DashboardController extends Controller {
return new TemplateResponse('dashboard', 'index');
}
+
+ /**
+ * @NoAdminRequired
+ * @param string $layout
+ * @return JSONResponse
+ */
+ public function updateLayout(string $layout): JSONResponse {
+ $this->config->setUserValue($this->userId, 'dashboard', 'layout', $layout);
+ return new JSONResponse(['layout' => $layout]);
+ }
}
diff --git a/apps/dashboard/src/App.vue b/apps/dashboard/src/App.vue
index 87c76a603b4..44cb763b020 100644
--- a/apps/dashboard/src/App.vue
+++ b/apps/dashboard/src/App.vue
@@ -2,16 +2,41 @@
<div id="app-dashboard">
<h2>{{ greeting.icon }} {{ greeting.text }}</h2>
- <div class="panels">
- <div v-for="panel in panels" :key="panel.id" class="panel">
- <a :href="panel.url">
- <h3 :class="panel.iconClass">
- {{ panel.title }}
- </h3>
- </a>
- <div :ref="panel.id" :data-id="panel.id" />
+ <Container class="panels"
+ orientation="horizontal"
+ drag-handle-selector=".panel--header"
+ @drop="onDrop">
+ <Draggable 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>
+ </div>
+ <div :ref="panels[panelId].id" :data-id="panels[panelId].id" />
+ </Draggable>
+ </Container>
+ <a class="add-panels icon-add" @click="showModal">Add more panels</a>
+ <Modal v-if="modal" @close="closeModal">
+ <div class="modal__content">
+ <transition-group name="flip-list" tag="ol">
+ <li v-for="panel in sortedPanels" :key="panel.id">
+ <input :id="'panel-checkbox-' + panel.id"
+ type="checkbox"
+ class="checkbox"
+ :checked="isActive(panel)"
+ @input="updateCheckbox(panel, $event.target.checked)">
+ <label :for="'panel-checkbox-' + panel.id">
+ {{ panel.title }}
+ </label>
+ </li>
+ <li key="appstore">
+ <a href="/index.php/apps/settings" class="button">{{ t('dashboard', 'Get more panels from the app store') }}</a>
+ </li>
+ </transition-group>
</div>
- </div>
+ </Modal>
</div>
</template>
@@ -19,17 +44,46 @@
import Vue from 'vue'
import { loadState } from '@nextcloud/initial-state'
import { getCurrentUser } from '@nextcloud/auth'
+import { Modal } from '@nextcloud/vue'
+import { Container, Draggable } from 'vue-smooth-dnd'
+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() {
return {
timer: new Date(),
callbacks: {},
panels,
name: getCurrentUser()?.displayName,
+ layout: loadState('dashboard', 'layout').filter((panelId) => panels[panelId]),
+ modal: false,
}
},
computed: {
@@ -50,15 +104,46 @@ export default {
}
return { icon: '🦉', text: t('dashboard', 'Have a night owl, {name}', { name: this.name }) }
},
+ isActive() {
+ return (panel) => this.layout.indexOf(panel.id) > -1
+ },
+ sortedPanels() {
+ return Object.values(this.panels).sort((a, b) => {
+ const indexA = this.layout.indexOf(a.id)
+ const indexB = this.layout.indexOf(b.id)
+ if (indexA === -1 || indexB === -1) {
+ return indexB - indexA || a.id - b.id
+ }
+ return indexA - indexB || a.id - b.id
+ })
+ },
},
watch: {
callbacks() {
+ this.rerenderPanels()
+ },
+ },
+ mounted() {
+ setInterval(() => {
+ this.timer = new Date()
+ }, 30000)
+ },
+ methods: {
+ /**
+ * Method to register panels that will be called by the integrating apps
+ *
+ * @param {string} app The unique app id for the widget
+ * @param {function} callback The callback function to register a panel which gets the DOM element passed as parameter
+ */
+ register(app, callback) {
+ Vue.set(this.callbacks, app, callback)
+ },
+ rerenderPanels() {
for (const app in this.callbacks) {
const element = this.$refs[app]
- if (this.panels[app].mounted) {
+ if (this.panels[app] && this.panels[app].mounted) {
continue
}
-
if (element) {
this.callbacks[app](element[0])
Vue.set(this.panels[app], 'mounted', true)
@@ -67,15 +152,33 @@ export default {
}
}
},
- },
- mounted() {
- setInterval(() => {
- this.timer = new Date()
- }, 30000)
- },
- methods: {
- register(app, callback) {
- Vue.set(this.callbacks, app, callback)
+
+ saveLayout() {
+ axios.post(generateUrl('/apps/dashboard/layout'), {
+ layout: this.layout.join(','),
+ })
+ },
+ onDrop(dropResult) {
+ this.layout = applyDrag(this.layout, dropResult)
+ this.saveLayout()
+ },
+ showModal() {
+ this.modal = true
+ },
+ closeModal() {
+ this.modal = false
+ },
+ updateCheckbox(panel, currentValue) {
+ const index = this.layout.indexOf(panel.id)
+ if (!currentValue && index > -1) {
+ this.layout.splice(index, 1)
+
+ } else {
+ this.layout.push(panel.id)
+ }
+ Vue.set(this.panels[panel.id], 'mounted', false)
+ this.saveLayout()
+ this.$nextTick(() => this.rerenderPanels())
},
},
}
@@ -101,18 +204,30 @@ export default {
flex-wrap: wrap;
}
- .panel {
- width: 250px;
- margin: 16px;
+ .panel, .panels > div {
+ width: 280px;
+ padding: 16px;
- & > a {
+ .panel--header h3 {
+ cursor: grab;
+ &:active {
+ cursor: grabbing;
+ }
+ }
+
+ & > .panel--header {
position: sticky;
top: 50px;
- display: block;
background: linear-gradient(var(--color-main-background-translucent), var(--color-main-background-translucent) 80%, rgba(255, 255, 255, 0));
backdrop-filter: blur(4px);
+ display: flex;
+ a {
+ flex-grow: 1;
+ }
h3 {
+ display: block;
+ flex-grow: 1;
margin: 0;
font-size: 20px;
font-weight: bold;
@@ -123,4 +238,35 @@ export default {
}
}
+ .add-panels {
+ position: fixed;
+ bottom: 20px;
+ right: 20px;
+ padding: 10px;
+ padding-left: 35px;
+ padding-right: 15px;
+ background-position: 10px center;
+ border-radius: 100px;
+ &:hover {
+ background-color: var(--color-background-hover);
+ }
+ }
+
+ .modal__content {
+ width: 30vw;
+ margin: 20px;
+ ol {
+ list-style-type: none;
+ }
+ li label {
+ padding: 10px;
+ display: block;
+ list-style-type: none;
+ }
+ }
+
+ .flip-list-move {
+ transition: transform 1s;
+ }
+
</style>
diff --git a/apps/dashboard/src/main.js b/apps/dashboard/src/main.js
index 998f538356b..e1c2c59a10f 100644
--- a/apps/dashboard/src/main.js
+++ b/apps/dashboard/src/main.js
@@ -1,5 +1,7 @@
import Vue from 'vue'
import App from './App.vue'
+import { translate as t } from '@nextcloud/l10n'
+Vue.prototype.t = t
const Dashboard = Vue.extend(App)
const Instance = new Dashboard({}).$mount('#app')