Merge branch 'feature/Introduce_tabs_in_mobile_chat_client' into develop

This commit is contained in:
Josako
2025-11-28 10:04:49 +01:00
13 changed files with 558 additions and 206 deletions

View File

@@ -36,7 +36,10 @@ def get_default_chat_customisation(tenant_customisation=None):
'ai_message_text_color': '#212529',
'human_message_background': '#212529',
'human_message_text_color': '#ffffff',
'human_message_inactive_text_color': '#808080'
'human_message_inactive_text_color': '#808080',
'tab_background': '#0a0a0a',
'tab_icon_active_color': '#ffffff',
'tab_icon_inactive_color': '#f0f0f0',
}
# If no tenant customization is provided, return the defaults

View File

@@ -87,6 +87,21 @@ configuration:
description: "Human Message Inactive Text Color"
type: "color"
required: false
tab_background:
name: "Tab Background Color"
description: "Tab Background Color"
type: "color"
required: false
tab_icon_active_color:
name: "Tab Icon Active Color"
description: "Tab Icon Active Color"
type: "color"
required: false
tab_icon_inactive_color:
name: "Tab Icon Inactive Color"
description: "Tab Icon Inactive Color"
type: "color"
required: false
metadata:
author: "Josako"
date_added: "2024-06-06"

View File

@@ -1,6 +1,6 @@
{
"dist/chat-client.js": "dist/chat-client.f8ee4d5a.js",
"dist/chat-client.css": "dist/chat-client.2fffefae.css",
"dist/chat-client.js": "dist/chat-client.5b709f8c.js",
"dist/chat-client.css": "dist/chat-client.cb306abb.css",
"dist/main.js": "dist/main.6a617099.js",
"dist/main.css": "dist/main.7182aac3.css"
}

View File

