Skip to content

Commit

Permalink
Merge pull request #12850 from nextcloud/fix/noid/participant-search-…
Browse files Browse the repository at this point in the history
…result
  • Loading branch information
Antreesy committed Sep 15, 2024
2 parents 95fb56c + d7fb119 commit 8dcfb13
Show file tree
Hide file tree
Showing 8 changed files with 142 additions and 216 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@
</template>

<script>
import { provide } from 'vue'
import ArrowLeft from 'vue-material-design-icons/ArrowLeft.vue'
import Delete from 'vue-material-design-icons/Delete.vue'
import DotsCircle from 'vue-material-design-icons/DotsCircle.vue'
Expand Down Expand Up @@ -144,6 +146,9 @@ export default {
emits: ['back', 'close'],
setup() {
// Add a visual bulk selection state for SelectableParticipant component
provide('bulkParticipantsSelection', true)
return {
breakoutRoomsStore: useBreakoutRoomsStore(),
}
Expand Down
90 changes: 78 additions & 12 deletions src/components/BreakoutRoomsEditor/SelectableParticipant.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,37 +4,44 @@
-->

<template>
<label class="selectable-participant">
<label class="selectable-participant" :data-nav-id="participantNavigationId">
<input v-model="modelProxy"
:value="participant.attendeeId"
:value="value"
:aria-label="participantAriaLabel"
type="checkbox"
class="selectable-participant__checkbox">
class="selectable-participant__checkbox"
@keydown.enter="handleEnter">
<!-- Participant's avatar -->
<AvatarWrapper :id="participant.actorId"
:name="participant.displayName"
:source="participant.source || participant.actorType"
<AvatarWrapper :id="actorId"
token="new"
:name="computedName"
:source="actorType"
disable-menu
disable-tooltip
:preloaded-user-status="preloadedUserStatus"
show-user-status />
:show-user-status="showUserStatus" />

<span class="selectable-participant__content">
<span class="selectable-participant__content-name">
{{ participant.displayName }}
{{ computedName }}
</span>
<span v-if="participantStatus"
class="selectable-participant__content-subname">
{{ participantStatus }}
</span>
</span>

<IconCheck class="selectable-participant__check-icon" :size="20" />
<IconCheck v-if="isBulkSelection" class="selectable-participant__check-icon" :size="20" />
</label>
</template>

<script>
import { inject } from 'vue'
import IconCheck from 'vue-material-design-icons/Check.vue'
import { t } from '@nextcloud/l10n'
import AvatarWrapper from '../AvatarWrapper/AvatarWrapper.vue'
import { getPreloadedUserStatus, getStatusMessage } from '../../utils/userStatus.ts'
Expand All @@ -60,28 +67,81 @@ export default {
type: Array,
required: true,
},
showUserStatus: {
type: Boolean,
default: true,
},
},
emits: ['update:checked'],
emits: ['update:checked', 'click-participant'],
setup() {
// Toggles the bulk selection state of this component
const isBulkSelection = inject('bulkParticipantsSelection', false)
return {
isBulkSelection,
}
},
computed: {
modelProxy: {
get() {
return this.checked
},
set(value) {
this.$emit('update:checked', value)
this.isBulkSelection
? this.$emit('update:checked', value)
: this.$emit('click-participant', this.participant)
},
},
value() {
return this.participant.attendeeId || this.participant
},
actorId() {
return this.participant.actorId || this.participant.id
},
actorType() {
return this.participant.actorType || this.participant.source
},
computedName() {
return this.participant.displayName || this.participant.label
},
preloadedUserStatus() {
return getPreloadedUserStatus(this.participant)
},
participantStatus() {
return getStatusMessage(this.participant)
return this.participant.shareWithDisplayNameUnique
?? getStatusMessage(this.participant)
},
participantAriaLabel() {
return t('spreed', 'Add participant "{user}"', { user: this.computedName })
},
participantNavigationId() {
if (this.participant.actorType && this.participant.actorId) {
return this.participant.actorType + '_' + this.participant.actorId
} else {
return this.participant.source + '_' + this.participant.id
}
},
},
methods: {
t,
handleEnter(event) {
event.target.checked = !event.target.checked
},
}
}
</script>
Expand All @@ -97,6 +157,10 @@ export default {
border-radius: var(--border-radius-element, 32px);
line-height: 20px;
&, & * {
cursor: pointer;
}
&:hover,
&:focus-within,
&:has(:active),
Expand Down Expand Up @@ -129,6 +193,7 @@ export default {
top: 0;
left: 0;
z-index: -1;
opacity: 0;
}
&__content {
Expand All @@ -149,6 +214,7 @@ export default {
display: none;
margin-left: auto;
width: var(--default-clickable-area);
flex-shrink: 0;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
:value="searchText"
:participant-phone-item.sync="participantPhoneItem"
@select="addParticipantPhone" />
<ParticipantSearchResults :search-results="searchResults"
<ParticipantsSearchResults :search-results="searchResults"
:contacts-loading="contactsLoading"
:no-results="noResults"
scrollable
Expand All @@ -69,7 +69,7 @@ import { t } from '@nextcloud/l10n'
import NcTextField from '@nextcloud/vue/dist/Components/NcTextField.js'
import ParticipantSearchResults from '../RightSidebar/Participants/ParticipantsSearchResults.vue'
import ParticipantsSearchResults from '../RightSidebar/Participants/ParticipantsSearchResults.vue'
import SelectPhoneNumber from '../SelectPhoneNumber.vue'
import ContactSelectionBubble from '../UIShared/ContactSelectionBubble.vue'
import DialpadPanel from '../UIShared/DialpadPanel.vue'
Expand All @@ -86,7 +86,7 @@ export default {
ContactSelectionBubble,
DialpadPanel,
NcTextField,
ParticipantSearchResults,
ParticipantsSearchResults,
SelectPhoneNumber,
TransitionWrapper,
// Icons
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ export default {
const selectedParticipants = ref([])
provide('selectedParticipants', selectedParticipants)
// Add a visual bulk selection state for Participant component
// Add a visual bulk selection state for SelectableParticipant component
provide('bulkParticipantsSelection', true)
const dialogHeaderPrepId = `new-conversation-prepare-${useId()}`
Expand Down
58 changes: 0 additions & 58 deletions src/components/RightSidebar/Participants/Participant.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,19 +162,6 @@ describe('Participant.vue', () => {

expect(avatarEl.props('offline')).toBe(true)
})

test('renders avatar from search result', () => {
participant.label = 'Name from label'
participant.source = 'source-from-search'
participant.id = 'id-from-search'
const wrapper = mountParticipant(participant, true)
const avatarEl = wrapper.findComponent(AvatarWrapper)
expect(avatarEl.exists()).toBe(true)

expect(avatarEl.props('id')).toBe('id-from-search')
expect(avatarEl.props('name')).toBe('Name from label')
expect(avatarEl.props('source')).toBe('source-from-search')
})
})

describe('user name', () => {
Expand Down Expand Up @@ -318,14 +305,6 @@ describe('Participant.vue', () => {
participant.inCall = PARTICIPANT.CALL_FLAG.WITH_VIDEO | PARTICIPANT.CALL_FLAG.WITH_PHONE
checkStateIconsRendered(participant, VideoIcon)
})
test('does not render hand raised icon when searched', () => {
participant.inCall = PARTICIPANT.CALL_FLAG.WITH_VIDEO
participant.label = 'searched result'
getParticipantRaisedHandMock = jest.fn().mockReturnValue({ state: true })

checkStateIconsRendered(participant, null)
expect(getParticipantRaisedHandMock).not.toHaveBeenCalled()
})
})

describe('actions', () => {
Expand Down Expand Up @@ -786,41 +765,4 @@ describe('Participant.vue', () => {
})
})
})

describe('as search result', () => {
beforeEach(() => {
participant.label = 'Alice Search'
participant.source = 'users'
})

test('does not show actions for search results', () => {
const wrapper = mountParticipant(participant)

// no actions
expect(wrapper.findAllComponents(NcActionButton).exists()).toBe(false)
})

test('triggers event when clicking', async () => {
const eventHandler = jest.fn()
const wrapper = mountParticipant(participant)
wrapper.vm.$on('click-participant', eventHandler)

wrapper.find('a').trigger('click')

expect(eventHandler).toHaveBeenCalledWith(participant)
})

test('does not trigger click event when not a search result', async () => {
const eventHandler = jest.fn()
delete participant.label
delete participant.source
const wrapper = mountParticipant(participant)
wrapper.vm.$on('click-participant', eventHandler)

wrapper.find('a').trigger('click')

expect(eventHandler).not.toHaveBeenCalledWith(participant)
})
})

})
Loading

0 comments on commit 8dcfb13

Please sign in to comment.