Skip to content

Commit

Permalink
WIP: Narrow search to single conversation in unified search
Browse files Browse the repository at this point in the history
Signed-off-by: fenn-cs <fenn25.fn@gmail.com>
  • Loading branch information
nfebe committed Feb 4, 2024
1 parent 489608a commit dd46a77
Show file tree
Hide file tree
Showing 5 changed files with 590 additions and 2 deletions.
64 changes: 64 additions & 0 deletions core/src/components/ConversationConstants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
export const CONVERSATION = {
START_CALL: {
EVERYONE: 0,
USERS: 1,
MODERATORS: 2,
},

STATE: {
READ_WRITE: 0,
READ_ONLY: 1,
},

LISTABLE: {
NONE: 0,
USERS: 1,
ALL: 2,
},

TYPE: {
ONE_TO_ONE: 1,
GROUP: 2,
PUBLIC: 3,
CHANGELOG: 4,
ONE_TO_ONE_FORMER: 5,
NOTE_TO_SELF: 6,
},

BREAKOUT_ROOM_MODE: {
NOT_CONFIGURED: 0,
AUTOMATIC: 1,
MANUAL: 2,
FREE: 3,
},

BREAKOUT_ROOM_STATUS: {
// Main room
STOPPED: 0,
STARTED: 1,
// Breakout rooms
STATUS_ASSISTANCE_RESET: 0,
STATUS_ASSISTANCE_REQUESTED: 2,
},

OBJECT_TYPE: {
EMAIL: 'emails',
FILE: 'file',
PHONE: 'phone',
VIDEO_VERIFICATION: 'share:password',
BREAKOUT_ROOM: 'room',
DEFAULT: '',
},
}

export const AVATAR = {
SIZE: {
EXTRA_SMALL: 22,
SMALL: 32,
DEFAULT: 44,
MEDIUM: 64,
LARGE: 128,
EXTRA_LARGE: 180,
FULL: 512,
},
}
266 changes: 266 additions & 0 deletions core/src/components/UnifiedSearch/ConversationIcon.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
<!--
- @copyright Copyright (c) 2019 Joas Schilling <coding@schilljs.com>
-
- @author Joas Schilling <coding@schilljs.com>
-
- @license GNU AGPL version 3 or any later version
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-->

<template>
<div class="conversation-icon" :style="{ '--icon-size': `${size}px` }" :class="{ 'offline': offline }">
<div v-if="iconClass" class="avatar icon" :class="iconClass" />
<!-- img is used here instead of NcAvatar to explicitly set key required to avoid glitching in virtual scrolling -->
<img v-else-if="!isOneToOne"
:key="avatarUrl"
:src="avatarUrl"
:width="size"
:height="size"
:alt="item.displayName"
class="avatar icon">
<!-- NcAvatar doesn't fully support props update and works only for 1 user -->
<!-- Using key on NcAvatar forces NcAvatar re-mount and solve the problem, could not really optimal -->
<!-- TODO: Check if props update support in NcAvatar is more performant -->
<NcAvatar v-else
:key="item.token"
:size="size"
:user="item.name"
:disable-menu="disableMenu"
:display-name="item.displayName"
:preloaded-user-status="preloadedUserStatus"
:show-user-status="!hideUserStatus"
:show-user-status-compact="!showUserOnlineStatus"
:menu-container="menuContainer"
class="conversation-icon__avatar" />
<div v-if="showCall" class="overlap-icon">
<VideoIcon :size="20" :fill-color="'#E9322D'" />
<span class="hidden-visually">{{ t('spreed', 'Call in progress') }}</span>
</div>
<div v-else-if="showFavorite" class="overlap-icon">
<Star :size="20" :fill-color="'#FFCC00'" />
<span class="hidden-visually">{{ t('spreed', 'Favorite') }}</span>
</div>
</div>
</template>

<script>
import Star from 'vue-material-design-icons/Star.vue'
import VideoIcon from 'vue-material-design-icons/Video.vue'

import { getCapabilities } from '@nextcloud/capabilities'
import { generateOcsUrl } from '@nextcloud/router'

import NcAvatar from '@nextcloud/vue/dist/Components/NcAvatar.js'

import { AVATAR, CONVERSATION } from '../ConversationConstants.js'

const supportsAvatar = getCapabilities()?.spreed?.features?.includes('avatar')

