Merge pull request #21598 from nextcloud/enh/20930/dashboard-drag
This commit is contained in:
commit
5b6246f52a
|
@ -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
|
||||
|
|
|
@ -27,5 +27,6 @@ declare(strict_types=1);
|
|||
return [
|
||||
'routes' => [
|
||||
['name' => 'dashboard#index', 'url' => '/', 'verb' => 'GET'],
|
||||
['name' => 'dashboard#updateLayout', 'url' => '/layout', 'verb' => 'POST'],
|
||||
]
|
||||
];
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -26,13 +26,16 @@ declare(strict_types=1);
|
|||
|
||||
namespace OCA\Dashboard\Controller;
|
||||
|
||||
use OCA\Files\Event\LoadSidebar;
|
||||
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\Dashboard\IWidget;
|
||||
use OCP\Dashboard\RegisterWidgetEvent;
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -65,23 +76,37 @@ class DashboardController extends Controller {
|
|||
* @return TemplateResponse
|
||||
*/
|
||||
public function index(): TemplateResponse {
|
||||
$this->eventDispatcher->dispatchTyped(new RegisterPanelEvent($this->dashboardManager));
|
||||
|
||||
$dashboardManager = $this->dashboardManager;
|
||||
$panels = array_map(function (IPanel $panel) {
|
||||
return [
|
||||
'id' => $panel->getId(),
|
||||
'title' => $panel->getTitle(),
|
||||
'iconClass' => $panel->getIconClass(),
|
||||
'url' => $panel->getUrl()
|
||||
];
|
||||
}, $dashboardManager->getPanels());
|
||||
$this->inititalStateService->provideInitialState('dashboard', 'panels', $panels);
|
||||
|
||||
$this->eventDispatcher->dispatchTyped(new LoadSidebar());
|
||||
if (class_exists(LoadViewer::class)) {
|
||||
$this->eventDispatcher->dispatchTyped(new LoadViewer());
|
||||
}
|
||||
|
||||
$this->eventDispatcher->dispatchTyped(new RegisterWidgetEvent($this->dashboardManager));
|
||||
|
||||
$userLayout = explode(',', $this->config->getUserValue($this->userId, 'dashboard', 'layout', 'recommendations,spreed,mail,calendar'));
|
||||
$widgets = array_map(function (IWidget $widget) {
|
||||
return [
|
||||
'id' => $widget->getId(),
|
||||
'title' => $widget->getTitle(),
|
||||
'iconClass' => $widget->getIconClass(),
|
||||
'url' => $widget->getUrl()
|
||||
];
|
||||
}, $this->dashboardManager->getWidgets());
|
||||
$this->inititalStateService->provideInitialState('dashboard', 'panels', $widgets);
|
||||
$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');
|
||||
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,57 @@
|
|||
<template>
|
||||
<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" />
|
||||
</div>
|
||||
<div class="statuses">
|
||||
<div v-for="status in registeredStatus"
|
||||
:id="'status-' + status"
|
||||
:key="status"
|
||||
:ref="'status-' + status" />
|
||||
</div>
|
||||
|
||||
<Draggable v-model="layout"
|
||||
class="panels"
|
||||
handle=".panel--header"
|
||||
@end="saveLayout">
|
||||
<div v-for="panelId in layout" :key="panels[panelId].id" class="panel">
|
||||
<div class="panel--header">
|
||||
<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>
|
||||
</div>
|
||||
</Draggable>
|
||||
|
||||
<a v-tooltip="tooltip"
|
||||
class="edit-panels icon-add"
|
||||
:class="{ firstrun: firstRun }"
|
||||
@click="showModal">{{ t('dashboard', 'Edit widgets') }}</a>
|
||||
|
||||
<Modal v-if="modal" @close="closeModal">
|
||||
<div class="modal__content">
|
||||
<h3>{{ t('dashboard', 'Edit widgets') }}</h3>
|
||||
<Draggable v-model="layout"
|
||||
class="panels"
|
||||
tag="ol"
|
||||
handle=".draggable"
|
||||
@end="saveLayout">
|
||||
<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" :class="isActive(panel) ? 'draggable ' + panel.iconClass : panel.iconClass">
|
||||
{{ panel.title }}
|
||||
</label>
|
||||
</li>
|
||||
</Draggable>
|
||||
|
||||
<a :href="appStoreUrl" class="button">{{ t('dashboard', 'Get more widgets from the app store') }}</a>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -19,49 +59,93 @@
|
|||
import Vue from 'vue'
|
||||
import { loadState } from '@nextcloud/initial-state'
|
||||
import { getCurrentUser } from '@nextcloud/auth'
|
||||
import { Modal } from '@nextcloud/vue'
|
||||
import Draggable from 'vuedraggable'
|
||||
import axios from '@nextcloud/axios'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
|
||||
const panels = loadState('dashboard', 'panels')
|
||||
const firstRun = loadState('dashboard', 'firstRun')
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {
|
||||
Modal,
|
||||
Draggable,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
timer: new Date(),
|
||||
registeredStatus: [],
|
||||
callbacks: {},
|
||||
callbacksStatus: {},
|
||||
panels,
|
||||
name: getCurrentUser()?.displayName,
|
||||
firstRun,
|
||||
displayName: getCurrentUser()?.displayName,
|
||||
uid: getCurrentUser()?.uid,
|
||||
layout: loadState('dashboard', 'layout').filter((panelId) => panels[panelId]),
|
||||
modal: false,
|
||||
appStoreUrl: generateUrl('/settings/apps/dashboard'),
|
||||
statuses: {},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
tooltip() {
|
||||
if (!this.firstRun) {
|
||||
return null
|
||||
}
|
||||
return {
|
||||
content: t('dashboard', 'Adjust the dashboard to your needs'),
|
||||
placement: 'top',
|
||||
show: true,
|
||||
trigger: 'manual',
|
||||
}
|
||||
},
|
||||
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.displayName }) : 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.displayName }) : 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.displayName }) : 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.displayName }) : 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.displayName }) : t('dashboard', 'Have a night owl') }
|
||||
},
|
||||
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) {
|
||||
this.rerenderPanels()
|
||||
},
|
||||
callbacksStatus() {
|
||||
for (const app in this.callbacksStatus) {
|
||||
const element = this.$refs['status-' + app]
|
||||
if (this.statuses[app] && this.statuses[app].mounted) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (element) {
|
||||
this.callbacks[app](element[0])
|
||||
Vue.set(this.panels[app], 'mounted', true)
|
||||
this.callbacksStatus[app](element[0])
|
||||
Vue.set(this.statuses, app, { mounted: true })
|
||||
} else {
|
||||
console.error('Failed to register panel in the frontend as no backend data was provided for ' + app)
|
||||
}
|
||||
|
@ -72,11 +156,74 @@ export default {
|
|||
setInterval(() => {
|
||||
this.timer = new Date()
|
||||
}, 30000)
|
||||
|
||||
if (this.firstRun) {
|
||||
window.addEventListener('scroll', this.disableFirstrunHint)
|
||||
}
|
||||
},
|
||||
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)
|
||||
},
|
||||
registerStatus(app, callback) {
|
||||
this.registeredStatus.push(app)
|
||||
this.$nextTick(() => {
|
||||
Vue.set(this.callbacksStatus, app, callback)
|
||||
})
|
||||
},
|
||||
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
|
||||
}
|
||||
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(','),
|
||||
})
|
||||
},
|
||||
showModal() {
|
||||
this.modal = true
|
||||
this.firstRun = false
|
||||
},
|
||||
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())
|
||||
},
|
||||
disableFirstrunHint() {
|
||||
window.removeEventListener('scroll', this.disableFirstrunHint)
|
||||
setTimeout(() => {
|
||||
this.firstRun = false
|
||||
}, 1000)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
@ -84,16 +231,20 @@ export default {
|
|||
<style lang="scss" scoped>
|
||||
#app-dashboard {
|
||||
width: 100%;
|
||||
margin-bottom: 100px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
text-align: center;
|
||||
font-size: 32px;
|
||||
line-height: 130%;
|
||||
padding: 80px 16px 32px;
|
||||
padding: 80px 16px 0px;
|
||||
}
|
||||
|
||||
.panels {
|
||||
width: 100%;
|
||||
width: auto;
|
||||
margin: auto;
|
||||
max-width: 1500px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: row;
|
||||
|
@ -101,26 +252,125 @@ export default {
|
|||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.panel {
|
||||
width: 250px;
|
||||
.panel, .panels > div {
|
||||
width: 320px;
|
||||
max-width: 100%;
|
||||
margin: 16px;
|
||||
background-color: var(--color-main-background-translucent);
|
||||
border-radius: var(--border-radius-large);
|
||||
border: 2px solid var(--color-border);
|
||||
|
||||
& > a {
|
||||
position: sticky;
|
||||
&.sortable-ghost {
|
||||
opacity: 0.1;
|
||||
}
|
||||
|
||||
& > .panel--header {
|
||||
display: flex;
|
||||
z-index: 1;
|
||||
top: 50px;
|
||||
display: block;
|
||||
background: linear-gradient(var(--color-main-background-translucent), var(--color-main-background-translucent) 80%, rgba(255, 255, 255, 0));
|
||||
padding: 16px;
|
||||
// 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;
|
||||
|
||||
&, ::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;
|
||||
}
|
||||
|
||||
a {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
h3 {
|
||||
display: block;
|
||||
flex-grow: 1;
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
background-size: 32px;
|
||||
background-position: 10px 10px;
|
||||
padding: 16px 8px 16px 52px;
|
||||
background-position: 14px 12px;
|
||||
padding: 16px 8px 16px 60px;
|
||||
cursor: grab;
|
||||
}
|
||||
}
|
||||
|
||||
& > .panel--content {
|
||||
margin: 0 16px 16px 16px;
|
||||
height: 420px;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-panels {
|
||||
z-index: 99;
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
padding: 10px 15px 10px 35px;
|
||||
background-position: 10px center;
|
||||
opacity: .7;
|
||||
background-color: var(--color-main-background);
|
||||
border-radius: var(--border-radius-pill);
|
||||
transition: right var(--animation-slow) ease-in-out;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
background-color: var(--color-background-hover);
|
||||
}
|
||||
|
||||
&.firstrun {
|
||||
right: 50%;
|
||||
transform: translateX(50%);
|
||||
max-width: 200px;
|
||||
box-shadow: 0px 0px 3px var(--color-box-shadow);
|
||||
opacity: 1;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.modal__content {
|
||||
width: 30vw;
|
||||
margin: 20px;
|
||||
ol {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
list-style-type: none;
|
||||
}
|
||||
li label {
|
||||
padding: 10px;
|
||||
display: block;
|
||||
list-style-type: none;
|
||||
background-size: 16px;
|
||||
background-position: left center;
|
||||
padding-left: 26px;
|
||||
}
|
||||
}
|
||||
|
||||
.flip-list-move {
|
||||
transition: transform var(--animation-slow);
|
||||
}
|
||||
|
||||
.statuses {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
margin-bottom: 40px;
|
||||
|
||||
& > div {
|
||||
max-width: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -1,9 +1,19 @@
|
|||
import Vue from 'vue'
|
||||
import App from './App.vue'
|
||||
import { translate as t } from '@nextcloud/l10n'
|
||||
import VTooltip from '@nextcloud/vue/dist/Directives/Tooltip'
|
||||
|
||||
Vue.directive('Tooltip', VTooltip)
|
||||
|
||||
Vue.prototype.t = t
|
||||
|
||||
// FIXME workaround to make the sidebar work
|
||||
Object.assign(window.OCA.Files, { App: { fileList: { filesClient: OC.Files.getClient() } } }, window.OCA.Files)
|
||||
|
||||
const Dashboard = Vue.extend(App)
|
||||
const Instance = new Dashboard({}).$mount('#app')
|
||||
const Instance = new Dashboard({}).$mount('#app-content-vue')
|
||||
|
||||
window.OCA.Dashboard = {
|
||||
register: (app, callback) => Instance.register(app, callback),
|
||||
registerStatus: (app, callback) => Instance.registerStatus(app, callback),
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
\OCP\Util::addScript('dashboard', 'dashboard');
|
||||
?>
|
||||
<div id="app"></div>
|
||||
<div id="app-content-vue"></div>
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -20,9 +20,10 @@
|
|||
-->
|
||||
|
||||
<template>
|
||||
<li>
|
||||
<li :class="{ inline }">
|
||||
<div id="user-status-menu-item">
|
||||
<span
|
||||
v-if="!inline"
|
||||
id="user-status-menu-item__header"
|
||||
:title="displayName">
|
||||
{{ displayName }}
|
||||
|
@ -71,6 +72,12 @@ export default {
|
|||
ActionButton,
|
||||
SetStatusModal,
|
||||
},
|
||||
props: {
|
||||
inline: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isModalOpen: false,
|
||||
|
@ -237,7 +244,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
#user-status-menu-item {
|
||||
li:not(.inline) #user-status-menu-item {
|
||||
&__header {
|
||||
display: block;
|
||||
color: var(--color-main-text);
|
||||
|
@ -270,4 +277,33 @@ export default {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.inline #user-status-menu-item__subheader {
|
||||
width: 100%;
|
||||
|
||||
> button {
|
||||
background-color: var(--color-main-background);
|
||||
background-size: 16px;
|
||||
border: 0;
|
||||
border-radius: var(--border-radius-pill);
|
||||
font-weight: normal;
|
||||
font-size: 0.875em;
|
||||
padding-left: 40px;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: var(--color-background-hover);
|
||||
}
|
||||
|
||||
&.icon-loading-small {
|
||||
&::after {
|
||||
left: 21px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
li {
|
||||
list-style-type: none;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -20,4 +20,20 @@ const app = new Vue({
|
|||
store,
|
||||
}).$mount('li[data-id="user_status-menuitem"]')
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
if (!OCA.Dashboard) {
|
||||
return
|
||||
}
|
||||
|
||||
OCA.Dashboard.registerStatus('status', (el) => {
|
||||
const Dashboard = Vue.extend(App)
|
||||
return new Dashboard({
|
||||
propsData: {
|
||||
inline: true,
|
||||
},
|
||||
store,
|
||||
}).$mount(el)
|
||||
})
|
||||
})
|
||||
|
||||
export { app }
|
||||
|
|
|
@ -170,13 +170,13 @@ return array(
|
|||
'OCP\\Dashboard\\IDashboardManager' => $baseDir . '/lib/public/Dashboard/IDashboardManager.php',
|
||||
'OCP\\Dashboard\\IDashboardWidget' => $baseDir . '/lib/public/Dashboard/IDashboardWidget.php',
|
||||
'OCP\\Dashboard\\IManager' => $baseDir . '/lib/public/Dashboard/IManager.php',
|
||||
'OCP\\Dashboard\\IPanel' => $baseDir . '/lib/public/Dashboard/IPanel.php',
|
||||
'OCP\\Dashboard\\IWidget' => $baseDir . '/lib/public/Dashboard/IWidget.php',
|
||||
'OCP\\Dashboard\\Model\\IWidgetConfig' => $baseDir . '/lib/public/Dashboard/Model/IWidgetConfig.php',
|
||||
'OCP\\Dashboard\\Model\\IWidgetRequest' => $baseDir . '/lib/public/Dashboard/Model/IWidgetRequest.php',
|
||||
'OCP\\Dashboard\\Model\\WidgetSetting' => $baseDir . '/lib/public/Dashboard/Model/WidgetSetting.php',
|
||||
'OCP\\Dashboard\\Model\\WidgetSetup' => $baseDir . '/lib/public/Dashboard/Model/WidgetSetup.php',
|
||||
'OCP\\Dashboard\\Model\\WidgetTemplate' => $baseDir . '/lib/public/Dashboard/Model/WidgetTemplate.php',
|
||||
'OCP\\Dashboard\\RegisterPanelEvent' => $baseDir . '/lib/public/Dashboard/RegisterPanelEvent.php',
|
||||
'OCP\\Dashboard\\RegisterWidgetEvent' => $baseDir . '/lib/public/Dashboard/RegisterWidgetEvent.php',
|
||||
'OCP\\Dashboard\\Service\\IEventsService' => $baseDir . '/lib/public/Dashboard/Service/IEventsService.php',
|
||||
'OCP\\Dashboard\\Service\\IWidgetsService' => $baseDir . '/lib/public/Dashboard/Service/IWidgetsService.php',
|
||||
'OCP\\Defaults' => $baseDir . '/lib/public/Defaults.php',
|
||||
|
|
|
@ -199,13 +199,13 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
|
|||
'OCP\\Dashboard\\IDashboardManager' => __DIR__ . '/../../..' . '/lib/public/Dashboard/IDashboardManager.php',
|
||||
'OCP\\Dashboard\\IDashboardWidget' => __DIR__ . '/../../..' . '/lib/public/Dashboard/IDashboardWidget.php',
|
||||
'OCP\\Dashboard\\IManager' => __DIR__ . '/../../..' . '/lib/public/Dashboard/IManager.php',
|
||||
'OCP\\Dashboard\\IPanel' => __DIR__ . '/../../..' . '/lib/public/Dashboard/IPanel.php',
|
||||
'OCP\\Dashboard\\IWidget' => __DIR__ . '/../../..' . '/lib/public/Dashboard/IWidget.php',
|
||||
'OCP\\Dashboard\\Model\\IWidgetConfig' => __DIR__ . '/../../..' . '/lib/public/Dashboard/Model/IWidgetConfig.php',
|
||||
'OCP\\Dashboard\\Model\\IWidgetRequest' => __DIR__ . '/../../..' . '/lib/public/Dashboard/Model/IWidgetRequest.php',
|
||||
'OCP\\Dashboard\\Model\\WidgetSetting' => __DIR__ . '/../../..' . '/lib/public/Dashboard/Model/WidgetSetting.php',
|
||||
'OCP\\Dashboard\\Model\\WidgetSetup' => __DIR__ . '/../../..' . '/lib/public/Dashboard/Model/WidgetSetup.php',
|
||||
'OCP\\Dashboard\\Model\\WidgetTemplate' => __DIR__ . '/../../..' . '/lib/public/Dashboard/Model/WidgetTemplate.php',
|
||||
'OCP\\Dashboard\\RegisterPanelEvent' => __DIR__ . '/../../..' . '/lib/public/Dashboard/RegisterPanelEvent.php',
|
||||
'OCP\\Dashboard\\RegisterWidgetEvent' => __DIR__ . '/../../..' . '/lib/public/Dashboard/RegisterWidgetEvent.php',
|
||||
'OCP\\Dashboard\\Service\\IEventsService' => __DIR__ . '/../../..' . '/lib/public/Dashboard/Service/IEventsService.php',
|
||||
'OCP\\Dashboard\\Service\\IWidgetsService' => __DIR__ . '/../../..' . '/lib/public/Dashboard/Service/IWidgetsService.php',
|
||||
'OCP\\Defaults' => __DIR__ . '/../../..' . '/lib/public/Defaults.php',
|
||||
|
|
|
@ -100,10 +100,10 @@ class RegistrationContext {
|
|||
);
|
||||
}
|
||||
|
||||
public function registerDashboardPanel(string $panelClass): void {
|
||||
public function registerDashboardWidget(string $widgetClass): void {
|
||||
$this->context->registerDashboardPanel(
|
||||
$this->appId,
|
||||
$panelClass
|
||||
$widgetClass
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -282,7 +282,7 @@ class RegistrationContext {
|
|||
public function delegateDashboardPanelRegistrations(array $apps, IManager $dashboardManager): void {
|
||||
foreach ($this->dashboardPanels as $panel) {
|
||||
try {
|
||||
$dashboardManager->lazyRegisterPanel($panel['class']);
|
||||
$dashboardManager->lazyRegisterWidget($panel['class']);
|
||||
} catch (Throwable $e) {
|
||||
$appId = $panel['appId'];
|
||||
$this->logger->logException($e, [
|
||||
|
|
|
@ -29,7 +29,7 @@ namespace OC\Dashboard;
|
|||
use InvalidArgumentException;
|
||||
use OCP\AppFramework\QueryException;
|
||||
use OCP\Dashboard\IManager;
|
||||
use OCP\Dashboard\IPanel;
|
||||
use OCP\Dashboard\IWidget;
|
||||
use OCP\ILogger;
|
||||
use OCP\IServerContainer;
|
||||
use Throwable;
|
||||
|
@ -37,10 +37,10 @@ use Throwable;
|
|||
class Manager implements IManager {
|
||||
|
||||
/** @var array */
|
||||
private $lazyPanels = [];
|
||||
private $lazyWidgets = [];
|
||||
|
||||
/** @var IPanel[] */
|
||||
private $panels = [];
|
||||
/** @var IWidget[] */
|
||||
private $widgets = [];
|
||||
|
||||
/** @var IServerContainer */
|
||||
private $serverContainer;
|
||||
|
@ -49,31 +49,31 @@ class Manager implements IManager {
|
|||
$this->serverContainer = $serverContainer;
|
||||
}
|
||||
|
||||
private function registerPanel(IPanel $panel): void {
|
||||
if (array_key_exists($panel->getId(), $this->panels)) {
|
||||
throw new InvalidArgumentException('Dashboard panel with this id has already been registered');
|
||||
private function registerWidget(IWidget $widget): void {
|
||||
if (array_key_exists($widget->getId(), $this->widgets)) {
|
||||
throw new InvalidArgumentException('Dashboard widget with this id has already been registered');
|
||||
}
|
||||
|
||||
$this->panels[$panel->getId()] = $panel;
|
||||
$this->widgets[$widget->getId()] = $widget;
|
||||
}
|
||||
|
||||
public function lazyRegisterPanel(string $panelClass): void {
|
||||
$this->lazyPanels[] = $panelClass;
|
||||
public function lazyRegisterWidget(string $widgetClass): void {
|
||||
$this->lazyWidgets[] = $widgetClass;
|
||||
}
|
||||
|
||||
public function loadLazyPanels(): void {
|
||||
$classes = $this->lazyPanels;
|
||||
$classes = $this->lazyWidgets;
|
||||
foreach ($classes as $class) {
|
||||
try {
|
||||
/** @var IPanel $panel */
|
||||
$panel = $this->serverContainer->query($class);
|
||||
/** @var IWidget $widget */
|
||||
$widget = $this->serverContainer->query($class);
|
||||
} catch (QueryException $e) {
|
||||
/*
|
||||
* There is a circular dependency between the logger and the registry, so
|
||||
* we can not inject it. Thus the static call.
|
||||
*/
|
||||
\OC::$server->getLogger()->logException($e, [
|
||||
'message' => 'Could not load lazy dashbaord panel: ' . $e->getMessage(),
|
||||
'message' => 'Could not load lazy dashbaord widget: ' . $e->getMessage(),
|
||||
'level' => ILogger::FATAL,
|
||||
]);
|
||||
}
|
||||
|
@ -82,32 +82,32 @@ class Manager implements IManager {
|
|||
* type, so we might get a TypeError here that we should catch.
|
||||
*/
|
||||
try {
|
||||
$this->registerPanel($panel);
|
||||
$this->registerWidget($widget);
|
||||
} catch (Throwable $e) {
|
||||
/*
|
||||
* There is a circular dependency between the logger and the registry, so
|
||||
* we can not inject it. Thus the static call.
|
||||
*/
|
||||
\OC::$server->getLogger()->logException($e, [
|
||||
'message' => 'Could not register lazy dashboard panel: ' . $e->getMessage(),
|
||||
'message' => 'Could not register lazy dashboard widget: ' . $e->getMessage(),
|
||||
'level' => ILogger::FATAL,
|
||||
]);
|
||||
}
|
||||
|
||||
try {
|
||||
$panel->load();
|
||||
$widget->load();
|
||||
} catch (Throwable $e) {
|
||||
\OC::$server->getLogger()->logException($e, [
|
||||
'message' => 'Error during dashboard panel loading: ' . $e->getMessage(),
|
||||
'message' => 'Error during dashboard widget loading: ' . $e->getMessage(),
|
||||
'level' => ILogger::FATAL,
|
||||
]);
|
||||
}
|
||||
}
|
||||
$this->lazyPanels = [];
|
||||
$this->lazyWidgets = [];
|
||||
}
|
||||
|
||||
public function getPanels(): array {
|
||||
public function getWidgets(): array {
|
||||
$this->loadLazyPanels();
|
||||
return $this->panels;
|
||||
return $this->widgets;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,6 +68,7 @@ use OCP\IConfig;
|
|||
use OCP\IGroupManager;
|
||||
use OCP\ILogger;
|
||||
use OCP\IUser;
|
||||
use OCP\IUserSession;
|
||||
|
||||
class OC_Util {
|
||||
public static $scripts = [];
|
||||
|
@ -1088,6 +1089,8 @@ class OC_Util {
|
|||
* @suppress PhanDeprecatedFunction
|
||||
*/
|
||||
public static function getDefaultPageUrl() {
|
||||
/** @var IConfig $config */
|
||||
$config = \OC::$server->get(IConfig::class);
|
||||
$urlGenerator = \OC::$server->getURLGenerator();
|
||||
// Deny the redirect if the URL contains a @
|
||||
// This prevents unvalidated redirects like ?redirect_url=:user@domain.com
|
||||
|
@ -1099,8 +1102,16 @@ class OC_Util {
|
|||
$location = $urlGenerator->getAbsoluteURL($defaultPage);
|
||||
} else {
|
||||
$appId = 'files';
|
||||
$config = \OC::$server->getConfig();
|
||||
$defaultApps = explode(',', $config->getSystemValue('defaultapp', 'files'));
|
||||
$defaultApps = explode(',', $config->getSystemValue('defaultapp', 'dashboard,files'));
|
||||
|
||||
/** @var IUserSession $userSession */
|
||||
$userSession = \OC::$server->get(IUserSession::class);
|
||||
$user = $userSession->getUser();
|
||||
if ($user) {
|
||||
$userDefaultApps = explode(',', $config->getUserValue($user->getUID(), 'core', 'defaultapp'));
|
||||
$defaultApps = array_filter(array_merge($userDefaultApps, $defaultApps));
|
||||
}
|
||||
|
||||
// find the first app that is enabled for the current user
|
||||
foreach ($defaultApps as $defaultApp) {
|
||||
$defaultApp = OC_App::cleanAppId(strip_tags($defaultApp));
|
||||
|
|
|
@ -59,11 +59,11 @@ interface IRegistrationContext {
|
|||
* Register an implementation of \OCP\Dashboard\IPanel that
|
||||
* will handle the implementation of a dashboard panel
|
||||
*
|
||||
* @param string $panelClass
|
||||
* @param string $widgetClass
|
||||
* @return void
|
||||
* @since 20.0.0
|
||||
*/
|
||||
public function registerDashboardPanel(string $panelClass): void;
|
||||
public function registerDashboardWidget(string $widgetClass): void;
|
||||
/**
|
||||
* Register a service
|
||||
*
|
||||
|
|
|
@ -35,15 +35,15 @@ namespace OCP\Dashboard;
|
|||
interface IManager {
|
||||
|
||||
/**
|
||||
* @param string $panelClass
|
||||
* @param string $widgetClass
|
||||
* @since 20.0.0
|
||||
*/
|
||||
public function lazyRegisterPanel(string $panelClass): void;
|
||||
public function lazyRegisterWidget(string $widgetClass): void;
|
||||
|
||||
/**
|
||||
* @since 20.0.0
|
||||
*
|
||||
* @return IPanel[]
|
||||
* @return IWidget[]
|
||||
*/
|
||||
public function getPanels(): array;
|
||||
public function getWidgets(): array;
|
||||
}
|
||||
|
|
|
@ -27,33 +27,33 @@ declare(strict_types=1);
|
|||
namespace OCP\Dashboard;
|
||||
|
||||
/**
|
||||
* Interface IPanel
|
||||
* Interface IWidget
|
||||
*
|
||||
* @package OCP\Dashboard
|
||||
* @since 20.0.0
|
||||
*/
|
||||
interface IPanel {
|
||||
interface IWidget {
|
||||
|
||||
/**
|
||||
* @return string Unique id that identifies the panel, e.g. the app id
|
||||
* @return string Unique id that identifies the widget, e.g. the app id
|
||||
* @since 20.0.0
|
||||
*/
|
||||
public function getId(): string;
|
||||
|
||||
/**
|
||||
* @return string User facing title of the panel
|
||||
* @return string User facing title of the widget
|
||||
* @since 20.0.0
|
||||
*/
|
||||
public function getTitle(): string;
|
||||
|
||||
/**
|
||||
* @return int Initial order for panel sorting
|
||||
* @return int Initial order for widget sorting
|
||||
* @since 20.0.0
|
||||
*/
|
||||
public function getOrder(): int;
|
||||
|
||||
/**
|
||||
* @return string css class that displays an icon next to the panel title
|
||||
* @return string css class that displays an icon next to the widget title
|
||||
* @since 20.0.0
|
||||
*/
|
||||
public function getIconClass(): string;
|
||||
|
@ -65,7 +65,7 @@ interface IPanel {
|
|||
public function getUrl(): ?string;
|
||||
|
||||
/**
|
||||
* Execute panel bootstrap code like loading scripts and providing initial state
|
||||
* Execute widget bootstrap code like loading scripts and providing initial state
|
||||
* @since 20.0.0
|
||||
*/
|
||||
public function load(): void;
|
|
@ -40,7 +40,7 @@ use OCP\EventDispatcher\Event;
|
|||
* @since 20.0.0
|
||||
* @deprecated 20.0.0
|
||||
*/
|
||||
class RegisterPanelEvent extends Event {
|
||||
class RegisterWidgetEvent extends Event {
|
||||
private $manager;
|
||||
|
||||
public function __construct(IManager $manager) {
|
||||
|
@ -53,7 +53,7 @@ class RegisterPanelEvent extends Event {
|
|||
* @param string $panelClass
|
||||
* @since 20.0.0
|
||||
*/
|
||||
public function registerPanel(string $panelClass) {
|
||||
$this->manager->lazyRegisterPanel($panelClass);
|
||||
public function registerWidget(string $panelClass) {
|
||||
$this->manager->lazyRegisterWidget($panelClass);
|
||||
}
|
||||
}
|
|
@ -8900,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",
|
||||
|
@ -9963,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",
|
||||
|
|
|
@ -82,6 +82,7 @@
|
|||
"vue-material-design-icons": "^4.8.0",
|
||||
"vue-multiselect": "^2.1.6",
|
||||
"vue-router": "^3.3.4",
|
||||
"vuedraggable": "^2.24.0",
|
||||
"vuex": "^3.5.1",
|
||||
"vuex-router-sync": "^5.0.0"
|
||||
},
|
||||
|
|
|
@ -341,6 +341,7 @@
|
|||
|
||||
<xs:simpleType name="category">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="dashboard"/>
|
||||
<xs:enumeration value="security"/>
|
||||
<xs:enumeration value="customization"/>
|
||||
<xs:enumeration value="files"/>
|
||||
|
|
|
@ -341,6 +341,7 @@
|
|||
|
||||
<xs:simpleType name="category">
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:enumeration value="dashboard"/>
|
||||
<xs:enumeration value="security"/>
|
||||
<xs:enumeration value="customization"/>
|
||||
<xs:enumeration value="files"/>
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue