- Ajout du store userPreferencesStore (thème, vue, tri, pagination, lecteur) - Page UserPreferencesPage pour configurer toutes les préférences - Câblage des prefs dans HomePage (viewMode, sortBy, itemsPerPage), readerStore (fallback prefs), ChapterReader (autoHide, autoFullscreen, sync), useNotifications (toastDuration) - Thème sombre (dark: Tailwind) sur tous les composants Vue : Layout, Pagination, NotificationToast, MangaCard, MangaVolume, MangaDetails, AddManga, HomePage, ActivityPage, JobItem, MangaDeleteModal, MangaEditModal, MangaPreferredSourcesModal, ManageChaptersModal, MangaChapterList, MangaChapter, ConversionPage, FileUploadArea, ConversionProgress, NewImportPage, FileImportCard, MangaMatchCard, StatusBadge, ImportResults - i18n partiellement initialisé Jeremy Guillot
276 lines
9.6 KiB
Vue
276 lines
9.6 KiB
Vue
<template>
|
|
<div class="chapter-reader" :class="{ rtl: store.readingDirection === 'rtl' }">
|
|
<div v-if="store.isLoading" class="loading">
|
|
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
|
|
</div>
|
|
|
|
<div v-else-if="store.error" class="error">
|
|
{{ store.error }}
|
|
</div>
|
|
|
|
<div v-else class="reader-content">
|
|
<ReaderControls
|
|
v-if="store.readingMode === 'single'"
|
|
:current-page="store.currentPage"
|
|
:total-pages="store.totalPages"
|
|
:is-first-page="store.isFirstPage"
|
|
:is-last-page="store.isLastPage"
|
|
:available-chapters="availableChapters"
|
|
:settings-open="settingsOpen"
|
|
@previous="store.previousPage"
|
|
@next="store.nextPage"
|
|
@chapter-selected="handleChapterSelected"
|
|
@toggle-settings="toggleSettings" />
|
|
|
|
<template v-if="store.readingMode === 'single'">
|
|
<SingleModeReader
|
|
:page-data="store.currentPageData"
|
|
:page-number="store.currentPage + 1"
|
|
:zoom="store.zoom"
|
|
:double-page-mode="store.effectiveDoublePageMode"
|
|
@button-click="showButtonsWithTimer" />
|
|
</template>
|
|
<template v-else>
|
|
<InfiniteReader
|
|
:pages="store.pages"
|
|
:zoom="store.zoom"
|
|
:double-page-mode="store.effectiveDoublePageMode"
|
|
@page-visible="store.handlePageVisible"
|
|
@buttons-visibility-change="handleButtonsVisibilityChange"
|
|
ref="infiniteReaderRef" />
|
|
</template>
|
|
|
|
<ReaderSettings
|
|
:reading-mode="store.readingMode"
|
|
:reading-direction="store.readingDirection"
|
|
:zoom="store.zoom"
|
|
:double-page-mode="store.effectiveDoublePageMode"
|
|
:double-page-settings="store.doublePageSettings"
|
|
:visible="showFloatingButtons"
|
|
:force-open="store.readingMode === 'single' ? settingsOpen : null"
|
|
@toggle-reading-mode="toggleReadingMode"
|
|
@toggle-reading-direction="toggleReadingDirection"
|
|
@zoom-in="zoomIn"
|
|
@zoom-out="zoomOut"
|
|
@zoom-change="handleZoomChange"
|
|
@double-page-mode-change="handleDoublePageModeChange"
|
|
@double-page-auto-detect-change="handleDoublePageAutoDetectChange"
|
|
@detection-threshold-change="handleDetectionThresholdChange"
|
|
@reset-preferences="handleResetPreferences"
|
|
@button-click="resetButtonsTimer" />
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { onMounted, onUnmounted, ref, watch } from 'vue';
|
|
import { useHeaderStore } from '../../../../shared/stores/headerStore';
|
|
import { useUserPreferencesStore } from '../../../../domain/setting/application/store/userPreferencesStore';
|
|
import { useReaderStore } from '../../application/store/readerStore';
|
|
import InfiniteReader from './InfiniteReader.vue';
|
|
import ReaderControls from './ReaderControls.vue';
|
|
import ReaderSettings from './ReaderSettings.vue';
|
|
import SingleModeReader from './SingleModeReader.vue';
|
|
|
|
const props = defineProps({
|
|
chapterId: {
|
|
type: String,
|
|
required: true
|
|
},
|
|
availableChapters: {
|
|
type: Array,
|
|
default: () => []
|
|
}
|
|
});
|
|
|
|
const store = useReaderStore();
|
|
const headerStore = useHeaderStore();
|
|
const prefs = useUserPreferencesStore();
|
|
|
|
// Référence vers InfiniteReader pour accéder à ses méthodes
|
|
const infiniteReaderRef = ref(null);
|
|
|
|
// État pour la visibilité des boutons (géré par InfiniteReader en mode infini, localement en mode simple)
|
|
const showFloatingButtons = ref(false);
|
|
const settingsOpen = ref(false); // Nouvel état pour gérer l'ouverture des paramètres
|
|
let localButtonsTimer = null;
|
|
|
|
// Actions de l'interface lecteur
|
|
const toggleReadingMode = () => {
|
|
const newMode = store.readingMode === 'single' ? 'infinite' : 'single';
|
|
store.setReadingMode(newMode);
|
|
prefs.setReadingMode(newMode === 'infinite' ? 'scroll' : 'single');
|
|
|
|
// Gérer la visibilité selon le mode
|
|
if (newMode === 'single') {
|
|
headerStore.disableAutoHide();
|
|
// En mode simple : toujours visible
|
|
showFloatingButtons.value = true;
|
|
clearTimeout(localButtonsTimer); // Annuler tout timer local
|
|
} else {
|
|
// En mode infini : utiliser la logique d'InfiniteReader
|
|
showButtonsWithTimer();
|
|
}
|
|
};
|
|
|
|
const toggleReadingDirection = () => {
|
|
const newDir = store.readingDirection === 'ltr' ? 'rtl' : 'ltr';
|
|
store.setReadingDirection(newDir);
|
|
prefs.setReadingDirection(newDir);
|
|
resetButtonsTimer();
|
|
};
|
|
|
|
const zoomIn = () => {
|
|
store.setZoom(Math.min(store.zoom + 0.1, 2));
|
|
resetButtonsTimer();
|
|
};
|
|
|
|
const zoomOut = () => {
|
|
store.setZoom(Math.max(store.zoom - 0.1, 0.5));
|
|
resetButtonsTimer();
|
|
};
|
|
|
|
const handleZoomChange = (zoom) => {
|
|
store.setZoom(zoom);
|
|
resetButtonsTimer();
|
|
};
|
|
|
|
// Fonctions pour les doubles pages
|
|
const handleDoublePageModeChange = (mode) => {
|
|
store.setDoublePageMode(mode);
|
|
resetButtonsTimer();
|
|
};
|
|
|
|
const handleDoublePageAutoDetectChange = (enabled) => {
|
|
store.setDoublePageAutoDetect(enabled);
|
|
resetButtonsTimer();
|
|
};
|
|
|
|
const handleDetectionThresholdChange = (threshold) => {
|
|
store.setDoublePageDetectionThreshold(threshold);
|
|
resetButtonsTimer();
|
|
};
|
|
|
|
const handleResetPreferences = () => {
|
|
store.resetPreferences();
|
|
resetButtonsTimer();
|
|
};
|
|
|
|
// Fonction pour afficher les boutons avec timer (avec fallback pour mode simple)
|
|
const showButtonsWithTimer = () => {
|
|
if (store.readingMode === 'infinite' && infiniteReaderRef.value) {
|
|
// Mode infini : utiliser la logique d'InfiniteReader
|
|
infiniteReaderRef.value.showButtonsWithTimer();
|
|
} else {
|
|
// Mode simple : toujours visible, pas de timer
|
|
showFloatingButtons.value = true;
|
|
}
|
|
};
|
|
|
|
// Fonction centralisée pour réinitialiser le timer
|
|
const resetButtonsTimer = () => {
|
|
if (store.readingMode === 'infinite' && infiniteReaderRef.value) {
|
|
// Mode infini : utiliser la logique d'InfiniteReader
|
|
infiniteReaderRef.value.resetButtonsTimer();
|
|
} else {
|
|
// Mode simple : toujours visible, pas de timer
|
|
showFloatingButtons.value = true;
|
|
}
|
|
};
|
|
|
|
// Gestionnaire pour les changements de visibilité des boutons
|
|
const handleButtonsVisibilityChange = (visible) => {
|
|
if (store.readingMode === 'infinite') {
|
|
showFloatingButtons.value = visible;
|
|
}
|
|
// En mode simple, on ignore les changements et on reste toujours visible
|
|
};
|
|
|
|
const handleKeyPress = event => {
|
|
if (store.readingMode === 'single') {
|
|
if (event.key === 'ArrowRight') {
|
|
store.nextPage();
|
|
showButtonsWithTimer(); // Afficher les boutons lors de la navigation clavier
|
|
} else if (event.key === 'ArrowLeft') {
|
|
store.previousPage();
|
|
showButtonsWithTimer(); // Afficher les boutons lors de la navigation clavier
|
|
}
|
|
}
|
|
};
|
|
|
|
const handleChapterSelected = (chapterId) => {
|
|
// La navigation est déjà gérée par le ChapterSelector via le store
|
|
// Cette fonction est là pour d'éventuelles actions supplémentaires
|
|
console.log('Chapitre sélectionné:', chapterId);
|
|
resetButtonsTimer();
|
|
};
|
|
|
|
// Gestion des paramètres via le bouton intégré
|
|
const toggleSettings = () => {
|
|
settingsOpen.value = !settingsOpen.value;
|
|
resetButtonsTimer(); // Réinitialiser le timer lors de l'interaction
|
|
};
|
|
|
|
watch(
|
|
() => props.chapterId,
|
|
newId => {
|
|
if (newId) {
|
|
store.loadChapter(newId);
|
|
}
|
|
},
|
|
{ immediate: true }
|
|
);
|
|
|
|
onMounted(() => {
|
|
// Charger les préférences sauvegardées
|
|
store.loadPreferences();
|
|
|
|
window.addEventListener('keydown', handleKeyPress);
|
|
|
|
// Auto-hide header si activé dans les préférences
|
|
if (prefs.autoHideHeaderReader) {
|
|
headerStore.enableAutoHide();
|
|
}
|
|
|
|
// Auto-fullscreen si activé dans les préférences
|
|
if (prefs.autoFullscreen && document.documentElement.requestFullscreen) {
|
|
document.documentElement.requestFullscreen().catch(() => {});
|
|
}
|
|
|
|
// Afficher les boutons au démarrage
|
|
showButtonsWithTimer();
|
|
});
|
|
|
|
onUnmounted(() => {
|
|
window.removeEventListener('keydown', handleKeyPress);
|
|
// S'assurer que l'auto-hide est désactivé en quittant le lecteur
|
|
headerStore.disableAutoHide();
|
|
// Nettoyer le timer local
|
|
clearTimeout(localButtonsTimer);
|
|
});
|
|
</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 {
|
|
@apply flex items-center justify-center h-full;
|
|
}
|
|
|
|
.error {
|
|
@apply text-red-500 text-xl;
|
|
}
|
|
|
|
.reader-content {
|
|
@apply w-full h-full flex flex-col;
|
|
@apply p-0 sm:p-2;
|
|
}
|
|
|
|
.rtl {
|
|
direction: rtl;
|
|
}
|
|
</style>
|