Skip to content

Commit

Permalink
fix(MediaSettings): improve tabs transition
Browse files Browse the repository at this point in the history
Signed-off-by: Grigorii K. Shartsev <[email protected]>
  • Loading branch information
ShGKme committed Jul 29, 2024
1 parent 31d527a commit 7c888d2
Show file tree
Hide file tree
Showing 3 changed files with 187 additions and 65 deletions.
108 changes: 43 additions & 65 deletions src/components/MediaSettings/MediaSettings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -74,46 +74,28 @@
</div>
</div>

<!-- Tabs -->
<div class="media-settings__call-preferences">
<NcButton :type="showDeviceSelection ? 'secondary' : 'tertiary'"
wide
@click="toggleTab('devices')">
<template #icon>
<Cog :size="20" />
</template>
{{ t('spreed', 'Devices') }}
</NcButton>
<NcButton v-if="isVirtualBackgroundAvailable"
:type="showBackgroundEditor ? 'secondary' : 'tertiary'"
wide
@click="toggleTab('backgrounds')">
<template #icon>
<Creation :size="20" />
</template>
{{ t('spreed', 'Backgrounds') }}
</NcButton>
</div>

<!-- Device selection -->
<div v-if="showDeviceSelection" class="media-settings__device-selection">
<MediaDevicesSelector kind="audioinput"
:devices="devices"
:device-id="audioInputId"
@refresh="updateDevices"
@update:deviceId="handleAudioInputIdChange" />
<MediaDevicesSelector kind="videoinput"
:devices="devices"
:device-id="videoInputId"
@refresh="updateDevices"
@update:deviceId="handleVideoInputIdChange" />
<MediaDevicesSpeakerTest />
</div>
<!-- Tab panels -->
<MediaSettingsTabs :active.sync="tabContent" :tabs="tabs">
<template #tab-panel:devices>
<MediaDevicesSelector kind="audioinput"
:devices="devices"
:device-id="audioInputId"
@refresh="updateDevices"
@update:deviceId="handleAudioInputIdChange" />
<MediaDevicesSelector kind="videoinput"
:devices="devices"
:device-id="videoInputId"
@refresh="updateDevices"
@update:deviceId="handleVideoInputIdChange" />
<MediaDevicesSpeakerTest />
</template>

<!-- Background selection -->
<VideoBackgroundEditor v-if="showBackgroundEditor"
:token="token"
@update-background="handleUpdateVirtualBackground" />
<template #tab-panel:backgrounds>
<VideoBackgroundEditor class="media-settings__tab"
:token="token"
@update-background="handleUpdateVirtualBackground" />
</template>
</MediaSettingsTabs>

<!-- "Always show" setting -->
<NcCheckboxRadioSwitch v-if="!isPublicShareAuthSidebar"
Expand Down Expand Up @@ -192,7 +174,7 @@
</template>

