mirror of https://github.com/nextcloud/server
feat(dashboard): implement widget item api v2
This API enables the dashboard to render all widgets from the API data alone without having apps to provide their own bundles. This saves a lot of traffic and execution time as a lot less javascript has to be parsed on the frontend. Signed-off-by: Richard Steinmetz <richard@steinmetz.cloud>
This commit is contained in:
parent
2a435e8bd2
commit
3fd18ce450
|
@ -7,6 +7,7 @@ declare(strict_types=1);
|
|||
*
|
||||
* @author Julien Veyssier <eneiluj@posteo.net>
|
||||
* @author Julius Härtl <jus@bitgrid.net>
|
||||
* @author Richard Steinmetz <richard@steinmetz.cloud>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
|
@ -33,5 +34,6 @@ return [
|
|||
'ocs' => [
|
||||
['name' => 'dashboardApi#getWidgets', 'url' => '/api/v1/widgets', 'verb' => 'GET'],
|
||||
['name' => 'dashboardApi#getWidgetItems', 'url' => '/api/v1/widget-items', 'verb' => 'GET'],
|
||||
['name' => 'dashboardApi#getWidgetItemsV2', 'url' => '/api/v2/widget-items', 'verb' => 'GET'],
|
||||
]
|
||||
];
|
||||
|
|
|
@ -6,6 +6,7 @@ declare(strict_types=1);
|
|||
* @copyright Copyright (c) 2021 Julien Veyssier <eneiluj@posteo.net>
|
||||
*
|
||||
* @author Julien Veyssier <eneiluj@posteo.net>
|
||||
* @author Richard Steinmetz <richard@steinmetz.cloud>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
|
@ -32,6 +33,7 @@ use OCP\Dashboard\IButtonWidget;
|
|||
use OCP\Dashboard\IIconWidget;
|
||||
use OCP\Dashboard\IOptionWidget;
|
||||
use OCP\Dashboard\IManager;
|
||||
use OCP\Dashboard\IReloadableWidget;
|
||||
use OCP\Dashboard\IWidget;
|
||||
use OCP\Dashboard\Model\WidgetButton;
|
||||
use OCP\Dashboard\Model\WidgetOptions;
|
||||
|
@ -39,7 +41,9 @@ use OCP\IConfig;
|
|||
use OCP\IRequest;
|
||||
|
||||
use OCP\Dashboard\IAPIWidget;
|
||||
use OCP\Dashboard\IAPIWidgetV2;
|
||||
use OCP\Dashboard\Model\WidgetItem;
|
||||
use OCP\Dashboard\Model\WidgetItems;
|
||||
|
||||
class DashboardApiController extends OCSController {
|
||||
|
||||
|
@ -68,6 +72,24 @@ class DashboardApiController extends OCSController {
|
|||
* Example request with Curl:
|
||||
* curl -u user:passwd http://my.nc/ocs/v2.php/apps/dashboard/api/v1/widget-items -H Content-Type:application/json -X GET -d '{"sinceIds":{"github_notifications":"2021-03-22T15:01:10Z"}}'
|
||||
*
|
||||
* @param string[] $widgetIds Limit widgets to given ids
|
||||
* @return IWidget[]
|
||||
*/
|
||||
private function getShownWidgets(array $widgetIds): array {
|
||||
if (empty($widgetIds)) {
|
||||
$systemDefault = $this->config->getAppValue('dashboard', 'layout', 'recommendations,spreed,mail,calendar');
|
||||
$widgetIds = explode(',', $this->config->getUserValue($this->userId, 'dashboard', 'layout', $systemDefault));
|
||||
}
|
||||
|
||||
return array_filter(
|
||||
$this->dashboardManager->getWidgets(),
|
||||
static function (IWidget $widget) use ($widgetIds) {
|
||||
return in_array($widget->getId(), $widgetIds);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $sinceIds Array indexed by widget Ids, contains date/id from which we want the new items
|
||||
* @param int $limit Limit number of result items per widget
|
||||
* @param string[] $widgets Limit results to specific widgets
|
||||
|
@ -76,18 +98,11 @@ class DashboardApiController extends OCSController {
|
|||
* @NoCSRFRequired
|
||||
*/
|
||||
public function getWidgetItems(array $sinceIds = [], int $limit = 7, array $widgets = []): DataResponse {
|
||||
$showWidgets = $widgets;
|
||||
$items = [];
|
||||
|
||||
if (empty($showWidgets)) {
|
||||
$systemDefault = $this->config->getAppValue('dashboard', 'layout', 'recommendations,spreed,mail,calendar');
|
||||
$showWidgets = explode(',', $this->config->getUserValue($this->userId, 'dashboard', 'layout', $systemDefault));
|
||||
}
|
||||
|
||||
$widgets = $this->dashboardManager->getWidgets();
|
||||
$widgets = $this->getShownWidgets($widgets);
|
||||
foreach ($widgets as $widget) {
|
||||
if ($widget instanceof IAPIWidget && in_array($widget->getId(), $showWidgets)) {
|
||||
$items[$widget->getId()] = array_map(function (WidgetItem $item) {
|
||||
if ($widget instanceof IAPIWidget) {
|
||||
$items[$widget->getId()] = array_map(static function (WidgetItem $item) {
|
||||
return $item->jsonSerialize();
|
||||
}, $widget->getItems($this->userId, $sinceIds[$widget->getId()] ?? null, $limit));
|
||||
}
|
||||
|
@ -102,6 +117,33 @@ class DashboardApiController extends OCSController {
|
|||
*
|
||||
* @NoAdminRequired
|
||||
* @NoCSRFRequired
|
||||
*
|
||||
* Get the items for the widgets
|
||||
*
|
||||
* @param array<string, string> $sinceIds Array indexed by widget Ids, contains date/id from which we want the new items
|
||||
* @param int $limit Limit number of result items per widget
|
||||
* @param string[] $widgets Limit results to specific widgets
|
||||
* @return DataResponse<Http::STATUS_OK, array<string, DashboardWidgetItems>, array{}>
|
||||
*/
|
||||
public function getWidgetItemsV2(array $sinceIds = [], int $limit = 7, array $widgets = []): DataResponse {
|
||||
$items = [];
|
||||
$widgets = $this->getShownWidgets($widgets);
|
||||
foreach ($widgets as $widget) {
|
||||
if ($widget instanceof IAPIWidgetV2) {
|
||||
$items[$widget->getId()] = $widget
|
||||
->getItemsV2($this->userId, $sinceIds[$widget->getId()] ?? null, $limit)
|
||||
->jsonSerialize();
|
||||
}
|
||||
}
|
||||
|
||||
return new DataResponse($items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the widgets
|
||||
*
|
||||
* @NoAdminRequired
|
||||
* @NoCSRFRequired
|
||||
*/
|
||||
public function getWidgets(): DataResponse {
|
||||
$widgets = $this->dashboardManager->getWidgets();
|
||||
|
@ -116,6 +158,8 @@ class DashboardApiController extends OCSController {
|
|||
'icon_url' => ($widget instanceof IIconWidget) ? $widget->getIconUrl() : '',
|
||||
'widget_url' => $widget->getUrl(),
|
||||
'item_icons_round' => $options->withRoundItemIcons(),
|
||||
'item_api_versions' => [],
|
||||
'reload_interval' => 0,
|
||||
];
|
||||
if ($widget instanceof IButtonWidget) {
|
||||
$data += [
|
||||
|
@ -128,6 +172,15 @@ class DashboardApiController extends OCSController {
|
|||
}, $widget->getWidgetButtons($this->userId)),
|
||||
];
|
||||
}
|
||||
if ($widget instanceof IReloadableWidget) {
|
||||
$data['reload_interval'] = $widget->getReloadInterval();
|
||||
}
|
||||
if ($widget instanceof IAPIWidget) {
|
||||
$data['item_api_versions'][] = 1;
|
||||
}
|
||||
if ($widget instanceof IAPIWidgetV2) {
|
||||
$data['item_api_versions'][] = 2;
|
||||
}
|
||||
return $data;
|
||||
}, $widgets);
|
||||
|
||||
|
|
|
@ -1,293 +0,0 @@
|
|||
{
|
||||
"openapi": "3.0.3",
|
||||
"info": {
|
||||
"title": "dashboard",
|
||||
"version": "0.0.1",
|
||||
"description": "Dashboard app",
|
||||
"license": {
|
||||
"name": "agpl"
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"securitySchemes": {
|
||||
"basic_auth": {
|
||||
"type": "http",
|
||||
"scheme": "basic"
|
||||
},
|
||||
"bearer_auth": {
|
||||
"type": "http",
|
||||
"scheme": "bearer"
|
||||
}
|
||||
},
|
||||
"schemas": {
|
||||
"OCSMeta": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"status",
|
||||
"statuscode"
|
||||
],
|
||||
"properties": {
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"statuscode": {
|
||||
"type": "integer"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"totalitems": {
|
||||
"type": "string"
|
||||
},
|
||||
"itemsperpage": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Widget": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id",
|
||||
"title",
|
||||
"order",
|
||||
"icon_class",
|
||||
"icon_url",
|
||||
"widget_url",
|
||||
"item_icons_round"
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"order": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"icon_class": {
|
||||
"type": "string"
|
||||
},
|
||||
"icon_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"widget_url": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"item_icons_round": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"buttons": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type",
|
||||
"text",
|
||||
"link"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"text": {
|
||||
"type": "string"
|
||||
},
|
||||
"link": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"WidgetItem": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"subtitle",
|
||||
"title",
|
||||
"link",
|
||||
"iconUrl",
|
||||
"sinceId"
|
||||
],
|
||||
"properties": {
|
||||
"subtitle": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"link": {
|
||||
"type": "string"
|
||||
},
|
||||
"iconUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"sinceId": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"paths": {
|
||||
"/ocs/v2.php/apps/dashboard/api/v1/widgets": {
|
||||
"get": {
|
||||
"operationId": "dashboard_api-get-widgets",
|
||||
"summary": "Get the widgets",
|
||||
"tags": [
|
||||
"dashboard_api"
|
||||
],
|
||||
"security": [
|
||||
{
|
||||
"bearer_auth": []
|
||||
},
|
||||
{
|
||||
"basic_auth": []
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "OCS-APIRequest",
|
||||
"in": "header",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"default": "true"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Widget"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/ocs/v2.php/apps/dashboard/api/v1/widget-items": {
|
||||
"get": {
|
||||
"operationId": "dashboard_api-get-widget-items",
|
||||
"summary": "Get the items for the widgets",
|
||||
"tags": [
|
||||
"dashboard_api"
|
||||
],
|
||||
"security": [
|
||||
{
|
||||
"bearer_auth": []
|
||||
},
|
||||
{
|
||||
"basic_auth": []
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "sinceIds",
|
||||
"in": "query",
|
||||
"description": "Array indexed by widget Ids, contains date/id from which we want the new items",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "limit",
|
||||
"in": "query",
|
||||
"description": "Limit number of result items per widget",
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"default": 7
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "widgets",
|
||||
"in": "query",
|
||||
"description": "Limit results to specific widgets",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "OCS-APIRequest",
|
||||
"in": "header",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"default": "true"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/WidgetItem"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": []
|
||||
}
|
|
@ -14,21 +14,44 @@
|
|||
v-bind="{swapThreshold: 0.30, delay: 500, delayOnTouchOnly: true, touchStartThreshold: 3}"
|
||||
handle=".panel--header"
|
||||
@end="saveLayout">
|
||||
<div v-for="panelId in layout" :key="panels[panelId].id" class="panel">
|
||||
<div class="panel--header">
|
||||
<h2>
|
||||
<div aria-labelledby="panel--header--icon--description"
|
||||
aria-hidden="true"
|
||||
:class="panels[panelId].iconClass"
|
||||
role="img" />
|
||||
{{ panels[panelId].title }}
|
||||
</h2>
|
||||
<span id="panel--header--icon--description" class="hidden-visually"> {{ t('dashboard', '"{title} icon"', { title: panels[panelId].title }) }} </span>
|
||||
<template v-for="panelId in layout">
|
||||
<div v-if="isApiWidgetV2(panels[panelId].id)"
|
||||
:key="`${panels[panelId].id}-v2`"
|
||||
class="panel">
|
||||
<div class="panel--header">
|
||||
<h2>
|
||||
<div aria-labelledby="panel--header--icon--description"
|
||||
aria-hidden="true"
|
||||
:class="apiWidgets[panels[panelId].id].icon_class"
|
||||
role="img" />
|
||||
{{ apiWidgets[panels[panelId].id].title }}
|
||||
</h2>
|
||||
<span id="panel--header--icon--description" class="hidden-visually">
|
||||
{{ t('dashboard', '"{title} icon"', { title: apiWidgets[panels[panelId].id].title }) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="panel--content">
|
||||
<ApiDashboardWidget :widget="apiWidgets[panels[panelId].id]"
|
||||
:data="apiWidgetItems[panels[panelId].id]"
|
||||
:loading="loadingItems" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel--content" :class="{ loading: !panels[panelId].mounted }">
|
||||
<div :ref="panels[panelId].id" :data-id="panels[panelId].id" />
|
||||
<div v-else :key="panels[panelId].id" class="panel">
|
||||
<div class="panel--header">
|
||||
<h2>
|
||||
<div aria-labelledby="panel--header--icon--description"
|
||||
aria-hidden="true"
|
||||
:class="panels[panelId].iconClass"
|
||||
role="img" />
|
||||
{{ panels[panelId].title }}
|
||||
</h2>
|
||||
<span id="panel--header--icon--description" class="hidden-visually"> {{ t('dashboard', '"{title} icon"', { title: panels[panelId].title }) }} </span>
|
||||
</div>
|
||||
<div class="panel--content" :class="{ loading: !panels[panelId].mounted }">
|
||||
<div :ref="panels[panelId].id" :data-id="panels[panelId].id" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Draggable>
|
||||
|
||||
<div class="footer">
|
||||
|
@ -94,7 +117,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
import { generateUrl, generateOcsUrl } from '@nextcloud/router'
|
||||
import { getCurrentUser } from '@nextcloud/auth'
|
||||
import { loadState } from '@nextcloud/initial-state'
|
||||
import axios from '@nextcloud/axios'
|
||||
|
@ -105,6 +128,7 @@ import Pencil from 'vue-material-design-icons/Pencil.vue'
|
|||
import Vue from 'vue'
|
||||
|
||||
import isMobile from './mixins/isMobile.js'
|
||||
import ApiDashboardWidget from './components/ApiDashboardWidget.vue'
|
||||
|
||||
const panels = loadState('dashboard', 'panels')
|
||||
const firstRun = loadState('dashboard', 'firstRun')
|
||||
|
@ -123,6 +147,7 @@ const statusInfo = {
|
|||
export default {
|
||||
name: 'DashboardApp',
|
||||
components: {
|
||||
ApiDashboardWidget,
|
||||
NcButton,
|
||||
Draggable,
|
||||
NcModal,
|
||||
|
@ -150,6 +175,9 @@ export default {
|
|||
modal: false,
|
||||
appStoreUrl: generateUrl('/settings/apps/dashboard'),
|
||||
statuses: {},
|
||||
apiWidgets: [],
|
||||
apiWidgetItems: {},
|
||||
loadingItems: true,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -239,6 +267,23 @@ export default {
|
|||
},
|
||||
},
|
||||
|
||||
async created() {
|
||||
await this.fetchApiWidgets()
|
||||
|
||||
const apiWidgetIdsToFetch = Object
|
||||
.values(this.apiWidgets)
|
||||
.filter(widget => this.isApiWidgetV2(widget.id))
|
||||
.map(widget => widget.id)
|
||||
await Promise.all(apiWidgetIdsToFetch.map(id => this.fetchApiWidgetItems([id], true)))
|
||||
|
||||
for (const widget of Object.values(this.apiWidgets)) {
|
||||
if (widget.reload_interval > 0) {
|
||||
setInterval(async () => {
|
||||
await this.fetchApiWidgetItems([widget.id], true)
|
||||
}, widget.reload_interval * 1000)
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.updateSkipLink()
|
||||
window.addEventListener('scroll', this.handleScroll)
|
||||
|
@ -278,6 +323,11 @@ export default {
|
|||
},
|
||||
rerenderPanels() {
|
||||
for (const app in this.callbacks) {
|
||||
// TODO: Properly rerender v2 widgets
|
||||
if (this.isApiWidgetV2(this.panels[app].id)) {
|
||||
continue
|
||||
}
|
||||
|
||||
const element = this.$refs[app]
|
||||
if (this.layout.indexOf(app) === -1) {
|
||||
continue
|
||||
|
@ -374,6 +424,33 @@ export default {
|
|||
document.body.classList.remove('dashboard--scrolled')
|
||||
}
|
||||
},
|
||||
async fetchApiWidgets() {
|
||||
const response = await axios.get(generateOcsUrl('/apps/dashboard/api/v1/widgets'))
|
||||
this.apiWidgets = response.data.ocs.data
|
||||
},
|
||||
async fetchApiWidgetItems(widgetIds, merge = false) {
|
||||
try {
|
||||
const url = generateOcsUrl('/apps/dashboard/api/v2/widget-items')
|
||||
const params = new URLSearchParams(widgetIds.map(id => ['widgets[]', id]))
|
||||
const response = await axios.get(`${url}?${params.toString()}`)
|
||||
const widgetItems = response.data.ocs.data
|
||||
if (merge) {
|
||||
this.apiWidgetItems = Object.assign({}, this.apiWidgetItems, widgetItems)
|
||||
} else {
|
||||
this.apiWidgetItems = widgetItems
|
||||
}
|
||||
} finally {
|
||||
this.loadingItems = false
|
||||
}
|
||||
},
|
||||
isApiWidgetV2(id) {
|
||||
for (const widget of Object.values(this.apiWidgets)) {
|
||||
if (widget.id === id && widget.item_api_versions.includes(2)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
@ -470,6 +547,7 @@ export default {
|
|||
margin-right: 16px;
|
||||
background-position: center;
|
||||
float: left;
|
||||
margin-top: -6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,140 @@
|
|||
<!--
|
||||
- @copyright Copyright (c) 2023 Richard Steinmetz <richard@steinmetz.cloud>
|
||||
-
|
||||
- @author Richard Steinmetz <richard@steinmetz.cloud>
|
||||
-
|
||||
- @license AGPL-3.0-or-later
|
||||
-
|
||||
- This program is free software: you can redistribute it and/or modify
|
||||
- it under the terms of the GNU General Public License as published by
|
||||
- the Free Software Foundation, either version 3 of the License, or
|
||||
- (at your option) any later version.
|
||||
-
|
||||
- This program is distributed in the hope that it will be useful,
|
||||
- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
- GNU General Public License for more details.
|
||||
-
|
||||
- You should have received a copy of the GNU General Public License
|
||||
- along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-
|
||||
-->
|
||||
|
||||
<template>
|
||||
<NcDashboardWidget :items="items"
|
||||
:show-more-label="showMoreLabel"
|
||||
:show-more-url="showMoreUrl"
|
||||
:loading="loading"
|
||||
:show-items-and-empty-content="!!halfEmptyContentMessage"
|
||||
:half-empty-content-message="halfEmptyContentMessage">
|
||||
<template #default="{ item }">
|
||||
<NcDashboardWidgetItem :target-url="item.link"
|
||||
:overlay-icon-url="item.overlayIconUrl ? item.overlayIconUrl : ''"
|
||||
:main-text="item.title"
|
||||
:sub-text="item.subtitle">
|
||||
<template #avatar>
|
||||
<template v-if="item.iconUrl">
|
||||
<NcAvatar :size="44" :url="item.iconUrl" />
|
||||
</template>
|
||||
</template>
|
||||
</NcDashboardWidgetItem>
|
||||
</template>
|
||||
<template #empty-content>
|
||||
<NcEmptyContent v-if="items.length === 0"
|
||||
:description="emptyContentMessage">
|
||||
<template #icon>
|
||||
<CheckIcon v-if="emptyContentMessage" :size="65" />
|
||||
</template>
|
||||
<template #action>
|
||||
<NcButton v-if="setupButton" :href="setupButton.link">
|
||||
{{ setupButton.text }}
|
||||
</NcButton>
|
||||
</template>
|
||||
</NcEmptyContent>
|
||||
</template>
|
||||
</NcDashboardWidget>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
NcAvatar,
|
||||
NcDashboardWidget,
|
||||
NcDashboardWidgetItem,
|
||||
NcEmptyContent,
|
||||
NcButton,
|
||||
} from '@nextcloud/vue'
|
||||
import CheckIcon from 'vue-material-design-icons/Check.vue'
|
||||
|
||||
export default {
|
||||
name: 'ApiDashboardWidget',
|
||||
components: {
|
||||
NcAvatar,
|
||||
NcDashboardWidget,
|
||||
NcDashboardWidgetItem,
|
||||
NcEmptyContent,
|
||||
NcButton,
|
||||
CheckIcon,
|
||||
},
|
||||
props: {
|
||||
widget: {
|
||||
type: [Object, undefined],
|
||||
default: undefined,
|
||||
},
|
||||
data: {
|
||||
type: [Object, undefined],
|
||||
default: undefined,
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
/** @return {object[]} */
|
||||
items() {
|
||||
return this.data?.items ?? []
|
||||
},
|
||||
|
||||
/** @return {string} */
|
||||
emptyContentMessage() {
|
||||
return this.data?.emptyContentMessage ?? ''
|
||||
},
|
||||
|
||||
/** @return {string} */
|
||||
halfEmptyContentMessage() {
|
||||
return this.data?.halfEmptyContentMessage ?? ''
|
||||
},
|
||||
|
||||
/** @return {object|undefined} */
|
||||
newButton() {
|
||||
// TODO: Render new button in the template
|
||||
// I couldn't find a widget that makes use of the button. Furthermore, there is no convenient
|
||||
// way to render such a button using the official widget component.
|
||||
return this.widget?.buttons?.find(button => button.type === 'new')
|
||||
},
|
||||
|
||||
/** @return {object|undefined} */
|
||||
moreButton() {
|
||||
return this.widget?.buttons?.find(button => button.type === 'more')
|
||||
},
|
||||
|
||||
/** @return {object|undefined} */
|
||||
setupButton() {
|
||||
return this.widget?.buttons?.find(button => button.type === 'setup')
|
||||
},
|
||||
|
||||
/** @return {string|undefined} */
|
||||
showMoreLabel() {
|
||||
return this.moreButton?.text
|
||||
},
|
||||
|
||||
/** @return {string|undefined} */
|
||||
showMoreUrl() {
|
||||
return this.moreButton?.link
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
|
@ -6,6 +6,7 @@ declare(strict_types=1);
|
|||
* @copyright Copyright (c) 2020, Georg Ehrke
|
||||
*
|
||||
* @author Georg Ehrke <oc.list@georgehrke.com>
|
||||
* @author Richard Steinmetz <richard@steinmetz.cloud>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
|
@ -31,9 +32,11 @@ use OCA\UserStatus\Service\StatusService;
|
|||
use OCP\AppFramework\Services\IInitialState;
|
||||
use OCP\Dashboard\IAPIWidget;
|
||||
use OCP\Dashboard\IButtonWidget;
|
||||
use OCP\Dashboard\IAPIWidgetV2;
|
||||
use OCP\Dashboard\IIconWidget;
|
||||
use OCP\Dashboard\IOptionWidget;
|
||||
use OCP\Dashboard\Model\WidgetItem;
|
||||
use OCP\Dashboard\Model\WidgetItems;
|
||||
use OCP\Dashboard\Model\WidgetOptions;
|
||||
use OCP\IDateTimeFormatter;
|
||||
use OCP\IL10N;
|
||||
|
@ -48,7 +51,7 @@ use OCP\Util;
|
|||
*
|
||||
* @package OCA\UserStatus
|
||||
*/
|
||||
class UserStatusWidget implements IAPIWidget, IIconWidget, IOptionWidget {
|
||||
class UserStatusWidget implements IAPIWidget, IAPIWidgetV2, IIconWidget, IOptionWidget {
|
||||
private IL10N $l10n;
|
||||
private IDateTimeFormatter $dateTimeFormatter;
|
||||
private IURLGenerator $urlGenerator;
|
||||
|
@ -132,17 +135,6 @@ class UserStatusWidget implements IAPIWidget, IIconWidget, IOptionWidget {
|
|||
* @inheritDoc
|
||||
*/
|
||||
public function load(): void {
|
||||
Util::addScript(Application::APP_ID, 'dashboard');
|
||||
|
||||
$currentUser = $this->userSession->getUser();
|
||||
if ($currentUser === null) {
|
||||
$this->initialStateService->provideInitialState('dashboard_data', []);
|
||||
return;
|
||||
}
|
||||
$currentUserId = $currentUser->getUID();
|
||||
|
||||
$widgetItemsData = $this->getWidgetData($currentUserId);
|
||||
$this->initialStateService->provideInitialState('dashboard_data', $widgetItemsData);
|
||||
}
|
||||
|
||||
private function getWidgetData(string $userId, ?string $since = null, int $limit = 7): array {
|
||||
|
@ -201,6 +193,17 @@ class UserStatusWidget implements IAPIWidget, IIconWidget, IOptionWidget {
|
|||
}, $widgetItemsData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getItemsV2(string $userId, ?string $since = null, int $limit = 7): WidgetItems {
|
||||
$items = $this->getItems($userId, $since, $limit);
|
||||
return new WidgetItems(
|
||||
$items,
|
||||
count($items) === 0 ? $this->l10n->t('No recent status changes') : '',
|
||||
);
|
||||
}
|
||||
|
||||
public function getWidgetOptions(): WidgetOptions {
|
||||
return new WidgetOptions(true);
|
||||
}
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
/**
|
||||
* @copyright Copyright (c) 2020 Georg Ehrke
|
||||
*
|
||||
* @author Georg Ehrke <oc.list@georgehrke.com>
|
||||
*
|
||||
* @license AGPL-3.0-or-later
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
import Vue from 'vue'
|
||||
import { getRequestToken } from '@nextcloud/auth'
|
||||
import { translate, translatePlural } from '@nextcloud/l10n'
|
||||
import Dashboard from './views/Dashboard.vue'
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
__webpack_nonce__ = btoa(getRequestToken())
|
||||
|
||||
Vue.prototype.t = translate
|
||||
Vue.prototype.n = translatePlural
|
||||
Vue.prototype.OC = OC
|
||||
Vue.prototype.OCA = OCA
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
OCA.Dashboard.register('user_status', (el) => {
|
||||
const View = Vue.extend(Dashboard)
|
||||
new View({
|
||||
propsData: {},
|
||||
}).$mount(el)
|
||||
})
|
||||
|
||||
})
|
|
@ -1,121 +0,0 @@
|
|||
<!--
|
||||
- @copyright Copyright (c) 2020 Georg Ehrke <oc.list@georgehrke.com>
|
||||
- @author Georg Ehrke <oc.list@georgehrke.com>
|
||||
-
|
||||
- @license GNU AGPL version 3 or any later version
|
||||
-
|
||||
- This program is free software: you can redistribute it and/or modify
|
||||
- it under the terms of the GNU Affero General Public License as
|
||||
- published by the Free Software Foundation, either version 3 of the
|
||||
- License, or (at your option) any later version.
|
||||
-
|
||||
- This program is distributed in the hope that it will be useful,
|
||||
- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
- GNU Affero General Public License for more details.
|
||||
-
|
||||
- You should have received a copy of the GNU Affero General Public License
|
||||
- along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-
|
||||
-->
|
||||
|
||||
<template>
|
||||
<NcDashboardWidget id="user-status_panel"
|
||||
:items="items"
|
||||
:loading="loading"
|
||||
:empty-content-message="t('user_status', 'No recent status changes')">
|
||||
<template #default="{ item }">
|
||||
<NcDashboardWidgetItem :main-text="item.mainText"
|
||||
:sub-text="item.subText">
|
||||
<template #avatar>
|
||||
<NcAvatar class="item-avatar"
|
||||
:size="44"
|
||||
:user="item.avatarUsername"
|
||||
:display-name="item.mainText"
|
||||
:show-user-status="false"
|
||||
:show-user-status-compact="false" />
|
||||
</template>
|
||||
</NcDashboardWidgetItem>
|
||||
</template>
|
||||
<template #emptyContentIcon>
|
||||
<div class="icon-user-status-dark" />
|
||||
</template>
|
||||
</NcDashboardWidget>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { loadState } from '@nextcloud/initial-state'
|
||||
import NcAvatar from '@nextcloud/vue/dist/Components/NcAvatar.js'
|
||||
import NcDashboardWidget from '@nextcloud/vue/dist/Components/NcDashboardWidget.js'
|
||||
import NcDashboardWidgetItem from '@nextcloud/vue/dist/Components/NcDashboardWidgetItem.js'
|
||||
import moment from '@nextcloud/moment'
|
||||
|
||||
export default {
|
||||
name: 'Dashboard',
|
||||
components: {
|
||||
NcAvatar,
|
||||
NcDashboardWidget,
|
||||
NcDashboardWidgetItem,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
statuses: [],
|
||||
loading: true,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
items() {
|
||||
return this.statuses.map((item) => {
|
||||
const icon = item.icon || ''
|
||||
let message = item.message || ''
|
||||
if (message === '') {
|
||||
if (item.status === 'away') {
|
||||
message = t('user_status', 'Away')
|
||||
}
|
||||
if (item.status === 'dnd') {
|
||||
message = t('user_status', 'Do not disturb')
|
||||
}
|
||||
}
|
||||
const status = item.icon !== '' ? `${icon} ${message}` : message
|
||||
|
||||
let subText
|
||||
if (item.icon === null && message === '' && item.timestamp === null) {
|
||||
subText = ''
|
||||
} else if (item.icon === null && message === '' && item.timestamp !== null) {
|
||||
subText = moment(item.timestamp, 'X').fromNow()
|
||||
} else if (item.timestamp !== null) {
|
||||
subText = this.t('user_status', '{status}, {timestamp}', {
|
||||
status,
|
||||
timestamp: moment(item.timestamp, 'X').fromNow(),
|
||||
}, null, { escape: false, sanitize: false })
|
||||
} else {
|
||||
subText = status
|
||||
}
|
||||
|
||||
return {
|
||||
mainText: item.displayName,
|
||||
subText,
|
||||
avatarUsername: item.userId,
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
try {
|
||||
this.statuses = loadState('user_status', 'dashboard_data')
|
||||
this.loading = false
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.icon-user-status-dark {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
background-size: 64px;
|
||||
filter: var(--background-invert-if-dark);
|
||||
}
|
||||
</style>
|
|
@ -7,6 +7,7 @@ declare(strict_types=1);
|
|||
*
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
* @author Georg Ehrke <oc.list@georgehrke.com>
|
||||
* @author Richard Steinmetz <richard@steinmetz.cloud>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
|
@ -27,13 +28,11 @@ declare(strict_types=1);
|
|||
namespace OCA\UserStatus\Tests\Dashboard;
|
||||
|
||||
use OCA\UserStatus\Dashboard\UserStatusWidget;
|
||||
use OCA\UserStatus\Db\UserStatus;
|
||||
use OCA\UserStatus\Service\StatusService;
|
||||
use OCP\AppFramework\Services\IInitialState;
|
||||
use OCP\IDateTimeFormatter;
|
||||
use OCP\IL10N;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\IUser;
|
||||
use OCP\IUserManager;
|
||||
use OCP\IUserSession;
|
||||
use Test\TestCase;
|
||||
|
@ -101,172 +100,4 @@ class UserStatusWidgetTest extends TestCase {
|
|||
public function testGetUrl(): void {
|
||||
$this->assertNull($this->widget->getUrl());
|
||||
}
|
||||
|
||||
public function testLoadNoUserSession(): void {
|
||||
$this->userSession->expects($this->once())
|
||||
->method('getUser')
|
||||
->willReturn(null);
|
||||
|
||||
$this->initialState->expects($this->once())
|
||||
->method('provideInitialState')
|
||||
->with('dashboard_data', []);
|
||||
|
||||
$this->service->expects($this->never())
|
||||
->method('findAllRecentStatusChanges');
|
||||
|
||||
$this->widget->load();
|
||||
}
|
||||
|
||||
public function testLoadWithCurrentUser(): void {
|
||||
$user = $this->createMock(IUser::class);
|
||||
$user->method('getUid')->willReturn('john.doe');
|
||||
$this->userSession->expects($this->once())
|
||||
->method('getUser')
|
||||
->willReturn($user);
|
||||
|
||||
$user1 = $this->createMock(IUser::class);
|
||||
$user1->method('getDisplayName')->willReturn('User No. 1');
|
||||
|
||||
$this->userManager
|
||||
->method('get')
|
||||
->willReturnMap([
|
||||
['user_1', $user1],
|
||||
['user_2', null],
|
||||
['user_3', null],
|
||||
['user_4', null],
|
||||
['user_5', null],
|
||||
['user_6', null],
|
||||
['user_7', null],
|
||||
]);
|
||||
|
||||
$userStatuses = [
|
||||
UserStatus::fromParams([
|
||||
'userId' => 'user_1',
|
||||
'status' => 'online',
|
||||
'customIcon' => '💻',
|
||||
'customMessage' => 'Working',
|
||||
'statusTimestamp' => 5000,
|
||||
]),
|
||||
UserStatus::fromParams([
|
||||
'userId' => 'user_2',
|
||||
'status' => 'away',
|
||||
'customIcon' => '☕️',
|
||||
'customMessage' => 'Office Hangout',
|
||||
'statusTimestamp' => 6000,
|
||||
]),
|
||||
UserStatus::fromParams([
|
||||
'userId' => 'user_3',
|
||||
'status' => 'dnd',
|
||||
'customIcon' => null,
|
||||
'customMessage' => null,
|
||||
'statusTimestamp' => 7000,
|
||||
]),
|
||||
UserStatus::fromParams([
|
||||
'userId' => 'john.doe',
|
||||
'status' => 'away',
|
||||
'customIcon' => '☕️',
|
||||
'customMessage' => 'Office Hangout',
|
||||
'statusTimestamp' => 90000,
|
||||
]),
|
||||
UserStatus::fromParams([
|
||||
'userId' => 'user_4',
|
||||
'status' => 'dnd',
|
||||
'customIcon' => null,
|
||||
'customMessage' => null,
|
||||
'statusTimestamp' => 7000,
|
||||
]),
|
||||
UserStatus::fromParams([
|
||||
'userId' => 'user_5',
|
||||
'status' => 'invisible',
|
||||
'customIcon' => '🏝',
|
||||
'customMessage' => 'On vacation',
|
||||
'statusTimestamp' => 7000,
|
||||
]),
|
||||
UserStatus::fromParams([
|
||||
'userId' => 'user_6',
|
||||
'status' => 'offline',
|
||||
'customIcon' => null,
|
||||
'customMessage' => null,
|
||||
'statusTimestamp' => 7000,
|
||||
]),
|
||||
UserStatus::fromParams([
|
||||
'userId' => 'user_7',
|
||||
'status' => 'invisible',
|
||||
'customIcon' => null,
|
||||
'customMessage' => null,
|
||||
'statusTimestamp' => 7000,
|
||||
]),
|
||||
];
|
||||
|
||||
$this->service->expects($this->once())
|
||||
->method('findAllRecentStatusChanges')
|
||||
->with(8, 0)
|
||||
->willReturn($userStatuses);
|
||||
|
||||
$this->initialState->expects($this->once())
|
||||
->method('provideInitialState')
|
||||
->with('dashboard_data', $this->callback(function ($data): bool {
|
||||
$this->assertEquals([
|
||||
[
|
||||
'userId' => 'user_1',
|
||||
'displayName' => 'User No. 1',
|
||||
'status' => 'online',
|
||||
'icon' => '💻',
|
||||
'message' => 'Working',
|
||||
'timestamp' => 5000,
|
||||
],
|
||||
[
|
||||
'userId' => 'user_2',
|
||||
'displayName' => 'user_2',
|
||||
'status' => 'away',
|
||||
'icon' => '☕️',
|
||||
'message' => 'Office Hangout',
|
||||
'timestamp' => 6000,
|
||||
],
|
||||
[
|
||||
'userId' => 'user_3',
|
||||
'displayName' => 'user_3',
|
||||
'status' => 'dnd',
|
||||
'icon' => null,
|
||||
'message' => null,
|
||||
'timestamp' => 7000,
|
||||
],
|
||||
[
|
||||
'userId' => 'user_4',
|
||||
'displayName' => 'user_4',
|
||||
'status' => 'dnd',
|
||||
'icon' => null,
|
||||
'message' => null,
|
||||
'timestamp' => 7000,
|
||||
],
|
||||
[
|
||||
'userId' => 'user_5',
|
||||
'displayName' => 'user_5',
|
||||
'status' => 'offline',
|
||||
'icon' => '🏝',
|
||||
'message' => 'On vacation',
|
||||
'timestamp' => 7000,
|
||||
],
|
||||
[
|
||||
'userId' => 'user_6',
|
||||
'displayName' => 'user_6',
|
||||
'status' => 'offline',
|
||||
'icon' => null,
|
||||
'message' => null,
|
||||
'timestamp' => 7000,
|
||||
],
|
||||
[
|
||||
'userId' => 'user_7',
|
||||
'displayName' => 'user_7',
|
||||
'status' => 'offline',
|
||||
'icon' => null,
|
||||
'message' => null,
|
||||
'timestamp' => 7000,
|
||||
],
|
||||
], $data);
|
||||
return true;
|
||||
}));
|
||||
|
||||
$this->widget->load();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -217,14 +217,17 @@ return array(
|
|||
'OCP\\DB\\QueryBuilder\\IQueryFunction' => $baseDir . '/lib/public/DB/QueryBuilder/IQueryFunction.php',
|
||||
'OCP\\DB\\Types' => $baseDir . '/lib/public/DB/Types.php',
|
||||
'OCP\\Dashboard\\IAPIWidget' => $baseDir . '/lib/public/Dashboard/IAPIWidget.php',
|
||||
'OCP\\Dashboard\\IAPIWidgetV2' => $baseDir . '/lib/public/Dashboard/IAPIWidgetV2.php',
|
||||
'OCP\\Dashboard\\IButtonWidget' => $baseDir . '/lib/public/Dashboard/IButtonWidget.php',
|
||||
'OCP\\Dashboard\\IConditionalWidget' => $baseDir . '/lib/public/Dashboard/IConditionalWidget.php',
|
||||
'OCP\\Dashboard\\IIconWidget' => $baseDir . '/lib/public/Dashboard/IIconWidget.php',
|
||||
'OCP\\Dashboard\\IManager' => $baseDir . '/lib/public/Dashboard/IManager.php',
|
||||
'OCP\\Dashboard\\IOptionWidget' => $baseDir . '/lib/public/Dashboard/IOptionWidget.php',
|
||||
'OCP\\Dashboard\\IReloadableWidget' => $baseDir . '/lib/public/Dashboard/IReloadableWidget.php',
|
||||
'OCP\\Dashboard\\IWidget' => $baseDir . '/lib/public/Dashboard/IWidget.php',
|
||||
'OCP\\Dashboard\\Model\\WidgetButton' => $baseDir . '/lib/public/Dashboard/Model/WidgetButton.php',
|
||||
'OCP\\Dashboard\\Model\\WidgetItem' => $baseDir . '/lib/public/Dashboard/Model/WidgetItem.php',
|
||||
'OCP\\Dashboard\\Model\\WidgetItems' => $baseDir . '/lib/public/Dashboard/Model/WidgetItems.php',
|
||||
'OCP\\Dashboard\\Model\\WidgetOptions' => $baseDir . '/lib/public/Dashboard/Model/WidgetOptions.php',
|
||||
'OCP\\Dashboard\\RegisterWidgetEvent' => $baseDir . '/lib/public/Dashboard/RegisterWidgetEvent.php',
|
||||
'OCP\\DataCollector\\AbstractDataCollector' => $baseDir . '/lib/public/DataCollector/AbstractDataCollector.php',
|
||||
|
|
|
@ -250,14 +250,17 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
|
|||
'OCP\\DB\\QueryBuilder\\IQueryFunction' => __DIR__ . '/../../..' . '/lib/public/DB/QueryBuilder/IQueryFunction.php',
|
||||
'OCP\\DB\\Types' => __DIR__ . '/../../..' . '/lib/public/DB/Types.php',
|
||||
'OCP\\Dashboard\\IAPIWidget' => __DIR__ . '/../../..' . '/lib/public/Dashboard/IAPIWidget.php',
|
||||
'OCP\\Dashboard\\IAPIWidgetV2' => __DIR__ . '/../../..' . '/lib/public/Dashboard/IAPIWidgetV2.php',
|
||||
'OCP\\Dashboard\\IButtonWidget' => __DIR__ . '/../../..' . '/lib/public/Dashboard/IButtonWidget.php',
|
||||
'OCP\\Dashboard\\IConditionalWidget' => __DIR__ . '/../../..' . '/lib/public/Dashboard/IConditionalWidget.php',
|
||||
'OCP\\Dashboard\\IIconWidget' => __DIR__ . '/../../..' . '/lib/public/Dashboard/IIconWidget.php',
|
||||
'OCP\\Dashboard\\IManager' => __DIR__ . '/../../..' . '/lib/public/Dashboard/IManager.php',
|
||||
'OCP\\Dashboard\\IOptionWidget' => __DIR__ . '/../../..' . '/lib/public/Dashboard/IOptionWidget.php',
|
||||
'OCP\\Dashboard\\IReloadableWidget' => __DIR__ . '/../../..' . '/lib/public/Dashboard/IReloadableWidget.php',
|
||||
'OCP\\Dashboard\\IWidget' => __DIR__ . '/../../..' . '/lib/public/Dashboard/IWidget.php',
|
||||
'OCP\\Dashboard\\Model\\WidgetButton' => __DIR__ . '/../../..' . '/lib/public/Dashboard/Model/WidgetButton.php',
|
||||
'OCP\\Dashboard\\Model\\WidgetItem' => __DIR__ . '/../../..' . '/lib/public/Dashboard/Model/WidgetItem.php',
|
||||
'OCP\\Dashboard\\Model\\WidgetItems' => __DIR__ . '/../../..' . '/lib/public/Dashboard/Model/WidgetItems.php',
|
||||
'OCP\\Dashboard\\Model\\WidgetOptions' => __DIR__ . '/../../..' . '/lib/public/Dashboard/Model/WidgetOptions.php',
|
||||
'OCP\\Dashboard\\RegisterWidgetEvent' => __DIR__ . '/../../..' . '/lib/public/Dashboard/RegisterWidgetEvent.php',
|
||||
'OCP\\DataCollector\\AbstractDataCollector' => __DIR__ . '/../../..' . '/lib/public/DataCollector/AbstractDataCollector.php',
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright Copyright (c) 2023 Richard Steinmetz <richard@steinmetz.cloud>
|
||||
*
|
||||
* @author Richard Steinmetz <richard@steinmetz.cloud>
|
||||
*
|
||||
* @license AGPL-3.0-or-later
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCP\Dashboard;
|
||||
|
||||
use OCP\Dashboard\Model\WidgetItems;
|
||||
|
||||
/**
|
||||
* Interface IAPIWidgetV2
|
||||
*
|
||||
* @since 27.1.0
|
||||
*/
|
||||
interface IAPIWidgetV2 extends IWidget {
|
||||
/**
|
||||
* Items to render in the widget
|
||||
*
|
||||
* @since 27.1.0
|
||||
*/
|
||||
public function getItemsV2(string $userId, ?string $since = null, int $limit = 7): WidgetItems;
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright Copyright (c) 2023 Richard Steinmetz <richard@steinmetz.cloud>
|
||||
*
|
||||
* @author Richard Steinmetz <richard@steinmetz.cloud>
|
||||
*
|
||||
* @license AGPL-3.0-or-later
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCP\Dashboard;
|
||||
|
||||
/**
|
||||
* Allow {@see IAPIWidgetV2} to reload their items
|
||||
*
|
||||
* @since 27.1.0
|
||||
*/
|
||||
interface IReloadableWidget extends IAPIWidgetV2 {
|
||||
/**
|
||||
* Periodic interval in seconds in which to reload the widget's items
|
||||
*
|
||||
* @since 27.1.0
|
||||
*/
|
||||
public function getReloadInterval(): int;
|
||||
}
|
|
@ -6,6 +6,7 @@ declare(strict_types=1);
|
|||
* @copyright 2021, Julien Veyssier <eneiluj@posteo.net>
|
||||
*
|
||||
* @author Julien Veyssier <eneiluj@posteo.net>
|
||||
* @author Richard Steinmetz <richard@steinmetz.cloud>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
|
@ -56,24 +57,30 @@ final class WidgetItem implements JsonSerializable {
|
|||
*/
|
||||
private $sinceId = '';
|
||||
|
||||
/**
|
||||
* Overlay icon to show in the bottom right corner of {@see $iconUrl}
|
||||
*
|
||||
* @since 27.1.0
|
||||
*/
|
||||
private string $overlayIconUrl = '';
|
||||
|
||||
/**
|
||||
* WidgetItem constructor
|
||||
*
|
||||
* @since 22.0.0
|
||||
*
|
||||
* @param string $type
|
||||
*/
|
||||
public function __construct(string $title = '',
|
||||
string $subtitle = '',
|
||||
string $link = '',
|
||||
string $iconUrl = '',
|
||||
string $sinceId = '') {
|
||||
string $sinceId = '',
|
||||
string $overlayIconUrl = '') {
|
||||
$this->title = $title;
|
||||
$this->subtitle = $subtitle;
|
||||
$this->iconUrl = $iconUrl;
|
||||
$this->link = $link;
|
||||
$this->sinceId = $sinceId;
|
||||
$this->overlayIconUrl = $overlayIconUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -132,6 +139,17 @@ final class WidgetItem implements JsonSerializable {
|
|||
return $this->sinceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the overlay icon url
|
||||
*
|
||||
* @since 27.1.0
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getOverlayIconUrl(): string {
|
||||
return $this->overlayIconUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 22.0.0
|
||||
*
|
||||
|
@ -143,6 +161,7 @@ final class WidgetItem implements JsonSerializable {
|
|||
'title' => $this->getTitle(),
|
||||
'link' => $this->getLink(),
|
||||
'iconUrl' => $this->getIconUrl(),
|
||||
'overlayIconUrl' => $this->getOverlayIconUrl(),
|
||||
'sinceId' => $this->getSinceId(),
|
||||
];
|
||||
}
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright Copyright (c) 2023 Richard Steinmetz <richard@steinmetz.cloud>
|
||||
*
|
||||
* @author Richard Steinmetz <richard@steinmetz.cloud>
|
||||
*
|
||||
* @license AGPL-3.0-or-later
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCP\Dashboard\Model;
|
||||
|
||||
use JsonSerializable;
|
||||
use OCP\Dashboard\IAPIWidgetV2;
|
||||
|
||||
/**
|
||||
* Interface WidgetItems
|
||||
*
|
||||
* This class is used by {@see IAPIWidgetV2} interface.
|
||||
* It represents an array of widget items and additional context information that can be provided to clients via the Dashboard API
|
||||
*
|
||||
* @see IAPIWidgetV2::getItemsV2
|
||||
*
|
||||
* @since 27.1.0
|
||||
*/
|
||||
class WidgetItems implements JsonSerializable {
|
||||
/**
|
||||
* @param $items WidgetItem[]
|
||||
*
|
||||
* @since 27.1.0
|
||||
*/
|
||||
public function __construct(
|
||||
private array $items = [],
|
||||
private string $emptyContentMessage = '',
|
||||
private string $halfEmptyContentMessage = '',
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Items to render in the widgets
|
||||
*
|
||||
* @since 27.1.0
|
||||
*
|
||||
* @return WidgetItem[]
|
||||
*/
|
||||
public function getItems(): array {
|
||||
return $this->items;
|
||||
}
|
||||
|
||||
/**
|
||||
* The "half" empty content message to show above the list of items.
|
||||
*
|
||||
* A non-empty string enables this feature.
|
||||
* An empty string hides the message and disables this feature.
|
||||
*
|
||||
* @since 27.1.0
|
||||
*/
|
||||
public function getEmptyContentMessage(): string {
|
||||
return $this->emptyContentMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* The empty content message to show in case of no items at all
|
||||
*
|
||||
* @since 27.1.0
|
||||
*/
|
||||
public function getHalfEmptyContentMessage(): string {
|
||||
return $this->halfEmptyContentMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 27.1.0
|
||||
*/
|
||||
public function jsonSerialize(): array {
|
||||
$items = array_map(static function (WidgetItem $item) {
|
||||
return $item->jsonSerialize();
|
||||
}, $this->getItems());
|
||||
return [
|
||||
'items' => $items,
|
||||
'emptyContentMessage' => $this->getEmptyContentMessage(),
|
||||
'halfEmptyContentMessage' => $this->getHalfEmptyContentMessage(),
|
||||
];
|
||||
}
|
||||
}
|
|
@ -109,7 +109,6 @@ module.exports = {
|
|||
updatenotification: path.join(__dirname, 'apps/updatenotification/src', 'init.js'),
|
||||
},
|
||||
user_status: {
|
||||
dashboard: path.join(__dirname, 'apps/user_status/src', 'dashboard.js'),
|
||||
menu: path.join(__dirname, 'apps/user_status/src', 'menu.js'),
|
||||
},
|
||||
weather_status: {
|
||||
|
|
Loading…
Reference in New Issue