mirror of
https://github.com/moku-project/Moku.git
synced 2026-06-14 01:39:56 -05:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 17d739a1cd | |||
| 2867dc9612 |
@@ -58,6 +58,26 @@
|
|||||||
onTabDragStart, onTabDragOver, onTabDragLeave, onTabDrop, onTabDragEnd,
|
onTabDragStart, onTabDragOver, onTabDragLeave, onTabDrop, onTabDragEnd,
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
|
function onTabsWheel(e: WheelEvent) {
|
||||||
|
const ids = visibleTabIds.filter(id => id === "library" || id === "downloaded" || visibleCategories.some(c => String(c.id) === id));
|
||||||
|
const idx = ids.indexOf(tab);
|
||||||
|
if (e.deltaY > 0 && idx < ids.length - 1) onTabChange(ids[idx + 1]);
|
||||||
|
else if (e.deltaY < 0 && idx > 0) onTabChange(ids[idx - 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
tab;
|
||||||
|
if (!tabsEl) return;
|
||||||
|
const active = tabsEl.querySelector<HTMLElement>(".tab.active");
|
||||||
|
if (!active) return;
|
||||||
|
const pl = tabsEl.scrollLeft;
|
||||||
|
const cw = tabsEl.clientWidth;
|
||||||
|
const ol = active.offsetLeft;
|
||||||
|
const ow = active.offsetWidth;
|
||||||
|
if (ol < pl) tabsEl.scrollTo({ left: ol, behavior: "smooth" });
|
||||||
|
else if (ol + ow > pl + cw) tabsEl.scrollTo({ left: ol + ow - cw, behavior: "smooth" });
|
||||||
|
});
|
||||||
|
|
||||||
const SORT_LABELS: Record<LibrarySortMode, string> = {
|
const SORT_LABELS: Record<LibrarySortMode, string> = {
|
||||||
az: "A–Z",
|
az: "A–Z",
|
||||||
unreadCount: "Unread chapters",
|
unreadCount: "Unread chapters",
|
||||||
@@ -76,7 +96,7 @@
|
|||||||
<div class="header">
|
<div class="header">
|
||||||
<span class="heading">Library</span>
|
<span class="heading">Library</span>
|
||||||
|
|
||||||
<div class="tabs" class:tabs-anims={anims} bind:this={tabsEl}>
|
<div class="tabs" class:tabs-anims={anims} bind:this={tabsEl} onwheel={onTabsWheel}>
|
||||||
{#each visibleTabIds as id, idx}
|
{#each visibleTabIds as id, idx}
|
||||||
{@const cat = visibleCategories.find(c => String(c.id) === id)}
|
{@const cat = visibleCategories.find(c => String(c.id) === id)}
|
||||||
{#if id === "library" || id === "downloaded" || cat}
|
{#if id === "library" || id === "downloaded" || cat}
|
||||||
@@ -198,7 +218,7 @@
|
|||||||
.header { position: relative; z-index: 100; display: flex; align-items: center; gap: var(--sp-4); padding: var(--sp-4) var(--sp-6); border-bottom: 1px solid var(--border-dim); flex-shrink: 0; min-width: 0; }
|
.header { position: relative; z-index: 100; display: flex; align-items: center; gap: var(--sp-4); padding: var(--sp-4) var(--sp-6); border-bottom: 1px solid var(--border-dim); flex-shrink: 0; min-width: 0; }
|
||||||
.header-right { display: flex; align-items: center; gap: var(--sp-2); margin-left: auto; flex-shrink: 0; }
|
.header-right { display: flex; align-items: center; gap: var(--sp-2); margin-left: auto; flex-shrink: 0; }
|
||||||
.heading { font-family: var(--font-ui); font-size: var(--text-xs); color: var(--text-faint); letter-spacing: var(--tracking-wider); text-transform: uppercase; flex-shrink: 0; }
|
.heading { font-family: var(--font-ui); font-size: var(--text-xs); color: var(--text-faint); letter-spacing: var(--tracking-wider); text-transform: uppercase; flex-shrink: 0; }
|
||||||
.tabs { display: flex; align-items: center; gap: 2px; background: var(--bg-raised); border: 1px solid var(--border-dim); border-radius: var(--radius-md); padding: 2px; position: relative; flex-shrink: 1; min-width: 0; overflow-x: auto; scrollbar-width: none; }
|
.tabs { display: flex; align-items: center; gap: 2px; background: var(--bg-raised); border: 1px solid var(--border-dim); border-radius: var(--radius-md); padding: 2px; position: relative; flex-shrink: 1; min-width: 0; overflow-x: auto; scrollbar-width: none; overscroll-behavior-x: contain; }
|
||||||
.tabs::-webkit-scrollbar { display: none; }
|
.tabs::-webkit-scrollbar { display: none; }
|
||||||
.tab { position: relative; z-index: 1; display: flex; align-items: center; gap: 5px; font-family: var(--font-ui); font-size: var(--text-2xs); letter-spacing: var(--tracking-wide); text-transform: uppercase; padding: 4px 10px; border-radius: var(--radius-sm); border: 1px solid transparent; color: var(--text-faint); white-space: nowrap; transition: background var(--t-base), color var(--t-base), border-color var(--t-base); cursor: grab; flex-shrink: 0; }
|
.tab { position: relative; z-index: 1; display: flex; align-items: center; gap: 5px; font-family: var(--font-ui); font-size: var(--text-2xs); letter-spacing: var(--tracking-wide); text-transform: uppercase; padding: 4px 10px; border-radius: var(--radius-sm); border: 1px solid transparent; color: var(--text-faint); white-space: nowrap; transition: background var(--t-base), color var(--t-base), border-color var(--t-base); cursor: grab; flex-shrink: 0; }
|
||||||
.tab:hover { color: var(--text-muted); }
|
.tab:hover { color: var(--text-muted); }
|
||||||
|
|||||||
@@ -394,6 +394,7 @@
|
|||||||
class="{imgCls}{store.settings.pageGap ? ' strip-gap' : ''}"
|
class="{imgCls}{store.settings.pageGap ? ' strip-gap' : ''}"
|
||||||
loading="eager"
|
loading="eager"
|
||||||
decoding="async"
|
decoding="async"
|
||||||
|
draggable="false"
|
||||||
onload={(e) => {
|
onload={(e) => {
|
||||||
const img = e.currentTarget as HTMLImageElement;
|
const img = e.currentTarget as HTMLImageElement;
|
||||||
const slot = img.closest<HTMLElement>(".strip-slot");
|
const slot = img.closest<HTMLElement>(".strip-slot");
|
||||||
@@ -412,9 +413,9 @@
|
|||||||
{:else if style === "fade" && pageReady}
|
{:else if style === "fade" && pageReady}
|
||||||
<div class="inspect-wrap" style="transform:scale({readerState.inspectScale}) translate({readerState.inspectPanX / readerState.inspectScale}px,{readerState.inspectPanY / readerState.inspectScale}px)">
|
<div class="inspect-wrap" style="transform:scale({readerState.inspectScale}) translate({readerState.inspectPanX / readerState.inspectScale}px,{readerState.inspectPanY / readerState.inspectScale}px)">
|
||||||
{#await resolveUrl(store.pageUrls[store.pageNumber - 1], 999)}
|
{#await resolveUrl(store.pageUrls[store.pageNumber - 1], 999)}
|
||||||
<img src="" alt="Page {store.pageNumber}" class={imgCls} decoding="async" style="opacity:0" />
|
<img src="" alt="Page {store.pageNumber}" class={imgCls} decoding="async" style="opacity:0" draggable="false" />
|
||||||
{:then src}
|
{:then src}
|
||||||
<img {src} alt="Page {store.pageNumber}" class={imgCls} decoding="async" style="opacity: {fadingOut ? 0 : 1}; transition: opacity 0.1s ease;" />
|
<img {src} alt="Page {store.pageNumber}" class={imgCls} decoding="async" style="opacity: {fadingOut ? 0 : 1}; transition: opacity 0.1s ease;" draggable="false" />
|
||||||
{/await}
|
{/await}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -424,9 +425,9 @@
|
|||||||
<div class="double-wrap">
|
<div class="double-wrap">
|
||||||
{#each currentGroup as pg, i}
|
{#each currentGroup as pg, i}
|
||||||
{#await resolveUrl(store.pageUrls[pg - 1], 999)}
|
{#await resolveUrl(store.pageUrls[pg - 1], 999)}
|
||||||
<img src="" alt="Page {pg}" class="{imgCls} page-half {i === 0 ? 'gap-left' : 'gap-right'}" decoding="async" />
|
<img src="" alt="Page {pg}" class="{imgCls} page-half {i === 0 ? 'gap-left' : 'gap-right'}" decoding="async" draggable="false" />
|
||||||
{:then src}
|
{:then src}
|
||||||
<img {src} alt="Page {pg}" class="{imgCls} page-half {i === 0 ? 'gap-left' : 'gap-right'}" decoding="async" />
|
<img {src} alt="Page {pg}" class="{imgCls} page-half {i === 0 ? 'gap-left' : 'gap-right'}" decoding="async" draggable="false" />
|
||||||
{/await}
|
{/await}
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
@@ -438,9 +439,9 @@
|
|||||||
{:else if pageReady}
|
{:else if pageReady}
|
||||||
<div class="inspect-wrap" style="transform:scale({readerState.inspectScale}) translate({readerState.inspectPanX / readerState.inspectScale}px,{readerState.inspectPanY / readerState.inspectScale}px)">
|
<div class="inspect-wrap" style="transform:scale({readerState.inspectScale}) translate({readerState.inspectPanX / readerState.inspectScale}px,{readerState.inspectPanY / readerState.inspectScale}px)">
|
||||||
{#await resolveUrl(store.pageUrls[store.pageNumber - 1], 999)}
|
{#await resolveUrl(store.pageUrls[store.pageNumber - 1], 999)}
|
||||||
<img src="" alt="Page {store.pageNumber}" class={imgCls} decoding="async" />
|
<img src="" alt="Page {store.pageNumber}" class={imgCls} decoding="async" draggable="false" />
|
||||||
{:then src}
|
{:then src}
|
||||||
<img {src} alt="Page {store.pageNumber}" class={imgCls} decoding="async" />
|
<img {src} alt="Page {store.pageNumber}" class={imgCls} decoding="async" draggable="false" />
|
||||||
{/await}
|
{/await}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -118,6 +118,7 @@
|
|||||||
class:bar-left={barPosition === "left"}
|
class:bar-left={barPosition === "left"}
|
||||||
class:bar-right={barPosition === "right"}
|
class:bar-right={barPosition === "right"}
|
||||||
class:hidden={!uiVisible}
|
class:hidden={!uiVisible}
|
||||||
|
data-tauri-drag-region={barPosition === "top" ? true : undefined}
|
||||||
>
|
>
|
||||||
<div class="bar-start">
|
<div class="bar-start">
|
||||||
<button class="icon-btn" onclick={closeReader} title="Close reader"><X size={15} weight="light" /></button>
|
<button class="icon-btn" onclick={closeReader} title="Close reader"><X size={15} weight="light" /></button>
|
||||||
@@ -366,6 +367,7 @@
|
|||||||
z-index: 2;
|
z-index: 2;
|
||||||
transition: opacity 0.25s ease;
|
transition: opacity 0.25s ease;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
.bar.hidden { opacity: 0; pointer-events: none; }
|
.bar.hidden { opacity: 0; pointer-events: none; }
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user