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>pull/39937/head
parent
82835eaa46
commit
6982597b6a
@ -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> |
||||
@ -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> |
||||
@ -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; |
||||
} |
||||
@ -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(), |
||||
]; |
||||
} |
||||
} |
||||
Loading…
Reference in new issue