Skip to content

Commit

Permalink
Merge pull request #5632 from owncloud/feat/redesign-role-dropdown
Browse files Browse the repository at this point in the history
  • Loading branch information
Lukas Hirt authored Sep 7, 2021
2 parents 8fa0cf0 + 681ea84 commit 52c0181
Show file tree
Hide file tree
Showing 33 changed files with 322 additions and 166 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Enhancement: Re-design recipients role select

We've redesigned recipient role select in the Files app sidebar.

https://github.com/owncloud/web/pull/5632
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,9 @@ export default {
}
}
</script>

<style lang="scss" scoped>
.roles-select-role-item {
text-align: left;
}
</style>
Original file line number Diff line number Diff line change
@@ -1,32 +1,51 @@
<template>
<div>
<translate tag="label" for="files-collaborators-role-button" class="oc-label">Role</translate>
<oc-select
v-model="selectedRole"
input-id="files-collaborators-role-button"
class="files-collaborators-role-button-wrapper"
:options="roles"
:clearable="false"
label="label"
<hr />
<oc-button
id="files-collaborators-role-button"
appearance="raw"
justify-content="left"
gap-size="xsmall"
>
<template #option="option">
<role-item :role="option" />
<translate v-if="isAdvancedRoleSelected" key="advanced-permissions-select"
>Invite with custom permissions</translate
>
<translate v-else key="role-select" :translate-params="{ name: selectedRole.inlineLabel }"
>Invite as %{ name }</translate
>
<oc-icon name="expand_more" />
</oc-button>
<oc-drop ref="rolesDrop" toggle="#files-collaborators-role-button" mode="click" close-on-click>
<template #special>
<oc-list class="files-recipient-role-drop-list" :aria-label="rolesListAriaLabel">
<li v-for="role in roles" :key="role.name">
<oc-button
ref="roleSelect"
appearance="raw"
justify-content="space-between"
class="files-recipient-role-drop-btn oc-py-xs oc-px-s"
:class="{ selected: isSelectedRole(role) }"
@click="selectRole(role)"
>
<role-item :role="role" />
<oc-icon v-if="isSelectedRole(role)" name="check" />
</oc-button>
</li>
</oc-list>
</template>
<template #no-options v-translate>
No matching role found
</template>
</oc-select>
</oc-drop>
<template v-if="$_ocCollaborators_hasAdditionalPermissions">
<label v-if="selectedRole.name !== 'advancedRole'" class="oc-label oc-mt-s">
<label v-if="!isAdvancedRoleSelected" class="oc-label oc-mt-s">
<translate>Additional permissions</translate>
</label>
<additional-permissions
:available-permissions="selectedRole.additionalPermissions"
:collaborators-permissions="collaboratorsPermissions"
:class="{ 'oc-mt-s': selectedRole.name === 'advancedRole' }"
:class="{ 'oc-mt-s': isAdvancedRoleSelected }"
@permissionChecked="checkAdditionalPermissions"
/>
</template>
<hr />
<div v-if="expirationSupported" class="oc-mt-m">
<div class="uk-position-relative">
<oc-datepicker
Expand All @@ -47,13 +66,16 @@
@click="resetExpirationDate"
/>
</div>
<hr />
</div>
</div>
</template>

