feat: ajout de la gestion de l'auto-hide du header et amélioration de la réactivité des composants en fonction de la taille de la fenêtre, ainsi que des optimisations CSS pour une meilleure expérience utilisateur sur mobile.

This commit is contained in:
ext.jeremy.guillot@maxicoffee.domains
2025-06-26 22:59:21 +02:00
parent 4848a1736f
commit ebcca466a9
10 changed files with 279 additions and 50 deletions

View File

@@ -44,6 +44,7 @@
<script setup>
import { onMounted, onUnmounted, watch } from 'vue';
import { useHeaderStore } from '../../../../shared/stores/headerStore';
import { useReaderStore } from '../../application/store/readerStore';
import InfiniteReader from './InfiniteReader.vue';
import ReaderControls from './ReaderControls.vue';
@@ -62,9 +63,16 @@ import SingleModeReader from './SingleModeReader.vue';
});
const store = useReaderStore();
const headerStore = useHeaderStore();
const toggleReadingMode = () => {
store.setReadingMode(store.readingMode === 'single' ? 'infinite' : 'single');
const newMode = store.readingMode === 'single' ? 'infinite' : 'single';
store.setReadingMode(newMode);
// Désactiver l'auto-hide si on passe en mode single
if (newMode === 'single') {
headerStore.disableAutoHide();
}
};
const toggleReadingDirection = () => {
@@ -111,12 +119,15 @@ import SingleModeReader from './SingleModeReader.vue';
onUnmounted(() => {
window.removeEventListener('keydown', handleKeyPress);
// S'assurer que l'auto-hide est désactivé en quittant le lecteur
headerStore.disableAutoHide();
});
</script>
<style lang="postcss" scoped>
.chapter-reader {
@apply w-full h-full flex flex-col items-center justify-center bg-gray-900 text-white;
@apply p-0 sm:p-2;
}
.loading {
@@ -129,6 +140,7 @@ import SingleModeReader from './SingleModeReader.vue';
.reader-content {
@apply w-full h-full flex flex-col;
@apply p-0 sm:p-2;
}
.rtl {

View File

@@ -46,6 +46,7 @@
<script setup>
import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
import { useHeaderStore } from '../../../../shared/stores/headerStore';
import ChapterNavigation from './ChapterNavigation.vue';
import ReaderPage from './ReaderPage.vue';
@@ -62,8 +63,10 @@ import ReaderPage from './ReaderPage.vue';
const emit = defineEmits(['pageVisible']);
const headerStore = useHeaderStore();
const containerRef = ref(null);
const observer = ref(null);
const windowWidth = ref(window.innerWidth);
// État pour le bouton scroll to top
const showScrollToTop = ref(false);
@@ -102,7 +105,7 @@ import ReaderPage from './ReaderPage.vue';
});
};
// Gestion du scroll pour le bouton "revenir en haut"
// Gestion du scroll pour le bouton "revenir en haut" et le header
const handleScroll = () => {
let scrollTop = 0;
@@ -133,6 +136,11 @@ import ReaderPage from './ReaderPage.vue';
scrollDirection = 'up';
}
// Gestion du header auto-hide (seulement si largeur < 1200px)
if (windowWidth.value < 1200) {
headerStore.updateScrollDirection(scrollTop);
}
// Mise à jour de la visibilité du bouton
// Afficher si on scroll vers le bas et qu'on est à plus de 300px
// Masquer si on scroll vers le haut ou qu'on est en haut de page
@@ -195,9 +203,27 @@ import ReaderPage from './ReaderPage.vue';
{ immediate: true }
);
// Gestion du redimensionnement de la fenêtre
const handleResize = () => {
const newWidth = window.innerWidth;
windowWidth.value = newWidth;
// Activer/désactiver l'auto-hide selon la largeur
if (newWidth < 1200) {
headerStore.enableAutoHide();
} else {
headerStore.disableAutoHide();
}
};
onMounted(() => {
setupIntersectionObserver();
// Activer l'auto-hide du header si la largeur < 1200px
if (windowWidth.value < 1200) {
headerStore.enableAutoHide();
}
// Ajouter l'écouteur de scroll sur le conteneur
if (containerRef.value) {
containerRef.value.addEventListener('scroll', handleScroll, { passive: true });
@@ -205,6 +231,9 @@ import ReaderPage from './ReaderPage.vue';
// Ajouter l'écouteur de scroll sur la fenêtre
window.addEventListener('scroll', handleScroll, { passive: true });
// Ajouter l'écouteur de redimensionnement
window.addEventListener('resize', handleResize, { passive: true });
});
onUnmounted(() => {
@@ -212,6 +241,9 @@ import ReaderPage from './ReaderPage.vue';
observer.value.disconnect();
}
// Désactiver l'auto-hide du header en quittant
headerStore.disableAutoHide();
// Nettoyer l'écouteur de scroll du conteneur
if (containerRef.value) {
containerRef.value.removeEventListener('scroll', handleScroll);
@@ -219,23 +251,46 @@ import ReaderPage from './ReaderPage.vue';
// Nettoyer l'écouteur de scroll de la fenêtre
window.removeEventListener('scroll', handleScroll);
// Nettoyer l'écouteur de redimensionnement
window.removeEventListener('resize', handleResize);
});
</script>
<style lang="postcss" scoped>
.infinite-reader {
@apply flex-1 flex flex-col items-center overflow-y-auto py-8 relative;
@apply flex-1 flex flex-col items-center overflow-y-auto relative;
/* Réduction du padding sur mobile */
@apply py-2 sm:py-8;
height: calc(100vh - 8rem);
scroll-behavior: smooth;
}
.page-wrapper {
@apply w-full flex justify-center min-h-[200px] mb-4;
@apply w-full flex justify-center min-h-[200px];
/* Réduction des marges sur mobile */
@apply mb-2 sm:mb-4 px-1 sm:px-4;
}
.loading,
.error {
@apply flex items-center justify-center w-[70vw] min-h-[400px];
@apply flex items-center justify-center min-h-[400px];
/* Largeur adaptative selon la taille d'écran */
width: 95vw; /* Mobile : 95% de la largeur */
}
@screen sm {
.loading,
.error {
width: 80vw; /* Tablette : 80% de la largeur */
}
}
@screen lg {
.loading,
.error {
width: 70vw; /* Desktop : 70% de la largeur */
}
}
.error {

View File

@@ -14,7 +14,7 @@
</template>
<script setup>
import { computed, ref, onMounted } from 'vue';
import { computed, onMounted, onUnmounted, ref } from 'vue';
const props = defineProps({
pageData: {
@@ -34,6 +34,7 @@
const imageRef = ref(null);
const naturalWidth = ref(0);
const naturalHeight = ref(0);
const windowWidth = ref(window.innerWidth);
const imageSource = computed(() => {
if (!props.pageData?.base64Content || !props.pageData?.mimeType) {
@@ -49,19 +50,19 @@
}
};
// Calculer la largeur maximale en fonction de la hauteur de la fenêtre
// Calculer la largeur maximale en fonction de la largeur disponible
const maxWidth = computed(() => {
if (!naturalWidth.value || !naturalHeight.value) return null;
// On prend 70% de la largeur de la fenêtre comme largeur maximale
const maxWidthPx = window.innerWidth * 0.7;
const availableWidth = windowWidth.value;
// Si l'image est plus petite que la largeur maximale, on garde sa taille naturelle
if (naturalWidth.value <= maxWidthPx) {
return naturalWidth.value;
// Si la largeur disponible est < 1200px : utiliser 95% de la largeur
if (availableWidth < 1200) {
return Math.min(naturalWidth.value, availableWidth * 0.95);
}
return maxWidthPx;
// Si la largeur disponible est >= 1200px : limiter à 1200px maximum
return Math.min(naturalWidth.value, 1200);
});
const imageStyle = computed(() => {
@@ -74,10 +75,20 @@
};
});
// Gestion du redimensionnement de la fenêtre
const handleResize = () => {
windowWidth.value = window.innerWidth;
};
onMounted(() => {
if (imageRef.value && imageRef.value.complete) {
handleImageLoad();
}
window.addEventListener('resize', handleResize);
});
onUnmounted(() => {
window.removeEventListener('resize', handleResize);
});
</script>
@@ -85,13 +96,20 @@
.page-container {
@apply flex-1 flex items-center justify-center overflow-hidden;
transform-origin: center;
/* Réduction des marges sur mobile */
@apply p-0 sm:p-2;
}
.page-image {
@apply max-w-full max-h-full object-contain;
@apply object-contain;
/* La largeur est gérée par le JavaScript, on garde juste les contraintes max */
max-width: 100%;
max-height: 100%;
}
.error {
@apply text-red-500 text-xl;
}
</style>

View File

@@ -143,6 +143,8 @@ const hideRightHint = () => {
<style lang="postcss" scoped>
.single-mode-reader {
@apply relative w-full h-full flex items-center justify-center;
/* Suppression des marges sur mobile */
@apply p-0 sm:p-2;
}
.page-navigation-wrapper {
@@ -152,6 +154,8 @@ const hideRightHint = () => {
.page-content {
@apply flex-1 h-full flex items-center justify-center;
pointer-events: none; /* Empêche les clics sur l'image elle-même */
/* Optimisation pour mobile */
@apply p-0;
}
.navigation-zone {

View File

@@ -1,11 +1,28 @@
<template>
<div class="chapter-page">
<div class="chapter-header">
<h1 class="text-2xl font-bold">
{{ currentChapter?.title || 'Chargement...' }}
</h1>
<div class="chapter-info">
<span class="text-gray-400"> Chapitre {{ currentChapter?.number }} </span>
<!-- Bouton de retour -->
<div class="flex items-center gap-4 mb-4">
<button
@click="goBackToManga"
class="flex items-center gap-2 px-3 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg text-white transition-colors duration-200"
:disabled="!currentChapter?.mangaId"
>
<ArrowLeftIcon class="h-5 w-5" />
<span class="text-sm font-medium">Retour au manga</span>
</button>
</div>
<!-- Titre du chapitre amélioré -->
<div class="chapter-title-section">
<h1 class="text-3xl md:text-4xl font-bold text-white leading-tight">
{{ currentChapter?.title || 'Chargement...' }}
</h1>
<div class="chapter-meta mt-3">
<span class="inline-flex items-center px-3 py-1 bg-blue-600 text-white text-sm font-semibold rounded-full">
Chapitre {{ currentChapter?.number }}
</span>
</div>
</div>
</div>
@@ -16,16 +33,24 @@
</template>
<script setup>
import { computed } from 'vue';
import { useRoute } from 'vue-router';
import { useReaderStore } from '../../application/store/readerStore';
import ChapterReader from '../components/ChapterReader.vue';
import { ArrowLeftIcon } from '@heroicons/vue/24/outline';
import { computed } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useReaderStore } from '../../application/store/readerStore';
import ChapterReader from '../components/ChapterReader.vue';
const route = useRoute();
const router = useRouter();
const store = useReaderStore();
const chapterId = computed(() => route.params.chapterId);
const currentChapter = computed(() => store.currentChapter);
const goBackToManga = () => {
if (currentChapter.value?.mangaId) {
router.push({ name: 'manga-details', params: { id: currentChapter.value.mangaId } });
}
};
</script>
<style lang="postcss" scoped>
@@ -34,11 +59,15 @@
}
.chapter-header {
@apply p-4 bg-gray-800 border-b border-gray-700;
@apply p-6 bg-gradient-to-b from-gray-800 to-gray-900 border-b border-gray-700 shadow-lg;
}
.chapter-info {
@apply mt-2;
.chapter-title-section {
@apply space-y-2;
}
.chapter-meta {
@apply flex flex-wrap items-center gap-3;
}
.reader-container {