Initial commit
This commit is contained in:
103
pages/auth/forgot.vue
Normal file
103
pages/auth/forgot.vue
Normal file
@@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<div>
|
||||
<video-background
|
||||
src="/video/bg-video.mp4"
|
||||
style=" height: 100vh;"
|
||||
poster="/images/poster.png"
|
||||
>
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-12 col-lg-4 bg-img d-none d-lg-block" style="background-image: url('/images/login.svg');background-size: cover;height: 100vh;" />
|
||||
<div class="col-12 col-lg-8 pt-4 px-3 px-sm-5 px-md-5 pb-5">
|
||||
<div class="row">
|
||||
<div class="col-12 py-3 text-center">
|
||||
<img src="/images/logo.svg" height="35" class="img " alt="Mindboost Logo">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-lg-0 mt-4" style="height: 90%">
|
||||
<div class="col-12 my-auto">
|
||||
<div class="text-center pt-4 pt-md-2 col-12 col-md-8 col-xl-8 mx-auto">
|
||||
<h1 class="fw-bolder h3" style="color: #585C5E;">
|
||||
{{ t("Forgot Password?") }}
|
||||
</h1>
|
||||
<h2 class="text-muted h5 pt-2">
|
||||
{{ t("We send link") }}
|
||||
</h2>
|
||||
</div>
|
||||
<div class="row pt-2">
|
||||
<div class="col-12 col-md-8 col-xl-8 mx-auto">
|
||||
<div v-if="resetSuccess" class="alert alert-success text-center">
|
||||
{{ t('Reset Password Message') }}
|
||||
</div>
|
||||
<form method="POST" @submit.prevent="submit">
|
||||
<div class="row pt-4">
|
||||
<div class="col-12">
|
||||
<label class="form-label">{{ t('Email') }}</label>
|
||||
<input v-model="email" type="email" placeholder="email@mail.com" class="form-control">
|
||||
<div v-if="errors.email" class="invalid-feedback d-block">
|
||||
{{ t(errors.email[0]) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row pt-4">
|
||||
<div class="col">
|
||||
<button type="submit" style="min-width:fit-content" class="login-btn col-4">
|
||||
{{ t('Reset Password') }} <div v-if="loading" class="spinner-border spinner-border-sm" role="status">
|
||||
<span class="sr-only">{{ t("Loading...") }}</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</video-background>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
setup () {
|
||||
const { t } = useI18n()
|
||||
const localePath = useLocalePath()
|
||||
|
||||
return {
|
||||
t,
|
||||
localePath
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
email: '',
|
||||
resetSuccess: false,
|
||||
errors: [],
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
submit () {
|
||||
this.loading = true
|
||||
this.errors = []
|
||||
this.$axios.post('/api/password/email', { email: this.email })
|
||||
.then(({ data }) => {
|
||||
this.loading = false
|
||||
this.$toast.success('Wenn die E-Mail existiert, wurde ein Link zum Zurücksetzen gesendet.')
|
||||
this.resetSuccess = true
|
||||
})
|
||||
.catch((error) => {
|
||||
this.loading = false
|
||||
if (error.response.status === 422) {
|
||||
this.errors = error.response.data.errors
|
||||
} else {
|
||||
this.$toast.error('Ein Fehler ist aufgetreten.')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
214
pages/auth/login.vue
Normal file
214
pages/auth/login.vue
Normal file
@@ -0,0 +1,214 @@
|
||||
<template>
|
||||
<div>
|
||||
<video-background
|
||||
src="/video/bg-video.mp4"
|
||||
poster="/images/poster.png"
|
||||
style="height: 100vh;"
|
||||
>
|
||||
<div class="container-fluid">
|
||||
<div class="row ">
|
||||
<div class="col-12 col-lg-4 bg-img d-none d-lg-block" style="background-image: url('/images/login.svg');background-size: cover;height: 100vh;" />
|
||||
<div class="col-12 col-lg-8 pt-4 px-3 px-sm-5 px-md-5 pb-5">
|
||||
<div class="row">
|
||||
<div class="col-12 py-3 text-center">
|
||||
<img src="/images/logo.svg" height="35" class="img " alt="Mindboost Logo">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-lg-0 mt-4" style="height: 90%">
|
||||
<div class="col-12 my-auto">
|
||||
<div class="text-center pt-4 pt-md-2 col-12 col-md-8 col-xl-8 mx-auto">
|
||||
<h1 class="fw-bolder h3" style="color: #585C5E;">
|
||||
{{ t('Log in Header') }}
|
||||
</h1>
|
||||
<h2 class="text-muted h5 pt-2">
|
||||
{{ t('Log in') }}
|
||||
</h2>
|
||||
</div>
|
||||
<div class="row pt-2">
|
||||
<div class="col-12 col-md-8 col-xl-8 mx-auto">
|
||||
<form method="POST" @submit.prevent="loginNow">
|
||||
<div class="row pt-4">
|
||||
<div class="col-12">
|
||||
<label class="form-label">{{ t('Email') }}</label>
|
||||
<input v-model="form.email" type="email" autocomplete="email" class="form-control">
|
||||
<div v-if="errors.email" class="invalid-feedback d-block">
|
||||
{{ errors.email[0] }}
|
||||
</div>
|
||||
<div v-if="auth_error" class="invalid-feedback d-block">
|
||||
{{ t(auth_error_message) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row pt-3">
|
||||
<div class="col">
|
||||
<label class="form-label">{{ t('Password') }}</label>
|
||||
<input
|
||||
v-model="form.password"
|
||||
type="password"
|
||||
name="password"
|
||||
autocomplete="current-password"
|
||||
class="form-control"
|
||||
>
|
||||
<span class="float-end pt-2">
|
||||
<router-link :to="localePath('/auth/forgot')" class="forgot-link" href="#">{{ t('Forgot Password?') }}</router-link>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row pt-4">
|
||||
<div class="col">
|
||||
<button type="submit" style="min-width:fit-content" class="login-btn col-4">
|
||||
{{ t('Sign in') }} <div v-if="loading" class="spinner-border spinner-border-sm" role="status">
|
||||
<span class="sr-only">{{ t('Loading...') }}</span>
|
||||
</div>
|
||||
</button>
|
||||
<p class="pt-3">
|
||||
{{ t('No account?') }} <NuxtLink class="signup-link" :to="localePath('/auth/signup')">
|
||||
{{ t("Sign up now") }}
|
||||
</NuxtLink>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</video-background>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapState, mapActions } from 'pinia'
|
||||
import backgroundImagePath from '~/assets/image/login4.png'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
// import { useBufferStore } from '~/stores/buffer'
|
||||
export default {
|
||||
name: 'Login',
|
||||
setup () {
|
||||
const { t } = useI18n()
|
||||
const localePath = useLocalePath()
|
||||
|
||||
return {
|
||||
t,
|
||||
localePath
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
loading: false,
|
||||
form: {
|
||||
email: '',
|
||||
password: ''
|
||||
},
|
||||
auth_error: false,
|
||||
auth_error_message: '',
|
||||
backgroundImagePath,
|
||||
errors: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(useUserStore, ['is_login', 'user', 'token']),
|
||||
translate (tag) {
|
||||
return this.t(tag)
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
if (this.$route.query.verified === '1') {
|
||||
this.$toast.success('Deine E-Mail wurde erfolgreich verifiziert. Bitte melde dich an.')
|
||||
}
|
||||
// useNuxtApp().$logger.log({ nuxtApp })
|
||||
|
||||
// useNuxtApp().$logger.log({ db })
|
||||
|
||||
// if (this.is_login){
|
||||
// this.$router.push('/onboarding');
|
||||
// }
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useUserStore, ['login', 'logout']),
|
||||
loginNow () {
|
||||
this.errors = []
|
||||
this.auth_error = false
|
||||
this.loading = true
|
||||
|
||||
this.$axios.post('/api/login', this.form).then(({ data }) => {
|
||||
this.loading = false
|
||||
|
||||
if (data.status === 'success') {
|
||||
this.$axios.head.Authorization = 'bearer ' + data.authorisation.token
|
||||
this.login(data.user, data.authorisation.token)
|
||||
this.$toast.success('Login successful!')
|
||||
//
|
||||
// If a user already did the onboarding, then the soundscape will be defined, so we can jump
|
||||
// directly to the selected soundscape and start play.
|
||||
//
|
||||
let soundscape = 'Lagoon'
|
||||
if (data.user.settings) { // if the user logged in first time no settings available, so push to onboarding
|
||||
soundscape = data.user.settings.soundscape
|
||||
if (soundscape !== '') {
|
||||
// const soundscape = user.user.settings.soundscape
|
||||
let url
|
||||
switch (soundscape) {
|
||||
case 'Lagoon':
|
||||
url = '/'
|
||||
break
|
||||
case 'Meadow':
|
||||
url = '/'
|
||||
break
|
||||
case 'Tropics':
|
||||
url = '/'
|
||||
break
|
||||
case 'Forest':
|
||||
url = '/'
|
||||
break
|
||||
default:
|
||||
url = '/'
|
||||
break
|
||||
}
|
||||
this.$router.push(this.localePath(url))
|
||||
}
|
||||
} else {
|
||||
this.$router.push(this.localePath('/onboarding'))
|
||||
}
|
||||
} else {
|
||||
this.$toast.error('Email or password is incorrect.')
|
||||
}
|
||||
}).catch((error) => {
|
||||
if (error.message === 'user is not defined') {
|
||||
this.loading = false
|
||||
} else {
|
||||
this.loading = false
|
||||
if ((error.code === 'ERR_NETWORK' || error.code === 'NS_ERROR_DOM_BAD_URI') && this.user !== null && this.token !== '') {
|
||||
this.$toast.warn(this.translate('Offline mode, please go online soon.'))
|
||||
this.login(this.user, this.token)
|
||||
}
|
||||
this.auth_error = true
|
||||
|
||||
if (error.response.status === 500) {
|
||||
this.auth_error_message = 'Es ist etwas schiefgelaufen. Versuche es später erneut'
|
||||
}
|
||||
if (error.response.status === 401) {
|
||||
this.auth_error_message = 'Email oder Passwort ist falsch.'
|
||||
}
|
||||
if (error.response.status === 403) {
|
||||
this.auth_error_message = 'Bitte bestätige zuerst deine E-Mail-Adresse.'
|
||||
}
|
||||
if (error.response.status === 422) {
|
||||
this.errors = error.response.data.errors
|
||||
this.auth_error = false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.videobg-content{
|
||||
overflow: auto !important;
|
||||
}
|
||||
</style>
|
||||
~/stores/audio
|
131
pages/auth/newpassword.vue
Normal file
131
pages/auth/newpassword.vue
Normal file
@@ -0,0 +1,131 @@
|
||||
<template>
|
||||
<div>
|
||||
<video-background
|
||||
src="/video/bg-video.mp4"
|
||||
poster="/images/poster.png"
|
||||
style="height: 100vh;"
|
||||
>
|
||||
<div class="container-fluid">
|
||||
<div class="row ">
|
||||
<div class="col-12 col-lg-4 bg-img d-none d-lg-block" style="background-image: url('/images/login.svg');background-size: cover;height: 100vh;" />
|
||||
<div class="col-12 col-lg-8 pt-4 px-3 px-sm-5 px-md-5 pb-5">
|
||||
<div class="row">
|
||||
<div class="col-12 py-3 text-center">
|
||||
<img src="/images/logo.svg" height="35" class="img " alt="Mindboost Logo">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-lg-0 mt-4" style="height: 90%">
|
||||
<div class="col-12 my-auto">
|
||||
<div class="text-center pt-4 pt-md-2 col-12 col-md-8 col-xl-8 mx-auto">
|
||||
<h1 class="fw-bolder h3" style="color: #585C5E;">
|
||||
{{ t('New Password') }}
|
||||
</h1>
|
||||
</div>
|
||||
<div class="row pt-2">
|
||||
<div class="col-12 col-md-8 col-xl-8 mx-auto">
|
||||
<form method="POST" @submit.prevent="validateAndReset">
|
||||
<div class="row pt-4">
|
||||
<div class="col-12">
|
||||
<label for="password" class="text-muted ">{{ t("Password") }} </label>
|
||||
<input id="password" v-model="form.password" type="password" class="form-control" placeholder="***">
|
||||
<div v-if="errors.password" class="invalid-feedback d-block">
|
||||
{{ t(errors.password[0]) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row pt-3">
|
||||
<div class="col-12">
|
||||
<label for="confirm-password" class="text-muted ">{{ t("Confirm Password") }} </label>
|
||||
<input id="confirm-password" v-model="form.password_confirmation" type="password" class="form-control" placeholder="***">
|
||||
<div v-if="errors.password_confirmation" class="invalid-feedback d-block">
|
||||
{{ t(errors.password_confirmation[0]) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row pt-4">
|
||||
<div class="col">
|
||||
<button type="submit" class="login-btn col-4" style="min-width:fit-content">
|
||||
{{ t("Reset Password") }} <div v-if="loading" class="spinner-border spinner-border-sm" role="status">
|
||||
<span class="sr-only">{{ t("Loading...") }}</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</video-background>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
setup () {
|
||||
const { t } = useI18n()
|
||||
const localePath = useLocalePath()
|
||||
const router = useRouter()
|
||||
|
||||
return {
|
||||
t,
|
||||
localePath,
|
||||
router
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
form: {
|
||||
email: '', // wird aus URL-Param geholt
|
||||
token: '', // wird aus URL-Param geholt
|
||||
password: '',
|
||||
password_confirmation: ''
|
||||
},
|
||||
errors: [],
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.form.token = this.$route.query.token || ''
|
||||
this.form.email = this.$route.query.email || ''
|
||||
},
|
||||
methods: {
|
||||
validateAndReset () {
|
||||
this.errors = []
|
||||
|
||||
if (this.form.password.length < 6) {
|
||||
this.errors.password = ['The password must be at least 6 characters.']
|
||||
return
|
||||
}
|
||||
|
||||
if (this.form.password !== this.form.password_confirmation) {
|
||||
this.errors.password_confirmation = ['The passwords do not match.']
|
||||
return
|
||||
}
|
||||
|
||||
this.resetPassword()
|
||||
},
|
||||
|
||||
resetPassword () {
|
||||
this.loading = true
|
||||
this.errors = []
|
||||
this.$axios.post('/api/password/reset', this.form).then(({ data }) => {
|
||||
this.loading = false
|
||||
if (data.success) {
|
||||
this.$toast.success('Password successfully reset!')
|
||||
this.$router.push(this.localePath('/auth/login'))
|
||||
}
|
||||
}).catch((error) => {
|
||||
this.loading = false
|
||||
this.$toast.error('Fehler beim Zurücksetzen des Passworts.')
|
||||
if (error.response && error.response.status === 422) {
|
||||
this.errors = error.response.data.errors
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
216
pages/auth/signup.vue
Normal file
216
pages/auth/signup.vue
Normal file
@@ -0,0 +1,216 @@
|
||||
<template>
|
||||
<div>
|
||||
<video-background
|
||||
src="/video/bg-video.mp4"
|
||||
poster="/images/poster.png"
|
||||
style=" height: 100vh;"
|
||||
>
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div
|
||||
class="col-12 col-lg-4 bg-img d-none d-lg-block"
|
||||
style="background-image: url('/images/login.svg');background-size: cover;height: 100vh;"
|
||||
/>
|
||||
<div class="col-12 col-lg-8 pt-4 px-3 px-sm-5 px-md-5 pb-5">
|
||||
<div class="row">
|
||||
<div class="col-12 py-3 text-center">
|
||||
<img src="/images/logo.svg" height="35" class="img " alt="Mindboost Logo">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-lg-0 mt-4" style="height: 90%">
|
||||
<div class="col-12 my-auto">
|
||||
<div class="text-center pt-4 pt-md-2 col-12 col-md-8 col-xl-8 mx-auto">
|
||||
<h1 class="fw-bolder h3" style="color: #585C5E;">
|
||||
{{ t('Sign up Header') }}
|
||||
</h1>
|
||||
<h2 class="text-muted h5 pt-2">
|
||||
{{ t('Sign up') }}
|
||||
</h2>
|
||||
</div>
|
||||
<div class="row pt-2">
|
||||
<div class="col-12 col-xl-10 mx-auto">
|
||||
<div v-if="registrationSuccess" class="alert alert-success text-center mx-0 mx-md-4">
|
||||
{{ t('registersuccess') }}
|
||||
</div>
|
||||
<div
|
||||
v-if="registrationError"
|
||||
class="alert alert-danger text-center mx-0 mx-md-4"
|
||||
>
|
||||
{{ registrationErrorMessage }}
|
||||
</div>
|
||||
<form method="POST" @submit.prevent="submitRegister">
|
||||
<!-- Hidden input for the token -->
|
||||
<input type="hidden" name="token" :value="token">
|
||||
<div class="row px-0 px-md-4 pt-3">
|
||||
<div class="col-12 pt-2 col-md-6 col-lg-6 ">
|
||||
<label class="form-label">{{ t("First Name") }}</label>
|
||||
<input v-model="form.first_name" autocomplete="given-name" type="text" :placeholder="t('Your first name')" class="form-control">
|
||||
<div v-if="errors.first_name" class="invalid-feedback d-block">
|
||||
{{ t(errors.first_name[0]) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 pt-2 col-md-6 col-lg-6 ">
|
||||
<label class="form-label">{{ t("Surname") }}</label>
|
||||
<input v-model="form.surname" autocomplete="family-name" type="text" :placeholder="t('Your surname')" class="form-control">
|
||||
<div v-if="errors.surname" class="invalid-feedback d-block">
|
||||
{{ t(errors.surname[0]) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row px-0 px-md-4 pt-3">
|
||||
<div class="col-12 col-md-6 pt-2 col-lg-6 ">
|
||||
<label class="form-label">{{ t("Email") }}</label>
|
||||
<input v-model="form.email" autocomplete="email" type="email" placeholder="alex@mail.com" class="form-control">
|
||||
<div v-if="errors.email" class="invalid-feedback d-block">
|
||||
{{ t(errors.email[0]) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 pt-2 col-md-6 col-lg-6 ">
|
||||
<label class="form-label">{{ t("Password") }}</label>
|
||||
<input v-model="form.password" type="password" autocomplete="new-password" :placeholder="t('Your password')" class="form-control">
|
||||
<div v-if="errors.password" class="invalid-feedback d-block">
|
||||
{{ t(errors.password[0]) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row px-md-4 pt-5 pb-2">
|
||||
<div class="col-12 col-md-10">
|
||||
<div class="form-check">
|
||||
<input id="flexCheckIndeterminate" v-model="form.privacy" class="form-check-input" type="checkbox" value="">
|
||||
<label class="form-check-label" for="flexCheckIndeterminate">
|
||||
{{ t("I have read") }}
|
||||
<NuxtLink class="signup-link" :to="localePath('/settings/termsandcondition')">
|
||||
{{ t("Terms & Conditions") }}
|
||||
</NuxtLink>
|
||||
{{ t("and the") }}
|
||||
<NuxtLink class="signup-link" :to="localePath('/settings/dataprotection')">
|
||||
{{ t("Privacy Policy") }}
|
||||
</NuxtLink>
|
||||
{{ t("and accept") }}
|
||||
</label>
|
||||
<div v-if="errors.privacy" class="invalid-feedback d-block">
|
||||
{{ t(errors.privacy[0]) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row px-0 px-md-4 pt-4">
|
||||
<div class="col">
|
||||
<button :disabled="loading" type="submit" style="min-width:fit-content" class="login-btn col-4">
|
||||
{{ t("Sign up btn") }}
|
||||
<div v-if="loading" class="spinner-border spinner-border-sm" role="status">
|
||||
<span class="sr-only">{{ t("Loading...") }}</span>
|
||||
</div>
|
||||
</button>
|
||||
<p class="pt-3">
|
||||
{{ t("Already have an Account?") }} <NuxtLink class="signup-link" :to="localePath('/auth/login')">
|
||||
{{ t("Log in now") }}
|
||||
</NuxtLink>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</video-background>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { useRoute } from 'vue-router'
|
||||
import { mapState, mapActions } from 'pinia'
|
||||
import { ref, watch } from 'vue'
|
||||
import backgroundImagePath from '~/assets/image/login4.png'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
|
||||
export default {
|
||||
name: 'LoGin',
|
||||
setup () {
|
||||
const { t } = useI18n()
|
||||
const localePath = useLocalePath()
|
||||
|
||||
const route = useRoute() // Get the current route
|
||||
const token = ref(route.query.token) // Extract the token from the query parameters
|
||||
|
||||
// Update form token whenever route.query.token changes
|
||||
watch(() => route.query.token, (newToken) => {
|
||||
token.value = newToken
|
||||
})
|
||||
|
||||
return {
|
||||
t,
|
||||
localePath,
|
||||
token
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
backgroundImagePath,
|
||||
loading: false,
|
||||
errors: [],
|
||||
registrationSuccess: false,
|
||||
registrationError: false,
|
||||
registrationErrorMessage: '',
|
||||
form: {
|
||||
first_name: '',
|
||||
surname: '',
|
||||
email: '',
|
||||
password: '',
|
||||
privacy: false,
|
||||
token: '',
|
||||
language: this.$i18n.locale
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(useUserStore, ['user'])
|
||||
},
|
||||
mounted () {
|
||||
if (this.$route.query.error === 'invalid_or_expired_token') {
|
||||
this.registrationError = true
|
||||
this.registrationErrorMessage = 'Invalid or expired token. Please try again.'
|
||||
}
|
||||
if (this.$route.query.error === 'verification_failed') {
|
||||
this.registrationError = true
|
||||
this.registrationErrorMessage = 'Something went wrong. Please try again later.'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useUserStore, ['login', 'logout']),
|
||||
|
||||
submitRegister () {
|
||||
this.loading = true
|
||||
this.errors = []
|
||||
// Update form with the token from setup
|
||||
this.form.token = this.token
|
||||
|
||||
this.$axios.post('/api/register', this.form)
|
||||
.then(({ data }) => {
|
||||
// useNuxtApp().$logger.log(data)
|
||||
|
||||
this.loading = false
|
||||
this.$toast.success('Signup successful!')
|
||||
this.registrationSuccess = true
|
||||
})
|
||||
.catch((error) => {
|
||||
this.loading = false
|
||||
if (error.response.status === 422) {
|
||||
this.errors = error.response.data.errors
|
||||
this.$toast.error(error.response.data.message)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
} </script>
|
||||
|
||||
<style scoped>
|
||||
@media only screen and (max-width: 587px) {
|
||||
p{
|
||||
font-size: 16px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
230
pages/auth/testerlogin.vue
Normal file
230
pages/auth/testerlogin.vue
Normal file
@@ -0,0 +1,230 @@
|
||||
<template>
|
||||
<div>
|
||||
<video-background
|
||||
src="/video/bg-video.mp4"
|
||||
style="height: 100vh;"
|
||||
poster="/images/poster.png"
|
||||
>
|
||||
<div class="container-fluid overflow-auto">
|
||||
<div class="row ">
|
||||
<div class="col-12 col-lg-4 bg-img d-none d-lg-block" style="background-image: url('/images/login.svg');background-size: cover;height: 100vh;" />
|
||||
<div class="col-12 col-lg-8 pt-4 px-3 px-sm-5 px-md-5 pb-5">
|
||||
<div class="row">
|
||||
<div class="col-12 py-3 text-center">
|
||||
<img src="/images/logo.svg" height="35" class="img " alt="Mindboost Logo">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" style="height: 90%">
|
||||
<div class="col-12 my-auto">
|
||||
<div class="text-center pt-1 ">
|
||||
<h2 class="fw-bolder display-6 text-center">
|
||||
{{ t('Welcome, Tester!') }}
|
||||
</h2>
|
||||
<span class="fs-5 pt-1 text-muted">{{ t('Thank you for using and improving Mindboost') }}</span>
|
||||
</div>
|
||||
<div class="row pt-2">
|
||||
<div class="col-12">
|
||||
<form method="POST" class=" pb-5 " @submit.prevent="submitRegister()">
|
||||
<div class="row pt-3 justify-content-center">
|
||||
<div class="col-12 col-sm-8 col-md-8 col-lg-6">
|
||||
<label class="form-label">{{ t('Name') }}</label>
|
||||
<input v-model="form.email" type="input" placeholder="e.g. your name, a tag, date or what ever you like.." class="form-control">
|
||||
<div v-if="errors.email" class="invalid-feedback d-block">
|
||||
{{ t('Please add first a name') }}
|
||||
</div>
|
||||
<div v-if="auth_error" class="invalid-feedback d-block">
|
||||
{{ t('Something is wrong, please try again!') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center pt-3">
|
||||
<div class="col-12 col-sm-8 col-md-8 col-lg-6">
|
||||
<label class="form-label">{{ t('Comments') }}</label>
|
||||
<input v-model="form.password" type="input" :placeholder="t('How did you discover Mindboost?')" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center pb-5">
|
||||
<div class="col-12 col-sm-8 col-md-8 col-lg-6 pt-4 text-center">
|
||||
<button type="submit" class=" login-btn col-12">
|
||||
{{ t('Go') }} <div v-if="loading" class="spinner-border spinner-border-sm" role="status">
|
||||
<span class="sr-only">{{ t('Loading...') }}</span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<h5 class="text-center pt-3">
|
||||
{{ t("Want to have an Account?") }} <NuxtLink class="signup-link" :to="localePath('/auth/signup')">
|
||||
{{ t("Sign Up") }}
|
||||
</NuxtLink>
|
||||
</h5>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</video-background>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapState, mapActions } from 'pinia'
|
||||
import backgroundImagePath from '~/assets/image/login4.png'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
// import { useBufferStore } from '~/stores/buffer'
|
||||
export default {
|
||||
name: 'TestLogin',
|
||||
setup () {
|
||||
const { t } = useI18n()
|
||||
const localePath = useLocalePath()
|
||||
|
||||
return {
|
||||
t,
|
||||
localePath
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
loading: false,
|
||||
form: {
|
||||
email: '',
|
||||
password: ''
|
||||
},
|
||||
auth_error: false,
|
||||
backgroundImagePath,
|
||||
errors: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(useUserStore, ['is_login', 'user', 'token']),
|
||||
translate (tag) {
|
||||
const { t } = useI18n()
|
||||
return t(tag)
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
|
||||
// useNuxtApp().$logger.log({ nuxtApp })
|
||||
|
||||
// useNuxtApp().$logger.log({ db })
|
||||
|
||||
// if (this.is_login){
|
||||
// this.$router.push('/onboarding');
|
||||
// }
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useUserStore, ['login', 'logout']),
|
||||
formatToValidEmail (input) {
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
let email = input.replace(/[^a-zA-Z0-9\._-]/g, '') // Remove invalid characters
|
||||
if (!/@/.test(email)) {
|
||||
email += '@betatester.mindboost.team' // Append a default domain if no '@'
|
||||
} else if (email.split('@')[1] === '') {
|
||||
email += '@betatester.mindboost.team' // Append "example.com" if there is no domain part
|
||||
}
|
||||
return email
|
||||
},
|
||||
submitRegister () {
|
||||
this.loading = true
|
||||
const email = this.formatToValidEmail(this.form.email)
|
||||
this.$axios.post('/api/register', {
|
||||
first_name: this.form.email,
|
||||
surname: this.form.password || 'no comments',
|
||||
email,
|
||||
password: email
|
||||
}).then(({ data }) => {
|
||||
this.loading = false
|
||||
this.$toast.success('Signup successfully....')
|
||||
this.login(data.user, data.authorisation.token)
|
||||
this.$router.push(this.localePath('/onboarding'))
|
||||
}).catch((error) => {
|
||||
this.loading = false
|
||||
if (error.response.status === 422) {
|
||||
this.errors = error.response.data.errors
|
||||
this.$toast.error(error.response.data.message)
|
||||
}
|
||||
})
|
||||
},
|
||||
loginNow () {
|
||||
this.errors = []
|
||||
this.auth_error = false
|
||||
this.loading = true
|
||||
|
||||
this.$axios.post('/api/login', this.form).then(({ data }) => {
|
||||
this.loading = false
|
||||
|
||||
if (data.status === 'success') {
|
||||
this.$axios.head.Authorization = 'bearer ' + data.authorisation.token
|
||||
this.login(data.user, data.authorisation.token)
|
||||
this.$toast.success('Login Successfully....')
|
||||
|
||||
//
|
||||
// If a user already did the onboarding, then the soundscape will be defined, so we can jump
|
||||
// directly to the selected soundscape and start play.
|
||||
//
|
||||
let soundscape = 'Lagoon'
|
||||
if (data.user.settings) { // if the user logged in first time no settings available, so push to onboarding
|
||||
soundscape = data.user.settings.soundscape
|
||||
if (soundscape !== '') {
|
||||
// const soundscape = user.user.settings.soundscape
|
||||
let url
|
||||
switch (soundscape) {
|
||||
case 'Lagoon':
|
||||
url = '/'
|
||||
break
|
||||
case 'Meadow':
|
||||
url = '/'
|
||||
break
|
||||
case 'Tropics':
|
||||
url = '/'
|
||||
break
|
||||
case 'Forest':
|
||||
url = '/'
|
||||
break
|
||||
default:
|
||||
url = '/'
|
||||
break
|
||||
}
|
||||
this.$router.push(this.localePath(url))
|
||||
}
|
||||
} else {
|
||||
this.$router.push(this.localePath('/onboarding'))
|
||||
}
|
||||
} else {
|
||||
this.$toast.error('Email or password is incorrect.')
|
||||
}
|
||||
}).catch((error) => {
|
||||
if (error.message === 'user is not defined') {
|
||||
this.loading = false
|
||||
} else {
|
||||
this.loading = false
|
||||
if ((error.code === 'ERR_NETWORK' || error.code === 'NS_ERROR_DOM_BAD_URI') && this.user !== null && this.token !== '') {
|
||||
this.$toast.warn(this.translate('Offline mode, please go online soon.'))
|
||||
this.login(this.user, this.token)
|
||||
}
|
||||
if (error.response.status === 500) {
|
||||
this.auth_error = true
|
||||
this.$toast.error('Es ist etwas schiefgelaufen. Versuche es später erneut')
|
||||
}
|
||||
if (error.response.status === 401) {
|
||||
this.auth_error = true
|
||||
this.$toast.error('Email or password is incorrect.')
|
||||
}
|
||||
if (error.response.status === 422) {
|
||||
this.errors = error.response.data.errors
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.videobg-content{
|
||||
overflow: auto !important;
|
||||
}
|
||||
</style>
|
||||
~/stores/audio
|
43
pages/auth/verify-email/[token].vue
Normal file
43
pages/auth/verify-email/[token].vue
Normal file
@@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<div class="text-center mt-10">
|
||||
<h1 class="text-2xl font-bold">E-Mail wird verifiziert...</h1>
|
||||
<p>Bitte einen Moment Geduld.</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const localePath = useLocalePath()
|
||||
const config = useRuntimeConfig()
|
||||
const apiBase = config.public.apiUrl
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const token = route.params.token
|
||||
await axios.get(`${apiBase}/api/verify-email/${token}`)
|
||||
|
||||
// Weiterleitung mit Query-Param (z.B. für Erfolgsmeldung im Login)
|
||||
router.push({
|
||||
path: localePath('/auth/login'),
|
||||
query: { verified: '1' }
|
||||
})
|
||||
} catch (error) {
|
||||
if (error.response && (error.response.status === 400 || error.response.status === 404)) {
|
||||
// Token ungültig oder abgelaufen – Weiterleitung zur Registrierung
|
||||
router.push({
|
||||
path: localePath('/auth/signup'),
|
||||
query: { error: 'invalid_or_expired_token' }
|
||||
})
|
||||
} else {
|
||||
router.push({
|
||||
path: localePath('/auth/login'),
|
||||
query: { error: 'verification_failed' }
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
26
pages/experiments/AudioReactiveMeter.vue
Normal file
26
pages/experiments/AudioReactiveMeter.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<div>
|
||||
<svg
|
||||
width="150px"
|
||||
height="150px"
|
||||
class="icon"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="#CCCCCC"
|
||||
>
|
||||
<g id="SVGRepo_bgCarrier" stroke-width="0" />
|
||||
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<g id="SVGRepo_iconCarrier">
|
||||
<path d="M512 669.824a155.584 155.584 0 0 0 155.136-155.136V223.808c0-85.312-69.824-155.136-155.136-155.136S356.864 138.496 356.864 223.808v290.88A155.584 155.584 0 0 0 512 669.824z" fill="" />
|
||||
<path d="M512 625.024a110.4 110.4 0 0 1-110.336-110.336V223.808c0-60.864 49.472-110.336 110.336-110.336s110.336 49.472 110.336 110.336v290.88c0 60.864-49.472 110.336-110.336 110.336z" fill="#e9c045" />
|
||||
<path d="M753.216 398.336a19.2 19.2 0 0 0-19.072-18.624 19.328 19.328 0 0 0-19.136 18.624h-0.32v117.632A203.008 203.008 0 0 1 512 718.976a203.136 203.136 0 0 1-202.752-203.008V398.336h-0.32a19.2 19.2 0 0 0-19.072-18.624 19.328 19.328 0 0 0-19.136 18.624h-0.256v117.632c0 126.656 98.176 229.696 222.144 239.808v108.672H376.256a19.392 19.392 0 1 0 0 38.848h271.488a19.392 19.392 0 0 0 0-38.848H531.392V755.84c123.968-10.112 222.144-113.216 222.144-239.808V398.336h-0.32z" fill="" />
|
||||
<path d="M753.216 398.336a19.2 19.2 0 0 0-19.072-18.624 19.328 19.328 0 0 0-19.136 18.624h-0.32v117.632A203.008 203.008 0 0 1 512 718.976a203.136 203.136 0 0 1-202.752-203.008V398.336h-0.32a19.2 19.2 0 0 0-19.072-18.624 19.328 19.328 0 0 0-19.136 18.624h-0.256v117.632c0 126.656 98.176 229.696 222.144 239.808v108.672H376.256a19.392 19.392 0 1 0 0 38.848h271.488a19.392 19.392 0 0 0 0-38.848H531.392V755.84c123.968-10.112 222.144-113.216 222.144-239.808V398.336h-0.32zM390.72 305.344a16 16 0 0 1 0-32h129.344a16 16 0 0 1 0 32H390.72zM390.72 379.712a16 16 0 0 1 0-32h129.344a16 16 0 0 1 0 32H390.72z" fill="" />
|
||||
<path d="M390.72 454.08a16 16 0 0 1 0-32h129.344a16 16 0 0 1 0 32H390.72z" fill="" />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// import Player from '~/components/Player.vue'
|
||||
</script>
|
44
pages/experiments/AudioTag.vue
Normal file
44
pages/experiments/AudioTag.vue
Normal file
@@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<div>
|
||||
<AudioFileSelector @file-selected="updateSrc" />
|
||||
<KeyboardPlayHandler />
|
||||
<AudioTag :src="audioSrc" :volume="volume" :play="playing" />
|
||||
<input
|
||||
id="gain-control"
|
||||
v-model="volume"
|
||||
type="range"
|
||||
min="0"
|
||||
max="1.0"
|
||||
step="0.01"
|
||||
@wheel="changeNoiseGain"
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import AudioTag from '~/components/experiments/AudioTag.vue'
|
||||
import tracksConfig from '~/tracks.config'
|
||||
import AudioFileSelector from '~/components/AudioFileSelector.vue'
|
||||
import { useAudioStore } from '~/stores/audio'
|
||||
import KeyboardPlayHandler from '~/archive/components/KeyboardPlayHandler.vue'
|
||||
|
||||
const audioSrc = ref('')
|
||||
const volume = ref(0)
|
||||
const playing = computed(() => useAudioStore().playing)
|
||||
const updateSrc = (e) => {
|
||||
useNuxtApp().$logger.log('file Selected update', { e })
|
||||
// Update the audioSrc with the selected file
|
||||
audioSrc.value = e
|
||||
}
|
||||
const changeNoiseGain = (event) => {
|
||||
event.preventDefault()
|
||||
const volumepercents = volume.value * 100
|
||||
// Determine the direction of the scroll
|
||||
const delta = Math.sign(event.deltaY)
|
||||
if (volumepercents - delta < 101 && volumepercents - delta > -1) {
|
||||
volume.value -= delta / 100
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
40
pages/experiments/AudioTagWebAudio.vue
Normal file
40
pages/experiments/AudioTagWebAudio.vue
Normal file
@@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<div>
|
||||
<AudioFileSelector @file-selected="updateSrc" />
|
||||
<AudioTagWebAudio :src="audioSrc" :volume="volume" />
|
||||
<input
|
||||
id="gain-control"
|
||||
v-model="volume"
|
||||
type="range"
|
||||
min="0"
|
||||
max="1.0"
|
||||
step="0.01"
|
||||
@wheel="changeNoiseGain"
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import AudioTagWebAudio from '~/components/experiments/AudioTagWebAudio.vue'
|
||||
import tracksConfig from '~/tracks.config'
|
||||
import AudioFileSelector from '~/components/AudioFileSelector.vue'
|
||||
|
||||
const audioSrc = ref('')
|
||||
const volume = ref(0)
|
||||
const updateSrc = (e) => {
|
||||
useNuxtApp().$logger.log('file Selected update', { e })
|
||||
// Update the audioSrc with the selected file
|
||||
audioSrc.value = e
|
||||
}
|
||||
const changeNoiseGain = (event) => {
|
||||
event.preventDefault()
|
||||
const volumepercents = volume.value * 100
|
||||
// Determine the direction of the scroll
|
||||
const delta = Math.sign(event.deltaY)
|
||||
if (volumepercents - delta < 101 && volumepercents - delta > -1) {
|
||||
volume.value -= delta / 100
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
39
pages/experiments/AudioTagWebAudioStreaming.vue
Normal file
39
pages/experiments/AudioTagWebAudioStreaming.vue
Normal file
@@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<div>
|
||||
<AudioFileSelector @file-selected="updateSrc" />
|
||||
<AudioTagWebAudioStreaming :src="audioSrc" :volume="volume" />
|
||||
<input
|
||||
id="gain-control"
|
||||
v-model="volume"
|
||||
type="range"
|
||||
min="0"
|
||||
max="1.0"
|
||||
step="0.01"
|
||||
@wheel="changeNoiseGain"
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import AudioFileSelector from '~/components/AudioFileSelector.vue'
|
||||
import AudioTagWebAudioStreaming from '~/components/experiments/AudioTagWebAudioStreaming.vue'
|
||||
|
||||
const audioSrc = ref('')
|
||||
const volume = ref(0)
|
||||
const updateSrc = (e) => {
|
||||
useNuxtApp().$logger.log('file Selected update', { e })
|
||||
// Update the audioSrc with the selected file
|
||||
audioSrc.value = e
|
||||
}
|
||||
const changeNoiseGain = (event) => {
|
||||
event.preventDefault()
|
||||
const volumepercents = volume.value * 100
|
||||
// Determine the direction of the scroll
|
||||
const delta = Math.sign(event.deltaY)
|
||||
if (volumepercents - delta < 101 && volumepercents - delta > -1) {
|
||||
volume.value -= delta / 100
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
44
pages/experiments/AudioTags.vue
Normal file
44
pages/experiments/AudioTags.vue
Normal file
@@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<div>
|
||||
<AudioFileSelector @file-selected="updateSrc" />
|
||||
|
||||
<AudioTag :src="audioSrc" :volume="volume" />
|
||||
<AudioTagWebAudio :src="audioSrc" :volume="volume" />
|
||||
<AudioTagWebAudioStreaming :src="audioSrc" :volume="volume" />
|
||||
<input
|
||||
id="gain-control"
|
||||
v-model="volume"
|
||||
type="range"
|
||||
min="0"
|
||||
max="1.0"
|
||||
step="0.01"
|
||||
@wheel="changeNoiseGain"
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import AudioTag from '~/components/experiments/AudioTag.vue'
|
||||
import AudioFileSelector from '~/components/AudioFileSelector.vue'
|
||||
import AudioTagWebAudio from '~/components/experiments/AudioTagWebAudio.vue'
|
||||
import AudioTagWebAudioStreaming from '~/components/experiments/AudioTagWebAudioStreaming.vue'
|
||||
|
||||
const audioSrc = ref('')
|
||||
const volume = ref(0)
|
||||
const updateSrc = (e) => {
|
||||
useNuxtApp().$logger.log('file Selected update', { e })
|
||||
// Update the audioSrc with the selected file
|
||||
audioSrc.value = e
|
||||
}
|
||||
const changeNoiseGain = (event) => {
|
||||
event.preventDefault()
|
||||
const volumepercents = volume.value * 100
|
||||
// Determine the direction of the scroll
|
||||
const delta = Math.sign(event.deltaY)
|
||||
if (volumepercents - delta < 101 && volumepercents - delta > -1) {
|
||||
volume.value -= delta / 100
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
9
pages/experiments/MicNoisePatchMusic.vue
Normal file
9
pages/experiments/MicNoisePatchMusic.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<div>
|
||||
<NoisePatchMusic />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import NoisePatchMusic from '~/archive/components/tests/NoisePatchMusic_glitchOnLoad.vue'
|
||||
</script>
|
10
pages/experiments/MicNoisePatchMusic2.vue
Normal file
10
pages/experiments/MicNoisePatchMusic2.vue
Normal file
@@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<div>
|
||||
<MicNoisePatchMusic2 />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// import Player from '~/components/Player.vue'
|
||||
import MicNoisePatchMusic2 from '~/archive/components/tests/MicNoisePatchMusic2.vue'
|
||||
</script>
|
69
pages/experiments/MicrophoneStore.vue
Normal file
69
pages/experiments/MicrophoneStore.vue
Normal file
@@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<div v-show="true">
|
||||
<h1>Microphone</h1>
|
||||
<button class="btn btn-primary" @click="attach">
|
||||
{{ 'Mikrofon aktivieren' }}
|
||||
</button>
|
||||
<button class="btn btn-secondary" @click="detach">
|
||||
{{ 'Mikrofon trennen' }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { ref, onMounted, onUnmounted, watch } from 'vue'
|
||||
import type { Logger } from 'pino'
|
||||
import { useMicStore } from '~/stores/microphone'
|
||||
import { useAudioStore } from '~/stores/audio'
|
||||
|
||||
export default {
|
||||
name: 'MicrophoneStoreHandler',
|
||||
emits: ['update:attach'],
|
||||
setup (_props, { emit }) {
|
||||
const audioStore = useAudioStore()
|
||||
const microphone = ref<Promise<MediaStream> | null>(null)
|
||||
const microphoneActive = ref(false)
|
||||
const logger = useNuxtApp().$logger as Logger
|
||||
|
||||
const attach = () => {
|
||||
logger.info('attach microphone')
|
||||
if (!microphone.value) {
|
||||
microphone.value = navigator.mediaDevices.getUserMedia({
|
||||
audio: {
|
||||
echoCancellation: false,
|
||||
noiseSuppression: false,
|
||||
autoGainControl: false
|
||||
},
|
||||
video: false
|
||||
})
|
||||
microphoneActive.value = true
|
||||
}
|
||||
|
||||
return microphone.value.then((stream) => {
|
||||
emit('update:attach', stream)
|
||||
return stream
|
||||
})
|
||||
}
|
||||
|
||||
const detach = async () => {
|
||||
logger.info('detach microphone')
|
||||
if (microphone.value) {
|
||||
try {
|
||||
const stream = await microphone.value
|
||||
stream.getTracks().forEach(track => track.stop())
|
||||
} catch (error) {
|
||||
}
|
||||
microphone.value = null
|
||||
microphoneActive.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// Return the public properties and methods
|
||||
return {
|
||||
attach,
|
||||
detach,
|
||||
isPlaying: () => audioStore.playing,
|
||||
microphoneActive
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
10
pages/experiments/MicrophoneTest.vue
Normal file
10
pages/experiments/MicrophoneTest.vue
Normal file
@@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<div>
|
||||
<StateBar />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import Microphone from '~/components/experiments/tests/Microphone.vue'
|
||||
import StateBar from '~/components/experiments/statemanagement/StateBar.vue'
|
||||
</script>
|
9
pages/experiments/Noise3BandGain.vue
Normal file
9
pages/experiments/Noise3BandGain.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<div>
|
||||
<NoiseGain />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import NoiseGain from '~/components/experiments/homepages/NoiseGain.vue'
|
||||
</script>
|
10
pages/experiments/NoiseControlledBand.vue
Normal file
10
pages/experiments/NoiseControlledBand.vue
Normal file
@@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<div>
|
||||
<NoiseControlledBand center-frequency="1000" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// import Player from '~/components/Player.vue'
|
||||
import NoiseControlledBand from '~/components/experiments/tests/ControlValues/NoiseControlledWebAudioBand.vue'
|
||||
</script>
|
12
pages/experiments/NoiseMusicGain.vue
Normal file
12
pages/experiments/NoiseMusicGain.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<div>
|
||||
<KeyboardPlayHandler />
|
||||
<NoiseMusicGain />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// import Player from '~/components/Player.vue'
|
||||
import NoiseMusicGain from '~/components/experiments/tests/NoiseMusicGain.vue'
|
||||
import KeyboardPlayHandler from '~/archive/components/KeyboardPlayHandler.vue'
|
||||
</script>
|
9
pages/experiments/NoiseMusicGainFadeIn.vue
Normal file
9
pages/experiments/NoiseMusicGainFadeIn.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<div>
|
||||
<NoiseMusicGainFadeIn />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import NoiseMusicGainFadeIn from '~/components/experiments/tests/NoiseMusicGainFadeIn.vue'
|
||||
</script>
|
9
pages/experiments/NoiseMusicGainMic.vue
Normal file
9
pages/experiments/NoiseMusicGainMic.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<div>
|
||||
<NoiseMusicGainMic />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import NoiseMusicGainMic from '~/components/experiments/tests/NoiseMusicGainMic.vue'
|
||||
</script>
|
10
pages/experiments/NoiseMusicGainPlayPause.vue
Normal file
10
pages/experiments/NoiseMusicGainPlayPause.vue
Normal file
@@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<div>
|
||||
<NoiseMusicGainPlayPause />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// import Player from '~/components/Player.vue'
|
||||
import NoiseMusicGainPlayPause from '~/components/experiments/tests/NoiseMusicGainPlayPause.vue'
|
||||
</script>
|
11
pages/experiments/NoiseMusicGainPlayPauseDevice.vue
Normal file
11
pages/experiments/NoiseMusicGainPlayPauseDevice.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<div>
|
||||
<KeyboardPlayHandler />
|
||||
<NoiseMusicGainPlayPauseDevice />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import NoiseMusicGainPlayPauseDevice from '~/archive/components/tests/NoiseMusicGainPlayPauseDevice.vue'
|
||||
import KeyboardPlayHandler from '~/archive/components/KeyboardPlayHandler.vue'
|
||||
</script>
|
11
pages/experiments/NoiseMusicWebAudio.vue
Normal file
11
pages/experiments/NoiseMusicWebAudio.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<div>
|
||||
Version 1 Noise Music WebAudio:
|
||||
<NoiseMusicWebAudio />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// import Player from '~/components/Player.vue'
|
||||
import NoiseMusicWebAudio from '~/components/experiments/tests/NoiseMusicWebAudio.vue'
|
||||
</script>
|
77
pages/experiments/Player/HowlWebAudioBridge.vue
Normal file
77
pages/experiments/Player/HowlWebAudioBridge.vue
Normal file
@@ -0,0 +1,77 @@
|
||||
<!-- eslint-disable vue/no-multiple-template-root -->
|
||||
<template>
|
||||
<h4> Die Audio HowlWebAudioBridge sorgt zwar für ein sauberes Streaming, aber lässt sich dann nicht über WebAudio regeln</h4>
|
||||
<div>
|
||||
<HowlWebAudioBridge
|
||||
:src="source"
|
||||
:audio-context="audioContext"
|
||||
:on-ready="handleAudioNode"
|
||||
/>
|
||||
<button @click="testAudio">Test Audio</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import HowlWebAudioBridge from '~/components/Player/HowlWebAudioBridge.vue'
|
||||
import tracksConfig from '~/tracks.config'
|
||||
import { useAudioStore, ensureAudio } from '~/stores/audio'
|
||||
|
||||
const audioContext = useAudioStore().getContext()
|
||||
const source = tracksConfig.debug_src
|
||||
const audioNode = ref({} as AudioBufferSourceNode | MediaElementAudioSourceNode | MediaStreamAudioSourceNode)
|
||||
const gainNode = ref({} as GainNode)
|
||||
const audioReady = ref(false as boolean)
|
||||
const howlElement = ref({} as Howl)
|
||||
|
||||
async function testAudio () {
|
||||
await ensureAudio()
|
||||
if (audioReady && gainNode && howlElement) {
|
||||
howlElement.value.play()
|
||||
const audioDestination = audioContext.destination
|
||||
const gain = gainNode.value as GainNode
|
||||
if (audioNode instanceof AudioBufferSourceNode) {
|
||||
audioNode.loop = true
|
||||
audioNode.playbackRate.value = 1
|
||||
audioNode.connect(gain).connect(audioDestination)
|
||||
}
|
||||
if (audioNode instanceof MediaElementAudioSourceNode) {
|
||||
audioNode.connect(gain).connect(audioDestination)
|
||||
}
|
||||
if (audioNode instanceof MediaStreamAudioSourceNode) {
|
||||
audioNode.connect(gain).connect(audioDestination)
|
||||
}
|
||||
gain.gain.cancelScheduledValues(audioContext.currentTime)
|
||||
gain.gain.linearRampToValueAtTime(1, audioContext.currentTime + 2)
|
||||
|
||||
useNuxtApp().$logger.log('Gain connected to destination')
|
||||
} else {
|
||||
useNuxtApp().$logger.warn('Audio is not yet ready to test it')
|
||||
}
|
||||
}
|
||||
|
||||
function handleAudioNode (node: MediaElementAudioSourceNode | AudioBufferSourceNode | MediaStreamAudioSourceNode, howl: Howl | null) {
|
||||
useNuxtApp().$logger.log('AUDIONODE IST ANGEKOMMEN ')
|
||||
useNuxtApp().$logger.log({ node })
|
||||
useNuxtApp().$logger.log({ howl })
|
||||
audioNode.value = node
|
||||
if (howl) { howlElement.value = howl }
|
||||
|
||||
gainNode.value = audioContext.createGain() as GainNode
|
||||
gainNode.value.gain.setValueAtTime(0, audioContext.currentTime)
|
||||
|
||||
// Not connected to any destination, gain currently muted
|
||||
if (node instanceof AudioBufferSourceNode) {
|
||||
node.loop = true
|
||||
node.playbackRate.value = 1
|
||||
node.connect(gainNode.value)
|
||||
}
|
||||
if (node instanceof MediaElementAudioSourceNode) {
|
||||
node.connect(gainNode.value)
|
||||
}
|
||||
if (node instanceof MediaStreamAudioSourceNode) {
|
||||
node.connect(gainNode.value)
|
||||
}
|
||||
audioReady.value = true
|
||||
}
|
||||
|
||||
</script>
|
12
pages/experiments/Player/RNBODevice.vue
Normal file
12
pages/experiments/Player/RNBODevice.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<div>
|
||||
<RNBODevice />
|
||||
<GainController />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import GainController from '~/components/experiments/GainController.vue'
|
||||
import RNBODevice from '~/components/experiments/homepages/RNBODevice.vue'
|
||||
import NavigationBar from '~/components/NavigationBar.vue'
|
||||
</script>
|
49
pages/experiments/Player/TestWebAudioControlOfHowl.vue
Normal file
49
pages/experiments/Player/TestWebAudioControlOfHowl.vue
Normal file
@@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<div class="p-4">
|
||||
<h1 class="text-xl font-bold mb-4">Audio Fade-In Beispiel</h1>
|
||||
<audio
|
||||
ref="audioEl"
|
||||
:src="source"
|
||||
crossorigin="anonymous"
|
||||
/>
|
||||
<button
|
||||
class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition"
|
||||
@click="playWithFadeIn"
|
||||
>
|
||||
Abspielen mit Fade-In
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import tracksConfig from '~/tracks.config'
|
||||
|
||||
const audioEl = ref(null)
|
||||
const source = ref(tracksConfig.lagoon_48_mp3_src)
|
||||
|
||||
const playWithFadeIn = async () => {
|
||||
const audio = audioEl.value
|
||||
const sink = useUserStore().audioOutputDevice
|
||||
audio.value?.setSinkId(sink.deviceId)
|
||||
|
||||
if (!audio) { return }
|
||||
|
||||
const audioContext = new (window.AudioContext || window.webkitAudioContext)()
|
||||
const sourceNode = audioContext.createMediaElementSource(audio)
|
||||
const gainNode = audioContext.createGain()
|
||||
|
||||
// Anfangslautstärke auf 0 setzen
|
||||
gainNode.gain.setValueAtTime(0, audioContext.currentTime)
|
||||
|
||||
// Ziel-Lautstärke in 3 Sekunden erreichen
|
||||
gainNode.gain.linearRampToValueAtTime(1.0, audioContext.currentTime + 3)
|
||||
|
||||
// Verkabeln
|
||||
sourceNode.connect(gainNode)
|
||||
gainNode.connect(audioContext.destination)
|
||||
|
||||
// Abspielen
|
||||
await audio.play()
|
||||
}
|
||||
</script>
|
@@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<div class="p-4">
|
||||
<h1 class="text-xl font-bold mb-4">Howler → MediaStream + volume Fade-In</h1>
|
||||
<h3>Startet, Kontrolle der Lautstärke über volume-API, aber erst kommt 100% Lautstärke Audio, bevor es verstummt und dann einfaded</h3>
|
||||
<button
|
||||
class="px-4 py-2 bg-indigo-600 text-white rounded hover:bg-indigo-700 transition"
|
||||
@click="startCaptureWithVolumeRamp"
|
||||
>
|
||||
Starten & Lautstärke einblenden
|
||||
</button>
|
||||
<div v-if="mediaStream" class="mt-4 text-green-700 font-mono">
|
||||
✅ MediaStream erzeugt!
|
||||
</div>
|
||||
<p v-if="error" class="text-red-600 mt-2">{{ error }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { Howl } from 'howler'
|
||||
import tracksConfig from '~/tracks.config'
|
||||
|
||||
const mediaStream = ref(null)
|
||||
const error = ref(null)
|
||||
|
||||
const startCaptureWithVolumeRamp = async () => {
|
||||
try {
|
||||
const sound = new Howl({
|
||||
src: [tracksConfig.lagoon_48_mp3_src],
|
||||
html5: true,
|
||||
preload: true,
|
||||
muted: true
|
||||
})
|
||||
|
||||
await new Promise(resolve => sound.once('load', resolve))
|
||||
|
||||
const audioElement = sound._sounds[0]._node
|
||||
|
||||
// Volume auf 0 (mute) setzen
|
||||
audioElement.volume = 0
|
||||
|
||||
// captureStream vor play aufrufen
|
||||
const stream = audioElement.captureStream()
|
||||
|
||||
// Starte Audio
|
||||
sound.play()
|
||||
|
||||
// Starte Volume-Ramp (linear über ~3 Sekunden)
|
||||
let volume = 0
|
||||
const step = 0.05 // Schrittgröße
|
||||
const interval = 150 // ms zwischen Schritten
|
||||
|
||||
const ramp = setInterval(() => {
|
||||
volume += step
|
||||
audioElement.volume = Math.min(1, volume)
|
||||
|
||||
if (volume >= 1) {
|
||||
clearInterval(ramp)
|
||||
}
|
||||
}, interval)
|
||||
|
||||
mediaStream.value = stream
|
||||
} catch (err) {
|
||||
useNuxtApp().$logger.error(err)
|
||||
error.value = 'Fehler beim Start: ' + err.message
|
||||
}
|
||||
}
|
||||
</script>
|
9
pages/experiments/Player/index.vue
Normal file
9
pages/experiments/Player/index.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<div>
|
||||
<Player />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import Player from '~/components/experiments/tests/showcases/PlayerComponent.vue'
|
||||
</script>
|
168
pages/experiments/Player/newRNBOPlayer.vue
Normal file
168
pages/experiments/Player/newRNBOPlayer.vue
Normal file
@@ -0,0 +1,168 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Test Fall 4</h1>
|
||||
<h3>Howler als Stream mit der HowlWebAudioBridge (createMediaElementSource)</h3>
|
||||
<KeyboardPlayHandler />
|
||||
<button @click="play">Play</button>
|
||||
<div v-if="false" id="statistics">
|
||||
{{ Object.keys(gains).length }}
|
||||
{{ Object.keys(nodes).length }}
|
||||
</div>
|
||||
<HowlWebAudioBridge
|
||||
:src="source[0]"
|
||||
:audio-context="audioContext"
|
||||
:on-ready="(node, howl) => registerAudioNode(node, howl, 63)"
|
||||
/>
|
||||
<HowlWebAudioBridge
|
||||
:src="source[1]"
|
||||
:audio-context="audioContext"
|
||||
:on-ready="(node, howl) => registerAudioNode(node, howl, 125)"
|
||||
/>
|
||||
<HowlWebAudioBridge
|
||||
:src="source[2]"
|
||||
:audio-context="audioContext"
|
||||
:on-ready="(node, howl) => registerAudioNode(node, howl, 250)"
|
||||
/>
|
||||
<HowlWebAudioBridge
|
||||
:src="source[3]"
|
||||
:audio-context="audioContext"
|
||||
:on-ready="(node, howl) => registerAudioNode(node, howl, 500)"
|
||||
/>
|
||||
<HowlWebAudioBridge
|
||||
:src="source[4]"
|
||||
:audio-context="audioContext"
|
||||
:on-ready="(node, howl) => registerAudioNode(node, howl, 1000)"
|
||||
/>
|
||||
<HowlWebAudioBridge
|
||||
:src="source[5]"
|
||||
:audio-context="audioContext"
|
||||
:on-ready="(node, howl) => registerAudioNode(node, howl, 2000)"
|
||||
/>
|
||||
<HowlWebAudioBridge
|
||||
:src="source[6]"
|
||||
:audio-context="audioContext"
|
||||
:on-ready="(node, howl) => registerAudioNode(node, howl, 4000)"
|
||||
/>
|
||||
<HowlWebAudioBridge
|
||||
:src="source[7]"
|
||||
:audio-context="audioContext"
|
||||
:on-ready="(node, howl) => registerAudioNode(node, howl, 8000)"
|
||||
/>
|
||||
<HowlWebAudioBridge
|
||||
:src="source[8]"
|
||||
:audio-context="audioContext"
|
||||
:on-ready="(node, howl) => registerAudioNode(node, howl, 16000)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Logger } from 'pino'
|
||||
import { useAudioStore } from '~/stores/audio'
|
||||
import tracksConfig from '~/tracks.config'
|
||||
import HowlWebAudioBridge from '~/components/Player/HowlWebAudioBridge.vue'
|
||||
import KeyboardPlayHandler from '~/archive/components/KeyboardPlayHandler.vue'
|
||||
|
||||
const source = ref([tracksConfig['63_src'], tracksConfig['125_src'], tracksConfig['250_src'], tracksConfig['500_src'], tracksConfig['1000_src'], tracksConfig['2000_src'], tracksConfig['4000_src'], tracksConfig['8000_src'], tracksConfig['16000_src']])
|
||||
const audioContext = ref(useAudioStore().getContext()as AudioContext)
|
||||
const nodes = ref([] as Record<number, AudioNode>)
|
||||
const gains = ref([] as Record<number, GainNode>)
|
||||
const howls = ref([] as Array<Howl>)
|
||||
const masterGain = ref(null as (GainNode | null))
|
||||
const lastAction = audioContext.value.currentTime
|
||||
const logger = useNuxtApp().$logger as Logger
|
||||
|
||||
const registerAudioNode = (node:AudioNode | null, howl: Howl| null, freq: number) => {
|
||||
if (howl) {
|
||||
logger.info('register new Node push it', howl.state)
|
||||
howls.value.push(howl)
|
||||
const mediaElement = (howl as any)._sounds[0]._node as HTMLAudioElement
|
||||
mediaElement.muted = false
|
||||
logger.info('mediaElement muted')
|
||||
}
|
||||
if (node) {
|
||||
const gainNode = node.context.createGain()
|
||||
const pannerNode = audioContext.value.createStereoPanner()
|
||||
pannerNode.pan.value = 1
|
||||
|
||||
gainNode.gain.setValueAtTime(0, audioContext.value.currentTime)
|
||||
gainNode.gain.linearRampToValueAtTime(0.5, audioContext.value.currentTime + 2)
|
||||
const master = masterGain.value || useAudioStore().getMasterGainNoise()
|
||||
nodes.value[freq] = node
|
||||
gains.value[freq] = gainNode
|
||||
node.connect(gainNode)
|
||||
gainNode.connect(master)
|
||||
pannerNode.connect(master)
|
||||
|
||||
logger.info('mediaElement nicht mehr gemutet')
|
||||
logger.info('AudioNode ready, but not connected to destination', { node })
|
||||
logger.info('Number of GainNodes', Object.keys(gains.value).length)
|
||||
logger.info('Number of AudioNodes', Object.keys(nodes.value).length)
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => useAudioStore().playing, // Überwache explizit den state
|
||||
(newState) => {
|
||||
if (newState) {
|
||||
play()
|
||||
} else {
|
||||
pause()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
watch(() => lastAction, // Überwache explizit den state
|
||||
(lastTimestamp) => {
|
||||
if (lastTimestamp > 50) {
|
||||
play() // Overlay anzeigen
|
||||
} else {
|
||||
pause()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const pause = () => {
|
||||
logger.info('pause')
|
||||
const master = masterGain.value
|
||||
const currentTime = audioContext.value.currentTime
|
||||
// master?.gain.cancelScheduledValues(currentTime)
|
||||
master?.gain.linearRampToValueAtTime(0, currentTime + 5)
|
||||
const lastAction = currentTime
|
||||
howls.value.forEach((howl) => {
|
||||
howl.pause()
|
||||
}
|
||||
)
|
||||
master?.disconnect()
|
||||
// if (gainNode) {
|
||||
// gainNode.connect(audioContext.value.destination)
|
||||
// gainNode.gain.cancelScheduledValues(audioContext.value.currentTime)
|
||||
// gainNode.gain.setValueAtTime(0, audioContext.value.currentTime)
|
||||
// gainNode.gain.linearRampToValueAtTime(2, audioContext.value.currentTime + 4)
|
||||
// } else {
|
||||
// logger.info('GAINNODE NOT CREATED WE WILL NOT START')
|
||||
// }
|
||||
}
|
||||
|
||||
const play = () => {
|
||||
const newHowls = howls.value
|
||||
const master = masterGain.value || useAudioStore().getMasterGainNoise()
|
||||
logger.info('Start this', { newHowls })
|
||||
howls.value.forEach(async (howl) => {
|
||||
howl.play()
|
||||
logger.info('Start this', { newHowls })
|
||||
masterGain?.value?.gain.linearRampToValueAtTime(1, audioContext.value.currentTime + 3)
|
||||
const mediaElement = await (howl as any)._sounds[0]._node as HTMLAudioElement
|
||||
mediaElement.muted = false
|
||||
}
|
||||
)
|
||||
master.connect(audioContext.value.destination)
|
||||
// if (gainNode) {
|
||||
// gainNode.connect(audioContext.value.destination)
|
||||
// gainNode.gain.cancelScheduledValues(audioContext.value.currentTime)
|
||||
// gainNode.gain.setValueAtTime(0, audioContext.value.currentTime)
|
||||
// gainNode.gain.linearRampToValueAtTime(2, audioContext.value.currentTime + 4)
|
||||
// } else {
|
||||
// logger.info('GAINNODE NOT CREATED WE WILL NOT START')
|
||||
// }
|
||||
}
|
||||
</script>
|
105
pages/experiments/Player/test1.vue
Normal file
105
pages/experiments/Player/test1.vue
Normal file
@@ -0,0 +1,105 @@
|
||||
<!--
|
||||
/**
|
||||
* @component ControlValueBasedPlayer
|
||||
* @description A component that renders multiple NoiseControlledBand components,
|
||||
* each centered around a specific frequency. This can be used for
|
||||
* audio spectrum visualization or control. Now includes a loading spinner
|
||||
* that disappears when all NoiseControlledBand components are ready.
|
||||
*
|
||||
* @uses NoiseControlledBand
|
||||
*
|
||||
* @example
|
||||
* <ControlValueBasedPlayer />
|
||||
*
|
||||
* @remarks
|
||||
* - Utilizes Vue 3 Composition API
|
||||
* - Renders NoiseControlledBand components for standard audio frequency bands
|
||||
* - Frequencies: 63, 125, 250, 500, 1000, 2000, 4000, 8000, 16000 Hz
|
||||
* - Includes a loading spinner that disappears when all components are ready
|
||||
*/
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h1> Test Fall 1 mit Control Value Patch</h1>
|
||||
|
||||
<h3> Ein AudioTag, dass über die Volume-APi gesteuert wird</h3>
|
||||
<div v-if="isExperimentsRoute">
|
||||
<KeyboardPlayHandler />
|
||||
<PlayButton />
|
||||
</div>
|
||||
<div v-if="loading" class="spinner-border spinner-border-sm" role="status">
|
||||
<span class="sr-only">{{ t("Loading...") }}</span>
|
||||
</div>
|
||||
Loaded Bands: {{ loadedBands }}
|
||||
<div>
|
||||
<label>Attack: {{ (masterAttack / 480000).toFixed(2) }}s</label>
|
||||
<input
|
||||
v-model.number="masterAttack"
|
||||
type="range"
|
||||
:min="4800"
|
||||
:max="1920000"
|
||||
>
|
||||
<label>Release: {{ (masterRelease / 480000).toFixed(2) }}s</label>
|
||||
<input
|
||||
v-model.number="masterRelease"
|
||||
type="range"
|
||||
:min="4800"
|
||||
:max="1920000"
|
||||
>
|
||||
</div>
|
||||
<NoiseControlledBand
|
||||
v-for="frequency in frequencies"
|
||||
:key="frequency"
|
||||
:master-attack="masterAttack"
|
||||
:master-release="masterRelease"
|
||||
:center-frequency="frequency"
|
||||
@ready="onBandReady"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRoute } from 'vue-router'
|
||||
import KeyboardPlayHandler from '~/archive/components/KeyboardPlayHandler.vue'
|
||||
import PlayButton from '~/components/experiments/statemanagement/PlayButton.vue'
|
||||
import NoiseControlledBand from '~/components/experiments/tests/ControlValues/NoiseControlledBand.vue'
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
NoiseControlledBand,
|
||||
KeyboardPlayHandler,
|
||||
PlayButton
|
||||
},
|
||||
setup () {
|
||||
const { t } = useI18n()
|
||||
const frequencies = ref([63, 125, 250, 500, 1000, 2000, 4000, 8000, 16000])
|
||||
const loadedBands = ref(0)
|
||||
|
||||
const route = useRoute()
|
||||
const isExperimentsRoute = computed(() => route.path.match(/\/[a-z]{2}\/experiments/))
|
||||
|
||||
const masterAttack = ref(120000) // Beispielwert in Samples
|
||||
const masterRelease = ref(144000)
|
||||
|
||||
const loading = computed(() => loadedBands.value < frequencies.value.length)
|
||||
|
||||
const onBandReady = () => {
|
||||
loadedBands.value++
|
||||
}
|
||||
|
||||
return {
|
||||
frequencies,
|
||||
loading,
|
||||
onBandReady,
|
||||
t,
|
||||
loadedBands,
|
||||
masterAttack,
|
||||
masterRelease,
|
||||
isExperimentsRoute
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
74
pages/experiments/Player/test2.vue
Normal file
74
pages/experiments/Player/test2.vue
Normal file
@@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<div>
|
||||
<KeyboardPlayHandler />
|
||||
<h1> Test Fall 2 mit Control Value Patch</h1>
|
||||
<h2>Use AudioBufferSourceNode over controlled NoiseControlledWebAudioBand</h2>
|
||||
<h3> press space to start</h3>
|
||||
<div>
|
||||
Masking Gain : <input
|
||||
id="gain-control"
|
||||
:onchange="updateMasterGain"
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.01"
|
||||
>
|
||||
</div>
|
||||
<div v-if="loading" class="spinner-border spinner-border-sm" role="status">
|
||||
<span class="sr-only">{{ t("Loading...") }}</span>
|
||||
</div>
|
||||
Loaded Bands: {{ loadedBands }}
|
||||
<NoiseControlledWebAudioBand
|
||||
v-for="frequency in frequencies"
|
||||
v-show="false"
|
||||
:key="frequency"
|
||||
:center-frequency="frequency"
|
||||
:master-gain="masterGainNode"
|
||||
@ready="onBandReady"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import KeyboardPlayHandler from '~/archive/components/KeyboardPlayHandler.vue'
|
||||
import NoiseControlledWebAudioBand from '~/components/experiments/tests/ControlValues/NoiseControlledWebAudioBand.vue'
|
||||
import { useAudioStore } from '~/stores/audio'
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
NoiseControlledWebAudioBand,
|
||||
KeyboardPlayHandler
|
||||
},
|
||||
setup () {
|
||||
const { t } = useI18n()
|
||||
const frequencies = ref([63, 125, 250, 500, 1000, 2000, 4000, 8000, 16000])
|
||||
const loadedBands = ref(0)
|
||||
const masterGainNode = computed(() => { return useAudioStore().getMasterGainNoise() })
|
||||
const masterAttack = ref(120000 * 2) // Beispielwert in Samples
|
||||
const masterRelease = ref(144000 * 2)
|
||||
const loading = computed(() => loadedBands.value < frequencies.value.length)
|
||||
|
||||
const updateMasterGain = (changeEvent:Event) => {
|
||||
const newValue = changeEvent?.target as any
|
||||
masterGainNode.value.gain.linearRampToValueAtTime(newValue.value, masterGainNode.value.context.currentTime + 0.25)
|
||||
}
|
||||
const onBandReady = () => {
|
||||
loadedBands.value++
|
||||
}
|
||||
|
||||
return {
|
||||
frequencies,
|
||||
loading,
|
||||
onBandReady,
|
||||
t,
|
||||
loadedBands,
|
||||
masterGainNode,
|
||||
updateMasterGain,
|
||||
masterAttack,
|
||||
masterRelease
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
110
pages/experiments/Player/test3.vue
Normal file
110
pages/experiments/Player/test3.vue
Normal file
@@ -0,0 +1,110 @@
|
||||
<!--
|
||||
/**
|
||||
* @component ControlValueBasedPlayer
|
||||
* @description A component that renders multiple NoiseControlledBand components,
|
||||
* each centered around a specific frequency. This can be used for
|
||||
* audio spectrum visualization or control. Now includes a loading spinner
|
||||
* that disappears when all NoiseControlledBand components are ready.
|
||||
*
|
||||
* @uses NoiseControlledBand
|
||||
*
|
||||
* @example
|
||||
* <ControlValueBasedPlayer />
|
||||
*
|
||||
* @remarks
|
||||
* - Utilizes Vue 3 Composition API
|
||||
* - Renders NoiseControlledBand components for standard audio frequency bands
|
||||
* - Frequencies: 63, 125, 250, 500, 1000, 2000, 4000, 8000, 16000 Hz
|
||||
* - Includes a loading spinner that disappears when all components are ready
|
||||
*/
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h1> Test Fall 3 mit Control Value Patch</h1>
|
||||
<h3> Drei AudioTags gesteuert über 3 RNBOControlValues. Über die Volume-APi wird die Lautstärke gesteuert</h3>
|
||||
<div v-if="isExperimentsRoute">
|
||||
<KeyboardPlayHandler />
|
||||
<PlayButton />
|
||||
|
||||
<div v-if="loading" class="spinner-border spinner-border-sm" role="status">
|
||||
<span class="sr-only">{{ t("Loading...") }}</span>
|
||||
</div>
|
||||
Loaded Bands: {{ loadedBands }}
|
||||
<div>
|
||||
<label>Attack: {{ (masterAttack / 480000).toFixed(2) }}s</label>
|
||||
<input
|
||||
v-model.number="masterAttack"
|
||||
type="range"
|
||||
:min="4800"
|
||||
:max="1920000"
|
||||
>
|
||||
<label>Release: {{ (masterRelease / 480000).toFixed(2) }}s</label>
|
||||
<input
|
||||
v-model.number="masterRelease"
|
||||
type="range"
|
||||
:min="4800"
|
||||
:max="1920000"
|
||||
>
|
||||
</div>
|
||||
<NoiseControlled3Band
|
||||
v-for="(frequency, index) in frequencies"
|
||||
:key="frequency"
|
||||
:master-attack="masterAttack"
|
||||
:master-release="masterRelease"
|
||||
:center-frequency="frequency"
|
||||
:q-factor="qFactors[index]"
|
||||
@ready="onBandReady"
|
||||
@update:mid-volume="controlMusicGain"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRoute } from 'vue-router'
|
||||
import KeyboardPlayHandler from '~/archive/components/KeyboardPlayHandler.vue'
|
||||
import PlayButton from '~/components/experiments/statemanagement/PlayButton.vue'
|
||||
import NoiseControlled3Band from '~/components/experiments/tests/ControlValues/NoiseControlled3Band.vue'
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
NoiseControlled3Band,
|
||||
KeyboardPlayHandler,
|
||||
PlayButton
|
||||
},
|
||||
setup () {
|
||||
const { t } = useI18n()
|
||||
const frequencies = ref([150, 1500, 8000])
|
||||
const qFactors = ref([0.8, 0.9, 0.6])
|
||||
const loadedBands = ref(0)
|
||||
|
||||
const route = useRoute()
|
||||
const isExperimentsRoute = computed(() => route.path.match(/\/[a-z]{2}\/experiments/))
|
||||
|
||||
const masterAttack = ref(120000 * 2) // Beispielwert in Samples
|
||||
const masterRelease = ref(144000 * 2)
|
||||
|
||||
const loading = computed(() => loadedBands.value < frequencies.value.length)
|
||||
|
||||
const onBandReady = () => {
|
||||
loadedBands.value++
|
||||
}
|
||||
|
||||
return {
|
||||
frequencies,
|
||||
loading,
|
||||
onBandReady,
|
||||
t,
|
||||
loadedBands,
|
||||
masterAttack,
|
||||
masterRelease,
|
||||
isExperimentsRoute,
|
||||
qFactors,
|
||||
controlMusicGain
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
180
pages/experiments/Player/test4.vue
Normal file
180
pages/experiments/Player/test4.vue
Normal file
@@ -0,0 +1,180 @@
|
||||
<!--
|
||||
/**
|
||||
* @component ControlValueBasedPlayer
|
||||
* @description A component that renders multiple NoiseControlledBand components,
|
||||
* each centered around a specific frequency. This can be used for
|
||||
* audio spectrum visualization or control. Now includes a loading spinner
|
||||
* that disappears when all NoiseControlledBand components are ready.
|
||||
*
|
||||
* @uses NoiseControlledBand
|
||||
*
|
||||
* @example
|
||||
* <ControlValueBasedPlayer />
|
||||
*
|
||||
* @remarks
|
||||
* - Utilizes Vue 3 Composition API
|
||||
* - Renders NoiseControlledBand components for standard audio frequency bands
|
||||
* - Frequencies: 63, 125, 250, 500, 1000, 2000, 4000, 8000, 16000 Hz
|
||||
* - Includes a loading spinner that disappears when all components are ready
|
||||
*/
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="isExperimentsRoute">
|
||||
<h1> Test Fall 4 mit Control Value Patch</h1>
|
||||
<h3> Drei WebAudioTag gesteuert über 3 RNBOControlValues. Über die WebAudio-APi wird die Lautstärke gesteuert</h3>
|
||||
<KeyboardPlayHandler />
|
||||
<PlayButton />
|
||||
|
||||
<div v-if="loading" class="spinner-border spinner-border-sm" role="status">
|
||||
<span class="sr-only">{{ t("Loading...") }}</span>
|
||||
</div>
|
||||
Loaded Bands: {{ loadedBands }}
|
||||
<div>
|
||||
<label>Attack: {{ (masterAttack / 480000).toFixed(2) }}s</label>
|
||||
<input
|
||||
v-model.number="masterAttack"
|
||||
type="range"
|
||||
:min="4800"
|
||||
:max="1920000"
|
||||
>
|
||||
<label>Release: {{ (masterRelease / 480000).toFixed(2) }}s</label>
|
||||
<input
|
||||
v-model.number="masterRelease"
|
||||
type="range"
|
||||
:min="4800"
|
||||
:max="1920000"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<NoiseControlled3BandWebAudio
|
||||
v-for="(frequency, index) in frequencies"
|
||||
v-show="false"
|
||||
:key="frequency"
|
||||
:master-attack="masterAttack"
|
||||
:master-release="masterRelease"
|
||||
:center-frequency="frequency"
|
||||
:master-gain="masterGain"
|
||||
:q-factor="qFactors[index]"
|
||||
@ready="onBandReady"
|
||||
@update:mid-volume="controlMusicGain"
|
||||
/>
|
||||
<div class="slider-wrapper">
|
||||
<img
|
||||
v-if="muted"
|
||||
style="width: 25px; height: 25px;"
|
||||
src="~/assets/image/sound_muted.svg"
|
||||
title="Click to unmute"
|
||||
@click="toggleMute()"
|
||||
>
|
||||
<img
|
||||
v-else
|
||||
style="width: 25px; height: 25px;"
|
||||
src="~/assets/image/sound.svg"
|
||||
title="Click to mute"
|
||||
@click="toggleMute()"
|
||||
>
|
||||
<div class="slider">
|
||||
<input
|
||||
id="gain-control"
|
||||
v-model="masterGain.gain.value"
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.02"
|
||||
data-toggle="tooltip"
|
||||
data-placement="top"
|
||||
title="Change the volume by click, scroll or touch"
|
||||
@wheel.prevent="changeVolumeOnWheel"
|
||||
>
|
||||
<span
|
||||
class="slider-progress-bar"
|
||||
:style="{ width: `${masterGain.gain.value * 100}%` }"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRoute } from 'vue-router'
|
||||
import KeyboardPlayHandler from '~/archive/components/KeyboardPlayHandler.vue'
|
||||
import PlayButton from '~/components/experiments/statemanagement/PlayButton.vue'
|
||||
import NoiseControlled3BandWebAudio from '~/components/experiments/tests/ControlValues/NoiseControlledWebAudio3Band.vue'
|
||||
import { useAudioStore } from '~/stores/audio'
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
NoiseControlled3BandWebAudio,
|
||||
KeyboardPlayHandler,
|
||||
PlayButton
|
||||
},
|
||||
setup () {
|
||||
const masterGain = ref(useAudioStore().getMasterGainNoise())
|
||||
const { t } = useI18n()
|
||||
const frequencies = ref([150, 1500, 8000])
|
||||
const qFactors = ref([0.8, 0.9, 0.6])
|
||||
const loadedBands = ref(0)
|
||||
const muted = computed(() => useAudioStore().getMasterGainNoise().gain.value === 0)
|
||||
let oldVolume = 0
|
||||
|
||||
const route = useRoute()
|
||||
const isExperimentsRoute = computed(() => route.path.match(/\/[a-z]{2}\/experiments/))
|
||||
|
||||
const masterAttack = ref(120000 * 2) // Beispielwert in Samples
|
||||
const masterRelease = ref(144000 * 2)
|
||||
|
||||
const loading = computed(() => loadedBands.value < frequencies.value.length)
|
||||
|
||||
const onBandReady = () => {
|
||||
loadedBands.value++
|
||||
}
|
||||
const toggleMute = () => {
|
||||
if (!muted.value) {
|
||||
oldVolume = masterGain.value.gain.value
|
||||
masterGain.value.gain.linearRampToValueAtTime(0, masterGain.value.context.currentTime + 0.4)
|
||||
} else if (oldVolume > 0) {
|
||||
masterGain.value.gain.linearRampToValueAtTime(oldVolume, masterGain.value.context.currentTime + 0.4)
|
||||
} else {
|
||||
masterGain.value.gain.linearRampToValueAtTime(1, masterGain.value.context.currentTime + 0.4)
|
||||
}
|
||||
}
|
||||
|
||||
const controlMusicGain = (value: string) => {
|
||||
|
||||
}
|
||||
const changeVolumeOnWheel = (event:WheelEvent) => {
|
||||
let gainValue = masterGain.value.gain.value
|
||||
// Adjust volume on wheel scroll
|
||||
const deltaY = event.deltaY
|
||||
if (deltaY < 0) {
|
||||
const volumeAdd = (Math.min(1, gainValue + 0.02))
|
||||
gainValue = volumeAdd
|
||||
} else {
|
||||
const volumeCut = (Math.max(0, gainValue - 0.02))
|
||||
gainValue = volumeCut
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
frequencies,
|
||||
loading,
|
||||
onBandReady,
|
||||
t,
|
||||
loadedBands,
|
||||
masterAttack,
|
||||
masterRelease,
|
||||
isExperimentsRoute,
|
||||
qFactors,
|
||||
controlMusicGain,
|
||||
masterGain,
|
||||
changeVolumeOnWheel,
|
||||
toggleMute,
|
||||
muted
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
50
pages/experiments/Spotify.vue
Normal file
50
pages/experiments/Spotify.vue
Normal file
@@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<div id="spotify">
|
||||
<spotify-embed :spotify-uri="currentSpotifyUri" />
|
||||
<pomodoro-playlist />
|
||||
<state-bar />
|
||||
<button @click="changeTrack">
|
||||
Change Track
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SpotifyEmbed from '@/components/experiments/spotify/SpotifyEmbed.vue'
|
||||
import PomodoroPlaylist from '@/components/experiments/spotify/PomodoroPlaylist.vue'
|
||||
import StateBar from '@/components/experiments/statemanagement/StateBar.vue'
|
||||
export default {
|
||||
components: {
|
||||
SpotifyEmbed, PomodoroPlaylist, StateBar
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
audioList: [
|
||||
{ id: 1, title: 'Lagoon', src: window.location.origin + '/sounds/lagoon.m4a', spotifyUri: 'track/4O2wnyNQ2NLmd5BHkLTgu2' },
|
||||
{ id: 2, title: 'Forest', src: window.location.origin + '/sounds/forest.m4a', spotifyUri: 'track/0MEMvf26SLFCXhnPj1qXJ1' },
|
||||
{ id: 3, title: 'Meadow', src: window.location.origin + '/sounds/meadow.m4a', spotifyUri: 'track/48NC8HTZo0N3Kdpicx9wVp' },
|
||||
{ id: 4, title: 'Tropics', src: window.location.origin + '/sounds/tropics.m4a', spotifyUri: 'track/12QLlNzfZTmSO6OMrjujKt' }
|
||||
],
|
||||
currentSpotifyUri: 'track/1hR0fIFK2qRG3f3RF70pb7' // Default URI
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
changeTrack () {
|
||||
const newSong = this.getSong(this.currentSpotifyUri, 'next')
|
||||
this.currentSpotifyUri = newSong.spotifyUri // Change this to the new URI
|
||||
},
|
||||
getSong (currentTitle, direction) {
|
||||
const index = this.audioList.findIndex(song => song.spotifyUri === currentTitle)
|
||||
let adjacentIndex = index + (direction === 'next' ? 1 : -1)
|
||||
// Loop back to the first song if 'next' goes beyond the last index
|
||||
if (adjacentIndex >= this.audioList.length) {
|
||||
adjacentIndex = 0
|
||||
} else if (adjacentIndex < 0) {
|
||||
adjacentIndex = this.audioList.length - 1
|
||||
}
|
||||
return this.audioList[adjacentIndex]
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
14
pages/experiments/controlGainsByValue/ControlGains.vue
Normal file
14
pages/experiments/controlGainsByValue/ControlGains.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<div>
|
||||
<h3>
|
||||
component: TotalGainController darin enthalten, AudioTagWebAudio & RNBOControlValue, AudioFileSelector <br>
|
||||
features: Anpassbare Ramptime <br>
|
||||
rnbo: ja
|
||||
</h3>
|
||||
<GainController audio-src="" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import GainController from '~/components/experiments/TotalGainController.vue'
|
||||
</script>
|
7
pages/experiments/controlValues/ControlValues.vue
Normal file
7
pages/experiments/controlValues/ControlValues.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<RNBOControlValue />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import RNBOControlValue from '@/components/experiments/tests/ControlValues/RNBOControlValue.vue'
|
||||
</script>
|
106
pages/experiments/controlValues/ControlValues9.vue
Normal file
106
pages/experiments/controlValues/ControlValues9.vue
Normal file
@@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Test Fall 3</h1>
|
||||
<h3>Dynamic Patches in parallel</h3>
|
||||
<div>
|
||||
<label for="patchCount">Number of Patches:</label>
|
||||
<input
|
||||
id="patchCount"
|
||||
v-model.number="patchCount"
|
||||
type="number"
|
||||
min="1"
|
||||
max="20"
|
||||
:disabled="isStarted"
|
||||
>
|
||||
</div>
|
||||
<button :disabled="isStarted" @click="startAllPatches">Start All Patches</button>
|
||||
<table v-if="isStarted">
|
||||
<thead>
|
||||
<tr>
|
||||
<th v-for="index in patchCount" :key="index">Patch {{ index }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td v-for="(value, index) in controlValues" :key="index">{{ value?.value.toFixed(2) }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<RNBOControlValue
|
||||
v-for="index in patchCount"
|
||||
:key="index"
|
||||
:ref="el => { if (el) rnboRefs[index - 1] = el }"
|
||||
:center-frequency="centerFrequencies.at(index - 1)"
|
||||
@control-value-change="updateValueTable(index - 1, $event)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, watch } from 'vue'
|
||||
import RNBOControlValue from '@/components/experiments/tests/ControlValues/RNBOControlValue.vue'
|
||||
|
||||
const patchCount = ref(9)
|
||||
const controlValues = ref([])
|
||||
const rnboRefs = ref([])
|
||||
const isStarted = ref(false)
|
||||
const centerFrequencies = ref([63, 125, 250, 500, 1000, 2000, 4000, 8000, 16000])
|
||||
const updateValueTable = (index, value) => {
|
||||
controlValues.value[index] = value
|
||||
}
|
||||
|
||||
const startAllPatches = () => {
|
||||
isStarted.value = true
|
||||
rnboRefs.value.forEach((rnbo) => {
|
||||
if (rnbo && typeof rnbo.testControlValuesDevice === 'function') {
|
||||
rnbo.testControlValuesDevice()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const formatValue = (value) => {
|
||||
return Number(value).toFixed(2)
|
||||
}
|
||||
|
||||
watch(patchCount, (newCount) => {
|
||||
controlValues.value = new Array(newCount).fill(0)
|
||||
rnboRefs.value = new Array(newCount).fill(null)
|
||||
})
|
||||
onMounted(() => {
|
||||
controlValues.value = new Array(patchCount.value).fill(0)
|
||||
rnboRefs.value = new Array(patchCount.value).fill(null)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
th, td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
||||
button, input {
|
||||
margin: 10px;
|
||||
padding: 10px;
|
||||
font-size: 16px;
|
||||
}
|
||||
button {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input:disabled, button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
155
pages/experiments/controlValues/ControlValuesAudio.vue
Normal file
155
pages/experiments/controlValues/ControlValuesAudio.vue
Normal file
@@ -0,0 +1,155 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Dynamic Patches in parallel mit knistern</h1>
|
||||
<p>In diesem Test funktioniert die Messung der Störwerte sowie eine Umwandlung nach StevensLoudness. Verwendet wird hier eine AudioBufferSourceNode pro Band.</p>
|
||||
<div>
|
||||
<label for="patchCount">Number of Patches:</label>
|
||||
<input
|
||||
id="patchCount"
|
||||
v-model.number="patchCount"
|
||||
type="number"
|
||||
min="1"
|
||||
max="20"
|
||||
:disabled="isStarted"
|
||||
>
|
||||
</div>
|
||||
<button :disabled="isStarted" @click="startAllPatches">Start All Patches</button>
|
||||
<table v-if="isStarted">
|
||||
<thead>
|
||||
<tr>
|
||||
<th v-for="index in patchCount" :key="index">Patch {{ index }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td v-for="(value, index) in controlValues" :key="index">{{ formatValue(value.value) }} </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td v-for="(value, index) in controlValues" :key="index">{{ formatValue(dBToStevensLoudness(value?.value)) }} </td>
|
||||
</tr>
|
||||
<!--
|
||||
<tr>
|
||||
<td v-for="(value, index) in controlValues" :key="index"> <AudioTagWebAudio :src="audioSources[index]" :volume="dBToStevensLoudness(value?.value)" /> </td>
|
||||
</tr>
|
||||
-->
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<AudioTagWebAudio
|
||||
v-for="index in patchCount"
|
||||
:key="index"
|
||||
:ref="el => { if (el) audioRefs[index - 1] = el }"
|
||||
:src="audioSources.at(index-1)"
|
||||
:volume="dBToStevensLoudness(controlValues[index])"
|
||||
/>
|
||||
|
||||
<RNBOControlValue
|
||||
v-for="index in patchCount"
|
||||
:key="index"
|
||||
:ref="el => { if (el) rnboRefs[index - 1] = el }"
|
||||
:center-frequency="centerFrequencies.at(index-1)"
|
||||
@control-value-change="updateValueTable(index - 1, $event)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, watch } from 'vue'
|
||||
import RNBOControlValue from '@/components/experiments/tests/ControlValues/RNBOControlValue.vue'
|
||||
import AudioTagWebAudio from '~/components/experiments/AudioTagWebAudio.vue'
|
||||
import { calculateNormalizedVolume } from '~/lib/AudioFunctions'
|
||||
|
||||
const patchCount = ref(9)
|
||||
const controlValues = ref([])
|
||||
const rnboRefs = ref([])
|
||||
const audioRefs = ref([])
|
||||
const isStarted = ref(false)
|
||||
const centerFrequencies = ref([63, 125, 250, 500, 1000, 2000, 4000, 8000, 16000])
|
||||
const audioSources = ref(
|
||||
[
|
||||
'https://192.168.188.182:3000/masking/bands/Quellrauschen63.wav',
|
||||
'https://192.168.188.182:3000/masking/bands/Quellrauschen125.wav',
|
||||
'https://192.168.188.182:3000/masking/bands/Quellrauschen250.wav',
|
||||
'https://192.168.188.182:3000/masking/bands/Quellrauschen500.wav',
|
||||
'https://192.168.188.182:3000/masking/bands/Quellrauschen1000.wav',
|
||||
'https://192.168.188.182:3000/masking/bands/Quellrauschen2000.wav',
|
||||
'https://192.168.188.182:3000/masking/bands/Quellrauschen4000.wav',
|
||||
'https://192.168.188.182:3000/masking/bands/Quellrauschen8000.wav',
|
||||
'https://192.168.188.182:3000/masking/bands/Quellrauschen16000.wav'
|
||||
])
|
||||
const updateValueTable = (index, value) => {
|
||||
controlValues.value[index] = value
|
||||
}
|
||||
|
||||
const startAllPatches = () => {
|
||||
isStarted.value = true
|
||||
rnboRefs.value.forEach((rnbo) => {
|
||||
if (rnbo && typeof rnbo.testControlValuesDevice === 'function') {
|
||||
rnbo.testControlValuesDevice()
|
||||
}
|
||||
})
|
||||
audioRefs.value.forEach((audio) => {
|
||||
useNuxtApp().$logger.log('AudioRef = ', { audio })
|
||||
})
|
||||
}
|
||||
|
||||
const formatValue = (value) => {
|
||||
return Number(value).toFixed(2)
|
||||
}
|
||||
|
||||
const mapLinearRange = (
|
||||
value,
|
||||
inMin,
|
||||
inMax,
|
||||
outMin,
|
||||
outMax
|
||||
) => {
|
||||
const clamped = Math.max(inMin, Math.min(value, inMax)) // optional: clamp
|
||||
return ((clamped - inMin) / (inMax - inMin)) * (outMax - outMin) + outMin
|
||||
}
|
||||
|
||||
const dBToStevensLoudness = (db) => {
|
||||
return calculateNormalizedVolume(db)
|
||||
}
|
||||
|
||||
watch(patchCount, (newCount) => {
|
||||
controlValues.value = new Array(newCount).fill(0)
|
||||
rnboRefs.value = new Array(newCount).fill(null)
|
||||
})
|
||||
onMounted(() => {
|
||||
controlValues.value = new Array(patchCount.value).fill(0)
|
||||
rnboRefs.value = new Array(patchCount.value).fill(null)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
th, td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
||||
button, input {
|
||||
margin: 10px;
|
||||
padding: 10px;
|
||||
font-size: 16px;
|
||||
}
|
||||
button {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input:disabled, button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
143
pages/experiments/controlValues/ControlValuesAudioStreaming.vue
Normal file
143
pages/experiments/controlValues/ControlValuesAudioStreaming.vue
Normal file
@@ -0,0 +1,143 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Dynamic Patches in parallel</h1>
|
||||
<p>In diesem Test funktioniert die Messung der Störwerte sowie eine Umwandlung nach StevensLoudness, nach dem Starten der Audio-Nodes (fetch) wird der AudioContext gestoppt. Eventuell von Howler </p>
|
||||
<div>
|
||||
<label for="patchCount">Number of Patches:</label>
|
||||
<input
|
||||
id="patchCount"
|
||||
v-model.number="patchCount"
|
||||
type="number"
|
||||
min="1"
|
||||
max="20"
|
||||
:disabled="isStarted"
|
||||
>
|
||||
</div>
|
||||
<button :disabled="isStarted" @click="startAllPatches">Start All Patches</button>
|
||||
<table v-if="isStarted">
|
||||
<thead>
|
||||
<tr>
|
||||
<th v-for="index in patchCount" :key="index">Patch {{ index }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td v-for="(value, index) in controlValues" :key="index">{{ formatValue(value.value) }} </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td v-for="(value, index) in controlValues" :key="index">{{ formatValue(dBToStevensLoudness(value?.value)) }} </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td v-for="(value, index) in controlValues" :key="index"> <AudioTagWebAudioStreaming :src="audioSources[index]" :volume="dBToStevensLoudness(value?.value)" /> </td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<RNBOControlValue
|
||||
v-for="index in patchCount"
|
||||
:key="index"
|
||||
:ref="el => { if (el) rnboRefs[index - 1] = el }"
|
||||
:center-frequency="centerFrequencies.at(index-1)"
|
||||
@control-value-change="updateValueTable(index - 1, $event)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, watch } from 'vue'
|
||||
import RNBOControlValue from '@/components/experiments/tests/ControlValues/RNBOControlValue.vue'
|
||||
import AudioTagWebAudioStreaming from '~/components/experiments/AudioTagWebAudioStreaming.vue'
|
||||
|
||||
const patchCount = ref(9)
|
||||
const controlValues = ref([])
|
||||
const rnboRefs = ref([])
|
||||
const isStarted = ref(false)
|
||||
const centerFrequencies = ref([63, 125, 250, 500, 1000, 2000, 4000, 8000, 16000])
|
||||
const audioSources = ref(
|
||||
[
|
||||
'https://192.168.188.182:3000/masking/bands/Quellrauschen63.wav',
|
||||
'https://192.168.188.182:3000/masking/bands/Quellrauschen125.wav',
|
||||
'https://192.168.188.182:3000/masking/bands/Quellrauschen250.wav',
|
||||
'https://192.168.188.182:3000/masking/bands/Quellrauschen500.wav',
|
||||
'https://192.168.188.182:3000/masking/bands/Quellrauschen1000.wav',
|
||||
'https://192.168.188.182:3000/masking/bands/Quellrauschen2000.wav',
|
||||
'https://192.168.188.182:3000/masking/bands/Quellrauschen4000.wav',
|
||||
'https://192.168.188.182:3000/masking/bands/Quellrauschen8000.wav',
|
||||
'https://192.168.188.182:3000/masking/bands/Quellrauschen16000.wav'
|
||||
])
|
||||
const updateValueTable = (index, value) => {
|
||||
controlValues.value[index] = value
|
||||
}
|
||||
|
||||
const startAllPatches = () => {
|
||||
isStarted.value = true
|
||||
rnboRefs.value.forEach((rnbo) => {
|
||||
if (rnbo && typeof rnbo.testControlValuesDevice === 'function') {
|
||||
rnbo.testControlValuesDevice()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const formatValue = (value) => {
|
||||
return Number(value).toFixed(2)
|
||||
}
|
||||
|
||||
const mapLinearRange = (
|
||||
value,
|
||||
inMin,
|
||||
inMax,
|
||||
outMin,
|
||||
outMax
|
||||
) => {
|
||||
const clamped = Math.max(inMin, Math.min(value, inMax)) // optional: clamp
|
||||
return ((clamped - inMin) / (inMax - inMin)) * (outMax - outMin) + outMin
|
||||
}
|
||||
|
||||
const dBToStevensLoudness = (db, exponent = 0.3, minDb = -60) => {
|
||||
const clampedDb = Math.max(db, minDb)
|
||||
const intensity = Math.pow(10, clampedDb / 10)
|
||||
const loudness = Math.pow(intensity, exponent)
|
||||
return loudness
|
||||
}
|
||||
|
||||
watch(patchCount, (newCount) => {
|
||||
controlValues.value = new Array(newCount).fill(0)
|
||||
rnboRefs.value = new Array(newCount).fill(null)
|
||||
})
|
||||
onMounted(() => {
|
||||
controlValues.value = new Array(patchCount.value).fill(0)
|
||||
rnboRefs.value = new Array(patchCount.value).fill(null)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
th, td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
||||
button, input {
|
||||
margin: 10px;
|
||||
padding: 10px;
|
||||
font-size: 16px;
|
||||
}
|
||||
button {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input:disabled, button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
379
pages/fixproblem.vue
Normal file
379
pages/fixproblem.vue
Normal file
@@ -0,0 +1,379 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-12 px-0 mx-0">
|
||||
<video-background
|
||||
src="/video/bg-video.mp4"
|
||||
style=" height: 100vh;"
|
||||
poster="/images/poster.png"
|
||||
>
|
||||
<div class="container-fluid pt-3 px-lg-4">
|
||||
<div class="header d-flex flex-column flex-fill flex-md-row justify-content-between">
|
||||
<div class="header__logo pt-md-1 pt-lg-1">
|
||||
<nuxt-link class="navbar-brand" to="/">
|
||||
<div class="text-center text-md-start">
|
||||
<img src="/mindboostlogo.svg" height="35" class="img " alt="imae">
|
||||
</div>
|
||||
</nuxt-link>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="row text-center">
|
||||
<div class="col-12">
|
||||
<h1 v-if="true" class="h3 fw-bold mb-4">
|
||||
{{ t("The microphone does not provide any level") }}
|
||||
</h1>
|
||||
<h1 v-else class="h3 fw-bold mb-4">
|
||||
{{ t("Nothing to worry, the microphone is working.") }}
|
||||
</h1>
|
||||
<p class="text-muted">
|
||||
{{ t("Please check the input level of the microphone in the audio") }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="row justify-content-center pt-4">
|
||||
<div class="col-11">
|
||||
<form>
|
||||
<div class="row justify-content-center mb-4">
|
||||
<div class="col-md-5 text-center">
|
||||
<label for="inputSelect" class="fw-semibold">{{ t('Input device:') }}</label>
|
||||
<p class="pt-0 mt-0 text-muted pb-2 mb-0" style="font-size: 14px;font-weight: 500">
|
||||
({{ t('select laptop or mobile device microphone') }})
|
||||
</p>
|
||||
<select id="inputSelect" v-model="selectedInput" class="form-select pt-1 mt-0 select-box" @change="handleInputChange">
|
||||
<option v-for="device in audioInputDevices" :key="device.value" :value="device.value">
|
||||
{{ device.label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class=" pt-3 text-center">
|
||||
<div class="progress-container">
|
||||
<div class="checkmark col-md-6 pt-2 d-block d-sm-inline-block d-inline-block">
|
||||
<p class="fw-semibold">{{ t("Microphone Indicator") }}</p>
|
||||
<div class="col-12 text-center d-flex justify-content-center col-lg-12 pb-5">
|
||||
<AudioReactiveBar ref="AudioReactiveBar" />
|
||||
</div>
|
||||
<div class="col-12 text-center pt-1">
|
||||
<a href="/" class="btn text-white px-4 fs-5 fw-bolder py-2" type="button" style="background-color: rgb(233, 192, 70);">{{ t('Next') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</video-background>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import { mapActions, mapState } from 'pinia'
|
||||
import { useCounterStore } from '@/stores/counter'
|
||||
import { useMicStore } from '@/stores/microphone'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
|
||||
export default {
|
||||
name: 'FixProblem',
|
||||
setup () {
|
||||
const { t } = useI18n()
|
||||
const localePath = useLocalePath()
|
||||
const AudioReactiveBar = ref(null)
|
||||
const micStore = useMicStore()
|
||||
|
||||
return {
|
||||
micStore,
|
||||
t,
|
||||
localePath,
|
||||
AudioReactiveBar
|
||||
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
bar_val: 100,
|
||||
audioOutputDevices: [],
|
||||
selectedInput: null,
|
||||
selectedOutput: null,
|
||||
stream: null,
|
||||
micReady: false,
|
||||
detectedInput: false
|
||||
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(useCounterStore, ['count']),
|
||||
// ...mapState(useMicStore, ['microphones'])
|
||||
audioInputDevices () {
|
||||
return this.micStore.availableDevices.map(device => ({
|
||||
label: device.label || `Microphone ${device.deviceId}`,
|
||||
value: device.deviceId
|
||||
}))
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.$watch(
|
||||
() => this.audioInputDevices,
|
||||
(newDevices) => {
|
||||
if (newDevices.length > 0 && this.selectedInput === null) {
|
||||
this.selectedInput = newDevices[0].value
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
},
|
||||
fillInputList () {
|
||||
try {
|
||||
this.getUserMedia()
|
||||
.then(() => {
|
||||
return navigator.mediaDevices.enumerateDevices()
|
||||
})
|
||||
.then((devices) => {
|
||||
// useNuxtApp().$logger.log('DEVICES', { devices })
|
||||
// Filtere die Geräte nach Typ
|
||||
this.audioInputDevices = devices.filter(device => device.kind === 'audioinput')
|
||||
this.audioOutputDevices = devices.filter(device => device.kind === 'audiooutput')
|
||||
|
||||
// Setze die Standardwerte, falls keine Geräte verfügbar sind
|
||||
this.selectedDevice = this.audioInputDevices.length > 0 ? this.audioInputDevices[0].deviceId : null
|
||||
|
||||
// Überprüfe, ob `audioInputDevice` und `audioOutputDevice` existieren
|
||||
if (this.audioInputDevice && this.audioInputDevice.deviceId) {
|
||||
this.selectedInput = this.audioInputDevices.findIndex(item => item.deviceId === this.audioInputDevice.deviceId)
|
||||
} else {
|
||||
this.selectedInput = -1 // Kein passendes Gerät gefunden
|
||||
}
|
||||
|
||||
if (this.audioOutputDevice && this.audioOutputDevice.deviceId) {
|
||||
this.selectedOutput = this.audioOutputDevices.findIndex(item => item.deviceId === this.audioOutputDevice.deviceId)
|
||||
} else {
|
||||
this.selectedOutput = -1 // Kein passendes Gerät gefunden
|
||||
}
|
||||
|
||||
// Standardauswahl, falls nichts gefunden wird
|
||||
if (this.selectedInput < 0) { this.selectedInput = 0 }
|
||||
if (this.selectedOutput < 0) { this.selectedOutput = 0 }
|
||||
|
||||
// Warnung anzeigen, wenn Geräte gleich sind
|
||||
if (this.checkIfSameDevice()) {
|
||||
this.$toast.warning(this.t('bluetoothWarning'), {
|
||||
duration: 6000,
|
||||
pauseOnHover: true,
|
||||
dismissible: true,
|
||||
queue: true,
|
||||
position: 'bottom-left'
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
// Fehler bei der Geräteabfrage behandeln
|
||||
this.$toast.error(`Error enumerating media devices: ${error.message}`)
|
||||
useNuxtApp().$logger.error('Error during device enumeration:', error)
|
||||
})
|
||||
} catch (error) {
|
||||
// Allgemeine Fehlerbehandlung
|
||||
this.$toast.error('Unexpected error occurred.')
|
||||
useNuxtApp().$logger.error('Unexpected error:', error)
|
||||
}
|
||||
},
|
||||
async mounted () {
|
||||
this.increment(75)
|
||||
await this.micStore.updateAvailableDevices()
|
||||
},
|
||||
beforeUnmount () {
|
||||
this.detachMicrophone()
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useCounterStore, ['increment']),
|
||||
...mapActions(useMicStore, ['getMicrophone', 'getMediaStream', 'switchMicrophone', 'detachMicrophone']),
|
||||
...mapActions(useUserStore, ['saveInputdevice', 'saveOutputDevice']),
|
||||
checkMicrophoneReadiness (mic) {
|
||||
if (mic.microphoneNode instanceof MediaStreamAudioSourceNode &&
|
||||
mic.microphoneStream instanceof MediaStream) { this.micReady = true }
|
||||
this.stream = mic.microphoneStream
|
||||
useNuxtApp().$logger.log(this.micReady)
|
||||
},
|
||||
normalizeDeviceName (deviceName) {
|
||||
return deviceName
|
||||
.replace('Standard - ', '') // Remove 'Standard - '
|
||||
.replace(/ \(.*\)$/, '') // Remove any content in parentheses
|
||||
.trim()
|
||||
},
|
||||
async check (event) {
|
||||
useNuxtApp().$logger.log({ event })
|
||||
const inputDeviceId = this.audioInputDevices[(event.target).value].deviceId
|
||||
useNuxtApp().$logger.log('INPUT DEVICE', { inputDeviceId })
|
||||
await this.switchMicrophone(inputDeviceId)
|
||||
this.checkIfSameDevice()
|
||||
},
|
||||
checkInputLevel () {
|
||||
const audioreactivebar = AudioReactiveBar.value
|
||||
useNuxtApp().$logger.log({ audioreactivebar })
|
||||
this.detectedInput = true
|
||||
},
|
||||
getDeviceLabel (outputDevice) {
|
||||
if (!outputDevice || !outputDevice.label) {
|
||||
return 'Unknown Device' // Fallback-Label
|
||||
}
|
||||
return outputDevice.label
|
||||
},
|
||||
checkIfSameDevice () {
|
||||
const outputDevice = this.audioOutputDevices[this.selectedOutput] || this.audioOutputDevices[this.index]
|
||||
const inputDevice = this.audioInputDevices[this.selectedInput]
|
||||
const inputLabel = this.getDeviceLabel(inputDevice)
|
||||
const outputLabel = this.getDeviceLabel(outputDevice)
|
||||
|
||||
if (!inputLabel || !outputLabel) {
|
||||
throw new Error('Device labels are not selected or not available')
|
||||
}
|
||||
const normalizedInput = this.normalizeDeviceName(inputLabel)
|
||||
const normalizedOutput = this.normalizeDeviceName(outputLabel)
|
||||
// useNuxtApp().$logger.log(`Normalized Input: ${normalizedInput}, Normalized Output: ${normalizedOutput}`)
|
||||
return normalizedInput === normalizedOutput
|
||||
},
|
||||
saveDevices () {
|
||||
this.saveInputdevice(this.audioInputDevices[this.selectedInput])
|
||||
this.saveOutputDevice(this.audioOutputDevices[this.selectedOutput])
|
||||
this.$router.push(this.localePath('/onboarding'))
|
||||
},
|
||||
getUserMedia () {
|
||||
const constraints = {
|
||||
audio: { deviceId: this.selectedDevice ? { exact: this.selectedDevice } : undefined }
|
||||
}
|
||||
try {
|
||||
return navigator.mediaDevices.getUserMedia(constraints)
|
||||
} catch (error) {
|
||||
this.$toast.error('Error accessing media devices: ', error)
|
||||
}
|
||||
},
|
||||
async handleInputChange () {
|
||||
await this.switchMicrophone(this.selectedInput)
|
||||
this.checkIfSameDevice()
|
||||
if (this.$refs.AudioReactiveBar) {
|
||||
this.$refs.AudioReactiveBar.updateMicrophone(this.selectedInput)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.progress-container {
|
||||
display: flex;
|
||||
flex-direction: column; /* Elemente werden vertikal angeordnet */
|
||||
align-items: center; /* Zentriert die Progressbar und den Button horizontal */
|
||||
}
|
||||
|
||||
.checklabel:hover{
|
||||
border-color: #e9c046;
|
||||
}
|
||||
.checklabel {
|
||||
color: #e9c046 !important;
|
||||
border: 1px solid #e9c046;
|
||||
}
|
||||
.checkmark .checklabel svg path{
|
||||
/*background-color: white;*/
|
||||
fill: #e9c046;
|
||||
}
|
||||
.checkmark input:checked ~ .checklabel {
|
||||
background-color: #e9c046;
|
||||
border: 1px solid #e9c046;
|
||||
color: #f4f5f7 !important;
|
||||
}
|
||||
.checkmark input:checked ~ .checklabel svg path {
|
||||
fill: white;
|
||||
}
|
||||
.checkmark input:hover ~ .checklabel {
|
||||
background-color: #e9c046;
|
||||
border: 1px solid #e9c046;
|
||||
color: #f4f5f7 !important;
|
||||
}
|
||||
.checkmark input:hover ~ .checklabel svg path {
|
||||
fill: white;
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tooltip .tooltiptext {
|
||||
visibility: hidden;
|
||||
width: 200px;
|
||||
background-color: #333;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
border-radius: 4px;
|
||||
padding: 5px;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
bottom: 125%; /* Position above the text */
|
||||
left: 50%;
|
||||
margin-left: -100px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
.tooltip:hover .tooltiptext {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
.bar{
|
||||
background-color: #e9c046 !important;
|
||||
}
|
||||
.checklabel{
|
||||
background-color: white !important;
|
||||
width: 150px ;
|
||||
height: 134px ;
|
||||
}
|
||||
.px-4{
|
||||
transition: 1s;
|
||||
}
|
||||
|
||||
.content {
|
||||
background-color: rgba(255, 255, 255, 0.6);
|
||||
border-radius: 10px;
|
||||
padding: 4.5em;
|
||||
max-width: 1120px;
|
||||
margin: 10% auto;
|
||||
min-height: 50vh;
|
||||
max-height: 75vh;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
.content {
|
||||
padding: 4.5em 3em;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 576px) {
|
||||
.content {
|
||||
padding: 2em 1em;
|
||||
margin: 2.5em auto;
|
||||
overflow-y: scroll;
|
||||
max-height: 75vh;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-buttons {
|
||||
position: fixed;
|
||||
bottom: 0px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: rgb(255,255,255);
|
||||
background: linear-gradient(0deg, rgba(255,255,255,1) 20%, rgba(255,255,255,0) 100%);
|
||||
}
|
||||
.checkmark {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
}
|
||||
</style>
|
194
pages/free/index.vue
Normal file
194
pages/free/index.vue
Normal file
@@ -0,0 +1,194 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-12 px-0 mx-0">
|
||||
<video-background
|
||||
src="/video/Tropics.mp4"
|
||||
preload="auto"
|
||||
poster="/images/posters/poster.png"
|
||||
style=" height: 100vh;"
|
||||
>
|
||||
<home-bar :title="title" />
|
||||
<div class="overlay tooltip">
|
||||
<span class="tooltiptext">Upgrade to Pro to access this feature</span>
|
||||
</div>
|
||||
<div class="container-fluid">
|
||||
<div class="row justify-content-center">
|
||||
<div class="adaptive pb-3">
|
||||
<spotify-embed :spotify-uri="currentSpotifyUri" class="spotifyplayer" />
|
||||
<button class="btn btn-dark" style="margin:auto; margin-bottom:2em" @click="changeTrack">
|
||||
Next
|
||||
</button>
|
||||
<div class="col-12 ">
|
||||
<div class="d-none d-md-block mx-auto pb-1" style="width: 300px">
|
||||
<div class="progress " style="height: 10px">
|
||||
<div
|
||||
class="progress-bar bar"
|
||||
role="progressbar"
|
||||
aria-label="Basic example"
|
||||
:style="{width:bar_width+'%'}"
|
||||
style="background-color: #e9c046;transition: 0.1s"
|
||||
aria-valuenow="75"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex justify-content-center mb-1">
|
||||
<nuxt-link to="#adaptive-modal" data-bs-target="#adaptive-modal" data-bs-toggle="modal" class="text-muted text-decoration-none fw-bold fs-6 ">
|
||||
{{ t('Adaptive soundscape') }}: <span class="" style="color: #e9c046">{{ t('Off') }}</span>
|
||||
</nuxt-link><span class="ps-3"><i style="padding: 5px 0px;" class="fa-solid text-muted d-flex fa-chevron-right" /></span>
|
||||
</div>
|
||||
<div class="d-block d-md-none mx-auto pb-1" style="width: 225px">
|
||||
<div class="progress " style="height: 10px">
|
||||
<div
|
||||
class="progress-bar bar"
|
||||
role="progressbar"
|
||||
aria-label="Basic example"
|
||||
:style="{width:bar_width+'%'}"
|
||||
style="background-color: #e9c046;transition: 0.1s"
|
||||
aria-valuenow="75"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<BootomBar />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</video-background>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
|
||||
import HomeBar from '../components/homebar'
|
||||
import SpotifyEmbed from '@/components/experiments/spotify/SpotifyEmbed.vue'
|
||||
export default {
|
||||
components: { HomeBar, SpotifyEmbed },
|
||||
|
||||
setup () {
|
||||
definePageMeta({
|
||||
|
||||
middleware: 'auth'
|
||||
})
|
||||
const { t } = useI18n()
|
||||
const localePath = useLocalePath()
|
||||
|
||||
return {
|
||||
t,
|
||||
localePath
|
||||
}
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
audioList: [
|
||||
{ id: 1, title: 'Lagoon', src: window.location.origin + '/sounds/lagoon.m4a', spotifyUri: 'track/4O2wnyNQ2NLmd5BHkLTgu2' },
|
||||
{ id: 2, title: 'Forest', src: window.location.origin + '/sounds/forest.m4a', spotifyUri: 'track/0MEMvf26SLFCXhnPj1qXJ1' },
|
||||
{ id: 3, title: 'Meadow', src: window.location.origin + '/sounds/meadow.m4a', spotifyUri: 'track/48NC8HTZo0N3Kdpicx9wVp' },
|
||||
{ id: 4, title: 'Tropics', src: window.location.origin + '/sounds/tropics.m4a', spotifyUri: 'track/12QLlNzfZTmSO6OMrjujKt' }
|
||||
],
|
||||
currentSpotifyUri: 'track/4O2wnyNQ2NLmd5BHkLTgu2', // Default URI
|
||||
bar_width: 230,
|
||||
analyser: null,
|
||||
dataArray: null,
|
||||
bar_val: 25
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// Berechnete Eigenschaft für die dynamische Übersetzung des Titels
|
||||
title () {
|
||||
const title = this.audioList.filter(element =>
|
||||
element.spotifyUri === this.currentSpotifyUri)[0].title
|
||||
return this.$t(title)
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
changeTrack () {
|
||||
const newSong = this.getSong(this.currentSpotifyUri, 'next')
|
||||
this.currentSpotifyUri = newSong.spotifyUri // Change this to the new URI
|
||||
},
|
||||
getSong (currentTitle, direction) {
|
||||
const index = this.audioList.findIndex(song => song.spotifyUri === currentTitle)
|
||||
let adjacentIndex = index + (direction === 'next' ? 1 : -1)
|
||||
// Loop back to the first song if 'next' goes beyond the last index
|
||||
if (adjacentIndex >= this.audioList.length) {
|
||||
adjacentIndex = 0
|
||||
} else if (adjacentIndex < 0) {
|
||||
adjacentIndex = this.audioList.length - 1
|
||||
}
|
||||
return this.audioList[adjacentIndex]
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#myVideo{
|
||||
position: fixed;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
min-width: 100%;
|
||||
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
z-index: -99;
|
||||
}
|
||||
.adaptive{
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
.spotifyplayer{
|
||||
display: flex;
|
||||
flex-direction: column; /* Ensures vertical centering if needed */
|
||||
align-items: center; /* Horizontally centers content in a flex-direction: column */
|
||||
justify-content: center;
|
||||
}
|
||||
.overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(128, 128, 128, 0.5); /* Grey color with opacity */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
pointer-events: none; /* Allows interaction with elements under the overlay */
|
||||
}
|
||||
.tooltip {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.tooltip .tooltiptext {
|
||||
visibility: hidden;
|
||||
width: 120px;
|
||||
background-color: black;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
border-radius: 6px;
|
||||
padding: 5px 0;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
bottom: 125%;
|
||||
left: 50%;
|
||||
margin-left: -60px;
|
||||
}
|
||||
|
||||
.tooltip:hover .tooltiptext {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
</style>
|
56
pages/getstarted.vue
Normal file
56
pages/getstarted.vue
Normal file
@@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<div>
|
||||
<video-background
|
||||
src="video/bg-video.mp4"
|
||||
style=" height: 100vh;"
|
||||
>
|
||||
<top-logo-bar />
|
||||
<div class="container-fluid">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-6 pt-2">
|
||||
<h3 class="text-center fs-2">
|
||||
Hey, We’re About to Get Started
|
||||
</h3>
|
||||
<p class="text-center text-muted fs-5 col-8 mx-auto">
|
||||
Please answer a few questions while we analyze the acoustics in your room.
|
||||
</p>
|
||||
<div class="text-center">
|
||||
<img src="~/assets/image/Delivery.png">
|
||||
</div>
|
||||
<form>
|
||||
<div class="row pt-4 justify-content-center">
|
||||
<div class="col-7 ">
|
||||
<div class="form-check">
|
||||
<input id="flexCheckIndeterminate" class="form-check-input" type="checkbox" value="">
|
||||
<label class="form-check-label" for="flexCheckIndeterminate">
|
||||
I give the app permission to use my microphone
|
||||
{{ t('I give the app permission to use my microphone') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row pt-3">
|
||||
<div class="col-12 text-center pt-3">
|
||||
<button type="button" class="login-btn col-4">
|
||||
{{ t('Next') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</video-background>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
// can be used both composition api and roller api
|
||||
export default {
|
||||
components: { },
|
||||
methods: {
|
||||
handleAnimation: function (anim) {
|
||||
this.anim = anim
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
286
pages/homeforest.vue
Normal file
286
pages/homeforest.vue
Normal file
@@ -0,0 +1,286 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-12 px-0 mx-0">
|
||||
<video-background
|
||||
poster="/images/posters/poster2.png"
|
||||
src="/video/Forest.mp4"
|
||||
style=" height: 100vh;"
|
||||
preload="auto"
|
||||
>
|
||||
<home-bar :title="title" />
|
||||
<div class="container-fluid">
|
||||
<div class="row justify-content-center">
|
||||
<div class="adaptive pb-3">
|
||||
<div class="col-12 ">
|
||||
<NoiseMusicGainForest />
|
||||
<div class="d-none d-md-block mx-auto pb-1" style="width: 225px">
|
||||
<div class="progress " style="height: 10px">
|
||||
<div
|
||||
class="progress-bar bar"
|
||||
role="progressbar"
|
||||
aria-label="Basic example"
|
||||
:style="{width:bar_width+'%'}"
|
||||
style="background-color: #e9c046;transition: 0.1s"
|
||||
aria-valuenow="75"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex justify-content-center mb-1">
|
||||
<nuxt-link to="#adaptive-modal" data-bs-target="#adaptive-modal" data-bs-toggle="modal" class="text-muted text-decoration-none fw-bold fs-6 ">
|
||||
{{ t('Adaptive soundscape') }} : <span class="" style="color: #e9c046">{{ t('On') }}</span>
|
||||
</nuxt-link><span class="ps-3"><i style="padding: 5px 0px;" class="fa-solid text-muted d-flex mt-1 fa-chevron-right" /></span>
|
||||
</div>
|
||||
<div class="d-block d-md-none mx-auto pb-1" style="width: 225px">
|
||||
<div class="progress " style="height: 10px">
|
||||
<div
|
||||
class="progress-bar bar"
|
||||
role="progressbar"
|
||||
aria-label="Basic example"
|
||||
:style="{width:bar_width+'%'}"
|
||||
style="background-color: #e9c046;transition: 0.1s"
|
||||
aria-valuenow="75"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<BootomBar />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</video-background>
|
||||
<!-- <video muted id="myVideo" autoplay loop>-->
|
||||
<!-- <source src="~/assets/video/bg-video.mp4" type="video/mp4">-->
|
||||
<!-- <source src="~/assets/video/bg-video.ogg" type="video/ogg">-->
|
||||
<!-- Your browser does not support HTML5 video.-->
|
||||
<!-- </video>-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="adaptive-modal" class="modal fade " tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-body">
|
||||
<div class=" bg-white " style="border-radius: 15px">
|
||||
<div class="row px-3">
|
||||
<div class="col-2">
|
||||
<span><i class="fa-solid fs-3 fa-arrow-left-long" style="cursor: pointer" data-bs-dismiss="modal" /></span>
|
||||
</div>
|
||||
<div class="col-8">
|
||||
<h4 class="text-center fw-bolder">
|
||||
{{ t('Adaptive soundscape') }}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<div class="form-check form-switch float-end">
|
||||
<input id="flexSwitchCheckDefault" class="form-check-input" type="checkbox" role="switch">
|
||||
</div>
|
||||
<div class="row pt-4 ps-3">
|
||||
<div class="col-auto">
|
||||
<span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="60" height="54" viewBox="0 0 60 54" fill="none">
|
||||
<path d="M28.9879 0.600098C28.6223 0.600098 28.2613 0.675098 27.9004 0.787598C27.1738 1.01728 26.4801 1.44853 25.8754 2.0626L11.7754 16.1626H3.60039C1.59883 16.1626 -0.0371094 17.7985 -0.0371094 19.8001V34.2001C-0.0371094 36.2017 1.59883 37.8376 3.60039 37.8376H11.7754L25.8379 51.8251C27.1785 53.1704 28.8988 53.761 30.3004 53.1001C31.7113 52.4345 32.4004 50.8313 32.4004 48.9751V4.5751C32.4004 2.7751 31.4816 1.27978 30.0754 0.787598C29.7238 0.665723 29.3535 0.600098 28.9879 0.600098ZM29.0254 3.0001C29.1473 3.0001 29.241 3.00947 29.3254 3.0376C29.6676 3.15478 30.0004 3.45947 30.0004 4.5751V48.9751C30.0004 50.2548 29.5785 50.8267 29.2879 50.9626C28.9973 51.0985 28.4113 51.1126 27.4879 50.1751L12.3754 35.0626C12.352 35.0485 12.3238 35.0345 12.3004 35.0251C12.3895 35.1048 12.0004 34.3454 12.0004 33.9001V20.1001C12.0004 19.6548 12.4035 18.886 12.3004 18.9751C12.3285 18.9517 12.352 18.9282 12.3754 18.9001L27.5629 3.7501C28.1254 3.18291 28.6551 3.00479 29.0254 3.0001ZM45.9379 5.1751C45.4129 5.26416 45.0098 5.69072 44.9488 6.22041C44.8926 6.7501 45.1832 7.25635 45.6754 7.4626C52.8144 11.0626 57.6754 18.4454 57.6754 27.0001C57.6754 35.5548 52.8144 42.9376 45.6754 46.5376C45.2348 46.6876 44.9254 47.0813 44.8738 47.5407C44.8223 48.0048 45.0426 48.4548 45.441 48.6985C45.8348 48.9376 46.3363 48.9282 46.7254 48.6751C54.6473 44.6767 60.0754 36.4735 60.0754 27.0001C60.0754 17.5267 54.6473 9.32353 46.7254 5.3251C46.5191 5.20791 46.2848 5.15635 46.0504 5.1751C46.0129 5.1751 45.9754 5.1751 45.9379 5.1751ZM41.6629 11.5501C41.1285 11.6157 40.702 12.0329 40.6269 12.5673C40.5473 13.097 40.8332 13.6173 41.3254 13.8376C46.0082 16.3642 49.2004 21.2954 49.2004 27.0001C49.2004 32.686 46.0223 37.6313 41.3629 40.1626C40.7816 40.4813 40.5707 41.2126 40.8941 41.7938C41.2129 42.3751 41.9441 42.586 42.5254 42.2626C47.9348 39.3235 51.6004 33.5813 51.6004 27.0001C51.6004 20.4001 47.9254 14.6345 42.4879 11.7001C42.2723 11.5782 42.0238 11.5267 41.7754 11.5501C41.7379 11.5501 41.7004 11.5501 41.6629 11.5501ZM3.60039 18.6376H9.82539C9.6707 19.097 9.60039 19.5985 9.60039 20.1001V33.9001C9.60039 34.4017 9.67539 34.8985 9.82539 35.3626H3.60039C2.95352 35.3626 2.43789 34.847 2.43789 34.2001V19.8001C2.43789 19.1532 2.95352 18.6376 3.60039 18.6376ZM36.6379 19.0876C36.0566 19.1579 35.616 19.636 35.5879 20.222C35.5598 20.8032 35.9535 21.3235 36.5254 21.4501C38.9816 22.0923 40.8004 24.3329 40.8004 27.0001C40.8004 29.6673 38.9816 31.9079 36.5254 32.5501C35.8832 32.7142 35.4988 33.3704 35.6629 34.0126C35.827 34.6548 36.4832 35.0392 37.1254 34.8751C40.6176 33.961 43.2004 30.7642 43.2004 27.0001C43.2004 23.236 40.6176 20.0392 37.1254 19.1251C36.966 19.0782 36.802 19.0688 36.6379 19.0876Z" fill="#E9C046" />
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-11 col-md-10 ps-3">
|
||||
<h5>{{ t("Noisy Environment") }}</h5>
|
||||
<p>{{ t("The background noise at your workplace is disturbing. Your concentration is severely impaired. Mindboost protects you from the disturbing background noise.") }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row px-2 pt-4">
|
||||
<div class="col-12 ps-3 fs-5" style="line-height: 25px">
|
||||
<p class="p-0 m-0">
|
||||
{{ t("The Mindboost sounscape responds to the acousitcs in your room.") }}
|
||||
</p>
|
||||
<p class="p-0 m-0" />
|
||||
<p class="">
|
||||
{{ t("Currently, your room has a:") }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row pt-4 ps-3">
|
||||
<div class="col-auto">
|
||||
<span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="52" height="54" viewBox="0 0 60 54" fill="none">
|
||||
<path d="M28.9879 0.600098C28.6223 0.600098 28.2613 0.675098 27.9004 0.787598C27.1738 1.01728 26.4801 1.44853 25.8754 2.0626L11.7754 16.1626H3.60039C1.59883 16.1626 -0.0371094 17.7985 -0.0371094 19.8001V34.2001C-0.0371094 36.2017 1.59883 37.8376 3.60039 37.8376H11.7754L25.8379 51.8251C27.1785 53.1704 28.8988 53.761 30.3004 53.1001C31.7113 52.4345 32.4004 50.8313 32.4004 48.9751V4.5751C32.4004 2.7751 31.4816 1.27978 30.0754 0.787598C29.7238 0.665723 29.3535 0.600098 28.9879 0.600098ZM29.0254 3.0001C29.1473 3.0001 29.241 3.00947 29.3254 3.0376C29.6676 3.15478 30.0004 3.45947 30.0004 4.5751V48.9751C30.0004 50.2548 29.5785 50.8267 29.2879 50.9626C28.9973 51.0985 28.4113 51.1126 27.4879 50.1751L12.3754 35.0626C12.352 35.0485 12.3238 35.0345 12.3004 35.0251C12.3895 35.1048 12.0004 34.3454 12.0004 33.9001V20.1001C12.0004 19.6548 12.4035 18.886 12.3004 18.9751C12.3285 18.9517 12.352 18.9282 12.3754 18.9001L27.5629 3.7501C28.1254 3.18291 28.6551 3.00479 29.0254 3.0001ZM45.9379 5.1751C45.4129 5.26416 45.0098 5.69072 44.9488 6.22041C44.8926 6.7501 45.1832 7.25635 45.6754 7.4626C52.8144 11.0626 57.6754 18.4454 57.6754 27.0001C57.6754 35.5548 52.8144 42.9376 45.6754 46.5376C45.2348 46.6876 44.9254 47.0813 44.8738 47.5407C44.8223 48.0048 45.0426 48.4548 45.441 48.6985C45.8348 48.9376 46.3363 48.9282 46.7254 48.6751C54.6473 44.6767 60.0754 36.4735 60.0754 27.0001C60.0754 17.5267 54.6473 9.32353 46.7254 5.3251C46.5191 5.20791 46.2848 5.15635 46.0504 5.1751C46.0129 5.1751 45.9754 5.1751 45.9379 5.1751ZM41.6629 11.5501C41.1285 11.6157 40.702 12.0329 40.6269 12.5673C40.5473 13.097 40.8332 13.6173 41.3254 13.8376C46.0082 16.3642 49.2004 21.2954 49.2004 27.0001C49.2004 32.686 46.0223 37.6313 41.3629 40.1626C40.7816 40.4813 40.5707 41.2126 40.8941 41.7938C41.2129 42.3751 41.9441 42.586 42.5254 42.2626C47.9348 39.3235 51.6004 33.5813 51.6004 27.0001C51.6004 20.4001 47.9254 14.6345 42.4879 11.7001C42.2723 11.5782 42.0238 11.5267 41.7754 11.5501C41.7379 11.5501 41.7004 11.5501 41.6629 11.5501ZM3.60039 18.6376H9.82539C9.6707 19.097 9.60039 19.5985 9.60039 20.1001V33.9001C9.60039 34.4017 9.67539 34.8985 9.82539 35.3626H3.60039C2.95352 35.3626 2.43789 34.847 2.43789 34.2001V19.8001C2.43789 19.1532 2.95352 18.6376 3.60039 18.6376ZM36.6379 19.0876C36.0566 19.1579 35.616 19.636 35.5879 20.222C35.5598 20.8032 35.9535 21.3235 36.5254 21.4501C38.9816 22.0923 40.8004 24.3329 40.8004 27.0001C40.8004 29.6673 38.9816 31.9079 36.5254 32.5501C35.8832 32.7142 35.4988 33.3704 35.6629 34.0126C35.827 34.6548 36.4832 35.0392 37.1254 34.8751C40.6176 33.961 43.2004 30.7642 43.2004 27.0001C43.2004 23.236 40.6176 20.0392 37.1254 19.1251C36.966 19.0782 36.802 19.0688 36.6379 19.0876Z" fill="#E9C046" />
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-11 col-md-10 ps-3">
|
||||
<h5>{{ t("Noisy Environment") }}</h5>
|
||||
<p>{{ t("The background noise at your workplace is disturbing. Your concentration is severely impaired. Mindboost protects you from the disturbing background noise.") }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row pt-3 ps-3 opacity-75">
|
||||
<div class="col-auto">
|
||||
<span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="52" height="54" viewBox="0 0 52 54" fill="none">
|
||||
<path d="M28.9879 0.600098C28.6223 0.600098 28.2613 0.675098 27.9004 0.787598C27.1738 1.01728 26.4801 1.44853 25.8754 2.0626L11.7754 16.1626H3.60039C1.59883 16.1626 -0.0371094 17.7985 -0.0371094 19.8001V34.2001C-0.0371094 36.2017 1.59883 37.8376 3.60039 37.8376H11.7754L25.8379 51.8251C27.1785 53.1704 28.8988 53.761 30.3004 53.1001C31.7113 52.4345 32.4004 50.8313 32.4004 48.9751V4.5751C32.4004 2.7751 31.4816 1.27978 30.0754 0.787598C29.7238 0.665723 29.3535 0.600098 28.9879 0.600098ZM29.0254 3.0001C29.1473 3.0001 29.241 3.00947 29.3254 3.0376C29.6676 3.15478 30.0004 3.45947 30.0004 4.5751V48.9751C30.0004 50.2548 29.5785 50.8267 29.2879 50.9626C28.9973 51.0985 28.4113 51.1126 27.4879 50.1751L12.3754 35.0626C12.352 35.0485 12.3238 35.0345 12.3004 35.0251C12.3895 35.1048 12.0004 34.3454 12.0004 33.9001V20.1001C12.0004 19.6548 12.4035 18.886 12.3004 18.9751C12.3285 18.9517 12.352 18.9282 12.3754 18.9001L27.5629 3.7501C28.1254 3.18291 28.6551 3.00479 29.0254 3.0001ZM41.6629 11.5501C41.1285 11.6157 40.702 12.0329 40.627 12.5673C40.5473 13.097 40.8332 13.6173 41.3254 13.8376C46.0082 16.3642 49.2004 21.2954 49.2004 27.0001C49.2004 32.686 46.0223 37.6313 41.3629 40.1626C40.7816 40.4813 40.5707 41.2126 40.8941 41.7938C41.2129 42.3751 41.9441 42.586 42.5254 42.2626C47.9348 39.3235 51.6004 33.5813 51.6004 27.0001C51.6004 20.4001 47.9254 14.6345 42.4879 11.7001C42.2723 11.5782 42.0238 11.5267 41.7754 11.5501C41.7379 11.5501 41.7004 11.5501 41.6629 11.5501ZM3.60039 18.6376H9.82539C9.6707 19.097 9.60039 19.5985 9.60039 20.1001V33.9001C9.60039 34.4017 9.67539 34.8985 9.82539 35.3626H3.60039C2.95352 35.3626 2.43789 34.847 2.43789 34.2001V19.8001C2.43789 19.1532 2.95352 18.6376 3.60039 18.6376ZM36.6379 19.0876C36.0566 19.1579 35.616 19.636 35.5879 20.222C35.5598 20.8032 35.9535 21.3235 36.5254 21.4501C38.9816 22.0923 40.8004 24.3329 40.8004 27.0001C40.8004 29.6673 38.9816 31.9079 36.5254 32.5501C35.8832 32.7142 35.4988 33.3704 35.6629 34.0126C35.827 34.6548 36.4832 35.0392 37.1254 34.8751C40.6176 33.961 43.2004 30.7642 43.2004 27.0001C43.2004 23.236 40.6176 20.0392 37.1254 19.1251C36.966 19.0782 36.802 19.0688 36.6379 19.0876Z" fill="#E9C046" />
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-11 col-md-10 ps-3">
|
||||
<h5>{{ t("Medium-noise Environment") }}</h5>
|
||||
<p>{{ t("The background noise at your workplace should be optimized. In the long term, it could disturb and have a negative impact on your health. Protect yourself with mindboost.") }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row pt-3 ps-3 opacity-50">
|
||||
<div class="col-auto">
|
||||
<span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="44" height="54" viewBox="0 0 44 54" fill="none">
|
||||
<path d="M28.9879 0.600098C28.6223 0.600098 28.2613 0.675098 27.9004 0.787598C27.1738 1.01728 26.4801 1.44853 25.8754 2.0626L11.7754 16.1626H3.60039C1.59883 16.1626 -0.0371094 17.7985 -0.0371094 19.8001V34.2001C-0.0371094 36.2017 1.59883 37.8376 3.60039 37.8376H11.7754L25.8379 51.8251C27.1785 53.1704 28.8988 53.761 30.3004 53.1001C31.7113 52.4345 32.4004 50.8313 32.4004 48.9751V4.5751C32.4004 2.7751 31.4816 1.27978 30.0754 0.787598C29.7238 0.665723 29.3535 0.600098 28.9879 0.600098ZM29.0254 3.0001C29.1473 3.0001 29.241 3.00947 29.3254 3.0376C29.6676 3.15478 30.0004 3.45947 30.0004 4.5751V48.9751C30.0004 50.2548 29.5785 50.8267 29.2879 50.9626C28.9973 51.0985 28.4113 51.1126 27.4879 50.1751L12.3754 35.0626C12.352 35.0485 12.3238 35.0345 12.3004 35.0251C12.3895 35.1048 12.0004 34.3454 12.0004 33.9001V20.1001C12.0004 19.6548 12.4035 18.886 12.3004 18.9751C12.3285 18.9517 12.352 18.9282 12.3754 18.9001L27.5629 3.7501C28.1254 3.18291 28.6551 3.00479 29.0254 3.0001ZM3.60039 18.6376H9.82539C9.6707 19.097 9.60039 19.5985 9.60039 20.1001V33.9001C9.60039 34.4017 9.67539 34.8985 9.82539 35.3626H3.60039C2.95352 35.3626 2.43789 34.847 2.43789 34.2001V19.8001C2.43789 19.1532 2.95352 18.6376 3.60039 18.6376ZM36.6379 19.0876C36.0566 19.1579 35.616 19.636 35.5879 20.222C35.5598 20.8032 35.9535 21.3235 36.5254 21.4501C38.9816 22.0923 40.8004 24.3329 40.8004 27.0001C40.8004 29.6673 38.9816 31.9079 36.5254 32.5501C35.8832 32.7142 35.4988 33.3704 35.6629 34.0126C35.827 34.6548 36.4832 35.0392 37.1254 34.8751C40.6176 33.961 43.2004 30.7642 43.2004 27.0001C43.2004 23.236 40.6176 20.0392 37.1254 19.1251C36.966 19.0782 36.802 19.0688 36.6379 19.0876Z" fill="#E9C046" />
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-11 col-md-10 ps-3">
|
||||
<h5>{{ t("Good Environment") }}</h5>
|
||||
<p />
|
||||
{{ t("The background noise at your workplace provides a long’term healthy basis for concentrated work. With Mindboost you make sure that even sudden disturbances do not distract you.") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
|
||||
import { mapState, mapActions } from 'pinia'
|
||||
import HomeBar from '../components/homebar.vue'
|
||||
import NoiseMusicGainForest from '@/components/experiments/homepages/NoiseMusicGainForest.vue'
|
||||
import { useAudioStore } from '@/stores/audio'
|
||||
import { useMicStore } from '@/stores/microphone'
|
||||
export default {
|
||||
components: { HomeBar, NoiseMusicGainForest },
|
||||
setup () {
|
||||
definePageMeta({
|
||||
|
||||
middleware: 'auth'
|
||||
})
|
||||
const { t } = useI18n()
|
||||
const localePath = useLocalePath()
|
||||
|
||||
return {
|
||||
t,
|
||||
localePath
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
bar_width: 0,
|
||||
analyser: null,
|
||||
dataArray: null,
|
||||
bar_val: 25
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(useAudioStore, ['audioContext', 'microphone']),
|
||||
// Berechnete Eigenschaft für die dynamische Übersetzung des Titels
|
||||
title () {
|
||||
return this.$t('Forest')
|
||||
}
|
||||
},
|
||||
beforeUnmount () {
|
||||
// Stop each track of the microphone stream and reset the AudioContext
|
||||
try {
|
||||
useMicStore().detachMicrophone()
|
||||
useAudioStore().pauseContext()
|
||||
} catch (_error) {
|
||||
// useNuxtApp().$logger.log(_error)
|
||||
}
|
||||
},
|
||||
async mounted () {
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useAudioStore, ['getAudioContext']),
|
||||
updateMeter () {
|
||||
requestAnimationFrame(this.updateMeter)
|
||||
this.analyser.getByteFrequencyData(this.dataArray)
|
||||
const rms = this.getRMS(this.dataArray)
|
||||
let level = 20 * Math.log10(rms / 128)
|
||||
level = Math.max(0, Math.min(100, level + 100))
|
||||
// bar.style.width = level + '%'
|
||||
this.bar_width = level
|
||||
},
|
||||
getRMS (dataArray) {
|
||||
let rms = 0
|
||||
for (let i = 0; i < dataArray.length; i++) {
|
||||
rms += dataArray[i] * dataArray[i]
|
||||
}
|
||||
rms /= dataArray.length
|
||||
rms = Math.sqrt(rms)
|
||||
return rms
|
||||
},
|
||||
handleAnimation: function (anim) {
|
||||
this.anim = anim
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px; /* Spacing between items */
|
||||
width: 225px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.icon, .slider {
|
||||
flex: 1 1 100px; /* Flex-grow, flex-shrink, flex-basis */
|
||||
display: flex;
|
||||
align-items: center; /* Center items vertically */
|
||||
justify-content: center; /* Center items horizontally */
|
||||
}
|
||||
.icon {
|
||||
/* Add padding around the icon for margin */
|
||||
margin-right: 15px; /* Adjust this value as needed */
|
||||
|
||||
/* Align items if using flexbox */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#myVideo{
|
||||
position: fixed;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
min-width: 100%;
|
||||
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
z-index: -99;
|
||||
}
|
||||
.adaptive{
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
.rnboplayer{
|
||||
position:sticky;
|
||||
width: 225px;
|
||||
height: inherit;
|
||||
display:flex;
|
||||
float: bottom;
|
||||
}
|
||||
|
||||
</style>
|
||||
~/stores/audio
|
262
pages/homemeadow.vue
Normal file
262
pages/homemeadow.vue
Normal file
@@ -0,0 +1,262 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-12 px-0 mx-0">
|
||||
<video-background
|
||||
preload="auto"
|
||||
src="/video/Meadow.mp4"
|
||||
poster="/images/posters/poster2.png"
|
||||
style=" height: 100vh;"
|
||||
>
|
||||
<home-bar :title="title" />
|
||||
<div class="container-fluid">
|
||||
<div class="row justify-content-center">
|
||||
<div class="adaptive pb-3">
|
||||
<NoiseMusicGainMeadow />
|
||||
<div class="col-12 ">
|
||||
<div class="d-none d-md-block mx-auto pb-1" style="width: 225px">
|
||||
<div class="progress " style="height: 10px">
|
||||
<div
|
||||
class="progress-bar bar"
|
||||
role="progressbar"
|
||||
aria-label="Basic example"
|
||||
:style="{width:bar_width+'%'}"
|
||||
style="background-color: #e9c046;transition: 0.1s"
|
||||
aria-valuenow="75"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex justify-content-center mb-1">
|
||||
<nuxt-link to="#adaptive-modal" data-bs-target="#adaptive-modal" data-bs-toggle="modal" class="text-muted text-decoration-none fw-bold fs-6 ">
|
||||
{{ t('Adaptive soundscape') }}: <span class="" style="color: #e9c046">{{ t('On') }}</span>
|
||||
</nuxt-link><span class="ps-3"><i style="padding: 5px 0px;" class="fa-solid text-muted d-flex fa-chevron-right" /></span>
|
||||
</div>
|
||||
<div class="d-block d-md-none mx-auto pb-1" style="width: 225px">
|
||||
<div class="progress " style="height: 10px">
|
||||
<div
|
||||
class="progress-bar bar"
|
||||
role="progressbar"
|
||||
aria-label="Basic example"
|
||||
:style="{width:bar_width+'%'}"
|
||||
style="background-color: #e9c046;transition: 0.1s"
|
||||
aria-valuenow="75"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<BootomBar />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</video-background>
|
||||
<!-- <video muted id="myVideo" autoplay loop>-->
|
||||
<!-- <source src="~/assets/video/bg-video.mp4" type="video/mp4">-->
|
||||
<!-- <source src="~/assets/video/bg-video.ogg" type="video/ogg">-->
|
||||
<!-- Your browser does not support HTML5 video.-->
|
||||
<!-- </video>-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="adaptive-modal" class="modal fade " tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-body">
|
||||
<div class=" bg-white " style="border-radius: 15px">
|
||||
<div class="row px-3">
|
||||
<div class="col-2">
|
||||
<span><i class="fa-solid fs-3 fa-arrow-left-long" style="cursor: pointer" data-bs-dismiss="modal" /></span>
|
||||
</div>
|
||||
<div class="col-8">
|
||||
<h4 class="text-center fw-bolder">
|
||||
{{ t('Adaptive soundscape') }}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<div class="form-check form-switch float-end">
|
||||
<input id="flexSwitchCheckDefault" class="form-check-input" type="checkbox" role="switch">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row px-2 pt-4">
|
||||
<div class="col-12 ps-3 fs-5" style="line-height: 25px">
|
||||
<p class="p-0 m-0">
|
||||
{{ t("The Mindboost sounscape responds to the acousitcs in your room.") }}
|
||||
</p>
|
||||
<p class="p-0 m-0" />
|
||||
<p class="">
|
||||
{{ t("Currently, your room has a:") }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row pt-4 ps-3">
|
||||
<div class="col-auto">
|
||||
<span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="52" height="54" viewBox="0 0 60 54" fill="none">
|
||||
<path d="M28.9879 0.600098C28.6223 0.600098 28.2613 0.675098 27.9004 0.787598C27.1738 1.01728 26.4801 1.44853 25.8754 2.0626L11.7754 16.1626H3.60039C1.59883 16.1626 -0.0371094 17.7985 -0.0371094 19.8001V34.2001C-0.0371094 36.2017 1.59883 37.8376 3.60039 37.8376H11.7754L25.8379 51.8251C27.1785 53.1704 28.8988 53.761 30.3004 53.1001C31.7113 52.4345 32.4004 50.8313 32.4004 48.9751V4.5751C32.4004 2.7751 31.4816 1.27978 30.0754 0.787598C29.7238 0.665723 29.3535 0.600098 28.9879 0.600098ZM29.0254 3.0001C29.1473 3.0001 29.241 3.00947 29.3254 3.0376C29.6676 3.15478 30.0004 3.45947 30.0004 4.5751V48.9751C30.0004 50.2548 29.5785 50.8267 29.2879 50.9626C28.9973 51.0985 28.4113 51.1126 27.4879 50.1751L12.3754 35.0626C12.352 35.0485 12.3238 35.0345 12.3004 35.0251C12.3895 35.1048 12.0004 34.3454 12.0004 33.9001V20.1001C12.0004 19.6548 12.4035 18.886 12.3004 18.9751C12.3285 18.9517 12.352 18.9282 12.3754 18.9001L27.5629 3.7501C28.1254 3.18291 28.6551 3.00479 29.0254 3.0001ZM45.9379 5.1751C45.4129 5.26416 45.0098 5.69072 44.9488 6.22041C44.8926 6.7501 45.1832 7.25635 45.6754 7.4626C52.8144 11.0626 57.6754 18.4454 57.6754 27.0001C57.6754 35.5548 52.8144 42.9376 45.6754 46.5376C45.2348 46.6876 44.9254 47.0813 44.8738 47.5407C44.8223 48.0048 45.0426 48.4548 45.441 48.6985C45.8348 48.9376 46.3363 48.9282 46.7254 48.6751C54.6473 44.6767 60.0754 36.4735 60.0754 27.0001C60.0754 17.5267 54.6473 9.32353 46.7254 5.3251C46.5191 5.20791 46.2848 5.15635 46.0504 5.1751C46.0129 5.1751 45.9754 5.1751 45.9379 5.1751ZM41.6629 11.5501C41.1285 11.6157 40.702 12.0329 40.6269 12.5673C40.5473 13.097 40.8332 13.6173 41.3254 13.8376C46.0082 16.3642 49.2004 21.2954 49.2004 27.0001C49.2004 32.686 46.0223 37.6313 41.3629 40.1626C40.7816 40.4813 40.5707 41.2126 40.8941 41.7938C41.2129 42.3751 41.9441 42.586 42.5254 42.2626C47.9348 39.3235 51.6004 33.5813 51.6004 27.0001C51.6004 20.4001 47.9254 14.6345 42.4879 11.7001C42.2723 11.5782 42.0238 11.5267 41.7754 11.5501C41.7379 11.5501 41.7004 11.5501 41.6629 11.5501ZM3.60039 18.6376H9.82539C9.6707 19.097 9.60039 19.5985 9.60039 20.1001V33.9001C9.60039 34.4017 9.67539 34.8985 9.82539 35.3626H3.60039C2.95352 35.3626 2.43789 34.847 2.43789 34.2001V19.8001C2.43789 19.1532 2.95352 18.6376 3.60039 18.6376ZM36.6379 19.0876C36.0566 19.1579 35.616 19.636 35.5879 20.222C35.5598 20.8032 35.9535 21.3235 36.5254 21.4501C38.9816 22.0923 40.8004 24.3329 40.8004 27.0001C40.8004 29.6673 38.9816 31.9079 36.5254 32.5501C35.8832 32.7142 35.4988 33.3704 35.6629 34.0126C35.827 34.6548 36.4832 35.0392 37.1254 34.8751C40.6176 33.961 43.2004 30.7642 43.2004 27.0001C43.2004 23.236 40.6176 20.0392 37.1254 19.1251C36.966 19.0782 36.802 19.0688 36.6379 19.0876Z" fill="#E9C046" />
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-11 col-md-10 ps-3">
|
||||
<h5>{{ t("Noisy Environment") }}</h5>
|
||||
<p>
|
||||
{{ t("The background noise at your workplace is disturbing. Your concentration is severely impaired. Mindboost protects you from the disturbing background noise.") }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row pt-3 ps-3 opacity-75">
|
||||
<div class="col-auto">
|
||||
<span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="52" height="54" viewBox="0 0 52 54" fill="none">
|
||||
<path d="M28.9879 0.600098C28.6223 0.600098 28.2613 0.675098 27.9004 0.787598C27.1738 1.01728 26.4801 1.44853 25.8754 2.0626L11.7754 16.1626H3.60039C1.59883 16.1626 -0.0371094 17.7985 -0.0371094 19.8001V34.2001C-0.0371094 36.2017 1.59883 37.8376 3.60039 37.8376H11.7754L25.8379 51.8251C27.1785 53.1704 28.8988 53.761 30.3004 53.1001C31.7113 52.4345 32.4004 50.8313 32.4004 48.9751V4.5751C32.4004 2.7751 31.4816 1.27978 30.0754 0.787598C29.7238 0.665723 29.3535 0.600098 28.9879 0.600098ZM29.0254 3.0001C29.1473 3.0001 29.241 3.00947 29.3254 3.0376C29.6676 3.15478 30.0004 3.45947 30.0004 4.5751V48.9751C30.0004 50.2548 29.5785 50.8267 29.2879 50.9626C28.9973 51.0985 28.4113 51.1126 27.4879 50.1751L12.3754 35.0626C12.352 35.0485 12.3238 35.0345 12.3004 35.0251C12.3895 35.1048 12.0004 34.3454 12.0004 33.9001V20.1001C12.0004 19.6548 12.4035 18.886 12.3004 18.9751C12.3285 18.9517 12.352 18.9282 12.3754 18.9001L27.5629 3.7501C28.1254 3.18291 28.6551 3.00479 29.0254 3.0001ZM41.6629 11.5501C41.1285 11.6157 40.702 12.0329 40.627 12.5673C40.5473 13.097 40.8332 13.6173 41.3254 13.8376C46.0082 16.3642 49.2004 21.2954 49.2004 27.0001C49.2004 32.686 46.0223 37.6313 41.3629 40.1626C40.7816 40.4813 40.5707 41.2126 40.8941 41.7938C41.2129 42.3751 41.9441 42.586 42.5254 42.2626C47.9348 39.3235 51.6004 33.5813 51.6004 27.0001C51.6004 20.4001 47.9254 14.6345 42.4879 11.7001C42.2723 11.5782 42.0238 11.5267 41.7754 11.5501C41.7379 11.5501 41.7004 11.5501 41.6629 11.5501ZM3.60039 18.6376H9.82539C9.6707 19.097 9.60039 19.5985 9.60039 20.1001V33.9001C9.60039 34.4017 9.67539 34.8985 9.82539 35.3626H3.60039C2.95352 35.3626 2.43789 34.847 2.43789 34.2001V19.8001C2.43789 19.1532 2.95352 18.6376 3.60039 18.6376ZM36.6379 19.0876C36.0566 19.1579 35.616 19.636 35.5879 20.222C35.5598 20.8032 35.9535 21.3235 36.5254 21.4501C38.9816 22.0923 40.8004 24.3329 40.8004 27.0001C40.8004 29.6673 38.9816 31.9079 36.5254 32.5501C35.8832 32.7142 35.4988 33.3704 35.6629 34.0126C35.827 34.6548 36.4832 35.0392 37.1254 34.8751C40.6176 33.961 43.2004 30.7642 43.2004 27.0001C43.2004 23.236 40.6176 20.0392 37.1254 19.1251C36.966 19.0782 36.802 19.0688 36.6379 19.0876Z" fill="#E9C046" />
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-11 col-md-10 ps-3">
|
||||
<h5>{{ t("Medium-noise Environment") }}</h5>
|
||||
<p>{{ t("The background noise at your workplace should be optimized. In the long term, it could disturb and have a negative impact on your health. Protect yourself with mindboost.") }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row pt-3 ps-3 opacity-50">
|
||||
<div class="col-auto">
|
||||
<span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="44" height="54" viewBox="0 0 44 54" fill="none">
|
||||
<path d="M28.9879 0.600098C28.6223 0.600098 28.2613 0.675098 27.9004 0.787598C27.1738 1.01728 26.4801 1.44853 25.8754 2.0626L11.7754 16.1626H3.60039C1.59883 16.1626 -0.0371094 17.7985 -0.0371094 19.8001V34.2001C-0.0371094 36.2017 1.59883 37.8376 3.60039 37.8376H11.7754L25.8379 51.8251C27.1785 53.1704 28.8988 53.761 30.3004 53.1001C31.7113 52.4345 32.4004 50.8313 32.4004 48.9751V4.5751C32.4004 2.7751 31.4816 1.27978 30.0754 0.787598C29.7238 0.665723 29.3535 0.600098 28.9879 0.600098ZM29.0254 3.0001C29.1473 3.0001 29.241 3.00947 29.3254 3.0376C29.6676 3.15478 30.0004 3.45947 30.0004 4.5751V48.9751C30.0004 50.2548 29.5785 50.8267 29.2879 50.9626C28.9973 51.0985 28.4113 51.1126 27.4879 50.1751L12.3754 35.0626C12.352 35.0485 12.3238 35.0345 12.3004 35.0251C12.3895 35.1048 12.0004 34.3454 12.0004 33.9001V20.1001C12.0004 19.6548 12.4035 18.886 12.3004 18.9751C12.3285 18.9517 12.352 18.9282 12.3754 18.9001L27.5629 3.7501C28.1254 3.18291 28.6551 3.00479 29.0254 3.0001ZM3.60039 18.6376H9.82539C9.6707 19.097 9.60039 19.5985 9.60039 20.1001V33.9001C9.60039 34.4017 9.67539 34.8985 9.82539 35.3626H3.60039C2.95352 35.3626 2.43789 34.847 2.43789 34.2001V19.8001C2.43789 19.1532 2.95352 18.6376 3.60039 18.6376ZM36.6379 19.0876C36.0566 19.1579 35.616 19.636 35.5879 20.222C35.5598 20.8032 35.9535 21.3235 36.5254 21.4501C38.9816 22.0923 40.8004 24.3329 40.8004 27.0001C40.8004 29.6673 38.9816 31.9079 36.5254 32.5501C35.8832 32.7142 35.4988 33.3704 35.6629 34.0126C35.827 34.6548 36.4832 35.0392 37.1254 34.8751C40.6176 33.961 43.2004 30.7642 43.2004 27.0001C43.2004 23.236 40.6176 20.0392 37.1254 19.1251C36.966 19.0782 36.802 19.0688 36.6379 19.0876Z" fill="#E9C046" />
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-11 col-md-10 ps-3">
|
||||
<h5>{{ t("Good Environment") }}</h5>
|
||||
<p>
|
||||
{{ t("The background noise at your workplace provides a long’term healthy basis for concentrated work. With Mindboost you make sure that even sudden disturbances do not distract you.") }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
|
||||
import { mapState, mapActions } from 'pinia'
|
||||
import HomeBar from '../components/homebar'
|
||||
import NoiseMusicGainMeadow from '@/components/experiments/homepages/NoiseMusicGainMeadow.vue'
|
||||
import { useAudioStore } from '@/stores/audio'
|
||||
export default {
|
||||
components: { HomeBar, NoiseMusicGainMeadow },
|
||||
setup () {
|
||||
definePageMeta({
|
||||
|
||||
middleware: 'auth'
|
||||
})
|
||||
const { t } = useI18n()
|
||||
const localePath = useLocalePath()
|
||||
|
||||
return {
|
||||
t,
|
||||
localePath
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
bar_width: 0,
|
||||
analyser: null,
|
||||
dataArray: null,
|
||||
bar_val: 25
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(useAudioStore, ['audioContext', 'microphone']),
|
||||
|
||||
// Berechnete Eigenschaft für die dynamische Übersetzung des Titels
|
||||
title () {
|
||||
return this.$t('Meadow')
|
||||
}
|
||||
},
|
||||
beforeUnmount () {
|
||||
// Stop each track of the microphone stream and reset the AudioContext
|
||||
try {
|
||||
useMicStore().detachMicrophone()
|
||||
useAudioStore().resetAudioContext()
|
||||
} catch (_error) {
|
||||
// useNuxtApp().$logger.log(error)
|
||||
}
|
||||
},
|
||||
async mounted () {
|
||||
/*
|
||||
await useMicStore().getMicrophone().then((mic) => {
|
||||
const audioCtx = mic.microphoneNode.context
|
||||
this.analyser = audioCtx.createAnalyser()
|
||||
mic.microphoneNode.connect(this.analyser)
|
||||
this.analyser.fftSize = 2048
|
||||
const bufferLength = this.analyser.frequencyBinCount
|
||||
this.dataArray = new Uint8Array(bufferLength)
|
||||
this.updateMeter()
|
||||
}) */
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapActions(useAudioStore, ['getAudioContext']),
|
||||
updateMeter () {
|
||||
requestAnimationFrame(this.updateMeter)
|
||||
this.analyser.getByteFrequencyData(this.dataArray)
|
||||
const rms = this.getRMS(this.dataArray)
|
||||
let level = 20 * Math.log10(rms / 128)
|
||||
level = Math.max(0, Math.min(100, level + 100))
|
||||
// bar.style.width = level + '%';
|
||||
|
||||
this.bar_width = level
|
||||
},
|
||||
getRMS (dataArray) {
|
||||
let rms = 0
|
||||
for (let i = 0; i < dataArray.length; i++) {
|
||||
rms += dataArray[i] * dataArray[i]
|
||||
}
|
||||
rms /= dataArray.length
|
||||
rms = Math.sqrt(rms)
|
||||
return rms
|
||||
},
|
||||
handleAnimation: function (anim) {
|
||||
this.anim = anim
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#myVideo{
|
||||
position: fixed;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
min-width: 100%;
|
||||
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
z-index: -99;
|
||||
}
|
||||
.adaptive{
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
.rnboplayer{
|
||||
position:sticky;
|
||||
width: 225px;
|
||||
height: inherit;
|
||||
display:flex;
|
||||
float: bottom;
|
||||
}
|
||||
|
||||
</style>
|
250
pages/hometropics.vue
Normal file
250
pages/hometropics.vue
Normal file
@@ -0,0 +1,250 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-12 px-0 mx-0">
|
||||
<video-background
|
||||
src="/video/Tropics.mp4"
|
||||
preload="auto"
|
||||
poster="/images/posters/poster2.png"
|
||||
style=" height: 100vh;"
|
||||
>
|
||||
<home-bar :title="title" />
|
||||
<div class="container-fluid">
|
||||
<div class="row justify-content-center">
|
||||
<div class="adaptive pb-3">
|
||||
<NoiseMusicGainTropics />
|
||||
<div class="col-12 ">
|
||||
<div class="d-none d-md-block mx-auto pb-1" style="width: 225px">
|
||||
<div class="progress " style="height: 10px">
|
||||
<div
|
||||
class="progress-bar bar"
|
||||
role="progressbar"
|
||||
aria-label="Basic example"
|
||||
:style="{width:bar_width+'%'}"
|
||||
style="background-color: #e9c046;transition: 0.1s"
|
||||
aria-valuenow="75"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex justify-content-center mb-1">
|
||||
<nuxt-link to="#adaptive-modal" data-bs-target="#adaptive-modal" data-bs-toggle="modal" class="text-muted text-decoration-none fw-bold fs-6 ">
|
||||
{{ t('Adaptive soundscape') }}: <span class="" style="color: #e9c046">{{ t('On') }}</span>
|
||||
</nuxt-link><span class="ps-3"><i style="padding: 5px 0px;" class="fa-solid text-muted d-flex fa-chevron-right" /></span>
|
||||
</div>
|
||||
<div class="d-block d-md-none mx-auto pb-1" style="width: 225px">
|
||||
<div class="progress " style="height: 10px">
|
||||
<div
|
||||
class="progress-bar bar"
|
||||
role="progressbar"
|
||||
aria-label="Basic example"
|
||||
:style="{width:bar_width+'%'}"
|
||||
style="background-color: #e9c046;transition: 0.1s"
|
||||
aria-valuenow="75"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<BootomBar />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</video-background>
|
||||
<!-- <video muted id="myVideo" autoplay loop>-->
|
||||
<!-- <source src="~/assets/video/bg-video.mp4" type="video/mp4">-->
|
||||
<!-- <source src="~/assets/video/bg-video.ogg" type="video/ogg">-->
|
||||
<!-- Your browser does not support HTML5 video.-->
|
||||
<!-- </video>-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="adaptive-modal" class="modal fade " tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-body">
|
||||
<div class=" bg-white " style="border-radius: 15px">
|
||||
<div class="row px-3">
|
||||
<div class="col-2">
|
||||
<span><i class="fa-solid fs-3 fa-arrow-left-long" style="cursor: pointer" data-bs-dismiss="modal" /></span>
|
||||
</div>
|
||||
<div class="col-8">
|
||||
<h4 class="text-center fw-bolder">
|
||||
{{ t('Adaptive soundscape') }}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<div class="form-check form-switch float-end">
|
||||
<input id="flexSwitchCheckDefault" class="form-check-input" type="checkbox" role="switch">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row px-2 pt-4">
|
||||
<div class="col-12 ps-3 fs-5" style="line-height: 25px">
|
||||
<p class="p-0 m-0">
|
||||
{{ t("The Mindboost sounscape responds to the acousitcs in your room.") }}
|
||||
</p>
|
||||
<p class="p-0 m-0" />
|
||||
<p class="">
|
||||
{{ t("Currently, your room has a:") }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row pt-4 ps-3">
|
||||
<div class="col-auto">
|
||||
<span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="52" height="54" viewBox="0 0 60 54" fill="none">
|
||||
<path d="M28.9879 0.600098C28.6223 0.600098 28.2613 0.675098 27.9004 0.787598C27.1738 1.01728 26.4801 1.44853 25.8754 2.0626L11.7754 16.1626H3.60039C1.59883 16.1626 -0.0371094 17.7985 -0.0371094 19.8001V34.2001C-0.0371094 36.2017 1.59883 37.8376 3.60039 37.8376H11.7754L25.8379 51.8251C27.1785 53.1704 28.8988 53.761 30.3004 53.1001C31.7113 52.4345 32.4004 50.8313 32.4004 48.9751V4.5751C32.4004 2.7751 31.4816 1.27978 30.0754 0.787598C29.7238 0.665723 29.3535 0.600098 28.9879 0.600098ZM29.0254 3.0001C29.1473 3.0001 29.241 3.00947 29.3254 3.0376C29.6676 3.15478 30.0004 3.45947 30.0004 4.5751V48.9751C30.0004 50.2548 29.5785 50.8267 29.2879 50.9626C28.9973 51.0985 28.4113 51.1126 27.4879 50.1751L12.3754 35.0626C12.352 35.0485 12.3238 35.0345 12.3004 35.0251C12.3895 35.1048 12.0004 34.3454 12.0004 33.9001V20.1001C12.0004 19.6548 12.4035 18.886 12.3004 18.9751C12.3285 18.9517 12.352 18.9282 12.3754 18.9001L27.5629 3.7501C28.1254 3.18291 28.6551 3.00479 29.0254 3.0001ZM45.9379 5.1751C45.4129 5.26416 45.0098 5.69072 44.9488 6.22041C44.8926 6.7501 45.1832 7.25635 45.6754 7.4626C52.8144 11.0626 57.6754 18.4454 57.6754 27.0001C57.6754 35.5548 52.8144 42.9376 45.6754 46.5376C45.2348 46.6876 44.9254 47.0813 44.8738 47.5407C44.8223 48.0048 45.0426 48.4548 45.441 48.6985C45.8348 48.9376 46.3363 48.9282 46.7254 48.6751C54.6473 44.6767 60.0754 36.4735 60.0754 27.0001C60.0754 17.5267 54.6473 9.32353 46.7254 5.3251C46.5191 5.20791 46.2848 5.15635 46.0504 5.1751C46.0129 5.1751 45.9754 5.1751 45.9379 5.1751ZM41.6629 11.5501C41.1285 11.6157 40.702 12.0329 40.6269 12.5673C40.5473 13.097 40.8332 13.6173 41.3254 13.8376C46.0082 16.3642 49.2004 21.2954 49.2004 27.0001C49.2004 32.686 46.0223 37.6313 41.3629 40.1626C40.7816 40.4813 40.5707 41.2126 40.8941 41.7938C41.2129 42.3751 41.9441 42.586 42.5254 42.2626C47.9348 39.3235 51.6004 33.5813 51.6004 27.0001C51.6004 20.4001 47.9254 14.6345 42.4879 11.7001C42.2723 11.5782 42.0238 11.5267 41.7754 11.5501C41.7379 11.5501 41.7004 11.5501 41.6629 11.5501ZM3.60039 18.6376H9.82539C9.6707 19.097 9.60039 19.5985 9.60039 20.1001V33.9001C9.60039 34.4017 9.67539 34.8985 9.82539 35.3626H3.60039C2.95352 35.3626 2.43789 34.847 2.43789 34.2001V19.8001C2.43789 19.1532 2.95352 18.6376 3.60039 18.6376ZM36.6379 19.0876C36.0566 19.1579 35.616 19.636 35.5879 20.222C35.5598 20.8032 35.9535 21.3235 36.5254 21.4501C38.9816 22.0923 40.8004 24.3329 40.8004 27.0001C40.8004 29.6673 38.9816 31.9079 36.5254 32.5501C35.8832 32.7142 35.4988 33.3704 35.6629 34.0126C35.827 34.6548 36.4832 35.0392 37.1254 34.8751C40.6176 33.961 43.2004 30.7642 43.2004 27.0001C43.2004 23.236 40.6176 20.0392 37.1254 19.1251C36.966 19.0782 36.802 19.0688 36.6379 19.0876Z" fill="#E9C046" />
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-11 col-md-10 ps-3">
|
||||
<h5>{{ t("Noisy Environment") }}</h5>
|
||||
<p>
|
||||
{{ t("The background noise at your workplace is disturbing. Your concentration is severely impaired. Mindboost protects you from the disturbing background noise.") }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row pt-3 ps-3 opacity-75">
|
||||
<div class="col-auto">
|
||||
<span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="52" height="54" viewBox="0 0 52 54" fill="none">
|
||||
<path d="M28.9879 0.600098C28.6223 0.600098 28.2613 0.675098 27.9004 0.787598C27.1738 1.01728 26.4801 1.44853 25.8754 2.0626L11.7754 16.1626H3.60039C1.59883 16.1626 -0.0371094 17.7985 -0.0371094 19.8001V34.2001C-0.0371094 36.2017 1.59883 37.8376 3.60039 37.8376H11.7754L25.8379 51.8251C27.1785 53.1704 28.8988 53.761 30.3004 53.1001C31.7113 52.4345 32.4004 50.8313 32.4004 48.9751V4.5751C32.4004 2.7751 31.4816 1.27978 30.0754 0.787598C29.7238 0.665723 29.3535 0.600098 28.9879 0.600098ZM29.0254 3.0001C29.1473 3.0001 29.241 3.00947 29.3254 3.0376C29.6676 3.15478 30.0004 3.45947 30.0004 4.5751V48.9751C30.0004 50.2548 29.5785 50.8267 29.2879 50.9626C28.9973 51.0985 28.4113 51.1126 27.4879 50.1751L12.3754 35.0626C12.352 35.0485 12.3238 35.0345 12.3004 35.0251C12.3895 35.1048 12.0004 34.3454 12.0004 33.9001V20.1001C12.0004 19.6548 12.4035 18.886 12.3004 18.9751C12.3285 18.9517 12.352 18.9282 12.3754 18.9001L27.5629 3.7501C28.1254 3.18291 28.6551 3.00479 29.0254 3.0001ZM41.6629 11.5501C41.1285 11.6157 40.702 12.0329 40.627 12.5673C40.5473 13.097 40.8332 13.6173 41.3254 13.8376C46.0082 16.3642 49.2004 21.2954 49.2004 27.0001C49.2004 32.686 46.0223 37.6313 41.3629 40.1626C40.7816 40.4813 40.5707 41.2126 40.8941 41.7938C41.2129 42.3751 41.9441 42.586 42.5254 42.2626C47.9348 39.3235 51.6004 33.5813 51.6004 27.0001C51.6004 20.4001 47.9254 14.6345 42.4879 11.7001C42.2723 11.5782 42.0238 11.5267 41.7754 11.5501C41.7379 11.5501 41.7004 11.5501 41.6629 11.5501ZM3.60039 18.6376H9.82539C9.6707 19.097 9.60039 19.5985 9.60039 20.1001V33.9001C9.60039 34.4017 9.67539 34.8985 9.82539 35.3626H3.60039C2.95352 35.3626 2.43789 34.847 2.43789 34.2001V19.8001C2.43789 19.1532 2.95352 18.6376 3.60039 18.6376ZM36.6379 19.0876C36.0566 19.1579 35.616 19.636 35.5879 20.222C35.5598 20.8032 35.9535 21.3235 36.5254 21.4501C38.9816 22.0923 40.8004 24.3329 40.8004 27.0001C40.8004 29.6673 38.9816 31.9079 36.5254 32.5501C35.8832 32.7142 35.4988 33.3704 35.6629 34.0126C35.827 34.6548 36.4832 35.0392 37.1254 34.8751C40.6176 33.961 43.2004 30.7642 43.2004 27.0001C43.2004 23.236 40.6176 20.0392 37.1254 19.1251C36.966 19.0782 36.802 19.0688 36.6379 19.0876Z" fill="#E9C046" />
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-11 col-md-10 ps-3">
|
||||
<h5>{{ t("Medium-noise Environment") }}</h5>
|
||||
<p>{{ t("The background noise at your workplace should be optimized. In the long term, it could disturb and have a negative impact on your health. Protect yourself with mindboost.") }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row pt-3 ps-3 opacity-50">
|
||||
<div class="col-auto">
|
||||
<span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="44" height="54" viewBox="0 0 44 54" fill="none">
|
||||
<path d="M28.9879 0.600098C28.6223 0.600098 28.2613 0.675098 27.9004 0.787598C27.1738 1.01728 26.4801 1.44853 25.8754 2.0626L11.7754 16.1626H3.60039C1.59883 16.1626 -0.0371094 17.7985 -0.0371094 19.8001V34.2001C-0.0371094 36.2017 1.59883 37.8376 3.60039 37.8376H11.7754L25.8379 51.8251C27.1785 53.1704 28.8988 53.761 30.3004 53.1001C31.7113 52.4345 32.4004 50.8313 32.4004 48.9751V4.5751C32.4004 2.7751 31.4816 1.27978 30.0754 0.787598C29.7238 0.665723 29.3535 0.600098 28.9879 0.600098ZM29.0254 3.0001C29.1473 3.0001 29.241 3.00947 29.3254 3.0376C29.6676 3.15478 30.0004 3.45947 30.0004 4.5751V48.9751C30.0004 50.2548 29.5785 50.8267 29.2879 50.9626C28.9973 51.0985 28.4113 51.1126 27.4879 50.1751L12.3754 35.0626C12.352 35.0485 12.3238 35.0345 12.3004 35.0251C12.3895 35.1048 12.0004 34.3454 12.0004 33.9001V20.1001C12.0004 19.6548 12.4035 18.886 12.3004 18.9751C12.3285 18.9517 12.352 18.9282 12.3754 18.9001L27.5629 3.7501C28.1254 3.18291 28.6551 3.00479 29.0254 3.0001ZM3.60039 18.6376H9.82539C9.6707 19.097 9.60039 19.5985 9.60039 20.1001V33.9001C9.60039 34.4017 9.67539 34.8985 9.82539 35.3626H3.60039C2.95352 35.3626 2.43789 34.847 2.43789 34.2001V19.8001C2.43789 19.1532 2.95352 18.6376 3.60039 18.6376ZM36.6379 19.0876C36.0566 19.1579 35.616 19.636 35.5879 20.222C35.5598 20.8032 35.9535 21.3235 36.5254 21.4501C38.9816 22.0923 40.8004 24.3329 40.8004 27.0001C40.8004 29.6673 38.9816 31.9079 36.5254 32.5501C35.8832 32.7142 35.4988 33.3704 35.6629 34.0126C35.827 34.6548 36.4832 35.0392 37.1254 34.8751C40.6176 33.961 43.2004 30.7642 43.2004 27.0001C43.2004 23.236 40.6176 20.0392 37.1254 19.1251C36.966 19.0782 36.802 19.0688 36.6379 19.0876Z" fill="#E9C046" />
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-11 col-md-10 ps-3">
|
||||
<h5>{{ t("Good Environment") }}</h5>
|
||||
<p>
|
||||
{{ t("The background noise at your workplace provides a long’term healthy basis for concentrated work. With Mindboost you make sure that even sudden disturbances do not distract you.") }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import NoiseMusicGainTropics from '../components/experiments/homepages/NoiseMusicGainTropics'
|
||||
import HomeBar from '../components/homebar'
|
||||
|
||||
export default {
|
||||
components: { HomeBar, NoiseMusicGainTropics },
|
||||
setup () {
|
||||
definePageMeta({
|
||||
middleware: 'auth'
|
||||
})
|
||||
const { t } = useI18n()
|
||||
const localePath = useLocalePath()
|
||||
|
||||
return {
|
||||
t,
|
||||
localePath
|
||||
}
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
bar_width: 0,
|
||||
analyser: null,
|
||||
dataArray: null,
|
||||
bar_val: 25
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// Berechnete Eigenschaft für die dynamische Übersetzung des Titels
|
||||
title () {
|
||||
return this.$t('Tropics')
|
||||
}
|
||||
},
|
||||
beforeUnmount () {
|
||||
},
|
||||
|
||||
async mounted () {
|
||||
/* await useMicStore().getMicrophone().then((mic) => {
|
||||
const audioCtx = mic.microphoneNode.context
|
||||
this.analyser = audioCtx.createAnalyser()
|
||||
mic.microphoneNode.connect(this.analyser)
|
||||
this.analyser.fftSize = 2048
|
||||
const bufferLength = this.analyser.frequencyBinCount
|
||||
this.dataArray = new Uint8Array(bufferLength)
|
||||
this.updateMeter()
|
||||
}) */
|
||||
},
|
||||
methods: {
|
||||
handleAnimation: function (anim) {
|
||||
this.anim = anim
|
||||
},
|
||||
|
||||
updateMeter () {
|
||||
requestAnimationFrame(this.updateMeter)
|
||||
this.analyser.getByteFrequencyData(this.dataArray)
|
||||
const rms = this.getRMS(this.dataArray)
|
||||
let level = 20 * Math.log10(rms / 128)
|
||||
level = Math.max(0, Math.min(100, level + 100))
|
||||
// bar.style.width = level + '%';
|
||||
|
||||
this.bar_width = level
|
||||
},
|
||||
getRMS (dataArray) {
|
||||
let rms = 0
|
||||
for (let i = 0; i < dataArray.length; i++) {
|
||||
rms += dataArray[i] * dataArray[i]
|
||||
}
|
||||
rms /= dataArray.length
|
||||
rms = Math.sqrt(rms)
|
||||
return rms
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#myVideo{
|
||||
position: fixed;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
min-width: 100%;
|
||||
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
z-index: -99;
|
||||
}
|
||||
.adaptive{
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
.rnboplayer{
|
||||
position:sticky;
|
||||
width: 225px;
|
||||
height: inherit;
|
||||
display:flex;
|
||||
float: bottom;
|
||||
}
|
||||
|
||||
</style>
|
283
pages/index.vue
Normal file
283
pages/index.vue
Normal file
@@ -0,0 +1,283 @@
|
||||
<template>
|
||||
<div id="step-6">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-12 px-0 mx-0">
|
||||
<div class="video-wrapper">
|
||||
<!-- Poster -->
|
||||
<img
|
||||
v-if="!videoLoaded"
|
||||
:src="posterPath"
|
||||
class="background-poster"
|
||||
alt="Poster"
|
||||
>
|
||||
<!-- Video -->
|
||||
<transition name="fade">
|
||||
<video
|
||||
v-if="showVideo && videoPath"
|
||||
:key="videoPath"
|
||||
class="background-video"
|
||||
:src="videoPath"
|
||||
autoplay
|
||||
muted
|
||||
loop
|
||||
playsinline
|
||||
@loadedmetadata="handleFadeIn"
|
||||
/>
|
||||
</transition>
|
||||
|
||||
<div class="overlay-gradient" />
|
||||
|
||||
<div class="page-content">
|
||||
<PageHeader
|
||||
:title="currentTitle"
|
||||
/>
|
||||
<div v-if="showOverlay" class="overlay">
|
||||
<button
|
||||
class="btn btn-primary btn-lg"
|
||||
style="background-color:#e9c046; border:none; color:black;"
|
||||
@click="unlockAudio"
|
||||
>
|
||||
{{ t('Ready') }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="container-fluid">
|
||||
<div class="row justify-content-center">
|
||||
<NavigationBar
|
||||
v-model:current-index="currentIndex"
|
||||
:current-soundscape="currentTitle"
|
||||
:adaptive="adaptive"
|
||||
@update:soundscape="changeSoundscape"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref, watch, computed, nextTick, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useAudioStore } from '@/stores/audio'
|
||||
import { useMicStore } from '@/stores/microphone'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { getSoundcapeList } from '~/tracks.config.ts'
|
||||
import PageHeader from '@/components/PageHeader.vue'
|
||||
import NavigationBar from '@/components/NavigationBar.vue'
|
||||
// import VideoBackground from '@/components/VideoBackground.vue'
|
||||
definePageMeta({
|
||||
middleware: 'auth'
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
const router = useRouter()
|
||||
const audioStore = useAudioStore()
|
||||
const userStore = useUserStore()
|
||||
const { $axios } = useNuxtApp()
|
||||
|
||||
// States
|
||||
const isReady = ref(false)
|
||||
const showOverlay = ref(false)
|
||||
|
||||
const soundscapes = getSoundcapeList()
|
||||
const defaultSoundscape = 'Lagoon'
|
||||
|
||||
const currentIndex = userStore.user?.settings?.soundscape
|
||||
? ref(soundscapes.indexOf(userStore.user.settings.soundscape))
|
||||
: ref(0)
|
||||
|
||||
// Soundscape aus Store oder Fallback
|
||||
const adaptive = userStore.user?.settings?.adaptive_sound_scape || 'no'
|
||||
|
||||
// Computed
|
||||
const currentTitle = computed(() => userStore.user?.settings?.soundscape || defaultSoundscape)
|
||||
const videoPath = computed(() => `/video/${currentTitle.value}.mp4`)
|
||||
const posterPath = computed(() => `/images/posters/${currentTitle.value}.jpg`)
|
||||
|
||||
const showVideo = ref(true)
|
||||
const videoLoaded = ref(false)
|
||||
|
||||
// Watcher für AudioContext-Zustand
|
||||
watch(() => audioStore.audioContext?.state, (newState) => {
|
||||
showOverlay.value = newState === 'suspended'
|
||||
})
|
||||
|
||||
// Wechsel Video bei Klangänderung
|
||||
watch(currentIndex, async () => {
|
||||
videoLoaded.value = false
|
||||
showVideo.value = false
|
||||
await nextTick()
|
||||
showVideo.value = true
|
||||
})
|
||||
|
||||
// Methoden
|
||||
function changeSoundscape (newSoundscape) {
|
||||
const map = {
|
||||
Lagoon: 0,
|
||||
Tropics: 1,
|
||||
Forest: 2,
|
||||
Meadow: 3
|
||||
}
|
||||
currentIndex.value = map[newSoundscape.soundscape] ?? 0
|
||||
unmuteActive()
|
||||
|
||||
// Speichern der Einstellungen im Backend
|
||||
$axios.post('/api/update-setting', newSoundscape).then((response) => {
|
||||
if (soundscapes.includes(newSoundscape.soundscape)) { userStore.user.settings.soundscape = newSoundscape.soundscape }
|
||||
if (response.status !== 200) {
|
||||
// wenn der request fehlgeschlagen hat aus welchen gründen auch immer,
|
||||
// z.B. keine Internetverbindung, wird ein Interval angeschmissen, dass alle 30 Sekunden probiert die settings zu aktualisieren
|
||||
// bis es am Ende mal klappt
|
||||
} else {
|
||||
// reponse is not 200
|
||||
|
||||
}
|
||||
}).catch((e) => {
|
||||
useNuxtApp().$toast.error('save: something went wrong while saving...')
|
||||
})
|
||||
}
|
||||
|
||||
function unmuteActive () {
|
||||
audioStore.setPlaying(false)
|
||||
setTimeout(() => {
|
||||
audioStore.setPlaying(true)
|
||||
}, 50)
|
||||
}
|
||||
|
||||
async function unlockAudio () {
|
||||
await audioStore.ensureAudioContextRunning()
|
||||
}
|
||||
|
||||
function handleFadeIn (e) {
|
||||
e.target.style.opacity = 1
|
||||
videoLoaded.value = true
|
||||
}
|
||||
|
||||
async function fetchSettings () {
|
||||
try {
|
||||
const { data } = await useNuxtApp().$axios.post('/api/fetch-settings')
|
||||
if (data.success) {
|
||||
userStore.settings = data.setting
|
||||
const soundscape = data.setting.soundscape || 'Lagoon'
|
||||
const index = soundscapes.indexOf(soundscape)
|
||||
|
||||
currentIndex.value = index >= 0 ? index : 0 // jetzt erst setzen
|
||||
// audioStore.changeTrack(soundscape)
|
||||
}
|
||||
} catch (error) {
|
||||
useNuxtApp().$logger.error('Nothing fetched from the backend, please reload and try again.')
|
||||
}
|
||||
}
|
||||
|
||||
// Lifecycle
|
||||
onMounted(async () => {
|
||||
showOverlay.value = true
|
||||
await useNuxtApp().$audioPrepared
|
||||
showOverlay.value = false
|
||||
|
||||
await fetchSettings()
|
||||
|
||||
isReady.value = true
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
useAudioStore().stopAudioContextMonitor()
|
||||
useMicStore().detachMicrophone()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
.video-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.background-poster,
|
||||
.background-video {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.background-video {
|
||||
opacity: 0;
|
||||
transition: opacity 1s ease-in-out;
|
||||
}
|
||||
|
||||
.page-content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.fade-enter-active, .fade-leave-active {
|
||||
transition: opacity 1.5s ease-in-out;
|
||||
}
|
||||
.fade-enter-from, .fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.adaptive{
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
.rnboplayer{
|
||||
position:sticky;
|
||||
width: 225px;
|
||||
height: inherit;
|
||||
display:flex;
|
||||
float: bottom;
|
||||
}
|
||||
.overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: white;
|
||||
font-size: 24px;
|
||||
z-index: 1000;
|
||||
}
|
||||
.overlay button {
|
||||
padding: 10px 20px;
|
||||
font-size: 18px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.bg-gradient {
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
background: rgb(153,166,188);
|
||||
background: linear-gradient(0deg, rgba(153,166,188,0) 0%, rgba(153,166,188,0.413624824929972) 55%, rgba(153,166,188,1) 100%);
|
||||
height: 40vh;
|
||||
}
|
||||
}
|
||||
</style>
|
40
pages/index2.vue
Normal file
40
pages/index2.vue
Normal file
@@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<div class="container-fluid">
|
||||
<HomeBar />
|
||||
<div v-if="soundscapeSelected === 'Forest'">
|
||||
Forest
|
||||
<MusicGainForest />
|
||||
</div>
|
||||
<div v-if="soundscapeSelected === 'Lagoon'">
|
||||
Laggon
|
||||
<MusicGainLagoon />
|
||||
</div>
|
||||
<div @click="changeSoundscape">
|
||||
Change
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import HomeBar from '@/components/homebar.vue'
|
||||
import MusicGainForest from '~/components/experiments/homepages/MusicGainForest.vue'
|
||||
import MusicGainLagoon from '~/components/experiments/homepages/MusicGainLagoon.vue'
|
||||
|
||||
export default {
|
||||
components: { HomeBar, MusicGainForest, MusicGainLagoon },
|
||||
data () {
|
||||
return {
|
||||
soundscapeSelected: 'Lagoon'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
changeSoundscape () {
|
||||
if (this.soundscapeSelected === 'Lagoon') {
|
||||
this.soundscapeSelected = 'Forest'
|
||||
} else {
|
||||
this.soundscapeSelected = 'Lagoon'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
170
pages/onboarding.vue
Normal file
170
pages/onboarding.vue
Normal file
@@ -0,0 +1,170 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-12 px-0 mx-0">
|
||||
<video-background
|
||||
src="/video/bg-video.mp4"
|
||||
style=" height: 100vh;"
|
||||
poster="/images/poster.png"
|
||||
>
|
||||
<div class="container-fluid pt-3 px-lg-4">
|
||||
<div class="header d-flex flex-column flex-fill flex-md-row justify-content-between">
|
||||
<div class="header__logo pt-md-1 pt-lg-1">
|
||||
<nuxt-link class="navbar-brand" to="/">
|
||||
<div class="text-center text-md-start">
|
||||
<img src="/mindboostlogo.svg" height="35" class="img " alt="imae">
|
||||
</div>
|
||||
</nuxt-link>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content-wrapper">
|
||||
<component :is="currentStep" @next-step="nextStep" @previous-step="previousStep" />
|
||||
</div>
|
||||
<div class="nav-buttons row pt-5 mt-0 mt-sm-4">
|
||||
<div class="col-12 text-center pt-5 mt-3 pb-2 mb-2">
|
||||
<button
|
||||
v-for="(step, index) in steps"
|
||||
:key="index"
|
||||
class="btn btn-bullet mx-1"
|
||||
:class="{ 'is-active': currentStepIndex === index }"
|
||||
@click="goToStep(index)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</video-background>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapActions } from 'pinia'
|
||||
import { ref, markRaw } from 'vue'
|
||||
import Onboarding1 from './onboarding/onboarding1.vue'
|
||||
import Onboarding2 from './onboarding/onboarding2.vue'
|
||||
import Onboarding3 from './onboarding/onboarding3.vue'
|
||||
import Onboarding4 from './onboarding/onboarding4.vue'
|
||||
import Onboarding5 from './onboarding/onboarding5.vue'
|
||||
// import Onboarding6 from './onboarding/onboarding6.vue'
|
||||
import { useMicStore } from '@/stores/microphone'
|
||||
import { useAudioStore } from '@/stores/audio'
|
||||
import { useCounterStore } from '@/stores/counter'
|
||||
export default {
|
||||
setup () {
|
||||
const { t } = useI18n()
|
||||
const localePath = useLocalePath()
|
||||
const currentStepIndex = ref(0)
|
||||
const steps = [
|
||||
markRaw(Onboarding1),
|
||||
markRaw(Onboarding2),
|
||||
markRaw(Onboarding3),
|
||||
markRaw(Onboarding4),
|
||||
markRaw(Onboarding5)
|
||||
]
|
||||
|
||||
const currentStep = computed(() => steps[currentStepIndex.value])
|
||||
|
||||
const nextStep = () => {
|
||||
if (currentStepIndex.value < steps.length - 1) {
|
||||
currentStepIndex.value++
|
||||
}
|
||||
}
|
||||
|
||||
const previousStep = () => {
|
||||
if (currentStepIndex.value > 0) {
|
||||
currentStepIndex.value--
|
||||
}
|
||||
}
|
||||
|
||||
const goToStep = (index) => {
|
||||
currentStepIndex.value = index
|
||||
}
|
||||
|
||||
return {
|
||||
t,
|
||||
localePath,
|
||||
currentStep,
|
||||
currentStepIndex,
|
||||
steps,
|
||||
nextStep,
|
||||
previousStep,
|
||||
goToStep
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
bar_val: 25,
|
||||
bar_width: 0,
|
||||
analyser: null,
|
||||
dataArray: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(useCounterStore, ['count']),
|
||||
...mapState(useAudioStore, ['audioContext']),
|
||||
isFixProblemRoute () {
|
||||
return !this.$route.name.includes('fixproblem')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.bar{
|
||||
background-color: #e9c046 !important;
|
||||
}
|
||||
.checklabel{
|
||||
background-color: white !important;
|
||||
width: 150px ;
|
||||
height: 134px ;
|
||||
}
|
||||
.px-4{
|
||||
transition: 1s;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
background-color: rgba(255, 255, 255, 0.6);
|
||||
border-radius: 10px;
|
||||
padding: 4em;
|
||||
max-width: 1120px;
|
||||
margin: 100px auto 1.5em;
|
||||
min-height: 50vh;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1024px) {
|
||||
.content-wrapper {
|
||||
padding: 4em;
|
||||
margin-top: 70px;
|
||||
max-height: 80vh;
|
||||
}
|
||||
}
|
||||
@media only screen and (max-width: 768px) {
|
||||
.content-wrapper {
|
||||
padding: 4em;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 576px) {
|
||||
.content-wrapper {
|
||||
padding: 2em 1em;
|
||||
margin: 4.5em auto 1.5em;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-buttons {
|
||||
position: fixed;
|
||||
bottom: 0px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: rgb(255,255,255);
|
||||
background: linear-gradient(0deg, rgba(255,255,255,1) 20%, rgba(255,255,255,0) 100%);
|
||||
}
|
||||
.checkmark {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
}
|
||||
</style>
|
191
pages/onboarding/onboarding1.vue
Normal file
191
pages/onboarding/onboarding1.vue
Normal file
@@ -0,0 +1,191 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="row text-center">
|
||||
<div class="col-12">
|
||||
<h1 class="h3 fw-bold mb-4">
|
||||
{{ t("How is your audio hardware connected?") }}
|
||||
</h1>
|
||||
<p class="text-muted mx-auto">
|
||||
{{ t('Onboarding-input') }}<br>
|
||||
{{ t('Onboarding-output') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center pt-4">
|
||||
<div class="col-11">
|
||||
<form v-if="!loading">
|
||||
<div class="row justify-content-center mb-4">
|
||||
<div class="col-md-5 text-center">
|
||||
<label for="inputSelect" class="fw-semibold">{{ t('Input device:') }}</label>
|
||||
<p class="pt-0 mt-0 text-muted pb-2 mb-0" style="font-size: 14px;font-weight: 500">
|
||||
({{ t('select laptop or mobile device microphone') }})
|
||||
</p>
|
||||
<select id="inputSelect" v-model="selectedInput" class="form-select pt-1 mt-0 select-box" @change="checkIfSameDevice">
|
||||
<option v-for="(item,index) in audioInputDevices" :key="index" :value="index">
|
||||
{{ item.label || `Eingabegerät ${index + 1}` }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row justify-content-center mb-4">
|
||||
<div class="col-md-5 text-center">
|
||||
<label for="outputSelect" class="fw-semibold">{{ t('Output device:') }}</label>
|
||||
<p class="pt-0 mt-0 text-muted pb-0 mb-0" style="font-size: 14px;font-weight: 500">
|
||||
{{ t('select headphones or headphone output') }}
|
||||
</p>
|
||||
<select id="outputSelect" v-model="selectedOutput" class="form-select pt-1 mt-0 select-box" @change="checkIfSameDevice">
|
||||
<option v-for="(item,index) in audioOutputDevices" :key="index" :value="index">
|
||||
{{ item.label || `Ausgabegerät ${index + 1}` }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row justify-content-center pt-4">
|
||||
<div class="col-md-3 text-center" style="z-index: 1000000;">
|
||||
<button
|
||||
type="button"
|
||||
class="btn col-4 btn-primary-custom"
|
||||
:disabled="audioInputDevices.length === 0 || audioOutputDevices.length === 0"
|
||||
@click.prevent="saveDevices"
|
||||
>
|
||||
{{ t("Next") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div v-else class="text-center">
|
||||
<p>Lade Geräte...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapState, mapActions } from 'pinia'
|
||||
import { useUserStore } from '~/stores/user'
|
||||
|
||||
export default {
|
||||
emits: ['next-step'],
|
||||
setup () {
|
||||
const { t } = useI18n()
|
||||
const localePath = useLocalePath()
|
||||
|
||||
return {
|
||||
t,
|
||||
localePath
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
loading: true,
|
||||
audioInputDevices: [],
|
||||
audioOutputDevices: [],
|
||||
selectedInput: null,
|
||||
selectedOutput: null,
|
||||
stream: null
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState(useUserStore, ['audioInputDevice', 'audioOutputDevice'])
|
||||
|
||||
},
|
||||
async created () {
|
||||
try {
|
||||
// Zuerst Media-Zugriff einholen (sonst keine Labels)
|
||||
await this.getUserMedia()
|
||||
|
||||
const devices = await navigator.mediaDevices.enumerateDevices()
|
||||
|
||||
this.audioInputDevices = devices.filter(d => d.kind === 'audioinput')
|
||||
this.audioOutputDevices = devices.filter(d => d.kind === 'audiooutput')
|
||||
|
||||
// Setze Indexe basierend auf gespeicherten IDs
|
||||
this.selectedInput = this.audioInputDevices.findIndex(d => d.deviceId === this.audioInputDevice.deviceId)
|
||||
this.selectedOutput = this.audioOutputDevices.findIndex(d => d.deviceId === this.audioOutputDevice.deviceId)
|
||||
|
||||
// Fallback auf erstes Gerät
|
||||
if (this.selectedInput < 0) { this.selectedInput = 0 }
|
||||
if (this.selectedOutput < 0) { this.selectedOutput = 0 }
|
||||
|
||||
// Warnung, wenn gleiche Geräte gewählt sind
|
||||
if (this.checkIfSameDevice()) {
|
||||
this.$toast.warning(this.t('bluetoothWarning'), {
|
||||
duration: 6000,
|
||||
pauseOnHover: true,
|
||||
dismissible: true,
|
||||
queue: true,
|
||||
position: 'bottom-left'
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
this.$toast.error(this.t('Could not access audio devices.'))
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useUserStore, ['saveInputdevice', 'saveOutputDevice']),
|
||||
|
||||
normalizeDeviceName (deviceName) {
|
||||
return deviceName
|
||||
.replace('Standard - ', '') // Remove 'Standard - '
|
||||
.replace(/ \(.*\)$/, '') // Remove any content in parentheses
|
||||
.trim()
|
||||
},
|
||||
checkIfSameDevice () {
|
||||
const input = this.audioInputDevices[this.selectedInput]
|
||||
const output = this.audioOutputDevices[this.selectedOutput]
|
||||
|
||||
if (!input?.label || !output?.label) { return false }
|
||||
|
||||
return this.normalizeDeviceName(input.label) === this.normalizeDeviceName(output.label)
|
||||
},
|
||||
saveDevices () {
|
||||
this.saveInputdevice(this.audioInputDevices[this.selectedInput])
|
||||
this.saveOutputDevice(this.audioOutputDevices[this.selectedOutput])
|
||||
this.$emit('next-step')
|
||||
},
|
||||
async getUserMedia () {
|
||||
try {
|
||||
return await navigator.mediaDevices.getUserMedia({ audio: true })
|
||||
} catch (error) {
|
||||
this.$logger('keine Mikrofon-Berechtigung erteilt.')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.bar{
|
||||
background-color: #e9c046;
|
||||
}
|
||||
|
||||
.select-box{
|
||||
border: 2px solid #e9c046;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.select-box:focus{
|
||||
border: 2px solid #e9c046;
|
||||
box-shadow: none;
|
||||
|
||||
}
|
||||
|
||||
.btn-primary-custom {
|
||||
background-color: #e9c046;
|
||||
border-color: #e9c046;
|
||||
color: white;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.btn-primary-custom:hover, .btn-primary-custom:focus{
|
||||
background-color: transparent;
|
||||
border-color: #e9c046;
|
||||
color: #e9c046;
|
||||
}
|
||||
</style>
|
117
pages/onboarding/onboarding2.vue
Normal file
117
pages/onboarding/onboarding2.vue
Normal file
File diff suppressed because one or more lines are too long
132
pages/onboarding/onboarding3.vue
Normal file
132
pages/onboarding/onboarding3.vue
Normal file
@@ -0,0 +1,132 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="row text-center">
|
||||
<div class="col-12">
|
||||
<h1 class="h3 fw-bold mb-4">
|
||||
{{ t('Do your headphones have ANC?') }}
|
||||
</h1>
|
||||
</div>
|
||||
<div class="col-12 pt-4">
|
||||
<div class="d-flex justify-content-center">
|
||||
<div class="checkmark pt-2 px-1 d-inline-block" @click.prevent="saveSetting('Yes')">
|
||||
<input id="danger-outlined1" type="radio" class="btn-check checkmark" name="options-outlined" autocomplete="off">
|
||||
<label class="btn checklabel ms-0 ms-sm-3 pt-4" for="danger-outlined1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="60" height="60" viewBox="0 0 60 46" fill="none">
|
||||
<path d="M15.4874 0.162403C15.4358 0.171779 15.3843 0.18584 15.3374 0.199903C14.7796 0.326466 14.3858 0.828029 14.3999 1.3999V44.5999C14.3952 45.0312 14.6202 45.4343 14.9952 45.6546C15.3702 45.8702 15.8296 45.8702 16.2046 45.6546C16.5796 45.4343 16.8046 45.0312 16.7999 44.5999V1.3999C16.8139 1.05303 16.6733 0.720216 16.4249 0.48584C16.1718 0.246778 15.8296 0.12959 15.4874 0.162403ZM44.2874 0.162403C44.2358 0.171779 44.1843 0.18584 44.1374 0.199903C43.5796 0.326466 43.1858 0.828029 43.1999 1.3999V44.5999C43.1952 45.0312 43.4202 45.4343 43.7952 45.6546C44.1702 45.8702 44.6296 45.8702 45.0046 45.6546C45.3796 45.4343 45.6046 45.0312 45.5999 44.5999V1.3999C45.6139 1.05303 45.4733 0.720216 45.2249 0.48584C44.9718 0.246778 44.6296 0.12959 44.2874 0.162403ZM8.28738 10.9624C8.23581 10.9718 8.18425 10.9858 8.13738 10.9999C7.57956 11.1265 7.18581 11.628 7.19988 12.1999V33.7999C7.19519 34.2312 7.42019 34.6343 7.79519 34.8546C8.17019 35.0702 8.62956 35.0702 9.00456 34.8546C9.37956 34.6343 9.60456 34.2312 9.59988 33.7999V12.1999C9.61394 11.853 9.47331 11.5202 9.22488 11.2858C8.97175 11.0468 8.62956 10.9296 8.28738 10.9624ZM51.4874 10.9624C51.4358 10.9718 51.3843 10.9858 51.3374 10.9999C50.7796 11.1265 50.3858 11.628 50.3999 12.1999V33.7999C50.3952 34.2312 50.6202 34.6343 50.9952 34.8546C51.3702 35.0702 51.8296 35.0702 52.2046 34.8546C52.5796 34.6343 52.8046 34.2312 52.7999 33.7999V12.1999C52.8139 11.853 52.6733 11.5202 52.4249 11.2858C52.1718 11.0468 51.8296 10.9296 51.4874 10.9624ZM22.6874 12.1624C22.6358 12.1718 22.5843 12.1858 22.5374 12.1999C21.9796 12.3265 21.5858 12.828 21.5999 13.3999V22.9999V32.5999C21.5952 33.0312 21.8202 33.4343 22.1952 33.6546C22.5702 33.8702 23.0296 33.8702 23.4046 33.6546C23.7796 33.4343 24.0046 33.0312 23.9999 32.5999V13.3999C24.0139 13.053 23.8733 12.7202 23.6249 12.4858C23.3718 12.2468 23.0296 12.1296 22.6874 12.1624ZM37.0874 12.1624C37.0358 12.1718 36.9843 12.1858 36.9374 12.1999C36.3796 12.3265 35.9858 12.828 35.9999 13.3999V32.5999C35.9952 33.0312 36.2202 33.4343 36.5952 33.6546C36.9702 33.8702 37.4296 33.8702 37.8046 33.6546C38.1796 33.4343 38.4046 33.0312 38.3999 32.5999V13.3999C38.4139 13.053 38.2733 12.7202 38.0249 12.4858C37.7718 12.2468 37.4296 12.1296 37.0874 12.1624ZM1.08738 18.1624C1.03581 18.1718 0.984252 18.1858 0.937377 18.1999C0.379564 18.3265 -0.0141859 18.828 -0.000123334 19.3999V26.5999C-0.00481083 27.0312 0.220189 27.4343 0.59519 27.6546C0.97019 27.8702 1.42956 27.8702 1.80456 27.6546C2.17956 27.4343 2.40456 27.0312 2.39988 26.5999V19.3999C2.41394 19.053 2.27331 18.7202 2.02488 18.4858C1.77175 18.2468 1.42956 18.1296 1.08738 18.1624ZM29.8874 18.1624C29.8358 18.1718 29.7843 18.1858 29.7374 18.1999C29.1796 18.3265 28.7858 18.828 28.7999 19.3999V26.5999C28.7952 27.0312 29.0202 27.4343 29.3952 27.6546C29.7702 27.8702 30.2296 27.8702 30.6046 27.6546C30.9796 27.4343 31.2046 27.0312 31.1999 26.5999V19.3999C31.2139 19.053 31.0733 18.7202 30.8249 18.4858C30.5718 18.2468 30.2296 18.1296 29.8874 18.1624ZM58.6874 18.1624C58.6358 18.1718 58.5843 18.1858 58.5374 18.1999C57.9796 18.3265 57.5858 18.828 57.5999 19.3999V26.5999C57.5952 27.0312 57.8202 27.4343 58.1952 27.6546C58.5702 27.8702 59.0296 27.8702 59.4046 27.6546C59.7796 27.4343 60.0046 27.0312 59.9999 26.5999V19.3999C60.0139 19.053 59.8733 18.7202 59.6249 18.4858C59.3718 18.2468 59.0296 18.1296 58.6874 18.1624Z" fill="white" />
|
||||
</svg>
|
||||
<p class="text-center">{{ t("Yes") }}</p>
|
||||
</label>
|
||||
</div>
|
||||
<div class="checkmark pt-2 px-1 d-block d-sm-inline-block d-inline-block" @click.prevent="saveSetting('No')">
|
||||
<input id="success-outlined1" type="radio" class="btn-check" name="options-outlined" autocomplete="off">
|
||||
<label class="btn pt-4 checklabel" for="success-outlined1">
|
||||
<svg width="60" height="60" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_73_763)">
|
||||
<path d="M15.4874 7.1624C15.4358 7.17178 15.3843 7.18584 15.3374 7.1999C14.7796 7.32647 14.3858 7.82803 14.3999 8.3999V51.5999C14.3952 52.0312 14.6202 52.4343 14.9952 52.6546C15.3702 52.8702 15.8296 52.8702 16.2046 52.6546C16.5796 52.4343 16.8046 52.0312 16.7999 51.5999V8.3999C16.8139 8.05303 16.6733 7.72022 16.4249 7.48584C16.1718 7.24678 15.8296 7.12959 15.4874 7.1624ZM44.2874 7.1624C44.2358 7.17178 44.1843 7.18584 44.1374 7.1999C43.5796 7.32647 43.1858 7.82803 43.1999 8.3999V51.5999C43.1952 52.0312 43.4202 52.4343 43.7952 52.6546C44.1702 52.8702 44.6296 52.8702 45.0046 52.6546C45.3796 52.4343 45.6046 52.0312 45.5999 51.5999V8.3999C45.6139 8.05303 45.4733 7.72022 45.2249 7.48584C44.9718 7.24678 44.6296 7.12959 44.2874 7.1624ZM8.28738 17.9624C8.23581 17.9718 8.18425 17.9858 8.13738 17.9999C7.57956 18.1265 7.18581 18.628 7.19988 19.1999V40.7999C7.19519 41.2312 7.42019 41.6343 7.79519 41.8546C8.17019 42.0702 8.62956 42.0702 9.00456 41.8546C9.37956 41.6343 9.60456 41.2312 9.59988 40.7999V19.1999C9.61394 18.853 9.47331 18.5202 9.22488 18.2858C8.97175 18.0468 8.62956 17.9296 8.28738 17.9624ZM51.4874 17.9624C51.4358 17.9718 51.3843 17.9858 51.3374 17.9999C50.7796 18.1265 50.3858 18.628 50.3999 19.1999V40.7999C50.3952 41.2312 50.6202 41.6343 50.9952 41.8546C51.3702 42.0702 51.8296 42.0702 52.2046 41.8546C52.5796 41.6343 52.8046 41.2312 52.7999 40.7999V19.1999C52.8139 18.853 52.6733 18.5202 52.4249 18.2858C52.1718 18.0468 51.8296 17.9296 51.4874 17.9624ZM22.6874 19.1624C22.6358 19.1718 22.5843 19.1858 22.5374 19.1999C21.9796 19.3265 21.5858 19.828 21.5999 20.3999V29.9999V39.5999C21.5952 40.0312 21.8202 40.4343 22.1952 40.6546C22.5702 40.8702 23.0296 40.8702 23.4046 40.6546C23.7796 40.4343 24.0046 40.0312 23.9999 39.5999V20.3999C24.0139 20.053 23.8733 19.7202 23.6249 19.4858C23.3718 19.2468 23.0296 19.1296 22.6874 19.1624ZM37.0874 19.1624C37.0358 19.1718 36.9843 19.1858 36.9374 19.1999C36.3796 19.3265 35.9858 19.828 35.9999 20.3999V39.5999C35.9952 40.0312 36.2202 40.4343 36.5952 40.6546C36.9702 40.8702 37.4296 40.8702 37.8046 40.6546C38.1796 40.4343 38.4046 40.0312 38.3999 39.5999V20.3999C38.4139 20.053 38.2733 19.7202 38.0249 19.4858C37.7718 19.2468 37.4296 19.1296 37.0874 19.1624ZM1.08738 25.1624C1.03581 25.1718 0.984252 25.1858 0.937377 25.1999C0.379564 25.3265 -0.0141859 25.828 -0.000123334 26.3999V33.5999C-0.00481083 34.0312 0.220189 34.4343 0.59519 34.6546C0.97019 34.8702 1.42956 34.8702 1.80456 34.6546C2.17956 34.4343 2.40456 34.0312 2.39988 33.5999V26.3999C2.41394 26.053 2.27331 25.7202 2.02488 25.4858C1.77175 25.2468 1.42956 25.1296 1.08738 25.1624ZM29.8874 25.1624C29.8358 25.1718 29.7843 25.1858 29.7374 25.1999C29.1796 25.3265 28.7858 25.828 28.7999 26.3999V33.5999C28.7952 34.0312 29.0202 34.4343 29.3952 34.6546C29.7702 34.8702 30.2296 34.8702 30.6046 34.6546C30.9796 34.4343 31.2046 34.0312 31.1999 33.5999V26.3999C31.2139 26.053 31.0733 25.7202 30.8249 25.4858C30.5718 25.2468 30.2296 25.1296 29.8874 25.1624ZM58.6874 25.1624C58.6358 25.1718 58.5843 25.1858 58.5374 25.1999C57.9796 25.3265 57.5858 25.828 57.5999 26.3999V33.5999C57.5952 34.0312 57.8202 34.4343 58.1952 34.6546C58.5702 34.8702 59.0296 34.8702 59.4046 34.6546C59.7796 34.4343 60.0046 34.0312 59.9999 33.5999V26.3999C60.0139 26.053 59.8733 25.7202 59.6249 25.4858C59.3718 25.2468 59.0296 25.1296 58.6874 25.1624Z" fill="white" />
|
||||
<rect
|
||||
x="5.47949"
|
||||
y="2.36826"
|
||||
width="73.529"
|
||||
height="4.4"
|
||||
rx="2.2"
|
||||
transform="rotate(45 5.47949 2.36826)"
|
||||
fill="white"
|
||||
stroke="#E9C046"
|
||||
stroke-width="2"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_73_763">
|
||||
<rect width="60" height="60" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
<p class=" text-center">{{ t("No") }}</p>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapState, mapActions } from 'pinia'
|
||||
import { useCounterStore } from '@/stores/counter'
|
||||
import { useAudioStore } from '@/stores/audio'
|
||||
import { ANC } from '@/stores/interfaces/ANC'
|
||||
export default {
|
||||
emits: ['next-step'],
|
||||
setup () {
|
||||
const { t } = useI18n()
|
||||
const localePath = useLocalePath()
|
||||
|
||||
return {
|
||||
t,
|
||||
localePath
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
bar_val: 25,
|
||||
form: {
|
||||
anc_type: 'No'
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: { ...mapState(useCounterStore, ['count']) },
|
||||
...mapState(useAudioStore, ['ancDevice']),
|
||||
mounted () {
|
||||
this.increment(50)
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useCounterStore, ['increment']),
|
||||
saveSetting (value) {
|
||||
this.form.anc_type = value
|
||||
if (value === 'Yes') { this.ancDevice = ANC.Yes } else { this.ancDevice = ANC.No }
|
||||
this.$axios.post('/api/update-setting', this.form).then(({ data }) => {
|
||||
if (data.success) {
|
||||
// this.$toast.success(data.message);
|
||||
this.$emit('next-step')
|
||||
}
|
||||
}).catch(() => {
|
||||
this.$emit('next-step')
|
||||
this.$toast.error('something went wrong while saving...')
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.bar{
|
||||
background-color: #e9c046 !important;
|
||||
}
|
||||
.checklabel:hover{
|
||||
border-color: #e9c046;
|
||||
}
|
||||
.checklabel {
|
||||
color: #e9c046 !important;
|
||||
border: 1px solid #e9c046;
|
||||
}
|
||||
.checkmark .checklabel svg path{
|
||||
/*background-color: white;*/
|
||||
fill: #e9c046;
|
||||
}
|
||||
.checkmark input:checked ~ .checklabel {
|
||||
background-color: #e9c046;
|
||||
border: 1px solid #e9c046;
|
||||
color: #f4f5f7 !important;
|
||||
}
|
||||
.checkmark input:checked ~ .checklabel svg path {
|
||||
fill: white;
|
||||
}
|
||||
.checkmark input:hover ~ .checklabel {
|
||||
background-color: #e9c046;
|
||||
border: 1px solid #e9c046;
|
||||
color: #f4f5f7 !important;
|
||||
}
|
||||
.checkmark input:hover ~ .checklabel svg path {
|
||||
fill: white;
|
||||
}
|
||||
</style>
|
||||
~/stores/audio
|
112
pages/onboarding/onboarding4.vue
Normal file
112
pages/onboarding/onboarding4.vue
Normal file
File diff suppressed because one or more lines are too long
150
pages/onboarding/onboarding5.vue
Normal file
150
pages/onboarding/onboarding5.vue
Normal file
@@ -0,0 +1,150 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="row text-center">
|
||||
<div class="col-12">
|
||||
<h1 class="h3 fw-bold mb-4">
|
||||
{{ t("Please choose a soundscape") }}
|
||||
</h1>
|
||||
<p class="text-muted mx-auto">
|
||||
{{ t("You can change your selection at any time.") }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-12 pt-4">
|
||||
<div class="d-flex justify-content-center">
|
||||
<div class="checkmark px-1 pt-2 d-block d-sm-inline-block d-inline-block" @click.prevent="saveSetting('Lagoon')">
|
||||
<input id="success-outlined1" type="radio" class="btn-check" name="options-outlined" autocomplete="off">
|
||||
<label class="btn pt-4 checklabel" for="success-outlined1">
|
||||
<svg width="75" height="60" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M45.524 2c-6.347 0-11.391 2.593-15.426 6.39-4.034 3.797-7.11 8.764-9.814 13.642-2.703 4.878-5.047 9.685-7.452 13.157-2.406 3.47-4.707 5.458-7.474 5.458a1.219 1.219 0 0 0-.518.107 1.339 1.339 0 0 0-.441.32 1.506 1.506 0 0 0-.295.485 1.63 1.63 0 0 0 0 1.148c.068.181.169.346.295.484.126.139.276.248.44.321.165.074.342.11.519.107 3.927 0 6.981-2.844 9.597-6.619 2.616-3.775 4.958-8.629 7.61-13.412 2.651-4.784 5.6-9.479 9.265-12.928 3.664-3.449 7.992-5.687 13.694-5.687 1.97 0 5.187.55 7.72 1.977 2.533 1.427 4.33 3.462 4.33 6.941 0 4.46-6.184 6.167-8.208 2.236a1.432 1.432 0 0 0-.434-.51 1.217 1.217 0 0 0-1.22-.136 1.349 1.349 0 0 0-.517.405c-1.007 1.278-1.838 1.665-2.536 1.724-.699.06-1.396-.226-2.09-.813-.693-.586-1.331-1.464-1.765-2.328-.433-.864-.635-1.755-.635-2.064a1.629 1.629 0 0 0-.097-.575 1.509 1.509 0 0 0-.289-.49 1.34 1.34 0 0 0-.436-.328 1.226 1.226 0 0 0-1.034 0 1.339 1.339 0 0 0-.437.328c-.124.14-.222.307-.289.49a1.638 1.638 0 0 0-.096.575c0 1.177.384 2.33.975 3.51.592 1.18 1.418 2.345 2.482 3.245a6.356 6.356 0 0 0 1.925 1.13c.257 6.345 3.448 12.1 8.177 16.272 4.934 4.352 11.567 7.057 18.574 7.057a1.23 1.23 0 0 0 .519-.107 1.34 1.34 0 0 0 .44-.32c.127-.139.227-.304.295-.485a1.627 1.627 0 0 0 0-1.148 1.505 1.505 0 0 0-.295-.485 1.344 1.344 0 0 0-.44-.32 1.22 1.22 0 0 0-.519-.107c-6.382 0-12.468-2.498-16.906-6.414-4.182-3.689-6.837-8.578-7.134-13.807.837-.253 1.66-.796 2.459-1.507 3.944 4.576 12.209 1.793 12.209-5.028 0-4.695-2.721-7.863-5.795-9.595C51.384 2.565 47.906 2 45.524 2ZM37.47 22.79a1.28 1.28 0 0 0-.94.45 1.578 1.578 0 0 0-.378 1.056c0 3.468 1.48 7.008 3.415 10.132 1.935 3.124 4.345 5.82 6.554 7.455.146.112.31.19.484.231.174.04.353.042.528.005.174-.037.34-.112.488-.221.148-.11.275-.25.374-.414.098-.164.166-.349.2-.542a1.646 1.646 0 0 0-.004-.586 1.576 1.576 0 0 0-.207-.538 1.41 1.41 0 0 0-.378-.408c-1.809-1.339-4.084-3.845-5.832-6.666-1.747-2.821-2.944-5.97-2.944-8.448a1.623 1.623 0 0 0-.099-.58 1.508 1.508 0 0 0-.294-.492 1.334 1.334 0 0 0-.444-.326 1.224 1.224 0 0 0-.523-.109Zm-7.994 25.265c-.517 0-.994.338-1.213.86 0 0-2.327 5.112-6.82 5.112-4.494 0-6.82-5.112-6.82-5.112-.246-.564-.78-.902-1.339-.84l-.126.003c-.423.094-.778.401-.962.837 0 0-2.395 5.112-6.82 5.112-.48-.006-.93.27-1.177.734a1.648 1.648 0 0 0 0 1.501c.247.465.697.744 1.177.738 4.566 0 6.846-3.033 8.033-4.973C14.592 53.972 16.85 57 21.444 57c4.592 0 6.85-3.028 8.033-4.973C30.653 53.972 32.917 57 37.51 57c4.593 0 6.857-3.028 8.034-4.973C46.719 53.972 48.978 57 53.576 57c4.598 0 6.856-3.028 8.033-4.973C62.791 53.967 65.071 57 69.642 57c.481.006.93-.274 1.177-.738a1.648 1.648 0 0 0 0-1.5c-.246-.466-.696-.74-1.177-.735-4.435 0-6.82-5.112-6.82-5.112-.22-.524-.696-.86-1.213-.86-.517 0-.994.338-1.213.86 0 0-2.322 5.112-6.82 5.112-4.499 0-6.82-5.112-6.82-5.112-.22-.524-.697-.86-1.213-.86-.517 0-.994.338-1.214.86 0 0-2.321 5.112-6.82 5.112-4.498 0-6.82-5.112-6.82-5.112-.22-.524-.696-.86-1.213-.86Z" fill="currentColor" stroke="currentColor" /></svg>
|
||||
<p class="text-center">{{ t("Lagoon") }}</p>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="checkmark px-1 pt-2 d-inline-block" @click.prevent="saveSetting('Tropics')">
|
||||
<input id="danger-outlined1" type="radio" class="btn-check checkmark" name="options-outlined" autocomplete="off">
|
||||
<label class="btn checklabel ms-0 ms-sm-3 pt-4 " for="danger-outlined1">
|
||||
<svg width="60" height="60" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#a)"><path d="M29.965 1.006c-.059.011-.118.027-.172.043-.64.146-1.09.722-1.074 1.38v8.276c-.006.495.252.959.682 1.212.43.248.957.248 1.386 0 .43-.253.688-.717.683-1.212V2.429a1.373 1.373 0 0 0-.43-1.051 1.373 1.373 0 0 0-1.075-.372ZM10.363 9.11c-.515.091-.93.469-1.074.97a1.388 1.388 0 0 0 .387 1.4l5.846 5.863c.333.41.865.598 1.38.48.511-.12.914-.523 1.032-1.035a1.388 1.388 0 0 0-.478-1.385L11.61 9.541a1.379 1.379 0 0 0-1.118-.431h-.129Zm39.032 0c-.312.043-.602.2-.817.43l-5.846 5.863a1.388 1.388 0 0 0-.478 1.385c.118.512.521.916 1.032 1.034a1.38 1.38 0 0 0 1.38-.48l5.847-5.861c.43-.41.553-1.051.3-1.59a1.375 1.375 0 0 0-1.418-.781Zm-19.43 7.112c-.043.01-.086.027-.129.043a1.422 1.422 0 0 0-.258.043l-.043.043c-7.297.313-13.197 6.272-13.197 13.664 0 7.592 6.185 13.794 13.756 13.794 7.571 0 13.756-6.202 13.756-13.794 0-7.365-5.852-13.303-13.111-13.664-.048 0-.08-.043-.129-.043a1.324 1.324 0 0 0-.43-.086h-.215Zm.043 2.759h.216c6.017.07 10.875 4.983 10.875 11.034 0 6.1-4.922 11.035-11.005 11.035-6.077 0-11.004-4.935-11.004-11.035 0-6.067 4.879-10.986 10.918-11.034ZM2.196 28.636a1.392 1.392 0 0 0 .387 2.759h8.253c.495.005.957-.254 1.21-.685a1.396 1.396 0 0 0 0-1.39 1.383 1.383 0 0 0-1.21-.684H2.195Zm46.77 0a1.392 1.392 0 0 0 .387 2.759h8.252c.495.005.957-.254 1.21-.685a1.396 1.396 0 0 0 0-1.39 1.383 1.383 0 0 0-1.21-.684H48.965Zm-32.628 13.62a1.38 1.38 0 0 0-.816.432L9.676 48.55a1.388 1.388 0 0 0-.479 1.385c.119.512.522.916 1.032 1.034a1.38 1.38 0 0 0 1.381-.48l5.846-5.861a1.378 1.378 0 0 0 .312-1.525 1.378 1.378 0 0 0-1.3-.846h-.13Zm27.082 0c-.516.093-.93.47-1.075.97a1.388 1.388 0 0 0 .387 1.401l5.846 5.863c.333.41.865.598 1.381.48.51-.12.914-.523 1.032-1.035a1.388 1.388 0 0 0-.478-1.385l-5.846-5.862a1.373 1.373 0 0 0-.99-.431h-.258Zm-13.455 5.648c-.059.01-.118.026-.172.043-.64.145-1.09.722-1.074 1.379v8.276c-.006.496.252.959.682 1.212.43.248.957.248 1.386 0 .43-.253.688-.716.683-1.212v-8.276a1.373 1.373 0 0 0-.43-1.05 1.373 1.373 0 0 0-1.075-.373Z" fill="currentColor" stroke="currentColor" stroke-width="1.2" /></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h60v60H0z" /></clipPath></defs></svg>
|
||||
<p class="text-center">{{ t("Tropics") }}</p>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex mx-auto justify-content-center ">
|
||||
<div class="checkmark box12 pt-2 pt-sm-3 px-1 d-block d-sm-inline-block" @click.prevent="saveSetting('Forest')">
|
||||
<input id="success-outlined2" type="radio" class="btn-check" name="options-outlined" autocomplete="off">
|
||||
<label class="btn pt-4 checklabel" for="success-outlined2">
|
||||
<svg width="60" height="60" fill="none" xmlns="http://www.w3.org/2000/svg"><path
|
||||
fill="none"
|
||||
d="M16.998 39.947V57m17.053-8.526V57m-8.526-34.105v.568a8.526 8.526 0 0 1-3.127 16.484s-9.11.05-11.084 0a8.526 8.526 0 0 1-2.842-16.484v-.568a8.526 8.526 0 1 1 17.053 0Z"
|
||||
stroke="currentColor"
|
||||
stroke-width="4"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/><path
|
||||
fill="none"
|
||||
d="M31.209 48.474h23.59a2.842 2.842 0 0 0 1.989-4.832l-8.526-9.379h.852a2.843 2.843 0 0 0 1.99-4.831l-8.527-9.38h.569a2.843 2.843 0 0 0 2.273-4.831L34.051 3l-3.979 4.263"
|
||||
stroke="currentColor"
|
||||
stroke-width="4"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/></svg>
|
||||
<p class="text-center">{{ t("Forest") }}</p>
|
||||
</label>
|
||||
</div>
|
||||
<div class="checkmark box12 pt-2 px-1 pt-sm-3 d-block d-sm-inline-block" @click.prevent="saveSetting('Meadow')">
|
||||
<input id="danger-outlined" type="radio" class="btn-check " name="options-outlined" autocomplete="off">
|
||||
<label class="btn pt-4 checklabel ms-0 ms-lg-3" for="danger-outlined">
|
||||
<svg width="60" height="60" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M12.716 1.592C13.234 1.155 14.08.975 15 1c1.574.042 2.951 1.506 3.5 2 10 9 15.035 13.525 17.402 23.261 1.598 6.573 2.014 11.137 2.5 13.573 3.316-5.574 8.12-15.3 16.098-18.49.563-.226 1.404-.61 2-.5.596.108 1.053.09 1.5.5.447.408.693.415.854.999.162.584.174.92 0 1.5l-.934 2.418c-4.382 14.59-7.018 19.582-6.518 30.073 0 .855.004 1.134-.402 1.666-.124.162-.743.834-1.598.834-.855 0-1.41-.072-1.902-.834-.413-.64-.486-1.533-.486-2.388 0-9.844 4.198-19.51 7.388-30.278-3.615 2.398-9.402 9.917-11 13-2.465 4.75-3.777 10.205-3.777 17.278 0 .855-.118 1.603-.625 2.388-.394.61-.798.834-1.653.834s-1.379-.578-1.847-1.334c-.398-.643-.432-1.033-.432-1.888 0-9.64-1.736-24.465-4.568-32.778-1.901-5.571-8.871-11.824-13.692-16.5 3.277 12.438 7.65 31.088 7.65 48.456v.822c0 .855.115 1.631-.458 2.388-.425.561-.931.826-1.777.834-.838.009-1.28-.33-1.723-.834-.657-.748-.5-1.645-.5-2.5 0-7.379-.16-13.989-1.987-19.657-1.05-3.264-8.977-12.07-11.693-14.5C9.284 31.503 13 44.923 13 55.612c0 .855.124 1.668-.5 2.388-.439.507-.83.834-1.5.834s-1.058-.33-1.5-.834c-.657-.748-.5-1.645-.5-2.5 0-12.45-2.174-20.043-6.933-34.314a3.222 3.222 0 0 1 4.253-4.008c4.495 1.794 9.116 9.046 11.693 11.878-.859-6.502-3.746-17.723-6.212-23.8a3.222 3.222 0 0 1 .915-3.664Z" fill="currentColor" /></svg>
|
||||
<p class="text-center">{{ t("Meadow") }}</p>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapState, mapActions } from 'pinia'
|
||||
import { useAudioStore } from '@/stores/audio'
|
||||
import { useCounterStore } from '@/stores/counter'
|
||||
export default {
|
||||
emits: ['next-step'],
|
||||
setup () {
|
||||
const { t } = useI18n()
|
||||
const localePath = useLocalePath()
|
||||
|
||||
return {
|
||||
t,
|
||||
localePath
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
bar_val: 100,
|
||||
form: {
|
||||
soundscape: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: { ...mapState(useCounterStore, ['count']) },
|
||||
...mapState(useAudioStore, ['connectedSoundScape']),
|
||||
mounted () {
|
||||
this.increment(100)
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useCounterStore, ['increment']),
|
||||
...mapActions(useCounterStore, ['decrement']),
|
||||
|
||||
async saveSetting (value) {
|
||||
this.form.soundscape = value
|
||||
this.connectedSoundScape = value
|
||||
|
||||
try {
|
||||
const { data } = await this.$axios.post('/api/update-setting', this.form)
|
||||
if (data.success) {
|
||||
this.form = data.setting
|
||||
|
||||
// Jetzt erst weiterleiten
|
||||
useRouter().push('/')
|
||||
} else {
|
||||
this.$toast.error('Speichern fehlgeschlagen.')
|
||||
}
|
||||
} catch (e) {
|
||||
this.$toast.error('something went wrong while saving...')
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.bar{
|
||||
background-color: #e9c046;
|
||||
}
|
||||
.checklabel:hover{
|
||||
border-color: #e9c046;
|
||||
}
|
||||
.checklabel {
|
||||
color: #e9c046 !important;
|
||||
border: 1px solid #e9c046;
|
||||
}
|
||||
|
||||
.checkmark input:checked ~ .checklabel {
|
||||
background-color: #e9c046;
|
||||
border: 1px solid #e9c046;
|
||||
color: #f4f5f7 !important;
|
||||
}
|
||||
.checkmark input:checked ~ .checklabel svg path {
|
||||
fill: white;
|
||||
}
|
||||
.checkmark input:hover ~ .checklabel {
|
||||
background-color: #e9c046;
|
||||
border: 1px solid #e9c046;
|
||||
color: #f4f5f7 !important;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 992px) and (min-width: 575px) {
|
||||
.box12{
|
||||
padding: 11px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
199
pages/onboarding/onboarding6.vue
Normal file
199
pages/onboarding/onboarding6.vue
Normal file
File diff suppressed because one or more lines are too long
23
pages/routes.vue
Normal file
23
pages/routes.vue
Normal file
@@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>Routen-Übersicht</h1>
|
||||
<ul>
|
||||
<li v-for="route in routes" :key="route.path">
|
||||
<NuxtLink :to="route.path">{{ route.path }}</NuxtLink>
|
||||
<ul v-if="route.children">
|
||||
<li v-for="child in route.children" :key="child.path">
|
||||
<NuxtLink :to="child.path">{{ child.path }}</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useRouter } from 'vue-router'
|
||||
import { NuxtLink } from '#components'
|
||||
|
||||
const router = useRouter()
|
||||
const routes = router.getRoutes()
|
||||
</script>
|
226
pages/settings.vue
Normal file
226
pages/settings.vue
Normal file
@@ -0,0 +1,226 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="container-fluid p-0">
|
||||
<div class="settings d-flex flex-nowrap">
|
||||
<div class="settings__sidebar col-lg-3 col-md-4 px-0" style="background-color: #f4f5f7">
|
||||
<div class="d-flex flex-column align-items-sm-start pt-2 text-white min-vh-100 h-100">
|
||||
<nuxt-link :to="localePath('/')" class="d-flex align-items-center pb-5 text-white text-decoration-none">
|
||||
<div class="d-none d-md-inline px-sm-2 px-md-3 px-lg-3 pt-3 main-logo">
|
||||
<img src="/logo-gray.svg" height="35" class="img " alt="Mindboost Logo">
|
||||
</div>
|
||||
<div class="d-md-none px-2 px-md-3 px-lg-3 pt-3 main-logo-mobile">
|
||||
<img src="/mindboost-logo-small.svg" width="45" class="img " alt="Mindboost Logo">
|
||||
</div>
|
||||
</nuxt-link>
|
||||
<div class="sidebar sidebar--settings">
|
||||
<ul class="sidebar__main-menu">
|
||||
<li class="sidebar__item">
|
||||
<nuxt-link :to="localePath('/settings/account')" class="sidebar__link list-active">
|
||||
<i class="fa-solid d-md-none d-block fa-user" /><span class="d-none d-md-inline">{{ t('Account') }}</span>
|
||||
</nuxt-link>
|
||||
</li>
|
||||
<li v-if="dataStore.user.team_subscription_plan == '0' && dataStore.user.enterprise_subscription_plan == '0' && (dataStore.user.basic_subscription_plan == '1')" class="sidebar__item">
|
||||
<nuxt-link :to="localePath('/settings/subscription')" class="sidebar__link list-active">
|
||||
<i class="fa-solid d-md-none d-block fa-credit-card" /><span class="d-none d-md-inline">{{ t('Subscription') }}</span>
|
||||
</nuxt-link>
|
||||
</li>
|
||||
<li class="sidebar__item">
|
||||
<nuxt-link :to="localePath('/settings/soundscape')" class="sidebar__link list-active">
|
||||
<i class="fa-solid d-md-none d-block fa-volume-high" /><span class="d-none d-md-inline">{{ t('Settings') }}</span>
|
||||
</nuxt-link>
|
||||
</li>
|
||||
<!-- <li class="sidebar__item">
|
||||
<nuxt-link :to="localePath('/settings/timersettings')" class="sidebar__link list-active">
|
||||
<i class="fa-solid fa-volume-high sidebar__icon" /><span class="sidebar__text">{{ t('Timer') }}</span>
|
||||
</nuxt-link>
|
||||
</li> -->
|
||||
<li class="sidebar__item">
|
||||
<nuxt-link :to="localePath('/settings/about')" class="sidebar__link list-active">
|
||||
<i class="fa-solid fa-circle-info d-block d-md-none" /><span class="d-none d-md-inline"> {{ t('About Mindboost') }}</span>
|
||||
</nuxt-link>
|
||||
</li>
|
||||
<li class="sidebar__item">
|
||||
<nuxt-link :to="localePath('/settings/faq')" class="sidebar__link list-active">
|
||||
<i class="fa-solid d-block d-md-none fa-circle-question" /><span class="d-none d-md-inline">{{ t('FAQ') }} </span>
|
||||
</nuxt-link>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="sidebar__secondary-menu">
|
||||
<li class="sidebar__item">
|
||||
<nuxt-link :to="localePath('/settings/imprint')" class="sidebar__link sidebar__link--secondary">
|
||||
<i class="fa-solid fa-stamp d-md-none d-block" /><span class="d-none d-md-inline">{{ t('Imprint') }} </span>
|
||||
</nuxt-link>
|
||||
</li>
|
||||
|
||||
<li class="sidebar__item">
|
||||
<nuxt-link :to="localePath('/settings/dataprotection')" class="sidebar__link sidebar__link--secondary">
|
||||
<i class="fa-solid fa-shield d-block d-md-none" /><span class="d-none d-md-inline">{{ t('Data Protection') }}</span>
|
||||
</nuxt-link>
|
||||
</li>
|
||||
|
||||
<li class="sidebar__item">
|
||||
<nuxt-link :to="localePath('/settings/termsandcondition')" class="sidebar__link sidebar__link--secondary">
|
||||
<i class="fa-solid fa-scale-balanced d-block d-md-none" /><span class="d-none d-md-inline">{{ t('Terms & Conditions') }}</span>
|
||||
</nuxt-link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings__content col-md-8 col-lg-9 justify-content-end px-3">
|
||||
<div class="d-flex justify-content-end pb-sm-5 pt-3">
|
||||
<router-link :to="localePath('/')" style="cursor: pointer">
|
||||
<svg width="36" height="36" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="m18 20.1-7.35 7.35c-.275.275-.625.413-1.05.413-.425 0-.775-.138-1.05-.413-.275-.275-.412-.625-.412-1.05 0-.425.137-.775.412-1.05L15.9 18l-7.35-7.35c-.275-.275-.412-.625-.412-1.05 0-.425.137-.775.412-1.05.275-.275.625-.413 1.05-.413.425 0 .775.138 1.05.413L18 15.9l7.35-7.35c.275-.275.625-.413 1.05-.413.425 0 .775.138 1.05.413.275.275.413.625.413 1.05 0 .425-.138.775-.413 1.05L20.1 18l7.35 7.35c.275.275.413.625.413 1.05 0 .425-.138.775-.413 1.05-.275.275-.625.413-1.05.413-.425 0-.775-.138-1.05-.413L18 20.1Z" fill="#585C5E" /></svg>
|
||||
</router-link>
|
||||
</div>
|
||||
<nuxt-page key="child" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapActions } from 'pinia'
|
||||
import { ANC } from '@/stores/interfaces/ANC'
|
||||
import { HeadsetType } from '@/stores/interfaces/HeadsetType'
|
||||
import { useUserStore } from '~/stores/user'
|
||||
export default {
|
||||
setup () {
|
||||
definePageMeta({
|
||||
middleware: 'auth'
|
||||
})
|
||||
// Use useUserStore to get the user store
|
||||
const dataStore = useUserStore()
|
||||
const { t } = useI18n()
|
||||
const localePath = useLocalePath()
|
||||
// useNuxtApp().$logger.log('dataStore.user.email', dataStore.user.basic_subscription_plan)
|
||||
return { t, localePath, dataStore }
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
adaptiveSounscape: false,
|
||||
form: {
|
||||
soundscape: '',
|
||||
headphone_type: '',
|
||||
anc_type: '',
|
||||
adaptive_sound_scape: '',
|
||||
dataStore: useUserStore()
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.fetchSettings()
|
||||
},
|
||||
methods: {
|
||||
...mapActions(useUserStore, ['updateHeadsetType', 'updateANC']),
|
||||
fetchSettings () {
|
||||
this.$axios.post('/api/fetch-settings').then(({ data }) => {
|
||||
if (data.success) {
|
||||
this.form = data.setting
|
||||
if (data.setting.adaptive_sound_scape === 'yes' || data.setting.adaptive_sound_scape === 'Yes') {
|
||||
this.adaptiveSounscape = true
|
||||
} else {
|
||||
this.adaptiveSounscape = false
|
||||
}
|
||||
}
|
||||
}).catch(() => {
|
||||
this.$toast.error('something went wrong while saving...')
|
||||
})
|
||||
},
|
||||
saveSetting (value, type) {
|
||||
if (type === 'soundscape') {
|
||||
this.form.soundscape = value
|
||||
}
|
||||
if (type === 'headphone') {
|
||||
this.form.headphone_type = value
|
||||
if (value === 'In-ear') { this.updateHeadsetType(HeadsetType.InEar) } else { this.updateHeadsetType(HeadsetType.OverEar) }
|
||||
}
|
||||
if (type === 'anc') {
|
||||
this.form.anc_type = value
|
||||
if (value === 'yes' || value === 'Yes') { this.updateANC(ANC.Yes) } else { this.updateANC(ANC.No) }
|
||||
}
|
||||
if (type === 'adaptivesoundscape') {
|
||||
if (this.adaptiveSounscape === true) {
|
||||
this.form.adaptive_sound_scape = 'no'
|
||||
} else {
|
||||
this.form.adaptive_sound_scape = 'yes'
|
||||
}
|
||||
}
|
||||
|
||||
this.$axios.post('/api/update-setting', this.form).then(({ data }) => {
|
||||
if (data.success) {
|
||||
this.$toast.success(data.message)
|
||||
this.form = data.setting
|
||||
}
|
||||
}).catch(() => {
|
||||
this.$toast.error('something went wrong while saving...')
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.settings__sidebar {
|
||||
height: 100vh;
|
||||
position: sticky;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.settings__content {
|
||||
flex-grow: 1;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.list-active{
|
||||
width: 100% !important;
|
||||
font-size: 18px;
|
||||
}
|
||||
.list-active.router-link-exact-active{
|
||||
background-color: #e9c046;
|
||||
color: white !important;
|
||||
width: 100% !important;
|
||||
border-radius: 0;
|
||||
}
|
||||
.list-active:active a{
|
||||
color: white !important;
|
||||
}
|
||||
.list-active:hover, .list-active:focus {
|
||||
border-radius: 0;
|
||||
}
|
||||
.main-logo img {
|
||||
width: 100%;
|
||||
}
|
||||
@media only screen and (max-width: 1024px) {
|
||||
.main-logo img {
|
||||
height: auto;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
.fivith-icon.router-link-exact-active{
|
||||
content: url('~/assets/image/Vector (4).png');
|
||||
background-color: #e9c046;
|
||||
border-radius: 5px;
|
||||
padding: 10px 10px;
|
||||
text-align: center;
|
||||
}
|
||||
.accordion-button-homebar::after {
|
||||
background-size: 16px;
|
||||
background-position-y: 4px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
.navbar-logo{
|
||||
width: 160px !important;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
}
|
||||
.drop-active{
|
||||
color: white !important;
|
||||
background-color: #e9c046;
|
||||
border-radius: 10px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
</style>
|
207
pages/settings/_index.vue
Normal file
207
pages/settings/_index.vue
Normal file
@@ -0,0 +1,207 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-12 col-lg-9 col-md-11 col-sm-12 col-xl-8">
|
||||
<!-- Header Section -->
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<h1 class="h3 fw-bold text-center pt-3">
|
||||
{{ t('Account') }}
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Account Details -->
|
||||
<div class="border-bottom row mx-0 pt-4">
|
||||
<div class="col-6">
|
||||
<p class="fw-bold text-start">
|
||||
{{ t('First Name') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<p class="fw-bold text-end text-muted">
|
||||
{{ user.first_name }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-bottom row mx-0 pt-4">
|
||||
<div class="col-6">
|
||||
<p class="fw-bold text-start">
|
||||
{{ t('Last Name') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<p class="fw-bold text-end text-muted">
|
||||
{{ user.last_name }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-bottom row mx-0 pt-4">
|
||||
<div class="col-6">
|
||||
<p class="fw-bold text-start">
|
||||
{{ t('Language') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<p class="fw-bold text-end text-muted">
|
||||
{{ user.language == 'de' ? 'German' : 'English' }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-bottom row mx-0 pt-4">
|
||||
<div class="col-6">
|
||||
<p class="fw-bold text-start">
|
||||
{{ t('Email') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<p class="fw-bold text-end text-muted">
|
||||
{{ user.email }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-bottom row mx-0 pt-4">
|
||||
<div class="col-6">
|
||||
<p class="fw-bold text-start">
|
||||
{{ t('Password') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<p class="fw-bold text-end text-muted">•••••••••</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Conditional Manager Details -->
|
||||
<div v-if="user.inviter" class="border-bottom row mx-0 pt-4">
|
||||
<div class="col-6">
|
||||
<p class="fw-bold text-start">
|
||||
{{ t('Manager Name') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<p class="fw-bold text-end text-muted">
|
||||
{{ user.inviter.first_name ?? '' }} {{ user.inviter.last_name ?? '' }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="user.inviter" class="border-bottom row mx-0 pt-4">
|
||||
<div class="col-6">
|
||||
<p class="fw-bold text-start">
|
||||
{{ t('Manager Email') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<p class="fw-bold text-end text-muted">
|
||||
{{ user.inviter.email ?? '' }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="user.inviter" class="row pt-4">
|
||||
<div class="col-6">
|
||||
<p class="fw-bold text-start">
|
||||
{{ t('Current Package') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<p class="fw-bold text-end text-muted">
|
||||
{{ showManagerPlan() }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Logout Button -->
|
||||
<div class="account-buttons d-flex justify-content-center pt-5">
|
||||
<nuxt-link
|
||||
:to="localePath('/settings/editaccount')"
|
||||
class="btn btn-primary-custom text-decoration-none"
|
||||
>
|
||||
{{ t('Edit_') }}
|
||||
</nuxt-link>
|
||||
<button class="btn fw-bold btn-outline-dark" @click="logoutNow">
|
||||
{{ t("Log Out") }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Subscription Call-to-Action -->
|
||||
<div class="row pt-5 pb-4 px-2 mx-0">
|
||||
<div
|
||||
class="col-12 rounded text-center py-3 py-md-5"
|
||||
style="background-image: linear-gradient(40.53deg, #E9A446 6.68%, #E9C046 100%)"
|
||||
>
|
||||
<h4 class="text-center text-white">
|
||||
{{ t("Get another free 30 days") }}
|
||||
</h4>
|
||||
<h5 class="text-center px-2 px-md-5 text-white pt-2">
|
||||
{{ t("You can still use Mindboost for 30 days for free. Subscribe to increase your productivity with Mindboost afterwards.") }}
|
||||
</h5>
|
||||
<NuxtLink
|
||||
disabled
|
||||
type="button"
|
||||
class="btn px-4 py-2 mt-2 mt-md-3 fs-5"
|
||||
style="background-color: white; gap: 10px;"
|
||||
>
|
||||
{{ t("Subscribe") }}
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
|
||||
// Composition API setup
|
||||
const { t } = useI18n()
|
||||
const localePath = useLocalePath()
|
||||
const userStore = useUserStore()
|
||||
const router = useRouter()
|
||||
|
||||
// Computed property for user state
|
||||
const user = computed(() => userStore.user)
|
||||
|
||||
// Logout method
|
||||
const logoutNow = async () => {
|
||||
await userStore.logout()
|
||||
try {
|
||||
await $axios.post('/api/logout')
|
||||
} catch (error) {
|
||||
// Handle the error if needed
|
||||
}
|
||||
router.push(localePath('/auth/login'))
|
||||
}
|
||||
|
||||
function showManagerPlan () {
|
||||
if (user.value.inviter.current_subscription_plan === 1) {
|
||||
return 'Basic'
|
||||
} else if (user.value.inviter.current_subscription_plan === 2) {
|
||||
return 'Team'
|
||||
} else if (user.value.inviter.current_subscription_plan === 3) {
|
||||
return 'Enterprise'
|
||||
}
|
||||
|
||||
// this.$toast.success(`Manager already has the ${managerPlan} plan`) // Fixed typo
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.account-buttons {
|
||||
gap: 1.5em;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 576px) {
|
||||
.account-buttons {
|
||||
flex-direction: column;
|
||||
gap: 0.75em;
|
||||
}
|
||||
}
|
||||
</style>
|
60
pages/settings/about.vue
Normal file
60
pages/settings/about.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-12 col-lg-9 col-md-11 col-sm-12 col-xl-8">
|
||||
<h1 class="h3 fw-bold text-center py-3">
|
||||
{{ t('About Mindboost') }}
|
||||
</h1>
|
||||
<div class="row pt-4">
|
||||
<div class="col-12">
|
||||
<h2 class="h6 fw-bold">
|
||||
{{ t('WHAT IS MINDBOOST?') }}
|
||||
</h2>
|
||||
<p class="text-muted ">
|
||||
{{ t('Mindboost creates soundscapes for your headphones that help you focus up to 35% better. Mindboost combines voice masking, binaural beats and alpha waves.') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-12 pt-3">
|
||||
<h2 class="h6 fw-bold">
|
||||
{{ t('HOW DOES MINDBOOST WORK?') }}
|
||||
</h2>
|
||||
<p class="text-muted ">
|
||||
{{ t('Our algorithm measures the acoustics of your cell phone or other device in the room in real time and generates a soundscape that optimally blocks out disruptive background noise such as colleagues on the phone.') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-12 pt-3">
|
||||
<h2 class="h6 fw-bold">
|
||||
{{ t('WHY DOES MINDBOOST WORK?') }}
|
||||
</h2>
|
||||
<p class="text-muted ">
|
||||
{{ t('All soundscapes in Mindboost have been tested and optimized in listening tests in cooperation with the Fraunhofer IBP. So you can be sure that Mindboost supports you optimally with your concentration.') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-12 pt-3">
|
||||
<h2 class="h6 fw-bold">
|
||||
{{ t('WHO WE ARE?') }}
|
||||
</h2>
|
||||
<p class="text-muted ">
|
||||
{{ t('We are a small team of audio developers. Inspiried by the power of sound, well known from music, we cannot stop exploring the digital audio world. We are happy that we can be part of a community that uses audio next to practical services mainly to generate fun.') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'SettingPage',
|
||||
setup () {
|
||||
const { t } = useI18n()
|
||||
const localePath = useLocalePath()
|
||||
return { t, localePath }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
271
pages/settings/account.vue
Normal file
271
pages/settings/account.vue
Normal file
@@ -0,0 +1,271 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-12 col-lg-9 col-md-11 col-sm-12 col-xl-8">
|
||||
<!-- Header Section -->
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<h1 class="h3 fw-bold text-center pt-3">
|
||||
{{ t('Account') }}
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Account Details -->
|
||||
<div class="border-bottom row flex-column flex-sm-row mx-0 pt-4">
|
||||
<div class="col-6">
|
||||
<p class="fw-bold text-start">
|
||||
{{ t('First Name') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<p class="fw-bold text-sm-end text-muted">
|
||||
{{ user.first_name }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-bottom row flex-column flex-sm-row mx-0 pt-4">
|
||||
<div class="col-6">
|
||||
<p class="fw-bold text-start">
|
||||
{{ t('Last Name') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<p class="fw-bold text-sm-end text-muted">
|
||||
{{ user.surname }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-bottom row flex-column flex-sm-row mx-0 pt-4">
|
||||
<div class="col-6">
|
||||
<p class="fw-bold text-start">
|
||||
{{ t('Language') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<p class="fw-bold text-sm-end text-muted">
|
||||
{{ user.language == 'de' ? 'German' : 'English' }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-bottom row flex-column flex-sm-row mx-0 pt-4">
|
||||
<div class="col-6">
|
||||
<p class="fw-bold text-start">
|
||||
{{ t('Email') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<p class="fw-bold text-sm-end text-muted">
|
||||
{{ user.email }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="border-bottom row flex-column flex-sm-row mx-0 pt-4">
|
||||
<div class="col-6">
|
||||
<p class="fw-bold text-start">
|
||||
{{ t('Password') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<p class="fw-bold text-sm-end text-muted">•••••••••</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Conditional Manager Details -->
|
||||
<div v-if="user.inviter" class="border-bottom row flex-column flex-sm-row mx-0 pt-4">
|
||||
<div class="col-6">
|
||||
<p class="fw-bold text-start">
|
||||
{{ t('Manager Name') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<p class="fw-bold text-sm-end text-muted">
|
||||
{{ user.inviter.first_name ?? '' }} {{ user.inviter.surname ?? '' }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="user.inviter" class="border-bottom row flex-column flex-sm-row mx-0 pt-4">
|
||||
<div class="col-6">
|
||||
<p class="fw-bold text-start">
|
||||
{{ t('Manager Email') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<p class="fw-bold text-sm-end text-muted">
|
||||
{{ user.inviter.email ?? '' }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="user.inviter" class="row flex-column flex-sm-row pt-4">
|
||||
<div class="col-6">
|
||||
<p class="fw-bold text-start">
|
||||
{{ t('Current Package') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<p class="fw-bold text-sm-end text-muted">
|
||||
{{ showManagerPlan() }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Logout Button -->
|
||||
<div class="account-buttons d-flex justify-content-center pt-5">
|
||||
<nuxt-link
|
||||
:to="localePath('/settings/editaccount')"
|
||||
class="btn btn-primary-custom text-decoration-none"
|
||||
>
|
||||
{{ t('Edit_') }}
|
||||
</nuxt-link>
|
||||
<button class="btn fw-bold btn-outline-dark" @click="logoutNow">
|
||||
{{ t("Log Out") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<button type="button" class="btn btn-link position-absolute bottom-0 start-50 translate-middle-x mb-3" @click="showModal = true">
|
||||
{{ t("Delete Account") }}
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="modal fade"
|
||||
tabindex="-1"
|
||||
:class="{ show: showModal }"
|
||||
:style="showModal ? 'display: block;' : 'display: none;'"
|
||||
aria-modal="true"
|
||||
role="dialog"
|
||||
@click.self="closeModal"
|
||||
>
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">{{ t("Delete Account") }}</h5>
|
||||
<button type="button" class="btn-close" @click="closeModal" />
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>{{ t('Delete Account Text') }}</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-dark-custom" @click="closeModal">{{ t('Cancel') }}</button>
|
||||
<button class="btn btn-primary-custom" @click="deleteAccount">{{ t('Delete Account') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hintergrund-Overlay -->
|
||||
<div v-if="showModal" class="modal-backdrop fade show" />
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
|
||||
import { mapActions, mapState } from 'pinia'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
|
||||
export default {
|
||||
name: 'Account',
|
||||
setup () {
|
||||
const { t } = useI18n()
|
||||
const localePath = useLocalePath()
|
||||
const userStore = useUserStore()
|
||||
const user = useUserStore().user
|
||||
const router = useRouter()
|
||||
|
||||
return { t, localePath, router, user, userStore }
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
showModal: false,
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async logoutNow () {
|
||||
await this.userStore.logout()
|
||||
try {
|
||||
await this.$axios.post('/api/logout')
|
||||
} catch (error) {
|
||||
// Handle the error if needed
|
||||
}
|
||||
this.$router.push(this.localePath('/auth/login'))
|
||||
},
|
||||
closeModal () {
|
||||
this.showModal = false
|
||||
},
|
||||
async deleteAccount () {
|
||||
this.loading = true
|
||||
try {
|
||||
await this.$axios.post('/api/account/delete')
|
||||
await this.userStore.logout()
|
||||
this.$router.push(this.localePath('/auth/login'))
|
||||
this.$toast.success('Account gelöscht. Du wurdest ausgeloggt.')
|
||||
} catch (error) {
|
||||
this.$toast.error('Fehler beim Löschen: ' + (error.response?.data?.message || 'Unbekannter Fehler'))
|
||||
} finally {
|
||||
this.loading = false
|
||||
this.closeModal()
|
||||
}
|
||||
},
|
||||
showManagerPlan () {
|
||||
if (user.value.inviter.current_subscription_plan === 1) {
|
||||
return 'Basic'
|
||||
} else if (user.value.inviter.current_subscription_plan === 2) {
|
||||
return 'Team'
|
||||
} else if (user.value.inviter.current_subscription_plan === 3) {
|
||||
return 'Enterprise'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.btn-primary-custom {
|
||||
background-color: #e9c046;
|
||||
border-color: #e9c046;
|
||||
color: white;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.btn-primary-custom:hover, .btn-primary-custom:focus{
|
||||
background-color: transparent;
|
||||
border-color: #e9c046;
|
||||
color: #e9c046;
|
||||
}
|
||||
|
||||
.account-buttons {
|
||||
gap: 1.5em;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 576px) {
|
||||
.account-buttons {
|
||||
flex-direction: column;
|
||||
gap: 0.75em;
|
||||
}
|
||||
}
|
||||
|
||||
.modal {
|
||||
z-index: 1050;
|
||||
}
|
||||
.modal-backdrop {
|
||||
z-index: 1040;
|
||||
}
|
||||
.btn-link {
|
||||
color: black;
|
||||
transition: 250ms ease-in-out;
|
||||
|
||||
&:hover, &:focus {
|
||||
color: #e9c046;
|
||||
}
|
||||
}
|
||||
</style>
|
66
pages/settings/dataprotection.vue
Normal file
66
pages/settings/dataprotection.vue
Normal file
@@ -0,0 +1,66 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-12 col-lg-9 col-md-11 col-sm-12 col-xl-8">
|
||||
<h1 class="h3 fw-bold py-3">Datenschutz­erklärung</h1>
|
||||
<div class="row pt-4">
|
||||
<div class="col-12">
|
||||
<h2 class="h4">1. Datenschutz auf einen Blick</h2>
|
||||
<h3 class="h5">Allgemeine Hinweise</h3> <p>Die folgenden Hinweise geben einen einfachen Überblick darüber, was mit Ihren personenbezogenen Daten passiert, wenn Sie diese Website besuchen. Personenbezogene Daten sind alle Daten, mit denen Sie persönlich identifiziert werden können. Ausführliche Informationen zum Thema Datenschutz entnehmen Sie unserer unter diesem Text aufgeführten Datenschutzerklärung.</p>
|
||||
<h3 class="h5">Datenerfassung auf dieser Website</h3>
|
||||
<h4 class="h6">Wer ist verantwortlich für die Datenerfassung auf dieser Website?</h4> <p>Die Datenverarbeitung auf dieser Website erfolgt durch den Websitebetreiber. Dessen Kontaktdaten können Sie dem Abschnitt „Hinweis zur Verantwortlichen Stelle“ in dieser Datenschutzerklärung entnehmen.</p> <h4 class="h6">Wie erfassen wir Ihre Daten?</h4> <p>Ihre Daten werden zum einen dadurch erhoben, dass Sie uns diese mitteilen. Hierbei kann es sich z. B. um Daten handeln, die Sie in ein Kontaktformular eingeben.</p> <p>Andere Daten werden automatisch oder nach Ihrer Einwilligung beim Besuch der Website durch unsere IT-Systeme erfasst. Das sind vor allem technische Daten (z. B. Internetbrowser, Betriebssystem oder Uhrzeit des Seitenaufrufs). Die Erfassung dieser Daten erfolgt automatisch, sobald Sie diese Website betreten.</p> <h4 class="h6">Wofür nutzen wir Ihre Daten?</h4> <p>Ein Teil der Daten wird erhoben, um eine fehlerfreie Bereitstellung der Website zu gewährleisten. Andere Daten können zur Analyse Ihres Nutzerverhaltens verwendet werden.</p> <h4 class="h6">Welche Rechte haben Sie bezüglich Ihrer Daten?</h4> <p>Sie haben jederzeit das Recht, unentgeltlich Auskunft über Herkunft, Empfänger und Zweck Ihrer gespeicherten personenbezogenen Daten zu erhalten. Sie haben außerdem ein Recht, die Berichtigung oder Löschung dieser Daten zu verlangen. Wenn Sie eine Einwilligung zur Datenverarbeitung erteilt haben, können Sie diese Einwilligung jederzeit für die Zukunft widerrufen. Außerdem haben Sie das Recht, unter bestimmten Umständen die Einschränkung der Verarbeitung Ihrer personenbezogenen Daten zu verlangen. Des Weiteren steht Ihnen ein Beschwerderecht bei der zuständigen Aufsichtsbehörde zu.</p> <p>Hierzu sowie zu weiteren Fragen zum Thema Datenschutz können Sie sich jederzeit an uns wenden.</p>
|
||||
<h2 class="h4">2. Hosting</h2>
|
||||
<p>Wir hosten die Inhalte unserer Website bei folgendem Anbieter:</p>
|
||||
<h3 class="h5">Strato</h3> <p>Anbieter ist die Strato AG, Otto-Ostrowski-Straße 7, 10249 Berlin (nachfolgend „Strato“). Wenn Sie unsere Website besuchen, erfasst Strato verschiedene Logfiles inklusive Ihrer IP-Adressen.</p> <p>Weitere Informationen entnehmen Sie der Datenschutzerklärung von Strato: <a href="https://www.strato.de/datenschutz/" target="_blank" rel="noopener noreferrer">https://www.strato.de/datenschutz/</a>.</p> <p>Die Verwendung von Strato erfolgt auf Grundlage von Art. 6 Abs. 1 lit. f DSGVO. Wir haben ein berechtigtes Interesse an einer möglichst zuverlässigen Darstellung unserer Website. Sofern eine entsprechende Einwilligung abgefragt wurde, erfolgt die Verarbeitung ausschließlich auf Grundlage von Art. 6 Abs. 1 lit. a DSGVO und § 25 Abs. 1 TTDSG, soweit die Einwilligung die Speicherung von Cookies oder den Zugriff auf Informationen im Endgerät des Nutzers (z. B. Device-Fingerprinting) im Sinne des TTDSG umfasst. Die Einwilligung ist jederzeit widerrufbar.</p>
|
||||
|
||||
<h4 class="h6">Auftragsverarbeitung</h4> <p>Wir haben einen Vertrag über Auftragsverarbeitung (AVV) zur Nutzung des oben genannten Dienstes geschlossen. Hierbei handelt es sich um einen datenschutzrechtlich vorgeschriebenen Vertrag, der gewährleistet, dass dieser die personenbezogenen Daten unserer Websitebesucher nur nach unseren Weisungen und unter Einhaltung der DSGVO verarbeitet.</p>
|
||||
<h2 class="h4">3. Allgemeine Hinweise und Pflicht­informationen</h2>
|
||||
<h3 class="h5">Datenschutz</h3> <p>Die Betreiber dieser Seiten nehmen den Schutz Ihrer persönlichen Daten sehr ernst. Wir behandeln Ihre personenbezogenen Daten vertraulich und entsprechend den gesetzlichen Datenschutzvorschriften sowie dieser Datenschutzerklärung.</p> <p>Wenn Sie diese Website benutzen, werden verschiedene personenbezogene Daten erhoben. Personenbezogene Daten sind Daten, mit denen Sie persönlich identifiziert werden können. Die vorliegende Datenschutzerklärung erläutert, welche Daten wir erheben und wofür wir sie nutzen. Sie erläutert auch, wie und zu welchem Zweck das geschieht.</p> <p>Wir weisen darauf hin, dass die Datenübertragung im Internet (z. B. bei der Kommunikation per E-Mail) Sicherheitslücken aufweisen kann. Ein lückenloser Schutz der Daten vor dem Zugriff durch Dritte ist nicht möglich.</p>
|
||||
<h3 class="h5">Hinweis zur verantwortlichen Stelle</h3> <p>Die verantwortliche Stelle für die Datenverarbeitung auf dieser Website ist:</p> <p>
|
||||
Robert-Carl Rapp<br>
|
||||
Sindlingerstr. 23,<br>
|
||||
71083 Herrenberg
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Telefon: (+49) 15784037946<br>
|
||||
E-Mail: kontakt@mindboost.team
|
||||
</p>
|
||||
<p>Verantwortliche Stelle ist die natürliche oder juristische Person, die allein oder gemeinsam mit anderen über die Zwecke und Mittel der Verarbeitung von personenbezogenen Daten (z. B. Namen, E-Mail-Adressen o. Ä.) entscheidet.</p>
|
||||
|
||||
<h3 class="h5">Speicherdauer</h3> <p>Soweit innerhalb dieser Datenschutzerklärung keine speziellere Speicherdauer genannt wurde, verbleiben Ihre personenbezogenen Daten bei uns, bis der Zweck für die Datenverarbeitung entfällt. Wenn Sie ein berechtigtes Löschersuchen geltend machen oder eine Einwilligung zur Datenverarbeitung widerrufen, werden Ihre Daten gelöscht, sofern wir keine anderen rechtlich zulässigen Gründe für die Speicherung Ihrer personenbezogenen Daten haben (z. B. steuer- oder handelsrechtliche Aufbewahrungsfristen); im letztgenannten Fall erfolgt die Löschung nach Fortfall dieser Gründe.</p>
|
||||
<h3 class="h5">Allgemeine Hinweise zu den Rechtsgrundlagen der Datenverarbeitung auf dieser Website</h3> <p>Sofern Sie in die Datenverarbeitung eingewilligt haben, verarbeiten wir Ihre personenbezogenen Daten auf Grundlage von Art. 6 Abs. 1 lit. a DSGVO bzw. Art. 9 Abs. 2 lit. a DSGVO, sofern besondere Datenkategorien nach Art. 9 Abs. 1 DSGVO verarbeitet werden. Im Falle einer ausdrücklichen Einwilligung in die Übertragung personenbezogener Daten in Drittstaaten erfolgt die Datenverarbeitung außerdem auf Grundlage von Art. 49 Abs. 1 lit. a DSGVO. Sofern Sie in die Speicherung von Cookies oder in den Zugriff auf Informationen in Ihr Endgerät (z. B. via Device-Fingerprinting) eingewilligt haben, erfolgt die Datenverarbeitung zusätzlich auf Grundlage von § 25 Abs. 1 TTDSG. Die Einwilligung ist jederzeit widerrufbar. Sind Ihre Daten zur Vertragserfüllung oder zur Durchführung vorvertraglicher Maßnahmen erforderlich, verarbeiten wir Ihre Daten auf Grundlage des Art. 6 Abs. 1 lit. b DSGVO. Des Weiteren verarbeiten wir Ihre Daten, sofern diese zur Erfüllung einer rechtlichen Verpflichtung erforderlich sind auf Grundlage von Art. 6 Abs. 1 lit. c DSGVO. Die Datenverarbeitung kann ferner auf Grundlage unseres berechtigten Interesses nach Art. 6 Abs. 1 lit. f DSGVO erfolgen. Über die jeweils im Einzelfall einschlägigen Rechtsgrundlagen wird in den folgenden Absätzen dieser Datenschutzerklärung informiert.</p>
|
||||
<h3 class="h5">Empfänger von personenbezogenen Daten</h3> <p>Im Rahmen unserer Geschäftstätigkeit arbeiten wir mit verschiedenen externen Stellen zusammen. Dabei ist teilweise auch eine Übermittlung von personenbezogenen Daten an diese externen Stellen erforderlich. Wir geben personenbezogene Daten nur dann an externe Stellen weiter, wenn dies im Rahmen einer Vertragserfüllung erforderlich ist, wenn wir gesetzlich hierzu verpflichtet sind (z. B. Weitergabe von Daten an Steuerbehörden), wenn wir ein berechtigtes Interesse nach Art. 6 Abs. 1 lit. f DSGVO an der Weitergabe haben oder wenn eine sonstige Rechtsgrundlage die Datenweitergabe erlaubt. Beim Einsatz von Auftragsverarbeitern geben wir personenbezogene Daten unserer Kunden nur auf Grundlage eines gültigen Vertrags über Auftragsverarbeitung weiter. Im Falle einer gemeinsamen Verarbeitung wird ein Vertrag über gemeinsame Verarbeitung geschlossen.</p>
|
||||
<h3 class="h5">Widerruf Ihrer Einwilligung zur Datenverarbeitung</h3> <p>Viele Datenverarbeitungsvorgänge sind nur mit Ihrer ausdrücklichen Einwilligung möglich. Sie können eine bereits erteilte Einwilligung jederzeit widerrufen. Die Rechtmäßigkeit der bis zum Widerruf erfolgten Datenverarbeitung bleibt vom Widerruf unberührt.</p>
|
||||
<h3 class="h5">Widerspruchsrecht gegen die Datenerhebung in besonderen Fällen sowie gegen Direktwerbung (Art. 21 DSGVO)</h3> <p>WENN DIE DATENVERARBEITUNG AUF GRUNDLAGE VON ART. 6 ABS. 1 LIT. E ODER F DSGVO ERFOLGT, HABEN SIE JEDERZEIT DAS RECHT, AUS GRÜNDEN, DIE SICH AUS IHRER BESONDEREN SITUATION ERGEBEN, GEGEN DIE VERARBEITUNG IHRER PERSONENBEZOGENEN DATEN WIDERSPRUCH EINZULEGEN; DIES GILT AUCH FÜR EIN AUF DIESE BESTIMMUNGEN GESTÜTZTES PROFILING. DIE JEWEILIGE RECHTSGRUNDLAGE, AUF DENEN EINE VERARBEITUNG BERUHT, ENTNEHMEN SIE DIESER DATENSCHUTZERKLÄRUNG. WENN SIE WIDERSPRUCH EINLEGEN, WERDEN WIR IHRE BETROFFENEN PERSONENBEZOGENEN DATEN NICHT MEHR VERARBEITEN, ES SEI DENN, WIR KÖNNEN ZWINGENDE SCHUTZWÜRDIGE GRÜNDE FÜR DIE VERARBEITUNG NACHWEISEN, DIE IHRE INTERESSEN, RECHTE UND FREIHEITEN ÜBERWIEGEN ODER DIE VERARBEITUNG DIENT DER GELTENDMACHUNG, AUSÜBUNG ODER VERTEIDIGUNG VON RECHTSANSPRÜCHEN (WIDERSPRUCH NACH ART. 21 ABS. 1 DSGVO).</p> <p>WERDEN IHRE PERSONENBEZOGENEN DATEN VERARBEITET, UM DIREKTWERBUNG ZU BETREIBEN, SO HABEN SIE DAS RECHT, JEDERZEIT WIDERSPRUCH GEGEN DIE VERARBEITUNG SIE BETREFFENDER PERSONENBEZOGENER DATEN ZUM ZWECKE DERARTIGER WERBUNG EINZULEGEN; DIES GILT AUCH FÜR DAS PROFILING, SOWEIT ES MIT SOLCHER DIREKTWERBUNG IN VERBINDUNG STEHT. WENN SIE WIDERSPRECHEN, WERDEN IHRE PERSONENBEZOGENEN DATEN ANSCHLIESSEND NICHT MEHR ZUM ZWECKE DER DIREKTWERBUNG VERWENDET (WIDERSPRUCH NACH ART. 21 ABS. 2 DSGVO).</p>
|
||||
<h3 class="h5">Beschwerde­recht bei der zuständigen Aufsichts­behörde</h3> <p>Im Falle von Verstößen gegen die DSGVO steht den Betroffenen ein Beschwerderecht bei einer Aufsichtsbehörde, insbesondere in dem Mitgliedstaat ihres gewöhnlichen Aufenthalts, ihres Arbeitsplatzes oder des Orts des mutmaßlichen Verstoßes zu. Das Beschwerderecht besteht unbeschadet anderweitiger verwaltungsrechtlicher oder gerichtlicher Rechtsbehelfe.</p>
|
||||
<h3 class="h5">Recht auf Daten­übertrag­barkeit</h3> <p>Sie haben das Recht, Daten, die wir auf Grundlage Ihrer Einwilligung oder in Erfüllung eines Vertrags automatisiert verarbeiten, an sich oder an einen Dritten in einem gängigen, maschinenlesbaren Format aushändigen zu lassen. Sofern Sie die direkte Übertragung der Daten an einen anderen Verantwortlichen verlangen, erfolgt dies nur, soweit es technisch machbar ist.</p>
|
||||
<h3 class="h5">Auskunft, Berichtigung und Löschung</h3> <p>Sie haben im Rahmen der geltenden gesetzlichen Bestimmungen jederzeit das Recht auf unentgeltliche Auskunft über Ihre gespeicherten personenbezogenen Daten, deren Herkunft und Empfänger und den Zweck der Datenverarbeitung und ggf. ein Recht auf Berichtigung oder Löschung dieser Daten. Hierzu sowie zu weiteren Fragen zum Thema personenbezogene Daten können Sie sich jederzeit an uns wenden.</p>
|
||||
<h3 class="h5">Recht auf Einschränkung der Verarbeitung</h3> <p>Sie haben das Recht, die Einschränkung der Verarbeitung Ihrer personenbezogenen Daten zu verlangen. Hierzu können Sie sich jederzeit an uns wenden. Das Recht auf Einschränkung der Verarbeitung besteht in folgenden Fällen:</p> <ul> <li>Wenn Sie die Richtigkeit Ihrer bei uns gespeicherten personenbezogenen Daten bestreiten, benötigen wir in der Regel Zeit, um dies zu überprüfen. Für die Dauer der Prüfung haben Sie das Recht, die Einschränkung der Verarbeitung Ihrer personenbezogenen Daten zu verlangen.</li> <li>Wenn die Verarbeitung Ihrer personenbezogenen Daten unrechtmäßig geschah/geschieht, können Sie statt der Löschung die Einschränkung der Datenverarbeitung verlangen.</li> <li>Wenn wir Ihre personenbezogenen Daten nicht mehr benötigen, Sie sie jedoch zur Ausübung, Verteidigung oder Geltendmachung von Rechtsansprüchen benötigen, haben Sie das Recht, statt der Löschung die Einschränkung der Verarbeitung Ihrer personenbezogenen Daten zu verlangen.</li> <li>Wenn Sie einen Widerspruch nach Art. 21 Abs. 1 DSGVO eingelegt haben, muss eine Abwägung zwischen Ihren und unseren Interessen vorgenommen werden. Solange noch nicht feststeht, wessen Interessen überwiegen, haben Sie das Recht, die Einschränkung der Verarbeitung Ihrer personenbezogenen Daten zu verlangen.</li> </ul> <p>Wenn Sie die Verarbeitung Ihrer personenbezogenen Daten eingeschränkt haben, dürfen diese Daten – von ihrer Speicherung abgesehen – nur mit Ihrer Einwilligung oder zur Geltendmachung, Ausübung oder Verteidigung von Rechtsansprüchen oder zum Schutz der Rechte einer anderen natürlichen oder juristischen Person oder aus Gründen eines wichtigen öffentlichen Interesses der Europäischen Union oder eines Mitgliedstaats verarbeitet werden.</p>
|
||||
<h3 class="h5">SSL- bzw. TLS-Verschlüsselung</h3> <p>Diese Seite nutzt aus Sicherheitsgründen und zum Schutz der Übertragung vertraulicher Inhalte, wie zum Beispiel Bestellungen oder Anfragen, die Sie an uns als Seitenbetreiber senden, eine SSL- bzw. TLS-Verschlüsselung. Eine verschlüsselte Verbindung erkennen Sie daran, dass die Adresszeile des Browsers von „http://“ auf „https://“ wechselt und an dem Schloss-Symbol in Ihrer Browserzeile.</p> <p>Wenn die SSL- bzw. TLS-Verschlüsselung aktiviert ist, können die Daten, die Sie an uns übermitteln, nicht von Dritten mitgelesen werden.</p>
|
||||
<h3 class="h5">Widerspruch gegen Werbe-E-Mails</h3> <p>Der Nutzung von im Rahmen der Impressumspflicht veröffentlichten Kontaktdaten zur Übersendung von nicht ausdrücklich angeforderter Werbung und Informationsmaterialien wird hiermit widersprochen. Die Betreiber der Seiten behalten sich ausdrücklich rechtliche Schritte im Falle der unverlangten Zusendung von Werbeinformationen, etwa durch Spam-E-Mails, vor.</p>
|
||||
<h2>4. Datenerfassung auf dieser Website</h2>
|
||||
<h3 class="h5">Kontaktformular</h3> <p>Wenn Sie uns per Kontaktformular Anfragen zukommen lassen, werden Ihre Angaben aus dem Anfrageformular inklusive der von Ihnen dort angegebenen Kontaktdaten zwecks Bearbeitung der Anfrage und für den Fall von Anschlussfragen bei uns gespeichert. Diese Daten geben wir nicht ohne Ihre Einwilligung weiter.</p> <p>Die Verarbeitung dieser Daten erfolgt auf Grundlage von Art. 6 Abs. 1 lit. b DSGVO, sofern Ihre Anfrage mit der Erfüllung eines Vertrags zusammenhängt oder zur Durchführung vorvertraglicher Maßnahmen erforderlich ist. In allen übrigen Fällen beruht die Verarbeitung auf unserem berechtigten Interesse an der effektiven Bearbeitung der an uns gerichteten Anfragen (Art. 6 Abs. 1 lit. f DSGVO) oder auf Ihrer Einwilligung (Art. 6 Abs. 1 lit. a DSGVO) sofern diese abgefragt wurde; die Einwilligung ist jederzeit widerrufbar.</p> <p>Die von Ihnen im Kontaktformular eingegebenen Daten verbleiben bei uns, bis Sie uns zur Löschung auffordern, Ihre Einwilligung zur Speicherung widerrufen oder der Zweck für die Datenspeicherung entfällt (z. B. nach abgeschlossener Bearbeitung Ihrer Anfrage). Zwingende gesetzliche Bestimmungen – insbesondere Aufbewahrungsfristen – bleiben unberührt.</p>
|
||||
<h3 class="h5">Anfrage per E-Mail, Telefon oder Telefax</h3> <p>Wenn Sie uns per E-Mail, Telefon oder Telefax kontaktieren, wird Ihre Anfrage inklusive aller daraus hervorgehenden personenbezogenen Daten (Name, Anfrage) zum Zwecke der Bearbeitung Ihres Anliegens bei uns gespeichert und verarbeitet. Diese Daten geben wir nicht ohne Ihre Einwilligung weiter.</p> <p>Die Verarbeitung dieser Daten erfolgt auf Grundlage von Art. 6 Abs. 1 lit. b DSGVO, sofern Ihre Anfrage mit der Erfüllung eines Vertrags zusammenhängt oder zur Durchführung vorvertraglicher Maßnahmen erforderlich ist. In allen übrigen Fällen beruht die Verarbeitung auf unserem berechtigten Interesse an der effektiven Bearbeitung der an uns gerichteten Anfragen (Art. 6 Abs. 1 lit. f DSGVO) oder auf Ihrer Einwilligung (Art. 6 Abs. 1 lit. a DSGVO) sofern diese abgefragt wurde; die Einwilligung ist jederzeit widerrufbar.</p> <p>Die von Ihnen an uns per Kontaktanfragen übersandten Daten verbleiben bei uns, bis Sie uns zur Löschung auffordern, Ihre Einwilligung zur Speicherung widerrufen oder der Zweck für die Datenspeicherung entfällt (z. B. nach abgeschlossener Bearbeitung Ihres Anliegens). Zwingende gesetzliche Bestimmungen – insbesondere gesetzliche Aufbewahrungsfristen – bleiben unberührt.</p>
|
||||
<h2>5. Newsletter</h2>
|
||||
<h3 class="h5">Newsletter­daten</h3> <p>Wenn Sie den auf der Website angebotenen Newsletter beziehen möchten, benötigen wir von Ihnen eine E-Mail-Adresse sowie Informationen, welche uns die Überprüfung gestatten, dass Sie der Inhaber der angegebenen E-Mail-Adresse sind und mit dem Empfang des Newsletters einverstanden sind. Weitere Daten werden nicht bzw. nur auf freiwilliger Basis erhoben. Diese Daten verwenden wir ausschließlich für den Versand der angeforderten Informationen und geben diese nicht an Dritte weiter.</p> <p>Die Verarbeitung der in das Newsletteranmeldeformular eingegebenen Daten erfolgt ausschließlich auf Grundlage Ihrer Einwilligung (Art. 6 Abs. 1 lit. a DSGVO). Die erteilte Einwilligung zur Speicherung der Daten, der E-Mail-Adresse sowie deren Nutzung zum Versand des Newsletters können Sie jederzeit widerrufen, etwa über den „Austragen“-Link im Newsletter. Die Rechtmäßigkeit der bereits erfolgten Datenverarbeitungsvorgänge bleibt vom Widerruf unberührt.</p> <p>Die von Ihnen zum Zwecke des Newsletter-Bezugs bei uns hinterlegten Daten werden von uns bis zu Ihrer Austragung aus dem Newsletter bei uns bzw. dem Newsletterdiensteanbieter gespeichert und nach der Abbestellung des Newsletters oder nach Zweckfortfall aus der Newsletterverteilerliste gelöscht. Wir behalten uns vor, E-Mail-Adressen aus unserem Newsletterverteiler nach eigenem Ermessen im Rahmen unseres berechtigten Interesses nach Art. 6 Abs. 1 lit. f DSGVO zu löschen oder zu sperren.</p> <p>Daten, die zu anderen Zwecken bei uns gespeichert wurden, bleiben hiervon unberührt.</p> <p>Nach Ihrer Austragung aus der Newsletterverteilerliste wird Ihre E-Mail-Adresse bei uns bzw. dem Newsletterdiensteanbieter ggf. in einer Blacklist gespeichert, sofern dies zur Verhinderung künftiger Mailings erforderlich ist. Die Daten aus der Blacklist werden nur für diesen Zweck verwendet und nicht mit anderen Daten zusammengeführt. Dies dient sowohl Ihrem Interesse als auch unserem Interesse an der Einhaltung der gesetzlichen Vorgaben beim Versand von Newslettern (berechtigtes Interesse im Sinne des Art. 6 Abs. 1 lit. f DSGVO). Die Speicherung in der Blacklist ist zeitlich nicht befristet. <strong>Sie können der Speicherung widersprechen, sofern Ihre Interessen unser berechtigtes Interesse überwiegen.</strong></p>
|
||||
<h2>6. Plugins und Tools</h2>
|
||||
<h3 class="h5">Google Fonts (lokales Hosting)</h3> <p>Diese Seite nutzt zur einheitlichen Darstellung von Schriftarten so genannte Google Fonts, die von Google bereitgestellt werden. Die Google Fonts sind lokal installiert. Eine Verbindung zu Servern von Google findet dabei nicht statt.</p> <p>Weitere Informationen zu Google Fonts finden Sie unter <a href="https://developers.google.com/fonts/faq" target="_blank" rel="noopener noreferrer">https://developers.google.com/fonts/faq</a> und in der Datenschutzerklärung von Google: <a href="https://policies.google.com/privacy?hl=de" target="_blank" rel="noopener noreferrer">https://policies.google.com/privacy?hl=de</a>.</p>
|
||||
<h3 class="h5">Font Awesome (lokales Hosting)</h3> <p>Diese Seite nutzt zur einheitlichen Darstellung von Schriftarten Font Awesome. Font Awesome ist lokal installiert. Eine Verbindung zu Servern von Fonticons, Inc. findet dabei nicht statt.</p> <p>Weitere Informationen zu Font Awesome finden Sie in der Datenschutzerklärung für Font Awesome unter: <a href="https://fontawesome.com/privacy" target="_blank" rel="noopener noreferrer">https://fontawesome.com/privacy</a>.</p>
|
||||
<p>Quelle: <a href="https://www.e-recht24.de">https://www.e-recht24.de</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
setup () {
|
||||
const { t } = useI18n()
|
||||
const localePath = useLocalePath()
|
||||
return { t, localePath }
|
||||
}
|
||||
}
|
||||
</script>
|
310
pages/settings/donation.vue
Normal file
310
pages/settings/donation.vue
Normal file
@@ -0,0 +1,310 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-12 col-lg-9 col-md-11 col-sm-12 col-xl-8">
|
||||
<h1 class="h3 fw-bold text-center pt-3">
|
||||
{{ t("Donation") }}
|
||||
</h1>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<p class="fw-light text-center py-3">
|
||||
{{ t("DonationHeading") }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex flex-column flex-md-row gap-3">
|
||||
<div class="col-12 py-1 flex-auto">
|
||||
<div class="card" :class="{'active-sub':oneMonth,'sub-card':!oneMonth}" @click="oneMonth=true;twoMonth=false;threeMonth=false">
|
||||
<div class="card-body py-4">
|
||||
<h2 class="text-center fw-bolder">
|
||||
€3.00
|
||||
</h2>
|
||||
<p class="text-center">
|
||||
{{ t("SmallAmount") }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 py-1 flex-auto">
|
||||
<div class="card" :class="{'active-sub':twoMonth,'sub-card':!twoMonth}" @click="twoMonth=true;oneMonth=false;threeMonth=false">
|
||||
<div class="card-body py-4">
|
||||
<h2 class="text-center fw-bolder">
|
||||
€10.00
|
||||
</h2>
|
||||
<p class=" text-center">
|
||||
{{ t("MediumAmount") }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 py-1 flex-auto">
|
||||
<div class="card" :class="{'active-sub':threeMonth,'sub-card':!threeMonth}" @click="threeMonth=true;twoMonth=false;oneMonth=false">
|
||||
<div class="card-body py-4">
|
||||
<h2 class="text-center fw-bolder">
|
||||
€15.00
|
||||
</h2>
|
||||
<p class=" text-center">
|
||||
{{ t("BigAmount") }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row pt-5">
|
||||
<div class="col-12 px-md-5 px-0">
|
||||
<p class="text-center text-muted">
|
||||
{{ t("EndStatement") }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-12 text-end" />
|
||||
</div>
|
||||
<div class="row pt-5 justify-content-center ">
|
||||
<div class="col-12 col-md-6 col-lg-6 text-center">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<p class="fw-medium text-center"><strong>{{ t("bankaccount") }}:</strong><br></p>
|
||||
<p class="fw-light text-center">
|
||||
{{ t("Name") }}: Robert Rapp<br>
|
||||
</p>
|
||||
<p class="fw-light text-center">
|
||||
IBAN: DE42 1001 1001 2629 4590 53
|
||||
<span class="tooltip-container">
|
||||
<i class="fas fa-copy ml-2 cursor-pointer" title="In die Zwischenablage kopieren" @click="copyIBANToClipboard('DE42 1001 1001 2629 4590 53')" />
|
||||
<span class="tooltiptext" :class="{ 'show': showTooltip }">Kopiert!</span>
|
||||
</span>
|
||||
</p>
|
||||
<p class="fw-light text-center">
|
||||
BIC: NTSBDEB1XXX
|
||||
</p>
|
||||
<p class="fw-light text-center">
|
||||
{{ userVerwendungszweck }}
|
||||
<span class="tooltip-container">
|
||||
<i class="fas fa-copy ml-2 cursor-pointer" title="In die Zwischenablage kopieren" @click="copyToClipboard(userVerwendungszweck)" />
|
||||
<span class="tooltiptext" :class="{ 'show': showTooltip1 }">Kopiert!</span>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <StripeElements-->
|
||||
<!-- v-if="stripeLoaded"-->
|
||||
<!-- v-slot="{ elements, instance }" ref="elms"-->
|
||||
<!-- :stripe-key="stripeKey"-->
|
||||
<!-- :instance-options="instanceOptions"-->
|
||||
<!-- :elements-options="elementsOptions"-->
|
||||
<!-- >-->
|
||||
<!-- <StripeElement-->
|
||||
<!-- ref="card"-->
|
||||
<!-- :elements="elements"-->
|
||||
<!-- :options="cardOptions"-->
|
||||
<!-- />-->
|
||||
<!-- </StripeElements>-->
|
||||
<!-- <button type="button" class="btn text-white fs-5 col-12 fw-bold py-2" @click="pay" style="background-color: #e9c046">Pay</button>-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import { mapActions, mapStores } from 'pinia'
|
||||
import { useUserStore } from '~/stores/user'
|
||||
export default {
|
||||
setup () {
|
||||
const { t } = useI18n()
|
||||
const localePath = useLocalePath()
|
||||
const userVerwendungszweck = ref(useUserStore().user.email)
|
||||
const usermail = useUserStore().user.email
|
||||
|
||||
return { t, localePath, userVerwendungszweck, usermail }
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
oneMonth: false,
|
||||
twoMonth: false,
|
||||
threeMonth: false,
|
||||
loading: false,
|
||||
cancel_loading: false,
|
||||
showTooltip: false,
|
||||
showTooltip1: false,
|
||||
subscription: '',
|
||||
form: {
|
||||
type: 'one'
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// ...mapState(useUserStore, ['user']),
|
||||
...mapStores(useUserStore)
|
||||
},
|
||||
mounted () {
|
||||
this.fetchSubscription()
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapActions(useUserStore, ['updateUser', 'logout']),
|
||||
copyToClipboard (text) {
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
this.showTooltip1 = true
|
||||
setTimeout(() => {
|
||||
this.showTooltip1 = false
|
||||
}, 2000) // Tooltip verschwindet nach 2 Sekunden
|
||||
}).catch((err) => {
|
||||
useNuxtApp().$logger.error('Fehler beim Kopieren: ', err)
|
||||
})
|
||||
},
|
||||
copyIBANToClipboard (text) {
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
this.showTooltip = true
|
||||
setTimeout(() => {
|
||||
this.showTooltip = false
|
||||
}, 2000) // Tooltip verschwindet nach 2 Sekunden
|
||||
}).catch((err) => {
|
||||
useNuxtApp().$logger.error('Fehler beim Kopieren: ', err)
|
||||
})
|
||||
},
|
||||
updateUserNow () {
|
||||
this.$axios.post('/api/auth/me').then(({ data }) => {
|
||||
this.updateUser(data.user)
|
||||
}).catch(() => {
|
||||
this.logout()
|
||||
this.$router.push(this.localePath('/auth/login'))
|
||||
})
|
||||
},
|
||||
paypal () {
|
||||
window.open('https://www.paypal.com/pools/c/93YzF9GqEP', '_blank')
|
||||
},
|
||||
fetchSubscription () {
|
||||
this.$axios.post('/api/fetch-subscription').then(({ data }) => {
|
||||
this.subscription = data.name
|
||||
if (data.name === 'monthly') {
|
||||
this.oneMonth = true
|
||||
this.twoMonth = false
|
||||
this.threeMonth = false
|
||||
}
|
||||
if (data.name === 'sixmonth') {
|
||||
this.oneMonth = false
|
||||
this.twoMonth = true
|
||||
this.threeMonth = false
|
||||
}
|
||||
if (data.name === 'yearly') {
|
||||
this.oneMonth = false
|
||||
this.twoMonth = false
|
||||
this.threeMonth = true
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// export default {
|
||||
// name: 'CardOnly',
|
||||
// data() {
|
||||
// return {
|
||||
// stripeKey: 'pk_test_0S0H9CTYtkhPlArgg4KkPFcZ',
|
||||
// instanceOptions: {},
|
||||
// elementsOptions: {},
|
||||
// cardOptions: {},
|
||||
// stripeLoaded: false,
|
||||
// card: null,
|
||||
// elms: null,
|
||||
// oneMonth:false,
|
||||
// twoMonth:false,
|
||||
// threeMonth:false,
|
||||
// };
|
||||
// },
|
||||
// components: {
|
||||
// StripeElements,
|
||||
// StripeElement,
|
||||
// },
|
||||
// beforeMount() {
|
||||
// const stripePromise = loadStripe(this.stripeKey);
|
||||
// stripePromise.then(() => {
|
||||
// this.stripeLoaded = true;
|
||||
// });
|
||||
// },
|
||||
// methods: {
|
||||
// pay() {
|
||||
// const cardElement = this.$refs.card.stripeElement;
|
||||
//
|
||||
// this.$refs.elms.instance
|
||||
// .createPaymentMethod({ type: 'card', card: cardElement })
|
||||
// .then((result) => {
|
||||
// // useNuxtApp().$logger.log(result);
|
||||
// // useNuxtApp().$logger.log(result.paymentMethod);
|
||||
// this.$axios.post('/api/subscribe', result).then((response) => {
|
||||
// // useNuxtApp().$logger.log(response.data);
|
||||
// });
|
||||
// });
|
||||
// },
|
||||
// },
|
||||
// computed: {
|
||||
//
|
||||
// },
|
||||
// created() {
|
||||
//
|
||||
// },
|
||||
// };
|
||||
</script>
|
||||
<style>
|
||||
.flex-auto {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
.active-sub{
|
||||
background: #E9C046;
|
||||
box-shadow: 0px 0px 16px 1px rgba(108, 97, 97, 0.05);
|
||||
border-radius: 12px;
|
||||
color:white;
|
||||
background-color:#E9C046 !important;
|
||||
}
|
||||
.sub-card{
|
||||
background: #FFFFFF;
|
||||
box-shadow: 0px 0px 16px 1px rgba(108, 97, 97, 0.05);
|
||||
border-radius: 12px;
|
||||
color:black;
|
||||
height: 100%;
|
||||
transition: 250ms ease-in-out;
|
||||
}
|
||||
.sub-card:hover, .sub-card:focus{
|
||||
background: #E9C046;
|
||||
box-shadow: 0px 0px 16px 1px rgba(108, 97, 97, 0.05);
|
||||
border-radius: 12px;
|
||||
color:white;
|
||||
border-color: #E9C046;
|
||||
background-color:#E9C046 !important;
|
||||
}
|
||||
.tooltip-container {
|
||||
margin-left: 10px;
|
||||
font-size: smaller;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.tooltiptext {
|
||||
visibility: hidden;
|
||||
font-family: 'Montserrat', sans-serif;
|
||||
font-size: 14px;
|
||||
width: auto;
|
||||
background-color: #e9c046;
|
||||
color: #000000;
|
||||
text-align: center;
|
||||
border-radius: 6px;
|
||||
padding: 5px;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
bottom: 125%;
|
||||
left: 20%;
|
||||
margin-left: -30px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
.tooltiptext.show {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
632
pages/settings/donation_paddle.vue
Normal file
632
pages/settings/donation_paddle.vue
Normal file
@@ -0,0 +1,632 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-12 col-sm-9">
|
||||
<h2 class="fw-bold text-center me-5 pt-5">
|
||||
<span class="float-start" style="cursor: pointer" @click="$router.go(-1)"><i
|
||||
class="fa-solid fa-arrow-left-long"
|
||||
/></span> {{ t("Donation") }}
|
||||
</h2>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<h4 class="fw-light text-center pt-3">
|
||||
{{ t("DonationHeading") }}
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<section class="pricing py-5">
|
||||
<div class="row">
|
||||
<!-- Basic Tier -->
|
||||
<div class="col-md-4">
|
||||
<div id="basic-plan" class="card mb-5 mb-lg-0" data-plan="starter">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title text-muted text-uppercase text-center">
|
||||
Basic
|
||||
</h5>
|
||||
<h6 class="card-price text-center">
|
||||
<span class="price" data-price-type="starter">8.00</span><span class="period">/month</span>
|
||||
</h6>
|
||||
<div v-if="currentSubscription() == 1" class="d-flex justify-content-center my-2">
|
||||
<span class="badge bg-warning">Active</span>
|
||||
</div>
|
||||
<hr>
|
||||
<ul class="fa-ul">
|
||||
<li><span class="fa-li"><i class="fas fa-check" /></span>Good for students, employees</li>
|
||||
<li><span class="fa-li"><i class="fas fa-check" /></span>Full access to app</li>
|
||||
<li><span class="fa-li"><i class="fas fa-check" /></span>3 Professional soundscapes</li>
|
||||
<li><span class="fa-li"><i class="fas fa-check" /></span>mindboost@technology</li>
|
||||
<li><span class="fa-li"><i class="fas fa-check" /></span>30 Days Trial</li>
|
||||
<li>
|
||||
<div class="pricing-toggle my-2 d-flex align-items-center">
|
||||
<div class="me-3">
|
||||
<input
|
||||
id="starter-month"
|
||||
type="radio"
|
||||
class="me-1"
|
||||
name="cycle-starter"
|
||||
value="month"
|
||||
:checked="currentSubscription() === 1 && dataStore.user.montly === '1' || currentSubscription() !== 1"
|
||||
>
|
||||
<label for="starter-month" class="form-check-label">Monthly</label>
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
id="starter-year"
|
||||
type="radio"
|
||||
class="me-1"
|
||||
name="cycle-starter"
|
||||
:checked="currentSubscription() == 1 && dataStore.user.yearly === '1'"
|
||||
value="year"
|
||||
>
|
||||
<label for="starter-year" class="form-check-label">Yearly <sup>save 20%</sup></label>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="d-grid">
|
||||
<span v-if="isInvited">
|
||||
<div class="row">
|
||||
<button class="btn btn-warning text-uppercase buy-now" @click="showManagerPlan()">Buy
|
||||
Now</button>
|
||||
</div>
|
||||
</span>
|
||||
<span v-else>
|
||||
<div class="row">
|
||||
<button
|
||||
class="btn btn-warning text-uppercase buy-now"
|
||||
:disabled="currentSubscription() === 1"
|
||||
>Active</button>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Team Tier -->
|
||||
<div class="col-md-4">
|
||||
<div id="team-plan" class="card mb-5 mb-lg-0" data-plan="pro">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title text-muted text-uppercase text-center">
|
||||
Team
|
||||
</h5>
|
||||
<h6 class="card-price text-center">
|
||||
<span class="price" data-price-type="pro">5</span><span class="period">/month</span>
|
||||
</h6>
|
||||
<div v-if="currentSubscription() == 2" class="d-flex justify-content-center my-2">
|
||||
<span class="badge bg-warning">Active</span>
|
||||
</div>
|
||||
<hr>
|
||||
<ul class="fa-ul">
|
||||
<li>
|
||||
<span class="fa-li"><i class="fas fa-check" /></span>Good for colleagues, groups, teams,
|
||||
departments
|
||||
</li>
|
||||
<li><span class="fa-li"><i class="fas fa-check" /></span>Full access to app</li>
|
||||
<li><span class="fa-li"><i class="fas fa-check" /></span>3 Professional soundscapes</li>
|
||||
<li><span class="fa-li"><i class="fas fa-check" /></span>mindboost@technology</li>
|
||||
<li><span class="fa-li"><i class="fas fa-plus" /></span>Individual User Management</li>
|
||||
<li><span class="fa-li"><i class="fas fa-plus" /></span>Flexible Trial Version</li>
|
||||
<li><span class="fa-li"><i class="fas fa-plus" /></span>Coherent invoicing</li>
|
||||
<li><span class="fa-li"><i class="fas fa-plus" /></span> + Basic Per User</li>
|
||||
<li><span class="fa-li"><i class="fas fa-plus" /></span>30 Days Trial</li>
|
||||
<li>
|
||||
<div class="pricing-toggle my-2 d-flex align-items-center">
|
||||
<div class="me-3">
|
||||
<input
|
||||
id="pro-month"
|
||||
type="radio"
|
||||
class="me-1"
|
||||
name="cycle-pro"
|
||||
value="month"
|
||||
:checked="currentSubscription() === 2 && dataStore.user.montly === '1' || currentSubscription() !== 2"
|
||||
>
|
||||
<label for="pro-month" class="form-check-label">Monthly</label>
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
id="pro-year"
|
||||
type="radio"
|
||||
class="me-1"
|
||||
name="cycle-pro"
|
||||
:checked="currentSubscription() === 2 && dataStore.user.yearly === '1'"
|
||||
value="year"
|
||||
>
|
||||
<label for="pro-year" class="form-check-label">Yearly <sup>save 20%</sup></label>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="d-grid">
|
||||
<span v-if="isInvited">
|
||||
<div class="row">
|
||||
<button class="btn btn-warning text-uppercase team-plan" @click="showManagerPlan()">Buy
|
||||
Now</button>
|
||||
</div>
|
||||
</span>
|
||||
<span v-else>
|
||||
<div class="row">
|
||||
<button
|
||||
class="btn btn-warning text-uppercase team-plan"
|
||||
:disabled="currentSubscription() === 2"
|
||||
>Active</button>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Enterprise Tier -->
|
||||
<div class="col-md-4">
|
||||
<div id="enterprise-plan" class="card" data-plan="enterprise">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title text-muted text-uppercase text-center">
|
||||
Enterprise
|
||||
</h5>
|
||||
<h6 class="card-price text-center">
|
||||
<span>Depends</span>
|
||||
</h6>
|
||||
<div v-if="currentSubscription() == 3" class="d-flex justify-content-center my-2">
|
||||
<span class="badge bg-warning">Active</span>
|
||||
</div>
|
||||
<hr>
|
||||
<ul class="fa-ul">
|
||||
<li><span class="fa-li"><i class="fas fa-check" /></span>Good For Enterprise > 200</li>
|
||||
<li><span class="fa-li"><i class="fas fa-check" /></span>Full access to app</li>
|
||||
<li><span class="fa-li"><i class="fas fa-check" /></span>3 Professional soundscapes</li>
|
||||
<li><span class="fa-li"><i class="fas fa-check" /></span>mindboost@technology</li>
|
||||
<li><span class="fa-li"><i class="fas fa-check" /></span>Individual User Management</li>
|
||||
<li><span class="fa-li"><i class="fas fa-check" /></span>Flexible Trial Version</li>
|
||||
<li><span class="fa-li"><i class="fas fa-check" /></span>Coherent invoicing</li>
|
||||
<li><span class="fa-li"><i class="fas fa-plus" /></span>Dedicated Partnership</li>
|
||||
<li><span class="fa-li"><i class="fas fa-plus" /></span>In-person Onboarding</li>
|
||||
<li><span class="fa-li"><i class="fas fa-plus" /></span>Individual Pricing</li>
|
||||
<li><span class="fa-li"><i class="fas fa-plus" /></span>30 Days Trial</li>
|
||||
<li>
|
||||
<div class="pricing-toggle my-2 d-flex align-items-center">
|
||||
<div class="me-3">
|
||||
<input
|
||||
id="enterprise-month"
|
||||
type="radio"
|
||||
class="me-1"
|
||||
name="cycle-enterprise"
|
||||
value="month"
|
||||
checked
|
||||
>
|
||||
<label for="enterprise-month" class="form-check-label">Monthly</label>
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
id="enterprise-year"
|
||||
type="radio"
|
||||
class="me-1"
|
||||
name="cycle-enterprise"
|
||||
value="year"
|
||||
>
|
||||
<label for="enterprise-year" class="form-check-label">Yearly <sup>save 20%</sup></label>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="d-grid">
|
||||
<span v-if="isInvited">
|
||||
<div class="row">
|
||||
<button type="button" class="btn btn-warning text-uppercase" @click="showManagerPlan()">
|
||||
contact us
|
||||
</button>
|
||||
</div>
|
||||
</span>
|
||||
<span v-else>
|
||||
<div class="row">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-warning text-uppercase"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#exampleModal"
|
||||
>
|
||||
contact us
|
||||
</button>
|
||||
</div>
|
||||
</span>
|
||||
|
||||
<!-- Modal for enterprise contact us form -->
|
||||
<div
|
||||
id="exampleModal"
|
||||
class="modal fade"
|
||||
tabindex="-1"
|
||||
aria-labelledby="exampleModalLabel"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 id="exampleModalLabel" class="modal-title fs-5">
|
||||
Contact Us
|
||||
</h1>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" />
|
||||
</div>
|
||||
<form @submit.prevent="submitForm">
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label for="name" class="form-label">Name</label>
|
||||
<input
|
||||
id="name"
|
||||
v-model="contact_form.name"
|
||||
type="text"
|
||||
class="form-control"
|
||||
required
|
||||
>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="companyName" class="form-label">Company Name</label>
|
||||
<input
|
||||
id="companyName"
|
||||
v-model="contact_form.company_name"
|
||||
type="text"
|
||||
class="form-control"
|
||||
required
|
||||
>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="message" class="form-label">Message</label>
|
||||
<textarea id="message" v-model="contact_form.message" class="form-control" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-warning text-uppercase">
|
||||
Send Message
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapActions, mapStores } from 'pinia'
|
||||
import { useUserStore } from '~/stores/user'
|
||||
// entery point
|
||||
export default {
|
||||
components: { },
|
||||
setup () {
|
||||
const { t } = useI18n()
|
||||
const localePath = useLocalePath()
|
||||
const dataStore = useUserStore() // Initialize store in setup()
|
||||
|
||||
return {
|
||||
t,
|
||||
localePath,
|
||||
dataStore
|
||||
}
|
||||
},
|
||||
// dara store
|
||||
data () {
|
||||
return {
|
||||
oneMonth: false,
|
||||
twoMonth: false,
|
||||
threeMonth: false,
|
||||
loading: false,
|
||||
cancel_loading: false,
|
||||
subscription: '',
|
||||
form: {
|
||||
type: 'one'
|
||||
},
|
||||
contact_form: {
|
||||
name: '',
|
||||
company_name: '',
|
||||
email: 'dowhf@access.bro', // Removed direct reference to userStore
|
||||
message: ''
|
||||
},
|
||||
isInvited: 0,
|
||||
isManagerPlan: 0
|
||||
}
|
||||
},
|
||||
// computed
|
||||
computed: {
|
||||
...mapStores(useUserStore)
|
||||
},
|
||||
// mounted
|
||||
mounted () {
|
||||
const urlBackend = useRuntimeConfig().public.BACKEND_URL || 'https://b.mindboost.team'
|
||||
this.fetchSubscription()
|
||||
this.GetManagerDetails()
|
||||
|
||||
if (window.Paddle) {
|
||||
// Initialize Paddle
|
||||
Paddle.Environment.set('sandbox')
|
||||
Paddle.Initialize({
|
||||
token: 'test_27bb608597d1a4d75fe1bce2be7',
|
||||
eventCallback: async function (data) {
|
||||
if (data.name === 'checkout.completed') {
|
||||
// useNuxtApp().$logger.log(data)
|
||||
try {
|
||||
const response = await axios.post(urlBackend + '/api/paddle-webhook', data)
|
||||
// useNuxtApp().$logger.log('this is subscription data data', response)
|
||||
// Access the Pinia store and update the user data
|
||||
const userStore = useUserStore()
|
||||
userStore.updateUser(response.data.user)
|
||||
} catch (error) {
|
||||
// useNuxtApp().$logger.log(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
// Product definitions for each plan
|
||||
const products = {
|
||||
starter: {
|
||||
productId: 'pro_01j508hk3rp5f5g68n7tqv1thd',
|
||||
monthPriceId: 'pri_01j508ssstj4n6xra9j8ymr3d1',
|
||||
yearPriceId: 'pri_01j508x0rwntqs581rj3xfx49n'
|
||||
},
|
||||
pro: {
|
||||
productId: 'pro_01j5090jy3jkf4w9vr0j279fm5',
|
||||
monthPriceId: 'pri_01j50947nd6t9ztgsrq5h6fsmx',
|
||||
yearPriceId: 'pri_01j5097bzyp9zrevw0nsagb477'
|
||||
},
|
||||
enterprise: {
|
||||
productId: 'pro_01j509nre47wbdtmqmd6x098s5',
|
||||
monthPriceId: 'pri_01j509qknvfx3hv0459kpkvvar',
|
||||
yearPriceId: 'pri_01j509ryg22rzs0ahwrb2vwzhq'
|
||||
}
|
||||
}
|
||||
|
||||
// Functions to handle pricing updates
|
||||
const updateBasicPlan = () => {
|
||||
const plan = 'starter'
|
||||
const cycle = document.querySelector('input[name="cycle-starter"]:checked').value
|
||||
const priceId = cycle === 'month' ? products[plan].monthPriceId : products[plan].yearPriceId
|
||||
Paddle.PricePreview({ items: [{ quantity: 1, priceId }] })
|
||||
.then((result) => {
|
||||
document.querySelector('#basic-plan .price').textContent = result.data.details.lineItems[0].formattedTotals.subtotal
|
||||
document.querySelector('#basic-plan .period').textContent = cycle === 'month' ? '/month' : '/year'
|
||||
})
|
||||
}
|
||||
|
||||
// Update the basic plan pricing on page load
|
||||
const updateTeamPlan = () => {
|
||||
const plan = 'pro'
|
||||
const cycle = document.querySelector('input[name="cycle-pro"]:checked').value
|
||||
const priceId = cycle === 'month' ? products[plan].monthPriceId : products[plan].yearPriceId
|
||||
Paddle.PricePreview({ items: [{ quantity: 5, priceId }] })
|
||||
.then((result) => {
|
||||
document.querySelector('#team-plan .price').textContent = result.data.details.lineItems[0].formattedTotals.subtotal
|
||||
document.querySelector('#team-plan .period').textContent = cycle === 'month' ? '/month' : '/year'
|
||||
})
|
||||
}
|
||||
|
||||
// Update the enterprise plan pricing on page load
|
||||
const updateEnterprisePlan = () => {
|
||||
const plan = 'enterprise'
|
||||
const cycle = document.querySelector('input[name="cycle-enterprise"]:checked').value
|
||||
const priceId = cycle === 'month' ? products[plan].monthPriceId : products[plan].yearPriceId
|
||||
Paddle.PricePreview({ items: [{ quantity: 1, priceId }] })
|
||||
.then((result) => {
|
||||
document.querySelector('#enterprise-plan .price').textContent = result.data.details.lineItems[0].formattedTotals.subtotal
|
||||
document.querySelector('#enterprise-plan .period').textContent = cycle === 'month' ? '/month' : '/year'
|
||||
})
|
||||
}
|
||||
|
||||
// Event handlers for plan and cycle changes
|
||||
const handlePlanChange = (event) => {
|
||||
const plan = event.target.closest('.card').getAttribute('data-plan')
|
||||
if (plan === 'starter') {
|
||||
updateBasicPlan()
|
||||
} else if (plan === 'pro') {
|
||||
updateTeamPlan()
|
||||
} else if (plan === 'enterprise') {
|
||||
updateEnterprisePlan()
|
||||
}
|
||||
}
|
||||
|
||||
// Set event listeners for plan and cycle changes
|
||||
document.querySelectorAll('input[name^="cycle-"]').forEach((input) => {
|
||||
input.addEventListener('change', handlePlanChange)
|
||||
})
|
||||
|
||||
// Event handlers for buy now buttons
|
||||
document.querySelectorAll('.buy-now').forEach((button) => {
|
||||
button.addEventListener('click', () => {
|
||||
const plan = button.closest('.card').getAttribute('data-plan')
|
||||
const cycle = document.querySelector(`input[name="cycle-${plan}"]:checked`).value
|
||||
const priceId = cycle === 'month' ? products[plan].monthPriceId : products[plan].yearPriceId
|
||||
Paddle.Checkout.open({
|
||||
items: [{ priceId, quantity: 1 }],
|
||||
customer: {
|
||||
email: this.dataStore.user.email,
|
||||
address: { countryCode: 'PK', postalCode: '59351' }
|
||||
},
|
||||
source: window.location.href
|
||||
})
|
||||
})
|
||||
})
|
||||
// Event handlers for team-plan class
|
||||
document.querySelectorAll('.team-plan').forEach((button) => {
|
||||
button.addEventListener('click', () => {
|
||||
const plan = button.closest('.card').getAttribute('data-plan')
|
||||
const cycle = document.querySelector(`input[name="cycle-${plan}"]:checked`).value
|
||||
const priceId = cycle === 'month' ? products[plan].monthPriceId : products[plan].yearPriceId
|
||||
Paddle.Checkout.open({
|
||||
items: [{ priceId, quantity: 5 }],
|
||||
customer: {
|
||||
email: this.dataStore.user.email,
|
||||
address: { countryCode: 'PK', postalCode: '59351' }
|
||||
},
|
||||
source: window.location.href
|
||||
})
|
||||
})
|
||||
})
|
||||
// Initialize prices
|
||||
updateBasicPlan()
|
||||
updateTeamPlan()
|
||||
updateEnterprisePlan()
|
||||
}
|
||||
},
|
||||
// ======================= methods =======================
|
||||
methods: {
|
||||
// check user plan
|
||||
isDisabled () {
|
||||
const dataStore = useUserStore()
|
||||
const userasdf = dataStore
|
||||
// useNuxtApp().$logger.log('store', user); // Debugging
|
||||
return !(userasdf.user.team_subscription_plan === '1' ||
|
||||
userasdf.user.enterprise_subscription_plan === '1') &&
|
||||
(userasdf.user.basic_subscription_plan !== '0')
|
||||
},
|
||||
// get user current plan
|
||||
currentSubscription () {
|
||||
const dataStore = useUserStore()
|
||||
const userasdf = dataStore
|
||||
return (userasdf.user.current_subscription_plan)
|
||||
},
|
||||
/**
|
||||
* showManagerPlan in toster form
|
||||
*/
|
||||
showManagerPlan () {
|
||||
let managerPlan = '' // Changed from const to let
|
||||
if (this.isManagerPlan === 1) {
|
||||
managerPlan = 'Basic'
|
||||
} else if (this.isManagerPlan === 2) {
|
||||
managerPlan = 'Team'
|
||||
} else if (this.isManagerPlan === 3) {
|
||||
managerPlan = 'Enterprise'
|
||||
}
|
||||
|
||||
this.$toast.success(`Manager already has the ${managerPlan} plan`) // Fixed typo
|
||||
},
|
||||
/**
|
||||
* Submits the contact form to the server
|
||||
*/
|
||||
async submitForm () {
|
||||
try {
|
||||
await axios.post(urlBackend + '/api/enterprise-plan-contact', this.contact_form)
|
||||
// useNuxtApp().$logger.log('Success:', response.data)
|
||||
Swal.fire({
|
||||
title: 'Good job!',
|
||||
text: 'Message sent successfully! Support team will contact you soon.',
|
||||
icon: 'success'
|
||||
})
|
||||
} catch (error) {
|
||||
// useNuxtApp().$logger.error('Error:', error.response);
|
||||
this.$toast.error('Oops, something went wrong!')
|
||||
}
|
||||
},
|
||||
...mapActions(useUserStore, ['updateUser', 'logout']),
|
||||
updateUserNow () {
|
||||
this.$axios.post('/api/auth/me').then(({ data }) => {
|
||||
this.updateUser(data.user)
|
||||
}).catch(() => {
|
||||
this.logout()
|
||||
this.$router.push(this.localePath('/auth/login'))
|
||||
})
|
||||
},
|
||||
paypal () {
|
||||
window.open('https://www.paypal.com/pools/c/93YzF9GqEP', '_blank')
|
||||
},
|
||||
fetchSubscription () {
|
||||
this.$axios.post('/api/fetch-subscription').then(({ data }) => {
|
||||
this.subscription = data.name
|
||||
this.oneMonth = data.name === 'monthly'
|
||||
this.twoMonth = data.name === 'sixmonth'
|
||||
this.threeMonth = data.name === 'yearly'
|
||||
})
|
||||
},
|
||||
async GetManagerDetails () {
|
||||
const userStore = useUserStore()
|
||||
if (userStore.user.inviter_id) {
|
||||
const response = await axios.post(urlBackend + '/api/fetch-manger-detail', { manager_id: userStore.user.inviter_id })
|
||||
// useNuxtApp().$logger.log('manager_id', response.data.data)
|
||||
this.isInvited = 1
|
||||
// useNuxtApp().$logger.log('response.data.data.inviter_id', response.data.data.inviter_id)
|
||||
this.isManagerPlan = response.data.data.current_subscription_plan
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.active-sub {
|
||||
background: #E9C046;
|
||||
box-shadow: 0px 0px 16px 1px rgba(108, 97, 97, 0.05);
|
||||
border-radius: 12px;
|
||||
color: white;
|
||||
background-color: #E9C046 !important;
|
||||
}
|
||||
|
||||
.sub-card {
|
||||
background: #FFFFFF;
|
||||
box-shadow: 0px 0px 16px 1px rgba(108, 97, 97, 0.05);
|
||||
border-radius: 12px;
|
||||
color: black;
|
||||
}
|
||||
|
||||
/* style for plan cards */
|
||||
.pricing .card {
|
||||
border: none;
|
||||
border-radius: 1rem;
|
||||
transition: all 0.2s;
|
||||
box-shadow: 0 0.5rem 1rem 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.pricing hr {
|
||||
margin: 1.5rem 0;
|
||||
}
|
||||
|
||||
.pricing .card-title {
|
||||
margin: 0.5rem 0;
|
||||
font-size: 0.9rem;
|
||||
letter-spacing: .1rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.pricing .card-price {
|
||||
font-size: 3rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.pricing .card-price .period {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.pricing ul li {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.pricing .text-muted {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.pricing .btn {
|
||||
font-size: 80%;
|
||||
border-radius: 5rem;
|
||||
letter-spacing: .1rem;
|
||||
font-weight: bold;
|
||||
padding: 1rem;
|
||||
opacity: 0.7;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
/* Hover Effects on Card */
|
||||
|
||||
@media (min-width: 992px) {
|
||||
.pricing .card:hover {
|
||||
margin-top: -.25rem;
|
||||
margin-bottom: .25rem;
|
||||
box-shadow: 0 0.5rem 1rem 0 rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.pricing .card:hover .btn {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
175
pages/settings/editaccount.vue
Normal file
175
pages/settings/editaccount.vue
Normal file
@@ -0,0 +1,175 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-12 col-lg-9 col-md-11 col-sm-12 col-xl-8">
|
||||
<h1 class="h3 fw-bold text-center py-3">
|
||||
<span class="float-start" style="cursor: pointer" @click="$router.go(-1)"><i class="fa-solid fa-arrow-left-long" /></span>
|
||||
{{ t("Edit Account") }}
|
||||
</h1>
|
||||
<div class="row pt-4">
|
||||
<div class="col-12">
|
||||
<form @submit.prevent="validateAndSave">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<label for="firstname" class="text-muted ">{{ t("First Name") }} </label>
|
||||
<input id="firstname" v-model="form.first_name" type="text" class="form-control" :placeholder="t('First Name')">
|
||||
<div v-if="errors.first_name" class="invalid-feedback d-block">
|
||||
{{ errors.first_name[0] }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row pt-3">
|
||||
<div class="col-12">
|
||||
<label for="surname" class="text-muted ">{{ t("Surname") }} </label>
|
||||
<input id="surname" v-model="form.surname" type="text" class="form-control" :placeholder="t('Surname')">
|
||||
<div v-if="errors.surname" class="invalid-feedback d-block">
|
||||
{{ errors.surname[0] }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row pt-3">
|
||||
<div class="col-12">
|
||||
<label for="email" class="text-muted ">{{ t("Email") }} </label>
|
||||
<input id="email" v-model="form.email" type="email" class="form-control" :placeholder="t('Email')">
|
||||
<div v-if="errors.email" class="invalid-feedback d-block">
|
||||
{{ errors.email[0] }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row pt-3">
|
||||
<div class="col-12 col-sm-6">
|
||||
<label for="password" class="text-muted ">{{ t("Password") }} </label>
|
||||
<input id="password" v-model="form.password" type="password" class="form-control" placeholder="***">
|
||||
<div v-if="errors.password" class="invalid-feedback d-block">
|
||||
{{ errors.password[0] }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-sm-6">
|
||||
<label for="confirm-password" class="text-muted ">{{ t("Confirm Password") }} </label>
|
||||
<input id="confirm-password" v-model="form.confirm_password" type="password" class="form-control" placeholder="***">
|
||||
<div v-if="errors.confirm_password" class="invalid-feedback d-block">
|
||||
{{ errors.confirm_password[0] }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row pt-3">
|
||||
<div class="col-12">
|
||||
<label for="language" class="text-muted ">{{ t("Language") }} </label>
|
||||
<select id="language" v-model="form.language" class="form-select" @change="changeLanguage">
|
||||
<option value="en">
|
||||
English
|
||||
</option>
|
||||
<option value="de">
|
||||
German
|
||||
</option>
|
||||
</select>
|
||||
<div v-if="errors.language" class="invalid-feedback d-block">
|
||||
{{ errors.language[0] }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row pt-5 g-2">
|
||||
<button type="submit" class="btn btn-primary-custom mx-auto col-4" style="background-color: #e9c046">
|
||||
{{ t("Save Changes") }} <div v-if="loading" class="spinner-border spinner-border-sm" role="status">
|
||||
<span class="sr-only">{{ t("Loading...") }} </span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapActions } from 'pinia'
|
||||
// import Index from '~/pages/index.vue'
|
||||
import { useUserStore } from '~/stores/user'
|
||||
export default {
|
||||
name: 'EditAccount',
|
||||
components: {
|
||||
// Index
|
||||
},
|
||||
|
||||
setup () {
|
||||
const { t } = useI18n()
|
||||
const localePath = useLocalePath()
|
||||
const switchLocalePath = useSwitchLocalePath()
|
||||
const changeLanguage = (locale) => {
|
||||
// useNuxtApp().$logger.log('switch', locale)
|
||||
useRouter().push(switchLocalePath(locale))
|
||||
|
||||
// i18n.global.locale.value=
|
||||
}
|
||||
return { t, localePath, changeLanguage }
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
loading: false,
|
||||
form: {
|
||||
first_name: '',
|
||||
surname: '',
|
||||
email: 'email',
|
||||
password: '',
|
||||
confirm_password: '',
|
||||
language: 'de'
|
||||
},
|
||||
errors: []
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState(useUserStore, ['user'])
|
||||
},
|
||||
mounted () {
|
||||
this.form.first_name = this.user.first_name
|
||||
this.form.email = this.user.email
|
||||
this.form.surname = this.user.surname
|
||||
this.form.language = this.user.language
|
||||
},
|
||||
methods: {
|
||||
|
||||
...mapActions(useUserStore, ['updateUser']),
|
||||
|
||||
validateAndSave () {
|
||||
this.errors = []
|
||||
|
||||
// Wenn Passwort leer ist, lassen wir die Prüfung zu – Änderung des Passworts ist optional
|
||||
if (this.form.password !== '') {
|
||||
if (this.form.password !== this.form.confirm_password) {
|
||||
this.errors.confirm_password = ['Passwörter stimmen nicht überein.']
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
this.saveUser()
|
||||
},
|
||||
saveUser () {
|
||||
this.loading = true
|
||||
|
||||
const payload = { ...this.form }
|
||||
if (!payload.password) { delete payload.password }
|
||||
if (!payload.confirm_password) { delete payload.confirm_password }
|
||||
|
||||
this.$axios.post('/api/account/update', payload).then(({ data }) => {
|
||||
this.changeLanguage(this.form.language)
|
||||
this.loading = false
|
||||
if (data.success) {
|
||||
this.$toast.success(data.message)
|
||||
this.updateUser(data.user)
|
||||
this.$router.push(this.localePath('/settings/account'))
|
||||
}
|
||||
}).catch((error) => {
|
||||
this.loading = false
|
||||
this.$toast.error('Something wrong while saving...')
|
||||
if (error.response.status === 422) {
|
||||
this.errors = error.response.data.errors
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
238
pages/settings/faq.vue
Normal file
238
pages/settings/faq.vue
Normal file
@@ -0,0 +1,238 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="row justify-content-center m-0 p-0">
|
||||
<div class="col-12 col-lg-9 col-md-11 col-sm-12 col-xl-8">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="me-4">
|
||||
<h1 class="h3 fw-bold text-center py-3">
|
||||
{{ t("FAQ") }}
|
||||
</h1>
|
||||
</div>
|
||||
<div id="accordionExample" class="accordion">
|
||||
<div class="accordion-item">
|
||||
<h2 id="headingOne" class="accordion-header">
|
||||
<button
|
||||
class="accordion-button fw-semibold"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#collapseOne"
|
||||
aria-expanded="true"
|
||||
aria-controls="collapseOne"
|
||||
>
|
||||
{{ t("question1") }}
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapseOne" class="accordion-collapse collapse show" aria-labelledby="headingOne" data-bs-parent="#accordionExample">
|
||||
<div class="accordion-body">
|
||||
<p>
|
||||
{{ t("answer_part_1") }}
|
||||
<br>{{ t("answer_part_2") }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion-item">
|
||||
<h2 id="headingTwo" class="accordion-header">
|
||||
<button
|
||||
class="accordion-button fw-semibold collapsed"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#collapseTwo"
|
||||
aria-expanded="false"
|
||||
aria-controls="collapseTwo"
|
||||
>
|
||||
{{ t("question_2") }}
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapseTwo" class="accordion-collapse collapse" aria-labelledby="headingTwo" data-bs-parent="#accordionExample">
|
||||
<div class="accordion-body">
|
||||
{{ t("answer_2") }} 01
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion-item">
|
||||
<h2 id="headingThree" class="accordion-header">
|
||||
<button
|
||||
class="accordion-button fw-semibold collapsed"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#collapseThree"
|
||||
aria-expanded="false"
|
||||
aria-controls="collapseThree"
|
||||
>
|
||||
{{ t("question_3") }}
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapseThree" class="accordion-collapse collapse" aria-labelledby="headingThree" data-bs-parent="#accordionExample">
|
||||
<div class="accordion-body">
|
||||
{{ t("answer_3") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion-item">
|
||||
<h2 id="headingFour" class="accordion-header">
|
||||
<button
|
||||
class="accordion-button fw-semibold collapsed"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#collapseFour"
|
||||
aria-expanded="false"
|
||||
aria-controls="collapseThree"
|
||||
>
|
||||
{{ t("question_4") }}
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapseFour" class="accordion-collapse collapse" aria-labelledby="headingThree" data-bs-parent="#accordionExample">
|
||||
<div class="accordion-body">
|
||||
{{ t("answer_4") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion-item">
|
||||
<h2 id="headingFive" class="accordion-header">
|
||||
<button
|
||||
class="accordion-button fw-semibold collapsed"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#collapseFive"
|
||||
aria-expanded="false"
|
||||
aria-controls="collapseThree"
|
||||
>
|
||||
{{ t("question_5") }}
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapseFive" class="accordion-collapse collapse" aria-labelledby="headingThree" data-bs-parent="#accordionExample">
|
||||
<div class="accordion-body">
|
||||
{{ t("answer_5") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="accordion-item">
|
||||
<h2 id="headingsix" class="accordion-header">
|
||||
<button
|
||||
class="accordion-button fw-semibold collapsed"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#collapsesix"
|
||||
aria-expanded="false"
|
||||
aria-controls="collapseThree"
|
||||
>
|
||||
{{ t("question_6") }}
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapsesix" class="accordion-collapse collapse" aria-labelledby="headingThree" data-bs-parent="#accordionExample">
|
||||
<div class="accordion-body">
|
||||
{{ t("answer_6") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion-item">
|
||||
<h2 id="headingsaven" class="accordion-header">
|
||||
<button
|
||||
class="accordion-button fw-semibold collapsed"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#collapseseven"
|
||||
aria-expanded="false"
|
||||
aria-controls="collapseThree"
|
||||
>
|
||||
{{ t("question_7") }}
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapseseven" class="accordion-collapse collapse" aria-labelledby="headingThree" data-bs-parent="#accordionExample">
|
||||
<div class="accordion-body">
|
||||
{{ t("answer_7") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion-item">
|
||||
<h2 id="headingeight" class="accordion-header">
|
||||
<button
|
||||
class="accordion-button fw-semibold collapsed"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#collapseeight"
|
||||
aria-expanded="false"
|
||||
aria-controls="collapseThree"
|
||||
>
|
||||
{{ t("question_8") }}
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapseeight" class="accordion-collapse collapse" aria-labelledby="headingThree" data-bs-parent="#accordionExample">
|
||||
<div class="accordion-body">
|
||||
<ol class="px-0 mx-0">
|
||||
<li>{{ t("answer_8_1") }}</li>
|
||||
<li>{{ t("answer_8_2") }}</li>
|
||||
<li>{{ t("answer_8_3") }}</li>
|
||||
<li>{{ t("answer_8_4") }}</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion-item">
|
||||
<h2 id="headingnine" class="accordion-header">
|
||||
<button
|
||||
class="accordion-button fw-semibold collapsed"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#collapsenine"
|
||||
aria-expanded="false"
|
||||
aria-controls="collapseThree"
|
||||
>
|
||||
{{ t("question_9") }}
|
||||
</button>
|
||||
</h2>
|
||||
<div id="collapsenine" class="accordion-collapse collapse" aria-labelledby="headingThree" data-bs-parent="#accordionExample">
|
||||
<div class="accordion-body">
|
||||
{{ t("answer_9") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Subscription',
|
||||
setup () {
|
||||
const { t } = useI18n()
|
||||
const localePath = useLocalePath()
|
||||
return { t, localePath }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.accordion-item {
|
||||
border: none;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
.accordion-button {
|
||||
background-color: rgb(244, 245, 247) !important;
|
||||
}
|
||||
.accordion-button:focus {
|
||||
z-index: 3;
|
||||
border:none;
|
||||
outline: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
.accordion-button:not(.open) {
|
||||
color: black;
|
||||
box-shadow: none;
|
||||
background-color: rgb(244, 245, 247) !important;
|
||||
font-weight: 600;
|
||||
}
|
||||
.accordion-collapse {
|
||||
background-color: rgb(244, 245, 247);
|
||||
}
|
||||
.accordion-button:not(.open):hover, .accordion-button:not(.open):focus {
|
||||
background-color: rgb(226, 229, 234) !important;
|
||||
}
|
||||
</style>
|
51
pages/settings/imprint.vue
Normal file
51
pages/settings/imprint.vue
Normal file
@@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-12 col-lg-9 col-md-11 col-sm-12 col-xl-8">
|
||||
<h1 class="h3 fw-bold py-3">Impressum</h1>
|
||||
<div class="row pt-4">
|
||||
<div class="col-12">
|
||||
<h2 class="h4 mt-4">Angaben gemäß § 5 TMG</h2>
|
||||
<p>
|
||||
Robert-Carl Rapp<br>
|
||||
Sindlingerstr. 23<br>
|
||||
71083 Herrenberg
|
||||
</p>
|
||||
|
||||
<h2 class="h4 mt-4">Postadresse</h2>
|
||||
<p>
|
||||
Sindlingerstr. 23</br>
|
||||
71083 Herrenberg
|
||||
</p>
|
||||
|
||||
<h2 class="h4 mt-4">Kontakt</h2>
|
||||
<p>
|
||||
Telefon: +(49)15784037946<br>
|
||||
E-Mail: kontakt@mindboost.team
|
||||
</p>
|
||||
|
||||
<p>Quelle: <a href="https://www.e-recht24.de/impressum-generator.html">Generated by eRecht24</a></p>
|
||||
|
||||
<h2 class="h4 mt-4">Bildnachweise</h2>
|
||||
<a href="https://de.vecteezy.com/kostenlos-videos/wei%C3%9F">Weiß Stockvideos von Vecteezy</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'SettingPage',
|
||||
setup () {
|
||||
const { t } = useI18n()
|
||||
const localePath = useLocalePath()
|
||||
return { t, localePath }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
88
pages/settings/soundscap1.vue
Normal file
88
pages/settings/soundscap1.vue
Normal file
File diff suppressed because one or more lines are too long
480
pages/settings/soundscape.vue
Normal file
480
pages/settings/soundscape.vue
Normal file
File diff suppressed because one or more lines are too long
54
pages/settings/termsandcondition.vue
Normal file
54
pages/settings/termsandcondition.vue
Normal file
@@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-12 col-lg-9 col-md-11 col-sm-12 col-xl-8">
|
||||
<h1 class="h3 fw-bold py-3">Nutzungsbedingungen der App von Mindboost</h1>
|
||||
<div class="row pt-4">
|
||||
<div class="col-12">
|
||||
<h2 class="h4">1. Geltungsbereich</h2>
|
||||
<p>Diese Nutzungsbedingungen gelten für alle Dienste und Inhalte, die durch die App von Mindboost bereitgestellt werden. Mit der Registrierung bei oder der Nutzung unserer Dienste erklären Sie sich mit diesen Bedingungen einverstanden.</p>
|
||||
|
||||
<h2 class="h4">2. Nutzung von Audiodateien</h2>
|
||||
<p>Die in dieser App verfügbaren Audiodateien dürfen ausschließlich auf der Webseite von Mindboost genutzt werden. Jede Verwendung dieser Audiodateien außerhalb dieser Domain ist strengstens untersagt.</p>
|
||||
|
||||
<h2 class="h4">3. Nutzung des Algorithmus</h2>
|
||||
<p>Der in dieser App enthaltene Algorithmus darf nur von Nutzern mit einer aktiven und gültigen Lizenz verwendet werden. Die Lizenz muss während der gesamten Nutzungsdauer des Algorithmus gültig sein. Eine Nutzung ohne gültige Lizenz verstößt gegen unsere Nutzungsbedingungen.</p>
|
||||
|
||||
<h2 class="h4">4. Trial-Version</h2>
|
||||
<p>Jede natürliche oder juristische Person darf die Trial-Version dieser App nur einmal nutzen. Eine erneute Nutzung der Trial-Version ohne vorherige Absprache mit uns ist nicht erlaubt.</p>
|
||||
|
||||
<h2 class="h4">5. Account-Registrierung und Verantwortlichkeit</h2>
|
||||
<p>Die Nutzer sind verantwortlich für das sichere Aufbewahren ihrer Passwörter und für alle Aktivitäten, die unter ihrem Account stattfinden. Jeder Verdacht auf unbefugte Nutzung des Accounts sollte unverzüglich an unseren Kundenservice gemeldet werden.</p>
|
||||
|
||||
<h2 class="h4">6. Zahlungsbedingungen</h2>
|
||||
<p>Für bestimmte Teile unserer Dienste kann eine Gebühr anfallen. Alle Gebühren sind klar angegeben und sind zum Zeitpunkt der Fälligkeit zahlbar. Zahlungsverzögerungen können zur Deaktivierung des Zugangs führen.</p>
|
||||
|
||||
<h2 class="h4">7. Datenschutz</h2>
|
||||
<p>Ihre persönlichen Daten werden gemäß unserer Datenschutzrichtlinie behandelt, die Sie auf unserer Webseite einsehen können. Mit der Nutzung unserer Dienste stimmen Sie der Verarbeitung Ihrer Daten zu.</p>
|
||||
|
||||
<h2 class="h4">8. Änderungen der Dienste</h2>
|
||||
<p>Wir behalten uns das Recht vor, jederzeit Änderungen an unseren Diensten vorzunehmen oder Dienste einzustellen. Wir werden uns bemühen, die Nutzer rechtzeitig über solche Änderungen zu informieren.</p>
|
||||
|
||||
<h2 class="h4">9. Kündigung</h2>
|
||||
<p>Die Nutzer können ihr Abonnement jederzeit kündigen. Mindboost behält sich das Recht vor, den Service eines Nutzers zu kündigen oder zu suspendieren, wenn dieser gegen die Nutzungsbedingungen verstößt.</p>
|
||||
|
||||
<h2 class="h4">10. Haftungsbeschränkung</h2>
|
||||
<p>Mindboost haftet nicht für indirekte, zufällige, spezielle oder folgeschädigte Schäden, die aus der Nutzung oder Unfähigkeit zur Nutzung der Dienste entstehen.</p>
|
||||
|
||||
<h2 class="h4">11. Geltendes Recht und Gerichtsstand</h2>
|
||||
<p>Diese Nutzungsbedingungen unterliegen dem Recht des Landes, in dem Mindboost seinen Sitz hat, und alle Streitigkeiten werden ausschließlich vor den dortigen Gerichten verhandelt. </p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
setup () {
|
||||
const { t } = useI18n()
|
||||
const localePath = useLocalePath()
|
||||
return { t, localePath }
|
||||
}
|
||||
}
|
||||
</script>
|
298
pages/settings/timersettings.vue
Normal file
298
pages/settings/timersettings.vue
Normal file
@@ -0,0 +1,298 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-12 col-lg-9 col-md-11 col-sm-12 col-xl-8">
|
||||
<h1 class="h3 fw-bold text-center py-3">
|
||||
Timer Settings
|
||||
</h1>
|
||||
<div class="timer__settings">
|
||||
<!-- Timer Settings -->
|
||||
<v-list class="px-2 px-md-16">
|
||||
<!-- Work Time -->
|
||||
<div class="slider">
|
||||
<input
|
||||
id="gain-control"
|
||||
v-model="timer().settings.timer.work"
|
||||
type="range"
|
||||
min="5"
|
||||
max="60"
|
||||
step="5"
|
||||
data-toggle="tooltip"
|
||||
data-placement="top"
|
||||
:title="tooltipTitle"
|
||||
@wheel.prevent="changeVolumeOnWheel"
|
||||
>
|
||||
<span
|
||||
class="slider-progress-bar"
|
||||
:style="{ width: `${volume * 100}%` }"
|
||||
/>
|
||||
</div>
|
||||
<!-- <v-list-item title="Work Sessions">
|
||||
<v-slider
|
||||
v-model="settings.timer.work"
|
||||
min="5"
|
||||
max="60"
|
||||
step="5"
|
||||
show-ticks
|
||||
thumb-label
|
||||
:color="timer().getCurrentColor"
|
||||
class="w-100"
|
||||
:ticks="sliderTicks.workTimer"
|
||||
:rules="rules.workRules"
|
||||
>
|
||||
<template #append>
|
||||
<v-text-field
|
||||
v-model="settings.timer.work"
|
||||
hide-details
|
||||
single-line
|
||||
density="compact"
|
||||
type="number"
|
||||
style="width: 70px"
|
||||
min="5"
|
||||
max="60"
|
||||
/>
|
||||
</template>
|
||||
</v-slider> -->
|
||||
<!-- </v-list-item> -->
|
||||
<!-- Breaks Sessions -->
|
||||
<v-list-item
|
||||
v-for="value, i in ['short-break', 'long-break']"
|
||||
:key="i"
|
||||
:title="timer().settings.timer[value].text"
|
||||
>
|
||||
<v-slider
|
||||
v-model="settings.timer[value]"
|
||||
min="0"
|
||||
max="60"
|
||||
step="5"
|
||||
show-ticks
|
||||
thumb-label
|
||||
:color="timer().getCurrentColor"
|
||||
class="w-100"
|
||||
:ticks="sliderTicks.breakTimers"
|
||||
:rules="rules.breakRules"
|
||||
>
|
||||
<template #append>
|
||||
<v-text-field
|
||||
v-model="settings.timer[value]"
|
||||
hide-details
|
||||
single-line
|
||||
density="compact"
|
||||
type="number"
|
||||
style="width: 70px"
|
||||
min="0"
|
||||
max="60"
|
||||
/>
|
||||
</template>
|
||||
</v-slider>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item
|
||||
title="Max Sessions"
|
||||
subtitle="The number of work sessions before a long break"
|
||||
>
|
||||
<v-slider
|
||||
v-model="settings.maxSessions"
|
||||
min="1"
|
||||
max="10"
|
||||
step="1"
|
||||
show-ticks
|
||||
thumb-label
|
||||
:color="timer().getCurrentColor"
|
||||
class="w-100"
|
||||
:ticks="sliderTicks.maxSessions"
|
||||
:rules="rules.maxSessionsRules"
|
||||
>
|
||||
<template #append>
|
||||
<v-text-field
|
||||
v-model="settings.maxSessions"
|
||||
hide-details
|
||||
single-line
|
||||
density="compact"
|
||||
type="number"
|
||||
style="width: 70px"
|
||||
min="1"
|
||||
max="10"
|
||||
/>
|
||||
</template>
|
||||
</v-slider>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-checkbox
|
||||
v-model="timer().settings.autoStart"
|
||||
:color="timer().getCurrentColor"
|
||||
label="Auto Start Sessions"
|
||||
true-icon="mdi-timer-play-outline"
|
||||
false-icon="mdi-timer-play-outline"
|
||||
/>
|
||||
</v-list-item>
|
||||
|
||||
<v-divider />
|
||||
|
||||
<!-- General Settings -->
|
||||
<v-list-subheader>General Settings</v-list-subheader>
|
||||
<v-list-item>
|
||||
<v-checkbox
|
||||
v-model="settings.notificationsEnabled"
|
||||
:color="timer().getCurrentColor"
|
||||
label="Notifications"
|
||||
true-icon="mdi-bell"
|
||||
false-icon="mdi-bell-off"
|
||||
/>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-checkbox
|
||||
v-model="timer().playSessionEndSound"
|
||||
:color="timer().getCurrentColor"
|
||||
label="Sounds"
|
||||
true-icon="mdi-volume-high"
|
||||
false-icon="mdi-volume-off"
|
||||
/>
|
||||
</v-list-item>
|
||||
<!-- <v-list-item>
|
||||
<v-checkbox
|
||||
v-model="app().showThemeToggle"
|
||||
:color="timer().getCurrentColor"
|
||||
label="Show Theme Toggle"
|
||||
true-icon="mdi-theme-light-dark"
|
||||
false-icon="mdi-theme-light-dark"
|
||||
/>
|
||||
</v-list-item> -->
|
||||
</v-list>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { useTimerStore as timer } from '~~/stores/timer'
|
||||
|
||||
export default {
|
||||
name: 'TimerSettings',
|
||||
setup () {
|
||||
const { t } = useI18n()
|
||||
const localePath = useLocalePath()
|
||||
|
||||
const sliderTicks = reactive({
|
||||
workTimer: {
|
||||
5: '5',
|
||||
60: '60'
|
||||
},
|
||||
breakTimers: {
|
||||
0: '0',
|
||||
60: '60'
|
||||
},
|
||||
maxSessions: {
|
||||
1: '1',
|
||||
10: '10'
|
||||
}
|
||||
})
|
||||
// only the settings that need validation
|
||||
const settings = reactive({
|
||||
timer: {
|
||||
work: 25,
|
||||
'short-break': 5,
|
||||
'long-break': 15
|
||||
},
|
||||
maxSessions: 4,
|
||||
notificationsEnabled: false
|
||||
})
|
||||
const state = reactive({
|
||||
dialog: false,
|
||||
snackbar: false
|
||||
})
|
||||
|
||||
// rules for validating inputs
|
||||
const rules = reactive({
|
||||
workRules: [
|
||||
(value) => {
|
||||
if (value >= 5 && value <= 60) { return true }
|
||||
|
||||
if (value <= 0) { return "Are you kidding me? if you don't wanna work then just go find something better to do than playing with my settings." }
|
||||
if (value < 5) { return 'Do you want to work for less than 5 mins?! You can do better.' }
|
||||
return "Don't be so hard on yourself, work for an hour or less before taking a break."
|
||||
}
|
||||
],
|
||||
breakRules: [
|
||||
(value) => {
|
||||
if (value >= 0 && value <= 60) { return true }
|
||||
|
||||
if (value < 0) { return 'How tf would you take a negative break time you moron?' }
|
||||
return "That's too much time for a break, stop wasting your time."
|
||||
}
|
||||
],
|
||||
maxSessionsRules: [
|
||||
(value) => {
|
||||
if (value >= 1) { return true }
|
||||
|
||||
return "Max sessions can't be less than one!"
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
// save settings to localstorage
|
||||
const saveSettings = () => {
|
||||
saveNotification()
|
||||
|
||||
// validate user inputs before saving
|
||||
if (rules.workRules[0](settings.timer.work) === true) {
|
||||
timer().settings.timer.work.time = settings.timer.work
|
||||
}
|
||||
if (rules.breakRules[0](settings.timer['short-break']) === true) {
|
||||
timer().settings.timer['short-break'].time = settings.timer['short-break']
|
||||
}
|
||||
if (rules.breakRules[0](settings.timer['long-break']) === true) {
|
||||
timer().settings.timer['long-break'].time = settings.timer['long-break']
|
||||
}
|
||||
if (rules.maxSessionsRules[0](settings.maxSessions) === true) {
|
||||
timer().settings.maxSessions = settings.maxSessions
|
||||
}
|
||||
|
||||
storeSettings()
|
||||
|
||||
state.dialog = false
|
||||
state.snackbar = true
|
||||
}
|
||||
return { t, localePath }
|
||||
}
|
||||
}
|
||||
|
||||
// watch(
|
||||
// () => state.dialog,
|
||||
// (dialog) => {
|
||||
// if (!dialog) { // if dialog is closed
|
||||
// saveSettings()
|
||||
// } else {
|
||||
// // retrieve settings from state when dialog is opened
|
||||
// // settings.notificationsEnabled = timer().showNotification
|
||||
// settings.maxSessions = timer().settings.maxSessions
|
||||
// for (const session of Object.keys(settings.timer)) {
|
||||
// settings.timer[session] = timer().settings.timer[session].time
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// )
|
||||
|
||||
// get notification permission from the browser
|
||||
// const saveNotification = () => {
|
||||
// const { isSupported, show } = useWebNotification({
|
||||
// title: 'Notifications enabled.'
|
||||
// })
|
||||
// if (isSupported.value) {
|
||||
// if (!timer().showNotification && settings.notificationsEnabled) { show() }
|
||||
// } else {
|
||||
// useNuxtApp().$logger.error('Your browser does not support notifications.')
|
||||
// }
|
||||
// timer().showNotification = settings.notificationsEnabled
|
||||
// }
|
||||
|
||||
</script>
|
||||
<style>
|
||||
.dialog-bottom-transition-enter-active,
|
||||
.dialog-bottom-transition-leave-active {
|
||||
transition: transform .1s ease-in-out;
|
||||
}
|
||||
.v-footer {
|
||||
row-gap: 0.4rem;
|
||||
}
|
||||
</style>
|
106
pages/success.vue
Normal file
106
pages/success.vue
Normal file
@@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-12 px-0 mx-0">
|
||||
<video-background
|
||||
src="video/bg-video.mp4"
|
||||
poster="images/posters/poster1.png"
|
||||
style=" height: 100vh;"
|
||||
>
|
||||
<home-bar :title="title" />
|
||||
|
||||
<div class="container pt-4">
|
||||
<div class="row justify-content-center px-1 ">
|
||||
<div class="col-12 col-sm-11 col-md-10 p-3 py-4 col-lg-7 " style="border-radius: 15px;background-color: rgb(233, 192, 70);">
|
||||
<div class="success-page">
|
||||
<div class="success-icon">
|
||||
<i class="fas fa-check-circle" />
|
||||
</div>
|
||||
<div class="success-message">
|
||||
{{ t("Successfully subscribed") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<BootomBar style="position: fixed;bottom: 10px;" />
|
||||
</video-background>
|
||||
<!-- <video muted id="myVideo" autoplay loop>-->
|
||||
<!-- <source src="~/assets/video/bg-video.mp4" type="video/mp4">-->
|
||||
<!-- <source src="~/assets/video/bg-video.ogg" type="video/ogg">-->
|
||||
<!-- Your browser does not support HTML5 video.-->
|
||||
<!-- </video>-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
|
||||
import HomeBar from '../components/homebar'
|
||||
export default {
|
||||
components: { HomeBar },
|
||||
setup () {
|
||||
const { t } = useI18n()
|
||||
const localePath = useLocalePath()
|
||||
|
||||
return {
|
||||
t,
|
||||
localePath
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
title: 'Subscribed Successfully'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleAnimation: function (anim) {
|
||||
this.anim = anim
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#myVideo{
|
||||
position: fixed;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
min-width: 100%;
|
||||
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
z-index: -99;
|
||||
}
|
||||
.adaptive{
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
.success-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
|
||||
}
|
||||
|
||||
.success-icon {
|
||||
font-size: 64px;
|
||||
color: white;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.success-message {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
118
pages/viz/index.vue
Normal file
118
pages/viz/index.vue
Normal file
@@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-12 px-0 mx-0">
|
||||
<video-background
|
||||
src="/video/Tropics.mp4"
|
||||
preload="auto"
|
||||
poster="/images/posters/poster.png"
|
||||
style=" height: 100vh;"
|
||||
>
|
||||
<h2>Line Chart</h2>
|
||||
<LineChart />
|
||||
<h2>Time Chart</h2>
|
||||
<TimeDifference />
|
||||
</video-background>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
|
||||
// import DeepCareAnalyzer from '../components/viz/DeepCareAnalyzer'
|
||||
import LineChart from '~/components/viz/LineChart.vue'
|
||||
import TimeDifference from '~/components/viz/TimeDifference.vue'
|
||||
|
||||
export default {
|
||||
components: { LineChart, TimeDifference },
|
||||
setup () {
|
||||
definePageMeta({
|
||||
middleware: 'auth'
|
||||
})
|
||||
const { t } = useI18n()
|
||||
const localePath = useLocalePath()
|
||||
const lineChart = ref(null)
|
||||
return {
|
||||
t,
|
||||
localePath,
|
||||
lineChart
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
// Berechnete Eigenschaft für die dynamische Übersetzung des Titels
|
||||
title () {
|
||||
return 'Deep Care Viz'
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#myVideo{
|
||||
position: fixed;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
min-width: 100%;
|
||||
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
z-index: -99;
|
||||
}
|
||||
.adaptive{
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
.spotifyplayer{
|
||||
display: flex;
|
||||
flex-direction: column; /* Ensures vertical centering if needed */
|
||||
align-items: center; /* Horizontally centers content in a flex-direction: column */
|
||||
justify-content: center;
|
||||
}
|
||||
.overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(128, 128, 128, 0.5); /* Grey color with opacity */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
pointer-events: none; /* Allows interaction with elements under the overlay */
|
||||
}
|
||||
.tooltip {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.tooltip .tooltiptext {
|
||||
visibility: hidden;
|
||||
width: 120px;
|
||||
background-color: black;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
border-radius: 6px;
|
||||
padding: 5px 0;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
bottom: 125%;
|
||||
left: 50%;
|
||||
margin-left: -60px;
|
||||
}
|
||||
|
||||
.tooltip:hover .tooltiptext {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
</style>
|
1012
pages/viz_band.vue
Normal file
1012
pages/viz_band.vue
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user