From a7799d536cc2fa6aae2898fe4684edd4988d19de Mon Sep 17 00:00:00 2001 From: Christopher Ng Date: Tue, 7 Sep 2021 23:21:12 +0000 Subject: [PATCH] [WHIP] Updates Signed-off-by: Christopher Ng --- apps/settings/css/settings.scss | 8 +- .../lib/Settings/Personal/PersonalInfo.php | 24 +++ .../PersonalInfo/CompanySection/Company.vue | 178 ++++++++++++++++++ .../CompanySection/CompanySection.vue | 83 ++++++++ .../DisplayNameSection/DisplayName.vue | 2 +- .../ProfileSection/ProfileCheckbox.vue | 2 +- apps/settings/src/main-personal-info.js | 11 +- .../settings/personal/personal.info.php | 20 +- apps/theming/css/theming.scss | 9 +- apps/user_status/src/UserStatus.vue | 8 +- core/Controller/ProfileController.php | 125 ++++++++++-- core/css/css-variables.scss | 2 - core/css/variables.scss | 2 - core/src/views/Profile.vue | 64 +++---- 14 files changed, 456 insertions(+), 82 deletions(-) create mode 100644 apps/settings/src/components/PersonalInfo/CompanySection/Company.vue create mode 100644 apps/settings/src/components/PersonalInfo/CompanySection/CompanySection.vue diff --git a/apps/settings/css/settings.scss b/apps/settings/css/settings.scss index 6a00519f797a7..ab5c0e5e173f1 100644 --- a/apps/settings/css/settings.scss +++ b/apps/settings/css/settings.scss @@ -114,7 +114,7 @@ input { .profile-settings-container { display: inline-grid; grid-template-columns: 1fr; - grid-template-rows: 1fr 1fr 2fr; + grid-template-rows: 1fr 1fr 1fr 2fr; #locale { h3 { @@ -223,7 +223,7 @@ select { .personal-settings-container { grid-template-columns: 1fr 1fr; - grid-template-rows: 1fr 1fr 1fr; + grid-template-rows: 1fr 1fr 1fr 1fr; } .profile-settings-container { @@ -245,7 +245,7 @@ select { .personal-settings-container { grid-template-columns: 1fr 1fr; - grid-template-rows: 1fr 1fr 1fr; + grid-template-rows: 1fr 1fr 1fr 1fr; } .profile-settings-container { @@ -279,7 +279,7 @@ select { .personal-settings-container { display: inline-grid; grid-template-columns: 1fr 1fr; - grid-template-rows: 1fr 1fr 2fr; + grid-template-rows: 1fr 1fr 1fr 2fr; &:after { clear: both; diff --git a/apps/settings/lib/Settings/Personal/PersonalInfo.php b/apps/settings/lib/Settings/Personal/PersonalInfo.php index c0a2b6a0a9fee..b5c55c7a858f7 100644 --- a/apps/settings/lib/Settings/Personal/PersonalInfo.php +++ b/apps/settings/lib/Settings/Personal/PersonalInfo.php @@ -151,6 +151,7 @@ public function getForm(): TemplateResponse { 'emails' => $this->getEmails($account), 'languages' => $this->getLanguages($user), 'profileEnabled' => $this->getProfileEnabled($account), + 'companies' => $this->getCompanies($account), ]; $accountParameters = [ @@ -164,6 +165,29 @@ public function getForm(): TemplateResponse { return new TemplateResponse('settings', 'settings/personal/personal.info', $parameters, ''); } + /** + * returns the primary display name in an + * associative array + * + * NOTE may be extended to provide additional companies in the future + * + * @param IAccount $account + * @return array + */ + private function getCompanies(IAccount $account): array { + $primaryCompany = [ + 'value' => $account->getProperty(IAccountManager::PROPERTY_COMPANY)->getValue(), + 'scope' => $account->getProperty(IAccountManager::PROPERTY_COMPANY)->getScope(), + 'verified' => $account->getProperty(IAccountManager::PROPERTY_COMPANY)->getVerified(), + ]; + + $companies = [ + 'primaryCompany' => $primaryCompany, + ]; + + return $companies; + } + /** * @return string the section ID, e.g. 'sharing' * @since 9.1 diff --git a/apps/settings/src/components/PersonalInfo/CompanySection/Company.vue b/apps/settings/src/components/PersonalInfo/CompanySection/Company.vue new file mode 100644 index 0000000000000..5033265ef600d --- /dev/null +++ b/apps/settings/src/components/PersonalInfo/CompanySection/Company.vue @@ -0,0 +1,178 @@ + + + + + + + diff --git a/apps/settings/src/components/PersonalInfo/CompanySection/CompanySection.vue b/apps/settings/src/components/PersonalInfo/CompanySection/CompanySection.vue new file mode 100644 index 0000000000000..9dc18d97be066 --- /dev/null +++ b/apps/settings/src/components/PersonalInfo/CompanySection/CompanySection.vue @@ -0,0 +1,83 @@ + + + + + + + diff --git a/apps/settings/src/components/PersonalInfo/DisplayNameSection/DisplayName.vue b/apps/settings/src/components/PersonalInfo/DisplayNameSection/DisplayName.vue index 67b94dbe8e9ee..a318ad9ce31e9 100644 --- a/apps/settings/src/components/PersonalInfo/DisplayNameSection/DisplayName.vue +++ b/apps/settings/src/components/PersonalInfo/DisplayNameSection/DisplayName.vue @@ -107,7 +107,7 @@ export default { if (status === 'ok') { // Ensure that local state reflects server state this.initialDisplayName = displayName - emit('settings:displayName:updated', displayName) + emit('settings:display-name:updated', displayName) this.showCheckmarkIcon = true setTimeout(() => { this.showCheckmarkIcon = false }, 2000) } else { diff --git a/apps/settings/src/components/PersonalInfo/ProfileSection/ProfileCheckbox.vue b/apps/settings/src/components/PersonalInfo/ProfileSection/ProfileCheckbox.vue index 36eb22fc8315f..74358849c4af4 100644 --- a/apps/settings/src/components/PersonalInfo/ProfileSection/ProfileCheckbox.vue +++ b/apps/settings/src/components/PersonalInfo/ProfileSection/ProfileCheckbox.vue @@ -88,7 +88,7 @@ export default { if (status === 'ok') { // Ensure that local state reflects server state this.initialProfileEnabled = isEnabled - emit('settings:profileEnabled:updated', isEnabled) + emit('settings:profile-enabled:updated', isEnabled) this.showCheckmarkIcon = true setTimeout(() => { this.showCheckmarkIcon = false }, 2000) } else { diff --git a/apps/settings/src/main-personal-info.js b/apps/settings/src/main-personal-info.js index 25a8d533590c4..bea4a2b061342 100644 --- a/apps/settings/src/main-personal-info.js +++ b/apps/settings/src/main-personal-info.js @@ -31,6 +31,7 @@ import DisplayNameSection from './components/PersonalInfo/DisplayNameSection/Dis import EmailSection from './components/PersonalInfo/EmailSection/EmailSection' import LanguageSection from './components/PersonalInfo/LanguageSection/LanguageSection' import ProfileSection from './components/PersonalInfo/ProfileSection/ProfileSection' +import CompanySection from './components/PersonalInfo/CompanySection/CompanySection' __webpack_nonce__ = btoa(getRequestToken()) @@ -47,8 +48,10 @@ const DisplayNameView = Vue.extend(DisplayNameSection) const EmailView = Vue.extend(EmailSection) const LanguageView = Vue.extend(LanguageSection) const ProfileView = Vue.extend(ProfileSection) +const CompanyView = Vue.extend(CompanySection) -new DisplayNameView().$mount('#vue-displaynamesection') -new EmailView().$mount('#vue-emailsection') -new LanguageView().$mount('#vue-languagesection') -new ProfileView().$mount('#vue-profilesection') +new DisplayNameView().$mount('#vue-displayname-section') +new EmailView().$mount('#vue-email-section') +new LanguageView().$mount('#vue-language-section') +new ProfileView().$mount('#vue-profile-section') +new CompanyView().$mount('#vue-company-section') diff --git a/apps/settings/templates/settings/personal/personal.info.php b/apps/settings/templates/settings/personal/personal.info.php index 359f7a2369719..b2e77127b4a62 100644 --- a/apps/settings/templates/settings/personal/personal.info.php +++ b/apps/settings/templates/settings/personal/personal.info.php @@ -100,10 +100,10 @@
-
+
-
+
@@ -241,14 +241,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
+
diff --git a/apps/theming/css/theming.scss b/apps/theming/css/theming.scss index 2b9d12d2cd495..2d3d0d6aad6bb 100644 --- a/apps/theming/css/theming.scss +++ b/apps/theming/css/theming.scss @@ -109,8 +109,13 @@ $invert: luma($color-primary) > 0.6; background-image: $image-logo; } -#body-user #header, #body-settings #header, #body-public #header { - @include faded-background; +#body-user, +#body-settings, +#body-public { + #header, + .profile__header { + @include faded-background; + } } #body-login, diff --git a/apps/user_status/src/UserStatus.vue b/apps/user_status/src/UserStatus.vue index 26b7483dff51f..b4f1a847c0a52 100644 --- a/apps/user_status/src/UserStatus.vue +++ b/apps/user_status/src/UserStatus.vue @@ -117,8 +117,8 @@ export default { * and stores it in Vuex */ mounted() { - subscribe('settings:displayName:updated', this.handleDisplayNameUpdate) - subscribe('settings:profileEnabled:updated', this.handleProfileEnabledUpdate) + subscribe('settings:display-name:updated', this.handleDisplayNameUpdate) + subscribe('settings:profile-enabled:updated', this.handleProfileEnabledUpdate) this.$store.dispatch('loadStatusFromInitialState') @@ -155,8 +155,8 @@ export default { * Some housekeeping before destroying the component */ beforeDestroy() { - unsubscribe('settings:displayName:updated', this.handleDisplayNameUpdate) - unsubscribe('settings:profileEnabled:updated', this.handleProfileEnabledUpdate) + unsubscribe('settings:display-name:updated', this.handleDisplayNameUpdate) + unsubscribe('settings:profile-enabled:updated', this.handleProfileEnabledUpdate) window.removeEventListener('mouseMove', this.mouseMoveListener) clearInterval(this.heartbeatInterval) }, diff --git a/core/Controller/ProfileController.php b/core/Controller/ProfileController.php index 9cc5f5cda6a45..657565afc641a 100644 --- a/core/Controller/ProfileController.php +++ b/core/Controller/ProfileController.php @@ -1,4 +1,5 @@ * @@ -24,10 +25,10 @@ declare(strict_types=1); - namespace OC\Core\Controller; use EmailAction; +use \OCP\AppFramework\Controller; use OCP\Accounts\IAccountManager; use OCP\AppFramework\Http\TemplateResponse; use OCP\AppFramework\Services\IInitialState; @@ -40,8 +41,17 @@ use OCP\App\IAppManager; use OCP\IUserManager; use OCP\Profile\IActionManager; +use OCP\IURLGenerator; +use OCA\Federation\TrustedServers; +use function Safe\substr; + +class ProfileController extends Controller { -class ProfileController extends \OCP\AppFramework\Controller { + /** @var IURLGenerator */ + private $urlGenerator; + + /** @var TrustedServers */ + private $trustedServers; /** @var IL10N */ private $l10n; @@ -71,16 +81,20 @@ public function __construct( $appName, IRequest $request, IL10N $l10n, + IURLGenerator $urlGenerator, + TrustedServers $trustedServers, IUserSession $userSession, IUserManager $userManager, IAccountManager $accountManager, IInitialState $initialStateService, IAppManager $appManager, - IManager $userStatusManager, + IManager $userStatusManager // IActionManager $actionManager ) { parent::__construct($appName, $request); $this->l10n = $l10n; + $this->urlGenerator = $urlGenerator; + $this->trustedServers = $trustedServers; $this->userSession = $userSession; $this->userManager = $userManager; $this->accountManager = $accountManager; @@ -90,7 +104,25 @@ public function __construct( // $this->actionManager = $actionManager; } - public const PROPERTY_ACTIONS = [ + public const PROFILE_DISPLAY_PROPERTIES = [ + IAccountManager::PROPERTY_DISPLAYNAME, + IAccountManager::PROPERTY_ADDRESS, + IAccountManager::PROPERTY_COMPANY, + IAccountManager::PROPERTY_JOB_TITLE, + IAccountManager::PROPERTY_HEADLINE, + IAccountManager::PROPERTY_BIOGRAPHY, + ]; + + public const PROFILE_DISPLAY_PROPERTY_JSON_MAP = [ + IAccountManager::PROPERTY_DISPLAYNAME => 'displayName', + IAccountManager::PROPERTY_ADDRESS => 'address', + IAccountManager::PROPERTY_COMPANY => 'company', + IAccountManager::PROPERTY_JOB_TITLE => 'jobTitle', + IAccountManager::PROPERTY_HEADLINE => 'headline', + IAccountManager::PROPERTY_BIOGRAPHY => 'biography', + ]; + + public const PROFILE_ACTION_PROPERTIES = [ IAccountManager::PROPERTY_EMAIL, IAccountManager::PROPERTY_PHONE, IAccountManager::PROPERTY_WEBSITE, @@ -98,9 +130,15 @@ public function __construct( ]; /** - * @NoCSRFRequired - * @UseSession + * Useful annotations + * @PublicPage + * @NoAdminRequired + */ + + /** * FIXME Public page annotation blocks the user session somehow + * @UseSession + * @NoCSRFRequired */ public function index(string $userId = null): TemplateResponse { $isLoggedIn = $this->userSession->isLoggedIn(); @@ -151,31 +189,84 @@ public function index(string $userId = null): TemplateResponse { * TODO handle federation scope permissions */ private function getProfileParams(IAccount $account): array { - // for scope, if: - // 1) Published - visible to everybody - // 2) Federated - visible to users on trusted servers - // 3) Local - visible to users and guests on same server instance - // 4) Private - visible to users matched through phone number integration on Talk app + $isLoggedIn = $this->userSession->isLoggedIn(); + $serverBaseUrl = $this->urlGenerator->getBaseUrl(); + $reqProtocol = $this->request->getServerProtocol(); + $reqHost = $this->request->getInsecureServerHost(); + $reqUri = $this->request->getRequestUri(); + $reqBaseUrl = substr("$reqProtocol://$reqHost$reqUri", 0, strlen($serverBaseUrl)); + $isSameServerInstance = $serverBaseUrl === $reqBaseUrl; $additionalEmails = array_map( function (IAccountProperty $property) { - return [ - 'value' => $property->getValue(), - 'scope' => $property->getScope(), - ]; + return $property->getValue(); }, $account->getPropertyCollection(IAccountManager::COLLECTION_EMAIL)->getProperties() ); + $profileParameters = [ + 'userId' => $account->getUser()->getUID(), + ]; + + // for scope, if: + // 1) Private - visible to users on same server instance + // 2) Local - visible to users and public link visitors on same server instance + // 3) Federated - visible to users and public link visitors on same server instance and trusted servers + // 4) Published - same as Federated but also published to public lookup server + foreach (self::PROFILE_DISPLAY_PROPERTIES as $property) { + $scope = $account->getProperty($property)->getScope(); + switch ($scope) { + case IAccountManager::SCOPE_PRIVATE: + $profileParameters[self::PROFILE_DISPLAY_PROPERTY_JSON_MAP[$property]] = + ($isLoggedIn && $isSameServerInstance) ? $account->getProperty($property)->getValue() : null; + break; + case IAccountManager::SCOPE_LOCAL: + $profileParameters[self::PROFILE_DISPLAY_PROPERTY_JSON_MAP[$property]] = + $isSameServerInstance ? $account->getProperty($property)->getValue() : null; + break; + case IAccountManager::SCOPE_FEDERATED: + $profileParameters[self::PROFILE_DISPLAY_PROPERTY_JSON_MAP[$property]] = + $this->trustedServers->isTrustedServer($serverBaseUrl) ? $account->getProperty($property)->getValue() : null; + break; + case IAccountManager::SCOPE_PUBLISHED: + $profileParameters[self::PROFILE_DISPLAY_PROPERTY_JSON_MAP[$property]] = + $this->trustedServers->isTrustedServer($serverBaseUrl) ? $account->getProperty($property)->getValue() : null; + break; + default: + $profileParameters[self::PROFILE_DISPLAY_PROPERTY_JSON_MAP[$property]] = null; + break; + } + } + + $avatarScope = $account->getProperty(IAccountManager::PROPERTY_AVATAR)->getScope(); + switch ($avatarScope) { + case IAccountManager::SCOPE_PRIVATE: + $profileParameters['isAvatarDisplayed'] = ($isLoggedIn && $isSameServerInstance); + break; + case IAccountManager::SCOPE_LOCAL: + $profileParameters['isAvatarDisplayed'] = $isSameServerInstance; + break; + case IAccountManager::SCOPE_FEDERATED: + $profileParameters['isAvatarDisplayed'] = $this->trustedServers->isTrustedServer($serverBaseUrl); + break; + case IAccountManager::SCOPE_PUBLISHED: + $profileParameters['isAvatarDisplayed'] = $this->trustedServers->isTrustedServer($serverBaseUrl); + break; + default: + $profileParameters['isAvatarDisplayed'] = false; + break; + } + + $profileParameters = [ 'userId' => $account->getUser()->getUID(), 'displayName' => $account->getProperty(IAccountManager::PROPERTY_DISPLAYNAME)->getValue(), - // 'additionalEmails' => $additionalEmails, 'address' => $account->getProperty(IAccountManager::PROPERTY_ADDRESS)->getValue(), // Ordered by precedence, order is preserved in PHP and modern JavaScript 'actionParameters' => [ 'talkEnabled' => $this->appManager->isEnabledForUser('spreed', $account->getUser()), 'email' => $account->getProperty(IAccountManager::PROPERTY_EMAIL)->getValue(), + // 'additionalEmails' => $additionalEmails, 'phoneNumber' => $account->getProperty(IAccountManager::PROPERTY_PHONE)->getValue(), 'website' => $account->getProperty(IAccountManager::PROPERTY_WEBSITE)->getValue(), 'twitterUsername' => $account->getProperty(IAccountManager::PROPERTY_TWITTER)->getValue(), @@ -186,7 +277,7 @@ function (IAccountProperty $property) { } protected function initActions(IAccount $account) { - foreach(self::PROPERTY_ACTIONS as $property) { + foreach (self::PROFILE_ACTION_PROPERTIES as $property) { $scope = $account->getProperty($property)->getScope(); $value = $account->getProperty($property)->getValue(); diff --git a/core/css/css-variables.scss b/core/css/css-variables.scss index b1f5b1265f544..86f80611a6c40 100644 --- a/core/css/css-variables.scss +++ b/core/css/css-variables.scss @@ -19,8 +19,6 @@ --color-primary: #{$color-primary}; --color-primary-light: #{$color-primary-light}; - --color-primary-gradient-dark: #{$color-primary-gradient-dark}; - --color-primary-gradient-light: #{$color-primary-gradient-light}; --color-primary-text: #{$color-primary-text}; --color-primary-text-dark: #{$color-primary-text-dark}; --color-primary-element: #{$color-primary-element}; diff --git a/core/css/variables.scss b/core/css/variables.scss index 7b188ae5e9ec6..4a5be2e4e8c4f 100644 --- a/core/css/variables.scss +++ b/core/css/variables.scss @@ -45,8 +45,6 @@ $color-placeholder-dark: nc-darken($color-main-background, 20%) !default; $color-primary: #0082c9 !default; $color-primary-light: mix($color-primary, $color-main-background, 10%) !default; -$color-primary-gradient-dark: #0082c9 !default; -$color-primary-gradient-light: #30b6ff !default; $color-primary-text: #ffffff !default; // do not use nc-darken/lighten in case of overriding because // primary-text is independent of color-main-text diff --git a/core/src/views/Profile.vue b/core/src/views/Profile.vue index c4261552529b2..d1ac8b4cb5785 100644 --- a/core/src/views/Profile.vue +++ b/core/src/views/Profile.vue @@ -61,37 +61,37 @@ :default-icon="primaryAction.icon" :menu-title="primaryAction.label" :force-menu="false"> - + :href="primaryAction.href" + :title="primaryAction.label"> {{ primaryAction.label }} - + @@ -132,11 +132,12 @@ import { generateUrl } from '@nextcloud/router' import Avatar from '@nextcloud/vue/dist/Components/Avatar' import Actions from '@nextcloud/vue/dist/Components/Actions' -import ActionButton from '@nextcloud/vue/dist/Components/ActionButton' +import ActionLink from '@nextcloud/vue/dist/Components/ActionLink' // import Modal from '@nextcloud/vue/dist/Components/Modal' // import ProfileSettings from './ProfileSettings' // import PhoneIcon from 'vue-material-design-icons/Phone' import MapMarkerIcon from 'vue-material-design-icons/MapMarker' +import { showError } from '@nextcloud/dialogs' const { userId, displayName, address, actionParameters } = loadState('core', 'profileParameters', {}) const status = loadState('core', 'status', {}) @@ -147,7 +148,7 @@ export default { components: { Avatar, Actions, - ActionButton, + ActionLink, // PhoneIcon, MapMarkerIcon, }, @@ -167,7 +168,7 @@ export default { computed: { isCurrentUser() { - return getCurrentUser().uid === this.userId + return getCurrentUser()?.uid === this.userId }, primaryAction() { @@ -182,27 +183,27 @@ export default { talkEnabled: { icon: 'icon-talk', label: `Talk to ${this.displayName}`, - cb: this.openTalk, + href: generateUrl('apps/spreed?callUser={userId}', { userId }), }, email: { icon: 'icon-mail', label: `Email ${actionParameters.email}`, - cb: this.sendEmail, + href: `mailto:${actionParameters.email}`, }, phoneNumber: { icon: 'icon-phone', label: `Call phone number (${actionParameters.phoneNumber})`, - cb: this.callPhone, + href: `tel:${actionParameters.phoneNumber}`, }, website: { icon: 'icon-timezone', label: `Visit website (${actionParameters.website})`, - cb: this.openWebsite, + href: actionParameters.website, }, twitterUsername: { icon: 'icon-twitter', label: `View Twitter profile ${actionParameters.twitterUsername[0] === '@' ? actionParameters.twitterUsername : `@${actionParameters.twitterUsername}`}`, - cb: this.openTwitterProfile, + href: `https://twitter.com/${actionParameters.twitterUsername}`, }, } @@ -236,32 +237,14 @@ export default { const statusMenuItem = document.querySelector('.user-status-menu-item__toggle') if (statusMenuItem) { statusMenuItem.click() + } else { + showError(t('core', 'Error opening the user status modal, try hard refreshing the page')) } }, openSettings() { location.href = generateUrl('/settings/user') }, - - sendEmail() { - location.href = `mailto:${actionParameters.email}` - }, - - openWebsite() { - window.open(actionParameters.website, '_blank') - }, - - openTalk() { - location.href = generateUrl('apps/spreed?callUser={userId}', { userId }) - }, - - callPhone() { - location.href = `tel:${actionParameters.phoneNumber}` - }, - - openTwitterProfile() { - window.open(`https://twitter.com/${actionParameters.twitterUsername}`, '_blank') - }, }, } @@ -289,7 +272,6 @@ $content-max-width: 640px; position: sticky; height: 190px; top: -40px; - background-image: linear-gradient(40deg, var(--color-primary-gradient-dark) 0%, var(--color-primary-gradient-light) 100%); &__container { align-self: flex-end;