@@ -21,7 +21,9 @@
/* App container layout */
.app-container {
display: flex;
/* Use visual viewport variable when available */
/* Op desktop gebruiken we de veilige viewporthoogte direct; op mobiel
laten we html/body de hoogte bepalen en neemt de app-container
eenvoudig 100% daarvan in via de media query verderop. */
min-height: 0;
height: calc(var(--safe-vh, var(--vvh, 1vh)) * 100);
width: 100%;
@@ -93,7 +95,7 @@
display: flex;
flex-direction: column;
min-height: 0;
height: auto; /* prefer dynamic viewport on desktop */
height: auto; /* desktop: dynamische hoogte, op mobiel overschreven */
}
.chat-container {
@@ -103,6 +105,26 @@
min-height: 0; /* laat kinderen (ChatApp) krimpen */
}
/* Op mobiel sluiten we de volledige content-kolom strak aan op de veilige
viewporthoogte zodat alleen de chatcontent zelf kan scrollen en niet de
gehele pagina wanneer het toetsenbord opent. */
@media (max-width: 768px) {
.app-container {
height: 100%;
}
.content-area {
height: 100%;
overflow: hidden;
}
.chat-container {
flex: 1 1 auto;
min-height: 0;
overflow: hidden;
}
}
html, body {
height: calc(var(--safe-vh, var(--vvh, 1vh)) * 100);
min-height: 0;

View File

@@ -1,38 +1,106 @@
active_text_color<template>
<div class="chat-app-container">
<!-- Message History - takes available space -->
<message-history
:messages="displayMessages"
:is-typing="isTyping"
:is-submitting-form="isSubmittingForm"
:api-prefix="apiPrefix"
:auto-scroll="true"
@specialist-error="handleSpecialistError"
@specialist-complete="handleSpecialistComplete"
ref="messageHistory"
class="chat-messages-area"
></message-history>
<!-- Desktop layout: huidige gedrag behouden -->
<div v-if="!isMobileFallback" class="chat-desktop-layout">
<message-history
:messages="displayMessages"
:is-typing="isTyping"
:is-submitting-form="isSubmittingForm"
:api-prefix="apiPrefix"
:auto-scroll="true"
@specialist-error="handleSpecialistError"
@specialist-complete="handleSpecialistComplete"
ref="messageHistory"
class="chat-messages-area"
></message-history>
<!-- Chat Input - to the bottom -->
<chat-input
:current-message="currentMessage"
:is-loading="isLoading"
:max-length="2000"
:allow-file-upload="true"
:allow-voice-message="false"
:form-data="currentInputFormData"
:active-ai-message="activeAiMessage"
:api-prefix="apiPrefix"
@send-message="sendMessage"
@update-message="updateCurrentMessage"
@upload-file="handleFileUpload"
@record-voice="handleVoiceRecord"
@submit-form="submitFormFromInput"
@specialist-error="handleSpecialistError"
@specialist-complete="handleSpecialistComplete"
ref="chatInput"
class="chat-input-area"
></chat-input>
<chat-input
:current-message="currentMessage"
:is-loading="isLoading"
:max-length="2000"
:allow-file-upload="true"
:allow-voice-message="false"
:form-data="currentInputFormData"
:active-ai-message="activeAiMessage"
:api-prefix="apiPrefix"
@send-message="sendMessage"
@update-message="updateCurrentMessage"
@upload-file="handleFileUpload"
@record-voice="handleVoiceRecord"
@submit-form="submitFormFromInput"
@specialist-error="handleSpecialistError"
@specialist-complete="handleSpecialistComplete"
ref="chatInput"
class="chat-input-area"
></chat-input>
</div>
<!-- Mobiele layout met tabs in de header -->
<div v-else class="chat-mobile-layout">
<header class="chat-mobile-header">
<div class="chat-mobile-header-left">
<SideBarLogo
:logo-url="tenantLogoUrl"
:make-name="tenantName"
/>
</div>
<div class="chat-mobile-header-right">
<MobileTabBar
v-model="activeTabId"
:tabs="mobileTabs"
placement="header"
/>
</div>
</header>
<div class="chat-mobile-content" :class="`tab-${activeTabId}`">
<message-history
v-if="activeTabId === 'history'"
:messages="displayMessages"
:is-typing="isTyping"
:is-submitting-form="isSubmittingForm"
:api-prefix="apiPrefix"
:auto-scroll="true"
@specialist-error="handleSpecialistError"
@specialist-complete="handleSpecialistComplete"
ref="messageHistory"
class="chat-messages-area"
></message-history>
<chat-input
v-else-if="activeTabId === 'chat'"
:current-message="currentMessage"
:is-loading="isLoading"
:max-length="settings.maxMessageLength"
:allow-file-upload="settings.allowFileUpload"
:allow-voice-message="settings.allowVoiceMessage"
:form-data="currentInputFormData"
:active-ai-message="activeAiMessage"
:api-prefix="apiPrefix"
@send-message="sendMessage"
@update-message="updateCurrentMessage"
@upload-file="handleFileUpload"
@record-voice="handleVoiceRecord"
@submit-form="submitFormFromInput"
@specialist-error="handleSpecialistError"
@specialist-complete="handleSpecialistComplete"
ref="chatInput"
class="chat-input-area tab-chat-input"
></chat-input>
<SideBarMobileSetup
v-else-if="activeTabId === 'setup'"
:tenant-make="{ name: tenantName, subtitle: tenantSubtitle }"
:explanation-text="originalExplanation"
:initial-language="currentLanguage"
:current-language="currentLanguage"
:supported-language-details="supportedLanguageDetails"
:allowed-languages="allowedLanguages"
:api-prefix="apiPrefix"
@language-changed="handleLanguageChangedFromSetup"
/>
</div>
</div>
<!-- Content Modal - positioned at ChatApp level -->
<content-modal
@@ -60,6 +128,9 @@ import ProgressTracker from './ProgressTracker.vue';
import LanguageSelector from './LanguageSelector.vue';
import ChatInput from './ChatInput.vue';
import ContentModal from './ContentModal.vue';
import SideBarLogo from './SideBarLogo.vue';
import MobileTabBar from './MobileTabBar.vue';
import SideBarMobileSetup from './SideBarMobileSetup.vue';
// Import language provider
import { createLanguageProvider, LANGUAGE_PROVIDER_KEY } from '../js/services/LanguageProvider.js';
@@ -77,7 +148,10 @@ export default {
MessageHistory,
ProgressTracker,
ChatInput,
ContentModal
ContentModal,
SideBarLogo,
MobileTabBar,
SideBarMobileSetup
},
setup() {
@@ -90,7 +164,7 @@ export default {
// Creëer en provide content modal
const contentModal = provideContentModal();
// Provide aan alle child components
provide(LANGUAGE_PROVIDER_KEY, languageProvider);
@@ -111,6 +185,7 @@ export default {
return {
// Tenant info
tenantName: tenantMake.name || 'EveAI',
tenantSubtitle: tenantMake.subtitle || '',
tenantLogoUrl: tenantMake.logo_url || '',
// Taal gerelateerde data
@@ -147,10 +222,13 @@ export default {
autoScroll: settings.autoScroll === true
},
// UI state
isMobile: window.innerWidth <= 768,
// UI state (fallback flags voor oudere logica)
isMobileFallback: window.innerWidth <= 768,
showSidebar: window.innerWidth > 768,
// Mobile tab state
activeTabId: 'chat',
// Advanced features
messageSearch: '',
filteredMessages: [],
@@ -193,16 +271,57 @@ export default {
return this.supportedLanguages.filter(lang =>
this.allowedLanguages.includes(lang.code)
);
},
mobileTabs() {
return [
{
id: 'chat',
iconName: 'chat',
label: 'Chat'
},
{
id: 'history',
iconName: 'history',
label: 'Historiek'
},
{
id: 'setup',
iconName: 'settings',
label: 'Setup'
}
];
}
},
mounted() {
this.initializeChat();
this.setupEventListeners();
// Stel initiale actieve tab in (optioneel via config)
const defaultTab = (window.chatConfig && window.chatConfig.defaultTab) || 'chat';
if (this.mobileTabs.find(t => t.id === defaultTab)) {
this.activeTabId = defaultTab;
}
// Luister naar globale events om tab te wisselen
this.globalTabListener = (event) => {
const tabId = event?.detail?.tabId;
if (!tabId) return;
if (this.mobileTabs.find(t => t.id === tabId)) {
this.activeTabId = tabId;
}
};
document.addEventListener('evie-chat-set-tab', this.globalTabListener);
},
beforeUnmount() {
this.cleanup();
if (this.globalTabListener) {
document.removeEventListener('evie-chat-set-tab', this.globalTabListener);
}
},
methods: {
@@ -453,11 +572,13 @@ export default {
}
});
// Window resize listener
window.addEventListener('resize', () => {
this.isMobile = window.innerWidth <= 768;
// Window resize listener voor fallback flags
this.handleResize = () => {
this.isMobileFallback = window.innerWidth <= 768;
this.showSidebar = window.innerWidth > 768;
});
};
window.addEventListener('resize', this.handleResize);
},
cleanup() {
@@ -516,6 +637,12 @@ export default {
this.isLoading = false;
},
handleLanguageChangedFromSetup(newLanguage) {
// Update lokale taalstate; verdere effecten worden opgepikt door
// bestaande global listener en LanguageProvider / chatConfig.
this.currentLanguage = newLanguage;
},
// UI helpers
scrollToBottom() {
if (this.$refs.messageHistory) {
@@ -560,7 +687,7 @@ export default {
display: flex;
flex-direction: column;
flex: 1;
/* height: 100%; avoided to let flex sizing control height */
height: 100%;
width: 100%;
min-height: 0;
max-width: 1000px;
@@ -571,6 +698,84 @@ export default {
overflow: hidden;
}
.chat-mobile-layout {
display: flex;
flex-direction: column;
/* Binnen SafeViewport vertrouwen we op de hoogte van de bovenliggende
containers (html/body/app-container). Deze layout moet zich daaraan
aanpassen en niet opnieuw zelf een safe-vh berekening doen, om
dubbele afrondingsfouten en extra scrollruimte te vermijden. */
flex: 1 1 auto;
height: 100%;
min-height: 0;
}
.chat-mobile-content {
flex: 1 1 auto;
min-height: 0;
overflow-y: auto;
display: flex;
flex-direction: column;
}
/* Mobiele header met logo links en tabs rechts */
.chat-mobile-header {
position: sticky;
top: 0;
z-index: 10;
display: flex;
flex-direction: row;
align-items: stretch;
justify-content: space-between;
padding: 6px 8px;
background: var(--tab-background, #0a0a0a);
}
.chat-mobile-header-left {
flex: 0 0 auto;
display: flex;
align-items: center;
justify-content: flex-start;
min-width: 56px; /* Zorg dat logo altijd minstens vierkant kan tonen */
margin-right: 4px;
}
.chat-mobile-header-right {
flex: 1 1 auto;
display: flex;
align-items: center;
justify-content: flex-end;
overflow: hidden;
}
/* Specifieke layout voor de chat-tab: inputblok onderaan */
.chat-mobile-content.tab-chat {
display: flex;
flex-direction: column;
}
.tab-chat-input {
margin-top: auto;
flex-shrink: 0;
/* Iets meer visuele marge boven de onderrand, maar nog steeds rekening houden met safe inset */
padding-bottom: calc(6px + var(--safe-bottom-inset, 0px));
}
/* Wanneer het toetsenbord open is (gedetecteerd door useChatViewport via
de body-klasse chat-keyboard-open), willen we geen extra grote
safe-bottom-inset meer onder de input. Dan sluiten we zo veel mogelijk
aan tegen de visuele viewport en houden we alleen een kleine vaste marge. */
body.chat-keyboard-open .tab-chat-input {
padding-bottom: 6px;
}
@media (max-width: 768px) {
.chat-app-container {
/* Minder padding op mobiel zodat de tabbar binnen de viewport valt */
padding: 8px 8px 0 8px;
}
}
.chat-messages-area {
flex: 1;
min-height: 0; /* ensure child can scroll */

View File

@@ -452,6 +452,7 @@ export default {
transition: opacity 0.2s ease-in-out;
margin-left: auto;
margin-right: auto;
margin-bottom: 20px;
}
/* Input veld en knoppen */

View File

@@ -784,21 +784,32 @@ export default {
}
}
/* Bubble height constraints and inner scroll containment (apply on all viewports) */
/* Bubble height constraints en inner scroll containment.
Fallback gebruikt klassieke vh-units; de @supports-blok hieronder
schakelt over naar SafeViewport via var(--safe-vh) wanneer mogelijk. */
.message .message-content {
max-height: 33vh; /* fallback */
overflow-y: auto;
overscroll-behavior: contain; /* prevent scroll chaining to parent */
-webkit-overflow-scrolling: touch; /* iOS smooth inertia */
max-height: 33vh; /* fallback */
overflow-y: auto;
overscroll-behavior: contain; /* prevent scroll chaining to parent */
-webkit-overflow-scrolling: touch; /* iOS smooth inertia */
}
/* Active contexts (input area or sticky area): allow up to half viewport */
/* Active contexts (input area of sticky area): mogen meer hoogte innemen */
.message.input-area .message-content,
.message.sticky-area .message-content {
max-height: 50vh; /* fallback */
max-height: 50vh; /* fallback */
}
@supports (max-height: 1svh) {
.message .message-content { max-height: 33svh; }
.message.input-area .message-content,
.message.sticky-area .message-content { max-height: 50svh; }
.message .message-content {
/* Gebruik veilige viewporthoogte die door useChatViewport gezet wordt */
max-height: calc(var(--safe-vh, 1vh) * 33);
}
.message.input-area .message-content,
.message.sticky-area .message-content {
/* In de input-/sticky-area mag de bubbel ruimer zijn */
max-height: calc(var(--safe-vh, 1vh) * 60);
}
}
</style>

View File

@@ -5,22 +5,11 @@
:make-name="tenantMake.name"
class="mobile-logo"
/>
<LanguageSelector
:initial-language="initialLanguage"
:current-language="currentLanguage"
:supported-language-details="supportedLanguageDetails"
:allowed-languages="allowedLanguages"
@language-changed="handleLanguageChange"
class="mobile-language-selector"
/>
</div>
</template>
<script setup>
import { ref } from 'vue';
import SideBarLogo from './SideBarLogo.vue';
import LanguageSelector from './LanguageSelector.vue';
const props = defineProps({
tenantMake: {
@@ -49,45 +38,21 @@ const props = defineProps({
}
});
const emit = defineEmits(['language-changed']);
const currentLanguage = ref(props.initialLanguage);
const handleLanguageChange = (newLanguage) => {
currentLanguage.value = newLanguage;
// Emit to parent
emit('language-changed', newLanguage);
// Global event for backward compatibility
const globalEvent = new CustomEvent('language-changed', {
detail: { language: newLanguage }
});
document.dispatchEvent(globalEvent);
// Update chatConfig
if (window.chatConfig) {
window.chatConfig.language = newLanguage;
}
// Save preference
localStorage.setItem('preferredLanguage', newLanguage);
};
// Mobile header toont enkel het logo; taalkeuze gebeurt via de Setup-tab.
</script>
<style scoped>
.mobile-header {
display: flex;
justify-content: space-between;
justify-content: flex-start;
align-items: center;
flex-wrap: wrap; /* allow wrapping to next line on narrow screens */
padding: 10px 15px;
background: var(--sidebar-background);
color: var(--sidebar-color);
border-bottom: 1px solid rgba(0,0,0,0.1);
min-height: 60px;
max-width: 100%; /* never exceed viewport width */
overflow: hidden; /* clip any accidental overflow */
max-width: 100%;
overflow: hidden;
}
/* Mobile logo container - meer specifieke styling */
@@ -129,34 +94,6 @@ const handleLanguageChange = (newLanguage) => {
justify-content: center !important;
}
/* Mobile language selector styling */
.mobile-language-selector {
flex-shrink: 1;
min-width: 0; /* allow selector area to shrink */
}
.mobile-language-selector :deep(.language-selector) {
margin: 0;
}
.mobile-language-selector :deep(label) {
display: none; /* Hide label in mobile header */
}
.mobile-language-selector :deep(.language-select) {
padding: 6px 10px;
font-size: 0.85rem;
min-width: 0; /* allow the select to shrink */
max-width: 100%; /* never exceed container width */
margin: 0;
}
/* Extra constraints on ultra-small screens */
@media (max-width: 360px) {
.mobile-language-selector :deep(.language-select) {
max-width: 60vw; /* avoid pushing beyond viewport */
}
}
/* Media queries voor responsiviteit */
@media (max-width: 768px) {

View File

@@ -0,0 +1,113 @@
<template>
<nav
class="mobile-tab-bar"
:class="{ 'mobile-tab-bar--header': placement === 'header' }"
>
<button
v-for="tab in tabs"
:key="tab.id"
type="button"
class="tab-button"
:class="{ 'is-active': tab.id === modelValue }"
@click="$emit('update:modelValue', tab.id)"
>
<span class="material-symbols-outlined" :class="`icon-${tab.iconName}`">
{{ tab.iconName }}
</span>
<span v-if="showLabels" class="tab-label">{{ tab.label }}</span>
</button>
</nav>
</template>
<script setup>
import { computed, watch } from 'vue';
import { useIconManager } from '../js/composables/useIconManager.js';
const props = defineProps({
tabs: {
type: Array,
default: () => []
},
modelValue: {
type: String,
default: ''
},
placement: {
type: String,
default: 'bottom' // 'bottom' | 'header'
},
showLabels: {
type: Boolean,
default: false
}
});
defineEmits(['update:modelValue']);
const { loadIcons } = useIconManager();
const iconNames = computed(() => props.tabs.map(t => t.iconName).filter(Boolean));
watch(iconNames, (names) => {
if (names && names.length) {
loadIcons(names);
}
}, { immediate: true });
</script>
<style scoped>
.mobile-tab-bar {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: space-around;
padding: 6px 8px;
padding-bottom: calc(6px + var(--safe-bottom-inset, 0px));
border-top: 1px solid rgba(255, 255, 255, 0.08);
background: var(--tab-background, #0a0a0a);
}
.mobile-tab-bar--header {
/* In de header geen extra safe-area padding en geen border-top */
padding: 4px 4px;
padding-bottom: 4px;
border-top: none;
}
.tab-button {
flex: 1 1 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 4px 0;
border: none;
background: transparent;
color: var(--tab-icon-inactive-color, rgba(240, 240, 240, 0.7));
font-size: 11px;
cursor: pointer;
}
.tab-button .material-symbols-outlined {
font-size: 22px;
line-height: 1;
}
.tab-button.is-active {
color: var(--tab-icon-active-color, #ffffff);
}
.tab-button.is-active .material-symbols-outlined {
font-variation-settings:
'FILL' 1,
'wght' 500,
'GRAD' 0,
'opsz' 24;
}
@media (min-width: 769px) {
.mobile-tab-bar {
display: none;
}
}
</style>

View File

@@ -24,5 +24,8 @@ useChatViewport();
height: 100%;
display: flex;
flex-direction: column;
flex: 1 1 auto;
min-height: 0;
overflow: hidden;
}
</style>

View File

@@ -0,0 +1,118 @@
<template>
<div class="sidebar-mobile-setup">
<SideBarMakeName
:make-name="tenantMake.name"
:subtitle="tenantMake.subtitle"
class="setup-make-name"
/>
<LanguageSelector
:initial-language="initialLanguage"
:current-language="currentLanguageInternal"
:supported-language-details="supportedLanguageDetails"
:allowed-languages="allowedLanguages"
@language-changed="handleLanguageChange"
class="setup-language-selector"
/>
<SideBarExplanation
:original-text="explanationText"
:current-language="currentLanguageInternal"
:api-prefix="apiPrefix"
class="setup-explanation"
/>
</div>
</template>
<script setup>
import { ref, watch } from 'vue';
import SideBarMakeName from './SideBarMakeName.vue';
import LanguageSelector from './LanguageSelector.vue';
import SideBarExplanation from './SideBarExplanation.vue';
const props = defineProps({
tenantMake: {
type: Object,
default: () => ({
name: '',
subtitle: ''
})
},
explanationText: {
type: String,
default: ''
},
initialLanguage: {
type: String,
default: 'en'
},
currentLanguage: {
type: String,
default: 'en'
},
supportedLanguageDetails: {
type: Object,
default: () => ({})
},
allowedLanguages: {
type: Array,
default: () => ['nl', 'en', 'fr', 'de']
},
apiPrefix: {
type: String,
default: ''
}
});
const emit = defineEmits(['language-changed']);
const currentLanguageInternal = ref(props.currentLanguage || props.initialLanguage);
watch(
() => props.currentLanguage,
(newVal) => {
if (newVal && newVal !== currentLanguageInternal.value) {
currentLanguageInternal.value = newVal;
}
}
);
const handleLanguageChange = (newLanguage) => {
currentLanguageInternal.value = newLanguage;
emit('language-changed', newLanguage);
};
</script>
<style scoped>
.sidebar-mobile-setup {
display: flex;
flex-direction: column;
height: 100%;
padding: 12px 8px 16px 8px;
gap: 8px;
}
.setup-make-name {
padding-bottom: 4px;
}
.setup-language-selector {
flex-shrink: 0;
border-radius: 12px;
background: var(--sidebar-background, rgba(255, 255, 255, 0.02));
padding: 8px;
}
.setup-explanation {
flex: 1 1 auto;
border-radius: 12px;
padding: 8px;
overflow-y: auto;
}
@media (min-width: 769px) {
.sidebar-mobile-setup {
display: none;
}
}
</style>

View File

@@ -44,6 +44,11 @@
--human-message-background: {{ customisation.human_message_background|default('#ffffff') }};
--human-message-text-color: {{ customisation.human_message_text_color|default('#212529') }};
/* Mobe Tab Bar Colors */
--tab-background: {{ customisation.tab_background|default('#0a0a0a') }};
--tab-icon-active-color: {{ customisation.tab_icon_active_color|default('#ffffff') }};
--tab-icon-inactive-color: {{ customisation.tab_icon_inactive_color|default('#f0f0f0') }};
}
</style>

View File

@@ -30,7 +30,6 @@ import LanguageSelector from '../../../eveai_chat_client/static/assets/vue-compo
import ChatApp from '../../../eveai_chat_client/static/assets/vue-components/ChatApp.vue';
import ChatRoot from '../../../eveai_chat_client/static/assets/vue-components/ChatRoot.vue';
import SideBar from '../../../eveai_chat_client/static/assets/vue-components/SideBar.vue';
import MobileHeader from '../../../eveai_chat_client/static/assets/vue-components/MobileHeader.vue';
// VueUse-setup voor de chatclient (maakt composables beschikbaar via window.VueUse)
import './vueuse-setup.js';
@@ -51,9 +50,6 @@ document.addEventListener('DOMContentLoaded', function() {
// Initialiseer sidebar (vervangt fillSidebarExplanation en initializeLanguageSelector)
initializeSidebar();
// Initialiseer mobile header
initializeMobileHeader();
// Initialiseer chat app (simpel)
initializeChatApp();
});
@@ -121,85 +117,8 @@ function initializeSidebar() {
}
}
/**
* Initialiseert de mobile header component
*/
function initializeMobileHeader() {
const container = document.getElementById('mobile-header-container');
if (!container) {
console.error('#mobile-header-container niet gevonden');
return;
}
try {
// Maak props voor de component
const props = {
tenantMake: {
name: window.chatConfig.tenantMake?.name || '',
logo_url: window.chatConfig.tenantMake?.logo_url || '',
subtitle: window.chatConfig.tenantMake?.subtitle || ''
},
initialLanguage: window.chatConfig.language || 'nl',
supportedLanguageDetails: window.chatConfig.supportedLanguageDetails || {},
allowedLanguages: window.chatConfig.allowedLanguages || ['nl', 'en', 'fr', 'de'],
apiPrefix: window.chatConfig.apiPrefix || ''
};
// Mount de component
const app = createApp(MobileHeader, props);
// Create and provide LanguageProvider for mobile header components
const initialLanguage = window.chatConfig?.language || 'nl';
const apiPrefix = window.chatConfig?.apiPrefix || '';
const languageProvider = createLanguageProvider(initialLanguage, apiPrefix);
app.provide(LANGUAGE_PROVIDER_KEY, languageProvider);
// Error handler
app.config.errorHandler = (err, vm, info) => {
console.error('🚨 [Vue Error in MobileHeader]', err);
console.error('Component:', vm);
console.error('Error Info:', info);
};
const mountedApp = app.mount(container);
// Dynamisch de headerhoogte doorgeven aan CSS
const updateHeaderHeightVar = () => {
const isMobile = window.matchMedia('(max-width: 768px)').matches;
if (!isMobile) {
document.documentElement.style.removeProperty('--mobile-header-height');
return;
}
const h = container.offsetHeight || 60; // fallback
document.documentElement.style.setProperty('--mobile-header-height', `${h}px`);
};
// Initieel instellen en bij gebeurtenissen herberekenen
requestAnimationFrame(updateHeaderHeightVar);
window.addEventListener('resize', updateHeaderHeightVar);
// Listen to language change events and update the mobile header's language provider
const languageChangeHandler = (event) => {
if (event.detail && event.detail.language) {
console.log('MobileHeader: Received language change event:', event.detail.language);
languageProvider.setLanguage(event.detail.language);
// taalwissel kan headerhoogte veranderen
requestAnimationFrame(updateHeaderHeightVar);
}
};
document.addEventListener('language-changed', languageChangeHandler);
// Store the handler for cleanup if needed
mountedApp._languageChangeHandler = languageChangeHandler;
mountedApp._updateHeaderHeightVar = updateHeaderHeightVar;
console.log('✅ MobileHeader component successfully mounted with LanguageProvider en dynamische headerhoogte');
return mountedApp;
} catch (error) {
console.error('🚨 [CRITICAL ERROR] Bij initialiseren mobile header:', error);
}
}
// initializeMobileHeader is verwijderd; de mobiele header wordt nu volledig
// binnen ChatApp.vue beheerd.
/**
* Initialiseert de chat app (Vue component)