Compare commits

...

2 Commits

Author SHA1 Message Date
e0e8f8742c Add watch progress feature to video model and UI
All checks were successful
Build / build (push) Successful in 2m29s
2025-06-17 20:05:40 +02:00
85e0c89ee8 Show title 2025-06-17 19:49:47 +02:00
4 changed files with 78 additions and 24 deletions

1
.gitignore vendored
View File

@ -1,6 +1,7 @@
.idea .idea
*.iml *.iml
*.db3 *.db3
*.db3.*
*.db3-* *.db3-*
*.bak* *.bak*
/downloads/ /downloads/

View File

@ -0,0 +1 @@
ALTER TABLE videos ADD COLUMN watch_progress REAL;

View File

@ -53,8 +53,9 @@
aspect-ratio: 16/9; aspect-ratio: 16/9;
width: 20vw; width: 20vw;
transition: width 0.3s ease, box-shadow 0.3s ease, left 0.3s ease; transition: width 0.3s ease, box-shadow 0.3s ease, left 0.3s ease;
transform: translate(-50%, -50%); transform: translate(-50%, 0);
position: fixed; position: fixed;
overflow: hidden; /* Ensure content fits within rounded corners */
} }
.video-thumbnail.focussed { .video-thumbnail.focussed {
@ -70,6 +71,47 @@
z-index: 1; z-index: 1;
} }
.video-title {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
background-color: rgba(0, 0, 0, 0.7);
color: white;
padding: 8px;
box-sizing: border-box;
text-align: center;
font-size: 1rem;
opacity: 0;
transition: opacity 0.3s ease-in-out;
border-bottom-left-radius: 8px;
border-bottom-right-radius: 8px;
}
.video-thumbnail:hover .video-title,
.video-thumbnail.focussed .video-title {
opacity: 1;
}
.progress-bar-container {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 5px; /* Or adjust as needed */
background-color: rgba(0, 0, 0, 0.5);
border-bottom-left-radius: 8px;
border-bottom-right-radius: 8px;
overflow: hidden;
}
.progress-bar {
height: 100%;
background-color: #3498db; /* A nice blue */
width: 0; /* Initially no progress */
transition: width 0.2s ease-in-out;
}
.play-button { .play-button {
position: absolute; position: absolute;
top: 50%; top: 50%;
@ -313,30 +355,38 @@
* @param append Set to true if this thumbnail should be appended to the video container, false if it should be prepended. * @param append Set to true if this thumbnail should be appended to the video container, false if it should be prepended.
*/ */
function createVideoThumbnailElement(data, selected, append) { function createVideoThumbnailElement(data, selected, append) {
let container = $(`<span></span>`); let container = $(`<div class="video-thumbnail" tabindex="0" role="button" aria-label="${data.title}"></div>`);
let thumbnail = $(`<img src="${data.thumbnail}" class="video-thumbnail" alt="Video Thumbnail" tabindex="0">`)[0]; let thumbnailImg = $(`<img src="${data.thumbnail}" alt="" style="width: 100%; height: 100%; object-fit: cover;">`);
container.append(thumbnail) let title = $(`<div class="video-title">${data.title}</div>`);
let index; let progressBarContainer = $(`<div class="progress-bar-container"><div class="progress-bar"></div></div>`);
if (append) {
$("#video-container").append(container); container.append(thumbnailImg).append(title).append(progressBarContainer);
if (data.watchProgress > 0) {
progressBarContainer.find(".progress-bar").css("width", (data.watchProgress * 100) + "%");
}
let index;
if (append) {
$("#video-container").append(container);
index = videoInfo.length; index = videoInfo.length;
videoInfo.push({ videoInfo.push({
thumbnail, thumbnail: container[0],
data data
}) })
} else { } else {
$("#video-container").prepend(container); $("#video-container").prepend(container);
index = 0; index = 0;
videoInfo.unshift({ videoInfo.unshift({
thumbnail, thumbnail: container[0],
data data
}); });
selectedThumbnailIndex++; selectedThumbnailIndex++;
}
if (selected) {
container[0].classList.add("focussed");
selectedThumbnailIndex = index;
} }
if (selected) {
thumbnail.classList.add("focussed");
selectedThumbnailIndex = index;
}
} }
/** /**

View File

@ -52,9 +52,11 @@ func serveWebview(db *mysqlite.Db) {
} }
type VideoModel struct { type VideoModel struct {
ID int `json:"id"` ID int `json:"id"`
Thumbnail string `json:"thumbnail"` Thumbnail string `json:"thumbnail"`
URL string `json:"url"` URL string `json:"url"`
Title string `json:"title"`
WatchProgress float64 `json:"watchProgress"`
} }
type HomePageModel struct { type HomePageModel struct {
@ -65,13 +67,13 @@ type HomePageModel struct {
func (a *App) homePage(c *gin.Context) { func (a *App) homePage(c *gin.Context) {
model := HomePageModel{} model := HomePageModel{}
errCurrent := a.db.Query("SELECT id, thumbnail FROM videos WHERE (watch_state IS NULL OR watch_state != 'watched') ORDER BY upload_date, episode LIMIT 1"). errCurrent := a.db.Query("SELECT id, thumbnail, title, watch_progress FROM videos WHERE (watch_state IS NULL OR watch_state != 'watched') ORDER BY upload_date, episode LIMIT 1").
ScanSingle(&model.Current.ID, &model.Current.Thumbnail) ScanSingle(&model.Current.ID, &model.Current.Thumbnail, &model.Current.Title, &model.Current.WatchProgress)
var errNext, errPrevious error var errNext, errPrevious error
for row := range a.db.Query("SELECT id, thumbnail FROM videos WHERE (watch_state IS NULL OR watch_state != 'watched') ORDER BY upload_date, episode LIMIT 3 OFFSET 1").Range(&errNext) { for row := range a.db.Query("SELECT id, thumbnail, title, watch_progress FROM videos WHERE (watch_state IS NULL OR watch_state != 'watched') ORDER BY upload_date, episode LIMIT 3 OFFSET 1").Range(&errNext) {
video := VideoModel{} video := VideoModel{}
err := row.Scan(&video.ID, &video.Thumbnail) err := row.Scan(&video.ID, &video.Thumbnail, &video.Title, &video.WatchProgress)
if err != nil { if err != nil {
log.Printf("Failed to scan next video: %v", err) log.Printf("Failed to scan next video: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve next videos"}) c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve next videos"})