Merge pull request #39937 from nextcloud/feat/dashboard/item-api-v2
feat(dashboard): implement widget item api v2pull/39950/head
commit
5234807c60
@ -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> |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,25 +0,0 @@ |
||||
/*! For license information please see NcDashboardWidget.js.LICENSE.txt */ |
||||
|
||||
/*! For license information please see NcDashboardWidgetItem.js.LICENSE.txt */ |
||||
|
||||
/** |
||||
* @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/>. |
||||
* |
||||
*/ |
File diff suppressed because one or more lines are too long
@ -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