casing i fe. detail embryo

This commit is contained in:
2025-09-17 15:38:29 +02:00
parent 6efa8aba8a
commit 910f500460
14 changed files with 238 additions and 48 deletions

View File

@@ -4,6 +4,7 @@ import Home from "./components/Home";
import BookList from "./components/BookList";
import AuthorList from "./components/AuthorList";
import SeriesList from "./components/SeriesList";
import BookDetail from "./components/BookDetail";
const App: Component = () => {
return (
@@ -48,6 +49,7 @@ const App: Component = () => {
<main class="container">
<Router>
<Route path="/books/author/:authorid" component={BookList} />
<Route path="/books/:id" component={BookDetail} />
<Route path="/books" component={BookList} />
<Route path="/authors" component={AuthorList} />
<Route path="/series" component={SeriesList} />

View File

@@ -1,17 +1,22 @@
import { Show, type Component } from "solid-js";
import { author } from "../types/types";
import { Author } from "../types/types";
import { Card } from "solid-bootstrap";
const AuthorCard: Component<{ author: author }> = (props: {
author: author;
const AuthorCard: Component<{ author: Author }> = (props: {
author: Author;
}) => {
return (
<Card class="book-card col-lg-2 col-md-3 col-sm-4">
<Card.Img
variant="top"
class="padding-1"
src={"/api/bibblan/authorcover/" + encodeURIComponent(props.author.id)}
/>
<div class="card-img-top missing">?</div>
<div
class="card-img-top author"
style={{
"background-image":
'url("/api/bibblan/authorcover/' +
encodeURIComponent(props.author.id) +
'")',
}}
></div>
<Card.Body>
<Card.Title>{props.author.name}</Card.Title>
<Card.Subtitle>

View File

@@ -1,10 +1,10 @@
import { createSignal, onMount, For, type Component } from "solid-js";
import AuthorCard from "./AuthorCard";
import BibblanService from "../services/bibblanservice";
import { author } from "../types/types";
import { Author } from "../types/types";
const BookList: Component = () => {
const [authors, setAuthors] = createSignal<author[]>([]);
const [authors, setAuthors] = createSignal<Author[]>([]);
const [query, setQuery] = createSignal("");
const update = (query: string) => {

View File

@@ -1,16 +1,27 @@
import { Show, type Component } from "solid-js";
import { book } from "../types/types";
import { Card } from "solid-bootstrap";
import { Show, createSignal, type Component } from "solid-js";
import { Book } from "../types/types";
import { Card, Modal } from "solid-bootstrap";
const BookCard: Component<{ book: Book }> = (props: { book: Book }) => {
const [show, setShow] = createSignal(false);
function handleClick(): void {
setShow(true);
}
const BookCard: Component<{ book: book }> = (props: { book: book }) => {
return (
<Card class="book-card col-lg-2 col-md-3 col-sm-4">
<div class="card-img-top missing">?</div>
<Show when={true || props.book.hasCover}>
<Card.Img
variant="top"
class="padding-1"
src={"/api/calibre/cover?path=" + encodeURIComponent(props.book.path)}
/>
<div
class="card-img-top"
style={{
"background-image":
'url("/api/calibre/cover?path=' +
encodeURIComponent(props.book.path) +
'")',
}}
></div>
</Show>
<Card.Body>
<Card.Title>{props.book.title}</Card.Title>
@@ -18,6 +29,7 @@ const BookCard: Component<{ book: book }> = (props: { book: book }) => {
<Card.Text>
Series: {props.book.seriesName} {props.book.seriesNumber}
</Card.Text>
<a href={`/books/${props.book.id}`} class="stretched-link"></a>
</Card.Body>
</Card>
);

View File

@@ -0,0 +1,31 @@
import { createSignal, onMount, For, Show, type Component } from "solid-js";
import { useParams } from "@solidjs/router";
import BibblanService from "../services/bibblanservice";
import { type BookDetail as BD } from "../types/types";
const BookDetail: Component = () => {
const [detail, setDetail] = createSignal<BD>({} as BD);
const params = useParams();
onMount(() => {
BibblanService.getBook(params.id).then((b) => {
console.log("detail", b);
setDetail(b);
});
});
return (
<>
<h1>Book detail - {detail()?.book?.title}</h1>
<Show when={detail()?.book?.id !== undefined}>
<br />
<pre>
<code>{JSON.stringify(detail(), null, 2)}</code>
</pre>
<br />
</Show>
</>
);
};
export default BookDetail;

View File

@@ -2,10 +2,10 @@ import { createSignal, onMount, For, Show, type Component } from "solid-js";
import { useParams } from "@solidjs/router";
import BookCard from "./BookCard";
import BibblanService from "../services/bibblanservice";
import { book } from "../types/types";
import { Book } from "../types/types";
const BookList: Component = () => {
const [books, setBooks] = createSignal<book[]>([]);
const [books, setBooks] = createSignal<Book[]>([]);
const [query, setQuery] = createSignal("");
const params = useParams();

View File

@@ -1,12 +1,11 @@
import { createSignal, onMount, For, type Component, Show } from "solid-js";
import BookCard from "./BookCard";
import BibblanService from "../services/bibblanservice";
import { series, listBook, book } from "../types/types";
import { Series, ListBook, Book } from "../types/types";
import { OverlayTrigger, Popover, Button } from "solid-bootstrap";
const SeriesList: Component = () => {
const [series, setSeries] = createSignal<series[]>([]);
const [series, setSeries] = createSignal<Series[]>([]);
const [query, setQuery] = createSignal("");
const [selected, setSelected] = createSignal<number>(-1);
const update = (query: string) => {
setQuery(query);
@@ -18,7 +17,7 @@ const SeriesList: Component = () => {
});
const distinctAuthors = (
books: listBook[]
books: ListBook[]
): { id: number; name: string }[] => {
return [
...new Set(
@@ -27,21 +26,21 @@ const SeriesList: Component = () => {
].map((x) => JSON.parse(x)) as { id: number; name: string }[];
};
const minDate = (books: listBook[]): string => {
const minDate = (books: ListBook[]): string => {
if (books.length === 0) return "";
const darr = books.map((b) => new Date(b.pubDate));
const d = darr.reduce((min, b) => (b < min ? b : min), darr[0]);
return d?.toISOString().substr(0, 10) ?? "";
};
const maxDate = (books: listBook[]): string => {
const maxDate = (books: ListBook[]): string => {
if (books.length === 0) return "";
const darr = books.map((b) => new Date(b.pubDate));
const d = darr.reduce((max, b) => (b > max ? b : max), darr[0]);
return d?.toISOString().substr(0, 10) ?? "";
};
function extendBook(item: listBook, seriesName: string): book {
function extendBook(item: ListBook, seriesName: string): Book {
return {
...item,
@@ -57,7 +56,7 @@ const SeriesList: Component = () => {
};
}
function orderBySeriesIndex(books: listBook[]): listBook[] {
function orderBySeriesIndex(books: ListBook[]): ListBook[] {
return [...books].sort((a, b) => {
const indexA = a.seriesIndex ?? 0;
const indexB = b.seriesIndex ?? 0;

View File

@@ -1,10 +1,10 @@
import { book, author } from "../types/types";
import { Book, Author, Series, BookDetail } from "../types/types";
const BibblanService = {
getBooks: async (
authorid: string | undefined = undefined,
query: string | undefined = undefined
): Promise<book[]> => {
): Promise<Book[]> => {
let url = "/api/bibblan/books";
if (authorid != undefined) {
url += `/author/${authorid}`;
@@ -15,9 +15,15 @@ const BibblanService = {
return response.json();
},
getBook: async (id: string): Promise<BookDetail> => {
let url = "/api/bibblan/books/" + id;
const response = await fetch(url);
return response.json();
},
getAuthors: async (
query: string | undefined = undefined
): Promise<author[]> => {
): Promise<Author[]> => {
let url = "/api/bibblan/authors";
if (query != undefined && query.length > 0) {
url += `?query=${encodeURIComponent(query)}`;
@@ -28,7 +34,7 @@ const BibblanService = {
getSeries: async (
query: string | undefined = undefined
): Promise<author[]> => {
): Promise<Series[]> => {
let url = "/api/bibblan/series";
if (query != undefined && query.length > 0) {
url += `?query=${encodeURIComponent(query)}`;

View File

@@ -1,7 +1,7 @@
import { book } from "../types/types";
import { Book } from "../types/types";
const CalibreService = {
getBooks: async (): Promise<book[]> => {
getBooks: async (): Promise<Book[]> => {
const response = await fetch("/api/calibre/books");
return response.json();
},

View File

@@ -3,7 +3,27 @@
.book-card {
.card-img-top {
padding: 1rem;
aspect-ratio: 3 / 4;
aspect-ratio: 30 / 47;
background-size: contain;
width: calc(100% - 1.6rem);
margin-left: 0.8rem;
margin-top: 0.8rem;
background-repeat: no-repeat;
background-position: 0 0;
z-index: 1;
&.missing {
position: absolute;
display: block;
font-size: 7rem;
font-weight: 900;
color: #ccc;
text-align: center;
opacity: 0.8;
z-index: 0;
}
/* &.author { } */
}
}

View File

@@ -1,4 +1,4 @@
interface book {
export interface Book {
id: number;
title: string;
authorId: number;
@@ -12,13 +12,13 @@ interface book {
seriesNumber: number;
}
interface author {
export interface Author {
id: number;
name: string;
bookCount: number;
}
interface listBook {
export interface ListBook {
id: number;
title: string;
authorId: number;
@@ -29,10 +29,58 @@ interface listBook {
seriesIndex: number;
}
interface series {
export interface Series {
id: number;
name: string;
books: Array<listBook>;
books: Array<ListBook>;
}
export type { book, author, series, listBook };
export interface Publisher {
id: number;
name: string;
sort: string;
}
export interface Tag {
id: number;
name: string;
}
export interface Rating {
id: number;
book: number;
rating: number;
}
export interface Language {
id: number;
book: number;
langCode: number;
itemOrder: number;
}
export interface BookComment {
id: number;
book: number;
text: string;
}
export interface BookData {
id: number;
book: number;
format: string;
uncompressedSize: number;
name: string;
}
export interface BookDetail {
book: Book;
authors: Author[];
publishers: Publisher[];
language: Language[];
ratings: any[];
series: Author[];
tags: Tag[];
comments: BookComment[];
data: BookData[];
}