Rework #6

Merged
monoid merged 38 commits from dev into main 2024-04-17 01:45:37 +09:00
7 changed files with 78 additions and 50 deletions
Showing only changes of commit 37d49069e5 - Show all commits

View File

@ -21,6 +21,7 @@ import Layout from "./components/layout/layout";
import NotFoundPage from "./page/404"; import NotFoundPage from "./page/404";
import LoginPage from "./page/loginPage"; import LoginPage from "./page/loginPage";
import ProfilePage from "./page/profilesPage"; import ProfilePage from "./page/profilesPage";
import ContentInfoPage from "./page/contentInfoPage";
const App = () => { const App = () => {
return ( return (
@ -31,7 +32,8 @@ const App = () => {
<Route path="/search" component={Gallery} /> <Route path="/search" component={Gallery} />
<Route path="/login" component={LoginPage} /> <Route path="/login" component={LoginPage} />
<Route path="/profile" component={ProfilePage}/> <Route path="/profile" component={ProfilePage}/>
{/* <Route path="/doc/:id" component={<DocumentAbout />}></Route> <Route path="/doc/:id" component={ContentInfoPage}/>
{/*
<Route path="/doc/:id/reader" component={<ReaderPage />}></Route> <Route path="/doc/:id/reader" component={<ReaderPage />}></Route>
<Route path="/difference" component={<DifferencePage />}></Route> <Route path="/difference" component={<DifferencePage />}></Route>
<Route path="/setting" component={<SettingPage />}></Route> <Route path="/setting" component={<SettingPage />}></Route>

View File

@ -2,6 +2,8 @@ import type { Document } from "dbtype/api";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import TagBadge from "@/components/gallery/TagBadge"; import TagBadge from "@/components/gallery/TagBadge";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { Link, useLocation } from "wouter";
import { LazyImage } from "./LazyImage";
function clipTagsWhenOverflow(tags: string[], limit: number) { function clipTagsWhenOverflow(tags: string[], limit: number) {
let l = 0; let l = 0;
@ -15,48 +17,12 @@ function clipTagsWhenOverflow(tags: string[], limit: number) {
return tags; return tags;
} }
function LazyImage({ src, alt, className }: { src: string; alt: string; className?: string; }) {
const ref = useRef<HTMLImageElement>(null);
const [loaded, setLoaded] = useState(false);
useEffect(() => {
if (ref.current) {
let toggle = false;
const observer = new IntersectionObserver((entries) => {
if (entries.some(x => x.isIntersecting)) {
setLoaded(true);
toggle = !toggle;
ref.current?.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 500, easing: "ease-in-out" });
observer.disconnect();
}
else {
if (toggle) {
console.log("fade out");
ref.current?.animate([{ opacity: 1 }, { opacity: 0 }], { duration: 500, easing: "ease-in-out" });
}
}
});
observer.observe(ref.current);
return () => {
observer.disconnect();
};
}
}, []);
return <img
ref={ref}
src={loaded ? src : undefined}
alt={alt}
className={className}
loading="lazy"
/>;
}
export function GalleryCard({ export function GalleryCard({
doc: x doc: x
}: { doc: Document; }) { }: { doc: Document; }) {
const ref = useRef<HTMLDivElement>(null); const ref = useRef<HTMLDivElement>(null);
const [clipCharCount, setClipCharCount] = useState(200); const [clipCharCount, setClipCharCount] = useState(200);
const [location] = useLocation();
const artists = x.tags.filter(x => x.startsWith("artist:")).map(x => x.replace("artist:", "")); const artists = x.tags.filter(x => x.startsWith("artist:")).map(x => x.replace("artist:", ""));
const groups = x.tags.filter(x => x.startsWith("group:")).map(x => x.replace("group:", "")); const groups = x.tags.filter(x => x.startsWith("group:")).map(x => x.replace("group:", ""));
@ -84,11 +50,13 @@ export function GalleryCard({
<LazyImage src={`/api/doc/${x.id}/comic/thumbnail`} <LazyImage src={`/api/doc/${x.id}/comic/thumbnail`}
alt={x.title} alt={x.title}
className="max-h-full max-w-full object-cover object-center" className="max-h-full max-w-full object-cover object-center"
/> />
</div> </div>
<div className="flex-1 flex flex-col"> <div className="flex-1 flex flex-col">
<CardHeader className="flex-none"> <CardHeader className="flex-none">
<CardTitle>{x.title}</CardTitle> <CardTitle>
<Link href={`/doc/${x.id}`} state={{fromUrl: location}}>{x.title}</Link>
</CardTitle>
<CardDescription> <CardDescription>
{artists.join(", ")} {groups.length > 0 && "|"} {groups.join(", ")} {artists.join(", ")} {groups.length > 0 && "|"} {groups.join(", ")}
</CardDescription> </CardDescription>

View File

@ -0,0 +1,37 @@
import { useEffect, useRef, useState } from "react";
export function LazyImage({ src, alt, className }: { src: string; alt: string; className?: string; }) {
const ref = useRef<HTMLImageElement>(null);
const [loaded, setLoaded] = useState(false);
useEffect(() => {
if (ref.current) {
let toggle = false;
const observer = new IntersectionObserver((entries) => {
if (entries.some(x => x.isIntersecting)) {
setLoaded(true);
toggle = !toggle;
ref.current?.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 500, easing: "ease-in-out" });
observer.disconnect();
}
else {
if (toggle) {
console.log("fade out");
ref.current?.animate([{ opacity: 1 }, { opacity: 0 }], { duration: 500, easing: "ease-in-out" });
}
}
});
observer.observe(ref.current);
return () => {
observer.disconnect();
};
}
}, []);
return <img
ref={ref}
src={loaded ? src : undefined}
alt={alt}
className={className}
loading="lazy" />;
}

View File

@ -0,0 +1,20 @@
export interface ContentInfoPageProps {
params: {
id: string;
};
}
export function ContentInfoPage({params}: ContentInfoPageProps) {
return (
<div className="p-4">
<h1>ContentInfoPage</h1>
{params.id}
<p>Find me in packages/client/src/page/contentInfoPage.tsx</p>
</div>
);
}
export default ContentInfoPage;

View File

@ -1,4 +1,4 @@
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { useLogin } from "@/state/user"; import { useLogin } from "@/state/user";
import { Redirect } from "wouter"; import { Redirect } from "wouter";
@ -9,7 +9,7 @@ export function ProfilePage() {
return <Redirect to="/login" />; return <Redirect to="/login" />;
} }
return ( return (
<div className="m-4"> <div className="p-4">
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle className="text-2xl">Profile</CardTitle> <CardTitle className="text-2xl">Profile</CardTitle>

View File

@ -1,11 +1,11 @@
import { type Context, DefaultContext, DefaultState, Next } from "koa"; import type { Context } from "koa";
import Router from "koa-router"; import Router from "koa-router";
import { createReadableStreamFromZip, entriesByNaturalOrder, readZip } from "../util/zipwrap"; import { createReadableStreamFromZip, entriesByNaturalOrder, readZip } from "../util/zipwrap";
import type { ContentContext } from "./context"; import type { ContentContext } from "./context";
import { since_last_modified } from "./util"; import { since_last_modified } from "./util";
import type { ZipReader } from "@zip.js/zip.js"; import type { ZipReader } from "@zip.js/zip.js";
import type { FileHandle } from "node:fs/promises"; import type { FileHandle } from "node:fs/promises";
import { Readable, Writable } from "node:stream"; import { Readable } from "node:stream";
/** /**
* zip stream cache. * zip stream cache.
@ -91,11 +91,15 @@ async function renderZipImage(ctx: Context, path: string, page: number) {
}, },
close() { close() {
nodeReadableStream.push(null); nodeReadableStream.push(null);
releaseZip(path);
// setTimeout(() => {
// }, 500);
}, },
})); }));
nodeReadableStream.on("error", (err) => {
console.error(err);
releaseZip(path);
});
nodeReadableStream.on("close", () => {
releaseZip(path);
});
ctx.body = nodeReadableStream; ctx.body = nodeReadableStream;
ctx.response.length = entry.uncompressedSize; ctx.response.length = entry.uncompressedSize;

View File

@ -4,15 +4,12 @@ import { ZipReader, Reader, type Entry } from "@zip.js/zip.js";
class FileReader extends Reader<FileHandle> { class FileReader extends Reader<FileHandle> {
private fd: FileHandle; private fd: FileHandle;
private offset: number;
constructor(fd: FileHandle) { constructor(fd: FileHandle) {
super(fd); super(fd);
this.fd = fd; this.fd = fd;
this.offset = 0;
} }
async init(): Promise<void> { async init(): Promise<void> {
this.offset = 0;
this.size = (await this.fd.stat()).size; this.size = (await this.fd.stat()).size;
} }
close(): void { close(): void {