fix and update
This commit is contained in:
@@ -152,10 +152,12 @@ interface PresignedUploadResponse {
|
||||
}
|
||||
|
||||
interface LoadedChatMedia {
|
||||
blobUrl: string;
|
||||
expiresAt: number;
|
||||
blobUrl?: string;
|
||||
expiresAt?: number;
|
||||
fileName?: string;
|
||||
mimeType?: string;
|
||||
status: 'loading' | 'ready' | 'error';
|
||||
errorMessage?: string;
|
||||
}
|
||||
|
||||
function formatMessageTime(value: string) {
|
||||
@@ -318,6 +320,7 @@ export function FamilyGroupView({ groupId }: { groupId: string }) {
|
||||
const mediaRecorderRef = useRef<MediaRecorder | null>(null);
|
||||
const recordingStartedAtRef = useRef<number | null>(null);
|
||||
const mediaUrlsRef = useRef<Record<string, LoadedChatMedia>>({});
|
||||
const mediaLoadInFlightRef = useRef<Set<string>>(new Set());
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const dragDepthRef = useRef(0);
|
||||
|
||||
@@ -678,7 +681,37 @@ export function FamilyGroupView({ groupId }: { groupId: string }) {
|
||||
if (!token || !activeRoomId || !message.storageKey) return;
|
||||
const storageKey = message.storageKey;
|
||||
const existing = mediaUrlsRef.current[storageKey];
|
||||
if (!force && existing && !shouldRefreshMedia(existing.expiresAt)) return;
|
||||
if (!force && existing?.status === 'ready' && existing.blobUrl && existing.expiresAt && !shouldRefreshMedia(existing.expiresAt)) {
|
||||
return;
|
||||
}
|
||||
if (mediaLoadInFlightRef.current.has(storageKey) && !force) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.isEncrypted && activeRoomIsE2E) {
|
||||
if (peerKeyLoading) return;
|
||||
if (!peerE2eKey || !user?.id || !message.content) {
|
||||
setMediaUrls((current) => ({
|
||||
...current,
|
||||
[storageKey]: {
|
||||
...current[storageKey],
|
||||
status: 'error',
|
||||
errorMessage: 'Не удалось расшифровать файл'
|
||||
}
|
||||
}));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
mediaLoadInFlightRef.current.add(storageKey);
|
||||
setMediaUrls((current) => ({
|
||||
...current,
|
||||
[storageKey]: {
|
||||
...current[storageKey],
|
||||
status: 'loading',
|
||||
errorMessage: undefined
|
||||
}
|
||||
}));
|
||||
|
||||
let meta = parseMessageMetadata(message.metadataJson);
|
||||
let fileName = meta.fileName || message.content || undefined;
|
||||
@@ -709,23 +742,50 @@ export function FamilyGroupView({ groupId }: { groupId: string }) {
|
||||
blobUrl,
|
||||
expiresAt: mediaExpiresAtMs(access.expiresAt),
|
||||
fileName,
|
||||
mimeType
|
||||
mimeType,
|
||||
status: 'ready'
|
||||
}
|
||||
};
|
||||
});
|
||||
} catch {
|
||||
// ignore failed media loads in background
|
||||
} catch (error) {
|
||||
setMediaUrls((current) => ({
|
||||
...current,
|
||||
[storageKey]: {
|
||||
...current[storageKey],
|
||||
status: 'error',
|
||||
errorMessage: error instanceof Error ? error.message : 'Не удалось загрузить файл'
|
||||
}
|
||||
}));
|
||||
} finally {
|
||||
mediaLoadInFlightRef.current.delete(storageKey);
|
||||
}
|
||||
},
|
||||
[activeRoomId, activeRoomIsE2E, peerE2eKey, token, user?.id]
|
||||
[activeRoomId, activeRoomIsE2E, peerE2eKey, peerKeyLoading, token, user?.id]
|
||||
);
|
||||
|
||||
const cacheUploadedMedia = useCallback((storageKey: string, file: File) => {
|
||||
const blobUrl = createBlobObjectUrl(file);
|
||||
setMediaUrls((current) => {
|
||||
revokeBlobObjectUrl(current[storageKey]?.blobUrl);
|
||||
return {
|
||||
...current,
|
||||
[storageKey]: {
|
||||
blobUrl,
|
||||
expiresAt: Date.now() + 30 * 60_000,
|
||||
fileName: file.name,
|
||||
mimeType: file.type || resolveChatContentType(file),
|
||||
status: 'ready'
|
||||
}
|
||||
};
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!token || !activeRoomId) return;
|
||||
messages.forEach((message) => {
|
||||
if (message.storageKey) void loadChatMedia(message);
|
||||
});
|
||||
}, [activeRoomId, loadChatMedia, messages, token]);
|
||||
}, [activeRoomId, loadChatMedia, messages, peerE2eKey, peerKeyLoading, token]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!token || !activeRoomId) return;
|
||||
@@ -733,7 +793,7 @@ export function FamilyGroupView({ groupId }: { groupId: string }) {
|
||||
messages.forEach((message) => {
|
||||
if (!message.storageKey) return;
|
||||
const cached = mediaUrlsRef.current[message.storageKey];
|
||||
if (cached && shouldRefreshMedia(cached.expiresAt)) {
|
||||
if (cached?.status === 'ready' && cached.expiresAt && shouldRefreshMedia(cached.expiresAt)) {
|
||||
void loadChatMedia(message, true);
|
||||
}
|
||||
});
|
||||
@@ -1323,6 +1383,7 @@ export function FamilyGroupView({ groupId }: { groupId: string }) {
|
||||
token
|
||||
);
|
||||
appendMessage(message);
|
||||
cacheUploadedMedia(presigned.storageKey, file);
|
||||
void loadGroup();
|
||||
} catch (error) {
|
||||
showToast(getApiErrorMessage(error, 'Не удалось отправить файл') ?? 'Ошибка');
|
||||
@@ -1366,7 +1427,13 @@ export function FamilyGroupView({ groupId }: { groupId: string }) {
|
||||
peerE2eKey,
|
||||
encryptOutgoing
|
||||
});
|
||||
uploaded.forEach((message) => appendMessage(message));
|
||||
uploaded.forEach((message, index) => {
|
||||
appendMessage(message);
|
||||
const sourceFile = mediaComposer.items[index]?.file;
|
||||
if (message.storageKey && sourceFile) {
|
||||
cacheUploadedMedia(message.storageKey, sourceFile);
|
||||
}
|
||||
});
|
||||
mediaComposer.close();
|
||||
void loadGroup();
|
||||
} catch (error) {
|
||||
@@ -1972,7 +2039,21 @@ export function FamilyGroupView({ groupId }: { groupId: string }) {
|
||||
const media = mediaUrls[message.storageKey];
|
||||
const meta = parseMessageMetadata(message.metadataJson);
|
||||
const fileName = e2ePayload?.fileName || meta.fileName || message.content || 'Файл';
|
||||
if (!media?.blobUrl) {
|
||||
if (media?.status === 'error') {
|
||||
return (
|
||||
<div className="space-y-2 text-sm text-[#667085]">
|
||||
<p>{media.errorMessage ?? 'Не удалось загрузить файл'}</p>
|
||||
<button
|
||||
type="button"
|
||||
className="font-medium text-[#3390ec] hover:underline"
|
||||
onClick={() => void loadChatMedia(message, true)}
|
||||
>
|
||||
Повторить
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (!media?.blobUrl || media.status === 'loading') {
|
||||
return (
|
||||
<div className="flex items-center gap-2 text-sm text-[#667085]">
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
@@ -2275,7 +2356,7 @@ export function FamilyGroupView({ groupId }: { groupId: string }) {
|
||||
type="file"
|
||||
className="hidden"
|
||||
multiple
|
||||
accept="image/*,video/*,audio/*,.pdf,.doc,.docx,.xls,.xlsx,.zip,.rar,.7z,.txt"
|
||||
accept="*/*"
|
||||
onChange={(event) => {
|
||||
const files = Array.from(event.target.files ?? []);
|
||||
if (files.length) queueMediaFiles(files);
|
||||
|
||||
Reference in New Issue
Block a user