Rework the media grid with a viewer, metadata banners, and load gate
This commit is contained in:
@@ -114,6 +114,8 @@ export interface MediaItemResult {
|
||||
width: number | null;
|
||||
height: number | null;
|
||||
aspectRatio: number | null;
|
||||
contentType: string | null;
|
||||
durationSecs: number | null;
|
||||
url: string;
|
||||
localUrl: string | null;
|
||||
thumbUrl: string | null;
|
||||
@@ -442,6 +444,38 @@ export class MediaService {
|
||||
return count;
|
||||
}
|
||||
|
||||
/** Item counts for the status row, under the same filters as queryMedia. */
|
||||
stats(options: {
|
||||
kind?: MediaKind;
|
||||
source?: string;
|
||||
channelId?: string;
|
||||
authorId?: string;
|
||||
aspect?: string;
|
||||
resolution?: string;
|
||||
after?: string;
|
||||
before?: string;
|
||||
} = {}): { total: number; downloaded: number; cached: number; notDownloaded: number } {
|
||||
const totals = { total: 0, downloaded: 0, cached: 0, notDownloaded: 0 };
|
||||
|
||||
for (const meta of this.engine.list()) {
|
||||
if (meta.state === "deleted") continue;
|
||||
if (options.channelId && meta.channelId !== options.channelId) continue;
|
||||
|
||||
const store = new ScrapeStore(scrapeDir(this.rootDir, meta.id));
|
||||
try {
|
||||
const stats = store.stats(options);
|
||||
totals.total += stats.total;
|
||||
totals.downloaded += stats.downloaded;
|
||||
totals.cached += stats.cached;
|
||||
totals.notDownloaded += stats.notDownloaded;
|
||||
} finally {
|
||||
store.close();
|
||||
}
|
||||
}
|
||||
|
||||
return totals;
|
||||
}
|
||||
|
||||
/** Distinct posters across every scrape, biggest first. */
|
||||
listAuthors(): Array<{ id: string | null; name: string | null; count: number }> {
|
||||
const totals = new Map<string, { id: string | null; name: string | null; count: number }>();
|
||||
@@ -532,6 +566,8 @@ export class MediaService {
|
||||
width: row.width,
|
||||
height: row.height,
|
||||
aspectRatio: row.aspect_ratio,
|
||||
contentType: row.content_type,
|
||||
durationSecs: row.duration_secs,
|
||||
url: row.url,
|
||||
localUrl:
|
||||
row.file_path && !meta.downloadDir
|
||||
|
||||
@@ -832,6 +832,29 @@ export function startWebUi(deps: WebUiDeps): WebUiHandle {
|
||||
})
|
||||
);
|
||||
|
||||
app.get(
|
||||
"/api/media/stats",
|
||||
wrap(async (req, res) => {
|
||||
const kind = ["image", "gif", "video", "audio"].includes(String(req.query.kind ?? ""))
|
||||
? (String(req.query.kind) as "image" | "gif" | "video" | "audio")
|
||||
: undefined;
|
||||
|
||||
res.json({
|
||||
ok: true,
|
||||
result: deps.media.stats({
|
||||
kind,
|
||||
source: req.query.source ? String(req.query.source) : undefined,
|
||||
channelId: req.query.channelId ? String(req.query.channelId) : undefined,
|
||||
authorId: req.query.authorId ? String(req.query.authorId) : undefined,
|
||||
aspect: req.query.aspect ? String(req.query.aspect) : undefined,
|
||||
resolution: req.query.resolution ? String(req.query.resolution) : undefined,
|
||||
after: req.query.after ? String(req.query.after) : undefined,
|
||||
before: req.query.before ? String(req.query.before) : undefined
|
||||
})
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
app.post(
|
||||
"/api/media/count",
|
||||
wrap(async (req, res) => {
|
||||
|
||||
Reference in New Issue
Block a user