chunk upload, modals, env update
This commit is contained in:
parent
7cb7a7d73a
commit
1c1ee5df16
|
|
@ -16,6 +16,7 @@
|
|||
"axios": "^1.6.7",
|
||||
"buffer": "^6.0.3",
|
||||
"clsx": "^2.1.0",
|
||||
"jssha": "^3.3.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hook-form": "^7.51.0",
|
||||
|
|
@ -1437,6 +1438,26 @@
|
|||
"jssha": "3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@ton/crypto-primitives/node_modules/jssha": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/jssha/-/jssha-3.2.0.tgz",
|
||||
"integrity": "sha512-QuruyBENDWdN4tZwJbQq7/eAK85FqrI4oDbXjy5IBhYD+2pTJyBUWZe8ctWaCkrV0gy6AaelgOZZBMeswEa/6Q==",
|
||||
"license": "BSD-3-Clause",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@ton/crypto/node_modules/jssha": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/jssha/-/jssha-3.2.0.tgz",
|
||||
"integrity": "sha512-QuruyBENDWdN4tZwJbQq7/eAK85FqrI4oDbXjy5IBhYD+2pTJyBUWZe8ctWaCkrV0gy6AaelgOZZBMeswEa/6Q==",
|
||||
"license": "BSD-3-Clause",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@tonconnect/isomorphic-eventsource": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@tonconnect/isomorphic-eventsource/-/isomorphic-eventsource-0.0.2.tgz",
|
||||
|
|
@ -4202,11 +4223,10 @@
|
|||
}
|
||||
},
|
||||
"node_modules/jssha": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/jssha/-/jssha-3.2.0.tgz",
|
||||
"integrity": "sha512-QuruyBENDWdN4tZwJbQq7/eAK85FqrI4oDbXjy5IBhYD+2pTJyBUWZe8ctWaCkrV0gy6AaelgOZZBMeswEa/6Q==",
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/jssha/-/jssha-3.3.1.tgz",
|
||||
"integrity": "sha512-VCMZj12FCFMQYcFLPRm/0lOBbLi8uM2BhXPTqw3U4YAfs4AZfiApOoBLoN8cQE60Z50m1MYMTQVCfgF/KaCVhQ==",
|
||||
"license": "BSD-3-Clause",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
interface ImportMetaEnv {
|
||||
readonly VITE_SENTRY_DSN: string
|
||||
readonly VITE_API_BASE_URL: string
|
||||
readonly VITE_API_BASE_STORAGE_URL: string
|
||||
readonly MODE: 'development' | 'production'
|
||||
readonly PROD: boolean
|
||||
readonly DEV: boolean
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { useHapticFeedback } from "@vkruglikov/react-telegram-web-app";
|
||||
import { useHapticFeedback, useWebApp } from "@vkruglikov/react-telegram-web-app";
|
||||
import { useEffect } from "react";
|
||||
import { Button } from "~/shared/ui/button";
|
||||
|
||||
type DisclaimerModalProps = {
|
||||
|
|
@ -9,6 +10,20 @@ export const DisclaimerModal = ({
|
|||
onConfirm,
|
||||
}: DisclaimerModalProps) => {
|
||||
const [impactOccurred] = useHapticFeedback();
|
||||
const WebApp = useWebApp();
|
||||
useEffect(() => {
|
||||
// Отключаем вертикальные свайпы при монтировании компонента
|
||||
if (WebApp && WebApp.disableVerticalSwipes) {
|
||||
WebApp.disableVerticalSwipes();
|
||||
}
|
||||
|
||||
// Включаем вертикальные свайпы обратно при размонтировании
|
||||
return () => {
|
||||
if (WebApp && WebApp.enableVerticalSwipes) {
|
||||
WebApp.enableVerticalSwipes();
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleClick = (fn: () => void) => {
|
||||
impactOccurred("light");
|
||||
|
|
@ -21,16 +36,17 @@ export const DisclaimerModal = ({
|
|||
"fixed left-0 top-0 z-30 flex h-full w-full items-center justify-center bg-black/80 px-[15px]"
|
||||
}
|
||||
>
|
||||
<div className={"flex flex-col gap-[30px]"}>
|
||||
<div className={"flex flex-col max-h-[80vh] w-[85vw] max-w-md"}>
|
||||
<div
|
||||
className={
|
||||
"border border-white bg-[#1D1D1B] px-[10px] py-[16px] text-start flex flex-col gap-12"
|
||||
"border border-white bg-[#1D1D1B] px-[15px] py-[16px] text-start flex flex-col gap-12 h-full overflow-y-auto"
|
||||
}
|
||||
>
|
||||
<p className="mt-4">
|
||||
«Внимание!
|
||||
<div className="flex flex-col gap-6">
|
||||
<p className="mt-2">
|
||||
Внимание!
|
||||
</p>
|
||||
<p className="">
|
||||
<p>
|
||||
MY снимает с себя ответственность за правомерность загрузки контента пользователем.
|
||||
</p>
|
||||
<p className="flex flex-col">
|
||||
|
|
@ -43,18 +59,17 @@ export const DisclaimerModal = ({
|
|||
<span className="tracking-[.25em] w-full">
|
||||
Перед загрузкой контента
|
||||
</span>
|
||||
необходимо убедиться, что первые 30 секунд контента, которые будут использоваться для превью, не содержат материалов, нарушающих возрастное ограничение 18+»
|
||||
необходимо убедиться, что первые 30 секунд контента, которые будут использоваться для превью, не содержат материалов, нарушающих возрастное ограничение 18+
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
className={"mt-[20px]"}
|
||||
className={"mt-[20px] sticky bottom-0"}
|
||||
label={"Принять и продолжить"}
|
||||
includeArrows={false}
|
||||
onClick={() => handleClick(onConfirm)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
import { useHapticFeedback, useWebApp } from "@vkruglikov/react-telegram-web-app";
|
||||
import { useEffect } from "react";
|
||||
import { Button } from "~/shared/ui/button";
|
||||
|
||||
type ErrorUploadProps = {
|
||||
onConfirm(): void;
|
||||
};
|
||||
|
||||
export const ErrorUploadModal = ({
|
||||
onConfirm,
|
||||
}: ErrorUploadProps) => {
|
||||
const [impactOccurred] = useHapticFeedback();
|
||||
const WebApp = useWebApp();
|
||||
useEffect(() => {
|
||||
// Отключаем вертикальные свайпы при монтировании компонента
|
||||
if (WebApp && WebApp.disableVerticalSwipes) {
|
||||
WebApp.disableVerticalSwipes();
|
||||
}
|
||||
|
||||
// Включаем вертикальные свайпы обратно при размонтировании
|
||||
return () => {
|
||||
if (WebApp && WebApp.enableVerticalSwipes) {
|
||||
WebApp.enableVerticalSwipes();
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleClick = (fn: () => void) => {
|
||||
impactOccurred("light");
|
||||
fn();
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
"fixed left-0 top-0 z-30 flex h-full w-full items-center justify-center bg-black/80 px-[15px]"
|
||||
}
|
||||
>
|
||||
<div className={"flex flex-col max-h-[80vh] w-[85vw] max-w-md"}>
|
||||
<div
|
||||
className={
|
||||
"border border-white bg-[#1D1D1B] px-[15px] py-[16px] text-start flex flex-col gap-12 h-full overflow-y-auto"
|
||||
}
|
||||
>
|
||||
<div className="flex flex-col gap-6">
|
||||
<p className="mt-2">
|
||||
Внимание!
|
||||
</p>
|
||||
<p>
|
||||
Произошла ошибка при загрузке видео.
|
||||
</p>
|
||||
<p className="flex flex-col">
|
||||
<span className="tracking-[.25em] w-full">
|
||||
Загрузка не завершена
|
||||
</span>
|
||||
из-за технических проблем или превышения допустимых ограничений. Вы можете попробовать загрузить файл ещё раз или выбрать другой файл.
|
||||
</p>
|
||||
<p className="flex flex-col">
|
||||
<span className="tracking-[.25em] w-full">
|
||||
Если проблема повторяется
|
||||
</span>
|
||||
обратитесь в техническую поддержку сервиса MY, предоставив подробную информацию о загружаемом файле и возникшей ошибке.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
className={"mt-[20px] sticky bottom-0"}
|
||||
label={"Понятно"}
|
||||
includeArrows={false}
|
||||
onClick={() => handleClick(onConfirm)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -2,7 +2,7 @@ import {
|
|||
useHapticFeedback,
|
||||
useWebApp,
|
||||
} from "@vkruglikov/react-telegram-web-app";
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import ReactPlayer from "react-player/lazy";
|
||||
|
||||
import { Button } from "~/shared/ui/button";
|
||||
|
|
@ -13,6 +13,7 @@ import { Progress } from "~/shared/ui/progress";
|
|||
import { useCreateNewContent } from "~/shared/services/content";
|
||||
import { BackButton } from "~/shared/ui/back-button";
|
||||
import { useTonConnectUI } from "@tonconnect/ui-react";
|
||||
import { ErrorUploadModal } from "./components/error-upload-modal";
|
||||
|
||||
|
||||
|
||||
|
|
@ -35,6 +36,20 @@ export const PresubmitStep = ({ prevStep }: PresubmitStepProps) => {
|
|||
|
||||
const createContent = useCreateNewContent();
|
||||
|
||||
const [isErrorUploadModal, setIsErrorUploadModal] = useState(false);
|
||||
|
||||
const handleErrorUploadModal = () => {
|
||||
setIsErrorUploadModal(false);
|
||||
uploadFile.resetUploadError();
|
||||
uploadCover.resetUploadError();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (uploadFile.uploadError || uploadCover.uploadError) {
|
||||
setIsErrorUploadModal(true);
|
||||
}
|
||||
}, [uploadFile.uploadError, uploadCover.uploadError]);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
let coverUploadResult = { content_id_v1: "" };
|
||||
|
|
@ -119,6 +134,11 @@ export const PresubmitStep = ({ prevStep }: PresubmitStepProps) => {
|
|||
|
||||
return (
|
||||
<section className={"mt-4 px-4 pb-8"}>
|
||||
{isErrorUploadModal && (
|
||||
<ErrorUploadModal
|
||||
onConfirm={() => {
|
||||
handleErrorUploadModal()}}
|
||||
/>)}
|
||||
<BackButton onClick={prevStep} />
|
||||
|
||||
<div className={"mb-[30px] flex flex-col text-sm"}>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { useHapticFeedback } from "@vkruglikov/react-telegram-web-app";
|
||||
import { useHapticFeedback, useWebApp } from "@vkruglikov/react-telegram-web-app";
|
||||
import { useEffect } from "react";
|
||||
import { Button } from "~/shared/ui/button";
|
||||
|
||||
type CongratsModalProps = {
|
||||
|
|
@ -9,25 +10,38 @@ export const CongratsModal = ({
|
|||
onConfirm,
|
||||
}: CongratsModalProps) => {
|
||||
const [impactOccurred] = useHapticFeedback();
|
||||
const WebApp = useWebApp();
|
||||
|
||||
const handleClick = (fn: () => void) => {
|
||||
impactOccurred("light");
|
||||
fn();
|
||||
};
|
||||
useEffect(() => {
|
||||
// Отключаем вертикальные свайпы при монтировании компонента
|
||||
if (WebApp && WebApp.disableVerticalSwipes) {
|
||||
WebApp.disableVerticalSwipes();
|
||||
}
|
||||
|
||||
// Включаем вертикальные свайпы обратно при размонтировании
|
||||
return () => {
|
||||
if (WebApp && WebApp.enableVerticalSwipes) {
|
||||
WebApp.enableVerticalSwipes();
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
"fixed left-0 top-0 z-30 flex h-full w-full items-center justify-center bg-black/80 px-[15px]"
|
||||
}
|
||||
>
|
||||
<div className={"flex flex-col gap-[30px]"}>
|
||||
<div className={"flex flex-col max-h-[80vh] w-[85vw] max-w-md"}>
|
||||
<div
|
||||
className={
|
||||
"border border-white bg-[#1D1D1B] px-[10px] py-[16px] text-start flex flex-col gap-12"
|
||||
"border border-white bg-[#1D1D1B] px-[15px] py-[16px] text-start flex flex-col gap-6 h-full overflow-y-auto"
|
||||
}
|
||||
>
|
||||
<p className="mt-12">
|
||||
<p className="mt-4">
|
||||
<span className="text-xl">🎉</span>
|
||||
<span className="px-1 font-bold">Поздравляем с покупкой!</span>
|
||||
<span className="text-xl">🎉</span>
|
||||
|
|
@ -44,6 +58,7 @@ export const CongratsModal = ({
|
|||
<p className="flex flex-col">
|
||||
Спасибо, что выбираете MY!
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
className={"mt-[20px]"}
|
||||
label={"Ок"}
|
||||
|
|
@ -52,6 +67,5 @@ export const CongratsModal = ({
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { useHapticFeedback } from "@vkruglikov/react-telegram-web-app";
|
||||
import { useHapticFeedback, useWebApp } from "@vkruglikov/react-telegram-web-app";
|
||||
import { useEffect } from "react";
|
||||
import { Button } from "~/shared/ui/button";
|
||||
|
||||
type ErrorModalProps = {
|
||||
|
|
@ -9,25 +10,39 @@ export const ErrorModal = ({
|
|||
onConfirm,
|
||||
}: ErrorModalProps) => {
|
||||
const [impactOccurred] = useHapticFeedback();
|
||||
const WebApp = useWebApp();
|
||||
|
||||
const handleClick = (fn: () => void) => {
|
||||
impactOccurred("light");
|
||||
fn();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// Отключаем вертикальные свайпы при монтировании компонента
|
||||
if (WebApp && WebApp.disableVerticalSwipes) {
|
||||
WebApp.disableVerticalSwipes();
|
||||
}
|
||||
|
||||
// Включаем вертикальные свайпы обратно при размонтировании
|
||||
return () => {
|
||||
if (WebApp && WebApp.enableVerticalSwipes) {
|
||||
WebApp.enableVerticalSwipes();
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
"fixed left-0 top-0 z-30 flex h-full w-full items-center justify-center bg-black/80 px-[15px]"
|
||||
}
|
||||
>
|
||||
<div className={"flex flex-col gap-[30px]"}>
|
||||
<div className={"flex flex-col max-h-[80vh] w-[85vw] max-w-md"}>
|
||||
<div
|
||||
className={
|
||||
"border border-white bg-[#1D1D1B] px-[10px] py-[16px] text-start flex flex-col gap-12"
|
||||
"border border-white bg-[#1D1D1B] px-[15px] py-[16px] text-start flex flex-col gap-6 h-full overflow-y-auto"
|
||||
}
|
||||
>
|
||||
<p className="mt-12">
|
||||
<p className="mt-4">
|
||||
<span className="font-bold">Ошибка запроса транзакции</span>
|
||||
</p>
|
||||
<p className="">
|
||||
|
|
@ -41,6 +56,7 @@ export const ErrorModal = ({
|
|||
<p className="flex flex-col">
|
||||
Если проблема не исчезает, убедитесь, что ваш кошелек работает корректно, или свяжитесь с поддержкой.
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
className={"mt-[20px]"}
|
||||
label={"Ок"}
|
||||
|
|
@ -49,6 +65,5 @@ export const ErrorModal = ({
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,46 +1,201 @@
|
|||
import { useMutation } from "react-query";
|
||||
import { useState } from "react";
|
||||
|
||||
import { request } from "~/shared/libs";
|
||||
|
||||
const STORAGE_API_URL = import.meta.env.VITE_API_BASE_STORAGE_URL;
|
||||
const MAX_CHUNK_SIZE = 80 * 1024 * 1024; // 80 MB
|
||||
|
||||
export const useUploadFile = () => {
|
||||
const [uploadProgress, setUploadProgress] = useState(0);
|
||||
const [isUploading, setIsUploading] = useState(false);
|
||||
const [uploadError, setUploadError] = useState<Error | null>(null);
|
||||
|
||||
const mutation = useMutation(["upload-file"], (file: File) => {
|
||||
const mutation = useMutation(["upload-file"], async (file: File) => {
|
||||
console.log(`Начинаем загрузку файла: ${file.name} (${file.size} байт)`);
|
||||
setIsUploading(true);
|
||||
setUploadProgress(0);
|
||||
setUploadError(null); // Сбрасываем ошибку перед началом новой загрузки
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
try {
|
||||
// Для маленьких файлов используем обычную загрузку, но с теми же заголовками
|
||||
if (file.size <= MAX_CHUNK_SIZE) {
|
||||
console.log("Используем обычную загрузку (файл <= MAX_CHUNK_SIZE)");
|
||||
|
||||
return request
|
||||
.post<{
|
||||
content_sha256: string;
|
||||
content_id_v1: string;
|
||||
content_url: string;
|
||||
}>("/storage", formData, {
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data",
|
||||
Authorization: localStorage.getItem('auth_v1_token') || ""
|
||||
},
|
||||
// Подготавливаем заголовки - такие же, как для чанковой загрузки
|
||||
const headers: Record<string, string> = {
|
||||
"X-File-Name": btoa(unescape(encodeURIComponent(file.name))), // Имя файла в base64
|
||||
"X-Chunk-Start": "0", // Начинаем с позиции 0
|
||||
"Content-Type": file.type || "application/octet-stream",
|
||||
"X-Last-Chunk": "1" // Это единственный и последний чанк
|
||||
};
|
||||
|
||||
// Добавляем заголовок авторизации
|
||||
const authToken = localStorage.getItem('auth_v1_token');
|
||||
if (authToken) {
|
||||
headers["Authorization"] = authToken;
|
||||
}
|
||||
|
||||
console.log("Заголовки запроса:", headers);
|
||||
|
||||
try {
|
||||
const response = await request.post<{
|
||||
upload_id?: string;
|
||||
content_sha256?: string;
|
||||
content_id?: string;
|
||||
content_id_v1?: string;
|
||||
content_url?: string;
|
||||
}>("", file, { // Отправляем файл напрямую вместо FormData
|
||||
baseURL: STORAGE_API_URL,
|
||||
headers,
|
||||
onUploadProgress: (progressEvent) => {
|
||||
const percentCompleted = Math.round(
|
||||
(progressEvent.loaded * 100) / (progressEvent?.total as number) ||
|
||||
0,
|
||||
(progressEvent.loaded * 100) / (progressEvent?.total as number || file.size)
|
||||
);
|
||||
setUploadProgress(percentCompleted);
|
||||
setUploadProgress(Math.min(99, percentCompleted));
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
setIsUploading(false);
|
||||
return response.data;
|
||||
})
|
||||
.catch((error) => {
|
||||
setIsUploading(false);
|
||||
throw error;
|
||||
});
|
||||
});
|
||||
|
||||
return { ...mutation, uploadProgress, isUploading };
|
||||
console.log("Ответ на обычную загрузку:", response.data);
|
||||
setUploadProgress(100);
|
||||
|
||||
return {
|
||||
content_sha256: response.data.content_sha256 || "",
|
||||
content_id_v1: response.data.content_id_v1 || response.data.content_id || "",
|
||||
content_url: response.data.content_url || ""
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Ошибка при обычной загрузке:", error);
|
||||
setUploadError(error instanceof Error ? error : new Error('Ошибка при загрузке файла'));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Для больших файлов используем чанковую загрузку
|
||||
console.log("Используем чанкированную загрузку (файл > MAX_CHUNK_SIZE)");
|
||||
|
||||
let offset = 0;
|
||||
let uploadId: string | null = null;
|
||||
let chunkNumber = 0;
|
||||
|
||||
// Загружаем файл по чанкам
|
||||
while (offset < file.size) {
|
||||
chunkNumber++;
|
||||
const chunkEnd = Math.min(offset + MAX_CHUNK_SIZE, file.size);
|
||||
const chunk = file.slice(offset, chunkEnd);
|
||||
console.log(`Загрузка чанка #${chunkNumber} начиная с байта ${offset}`);
|
||||
|
||||
// Определяем, является ли текущий чанк последним
|
||||
const isLastChunk = chunkEnd === file.size;
|
||||
|
||||
// Подготавливаем заголовки
|
||||
const headers: Record<string, string> = {
|
||||
"X-File-Name": btoa(unescape(encodeURIComponent(file.name))), // Имя файла в base64
|
||||
"X-Chunk-Start": offset.toString(),
|
||||
"Content-Type": file.type || "application/octet-stream"
|
||||
};
|
||||
|
||||
// Добавляем маркер последнего чанка, если это последний чанк
|
||||
if (isLastChunk) {
|
||||
headers["X-Last-Chunk"] = "1";
|
||||
}
|
||||
|
||||
// Добавляем заголовок авторизации
|
||||
const authToken = localStorage.getItem('auth_v1_token');
|
||||
if (authToken) {
|
||||
headers["Authorization"] = authToken;
|
||||
}
|
||||
|
||||
// Если есть uploadId, добавляем его в заголовки
|
||||
if (uploadId) {
|
||||
headers["X-Upload-ID"] = uploadId;
|
||||
}
|
||||
|
||||
console.log("Заголовки запроса:", headers);
|
||||
|
||||
try {
|
||||
const response = await request.post<{
|
||||
upload_id?: string;
|
||||
current_size?: number;
|
||||
content_id?: string;
|
||||
content_sha256?: string;
|
||||
content_id_v1?: string;
|
||||
content_url?: string;
|
||||
}>("", chunk, {
|
||||
baseURL: STORAGE_API_URL,
|
||||
headers,
|
||||
onUploadProgress: (progressEvent) => {
|
||||
// Прогресс загрузки текущего чанка
|
||||
const overallProgress = offset + progressEvent.loaded;
|
||||
const percentCompleted = Math.round((overallProgress / file.size) * 100);
|
||||
setUploadProgress(Math.min(99, percentCompleted));
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`Ответ на чанк #${chunkNumber}:`, response.data);
|
||||
|
||||
// Сохраняем uploadId из первого ответа, если не установлен
|
||||
if (!uploadId && response.data.upload_id) {
|
||||
uploadId = response.data.upload_id;
|
||||
console.log("Получен upload_id:", uploadId);
|
||||
}
|
||||
|
||||
// Проверка на наличие content_id
|
||||
if (response.data.content_id) {
|
||||
console.log("Загрузка завершена. ID файла:", response.data.content_id);
|
||||
setUploadProgress(100);
|
||||
|
||||
return {
|
||||
content_sha256: response.data.content_sha256 || "",
|
||||
content_id_v1: response.data.content_id_v1 || response.data.content_id || "",
|
||||
content_url: response.data.content_url || ""
|
||||
};
|
||||
}
|
||||
|
||||
// Обновляем смещение на основе ответа сервера
|
||||
if (response.data.current_size !== undefined) {
|
||||
offset = response.data.current_size;
|
||||
console.log(`Сервер сообщает current_size: ${offset}`);
|
||||
} else {
|
||||
console.warn("Неожиданный ответ от сервера, отсутствует current_size");
|
||||
const error = new Error("Missing current_size in response");
|
||||
setUploadError(error);
|
||||
throw error;
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(`Ошибка при загрузке чанка #${chunkNumber}:`, error);
|
||||
if (error.response) {
|
||||
console.error("Ответ сервера:", error.response.status, error.response.data);
|
||||
}
|
||||
setUploadError(error instanceof Error ? error : new Error(`Ошибка при загрузке чанка #${chunkNumber}`));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const error = new Error("Ошибка загрузки файла: все чанки загружены, но content_id не получен");
|
||||
console.error(error.message);
|
||||
setUploadError(error);
|
||||
throw error;
|
||||
} catch (error: any) {
|
||||
console.error("Ошибка при загрузке:", error);
|
||||
if (error.response) {
|
||||
console.error("Ответ сервера:", error.response.status, error.response.data);
|
||||
}
|
||||
setUploadError(error instanceof Error ? error : new Error("Неизвестная ошибка при загрузке"));
|
||||
setIsUploading(false);
|
||||
throw error;
|
||||
} finally {
|
||||
setIsUploading(false);
|
||||
}
|
||||
});
|
||||
|
||||
// Сбросить ошибку
|
||||
const resetUploadError = () => setUploadError(null);
|
||||
|
||||
return {
|
||||
...mutation,
|
||||
uploadProgress,
|
||||
isUploading,
|
||||
uploadError,
|
||||
resetUploadError
|
||||
};
|
||||
};
|
||||
|
|
@ -2076,6 +2076,11 @@ json5@^2.2.3:
|
|||
resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz"
|
||||
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
|
||||
|
||||
jssha@^3.3.1:
|
||||
version "3.3.1"
|
||||
resolved "https://registry.npmjs.org/jssha/-/jssha-3.3.1.tgz"
|
||||
integrity sha512-VCMZj12FCFMQYcFLPRm/0lOBbLi8uM2BhXPTqw3U4YAfs4AZfiApOoBLoN8cQE60Z50m1MYMTQVCfgF/KaCVhQ==
|
||||
|
||||
jssha@3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.npmjs.org/jssha/-/jssha-3.2.0.tgz"
|
||||
|
|
|
|||
Loading…
Reference in New Issue