mindboost-rnbo-test-project/components/NavigationBar.vue

688 lines
21 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<div class="nav">
<ul class="nav__list">
<li id="step-1" class="nav__item">
<a
v-if="!isPlaying()"
id="foucused-icon"
:class="{'is-active': true}"
class="nav__item-link"
@click.prevent="playSound"
>
<svg width="24" height="24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="m6.192 3.67 13.568 7.633a.8.8 0 0 1 0 1.394L6.192 20.33A.8.8 0 0 1 5 19.632V4.368a.8.8 0 0 1 1.192-.698Z"
fill="currentColor"
/>
</svg>
</a>
<a v-else id="foucused-icon" :class="{'is-active': false}" class="nav__item-link" @click.prevent="playSound">
<svg width="24" height="24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#a)">
<path
d="M17.083 19.917a2.326 2.326 0 0 1-1.706-.71 2.332 2.332 0 0 1-.71-1.707V5.417c0-.665.236-1.234.71-1.706A2.333 2.333 0 0 1 17.083 3c.664 0 1.233.236 1.708.71.474.475.71 1.044.709 1.707V17.5a2.33 2.33 0 0 1-.71 1.707 2.322 2.322 0 0 1-1.707.71Zm-9.666 0a2.326 2.326 0 0 1-1.707-.71A2.332 2.332 0 0 1 5 17.5V5.417c0-.665.237-1.234.71-1.706A2.333 2.333 0 0 1 7.417 3c.663 0 1.233.236 1.707.71.475.475.71 1.044.71 1.707V17.5a2.33 2.33 0 0 1-.71 1.707 2.322 2.322 0 0 1-1.707.71Z"
fill="#e9c046"
/>
</g>
<defs>
<clipPath id="a">
<path fill="#e9c046" d="M0 0h24v24H0z" />
</clipPath>
</defs>
</svg>
</a>
</li>
<li
id="step-2"
ref="soundscape"
class="nav__item nav__item--toggle"
:class="{ 'nav__item--active': userStore.soundMode === 'soundscape' }"
@click="setSoundMode('soundscape')"
>
<a class="nav__item-link" aria-current="page" href="#" @click.prevent="toggleDropdown('soundscape')">
<svg width="24" height="24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M8.286 18.875c0 .564-.226 1.104-.628 1.503a2.152 2.152 0 0 1-3.03 0 2.116 2.116 0 0 1 0-3.006 2.152 2.152 0 0 1 3.03 0c.402.399.628.94.628 1.503Zm0 0V6.479L19 4v12.396M8.286 10.73 19 8.25m0 8.5c0 .564-.226 1.104-.628 1.503a2.152 2.152 0 0 1-3.03 0 2.116 2.116 0 0 1 0-3.006 2.152 2.152 0 0 1 3.03 0c.402.399.628.94.628 1.503Z"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</a>
<ul
class="nav__dropdown nav__dropdown--no-padding"
:class="{ open: activeDropdown === 'soundscape' }"
@click.stop
>
<li>
<nuxt-link
id="lagoonLinkHomebar"
:class="{'is-select':form.soundscape=='Lagoon'}"
class="nav__dropdown-listItem py-2 fw-bold fs-6 "
@click="saveSetting('Lagoon', 'soundscape')"
>
{{ t('Lagoon') }}
<span class="float-end">
<WaveSVG />
</span>
</nuxt-link>
</li>
<li>
<nuxt-link
id="tropicsLinkHomebar"
:class="{'is-select':form.soundscape=='Tropics'}"
class="nav__dropdown-listItem fw-bold fs-6 py-2"
@click="saveSetting('Tropics', 'soundscape')"
>
{{ t('Tropics') }}
<span class="float-end">
<TropicsSVG />
</span>
</nuxt-link>
</li>
<li>
<nuxt-link
id="forestLinkHomebar"
:class="{'is-select':form.soundscape=='Forest'}"
class="nav__dropdown-listItem fw-bold fs-6 py-2"
@click="saveSetting('Forest', 'soundscape')"
>
{{ t('Forest') }}
<span class="float-end">
<ForestSVG />
</span>
</nuxt-link>
</li>
<li>
<nuxt-link
id="meadowLinkHomebar"
:class="{'is-select':form.soundscape=='Meadow'}"
class="nav__dropdown-listItem fw-bold fs-6 py-2"
@click="saveSetting('Meadow', 'soundscape')"
>
{{ t('Meadow') }}
<span class="float-end">
<MeadowSVG />
</span>
</nuxt-link>
</li>
</ul>
</li>
<li
id="step-3"
ref="music"
class="nav__item nav__item--toggle"
:class="{ 'nav__item--active': userStore.soundMode === 'music' }"
@click="setSoundMode('music')"
>
<a class="nav__item-link" aria-current="page" href="#" @click.prevent="toggleDropdown('music')">
<svg width="24" height="24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20.293 2 3 21.707" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
<path
d="M8.286 18.875c0 .564-.226 1.104-.628 1.503a2.152 2.152 0 0 1-3.03 0 2.116 2.116 0 0 1 0-3.006 2.152 2.152 0 0 1 3.03 0c.402.399.628.94.628 1.503Zm0 0V6.479L19 4v12.396M8.286 10.73 19 8.25m0 8.5c0 .564-.226 1.104-.628 1.503a2.152 2.152 0 0 1-3.03 0 2.116 2.116 0 0 1 0-3.006 2.152 2.152 0 0 1 3.03 0c.402.399.628.94.628 1.503Z"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</a>
<ul class="nav__dropdown text-muted-dark" :class="{ open: activeDropdown === 'music' }" @click.stop>
<p class="fw-bold pt-1">{{ t('musicmode_activated') }}</p>
<p>{{ t('musicmode_text') }}</p>
</ul>
</li>
<li id="step-4" ref="settings" class="nav__item" @click.stop="handleClickOutside">
<a class="nav__item-link" aria-current="page" href="#" @click.stop="toggleDropdown('settings')">
<svg width="18" height="18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M2.3 8.177V.05h2.15V8.205l.025.015a2.2 2.2 0 1 1-2.2 0l.025-.015v-.028Zm7.775-5.625v.028l.025.015a2.2 2.2 0 1 1-2.2 0l.025-.015V.05h2.15v2.502ZM15.7 8.177v.028l.025.015a2.2 2.2 0 1 1-2.2 0l.025-.015V.05h2.15v8.127ZM4.45 13.55v4.4H2.3v-4.4h2.15Zm5.625-5.625V17.95h-2.15V7.925h2.15Zm3.475 5.625h2.15v4.4h-2.15v-4.4Z"
fill="currentColor"
stroke="currentColor"
stroke-width=".1"
/>
</svg>
</a>
<ul class="nav__dropdown pt-4" :class="{ open: activeDropdown === 'settings' }" @click.stop>
<li v-for="soundscape in soundscapes" :key="soundscape.id">
<MusicGainSlider v-if="currentIndex === soundscape.id && userStore.soundMode !== 'music'" />
</li>
<li>
<NoiseGainSlider />
</li>
</ul>
</li>
<li
v-if="userStore.isTimerActivated"
id="step-5"
ref="pomodoro"
class="nav__item nav__item--timer-bg"
:style="`--progress-width: ${progressBarWidth}`"
:data-phase="timer.getCurrentSession"
:data-color="progressColor"
@click.stop="handleClickOutside"
>
<a
class="nav__item-link nav__item-link--timer"
aria-current="page"
:style="`--progress-color: ${progressColor}`"
href="#"
@click.stop="toggleDropdown('pomodoro')"
>
<svg width="27" height="27" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M15.11 6.301a7.78 7.78 0 0 1-1.111 15.478A7.777 7.777 0 0 1 8.734 8.275L7.162 6.703a10 10 0 1 0 5.726-2.64v6.7h2.222V6.3Z"
fill="currentColor"
/>
<path
d="M9.229 10.342a1.11 1.11 0 0 0 0 1.571l3.143 3.144a1.111 1.111 0 0 0 1.571-1.572L10.8 10.343a1.111 1.111 0 0 0-1.57 0Z"
fill="currentColor"
/>
</svg>
</a>
<ul class="nav__dropdown" :class="{ open: activeDropdown === 'pomodoro' }" @click.stop>
<PomodoroTimer />
</ul>
</li>
</ul>
</div>
</template>
<script>
import { ref, onMounted } from 'vue'
import { mapActions } from 'pinia'
import { driver } from 'driver.js'
import MusicGainSlider from '../components/sliders/MusicGainSlider'
import NoiseGainSlider from '../components/sliders/NoiseGainSlider'
import AdaptiveNoiseGain from '../components/experiments/homepages/AdaptiveNoiseGain'
import 'driver.js/dist/driver.css'
import WaveSVG from './svg/homebar/WaveSVG.vue'
import TropicsSVG from './svg/homebar/TropicsSVG.vue'
import ForestSVG from './svg/homebar/ForestSVG.vue'
import MeadowSVG from './svg/homebar/MeadowSVG.vue'
import PomodoroTimer from '~/components/timer/PomodoroTimer.vue'
import { useTimerStore } from '~/stores/timer'
import { useAudioStore } from '~/stores/audio'
import { calculateNormalizedVolume } from '~/lib/AudioFunctions'
import { useUserStore } from '~/stores/user'
export default {
name: 'NavigationBar',
components: {
WaveSVG,
TropicsSVG,
ForestSVG,
MeadowSVG,
NoiseGainSlider,
MusicGainSlider,
PomodoroTimer
},
props: {
title: {
type: String,
default: 'NavigationBar'
},
currentIndex: {
type: Number,
required: true
},
currentSoundscape: {
type: String,
required: true
},
adaptive: {
type: String,
required: true
}
},
emits: ['update:soundscape', 'update:currentIndex'],
setup () {
const { t } = useI18n()
const logger = useNuxtApp.$logger
const localePath = useLocalePath()
const timer = useTimerStore()
const audioStore = useAudioStore()
const userStore = useUserStore()
const { $axios } = useNuxtApp()
const activeDropdown = ref(null)
const progressBarWidth = computed(() => {
const totalTime = timer.getSessionTime * 60
return totalTime > 0 ? `${(timer.getTimeRemaining * 100) / totalTime}%` : '0%'
})
const progressColor = computed(() => {
return timer.getCurrentColor
})
const handleValueChange = (value) => {
// useNuxtApp().$logger.log('handle new control value: ' + value)
if (!isNaN(value)) {
const normaizedVolume = calculateNormalizedVolume(value)
// useNuxtApp().$logger.log('normalized volume: ' + normaizedVolume)
} else {
logger.log('value is not NaN', { value })
}
}
const onboarding = localStorage.getItem('onboarding')
const steps = [
{
element: '#step-1',
popover: {
title: t('Tooltip-start-title'),
description: t('Tooltip-start-content'),
side: 'top',
align: 'start'
}
},
{
element: '#step-2',
popover: {
title: t('Tooltip-sound-title'),
description: t('Tooltip-sound-content'),
side: 'top',
align: 'start'
}
},
{
element: '#step-3',
popover: {
title: t('Tooltip-music-title'),
description: t('Tooltip-music-content'),
side: 'top',
align: 'start'
}
},
{
element: '#step-4',
popover: {
title: t('Tooltip-settings-title'),
description: t('Tooltip-settings-content'),
side: 'top',
align: 'start'
}
},
{
element: '#step-5',
popover: {
title: t('Tooltip-timer-title'),
description: t('Tooltip-timer-content'),
side: 'top',
align: 'start'
}
},
{
element: '#step-6',
popover: {
title: t('Lets Go!'),
description: t('We have saved your selection.') + '</br>' + t('You can change your settings at any time.') + '</br>' + '<img src="/images/rocketman.svg" height="35" class="img " alt="image">',
align: 'center',
popoverClass: 'onboarding-popover--last'
}
}
]
// localstorage abgreifen und wenn onboarding null, dann starten
if (!userStore.isOnboardingCompleted) {
onMounted(() => {
const driverObj = driver({
popoverClass: 'onboarding-popover',
prevBtnText: '<svg width="16" height="16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M10.707 1.636a1 1 0 0 1 0 1.414L5.757 8l4.95 4.95a1 1 0 0 1-1.414 1.414L3.636 8.707a1 1 0 0 1 0-1.414l5.657-5.657a1 1 0 0 1 1.414 0Z" fill="#585C5E"/></svg>',
nextBtnText: '<svg width="16" height="16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M5.293 1.636a1 1 0 0 0 0 1.414L10.243 8l-4.95 4.95a1 1 0 0 0 1.414 1.414l5.657-5.657a1 1 0 0 0 0-1.414L6.707 1.636a1 1 0 0 0-1.414 0Z" fill="#585C5E"/></svg>',
doneBtnText: t('Lets Go!'),
steps,
onDestroyed: async () => {
audioStore.setPlaying(true)
// Backend updaten
try {
await $axios.post('/api/account/onboarding-completed')
userStore.markOnboardingCompleted() // Store aktualisieren
} catch (error) {
logger.error('Error updating onboarding status:', error)
}
driverObj.destroy()
}
})
driverObj.drive()
})
}
return {
t,
localePath,
timer,
progressBarWidth,
progressColor,
handleValueChange
}
},
data () {
return {
activeNav: 'soundscape',
activeDropdown: null,
userStore: useUserStore(),
form: {
soundscape: '',
headphone_type: '',
anc_type: '',
adaptive_sound_scape: ''
},
bar_width: 230,
analyser: null,
dataArray: null,
bar_val: 25,
soundscapes: [
{ id: 0, title: 'Lagoon', src: window.location.origin + useRuntimeConfig().public.tracks.lagoon_src },
{ id: 1, title: 'Tropics', src: window.location.origin + useRuntimeConfig().public.tracks.tropics_src },
{ id: 2, title: 'Forest', src: window.location.origin + useRuntimeConfig().public.tracks.forest_src },
{ id: 3, title: 'Meadow', src: window.location.origin + useRuntimeConfig().public.tracks.meadow_src }
]
}
},
mounted () {
this.updateActiveSoundscape()
getSettings()
const timerActivated = localStorage.getItem('timerActivated')
if (timerActivated !== null) {
const timer = useTimerStore()
timer.activated = JSON.parse(timerActivated)
}
document.addEventListener('click', this.handleClickOutside)
},
beforeUnmount () {
document.removeEventListener('click', this.handleClickOutside)
},
methods: {
...mapActions(useUserStore, ['updateHeadsetType', 'updateANC', 'logout', 'soundscape']),
...mapActions(useAudioStore, ['isPlaying', 'setPlaying']),
updateMusicVolume (volume) {
const musicGain = this.$refs[this.currentSoundscape]
musicGain.updateMusicGain(volume)
},
handleAdaptiveSoundscapeChange (newValue) {
this.adaptiveSoundscape = newValue
this.saveSetting(newValue, 'adaptivesoundscape')
useNuxtApp().$logger.log('Adaptive soundscape setting saved lets change the index page')
},
saveSettings (value, key) {
useNuxtApp().$logger.log(`Saving ${key}: ${value}`)
},
updateActiveSoundscape () {
switch (this.currentSoundscape) {
case 'Lagoon':
document.getElementById('lagoonLinkHomebar').classList.add('active')
break
case 'Tropics':
document.getElementById('tropicsLinkHomebar').classList.add('active')
break
case 'Forest':
document.getElementById('forestLinkHomebar').classList.add('active')
break
case 'Meadow':
document.getElementById('meadowLinkHomebar').classList.add('active')
break
}
},
removeActiveSoundscape () {
// foreach nav__dropdown-listItem remove active class
const soundscapeLinks = document.querySelectorAll('.nav__dropdown-listItem')
soundscapeLinks.forEach((link) => {
link.classList.remove('active')
})
},
playSound () {
this.setPlaying(!this.isPlaying())
},
toggleDropdown (navItem) {
this.activeDropdown = this.activeDropdown === navItem ? null : navItem
},
handleClickOutside (event) {
if (
this.$refs.soundscape &&
!this.$refs.soundscape.contains(event.target) &&
this.$refs.music &&
!this.$refs.music.contains(event.target)
) {
this.activeDropdown = null
}
},
setSoundMode (mode) {
if (this.userStore.soundMode !== mode) {
this.userStore.soundMode = mode
if (mode === 'soundscape') {
this.$emit('update:soundscape', this.form)
}
}
},
saveSetting (value, type) {
if (type === 'soundscape') {
this.form.soundscape = value
this.$emit('update:soundscape', this.form)
// Finde die passende Soundscape id
const soundscape = this.soundscapes.find(s => s.title === value)
// remove active state from all soundscapes before nuxt link does its automatic magic
this.removeActiveSoundscape()
if (soundscape) {
this.$emit('update:currentIndex', soundscape.id)
}
}
}
}
}
</script>
<style style="scss" scoped>
.nav {
position: fixed;
bottom: 3em;
text-align: center;
width: 100%;
padding-right: 0;
z-index: -1;
@media only screen and (max-width: 576px) {
bottom: 1.5em;
}
.nav__list {
box-shadow: 0 0 14px 0 rgba(0, 0, 0, 0.1);
-webkit-box-shadow: 0 0 14px 0 rgba(0, 0, 0, 0.1);
-moz-box-shadow: 0 0 14px 0 rgba(0, 0, 0, 0.1);
display: flex;
justify-content: space-between;
list-style: none;
padding: 0;
margin: 0 auto;
align-items: center;
position: relative;
border-radius: 6px;
background-color: white;
flex-basis: 100%;
max-width: 350px;
@media only screen and (max-width: 576px) {
width: 100%;
margin: 0 auto;
}
}
.nav__item {
height: 50px;
padding: 0.25em;
flex: 1;
&:hover:not(.nav__item--active) {
color: #e9c046;
/* .nav__dropdown {
display: block;
} */
}
&:first-of-type {
border-top-left-radius: 6px;
border-bottom-left-radius: 6px;
}
&:last-of-type {
border-top-right-radius: 6px;
border-bottom-right-radius: 6px;
}
&.nav__item--toggle {
cursor: pointer;
padding-left: 0;
padding-right: 0;
&:first-of-type {
margin-left: 0.25em;
}
&:last-of-type {
margin-right: 0.25em;
}
&.nav__item--active {
.nav__item-link {
background-color: #fff;
}
}
.nav__item-link {
transition: background-color 0.25s, box-shadow 0.25s, color 0.25s;
background-color: #F4F5F8;
box-shadow: inset 0 0 3px 0 rgba(0, 0, 0, 0.1);
}
}
&.nav__item--timer-bg {
padding: 0;
border-left: 2px solid #F4F5F8;
overflow: hidden;
.nav__item-link {
position: relative;
svg {
z-index: 2;
}
&::before {
content: '';
position: absolute;
left: 0;
top: 0;
right: calc(100% - var(--progress-width));
transition: right 0.25s ease-in-out;
bottom: 0;
background-color: var(--progress-color);
opacity: 0.5;
}
}
}
.nav__item-link {
color: #585C5E;
display: flex;
align-items: center;
cursor: pointer;
transition: 250ms ease-in-out;
height: 100%;
justify-content: center;
&:hover {
color: #e9c046;
}
}
.nav__item-link--timer {
&:hover {
color: #fff;
}
}
}
.nav__dropdown{
background-color: #F4F5F8;
position: absolute;
bottom: 0;
padding: 0.75em 1em 4em 1em;
left: 0;
right: 0;
display: none;
list-style: none;
border-radius: 6px;
box-shadow: 0 0 14px 0 rgba(0, 0, 0, 0.1);
-webkit-box-shadow: 0 0 14px 0 rgba(0, 0, 0, 0.1);
-moz-box-shadow: 0 0 14px 0 rgba(0, 0, 0, 0.1);
z-index: -1;
&.nav__dropdown--no-padding {
padding-left: 0;
padding-right: 0;
}
&.open {
display: block;
}
.nav__dropdown-listItem {
display: flex;
justify-content: space-between;
width: 100%;
padding: 0.5em 1em;
text-decoration: none;
cursor: pointer;
color: #585C5E;
transition: 250ms ease-in-out;
&.active, &.is-select {
background-color: #e9c046;
color: white;
}
&:hover {
background-color: #e9c046;
color: white;
}
}
p{
text-align: left;
}
.form-switch {
display: flex;
justify-content: space-between;
align-items: center;
gap: 1em;
padding-left: 0;
.form-switch__label {
font-weight: 600;
}
}
}
}
</style>