From f98ae2b5b0567f28a875106724c0475d6395f3c5 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Tue, 30 Aug 2022 13:27:21 +0200 Subject: [PATCH 1/4] Avatar new style Signed-off-by: Carl Schwan --- core/Controller/AvatarController.php | 40 +++++++++++++++++++++++- core/routes.php | 1 + lib/private/Avatar/Avatar.php | 37 +++++++++++++--------- lib/private/Avatar/UserAvatar.php | 46 +++++++++++++++++++--------- lib/public/Color.php | 8 +++++ lib/public/IAvatar.php | 6 ++-- 6 files changed, 105 insertions(+), 33 deletions(-) diff --git a/core/Controller/AvatarController.php b/core/Controller/AvatarController.php index 5fcd2a9abbe5b..804000bf2a754 100644 --- a/core/Controller/AvatarController.php +++ b/core/Controller/AvatarController.php @@ -84,6 +84,44 @@ public function __construct(string $appName, $this->timeFactory = $timeFactory; } + /** + * @NoAdminRequired + * @NoCSRFRequired + * @NoSameSiteCookieRequired + * @PublicPage + * + * @return JSONResponse|FileDisplayResponse + */ + public function getAvatarDark(string $userId, int $size) { + if ($size <= 64) { + if ($size !== 64) { + $this->logger->debug('Avatar requested in deprecated size ' . $size); + } + $size = 64; + } else { + if ($size !== 512) { + $this->logger->debug('Avatar requested in deprecated size ' . $size); + } + $size = 512; + } + + try { + $avatar = $this->avatarManager->getAvatar($userId); + $avatarFile = $avatar->getFile($size, true); + $response = new FileDisplayResponse( + $avatarFile, + Http::STATUS_OK, + ['Content-Type' => $avatarFile->getMimeType(), 'X-NC-IsCustomAvatar' => (int)$avatar->isCustomAvatar()] + ); + } catch (\Exception $e) { + return new JSONResponse([], Http::STATUS_NOT_FOUND); + } + + // Cache for 1 day + $response->cacheFor(60 * 60 * 24, false, true); + return $response; + } + /** * @NoAdminRequired @@ -93,7 +131,7 @@ public function __construct(string $appName, * * @return JSONResponse|FileDisplayResponse */ - public function getAvatar(string $userId, int $size) { + public function getAvatar(string $userId, int $size, bool $darkTheme = false) { if ($size <= 64) { if ($size !== 64) { $this->logger->debug('Avatar requested in deprecated size ' . $size); diff --git a/core/routes.php b/core/routes.php index b75cb0f6b3b44..ee5fbb34a4c74 100644 --- a/core/routes.php +++ b/core/routes.php @@ -45,6 +45,7 @@ ['name' => 'lost#setPassword', 'url' => '/lostpassword/set/{token}/{userId}', 'verb' => 'POST'], ['name' => 'ProfilePage#index', 'url' => '/u/{targetUserId}', 'verb' => 'GET'], ['name' => 'user#getDisplayNames', 'url' => '/displaynames', 'verb' => 'POST'], + ['name' => 'avatar#getAvatarDark', 'url' => '/avatar/{userId}/{size}/dark', 'verb' => 'GET'], ['name' => 'avatar#getAvatar', 'url' => '/avatar/{userId}/{size}', 'verb' => 'GET'], ['name' => 'avatar#deleteAvatar', 'url' => '/avatar/', 'verb' => 'DELETE'], ['name' => 'avatar#postCroppedAvatar', 'url' => '/avatar/cropped', 'verb' => 'POST'], diff --git a/lib/private/Avatar/Avatar.php b/lib/private/Avatar/Avatar.php index 0eb8f8816d826..aec0f6e10f4a9 100644 --- a/lib/private/Avatar/Avatar.php +++ b/lib/private/Avatar/Avatar.php @@ -59,7 +59,7 @@ abstract class Avatar implements IAvatar { private string $svgTemplate = ' - {letter} + {letter} '; public function __construct(LoggerInterface $logger) { @@ -88,9 +88,9 @@ private function getAvatarText(): string { /** * @inheritdoc */ - public function get(int $size = 64) { + public function get(int $size = 64, bool $darkTheme = false) { try { - $file = $this->getFile($size); + $file = $this->getFile($size, $darkTheme); } catch (NotFoundException $e) { return false; } @@ -111,25 +111,27 @@ public function get(int $size = 64) { * @return string * */ - protected function getAvatarVector(int $size): string { + protected function getAvatarVector(int $size, bool $dark): string { $userDisplayName = $this->getDisplayName(); - $bgRGB = $this->avatarBackgroundColor($userDisplayName); - $bgHEX = sprintf("%02x%02x%02x", $bgRGB->red(), $bgRGB->green(), $bgRGB->blue()); + $fgRGB = $this->avatarBackgroundColor($userDisplayName); + $bgRGB = $fgRGB->alphaBlending(0.1, $dark ? new Color(0, 0, 0) : new Color(255, 255, 255)); + $fill = sprintf("%02x%02x%02x", $bgRGB->red(), $bgRGB->green(), $bgRGB->blue()); + $fgFill = sprintf("%02x%02x%02x", $fgRGB->red(), $fgRGB->green(), $fgRGB->blue()); $text = $this->getAvatarText(); - $toReplace = ['{size}', '{fill}', '{letter}']; - return str_replace($toReplace, [$size, $bgHEX, $text], $this->svgTemplate); + $toReplace = ['{size}', '{fill}', '{fgFill}', '{letter}']; + return str_replace($toReplace, [$size, $fill, $fgFill, $text], $this->svgTemplate); } /** * Generate png avatar from svg with Imagick */ - protected function generateAvatarFromSvg(int $size): ?string { + protected function generateAvatarFromSvg(int $size, bool $dark): ?string { if (!extension_loaded('imagick')) { return null; } try { - $font = __DIR__ . '/../../core/fonts/NotoSans-Regular.ttf'; - $svg = $this->getAvatarVector($size); + $font = __DIR__ . '/../../../core/fonts/NotoSans-Regular.ttf'; + $svg = $this->getAvatarVector($size, $dark); $avatar = new Imagick(); $avatar->setFont($font); $avatar->readImageBlob($svg); @@ -145,9 +147,10 @@ protected function generateAvatarFromSvg(int $size): ?string { /** * Generate png avatar with GD */ - protected function generateAvatar(string $userDisplayName, int $size): string { + protected function generateAvatar(string $userDisplayName, int $size, bool $dark): string { $text = $this->getAvatarText(); - $backgroundColor = $this->avatarBackgroundColor($userDisplayName); + $textColor = $this->avatarBackgroundColor($userDisplayName); + $backgroundColor = $textColor->alphaBlending(0.1, $dark ? new Color() : new Color(255, 255, 255)); $im = imagecreatetruecolor($size, $size); $background = imagecolorallocate( @@ -156,7 +159,11 @@ protected function generateAvatar(string $userDisplayName, int $size): string { $backgroundColor->green(), $backgroundColor->blue() ); - $white = imagecolorallocate($im, 255, 255, 255); + $textColor = imagecolorallocate($im, + $textColor->red(), + $textColor->green(), + $textColor->blue() + ); imagefilledrectangle($im, 0, 0, $size, $size, $background); $font = __DIR__ . '/../../../core/fonts/NotoSans-Regular.ttf'; @@ -166,7 +173,7 @@ protected function generateAvatar(string $userDisplayName, int $size): string { $im, $text, $font, (int)$fontSize ); - imagettftext($im, $fontSize, 0, $x, $y, $white, $font, $text); + imagettftext($im, $fontSize, 0, $x, $y, $textColor, $font, $text); ob_start(); imagepng($im); diff --git a/lib/private/Avatar/UserAvatar.php b/lib/private/Avatar/UserAvatar.php index f5a1d7e77b177..02fcfcb0fc8ca 100644 --- a/lib/private/Avatar/UserAvatar.php +++ b/lib/private/Avatar/UserAvatar.php @@ -208,7 +208,14 @@ public function remove(bool $silent = false): void { * * @throws NotFoundException */ - private function getExtension(): string { + private function getExtension(bool $generated, bool $darkTheme): string { + if ($darkTheme && !$generated) { + if ($this->folder->fileExists('avatar-dark.jpg')) { + return 'jpg'; + } elseif ($this->folder->fileExists('avatar-dark.png')) { + return 'png'; + } + } if ($this->folder->fileExists('avatar.jpg')) { return 'jpg'; } elseif ($this->folder->fileExists('avatar.png')) { @@ -228,25 +235,36 @@ private function getExtension(): string { * @throws \OCP\Files\NotPermittedException * @throws \OCP\PreConditionNotMetException */ - public function getFile(int $size): ISimpleFile { + public function getFile(int $size, bool $darkTheme = false): ISimpleFile { + $generated = $this->folder->fileExists('generated'); + try { - $ext = $this->getExtension(); + $ext = $this->getExtension($generated, $darkTheme); } catch (NotFoundException $e) { - if (!$data = $this->generateAvatarFromSvg(1024)) { - $data = $this->generateAvatar($this->getDisplayName(), 1024); + if (!$data = $this->generateAvatarFromSvg(1024, $darkTheme)) { + $data = $this->generateAvatar($this->getDisplayName(), 1024, $darkTheme); } - $avatar = $this->folder->newFile('avatar.png'); + $avatar = $this->folder->newFile($darkTheme ? 'avatar-dark.png' : 'avatar.png'); $avatar->putContent($data); $ext = 'png'; $this->folder->newFile('generated', ''); $this->config->setUserValue($this->user->getUID(), 'avatar', 'generated', 'true'); + $generated = true; } - if ($size === -1) { - $path = 'avatar.' . $ext; + if ($generated) { + if ($size === -1) { + $path = 'avatar' . ($darkTheme ? '-dark' : '') . '.' . $ext; + } else { + $path = 'avatar' . ($darkTheme ? '-dark' : '') . '.' . $size . '.' . $ext; + } } else { - $path = 'avatar.' . $size . '.' . $ext; + if ($size === -1) { + $path = 'avatar.' . $ext; + } else { + $path = 'avatar.' . $size . '.' . $ext; + } } try { @@ -255,11 +273,9 @@ public function getFile(int $size): ISimpleFile { if ($size <= 0) { throw new NotFoundException; } - - // TODO: rework to integrate with the PlaceholderAvatar in a compatible way - if ($this->folder->fileExists('generated')) { - if (!$data = $this->generateAvatarFromSvg($size)) { - $data = $this->generateAvatar($this->getDisplayName(), $size); + if ($generated) { + if (!$data = $this->generateAvatarFromSvg($size, $darkTheme)) { + $data = $this->generateAvatar($this->getDisplayName(), $size, $darkTheme); } } else { $avatar = new \OCP\Image(); @@ -279,7 +295,7 @@ public function getFile(int $size): ISimpleFile { } if ($this->config->getUserValue($this->user->getUID(), 'avatar', 'generated', null) === null) { - $generated = $this->folder->fileExists('generated') ? 'true' : 'false'; + $generated = $generated ? 'true' : 'false'; $this->config->setUserValue($this->user->getUID(), 'avatar', 'generated', $generated); } diff --git a/lib/public/Color.php b/lib/public/Color.php index e2cabd9c2fc62..6c9a35ca49c2a 100644 --- a/lib/public/Color.php +++ b/lib/public/Color.php @@ -125,6 +125,14 @@ public static function mixPalette(int $steps, Color $color1, Color $color2): arr return $palette; } + public function alphaBlending(float $opacity, Color $source): Color { + return new Color( + (int)((1 - $opacity) * $source->red() + $opacity * $this->red()), + (int)((1 - $opacity) * $source->green() + $opacity * $this->green()), + (int)((1 - $opacity) * $source->blue() + $opacity * $this->blue()) + ); + } + /** * Calculate steps between two Colors * @param int $steps start color diff --git a/lib/public/IAvatar.php b/lib/public/IAvatar.php index d05a12e1dbf4b..f9fe9a645e65b 100644 --- a/lib/public/IAvatar.php +++ b/lib/public/IAvatar.php @@ -39,10 +39,11 @@ interface IAvatar { * Get the users avatar * * @param int $size size in px of the avatar, avatars are square, defaults to 64, -1 can be used to not scale the image + * @param bool $darkTheme Should the generated avatar be dark themed * @return false|\OCP\IImage containing the avatar or false if there's no image * @since 6.0.0 - size of -1 was added in 9.0.0 */ - public function get(int $size = 64); + public function get(int $size = 64, bool $darkTheme = false); /** * Check if an avatar exists for the user @@ -81,10 +82,11 @@ public function remove(bool $silent = false): void; * Get the file of the avatar * * @param int $size The desired image size. -1 can be used to not scale the image + * @param bool $darkTheme Should the generated avatar be dark themed * @throws NotFoundException * @since 9.0.0 */ - public function getFile(int $size): ISimpleFile; + public function getFile(int $size, bool $darkTheme = false): ISimpleFile; /** * Get the avatar background color From 76d01653309f1535bfe8d364a5aae50e83a348e3 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Tue, 30 Aug 2022 13:58:51 +0200 Subject: [PATCH 2/4] Dark theme for guest avatar And better caching policy Signed-off-by: Carl Schwan --- core/Controller/GuestAvatarController.php | 13 +++++++++++-- core/routes.php | 1 + lib/private/Avatar/Avatar.php | 2 +- lib/private/Avatar/GuestAvatar.php | 4 ++-- lib/private/Avatar/PlaceholderAvatar.php | 10 +++++----- 5 files changed, 20 insertions(+), 10 deletions(-) diff --git a/core/Controller/GuestAvatarController.php b/core/Controller/GuestAvatarController.php index 9c0606f368f58..09146ff3928db 100644 --- a/core/Controller/GuestAvatarController.php +++ b/core/Controller/GuestAvatarController.php @@ -60,8 +60,9 @@ public function __construct( * @param string $size The desired avatar size, e.g. 64 for 64x64px * @return FileDisplayResponse|Http\Response */ - public function getAvatar(string $guestName, string $size) { + public function getAvatar(string $guestName, string $size, ?bool $dark = false) { $size = (int) $size; + $dark = $dark === null ? false : $dark; if ($size <= 64) { if ($size !== 64) { @@ -94,7 +95,15 @@ public function getAvatar(string $guestName, string $size) { } // Cache for 30 minutes - $resp->cacheFor(1800); + $resp->cacheFor(1800, false, true); return $resp; } + + /** + * @PublicPage + * @NoCSRFRequired + */ + public function getAvatarDark(string $guestName, string $size) { + return $this->getAvatar($guestName, $size, true); + } } diff --git a/core/routes.php b/core/routes.php index ee5fbb34a4c74..5e08213828d30 100644 --- a/core/routes.php +++ b/core/routes.php @@ -51,6 +51,7 @@ ['name' => 'avatar#postCroppedAvatar', 'url' => '/avatar/cropped', 'verb' => 'POST'], ['name' => 'avatar#getTmpAvatar', 'url' => '/avatar/tmp', 'verb' => 'GET'], ['name' => 'avatar#postAvatar', 'url' => '/avatar/', 'verb' => 'POST'], + ['name' => 'GuestAvatar#getAvatarDark', 'url' => '/avatar/guest/{guestName}/{size}/dark', 'verb' => 'GET'], ['name' => 'GuestAvatar#getAvatar', 'url' => '/avatar/guest/{guestName}/{size}', 'verb' => 'GET'], ['name' => 'CSRFToken#index', 'url' => '/csrftoken', 'verb' => 'GET'], ['name' => 'login#tryLogin', 'url' => '/login', 'verb' => 'POST'], diff --git a/lib/private/Avatar/Avatar.php b/lib/private/Avatar/Avatar.php index aec0f6e10f4a9..1e6ebd7c61b81 100644 --- a/lib/private/Avatar/Avatar.php +++ b/lib/private/Avatar/Avatar.php @@ -150,7 +150,7 @@ protected function generateAvatarFromSvg(int $size, bool $dark): ?string { protected function generateAvatar(string $userDisplayName, int $size, bool $dark): string { $text = $this->getAvatarText(); $textColor = $this->avatarBackgroundColor($userDisplayName); - $backgroundColor = $textColor->alphaBlending(0.1, $dark ? new Color() : new Color(255, 255, 255)); + $backgroundColor = $textColor->alphaBlending(0.1, $dark ? new Color(0, 0, 0) : new Color(255, 255, 255)); $im = imagecreatetruecolor($size, $size); $background = imagecolorallocate( diff --git a/lib/private/Avatar/GuestAvatar.php b/lib/private/Avatar/GuestAvatar.php index 79d7e6ee09463..083deb4108fdf 100644 --- a/lib/private/Avatar/GuestAvatar.php +++ b/lib/private/Avatar/GuestAvatar.php @@ -84,8 +84,8 @@ public function remove(bool $silent = false): void { /** * Generates an avatar for the guest. */ - public function getFile(int $size): ISimpleFile { - $avatar = $this->generateAvatar($this->userDisplayName, $size); + public function getFile(int $size, bool $darkTheme = false): ISimpleFile { + $avatar = $this->generateAvatar($this->userDisplayName, $size, $darkTheme); return new InMemoryFile('avatar.png', $avatar); } diff --git a/lib/private/Avatar/PlaceholderAvatar.php b/lib/private/Avatar/PlaceholderAvatar.php index 504e5c1457dfc..e7ca89f4d3041 100644 --- a/lib/private/Avatar/PlaceholderAvatar.php +++ b/lib/private/Avatar/PlaceholderAvatar.php @@ -108,13 +108,13 @@ public function remove(bool $silent = false): void { * @throws \OCP\Files\NotPermittedException * @throws \OCP\PreConditionNotMetException */ - public function getFile(int $size): ISimpleFile { + public function getFile(int $size, bool $darkTheme = false): ISimpleFile { $ext = 'png'; if ($size === -1) { - $path = 'avatar-placeholder.' . $ext; + $path = 'avatar-placeholder' . ($darkTheme ? '-dark' : '') . '.' . $ext; } else { - $path = 'avatar-placeholder.' . $size . '.' . $ext; + $path = 'avatar-placeholder' . ($darkTheme ? '-dark' : '') . '.' . $size . '.' . $ext; } try { @@ -124,8 +124,8 @@ public function getFile(int $size): ISimpleFile { throw new NotFoundException; } - if (!$data = $this->generateAvatarFromSvg($size)) { - $data = $this->generateAvatar($this->getDisplayName(), $size); + if (!$data = $this->generateAvatarFromSvg($size, $darkTheme)) { + $data = $this->generateAvatar($this->getDisplayName(), $size, $darkTheme); } try { From f3ec1d3a9f6806aee32b27942e56931f79d9d02c Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Tue, 30 Aug 2022 16:42:22 +0200 Subject: [PATCH 3/4] Fix user status in the top bar with dark theme Signed-off-by: Carl Schwan --- apps/user_status/src/menu.js | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/user_status/src/menu.js b/apps/user_status/src/menu.js index 8714df3a1ff29..66010102f10f0 100644 --- a/apps/user_status/src/menu.js +++ b/apps/user_status/src/menu.js @@ -45,7 +45,6 @@ const propsData = { }, user: avatarDiv.dataset.user, displayName: avatarDiv.dataset.displayname, - url: avatarDiv.dataset.avatar, disableMenu: true, disableTooltip: true, } From bc9a48804688e06a842169be0ed3efbf2fead559 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Wed, 31 Aug 2022 14:24:25 +0200 Subject: [PATCH 4/4] Update avatars on update Signed-off-by: Carl Schwan --- core/Controller/AvatarController.php | 2 +- core/Controller/GuestAvatarController.php | 6 +++--- dist/user_status-menu.js | 4 ++-- dist/user_status-menu.js.map | 2 +- lib/private/Avatar/Avatar.php | 12 +++++------ .../Repair/ClearGeneratedAvatarCache.php | 20 ++++++------------ lib/public/Color.php | 6 ++++++ tests/data/guest_avatar_einstein_32.png | Bin 269 -> 270 bytes tests/lib/Avatar/GuestAvatarTest.php | 3 +-- tests/lib/Avatar/UserAvatarTest.php | 6 +++--- .../Repair/ClearGeneratedAvatarCacheTest.php | 4 ++-- version.php | 2 +- 12 files changed, 33 insertions(+), 34 deletions(-) diff --git a/core/Controller/AvatarController.php b/core/Controller/AvatarController.php index 804000bf2a754..673ed1be05b80 100644 --- a/core/Controller/AvatarController.php +++ b/core/Controller/AvatarController.php @@ -131,7 +131,7 @@ public function getAvatarDark(string $userId, int $size) { * * @return JSONResponse|FileDisplayResponse */ - public function getAvatar(string $userId, int $size, bool $darkTheme = false) { + public function getAvatar(string $userId, int $size) { if ($size <= 64) { if ($size !== 64) { $this->logger->debug('Avatar requested in deprecated size ' . $size); diff --git a/core/Controller/GuestAvatarController.php b/core/Controller/GuestAvatarController.php index 09146ff3928db..dc4f81bd643c3 100644 --- a/core/Controller/GuestAvatarController.php +++ b/core/Controller/GuestAvatarController.php @@ -60,9 +60,9 @@ public function __construct( * @param string $size The desired avatar size, e.g. 64 for 64x64px * @return FileDisplayResponse|Http\Response */ - public function getAvatar(string $guestName, string $size, ?bool $dark = false) { + public function getAvatar(string $guestName, string $size, ?bool $darkTheme = false) { $size = (int) $size; - $dark = $dark === null ? false : $dark; + $darkTheme = $darkTheme ?? false; if ($size <= 64) { if ($size !== 64) { @@ -78,7 +78,7 @@ public function getAvatar(string $guestName, string $size, ?bool $dark = false) try { $avatar = $this->avatarManager->getGuestAvatar($guestName); - $avatarFile = $avatar->getFile($size); + $avatarFile = $avatar->getFile($size, $darkTheme); $resp = new FileDisplayResponse( $avatarFile, diff --git a/dist/user_status-menu.js b/dist/user_status-menu.js index 62dedac35fa48..09862a5173978 100644 --- a/dist/user_status-menu.js +++ b/dist/user_status-menu.js @@ -1,3 +1,3 @@ /*! For license information please see user_status-menu.js.LICENSE.txt */ -!function(){var e,n,r,s={93365:function(t,e,n){var r={"./af":36026,"./af.js":36026,"./ar":28093,"./ar-dz":41943,"./ar-dz.js":41943,"./ar-kw":23969,"./ar-kw.js":23969,"./ar-ly":40594,"./ar-ly.js":40594,"./ar-ma":18369,"./ar-ma.js":18369,"./ar-sa":32579,"./ar-sa.js":32579,"./ar-tn":76442,"./ar-tn.js":76442,"./ar.js":28093,"./az":86425,"./az.js":86425,"./be":22004,"./be.js":22004,"./bg":42982,"./bg.js":42982,"./bm":21067,"./bm.js":21067,"./bn":8366,"./bn-bd":63837,"./bn-bd.js":63837,"./bn.js":8366,"./bo":95040,"./bo.js":95040,"./br":521,"./br.js":521,"./bs":83242,"./bs.js":83242,"./ca":73046,"./ca.js":73046,"./cs":25794,"./cs.js":25794,"./cv":28231,"./cv.js":28231,"./cy":10927,"./cy.js":10927,"./da":42832,"./da.js":42832,"./de":29415,"./de-at":3331,"./de-at.js":3331,"./de-ch":45524,"./de-ch.js":45524,"./de.js":29415,"./dv":44700,"./dv.js":44700,"./el":88752,"./el.js":88752,"./en-au":90444,"./en-au.js":90444,"./en-ca":65959,"./en-ca.js":65959,"./en-gb":62762,"./en-gb.js":62762,"./en-ie":40909,"./en-ie.js":40909,"./en-il":79909,"./en-il.js":79909,"./en-in":87942,"./en-in.js":87942,"./en-nz":75200,"./en-nz.js":75200,"./en-sg":21415,"./en-sg.js":21415,"./eo":27447,"./eo.js":27447,"./es":86756,"./es-do":47049,"./es-do.js":47049,"./es-mx":15915,"./es-mx.js":15915,"./es-us":57133,"./es-us.js":57133,"./es.js":86756,"./et":72182,"./et.js":72182,"./eu":14419,"./eu.js":14419,"./fa":2916,"./fa.js":2916,"./fi":49964,"./fi.js":49964,"./fil":16448,"./fil.js":16448,"./fo":26094,"./fo.js":26094,"./fr":35833,"./fr-ca":56994,"./fr-ca.js":56994,"./fr-ch":2740,"./fr-ch.js":2740,"./fr.js":35833,"./fy":69542,"./fy.js":69542,"./ga":93264,"./ga.js":93264,"./gd":77457,"./gd.js":77457,"./gl":83043,"./gl.js":83043,"./gom-deva":24034,"./gom-deva.js":24034,"./gom-latn":28379,"./gom-latn.js":28379,"./gu":406,"./gu.js":406,"./he":73219,"./he.js":73219,"./hi":99834,"./hi.js":99834,"./hr":28754,"./hr.js":28754,"./hu":93945,"./hu.js":93945,"./hy-am":81319,"./hy-am.js":81319,"./id":24875,"./id.js":24875,"./is":23724,"./is.js":23724,"./it":79906,"./it-ch":34303,"./it-ch.js":34303,"./it.js":79906,"./ja":77105,"./ja.js":77105,"./jv":15026,"./jv.js":15026,"./ka":67416,"./ka.js":67416,"./kk":79734,"./kk.js":79734,"./km":60757,"./km.js":60757,"./kn":58369,"./kn.js":58369,"./ko":77687,"./ko.js":77687,"./ku":95544,"./ku.js":95544,"./ky":85431,"./ky.js":85431,"./lb":13613,"./lb.js":13613,"./lo":34252,"./lo.js":34252,"./lt":84619,"./lt.js":84619,"./lv":93760,"./lv.js":93760,"./me":93393,"./me.js":93393,"./mi":12369,"./mi.js":12369,"./mk":48664,"./mk.js":48664,"./ml":23099,"./ml.js":23099,"./mn":98539,"./mn.js":98539,"./mr":778,"./mr.js":778,"./ms":39970,"./ms-my":82625,"./ms-my.js":82625,"./ms.js":39970,"./mt":15714,"./mt.js":15714,"./my":53055,"./my.js":53055,"./nb":73945,"./nb.js":73945,"./ne":63645,"./ne.js":63645,"./nl":4829,"./nl-be":12823,"./nl-be.js":12823,"./nl.js":4829,"./nn":23756,"./nn.js":23756,"./oc-lnc":41228,"./oc-lnc.js":41228,"./pa-in":97877,"./pa-in.js":97877,"./pl":53066,"./pl.js":53066,"./pt":28677,"./pt-br":81592,"./pt-br.js":81592,"./pt.js":28677,"./ro":32722,"./ro.js":32722,"./ru":59138,"./ru.js":59138,"./sd":32568,"./sd.js":32568,"./se":49753,"./se.js":49753,"./si":58024,"./si.js":58024,"./sk":31058,"./sk.js":31058,"./sl":43452,"./sl.js":43452,"./sq":2795,"./sq.js":2795,"./sr":26976,"./sr-cyrl":38819,"./sr-cyrl.js":38819,"./sr.js":26976,"./ss":7467,"./ss.js":7467,"./sv":42787,"./sv.js":42787,"./sw":80298,"./sw.js":80298,"./ta":57532,"./ta.js":57532,"./te":76076,"./te.js":76076,"./tet":40452,"./tet.js":40452,"./tg":64794,"./tg.js":64794,"./th":48245,"./th.js":48245,"./tk":8870,"./tk.js":8870,"./tl-ph":36056,"./tl-ph.js":36056,"./tlh":15249,"./tlh.js":15249,"./tr":22053,"./tr.js":22053,"./tzl":39871,"./tzl.js":39871,"./tzm":39574,"./tzm-latn":19210,"./tzm-latn.js":19210,"./tzm.js":39574,"./ug-cn":91532,"./ug-cn.js":91532,"./uk":11432,"./uk.js":11432,"./ur":88523,"./ur.js":88523,"./uz":54958,"./uz-latn":68735,"./uz-latn.js":68735,"./uz.js":54958,"./vi":83398,"./vi.js":83398,"./x-pseudo":56665,"./x-pseudo.js":56665,"./yo":11642,"./yo.js":11642,"./zh-cn":5462,"./zh-cn.js":5462,"./zh-hk":92530,"./zh-hk.js":92530,"./zh-mo":41650,"./zh-mo.js":41650,"./zh-tw":97333,"./zh-tw.js":97333};function s(t){var e=a(t);return n(e)}function a(t){if(!n.o(r,t)){var e=new Error("Cannot find module '"+t+"'");throw e.code="MODULE_NOT_FOUND",e}return r[t]}s.keys=function(){return Object.keys(r)},s.resolve=a,t.exports=s,s.id=93365},10527:function(e,n,r){"use strict";var s=r(20144),a=r(22200),o=r(79753),i=r(16453),u=r(74854),c=r(20296),l=r.n(c),d=r(4820);function m(t,e,n,r,s,a,o){try{var i=t[a](o),u=i.value}catch(t){return void n(t)}i.done?e(u):Promise.resolve(u).then(r,s)}var p=function(){var t,e=(t=regeneratorRuntime.mark((function t(e){var n,r;return regeneratorRuntime.wrap((function(t){for(;;)switch(t.prev=t.next){case 0:return n=(0,o.generateOcsUrl)("apps/user_status/api/v1/heartbeat?format=json"),t.next=3,d.default.put(n,{status:e?"away":"online"});case 3:return r=t.sent,t.abrupt("return",r.data);case 5:case"end":return t.stop()}}),t)})),function(){var e=this,n=arguments;return new Promise((function(r,s){var a=t.apply(e,n);function o(t){m(a,r,s,o,i,"next",t)}function i(t){m(a,r,s,o,i,"throw",t)}o(void 0)}))});return function(t){return e.apply(this,arguments)}}(),f=r(84387);function v(t,e,n,r,s,a,o){try{var i=t[a](o),u=i.value}catch(t){return void n(t)}i.done?e(u):Promise.resolve(u).then(r,s)}var h=(0,i.loadState)("user_status","profileEnabled",!1).profileEnabled,g={name:"UserStatus",components:{SetStatusModal:function(){return Promise.all([r.e(7874),r.e(8299)]).then(r.bind(r,37181))}},mixins:[f.Z],props:{inline:{type:Boolean,default:!1}},data:function(){return{displayName:(0,a.getCurrentUser)().displayName,heartbeatInterval:null,isAway:!1,isModalOpen:!1,loadingProfilePage:!1,mouseMoveListener:null,profileEnabled:h,setAwayTimeout:null}},computed:{elementTag:function(){return this.inline?"div":"li"},profilePageLink:function(){return this.profileEnabled?(0,o.generateUrl)("/u/{userId}",{userId:(0,a.getCurrentUser)().uid}):null}},mounted:function(){var t=this;(0,u.Ld)("settings:display-name:updated",this.handleDisplayNameUpdate),(0,u.Ld)("settings:profile-enabled:updated",this.handleProfileEnabledUpdate),this.$store.dispatch("loadStatusFromInitialState"),OC.config.session_keepalive&&(this.heartbeatInterval=setInterval(this._backgroundHeartbeat.bind(this),3e5),this.setAwayTimeout=function(){t.isAway=!0},this.mouseMoveListener=l()((function(){var e=t.isAway;t.isAway=!1,clearTimeout(t.setAwayTimeout),setTimeout(t.setAwayTimeout,12e4),e&&t._backgroundHeartbeat()}),2e3,!0),window.addEventListener("mousemove",this.mouseMoveListener,{capture:!0,passive:!0}),this._backgroundHeartbeat()),(0,u.Ld)("user_status:status.updated",this.handleUserStatusUpdated)},beforeDestroy:function(){(0,u.r1)("settings:display-name:updated",this.handleDisplayNameUpdate),(0,u.r1)("settings:profile-enabled:updated",this.handleProfileEnabledUpdate),window.removeEventListener("mouseMove",this.mouseMoveListener),clearInterval(this.heartbeatInterval),(0,u.r1)("user_status:status.updated",this.handleUserStatusUpdated)},methods:{handleDisplayNameUpdate:function(t){this.displayName=t},handleProfileEnabledUpdate:function(t){this.profileEnabled=t},loadProfilePage:function(){this.profileEnabled&&(this.loadingProfilePage=!0)},openModal:function(){this.isModalOpen=!0},closeModal:function(){this.isModalOpen=!1},_backgroundHeartbeat:function(){var t,e=this;return(t=regeneratorRuntime.mark((function t(){var n,r;return regeneratorRuntime.wrap((function(t){for(;;)switch(t.prev=t.next){case 0:return t.prev=0,t.next=3,p(e.isAway);case 3:if(null==(n=t.sent)||!n.userId){t.next=8;break}e.$store.dispatch("setStatusFromHeartbeat",n),t.next=10;break;case 8:return t.next=10,e.$store.dispatch("reFetchStatusFromServer");case 10:t.next=15;break;case 12:t.prev=12,t.t0=t.catch(0),console.debug("Failed sending heartbeat, got: "+(null===(r=t.t0.response)||void 0===r?void 0:r.status));case 15:case"end":return t.stop()}}),t,null,[[0,12]])})),function(){var e=this,n=arguments;return new Promise((function(r,s){var a=t.apply(e,n);function o(t){v(a,r,s,o,i,"next",t)}function i(t){v(a,r,s,o,i,"throw",t)}o(void 0)}))})()},handleUserStatusUpdated:function(t){OC.getCurrentUser().uid===t.userId&&this.$store.dispatch("setStatusFromObject",{status:t.status,icon:t.icon,message:t.message})}}},A=g,b=r(93379),j=r.n(b),y=r(7795),C=r.n(y),w=r(90569),x=r.n(w),_=r(3565),k=r.n(_),S=r(19216),P=r.n(S),O=r(44589),I=r.n(O),U=r(58564),D={};D.styleTagTransform=I(),D.setAttributes=k(),D.insert=x().bind(null,"head"),D.domAPI=C(),D.insertStyleElement=P(),j()(U.Z,D),U.Z&&U.Z.locals&&U.Z.locals;var M=(0,r(51900).Z)(A,(function(){var t=this,e=t.$createElement,n=t._self._c||e;return n(t.elementTag,{tag:"component"},[n("div",{staticClass:"user-status-menu-item"},[t.inline?t._e():n("a",{staticClass:"user-status-menu-item__header",attrs:{href:t.profilePageLink},on:{click:t.loadProfilePage}},[n("div",{staticClass:"user-status-menu-item__header-content"},[n("div",{staticClass:"user-status-menu-item__header-content-displayname"},[t._v(t._s(t.displayName))]),t._v(" "),t.loadingProfilePage?n("div",{staticClass:"icon-loading-small"}):n("div",{staticClass:"user-status-menu-item__header-content-placeholder"})]),t._v(" "),t.profileEnabled?n("div",[t._v("\n\t\t\t\t"+t._s(t.t("user_status","View profile"))+"\n\t\t\t")]):t._e()]),t._v(" "),n(t.inline?"button":"a",{tag:"toggle",staticClass:"user-status-menu-item__toggle",class:{"user-status-menu-item__toggle--inline":t.inline},attrs:{href:"#"},on:{click:function(e){return e.preventDefault(),e.stopPropagation(),t.openModal.apply(null,arguments)}}},[n("span",{staticClass:"user-status-menu-item__toggle-icon",class:t.statusIcon}),t._v("\n\t\t\t"+t._s(t.visibleMessage)+"\n\t\t")])],1),t._v(" "),t.isModalOpen?n("SetStatusModal",{on:{close:t.closeModal}}):t._e()],1)}),[],!1,null,"4aced826",null),E=M.exports,R=r(20629);function T(t,e,n,r,s,a,o){try{var i=t[a](o),u=i.value}catch(t){return void n(t)}i.done?e(u):Promise.resolve(u).then(r,s)}var z=function(){var t,e=(t=regeneratorRuntime.mark((function t(){var e,n;return regeneratorRuntime.wrap((function(t){for(;;)switch(t.prev=t.next){case 0:return e=(0,o.generateOcsUrl)("apps/user_status/api/v1/predefined_statuses?format=json"),t.next=3,d.default.get(e);case 3:return n=t.sent,t.abrupt("return",n.data.ocs.data);case 5:case"end":return t.stop()}}),t)})),function(){var e=this,n=arguments;return new Promise((function(r,s){var a=t.apply(e,n);function o(t){T(a,r,s,o,i,"next",t)}function i(t){T(a,r,s,o,i,"throw",t)}o(void 0)}))});return function(){return e.apply(this,arguments)}}();function B(t,e){var n="undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(!n){if(Array.isArray(t)||(n=function(t,e){if(t){if("string"==typeof t)return F(t,e);var n=Object.prototype.toString.call(t).slice(8,-1);return"Object"===n&&t.constructor&&(n=t.constructor.name),"Map"===n||"Set"===n?Array.from(t):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?F(t,e):void 0}}(t))||e&&t&&"number"==typeof t.length){n&&(t=n);var r=0,s=function(){};return{s:s,n:function(){return r>=t.length?{done:!0}:{done:!1,value:t[r++]}},e:function(t){throw t},f:s}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var a,o=!0,i=!1;return{s:function(){n=n.call(t)},n:function(){var t=n.next();return o=t.done,t},e:function(t){i=!0,a=t},f:function(){try{o||null==n.return||n.return()}finally{if(i)throw a}}}}function F(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,r=new Array(e);n0)){e.next=3;break}return e.abrupt("return");case 3:return e.next=5,z();case 5:s=e.sent,a=B(s);try{for(a.s();!(o=a.n()).done;)i=o.value,r("addPredefinedStatus",i)}catch(t){a.e(t)}finally{a.f()}case 8:case"end":return e.stop()}}),e)})),function(){var t=this,n=arguments;return new Promise((function(r,s){var a=e.apply(t,n);function o(t){$(a,r,s,o,i,"next",t)}function i(t){$(a,r,s,o,i,"throw",t)}o(void 0)}))})();var e}},N={state:{predefinedStatuses:[]},mutations:{addPredefinedStatus:function(t,e){t.predefinedStatuses.push(e)}},getters:{},actions:L};function Z(t,e,n,r,s,a,o){try{var i=t[a](o),u=i.value}catch(t){return void n(t)}i.done?e(u):Promise.resolve(u).then(r,s)}function q(t){return function(){var e=this,n=arguments;return new Promise((function(r,s){var a=t.apply(e,n);function o(t){Z(a,r,s,o,i,"next",t)}function i(t){Z(a,r,s,o,i,"throw",t)}o(void 0)}))}}var H=function(){var t=q(regeneratorRuntime.mark((function t(){var e,n;return regeneratorRuntime.wrap((function(t){for(;;)switch(t.prev=t.next){case 0:return e=(0,o.generateOcsUrl)("apps/user_status/api/v1/user_status"),t.next=3,d.default.get(e);case 3:return n=t.sent,t.abrupt("return",n.data.ocs.data);case 5:case"end":return t.stop()}}),t)})));return function(){return t.apply(this,arguments)}}(),G=function(){var t=q(regeneratorRuntime.mark((function t(e){var n;return regeneratorRuntime.wrap((function(t){for(;;)switch(t.prev=t.next){case 0:return n=(0,o.generateOcsUrl)("apps/user_status/api/v1/user_status/status"),t.next=3,d.default.put(n,{statusType:e});case 3:case"end":return t.stop()}}),t)})));return function(e){return t.apply(this,arguments)}}(),Q=function(){var t=q(regeneratorRuntime.mark((function t(e){var n,r,s=arguments;return regeneratorRuntime.wrap((function(t){for(;;)switch(t.prev=t.next){case 0:return n=s.length>1&&void 0!==s[1]?s[1]:null,r=(0,o.generateOcsUrl)("apps/user_status/api/v1/user_status/message/predefined?format=json"),t.next=4,d.default.put(r,{messageId:e,clearAt:n});case 4:case"end":return t.stop()}}),t)})));return function(e){return t.apply(this,arguments)}}(),W=function(){var t=q(regeneratorRuntime.mark((function t(e){var n,r,s,a=arguments;return regeneratorRuntime.wrap((function(t){for(;;)switch(t.prev=t.next){case 0:return n=a.length>1&&void 0!==a[1]?a[1]:null,r=a.length>2&&void 0!==a[2]?a[2]:null,s=(0,o.generateOcsUrl)("apps/user_status/api/v1/user_status/message/custom?format=json"),t.next=5,d.default.put(s,{message:e,statusIcon:n,clearAt:r});case 5:case"end":return t.stop()}}),t)})));return function(e){return t.apply(this,arguments)}}(),J=function(){var t=q(regeneratorRuntime.mark((function t(){var e;return regeneratorRuntime.wrap((function(t){for(;;)switch(t.prev=t.next){case 0:return e=(0,o.generateOcsUrl)("apps/user_status/api/v1/user_status/message?format=json"),t.next=3,d.default.delete(e);case 3:case"end":return t.stop()}}),t)})));return function(){return t.apply(this,arguments)}}(),K=r(64039),V=r(80351),X=r.n(V),Y=function(t){if(null===t)return null;var e=(0,K.n)();if("period"===t.type)return e.setSeconds(e.getSeconds()+t.time),Math.floor(e.getTime()/1e3);if("end-of"===t.type)switch(t.time){case"day":case"week":return Number(X()(e).endOf(t.time).format("X"))}return"_time"===t.type?t.time:null};function tt(t,e,n,r,s,a,o){try{var i=t[a](o),u=i.value}catch(t){return void n(t)}i.done?e(u):Promise.resolve(u).then(r,s)}function et(t){return function(){var e=this,n=arguments;return new Promise((function(r,s){var a=t.apply(e,n);function o(t){tt(a,r,s,o,i,"next",t)}function i(t){tt(a,r,s,o,i,"throw",t)}o(void 0)}))}}var nt={state:{status:null,statusIsUserDefined:null,message:null,icon:null,clearAt:null,messageIsPredefined:null,messageId:null},mutations:{setStatus:function(t,e){var n=e.statusType;t.status=n,t.statusIsUserDefined=!0},setPredefinedMessage:function(t,e){var n=e.messageId,r=e.clearAt,s=e.message,a=e.icon;t.messageId=n,t.messageIsPredefined=!0,t.message=s,t.icon=a,t.clearAt=r},setCustomMessage:function(t,e){var n=e.message,r=e.icon,s=e.clearAt;t.messageId=null,t.messageIsPredefined=!1,t.message=n,t.icon=r,t.clearAt=s},clearMessage:function(t){t.messageId=null,t.messageIsPredefined=!1,t.message=null,t.icon=null,t.clearAt=null},loadStatusFromServer:function(t,e){var n=e.status,r=e.statusIsUserDefined,s=e.message,a=e.icon,o=e.clearAt,i=e.messageIsPredefined,u=e.messageId;t.status=n,t.message=s,t.icon=a,void 0!==r&&(t.statusIsUserDefined=r),void 0!==o&&(t.clearAt=o),void 0!==i&&(t.messageIsPredefined=i),void 0!==u&&(t.messageId=u)}},getters:{},actions:{setStatus:function(t,e){return et(regeneratorRuntime.mark((function n(){var r,s,o,i;return regeneratorRuntime.wrap((function(n){for(;;)switch(n.prev=n.next){case 0:return s=t.commit,o=t.state,i=e.statusType,n.next=4,G(i);case 4:s("setStatus",{statusType:i}),(0,u.j8)("user_status:status.updated",{status:o.status,message:o.message,icon:o.icon,clearAt:o.clearAt,userId:null===(r=(0,a.getCurrentUser)())||void 0===r?void 0:r.uid});case 6:case"end":return n.stop()}}),n)})))()},setStatusFromObject:function(t,e){return et(regeneratorRuntime.mark((function n(){var r;return regeneratorRuntime.wrap((function(n){for(;;)switch(n.prev=n.next){case 0:r=t.commit,t.state,r("loadStatusFromServer",e);case 2:case"end":return n.stop()}}),n)})))()},setPredefinedMessage:function(t,e){return et(regeneratorRuntime.mark((function n(){var r,s,o,i,c,l,d,m,p,f;return regeneratorRuntime.wrap((function(n){for(;;)switch(n.prev=n.next){case 0:return s=t.commit,o=t.rootState,i=t.state,c=e.messageId,l=e.clearAt,d=Y(l),n.next=5,Q(c,d);case 5:m=o.predefinedStatuses.predefinedStatuses.find((function(t){return t.id===c})),p=m.message,f=m.icon,s("setPredefinedMessage",{messageId:c,clearAt:d,message:p,icon:f}),(0,u.j8)("user_status:status.updated",{status:i.status,message:i.message,icon:i.icon,clearAt:i.clearAt,userId:null===(r=(0,a.getCurrentUser)())||void 0===r?void 0:r.uid});case 9:case"end":return n.stop()}}),n)})))()},setCustomMessage:function(t,e){return et(regeneratorRuntime.mark((function n(){var r,s,o,i,c,l,d;return regeneratorRuntime.wrap((function(n){for(;;)switch(n.prev=n.next){case 0:return s=t.commit,o=t.state,i=e.message,c=e.icon,l=e.clearAt,d=Y(l),n.next=5,W(i,c,d);case 5:s("setCustomMessage",{message:i,icon:c,clearAt:d}),(0,u.j8)("user_status:status.updated",{status:o.status,message:o.message,icon:o.icon,clearAt:o.clearAt,userId:null===(r=(0,a.getCurrentUser)())||void 0===r?void 0:r.uid});case 7:case"end":return n.stop()}}),n)})))()},clearMessage:function(t){return et(regeneratorRuntime.mark((function e(){var n,r,s;return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return r=t.commit,s=t.state,e.next=3,J();case 3:r("clearMessage"),(0,u.j8)("user_status:status.updated",{status:s.status,message:s.message,icon:s.icon,clearAt:s.clearAt,userId:null===(n=(0,a.getCurrentUser)())||void 0===n?void 0:n.uid});case 5:case"end":return e.stop()}}),e)})))()},reFetchStatusFromServer:function(t){return et(regeneratorRuntime.mark((function e(){var n,r;return regeneratorRuntime.wrap((function(e){for(;;)switch(e.prev=e.next){case 0:return n=t.commit,e.next=3,H();case 3:r=e.sent,n("loadStatusFromServer",r);case 5:case"end":return e.stop()}}),e)})))()},setStatusFromHeartbeat:function(t,e){return et(regeneratorRuntime.mark((function n(){return regeneratorRuntime.wrap((function(n){for(;;)switch(n.prev=n.next){case 0:(0,t.commit)("loadStatusFromServer",e);case 2:case"end":return n.stop()}}),n)})))()},loadStatusFromInitialState:function(t){(0,t.commit)("loadStatusFromServer",(0,i.loadState)("user_status","status"))}}};s.ZP.use(R.ZP);var rt=new R.yh({modules:{predefinedStatuses:N,userStatus:nt},strict:!0}),st=r(75925),at=r.n(st);r.nc=btoa((0,a.getRequestToken)()),s.ZP.prototype.t=t,s.ZP.prototype.$t=t;var ot=document.getElementById("avatardiv-menu"),it=(0,i.loadState)("user_status","status"),ut={preloadedUserStatus:{message:it.message,icon:it.icon,status:it.status},user:ot.dataset.user,displayName:ot.dataset.displayname,url:ot.dataset.avatar,disableMenu:!0,disableTooltip:!0};new(s.ZP.extend(at()))({propsData:ut}).$mount("#avatardiv-menu"),new s.ZP({el:'li[data-id="user_status-menuitem"]',name:"UserStatusRoot",render:function(t){return t(E)},store:rt}),document.addEventListener("DOMContentLoaded",(function(){OCA.Dashboard&&OCA.Dashboard.registerStatus("status",(function(t){return new(s.ZP.extend(E))({propsData:{inline:!0},store:rt}).$mount(t)}))}))},84387:function(t,e,n){"use strict";var r=n(20629),s=n(26932);function a(t,e,n,r,s,a,o){try{var i=t[a](o),u=i.value}catch(t){return void n(t)}i.done?e(u):Promise.resolve(u).then(r,s)}function o(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(t);e&&(r=r.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),n.push.apply(n,r)}return n}function i(t){for(var e=1;e