diff --git a/.cursorrules b/.cursorrules new file mode 100644 index 0000000..61b5640 --- /dev/null +++ b/.cursorrules @@ -0,0 +1,20 @@ +DO NOT GIVE ME HIGH LEVEL SHIT, IF I ASK FOR FIX OR EXPLANATION, I WANT ACTUAL CODE OR EXPLANATION! I DON'T WANT "Here's how you can blablabla" +- Be casual unless otherwise specified +- Be terse +- Suggest solutions that I didn't think about-anticipate my needs +- Treat me as an expert +- Be accurate and thorough +- Give the answer immediately. Provide detailed explanations and restate my query in your own words if necessary after giving the answer +- Value good arguments over authorities, the source is irrelevant +- Consider new technologies and contrarian ideas, not just the conventional wisdom +- You may use high levels of speculation or prediction, just flag it for me +- No moral lectures +- Discuss safety only when it's crucial and non-obvious +- If your content policy is an issue, provide the closest acceptable response and explain the content policy issue afterward +- Cite sources whenever possible at the end, not inline +- No need to mention your knowledge cutoff +- No need to disclose you're an AI +- Please respect my formatting preferences when you provide code. +- Please respect all code comments, they're usually there for a reason. Remove them ONLY if they're completely irrelevant after a code change. if unsure, do not remove the comment. +- Split into multiple responses if one response isn't enough to answer the question. +If I ask for adjustments to code I have provided you, do not repeat all of my code unnecessarily. Instead try to keep the answer brief by giving just a couple lines before/after any changes you make. Multiple code blocks are ok. \ No newline at end of file diff --git a/.gitignore b/.gitignore index 95450a4..34a9465 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,5 @@ go.work data/ bin/ release/ + +docker-compose-local.yml diff --git a/Makefile b/Makefile index c2b3540..f7bf8ea 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,10 @@ -include .env.example -export +ifneq (,$(wildcard ./.env)) + include .env + export +else + include .env.example + export +endif LOCAL_BIN:=$(CURDIR)/bin PATH:=$(LOCAL_BIN):$(PATH) diff --git a/internal/controller/http/web/books.go b/internal/controller/http/web/books.go index 02e23b9..e9aae9a 100644 --- a/internal/controller/http/web/books.go +++ b/internal/controller/http/web/books.go @@ -9,17 +9,19 @@ import ( "github.com/vanadium23/kompanion/internal/entity" "github.com/vanadium23/kompanion/internal/library" "github.com/vanadium23/kompanion/internal/stats" + syncpkg "github.com/vanadium23/kompanion/internal/sync" "github.com/vanadium23/kompanion/pkg/logger" ) type booksRoutes struct { - shelf library.Shelf - stats stats.ReadingStats - logger logger.Interface + shelf library.Shelf + stats stats.ReadingStats + progress syncpkg.Progress + logger logger.Interface } -func newBooksRoutes(handler *gin.RouterGroup, shelf library.Shelf, stats stats.ReadingStats, l logger.Interface) { - r := &booksRoutes{shelf: shelf, stats: stats, logger: l} +func newBooksRoutes(handler *gin.RouterGroup, shelf library.Shelf, stats stats.ReadingStats, progress syncpkg.Progress, l logger.Interface) { + r := &booksRoutes{shelf: shelf, stats: stats, progress: progress, logger: l} handler.GET("/", r.listBooks) handler.POST("/upload", r.uploadBook) @@ -44,8 +46,26 @@ func (r *booksRoutes) listBooks(c *gin.Context) { return } + // Fetch progress for each book + type BookWithProgress struct { + entity.Book + Progress int + } + booksWithProgress := make([]BookWithProgress, len(books.Books)) + for i, book := range books.Books { + progress, err := r.progress.Fetch(c.Request.Context(), book.DocumentID) + if err != nil { + r.logger.Error(err, "failed to fetch progress for book %s", book.ID) + progress = entity.Progress{} + } + booksWithProgress[i] = BookWithProgress{ + Book: book, + Progress: int(progress.Percentage * 100), + } + } + c.HTML(200, "books", passStandartContext(c, gin.H{ - "books": books.Books, + "books": booksWithProgress, "pagination": gin.H{ "currentPage": page, "perPage": perPage, diff --git a/internal/controller/http/web/router.go b/internal/controller/http/web/router.go index 8a27ca2..a8df3de 100644 --- a/internal/controller/http/web/router.go +++ b/internal/controller/http/web/router.go @@ -5,8 +5,10 @@ import ( "fmt" "html/template" "io/fs" + "math" "net/http" "path/filepath" + "strings" "time" "github.com/foolin/goview" @@ -64,6 +66,7 @@ func NewRouter( "Version": func() string { return template.HTMLEscapeString(version) }, + "generateProgressBar": generateProgressBar, } gv := ginview.New(config) gv.SetFileHandler(embeddedFH) @@ -81,7 +84,7 @@ func NewRouter( // Product pages bookGroup := handler.Group("/books") bookGroup.Use(authMiddleware(a)) - newBooksRoutes(bookGroup, shelf, stats, l) + newBooksRoutes(bookGroup, shelf, stats, p, l) // Stats pages statsGroup := handler.Group("/stats") @@ -114,6 +117,30 @@ func formatDuration(seconds int) string { return fmt.Sprintf("%ds", secs) } +func generateProgressBar(percentage int, totalLength int) string { + if percentage < 0 { + percentage = 0 + } + numEquals := int(math.Round(float64(percentage) * float64(totalLength) / 100.0)) + if numEquals > totalLength { + numEquals = totalLength + } + if numEquals < 0 { + numEquals = 0 + } + + numDots := totalLength - numEquals + + var sb strings.Builder + sb.Grow(totalLength + 2) + sb.WriteString("[") + sb.WriteString(strings.Repeat("▓", numEquals)) + sb.WriteString(strings.Repeat("░", numDots)) + sb.WriteString("]") + + return sb.String() +} + // https://github.com/foolin/goview/issues/25#issuecomment-876889943 func embeddedFH(config goview.Config, tmpl string) (string, error) { path := filepath.Join(config.Root, tmpl) diff --git a/internal/sync/progress.go b/internal/sync/progress.go index 94fd6b6..0f86f7a 100644 --- a/internal/sync/progress.go +++ b/internal/sync/progress.go @@ -42,6 +42,10 @@ func (uc *ProgressSyncUseCase) Fetch(ctx context.Context, bookID string) (entity return entity.Progress{}, nil } + if len(doc) == 0 { + return entity.Progress{}, nil + } + last := doc[0] // rewrite koreader device with our authed device last.Device = last.AuthDeviceName diff --git a/web/static/static.css b/web/static/static.css index c9bed32..72b6a16 100644 --- a/web/static/static.css +++ b/web/static/static.css @@ -1,5 +1,3 @@ - - .edit-book-article { display: flex; flex-direction: row; @@ -19,50 +17,30 @@ height: auto; } - - /* books */ -.book-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); - gap: 20px; - padding: 20px; -} - -.book-item { +.book-card { display: flex; - flex-direction: column; - align-items: center; - text-align: center; + align-items: flex-start; + gap: 1rem; + border: var(--border-thickness) solid var(--text-color); + padding: 1rem; + margin-bottom: 1.5rem; } -.book-item .cover { - width: 100%; - height: 300px; - /* Фиксированная высота для всех обложек */ - display: flex; - align-items: center; - justify-content: center; - background-color: #f0f0f0; - /* Цвет фона для обложек, если изображение отсутствует */ +/* Container for the book cover image */ +.book-cover { + flex-shrink: 0; + width: 10rem; } -.book-item img { +/* Style the actual image */ +.book-cover img { + display: block; width: 100%; - height: 100%; - object-fit: cover; - /* Обеспечиваем, чтобы изображение полностью занимало контейнер */ + height: auto; } .book-info { - margin-top: 10px; -} - -.book-title { - font-weight: bold; + flex-grow: 1; + margin-top: 0; } - -.book-author { - font-style: italic; - color: #555; -} \ No newline at end of file diff --git a/web/templates/books.html b/web/templates/books.html index 82d08c7..3eb6322 100644 --- a/web/templates/books.html +++ b/web/templates/books.html @@ -6,21 +6,27 @@
{{ generateProgressBar .Progress 15 }} // {{ .Progress }}%
+| KOmpanion | +KOmpanion | {{ if .isAuthenticated }}> Books | > Statistics |