add navagation handler while file upload
This commit is contained in:
@@ -1,9 +1,10 @@
|
|||||||
import ProtectionSummary from '@/app/ui/marking-page/protection-summary';
|
import ProtectionSummary from '@/app/ui/marking-page/protection-summary';
|
||||||
import FilesTable from '@/app/ui/dashboard/files-table';
|
import FilesTable from '@/app/ui/dashboard/files-table';
|
||||||
import PageTitle from '@/app/ui/page-title';
|
import PageTitle from '@/app/ui/page-title';
|
||||||
|
import UploadSectionFile from '@/app/components/upload-section-file';
|
||||||
|
|
||||||
const ALLOWED_EXTENSIONS: string[] = ['jpg', 'jpeg', 'png', 'gif', 'bmp'];
|
const ALLOWED_EXTENSIONS: string[] = ['jpg', 'jpeg', 'png', 'gif', 'bmp'];
|
||||||
const FILE_TYPE = "VIDEO"
|
const FILE_TYPE = "AUDIO"
|
||||||
const MAX_SIZE = 1048576000;
|
const MAX_SIZE = 1048576000;
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
@@ -12,6 +13,7 @@ export default function Page() {
|
|||||||
<div>
|
<div>
|
||||||
<PageTitle title="audio-protection" />
|
<PageTitle title="audio-protection" />
|
||||||
<ProtectionSummary />
|
<ProtectionSummary />
|
||||||
|
<UploadSectionFile fileType={FILE_TYPE} allowedExtensions={ALLOWED_EXTENSIONS} maxFileSize={MAX_SIZE} />
|
||||||
{/* <TestSection /> */}
|
{/* <TestSection /> */}
|
||||||
<FilesTable />
|
<FilesTable />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ export async function fileUpload(messageBody: initMessageBody) {
|
|||||||
|
|
||||||
export async function cancelUpload(udloadId: string) {
|
export async function cancelUpload(udloadId: string) {
|
||||||
try {
|
try {
|
||||||
|
console.log('cancelUpload start');
|
||||||
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
@@ -93,6 +94,7 @@ export async function cancelUpload(udloadId: string) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (parsed.message_desc === 'Upload cancelled successfully') {
|
if (parsed.message_desc === 'Upload cancelled successfully') {
|
||||||
|
console.log('cancelUpload done');
|
||||||
return parsed.message_body;
|
return parsed.message_body;
|
||||||
} else {
|
} else {
|
||||||
throw parsed;
|
throw parsed;
|
||||||
@@ -101,6 +103,7 @@ export async function cancelUpload(udloadId: string) {
|
|||||||
throw (`${response.status}`);
|
throw (`${response.status}`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.log('cancelUpload error');
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import React, { useRef, useState, useCallback, ChangeEvent, DragEvent } from 'react';
|
import { useRef, useState, useCallback, ChangeEvent, DragEvent, useEffect } from 'react';
|
||||||
import { IconShieldAdd } from '@/app/ui/icons/icons';
|
import { IconShieldAdd } from '@/app/ui/icons/icons';
|
||||||
import { fileUpload, cancelUpload, chunkUpload, checkChunkStatus } from '@/app/actions/file-upload';
|
import { fileUpload, cancelUpload, chunkUpload, checkChunkStatus } from '@/app/actions/file-upload';
|
||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { useNavigationBlocker } from '@/app/hooks/useNavigationBlocker';
|
||||||
interface SelectedFile {
|
interface SelectedFile {
|
||||||
file: File;
|
file: File;
|
||||||
preview: string;
|
preview: string;
|
||||||
@@ -94,7 +94,6 @@ export default function UploadSectionFile({ fileType, allowedExtensions, maxFile
|
|||||||
|
|
||||||
const ALLOWED_EXTENSIONS: string[] = allowedExtensions;
|
const ALLOWED_EXTENSIONS: string[] = allowedExtensions;
|
||||||
|
|
||||||
|
|
||||||
const validateFile = (file: File): { isValid: boolean; errorMessage?: string } => {
|
const validateFile = (file: File): { isValid: boolean; errorMessage?: string } => {
|
||||||
if (!SUPPORTED_FORMATS.includes(file.type as string)) {
|
if (!SUPPORTED_FORMATS.includes(file.type as string)) {
|
||||||
const extension = file.name.split('.').pop()?.toLowerCase();
|
const extension = file.name.split('.').pop()?.toLowerCase();
|
||||||
@@ -308,6 +307,42 @@ export default function UploadSectionFile({ fileType, allowedExtensions, maxFile
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Обработка закрытия вкладки
|
||||||
|
// Тут можно попробовать изучить вопрос с navigator.sendBeacon('', blob)
|
||||||
|
// он нужен для того что бы послать запрос на бек при закрытии вкладки,
|
||||||
|
// но у него есть свои особенности и стандартные запросы не работают.
|
||||||
|
const handleUnload = () => {
|
||||||
|
if (uploadId) {
|
||||||
|
//const data = JSON.stringify({ uploadId, reason: 'tab_closed' });
|
||||||
|
//const blob = new Blob([data], { type: 'application/json' });
|
||||||
|
//navigator.sendBeacon('/api/v1/data/cancel', blob);
|
||||||
|
console.log('sendBeacon');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (uploadId) {
|
||||||
|
window.addEventListener('unload', handleUnload);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('unload', handleUnload);
|
||||||
|
};
|
||||||
|
}, [uploadId]);
|
||||||
|
|
||||||
|
useNavigationBlocker({
|
||||||
|
shouldBlock: !!uploadId,
|
||||||
|
message: t('have-unsaved-changes'),
|
||||||
|
onConfirm: async () => {
|
||||||
|
console.log('User confirmed navigation');
|
||||||
|
if (uploadId) {
|
||||||
|
await cancelUpload(uploadId);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onCancel: () => {
|
||||||
|
console.log('User cancelled navigation');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="upload-section">
|
<div className="upload-section">
|
||||||
<h3>
|
<h3>
|
||||||
@@ -354,14 +389,16 @@ export default function UploadSectionFile({ fileType, allowedExtensions, maxFile
|
|||||||
{t('select-files-to-protect')}
|
{t('select-files-to-protect')}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
{fileType === "IMAGE" && (
|
||||||
<p className="description">
|
<p className="description">
|
||||||
Разрешение изображение: 100x100 - 10000x10000
|
Разрешение изображение: 100x100 - 10000x10000
|
||||||
</p>
|
</p>
|
||||||
|
)}
|
||||||
<p className="description">
|
<p className="description">
|
||||||
Размер файла: до 20 МБ
|
Размер файла: до {(maxFileSize / 1024 / 1024)} МБ
|
||||||
</p>
|
</p>
|
||||||
<p className="description">
|
<p className="description">
|
||||||
Формат файла: JPG, PNG, GIF, BMP (WEBP НЕ поддерживается)
|
Формат файла: {ALLOWED_EXTENSIONS.join(', ')}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
@@ -411,17 +448,6 @@ export default function UploadSectionFile({ fileType, allowedExtensions, maxFile
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{fileType === 'VIDEO' && (
|
|
||||||
<video
|
|
||||||
src={selectedFile.preview}
|
|
||||||
className="uploaded-image"
|
|
||||||
controls
|
|
||||||
muted
|
|
||||||
onError={() => setError('Не удалось загрузить превью видео')}
|
|
||||||
onLoadedData={() => console.log('Видео загружено')}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="mt-4 flex gap-3 justify-center relative">
|
<div className="mt-4 flex gap-3 justify-center relative">
|
||||||
<button
|
<button
|
||||||
className="btn btn-cancel"
|
className="btn btn-cancel"
|
||||||
@@ -440,19 +466,6 @@ export default function UploadSectionFile({ fileType, allowedExtensions, maxFile
|
|||||||
>
|
>
|
||||||
{t('upload-file')}
|
{t('upload-file')}
|
||||||
</button>
|
</button>
|
||||||
<button
|
|
||||||
className="btn btn-cancel"
|
|
||||||
type="button"
|
|
||||||
onClick={
|
|
||||||
async () => {
|
|
||||||
if (uploadId) {
|
|
||||||
await checkChunkStatus(uploadId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
dev-test-button
|
|
||||||
</button>
|
|
||||||
{uploadProgress !== 0 && (
|
{uploadProgress !== 0 && (
|
||||||
<div
|
<div
|
||||||
className={`absolute top-5 right-5 font-medium ${isFileUploaded ? "text-green-600" : ""}`}
|
className={`absolute top-5 right-5 font-medium ${isFileUploaded ? "text-green-600" : ""}`}
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
|
||||||
|
|
||||||
|
interface UseNavigationBlockerProps {
|
||||||
|
shouldBlock: boolean;
|
||||||
|
message?: string;
|
||||||
|
onConfirm?: () => void;
|
||||||
|
onCancel?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useNavigationBlocker = ({
|
||||||
|
shouldBlock,
|
||||||
|
message,
|
||||||
|
onConfirm,
|
||||||
|
onCancel
|
||||||
|
}: UseNavigationBlockerProps) => {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!shouldBlock) return;
|
||||||
|
|
||||||
|
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.returnValue = message;
|
||||||
|
return message;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClick = (e: MouseEvent) => {
|
||||||
|
const target = e.target as HTMLElement;
|
||||||
|
const link = target.closest('a');
|
||||||
|
|
||||||
|
if (link && link.href) {
|
||||||
|
const currentUrl = window.location.href;
|
||||||
|
if (link.href !== currentUrl && !link.href.startsWith('#')) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
const confirmed = window.confirm(message);
|
||||||
|
if (confirmed) {
|
||||||
|
onConfirm?.();
|
||||||
|
window.location.href = link.href;
|
||||||
|
} else {
|
||||||
|
onCancel?.();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('beforeunload', handleBeforeUnload);
|
||||||
|
document.addEventListener('click', handleClick, true);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('beforeunload', handleBeforeUnload);
|
||||||
|
document.removeEventListener('click', handleClick, true);
|
||||||
|
};
|
||||||
|
}, [shouldBlock, message, onConfirm, onCancel]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!shouldBlock) return;
|
||||||
|
|
||||||
|
const originalPush = router.push;
|
||||||
|
|
||||||
|
router.push = async (...args) => {
|
||||||
|
const confirmed = window.confirm(message);
|
||||||
|
if (confirmed) {
|
||||||
|
onConfirm?.();
|
||||||
|
return originalPush.apply(router, args);
|
||||||
|
} else {
|
||||||
|
onCancel?.();
|
||||||
|
return Promise.resolve(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
router.push = originalPush;
|
||||||
|
};
|
||||||
|
}, [shouldBlock, message, router, onConfirm, onCancel]);
|
||||||
|
};
|
||||||
@@ -128,7 +128,8 @@
|
|||||||
"select-files-to-protect": "Select files to protect",
|
"select-files-to-protect": "Select files to protect",
|
||||||
"upload-for-protection": "Upload {fileType} for protection",
|
"upload-for-protection": "Upload {fileType} for protection",
|
||||||
"image": "image",
|
"image": "image",
|
||||||
"file-has-no-extension": "File has no extension"
|
"file-has-no-extension": "File has no extension",
|
||||||
|
"have-unsaved-changes": "You have unsaved changes. Are you sure you want to delete this page?"
|
||||||
},
|
},
|
||||||
"Login-register-form": {
|
"Login-register-form": {
|
||||||
"and": "and",
|
"and": "and",
|
||||||
|
|||||||
@@ -128,7 +128,8 @@
|
|||||||
"select-files-to-protect": "Выбрать файлы для защиты",
|
"select-files-to-protect": "Выбрать файлы для защиты",
|
||||||
"upload-for-protection": "Загрузить {fileType} для защиты",
|
"upload-for-protection": "Загрузить {fileType} для защиты",
|
||||||
"image": "изображений",
|
"image": "изображений",
|
||||||
"file-has-no-extension": "Файл не имеет расширения"
|
"file-has-no-extension": "Файл не имеет расширения",
|
||||||
|
"have-unsaved-changes": "У вас есть несохраненные изменения. Вы уверены, что хотите покинуть страницу?"
|
||||||
},
|
},
|
||||||
"Login-register-form": {
|
"Login-register-form": {
|
||||||
"and": "и",
|
"and": "и",
|
||||||
|
|||||||
Reference in New Issue
Block a user