mirror of
https://github.com/yusufipk/OpenFrame.git
synced 2026-09-11 09:36:08 +00:00
refactor: eslint and prettier conflict will be resolved and formatted
This commit is contained in:
@@ -64,45 +64,54 @@ export function useVideoAssets({
|
||||
const assetsEtagRef = useRef<string | null>(null);
|
||||
const isMutatingRef = useRef(false);
|
||||
|
||||
const fetchAssets = useCallback(async (options?: { useEtag?: boolean; silent?: boolean }) => {
|
||||
const useEtag = options?.useEtag ?? false;
|
||||
const silent = options?.silent ?? false;
|
||||
if (!silent) setIsLoadingAssets(true);
|
||||
try {
|
||||
const headers: HeadersInit = {};
|
||||
if (useEtag && assetsEtagRef.current) {
|
||||
headers['If-None-Match'] = assetsEtagRef.current;
|
||||
}
|
||||
const res = await fetch(`/api/videos/${videoId}/assets?limit=${ASSET_PAGE_SIZE}&offset=0`, { cache: 'no-store', headers });
|
||||
if (res.status === 304) return;
|
||||
const payload = (await res.json().catch(() => null)) as AssetsListResponse | null;
|
||||
if (!res.ok) {
|
||||
if (!silent) {
|
||||
toast.error(payload?.error || 'Failed to fetch assets');
|
||||
const fetchAssets = useCallback(
|
||||
async (options?: { useEtag?: boolean; silent?: boolean }) => {
|
||||
const useEtag = options?.useEtag ?? false;
|
||||
const silent = options?.silent ?? false;
|
||||
if (!silent) setIsLoadingAssets(true);
|
||||
try {
|
||||
const headers: HeadersInit = {};
|
||||
if (useEtag && assetsEtagRef.current) {
|
||||
headers['If-None-Match'] = assetsEtagRef.current;
|
||||
}
|
||||
return;
|
||||
const res = await fetch(`/api/videos/${videoId}/assets?limit=${ASSET_PAGE_SIZE}&offset=0`, {
|
||||
cache: 'no-store',
|
||||
headers,
|
||||
});
|
||||
if (res.status === 304) return;
|
||||
const payload = (await res.json().catch(() => null)) as AssetsListResponse | null;
|
||||
if (!res.ok) {
|
||||
if (!silent) {
|
||||
toast.error(payload?.error || 'Failed to fetch assets');
|
||||
}
|
||||
return;
|
||||
}
|
||||
const etag = res.headers.get('etag');
|
||||
if (etag) assetsEtagRef.current = etag;
|
||||
const list = Array.isArray(payload?.data?.assets) ? payload.data.assets : [];
|
||||
const pagination = payload?.data?.pagination;
|
||||
setAssets(list);
|
||||
setHasMoreAssets(!!pagination?.hasMore);
|
||||
setNextAssetsOffset(typeof pagination?.nextOffset === 'number' ? pagination.nextOffset : 0);
|
||||
} catch {
|
||||
if (!silent) {
|
||||
toast.error('Failed to fetch assets');
|
||||
}
|
||||
} finally {
|
||||
if (!silent) setIsLoadingAssets(false);
|
||||
}
|
||||
const etag = res.headers.get('etag');
|
||||
if (etag) assetsEtagRef.current = etag;
|
||||
const list = Array.isArray(payload?.data?.assets) ? payload.data.assets : [];
|
||||
const pagination = payload?.data?.pagination;
|
||||
setAssets(list);
|
||||
setHasMoreAssets(!!pagination?.hasMore);
|
||||
setNextAssetsOffset(typeof pagination?.nextOffset === 'number' ? pagination.nextOffset : 0);
|
||||
} catch {
|
||||
if (!silent) {
|
||||
toast.error('Failed to fetch assets');
|
||||
}
|
||||
} finally {
|
||||
if (!silent) setIsLoadingAssets(false);
|
||||
}
|
||||
}, [videoId]);
|
||||
},
|
||||
[videoId]
|
||||
);
|
||||
|
||||
const loadMoreAssets = useCallback(async () => {
|
||||
if (isLoadingMoreAssets || !hasMoreAssets) return;
|
||||
setIsLoadingMoreAssets(true);
|
||||
try {
|
||||
const res = await fetch(`/api/videos/${videoId}/assets?limit=${ASSET_PAGE_SIZE}&offset=${nextAssetsOffset}`, { cache: 'no-store' });
|
||||
const res = await fetch(
|
||||
`/api/videos/${videoId}/assets?limit=${ASSET_PAGE_SIZE}&offset=${nextAssetsOffset}`,
|
||||
{ cache: 'no-store' }
|
||||
);
|
||||
const payload = (await res.json().catch(() => null)) as AssetsListResponse | null;
|
||||
if (!res.ok) {
|
||||
toast.error(payload?.error || 'Failed to load more assets');
|
||||
@@ -110,7 +119,10 @@ export function useVideoAssets({
|
||||
}
|
||||
const list = Array.isArray(payload?.data?.assets) ? payload.data.assets : [];
|
||||
const pagination = payload?.data?.pagination;
|
||||
setAssets((prev) => [...prev, ...list.filter((asset) => !prev.some((existing) => existing.id === asset.id))]);
|
||||
setAssets((prev) => [
|
||||
...prev,
|
||||
...list.filter((asset) => !prev.some((existing) => existing.id === asset.id)),
|
||||
]);
|
||||
setHasMoreAssets(!!pagination?.hasMore);
|
||||
setNextAssetsOffset(typeof pagination?.nextOffset === 'number' ? pagination.nextOffset : 0);
|
||||
} catch {
|
||||
@@ -145,118 +157,135 @@ export function useVideoAssets({
|
||||
};
|
||||
}, [fetchAssets, isLoadingMoreAssets]);
|
||||
|
||||
const createAsset = useCallback(async (payload: CreateAssetPayload): Promise<VideoAsset | null> => {
|
||||
if (!canUploadAssets) {
|
||||
toast.error('You do not have permission to upload assets');
|
||||
return null;
|
||||
}
|
||||
|
||||
setIsCreatingAsset(true);
|
||||
isMutatingRef.current = true;
|
||||
try {
|
||||
const res = await fetch(`/api/videos/${videoId}/assets`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
...payload,
|
||||
...(isAuthenticated ? {} : { guestName: guestName?.trim() || 'Guest' }),
|
||||
}),
|
||||
});
|
||||
const body = (await res.json().catch(() => null)) as AssetCreateResponse | null;
|
||||
if (!res.ok || !body?.data) {
|
||||
toast.error(body?.error || 'Failed to create asset');
|
||||
const createAsset = useCallback(
|
||||
async (payload: CreateAssetPayload): Promise<VideoAsset | null> => {
|
||||
if (!canUploadAssets) {
|
||||
toast.error('You do not have permission to upload assets');
|
||||
return null;
|
||||
}
|
||||
|
||||
setAssets((prev) => [body.data!, ...prev]);
|
||||
setNextAssetsOffset((prev) => prev + 1);
|
||||
return body.data;
|
||||
} catch {
|
||||
toast.error('Failed to create asset');
|
||||
return null;
|
||||
} finally {
|
||||
setIsCreatingAsset(false);
|
||||
isMutatingRef.current = false;
|
||||
}
|
||||
}, [canUploadAssets, videoId, isAuthenticated, guestName]);
|
||||
|
||||
const deleteAsset = useCallback(async (assetId: string) => {
|
||||
setActiveDeleteAssetId(assetId);
|
||||
isMutatingRef.current = true;
|
||||
try {
|
||||
const res = await fetch(`/api/videos/${videoId}/assets/${assetId}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
const payload = (await res.json().catch(() => null)) as { error?: string } | null;
|
||||
if (!res.ok) {
|
||||
toast.error(payload?.error || 'Failed to delete asset');
|
||||
return false;
|
||||
}
|
||||
|
||||
setAssets((prev) => prev.filter((asset) => asset.id !== assetId));
|
||||
setNextAssetsOffset((prev) => Math.max(0, prev - 1));
|
||||
return true;
|
||||
} catch {
|
||||
toast.error('Failed to delete asset');
|
||||
return false;
|
||||
} finally {
|
||||
setActiveDeleteAssetId(null);
|
||||
isMutatingRef.current = false;
|
||||
}
|
||||
}, [videoId]);
|
||||
|
||||
const downloadAsset = useCallback(async (asset: VideoAsset, preference: BunnyDownloadPreference = 'compressed') => {
|
||||
if (!canDownloadAssets) {
|
||||
toast.error('Asset downloads require an authenticated account');
|
||||
return;
|
||||
}
|
||||
if (asset.provider === 'YOUTUBE') {
|
||||
toast.error('YouTube assets cannot be downloaded');
|
||||
return;
|
||||
}
|
||||
|
||||
setActiveDownloadAssetId(asset.id);
|
||||
try {
|
||||
let downloadUrl = `/api/videos/${videoId}/assets/${asset.id}/download`;
|
||||
|
||||
if (asset.provider === 'BUNNY') {
|
||||
const prepareRes = await fetch(`${downloadUrl}?source=${preference}&prepare=1`, { cache: 'no-store' });
|
||||
const prepareBody = (await prepareRes.json().catch(() => null)) as { error?: string } | null;
|
||||
if (!prepareRes.ok) {
|
||||
toast.error(prepareBody?.error || 'Download is not available');
|
||||
return;
|
||||
setIsCreatingAsset(true);
|
||||
isMutatingRef.current = true;
|
||||
try {
|
||||
const res = await fetch(`/api/videos/${videoId}/assets`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
...payload,
|
||||
...(isAuthenticated ? {} : { guestName: guestName?.trim() || 'Guest' }),
|
||||
}),
|
||||
});
|
||||
const body = (await res.json().catch(() => null)) as AssetCreateResponse | null;
|
||||
if (!res.ok || !body?.data) {
|
||||
toast.error(body?.error || 'Failed to create asset');
|
||||
return null;
|
||||
}
|
||||
downloadUrl = `${downloadUrl}?source=${preference}`;
|
||||
|
||||
setAssets((prev) => [body.data!, ...prev]);
|
||||
setNextAssetsOffset((prev) => prev + 1);
|
||||
return body.data;
|
||||
} catch {
|
||||
toast.error('Failed to create asset');
|
||||
return null;
|
||||
} finally {
|
||||
setIsCreatingAsset(false);
|
||||
isMutatingRef.current = false;
|
||||
}
|
||||
},
|
||||
[canUploadAssets, videoId, isAuthenticated, guestName]
|
||||
);
|
||||
|
||||
const deleteAsset = useCallback(
|
||||
async (assetId: string) => {
|
||||
setActiveDeleteAssetId(assetId);
|
||||
isMutatingRef.current = true;
|
||||
try {
|
||||
const res = await fetch(`/api/videos/${videoId}/assets/${assetId}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
const payload = (await res.json().catch(() => null)) as { error?: string } | null;
|
||||
if (!res.ok) {
|
||||
toast.error(payload?.error || 'Failed to delete asset');
|
||||
return false;
|
||||
}
|
||||
|
||||
setAssets((prev) => prev.filter((asset) => asset.id !== assetId));
|
||||
setNextAssetsOffset((prev) => Math.max(0, prev - 1));
|
||||
return true;
|
||||
} catch {
|
||||
toast.error('Failed to delete asset');
|
||||
return false;
|
||||
} finally {
|
||||
setActiveDeleteAssetId(null);
|
||||
isMutatingRef.current = false;
|
||||
}
|
||||
},
|
||||
[videoId]
|
||||
);
|
||||
|
||||
const downloadAsset = useCallback(
|
||||
async (asset: VideoAsset, preference: BunnyDownloadPreference = 'compressed') => {
|
||||
if (!canDownloadAssets) {
|
||||
toast.error('Asset downloads require an authenticated account');
|
||||
return;
|
||||
}
|
||||
if (asset.provider === 'YOUTUBE') {
|
||||
toast.error('YouTube assets cannot be downloaded');
|
||||
return;
|
||||
}
|
||||
|
||||
const a = document.createElement('a');
|
||||
a.href = downloadUrl;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
} catch {
|
||||
toast.error('Failed to start download');
|
||||
} finally {
|
||||
setActiveDownloadAssetId(null);
|
||||
}
|
||||
}, [canDownloadAssets, videoId]);
|
||||
setActiveDownloadAssetId(asset.id);
|
||||
try {
|
||||
let downloadUrl = `/api/videos/${videoId}/assets/${asset.id}/download`;
|
||||
|
||||
const getGuestUploadToken = useCallback(async (intent: 'image' | 'audio') => {
|
||||
if (isAuthenticated) return null;
|
||||
const response = await fetch(`/api/watch/${videoId}/upload-token`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ intent }),
|
||||
});
|
||||
const payload = (await response.json().catch(() => null)) as
|
||||
| { data?: { token?: string }; error?: string }
|
||||
| null;
|
||||
const token = payload?.data?.token;
|
||||
if (!response.ok || !token) {
|
||||
throw new Error(payload?.error || 'Failed to prepare upload');
|
||||
}
|
||||
return token;
|
||||
}, [isAuthenticated, videoId]);
|
||||
if (asset.provider === 'BUNNY') {
|
||||
const prepareRes = await fetch(`${downloadUrl}?source=${preference}&prepare=1`, {
|
||||
cache: 'no-store',
|
||||
});
|
||||
const prepareBody = (await prepareRes.json().catch(() => null)) as {
|
||||
error?: string;
|
||||
} | null;
|
||||
if (!prepareRes.ok) {
|
||||
toast.error(prepareBody?.error || 'Download is not available');
|
||||
return;
|
||||
}
|
||||
downloadUrl = `${downloadUrl}?source=${preference}`;
|
||||
}
|
||||
|
||||
const a = document.createElement('a');
|
||||
a.href = downloadUrl;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
} catch {
|
||||
toast.error('Failed to start download');
|
||||
} finally {
|
||||
setActiveDownloadAssetId(null);
|
||||
}
|
||||
},
|
||||
[canDownloadAssets, videoId]
|
||||
);
|
||||
|
||||
const getGuestUploadToken = useCallback(
|
||||
async (intent: 'image' | 'audio') => {
|
||||
if (isAuthenticated) return null;
|
||||
const response = await fetch(`/api/watch/${videoId}/upload-token`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ intent }),
|
||||
});
|
||||
const payload = (await response.json().catch(() => null)) as {
|
||||
data?: { token?: string };
|
||||
error?: string;
|
||||
} | null;
|
||||
const token = payload?.data?.token;
|
||||
if (!response.ok || !token) {
|
||||
throw new Error(payload?.error || 'Failed to prepare upload');
|
||||
}
|
||||
return token;
|
||||
},
|
||||
[isAuthenticated, videoId]
|
||||
);
|
||||
|
||||
return {
|
||||
assets,
|
||||
|
||||
Reference in New Issue
Block a user