<script>
import { mapGetters } from 'vuex'
import { DateTime } from 'luxon'
import get from 'lodash-es/get'
import collaboratorsMixins from '../../../../mixins/collaborators'
import RoleItem from '../../Shared/RoleItem.vue'
Expand Down Expand Up @@ -90,7 +112,7 @@ export default {
required: false,
default: null,
validator: function(value) {
return ['user', 'group'].indexOf(value) > -1 || value === null
return ['user', 'group'].includes(value) || value === null
}
}
},
Expand Down Expand Up @@ -224,6 +246,14 @@ export default {
canResetExpirationDate() {
return !this.expirationDateEnforced && this.enteredExpirationDate
},
isAdvancedRoleSelected() {
return this.isAdvancedRole(this.selectedRole)
},
rolesListAriaLabel() {
return this.$gettext('Sharing roles')
}
},
Expand All @@ -235,8 +265,8 @@ export default {
created() {
if (
(this.existingRole && this.existingRole.name === 'advancedRole' && !this.selectedRole) ||
(this.selectedRole && this.selectedRole.name === 'advancedRole')
(this.existingRole && this.isAdvancedRole(this.existingRole) && !this.selectedRole) ||
(this.selectedRole && this.isAdvancedRoleSelected)
) {
this.selectedRole = this.advancedRole
} else if (this.existingRole && !this.selectedRole) {
Expand All @@ -246,6 +276,10 @@ export default {
}
},
beforeDestroy() {
window.removeEventListener('keydown', this.cycleRoles)
},
mounted() {
if (this.expirationSupported) {
if (this.editingUser || this.editingGroup) {
Expand All @@ -259,6 +293,8 @@ export default {
this.enteredExpirationDate = this.defaultExpirationDate
}
}
window.addEventListener('keydown', this.cycleRoles)
},
methods: {
Expand All @@ -285,7 +321,113 @@ export default {
permissions: this.additionalPermissions,
expirationDate: this.enteredExpirationDate
})
},
selectRole(role) {
this.selectedRole = role
},
isSelectedRole(role) {
return this.selectedRole.name === role.name
},
isAdvancedRole(role) {
return role.name === 'advancedRole'
},
cycleRoles(event) {
// events only need to be captured if the roleSelect element is visible
if (!get(this.$refs.rolesDrop, 'tippy.state.isShown', false)) {
return
}
const { keyCode } = event
const isArrowUp = keyCode === 38
const isArrowDown = keyCode === 40
// to cycle through the list of roles only up and down keyboard events are allowed
// if this is not the case we can return early and stop the script execution from here
if (!isArrowUp && !isArrowDown) {
return
}
// if there is only 1 or no roleSelect we can early return
// it does not make sense to cycle through it if value is less than 1
const roleSelect = this.$refs.roleSelect || []
if (roleSelect.length <= 1) {
return
}
// obtain active role select in following priority chain:
// first try to get the focused select
// then try to get the selected select
// and if none of those applies we fall back to the first role select
const activeRoleSelect =
roleSelect.find(rs => rs.$el === document.activeElement) ||
roleSelect.find(rs => rs.$el.classList.contains('selected')) ||
roleSelect[0]
const activeRoleSelectIndex = roleSelect.indexOf(activeRoleSelect)
const activateRoleSelect = idx => roleSelect[idx].$el.focus()
// if the event key is arrow up
// and the next active role select index would be less than 0
// then activate the last available role select
if (isArrowUp && activeRoleSelectIndex - 1 < 0) {
activateRoleSelect(roleSelect.length - 1)
return
}
// if the event key is arrow down
// and the next active role select index would be greater or even to the available amount of role selects
// then activate the first available role select
if (isArrowDown && activeRoleSelectIndex + 1 >= roleSelect.length) {
activateRoleSelect(0)
return
}
// the only missing part is to navigate up or down, this only happens if:
// the next active role index is greater than 0
// the next active role index is less than the amount of all available role selects
activateRoleSelect(activeRoleSelectIndex + (isArrowUp ? -1 : 1))
}
}
}
</script>

