mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-08 03:32:27 +09:00
fix(app): refresh favorite gif previews that point at a provider page (#1925)
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {inferFormatContentType, PREVIEW_FORMAT_PRIORITY} from '@fluxer/schema/src/domains/gif/GifMediaFormatKeys';
|
||||
|
||||
export interface FavoriteGifMediaFormat {
|
||||
src: string;
|
||||
proxy_src: string;
|
||||
@@ -17,41 +19,90 @@ export interface FavoriteGifEntry {
|
||||
placeholder: string | null;
|
||||
}
|
||||
|
||||
const PREVIEW_FORMAT_PRIORITY = ['webm', 'mp4', 'tinywebm', 'tinymp4', 'webp', 'gif', 'tinygif', 'nanogif'] as const;
|
||||
const VIDEO_FORMAT_KEYS = new Set(['webm', 'mp4', 'tinywebm', 'tinymp4']);
|
||||
const FORMAT_CONTENT_TYPES: Record<string, string> = {
|
||||
webm: 'video/webm',
|
||||
tinywebm: 'video/webm',
|
||||
mp4: 'video/mp4',
|
||||
tinymp4: 'video/mp4',
|
||||
webp: 'image/webp',
|
||||
gif: 'image/gif',
|
||||
tinygif: 'image/gif',
|
||||
nanogif: 'image/gif',
|
||||
};
|
||||
export const PREVIEW_TILE_CSS_WIDTH = 200;
|
||||
|
||||
const PLAYABLE_CLASS = 0;
|
||||
const STILL_CLASS = 1;
|
||||
const UNKNOWN_CLASS = 2;
|
||||
|
||||
interface PreviewCandidate {
|
||||
key: string;
|
||||
format: FavoriteGifMediaFormat;
|
||||
formatClass: number;
|
||||
priority: number;
|
||||
}
|
||||
|
||||
export interface PreviewTileBox {
|
||||
cssWidth?: number;
|
||||
devicePixelRatio?: number;
|
||||
}
|
||||
|
||||
function isUsablePreviewFormat(format: FavoriteGifMediaFormat | undefined): format is FavoriteGifMediaFormat {
|
||||
return Boolean(format?.src && format.proxy_src && format.width > 0 && format.height > 0);
|
||||
}
|
||||
|
||||
function previewFormatClass(formatKey: string): number {
|
||||
const contentType = inferFormatContentType(formatKey);
|
||||
if (contentType.startsWith('video/')) return PLAYABLE_CLASS;
|
||||
if (contentType.startsWith('image/')) return STILL_CLASS;
|
||||
return UNKNOWN_CLASS;
|
||||
}
|
||||
|
||||
function previewFormatPriority(formatKey: string): number {
|
||||
const index = PREVIEW_FORMAT_PRIORITY.indexOf(formatKey as (typeof PREVIEW_FORMAT_PRIORITY)[number]);
|
||||
return index === -1 ? PREVIEW_FORMAT_PRIORITY.length : index;
|
||||
}
|
||||
|
||||
function currentDevicePixelRatio(): number {
|
||||
const ratio = (globalThis as {devicePixelRatio?: number}).devicePixelRatio;
|
||||
return typeof ratio === 'number' && Number.isFinite(ratio) && ratio > 0 ? ratio : 1;
|
||||
}
|
||||
|
||||
export function previewTileDeviceWidth(tile: PreviewTileBox = {}): number {
|
||||
return (tile.cssWidth ?? PREVIEW_TILE_CSS_WIDTH) * (tile.devicePixelRatio ?? currentDevicePixelRatio());
|
||||
}
|
||||
|
||||
export function pickBestPreviewFormat(
|
||||
media: Record<string, FavoriteGifMediaFormat> | null | undefined,
|
||||
kind: 'any' | 'image' = 'any',
|
||||
tile: PreviewTileBox = {},
|
||||
): {key: string; format: FavoriteGifMediaFormat} | null {
|
||||
if (!media) return null;
|
||||
const deviceWidth = previewTileDeviceWidth(tile);
|
||||
const candidates: Array<PreviewCandidate> = [];
|
||||
for (const [key, format] of Object.entries(media)) {
|
||||
if (!isUsablePreviewFormat(format)) continue;
|
||||
const formatClass = previewFormatClass(key);
|
||||
if (kind === 'image' && formatClass !== STILL_CLASS) continue;
|
||||
candidates.push({key, format, formatClass, priority: previewFormatPriority(key)});
|
||||
}
|
||||
if (candidates.length === 0) return null;
|
||||
const filling = candidates.filter((candidate) => candidate.format.width >= deviceWidth);
|
||||
const pool = filling.length > 0 ? filling : candidates;
|
||||
const widthDirection = filling.length > 0 ? 1 : -1;
|
||||
pool.sort(
|
||||
(a, b) =>
|
||||
a.formatClass - b.formatClass ||
|
||||
widthDirection * (a.format.width - b.format.width) ||
|
||||
a.priority - b.priority ||
|
||||
a.key.localeCompare(b.key),
|
||||
);
|
||||
const best = pool[0];
|
||||
return {key: best.key, format: best.format};
|
||||
}
|
||||
|
||||
export function pickCanonicalPreviewFormat(
|
||||
media: Record<string, FavoriteGifMediaFormat> | null | undefined,
|
||||
): {key: string; format: FavoriteGifMediaFormat} | null {
|
||||
if (!media) return null;
|
||||
for (const key of PREVIEW_FORMAT_PRIORITY) {
|
||||
const format = media[key];
|
||||
if (format?.src && format.proxy_src && format.width > 0 && format.height > 0) {
|
||||
return {key, format};
|
||||
}
|
||||
if (isUsablePreviewFormat(format)) return {key, format};
|
||||
}
|
||||
for (const [key, format] of Object.entries(media)) {
|
||||
if (format?.src && format.proxy_src && format.width > 0 && format.height > 0) {
|
||||
return {key, format};
|
||||
}
|
||||
if (isUsablePreviewFormat(format)) return {key, format};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function inferFormatContentType(formatKey: string): string {
|
||||
return FORMAT_CONTENT_TYPES[formatKey] ?? '';
|
||||
}
|
||||
|
||||
export function isVideoFormatKey(formatKey: string): boolean {
|
||||
return VIDEO_FORMAT_KEYS.has(formatKey);
|
||||
}
|
||||
export {inferFormatContentType};
|
||||
|
||||
@@ -5,10 +5,11 @@ import {Endpoints} from '@app/features/app/constants/Endpoints';
|
||||
import {
|
||||
type FavoriteGifEntry,
|
||||
inferFormatContentType,
|
||||
pickBestPreviewFormat,
|
||||
pickCanonicalPreviewFormat,
|
||||
} from '@app/features/channel/components/pickers/gif/FavoriteGifTypes';
|
||||
import FavoriteGif from '@app/features/expressions/state/FavoriteGif';
|
||||
import {FAVORITE_GIF_LIMIT_REACHED_DESCRIPTOR} from '@app/features/expressions/utils/FavoriteGifMessageDescriptors';
|
||||
import * as GifSlugUtils from '@app/features/expressions/utils/GifSlugUtils';
|
||||
import {http} from '@app/features/platform/transport/RestTransport';
|
||||
import {Logger} from '@app/features/platform/utils/AppLogger';
|
||||
import * as ModalCommands from '@app/features/ui/commands/ModalCommands';
|
||||
@@ -88,10 +89,10 @@ export function addFavoriteGif(i18n: I18n, entry: FavoriteGifEntry): void {
|
||||
|
||||
export function addFavoriteGifFromMedia(i18n: I18n, params: FavoriteGifFromMediaParams): void {
|
||||
const media = params.media ?? {};
|
||||
const bestPreview = pickBestPreviewFormat(media);
|
||||
const bestPreview = pickCanonicalPreviewFormat(media);
|
||||
addFavoriteGif(i18n, {
|
||||
url: params.url,
|
||||
proxy_url: bestPreview?.format.proxy_src ?? params.proxyUrl ?? params.url,
|
||||
proxy_url: bestPreview?.format.proxy_src ?? params.proxyUrl ?? '',
|
||||
width: bestPreview?.format.width ?? params.width ?? 0,
|
||||
height: bestPreview?.format.height ?? params.height ?? 0,
|
||||
media,
|
||||
@@ -147,7 +148,8 @@ export async function refreshFavoriteGifPreviews(): Promise<void> {
|
||||
}
|
||||
|
||||
function needsPreviewRefresh(entry: FavoriteGifEntry): boolean {
|
||||
if (pickBestPreviewFormat(entry.media)) return false;
|
||||
if (pickCanonicalPreviewFormat(entry.media)) return false;
|
||||
if (!entry.proxy_url || GifSlugUtils.isProviderPageUrl(entry.proxy_url)) return true;
|
||||
if (entry.width <= 0 || entry.height <= 0) return true;
|
||||
return !entry.content_type.startsWith('image/') && !entry.content_type.startsWith('video/');
|
||||
}
|
||||
|
||||
@@ -27,6 +27,17 @@ export function extractSlugFromUrl(provider: string, url: string): string | null
|
||||
return getAdapter(provider)?.extractSlugFromUrl(url) ?? null;
|
||||
}
|
||||
|
||||
export function isProviderPageUrl(url: string): boolean {
|
||||
if (!url) return false;
|
||||
return Object.values(PROVIDER_URL_ADAPTERS).some((adapter) => adapter.extractSlugFromUrl(url) != null);
|
||||
}
|
||||
|
||||
export function isUsableMediaSource(url: string): boolean {
|
||||
if (!url) return false;
|
||||
if (!/^https?:\/\//i.test(url.trim())) return false;
|
||||
return !isProviderPageUrl(url);
|
||||
}
|
||||
|
||||
function looksLikeUrl(value: string): boolean {
|
||||
const trimmed = value.trim();
|
||||
return /^https?:\/\//i.test(trimmed);
|
||||
|
||||
@@ -98,7 +98,7 @@ export function useMediaFavorite({
|
||||
} else {
|
||||
FavoriteGifCommands.addFavoriteGifFromMedia(i18n, {
|
||||
url: embedURL!,
|
||||
proxyUrl: proxyURL ?? embedURL!,
|
||||
proxyUrl: proxyURL,
|
||||
width: naturalWidth ?? 0,
|
||||
height: naturalHeight ?? 0,
|
||||
media: {},
|
||||
|
||||
Reference in New Issue
Block a user