Skip to content

Commit 813c5e8

Browse files
feat: Improved Letterboxd tab design, added progress when loading watchlist. Also fixed poster images not loading on Letterboxd tab (v1.4.1.0)
1 parent 97da631 commit 813c5e8

6 files changed

Lines changed: 277 additions & 67 deletions

File tree

src/Jellyfin.Plugin.BetterSeerrTabs/Controllers/BetterSeerrTabsController.cs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -576,13 +576,28 @@ public async Task<ActionResult> MakeDiscoverRequest(
576576
};
577577
}
578578

579+
[HttpGet("letterboxd/sync/progress")]
580+
[Authorize]
581+
public ActionResult<LetterboxdSyncProgressDto> GetLetterboxdSyncProgress()
582+
{
583+
Guid userId = GetUserId();
584+
if (userId == Guid.Empty)
585+
{
586+
return Forbid();
587+
}
588+
589+
Response.Headers.CacheControl = "no-cache, no-store, must-revalidate";
590+
return Ok(_letterboxdWatchlistService.GetSyncProgress(userId));
591+
}
592+
579593
[HttpPost("letterboxd/sync")]
580594
[Authorize]
581595
public async Task<ActionResult> SyncLetterboxdWatchlist(
582596
[FromQuery] string letterboxdUsername,
583597
CancellationToken cancellationToken)
584598
{
585-
if (GetUserId() == Guid.Empty)
599+
Guid userId = GetUserId();
600+
if (userId == Guid.Empty)
586601
{
587602
return Forbid();
588603
}
@@ -595,7 +610,7 @@ public async Task<ActionResult> SyncLetterboxdWatchlist(
595610
try
596611
{
597612
(List<BaseItemDto> items, int totalCount, int resolvedCount, int unresolvedCount) = await _letterboxdWatchlistService
598-
.SyncAsync(letterboxdUsername, cancellationToken)
613+
.SyncAsync(userId, letterboxdUsername, cancellationToken)
599614
.ConfigureAwait(false);
600615
Response.Headers.CacheControl = "no-cache, no-store, must-revalidate";
601616
return Ok(new

src/Jellyfin.Plugin.BetterSeerrTabs/Inject/betterseerr-letterboxd.css

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@
7676
}
7777

7878
.betterseerr-letterboxd-actionbar button {
79-
min-width: 11em;
79+
width: 100%;
8080
}
8181

8282
.betterseerr-letterboxd-help,
@@ -204,7 +204,7 @@
204204
align-items: center;
205205
gap: 0.75em 1em;
206206
margin-top: 1em;
207-
padding: 0.85em 1em;
207+
padding: 0.85em 0;
208208
border-top: 1px solid rgba(255, 255, 255, 0.08);
209209
background: rgba(16, 16, 16, 0.92);
210210
backdrop-filter: blur(8px);

src/Jellyfin.Plugin.BetterSeerrTabs/Inject/betterseerr-letterboxd.js

Lines changed: 65 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
selectedIds: new Set(),
1717
requestedIds: new Set(),
1818
syncing: false,
19+
syncProgressPercent: 0,
20+
syncProgressPollTimer: null,
1921
requesting: false,
2022
requestProgress: { done: 0, total: 0 },
2123
bulkRoot: null
@@ -70,6 +72,60 @@
7072
return date.toLocaleString();
7173
}
7274

75+
function getSyncButtonLabel() {
76+
if (state.syncing) {
77+
return state.syncProgressPercent + '%';
78+
}
79+
80+
return state.items.length > 0 ? 'Refresh watchlist' : 'Get watchlist';
81+
}
82+
83+
function updateSyncButton(container) {
84+
const button = container.querySelector('[data-sync-submit]');
85+
if (button) {
86+
button.textContent = getSyncButtonLabel();
87+
}
88+
}
89+
90+
function stopSyncProgressPolling() {
91+
if (state.syncProgressPollTimer) {
92+
clearInterval(state.syncProgressPollTimer);
93+
state.syncProgressPollTimer = null;
94+
}
95+
}
96+
97+
function pollSyncProgress(container) {
98+
ApiClient.ajax({
99+
url: ApiClient.getUrl('BetterSeerrTabs/letterboxd/sync/progress'),
100+
type: 'GET',
101+
dataType: 'json'
102+
}).then(function (result) {
103+
const raw = result?.percent ?? result?.Percent ?? 0;
104+
const percent = typeof raw === 'number' ? raw : parseInt(raw, 10);
105+
if (Number.isNaN(percent)) {
106+
return;
107+
}
108+
109+
const clamped = Math.max(0, Math.min(100, Math.round(percent)));
110+
if (clamped !== state.syncProgressPercent) {
111+
state.syncProgressPercent = clamped;
112+
updateSyncButton(container);
113+
}
114+
}).catch(function () {
115+
// Ignore polling errors while sync is in flight.
116+
});
117+
}
118+
119+
function startSyncProgressPolling(container) {
120+
stopSyncProgressPolling();
121+
state.syncProgressPercent = 0;
122+
updateSyncButton(container);
123+
pollSyncProgress(container);
124+
state.syncProgressPollTimer = setInterval(function () {
125+
pollSyncProgress(container);
126+
}, 400);
127+
}
128+
73129
function syncWatchlist(username) {
74130
return ApiClient.ajax({
75131
url: ApiClient.getUrl('BetterSeerrTabs/letterboxd/sync', { letterboxdUsername: username }),
@@ -134,13 +190,13 @@
134190
'placeholder="your-letterboxd-username" value="' + escapeHtml(state.username) + '" ' +
135191
(state.syncing || state.requesting ? 'disabled' : '') + ' />' +
136192
'</div>' +
137-
'<button type="submit" ' +
193+
'<button type="submit" data-sync-submit ' +
138194
(state.syncing || state.requesting ? 'disabled' : '') + '>' +
139-
(state.syncing ? 'Syncing…' : (hasWatchlist ? 'Sync again' : 'Sync watchlist')) +
195+
escapeHtml(getSyncButtonLabel()) +
140196
'</button>' +
141197
'</form>' +
142198
'</div>' +
143-
'<div class="betterseerr-letterboxd-help">Public Letterboxd watchlists only. Enter your username, sync, select movies, then request them in bulk.</div>';
199+
'<div class="betterseerr-letterboxd-help">Public Letterboxd watchlists only. Enter your username, get watchlist, select movies, then request them in bulk.</div>';
144200

145201
if (lastError) {
146202
html += '<div class="betterseerr-letterboxd-error">' + escapeHtml(lastError) + '</div>';
@@ -152,7 +208,7 @@
152208
html +=
153209
'<div class="betterseerr-letterboxd-meta">' +
154210
'<span>Last synced: <strong>' + escapeHtml(formatDate(lastSynced)) + '</strong></span>' +
155-
'<span>Resolved: <strong>' + escapeHtml(String(resolvedCount)) + '</strong></span>' +
211+
'<span>Gotten: <strong>' + escapeHtml(String(resolvedCount)) + '</strong></span>' +
156212
(unresolvedCount ? '<span>Unresolved: <strong>' + escapeHtml(String(unresolvedCount)) + '</strong></span>' : '') +
157213
'</div>';
158214
}
@@ -283,7 +339,7 @@
283339
window.betterSeerrModal.open(String(tmdbId), 'movie');
284340
}
285341
});
286-
slot.appendChild(actions);
342+
posterArea.appendChild(actions);
287343

288344
if (state.requestedIds.has(tmdbId)) {
289345
const badge = document.createElement('span');
@@ -379,8 +435,10 @@
379435
}
380436

381437
state.syncing = true;
438+
state.syncProgressPercent = 0;
382439
state.syncMeta = Object.assign({}, state.syncMeta || {}, { lastError: null });
383440
renderPanel(container);
441+
startSyncProgressPolling(container);
384442

385443
syncWatchlist(username)
386444
.then(function () {
@@ -393,7 +451,9 @@
393451
Dashboard.alert(message);
394452
})
395453
.finally(function () {
454+
stopSyncProgressPolling();
396455
state.syncing = false;
456+
state.syncProgressPercent = 0;
397457
renderPanel(container);
398458
});
399459
}

src/Jellyfin.Plugin.BetterSeerrTabs/Inject/betterseerr-tabs.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -530,7 +530,15 @@ if (typeof window.betterSeerrTabsPlugin === 'undefined') {
530530
posterUrl = window.ApiClient.getUrl(posterUrl);
531531
}
532532

533-
return posterUrl || null;
533+
if (posterUrl) {
534+
return posterUrl;
535+
}
536+
537+
if (posterPath) {
538+
return this.buildTmdbImageUrl(posterPath, null, 'w600_and_h900_bestv2');
539+
}
540+
541+
return null;
534542
},
535543

536544
shouldUseBackdropThumbnails: function () {
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace Jellyfin.Plugin.BetterSeerrTabs.Model;
2+
3+
public class LetterboxdSyncProgressDto
4+
{
5+
public int Percent { get; set; }
6+
7+
public string Phase { get; set; } = string.Empty; // pages (when gettings pages) or tmdb (matching tmdb ids)
8+
9+
public bool IsActive { get; set; }
10+
}

0 commit comments

Comments
 (0)