- iconManager MaterialIconManager.js zijn nu 'unified' in 1 component, en samen met translation utilities omgezet naar een meer moderne Vue composable

- De sidebar is nu eveneens omgezet naar een Vue component.
This commit is contained in:
Josako
2025-07-20 18:07:17 +02:00
parent ccb844c15c
commit e75c49d2fa
24 changed files with 2358 additions and 413 deletions

View File

@@ -0,0 +1,26 @@
// eveai_chat_client/static/assets/js/composables/index.js
/**
* Vue 3 Composables Barrel Export
* Provides easy access to all composables
*/
// Icon Management Composables
export {
useIconManager,
useIcon,
useFormIcon
} from './useIconManager.js';
// Translation Management Composables
export {
useTranslation,
useTranslationClient,
useReactiveTranslation
} from './useTranslation.js';
// Future composables can be added here:
// export { useFormValidation } from './useFormValidation.js';
// export { useChat } from './useChat.js';
console.log('Vue 3 composables loaded successfully');

View File

@@ -0,0 +1,192 @@
// eveai_chat_client/static/assets/js/composables/useIconManager.js
import { onMounted, watch, ref } from 'vue';
/**
* Vue 3 Composable for Material Symbols Outlined icon management
* Self-contained modern icon management without legacy dependencies
*/
export function useIconManager() {
const isIconManagerReady = ref(true); // Always ready since we're self-contained
const loadedIcons = ref([]);
/**
* Load a single Material Symbols Outlined icon
* @param {string} iconName - Name of the icon to load
* @param {Object} options - Icon options (opsz, wght, FILL, GRAD)
*/
const loadIcon = (iconName, options = {}) => {
if (!iconName) {
console.warn('No icon name provided');
return;
}
// Check if icon is already loaded
if (loadedIcons.value.includes(iconName)) {
return;
}
// Default options for Material Symbols Outlined
const defaultOptions = {
opsz: 24,
wght: 400,
FILL: 0,
GRAD: 0
};
const iconOptions = { ...defaultOptions, ...options };
// Create CSS for the icon with specific options
const cssRule = `
.material-symbols-outlined.icon-${iconName} {
font-variation-settings:
'FILL' ${iconOptions.FILL},
'wght' ${iconOptions.wght},
'GRAD' ${iconOptions.GRAD},
'opsz' ${iconOptions.opsz};
}
`;
// Add CSS rule to document
const style = document.createElement('style');
style.textContent = cssRule;
document.head.appendChild(style);
// Mark icon as loaded
loadedIcons.value.push(iconName);
console.log(`Icon loaded: ${iconName}`);
};
/**
* Load multiple icons
* @param {Array} iconNames - Array of icon names to load
* @param {Object} options - Icon options (opsz, wght, FILL, GRAD)
*/
const loadIcons = (iconNames, options = {}) => {
if (!Array.isArray(iconNames)) {
console.warn('iconNames must be an array');
return;
}
iconNames.forEach(iconName => {
loadIcon(iconName, options);
});
};
/**
* Ensure icons are loaded (alias for loadIcons for backward compatibility)
* @param {Object} options - Icon options (opsz, wght, FILL, GRAD)
* @param {Array} iconNames - Array of icon names to load
*/
const ensureIconsLoaded = (options = {}, iconNames = []) => {
if (iconNames && iconNames.length > 0) {
loadIcons(iconNames, options);
}
};
/**
* Watch a reactive property for icon changes and automatically load icons
* @param {Ref|Function} iconSource - Reactive source that contains icon name(s)
* @param {Object} options - Icon options
*/
const watchIcon = (iconSource, options = {}) => {
watch(
iconSource,
(newIcon) => {
if (newIcon) {
if (Array.isArray(newIcon)) {
loadIcons(newIcon, options);
} else {
loadIcon(newIcon, options);
}
}
},
{ immediate: true }
);
};
/**
* Watch formData for icon changes (common pattern in Vue components)
* @param {Ref} formData - Reactive formData object
* @param {Object} options - Icon options
*/
const watchFormDataIcon = (formData, options = {}) => {
watch(
() => formData.value?.icon,
(newIcon) => {
if (newIcon) {
loadIcon(newIcon, options);
}
},
{ immediate: true }
);
};
/**
* Preload common icons used throughout the application
* @param {Array} commonIcons - Array of commonly used icon names
* @param {Object} options - Icon options
*/
const preloadCommonIcons = (commonIcons = [], options = {}) => {
const defaultCommonIcons = [
'send', 'attach_file', 'mic', 'more_vert', 'close', 'check',
'error', 'warning', 'info', 'expand_more', 'expand_less'
];
const iconsToLoad = commonIcons.length > 0 ? commonIcons : defaultCommonIcons;
loadIcons(iconsToLoad, options);
};
return {
// State
isIconManagerReady,
// Methods
loadIcon,
loadIcons,
ensureIconsLoaded,
// Watchers
watchIcon,
watchFormDataIcon,
// Utilities
preloadCommonIcons
};
}
/**
* Simplified composable for basic icon loading
* Use this when you only need basic icon loading functionality
*/
export function useIcon(iconName, options = {}) {
const { loadIcon, isIconManagerReady } = useIconManager();
onMounted(() => {
if (iconName) {
loadIcon(iconName, options);
}
});
return {
loadIcon,
isIconManagerReady
};
}
/**
* Composable for form-related icon management
* Automatically handles formData.icon watching
*/
export function useFormIcon(formData, options = {}) {
const { watchFormDataIcon, loadIcon, isIconManagerReady } = useIconManager();
// Automatically watch formData for icon changes
watchFormDataIcon(formData, options);
return {
loadIcon,
isIconManagerReady
};
}

