refactor(reader): serve pages as static files instead of base64
Replace the per-page API call (base64 payload) with static image URLs
served directly by Caddy from public/images/pages/{chapterId}/.
- LocalImageStorage now stores to public/images/ (was MANGA_DATA_PATH)
- LegacyChapterRepository returns /images/pages/{id}/{file} URLs,
uses getimagesize() instead of loading file content into memory
- Delete GetChapterPage query/handler/response, ChapterPageResource,
ChapterPageProvider, PageContent model
- Remove getPageContent() from ChapterRepositoryInterface
- Frontend: loadChapter() fetches chapter + all pages in parallel,
ReaderPage uses URL instead of base64 data URI, InfiniteReader drops
lazy-load observer side effect, readerStore drops loadedPages/preload
- GetChapterPagesTest: extract fixture images from CBZ at runtime,
ignore tests/Fixtures/pages/ in .gitignore
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
6875ad4222
commit
322c396165
@@ -13,7 +13,6 @@ export const useReaderStore = defineStore('reader', {
|
||||
error: null,
|
||||
pages: [],
|
||||
totalPages: 0,
|
||||
loadedPages: new Set(), // Garder une trace des pages déjà chargées
|
||||
|
||||
// Paramètres pour les doubles pages
|
||||
doublePageSettings: {
|
||||
@@ -32,7 +31,6 @@ export const useReaderStore = defineStore('reader', {
|
||||
|
||||
// Getters pour les doubles pages
|
||||
effectiveDoublePageMode: (state) => {
|
||||
// Si la détection automatique est désactivée, retourner 'normal'
|
||||
if (!state.doublePageSettings.autoDetect) {
|
||||
return 'normal';
|
||||
}
|
||||
@@ -55,28 +53,20 @@ export const useReaderStore = defineStore('reader', {
|
||||
try {
|
||||
const repository = new ApiChapterRepository();
|
||||
|
||||
// Charger les informations du chapitre
|
||||
const chapterData = await repository.getChapter(chapterId);
|
||||
const [chapterData, pagesData] = await Promise.all([
|
||||
repository.getChapter(chapterId),
|
||||
repository.getChapterPages(chapterId, 1, 9999),
|
||||
]);
|
||||
|
||||
this.currentChapter = Chapter.create(chapterData);
|
||||
|
||||
// Charger la liste des pages
|
||||
const pagesData = await repository.getChapterPages(chapterId);
|
||||
|
||||
// Initialiser le tableau avec des placeholders
|
||||
this.pages = new Array(pagesData.totalItems).fill(null);
|
||||
this.pages = pagesData.pages.map(p => ({
|
||||
id: p.id,
|
||||
pageNumber: p.pageNumber,
|
||||
url: p.url,
|
||||
dimensions: p.dimensions,
|
||||
}));
|
||||
this.totalPages = pagesData.totalItems;
|
||||
this.loadedPages.clear();
|
||||
|
||||
// Charger la première page
|
||||
if (this.totalPages > 0) {
|
||||
this.currentPage = 0;
|
||||
await this.loadPageData(0);
|
||||
|
||||
// En mode infini, précharger les premières pages
|
||||
if (this.readingMode === 'infinite') {
|
||||
await this.preloadNextPages(0);
|
||||
}
|
||||
}
|
||||
this.currentPage = 0;
|
||||
} catch (error) {
|
||||
this.error = error.message;
|
||||
} finally {
|
||||
@@ -84,100 +74,28 @@ export const useReaderStore = defineStore('reader', {
|
||||
}
|
||||
},
|
||||
|
||||
async loadPageData(pageIndex) {
|
||||
if (!this.currentChapter || pageIndex < 0 || pageIndex >= this.totalPages) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Si la page est déjà chargée, ne rien faire
|
||||
if (this.loadedPages.has(pageIndex)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pageNumber = pageIndex + 1; // Convertir en 1-based pour l'API
|
||||
|
||||
// Marquer la page comme en cours de chargement
|
||||
const newPages = [...this.pages];
|
||||
newPages[pageIndex] = { loading: true };
|
||||
this.pages = newPages;
|
||||
|
||||
try {
|
||||
const repository = new ApiChapterRepository();
|
||||
const pageData = await repository.getChapterPage(this.currentChapter.id, pageNumber);
|
||||
|
||||
// Vérifier que les données sont valides
|
||||
if (!pageData || !pageData.base64Content) {
|
||||
throw new Error("Données de page invalides reçues de l'API");
|
||||
}
|
||||
|
||||
// Mettre à jour la page
|
||||
const updatedPages = [...this.pages];
|
||||
updatedPages[pageIndex] = {
|
||||
id: pageData.id,
|
||||
pageNumber: pageData.pageNumber,
|
||||
base64Content: pageData.base64Content,
|
||||
mimeType: pageData.mimeType,
|
||||
dimensions: pageData.dimensions
|
||||
};
|
||||
this.pages = updatedPages;
|
||||
this.loadedPages.add(pageIndex);
|
||||
} catch (error) {
|
||||
console.error(`Erreur lors du chargement de la page ${pageNumber}:`, error);
|
||||
// Marquer la page comme en erreur
|
||||
const errorPages = [...this.pages];
|
||||
errorPages[pageIndex] = { error: error.message };
|
||||
this.pages = errorPages;
|
||||
}
|
||||
},
|
||||
|
||||
async preloadNextPages(startIndex, count = 3) {
|
||||
const promises = [];
|
||||
for (let i = 1; i <= count; i++) {
|
||||
const pageIndex = startIndex + i;
|
||||
if (pageIndex < this.totalPages) {
|
||||
promises.push(this.loadPageData(pageIndex));
|
||||
}
|
||||
}
|
||||
await Promise.all(promises);
|
||||
},
|
||||
|
||||
async handlePageVisible(pageIndex) {
|
||||
handlePageVisible(pageIndex) {
|
||||
if (pageIndex !== this.currentPage) {
|
||||
this.currentPage = pageIndex;
|
||||
// Précharger les pages suivantes
|
||||
if (this.readingMode === 'infinite') {
|
||||
await this.preloadNextPages(pageIndex);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async nextPage() {
|
||||
nextPage() {
|
||||
if (!this.isLastPage) {
|
||||
this.currentPage++;
|
||||
await this.loadPageData(this.currentPage);
|
||||
}
|
||||
},
|
||||
|
||||
async previousPage() {
|
||||
previousPage() {
|
||||
if (!this.isFirstPage) {
|
||||
this.currentPage--;
|
||||
await this.loadPageData(this.currentPage);
|
||||
}
|
||||
},
|
||||
|
||||
async setReadingMode(mode) {
|
||||
if (mode === this.readingMode) return;
|
||||
|
||||
this.readingMode = mode;
|
||||
this.savePreferences();
|
||||
|
||||
// S'assurer que la page courante est chargée
|
||||
await this.loadPageData(this.currentPage);
|
||||
|
||||
// Si on passe en mode infini, précharger les pages suivantes
|
||||
if (mode === 'infinite') {
|
||||
await this.preloadNextPages(this.currentPage);
|
||||
}
|
||||
},
|
||||
|
||||
setReadingDirection(direction) {
|
||||
@@ -190,7 +108,6 @@ export const useReaderStore = defineStore('reader', {
|
||||
this.savePreferences();
|
||||
},
|
||||
|
||||
// Nouvelles actions pour les doubles pages
|
||||
setDoublePageMode(mode) {
|
||||
if (['rotate', 'scroll', 'normal'].includes(mode)) {
|
||||
this.doublePageSettings.mobileMode = mode;
|
||||
@@ -225,16 +142,10 @@ export const useReaderStore = defineStore('reader', {
|
||||
async goToPreviousChapter() {
|
||||
if (this.currentChapter?.navigation?.previousChapter) {
|
||||
await this.loadChapter(this.currentChapter.navigation.previousChapter);
|
||||
// Aller à la dernière page du chapitre précédent
|
||||
this.currentPage = Math.max(0, this.totalPages - 1);
|
||||
// S'assurer que la page est chargée
|
||||
if (this.totalPages > 0) {
|
||||
await this.loadPageData(this.currentPage);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Gestion de la persistance des préférences
|
||||
savePreferences() {
|
||||
try {
|
||||
const preferences = {
|
||||
@@ -255,7 +166,6 @@ export const useReaderStore = defineStore('reader', {
|
||||
if (stored) {
|
||||
const preferences = JSON.parse(stored);
|
||||
|
||||
// Appliquer les préférences sauvegardées
|
||||
if (preferences.readingMode) this.readingMode = preferences.readingMode;
|
||||
if (preferences.readingDirection) this.readingDirection = preferences.readingDirection;
|
||||
if (typeof preferences.zoom === 'number') this.zoom = preferences.zoom;
|
||||
@@ -277,7 +187,6 @@ export const useReaderStore = defineStore('reader', {
|
||||
}
|
||||
},
|
||||
|
||||
// Réinitialiser les préférences
|
||||
resetPreferences() {
|
||||
this.readingMode = 'single';
|
||||
this.readingDirection = 'ltr';
|
||||
|
||||
Reference in New Issue
Block a user