From 429049c809226f3750647a19a4cb48e0d3d4ea75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Mon, 15 Jun 2020 08:18:50 +0200 Subject: [PATCH 01/28] Allow userdefined order and start with drag and drop resorting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- apps/dashboard/appinfo/routes.php | 1 + .../lib/Controller/DashboardController.php | 28 ++- apps/dashboard/src/App.vue | 200 +++++++++++++++--- apps/dashboard/src/main.js | 2 + package-lock.json | 13 ++ package.json | 1 + 6 files changed, 215 insertions(+), 30 deletions(-) diff --git a/apps/dashboard/appinfo/routes.php b/apps/dashboard/appinfo/routes.php index 34792a9d47..4edca1a3ec 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 687fbace38..e796ae67cc 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 87c76a603b..44cb763b02 100644 --- a/apps/dashboard/src/App.vue +++ b/apps/dashboard/src/App.vue @@ -2,16 +2,41 @@

{{ greeting.icon }} {{ greeting.text }}

-
-
- -

- {{ panel.title }} -

-
- @@ -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,22 +104,23 @@ 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() { - for (const app in this.callbacks) { - const element = this.$refs[app] - if (this.panels[app].mounted) { - continue - } - - if (element) { - this.callbacks[app](element[0]) - Vue.set(this.panels[app], 'mounted', true) - } else { - console.error('Failed to register panel in the frontend as no backend data was provided for ' + app) - } - } + this.rerenderPanels() }, }, mounted() { @@ -74,9 +129,57 @@ export default { }, 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] && this.panels[app].mounted) { + continue + } + if (element) { + this.callbacks[app](element[0]) + Vue.set(this.panels[app], 'mounted', true) + } else { + console.error('Failed to register panel in the frontend as no backend data was provided for ' + app) + } + } + }, + + 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; + } + diff --git a/apps/dashboard/src/main.js b/apps/dashboard/src/main.js index 998f538356..e1c2c59a10 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') diff --git a/package-lock.json b/package-lock.json index ab643d9bea..a4715c500e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8798,6 +8798,11 @@ "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", @@ -9913,6 +9918,14 @@ "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", diff --git a/package.json b/package.json index e8487b19d2..db2528e21f 100644 --- a/package.json +++ b/package.json @@ -82,6 +82,7 @@ "vue-material-design-icons": "^4.8.0", "vue-multiselect": "^2.1.6", "vue-router": "^3.3.4", + "vue-smooth-dnd": "^0.8.1", "vuex": "^3.5.1", "vuex-router-sync": "^5.0.0" }, From 28a01a9728c9834b822377b380f5b15b02fd77ec Mon Sep 17 00:00:00 2001 From: Jan-Christoph Borchardt Date: Fri, 10 Jul 2020 17:04:57 +0200 Subject: [PATCH 02/28] Design enhancements to panels, headings, and edit button Signed-off-by: Jan-Christoph Borchardt --- apps/dashboard/src/App.vue | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/apps/dashboard/src/App.vue b/apps/dashboard/src/App.vue index 44cb763b02..05fd509d50 100644 --- a/apps/dashboard/src/App.vue +++ b/apps/dashboard/src/App.vue @@ -17,7 +17,7 @@
- Add more panels + {{ t('dashboard', 'Edit panels') }} -
+
+
+
{{ t('dashboard', 'Edit panels') }} @@ -208,24 +211,25 @@ export default { width: 320px; max-width: 100%; margin: 16px; - padding: 16px; + background-color: var(--color-main-background-translucent); border-radius: var(--border-radius-large); border: 2px solid var(--color-border); - .panel--header h3 { + & > .panel--header { + position: sticky; + display: flex; + top: 50px; + padding: 16px; + background: linear-gradient(var(--color-main-background-translucent), var(--color-main-background-translucent) 80%, rgba(255, 255, 255, 0)); + border-top-left-radius: var(--border-radius-large); + border-top-right-radius: var(--border-radius-large); + backdrop-filter: blur(4px); cursor: grab; + &:active { cursor: grabbing; } - } - & > .panel--header { - position: sticky; - top: 50px; - margin-bottom: 16px; - 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; } @@ -239,8 +243,13 @@ export default { background-size: 32px; background-position: 10px 10px; padding: 16px 8px 16px 52px; + cursor: grab; } } + + & > .panel--content { + margin: 0 16px 16px 16px; + } } .edit-panels { @@ -251,10 +260,11 @@ export default { padding-left: 35px; padding-right: 15px; background-position: 10px center; - color: var(--color-text-maxcontrast); + opacity: .7; + background-color: var(--color-main-background); border-radius: var(--border-radius-pill); &:hover { - color: var(--color-main-text); + opacity: 1; background-color: var(--color-background-hover); } } From 1448f54e04f5438676902913cf2861072cee31e6 Mon Sep 17 00:00:00 2001 From: Jan-Christoph Borchardt Date: Fri, 10 Jul 2020 17:59:17 +0200 Subject: [PATCH 04/28] Experiment with fade in dashboard panel header Signed-off-by: Jan-Christoph Borchardt --- apps/dashboard/src/App.vue | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/dashboard/src/App.vue b/apps/dashboard/src/App.vue index 268a6980a1..d30dc528de 100644 --- a/apps/dashboard/src/App.vue +++ b/apps/dashboard/src/App.vue @@ -220,9 +220,10 @@ export default { display: flex; top: 50px; padding: 16px; - background: linear-gradient(var(--color-main-background-translucent), var(--color-main-background-translucent) 80%, rgba(255, 255, 255, 0)); - border-top-left-radius: var(--border-radius-large); - border-top-right-radius: var(--border-radius-large); + // TO DO: use variables here + background: linear-gradient(170deg, rgba(0, 130,201, 0.2) 0%, rgba(255,255,255,.1) 50%, rgba(255,255,255,0) 100%); + border-top-left-radius: calc(var(--border-radius-large) - 2px); + border-top-right-radius: calc(var(--border-radius-large) - 2px); backdrop-filter: blur(4px); cursor: grab; From 0135eed7daa118f98b1166b5bf793a6769cfb2a5 Mon Sep 17 00:00:00 2001 From: Jan-Christoph Borchardt Date: Sat, 11 Jul 2020 11:50:24 +0200 Subject: [PATCH 05/28] Dashboard: Adjust headings to new spacing for 44px icons Signed-off-by: Jan-Christoph Borchardt --- apps/dashboard/src/App.vue | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/dashboard/src/App.vue b/apps/dashboard/src/App.vue index d30dc528de..51296c3029 100644 --- a/apps/dashboard/src/App.vue +++ b/apps/dashboard/src/App.vue @@ -191,6 +191,7 @@ export default { #app-dashboard { width: 100%; } + h2 { text-align: center; font-size: 32px; @@ -218,6 +219,7 @@ export default { & > .panel--header { position: sticky; display: flex; + z-index: 1; top: 50px; padding: 16px; // TO DO: use variables here @@ -242,8 +244,8 @@ export default { font-size: 20px; font-weight: bold; background-size: 32px; - background-position: 10px 10px; - padding: 16px 8px 16px 52px; + background-position: 12px 12px; + padding: 16px 8px 16px 60px; cursor: grab; } } From 90f56dd2b09479a61db085ea2c8f93887e1e6dad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Wed, 22 Jul 2020 11:29:35 +0200 Subject: [PATCH 06/28] Replace vue-smoothdnd with vuedraggable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- apps/dashboard/src/App.vue | 71 +++++++++++++++++--------------------- package-lock.json | 26 +++++++------- package.json | 2 +- 3 files changed, 46 insertions(+), 53 deletions(-) diff --git a/apps/dashboard/src/App.vue b/apps/dashboard/src/App.vue index 51296c3029..3841e35c60 100644 --- a/apps/dashboard/src/App.vue +++ b/apps/dashboard/src/App.vue @@ -2,23 +2,18 @@

{{ greeting.icon }} {{ greeting.text }}

- - + +
- -

- {{ panels[panelId].title }} -

-
+

+ {{ panels[panelId].title }} +

- - +
+ {{ t('dashboard', 'Edit panels') }} @@ -48,35 +43,16 @@ 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 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() { @@ -87,6 +63,7 @@ export default { name: getCurrentUser()?.displayName, layout: loadState('dashboard', 'layout').filter((panelId) => panels[panelId]), modal: false, + appStoreUrl: generateUrl('/settings/apps'), } }, computed: { @@ -161,10 +138,6 @@ export default { layout: this.layout.join(','), }) }, - onDrop(dropResult) { - this.layout = applyDrag(this.layout, dropResult) - this.saveLayout() - }, showModal() { this.modal = true }, @@ -200,7 +173,9 @@ export default { } .panels { - width: 100%; + width: auto; + margin: auto; + max-width: 1500px; display: flex; justify-content: center; flex-direction: row; @@ -216,6 +191,10 @@ export default { border-radius: var(--border-radius-large); border: 2px solid var(--color-border); + &.sortable-ghost { + opacity: 0.1; + } + & > .panel--header { position: sticky; display: flex; @@ -229,6 +208,15 @@ export default { 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; } @@ -252,6 +240,8 @@ export default { & > .panel--content { margin: 0 16px 16px 16px; + height: 420px; + overflow: auto; } } @@ -282,6 +272,9 @@ export default { padding: 10px; display: block; list-style-type: none; + background-size: 16px; + background-position: left center; + padding-left: 26px; } } diff --git a/package-lock.json b/package-lock.json index a4715c500e..70cc0d8d0f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8798,11 +8798,6 @@ "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", @@ -8905,6 +8900,11 @@ } } }, + "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", @@ -9918,14 +9918,6 @@ "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", @@ -9976,6 +9968,14 @@ "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", diff --git a/package.json b/package.json index db2528e21f..37588737a2 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "vue-material-design-icons": "^4.8.0", "vue-multiselect": "^2.1.6", "vue-router": "^3.3.4", - "vue-smooth-dnd": "^0.8.1", + "vuedraggable": "^2.24.0", "vuex": "^3.5.1", "vuex-router-sync": "^5.0.0" }, From e25bab98b73e79f4ae131136e516b96fa5cfbdb4 Mon Sep 17 00:00:00 2001 From: "Jan C. Borchardt" Date: Tue, 21 Jul 2020 15:27:20 +0200 Subject: [PATCH 07/28] Set Dashboard as default app Signed-off-by: Jan C. Borchardt --- lib/private/legacy/OC_Util.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/private/legacy/OC_Util.php b/lib/private/legacy/OC_Util.php index 82b7abf6c8..410acbce69 100644 --- a/lib/private/legacy/OC_Util.php +++ b/lib/private/legacy/OC_Util.php @@ -1100,7 +1100,7 @@ class OC_Util { } else { $appId = 'files'; $config = \OC::$server->getConfig(); - $defaultApps = explode(',', $config->getSystemValue('defaultapp', 'files')); + $defaultApps = explode(',', $config->getSystemValue('defaultapp', 'dashboard')); // find the first app that is enabled for the current user foreach ($defaultApps as $defaultApp) { $defaultApp = OC_App::cleanAppId(strip_tags($defaultApp)); From 4679926511c9e845a26eb4c9ab606ce889feea21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Fri, 24 Jul 2020 07:32:32 +0200 Subject: [PATCH 08/28] Redirect to files app after login in acceptance tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- tests/acceptance/features/bootstrap/LoginPageContext.php | 2 +- tests/acceptance/installAndConfigureServer.sh | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/acceptance/features/bootstrap/LoginPageContext.php b/tests/acceptance/features/bootstrap/LoginPageContext.php index bf44d31fb0..08046c6e3a 100644 --- a/tests/acceptance/features/bootstrap/LoginPageContext.php +++ b/tests/acceptance/features/bootstrap/LoginPageContext.php @@ -91,7 +91,7 @@ class LoginPageContext implements Context, ActorAwareInterface { */ public function iSeeThatTheCurrentPageIsTheLoginPage() { PHPUnit_Framework_Assert::assertStringStartsWith( - $this->actor->locatePath("/login"), + $this->actor->locatePath("/login?redirect_url=/index.php/apps/files"), $this->actor->getSession()->getCurrentUrl()); } diff --git a/tests/acceptance/installAndConfigureServer.sh b/tests/acceptance/installAndConfigureServer.sh index d24405fa44..99d51e951a 100755 --- a/tests/acceptance/installAndConfigureServer.sh +++ b/tests/acceptance/installAndConfigureServer.sh @@ -39,6 +39,8 @@ OC_PASS=123456acb php occ user:add --password-from-env user1 OC_PASS=123456acb php occ user:add --password-from-env disabledUser php occ user:disable disabledUser +php occ app:disable dashboard + if [ "$NEXTCLOUD_SERVER_DOMAIN" != "" ]; then # Default first trusted domain is "localhost"; replace it with given domain. php occ config:system:set trusted_domains 0 --value="$NEXTCLOUD_SERVER_DOMAIN" From 995144faa61d2746150e34780a107f316f4f7cb3 Mon Sep 17 00:00:00 2001 From: "Jan C. Borchardt" Date: Fri, 31 Jul 2020 14:48:59 +0200 Subject: [PATCH 09/28] Dashboard: Fix small misalignment of widget header icon Signed-off-by: Jan C. Borchardt --- apps/dashboard/src/App.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dashboard/src/App.vue b/apps/dashboard/src/App.vue index 3841e35c60..12e73121cc 100644 --- a/apps/dashboard/src/App.vue +++ b/apps/dashboard/src/App.vue @@ -232,7 +232,7 @@ export default { font-size: 20px; font-weight: bold; background-size: 32px; - background-position: 12px 12px; + background-position: 14px 12px; padding: 16px 8px 16px 60px; cursor: grab; } From da8a29f42edaebb18a1cb5ec31ffc053d1ac58a4 Mon Sep 17 00:00:00 2001 From: "Jan C. Borchardt" Date: Fri, 31 Jul 2020 14:51:14 +0200 Subject: [PATCH 10/28] Dashboard: Wording change from panels to widgets Signed-off-by: Jan C. Borchardt --- apps/dashboard/src/App.vue | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/dashboard/src/App.vue b/apps/dashboard/src/App.vue index 12e73121cc..1dd9543186 100644 --- a/apps/dashboard/src/App.vue +++ b/apps/dashboard/src/App.vue @@ -14,10 +14,10 @@
- {{ t('dashboard', 'Edit panels') }} + {{ t('dashboard', 'Edit widgets') }} From 5099a4fe74942a0109206e0f80bfcb374a228d0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Mon, 27 Jul 2020 11:17:18 +0200 Subject: [PATCH 11/28] Add binary attributes for dashboard bundles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- .gitattributes | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitattributes b/.gitattributes index fbcb8a02f0..6afffcdb16 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,6 +5,8 @@ /apps/accessibility/js/accessibility.js.map binary /apps/comments/js/*.js binary /apps/comments/js/*.js.map binary +/apps/dashboard/js/*.js binary +/apps/dashboard/js/*.js.map binary /apps/files/js/dist/*.js binary /apps/files/js/dist/*.js.map binary /apps/files_sharing/js/dist/*.js binary From c983094625e37c7062845edb929674cd2091b4c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Mon, 27 Jul 2020 11:17:31 +0200 Subject: [PATCH 12/28] WIP: drag and drop in modal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- apps/dashboard/src/App.vue | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/apps/dashboard/src/App.vue b/apps/dashboard/src/App.vue index 1dd9543186..6680daea45 100644 --- a/apps/dashboard/src/App.vue +++ b/apps/dashboard/src/App.vue @@ -2,7 +2,7 @@

{{ greeting.icon }} {{ greeting.text }}

- +

@@ -18,6 +18,21 @@

@@ -121,6 +135,9 @@ export default { rerenderPanels() { for (const app in this.callbacks) { const element = this.$refs[app] + if (this.layout.indexOf(app) === -1) { + continue + } if (this.panels[app] && this.panels[app].mounted) { continue } @@ -266,6 +283,8 @@ export default { width: 30vw; margin: 20px; ol { + display: flex; + flex-direction: column; list-style-type: none; } li label { From bd3d791ee032acd61ad7e62bb10a88a6335dcc39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Fri, 31 Jul 2020 13:18:47 +0200 Subject: [PATCH 13/28] Only show display name if set MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- apps/dashboard/src/App.vue | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/apps/dashboard/src/App.vue b/apps/dashboard/src/App.vue index 6680daea45..a87ce34370 100644 --- a/apps/dashboard/src/App.vue +++ b/apps/dashboard/src/App.vue @@ -74,7 +74,8 @@ export default { timer: new Date(), callbacks: {}, panels, - name: getCurrentUser()?.displayName, + displayName: getCurrentUser()?.displayName, + uid: getCurrentUser()?.uid, layout: loadState('dashboard', 'layout').filter((panelId) => panels[panelId]), modal: false, appStoreUrl: generateUrl('/settings/apps'), @@ -83,20 +84,21 @@ export default { computed: { greeting() { const time = this.timer.getHours() + const shouldShowName = this.displayName && this.uid !== this.displayName if (time > 18) { - return { icon: '🌙', text: t('dashboard', 'Good evening, {name}', { name: this.name }) } + return { icon: '🌙', text: shouldShowName ? t('dashboard', 'Good evening, {name}', { name: this.name }) : t('dashboard', 'Good evening') } } if (time > 12) { - return { icon: '☀', text: t('dashboard', 'Good afternoon, {name}', { name: this.name }) } + return { icon: '☀', text: shouldShowName ? t('dashboard', 'Good afternoon, {name}', { name: this.name }) : t('dashboard', 'Good afternoon') } } if (time === 12) { - return { icon: '🍽', text: t('dashboard', 'Time for lunch, {name}', { name: this.name }) } + return { icon: '🍽', text: shouldShowName ? t('dashboard', 'Time for lunch, {name}', { name: this.name }) : t('dashboard', 'Time for lunch') } } if (time > 5) { - return { icon: '🌄', text: t('dashboard', 'Good morning, {name}', { name: this.name }) } + return { icon: '🌄', text: shouldShowName ? t('dashboard', 'Good morning, {name}', { name: this.name }) : t('dashboard', 'Good morning') } } - return { icon: '🦉', text: t('dashboard', 'Have a night owl, {name}', { name: this.name }) } + return { icon: '🦉', text: shouldShowName ? t('dashboard', 'Have a night owl, {name}', { name: this.name }) : t('dashboard', 'Have a night owl') } }, isActive() { return (panel) => this.layout.indexOf(panel.id) > -1 From ae6be0c110dcf150440e9d7af70162b8510736c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Fri, 31 Jul 2020 13:27:43 +0200 Subject: [PATCH 14/28] Expose firstRun parameter to frontend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- apps/dashboard/lib/Controller/DashboardController.php | 2 ++ apps/dashboard/src/App.vue | 2 ++ 2 files changed, 4 insertions(+) diff --git a/apps/dashboard/lib/Controller/DashboardController.php b/apps/dashboard/lib/Controller/DashboardController.php index e796ae67cc..2da0174974 100644 --- a/apps/dashboard/lib/Controller/DashboardController.php +++ b/apps/dashboard/lib/Controller/DashboardController.php @@ -89,6 +89,8 @@ class DashboardController extends Controller { }, $this->dashboardManager->getPanels()); $this->inititalStateService->provideInitialState('dashboard', 'panels', $panels); $this->inititalStateService->provideInitialState('dashboard', 'layout', $userLayout); + $this->inititalStateService->provideInitialState('dashboard', 'firstRun', $this->config->getUserValue($this->userId, 'dashboard', 'firstRun', '1') === '1'); + $this->config->setUserValue($this->userId, 'dashboard', 'firstRun', '0'); if (class_exists(LoadViewer::class)) { $this->eventDispatcher->dispatchTyped(new LoadViewer()); diff --git a/apps/dashboard/src/App.vue b/apps/dashboard/src/App.vue index a87ce34370..f0a47ea535 100644 --- a/apps/dashboard/src/App.vue +++ b/apps/dashboard/src/App.vue @@ -62,6 +62,7 @@ import axios from '@nextcloud/axios' import { generateUrl } from '@nextcloud/router' const panels = loadState('dashboard', 'panels') +const firstRun = loadState('dashboard', 'firstRun') export default { name: 'App', @@ -74,6 +75,7 @@ export default { timer: new Date(), callbacks: {}, panels, + firstRun, displayName: getCurrentUser()?.displayName, uid: getCurrentUser()?.uid, layout: loadState('dashboard', 'layout').filter((panelId) => panels[panelId]), From 3be3c34e39f5066ea0161fa2ca5ede1fa0211f39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4rtl?= Date: Tue, 4 Aug 2020 11:58:14 +0200 Subject: [PATCH 15/28] Status integration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Julius Härtl --- apps/dashboard/src/App.vue | 53 ++++++++++++++++--- apps/dashboard/src/main.js | 1 + apps/user_status/src/App.vue | 40 +++++++++++++- apps/user_status/src/main-user-status-menu.js | 16 ++++++ 4 files changed, 101 insertions(+), 9 deletions(-) diff --git a/apps/dashboard/src/App.vue b/apps/dashboard/src/App.vue index f0a47ea535..2104c879da 100644 --- a/apps/dashboard/src/App.vue +++ b/apps/dashboard/src/App.vue @@ -1,6 +1,12 @@