Merge pull request #2857 from nextcloud/feature/2546/improve-new-group-creation

Improve new group conversation feature
This commit is contained in:
Joas Schilling 2020-01-31 10:17:04 +01:00 коммит произвёл GitHub
Родитель 920869aca6 9f5289e528
Коммит 52add20363
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
7 изменённых файлов: 141 добавлений и 64 удалений

Просмотреть файл

@ -21,6 +21,7 @@
<template>
<div>
<!-- Tooltip -->
<Popover trigger="hover" placement="bottom">
<Actions slot="trigger">
<ActionButton
@ -30,21 +31,27 @@
</Actions>
<p>{{ t('spreed','Create a new group conversation') }}</p>
</Popover>
<!-- New group form -->
<Modal
v-if="modal"
size="full"
@close="closeModal">
<!-- Wrapper for content & navigation -->
<div
class="new-group-conversation talk-modal">
<!-- Content -->
<div
class="new-group-conversation__content">
<!-- First page -->
<template
v-if="page === 0">
<SetConversationName
v-model="conversationNameInput" />
v-model="conversationNameInput"
@clickEnter="handleEnter" />
<SetConversationType
v-model="isPublic"
:conversation-name="conversationName" />
<!-- Password protection -->
<template v-if="isPublic">
<input
id="password-checkbox"
@ -58,11 +65,13 @@
v-model="password" />
</template>
</template>
<!-- Second page -->
<template v-if="page === 1">
<SetContacts
:conversation-name="conversationName"
@updateSelectedParticipants="handleUpdateSelectedParticipants" />
</template>
<!-- Third page -->
<template v-if="page === 2">
<Confirmation
:conversation-name="conversationName"
@ -73,27 +82,32 @@
:link-to-conversation="linkToConversation" />
</template>
</div>
<!-- Navigation: different buttons with different actions and
placement are rendered depending on the current page -->
<div
class="navigation">
<!-- First page -->
<button
v-if="page===0"
class="navigation__button-right primary"
:disabled="disabled"
@click="handleSetConversationName">
{{ t('spreed', 'Add participants') }}
</button>
<!-- Second page -->
<button
v-if="page===1"
class="navigation__button-left"
@click="handleClickBack">
{{ t('spreed', 'Back') }}
</button>
<button
v-if="page===0"
class="navigation__button-right primary"
:disabled="disabled"
@click="handleClickForward">
{{ t('spreed', 'Add participants') }}
</button>
<button
v-if="page===1"
class="navigation__button-right primary"
@click="handleCreateConversation">
{{ t('spreed', 'Create conversation') }}
</button>
<!-- Third page -->
<button
v-if="page===2 && (error || isPublic)"
class="navigation__button-right primary"
@ -158,14 +172,17 @@ export default {
},
computed: {
// Trims whitespaces from the input string
conversationName() {
return this.conversationNameInput.trim()
},
// Generates the link to the current conversation
linkToConversation() {
if (this.token !== '') {
return window.location.protocol + '//' + window.location.host + generateUrl('/call/' + this.token)
} else return ''
},
// Controls the disabled/enabled state of the first page's button.
disabled() {
return this.conversationName === '' || (this.passwordProtect && this.password === '')
},
@ -175,7 +192,9 @@ export default {
showModal() {
this.modal = true
},
// Resets to the base state of the component
/** Reinitialise the component to it's initial state. This is necessary
* because once the component is mounted it's data would persist even if
* the modal closes */
closeModal() {
this.modal = false
this.page = 0
@ -186,32 +205,25 @@ export default {
this.selectedParticipants = []
this.success = false
this.error = false
this.passwordProtect = false
this.password = ''
},
handleSetConversationName(event) {
/** Switch to page 2 */
handleSetConversationName() {
this.page = 1
},
handleSetConversationType(event) {
this.isPublic = event
},
handleClickForward() {
if (this.page === 0) {
if (this.conversationName !== '') {
this.page = 1
}
}
},
/** Switch to page 1 from page 2 */
handleClickBack() {
this.page = 0
},
handleUpdateSelectedParticipants(e) {
console.debug(e)
this.selectedParticipants = e
/** Updates the selected participants array
* @param {array} participants the participants array
*/
handleUpdateSelectedParticipants(participants) {
this.selectedParticipants = participants
},
/** Handles the creation of the group conversation, adds the seleced
* participants to it and routes to it */
async handleCreateConversation() {
this.page = 2
if (this.isPublic) {
@ -257,14 +269,16 @@ export default {
this.closeModal()
}
},
/** Creates a new private conversation, adds it to the store and sets
* the local token value to the newly created conversation's token */
async createPrivateConversation() {
const response = await createPrivateConversation(this.conversationName)
const conversation = response.data.ocs.data
this.$store.dispatch('addConversation', conversation)
this.token = conversation.token
},
/** Creates a new public conversation, adds it to the store and sets
* the local token value to the newly created conversation's token */
async createPublicConversation() {
const response = await createPublicConversation(this.conversationName)
const conversation = response.data.ocs.data
@ -282,6 +296,13 @@ export default {
this.password = ''
}
},
/** Handles the press of the enter key */
handleEnter() {
if (!this.disabled) {
this.handleSetConversationName()
this.page = 1
}
},
},
}

Просмотреть файл

@ -23,7 +23,7 @@
<input
ref="password"
v-observe-visibility="visibilityChanged"
type="text"
type="password"
:value="value"
class="password-protect"
:placeholder="t('spreed', 'Choose a password')"

Просмотреть файл

@ -21,6 +21,8 @@
<template>
<div class="set-contacts">
<!-- Search -->
<div class="icon-search" />
<input
ref="setContacts"
v-model="searchText"
@ -29,23 +31,21 @@
type="text"
:placeholder="t('spreed', 'Search participants')"
@input="handleInput">
<template v-if="isSearching">
<Caption
:title="t('spreed', 'Select participants')" />
<ParticipantsList
:add-on-click="false"
height="250px"
:loading="contactsLoading"
:no-results="noResults"
:items="searchResults"
@updateSelectedParticipants="handleUpdateSelectedParticipants" />
</template>
<template v-if="!isSearching">
<div class="icon-contacts-dark set-contacts__icon" />
<p class="set-contacts__hint">
{{ t('spreed', 'Search participants') }}
</p>
</template>
<!-- Loading state -->
<Caption v-if="contactsLoading"
:title="t('spreed', 'Loading contacts')" />
<!-- List of possilbe participants -->
<Caption v-if="!contactsLoading"
:title="t('spreed', 'Select participants')" />
<ParticipantsList
:add-on-click="false"
height="200px"
:loading="contactsLoading"
:no-results="noResults"
:items="searchResults"
:display-search-hint="!contactsLoading"
@updateSelectedParticipants="handleUpdateSelectedParticipants"
@clickSearchHint="focusInput" />
</div>
</template>
@ -73,20 +73,20 @@ export default {
return {
searchText: '',
searchResults: [],
contactsLoading: false,
// The loading state is true when the component is initialised as we perform a search for 'contacts'
// with an empty screen as search text.
contactsLoading: true,
noResults: false,
}
},
computed: {
isSearching() {
return this.searchText !== ''
},
},
mounted() {
async mounted() {
// Focus the input field of the current component.
this.$refs.setContacts.focus()
this.focusInput()
// Perform a search with an empty string
await this.fetchSearchResults()
// Once the contacts are fetched, remove the spinner.
this.contactsLoading = false
},
methods: {
@ -98,9 +98,7 @@ export default {
},
debounceFetchSearchResults: debounce(function() {
if (this.isSearching) {
this.fetchSearchResults()
}
this.fetchSearchResults()
}, 250),
async fetchSearchResults() {
@ -116,16 +114,22 @@ export default {
OCP.Toast.error(t('spreed', 'An error occurred while performing the search'))
}
},
// Forward the event from the children to the parent
/**
* Forward the event from the children to the parent with thenew selected participants
* @param {array} selectedParticipants the selected participants array
*/
handleUpdateSelectedParticipants(selectedParticipants) {
this.$emit('updateSelectedParticipants', selectedParticipants)
},
visibilityChanged(isVisible) {
if (isVisible) {
// Focus the input field of the current component.
this.$refs.setContacts.focus()
this.focusInput()
}
},
focusInput() {
this.$refs.setContacts.focus()
},
},
}
</script>
@ -136,6 +140,8 @@ export default {
&__input {
width: 100%;
font-size: 16px;
padding-left: 28px;
line-height: 34px;
}
&__icon {
margin-top: 40px;
@ -146,4 +152,9 @@ export default {
}
}
.icon-search {
position: absolute;
top: 12px;
left: 8px;
}
</style>

Просмотреть файл

@ -28,7 +28,8 @@
:value="value"
class="conversation-name"
:placeholder="t('spreed', 'Conversation name')"
@input="handleInput">
@input="handleInput"
@keydown.enter="handleKeydown">
</div>
</template>
@ -53,6 +54,10 @@ export default {
this.$refs.conversationName.focus()
}
},
// Forward the keydown event to the parent
handleKeydown() {
this.$emit('clickEnter')
},
},
}
@ -64,5 +69,7 @@ export default {
.conversation-name {
width: 100%;
font-size: 16px;
line-height: 34px;
}
</style>

Просмотреть файл

@ -35,6 +35,7 @@
export default {
name: 'SetConversationType',
// The value of the checkbox
props: {
value: {
type: Boolean,
@ -42,6 +43,9 @@ export default {
},
},
methods: {
/** Emits the input event with the checked bulean as a value
* @param {object} event The checkbox click event object.
*/
handleInput(event) {
this.$emit('input', event.target.checked)
},

Просмотреть файл

@ -237,6 +237,7 @@ export default {
},
methods: {
// Used to allow selecting participants in a search.
handleClick() {
if (this.isSearched) {
this.$emit('clickParticipant', this.participant)

Просмотреть файл

@ -22,13 +22,27 @@
<template>
<div>
<ul v-if="(!loading || addOnClick) && !noResults"
:class="{'scrollable': scrollable }"
:style="{'height': height}">
:style="{'height': height}"
:class="{'scrollable': scrollable }">
<Participant
v-for="participant in participants"
:key="participant.userId"
:participant="participant"
@clickParticipant="handleClickParticipant" />
<!-- 'search for more' empty content to display at the end of the
participants list, this is useful in case the participants list is used
to display the results of a search. Upon clicking on it, an event is
emitted to the parent component in order to be able to focus on it's
input field -->
<li
v-if="displaySearchHint"
class="participants-list__hint"
@click="handleClickHint">
<div class="icon-contacts-dark set-contacts__icon" />
<p>
{{ t('spreed', 'Search for more contacts') }}
</p>
</li>
</ul>
<template v-if="loading">
<template v-if="addOnClick">
@ -106,6 +120,13 @@ export default {
type: Boolean,
default: false,
},
/**
* Display 'search for more' empty content at the end of the list.
*/
displaySearchHint: {
type: Boolean,
default: false,
},
},
data() {
@ -180,6 +201,10 @@ export default {
}
},
handleClickHint() {
this.$emit('clickSearchHint')
},
},
}
</script>
@ -198,6 +223,14 @@ export default {
margin-top: 20px;
text-align: center;
}
&__hint {
margin: 20px 0;
cursor: pointer;
p {
margin-top: 20px;
text-align: center;
}
}
}
</style>