Adds web/src/api/trakt.ts wrapping the new /api/trakt/* endpoints (#160) plus URL-assert tests, and web/src/screens/TraktListsScreen.tsx: a table (slug, name, match count) with add-by-URL, match/refresh, delete, and a sub-path editor at /app/trakt-lists/{id} (slug read-only, autoRefresh/generatePlaylist toggles, save via PUT) - same allowSubPaths pattern as ChannelEditScreen/SettingsScreen. Since add/match/delete are async background jobs, the screen polls GET /api/trakt/status while busy (disabling actions) and refreshes the list on the busy -> idle transition. "View matched items" links out to the classic Blazor search page (/search?query=trakt_list:{traktId}) since the SPA has no search screen yet (#161) - same interim deep-link pattern used elsewhere. Registered in App.tsx's Media nav group; SettingsScreen's Classic-UI help text no longer lists trakt now that it's SPA-native.
71 lines
2.5 KiB
TypeScript
71 lines
2.5 KiB
TypeScript
import { ApiError, request } from './client';
|
|
import type { components } from './generated/v1';
|
|
|
|
export type TraktList = components['schemas']['TraktListResponseModel'];
|
|
export type PagedTraktLists = components['schemas']['PagedTraktListsResponseModel'];
|
|
export type TraktStatus = components['schemas']['TraktStatusResponseModel'];
|
|
export type AddTraktListRequest = components['schemas']['AddTraktListRequest'];
|
|
export type UpdateTraktListRequest = components['schemas']['UpdateTraktListRequest'];
|
|
|
|
export interface GetTraktListsParams {
|
|
pageNum?: number;
|
|
pageSize?: number;
|
|
}
|
|
|
|
export function getTraktLists(params: GetTraktListsParams = {}): Promise<PagedTraktLists> {
|
|
const searchParams = new URLSearchParams();
|
|
|
|
if (params.pageNum != null) {
|
|
searchParams.set('pageNum', String(params.pageNum));
|
|
}
|
|
|
|
if (params.pageSize != null) {
|
|
searchParams.set('pageSize', String(params.pageSize));
|
|
}
|
|
|
|
const queryString = searchParams.toString();
|
|
|
|
return request<PagedTraktLists>(`/api/trakt/lists${queryString ? `?${queryString}` : ''}`);
|
|
}
|
|
|
|
export function getTraktListById(id: number): Promise<TraktList> {
|
|
return request<TraktList>(`/api/trakt/lists/${id}`);
|
|
}
|
|
|
|
// 202 Accepted: the server dispatches to the same background worker channel the classic
|
|
// UI's "Add Trakt List" dialog uses. Fetch/save/match all happen asynchronously — poll
|
|
// getTraktStatus() and reload the list once it goes idle.
|
|
export function addTraktList(url: string): Promise<void> {
|
|
return request<void>('/api/trakt/lists', { body: { url } satisfies AddTraktListRequest, method: 'POST' });
|
|
}
|
|
|
|
// 202 Accepted; see addTraktList for the async/poll pattern.
|
|
export function matchTraktList(id: number): Promise<void> {
|
|
return request<void>(`/api/trakt/lists/${id}/match`, { method: 'POST' });
|
|
}
|
|
|
|
// 202 Accepted; see addTraktList for the async/poll pattern.
|
|
export function deleteTraktList(id: number): Promise<void> {
|
|
return request<void>(`/api/trakt/lists/${id}`, { method: 'DELETE' });
|
|
}
|
|
|
|
export function updateTraktList(id: number, body: UpdateTraktListRequest): Promise<TraktList> {
|
|
return request<TraktList>(`/api/trakt/lists/${id}`, { body, method: 'PUT' });
|
|
}
|
|
|
|
export function getTraktStatus(): Promise<TraktStatus> {
|
|
return request<TraktStatus>('/api/trakt/status');
|
|
}
|
|
|
|
export function messageFromTraktError(error: unknown, fallback = 'Unable to load Trakt lists'): string {
|
|
if (error instanceof ApiError) {
|
|
return error.detail ?? error.message;
|
|
}
|
|
|
|
if (error instanceof Error) {
|
|
return error.message;
|
|
}
|
|
|
|
return fallback;
|
|
}
|