blandat.. fixar o nullcheckar. filtreringar
This commit is contained in:
@@ -5,7 +5,7 @@ import { Card } from "solid-bootstrap";
|
||||
const BookCard: Component<{ book: book }> = (props: { book: book }) => {
|
||||
return (
|
||||
<Card class="book-card col-lg-2 col-md-3 col-sm-4">
|
||||
<Show when={props.book.hasCover}>
|
||||
<Show when={true || props.book.hasCover}>
|
||||
<Card.Img
|
||||
variant="top"
|
||||
class="padding-1"
|
||||
|
@@ -1,11 +1,12 @@
|
||||
import { createSignal, onMount, For, type Component } from "solid-js";
|
||||
import AuthorCard from "./AuthorCard";
|
||||
import { createSignal, onMount, For, type Component, Show } from "solid-js";
|
||||
import BookCard from "./BookCard";
|
||||
import BibblanService from "../services/bibblanservice";
|
||||
import { series, listBook } 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 [query, setQuery] = createSignal("");
|
||||
const [selected, setSelected] = createSignal<number>(-1);
|
||||
|
||||
const update = (query: string) => {
|
||||
setQuery(query);
|
||||
@@ -40,6 +41,30 @@ const SeriesList: Component = () => {
|
||||
return d?.toISOString().substr(0, 10) ?? "";
|
||||
};
|
||||
|
||||
function extendBook(item: listBook, seriesName: string): book {
|
||||
return {
|
||||
...item,
|
||||
|
||||
author: item.authorName,
|
||||
comments: "",
|
||||
|
||||
language: "",
|
||||
path: item.path,
|
||||
hasCover: item.hasCover,
|
||||
formats: [],
|
||||
seriesName,
|
||||
seriesNumber: item.seriesIndex,
|
||||
};
|
||||
}
|
||||
|
||||
function orderBySeriesIndex(books: listBook[]): listBook[] {
|
||||
return [...books].sort((a, b) => {
|
||||
const indexA = a.seriesIndex ?? 0;
|
||||
const indexB = b.seriesIndex ?? 0;
|
||||
return indexA - indexB;
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Series!</h1>
|
||||
@@ -49,7 +74,7 @@ const SeriesList: Component = () => {
|
||||
placeholder="Search..."
|
||||
onInput={(e) => update(e.currentTarget.value)}
|
||||
/>
|
||||
<table class="table p-3">
|
||||
<table class="table p-3 series-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
@@ -57,27 +82,53 @@ const SeriesList: Component = () => {
|
||||
<th>Author</th>
|
||||
<th>Book Count</th>
|
||||
<th>Published</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<For each={series()}>
|
||||
{(item) => (
|
||||
<tr>
|
||||
<td>{item.id}</td>
|
||||
<td>{item.name}</td>
|
||||
<td>
|
||||
<For each={distinctAuthors(item.books)}>
|
||||
{(author) => (
|
||||
<a href={`/books/author/${author.id}`}>{author.name}</a>
|
||||
)}
|
||||
</For>
|
||||
</td>
|
||||
<td>{item.books.length}</td>
|
||||
<td>
|
||||
{minDate(item.books) ?? "N/A"} -{" "}
|
||||
{maxDate(item.books) ?? "N/A"}
|
||||
</td>
|
||||
</tr>
|
||||
<>
|
||||
<tr>
|
||||
<td>{item.id}</td>
|
||||
<td>{item.name}</td>
|
||||
<td>
|
||||
<For each={distinctAuthors(item.books)}>
|
||||
{(author) => (
|
||||
<a href={`/books/author/${author.id}`}>{author.name}</a>
|
||||
)}
|
||||
</For>
|
||||
</td>
|
||||
<td>{item.books.length}</td>
|
||||
<td>
|
||||
{minDate(item.books) ?? "N/A"} -{" "}
|
||||
{maxDate(item.books) ?? "N/A"}
|
||||
</td>
|
||||
<td>
|
||||
<OverlayTrigger
|
||||
trigger="click"
|
||||
offset={[0, 8]}
|
||||
placement="left"
|
||||
overlay={
|
||||
<Popover id="popover-series" class="shadow">
|
||||
<Popover.Header as="h3">{item.name}</Popover.Header>
|
||||
<Popover.Body>
|
||||
<div class="book-grid d-flex flex-wrap justify-content-between gap-3 p-3">
|
||||
<For each={orderBySeriesIndex(item.books)}>
|
||||
{(b) => (
|
||||
<BookCard book={extendBook(b, item.name)} />
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</Popover.Body>
|
||||
</Popover>
|
||||
}
|
||||
>
|
||||
<Button variant="secondary">Books</Button>
|
||||
</OverlayTrigger>
|
||||
</td>
|
||||
</tr>
|
||||
</>
|
||||
)}
|
||||
</For>
|
||||
</tbody>
|
||||
|
@@ -6,3 +6,7 @@
|
||||
aspect-ratio: 3 / 4;
|
||||
}
|
||||
}
|
||||
|
||||
#popover-series {
|
||||
--bs-popover-max-width: 80%;
|
||||
}
|
||||
|
@@ -23,6 +23,8 @@ interface listBook {
|
||||
title: string;
|
||||
authorId: number;
|
||||
authorName: string;
|
||||
path: string;
|
||||
hasCover: boolean;
|
||||
pubDate: Date;
|
||||
seriesIndex: number;
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
using Bibblan.Models;
|
||||
using Bibblan.ViewModels;
|
||||
using Microsoft.AspNetCore.Http.HttpResults;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Bibblan.Business.Services
|
||||
@@ -17,6 +18,9 @@ namespace Bibblan.Business.Services
|
||||
public byte[] Cover(string path)
|
||||
{
|
||||
var fullPath = Path.Combine(_options.CalibreRoot, path, "cover.jpg");
|
||||
if (!File.Exists(fullPath))
|
||||
return null;
|
||||
|
||||
return File.ReadAllBytes(fullPath);
|
||||
}
|
||||
|
||||
|
@@ -13,18 +13,16 @@ namespace Bibblan.Business.Services
|
||||
public string? Query;
|
||||
}
|
||||
|
||||
public class DatabaseService
|
||||
public class DatabaseService(IOptions<BibblanOptions> options)
|
||||
{
|
||||
BibblanOptions settings;
|
||||
public DatabaseService(IOptions<BibblanOptions> options) {
|
||||
settings = options.Value;
|
||||
}
|
||||
readonly BibblanOptions settings = options.Value;
|
||||
|
||||
public IEnumerable<Books> GetBooks(int count, BookFilter filter = null)
|
||||
{
|
||||
var conn = new NpgsqlConnection(settings.BibblanConnection);
|
||||
var query = "select * from books";
|
||||
if(filter != null)
|
||||
object parameters = null;
|
||||
if (filter != null)
|
||||
{
|
||||
query += " where ";
|
||||
if(filter.Author != null)
|
||||
@@ -33,46 +31,48 @@ namespace Bibblan.Business.Services
|
||||
} else if(!String.IsNullOrWhiteSpace(query))
|
||||
{
|
||||
filter.Query = filter.Query.ToLowerInvariant();
|
||||
query += $"lower(title) like '%{filter.Query}%' or lower(author_sort) like '%{filter.Query}%'";
|
||||
query += $"lower(title) like @query or lower(author_sort) like @query";
|
||||
parameters = new { query = "%" + filter.Query + "%" };
|
||||
}
|
||||
}
|
||||
return conn.Query<Books>(query).Take(count).ToList();
|
||||
return conn.Query<Books>(query, parameters).Take(count).ToList();
|
||||
}
|
||||
|
||||
public IEnumerable<AuthorVm> GetAuthors(int count, BookFilter filter = null)
|
||||
{
|
||||
var query = "select a.id, a.name, count(bal.*) as bookcount\r\nfrom authors a\r\nleft join books_authors_link bal on a.id = bal.author\r\ngroup by a.id , a.name ";
|
||||
if(!String.IsNullOrWhiteSpace(filter?.Query))
|
||||
object parameters = null;
|
||||
if (!String.IsNullOrWhiteSpace(filter?.Query))
|
||||
{
|
||||
filter.Query = filter.Query.ToLowerInvariant();
|
||||
query += $" having lower(a.name) like '%{filter.Query}%'";
|
||||
query += $" having lower(a.name) like @query";
|
||||
parameters = new { query = "%" + filter.Query + "%" };
|
||||
}
|
||||
var conn = new NpgsqlConnection(settings.BibblanConnection);
|
||||
return conn.Query<AuthorVm>(query).Take(count).ToList();
|
||||
return conn.Query<AuthorVm>(query, parameters).Take(count).ToList();
|
||||
|
||||
}
|
||||
|
||||
internal List<SeriesVm> GetSeries(int count, BookFilter? filter)
|
||||
{
|
||||
var query = @"select s.id as seriesid, s.name as seriesname, b.*, a.*
|
||||
--b.id as bookid, b.title, b.pubdate, b.series_index,
|
||||
--a.id as authorid, a.name as authorname
|
||||
from series s
|
||||
inner join books_series_link bsl on s.id = bsl.series
|
||||
inner join books b on bsl.book = b.id
|
||||
inner join books_authors_link bal on b.id = bal.book
|
||||
inner join authors a on bal.author = a.id";
|
||||
object parameters = null;
|
||||
if (!String.IsNullOrWhiteSpace(filter?.Query))
|
||||
{
|
||||
filter.Query = filter.Query?.ToLowerInvariant().Trim() ?? "";
|
||||
query += $" where lower(s.name) like '%{filter.Query}%'";
|
||||
query += $" where lower(s.name) like @query";
|
||||
parameters = new { query = "%" + filter.Query + "%" };
|
||||
}
|
||||
var conn = new NpgsqlConnection(settings.BibblanConnection);
|
||||
var lookup = new Dictionary<long, SeriesVm>();
|
||||
conn.Query<SeriesVm, Books, Authors, SeriesVm>(query, (s,b,a) =>
|
||||
{
|
||||
SeriesVm svm;
|
||||
if (!lookup.TryGetValue(s.Id, out svm))
|
||||
if (!lookup.TryGetValue(s.Id, out SeriesVm svm))
|
||||
{
|
||||
lookup.Add(s.Id, svm = s);
|
||||
}
|
||||
@@ -82,13 +82,14 @@ namespace Bibblan.Business.Services
|
||||
AuthorName = a.Name,
|
||||
Id = b.Id,
|
||||
PubDate = b.Pubdate,
|
||||
HasCover = b.HasCover,
|
||||
Path = b.Path,
|
||||
SeriesIndex = b.SeriesIndex,
|
||||
Title = b.Title
|
||||
|
||||
});
|
||||
return svm;
|
||||
}, splitOn: "id").Take(count).ToList();
|
||||
return lookup.Values.AsList();
|
||||
}, splitOn: "id", param: parameters);
|
||||
return lookup.Values.Take(count).ToList();
|
||||
}
|
||||
|
||||
internal List<Tag> GetTags(BookFilter? filter)
|
||||
@@ -97,13 +98,15 @@ namespace Bibblan.Business.Services
|
||||
inner join books_tags_link btl on t.id = btl.tag
|
||||
group by t.id, t.name
|
||||
order by bookcount desc";
|
||||
object parameters = null;
|
||||
if (!String.IsNullOrWhiteSpace(filter?.Query))
|
||||
{
|
||||
filter.Query = filter.Query.ToLowerInvariant();
|
||||
query += $" having lower(name) like '%{filter.Query}%'";
|
||||
query += $" having lower(name) like @query";
|
||||
parameters = new { query = "%" + filter.Query + "%" };
|
||||
}
|
||||
var conn = new NpgsqlConnection(settings.BibblanConnection);
|
||||
return conn.Query<Tag>(query).ToList();
|
||||
return conn.Query<Tag>(query,parameters).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -21,6 +21,9 @@ namespace Bibblan.Controllers
|
||||
{
|
||||
//TODO: Bör kanske inte gå direkt mot calibres filer..
|
||||
var bytes = _calibre.Cover(path);
|
||||
if (bytes == null)
|
||||
return NotFound();
|
||||
|
||||
return File(bytes, "image/jpeg", true);
|
||||
}
|
||||
|
||||
|
@@ -31,6 +31,8 @@ namespace Bibblan.Controllers
|
||||
public IActionResult GetCover(string path)
|
||||
{
|
||||
var bytes = _service.Cover(path);
|
||||
if (bytes == null)
|
||||
return NotFound();
|
||||
return File(bytes, "image/jpeg", true);
|
||||
}
|
||||
|
||||
|
@@ -17,6 +17,8 @@ namespace Bibblan.ViewModels
|
||||
public double SeriesIndex { get; set; }
|
||||
public long AuthorId { get; set; }
|
||||
public string AuthorName { get; set; }
|
||||
public bool HasCover { get; set; }
|
||||
public string Path { get; set; }
|
||||
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user