Compare commits
7 Commits
e6eab32473
...
11da2335cd
| Author | SHA1 | Date | |
|---|---|---|---|
| 11da2335cd | |||
| feceb61e30 | |||
| 701a02b55c | |||
| b2664cce08 | |||
| ff44a781c8 | |||
| d535f9f28e | |||
| 2174579cc1 |
@@ -51,7 +51,7 @@ export function Header({
|
|||||||
<div className="mr-2 flex items-center md:mr-4">
|
<div className="mr-2 flex items-center md:mr-4">
|
||||||
<a className="mr-2 flex items-center md:mr-6" href="/">
|
<a className="mr-2 flex items-center md:mr-6" href="/">
|
||||||
<span className="inline-flex flex-col leading-none">
|
<span className="inline-flex flex-col leading-none">
|
||||||
<span className="bg-gradient-to-r from-primary via-cyan-500 to-fuchsia-500 bg-clip-text text-sm font-bold tracking-[0.06em] text-transparent sm:text-lg sm:tracking-[0.08em]">
|
<span className="bg-gradient-to-r from-primary via-cyan-500 to-fuchsia-500 bg-clip-text text-base font-bold tracking-[0.06em] text-transparent sm:text-lg sm:tracking-[0.08em]">
|
||||||
StripStream
|
StripStream
|
||||||
</span>
|
</span>
|
||||||
<span className="mt-1 hidden text-[10px] font-medium uppercase tracking-[0.22em] text-foreground/70 sm:inline">
|
<span className="mt-1 hidden text-[10px] font-medium uppercase tracking-[0.22em] text-foreground/70 sm:inline">
|
||||||
|
|||||||
@@ -76,13 +76,30 @@ export function PhotoswipeReader({ book, pages, onClose, nextBook }: BookReaderP
|
|||||||
onPreviousPage: handlePreviousPage,
|
onPreviousPage: handlePreviousPage,
|
||||||
onNextPage: handleNextPage,
|
onNextPage: handleNextPage,
|
||||||
pswpRef,
|
pswpRef,
|
||||||
|
isRTL,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Activer le zoom dans le reader en enlevant la classe no-pinch-zoom
|
// Activer le zoom dans le reader en enlevant la classe no-pinch-zoom
|
||||||
|
// et reset le zoom lors des changements d'orientation (iOS applique un zoom automatique)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.body.classList.remove("no-pinch-zoom");
|
document.body.classList.remove("no-pinch-zoom");
|
||||||
|
|
||||||
|
const handleOrientationChange = () => {
|
||||||
|
const viewport = document.querySelector('meta[name="viewport"]');
|
||||||
|
if (viewport) {
|
||||||
|
const original = viewport.getAttribute("content") || "";
|
||||||
|
viewport.setAttribute("content", original + ", maximum-scale=1");
|
||||||
|
// Restaurer après que iOS ait appliqué le nouveau layout
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
viewport.setAttribute("content", original);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("orientationchange", handleOrientationChange);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
window.removeEventListener("orientationchange", handleOrientationChange);
|
||||||
document.body.classList.add("no-pinch-zoom");
|
document.body.classList.add("no-pinch-zoom");
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ export const ControlButtons = ({
|
|||||||
icon={ChevronLeft}
|
icon={ChevronLeft}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onPreviousPage();
|
direction === "rtl" ? onNextPage() : onPreviousPage();
|
||||||
}}
|
}}
|
||||||
tooltip={t("reader.controls.previousPage")}
|
tooltip={t("reader.controls.previousPage")}
|
||||||
iconClassName="h-8 w-8"
|
iconClassName="h-8 w-8"
|
||||||
@@ -193,7 +193,7 @@ export const ControlButtons = ({
|
|||||||
icon={ChevronRight}
|
icon={ChevronRight}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onNextPage();
|
direction === "rtl" ? onPreviousPage() : onNextPage();
|
||||||
}}
|
}}
|
||||||
tooltip={t("reader.controls.nextPage")}
|
tooltip={t("reader.controls.nextPage")}
|
||||||
iconClassName="h-8 w-8"
|
iconClassName="h-8 w-8"
|
||||||
|
|||||||
@@ -1,31 +1,27 @@
|
|||||||
import { useCallback, useRef, useEffect } from "react";
|
import { useCallback, useRef, useEffect } from "react";
|
||||||
import { useReadingDirection } from "./useReadingDirection";
|
|
||||||
|
|
||||||
interface UseTouchNavigationProps {
|
interface UseTouchNavigationProps {
|
||||||
onPreviousPage: () => void;
|
onPreviousPage: () => void;
|
||||||
onNextPage: () => void;
|
onNextPage: () => void;
|
||||||
pswpRef: React.MutableRefObject<unknown>;
|
pswpRef: React.MutableRefObject<unknown>;
|
||||||
|
isRTL: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useTouchNavigation({
|
export function useTouchNavigation({
|
||||||
onPreviousPage,
|
onPreviousPage,
|
||||||
onNextPage,
|
onNextPage,
|
||||||
pswpRef,
|
pswpRef,
|
||||||
|
isRTL,
|
||||||
}: UseTouchNavigationProps) {
|
}: UseTouchNavigationProps) {
|
||||||
const { isRTL } = useReadingDirection();
|
|
||||||
const touchStartXRef = useRef<number | null>(null);
|
const touchStartXRef = useRef<number | null>(null);
|
||||||
const touchStartYRef = useRef<number | null>(null);
|
const touchStartYRef = useRef<number | null>(null);
|
||||||
const isPinchingRef = useRef(false);
|
const isPinchingRef = useRef(false);
|
||||||
|
|
||||||
// Helper pour vérifier si la page est zoomée (zoom natif du navigateur)
|
// Helper pour vérifier si la page est zoomée (zoom natif du navigateur)
|
||||||
const isZoomed = useCallback(() => {
|
const isZoomed = useCallback(() => {
|
||||||
// Utiliser visualViewport.scale pour détecter le zoom natif
|
|
||||||
// Si scale > 1, la page est zoomée
|
|
||||||
if (window.visualViewport) {
|
if (window.visualViewport) {
|
||||||
return window.visualViewport.scale > 1;
|
return window.visualViewport.scale > 1.05;
|
||||||
}
|
}
|
||||||
// Fallback pour les navigateurs qui ne supportent pas visualViewport
|
|
||||||
// Comparer la taille de la fenêtre avec la taille réelle
|
|
||||||
return window.innerWidth !== window.screen.width;
|
return window.innerWidth !== window.screen.width;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,10 @@ export class StripstreamAdapter {
|
|||||||
volume: book.volume ?? null,
|
volume: book.volume ?? null,
|
||||||
pageCount: book.page_count ?? 0,
|
pageCount: book.page_count ?? 0,
|
||||||
thumbnailUrl: `/api/stripstream/images/books/${book.id}/thumbnail`,
|
thumbnailUrl: `/api/stripstream/images/books/${book.id}/thumbnail`,
|
||||||
readProgress: {
|
readProgress:
|
||||||
|
book.reading_status === "unread" && !book.reading_current_page
|
||||||
|
? null
|
||||||
|
: {
|
||||||
page: book.reading_current_page ?? null,
|
page: book.reading_current_page ?? null,
|
||||||
completed: book.reading_status === "read",
|
completed: book.reading_status === "read",
|
||||||
lastReadAt: book.reading_last_read_at ?? null,
|
lastReadAt: book.reading_last_read_at ?? null,
|
||||||
@@ -52,7 +55,10 @@ export class StripstreamAdapter {
|
|||||||
volume: book.volume ?? null,
|
volume: book.volume ?? null,
|
||||||
pageCount: book.page_count ?? 0,
|
pageCount: book.page_count ?? 0,
|
||||||
thumbnailUrl: `/api/stripstream/images/books/${book.id}/thumbnail`,
|
thumbnailUrl: `/api/stripstream/images/books/${book.id}/thumbnail`,
|
||||||
readProgress: {
|
readProgress:
|
||||||
|
book.reading_status === "unread" && !book.reading_current_page
|
||||||
|
? null
|
||||||
|
: {
|
||||||
page: book.reading_current_page ?? null,
|
page: book.reading_current_page ?? null,
|
||||||
completed: book.reading_status === "read",
|
completed: book.reading_status === "read",
|
||||||
lastReadAt: book.reading_last_read_at ?? null,
|
lastReadAt: book.reading_last_read_at ?? null,
|
||||||
|
|||||||
@@ -222,11 +222,11 @@ export class StripstreamProvider implements IMediaProvider {
|
|||||||
|
|
||||||
async getHomeData(): Promise<HomeData> {
|
async getHomeData(): Promise<HomeData> {
|
||||||
const homeOpts = { revalidate: CACHE_TTL_MED, tags: [HOME_CACHE_TAG] };
|
const homeOpts = { revalidate: CACHE_TTL_MED, tags: [HOME_CACHE_TAG] };
|
||||||
const [ongoingBooksResult, ongoingSeriesResult, booksPage, libraries] = await Promise.allSettled([
|
const [ongoingBooksResult, ongoingSeriesResult, booksPage, latestSeriesResult] = await Promise.allSettled([
|
||||||
this.client.fetch<StripstreamBookItem[]>("books/ongoing", { limit: "20" }, homeOpts),
|
this.client.fetch<StripstreamBookItem[]>("books/ongoing", { limit: "20" }, homeOpts),
|
||||||
this.client.fetch<StripstreamSeriesItem[]>("series/ongoing", { limit: "10" }, homeOpts),
|
this.client.fetch<StripstreamSeriesItem[]>("series/ongoing", { limit: "10" }, homeOpts),
|
||||||
this.client.fetch<StripstreamBooksPage>("books", { limit: "10" }, homeOpts),
|
this.client.fetch<StripstreamBooksPage>("books", { sort: "latest", limit: "10" }, homeOpts),
|
||||||
this.client.fetch<StripstreamLibraryResponse[]>("libraries", undefined, { revalidate: CACHE_TTL_LONG, tags: [HOME_CACHE_TAG] }),
|
this.client.fetch<StripstreamSeriesPage>("series", { sort: "latest", limit: "10" }, homeOpts),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// /books/ongoing returns both currently reading and next unread per series
|
// /books/ongoing returns both currently reading and next unread per series
|
||||||
@@ -242,23 +242,9 @@ export class StripstreamProvider implements IMediaProvider {
|
|||||||
? booksPage.value.items.map(StripstreamAdapter.toNormalizedBook)
|
? booksPage.value.items.map(StripstreamAdapter.toNormalizedBook)
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
let latestSeries: NormalizedSeries[] = [];
|
const latestSeries = latestSeriesResult.status === "fulfilled"
|
||||||
if (libraries.status === "fulfilled" && libraries.value.length > 0) {
|
? latestSeriesResult.value.items.map(StripstreamAdapter.toNormalizedSeries)
|
||||||
const allSeriesResults = await Promise.allSettled(
|
: [];
|
||||||
libraries.value.map((lib) =>
|
|
||||||
this.client.fetch<StripstreamSeriesPage>(
|
|
||||||
`libraries/${lib.id}/series`,
|
|
||||||
{ limit: "10" },
|
|
||||||
homeOpts
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
latestSeries = allSeriesResults
|
|
||||||
.filter((r): r is PromiseFulfilledResult<StripstreamSeriesPage> => r.status === "fulfilled")
|
|
||||||
.flatMap((r) => r.value.items)
|
|
||||||
.map(StripstreamAdapter.toNormalizedSeries)
|
|
||||||
.slice(0, 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ongoing: ongoingSeries,
|
ongoing: ongoingSeries,
|
||||||
|
|||||||
@@ -118,11 +118,14 @@ body.no-pinch-zoom * {
|
|||||||
font-family: var(--font-ui);
|
font-family: var(--font-ui);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Empêche le zoom automatique iOS sur les inputs */
|
}
|
||||||
|
|
||||||
|
/* Empêche le zoom automatique iOS sur les inputs (hors @layer pour surcharger text-sm) */
|
||||||
|
@supports (-webkit-touch-callout: none) {
|
||||||
input,
|
input,
|
||||||
textarea,
|
textarea,
|
||||||
select {
|
select {
|
||||||
font-size: 16px;
|
font-size: 16px !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user