export default {
name: 'ConversationIcon',

components: {
NcAvatar,
Star,
VideoIcon,
},

props: {
/**
* Allow to hide the favorite icon, e.g. on mentions
*/
hideFavorite: {
type: Boolean,
default: true,
},

hideCall: {
type: Boolean,
default: true,
},

disableMenu: {
type: Boolean,
default: true,
},

hideUserStatus: {
type: Boolean,
default: false,
},

showUserOnlineStatus: {
type: Boolean,
default: false,
},

item: {
type: Object,
default() {
return {
objectType: '',
type: 0,
displayName: '',
isFavorite: false,
}
},
},

/**
* Reduces the opacity of the icon if true
*/
offline: {
type: Boolean,
default: false,
},

size: {
type: Number,
default: AVATAR.SIZE.DEFAULT,
},
},

computed: {
showCall() {
return !this.hideCall && this.item.hasCall
},

showFavorite() {
return !this.hideFavorite && this.item.isFavorite
},

preloadedUserStatus() {
if (!this.hideUserStatus && Object.prototype.hasOwnProperty.call(this.item, 'statusMessage')) {
// We preloaded the status
return {
status: this.item.status || null,
message: this.item.statusMessage || null,
icon: this.item.statusIcon || null,
}
}
return undefined
},

menuContainer() {
// The store may not be defined in the RoomSelector if used from
// the Collaboration menu outside Talk.
return this.$store?.getters.getMainContainerSelector()
},

iconClass() {
if (this.item.isDummyConversation) {
// Prevent a 404 when trying to load an avatar before the conversation data is actually loaded
return 'icon-contacts'
}

if (!supportsAvatar) {
if (this.item.objectType === CONVERSATION.OBJECT_TYPE.FILE) {
return 'icon-file'
} else if (this.item.objectType === CONVERSATION.OBJECT_TYPE.VIDEO_VERIFICATION) {
return 'icon-password'
} else if (this.item.objectType === CONVERSATION.OBJECT_TYPE.EMAIL) {
return 'icon-mail'
} else if (this.item.objectType === CONVERSATION.OBJECT_TYPE.PHONE) {
return 'icon-phone'
} else if (this.item.type === CONVERSATION.TYPE.CHANGELOG) {
return 'icon-changelog'
} else if (this.item.type === CONVERSATION.TYPE.ONE_TO_ONE_FORMER) {
return 'icon-user'
} else if (this.item.type === CONVERSATION.TYPE.GROUP) {
return 'icon-contacts'
} else if (this.item.type === CONVERSATION.TYPE.PUBLIC) {
return 'icon-public'
}
return undefined
}

if (this.item.token) {
// Existing conversations use the /avatar endpoint… Always!
return undefined
}

if (this.item.type === CONVERSATION.TYPE.GROUP) {
// Group icon for group conversation suggestions
return 'icon-contacts'
}

if (this.item.type === CONVERSATION.TYPE.PUBLIC) {
// Public icon for new conversation dialog
return 'icon-public'
}

// Fall-through for other conversation suggestions to user-avatar handling
return undefined
},

isOneToOne() {
return this.item.type === CONVERSATION.TYPE.ONE_TO_ONE
},
checkIfDarkTheme() {
// Nextcloud uses --background-invert-if-dark for dark theme filters in CSS
// Values:
// - 'invert(100%)' for dark theme
// - 'no' for light theme
return window.getComputedStyle(document.body).getPropertyValue('--background-invert-if-dark') === 'invert(100%)'
},
avatarUrl() {
if (!supportsAvatar) {
return undefined
}

const avatarEndpoint = 'apps/spreed/api/v1/room/{token}/avatar' + (this.checkIfDarkTheme ? '/dark' : '')

return generateOcsUrl(avatarEndpoint + '?v={avatarVersion}', {
token: this.item.token,
avatarVersion: this.item.avatarVersion,
})
},
},
}
</script>

<style lang="scss" scoped>
.conversation-icon {
width: var(--icon-size);
height: var(--icon-size);
position: relative;

.avatar.icon {
display: block;
width: var(--icon-size);
height: var(--icon-size);
line-height: var(--icon-size);
background-size: calc(var(--icon-size) / 2);
background-color: var(--color-background-darker);

&.icon-changelog {
background-size: cover !important;
}
}

.overlap-icon {
position: absolute;
top: 0;
left: calc(var(--icon-size) - 12px);
line-height: 100%;
display: inline-block;
vertical-align: middle;
}
}

.offline {
opacity: .4;
}
</style>
Loading

0 comments on commit dd46a77

Please sign in to comment.