diff options
Diffstat (limited to 'apps/settings/src/components/PersonalInfo/ProfileSection')
4 files changed, 418 insertions, 0 deletions
diff --git a/apps/settings/src/components/PersonalInfo/ProfileSection/EditProfileAnchorLink.vue b/apps/settings/src/components/PersonalInfo/ProfileSection/EditProfileAnchorLink.vue new file mode 100644 index 00000000000..3deb5340751 --- /dev/null +++ b/apps/settings/src/components/PersonalInfo/ProfileSection/EditProfileAnchorLink.vue @@ -0,0 +1,83 @@ +<!-- + - SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors + - SPDX-License-Identifier: AGPL-3.0-or-later +--> + +<template> + <a :class="{ disabled }" + href="#profile-visibility" + v-on="$listeners"> + <ChevronDownIcon class="anchor-icon" + :size="22" /> + {{ t('settings', 'Edit your Profile visibility') }} + </a> +</template> + +<script> +import ChevronDownIcon from 'vue-material-design-icons/ChevronDown.vue' + +export default { + name: 'EditProfileAnchorLink', + + components: { + ChevronDownIcon, + }, + + props: { + profileEnabled: { + type: Boolean, + required: true, + }, + }, + + computed: { + disabled() { + return !this.profileEnabled + }, + }, +} +</script> + +<style lang="scss"> +html { + scroll-behavior: smooth; + + @media screen and (prefers-reduced-motion: reduce) { + scroll-behavior: auto; + } +} +</style> + +<style lang="scss" scoped> +a { + display: block; + height: 44px; + width: min(100%, 290px); + overflow: hidden; + text-overflow: ellipsis; + line-height: 44px; + padding: 0 16px; + margin: 14px auto; + border-radius: var(--border-radius-pill); + color: var(--color-text-maxcontrast); + background-color: transparent; + + .anchor-icon { + display: inline-block; + vertical-align: middle; + margin-top: 6px; + margin-inline-end: 8px; + } + + &:hover, + &:focus, + &:active { + color: var(--color-main-text); + background-color: var(--color-background-dark); + } + + &.disabled { + pointer-events: none; + } +} +</style> diff --git a/apps/settings/src/components/PersonalInfo/ProfileSection/ProfileCheckbox.vue b/apps/settings/src/components/PersonalInfo/ProfileSection/ProfileCheckbox.vue new file mode 100644 index 00000000000..6eb7cf8c34c --- /dev/null +++ b/apps/settings/src/components/PersonalInfo/ProfileSection/ProfileCheckbox.vue @@ -0,0 +1,73 @@ +<!-- + - SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors + - SPDX-License-Identifier: AGPL-3.0-or-later +--> + +<template> + <div class="checkbox-container"> + <NcCheckboxRadioSwitch type="switch" + :checked.sync="isProfileEnabled" + :loading="loading" + @update:checked="saveEnableProfile"> + {{ t('settings', 'Enable profile') }} + </NcCheckboxRadioSwitch> + </div> +</template> + +<script> +import { emit } from '@nextcloud/event-bus' + +import { savePrimaryAccountProperty } from '../../../service/PersonalInfo/PersonalInfoService.js' +import { ACCOUNT_PROPERTY_ENUM } from '../../../constants/AccountPropertyConstants.js' +import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch' +import { handleError } from '../../../utils/handlers.ts' + +export default { + name: 'ProfileCheckbox', + + components: { + NcCheckboxRadioSwitch, + }, + + props: { + profileEnabled: { + type: Boolean, + required: true, + }, + }, + + data() { + return { + isProfileEnabled: this.profileEnabled, + loading: false, + } + }, + + methods: { + async saveEnableProfile() { + this.loading = true + try { + const responseData = await savePrimaryAccountProperty(ACCOUNT_PROPERTY_ENUM.PROFILE_ENABLED, this.isProfileEnabled) + this.handleResponse({ + isProfileEnabled: this.isProfileEnabled, + status: responseData.ocs?.meta?.status, + }) + } catch (e) { + this.handleResponse({ + errorMessage: t('settings', 'Unable to update profile enabled state'), + error: e, + }) + } + }, + + handleResponse({ isProfileEnabled, status, errorMessage, error }) { + if (status === 'ok') { + emit('settings:profile-enabled:updated', isProfileEnabled) + } else { + handleError(error, errorMessage) + } + this.loading = false + }, + }, +} +</script> diff --git a/apps/settings/src/components/PersonalInfo/ProfileSection/ProfilePreviewCard.vue b/apps/settings/src/components/PersonalInfo/ProfileSection/ProfilePreviewCard.vue new file mode 100644 index 00000000000..47894f64f34 --- /dev/null +++ b/apps/settings/src/components/PersonalInfo/ProfileSection/ProfilePreviewCard.vue @@ -0,0 +1,173 @@ +<!-- + - SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors + - SPDX-License-Identifier: AGPL-3.0-or-later +--> + +<template> + <a class="preview-card" + :class="{ disabled }" + :href="profilePageLink"> + <NcAvatar class="preview-card__avatar" + :user="userId" + :size="48" + :show-user-status="true" + :show-user-status-compact="false" + :disable-menu="true" + :disable-tooltip="true" /> + <div class="preview-card__header"> + <span>{{ displayName }}</span> + </div> + <div class="preview-card__footer"> + <span>{{ organisation }}</span> + </div> + </a> +</template> + +<script> +import { getCurrentUser } from '@nextcloud/auth' +import { generateUrl } from '@nextcloud/router' + +import NcAvatar from '@nextcloud/vue/components/NcAvatar' + +export default { + name: 'ProfilePreviewCard', + + components: { + NcAvatar, + }, + + props: { + displayName: { + type: String, + required: true, + }, + organisation: { + type: String, + required: true, + }, + profileEnabled: { + type: Boolean, + required: true, + }, + userId: { + type: String, + required: true, + }, + }, + + computed: { + disabled() { + return !this.profileEnabled + }, + + profilePageLink() { + if (this.profileEnabled) { + return generateUrl('/u/{userId}', { userId: getCurrentUser().uid }) + } + // Since an anchor element is used rather than a button for better UX, + // this hack removes href if the profile is disabled so that disabling pointer-events is not needed to prevent a click from opening a page + // and to allow the hover event (which disabling pointer-events wouldn't allow) for styling + return null + }, + }, +} +</script> + +<style lang="scss" scoped> +.preview-card { + display: flex; + flex-direction: column; + position: relative; + width: min(100%, 290px); + height: 116px; + margin: 14px auto; + border-radius: var(--border-radius-large); + background-color: var(--color-main-background); + font-weight: bold; + box-shadow: 0 2px 9px var(--color-box-shadow); + + &:hover, + &:focus, + &:active { + box-shadow: 0 2px 12px var(--color-box-shadow); + } + + &:focus-visible { + outline: var(--color-main-text) solid 1px; + outline-offset: 3px; + } + + &.disabled { + filter: grayscale(1); + opacity: 0.5; + cursor: default; + box-shadow: 0 0 3px var(--color-box-shadow); + + & *, + &:deep(*) { + cursor: default; + } + } + + &__avatar { + // Override Avatar component position to fix positioning on rerender + position: absolute !important; + top: 40px; + inset-inline-start: 18px; + z-index: 1; + + &:not(.avatardiv--unknown) { + box-shadow: 0 0 0 3px var(--color-main-background) !important; + } + } + + &__header, + &__footer { + position: relative; + width: auto; + + span { + position: absolute; + inset-inline-start: 78px; + overflow: hidden; + text-overflow: ellipsis; + overflow-wrap: anywhere; + + @supports (-webkit-line-clamp: 2) { + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + } + } + } + + &__header { + height: 70px; + border-radius: var(--border-radius-large) var(--border-radius-large) 0 0; + background-color: var(--color-primary-element); + + span { + bottom: 0; + color: var(--color-primary-element-text); + font-size: 18px; + font-weight: bold; + margin-block: 0 8px; + margin-inline: 0 4px; + } + } + + &__footer { + height: 46px; + + span { + top: 0; + color: var(--color-text-maxcontrast); + font-size: 14px; + font-weight: normal; + margin-block: 4px 0; + margin-inline: 0 4px; + line-height: 1.3; + } + } +} +</style> diff --git a/apps/settings/src/components/PersonalInfo/ProfileSection/ProfileSection.vue b/apps/settings/src/components/PersonalInfo/ProfileSection/ProfileSection.vue new file mode 100644 index 00000000000..22c03f72697 --- /dev/null +++ b/apps/settings/src/components/PersonalInfo/ProfileSection/ProfileSection.vue @@ -0,0 +1,89 @@ +<!-- + - SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors + - SPDX-License-Identifier: AGPL-3.0-or-later +--> + +<template> + <section> + <HeaderBar :is-heading="true" :readable="propertyReadable" /> + + <ProfileCheckbox :profile-enabled.sync="profileEnabled" /> + + <ProfilePreviewCard :organisation="organisation" + :display-name="displayName" + :profile-enabled="profileEnabled" + :user-id="userId" /> + + <EditProfileAnchorLink :profile-enabled="profileEnabled" /> + </section> +</template> + +<script> +import { loadState } from '@nextcloud/initial-state' +import { subscribe, unsubscribe } from '@nextcloud/event-bus' + +import EditProfileAnchorLink from './EditProfileAnchorLink.vue' +import HeaderBar from '../shared/HeaderBar.vue' +import ProfileCheckbox from './ProfileCheckbox.vue' +import ProfilePreviewCard from './ProfilePreviewCard.vue' + +import { ACCOUNT_PROPERTY_READABLE_ENUM } from '../../../constants/AccountPropertyConstants.js' + +const { + organisation: { value: organisation }, + displayName: { value: displayName }, + profileEnabled, + userId, +} = loadState('settings', 'personalInfoParameters', {}) + +export default { + name: 'ProfileSection', + + components: { + EditProfileAnchorLink, + HeaderBar, + ProfileCheckbox, + ProfilePreviewCard, + }, + + data() { + return { + propertyReadable: ACCOUNT_PROPERTY_READABLE_ENUM.PROFILE_ENABLED, + organisation, + displayName, + profileEnabled, + userId, + } + }, + + mounted() { + subscribe('settings:display-name:updated', this.handleDisplayNameUpdate) + subscribe('settings:organisation:updated', this.handleOrganisationUpdate) + }, + + beforeDestroy() { + unsubscribe('settings:display-name:updated', this.handleDisplayNameUpdate) + unsubscribe('settings:organisation:updated', this.handleOrganisationUpdate) + }, + + methods: { + handleDisplayNameUpdate(displayName) { + this.displayName = displayName + }, + + handleOrganisationUpdate(organisation) { + this.organisation = organisation + }, + }, +} +</script> + +<style lang="scss" scoped> +section { + padding: 10px 10px; + + &:deep(button:disabled) { + cursor: default; + } +} +</style> |