Feat: TouchScreen Support for SeriesDetail & Modularity Revamp (#29

This commit is contained in:
Youwes09
2026-04-29 18:40:41 -05:00
parent 1bb7da3b22
commit 78573eacb1
8 changed files with 272 additions and 48 deletions
+20 -29
View File
@@ -13,6 +13,7 @@
import { sortLibrary } from "../lib/librarySort";
import { startLibraryUpdate } from "../lib/libraryUpdater";
import { createPaginator } from "@core/algorithms/paginate";
import { longPress } from "@core/ui/touchscreen";
import {
store, setCategories, setLibraryUpdates, addToast,
setTabSort, toggleTabSortDir, setTabStatus, toggleTabFilter, clearTabFilters,
@@ -197,34 +198,28 @@
function selectAll() { selectedIds = new Set(visibleManga.map(m => m.id)); }
function loadMore() { renderVisible = paginator.nextVisible(renderVisible); }
let longPressTimer: ReturnType<typeof setTimeout> | null = null;
let longPressFired = false;
let emptyLongPressTimer: ReturnType<typeof setTimeout> | null = null;
let cardLongPressFired = false;
function onRootPointerDown(e: PointerEvent) {
if (e.button !== 0) return;
if ((e.target as HTMLElement).closest("button, .card")) return;
emptyLongPressTimer = setTimeout(() => {
emptyLongPressTimer = null;
emptyCtx = { x: e.clientX - SIDEBAR_W, y: e.clientY - TITLEBAR_H };
}, 500);
function rootLongPressAction(node: HTMLElement) {
return longPress(node, {
onLongPress(e) {
if ((e.target as HTMLElement).closest("button, .card")) return;
emptyCtx = { x: e.clientX - SIDEBAR_W, y: e.clientY - TITLEBAR_H };
},
});
}
function onRootPointerUp() { if (emptyLongPressTimer) { clearTimeout(emptyLongPressTimer); emptyLongPressTimer = null; } }
function onRootPointerLeave() { if (emptyLongPressTimer) { clearTimeout(emptyLongPressTimer); emptyLongPressTimer = null; } }
function onCardPointerDown(e: PointerEvent, m: Manga) {
if (e.button !== 0) return;
longPressFired = false;
longPressTimer = setTimeout(() => {
longPressTimer = null;
longPressFired = true;
ctx = { x: e.clientX - SIDEBAR_W, y: e.clientY - TITLEBAR_H, manga: m };
}, 500);
function cardLongPress(node: HTMLElement, m: Manga) {
return longPress(node, {
onLongPress(e) {
cardLongPressFired = true;
ctx = { x: e.clientX - SIDEBAR_W, y: e.clientY - TITLEBAR_H, manga: m };
},
});
}
function onCardPointerUp() { if (longPressTimer) { clearTimeout(longPressTimer); longPressTimer = null; } }
function onCardPointerLeave() { if (longPressTimer) { clearTimeout(longPressTimer); longPressTimer = null; } }
function onCardClick(e: MouseEvent, m: Manga) {
if (longPressFired) { longPressFired = false; return; }
if (cardLongPressFired) { cardLongPressFired = false; return; }
if (selectMode) { toggleSelect(m.id); return; }
if (e.metaKey || e.ctrlKey || e.shiftKey) { e.preventDefault(); enterSelectMode(m.id); return; }
store.activeManga = m;
@@ -523,14 +518,12 @@
class="root"
role="presentation"
bind:this={scrollEl}
use:rootLongPressAction
oncontextmenu={(e) => {
if ((e.target as HTMLElement).closest("button")) return;
e.preventDefault();
emptyCtx = { x: e.clientX - SIDEBAR_W, y: e.clientY - TITLEBAR_H };
}}
onpointerdown={onRootPointerDown}
onpointerup={onRootPointerUp}
onpointerleave={onRootPointerLeave}
>
{#if store.settings.libraryBranches ?? true}
<svg class="branches" viewBox="0 0 400 600" preserveAspectRatio="xMaxYMid slice" aria-hidden="true">
@@ -624,9 +617,7 @@
libraryFilter={tab}
onCardClick={onCardClick}
onCardContextMenu={openCtx}
onCardPointerDown={onCardPointerDown}
onCardPointerUp={onCardPointerUp}
onCardPointerLeave={onCardPointerLeave}
onCardLongPress={cardLongPress}
onLoadMore={loadMore}
onRetry={() => retryCount++}
onExitSelectMode={exitSelectMode}
@@ -2,6 +2,7 @@
import { Folder, Trash, CheckSquare, Robot } from "phosphor-svelte";
import Thumbnail from "@shared/manga/Thumbnail.svelte";
import { resolvedCover } from "@core/cover/coverResolver";
import { longPress } from "@core/ui/touchscreen";
import type { Manga, Category } from "@types";
interface Props {
@@ -22,9 +23,7 @@
visibleCategories: Category[];
onCardClick: (e: MouseEvent, m: Manga) => void;
onCardContextMenu: (e: MouseEvent, m: Manga) => void;
onCardPointerDown: (e: PointerEvent, m: Manga) => void;
onCardPointerUp: () => void;
onCardPointerLeave: () => void;
onCardLongPress: (node: HTMLElement, m: Manga) => ReturnType<typeof longPress>;
onLoadMore: () => void;
onRetry: () => void;
onExitSelectMode: () => void;
@@ -38,7 +37,7 @@
visibleManga, filtered, loading, cols, anims, selectMode, selectedIds,
hasMore, remainingCount, renderLimit, cropCovers, statsAlways, libraryFilter,
bulkWorking, visibleCategories,
onCardClick, onCardContextMenu, onCardPointerDown, onCardPointerUp, onCardPointerLeave,
onCardClick, onCardContextMenu, onCardLongPress,
onLoadMore, onRetry, onExitSelectMode, onSelectAll, onBulkMove, onBulkRemove, onBulkAutomate,
}: Props = $props();
@@ -122,11 +121,9 @@
class:card-selected={isSelected}
class:select-mode={selectMode}
class:anims={anims}
use:onCardLongPress={m}
onclick={(e) => onCardClick(e, m)}
oncontextmenu={(e) => onCardContextMenu(e, m)}
onpointerdown={(e) => onCardPointerDown(e, m)}
onpointerup={onCardPointerUp}
onpointerleave={onCardPointerLeave}
>
<div class="cover-wrap" class:completed={isCompleted}>
<Thumbnail src={resolvedCover(m.id, m.thumbnailUrl)} alt={m.title} class="cover" style="object-fit:{cropCovers ? 'cover' : 'contain'}" draggable="false" />