View File

@@ -0,0 +1,233 @@
// eveai_chat_client/static/assets/js/composables/useTranslation.js
import { ref, computed, onMounted } from 'vue';
/**
* Vue 3 Composable for translation management
* Provides direct backend API communication for translations
*/
export function useTranslation() {
const isTranslationReady = ref(false);
const currentLanguage = ref('nl');
const isTranslating = ref(false);
const lastError = ref(null);
// Check if translation system is available
const checkTranslationReady = () => {
// Translation is altijd ready omdat we de backend API gebruiken
// Controleer alleen of we in een browser environment zijn
if (typeof window !== 'undefined' && typeof fetch !== 'undefined') {
isTranslationReady.value = true;
return true;
}
return false;
};
onMounted(() => {
// Eenvoudige check - geen retry mechanism nodig
checkTranslationReady();
});
/**
* Translate text to target language
* @param {string} text - Text to translate
* @param {string} targetLang - Target language code
* @param {string|null} sourceLang - Source language code (optional)
* @param {string|null} context - Translation context (optional)
* @param {string} apiPrefix - API prefix for tenant routing
* @returns {Promise<object>} Translation result
*/
const translate = async (text, targetLang, sourceLang = null, context = null, apiPrefix = '') => {
if (!text || !text.trim()) {
const error = new Error('No text provided for translation');
lastError.value = error;
throw error;
}
isTranslating.value = true;
lastError.value = null;
try {
// Bepaal de juiste API URL
const baseUrl = apiPrefix || window.chatConfig?.apiPrefix || '';
const apiUrl = `${baseUrl}/api/translate`;
// Maak de request payload
const payload = {
text: text,
target_lang: targetLang
};
// Voeg optionele parameters toe
if (sourceLang) payload.source_lang = sourceLang;
if (context) payload.context = context;
// Maak de HTTP request
const response = await fetch(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
},
credentials: 'same-origin', // Voor sessie cookies
body: JSON.stringify(payload)
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
// Update current language if translation was successful
if (result.success) {
currentLanguage.value = targetLang;
}
return result;
} catch (error) {
console.error('Translation error in composable:', error);
lastError.value = error;
throw error;
} finally {
isTranslating.value = false;
}
};
/**
* Translate text with automatic error handling and loading state
* @param {string} text - Text to translate
* @param {string} targetLang - Target language code
* @param {Object} options - Translation options
* @returns {Promise<string|null>} Translated text or null on error
*/
const translateSafe = async (text, targetLang, options = {}) => {
const {
sourceLang = null,
context = null,
apiPrefix = '',
fallbackText = text
} = options;
try {
const result = await translate(text, targetLang, sourceLang, context, apiPrefix);
return result.success ? result.translated_text : fallbackText;
} catch (error) {
console.warn('Safe translation failed, using fallback:', error.message);
return fallbackText;
}
};
/**
* Batch translate multiple texts
* @param {Array<string>} texts - Array of texts to translate
* @param {string} targetLang - Target language code
* @param {Object} options - Translation options
* @returns {Promise<Array<string>>} Array of translated texts
*/
const translateBatch = async (texts, targetLang, options = {}) => {
const results = await Promise.allSettled(
texts.map(text => translateSafe(text, targetLang, options))
);
return results.map((result, index) =>
result.status === 'fulfilled' ? result.value : texts[index]
);
};
/**
* Get current language from chatConfig or fallback
*/
const getCurrentLanguage = () => {
return window.chatConfig?.language || currentLanguage.value || 'nl';
};
/**
* Get API prefix from chatConfig or fallback
*/
const getApiPrefix = () => {
return window.chatConfig?.apiPrefix || '';
};
return {
// State
isTranslationReady,
currentLanguage: computed(() => getCurrentLanguage()),
isTranslating,
lastError,
// Methods
translate,
translateSafe,
translateBatch,
// Utilities
getCurrentLanguage,
getApiPrefix
};
}
/**
* Simplified composable for basic translation needs
* Use this when you only need simple text translation
*/
export function useTranslationClient() {
const { translate, translateSafe, isTranslationReady, isTranslating, lastError } = useTranslation();
return {
translate,
translateSafe,
isTranslationReady,
isTranslating,
lastError
};
}
/**
* Composable for reactive text translation
* Automatically translates text when language changes
*/
export function useReactiveTranslation(text, options = {}) {
const { translateSafe, currentLanguage } = useTranslation();
const translatedText = ref(text);
const isLoading = ref(false);
const {
context = null,
sourceLang = null,
autoTranslate = true
} = options;
// Watch for language changes and auto-translate
if (autoTranslate) {
// We'll implement this when we have proper reactivity setup
// For now, provide manual translation method
}
const updateTranslation = async (newLanguage = null) => {
const targetLang = newLanguage || currentLanguage.value;
if (!text || targetLang === sourceLang) {
translatedText.value = text;
return;
}
isLoading.value = true;
try {
const result = await translateSafe(text, targetLang, {
sourceLang,
context,
apiPrefix: window.chatConfig?.apiPrefix || ''
});
translatedText.value = result;
} finally {
isLoading.value = false;
}
};
return {
translatedText,
isLoading,
updateTranslation
};
}

