From 20498d1ad52ab7b191e8d51300b93a9ec9cb7ea3 Mon Sep 17 00:00:00 2001 From: 37b7 Date: Sat, 17 May 2025 18:10:47 +0200 Subject: [PATCH] 41683 --- assets/ts/error/handle.ts | 16 +++++ composables/useProjectDetail.ts | 113 ++++++++++++++++---------------- composables/useTheme.ts | 12 +--- config/paths.ts | 7 +- nuxt.config.ts | 18 +++-- pages/projects/[id]/index.vue | 29 ++++---- types/project.ts | 10 --- 7 files changed, 105 insertions(+), 100 deletions(-) create mode 100644 assets/ts/error/handle.ts diff --git a/assets/ts/error/handle.ts b/assets/ts/error/handle.ts new file mode 100644 index 0000000..b4a72db --- /dev/null +++ b/assets/ts/error/handle.ts @@ -0,0 +1,16 @@ +import { DEFAULT_IMAGES } from "~/config/paths"; + +export const handleLightboxImageError = (event: Event): void => { + const target = event.target as HTMLImageElement; + target.src = DEFAULT_IMAGES.PROJECT; +}; + +export const handleImageError = (event: Event): void => { + const target = event.target as HTMLImageElement; + target.src = DEFAULT_IMAGES.PROJECT; +}; + +export const handleIconError = (event: Event): void => { + const target = event.target as HTMLImageElement; + target.src = DEFAULT_IMAGES.ICON; +}; \ No newline at end of file diff --git a/composables/useProjectDetail.ts b/composables/useProjectDetail.ts index 65fcd60..a9d5312 100644 --- a/composables/useProjectDetail.ts +++ b/composables/useProjectDetail.ts @@ -1,33 +1,66 @@ -import { ref, computed } from 'vue'; -import { useRoute } from 'vue-router'; -import { projects } from '~/config/projects'; -import { DEFAULT_IMAGES } from '~/config/paths'; +import { ref, onMounted, onUnmounted } from 'vue'; +import { handleImageError, handleIconError, handleLightboxImageError } from '~/assets/ts/error/handle'; import { TEXTS } from '~/config/content'; import { PATHS } from '~/config/paths'; +import { projects } from '~/config/projects'; +import type { Project } from '~/types/project'; + -export const useProjectDetail = () => { +export const useProjectDetail = (id: number) => { const { $theme } = useNuxtApp(); - const route = useRoute(); - const error = ref(null); - const lightboxOpen = ref(false); + const showLightbox = ref(false); const currentImageIndex = ref(0); + const processedImages = ref([]); + const currentProject = ref(null); - const currentProject = computed(() => { - const projectId = route.params.id as string; - return projects.find(p => String(p.id) === projectId) || null; - }); + currentProject.value = projects.find((project) => project.id === id) || null; + + const openLightbox = (index: number): void => { + currentImageIndex.value = index; + showLightbox.value = true; + }; + + const closeLightbox = (): void => { + showLightbox.value = false; + }; - const projectImage = computed(() => { - return currentProject.value?.image || DEFAULT_IMAGES.PROJECT; + const nextImage = (): void => { + if (!processedImages.value.length) return; + currentImageIndex.value = (currentImageIndex.value + 1) % processedImages.value.length; + }; + + const prevImage = (): void => { + if (!processedImages.value.length) return; + currentImageIndex.value = (currentImageIndex.value - 1 + processedImages.value.length) % processedImages.value.length; + }; + + const handleKeyDown = (event: KeyboardEvent): void => { + if (!showLightbox.value) return; + + switch (event.key) { + case 'Escape': + closeLightbox(); + break; + case 'ArrowLeft': + prevImage(); + break; + case 'ArrowRight': + nextImage(); + break; + } + }; + + onMounted(async () => { + window.addEventListener('keydown', handleKeyDown); }); - const projectGallery = computed(() => { - return currentProject.value?.gallery || []; + onUnmounted(() => { + window.removeEventListener('keydown', handleKeyDown); }); const getIconPath = (iconType: keyof typeof PATHS.ICONS) => { - return $theme.isDark.value - ? PATHS.ICONS[iconType]?.DARK + return $theme.isDark.value + ? PATHS.ICONS[iconType]?.DARK : PATHS.ICONS[iconType]?.LIGHT; }; @@ -50,51 +83,17 @@ export const useProjectDetail = () => { } : null })); - const handleImageError = (event: Event) => { - const target = event.target as HTMLImageElement; - target.src = DEFAULT_IMAGES.PROJECT; - error.value = TEXTS.COMMON.ERROR.IMAGE_LOAD; - }; - - const handleIconError = (event: Event) => { - const target = event.target as HTMLImageElement; - target.src = DEFAULT_IMAGES.ICON; - error.value = TEXTS.COMMON.ERROR.ICON_LOAD; - }; - - const openLightbox = (index: number) => { - currentImageIndex.value = index; - lightboxOpen.value = true; - }; - - const closeLightbox = () => { - lightboxOpen.value = false; - }; - - const nextImage = () => { - if (projectGallery.value.length === 0) return; - currentImageIndex.value = (currentImageIndex.value + 1) % projectGallery.value.length; - }; - - const prevImage = () => { - if (projectGallery.value.length === 0) return; - currentImageIndex.value = (currentImageIndex.value - 1 + projectGallery.value.length) % projectGallery.value.length; - }; - return { currentProject, - projectImage, - projectGallery, projectResources, - isLoading: ref(false), - error, - handleImageError, - handleIconError, - lightboxOpen, + showLightbox, currentImageIndex, openLightbox, closeLightbox, nextImage, - prevImage + prevImage, + handleImageError, + handleIconError, + handleLightboxImageError }; }; \ No newline at end of file diff --git a/composables/useTheme.ts b/composables/useTheme.ts index 19d630f..990d8c5 100644 --- a/composables/useTheme.ts +++ b/composables/useTheme.ts @@ -1,4 +1,4 @@ -import { ref, onMounted } from 'vue'; +import { ref } from 'vue'; const THEME_KEY = 'theme'; const THEMES = { @@ -6,11 +6,9 @@ const THEMES = { DARK: 'dark' } as const; -// Créer un état partagé pour le thème const isDark = ref(false); let initialized = false; -// Fonction pour définir le thème const setTheme = (theme: typeof THEMES[keyof typeof THEMES]) => { if (process.client) { document.documentElement.setAttribute('data-theme', theme); @@ -19,34 +17,26 @@ const setTheme = (theme: typeof THEMES[keyof typeof THEMES]) => { } }; -// Initialiser le thème uniquement côté client et une seule fois const initializeTheme = () => { if (process.client && !initialized) { const savedTheme = localStorage.getItem(THEME_KEY) as typeof THEMES[keyof typeof THEMES] | null; const prefersDark = window.matchMedia?.('(prefers-color-scheme: dark)').matches; - const theme = savedTheme || (prefersDark ? THEMES.DARK : THEMES.LIGHT); setTheme(theme); - - // Configurer un écouteur pour les changements de préférence window.matchMedia?.('(prefers-color-scheme: dark)').addEventListener('change', (e) => { - // Ne mettre à jour automatiquement que si l'utilisateur n'a pas défini de préférence if (!localStorage.getItem(THEME_KEY)) { setTheme(e.matches ? THEMES.DARK : THEMES.LIGHT); } }); - initialized = true; } }; -// Fonction pour basculer le thème const toggleTheme = () => { setTheme(isDark.value ? THEMES.LIGHT : THEMES.DARK); }; export const useTheme = () => { - // Initialiser le thème lors du premier appel côté client if (process.client) { initializeTheme(); } diff --git a/config/paths.ts b/config/paths.ts index f9c4b96..2033810 100644 --- a/config/paths.ts +++ b/config/paths.ts @@ -49,15 +49,15 @@ export const PATHS = { GITHUB: { LIGHT: withBasePath('/assets/icons/github-black.png'), DARK: withBasePath('/assets/icons/github-white.png') - }, + } as Icons, DEMO: { LIGHT: withBasePath('/assets/icons/demo-black.png'), DARK: withBasePath('/assets/icons/demo-white.png') - }, + } as Icons, DOCUMENTATION: { LIGHT: withBasePath('/assets/icons/documentation-black.png'), DARK: withBasePath('/assets/icons/documentation-white.png') - }, + } as Icons, YOUTUBE: { LIGHT: withBasePath('/assets/icons/youtube-black.png'), DARK: withBasePath('/assets/icons/youtube-white.png') @@ -76,7 +76,6 @@ export const PATHS = { PORTFOLIO: { GITHUB: 'https://github.com/37b7/portfolio_nuxt', DEMO: 'https://votre-portfolio.com', - DOCUMENTATION: 'https://votre-portfolio.com/docs' }, IRIS: { GITHUB: 'https://github.com/votre-username/todo-app', diff --git a/nuxt.config.ts b/nuxt.config.ts index adc13b9..5c5ec09 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -14,6 +14,10 @@ export default defineNuxtConfig({ '~/assets/css/pages/cv.css' ], + imports: { + dirs: ['composables'] + }, + app: { head: { title: 'Portfolio', @@ -23,12 +27,12 @@ export default defineNuxtConfig({ { name: 'description', content: 'Portfolio professionnel' } ], link: [ - { - rel: 'icon', - type: 'image/x-icon', - href: process.env.NODE_ENV === 'production' - ? (process.env.BASE_PATH || '/containers/matheothierry-portfolio_nuxt') + '/favicon.ico' - : '/favicon.ico' + { + rel: 'icon', + type: 'image/x-icon', + href: process.env.NODE_ENV === 'production' + ? (process.env.BASE_PATH || '/containers/matheothierry-portfolio_nuxt') + '/favicon.ico' + : '/favicon.ico' } ], }, @@ -69,4 +73,4 @@ export default defineNuxtConfig({ basePath: process.env.BASE_PATH || (process.env.NODE_ENV === 'production' ? '/containers/matheothierry-portfolio_nuxt' : '') } } -}) +}) \ No newline at end of file diff --git a/pages/projects/[id]/index.vue b/pages/projects/[id]/index.vue index 1bf31fe..3abbc73 100644 --- a/pages/projects/[id]/index.vue +++ b/pages/projects/[id]/index.vue @@ -33,19 +33,19 @@

{{ TEXTS.PROJECTS.DESCRIPTION.RESOURCES }}

@@ -68,7 +68,7 @@ -