mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
@@ -125,8 +125,12 @@ export function AssetPickerSheet({ open, onOpenChange, fileType, onSelect }) {
|
||||
useEffect(() => {
|
||||
if (!assets.length) return;
|
||||
|
||||
// Only request tokens for S3 assets we don't already have a URL for —
|
||||
// this prevents duplicate POST /tokens when the assets list triggers
|
||||
// this effect more than once per sheet open (e.g., two fetch effects
|
||||
// both reacting to open mounting, producing two assets updates).
|
||||
const s3Ids = assets
|
||||
.filter((a) => a.storage_provider === "s3" && !a.thumbnail_url && !a.file_url)
|
||||
.filter((a) => a.storage_provider === "s3" && !streamUrls[String(a.asset_id)])
|
||||
.map((a) => a.asset_id);
|
||||
|
||||
if (!s3Ids.length) return;
|
||||
@@ -135,10 +139,12 @@ export function AssetPickerSheet({ open, onOpenChange, fileType, onSelect }) {
|
||||
api.post("/admin/media/tokens", { asset_ids: s3Ids })
|
||||
.then(({ data }) => {
|
||||
if (cancelled) return;
|
||||
const tokens = data.data?.tokens ?? {};
|
||||
const tokens = data.data?.tokens ?? {};
|
||||
const thumbnails = data.data?.thumbnails ?? {};
|
||||
const urls = {};
|
||||
for (const [id, token] of Object.entries(tokens)) {
|
||||
urls[id] = `${STREAM_BASE}/${token}`;
|
||||
// Prefer presigned thumbnail URL (faster, direct); fall back to stream proxy
|
||||
urls[id] = thumbnails[id] ?? `${STREAM_BASE}/${token}`;
|
||||
}
|
||||
setStreamUrls((prev) => ({ ...prev, ...urls }));
|
||||
})
|
||||
@@ -177,7 +183,15 @@ export function AssetPickerSheet({ open, onOpenChange, fileType, onSelect }) {
|
||||
|
||||
const handleSelect = (asset) => {
|
||||
setSelected(asset.asset_id);
|
||||
onSelect(asset);
|
||||
// Pass the resolved stream/presigned URL as a second arg so callers
|
||||
// (e.g. badge image picker) can use the authenticated URL directly
|
||||
// rather than falling back to asset.file_url which is a private CDN
|
||||
// key that the browser cannot load without S3 credentials.
|
||||
const resolvedUrl = streamUrls[String(asset.asset_id)]
|
||||
?? asset.thumbnail_url
|
||||
?? asset.file_url
|
||||
?? null;
|
||||
onSelect(asset, resolvedUrl);
|
||||
onOpenChange(false);
|
||||
};
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* disabled? : boolean
|
||||
* placeholder?: string
|
||||
***********************************************************************************************************************************************************************/
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import api from '@/utils/api.util';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
@@ -60,7 +60,7 @@ export default function GroupMultiSelect({
|
||||
}, [groupsProp]);
|
||||
|
||||
// ── Position portal dropdown under trigger ────────────────────────────────
|
||||
useEffect(() => {
|
||||
useLayoutEffect(() => {
|
||||
if (!open || !triggerRef.current) return;
|
||||
|
||||
const reposition = () => {
|
||||
|
||||
Reference in New Issue
Block a user