-
-
Notifications
You must be signed in to change notification settings - Fork 4.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feat: Create filter-plugin architecture for unified search
This commit introduces the mechanism for apps out of the call, to add search filters to the unified search "Places" filter selector. Signed-off-by: fenn-cs <fenn25.fn@gmail.com>
- Loading branch information
Showing
6 changed files
with
475 additions
and
64 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import Vue from 'vue'; | ||
import Vuex from 'vuex'; | ||
import search from './unified-search-external-filters'; | ||
|
||
Vue.use(Vuex); | ||
|
||
export default new Vuex.Store({ | ||
modules: { | ||
search, | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
/* | ||
* @copyright Copyright (c) 2021 Fon E. Noel NFEBE <me@nfebe.com> | ||
* | ||
* @author Fon E. Noel NFEBE <me@nfebe.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/>. | ||
* | ||
*/ | ||
const state = { | ||
externalFilters: [], | ||
} | ||
|
||
const mutations = { | ||
registerExternalFilter(state, { id, label, callback, icon }) { | ||
state.externalFilters.push({ id, name: label, callback, icon, isPluginFilter: true }) | ||
}, | ||
} | ||
|
||
const actions = { | ||
registerExternalFilter({ commit }, { id, label, callback, icon }) { | ||
commit('registerExternalFilter', { id, label, callback, icon }) | ||
}, | ||
} | ||
|
||
export default { | ||
state, | ||
mutations, | ||
actions, | ||
} |
Oops, something went wrong.