<script>
import { ref } from 'vue'
import { computed, markRaw, ref } from 'vue'
import Bell from 'vue-material-design-icons/Bell.vue'
import BellOff from 'vue-material-design-icons/BellOff.vue'
Expand All @@ -215,6 +197,7 @@ import Tooltip from '@nextcloud/vue/dist/Directives/Tooltip.js'
import MediaDevicesSelector from './MediaDevicesSelector.vue'
import MediaDevicesSpeakerTest from './MediaDevicesSpeakerTest.vue'
import MediaSettingsTabs from './MediaSettingsTabs.vue'
import VideoBackgroundEditor from './VideoBackgroundEditor.vue'
import AvatarWrapper from '../AvatarWrapper/AvatarWrapper.vue'
import VideoBackground from '../CallView/shared/VideoBackground.vue'
Expand Down Expand Up @@ -242,8 +225,6 @@ export default {
Bell,
BellOff,
CallButton,
Cog,
Creation,
NcActionButton,
NcActions,
NcButton,
Expand All @@ -252,6 +233,7 @@ export default {
NcNoteCard,
MediaDevicesSelector,
MediaDevicesSpeakerTest,
MediaSettingsTabs,
ReflectHorizontal,
VideoBackground,
VideoIcon,
Expand Down Expand Up @@ -290,6 +272,20 @@ export default {
virtualBackground,
} = useDevices(video, false)
const isVirtualBackgroundAvailable = computed(() => virtualBackground.value.isAvailable())
const devicesTab = {
id: 'devices',
label: t('spreed', 'Devices'),
icon: markRaw(Cog),
}
const backgroundsTab = {
id: 'backgrounds',
label: t('spreed', 'Backgrounds'),
icon: markRaw(Creation),
}
const tabs = computed(() => isVirtualBackgroundAvailable.value ? [devicesTab, backgroundsTab] : [devicesTab])
return {
AVATAR,
isInCall,
Expand All @@ -310,13 +306,15 @@ export default {
stopDevices,
virtualBackground,
model: localMediaModel,
tabs,
isVirtualBackgroundAvailable,
}
},
data() {
return {
modal: false,
tabContent: 'none',
tabContent: undefined,
audioOn: undefined,
videoOn: undefined,
silentCall: false,
Expand Down Expand Up @@ -428,18 +426,6 @@ export default {
return !(this.hasCall && !this.isInLobby) && !this.isPublicShareAuthSidebar
},
showDeviceSelection() {
return this.tabContent === 'devices'
},
showBackgroundEditor() {
return this.tabContent === 'backgrounds'
},
isVirtualBackgroundAvailable() {
return this.virtualBackground.isAvailable()
},
showUpdateChangesButton() {
return this.updatedBackground || this.audioDeviceStateChanged
|| this.videoDeviceStateChanged
Expand Down Expand Up @@ -471,13 +457,13 @@ export default {
},
audioInputId(audioInputId) {
if (this.showDeviceSelection && audioInputId && !this.audioOn) {
if (this.tabContent === 'devices' && audioInputId && !this.audioOn) {
this.toggleAudio()
}
},
videoInputId(videoInputId) {
if (this.showDeviceSelection && videoInputId && !this.videoOn) {
if (this.tabContent === 'devices' && videoInputId && !this.videoOn) {
this.toggleVideo()
}
},
Expand Down Expand Up @@ -667,14 +653,6 @@ export default {
}
},
toggleTab(tab) {
if (this.tabContent !== tab) {
this.tabContent = tab
} else {
this.tabContent = 'none'
}
},
setShowMediaSettings(newValue) {
this.settingsStore.setShowMediaSettings(this.token, newValue)
},
Expand Down
139 changes: 139 additions & 0 deletions src/components/MediaSettings/MediaSettingsTabs.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
<!--
- SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->

<script setup lang="ts">
import { computed, ref } from 'vue'
import type { Component } from 'vue'
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
import TransitionExpandDown from './TransitionExpandDown.vue'
type TabDefinition = {
id: string,
label: string,
icon: Component,
}
const props = defineProps<{
tabs: TabDefinition[],
active?: string,
}>()
const emit = defineEmits<{
(event: 'update:active', value?: string): void
}>()
/** Whether the tab panel is open */
const isOpen = ref(false)
// A11y ReferenceIDs
const randomId = Math.random().toString(36).substring(7)
const getRefId = (scope: 'tab' | 'panel', key: string) => `tab-${randomId}-${scope}-${key}`
/**
* Whether the tab panel horizontal transition is enabled.
* Used to prevent the panel switch transition when the tab is first rendered.
*/
const enableTransition = ref(false)
/** Index of the active tab for the transition effect */
const activeIndex = computed(() => props.tabs.findIndex(tab => tab.id === props.active))
/**
* Whether the tab is active
* @param tabId - Tab ID
*/
function isActive(tabId: string) {
return tabId === props.active
}
/**
* Whether the tab is selected on UI
* @param tabId - Tab ID
*/
function isSelected(tabId: string) {
return isOpen.value && isActive(tabId)
}
/**
* Toggle the tab:
* - Toggle the tab on the current tab click
* - Switch and open tab on a new tab click
* @param tabId - New selected tabId
*/
function handleTabClick(tabId: string) {
if (isActive(tabId)) {
isOpen.value = !isOpen.value
} else {
emit('update:active', tabId)
isOpen.value = true
}
}
</script>

<template>
<div class="tabs">
<div class="tab-list" role="tablist">
<NcButton v-for="tab in tabs"
:id="getRefId('tab', tab.id)"
:key="tab.id"
wide
role="tab"
:type="isSelected(tab.id) ? 'secondary' : 'tertiary'"
:aria-selected="isSelected(tab.id) ? 'true' : 'false'"
:aria-controls="getRefId('panel', tab.id)"
@click.stop="handleTabClick(tab.id)">
<template #icon>
<component :is="tab.icon" :size="20" />
</template>
{{ tab.label }}
</NcButton>
</div>

<TransitionExpandDown :show="isOpen" @after-enter="enableTransition = true" @after-leave="enableTransition = false">
<div class="tab-panels-container">
<div v-for="tab in tabs"
:id="getRefId('panel', tab.id)"
:key="tab.id"
class="tab-panel"
:class="{ 'tab-panel--with-transition': enableTransition }"
role="tabpanel"
:inert="!isActive(tab.id)"
:aria-hidden="!isActive(tab.id)"
:aria-labelledby="getRefId('tab', tab.id)"
:style="activeIndex !== -1 ? `transform: translateX(${-activeIndex * 100}%)` : ''">
<slot :name="`tab-panel:${tab.id}`" />
</div>
</div>
</TransitionExpandDown>
</div>
</template>

<style lang="scss" scoped>
.tab-list {
display: flex;
justify-content: center;
align-items: center;
gap: calc(var(--default-grid-baseline) * 2);
}
.tab-panels-container {
display: flex;
width: 100%;
overflow: hidden;
transition: height ease var(--animation-slow);
}
.tab-panel {
width: 100%;
flex: 1 0 100%;
transition: none;
&--with-transition {
transition: transform ease var(--animation-slow);
}
}
</style>
5 changes: 5 additions & 0 deletions src/components/MediaSettings/VideoBackgroundEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,11 @@ export default {
&--selected {
box-shadow: inset 0 0 0 var(--default-grid-baseline) var(--color-primary-element);
}
&:focus-visible {
// Do not overflow container
outline-offset: -2px; // inline with server's global focus outline
}
}
}
Expand Down

0 comments on commit 7c888d2

Please sign in to comment.