diff --git a/config/static-manifest/manifest.json b/config/static-manifest/manifest.json index 0bb1b16..f2145d6 100644 --- a/config/static-manifest/manifest.json +++ b/config/static-manifest/manifest.json @@ -1,6 +1,6 @@ { - "dist/chat-client.js": "dist/chat-client.f7134231.js", - "dist/chat-client.css": "dist/chat-client.99e10656.css", + "dist/chat-client.js": "dist/chat-client.f8ee4d5a.js", + "dist/chat-client.css": "dist/chat-client.2fffefae.css", "dist/main.js": "dist/main.6a617099.js", "dist/main.css": "dist/main.7182aac3.css" } \ No newline at end of file diff --git a/eveai_chat_client/static/assets/css/chat.css b/eveai_chat_client/static/assets/css/chat.css index 11daad1..6bb3633 100644 --- a/eveai_chat_client/static/assets/css/chat.css +++ b/eveai_chat_client/static/assets/css/chat.css @@ -9,6 +9,13 @@ --message-bot-bg: #f8f9fa; --border-radius: 8px; --spacing: 16px; + + /* Nieuwe, veilige viewport-variabelen voor mobiele lay-out + - --vvh blijft als fallback uit viewport.js + - --safe-vh wordt gezet door useChatViewport() + - --safe-bottom-inset wordt gebruikt voor ondermarge bij de chat input */ + --safe-vh: var(--vvh, 1vh); + --safe-bottom-inset: 0px; } /* App container layout */ @@ -16,7 +23,7 @@ display: flex; /* Use visual viewport variable when available */ min-height: 0; - height: calc(var(--vvh, 1vh) * 100); + height: calc(var(--safe-vh, var(--vvh, 1vh)) * 100); width: 100%; } @@ -97,7 +104,8 @@ } html, body { - height: calc(var(--vvh, 1vh) * 100); min-height: 0; + height: calc(var(--safe-vh, var(--vvh, 1vh)) * 100); + min-height: 0; } /* Base reset & overflow control */ diff --git a/eveai_chat_client/static/assets/js/composables/useChatViewport.js b/eveai_chat_client/static/assets/js/composables/useChatViewport.js new file mode 100644 index 0000000..1de2f8d --- /dev/null +++ b/eveai_chat_client/static/assets/js/composables/useChatViewport.js @@ -0,0 +1,176 @@ +// useChatViewport.js +// Centrale viewport-/keyboard-laag voor de chat client. +// - Gebruikt visualViewport + VueUse (via window.VueUse) om veilige hoogte +// en keyboard-status te bepalen +// - Stelt CSS-variabelen --safe-vh en --safe-bottom-inset in op +// - Beheert body-klassen zoals chat-keyboard-open en ios-safari + +import { ref, computed, watchEffect, onMounted, onBeforeUnmount } from 'vue'; + +// Haal VueUse-composables uit window.VueUse, die in de chat-bundel +// worden geïnitialiseerd via nginx/frontend_src/js/vueuse-setup.js. +// Als ze ontbreken (bv. in een testsituatie), vallen we terug op +// eenvoudige lokale shims. + +const vueUse = (typeof window !== 'undefined' && window.VueUse) || {}; +let { useEventListener, useWindowSize } = vueUse; + +// Fallback-shim voor useEventListener +if (!useEventListener) { + useEventListener = (target, event, handler, options) => { + if (!target || !target.addEventListener) return () => {}; + const opts = options || { passive: true }; + target.addEventListener(event, handler, opts); + return () => target.removeEventListener(event, handler, opts); + }; +} + +// Fallback-shim voor useWindowSize +if (!useWindowSize) { + useWindowSize = () => { + const height = ref(typeof window !== 'undefined' ? window.innerHeight : 0); + if (typeof window !== 'undefined') { + window.addEventListener('resize', () => { + height.value = window.innerHeight; + }, { passive: true }); + } + return { height }; + }; +} + +function detectIosSafari() { + if (typeof navigator === 'undefined') return false; + const ua = navigator.userAgent || navigator.vendor || (window && window.opera) || ''; + const isIOS = /iP(ad|hone|od)/.test(ua); + const isSafari = /Safari/.test(ua) && !/Chrome|CriOS|FxiOS/.test(ua); + return isIOS && isSafari; +} + +// Drempel voor het detecteren van keyboard-open op basis van hoogteverschil +const KEYBOARD_DELTA_THRESHOLD = 120; // px, afgestemd op bestaande viewport.js heuristiek + +export function useChatViewport() { + const isClient = typeof window !== 'undefined' && typeof document !== 'undefined'; + + const safeHeight = ref(isClient ? window.innerHeight : 0); + const baselineHeight = ref(null); // baseline voor keyboard-detectie + const keyboardOpen = ref(false); + const safeBottomInset = ref(0); + + const { height: windowHeight } = useWindowSize(); + const isIosSafari = ref(isClient ? detectIosSafari() : false); + + const isMobile = computed(() => { + if (!isClient) return false; + // Houd de definitie in lijn met bestaande CSS (max-width: 768px) + return window.innerWidth <= 768; + }); + + function applyToCss() { + if (!isClient) return; + const root = document.documentElement; + const vhUnit = (safeHeight.value || windowHeight.value || 0) / 100; + + if (vhUnit > 0) { + root.style.setProperty('--safe-vh', `${vhUnit}px`); + } + + root.style.setProperty('--safe-bottom-inset', `${safeBottomInset.value || 0}px`); + + // Body-klassen + document.body.classList.toggle('chat-keyboard-open', !!keyboardOpen.value); + + if (isIosSafari.value) { + root.classList.add('ios-safari'); + } else { + root.classList.remove('ios-safari'); + } + } + + function updateFromVisualViewport() { + if (!isClient) return; + const vv = window.visualViewport; + + const currentHeight = vv ? vv.height : window.innerHeight; + safeHeight.value = currentHeight; + + // Bepaal een eenvoudige bottom-inset: verschil tussen layout- en visual viewport + const layoutHeight = window.innerHeight || currentHeight; + const inset = Math.max(0, layoutHeight - currentHeight); + safeBottomInset.value = inset; + + // Keyboard-detectie: vergelijk met baseline + if (baselineHeight.value == null) { + baselineHeight.value = currentHeight; + } + const delta = (baselineHeight.value || currentHeight) - currentHeight; + keyboardOpen.value = delta > KEYBOARD_DELTA_THRESHOLD; + } + + onMounted(() => { + if (!isClient) return; + + // Initiale meting + updateFromVisualViewport(); + applyToCss(); + + const vv = window.visualViewport; + + // Luister naar relevante events via VueUse + const stopResize = vv + ? useEventListener(vv, 'resize', () => { + updateFromVisualViewport(); + applyToCss(); + }) + : useEventListener(window, 'resize', () => { + updateFromVisualViewport(); + applyToCss(); + }); + + const stopScroll = vv + ? useEventListener(vv, 'scroll', () => { + updateFromVisualViewport(); + applyToCss(); + }) + : () => {}; + + const stopOrientation = useEventListener(window, 'orientationchange', () => { + // Bij oriëntatie-wijziging baseline resetten + baselineHeight.value = null; + updateFromVisualViewport(); + applyToCss(); + }); + + onBeforeUnmount(() => { + stopResize && stopResize(); + stopScroll && stopScroll(); + stopOrientation && stopOrientation(); + }); + }); + + // Reageer ook op generieke windowHeight veranderingen (fallback) + watchEffect(() => { + if (!isClient) return; + if (!window.visualViewport) { + safeHeight.value = windowHeight.value; + safeBottomInset.value = 0; + keyboardOpen.value = false; + applyToCss(); + } + }); + + // Initiale toepassing voor SSR / eerste render + if (isClient) { + applyToCss(); + } + + return { + safeHeight, + safeBottomInset, + keyboardOpen, + isMobile, + isIosSafari, + }; +} + +export default useChatViewport; diff --git a/eveai_chat_client/static/assets/vue-components/ChatRoot.vue b/eveai_chat_client/static/assets/vue-components/ChatRoot.vue new file mode 100644 index 0000000..ab3e903 --- /dev/null +++ b/eveai_chat_client/static/assets/vue-components/ChatRoot.vue @@ -0,0 +1,13 @@ + + + diff --git a/eveai_chat_client/static/assets/vue-components/SafeViewport.vue b/eveai_chat_client/static/assets/vue-components/SafeViewport.vue new file mode 100644 index 0000000..79d63c2 --- /dev/null +++ b/eveai_chat_client/static/assets/vue-components/SafeViewport.vue @@ -0,0 +1,28 @@ + + + + + diff --git a/nginx/frontend_src/js/chat-client.js b/nginx/frontend_src/js/chat-client.js index 6917659..901911f 100644 --- a/nginx/frontend_src/js/chat-client.js +++ b/nginx/frontend_src/js/chat-client.js @@ -10,7 +10,6 @@ import '../../../eveai_chat_client/static/assets/css/form-message.css'; // Dependencies import { createApp, version } from 'vue'; import { marked } from 'marked'; -import { FormField } from '../../../../../../../../../Users/josako/Library/Application Support/JetBrains/PyCharm2025.1/scratches/old js files/FormField.js'; // Import LanguageProvider for sidebar translation support import { createLanguageProvider, LANGUAGE_PROVIDER_KEY } from '../../../eveai_chat_client/static/assets/js/services/LanguageProvider.js'; @@ -29,9 +28,13 @@ console.log('Components loaded:', Object.keys(Components)); // Import specifieke componenten import LanguageSelector from '../../../eveai_chat_client/static/assets/vue-components/LanguageSelector.vue'; 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'; + // Globale Vue error tracking window.addEventListener('error', function(event) { console.error('🚨 [Global Error]', event.error); @@ -230,8 +233,8 @@ function initializeChatApp() { allowedLanguages: window.chatConfig.allowedLanguages || ['nl', 'en', 'fr', 'de'] }; - // Mount de component met alle nodige componenten - const app = createApp(ChatApp, props); + // Mount de component via ChatRoot zodat SafeViewport de layout kan beheren + const app = createApp(ChatRoot, props); // SSE verbinding configuratie - injecteren in ChatApp component app.provide('sseConfig', { diff --git a/nginx/frontend_src/js/vueuse-setup.js b/nginx/frontend_src/js/vueuse-setup.js new file mode 100644 index 0000000..f4cecc0 --- /dev/null +++ b/nginx/frontend_src/js/vueuse-setup.js @@ -0,0 +1,15 @@ +// vueuse-setup.js +// Setup-module voor VueUse in de chat client. +// - Importeert benodigde composables uit '@vueuse/core' +// - Maakt ze beschikbaar via window.VueUse zodat static assets +// onder eveai_chat_client/static ze kunnen gebruiken zonder +// directe npm-imports. + +import { useEventListener, useWindowSize } from '@vueuse/core'; + +if (typeof window !== 'undefined') { + window.VueUse = Object.assign({}, window.VueUse, { + useEventListener, + useWindowSize, + }); +} diff --git a/nginx/package-lock.json b/nginx/package-lock.json index ce48449..037034f 100644 --- a/nginx/package-lock.json +++ b/nginx/package-lock.json @@ -6,12 +6,13 @@ "": { "dependencies": { "@popperjs/core": "^2.11.8", + "@vueuse/core": "^14.0.0", "animejs": "^4.0.2", "bootstrap": "^5.3.6", "datatables.net": "^2.3.1", "highlight.js": "^11.11.1", "jquery": "^3.7.1", - "marked": "^16.0.0", + "marked": "16.3.0", "nouislider": "^15.8.1", "parallax": "^0.0.0", "prismjs": "^1.30.0", @@ -22,7 +23,6 @@ "vue": "^3.5.17" }, "devDependencies": { - "@parcel/reporter-bundle-analyzer": "^2.15.2", "@parcel/transformer-sass": "^2.15.2", "@parcel/transformer-vue": "^2.15.2", "parcel": "^2.15.2" @@ -1360,26 +1360,6 @@ "url": "https://opencollective.com/parcel" } }, - "node_modules/@parcel/reporter-bundle-analyzer": { - "version": "2.16.0", - "resolved": "https://registry.npmjs.org/@parcel/reporter-bundle-analyzer/-/reporter-bundle-analyzer-2.16.0.tgz", - "integrity": "sha512-81EazkM7YjeFvzRRlKKV8kmLfLXfWlfLqlpC8jFRXthC43aZtdqMnzRgi9Q+6FIB7Ft3vnMXZPqRs4cn7+XgUg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@parcel/plugin": "2.16.0", - "@parcel/utils": "2.16.0", - "nullthrows": "^1.1.1" - }, - "engines": { - "node": ">= 16.0.0", - "parcel": "^2.16.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, "node_modules/@parcel/reporter-cli": { "version": "2.16.0", "resolved": "https://registry.npmjs.org/@parcel/reporter-cli/-/reporter-cli-2.16.0.tgz", @@ -2722,6 +2702,12 @@ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "license": "MIT" }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", + "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", + "license": "MIT" + }, "node_modules/@vue/compiler-core": { "version": "3.5.21", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.21.tgz", @@ -2822,6 +2808,44 @@ "integrity": "sha512-+2k1EQpnYuVuu3N7atWyG3/xoFWIVJZq4Mz8XNOdScFI0etES75fbny/oU4lKWk/577P1zmg0ioYvpGEDZ3DLw==", "license": "MIT" }, + "node_modules/@vueuse/core": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-14.0.0.tgz", + "integrity": "sha512-d6tKRWkZE8IQElX2aHBxXOMD478fHIYV+Dzm2y9Ag122ICBpNKtGICiXKOhWU3L1kKdttDD9dCMS4bGP3jhCTQ==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.21", + "@vueuse/metadata": "14.0.0", + "@vueuse/shared": "14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/@vueuse/metadata": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-14.0.0.tgz", + "integrity": "sha512-6yoGqbJcMldVCevkFiHDBTB1V5Hq+G/haPlGIuaFZHpXC0HADB0EN1ryQAAceiW+ryS3niUwvdFbGiqHqBrfVA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-14.0.0.tgz", + "integrity": "sha512-mTCA0uczBgurRlwVaQHfG0Ja7UdGe4g9mwffiJmvLiTtp1G4AQyIjej6si/k8c8pUwTfVpNufck+23gXptPAkw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", diff --git a/nginx/package.json b/nginx/package.json index dc12848..20f3e60 100644 --- a/nginx/package.json +++ b/nginx/package.json @@ -14,7 +14,8 @@ "tabulator-tables": "^6.3.1", "typed.js": "^2.1.0", "vanilla-jsoneditor": "^3.5.0", - "vue": "^3.5.17" + "vue": "^3.5.17", + "@vueuse/core": "^14.0.0" }, "devDependencies": { "@parcel/transformer-sass": "^2.15.2",