Skip to content

Commit

Permalink
History
Browse files Browse the repository at this point in the history
  • Loading branch information
Robonau authored and Robonau committed Jun 25, 2024
1 parent e98d5f8 commit 8836939
Show file tree
Hide file tree
Showing 10 changed files with 509 additions and 174 deletions.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -137,5 +137,6 @@
"codeium.enableInComments": false,
"codeium.enableSearch": true,
"tailwind-fold.unfoldIfLineSelected": true,
"tailwind-fold.foldLengthThreshold": 5
"tailwind-fold.foldLengthThreshold": 80,
"tailwind-fold.unfoldedTextOpacity": 1
}
23 changes: 23 additions & 0 deletions src/lib/AppNavData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ export const AppNavData = [
return page.includes('/browse');
}
},
{
href: '/history',
title: 'History',
icon: 'mdi:history',
match: (page: string) => {
return page.includes('/history');
}
},
{
href: '/downloads',
title: 'Downloads',
Expand All @@ -46,3 +54,18 @@ export const AppNavData = [
}
}
];

export const SmallAppNavData = [
AppNavData[0],
AppNavData[1],
AppNavData[2],
AppNavData[3],
{
href: '/more',
title: 'More',
icon: 'mdi:dots-horizontal',
match: (page: string) => {
return page.includes('/more');
}
}
];
37 changes: 21 additions & 16 deletions src/lib/components/MobileAppNavigation.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
import { page } from '$app/stores';
import { TabGroup, TabAnchor } from '@skeletonlabs/skeleton';
import IconWrapper from './IconWrapper.svelte';
import { AppNavData } from '../AppNavData';
import { AppNavData, SmallAppNavData } from '../AppNavData';
import { screens } from '$lib/screens';
import MediaQuery from './MediaQuery.svelte';
</script>