<style lang="scss" scoped>
.files-recipient-role-drop {
&-list {
background-color: var(--oc-color-swatch-inverse-default);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.08);
&:hover .files-recipient-role-drop-btn.selected:not(:hover),
&:focus .files-recipient-role-drop-btn.selected:not(:focus) {
background-color: var(--oc-color-swatch-inverse-default);
color: var(--oc-color-swatch-passive-default);
::v-deep .oc-icon > svg {
fill: var(--oc-color-swatch-passive-default);
}
}
}
&-btn {
border-radius: 0;
width: 100%;
&:hover,
&:focus,
&.selected {
background-color: var(--oc-color-swatch-primary-default);
color: var(--oc-color-text-inverse);
::v-deep .oc-icon > svg {
fill: var(--oc-color-text-inverse);
}
}
}
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
class="oc-mb"
@optionChange="collaboratorOptionChanged"
/>
<hr class="divider" />
<oc-grid gutter="small" class="oc-mb">
<div>
<oc-button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
/>
</div>
<collaborators-edit-options class="oc-mb" @optionChange="collaboratorOptionChanged" />
<hr class="divider" />
<oc-grid gutter="small" class="oc-mb">
<div>
<oc-button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export default ({ isFolder = false, $gettext = returnOriginal, allowSharePerm =
{
name: 'viewer',
label: $gettext('Viewer'),
inlineLabel: $gettext('viewer'),
description: allowSharePerm
? $gettext('Download, preview and share')
: $gettext('Download and preview'),
Expand All @@ -25,6 +26,7 @@ export default ({ isFolder = false, $gettext = returnOriginal, allowSharePerm =
{
name: 'editor',
label: $gettext('Editor'),
inlineLabel: $gettext('editor'),
description: allowSharePerm
? $gettext('Upload, edit, delete, download, preview and share')
: $gettext('Upload, edit, delete, download and preview'),
Expand All @@ -39,6 +41,7 @@ export default ({ isFolder = false, $gettext = returnOriginal, allowSharePerm =
{
name: 'viewer',
label: $gettext('Viewer'),
inlineLabel: $gettext('viewer'),
description: allowSharePerm
? $gettext('Download, preview and share')
: $gettext('Download and preview'),
Expand All @@ -47,6 +50,7 @@ export default ({ isFolder = false, $gettext = returnOriginal, allowSharePerm =
{
name: 'editor',
label: $gettext('Editor'),
inlineLabel: $gettext('editor'),
description: allowSharePerm
? $gettext('Edit, download, preview and share')
: $gettext('Edit, download and preview'),
Expand Down
3 changes: 2 additions & 1 deletion packages/web-app-files/src/mixins/collaborators.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ export default {
advancedRole() {
const advancedRole = {
name: 'advancedRole',
label: this.$gettext('Advanced permissions'),
label: this.$gettext('Custom permissions'),
inlineLabel: this.$gettext('custom permissions'),
description: this.$gettext('Set detailed permissions'),
permissions: ['read'],
additionalPermissions: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ describe('Collaborator component', () => {
it.each([
{ name: 'viewer', expectedText: 'Viewer' },
{ name: 'editor', expectedText: 'Editor' },
{ name: 'advancedRole', expectedText: 'Advanced permissions' },
{ name: 'advancedRole', expectedText: 'Custom permissions' },
{ name: 'owner', expectedText: 'Owner' },
{ name: 'resharer', expectedText: 'Resharer' },
{ name: 'invalidRole', expectedText: 'Unknown Role' }
Expand Down
10 changes: 5 additions & 5 deletions tests/acceptance/features/webUIResharing1/reshareUsers.feature
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ Feature: Resharing shared files with different permissions
And user "Alice" has accepted the share "simple-folder" offered by user "Brian"
And user "Alice" has logged in using the webUI
And the user opens folder "Shares" using the webUI
When the user shares folder "simple-folder" with user "Carol King" as "Advanced permissions" with permissions "share, delete" using the webUI
When the user shares folder "simple-folder" with user "Carol King" as "Custom permissions" with permissions "share, delete" using the webUI
And user "Carol" accepts the share "simple-folder" offered by user "Alice" using the sharing API
And the user re-logs in as "Carol" using the webUI
And the user opens folder "Shares" using the webUI
Expand All @@ -168,12 +168,12 @@ Feature: Resharing shared files with different permissions
And user "Alice" has accepted the share "simple-folder" offered by user "Brian"
And user "Alice" has logged in using the webUI
And the user opens folder "Shares" using the webUI
When the user shares folder "simple-folder" with user "Carol King" as "Advanced permissions" with permissions "delete" using the webUI
When the user shares folder "simple-folder" with user "Carol King" as "Custom permissions" with permissions "delete" using the webUI
And user "Carol" accepts the share "simple-folder" offered by user "Alice" using the sharing API
And the user re-logs in as "Brian" using the webUI
Then user "Alice Hansen" should be listed as "Advanced permissions" in the collaborators list for folder "simple-folder" on the webUI
Then user "Alice Hansen" should be listed as "Custom permissions" in the collaborators list for folder "simple-folder" on the webUI
And custom permissions "share, delete" should be set for user "Alice Hansen" for folder "simple-folder" on the webUI
And user "Carol King" should be listed as "Advanced permissions" in the collaborators list for folder "simple-folder" on the webUI
And user "Carol King" should be listed as "Custom permissions" in the collaborators list for folder "simple-folder" on the webUI
And custom permissions "delete" should be set for user "Carol King" for folder "simple-folder" on the webUI
And user "Carol" should have received a share with these details:
| field | value |
Expand All @@ -189,7 +189,7 @@ Feature: Resharing shared files with different permissions
And user "Alice" has accepted the share "simple-folder" offered by user "Brian"
And user "Alice" has logged in using the webUI
When the user opens folder "Shares" using the webUI
And the user shares folder "simple-folder" with user "Carol King" as "Advanced permissions" with permissions "share, delete, update" using the webUI
And the user shares folder "simple-folder" with user "Carol King" as "Custom permissions" with permissions "share, delete, update" using the webUI
Then the error message with header "Error while sharing." should be displayed on the webUI
And as "Carol" folder "Shares/simple-folder" should not exist
And user "Carol" should not have received any shares
Expand Down
Loading

0 comments on commit 52c0181

Please sign in to comment.