View File

@@ -0,0 +1,238 @@
// eveai_chat_client/static/assets/js/composables/useTranslation.js
import { ref, computed, onMounted } from 'vue';
/**
* Vue 3 Composable for translation management
* Provides modern alternative to window.TranslationClient
*/
export function useTranslation() {
const isTranslationReady = ref(false);
const currentLanguage = ref('nl');
const isTranslating = ref(false);
const lastError = ref(null);
// Check if translation system is available with retry mechanism
const checkTranslationReady = () => {
if (window.TranslationClient && typeof window.TranslationClient.translate === 'function') {
isTranslationReady.value = true;
return true;
}
return false;
};
onMounted(() => {
// Initial check
if (checkTranslationReady()) {
return;
}
// Retry mechanism - wait for TranslationClient to become available
let retryCount = 0;
const maxRetries = 10;
const retryInterval = 100; // 100ms
const retryCheck = () => {
if (checkTranslationReady()) {
return; // Success!
}
retryCount++;
if (retryCount < maxRetries) {
setTimeout(retryCheck, retryInterval);
} else {
console.warn('TranslationClient is not available after retries');
isTranslationReady.value = false;
}
};
// Start retry process
setTimeout(retryCheck, retryInterval);
});
/**
* Translate text to target language
* @param {string} text - Text to translate
* @param {string} targetLang - Target language code
* @param {string|null} sourceLang - Source language code (optional)
* @param {string|null} context - Translation context (optional)
* @param {string} apiPrefix - API prefix for tenant routing
* @returns {Promise<object>} Translation result
*/
const translate = async (text, targetLang, sourceLang = null, context = null, apiPrefix = '') => {
if (!isTranslationReady.value || !window.TranslationClient) {
const error = new Error('Translation system not ready');
lastError.value = error;
throw error;
}
if (!text || !text.trim()) {
const error = new Error('No text provided for translation');
lastError.value = error;
throw error;
}
isTranslating.value = true;
lastError.value = null;
try {
const result = await window.TranslationClient.translate(
text,
targetLang,
sourceLang,
context,
apiPrefix
);
// Update current language if translation was successful
if (result.success) {
currentLanguage.value = targetLang;
}
return result;
} catch (error) {
console.error('Translation error in composable:', error);
lastError.value = error;
throw error;
} finally {
isTranslating.value = false;
}
};
/**
* Translate text with automatic error handling and loading state
* @param {string} text - Text to translate
* @param {string} targetLang - Target language code
* @param {Object} options - Translation options
* @returns {Promise<string|null>} Translated text or null on error
*/
const translateSafe = async (text, targetLang, options = {}) => {
const {
sourceLang = null,
context = null,
apiPrefix = '',
fallbackText = text
} = options;
try {
const result = await translate(text, targetLang, sourceLang, context, apiPrefix);
return result.success ? result.translated_text : fallbackText;
} catch (error) {
console.warn('Safe translation failed, using fallback:', error.message);
return fallbackText;
}
};
/**
* Batch translate multiple texts
* @param {Array<string>} texts - Array of texts to translate
* @param {string} targetLang - Target language code
* @param {Object} options - Translation options
* @returns {Promise<Array<string>>} Array of translated texts
*/
const translateBatch = async (texts, targetLang, options = {}) => {
const results = await Promise.allSettled(
texts.map(text => translateSafe(text, targetLang, options))
);
return results.map((result, index) =>
result.status === 'fulfilled' ? result.value : texts[index]
);
};
/**
* Get current language from chatConfig or fallback
*/
const getCurrentLanguage = () => {
return window.chatConfig?.language || currentLanguage.value;
};
/**
* Get API prefix from chatConfig or fallback
*/
const getApiPrefix = () => {
return window.chatConfig?.apiPrefix || '';
};
return {
// State
isTranslationReady,
currentLanguage: computed(() => getCurrentLanguage()),
isTranslating,
lastError,
// Methods
translate,
translateSafe,
translateBatch,
// Utilities
getCurrentLanguage,
getApiPrefix
};
}
/**
* Simplified composable for basic translation needs
* Use this when you only need simple text translation
*/
export function useTranslationClient() {
const { translate, translateSafe, isTranslationReady, isTranslating, lastError } = useTranslation();
return {
translate,
translateSafe,
isTranslationReady,
isTranslating,
lastError
};
}
/**
* Composable for reactive text translation
* Automatically translates text when language changes
*/
export function useReactiveTranslation(text, options = {}) {
const { translateSafe, currentLanguage } = useTranslation();
const translatedText = ref(text);
const isLoading = ref(false);
const {
context = null,
sourceLang = null,
autoTranslate = true
} = options;
// Watch for language changes and auto-translate
if (autoTranslate) {
// We'll implement this when we have proper reactivity setup
// For now, provide manual translation method
}
const updateTranslation = async (newLanguage = null) => {
const targetLang = newLanguage || currentLanguage.value;
if (!text || targetLang === sourceLang) {
translatedText.value = text;
return;
}
isLoading.value = true;
try {
const result = await translateSafe(text, targetLang, {
sourceLang,
context,
apiPrefix: window.chatConfig?.apiPrefix || ''
});
translatedText.value = result;
} finally {
isLoading.value = false;
}
};
return {
translatedText,
isLoading,
updateTranslation
};
}