<TabGroup
Expand All @@ -23,19 +25,22 @@
class="bg-surface-100-800-token w-full"
regionList=" h-16"
>
{#each AppNavData as Loc}
<TabAnchor
href={Loc.href}
selected={Loc.match($page.url.pathname)}
class="h-full [&>.tab-interface]:h-full [&>div>.tab-label]:h-full"
>
<div class="flex h-full w-full flex-col items-center">
<IconWrapper
name={Loc.icon}
class="aspect-square max-h-full w-full grow"
/>
<span class="text-sm">{Loc.title}</span>
</div>
</TabAnchor>
{/each}
<MediaQuery query="(max-width: {screens.sm})" let:matches>
{@const Nav = matches ? SmallAppNavData : AppNavData}
{#each Nav as Loc}
<TabAnchor
href={Loc.href}
selected={Loc.match($page.url.pathname)}
class="h-full [&>.tab-interface]:h-full [&>div>.tab-label]:h-full"
>
<div class="flex h-full w-full flex-col items-center">
<IconWrapper
name={Loc.icon}
class="aspect-square max-h-full w-full grow"
/>
<span class="text-sm">{Loc.title}</span>
</div>
</TabAnchor>
{/each}
</MediaQuery>
</TabGroup>
29 changes: 29 additions & 0 deletions src/lib/gql/Queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -495,3 +495,32 @@ export const categoryMangaNotInLibrary = graphql(
`,
[]
);

export const History = graphql(`
query History($offset: Int = 0) {
chapters(
condition: { isRead: true }
orderByType: DESC
orderBy: LAST_READ_AT
first: 100
offset: $offset
) {
totalCount
nodes {
id
name
lastReadAt
isDownloaded
isBookmarked
manga {
thumbnailUrl
id
title
}
}
pageInfo {
hasNextPage
}
}
}
`);
28 changes: 28 additions & 0 deletions src/lib/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -298,3 +298,31 @@ type hasEnumVals = {
function isENUM(enu: unknown): enu is hasEnumVals {
return enu instanceof Object && 'enumValues' in enu;
}

export function formatDate(date: Date) {
const diff = new Date().valueOf() - date.valueOf(); // the difference in milliseconds
const msPerMinute = 60 * 1000;
const msPerHour = msPerMinute * 60;
const msPerDay = msPerHour * 24;
const msPerMonth = msPerDay * 30;
const msPerYear = msPerDay * 365;

if (date.toDateString() === new Date().toDateString()) {
return date.toLocaleTimeString();
}
if (diff < msPerMinute) {
return Math.round(diff / 1000) + ' seconds ago';
} else if (diff < msPerHour) {
return Math.round(diff / msPerMinute) + ' minutes ago';
} else if (diff < msPerDay) {
return Math.round(diff / msPerHour) + ' hours ago';
} else if (diff < msPerMonth) {
return Math.round(diff / msPerDay) + ' days ago';
} else if (diff < msPerYear) {
return Math.round(diff / msPerMonth) + ' months ago';
} else {
return Math.round(diff / msPerYear) + ' years ago';
}

return date.toLocaleString();
}
178 changes: 178 additions & 0 deletions src/routes/(app)/history/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
<!--
Copyright (c) 2024 Contributors to the Suwayomi project
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at https://mozilla.org/MPL/2.0/.
-->

<script lang="ts">
import { AppBarData } from '$lib/MountTitleAction';
import { getContextClient, queryStore } from '@urql/svelte';
import { History } from '$lib/gql/Queries';
import { writable } from 'svelte/store';
import type { ResultOf } from '$lib/gql/graphql';
import { formatDate, gridValues, HelpDoSelect } from '$lib/util';
import MangaCard from '$lib/components/MangaCard.svelte';
import IntersectionObserver from '$lib/components/IntersectionObserver.svelte';
import IconWrapper from '$lib/components/IconWrapper.svelte';
import { display, Meta } from '$lib/simpleStores';
AppBarData('History');
const client = getContextClient();
let page = writable(0);
let all = writable<ResultOf<typeof History>['chapters'] | null>(null);
$: CurrentHistory = queryStore({
client,
query: History,
variables: { offset: $page }
});
$: $CurrentHistory, updateAll();
function updateAll() {
if (!$CurrentHistory.data?.chapters) return;
if (!$all) {
$all = structuredClone($CurrentHistory.data.chapters);
return;
}
$all.nodes.push(...$CurrentHistory.data.chapters.nodes);
$all.pageInfo = $CurrentHistory.data.chapters.pageInfo;
}
</script>

{#if !$all && $CurrentHistory.fetching}
<div class="grid {gridValues} m-2 gap-2">
{#each new Array(110) as _}
<div class="aspect-cover w-full">
<div
class="placeholder h-full animate-pulse
{$Meta.Display === display.Compact && 'rounded-lg'}
{$Meta.Display === display.Comfortable && 'rounded-none rounded-t-lg'}"
/>
{#if $Meta.Display === display.Comfortable}
<div
class="placeholder h-12 animate-pulse rounded-none rounded-b-lg px-2 text-center"
/>
{/if}
</div>
{/each}
</div>
{:else if !$all && $CurrentHistory.error}
<div class="white-space-pre-wrap">
{JSON.stringify($CurrentHistory.error, null, 4)}
</div>
{:else if $all?.nodes}
<div class="grid {gridValues} m-2 gap-2">
{#each $all.nodes as updat}
<IntersectionObserver
let:intersecting
root={document.querySelector('#page') ?? undefined}
top={400}
bottom={400}
class="aspect-cover w-full"
>
{#if intersecting}
<a
href="/manga/{updat.manga.id}#{updat.id}"
class="h-full cursor-pointer hover:opacity-70"
tabindex="-1"
>
<MangaCard
thumbnailUrl={updat.manga.thumbnailUrl ?? ''}
title={updat.manga.title}
titleA="{updat.isDownloaded ? 'Downloaded' : ''}
{updat.isBookmarked ? 'Bookmarked' : ''}"
rounded="{$Meta.Display === display.Compact && 'rounded-lg'}
{$Meta.Display === display.Comfortable && 'rounded-none rounded-t-lg'}"
>
{#if $Meta.Display === display.Compact}
<div
class="variant-glass absolute bottom-0 left-0 right-0 rounded-b-olg"
>
<div
class="line-clamp-1 h-6 px-2 text-center"
title={updat.manga.title}
>
{updat.manga.title}
</div>
<div
class="line-clamp-1 h-6 px-2 text-center"
title={updat.name}
>
{updat.name}
</div>
<div
class="line-clamp-1 h-6 px-2 text-center"
title={new Date(
parseInt(updat.lastReadAt) * 1000
).toLocaleString()}
>
{formatDate(new Date(parseInt(updat.lastReadAt) * 1000))}
</div>
</div>
{/if}
<div class="absolute left-2 top-2 flex h-8">
{#if updat.isDownloaded}
<IconWrapper class="h-full w-full" name="mdi:download" />
{/if}
{#if updat.isBookmarked}
<IconWrapper class="h-full w-full" name="mdi:bookmark" />
{/if}
</div>
</MangaCard>
{#if $Meta.Display === display.Comfortable}
<div class="variant-glass-surface rounded-b-lg">
<div
class="line-clamp-1 h-6 px-2 text-center"
title={updat.manga.title}
>
{updat.manga.title}
</div>
<div
class="line-clamp-1 h-6 px-2 text-center"
title={updat.name}
>
{updat.name}
</div>
<div
class="line-clamp-1 h-6 px-2 text-center"
title={new Date(
parseInt(updat.lastReadAt) * 1000
).toLocaleString()}
>
{new Date(parseInt(updat.lastReadAt) * 1000).toLocaleString()}
</div>
</div>
{/if}
</a>
{/if}
</IntersectionObserver>
{/each}
{#if !$CurrentHistory.fetching && $all.pageInfo.hasNextPage}
<IntersectionObserver
root={document.querySelector('#page') ?? undefined}
top={400}
bottom={400}
on:intersect={(e) => {
if (e.detail) $page = $all?.nodes.length ?? 0;
}}
/>
{/if}
{#if $CurrentHistory.fetching && $all.pageInfo.hasNextPage}
{#each new Array(10) as _}
<div class="aspect-cover w-full">
<div
class="placeholder h-full animate-pulse
{$Meta.Display === display.Compact && 'rounded-lg'}
{$Meta.Display === display.Comfortable && 'rounded-none rounded-t-lg'}"
/>
{#if $Meta.Display === display.Comfortable}
<div
class="placeholder h-12 animate-pulse rounded-none rounded-b-lg px-2 text-center"
/>
{/if}
</div>
{/each}
{/if}
</div>
{/if}
Loading

0 comments on commit 8836939

Please sign in to comment.